@lemoncode/lemony 0.1.2 → 0.3.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 (37) hide show
  1. package/README.md +19 -14
  2. package/catalog/VERSION +1 -1
  3. package/catalog/agents/architect.md +13 -4
  4. package/catalog/agents/implementer.md +87 -8
  5. package/catalog/agents/orchestrator.md +643 -386
  6. package/catalog/agents/partition.md +316 -0
  7. package/catalog/agents/reviewer.md +356 -21
  8. package/catalog/agents/spec-author.md +16 -4
  9. package/catalog/agents/spinoff.md +100 -0
  10. package/catalog/agents/triage.md +41 -0
  11. package/catalog/agents/ui-design.md +147 -0
  12. package/catalog/agents/ui-designer.md +3 -2
  13. package/catalog/commands/add-capability.md +4 -4
  14. package/catalog/commands/define.md +7 -0
  15. package/catalog/commands/hotfix.md +15 -1
  16. package/catalog/commands/pause.md +5 -0
  17. package/catalog/commands/resume.md +38 -10
  18. package/catalog/commands/triage.md +4 -3
  19. package/catalog/harness.config.schema.json +40 -0
  20. package/catalog/hooks/lib/merge-pr.sh +699 -0
  21. package/catalog/schemas/tier2-events-history.md +17 -0
  22. package/catalog/schemas/tier2-events.md +10 -10
  23. package/catalog/skills/mutation-testing/SKILL.md +80 -19
  24. package/catalog/skills/prd-to-spec/SKILL.md +74 -2
  25. package/catalog/skills/raise-discovery/SKILL.md +6 -0
  26. package/catalog/skills/resolve-discovery/SKILL.md +12 -7
  27. package/catalog/skills/security-review/SKILL.md +119 -6
  28. package/catalog/skills/spec-compliance-check/SKILL.md +8 -4
  29. package/catalog/skills/spec-to-issue/SKILL.md +7 -1
  30. package/catalog/skills/task-closeout/SKILL.md +85 -20
  31. package/catalog/skills/test-gap-report/SKILL.md +4 -0
  32. package/catalog/skills/triage-issue/SKILL.md +65 -4
  33. package/catalog/skills/verify/SKILL.md +3 -0
  34. package/catalog/templates/claude-code/agents.md.tpl +50 -16
  35. package/catalog/templates/claude-code/harness.config.yml.tpl +33 -0
  36. package/dist/cli.mjs +744 -37
  37. package/package.json +10 -6
@@ -22,12 +22,29 @@ Format per entry (one block per release):
22
22
 
23
23
  ### Renamed
24
24
  - `<event_type>.<old_field>` → `<new_field>` — <rationale>
25
+
26
+ ### Changed
27
+ - `<event_type>.<field>` — <semantics-only change: shape untouched, meaning/unit changed>
25
28
  ```
26
29
 
27
30
  Empty sections may be omitted.
28
31
 
29
32
  ---
30
33
 
34
+ ## 0.2.0 — 2026-07-31
35
+
36
+ ### Changed
37
+
38
+ - **`step` semantics: one step = one `tasks.md` group** (was: one task).
39
+ `tasks.md` now groups its atomic tasks under risk-sized step headers and the
40
+ step-by-step loop runs one implement→review→checkpoint cycle per group. **No
41
+ field renamed, added, or removed** — `step_completed.step`,
42
+ `review_rejected.step`, and `task_done.steps` keep their types; only the unit
43
+ they count changed. Readers comparing step counts across versions should
44
+ expect fewer steps per task from this version on.
45
+
46
+ ---
47
+
31
48
  ## 0.1.0-alpha.9 — 2026-06-18
32
49
 
33
50
  ### Added
@@ -149,14 +149,14 @@ Emitted by the Orchestrator when it transitions `spec-in-progress → spec-ready
149
149
  Emitted by the Orchestrator at closeout (after `gh pr view` confirms `MERGED`,
150
150
  before `git rm` of the task state).
151
151
 
152
- | Field | Type | Required | Axis | Notes |
153
- | ------------------- | ------ | -------- | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
154
- | `task_id` | string | yes | `identity` | Required for this type. |
155
- | `level` | string | yes | `internal-enum` | `L1` \| `L2` \| `L3` — the task-fit dial value used. |
156
- | `cycle_time_h` | number | yes | `metric` | Wall-clock hours from issue creation to merge. ≥ 0, finite. |
157
- | `review_rejections` | number | yes | `metric` | Count of `review_rejected` events for this `task_id` (≥ 0, int). |
158
- | `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). |
159
- | `steps` | number | no | `metric` | Count of `step_completed` events for this task (≥ 1, int). Only meaningful when `mode` is `step_by_step`; < total tasks after a mid-task downgrade. |
152
+ | Field | Type | Required | Axis | Notes |
153
+ | ------------------- | ------ | -------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
154
+ | `task_id` | string | yes | `identity` | Required for this type. |
155
+ | `level` | string | yes | `internal-enum` | `L1` \| `L2` \| `L3` — the task-fit dial value used. |
156
+ | `cycle_time_h` | number | yes | `metric` | Wall-clock hours from issue creation to merge. ≥ 0, finite. |
157
+ | `review_rejections` | number | yes | `metric` | Count of `review_rejected` events for this `task_id` (≥ 0, int). |
158
+ | `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). |
159
+ | `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. |
160
160
 
161
161
  ### 5. `review_rejected` _(P5)_
162
162
 
@@ -168,7 +168,7 @@ dedicated label).
168
168
  | `task_id` | string | yes | `identity` | Required for this type. |
169
169
  | `reason` | string | yes | `free-text` | Short human-readable reason (one line; never the full review comment). 1-500 chars. |
170
170
  | `iteration` | number | yes | `metric` | 1-based: the Nth rejection of this task (≥ 1, int). |
171
- | `step` | number | no | `metric` | The step (1-based `tasks.md` task number) whose per-step review rejected. **Absent** on full-pass and all-at-once rejections. |
171
+ | `step` | number | no | `metric` | The step (1-based; one step = one `tasks.md` group) whose per-step review rejected. **Absent** on full-pass and all-at-once rejections. |
172
172
  | `attributed_kind` | string | no | `internal-enum` | `agent` \| `skill` \| `playbook` — the kind of component the friction is attributed to. **Omitted when the emitter can't attribute.** |
173
173
  | `attributed_name` | string | no | `internal-enum` | The component's name (free string, 1-200 chars), e.g. `implementer`. Independently optional in the schema; emitters pair it with `attributed_kind` and omit both when they can't attribute. Free-string by design — see [Attribution](#attribution). |
174
174
 
@@ -218,7 +218,7 @@ humans bail out (`ok_downgrade`).
218
218
  | Field | Type | Required | Axis | Notes |
219
219
  | ------------------- | ------ | -------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
220
220
  | `task_id` | string | yes | `identity` | Required for this type. |
221
- | `step` | number | yes | `metric` | 1-based `tasks.md` task number the checkpoint belongs to (≥ 1, int). |
221
+ | `step` | number | yes | `metric` | 1-based step number the checkpoint belongs to (one step = one `tasks.md` group; ≥ 1, int). |
222
222
  | `review_iterations` | number | yes | `metric` | Reviewer invocations that preceded this checkpoint (≥ 1, int — every step is reviewed before the human; resets after a "changes"). |
223
223
  | `checkpoint_result` | string | yes | `internal-enum` | `ok` \| `changes` \| `ok_downgrade` (OK and switch the remaining tasks to all-at-once). |
224
224
  | `attributed_kind` | string | no | `internal-enum` | `agent` \| `skill` \| `playbook` — the kind of component the friction is attributed to. **Omitted when the emitter can't attribute.** |
@@ -1,9 +1,8 @@
1
1
  ---
2
2
  name: mutation-testing
3
- description: Behavioral test-strength analysis of a change — mutate the changed source and check whether the suite actually fails. Surviving mutants mark tests that pass regardless of behavior. Advisory, diff-scoped, and capability-gated (only lands when the project declares a `test:mutation` script). Distinct from `test-gap-report` (structural — which files lack a spec) and `verify` (which just runs the suite).
3
+ description: Behavioral test-strength analysis of a change — mutate the changed source and check whether the suite actually fails. Surviving mutants mark tests that pass regardless of behavior. Advisory and diff-scoped; a declared `test:mutation` script selects the cheap scripted path, its absence the hand-probe path. Distinct from `test-gap-report` (structural — which files lack a spec) and `verify` (which just runs the suite).
4
4
  origin: vendor
5
5
  vendor_version: '{{vendor_version}}'
6
- applies-when: [has-mutation-testing]
7
6
  phase: post-implementation
8
7
  invoked-by: [reviewer]
9
8
  ---
@@ -14,7 +13,7 @@ The **behavioral** counterpart to `test-gap-report`'s structural analysis. A str
14
13
  gap report asks "which changed files lack a dedicated spec?"; mutation testing asks the
15
14
  harder question: **do the tests that exist actually fail when the code misbehaves?** It
16
15
  mutates the source (flips `>` to `>=`, drops a line, swaps `&&`/`||`) and re-runs the
17
- suite. A mutant that **survives** — the tests still pass on the broken code — marks a
16
+ tests. A mutant that **survives** — the tests still pass on the broken code — marks a
18
17
  test that asserts nothing load-bearing. Line coverage can't catch this; only mutation
19
18
  can.
20
19
 
@@ -23,17 +22,31 @@ output is noisy — equivalent mutants are real and unavoidable), and it only mu
23
22
  code the change touched, because mutating the whole tree per review is prohibitively
24
23
  slow.
25
24
 
26
- ## Precondition (capability-gated)
25
+ ## Two paths, chosen at runtime
27
26
 
28
- This skill is only installed when the project declares a **`test:mutation`** script in
29
- `package.json` (the `has-mutation-testing` capability). If you are reading it, the
30
- script exists — run it. The harness stays tool-agnostic: the script may drive Stryker or
31
- any mutation tool. Delegate to it exactly as `verify` delegates to the project's
32
- declared scripts; never assume a specific tool's CLI.
27
+ Check whether `package.json` declares a non-empty **`test:mutation`** script. That one
28
+ fact picks the path:
33
29
 
34
- ## Process
30
+ - **Declared** → the **scripted path**: one batch run of the project's own script,
31
+ diff-scoped. The harness stays tool-agnostic — the script may drive Stryker or any
32
+ mutation tool. Delegate to it exactly as `verify` delegates to the project's declared
33
+ scripts; never assume a specific tool's CLI.
34
+ - **Absent** → the **hand-probe path**: you mutate the source yourself, one composite
35
+ trip per mutant, and revert every time.
35
36
 
36
- ### 1. Run the project's mutation script, scoped to the diff
37
+ The two paths do **not** apply equally, by design:
38
+
39
+ - The **scripted path runs on every review** where the script exists — it is a cheap
40
+ batch, and its advisory output also catches weak tests in changes that declared no
41
+ risk.
42
+ - The **hand-probe path is never a per-review mandate.** What you owe by hand is the
43
+ Reviewer's evidence-ledger mutant floor (accounting for every changed non-test file
44
+ under `declared-risk` — the Reviewer contract's Evidence-ledger section owns that
45
+ rule; this skill never duplicates it), plus your own judgment when a survivor-shaped
46
+ doubt is worth one probe. Probing beyond the floor is a judgment call, never a
47
+ mandate.
48
+
49
+ ## Scripted path: run the project's mutation script, scoped to the diff
37
50
 
38
51
  Run the declared script focused on the **files this change modified** — not the whole
39
52
  tree. How to scope depends on the project's tool; read its config and adapt:
@@ -46,10 +59,47 @@ tree. How to scope depends on the project's tool; read its config and adapt:
46
59
 
47
60
  Capture the surviving mutants with their file + line.
48
61
 
49
- ### 2. Classify each surviving mutantthe two-case model
62
+ ## Hand-probe path: mutate, test, revertone composite trip per mutant
63
+
64
+ Without a script, each probe is yours to run. The mechanics:
65
+
66
+ - **Choose mutants that would expose a weak test.** Target the load-bearing logic the
67
+ change touched: flip a comparison (`>` ↔ `>=`), negate a guard, drop an early return,
68
+ swap `&&`/`||`, delete a write. A good mutant is one a real assertion should kill; a
69
+ cosmetic mutation (rename, whitespace) proves nothing.
70
+ - **How many is judgment, anchored by the floor.** What a review owes, and what counts
71
+ as accounted-for, is the ledger contract's rule, not this skill's — the floor asks
72
+ for accounting, never exhaustion. One or two well-aimed probes per logic-bearing
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:
78
+
79
+ ```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
+ ```
84
+
85
+ (Line-addressed on purpose — one targeted mutant per trip keeps the kill
86
+ attributable; the `.bak` the in-place edit leaves is the revert.)
87
+
88
+ A **killed** mutant (the focused test went red) is the good outcome; a **survivor**
89
+ (still green on broken code) is the finding. Batching buys trips, never
90
+ experiments — the probe set itself never shrinks to save calls.
91
+
92
+ - **Say so in your verdict — the nudge.** When this path ran, note in your verdict that
93
+ the project can buy the cheaper batch: declaring a `test:mutation` script in
94
+ `package.json` (driving Stryker or any mutation tool) activates the scripted path on
95
+ the next review. The human reads it at the checkpoint, exactly while the artisanal
96
+ cost is being paid. (Only when this path actually ran — never as a standing line.)
97
+
98
+ ## Classify each surviving mutant — the two-case model (both paths)
50
99
 
51
100
  A diff-scoped run mutates changed _files_, but a changed file still contains
52
- **pre-existing, untouched lines**. Where the mutant survives decides how you report it:
101
+ **pre-existing, untouched lines**. Whichever path produced the survivor, where it
102
+ survives decides how you report it:
53
103
 
54
104
  | Mutant survives on… | Nature | Channel |
55
105
  | -------------------------------------------------------- | ------------------------------------------ | ---------------------------------------------------------------------- |
@@ -60,16 +110,26 @@ Use the diff to decide which lines the change actually touched. When unsure whet
60
110
  mutated line is new or pre-existing, treat it as in-scope (verdict) — over-reporting in
61
111
  the verdict is cheaper than mis-routing a real gap to a dismissable offer.
62
112
 
63
- ### 3. Report
113
+ ## Report
64
114
 
65
115
  - **In-scope surviving mutants** → list them in your review verdict as an **advisory**
66
- block: file:line, the mutation that survived, and the assertion that would have caught
67
- it. This is **not** a REJECT on its own (decision: advisory). The Implementer may
68
- strengthen the tests; the Reviewer may still REJECT by _judgment_ if a survivor exposes
69
- a genuinely dangerous untested path but the mutation result alone never auto-blocks.
116
+ block: file:line, the mutation that survived, and the assertion that would have
117
+ caught it all of them when few (over-reporting here is cheaper than mis-routing,
118
+ per the two-case model). On a noisy run, carry the highest-value ones in the
119
+ verdict and route the full list to the full-pass issue comment; on a per-step
120
+ review — which posts no comment — anything worth keeping goes in the verdict
121
+ bullets (the Orchestrator records those in `progress.md`); what you leave out dies
122
+ with your context. This is **not** a REJECT on its own (decision: advisory). The
123
+ Implementer may strengthen the tests; the Reviewer may still REJECT by _judgment_
124
+ if a survivor exposes a genuinely dangerous untested path — but the mutation result
125
+ alone never auto-blocks.
70
126
  - **Pre-existing surviving mutants** → run **`note-side-finding`**: one `## Side-findings`
71
127
  bullet each (file:line + the gap), then keep going. The Orchestrator collects it and
72
128
  offers the human a `/spinoff`. Don't fix it, don't reject on it — it isn't this task's.
129
+ - **Hand probes under declared risk also land in the ledger.** When the review's record
130
+ carries `basis: declared-risk`, every probe you ran belongs in the sidecar's
131
+ `mutants.files` entries per the Evidence-ledger section of the Reviewer contract —
132
+ this skill points at that contract and never duplicates it.
73
133
 
74
134
  If no mutant survives on the changed lines, say so in one line — a clean diff-scoped
75
135
  mutation run is a meaningful positive signal about the new tests.
@@ -79,6 +139,7 @@ mutation run is a meaningful positive signal about the new tests.
79
139
  - **Equivalent mutants** (a mutation that can't change observable behavior, so no test
80
140
  could kill it) are expected. Don't chase them; flag a survivor only when a real
81
141
  assertion would have caught it.
82
- - Mutation testing is **slow** — keep it diff-scoped. If a single review's run is
142
+ - Mutation testing is **slow** — keep it diff-scoped, and on the hand path run the
143
+ focused spec, never the whole suite per mutant. If a single review's scripted run is
83
144
  blowing the time budget, report what completed and note the un-covered files rather
84
145
  than silently truncating.
@@ -29,7 +29,7 @@ the gap by guessing — surface it (see "When the PRD is insufficient").
29
29
  real from the start — there is no `draft-<slug>` holder:
30
30
  - `requirements.md` — EARS requirements with acceptance criteria
31
31
  - `design.md` — files, functions, approach, edge cases, testing
32
- - `tasks.md` — atomic, ordered, checkbox tasks
32
+ - `tasks.md` — atomic, ordered checkbox tasks, grouped under risk-sized step headers
33
33
 
34
34
  ## Process
35
35
 
@@ -126,16 +126,84 @@ Atomic, ordered checkboxes. Each task is a **vertical slice** (a behavior, not a
126
126
  layer) so the Implementer can run TDD red→green per task. Reference the requirements
127
127
  each task satisfies.
128
128
 
129
+ Group the tasks under **risk-sized step headers**. In step-by-step mode the loop runs
130
+ one implement→review→human-checkpoint cycle **per group**, so the grouping decides how
131
+ often the human is interrupted; the human approves it at the spec gate with the rest
132
+ of the spec. Grouping criterion:
133
+
134
+ 1. **Risk isolates — hard rule.** A task gets its own group iff it concentrates risk:
135
+ a new seam, a hard-to-reverse decision, a data-loss or security surface.
136
+ 2. **Runnable surface closes — soft rule.** Among trivial stretches, prefer group
137
+ boundaries that leave something the human can run at the checkpoint. Never invent
138
+ a group to fabricate a demo.
139
+ 3. **Soft cap: 4 tasks per group**, exceedable only with explicit justification in
140
+ the group's rationale (e.g. "5 identical mechanical renames").
141
+ 4. **Every group header carries a one-line boundary rationale** so the grouping is
142
+ auditable at the spec gate.
143
+ 5. **A group whose risk has a name carries it as a machine-readable tag.** When the
144
+ damage a group can do is one the vocabulary below names, the header ends with
145
+ `[risk: <class>]` — the **last element of the line**, after the rationale; multiple
146
+ classes are comma-separated (`[risk: data-loss, secrets]`).
147
+ The vocabulary names what kind of damage the surface can do:
148
+
149
+ <!-- risk-vocabulary:start -->
150
+
151
+ | Class | Surface |
152
+ | ----------------- | ---------------------------------------------------------------- |
153
+ | `auth` | authentication, authorization, session or token handling |
154
+ | `payments` | money movement, billing, pricing |
155
+ | `shell-process` | shell invocation, subprocess spawning, argument construction |
156
+ | `data-loss` | writes, deletes, migrations, anything that can destroy user data |
157
+ | `secrets` | credentials, keys, tokens — at rest or in transit |
158
+ | `executable-mode` | file permissions, exec bits, anything that changes what can run |
159
+
160
+ <!-- risk-vocabulary:end -->
161
+
162
+ The tag keys on **damage, not on which rule made the group**. A group that isolates
163
+ a new seam or a hard-to-reverse decision is a risk isolate under rule 1 and still
164
+ carries **no tag**, because no class names that damage — and absence is itself the
165
+ declaration ("nothing here does one of those things"), never an omission.
166
+
167
+ If a group's damage has no class in the vocabulary, **leave the tag off and name that
168
+ damage in the group's rationale** — rule 4 already requires a rationale, and damage
169
+ named in prose beats a wrong class in a tag. Then say so in your handoff summary, in
170
+ one line, so the human can decide whether the vocabulary should grow. Do **not** pick
171
+ the nearest class (the self-check below rejects it), do not invent a one-off class,
172
+ and do not edit the table here: it is vendor-owned — generated into this skill and
173
+ `triage-issue` from one source, so the two always agree — and a local edit is
174
+ discarded on the next harness update.
175
+
176
+ The tag is what the **human approves at the spec gate**: approving the spec approves
177
+ the grouping _and its tags_, so a wrong tag is a spec defect, not a review defect.
178
+ That approval is where its force starts, and a reader now depends on it: at review the
179
+ tags are handed to `security-review`, which deepens the section named for each class
180
+ instead of inferring the surface from the diff. And a script reads them: the review
181
+ evidence ledger's validator (`lemony review-ledger validate`) checks every tag on the
182
+ group under review against the vocabulary and reports one outside it as
183
+ `unknown-risk-class` — a spec defect that reaches the human, never a silent drop.
184
+
129
185
  ```markdown
130
186
  # Tasks — <topic>
131
187
 
188
+ ## Group 1 — <name> _(<one-line boundary rationale>)_
189
+
132
190
  - [ ] T1 — <smallest behavior that proves the path end-to-end> (R1)
133
191
  - [ ] T2 — <next behavior> (R2)
192
+
193
+ ## Group 2 — <name> _(<one-line boundary rationale>)_ [risk: data-loss]
194
+
134
195
  - [ ] T3 — <error path> (R2, R3)
135
196
  ```
136
197
 
137
198
  Rules: order so the first task is a tracer bullet; never "write all tests" then
138
- "write all code"; keep each task small enough to verify on its own.
199
+ "write all code"; keep each task small enough to verify on its own. Grouping never
200
+ changes task granularity — checkboxes stay atomic and TDD runs per task; only review
201
+ and checkpoint frequency follow the groups (all-at-once mode ignores the headers).
202
+ Tasks added mid-implementation (from a discovery) default to **their own group** —
203
+ they are risk by definition, and grouping rule 5 applies to them unchanged — but they
204
+ are added **after** the spec gate, and no later gate presents a group header, so their
205
+ tags get no human reading at all. Tag them for the record; do not rely on anyone
206
+ catching a wrong one.
139
207
 
140
208
  ### 5. Self-check before handing off
141
209
 
@@ -143,6 +211,10 @@ Rules: order so the first task is a tracer bullet; never "write all tests" then
143
211
  - [ ] Every requirement is EARS-formatted and testable.
144
212
  - [ ] At least one unwanted-behavior (`If … then …`) requirement exists.
145
213
  - [ ] Every requirement is covered by at least one task.
214
+ - [ ] Every task sits in a group, and every group header carries a one-line
215
+ boundary rationale.
216
+ - [ ] Every group that can do damage the vocabulary names ends its header with a
217
+ `[risk: …]` tag; no group carries a class whose damage it cannot do.
146
218
  - [ ] No closed PRD decision is contradicted.
147
219
 
148
220
  ### 6. Emit `spec_created`
@@ -32,6 +32,12 @@ human, and you do not resume yourself — the Orchestrator owns all of that (it
32
32
  | **T5** | INFEASIBILITY | The plan is not implementable as written. |
33
33
  | **T6** | PLAYBOOK_CONFLICT | Honoring the spec would contradict a client playbook. |
34
34
 
35
+ **Oversize / partition is a T2.** When the scope you are structuring or implementing
36
+ hides **≥2 independently mergeable units** the plan didn't reveal — each could leave the
37
+ default branch green and shippable without the other — the plan is silent on the cut
38
+ and both "partition" and "keep together" are valid: raise it as `T2 UNSPECIFIED_DECISION`
39
+ with the candidate cut in **Proposed**. Never partition silently from inside a task.
40
+
35
41
  ## When NOT to raise (resolve it yourself)
36
42
 
37
43
  Escalation is for decisions with architectural or scope impact, not for everything
@@ -64,13 +64,16 @@ is a second discovery — append it, don't fold it in.
64
64
 
65
65
  The agent that **created** an artifact is the one that updates it. Map the decision to
66
66
  the artifact it changes, then invoke that owner (fresh context, Task tool) with the
67
- decision + the entry as context:
67
+ decision **stated in full** — at this moment it exists nowhere on disk; the entry's
68
+ `**Resolution**` block is only written at step 4 — plus the `discoveries.md` entry
69
+ **by path** for the surrounding context (it reads the entry itself):
68
70
 
69
- | Artifact changed by the decision | Owner to invoke |
70
- | -------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
71
- | `spec/requirements.md`, `spec/design.md`, `spec/tasks.md` | **Spec Author** |
72
- | Implementation code, `progress.md`, `notes.md` | **Implementer** (often just the resumed sub-agent) |
73
- | `docs/adr/NNNN-<slug>.md`, `docs/architecture.md`, `docs/playbooks/` | **Architect** (on-demand) `write-adr` to record the decision, `update-architecture` to keep the map true, `playbook-iterate` for a `T6 PLAYBOOK_CONFLICT` |
71
+ | Artifact changed by the decision | Owner to invoke |
72
+ | --------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
73
+ | `spec/requirements.md`, `spec/design.md`, `spec/tasks.md` | **Spec Author** |
74
+ | Implementation code, `progress.md`, `notes.md` | **Implementer** (often just the resumed sub-agent) |
75
+ | A child issue's trace lines, a triage issue's fix plan, the parent partition-plan issue | **Orchestrator** (you) `gh issue edit --body-file`, read-modify-write (an oversize discovery answered "partition": `.claude/agents/partition.md`) |
76
+ | `docs/adr/NNNN-<slug>.md`, `docs/architecture.md`, `docs/playbooks/` | **Architect** (on-demand) — `write-adr` to record the decision, `update-architecture` to keep the map true, `playbook-iterate` for a `T6 PLAYBOOK_CONFLICT` |
74
77
 
75
78
  Not every resolution needs an artifact update first. If the decision is simply "do X"
76
79
  with no change to the contract, skip straight to recording it and resuming. If it
@@ -104,7 +107,9 @@ do not edit the raise fields):
104
107
  Same fail-loud contract: a non-zero exit means the flag/status did not flip back.
105
108
 
106
109
  2. Re-invoke the paused sub-agent (fresh context) with: the issue, the task-state
107
- paths, the decision, and the updated artifact. It picks up from where it stopped.
110
+ paths, a one-line gist of the decision (the full `**Resolution**` block now
111
+ lives in the `discoveries.md` entry — reference it), and the updated artifact's
112
+ **path**. It reads the files itself and picks up from where it stopped.
108
113
 
109
114
  ## Closeout invariant
110
115
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: security-review
3
- description: Security review of a change — OWASP-style vectors plus AI/LLM-specific risks. Checks input validation, authorization/IDOR, injection, secrets & logging, transport/headers, dependencies, and prompt-injection where an LLM is in the loop. Use after `senior-review` on any change touching user input, auth, data access, or an LLM.
3
+ description: Security review of a change — OWASP-style vectors plus AI/LLM-specific risks. Checks input validation, authorization/IDOR, injection, secrets & logging, transport/headers, dependencies, prompt-injection where an LLM is in the loop, shell and subprocess use, money movement, destructive writes, and executable surface. Every risk class a change can be declared under names one section, and a declaration handed over with the invocation deepens that section. Use after `senior-review` on any change touching user input, auth, data access, money, destructive writes, a subprocess, file permissions, or an LLM.
4
4
  origin: vendor
5
5
  vendor_version: '{{vendor_version}}'
6
6
  phase: post-implementation
@@ -15,6 +15,38 @@ endpoint or an LLM call. Consult the project's **security playbook** for project
15
15
  specific rules (deployment model, auth scheme, secret tooling); the checklist below is
16
16
  the generic floor. Flag only findings you're confident are real, ranked by severity.
17
17
 
18
+ ## Declared risk surfaces
19
+
20
+ Your invocation may carry a **declaration**: the kind of damage this change can do, named
21
+ at the structuring moment — before a line of it was written — and approved by a human, as a
22
+ class paired with the group or the file it was declared against. When one reaches you, it
23
+ is a **floor for depth, never a bound on scope**:
24
+
25
+ - Every section below still runs. The declaration says where to press harder, not what to
26
+ skip: it was written before the code existed, so it cannot know what the diff ended up
27
+ touching. Deriving the surface from the change itself remains your job.
28
+ - For each declared class, go to the section whose heading carries it. Every class in the
29
+ vocabulary names **exactly one** section below — the backticked class at the end of the
30
+ heading (`` ## 2. Authorization … — `auth` ``) — and a guard pins that the set of classes
31
+ in these headings and the vocabulary are the same set, so no heading ends in
32
+ `` — `token` `` unless `token` is a class. Go deep there: read the declared file whole
33
+ rather than sampling its diff, and follow the vector out of the change into the code it
34
+ reaches.
35
+ - A declared class that matches **no heading** is drift between the vocabulary and this
36
+ checklist — a guard should have caught it — not a class you may skip. Review it against
37
+ the project's security playbook and the generic floor, and **name it in the report** as
38
+ covered without a dedicated section, so the gap surfaces as a gap and not as silence.
39
+ - Damage the vocabulary could not name arrives as prose rather than a class — an
40
+ `unclassified: <path> — <what it can do>` line, or the rationale of a group that left its
41
+ tag off. That is a declaration, not a gap: take the prose as the brief and review that
42
+ path against it.
43
+
44
+ **No declaration is not a clean bill of health.** A declaration of `none` is a claim that
45
+ the change touches no such surface; a _missing_ declaration is no claim at all — the task
46
+ may simply predate the section. In both cases scope the depth from the change itself,
47
+ exactly as you would without one, and never report a surface as absent on the strength of a
48
+ declaration alone.
49
+
18
50
  ## 1. Input validation at boundaries
19
51
 
20
52
  - Every entry point (request body, query/path params, headers, file, env, third-party
@@ -23,28 +55,41 @@ the generic floor. Flag only findings you're confident are real, ranked by sever
23
55
  internal type.
24
56
  - No mass-assignment: unexpected fields are rejected, not spread into a model.
25
57
 
26
- ## 2. Authorization (not just authentication)
58
+ ## 2. Authorization (not just authentication) — `auth`
27
59
 
28
60
  - **Authentication** (who are you?) and **authorization** (are you allowed?) are
29
61
  distinct checks — a valid session is not permission.
30
62
  - **Ownership / IDOR**: a resource fetched by id verifies it belongs to the caller. An
31
63
  auth guard never substitutes for the ownership check.
32
64
  - Privileged actions check role/scope server-side, never trusting a client claim.
65
+ - **Session & token handling**: a token is verified before it is trusted — signature and
66
+ algorithm pinned (never `none`, never caller-chosen), `exp`/`iss`/`aud` checked, claims
67
+ never read from an unverified decode. Sessions rotate on login and privilege change,
68
+ and logout/revocation invalidates server-side, not just client-side (cookie flags: §5).
33
69
 
34
70
  ## 3. Injection
35
71
 
36
72
  - **SQL/NoSQL**: parameterized queries / validated ids; never interpolate user input
37
73
  into a query, never pass a raw request object to a query (object-injection).
38
- - **Command/path**: no user input concatenated into a shell command or file path
39
- (path traversal).
74
+ - **Path**: a path built from user input is resolved and then proven to stay under a
75
+ fixed base — `join`/`resolve` alone are not that proof (`join` normalizes `..` through,
76
+ `resolve` lets an absolute segment discard the base); absolute and `..` segments are
77
+ rejected rather than stripped, and `realpath` is used where symlinks can exist.
78
+ Shell-command construction is §8.
40
79
  - **Output/XSS**: rendered user content is escaped by default; raw-HTML sinks
41
80
  (`dangerouslySetInnerHTML`, server-generated HTML/PDF) sanitize at render time.
42
81
 
43
- ## 4. Secrets & logging
82
+ ## 4. Secrets & logging — `secrets`
44
83
 
45
84
  - No secret, token, password, key, or PII in code, logs, or error messages (a
46
85
  never-log redaction list is configured; emails in failure logs are hashed).
47
- - No secret committed (`.env` gitignored; `.env.example` carries placeholders only).
86
+ - **At rest**: a secret lives in the platform's secret store or a file only the app can
87
+ read (0600) — never a world-readable config, an image layer, or the repo (`.env`
88
+ gitignored; `.env.example` placeholders only). Passwords are stored under a slow KDF
89
+ (argon2/bcrypt/scrypt), never a bare or fast hash.
90
+ - **In transit**: a secret never rides in a URL or query string (proxies, browser history
91
+ and `Referer` capture those) — header or body over TLS only (§5) — and a leaked one has
92
+ a rotation path that does not require a deploy.
48
93
  - Constant-time comparison for secrets/tokens (no `===` on a hash); strong randomness
49
94
  for generated keys.
50
95
 
@@ -73,11 +118,79 @@ the generic floor. Flag only findings you're confident are real, ranked by sever
73
118
  third-party model; tool/function access from the model is least-privilege.
74
119
  - **Resource limits**: token/cost/loop bounds on agentic calls to prevent runaway use.
75
120
 
121
+ ## 8. Shell & subprocess — `shell-process`
122
+
123
+ - **Argument construction**: no untrusted input concatenated into a command string;
124
+ arguments go as an array to an exec-style API (`execFile`/`spawn` without
125
+ `shell: true` — and on Windows a `.cmd`/`.bat` target is shell-parsed regardless),
126
+ never through a shell. An array is not sufficient on its own: a value that can begin
127
+ with `-` is read by the binary as a flag, so untrusted values sit after `--` or are
128
+ validated against a known form.
129
+ - **Spawn surface**: every new subprocess is deliberate — the binary resolved from a
130
+ known path rather than the inherited `PATH`, and the child running with no more
131
+ privilege than the caller (no `sudo`, setuid, or elevated container).
132
+ - **Secrets across the boundary**: a secret never travels as a command-line argument —
133
+ argv is world-readable in `ps`. It goes via the child's environment (pruned to what the
134
+ child needs), stdin, or a file descriptor.
135
+ - **Child I/O**: the child's output is untrusted input at a boundary (§1 — and §7 as well
136
+ if it reaches an LLM) before it drives anything; its exit code is checked, not assumed;
137
+ and it cannot hang or flood the parent unbounded (timeout, kill, output cap).
138
+
139
+ ## 9. Money, billing & pricing — `payments`
140
+
141
+ - **Idempotency**: every charge, refund, or transfer is idempotent under retry
142
+ (idempotency key, provider-side dedup) — a replayed webhook or a double submit cannot
143
+ move money twice.
144
+ - **Amount & currency integrity**: amount and currency come from the server-side record,
145
+ never from the client; arithmetic is in integer minor units (no floats), the rounding
146
+ rule is explicit, and totals are recomputed rather than trusted.
147
+ - **Bounds & entitlement**: amounts, quantities and discounts are range- and sign-checked
148
+ server-side — no negative charge, no refund above what was captured, no coupon reused
149
+ or stacked past its rule — and access a payment buys is granted on settled state and
150
+ revoked when that state reverses.
151
+ - **Provider trust boundary**: webhooks are signature-verified against the **raw** request
152
+ body (a JSON body parser mounted ahead of the route silently breaks this) and
153
+ replay-windowed; state transitions (pending → paid → refunded) are enforced server-side
154
+ and written to an audit trail (who, what, when).
155
+
156
+ ## 10. Destructive writes & data — `data-loss`
157
+
158
+ - **Scoped deletes and updates**: every delete or bulk update carries a bounding
159
+ predicate (tenant/owner/id) that is proven non-empty at runtime, not merely present in
160
+ the source — an `undefined` id the ORM silently drops, or a filter built from a request
161
+ that arrives `{}`, is a collection-wide call wearing a `where`.
162
+ - **Irreversibility**: a migration or schema change has a tested rollback, or is
163
+ explicitly one-way and says so; a destructive data migration runs behind a
164
+ backup/snapshot step. Soft-delete where a human may need to recover — and where it is
165
+ used, every read path filters deleted rows and records holding personal data still
166
+ have a real erasure path, not an indefinite tombstone.
167
+ - **Atomicity**: writes that must succeed together are transactional or idempotently
168
+ replayable — a partial failure cannot leave data half-written and report success.
169
+ - **On disk**: a write replaces a file atomically (temp + rename), never truncates or
170
+ deletes a path the change did not create, and never follows a symlink to do it. Any
171
+ destructive path is proven non-empty before use — a path variable that can be empty
172
+ turns a scoped delete into a root-relative one.
173
+
174
+ ## 11. Executable surface — `executable-mode`
175
+
176
+ - **Permission changes**: no `chmod`/exec-bit change beyond what the feature needs;
177
+ neither the file nor the directory holding it becomes world-writable (a 0755 script in
178
+ a 0777 directory is replaceable), and no write-then-execute path — a file the app
179
+ writes is never later executed without a trust decision.
180
+ - **Install-time surface**: files laid down by an installer or updater carry the minimum
181
+ mode; a hook or script that gains executable mode is reviewed as code that runs, not
182
+ as data.
183
+ - **What can run**: a change to what the system executes unattended — a new entry point,
184
+ hook, cron or launch agent, service unit, `postinstall`, or a changed interpreter
185
+ shebang — is named in the report with who can write to it and what triggers it, even
186
+ when it looks routine. How this change spawns a process is §8.
187
+
76
188
  ## Report
77
189
 
78
190
  ```
79
191
  ## Security Review — <task name>
80
192
 
193
+ **Declared surfaces**: <class → where you pressed> | none declared | no declaration handed over
81
194
  **Critical**: <vector — file:line — fix> | none
82
195
  **High**: <…> | none
83
196
  **Medium**: <…> | none
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: spec-compliance-check
3
- description: Walk an L1 spec point by point against the implementation and emit a per-requirement pass/fail verdict. Use during review of an SDD task to prove every EARS requirement (including unwanted-behavior paths) is satisfied and tested — a committable audit trail. Complements `senior-review` (quality) with traceability (coverage of intent).
3
+ description: Walk an L1 spec point by point against the implementation and emit a per-requirement pass/fail verdict. Use during review of an SDD task to prove every EARS requirement (including unwanted-behavior paths) is satisfied and tested — a durable audit trail. Complements `senior-review` (quality) with traceability (coverage of intent).
4
4
  origin: vendor
5
5
  vendor_version: '{{vendor_version}}'
6
6
  phase: post-implementation
@@ -11,7 +11,7 @@ invoked-by: [reviewer]
11
11
 
12
12
  `senior-review` judges whether the code is _good_; this skill proves it does what the
13
13
  **spec said** — every requirement, point by point. It produces a traceability matrix:
14
- a committable audit trail mapping each requirement to its implementation and its test.
14
+ a durable audit trail mapping each requirement to its implementation and its test.
15
15
  Only meaningful for **L1 (SDD) tasks** that have a spec under
16
16
  `.claude/state/tasks/<id>/spec/`; skip it for L2.
17
17
 
@@ -25,8 +25,8 @@ Read all three spec artifacts for the task:
25
25
  state-driven / optional-feature / **unwanted-behavior**), each numbered with
26
26
  acceptance criteria.
27
27
  - `design.md` — the intended files, interfaces, approach, edge cases.
28
- - `tasks.md` — the atomic checklist, each item referencing the requirements it
29
- satisfies.
28
+ - `tasks.md` — the atomic checklist grouped under risk-sized step headers, each
29
+ item referencing the requirements it satisfies.
30
30
 
31
31
  ### 2. Trace each requirement to the implementation
32
32
 
@@ -55,6 +55,10 @@ and asserts the rejection/guard.
55
55
 
56
56
  ### 4. Emit the matrix
57
57
 
58
+ Destination: the full-pass **issue comment**. In your return summary — and always on
59
+ a per-step review, which posts no comment — carry only the counts + the failing
60
+ rows, per the Reviewer's verdict-size rule.
61
+
58
62
  ```
59
63
  ## Spec Compliance — task #<id>
60
64
 
@@ -37,6 +37,8 @@ The body is the externalized spec — self-contained enough to read on GitHub, w
37
37
  links back to the committed files for the full detail:
38
38
 
39
39
  ```markdown
40
+ <trace lines, if the skeleton carried any — verbatim, first>
41
+
40
42
  ## Summary
41
43
 
42
44
  <one paragraph: the capability and why, from the PRD>
@@ -61,7 +63,11 @@ gh issue edit <id> --body-file <body>
61
63
  ```
62
64
 
63
65
  This overwrites the `🚧 Spec in progress …` skeleton the Orchestrator wrote at issue
64
- creation. Labels are untouched the issue still carries `harness:managed` +
66
+ creation. **Preserve its trace lines**: a task that is a part of a partitioned feature
67
+ carries `Part <k> of #<parent>` (and `Depends on #<sibling> …`) at the top of the
68
+ skeleton — read the current body first and keep those lines, verbatim, as the first
69
+ lines of the new body. They are how the parent plan, `/resume`, and closeout find the
70
+ task's place in the partition; dropping them orphans the part. Labels are untouched — the issue still carries `harness:managed` +
65
71
  `harness:sdd` + `harness:status:spec-in-progress`; the Orchestrator flips it to
66
72
  `spec-ready` after you hand back.
67
73