feat: scaffold astro publishing pipeline

This commit is contained in:
Marcos Paulo
2026-09-05 01:39:47 +00:00
parent 1cc9469d2d
commit 33df09f541
21 changed files with 720 additions and 185 deletions
+35
View File
@@ -0,0 +1,35 @@
AI For Dummies — Route map
← AI FOR DUMMIES
00 / ROUTE MAP
review desk ↗
Start here
Ship the
system.
This guide turns AI work into a shape: frame the problem, choose the model and agent, isolate changes, teach repeatable decisions, and verify the result.
01
Models
Capability and effort are separate knobs.
Open chapter →
02
Agents & trees
Bound roles, handoffs, and worktrees.
Open chapter →
03
Skills
Capture repeatable decisions.
Open chapter →
04
Rules
Connect guidance to enforcement.
Open chapter →
05
Practice
Compare prompts and skill-enabled runs.
Open lab →
06
Review desk
Browse original files and improved drafts.
Open desk →
Full field guide
Operations guide
Each chapter stands alone; the order follows a real task becoming a reliable change.
+55
View File
@@ -0,0 +1,55 @@
# Tier 3 gate and publication. The act-runner registration is stored in an
# emptyDir: after a runner pod restart, re-register it before debugging CI.
name: verify-and-publish
on:
push:
branches: [main]
pull_request:
jobs:
gate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm install --package-lock=false --prefer-offline
- run: npm run lint
- run: ./.agents/scripts/gate.sh
- name: visual regression
run: |
npx playwright install --with-deps chromium
node .agents/scripts/visual-regression.mjs
- uses: actions/upload-artifact@v4
if: failure()
with:
name: screenshots
path: .agents/snapshots/diff/
publish:
if: github.ref == 'refs/heads/main'
needs: gate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm install --package-lock=false --prefer-offline
- run: npm run build
- name: Publish generated site to pages
run: |
git config user.name 'gitea-actions[bot]'
git config user.email 'gitea-actions[bot]@users.noreply.local'
git switch --orphan pages
git rm -rf .
cp -a dist/. .
git add --all
git commit -m 'chore: publish site'
git push --force origin HEAD:pages
+4 -14
View File
@@ -1,16 +1,6 @@
{ {
"*.{js,mjs,ts,astro}": [ "*.{js,mjs,ts,astro}": ["prettier --write", "eslint --fix --max-warnings=0 --no-warn-ignored"],
"prettier --write", "*.css": ["prettier --write", "stylelint --fix --max-warnings=0"],
"eslint --fix --max-warnings=0" "src/**/*.{astro,css}": ["node .agents/scripts/check-tokens.mjs"],
], "*.{json,md,yml,yaml}": ["prettier --write"]
"*.css": [
"prettier --write",
"stylelint --fix --max-warnings=0"
],
"*.{astro,css}": [
"node .agents/scripts/check-tokens.mjs"
],
"*.{json,md,yml,yaml}": [
"prettier --write"
]
} }
+7
View File
@@ -0,0 +1,7 @@
dist
node_modules
public/hands-on
submitted-skills
skill-reviews
vote-service
.agents/snapshots
+11
View File
@@ -0,0 +1,11 @@
{
"printWidth": 100,
"singleQuote": true,
"semi": true,
"trailingComma": "all",
"plugins": ["prettier-plugin-astro"],
"overrides": [
{ "files": "*.astro", "options": { "parser": "astro" } },
{ "files": "*.md", "options": { "proseWrap": "always", "printWidth": 80 } }
]
}
+21
View File
@@ -0,0 +1,21 @@
{
"extends": ["stylelint-config-standard"],
"ignoreFiles": [
"dist/**",
"hands-on/**",
"public/hands-on/**",
"submitted-skills/**",
"skill-reviews/**",
"vote-service/**"
],
"rules": {
"custom-property-pattern": "^[a-z][a-z0-9]*(-[a-z0-9]+)*$",
"declaration-property-value-disallowed-list": {
"/^transition/": ["/width/", "/height/", "/^top/", "/^left/", "/margin/"],
"/^animation/": ["/width/", "/height/"]
},
"media-feature-name-no-unknown": true,
"no-descending-specificity": null,
"selector-class-pattern": "^[a-z][a-z0-9]*(-[a-z0-9]+)*$"
}
}
+6
View File
@@ -0,0 +1,6 @@
import { defineConfig } from 'astro/config';
export default defineConfig({
base: '/ai-for-dummies',
trailingSlash: 'always',
});
+166 -170
View File
@@ -18,45 +18,45 @@ worktree practices taught by the presentation fit together.
## Quick links ## Quick links
| Resource | Location | | Resource | Location |
| :--- | :--- | | :--------------------- | :------------------------------------------------------------------------------------------------------------------------- |
| Live presentation | [https://netcracker.pages.marcospaulo.dev.br/ai-for-dummies/](https://netcracker.pages.marcospaulo.dev.br/ai-for-dummies/) | | Live presentation | [https://netcracker.pages.marcospaulo.dev.br/ai-for-dummies/](https://netcracker.pages.marcospaulo.dev.br/ai-for-dummies/) |
| Gitea repository | [https://git.marcospaulo.dev.br/netcracker/ai-for-dummies](https://git.marcospaulo.dev.br/netcracker/ai-for-dummies) | | Gitea repository | [https://git.marcospaulo.dev.br/netcracker/ai-for-dummies](https://git.marcospaulo.dev.br/netcracker/ai-for-dummies) |
| Local checkout | `/home/marcos/Projects/ai-for-dummies` | | Local checkout | `/home/marcos/Projects/ai-for-dummies` |
| Source branch | `main` | | Source branch | `main` |
| Published branch | `pages` | | Published branch | `pages` |
| Local verification | `npm run verify` | | Local verification | `npm run verify` |
| SilverBullet page | `Guides/AI For Dummies Presentation` | | SilverBullet page | `Guides/AI For Dummies Presentation` |
| Skills-review vote API | `vote-service/` — separate pod, see `vote-service/README.md` | | Skills-review vote API | `vote-service/` — separate pod, see `vote-service/README.md` |
## How the site is built ## How the site is built
The presentation is deliberately dependency-free. Gitea Pages serves the The presentation is authored in Astro. `main` holds source; the Gitea Pages
repository files directly; there is no bundler or generated `dist/` folder. Server still serves a branch directly, so the `pages` branch holds generated
`dist/` output and is not a source branch.
| File | Responsibility | | File | Responsibility |
| :--- | :--- | | :---------------------------- | :--------------------------------------------------------- |
| `index.html` | Default route map and focused chapter navigation | | `src/` | Astro routes, layouts, components, and source styles |
| `full-guide/index.html` | Complete bilingual field guide, controls, labels, and English source copy | | `public/hands-on/` | Verbatim vanilla lab fixtures; Astro does not process them |
| `styles.css` | Base editorial visual system | | `astro.config.mjs` | Static build with `base: '/ai-for-dummies'` |
| `responsive.css` | Interactive diagrams and Full HD, 4K, tablet, and mobile adaptations | | `scripts/verify.mjs` | Content and interaction contract checks |
| `app.js` | Interactions, state, and Portuguese translations | | `.gitea/workflows/verify.yml` | Gate, build, and machine-owned publication to `pages` |
| `scripts/verify.mjs` | Content and interaction contract checks |
| `docs/references/` | Primary documentation and additional reading |
The English HTML is the fallback when JavaScript is unavailable. Portuguese Astro ships no JavaScript by default. Interactive islands opt in per component;
copy is applied by `app.js`; the language preference is stored in the bilingual behaviour remains a client-side concern until its dedicated
`localStorage`, and the document language changes to `pt-BR`. migration task.
## Normal edit and publish workflow ## Normal edit and publish workflow
```mermaid ```mermaid
flowchart LR flowchart LR
E[Edit main] --> V[npm run verify] E[Edit main] --> V[npm run gate]
V --> C[Commit] V --> C[Commit]
C --> M[Push main] C --> P[Push main]
M --> P[Fast-forward pages] P --> G[Gitea Actions: gate + build]
P --> S[Gitea Pages Server] G --> B[Force-push dist to pages]
B --> S[Gitea Pages Server]
S --> L[Live URL] S --> L[Live URL]
``` ```
@@ -69,41 +69,36 @@ git pull --ff-only
git status --short --branch git status --short --branch
``` ```
Do not overwrite unrelated local changes. The untracked Do not overwrite unrelated local changes. The untracked `scripts/inspect.py` and
`scripts/inspect.py` and `scripts/__pycache__/` are local visual-test artifacts `scripts/__pycache__/` are local visual-test artifacts and are intentionally not
and are intentionally not part of the published site. part of the published site.
### 2. Preview locally ### 2. Preview locally
```bash ```bash
python3 -m http.server 4173 npm run dev
``` ```
Open [http://localhost:4173](http://localhost:4173). Check English and Open
Portuguese, keyboard focus, the interactive panels, and at least one desktop [http://localhost:4321/ai-for-dummies/](http://localhost:4321/ai-for-dummies/).
and one mobile viewport. Check English and Portuguese, keyboard focus, the interactive panels, and at
least one desktop and one mobile viewport. The production site is under the
`/ai-for-dummies/` base path, so do not test only root-relative URLs.
### 3. Verify before committing ### 3. Verify before committing
```bash ```bash
npm run verify npm run gate
node --check app.js
node scripts/audit-ui.mjs
git diff --check
``` ```
Expected project verifier output: The gate runs Astro types and build checks, content contracts, the runtime
dependency audit, and the token check. It also refuses a reduced count of
```text `verify.mjs` assertions.
content verification passed
interaction verification passed
standalone verification passed
```
### 4. Commit and push the source branch ### 4. Commit and push the source branch
```bash ```bash
git add README.md app.js index.html full-guide/ styles.css responsive.css scripts/verify.mjs scripts/audit-ui.mjs docs/ git add <only-files-for-this-change>
git commit -m "feat: describe the change" git commit -m "feat: describe the change"
git push origin main git push origin main
``` ```
@@ -111,54 +106,44 @@ git push origin main
Stage only files that belong to the change. Review `git status --short` before Stage only files that belong to the change. Review `git status --short` before
committing. committing.
### 5. Fast-forward the published branch ### 5. CI publication and its manual fallback
Use a temporary worktree so the current checkout stays on `main`: On a successful `main` push, Gitea Actions builds `dist/` and force-pushes it to
`pages`. This force-push is intentional: `pages` is machine-owned generated
output, and no person or other workflow may write it.
This Gitea's act-runner registration is kept in an `emptyDir`. A pod restart
silently removes the registration; if a site does not update, check and
re-register the runner before changing the workflow.
If the runner is unavailable, use the following manual fallback from a clean
`main` checkout. It deliberately replaces the generated branch tree only:
```bash ```bash
git worktree add /tmp/ai-for-dummies-pages pages npm install --package-lock=false --prefer-offline
git -C /tmp/ai-for-dummies-pages merge --ff-only origin/pages # local pages is often stale npm run build
git -C /tmp/ai-for-dummies-pages merge --no-edit main git worktree add /tmp/ai-for-dummies-pages --detach pages
git -C /tmp/ai-for-dummies-pages push origin pages git -C /tmp/ai-for-dummies-pages rm -rf .
cp -a dist/. /tmp/ai-for-dummies-pages/
git -C /tmp/ai-for-dummies-pages add --all
git -C /tmp/ai-for-dummies-pages commit -m "chore: publish site"
git -C /tmp/ai-for-dummies-pages push --force origin HEAD:pages
git worktree remove /tmp/ai-for-dummies-pages git worktree remove /tmp/ai-for-dummies-pages
``` ```
The `pages` branch should represent the exact published source. Avoid editing
it directly and avoid force-pushing it.
The two histories have diverged — `pages` carries merge commits and
cherry-picked duplicates of `main` commits — so `merge --ff-only main` fails
with `Not possible to fast-forward`. A normal merge is correct here, and
matches the `Merge branch 'main' into pages` commits already on the branch.
The invariant worth checking is the *tree*, not the history:
```bash
git rev-parse main^{tree} pages^{tree} # must print the same hash twice
```
If the merge conflicts (duplicated commits touching the same lines will do
it), resolve by taking `main` wholesale, since `main` is the source of truth
for published content:
```bash
git -C /tmp/ai-for-dummies-pages checkout main -- .
git -C /tmp/ai-for-dummies-pages add -A
git -C /tmp/ai-for-dummies-pages diff --cached main --stat # must be empty
git -C /tmp/ai-for-dummies-pages commit --no-edit
```
### 6. Verify the deployment ### 6. Verify the deployment
```bash ```bash
curl -I https://netcracker.pages.marcospaulo.dev.br/ai-for-dummies/ curl -sS -o /dev/null -w '%{http_code}\n' \
"https://netcracker.pages.marcospaulo.dev.br/ai-for-dummies/summary/?v=$(git rev-parse --short HEAD)"
``` ```
If the edge still shows an older page, retry with the current commit as a Also check a nested static asset; base-path problems usually appear on assets
cache-busting query: first:
```bash ```bash
git rev-parse --short HEAD curl -sS -o /dev/null -w '%{http_code}\n' \
curl -I "https://netcracker.pages.marcospaulo.dev.br/ai-for-dummies/?v=COMMIT" "https://netcracker.pages.marcospaulo.dev.br/ai-for-dummies/hands-on/starter/?v=$(git rev-parse --short HEAD)"
``` ```
The correct URL pattern is **owner subdomain + repository path**: The correct URL pattern is **owner subdomain + repository path**:
@@ -173,12 +158,12 @@ certificate.
## Skills-review vote service ## Skills-review vote service
`skills-review/` is served by the same static Pages Server as the rest of `skills-review/` is served by the same static Pages Server as the rest of this
this site, so it cannot itself remember votes. `vote-service/` is a separate site, so it cannot itself remember votes. `vote-service/` is a separate Go API
Go API on its own pod for that: one JSON file as the store, one vote per on its own pod for that: one JSON file as the store, one vote per visitor
visitor enforced by IP (a MAC address never reaches a server across the enforced by IP (a MAC address never reaches a server across the internet). It is
internet). It is deployed independently of `main`/`pages` — the site can be deployed independently of `main`/`pages` — the site can be republished without
republished without touching it, and vice versa. touching it, and vice versa.
```bash ```bash
cd vote-service cd vote-service
@@ -197,23 +182,22 @@ microk8s kubectl apply -f deploy/ingress.yaml
microk8s kubectl -n ai-for-dummies rollout restart deploy ai-for-dummies-vote microk8s kubectl -n ai-for-dummies rollout restart deploy ai-for-dummies-vote
``` ```
Namespace `ai-for-dummies`, `ingressClassName: public`, no per-ingress TLS. Namespace `ai-for-dummies`, `ingressClassName: public`, no per-ingress TLS. The
The Deployment is pinned to node `kubernets` with a `nodeSelector`: the Deployment is pinned to node `kubernets` with a `nodeSelector`: the
`microk8s-hostpath` PV carries a `nodeAffinity` for whichever node first binds `microk8s-hostpath` PV carries a `nodeAffinity` for whichever node first binds
it, so scheduling and storage have to agree on one node. it, so scheduling and storage have to agree on one node.
The vote widget's browser-side `fetch` calls must reach the API over the public The vote widget's browser-side `fetch` calls must reach the API over the public
internet — a cluster-internal-only Service would be unreachable from a internet — a cluster-internal-only Service would be unreachable from a visitor's
visitor's browser even if the Pages Server happens to run on the same browser even if the Pages Server happens to run on the same network. Exposure is
network. Exposure is therefore public, terminated by **Caddy on the Oracle VPS therefore public, terminated by **Caddy on the Oracle VPS over Tailscale** (the
over Tailscale** (the same path as every other public host here, not the same path as every other public host here, not the cloudflared tunnel), with
cloudflared tunnel), with `ALLOWED_ORIGIN`/CORS as the boundary that restricts `ALLOWED_ORIGIN`/CORS as the boundary that restricts which site's script may
which site's script may call it. After deploying, keep call it. After deploying, keep `window.SKILLS_REVIEW_VOTE_API` in
`window.SKILLS_REVIEW_VOTE_API` in `skills-review/index.html` in sync with `skills-review/index.html` in sync with `ALLOWED_ORIGIN` on the service.
`ALLOWED_ORIGIN` on the service.
One cluster-wide gotcha worth knowing before reading the vote code: the ingress One cluster-wide gotcha worth knowing before reading the vote code: the ingress
controller runs with `use-forwarded-headers` off, so nginx *overwrites* controller runs with `use-forwarded-headers` off, so nginx _overwrites_
`X-Forwarded-For`/`X-Real-IP` with the VPS's tailnet address. Caddy stamps the `X-Forwarded-For`/`X-Real-IP` with the VPS's tailnet address. Caddy stamps the
true client address into `X-Client-IP` instead. Full rationale, the Caddy block, true client address into `X-Client-IP` instead. Full rationale, the Caddy block,
and the anti-abuse design are in and the anti-abuse design are in
@@ -221,7 +205,8 @@ and the anti-abuse design are in
## Adding or changing a presentation section ## Adding or changing a presentation section
1. Add semantic HTML and stable `data-*` hooks in the focused chapter or `full-guide/index.html`; keep `index.html` as the short route map. 1. Add semantic HTML and stable `data-*` hooks in the focused chapter or
`full-guide/index.html`; keep `index.html` as the short route map.
2. Put interactive content in a data object inside `app.js`. 2. Put interactive content in a data object inside `app.js`.
3. Add one focused render function and bind its controls once. 3. Add one focused render function and bind its controls once.
4. Add Portuguese static copy to `translations.pt` and dynamic copy to the 4. Add Portuguese static copy to `translations.pt` and dynamic copy to the
@@ -251,8 +236,8 @@ flowchart TD
Use a strong model where ambiguity dominates: repository inspection, Use a strong model where ambiguity dominates: repository inspection,
architecture, decomposition, risk analysis, and review. Use faster or cheaper architecture, decomposition, risk analysis, and review. Use faster or cheaper
models for bounded implementation only after the brief defines the goal, models for bounded implementation only after the brief defines the goal, files,
files, constraints, and checks. constraints, and checks.
Every worker should return: Every worker should return:
@@ -266,8 +251,8 @@ coordination cost, context cost, and integration risk.
## Worktree-per-worker model ## Worktree-per-worker model
A Git branch isolates history; a Git worktree also isolates the active files A Git branch isolates history; a Git worktree also isolates the active files and
and index. Give each editing agent one task, one branch, and one worktree. index. Give each editing agent one task, one branch, and one worktree.
```bash ```bash
git worktree add ../task-ui -b agent/ui git worktree add ../task-ui -b agent/ui
@@ -285,14 +270,14 @@ Recommended lifecycle:
5. Merge, request changes, or discard. 5. Merge, request changes, or discard.
6. Remove the finished worktree with `git worktree remove PATH`. 6. Remove the finished worktree with `git worktree remove PATH`.
Worktrees prevent agents from changing the same checkout underneath each Worktrees prevent agents from changing the same checkout underneath each other.
other. They do not eliminate semantic merge conflicts; task ownership and They do not eliminate semantic merge conflicts; task ownership and review still
review still matter. matter.
## What a skill is ## What a skill is
A skill is a reusable procedure that changes how an agent makes decisions. It A skill is a reusable procedure that changes how an agent makes decisions. It is
is not magical memory and does not replace a task brief or acceptance criteria. not magical memory and does not replace a task brief or acceptance criteria.
```text ```text
skill-name/ skill-name/
@@ -309,8 +294,8 @@ Progressive disclosure keeps context light:
2. **SKILL.md** loads when the skill applies. 2. **SKILL.md** loads when the skill applies.
3. **References, scripts, and assets** load only when the workflow needs them. 3. **References, scripts, and assets** load only when the workflow needs them.
Do not create empty resource directories. Every file should have a real Do not create empty resource directories. Every file should have a real consumer
consumer and should improve a decision or repeatable operation. and should improve a decision or repeatable operation.
## Skill-creation workflow ## Skill-creation workflow
@@ -329,14 +314,17 @@ likely false activation.
```yaml ```yaml
--- ---
name: review-ui name: review-ui
description: Review frontend changes for focus, responsive layout, and reduced-motion behavior. description:
Review frontend changes for focus, responsive layout, and reduced-motion
behavior.
--- ---
``` ```
### 3. Choose the smallest anatomy ### 3. Choose the smallest anatomy
- Put shared workflow and constraints in `SKILL.md`. - Put shared workflow and constraints in `SKILL.md`.
- Add `scripts/` when deterministic execution prevents repeated reimplementation. - Add `scripts/` when deterministic execution prevents repeated
reimplementation.
- Add `references/` for details needed only in certain modes. - Add `references/` for details needed only in certain modes.
- Add `assets/` for templates or generated-output inputs. - Add `assets/` for templates or generated-output inputs.
- Add `agents/openai.yaml` only when UI metadata or invocation policy is useful. - Add `agents/openai.yaml` only when UI metadata or invocation policy is useful.
@@ -360,33 +348,37 @@ accumulating universal instructions.
## Common skills and when to use them ## Common skills and when to use them
| Skill | Use it for | Core rule | Avoid when | | Skill | Use it for | Core rule | Avoid when |
| :--- | :--- | :--- | :--- | | :---------------- | :-------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------- |
| `ponytail-lite` | Requests inviting unnecessary frameworks or abstractions | Stop at the first sufficient solution: reuse, standard library, native platform, existing dependency, then minimum new code | Simplification would remove validation, security, accessibility, or real edge cases | | `ponytail-lite` | Requests inviting unnecessary frameworks or abstractions | Stop at the first sufficient solution: reuse, standard library, native platform, existing dependency, then minimum new code | Simplification would remove validation, security, accessibility, or real edge cases |
| `caveman` | Routine status, handoffs, and technical summaries | Put signal first and remove filler | Security warnings, irreversible actions, or sequences where terse wording can be misread | | `caveman` | Routine status, handoffs, and technical summaries | Put signal first and remove filler | Security warnings, irreversible actions, or sequences where terse wording can be misread |
| `unlazy` | Substantial builds, audits, and parallel work | Define observable gates and finish against evidence | Trivial edits or factual answers | | `unlazy` | Substantial builds, audits, and parallel work | Define observable gates and finish against evidence | Trivial edits or factual answers |
| `research` | APIs, standards, current behavior, and architecture facts | Trace claims to primary sources and save cited findings | The answer is already stable and locally proven | | `research` | APIs, standards, current behavior, and architecture facts | Trace claims to primary sources and save cited findings | The answer is already stable and locally proven |
| `diagnosing-bugs` | Hard bugs, flakes, and regressions | Build a fast red-capable feedback loop before theorizing | Simple known fixes with an existing regression test | | `diagnosing-bugs` | Hard bugs, flakes, and regressions | Build a fast red-capable feedback loop before theorizing | Simple known fixes with an existing regression test |
| `code-review` | Branch or PR review | Check repository standards and original specification as separate axes | No comparison point or review request exists | | `code-review` | Branch or PR review | Check repository standards and original specification as separate axes | No comparison point or review request exists |
| `token-saver` | Verbose tests, builds, logs, and Git output | Preserve signal and retain full failure output for recovery | Exact raw wording or full diff context is required | | `token-saver` | Verbose tests, builds, logs, and Git output | Preserve signal and retain full failure output for recovery | Exact raw wording or full diff context is required |
| `webapp-testing` | Frontend interaction and responsive verification | Drive the real UI and assert on DOM, console, and screenshots | Static structure checks are already decisive | | `webapp-testing` | Frontend interaction and responsive verification | Drive the real UI and assert on DOM, console, and screenshots | Static structure checks are already decisive |
Useful compositions: Useful compositions:
- **Large feature:** `unlazy``ponytail-lite` → implementation → `code-review`. - **Large feature:** `unlazy``ponytail-lite` → implementation →
- **Hard regression:** `diagnosing-bugs` → fix → `code-review``caveman` handoff. `code-review`.
- **Documentation with unstable facts:** `research` → writing → cited verification. - **Hard regression:** `diagnosing-bugs` → fix → `code-review``caveman`
- **Interactive presentation:** `frontend-design``webapp-testing` → responsive evidence. handoff.
- **Documentation with unstable facts:** `research` → writing → cited
verification.
- **Interactive presentation:** `frontend-design``webapp-testing`
responsive evidence.
## Model and effort routing ## Model and effort routing
Treat model tier and reasoning effort as separate controls: Treat model tier and reasoning effort as separate controls:
| Work shape | Capability tier | Effort baseline | | Work shape | Capability tier | Effort baseline |
| :--- | :--- | :--- | | :------------------------------------------ | :------------------------ | :----------------------------- |
| Formatting, lookup, narrow edit | Luna / Haiku / Flash-Lite | Low or minimal where supported | | Formatting, lookup, narrow edit | Luna / Haiku / Flash-Lite | Low or minimal where supported |
| Normal implementation and tests | Terra / Sonnet / Flash | Medium | | Normal implementation and tests | Terra / Sonnet / Flash | Medium |
| Architecture, orchestration, hard debugging | Sol / Opus / Pro | High | | Architecture, orchestration, hard debugging | Sol / Opus / Pro | High |
For Claude Code, `/model opus`, `/model sonnet`, and `/model haiku` switch the For Claude Code, `/model opus`, `/model sonnet`, and `/model haiku` switch the
model alias; `opusplan` can use Opus while planning and Sonnet while executing. model alias; `opusplan` can use Opus while planning and Sonnet while executing.
@@ -414,14 +406,14 @@ includes a copy-ready installation request that tells the coding agent to:
Important exceptions: `ponytail-lite` is published as `AGENTS.md`, not a Important exceptions: `ponytail-lite` is published as `AGENTS.md`, not a
conventional skill package; `token-saver` expects a separate RTK binary; and conventional skill package; `token-saver` expects a separate RTK binary; and
`unlazy` includes optional hooks. The prompt does not install binaries or enable `unlazy` includes optional hooks. The prompt does not install binaries or enable
hooks without separate approval. See [skill-sources.md](references/skill-sources.md) hooks without separate approval. See
for exact commits, package paths, and confidence notes. [skill-sources.md](references/skill-sources.md) for exact commits, package
paths, and confidence notes.
## Hands-on lab ## Hands-on lab
The presentation includes a dependency-free starter at The presentation includes a dependency-free starter at `hands-on/starter/`. It
`hands-on/starter/`. It renders a small task board but intentionally omits the renders a small task board but intentionally omits the All / Open / Done filter.
All / Open / Done filter.
Run it from the repository root: Run it from the repository root:
@@ -429,7 +421,8 @@ Run it from the repository root:
python3 -m http.server 4173 python3 -m http.server 4173
``` ```
Open [http://localhost:4173/hands-on/starter/](http://localhost:4173/hands-on/starter/). Open
[http://localhost:4173/hands-on/starter/](http://localhost:4173/hands-on/starter/).
In a fresh coding-agent session, copy **Run A — Good prompt** from the In a fresh coding-agent session, copy **Run A — Good prompt** from the
presentation. Record changed files, dependencies, checks, and evidence. Restore presentation. Record changed files, dependencies, checks, and evidence. Restore
the starter, then repeat with **Run B — Good prompt + skills**. the starter, then repeat with **Run B — Good prompt + skills**.
@@ -440,27 +433,27 @@ The skill-enabled prompt invokes only two working methods:
- `$webapp-testing` verifies filters, URL state, history navigation, - `$webapp-testing` verifies filters, URL state, history navigation,
accessibility state, empty state, and mobile layout. accessibility state, empty state, and mobile layout.
The goal is not to prove that a longer prompt is better. Both prompts define The goal is not to prove that a longer prompt is better. Both prompts define the
the same task contract. Run B adds reusable operating discipline without same task contract. Run B adds reusable operating discipline without repeating
repeating those skill instructions inside the prompt. those skill instructions inside the prompt.
Compare: Compare:
| Signal | Useful question | | Signal | Useful question |
| :--- | :--- | | :------------ | :-------------------------------------------------------------- |
| Files changed | Did the agent stay inside `hands-on/starter/`? | | Files changed | Did the agent stay inside `hands-on/starter/`? |
| Dependencies | Did it add a library where native APIs were enough? | | Dependencies | Did it add a library where native APIs were enough? |
| Verification | Did it actually exercise URL reload and browser history? | | Verification | Did it actually exercise URL reload and browser history? |
| Evidence | Did the final response name checks and results? | | Evidence | Did the final response name checks and results? |
| Complexity | Is the solution proportionate to three tasks and three filters? | | Complexity | Is the solution proportionate to three tasks and three filters? |
### Hands-on rules lab ### Hands-on rules lab
A second lab at `hands-on/rules/` mirrors the starter's visual system and runs A second lab at `hands-on/rules/` mirrors the starter's visual system and runs
the same exercise against rule sources. It lists five toggleable rule sources the same exercise against rule sources. It lists five toggleable rule sources
`AGENTS.md`, the `gate-discipline` skill body, the Husky `pre-commit` hook, `AGENTS.md`, the `gate-discipline` skill body, the Husky `pre-commit` hook, the
the `check-ui-contract.mjs` enforcer, and `commitlint` — and rebuilds the `check-ui-contract.mjs` enforcer, and `commitlint` — and rebuilds the **ruled**
**ruled** prompt live as each toggle flips. prompt live as each toggle flips.
Run it: Run it:
@@ -468,15 +461,16 @@ Run it:
python3 -m http.server 4173 python3 -m http.server 4173
``` ```
Open [http://localhost:4173/hands-on/rules/](http://localhost:4173/hands-on/rules/). Open
[http://localhost:4173/hands-on/rules/](http://localhost:4173/hands-on/rules/).
Compare the **naive** and **ruled** prompt panels. Toggle rules off to shrink Compare the **naive** and **ruled** prompt panels. Toggle rules off to shrink
the prompt; toggle them on to add more guards. Copy the final prompt and run the prompt; toggle them on to add more guards. Copy the final prompt and run it
it against a real coding agent. against a real coding agent.
## Rules and enforcement case study ## Rules and enforcement case study
The separate `/rules/` page uses `netcracker/interview` as a concrete example The separate `/rules/` page uses `netcracker/interview` as a concrete example of
of repository-level control. Its interactive pipeline shows five layers: repository-level control. Its interactive pipeline shows five layers:
1. `AGENTS.md` gives every agent the same product and toolchain context. 1. `AGENTS.md` gives every agent the same product and toolchain context.
2. `.agents/skills/` loads narrow procedures for frontend, Go API, gates, 2. `.agents/skills/` loads narrow procedures for frontend, Go API, gates,
@@ -494,19 +488,20 @@ together when the underlying interview workflow changes.
## Troubleshooting ## Troubleshooting
| Symptom | Check | Fix | | Symptom | Check | Fix |
| :--- | :--- | :--- | | :------------------------------------------- | :------------------------------------------------------ | :--------------------------------------------------------------------------------------- |
| Live page is old | Compare `main`, `pages`, and remote SHAs | Fast-forward and push `pages`; retry with `?v=COMMIT` | | Live page is old | Compare `main`, `pages`, and remote SHAs | Fast-forward and push `pages`; retry with `?v=COMMIT` |
| `ERR_SSL_PROTOCOL_ERROR` | Confirm the hostname | Use `netcracker.pages.marcospaulo.dev.br/ai-for-dummies/` | | `ERR_SSL_PROTOCOL_ERROR` | Confirm the hostname | Use `netcracker.pages.marcospaulo.dev.br/ai-for-dummies/` |
| Portuguese copy is missing | Inspect `translations.pt` and dynamic interaction data | Add both static and dynamic translations; reload after clearing saved language if needed | | Portuguese copy is missing | Inspect `translations.pt` and dynamic interaction data | Add both static and dynamic translations; reload after clearing saved language if needed |
| New control does nothing | Check `data-*` hook, event binding, and render function | Add the hook to `scripts/verify.mjs`; run `node --check app.js` | | New control does nothing | Check `data-*` hook, event binding, and render function | Add the hook to `scripts/verify.mjs`; run `node --check app.js` |
| Desktop works, mobile breaks | Inspect the section below 800 px and 600 px | Add an explicit stacking rule and preserve focus visibility | | Desktop works, mobile breaks | Inspect the section below 800 px and 600 px | Add an explicit stacking rule and preserve focus visibility |
| Worktree creation says branch is checked out | Run `git worktree list` | Reuse or remove the existing worktree; do not force it | | Worktree creation says branch is checked out | Run `git worktree list` | Reuse or remove the existing worktree; do not force it |
| Pages push is rejected | Fetch and inspect remote branch state | Reconcile normally; never force-push without an explicit recovery decision | | Pages push is rejected | Fetch and inspect remote branch state | Reconcile normally; never force-push without an explicit recovery decision |
## Safe rollback ## Safe rollback
Prefer a normal revert so history and the `pages` branch remain fast-forwardable: Prefer a normal revert so history and the `pages` branch remain
fast-forwardable:
```bash ```bash
git switch main git switch main
@@ -532,7 +527,8 @@ ordinary content recovery.
- [ ] `main` is pushed. - [ ] `main` is pushed.
- [ ] `pages` fast-forwards to the same commit. - [ ] `pages` fast-forwards to the same commit.
- [ ] Live endpoint returns HTTP 200 and contains the new section. - [ ] Live endpoint returns HTTP 200 and contains the new section.
- [ ] Research links and this SilverBullet guide are updated when the workflow changes. - [ ] Research links and this SilverBullet guide are updated when the workflow
changes.
## Resumo rápido em português ## Resumo rápido em português
@@ -542,6 +538,6 @@ Gitea Pages publica diretamente essa branch. Use o endereço com
`netcracker.pages.../ai-for-dummies/`; o formato inverso quebra o TLS. `netcracker.pages.../ai-for-dummies/`; o formato inverso quebra o TLS.
Para agentes: modelo forte planeja e revisa; workers delimitados implementam em Para agentes: modelo forte planeja e revisa; workers delimitados implementam em
worktrees separados; evidências voltam ao orquestrador. Para skills: capture worktrees separados; evidências voltam ao orquestrador. Para skills: capture uma
uma decisão repetida, defina um gatilho preciso, crie apenas os recursos úteis, decisão repetida, defina um gatilho preciso, crie apenas os recursos úteis,
escreva orientação que muda decisões e valide estrutura **e** comportamento. escreva orientação que muda decisões e valide estrutura **e** comportamento.
+43
View File
@@ -0,0 +1,43 @@
import js from '@eslint/js';
import astro from 'eslint-plugin-astro';
export default [
{
ignores: [
'.agents/**',
'.astro/**',
'app.js',
'dist/**',
'full-guide/**',
'hands-on/**',
'public/hands-on/**',
'rules/**',
'scripts/**',
'skill-reviews/**',
'skills-review/**',
'skills/**',
'submitted-skills/**',
'vote-service/**',
],
},
js.configs.recommended,
...astro.configs.recommended,
{
ignores: [
'dist/**',
'hands-on/**',
'public/hands-on/**',
'submitted-skills/**',
'skill-reviews/**',
'vote-service/**',
],
},
{
rules: {
'no-console': ['warn', { allow: ['warn', 'error'] }],
eqeqeq: ['error', 'always'],
'no-var': 'error',
'prefer-const': 'error',
},
},
];
+34 -1
View File
@@ -1,5 +1,38 @@
{ {
"name": "ai-for-dummies", "name": "ai-for-dummies",
"private": true, "private": true,
"scripts": { "verify": "node scripts/verify.mjs", "serve": "python3 -m http.server 4173" } "type": "module",
"scripts": {
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview",
"check": "astro check",
"lint": "eslint . --max-warnings=0 && stylelint --allow-empty-input 'src/**/*.css' --max-warnings=0",
"format": "prettier --write .",
"verify": "node scripts/verify.mjs && node scripts/audit-ui.mjs && node .agents/scripts/check-tokens.mjs",
"gate": "./.agents/scripts/gate.sh",
"snapshot": "node .agents/scripts/snapshot-route.mjs",
"serve": "python3 -m http.server 4173",
"prepare": "husky"
},
"devDependencies": {
"@astrojs/check": "0.9.4",
"@eslint/js": "^9",
"@typescript-eslint/parser": "^8",
"astro": "5.5.0",
"eslint": "^9",
"eslint-plugin-astro": "^1",
"husky": "^9",
"lint-staged": "^16",
"prettier": "^3",
"prettier-plugin-astro": "^0.14",
"stylelint": "^16",
"stylelint-config-standard": "^39",
"typescript": "^5.8.3"
},
"overrides": {
"@astrojs/language-server": "2.15.0",
"defu": "6.1.7",
"vite": "6.2.1"
}
} }
+28
View File
@@ -0,0 +1,28 @@
# Hands-on · Rules
A tiny, zero-dependency demo showing how rule sources reshape the same prompt.
## Run
Open `index.html` directly. No build, no server, no `npm install`.
## What it shows
- Five rule sources, taken from a real monorepo (`netcracker/interview`):
- `AGENTS.md` — repo-wide instruction file.
- `.agents/skills/gate-discipline/SKILL.md` — skill body loaded on demand.
- `.husky/pre-commit` — git hook that runs other enforcers.
- `scripts/check-ui-contract.mjs` — custom CLI enforcer (ratchet).
- `commitlint.config.cjs` — commit-msg linter.
- Each rule has an on/off switch. Toggling a rule prepends its body to the **ruled** prompt.
- EN ↔ PT toggle keeps both languages useful.
- "Copy ruled prompt" copies the current ruled-prompt text to clipboard.
- Responsive on mobile, Full HD, and 4K (one column under 720 px).
## Mirror of `/hands-on/starter`
Same visual system as the starter (`--paper`, `--ink`, `--blue`, `--gold`). Drop-in replacement under `hands-on/rules/`.
## Token budget
Page weight: ~5 KB total, no framework, no fetch.
+124
View File
@@ -0,0 +1,124 @@
// Five rule sources lifted from netcracker/interview.
// Toggling a rule injects its body into the ruled prompt.
const RULES = [
{
id: 'agents',
name: 'AGENTS.md',
kind: 'Repo-wide instruction',
path: 'AGENTS.md',
en: 'Read AGENTS.md before touching this repo. Stack: pnpm + turbo monorepo, Go API, Next.js apps. Gates run from the monorepo root: pnpm lint, typecheck, test.',
pt: 'Leia AGENTS.md antes de tocar neste repo. Stack: pnpm + turbo monorepo, API em Go, apps Next.js. Gates rodam da raiz: pnpm lint, typecheck, test.'
},
{
id: 'skill',
name: 'gate-discipline skill',
kind: 'Skill body',
path: '.agents/skills/gate-discipline/SKILL.md',
en: 'Skill `gate-discipline`: every gate runs separately with `$?`. No `| tail`. `TURBO_FORCE=true` if a pass looks too cheap. Generated code is regenerated, never hand-edited.',
pt: 'Skill `gate-discipline`: cada gate roda separado com `$?`. Nada de `| tail`. `TURBO_FORCE=true` se o passar for bom demais. Código gerado se regenera, nunca se edita.'
},
{
id: 'husky',
name: 'Husky pre-commit',
kind: 'Git hook (commit time)',
path: '.husky/pre-commit',
en: 'Pre-commit runs `pnpm exec lint-staged`, then `node scripts/check-ui-contract.mjs` (UI ratchet), then commitlint. Counts in the baseline may only go DOWN.',
pt: 'Pre-commit roda `pnpm exec lint-staged`, depois `node scripts/check-ui-contract.mjs` (ratchet de UI), depois commitlint. Contadores do baseline só podem DIMINUIR.'
},
{
id: 'enforcer',
name: 'check-ui-contract.mjs',
kind: 'Custom enforcer (CLI)',
path: 'scripts/check-ui-contract.mjs',
en: 'Enforcer scans for raw <button>, silent catches, pages without h1, hardcoded colors, duplicated components. Fails when a count goes UP. Fix = `--accept` to re-baseline lower.',
pt: 'Enforcer varre <button> cru, catch silencioso, páginas sem h1, cores hardcoded, componentes duplicados. Falha quando contador SOBE. Corrigir = `--accept` para re-baseline menor.'
},
{
id: 'commitlint',
name: 'commitlint',
kind: 'Commit-msg linter',
path: 'commitlint.config.cjs',
en: 'Commitlint enforces Conventional Commits. Format: `<type>(<scope>): <subject>`. Types: feat, fix, docs, refactor, test, chore, build, ci, perf, style.',
pt: 'Commitlint enforça Commits Convencionais. Formato: `<tipo>(<escopo>): <assunto>`. Tipos: feat, fix, docs, refactor, test, chore, build, ci, perf, style.'
}
];
// The task we're asking the agent to perform.
const TASK = {
en: { goal: 'Refactor the `getUserById` endpoint to return 404 instead of throwing.', ctx: 'services/api/internal/user/handler.go. No DB schema change.' },
pt: { goal: 'Refatorar o endpoint `getUserById` para retornar 404 em vez de lançar exceção.', ctx: 'services/api/internal/user/handler.go. Sem mudança de schema.' }
};
const NAIVE = {
en: `Task: ${TASK.en.goal}\nFile: ${TASK.en.ctx}\nPlease make the change and tell me when done.`,
pt: `Tarefa: ${TASK.pt.goal}\nArquivo: ${TASK.pt.ctx}\nFaça a mudança e me avise quando terminar.`
};
const state = { enabled: new Set(['agents']), lang: 'en' };
const ruleList = document.querySelector('#rule-list');
const ruleCount = document.querySelector('#rule-count');
const promptNaive = document.querySelector('#prompt-naive');
const promptRuled = document.querySelector('#prompt-ruled');
const copyBtn = document.querySelector('#copy-btn');
const langButtons = document.querySelectorAll('.lang-switch button');
function renderRules() {
ruleList.innerHTML = RULES.map((rule) => `
<article class="rule" data-id="${rule.id}">
<div>
<h3>${rule.name}</h3>
<p>${rule.kind} <code>${rule.path}</code></p>
</div>
<span class="rule-tag">${rule.id}</span>
<button class="toggle" type="button" data-rule="${rule.id}" aria-pressed="${state.enabled.has(rule.id)}" aria-label="Toggle ${rule.name}"></button>
</article>
`).join('');
ruleCount.textContent = `${state.enabled.size} / ${RULES.length} active`;
}
function renderPrompts() {
const lang = state.lang;
promptNaive.textContent = NAIVE[lang];
const active = RULES.filter((r) => state.enabled.has(r.id));
const blocks = active.map((r) => `# ${r.name} (${r.path})\n${r[lang]}`).join('\n\n');
const head = `Task: ${TASK[lang].goal}\nFile: ${TASK[lang].ctx}`;
const tail = lang === 'en'
? '\n\nRun each gate separately and print $? before claiming done.'
: '\n\nRode cada gate separado e imprima $? antes de dizer que terminou.';
promptRuled.textContent = blocks ? `${head}\n\n${blocks}${tail}` : head + tail;
}
function bind() {
ruleList.addEventListener('click', (e) => {
const btn = e.target.closest('.toggle');
if (!btn) return;
const id = btn.dataset.rule;
if (state.enabled.has(id)) state.enabled.delete(id); else state.enabled.add(id);
btn.setAttribute('aria-pressed', String(state.enabled.has(id)));
ruleCount.textContent = `${state.enabled.size} / ${RULES.length} active`;
renderPrompts();
});
langButtons.forEach((btn) => btn.addEventListener('click', () => {
state.lang = btn.dataset.lang;
langButtons.forEach((b) => b.setAttribute('aria-pressed', String(b === btn)));
renderPrompts();
}));
copyBtn.addEventListener('click', async () => {
try {
await navigator.clipboard.writeText(promptRuled.textContent);
copyBtn.dataset.copied = 'true';
copyBtn.textContent = 'Copied';
setTimeout(() => { copyBtn.dataset.copied = 'false'; copyBtn.textContent = 'Copy ruled prompt'; }, 1400);
} catch {
copyBtn.textContent = 'Copy failed — select and copy manually';
}
});
}
renderRules();
renderPrompts();
bind();
+43
View File
@@ -0,0 +1,43 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Guardrails — Hands-on Rules</title>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<main>
<header>
<div><span>HANDS-ON / RULES</span><h1>Guardrails</h1></div>
<p>Toggle rules. Same task, different coverage.</p>
</header>
<section aria-labelledby="rules-heading">
<div class="section-head"><h2 id="rules-heading">Rule sources</h2><span id="rule-count">0 / 5 active</span></div>
<div id="rule-list" class="rule-list"></div>
</section>
<section aria-labelledby="prompt-heading">
<div class="section-head"><h2 id="prompt-heading">Prompt diff</h2>
<div class="lang-switch" role="group" aria-label="Language">
<button type="button" data-lang="en" aria-pressed="true">EN</button>
<button type="button" data-lang="pt" aria-pressed="false">PT</button>
</div>
</div>
<div class="prompt-grid">
<article class="prompt-card" data-side="naive">
<header><span>NAIVE</span><h3>Plain prompt</h3></header>
<pre id="prompt-naive"></pre>
</article>
<article class="prompt-card" data-side="ruled">
<header><span>RULED</span><h3>With guardrails</h3></header>
<pre id="prompt-ruled"></pre>
<button id="copy-btn" type="button">Copy ruled prompt</button>
</article>
</div>
</section>
</main>
<script src="app.js" defer></script>
</body>
</html>
+1
View File
@@ -0,0 +1 @@
:root{--paper:#f4f3ef;--ink:#173044;--muted:#687d8c;--blue:#5683a1;--gold:#efc86d;--line:#d5dde1}*{box-sizing:border-box}body{margin:0;min-width:320px;background:var(--paper);color:var(--ink);font-family:Arial,sans-serif}main{width:min(880px,calc(100% - 40px));margin:0 auto;padding:70px 0}header,.section-head{display:flex;justify-content:space-between;gap:30px;align-items:end}header{padding-bottom:50px;border-bottom:1px solid var(--line)}header span,.prompt-card>header span{font:10px monospace;letter-spacing:.08em}h1{margin:12px 0 0;font-size:clamp(48px,10vw,100px);letter-spacing:-.08em}header p{max-width:220px;color:var(--muted);line-height:1.5}section{padding-top:45px}h2{font-size:24px}.section-head>span{color:var(--blue);font:11px monospace}.rule-list,.prompt-grid{display:grid;gap:1px;background:var(--line);border:1px solid var(--line)}.rule{display:grid;grid-template-columns:1fr auto auto;gap:20px;padding:22px;background:var(--paper);align-items:center}.rule h3{margin:0 0 6px;font-size:17px}.rule p{margin:0;color:var(--muted);font-size:13px;line-height:1.45}.rule code{font:11px ui-monospace,monospace;color:var(--ink);background:var(--paper);padding:1px 5px;border:1px solid var(--line)}.rule-tag{font:10px monospace;letter-spacing:.08em;color:var(--muted);text-transform:uppercase}.toggle{appearance:none;width:44px;height:24px;border:1px solid var(--line);background:var(--paper);border-radius:12px;position:relative;cursor:pointer;transition:background .15s ease}.toggle::after{content:"";position:absolute;top:2px;left:2px;width:18px;height:18px;background:var(--ink);border-radius:50%;transition:transform .15s ease,background .15s ease}.toggle[aria-pressed="true"]{background:var(--blue);border-color:var(--blue)}.toggle[aria-pressed="true"]::after{transform:translateX(20px);background:var(--gold)}.prompt-grid{grid-template-columns:1fr 1fr;margin-top:18px}.prompt-card{background:var(--paper);padding:22px;display:flex;flex-direction:column;gap:14px}.prompt-card>header{display:flex;justify-content:space-between;align-items:end;padding-bottom:0;border-bottom:0}.prompt-card h3{margin:0;font-size:17px}.prompt-card pre{margin:0;font:12px ui-monospace,monospace;white-space:pre-wrap;word-break:break-word;color:var(--ink);background:var(--paper);border:1px solid var(--line);padding:14px;min-height:160px;line-height:1.5}#copy-btn{align-self:flex-start;appearance:none;border:1px solid var(--ink);background:var(--ink);color:var(--paper);font:11px monospace;letter-spacing:.08em;padding:9px 14px;cursor:pointer;text-transform:uppercase}#copy-btn[data-copied="true"]{background:var(--blue);border-color:var(--blue)}.lang-switch{display:flex;gap:1px;border:1px solid var(--line)}.lang-switch button{appearance:none;border:0;background:var(--paper);color:var(--muted);font:11px monospace;letter-spacing:.08em;padding:6px 10px;cursor:pointer;text-transform:uppercase}.lang-switch button[aria-pressed="true"]{background:var(--ink);color:var(--paper)}@media(max-width:720px){.prompt-grid{grid-template-columns:1fr}.rule{grid-template-columns:1fr}.rule-tag{display:none}}@media(max-width:560px){header{display:block}header p{margin-top:24px}.lang-switch{flex:1}.lang-switch button{flex:1}}
+13
View File
@@ -0,0 +1,13 @@
# Tiny Tasks hands-on starter
A dependency-free HTML/CSS/JavaScript exercise used by the AI For Dummies
presentation. The task list renders; status filtering is intentionally absent.
Run from the repository root:
```bash
python3 -m http.server 4173
```
Open <http://localhost:4173/hands-on/starter/> and paste either prompt from the
presentation into a fresh coding-agent session rooted at this repository.
+15
View File
@@ -0,0 +1,15 @@
const tasks = [
{ title: 'Review pull request', owner: 'Maya', status: 'open' },
{ title: 'Write release notes', owner: 'Theo', status: 'done' },
{ title: 'Check mobile layout', owner: 'Lina', status: 'open' }
];
const list = document.querySelector('#task-list');
const count = document.querySelector('#task-count');
function renderTasks() {
list.innerHTML = tasks.map((task) => `<article class="task" data-status="${task.status}"><div><h3>${task.title}</h3><p>Owner: ${task.owner}</p></div><span class="task-meta">${task.status}</span></article>`).join('');
count.textContent = `${tasks.length} tasks`;
}
renderTasks();
+22
View File
@@ -0,0 +1,22 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Tiny Tasks — Hands-on Starter</title>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<main>
<header>
<div><span>HANDS-ON / STARTER</span><h1>Tiny Tasks</h1></div>
<p>Three tasks. One missing filter.</p>
</header>
<section aria-labelledby="task-heading">
<div class="section-head"><h2 id="task-heading">Today</h2><span id="task-count"></span></div>
<div id="task-list" class="task-list"></div>
</section>
</main>
<script src="app.js" defer></script>
</body>
</html>
+1
View File
@@ -0,0 +1 @@
:root{--paper:#f4f3ef;--ink:#173044;--muted:#687d8c;--blue:#5683a1;--gold:#efc86d;--line:#d5dde1}*{box-sizing:border-box}body{margin:0;min-width:320px;background:var(--paper);color:var(--ink);font-family:Arial,sans-serif}main{width:min(880px,calc(100% - 40px));margin:0 auto;padding:70px 0}header,.section-head{display:flex;justify-content:space-between;gap:30px;align-items:end}header{padding-bottom:50px;border-bottom:1px solid var(--line)}header span,.task-meta{font:10px monospace;letter-spacing:.08em}h1{margin:12px 0 0;font-size:clamp(48px,10vw,100px);letter-spacing:-.08em}header p{max-width:220px;color:var(--muted);line-height:1.5}section{padding-top:45px}h2{font-size:24px}.section-head>span{color:var(--blue);font:11px monospace}.task-list{display:grid;gap:1px;background:var(--line);border:1px solid var(--line)}.task{display:grid;grid-template-columns:1fr auto;gap:20px;padding:22px;background:var(--paper)}.task h3{margin:0 0 8px;font-size:17px}.task p{margin:0;color:var(--muted);font-size:13px}.task-meta{align-self:center;padding:7px 9px;color:var(--ink);background:var(--gold)}.task[data-status="done"] .task-meta{color:var(--paper);background:var(--blue)}@media(max-width:560px){header{display:block}header p{margin-top:24px}.task{grid-template-columns:1fr}.task-meta{justify-self:start}}
+23
View File
@@ -0,0 +1,23 @@
---
interface Props {
title: string;
description: string;
lang?: string;
}
const { title, description, lang = 'en' } = Astro.props;
---
<!doctype html>
<html lang={lang}>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{title}</title>
<meta name="description" content={description} />
<slot name="styles" />
</head>
<body>
<slot />
</body>
</html>
+63
View File
@@ -0,0 +1,63 @@
---
import BaseLayout from '../layouts/BaseLayout.astro';
import chaptersStylesheet from '../../chapters.css?url';
const base = import.meta.env.BASE_URL;
const introduction =
'This guide turns AI work into a shape: frame the problem, choose the model and agent, isolate changes, teach repeatable decisions, and verify the result.';
---
<BaseLayout title="AI For Dummies — Route map" description="">
<link slot="styles" rel="stylesheet" href={chaptersStylesheet} />
<main>
<header class="top">
<a href={`${base}full-guide/`}> AI FOR DUMMIES</a>
<span>00 / ROUTE MAP</span>
<a href={`${base}skills-review/`}>review desk </a>
</header>
<section class="hero">
<p class="eyebrow">Start here</p>
<h1>Ship the<br /><em>system.</em></h1>
<p>{introduction}</p>
</section>
<section class="grid">
<article class="card">
<b>01</b><h2>Models</h2><p>Capability and effort are separate knobs.</p><a
href={`${base}models/`}>Open chapter </a
>
</article>
<article class="card">
<b>02</b><h2>Agents & trees</h2><p>Bound roles, handoffs, and worktrees.</p><a
href={`${base}agents/`}>Open chapter </a
>
</article>
<article class="card">
<b>03</b><h2>Skills</h2><p>Capture repeatable decisions.</p><a href={`${base}skills/`}
>Open chapter </a
>
</article>
<article class="card">
<b>04</b><h2>Rules</h2><p>Connect guidance to enforcement.</p><a href={`${base}rules/`}
>Open chapter </a
>
</article>
<article class="card">
<b>05</b><h2>Practice</h2><p>Compare prompts and skill-enabled runs.</p><a
href={`${base}hands-on/starter/`}>Open lab </a
>
</article>
<article class="card">
<b>06</b><h2>Review desk</h2><p>Browse original files and improved drafts.</p><a
href={`${base}skills-review/`}>Open desk </a
>
</article>
</section>
<nav class="links">
<a href={`${base}full-guide/`}>Full field guide</a>
<a href={`${base}docs/operations-guide.md`}>Operations guide</a>
</nav>
<footer>
Each chapter stands alone; the order follows a real task becoming a reliable change.
</footer>
</main>
</BaseLayout>
+5
View File
@@ -0,0 +1,5 @@
{
"extends": "astro/tsconfigs/strict",
"include": [".astro/types.d.ts", "**/*"],
"exclude": ["dist"]
}