docs: add .agents workspace and the Astro refactor plan

Adds the agent-facing workspace and a 20-task plan for migrating the site
to Astro. Nothing here implements the refactor; these are briefs, rules and
templates that the task agents read.

- .agents/ holds context, rules, checklists, skills, specialist agents,
  component/page/config templates and gate scripts. It is vendor-neutral so
  MiniMax, Gemini and Codex can all read it; CLAUDE.md just points at
  AGENTS.md.
- .husky/ plus .lintstagedrc.json wire the three gate tiers. gate.sh locks on
  the shared git-common-dir so parallel worktrees serialise, and guards the
  assertion count in scripts/verify.mjs against a coverage drop.
- plans/astro-refactor/ carries the phase graph, per-task briefs and the
  model-routing recommendation.

These files must be tracked before fanning out: a worktree only checks out
tracked files, so an untracked plan is invisible to every agent working in one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Marcos Paulo
2026-09-05 01:18:27 +00:00
parent aa85c1d0b7
commit aae4d42229
79 changed files with 3805 additions and 0 deletions
+73
View File
@@ -0,0 +1,73 @@
# Which model runs which task
You have **MiniMax-M3** (primary), **Gemini**, and **Codex**. They are not
interchangeable here, and the split is not about which is "smartest" — it is
about which failure mode each task punishes.
**Honest caveat up front:** I have not benchmarked these three on this
repository. The routing below is reasoned from task shape and each model's
known strengths. Validate it cheaply on **task 07** (small, self-contained,
easy to judge) before fanning out across ten worktrees.
## Short answer
**Run the refactor on MiniMax-M3.** It is your primary, the work is mostly
bounded mechanical migration with a hard verification gate, and that is exactly
the shape M3 handles well at low cost. Reach for the other two at three specific
points where M3 is the wrong tool.
## Routing table
| Task | Model | Why this one |
| --- | --- | --- |
| 01 scaffold + gates | **Codex** | Config-heavy, many interacting tools (Astro + husky + lint-staged + CI), and success is binary — it builds and hooks fire, or not. Codex's long autonomous run-until-green loop suits it, and getting the foundation wrong is expensive later. |
| 02 design tokens | **Gemini** | Needs the whole CSS corpus in one context (8 stylesheets, ~90 KB) plus **visual judgement on screenshots**. Gemini's long context and multimodal comparison are the differentiator; the others would work file-by-file and miss cross-file drift. |
| 03 verification net | **Codex** | Writing test tooling with a tight feedback loop. Precision about what an assertion pins matters more than speed. |
| 04 content schema | **MiniMax-M3** | Small, well-specified, one file. |
| 0506 content migration | **MiniMax-M3** | High-volume mechanical string moves with a `diff` as the oracle. Cheap, parallel, verifiable. Exactly M3's sweet spot. |
| 07 primitives | **MiniMax-M3** | Small components from templates. Use this task to calibrate the whole routing decision. |
| 0811 component blocks | **MiniMax-M3 ×4 parallel** | Four bounded tasks, one template each, checklist-gated. Cost per task matters because there are many. |
| 1214, 17 pages | **MiniMax-M3** | Bounded, snapshot-diff verified. |
| 15 full guide | **Codex** | The hard one: 50 KB `app.js`, 12 render functions, tab state, bilingual swap. Long sustained reasoning over interacting pieces; the task most likely to need many iterations against a failing check. |
| 16 review desk | **Codex** | Same shape and worse — search, filtering, file fetching, six query params, markdown rendering, client-side diff. Highest defect risk in the plan. |
| 18 motion | **Gemini** | Judging whether motion looks right is perceptual. Feed it before/after captures. |
| 19 contract re-point | **Codex** | 42 assertions to translate without losing coverage. Meticulous, mechanical, verifiable. |
| 20 cutover | **you, with M3 assisting** | Touches production publishing. A human should be watching. |
| review of every task | **Gemini** | Fresh eyes, whole-diff-in-context, and it did not write the code. Never review with the model that wrote it. |
## The reasoning in one line each
- **MiniMax-M3** — cheapest per task and strong at bounded, tool-driven edits.
Use it for volume: 13 of the 20 tasks. Its weakness is long multi-file
reasoning where the spec is vague; every task above that it owns has a
template and a mechanical oracle.
- **Codex** — best at "keep iterating until the check passes" over a
complicated existing codebase. Use it where the loop is long and the answer is
not obvious: scaffold, the two hard pages, verification.
- **Gemini** — biggest context and genuinely useful multimodal comparison. Use
it where the input is *everything at once* or where the judgement is
**visual**: token consolidation, motion, screenshot diffing, and code review.
## Cross-checking rule
Whatever writes a task must not review it. Pair them: M3 writes → Gemini
reviews; Codex writes → Gemini reviews; Gemini writes → Codex reviews. The
`reviewer` agent definition is model-agnostic on purpose.
## Swap the routing if you see this
| Symptom | Move the task to |
| --- | --- |
| M3 spends more than ~3 iterations failing the same gate | Codex |
| M3 edits files outside its task scope | Codex, and tighten the brief |
| Codex "fixes" a red suite by deleting assertions | anything — but re-read `context/verification.md` to it first; `gate.sh` blocks the merge either way |
| Gemini gives confident visual sign-off with no screenshots attached | require the artifacts; do not accept prose |
| A task needs more than two models' worth of hand-holding | the task is too big — split it |
## Cost shape
Tasks 0514 are ~two-thirds of the work and are all M3-eligible. Running those
on Codex would work and cost several times more for no measurable quality gain,
because the checklist and `gate.sh` — not the model — are what guarantee those
outputs. Spend the expensive models where there is no mechanical oracle: 15, 16,
and the visual judgement calls.
+94
View File
@@ -0,0 +1,94 @@
# Astro refactor — plan
**Status: not started. These are briefs, not work.** Nothing in this plan has
been implemented.
Goal: move `ai-for-dummies` from ten hand-written HTML pages to Astro, so that
adding a chapter is a component and a content entry rather than a copy-pasted
file — **without changing how the site looks, what it says, or what it costs a
visitor to load.**
## Read before starting anything
| File | Why |
| --- | --- |
| [`../../AGENTS.md`](../../AGENTS.md) | entry point |
| [`../../.agents/context/design-system.md`](../../.agents/context/design-system.md) | three drifting palettes, a font that has never rendered |
| [`../../.agents/context/verification.md`](../../.agents/context/verification.md) | 42 assertions that will all break, and must not be deleted |
| [`../../.agents/context/publishing.md`](../../.agents/context/publishing.md) | Gitea Pages serves a branch and cannot build |
## The three things most likely to go wrong
1. **Content loss that nobody notices.** 50 KB of bilingual copy moves between
files. Snapshot every route *before* migrating it — task 03 exists to make
that possible and blocks all page work.
2. **Assertions deleted to make a red suite green.** That converts a content-loss
bug into a passing build. `gate.sh` refuses a coverage drop.
3. **Base-path bugs.** The site lives at `/ai-for-dummies/`. It will work
perfectly in `npm run preview` and 404 in production. Verify on the real host.
## Phases
```
Phase 0 foundation 01 → (02 ∥ 03 ∥ 04)
Phase 1 content 05 ∥ 06 after 04
Phase 2 components 07 → (08 ∥ 09 ∥ 10 ∥ 11) after 02
Phase 3 pages 12 ∥ 13 ∥ 14 ∥ 17, then 15 ∥ 16
Phase 4 polish 18 ∥ 19, then 20
```
| # | Task | Agent | Depends on | Parallel with |
| --- | --- | --- | --- | --- |
| 01 | [scaffold + gates](task-01-scaffold.md) | astro-architect | — | — |
| 02 | [design tokens](task-02-tokens.md) | design-system-keeper | 01 | 03, 04 |
| 03 | [verification net](task-03-verification-net.md) | verification-engineer | 01 | 02, 04 |
| 04 | [content schema](task-04-content-schema.md) | content-i18n-migrator | 01 | 02, 03 |
| 05 | [guide content](task-05-content-guide.md) | content-i18n-migrator | 04 | 06 |
| 06 | [review-desk content](task-06-content-review.md) | content-i18n-migrator | 04 | 05 |
| 07 | [primitives](task-07-primitives.md) | component-builder | 02 | — |
| 08 | [route cards](task-08-route-cards.md) | component-builder | 07 | 09, 10, 11 |
| 09 | [chapter blocks](task-09-chapter-blocks.md) | component-builder | 07 | 08, 10, 11 |
| 10 | [guide blocks](task-10-guide-blocks.md) | component-builder | 07 | 08, 09, 11 |
| 11 | [review-desk blocks](task-11-review-blocks.md) | component-builder | 07 | 08, 09, 10 |
| 12 | [landing page](task-12-page-landing.md) | page-migrator | 03, 08 | 13, 14, 17 |
| 13 | [chapter pages ×4](task-13-page-chapters.md) | page-migrator | 03, 09 | 12, 14, 17 |
| 14 | [rules page](task-14-page-rules.md) | page-migrator | 03, 09 | 12, 13, 17 |
| 15 | [full guide](task-15-page-full-guide.md) | page-migrator | 05, 10, 13 | 16 |
| 16 | [review desk](task-16-page-review-desk.md) | page-migrator | 06, 11, 13 | 15 |
| 17 | [hands-on passthrough](task-17-hands-on.md) | astro-architect | 01 | 12, 13, 14 |
| 18 | [motion pass](task-18-motion.md) | motion-designer | 15, 16 | 19 |
| 19 | [contract re-point](task-19-verify-repoint.md) | verification-engineer | 15, 16 | 18 |
| 20 | [cutover + cleanup](task-20-cutover.md) | astro-architect | all | — |
Widest parallelism: **four agents** (tasks 0811, then 12/13/14/17). More than
that and they start contending on review capacity, not on files.
## Running a task
```bash
.agents/scripts/worktree.sh start 08 route-cards
cd ../af-task-08
# agent reads: plans/astro-refactor/task-08-route-cards.md
# .agents/agents/component-builder.md (+ the skills it names)
```
The script runs `npm ci` and `verify-hooks.sh` for you. That matters: `.husky/_`
is generated, not committed, so a hand-made worktree has hooks configured but
**silently not running**.
Finishing:
```bash
npm run gate # tier 2, same as pre-push
# reviewer agent reads the diff against .agents/checklists/before-merge.md
.agents/scripts/worktree.sh finish 08 route-cards
```
## Which model to run each task
See [`MODEL-ROUTING.md`](MODEL-ROUTING.md).
## These files must be committed
Worktrees check out tracked files. If this plan stays untracked, every worktree
you create will be missing it. Commit `plans/` and `.agents/` before fanning out.
+59
View File
@@ -0,0 +1,59 @@
# Task 01 — Astro scaffold, gates, and the publishing decision
**Agent**: `astro-architect` · **Model**: Codex · **Depends on**: nothing
**Blocks**: everything · **Worktree**: `.agents/scripts/worktree.sh start 01 scaffold`
## Goal
A working Astro project that builds, serves one page correctly from the real
host under `/ai-for-dummies/`, and has all three gate tiers live.
## Scope
`astro.config.mjs`, `package.json`, `tsconfig.json`, `src/layouts/BaseLayout.astro`,
`.husky/`, `.lintstagedrc.json`, lint configs, `.gitea/workflows/verify.yml`,
`docs/operations-guide.md`.
Copy the configs from `.agents/templates/config/` — they are written for this
project (correct ignores for `hands-on/`, `submitted-skills/`, `vote-service/`).
## Steps
1. `npm create astro@latest` into the worktree — minimal template, TypeScript
strict, **no UI framework, no CSS framework**.
2. Set `base: '/ai-for-dummies'`. Every internal link goes through
`import.meta.env.BASE_URL` from here on.
3. `src/layouts/BaseLayout.astro`: `<html lang>`, viewport meta, title,
description, global styles slot. Nothing clever.
4. Merge `.agents/templates/config/package.scripts.json` into `package.json`.
**`"prepare": "husky"` is what makes hooks exist** — without it every hook is
inert.
5. `npm install husky lint-staged prettier eslint stylelint …`, then `npx husky init`.
6. Copy the three hooks and the lint configs into place. Verify with
`.agents/scripts/verify-hooks.sh`.
7. Migrate **one** page (`summary/` — smallest, zero JS) as a smoke test.
8. Copy `hands-on/` into `public/` (task 17 does this properly; a stub is fine here).
9. **Make the publishing decision** per `.agents/context/publishing.md`. Recommended:
Gitea Actions builds `dist/` and pushes `pages`. Copy
`.agents/templates/config/gitea-ci.yaml` to `.gitea/workflows/verify.yml`.
10. Rewrite the publishing section of `docs/operations-guide.md` to match.
## Done when
- [ ] `npm run build` succeeds; `npm run gate` passes
- [ ] `.agents/scripts/verify-hooks.sh` reports hooks live
- [ ] A bad commit message is rejected; a raw hex in a `.astro` file is rejected
- [ ] `/ai-for-dummies/summary/` serves correctly **from the real host**, not just preview
- [ ] `docs/operations-guide.md` describes the actual publishing path
## Do not
- Do not migrate more than one page. That is tasks 1216.
- Do not add a UI framework, CSS framework, or any runtime dependency.
- Do not touch `hands-on/` contents, `submitted-skills/`, or `vote-service/`.
## Watch for
The base path is the #1 production-only failure in this migration. `npm run
preview` will lie to you. Deploy the smoke-test page and curl it with a
`?v=<sha>` cache-buster.
+56
View File
@@ -0,0 +1,56 @@
# Task 02 — Design token layer
**Agent**: `design-system-keeper` · **Model**: Gemini (long context + visual judgement)
**Depends on**: 01 · **Parallel with**: 03, 04 · **Blocks**: 07
**Worktree**: `.agents/scripts/worktree.sh start 02 tokens`
## Goal
One value per token, a named type scale, five breakpoints — and photographic
proof the site looks the same.
## Read first
`.agents/context/design-system.md`. It documents two traps you will otherwise
walk into.
## Scope
`src/styles/tokens.css`, `src/styles/base.css`,
`.agents/scripts/check-tokens.mjs`. You are the only writer of these.
## The two decisions to surface
1. **Three palettes → one.** `--ink` is `#172f42` / `#122534` / `#173044`;
`--paper`, `--muted`, `--line`, `--gold` likewise. Most deltas are
sub-perceptual — canonicalize. **`--blue` (`#527f9f` vs `#215675`) is visibly
different**: screenshot both, get a human decision, record it here.
2. **The fonts have never rendered.** `styles.css:1` has a malformed
`@font-face` whose `src:` points at a Google Fonts *stylesheet*. Manrope and
DM Mono have always fallen back to Arial and generic monospace. Default:
delete the dead rule, declare what actually renders. Self-hosting the real
fonts is a **redesign** and needs sign-off.
## Steps
1. Run the extraction script in `.agents/skills/design-tokens/SKILL.md`.
2. Capture baseline screenshots: 10 routes × {560, 800, 1100, 1600} **plus the
eight breakpoint widths you are removing** (520, 530, 600, 620, 720, 850,
880, 900).
3. Write `tokens.css`: one value per semantic name, `--step-*` type scale
replacing the 14 ad-hoc `clamp()` triples, five `--bp-*` widths.
4. Re-capture. Compare. Explain every visible difference.
## Done when
- [ ] Extraction prints exactly one value per token
- [ ] `node .agents/scripts/check-tokens.mjs` passes on `src/`
- [ ] Before/after screenshots attached at all twelve widths
- [ ] The `--blue` decision and the font decision are written down here
## Do not
- Do not add a webfont.
- Do not rename tokens to numeric scales (`--color-neutral-900`).
- Do not convert the `gap:1px` over a coloured parent trick into `border` — it
is deliberate house style and appears everywhere.
@@ -0,0 +1,49 @@
# Task 03 — Verification net
**Agent**: `verification-engineer` · **Model**: Codex
**Depends on**: 01 · **Parallel with**: 02, 04 · **Blocks**: 1216
**Worktree**: `.agents/scripts/worktree.sh start 03 verification-net`
## Goal
Rendered-text baselines for all ten routes, captured from the **vanilla site**,
before any page is migrated. Without this the page migrators have nothing to
diff against and "no content was lost" becomes an opinion.
This task is on the critical path. Do it early.
## Scope
`.agents/snapshots/`, `scripts/audit-ui.mjs`, `.agents/scripts/visual-regression.mjs`.
## Steps
1. `npm run serve` against the **current, unmigrated** site.
2. Snapshot all ten routes:
```bash
for r in "" full-guide summary models agents skills rules skills-review \
hands-on/starter hands-on/rules; do
node .agents/scripts/snapshot-route.mjs "http://localhost:4173/$r/" \
> ".agents/snapshots/${r:-index}.txt"
done
```
Commit them. They are the regression net.
3. Write `.agents/scripts/visual-regression.mjs` (Playwright). Extend the
existing `scripts/inspect.py` pattern rather than inventing one. Baselines to
`.agents/snapshots/before/`.
4. **Fix the audit gap**: `audit-ui.mjs` rejects external `<script>`/`<link>`
but misses external URLs in CSS — which is exactly how the broken Google
Fonts `@font-face` got into this "dependency-free" site. Add `@import`,
`src: url(https:…)`, `url(https:…)`.
## Done when
- [ ] Ten committed snapshots, each non-empty and containing that page's real prose
- [ ] `visual-regression.mjs` captures 10 routes × 4 widths
- [ ] Extended `audit-ui.mjs` **fails** on today's `styles.css` (prove it catches the real bug), then the dead rule is removed by task 02
- [ ] `npm run gate` green
## Do not
Do not change any assertion in `verify.mjs` yet. That is task 19, after the
pages exist.
@@ -0,0 +1,37 @@
# Task 04 — Content collection schema
**Agent**: `content-i18n-migrator` · **Model**: MiniMax-M3
**Depends on**: 01 · **Parallel with**: 02, 03 · **Blocks**: 05, 06
**Worktree**: `.agents/scripts/worktree.sh start 04 content-schema`
## Goal
Typed collections that make a missing translation a build error.
## Scope
`src/content/config.ts` only. You own it.
## Steps
1. Define `const localized = z.object({ en: z.string(), pt: z.string() })`.
**Both required.** A missing `pt` must fail the build — silent English
fallback is how bilingual sites quietly become monolingual.
2. Collections:
- `guide``phases`, `modelGuide`, `skillSources`, `handsOnPrompts`, `skillInstallPrompts` from `app.js`
- `chapters` — copy for `/models/`, `/agents/`, `/skills/`, `/summary/`
- `reviews` — the 24 entries: `id`, `author`, `title`, `status`, `focus`, `wins[]`, `improve[]`, `extras`, `improved`
3. **Make the language-switching decision** and record it here:
- *client-side swap* — matches today, no URL change, both languages in the payload. **Recommended.**
- *route-based `/en/` `/pt/`* — better SEO, changes every existing URL, needs redirects.
## Done when
- [ ] `astro check` passes
- [ ] A deliberately missing `pt` field fails the build (prove it, then revert)
- [ ] The language decision is written down here with its reason
## Do not
Do not move any content yet. Schema only — tasks 05 and 06 fill it, and they
run in parallel against the shape you define.
@@ -0,0 +1,38 @@
# Task 05 — Guide content out of app.js
**Agent**: `content-i18n-migrator` · **Model**: MiniMax-M3
**Depends on**: 04 · **Parallel with**: 06 · **Blocks**: 15
**Worktree**: `.agents/scripts/worktree.sh start 05 content-guide`
## Goal
Every `{ en, pt }` string in `app.js` lives in `src/content/guide/`, byte-identical.
## Scope
`src/content/guide/**`. Do **not** delete anything from `app.js` yet — task 15
removes it once the page consumes the collection.
## Steps
1. `node .agents/scripts/extract-strings.mjs app.js > /tmp/before.json`
(~50 pairs across `phases`, `handsOnPrompts`, `modelGuide`, `skillSources`,
`skillInstallPrompts`).
2. Move them into the collection. **Copy mechanically — never retype.** These
are hand-written translations with deliberate tone (`'Transforme ambiguidade
em trabalho'`); retyping introduces drift nobody catches until a Portuguese
speaker reads it.
3. `node .agents/scripts/extract-strings.mjs src/content/guide/ > /tmp/after.json`
4. `diff /tmp/before.json /tmp/after.json`**must be empty**.
## Watch for
`handsOnPrompts` values are multi-line prompt strings built with `.join('\n')`.
Preserve the exact line breaks and leading `-` bullets: they are copy-pasted by
workshop attendees into an agent, and a mangled prompt breaks the exercise.
## Done when
- [ ] String diff empty
- [ ] `astro check` passes; both locales present on every entry
- [ ] `app.js` still untouched and the site still works
@@ -0,0 +1,43 @@
# Task 06 — Review-desk content out of catalog.js
**Agent**: `content-i18n-migrator` · **Model**: MiniMax-M3
**Depends on**: 04 · **Parallel with**: 05 · **Blocks**: 16
**Worktree**: `.agents/scripts/worktree.sh start 06 content-review`
## Goal
24 review entries become a typed collection, with the `improved` markdown as
real `.md` files.
## Scope
`src/content/reviews/**`, `scripts/build-skill-review.mjs`.
## Steps
1. Move all 24 entries from `skills-review/catalog.js` (27 KB) and
`submitted-catalog.js` (18 KB) into the collection.
2. `improved` becomes a real Markdown file per entry, rendered at build time.
That deletes the hand-rolled client-side renderer.
3. **Re-point `scripts/build-skill-review.mjs`** at the collection.
## The two traps
1. **The generator writes committed files.**
`skill-reviews/improved/**/SKILL.md` is generated from the `improved` fields
and the output is in git. Move `catalog.js` and the generator keeps running
against nothing — **silently, exit code 0**. Prove it still produces
byte-identical output:
```bash
node scripts/build-skill-review.mjs && git diff --exit-code skill-reviews/
```
2. **The diff view needs raw source.** The review desk compares original and
improved as *text*. If `improved` only exists as rendered HTML, the change
lens breaks. Keep the raw string reachable.
## Done when
- [ ] 24 entries in the collection; `verify.mjs`'s `id:'` count assertion still passes
- [ ] `git diff --exit-code skill-reviews/` clean after regenerating
- [ ] `astro check` passes
- [ ] Every reference to the generator still accurate (`package.json`, `README.md`, `docs/operations-guide.md`, the review desk footer)
@@ -0,0 +1,45 @@
# Task 07 — Primitives
**Agent**: `component-builder` · **Model**: MiniMax-M3 — **use this task to
calibrate the model routing before fanning out**
**Depends on**: 02 · **Blocks**: 0811
**Worktree**: `.agents/scripts/worktree.sh start 07 primitives`
## Goal
The four smallest reusable pieces, so the four parallel block tasks compose
rather than reinvent.
## Scope
`src/components/primitives/``Eyebrow.astro`, `Rule.astro`, `Callout.astro`,
`CodeBlock.astro`. Nothing else.
## Why these four
Each appears 10+ times across the current stylesheets:
- **Eyebrow** — `1011px` monospace, `letter-spacing:.08.1em`, uppercase. The
single most repeated treatment on the site and a signature of the design.
- **Rule** — the `border-top: 4px solid var(--gold)` section divider.
- **Callout** — gold-background emphasis block (`.callout`, `.thesis`).
- **CodeBlock** — `<pre>` on `--ink` with gold text.
## Steps
Copy `.agents/templates/components/static-block.astro` for each. Typed props,
tokens only, zero JS. Then grep the current CSS for every place each treatment
appears and confirm the component covers them all — if it needs five variants,
you have found two components, not one.
## Done when
- [ ] `.agents/checklists/before-component.md` complete for all four
- [ ] Rendered output visually identical to the CSS classes they replace
- [ ] `npm run gate` green
- [ ] **Routing note written**: how many iterations, what the model got wrong.
This decides whether 0811 run on M3 or move to Codex.
## Do not
Do not build blocks or touch pages. Do not edit `tokens.css`.
@@ -0,0 +1,37 @@
# Task 08 — Route cards and grid group
**Agent**: `component-builder` · **Model**: MiniMax-M3
**Depends on**: 07 · **Parallel with**: 09, 10, 11 · **Blocks**: 12
**Worktree**: `.agents/scripts/worktree.sh start 08 route-cards`
## Goal
The landing page's six chapter cards become one component over data.
## Scope
`src/components/blocks/RouteCard.astro`, `src/components/blocks/GridGroup.astro`.
## Source
`index.html` — six `<article class="card">` blocks differing only in number,
title, summary, and href. Textbook extraction. Styles in `landing.css` +
`chapters.css`.
## Steps
1. `GridGroup` from `.agents/templates/components/grid-group.astro`. Preserve
the `gap:1px` over a coloured parent separator technique — it is deliberate.
2. `RouteCard` from `static-block.astro`: `number`, `title`, `summary`, `href`.
3. Card content moves to the `chapters` collection, both locales.
## Done when
- [ ] Six cards render identically to `index.html` today
- [ ] Screenshots at 560/800/1100/1600 match
- [ ] Zero JS
- [ ] `.agents/checklists/before-component.md` complete; `npm run gate` green
## Do not
Do not migrate `index.html` itself — that is task 12.
@@ -0,0 +1,38 @@
# Task 09 — Chapter blocks
**Agent**: `component-builder` · **Model**: MiniMax-M3
**Depends on**: 07 · **Parallel with**: 08, 10, 11 · **Blocks**: 13, 14
**Worktree**: `.agents/scripts/worktree.sh start 09 chapter-blocks`
## Goal
The shared furniture of `/models/`, `/agents/`, `/skills/`, `/summary/`,
`/rules/` — five pages that today duplicate the same markup five times.
## Scope
`src/components/blocks/``ChapterHero.astro`, `SectionGrid.astro`,
`ComparisonTable.astro`, `TopBar.astro`, `SiteFooter.astro`.
`src/layouts/ChapterLayout.astro`.
## Source
`chapters.css` is already the shared layer; the duplication is in the five HTML
files. Diff them against each other first — what differs is props, what matches
is the component.
## Watch for
- `TopBar` carries `aria-current="page"`. Preserve it; it is the only
indication of location for assistive tech.
- The tables in `/models/` and `/rules/` scroll horizontally on narrow screens
(`overflow-x:auto`, `min-width` on the inner element). Keep that — dropping it
makes the tables unreadable on a phone, and `audit-ui.mjs` asserts related
responsive tokens.
## Done when
- [ ] All five pages' markup expressible with these components
- [ ] Rendered text identical to current pages
- [ ] Zero JS
- [ ] Checklist complete; `npm run gate` green
@@ -0,0 +1,44 @@
# Task 10 — Full-guide blocks
**Agent**: `component-builder` · **Model**: MiniMax-M3
**Depends on**: 07 · **Parallel with**: 08, 09, 11 · **Blocks**: 15
**Worktree**: `.agents/scripts/worktree.sh start 10 guide-blocks`
## Goal
The full guide's distinctive sections as components — **static shells only**.
Task 15 wires the interactivity.
## Scope
`src/components/blocks/``PhasePanel.astro`, `FleetDiagram.astro`,
`HandoffTable.astro`, `WorktreeMap.astro`, `RouteTable.astro`,
`SkillPackage.astro`.
## Source
`full-guide/index.html` (22 KB) + `styles.css` + `responsive.css`.
## Critical: the `data-*` hooks are asserted by name
`verify.mjs` requires these survive: `data-phase="plan|build|review"`,
`data-tree="main|ui"`, `data-worker="ui"`, `data-route="plan"`,
`data-model-provider="openai|claude|gemini"`, `data-effort="low|medium|high"`,
`data-skill-file="skill"`, `data-skill-step="observe|validate"`,
`data-common-skill="ponytail|caveman|unlazy"`, `role="tablist"`, `id="hands-on"`.
Each is a real feature hook, not decoration. Losing one is losing a feature.
## Watch for
`responsive.css` is 30 KB of append-only overrides on top of `styles.css`. **Do
not port it wholesale.** Take what each component needs, then prove the rest is
dead. Expect to delete most of it — that is a win, not a risk, provided the
screenshots agree.
## Done when
- [ ] Every `data-*` hook above present in rendered output
- [ ] Components take props and render static markup; **no `client:*` yet**
- [ ] Screenshots match the current guide at four widths
- [ ] Checklist complete; `npm run gate` green
@@ -0,0 +1,49 @@
# Task 11 — Review-desk blocks
**Agent**: `component-builder` · **Model**: MiniMax-M3
**Depends on**: 07 · **Parallel with**: 08, 09, 10 · **Blocks**: 16
**Worktree**: `.agents/scripts/worktree.sh start 11 review-blocks`
## Goal
Review-desk furniture as static components. Interactivity is task 16.
## Scope
`src/components/blocks/``SkillList.astro`, `ReviewDetail.astro`,
`FileTabs.astro`, `PreviewPane.astro`, `ChangeLens.astro`, `VoteWidget.astro`
(markup only).
## Source
`skills-review/index.html`, `app.js` (20 KB), `styles.css`, `change-lens.css`,
`vote.js`.
## One component, 24 entries
The 24 reviews are **data**, not 24 components. One `SkillReviewCard` iterating
the collection. If you are writing the second near-identical component, stop.
## CSS tokens asserted by verify.mjs
`.change-lens`, `.change-rows`, `.skill-diff`, `.diff-lines`,
`.markdown-preview`, `max-height:540px`, `.markdown-table-wrap`,
`.markdown-frontmatter`, `.markdown-toc`, `.preview-title`, `.preview-markdown`,
`grid-template-columns:minmax(0,1fr)`, `height:120px`, `-webkit-line-clamp:2`,
`.vote-widget`, `.vote-buttons`, `[aria-pressed="true"]`.
Class names may move into scoped `<style>` blocks, but each must still exist and
task 19 must be able to assert it. Coordinate names with the verification
engineer rather than renaming freely.
## Watch for
`aria-pressed` on the vote buttons and preview switcher is how state reaches
assistive tech. Colour alone is not enough. It is also asserted.
## Done when
- [ ] All listed CSS hooks present
- [ ] `aria-pressed`, `role="group"`, `aria-label` preserved
- [ ] Static render matches current desk at four widths
- [ ] Checklist complete; `npm run gate` green
@@ -0,0 +1,30 @@
# Task 12 — Landing page
**Agent**: `page-migrator` · **Model**: MiniMax-M3
**Depends on**: 03, 08 · **Parallel with**: 13, 14, 17
**Worktree**: `.agents/scripts/worktree.sh start 12 page-landing`
## Goal
`index.html``src/pages/index.astro`. Smallest page migration; sets the
pattern the other page tasks follow.
## Steps
Snapshot first (task 03 baseline exists — diff against it). Then compose from
`RouteCard` + `GridGroup`, move copy to the `chapters` collection, port
`landing.css` into component styles.
## Hard constraints
- URL stays `/ai-for-dummies/` exactly
- **Zero JS** — this page has none today
- `<title>`, `<meta name="description">`, viewport preserved
- Internal links via `import.meta.env.BASE_URL`, never hand-written absolute
## Done when
- [ ] `diff .agents/snapshots/index.txt <(snapshot of dist)` empty
- [ ] Screenshots match at four widths
- [ ] Built page ships **0 bytes** of JS
- [ ] `.agents/checklists/before-page.md` complete; `npm run gate` green
@@ -0,0 +1,33 @@
# Task 13 — Chapter pages ×4
**Agent**: `page-migrator` · **Model**: MiniMax-M3
**Depends on**: 03, 09 · **Parallel with**: 12, 14, 17 · **Blocks**: 15, 16
**Worktree**: `.agents/scripts/worktree.sh start 13 page-chapters`
## Goal
`/models/`, `/agents/`, `/skills/`, `/summary/` as Astro routes. One task
because they share a layout — four near-identical migrations, and doing them
together is what proves `ChapterLayout` is right.
Blocks 15 and 16 because those pages link here and share the layout.
## Hard constraints
- Four URLs unchanged, **trailing slashes included**
- `/skills/` has an interactive package explorer (`skills/app.js`,
`data-skill-file`, `data-skill-step`) — that becomes an **island**, and it is
the only JS across these four pages. The other three ship zero.
- All `data-*` hooks preserved; `verify.mjs` asserts several
## Done when
- [ ] Four snapshot diffs empty
- [ ] Three pages ship 0 bytes JS; `/skills/` ships only its island
- [ ] Screenshots match at four widths
- [ ] Checklist complete for each page; `npm run gate` green
## Do not
Do not generalise `ChapterLayout` beyond these four. `/rules/` is task 14 and
has its own interaction model.
@@ -0,0 +1,33 @@
# Task 14 — Rules page
**Agent**: `page-migrator` · **Model**: MiniMax-M3
**Depends on**: 03, 09 · **Parallel with**: 12, 13, 17
**Worktree**: `.agents/scripts/worktree.sh start 14 page-rules`
## Goal
`/rules/` → Astro. Separate from task 13: it is a standalone bilingual case
study with its own stylesheet (`rules/styles.css`, 9.8 KB), its own script
(`rules/app.js`, 16 KB), and its own responsive contract.
## Asserted by verify.mjs
- `rulesHtml` must have **no external `<script>`/`<link>`** — it is explicitly
checked as a standalone page
- `rulesCss` must contain `@media(min-width:2200px)`, `@media(max-width:900px)`,
`@media(max-width:600px)`, `prefers-reduced-motion`
Task 02 collapses those breakpoints to named tokens. **Coordinate with the
verification engineer** — the assertion must be re-pointed at the token, not
deleted. Screenshot at the old widths (900, 600, 2200) to prove equivalence.
## Watch for
`rules/styles.css` shares `--accent` and `--deep` with `styles.css` — it is on
the *first* palette. Confirm task 02 canonicalized it the same way.
## Done when
- [ ] Snapshot diff empty; bilingual toggle works, `<html lang>` follows
- [ ] Responsive behaviour identical at 600/900/2200
- [ ] No external dependency; checklist complete; `npm run gate` green
@@ -0,0 +1,62 @@
# Task 15 — Full guide
**Agent**: `page-migrator` · **Model**: **Codex** — hardest task in the plan
**Depends on**: 05, 10, 13 · **Parallel with**: 16
**Worktree**: `.agents/scripts/worktree.sh start 15 page-full-guide`
## Goal
`/full-guide/` → Astro. 22 KB of HTML, a 50 KB script, 30 KB of CSS, twelve
render functions, bilingual throughout.
## What `app.js` actually is
Not application code — a **bilingual content database** (task 05 already moved
it) plus ~12 `render*` functions that swap `innerHTML` on tab clicks. Once
content is a collection, the remaining JS is small: tab state and a language
toggle.
## Islands
Only these hydrate. Everything else is server-rendered.
| Island | Directive | Why |
| --- | --- | --- |
| Phase tabs | `client:visible` | click-driven panel swap |
| Tree / worker / route / model / effort / skill selectors | `client:visible` | same pattern; consider one generic selector island |
| Language toggle | `client:idle` | page-wide, not urgent |
| Copy-prompt buttons | `client:visible` | clipboard |
If you end up with 12 separate islands you have missed the pattern — they are
one selector component with different data.
## Asserted by verify.mjs — all must survive
`const phases`, `const handsOnPrompts`, `const modelGuide`, `const skillSources`,
`const skillInstallPrompts`, `render('plan')`, `renderTree`, `renderWorker`,
`renderRoute`, `renderModelProvider`, `renderEffort`, `renderSkillFile`,
`renderSkillWorkflow`, `renderCommonSkill`, `renderHandsOn`, `copyPrompt`.
These are **implementation-detail assertions** — they look deletable and are
not. Each pins a feature. Coordinate with task 19 to replace each with an
output-level assertion of the same behaviour. **Never delete one yourself.**
Also: `data-copy-target="prompt-install-skills|prompt-basic|prompt-skills"`,
`hands-on/starter/`, `additional-reading.md`, `role="tablist"`, `<table>`.
## Watch for
- `copyPrompt` uses `navigator.clipboard` with a `document.execCommand`
fallback. Keep both — the fallback exists for non-secure contexts.
- The hands-on prompt strings are copy-pasted by attendees into an agent.
Exact whitespace and line breaks matter.
- `responsive.css` (30 KB) mostly serves this page. Port what is needed, prove
the rest dead, delete it. Screenshots are the proof.
## Done when
- [ ] Snapshot diff empty
- [ ] Every interaction works: all tabs, both languages, all copy buttons
- [ ] Keyboard: arrow keys move between tabs; focus visible throughout
- [ ] JS payload **smaller** than today's 50 KB (content is now static)
- [ ] Screenshots match at four widths; checklist complete; `npm run gate` green
@@ -0,0 +1,63 @@
# Task 16 — Review desk
**Agent**: `page-migrator` · **Model**: **Codex** — highest defect risk
**Depends on**: 06, 11, 13 · **Parallel with**: 15
**Worktree**: `.agents/scripts/worktree.sh start 16 page-review-desk`
## Goal
`/skills-review/` → Astro. The most interactive page: search, filtering,
lazy file fetching, markdown rendering, a client-side diff view, six URL params,
and a live API call to `vote-service/`.
## The six query params are a public contract
`?author=`, `?skill=`, `?view=`, `?file=`, `?compare=`, `?render=` — documented
in the page footer and shared externally. All six must round-trip, and browser
back/forward must restore state (`syncUrl` / `selectFromUrl` today).
**Test every one manually.** A snapshot diff cannot catch a broken deep link.
## Islands
The catalog + detail pane is genuinely interactive: `client:load` is justified
here. The vote widget is `client:visible`. Everything else server-renders.
## vote-service integration
`window.SKILLS_REVIEW_VOTE_API = 'https://ai-for-dummies-vote.marcospaulo.dev.br'`
is set inline in `index.html` today. Either keep the global or move it to
`PUBLIC_VOTE_API` — **if you move it, update `vote-service/README.md` in the
same change**, since it documents the coupling.
`ALLOWED_ORIGIN` on the deployed service is
`https://netcracker.pages.marcospaulo.dev.br`. If the site's origin changes, the
vote API breaks with a CORS error. It does not change in this plan — but verify
after cutover.
## Asserted by verify.mjs
Interaction tokens: `from './catalog.js'`, `from './files.js'`, `renderList`,
`renderDetail`, `selectSkill`, `packageSummary`, `markdownHeadings`,
`markdownToc`, `loadSelectedFile`, `schedulePackageSearch`, `fetchSource`,
`packageSearchText`, `diffMarkup`, `diffRows`, `searchParams.set('compare')`,
`markdownMarkup`, `syncUrl`, `selectFromUrl`, `URLSearchParams`,
`navigator.clipboard`, `document.execCommand`.
Plus the catalog count: `id:'` occurrences across both catalogs **must equal 24**.
Same rule as task 15 — re-point with task 19, never delete.
## Watch for
The markdown renderer is hand-rolled (`markdownMarkup`, `markdownHeadings`,
`markdownToc`). Task 06 moves rendering to build time — but the **diff view
needs raw source text**, not rendered HTML. Keep both available.
## Done when
- [ ] All six query params round-trip; back/forward restores state
- [ ] Search, filter, file tabs, preview, change lens, download, copy all work
- [ ] Vote widget reaches the live API; CORS preflight succeeds
- [ ] Snapshot diff empty; screenshots match; checklist complete
- [ ] `npm run gate` green
+38
View File
@@ -0,0 +1,38 @@
# Task 17 — hands-on passthrough
**Agent**: `astro-architect` · **Model**: MiniMax-M3
**Depends on**: 01 · **Parallel with**: 12, 13, 14
**Worktree**: `.agents/scripts/worktree.sh start 17 hands-on`
## Goal
`hands-on/starter/` and `hands-on/rules/` ship **byte-identical** from
`public/`, unprocessed.
## Why this is a task and not a footnote
These are **lab fixtures**. The workshop exercise is that an attendee points an
agent at dependency-free HTML/CSS/JS and watches it work. Componentizing them,
minifying them, or letting a bundler touch them destroys the lesson — and the
prompts in `app.js` reference these files by path and content.
The risk is an agent "helpfully" improving them. This task exists to say
explicitly: do not.
## Steps
1. Move both directories to `public/hands-on/`.
2. Confirm Astro copies `public/` verbatim with no processing.
3. Confirm URLs `/ai-for-dummies/hands-on/starter/` and `.../rules/` resolve.
4. Confirm every lint/format config **ignores** them (the templates already do).
## Done when
- [ ] `diff -r` between the original directories and `dist/hands-on/` is empty
- [ ] Both URLs serve; both labs work standalone
- [ ] The `hands-on/starter/` reference in the full guide still resolves
- [ ] `verify.mjs`'s `starterHtml` / `starterJs` assertions still pass
## Do not
Do not reformat, lint, componentize, or "modernise" a single line.
+47
View File
@@ -0,0 +1,47 @@
# Task 18 — Motion pass
**Agent**: `motion-designer` · **Model**: Gemini (perceptual judgement)
**Depends on**: 15, 16 · **Parallel with**: 19
**Worktree**: `.agents/scripts/worktree.sh start 18 motion`
## Goal
Audit the motion that exists, add only motion that earns its place, and make
`prefers-reduced-motion` correct everywhere.
## Order of work — audit first
The audit is worth more than the additions.
1. Inventory every `transition`, `animation`, `@keyframes`, `transform` in `src/`.
2. Flag anything animating a layout property (`width`, `height`, `top`, `left`,
`margin`). Those force reflow every frame and fail the 200ms INP budget. The
Stylelint config already blocks new ones; find the ported ones.
3. Confirm every animated element has a reduced-motion path, then **test it**
under DevTools emulation. Reduced means reduced, not broken — end states must
still be correct.
## Then, sparingly
Candidates, each needing a written purpose:
- tab panel change in the guide (state change — justified)
- review-desk detail swap on selection (state change — justified)
- vote widget tally update (feedback — justified)
`transform` and `opacity` only. 150250ms. `cubic-bezier(.2,0,0,1)`. One thing
moves at a time — no staggered card cascades; this design has a point of view
and staggering reads as a template.
## View transitions — optional, decide explicitly
Astro's `<ClientRouter />` is the only sanctioned motion dependency. If adopted,
verify: JS-disabled navigation, browser back/forward, the review desk's six
query params, and reduced motion. If any fails, do not ship it.
## Done when
- [ ] Zero animations on layout properties
- [ ] Every animation has a one-line written purpose
- [ ] Reduced-motion tested under emulation; end states correct
- [ ] Before/after captures attached; `npm run gate` green
@@ -0,0 +1,44 @@
# Task 19 — Re-point the verification contract
**Agent**: `verification-engineer` · **Model**: Codex
**Depends on**: 15, 16 · **Parallel with**: 18
**Worktree**: `.agents/scripts/worktree.sh start 19 verify-repoint`
## Goal
All 42 assertions pin the same user-visible facts against the new architecture.
Coverage does not fall.
You are the only role permitted to remove an assertion, and every removal needs
a written reason.
## The three kinds
| Kind | Example | What to do |
| --- | --- | --- |
| Content presence | `data-phase="plan"` | re-point at `dist/full-guide/index.html`; the token should survive rendering. If it does not, a component dropped content — **stop and report** |
| Implementation detail | `const phases`, `renderTree`, `from './catalog.js'` | obsolete as written, but each pins a **feature**. Replace with an output-level assertion of that feature. Never drop |
| Asset version | `app.js?v=20260904-vote-widget` | Astro hashes assets — assert the built HTML references a hashed asset |
## Steps
1. `npm run build`, then re-point `read()` calls at `dist/`.
2. Work through all 42 in order. For each: does the fact it pins still exist?
Yes → re-point. No → content was lost; escalate.
3. Add rendered-text snapshot assertions for all ten routes so this class of
regression is caught structurally, not by string luck.
4. Confirm `check-tokens.mjs` and the extended `audit-ui.mjs` are in
`npm run verify`.
## Done when
- [ ] `grep -c 'throw new Error' scripts/verify.mjs` ≥ the `origin/main` baseline
- [ ] Every removal has a one-line reason in this file
- [ ] Snapshot assertions cover all ten routes
- [ ] `npm run gate` green, and it **fails** when you deliberately delete a
paragraph from a component (prove the net works, then revert)
## Do not
Do not weaken an assertion to make it pass. If it cannot pass, something is
broken — that is the assertion doing its job.
+58
View File
@@ -0,0 +1,58 @@
# Task 20 — Cutover and cleanup
**Agent**: `astro-architect`, **with a human watching** · **Model**: MiniMax-M3 assisting
**Depends on**: all · **Worktree**: `.agents/scripts/worktree.sh start 20 cutover`
This task touches production publishing. Do not run it unattended.
## Goal
The Astro build is what the world sees, the old files are gone, and the docs
describe reality.
## Steps
1. **Full verification** on the built site: all 10 routes, all 6 query params,
both languages, vote widget against the live API, screenshots at four widths.
2. **Delete the superseded files** — only after their replacements are proven:
`app.js`, `styles.css`, `responsive.css`, `chapters.css`, `landing.css`,
`rules/app.js`, `rules/styles.css`, `skills/app.js`, `skills/styles.css`,
`skills-review/*.js`, `skills-review/*.css`, and the ten old `index.html`
files. `git rm`, one commit, reviewable.
**Keep**: `hands-on/**` (now under `public/`), `submitted-skills/**`,
`skill-reviews/**`, `docs/**`, `vote-service/**`.
3. **Publish** via the mechanism chosen in task 01.
4. **Verify on the real host** with a cache-buster:
```bash
for r in "" full-guide summary models agents skills rules skills-review \
hands-on/starter hands-on/rules; do
curl -sS -o /dev/null -w "%{http_code} $r\n" \
"https://netcracker.pages.marcospaulo.dev.br/ai-for-dummies/$r/?v=$(git rev-parse --short HEAD)"
done
```
All ten must be 200. A stale cached 200 looks identical to success — the
`?v=` is what distinguishes them.
5. **Update the docs**: `README.md`, `docs/operations-guide.md` (build + publish
path, and it must stop claiming `pages` is "the exact published source" if
that is no longer true), `GATES.md`, and `AGENTS.md` (stack is no longer
"migration in progress").
6. **Fast-forward `pages`** per the procedure in `docs/operations-guide.md`.
## Rollback
`pages` still holds the working vanilla site until you overwrite it. If cutover
fails, reset `pages` to its previous commit — the old site returns immediately.
Note the SHA before you start:
```bash
git rev-parse origin/pages # write it down
```
## Done when
- [ ] Ten routes 200 on the real host with a fresh cache-buster
- [ ] Vote widget works end-to-end from the published origin (CORS is
origin-sensitive — `ALLOWED_ORIGIN` must still match)
- [ ] Old files deleted; `npm run gate` green
- [ ] Docs match reality
- [ ] Previous `pages` SHA recorded for rollback