@lemoncode/lemony 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/README.md +17 -16
  2. package/catalog/VERSION +1 -1
  3. package/catalog/agents/implementer.md +36 -1
  4. package/catalog/agents/orchestrator.md +206 -83
  5. package/catalog/agents/reviewer.md +8 -3
  6. package/catalog/agents/spinoff.md +3 -2
  7. package/catalog/agents/ui-design.md +3 -1
  8. package/catalog/agents/ui-designer.md +6 -3
  9. package/catalog/commands/pause.md +50 -5
  10. package/catalog/commands/resume.md +13 -2
  11. package/catalog/commands/spinoff.md +5 -3
  12. package/catalog/commands/sync-design-tokens.md +6 -2
  13. package/catalog/harness.config.schema.json +4 -0
  14. package/catalog/hooks/init.sh +80 -7
  15. package/catalog/hooks/lib/live-branch.sh +37 -0
  16. package/catalog/hooks/lib/merge-pr.sh +17 -6
  17. package/catalog/hooks/lib/playbook-scan.sh +4 -2
  18. package/catalog/hooks/session-close.sh +26 -7
  19. package/catalog/schemas/tier2-events-history.md +33 -2
  20. package/catalog/schemas/tier2-events.md +30 -22
  21. package/catalog/skills/build-ui/SKILL.md +5 -2
  22. package/catalog/skills/design-tool-sync/SKILL.md +48 -7
  23. package/catalog/skills/grill-ui/SKILL.md +4 -1
  24. package/catalog/skills/grill-ui/ui-handoff-format.md +2 -1
  25. package/catalog/skills/mutation-testing/SKILL.md +22 -8
  26. package/catalog/skills/prd-to-spec/SKILL.md +27 -5
  27. package/catalog/skills/review-pr/SKILL.md +7 -1
  28. package/catalog/skills/review-pr/reference.md +3 -2
  29. package/catalog/templates/claude-code/agents.md.tpl +2 -1
  30. package/catalog/templates/claude-code/harness.config.yml.tpl +12 -3
  31. package/dist/cli.mjs +2579 -775
  32. package/package.json +11 -10
@@ -36,14 +36,14 @@ Every event line starts with this envelope. Per-type fields are added at the
36
36
  same top level — there is no nested `payload`, so Zod discriminated unions key on
37
37
  `type`.
38
38
 
39
- | Field | Type | Required | Axis | Notes |
40
- | ----------------- | ------ | -------- | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
41
- | `type` | string | yes | `internal-enum` | One of the 9 event types listed below. Discriminator. |
42
- | `ts` | string | yes | `metric` | UTC ISO 8601 with `Z` suffix (e.g. `2026-05-28T14:30:00.000Z`). **No local offsets.** |
43
- | `user` | string | yes | `local-only` | `git config user.email` of the actor. Never exported in any tier. |
44
- | `project` | string | yes | `identity` | `task_storage.repo` slug (e.g. `acme/widgets`), from `harness.config.yml`. **Never `OWNER/REPO`** — the CLI refuses to emit while that placeholder is the value (see [Placeholder guard](#placeholder-guard)). |
45
- | `task_id` | string | no | `identity` | Task issue id (e.g. `42`) when the event has a task context. Absent on global events; `session_closed` carries it when HEAD is a `harness/<id>-<slug>` task branch. A per-project correlator — only meaningful alongside `project`, so it shares the `identity` axis. |
46
- | `harness_version` | string | yes | `metric` | `version` of the **installed** `@lemoncode/lemony` package — _not_ `vendor_version` from config. |
39
+ | Field | Type | Required | Axis | Notes |
40
+ | ----------------- | ------ | -------- | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
41
+ | `type` | string | yes | `internal-enum` | One of the 9 event types listed below. Discriminator. |
42
+ | `ts` | string | yes | `metric` | UTC ISO 8601 with `Z` suffix (e.g. `2026-05-28T14:30:00.000Z`). **No local offsets.** |
43
+ | `user` | string | yes | `local-only` | `git config user.email` of the actor. Never exported in any tier. |
44
+ | `project` | string | yes | `identity` | `task_storage.repo` slug (e.g. `acme/widgets`), from `harness.config.yml`. **Never `OWNER/REPO`** — the CLI refuses to emit while that placeholder is the value (see [Placeholder guard](#placeholder-guard)). |
45
+ | `task_id` | string | no | `identity` | Task issue id (e.g. `42`) when the event has a task context. Absent on global events; `session_closed` carries it when the live branch is a `harness/<id>-<slug>` or `harness/closeout-<id>` task branch. A per-project correlator — only meaningful alongside `project`, so it shares the `identity` axis. |
46
+ | `harness_version` | string | yes | `metric` | `version` of the **installed** `@lemoncode/lemony` package — _not_ `vendor_version` from config. |
47
47
 
48
48
  ### Placeholder guard
49
49
 
@@ -115,8 +115,9 @@ forward-compatible — readers dispatch on `type` and ignore unknowns.
115
115
 
116
116
  Emitted by `session-close.sh` on `SessionEnd` or `/pause` (manual). One per
117
117
  session. The envelope's `task_id` is derived from the live branch at close time
118
- (`harness/<id>-<slug>` → `<id>`); a session closed on the default branch or a
119
- detached HEAD carries none.
118
+ (`harness/<id>-<slug>` or `harness/closeout-<id>` → `<id>`) — while a rebase has
119
+ HEAD detached, the branch being rebased; a session closed on the default branch or
120
+ any other detached HEAD carries none.
120
121
 
121
122
  | Field | Type | Required | Axis | Notes |
122
123
  | ------------------ | ------- | -------- | --------------- | --------------------------------------------------------------------------------------------------------------- |
@@ -148,17 +149,18 @@ Emitted by the Orchestrator when it transitions `spec-in-progress → spec-ready
148
149
 
149
150
  ### 4. `task_done` _(P5)_
150
151
 
151
- Emitted by the Orchestrator at closeout (after `gh pr view` confirms `MERGED`,
152
- before `git rm` of the task state).
152
+ Emitted by the Orchestrator at closeout **finalize** — after the closeout PR,
153
+ which archives the spec and drops the task's `progress.md`, has merged.
153
154
 
154
- | Field | Type | Required | Axis | Notes |
155
- | ------------------- | ------ | -------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
156
- | `task_id` | string | yes | `identity` | Required for this type. |
157
- | `level` | string | yes | `internal-enum` | `L1` \| `L2` \| `L3` — the task-fit dial value used. |
158
- | `cycle_time_h` | number | yes | `metric` | Wall-clock hours from issue creation to merge. ≥ 0, finite. |
159
- | `review_rejections` | number | yes | `metric` | Count of `review_rejected` events for this `task_id` (≥ 0, int). |
160
- | `mode` | string | no | `internal-enum` | `all_at_once` \| `step_by_step` — the mode chosen at the L1 approval gate. **Absent on L2** (the question only exists where `tasks.md` does). |
161
- | `steps` | number | no | `metric` | Count of `step_completed` events for this task (≥ 1, int). Only meaningful when `mode` is `step_by_step`; < total groups after a mid-task downgrade. |
155
+ | Field | Type | Required | Axis | Notes |
156
+ | ------------------- | ------ | -------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
157
+ | `task_id` | string | yes | `identity` | Required for this type. |
158
+ | `level` | string | yes | `internal-enum` | `L1` \| `L2` \| `L3` — the task-fit dial value used. |
159
+ | `cycle_time_h` | number | yes | `metric` | Wall-clock hours from issue creation to merge. ≥ 0, finite. |
160
+ | `review_rejections` | number | yes | `metric` | Count of `review_rejected` events for this `task_id` (≥ 0, int). |
161
+ | `mode` | string | no | `internal-enum` | `all_at_once` \| `step_by_step` — the mode chosen at the L1 approval gate. **Absent on L2** (the question only exists where `tasks.md` does), and on an L1 task whose gate choice closeout could not recover. |
162
+ | `steps` | number | no | `metric` | Count of **distinct steps** (distinct `step` values) across this task's `step_completed` events (≥ 1, int). Only meaningful when `mode` is `step_by_step`; < total groups after a mid-task downgrade. Before 0.6.0 it counted every event — see [history](tier2-events-history.md). |
163
+ | `checkpoints` | number | no | `metric` | Count of `step_completed` events for this task (≥ 1, int) — one per resolved human checkpoint, so a step sent back counts again. Sent together with `steps`; `checkpoints − steps` is the task's extra checkpoint rounds — normally its `changes` answers at step checkpoints. Not counted: any human gate after a downgrade to all-at-once (it emits nothing), and change requests at the merge gate. |
162
164
 
163
165
  ### 5. `review_rejected` _(P5)_
164
166
 
@@ -211,7 +213,9 @@ post-merge / production signal; conflating them would dirty the post-merge metri
211
213
  ### 9. `step_completed` _(step-by-step mode)_
212
214
 
213
215
  Emitted by the Orchestrator each time a human checkpoint **resolves** in
214
- step-by-step mode (L1 opt-in, chosen at the approval gate). One event per
216
+ step-by-step mode (L1 opt-in, chosen at the approval gate), through the
217
+ `checkpoint` verb that also makes the resolution's commits — or by hand,
218
+ late, when that call never ran for an answer. One event per
215
219
  checkpoint, not per step: a step the human sends back ("changes") emits again
216
220
  when it re-checkpoints, with the same `step`. This is the signal that justifies
217
221
  (or condemns) the mode — the rate of checkpoints that catch things, and where
@@ -272,7 +276,11 @@ A writer (the `lemony emit` CLI):
272
276
  2. Merges per-type fields into the same top level (no nested `payload`).
273
277
  3. Validates against the Zod schema for `type` (each schema is `.strict()`,
274
278
  so an unknown key — typically a typo'd `--task-iid` flag — **rejects**
275
- loud). Never writes a partial line.
279
+ loud). `task_done` also rejects step counts that contradict each other:
280
+ `steps`/`checkpoints` on a task whose `mode` is not `step_by_step`,
281
+ `checkpoints` without `steps`, and `checkpoints < steps`. These rules run at
282
+ emit time only — readers do not re-check lines already written. Never writes
283
+ a partial line.
276
284
  4. Appends the JSON line via `fs.appendFile` (single `O_APPEND` `write(2)`,
277
285
  POSIX-atomic up to `PIPE_BUF`) to `.claude/state/events.jsonl`. Creates the
278
286
  file (and parent dir, scaffolded by `install`) when missing.
@@ -10,7 +10,9 @@ invoked-by: [implementer]
10
10
  # Build UI
11
11
 
12
12
  The implementer's **build method**: turn a `ui-handoff.md` (the design contract) plus
13
- `docs/design-tokens.json` (the token source of truth) into UI code that applies the
13
+ `docs/design-tokens.json` (the token source of truth — or the path `design_tokens.file` names
14
+ in `harness.config.yml`, when set; read that file wherever this skill says
15
+ `docs/design-tokens.json`) into UI code that applies the
14
16
  project's tokens correctly, carries the design's point of view instead of generic
15
17
  defaults, and is accessible by construction.
16
18
 
@@ -44,7 +46,8 @@ v3→v4 jump turns it into a lie); the live model does not. So this skill names
44
46
 
45
47
  ## Tokens-as-code — the contract
46
48
 
47
- `docs/design-tokens.json` is the **single, client-owned source of truth**, a 3-tier
49
+ `docs/design-tokens.json` is the **single, client-owned source of truth** (or the path
50
+ `harness.config.yml` names in `design_tokens.file`, when set — read that file instead), a 3-tier
48
51
  W3C-DTCG model: **primitive** (raw scale values) → **semantic** (intent: `color.surface`,
49
52
  `space.inset.md`) → **component** (a component's specific slots). Two rules hold on every
50
53
  stack:
@@ -20,6 +20,11 @@ ahead. That asymmetry shapes everything here:
20
20
  - **drift** is one-directional and deterministic: "has the JSON changed since the last
21
21
  export?" — a pure-JSON check that needs no tool connection.
22
22
 
23
+ **Which file.** `docs/design-tokens.json` is the default path. When `harness.config.yml` sets
24
+ `design_tokens.file`, the path it names (relative to the repository root) is the token file:
25
+ read the binding from it and expect the CLI's import and export to act on it, wherever this
26
+ skill says `docs/design-tokens.json`. Never create a second file at the default.
27
+
23
28
  You are invoked two ways: by `/sync-design-tokens` (the human's explicit handle, argument
24
29
  `import` or `export`), and at DEFINE when the drift check shows an export is pending and the
25
30
  current user has the tool connected.
@@ -70,34 +75,70 @@ variables and the DTCG JSON:
70
75
  - `name` — the variable's dotted path. Map it to/from the tool's own grouping separator.
71
76
  - `value` for a literal; `ref` for an alias (the name of the variable it points at).
72
77
  - `modes` — per-theme overrides (the base theme is `value`/`ref`, not repeated).
78
+ - Every `value` and mode value is a **string** — `"1024"`, not `1024` — and every `ref` a
79
+ non-empty one; each variable carries a `value` or a `ref` (a `value` beside a `ref` is
80
+ ignored), and `modes` is an object whose mode names are not blank nor `__proto__`. A name
81
+ is dot-separated segments, none empty, none starting with `$`, none `__proto__`, not just
82
+ a tier (`primitive`, `semantic`, `component`), and no two variables share one. The CLI
83
+ refuses a file that breaks any of these, naming the variable (and the mode, for a mode
84
+ value).
73
85
 
74
86
  The CLI maps this to the 3-tier DTCG model: `ref` → an alias `{path}`, `modes` →
75
- `$extensions["com.lemony.modes"]`, and the tier follows the path (a literal defaults to
76
- `primitive`, an alias to `semantic` when the name carries no tier).
87
+ `$extensions["com.lemony.modes"]`, and the tier follows the path. A name without a tier
88
+ lands on the one tier `docs/design-tokens.json` already has it in; otherwise a variable that
89
+ is literal in every mode defaults to `primitive`, and one that is an alias in its base value
90
+ or any mode to `semantic` (a primitive is literal in every mode). A bare `ref` — or a bare
91
+ `{alias}` mode value — resolves to the tiered path its target lands at in the same neutral
92
+ file, or to the one tier the token file has it in.
77
93
 
78
94
  ## import — tool → JSON
79
95
 
80
96
  1. **Read** the tool's variables over MCP and write them to a neutral file (a temp path).
81
97
  2. **Preview** the diff (writes nothing):
82
98
  `lemony design-tokens import --from=<neutral-file>`. It prints, per token, the target tier
83
- and `new` / `changed` (with the old → new value) / `unchanged`.
99
+ and `new` / `changed` (each thing that changes, old → new: the value, the `$type`, each
100
+ mode) / `unchanged`. When
101
+ `docs/design-tokens.json` already fails validation it adds a `Warning:` saying so: the
102
+ apply will refuse any slice that leaves it failing, so tell the human before curating.
84
103
  3. **Present and curate.** Walk the human through it: which new tokens to take, which changes
85
104
  to accept, and — where a tool-origin name is ambiguous — whether a literal belongs in
86
105
  `primitive` or `semantic`. The human owns the slice.
87
106
  4. **Apply** the agreed slice:
88
107
  `lemony design-tokens import --from=<neutral-file> --apply --only=<dotted,paths>`.
89
108
  This does the additive merge into `docs/design-tokens.json` deterministically (it
90
- bootstraps the file if it does not exist yet). Then run `lemony design-tokens validate` to
91
- confirm the result is well-formed.
109
+ bootstraps the file if it does not exist yet and the slice writes something). A changed
110
+ token keeps what the neutral file does not carry — `$description`, a contrast pairing,
111
+ other extensions — and an unchanged one is left as it is. A token the tool cannot hold
112
+ (see export) is never written over: the preview shows it as a change from
113
+ `(outside the sync: <why>)`, and the apply lists it as not applied. It writes nothing
114
+ when the merged file would fail validation — typically a `semantic` alias whose
115
+ `primitive` target is neither in the file nor in the slice: add the target to the same
116
+ `--only`, or fix the file first. When the neutral file does not carry the target at
117
+ all, the refusal says so: create it in the tool first, or leave out what references
118
+ it. A fault no slice fixes, such as a `primitive` that aliases, is named the same way:
119
+ fix that variable in the tool, or leave it out. An `--only` path no variable lands at
120
+ is refused rather than skipped. A refusal prints the preview again, so the slice can be
121
+ re-curated from the paths it lists. Then run `lemony design-tokens validate` to confirm
122
+ the result is well-formed.
92
123
 
93
124
  ## export — JSON → tool
94
125
 
95
- 1. **Plan.** Read the tool's current variables over MCP into a neutral file, then:
126
+ 1. **Plan.** `export` refuses (exit 1) a `docs/design-tokens.json` that fails
127
+ `lemony design-tokens validate` — fix the file first. Read the tool's current variables
128
+ over MCP into a neutral file, then:
96
129
  `lemony design-tokens export --tool-state=<tool-state> --out=<projection-file>`. It prints
97
130
  the additive upsert plan (how many to create, how many to update — **never any deletes**)
98
131
  and writes the projection the tool should hold to `<projection-file>`. The `--tool-state`
99
132
  is optional; without it every variable is planned as a create (the upsert is idempotent by
100
- name either way).
133
+ name either way). A tool variable named without its tier (`color.brand`) is matched to the
134
+ path `import` lands it at (`primitive.color.brand`), so when you push, write into that
135
+ existing variable rather than creating a tiered twin. A token a tool variable cannot hold
136
+ is not projected: a composite (a shadow, a typography, a cubic Bézier — an object or
137
+ array `$value`), a token whose modes the neutral format cannot carry (a mode value that
138
+ is not text, a number or a boolean, a blank or `__proto__` mode name, a modes extension
139
+ that is not an object), and any alias to one of those. The plan names each as "not
140
+ projected", with why — say so to the human. `--out` takes a file path (not a pipe, nor
141
+ the token file itself).
101
142
  2. **Confirm.** Show the plan; the human approves.
102
143
  3. **Push.** Write the projection's variables into the tool over MCP — create new ones, update
103
144
  changed ones, leave tool-only variables untouched.
@@ -75,7 +75,10 @@ the design direction it settles can inform the spec. Before the first question:
75
75
  [Persisting personas](#persisting-personas-when-absent)) — an opt-in offer on your human-facing
76
76
  surface, never an unasked write.
77
77
  - **Consume `docs/design-tokens.json` if it exists** — the single source of truth for tokens. The
78
- handoff §11 points at it; you never inline token values.
78
+ handoff §11 points at it; you never inline token values. That is the default path: when
79
+ `harness.config.yml` sets `design_tokens.file`, the path it names (relative to the repository
80
+ root) is the token file — read, scaffold, bind and point at that path wherever this skill says
81
+ `docs/design-tokens.json`, and never create a second file at the default.
79
82
  - **Read `docs/architecture.md` if it exists** — orient against the system's shape so design
80
83
  proposals don't contradict existing surfaces. Absent is fine; never push the user to create it.
81
84
 
@@ -84,7 +84,8 @@ Every section is **N/A-able** per task: mark a section N/A rather than padding i
84
84
 
85
85
  ## 11. Token reference
86
86
 
87
- Reference `docs/design-tokens.json` — the single, client-owned source of truth, a 3-tier
87
+ Reference the token file — `docs/design-tokens.json`, or the path `design_tokens.file` names in
88
+ `harness.config.yml` — the single, client-owned source of truth, a 3-tier
88
89
  W3C-DTCG model (`primitive` → `semantic` → `component`). **Never inline token values here; point at
89
90
  the file.** If the project has no token file yet, the interview offers to scaffold one (derived from
90
91
  the answers) or to connect a design tool; if the human declines both, say so and capture it as an
@@ -71,19 +71,33 @@ Without a script, each probe is yours to run. The mechanics:
71
71
  as accounted-for, is the ledger contract's rule, not this skill's — the floor asks
72
72
  for accounting, never exhaustion. One or two well-aimed probes per logic-bearing
73
73
  file usually answer the question.
74
- - **The round trip is one composite command with the revert unconditional.** Apply the
75
- mutation, run the focused test, and restore — `;`-separated or a scripted loop,
76
- **never `&&` before the revert** (a red test must still revert), and never separate
77
- edit / test / revert calls. It leaves the tree exactly as you found it:
74
+ - **The round trip is one composite command whose revert is a trap, installed before
75
+ the mutation.** Apply the mutation, run the focused test, and restore — in one
76
+ shell invocation, never separate edit / test / revert calls, and never a revert
77
+ that a red test or an interrupt can skip. The session can be stopped at any instant
78
+ (Esc stops the running command with SIGTERM), and a mutant left behind reads as
79
+ real work to whoever resumes. The `EXIT` trap restores on every way out — green,
80
+ red, or interrupted:
78
81
 
79
82
  ```bash
80
- sed -i.bak '128s/>=/>/' src/queue.ts; \
81
- npx vitest run src/queue.spec.ts; \
82
- mv src/queue.ts.bak src/queue.ts
83
+ ( trap 'mv -f src/queue.ts.bak src/queue.ts 2>/dev/null' EXIT; \
84
+ trap 'kill $! 2>/dev/null; exit 130' INT TERM HUP; \
85
+ sed -i.bak '128s/>=/>/' src/queue.ts; \
86
+ npx vitest run src/queue.spec.ts & wait $! )
83
87
  ```
84
88
 
85
89
  (Line-addressed on purpose — one targeted mutant per trip keeps the kill
86
- attributable; the `.bak` the in-place edit leaves is the revert.)
90
+ attributable; the `.bak` the in-place edit leaves is the revert. The subshell
91
+ scopes the traps to the probe; the `INT TERM HUP` trap stops the runner and turns
92
+ the signal into an ordinary exit so the `EXIT` trap runs. The test runs in the
93
+ background under `wait` because a shell runs no trap while a foreground child is
94
+ still running, and Claude Code follows Esc's SIGTERM with a SIGKILL within a
95
+ second or two: `wait` returns on the signal, so the restore lands first. The
96
+ `kill $!` matters for a Ctrl-C too — a background job in a non-interactive shell
97
+ ignores SIGINT. Only a `SIGKILL` before the trap runs or a machine crash can
98
+ still strand a mutant — `git status` after the
99
+ trip shows the tree exactly as you found it, and a leftover `.bak` is the sign
100
+ one was stranded.)
87
101
 
88
102
  A **killed** mutant (the focused test went red) is the good outcome; a **survivor**
89
103
  (still green on broken code) is the finding. Batching buys trips, never
@@ -209,7 +209,11 @@ close, is always read as the declaration: it is reported if it does not end the
209
209
  even when a list ends a later line. Keep every other `(R<n>)` mention in detail prose
210
210
  **mid-line**. When two lists end continuation lines the validator cannot tell which is
211
211
  the declaration and reports the task; on a task that declares no list, the one
212
- line-ending mention is read as its declaration, and a mid-line one is reported.
212
+ line-ending mention is read as its declaration, and a mid-line one is reported. An
213
+ aside that must open a parenthesis with a requirement id opens it with `per` —
214
+ `(per R42, …)` — because a bare `(R42…` falls under the rules above exactly like a
215
+ declaration, and can be elected over the real list, sometimes silently. A `per` aside
216
+ is never read, so it never stands in for the list: the task still declares its own.
213
217
 
214
218
  The review evidence ledger's validator enumerates each group's review slice from these
215
219
  refs; a task it cannot read is reported as `malformed-task-refs` — a spec defect that
@@ -220,14 +224,32 @@ when the task reaches green, with the task's commit. Write every task `- [ ]`; t
220
224
  validator reports a task still `[ ]` in a group the loop has passed
221
225
  (`unticked-completed-task`), so the file tracks the progress its shape promises.
222
226
 
227
+ **Line shapes — read by the same script.** A task line is `- [ ]` or `- [x]` and a bare
228
+ `T<n>` id, at the task list's own indentation; a group header is exactly `## Group <n>`,
229
+ numbered from 1, with `Group` in English even when the spec is written in another
230
+ language. A line that looks like one but is not — an in-progress `[-]`, a suffixed
231
+ `T12b` or `T1.1`, a numbered `1. [ ]` item, a `- T6` with no box, a task inside a `>`
232
+ quote, a `T<n>` sub-item indented under another task, `### Group 3`, `## Group 0`, a
233
+ digit-less `## Group P`, a translated `## Grupo 2 — … _(…)_` — is reported
234
+ (`malformed-task-line`, `malformed-group-header`), never skipped; a note bullet about a
235
+ task opens with
236
+ something other than its `T<n>`. Every `T<n>` is used once in the file
237
+ (`duplicate-task-id` otherwise). Detail prose may run to several paragraphs: a blank
238
+ line inside a task is fine while the next line stays indented at least to the task's
239
+ text (two spaces under `- [ ]`); less, and it is no longer the task's. Code fences and
240
+ `>` quotes are never read as tasks, but a task or header shape inside one is reported
241
+ too — a quoted sample looks exactly like a task under a fence left open — so quote
242
+ samples without a `T<n>` id or a group header.
243
+
223
244
  Rules: order so the first task is a tracer bullet; never "write all tests" then
224
245
  "write all code"; keep each task small enough to verify on its own. Grouping never
225
246
  changes task granularity — checkboxes stay atomic and TDD runs per task; only review
226
247
  and checkpoint frequency follow the groups (all-at-once mode ignores the headers).
227
- Tasks added mid-implementation (from a discovery) default to **their own group** —
228
- they are risk by definition, and grouping rule 5 applies to them unchanged — but they
229
- are added **after** the spec gate, and no later gate presents a group header, so their
230
- tags get no human reading at all. Tag them for the record; do not rely on anyone
248
+ Tasks added mid-implementation (from a discovery) take the **next free `T<n>`** (never
249
+ a suffixed `T12b`) and default to **their own group** — they are risk by definition,
250
+ and grouping rule 5 applies to them unchanged — but they are added **after** the spec
251
+ gate, and no later gate presents a group header, so their tags get no human reading at
252
+ all. Tag them for the record; do not rely on anyone
231
253
  catching a wrong one.
232
254
 
233
255
  ### 5. Self-check before handing off
@@ -37,7 +37,7 @@ Read the repo's `CLAUDE.md` (and `CONTEXT.md` if present). Summarize in ≤ 50 l
37
37
  code-style rules, comment conventions, naming, architecture constraints. This is what
38
38
  the analysis judges "against project convention" by.
39
39
 
40
- ### 2. Analyze (single sub-agent, the review lenses as a checklist)
40
+ ### 2. Analyze (one pass — a single sub-agent, or inline — the review lenses as a checklist)
41
41
 
42
42
  Spawn **one** `Explore` sub-agent with the full diff, the changed-files list, and the
43
43
  project context. Its prompt walks the five review lenses as a checklist — **not** five
@@ -58,6 +58,12 @@ The agent returns a strict JSON array of findings (see [reference.md](reference.
58
58
  the exact prompt and schema). It returns `[]` when the diff is clean — it must **not**
59
59
  manufacture findings to seem thorough.
60
60
 
61
+ **A Reviewer, or no `Agent` tool?** A Reviewer never spawns, whatever its tools (the
62
+ opening rule of the Reviewer contract, `.claude/agents/reviewer.md`), and an agent at
63
+ Claude Code's nesting depth limit has no `Agent` tool unless it is a fork. Then walk
64
+ the five lenses yourself, in this context, with the same prompt as your checklist, and
65
+ produce the same JSON array; the rest of the flow is unchanged.
66
+
61
67
  ### 3. Dry-run
62
68
 
63
69
  Print the findings as a numbered table (don't post anything yet):
@@ -2,8 +2,9 @@
2
2
 
3
3
  ## Analysis agent prompt
4
4
 
5
- Pass this to the single `Explore` sub-agent. The five lenses are a **checklist one
6
- agent walks**, not a fan-out.
5
+ Pass this to the single `Explore` sub-agent — or, as a Reviewer or with no `Agent`
6
+ tool (SKILL.md §2), walk it yourself. The five lenses are a **checklist one agent
7
+ walks**, not a fan-out.
7
8
 
8
9
  ```
9
10
  You are reviewing a GitHub PR diff. Find real issues worth posting as inline review
@@ -105,7 +105,8 @@ A task deserves the harness if it is **specifiable**, **verifiable**, or
105
105
  checks precondition (`.claude/hooks/lib/merge-pr.sh --approve-issue <issue>`)
106
106
  and never lands on red, absent, or still-pending checks without asking — nor
107
107
  content that no longer matches what the Reviewer's APPROVE reviewed (the
108
- stale-approve guard; a clean update-branch stays valid, any content change
108
+ stale-approve guard; a clean update-branch or a task-state write under
109
+ `.claude/state` stays valid, any other content change
109
110
  routes back to re-review). The task stays at `in-review`
110
111
  until merged.
111
112
  10. **Closeout** — `task-closeout`: confirm the merge via `gh`, run the three Architect
@@ -83,16 +83,25 @@ rollback:
83
83
  # - test
84
84
  # - build
85
85
 
86
- # Design tokens (`design-tokens validate` / `design-tokens contrast`).
86
+ # Design tokens (the `design-tokens` verbs, and the token lines of `doctor` / `status`).
87
87
  # `scan_extensions`: the anti-hardcode scan inspects a built-in set of UI/style
88
88
  # extensions (.css/.scss/.ts/.tsx/.vue/.svelte/.astro/.js/.mdx/.html/…). Add extra
89
89
  # suffixes here for a stack the built-ins don't cover — additive, never a replacement.
90
90
  # Default none.
91
- # `verify`: a command line `design-tokens contrast` runs after its own WCAG checks
91
+ # `verify`: a command line `design-tokens contrast` runs alongside its own WCAG checks
92
92
  # (through `sh -c`, in the repo root) — your design system's own verifier for the rules
93
93
  # only it can know (palette under colour-vision-deficiency simulation, property grammar,
94
- # CSS scans). Its output is forwarded; a non-zero exit fails the gate. Default none.
94
+ # CSS scans). Its output is forwarded; a non-zero exit fails the gate. It runs on every
95
+ # path: with no token file only the WCAG pair check is skipped, and a file
96
+ # that cannot be read or parsed is reported alongside it — never the verifier you
97
+ # declared. Default none.
98
+ # `file`: where the token file lives, relative to the repository root — for a DTCG
99
+ # file kept (or generated) somewhere other than docs/design-tokens.json. Every
100
+ # design-tokens verb, `doctor` and `status` read it from here. An absolute path, a `\`
101
+ # separator, a path that leads out of the repository or names its root, or one ending
102
+ # in `/` or `/.` is a config error. Default docs/design-tokens.json.
95
103
  # design_tokens:
96
104
  # scan_extensions:
97
105
  # - .foo
98
106
  # verify: pnpm exec my-design-system verify
107
+ # file: design/tokens.json