@jaybeeuu/agent-cortex 2.6.0 → 2.8.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 (64) hide show
  1. package/CHANGELOG.md +269 -0
  2. package/README.md +102 -8
  3. package/agents/README.md +2 -2
  4. package/agents/ralph/agent.md +19 -16
  5. package/agents/ralph/claude/wake.md +4 -4
  6. package/agents/ralph/pi/wake.md +15 -20
  7. package/bin/agent-cortex.mjs +176 -67
  8. package/bin/installers/claude.mjs +33 -8
  9. package/bin/installers/pi.mjs +11 -1
  10. package/docs/skills/skill-anatomy.md +212 -0
  11. package/extensions/post-merge-watch/README.md +133 -0
  12. package/extensions/post-merge-watch/index.test.ts +247 -0
  13. package/extensions/post-merge-watch/index.ts +123 -0
  14. package/extensions/post-merge-watch/package.json +16 -0
  15. package/extensions/post-merge-watch/watch.test.ts +469 -0
  16. package/extensions/post-merge-watch/watch.ts +431 -0
  17. package/extensions/session-clear/README.md +74 -0
  18. package/extensions/session-clear/index.test.ts +524 -0
  19. package/extensions/session-clear/index.ts +159 -0
  20. package/extensions/session-clear/package.json +16 -0
  21. package/lib/cli.mjs +88 -1
  22. package/lib/harness-update.mjs +254 -0
  23. package/lib/harness-versions.mjs +36 -2
  24. package/lib/self-update.mjs +553 -0
  25. package/package.json +2 -1
  26. package/scripts/lib/docs.mjs +67 -0
  27. package/skills/engineering/implement-task/SKILL.md +124 -0
  28. package/skills/engineering/tdd/SKILL.md +2 -0
  29. package/skills/planning/create-task/REFERENCE.md +20 -3
  30. package/skills/planning/create-task/SKILL.md +1 -1
  31. package/skills/planning/create-task/pipeline.json +16 -0
  32. package/skills/planning/create-task/scripts/create-chores.mjs +51 -24
  33. package/skills/planning/create-task/templates/code.md +8 -3
  34. package/skills/planning/create-task/templates/document.md +8 -3
  35. package/skills/planning/create-task/templates/fix.md +8 -3
  36. package/skills/planning/create-task/templates/housekeeping.md +69 -0
  37. package/skills/planning/create-task/templates/post-merge.md +67 -0
  38. package/skills/planning/create-task/templates/review.md +8 -3
  39. package/skills/planning/create-task/templates/verify.md +8 -3
  40. package/skills/productivity/bd-tool/SKILL.md +55 -0
  41. package/skills/productivity/write-a-skill/SKILL.md +2 -2
  42. package/skills/review/refactor-skill/SKILL.md +2 -2
  43. package/skills/review/review-defects/EXAMPLES.md +122 -0
  44. package/skills/review/review-defects/SKILL.md +91 -0
  45. package/skills/style/style-tests/EXAMPLES.md +30 -0
  46. package/skills/style/style-tests/SKILL.md +10 -6
  47. package/skills/workflow/plan/SKILL.md +14 -3
  48. package/skills/workflow/ralph/REFERENCE.md +137 -30
  49. package/skills/workflow/ralph/SKILL.md +26 -16
  50. package/skills/workflow/ralph/scripts/post-merge-check.mjs +651 -0
  51. package/skills/workflow/run-pipeline-stage/SKILL.md +41 -23
  52. package/skills/workflow/run-pipeline-stage/playbooks/code.md +14 -8
  53. package/skills/workflow/run-pipeline-stage/playbooks/document.md +2 -2
  54. package/skills/workflow/run-pipeline-stage/playbooks/fix.md +2 -2
  55. package/skills/workflow/run-pipeline-stage/playbooks/housekeeping.md +58 -0
  56. package/skills/workflow/run-pipeline-stage/playbooks/post-merge.md +54 -0
  57. package/skills/workflow/run-pipeline-stage/playbooks/review.md +34 -3
  58. package/skills/workflow/run-pipeline-stage/playbooks/verify.md +2 -2
  59. package/skills/workflow/run-pipeline-stage/prompts/stage-runner.md +66 -10
  60. package/skills/workflow/run-pipeline-stage/scripts/recover-brief.mjs +805 -0
  61. package/skills/workflow/run-pipeline-stage/scripts/trail-entry.mjs +702 -0
  62. package/skills/workflow/using-agent-skills/SKILL.md +15 -7
  63. package/token-map.README.md +41 -4
  64. package/token-map.json +16 -5
@@ -37,6 +37,61 @@ We use labels to track state. Key patterns:
37
37
  | `lifecycle:<phase>` | Lifecycle gate | `feature-pr` |
38
38
  | `epic:<id>` | Parent epic link | `epic:abc-123` |
39
39
  | `priority:<level>` | Priority (0-3) | `priority:1` |
40
+ | `defect:<class>` | The kind of defect a review rejection names — one per rejection, closed list, never the whole message | `defect:test-unproven` |
41
+
42
+ ## Where operational state lives
43
+
44
+ Operational state lives in bead fields, not in files: progress, what a stage learned, the
45
+ decisions taken, and the branch, PR, commit or worktree refs. A cold agent reads all of it from
46
+ the bead.
47
+
48
+ | State | Home |
49
+ |---|---|
50
+ | What a stage did, and what it learned | `bd note <id> "…"` — an append-only trail |
51
+ | Status: never started, in flight, blocked, paused, done | the bead's own status — `bd statuses` |
52
+ | A decision and its reason | `bd note <id> "…"` — the same trail |
53
+ | Branch, PR, commit, worktree | `bd provenance record --issue <id> …` |
54
+ | Reviewer handoff prose | `.agent-cortex/working-docs/hitl/<bead-id>/handoff.md` — unchanged |
55
+ | Screenshots, logs, repro scripts | the bead's working-docs directory — unchanged |
56
+
57
+ Durable, committable documents stay files: `SKILL.md`, `REFERENCE.md`, playbooks, prompts,
58
+ `docs/`, `AGENTS.md`. Never migrate those into beads — they are reviewed, diffed and versioned as
59
+ text. A scratch file for progress is wrong twice: it duplicates the bead, and no other agent finds
60
+ it.
61
+
62
+ ### Use the built-in statuses; add no state labels
63
+
64
+ `bd statuses` lists the seven statuses the tracker owns, and they already answer "where is this
65
+ work?": `open` never started, `in_progress` being worked on, `blocked` stopped with no bead to
66
+ wait on, `deferred` stopped on purpose, `closed` finished, and `pinned`/`hooked` parked by the
67
+ tracker. A label such as `progress:blocked` repeats a status and splits the answer across two
68
+ places, so do not add one.
69
+
70
+ A stop that needs a human decision becomes a bead, not a label: create the decision bead
71
+ (`execution:hitl`, the question in its title) and depend the waiting bead on it. The waiting bead
72
+ leaves `bd ready`, and the decision shows up as work a human can take. Use `--status blocked` only
73
+ when no bead can carry the blocker — a fix-round cap, an outage. Record the stop and its reason
74
+ with `bd note`, which appends, so the trail keeps every checkpoint in order.
75
+
76
+ ```text
77
+ $ bd create "Decide: which upstream API do we call?" -t task -l execution:hitl
78
+ ✓ Created issue: probe-3i4 — Decide: which upstream API do we call?
79
+ $ bd dep add probe-ijd probe-3i4 # probe-ijd waits on the decision
80
+ ✓ Added dependency: probe-ijd (Rate-limit the export endpoint) depends on probe-3i4 (…) (blocks)
81
+ $ bd ready # only the decision bead is ready now
82
+ ○ probe-3i4 P2 Decide: which upstream API do we call?
83
+ $ bd note probe-ijd "[2026-11-14T08:26:38Z] stopped: API choice open; decision bead filed"
84
+ ✓ Note added to probe-ijd — Rate-limit the export endpoint
85
+ ```
86
+
87
+ **Never set a state dimension.** `bd set-state` writes a label for a dimension and removes the
88
+ label it replaces, so `bd set-state <id> stage=verify` deletes `stage:code` and rewrites the
89
+ bead's route. Statuses and notes cover every case above, so no dimension is needed.
90
+
91
+ ### `defect:<class>` classifies a defect; it is not a state
92
+
93
+ A review rejection carries one class from the closed list in
94
+ [review-defects](../../review/review-defects/SKILL.md), which owns how to apply, remove and query it.
40
95
 
41
96
  ## Creating work
42
97
 
@@ -25,7 +25,7 @@ Clarify the skill goal with the user. Ask what triggers users would actually say
25
25
 
26
26
  ### 2. Draft from the anatomy template
27
27
 
28
- Read `docs/skills/skill-anatomy.md` as the source of truth. Follow the annotated template for section ordering and front-matter fields. Target ~150 lines for `SKILL.md` but stay under the 150-line hard cap. When the workflow branches or has decision points, include one inline ASCII workflow diagram per the anatomy doc's diagram style guide — ASCII only, never Mermaid.
28
+ Read `{{PATH:skill_anatomy}}` as the source of truth. Follow the annotated template for section ordering and front-matter fields. Target ~150 lines for `SKILL.md` but stay under the 150-line hard cap. When the workflow branches or has decision points, include one inline ASCII workflow diagram per the anatomy doc's diagram style guide — ASCII only, never Mermaid.
29
29
 
30
30
  ### 3. Apply voice and tone rules
31
31
 
@@ -62,7 +62,7 @@ Output: A new `skills/review-security/SKILL.md` with front matter, When to use,
62
62
 
63
63
  ## Verification checklist
64
64
 
65
- - [ ] `docs/skills/skill-anatomy.md` was read before drafting
65
+ - [ ] `{{PATH:skill_anatomy}}` was read before drafting
66
66
  - [ ] Required sections present: `## When to use`, `## Workflow`, `## Verification checklist`
67
67
  - [ ] Section order follows the anatomy template
68
68
  - [ ] Workflow diagram (if present) is ASCII-only and follows the anatomy diagram style guide
@@ -8,7 +8,7 @@ description: Refactor an existing SKILL.md to the canonical anatomy template thr
8
8
  ## When to use
9
9
 
10
10
  - A skill's SKILL.md predates the anatomy template and needs upgrading.
11
- - You want to align a skill with `docs/skills/skill-anatomy.md` before the refactor wave touches it.
11
+ - You want to align a skill with `{{PATH:skill_anatomy}}` before the refactor wave touches it.
12
12
  - You want to compare a skill's structure to the current template standard.
13
13
  - Trigger phrases: "refactor this skill", "restructure this skill", "review this skill", "upgrade X to the template", "align with anatomy".
14
14
 
@@ -27,7 +27,7 @@ Read the target skill's `skills/<name>/SKILL.md` and the canonical anatomy refer
27
27
 
28
28
  ```bash
29
29
  cat skills/<name>/SKILL.md
30
- cat docs/skills/skill-anatomy.md
30
+ cat {{PATH:skill_anatomy}}
31
31
  ```
32
32
 
33
33
  If the target has no `SKILL.md` yet, abort — this skill upgrades existing files; use `write-a-skill` for new creation.
@@ -0,0 +1,122 @@
1
+ # Review defects — worked examples
2
+
3
+ One case per class. Each shows what was observed, the label, and the rejection text to write. Six
4
+ were observed in real review rounds and one in a code stage's check of its own change; `scope-creep`
5
+ is described as a situation, because no recorded case exists, so you can match its shape without
6
+ guessing at history.
7
+
8
+ ## `test-unproven` — the name claims a settle-time cleanup
9
+
10
+ **Observed.** A test named "… removes it when the watch settles" called `stop()` and asserted the
11
+ entry was gone. It never settled the watch, so the settle path had no assertion. The cleanup on
12
+ settle could have been deleted and the test would still pass.
13
+
14
+ **Label.** `defect:test-unproven`
15
+
16
+ **Rejection text.** "Test `…removes it when the watch settles` claims removal at the settle step,
17
+ but the body stops the watch and asserts removal once. Add the settle trigger and assert the entry
18
+ is gone after it. Either the name or the assertion must change."
19
+
20
+ **Why this class.** Not `test-cannot-fail`: the assertion is genuine and would fail for the bug it
21
+ covers. The defect is narrower — the name promises a path the body never takes.
22
+
23
+ ## `test-cannot-fail` — the concurrent case is never exercised
24
+
25
+ **Observed.** A note appended "a double fire appends nothing twice". The test fired twice in
26
+ sequence and asserted one entry. Two concurrent fires were never run, so the claim could not fail.
27
+
28
+ **Label.** `defect:test-cannot-fail`
29
+
30
+ **Rejection text.** "The claim is about concurrent double fires, but the test fires sequentially.
31
+ Await both fires against the same target and assert one entry, or rename the claim. As written no
32
+ implementation can fail it."
33
+
34
+ **Why this class.** Not `test-unproven`: the name and the assertion agree on "two fires produce one
35
+ entry". The gap is that the case the claim is about is never constructed.
36
+
37
+ ## `missing-proof` — a report of a demonstration that was stubbed
38
+
39
+ **Observed.** A stage report stated "end-to-end run verified: the check writes the record and the
40
+ follow-up reads it". The command in the evidence block substituted a stub for the check, so no
41
+ end-to-end path ran.
42
+
43
+ **Label.** `defect:missing-proof`
44
+
45
+ **Rejection text.** "`SUMMARY` claims an end-to-end demonstration; the evidence command replaces the
46
+ check with a stub, so the record is written by the test, not by the code under review. Paste the
47
+ real invocation and its output."
48
+
49
+ **Why this class.** Nothing is missing from the code. The gap is between the claim and the evidence
50
+ for it, which is exactly what this class names.
51
+
52
+ ## `docs-drift` — shipped text that shipped past its change
53
+
54
+ **Observed.** Two shipped text defects in one round: a token map named an exact harness version, so
55
+ the mapping broke on the next release; and a peer-channel section still said one harness only, after
56
+ the fork had made it both.
57
+
58
+ **Label.** `defect:docs-drift`
59
+
60
+ **Rejection text.** "The token map names an exact harness version — it must read the running one or
61
+ a documented default. The peer-channel section says one harness only, but the mapping now covers
62
+ both; update the prose to what ships."
63
+
64
+ **Why this class.** The shipped artefact and the text describing it disagree. A reviewer reading the
65
+ text would predict behaviour the install does not have.
66
+
67
+ ## `criteria-unmet` — the change breaks the criterion it must satisfy
68
+
69
+ **Observed.** A criterion said an install leaves one directory free of the tool's own files. The
70
+ change wrote its state record into that very directory, so the reviewer could not affirm the
71
+ criterion at all.
72
+
73
+ **Label.** `defect:criteria-unmet`
74
+
75
+ **Rejection text.** "The criterion says that directory holds no file of ours; the new record is
76
+ written into it, so the criterion fails. Move the record under the tool's own state directory,
77
+ keyed by the directory it describes, or amend the criterion in the bead and say why."
78
+
79
+ **Why this class.** The work is real and the scope is honest — part of what was asked is absent.
80
+
81
+ ## `scope-creep` — unrelated tidying rides along
82
+
83
+ **Situation.** A change that adds one label also renames three unrelated helpers and reformats a
84
+ neighbouring module.
85
+
86
+ **Label.** `defect:scope-creep`
87
+
88
+ **Rejection text.** "The rename and the reformat are outside the criteria. Split them into their own
89
+ change so this one can be reviewed and reverted on its own."
90
+
91
+ **Why this class.** Every line is defensible in isolation. The defect is that the change is wider
92
+ than its reason, which hides the real diff.
93
+
94
+ ## `style-violation` — a written rule is broken
95
+
96
+ **Observed.** A change added a new skill and declared a `patch` bump in its changeset. The
97
+ versioning rule says a new skill is a `minor` bump and lists "wrong bump type" as a red flag. The
98
+ reviewer rejected the change citing that rule.
99
+
100
+ **Label.** `defect:style-violation`
101
+
102
+ **Rejection text.** "The changeset declares `patch`, but this change adds a skill. The versioning
103
+ rule makes an addition `minor` and calls a wrong bump type a red flag. Bump it to `minor`."
104
+
105
+ **Why this class.** Cite the rule, not a preference. A reviewer who cannot quote a written rule has
106
+ a preference, and preferences are not rejections.
107
+
108
+ ## `regression` — an existing behaviour breaks
109
+
110
+ **Observed.** A code stage added a manifest write that assumed its target directory existed. An
111
+ install from a source holding no agents and no skills had worked before; after the change it threw
112
+ `ENOENT` on that write. The stage found it in its own check of the change, reproduced it, added a
113
+ test that failed, then fixed it.
114
+
115
+ **Label.** `defect:regression`
116
+
117
+ **Rejection text.** "The manifest write assumes its directory exists, so an install from a source
118
+ with no agents and no skills now throws `ENOENT` where it used to succeed. Create the directory
119
+ before writing, and cover the empty-source case with a test."
120
+
121
+ **Why this class.** The change may satisfy its own criteria fully. It also breaks behaviour nobody
122
+ asked to change, which is a separate fact from `criteria-unmet`.
@@ -0,0 +1,91 @@
1
+ ---
2
+ name: review-defects
3
+ description: The closed vocabulary of `defect:<class>` labels a reviewer applies to a rejected bead, so rejections become countable data. Use when rejecting a change, when deciding which class names a finding, or when querying which defects recur.
4
+ ---
5
+
6
+ # Review defects
7
+
8
+ A rejection says what is wrong. The class says *which kind* of wrong, in one word, so rejections can
9
+ be counted, compared and trended across tasks instead of re-read one at a time.
10
+
11
+ ## One closed list, one label per rejection
12
+
13
+ Apply exactly one class from this table. Never invent one.
14
+
15
+ | Class | The defect | Evidence the rejection must show |
16
+ |---|---|---|
17
+ | `test-unproven` | A test's name claims a requirement its assertions never check. | The named clause, and the assertion that is missing. |
18
+ | `test-cannot-fail` | An assertion passes against a broken implementation. | The case that should fail and does not. |
19
+ | `criteria-unmet` | A criterion from the bead is not delivered. | The criterion, quoted, and what is absent. |
20
+ | `scope-creep` | The change does more than the criteria ask. | The touched behaviour that no criterion names. |
21
+ | `style-violation` | A `style-*` rule is broken. | The rule, quoted, and the line that breaks it. |
22
+ | `missing-proof` | A report claims success with no evidence. | The claim, and the command output that is absent. |
23
+ | `regression` | Behaviour that worked before now breaks. | The old behaviour, and the check that now fails. |
24
+ | `docs-drift` | Shipped text no longer matches what ships. | The text, and the behaviour that contradicts it. |
25
+
26
+ Two findings of different kinds are two entries in the rejection text and one label: the class of
27
+ the finding that must be fixed first.
28
+
29
+ ## A class is a classification, not a state
30
+
31
+ The bead's own status still answers "where is this work?". The label answers "what kind of defect
32
+ was this?" — so never use it to mean blocked, in progress or done, and never add a parallel state
33
+ label.
34
+
35
+ ## Apply it
36
+
37
+ 1. Name the class in the rejection text, next to the specific reason.
38
+ 2. Apply it to the bead whose work you reject:
39
+
40
+ ```bash
41
+ bd update <id> --add-label defect:test-unproven
42
+ ```
43
+
44
+ That bead is normally already closed — a pipeline closes the chore whose work is rejected before
45
+ the review runs — and the label attaches to it anyway. So the queries below pass `--all`.
46
+
47
+ 3. Write the specific reason — the file, the line, the missing assertion — in the rejection text.
48
+ The label is queryable; it is never the whole message.
49
+
50
+ ## Remove it when the fix closes
51
+
52
+ The review that confirms the fix removes the label, so the vocabulary describes only defects whose
53
+ fix is unconfirmed:
54
+
55
+ ```bash
56
+ bd update <id> --remove-label defect:test-unproven
57
+ ```
58
+
59
+ Record the removal in that review's trail entry ("`defect:test-unproven` cleared"), not as a label.
60
+ A closed defect is a fact in the trail, not a second class.
61
+
62
+ ## An unknown class is itself a defect
63
+
64
+ A class that is not on the list above is not a label. Applying one is a review defect in itself:
65
+ state it in the rejection text, use the nearest real class for the finding, and raise the gap with
66
+ the owner. Do not extend the list inside a review — a vocabulary that grows per rejection counts
67
+ nothing.
68
+
69
+ ## Query it
70
+
71
+ ```bash
72
+ bd list -l defect:test-unproven --all # one class, any status
73
+ bd list --label-pattern 'defect:*' --all # every defect, any class
74
+ ```
75
+
76
+ `--all` is not optional. `bd list` hides closed beads by default, defects are historical data, and
77
+ the bead carrying one is usually closed: drop the flag and the query reports "No issues found" on
78
+ a label that is set.
79
+
80
+ Count the pattern output per class to see what recurs, and fix the process that produces it — a
81
+ class that keeps appearing is a gap in the stage before this one.
82
+
83
+ ## Worked examples
84
+
85
+ `EXAMPLES.md` carries one real case per class, with the rejection text a reviewer should write.
86
+
87
+ ## See Also
88
+
89
+ - [review-security](../review-security/SKILL.md) — the security verdict that precedes any rejection.
90
+ - [run-pipeline-stage](../../workflow/run-pipeline-stage/SKILL.md) — the stage that applies these labels.
91
+ - [bd-tool](../../productivity/bd-tool/SKILL.md) — labels, notes and statuses.
@@ -434,6 +434,36 @@ for (const item of cart.items) {
434
434
  expect(cart.items.map((item) => item.price)).toStrictEqual([10, 20, 30]);
435
435
  ```
436
436
 
437
+ ### A test name is a claim
438
+
439
+ The name says what the test proves. Read it as a list of assertions and check that each one
440
+ appears in the body. A clause with no assertion is a requirement the suite never enforces, so the
441
+ test stays green while the behaviour breaks.
442
+
443
+ ```ts
444
+ // ❌ Instead — the name claims one notice; the body only claims that something exists.
445
+ // Remove the notice and this test still passes, because a list is always defined.
446
+ it("sends one notice", async () => {
447
+ await publishAll(recipients);
448
+
449
+ expect(await notices.list()).toBeDefined();
450
+ });
451
+
452
+ // ✅ Write — the count in the name is asserted, so two notices and no notices both fail
453
+ it("sends one notice", async () => {
454
+ await publishAll(recipients);
455
+
456
+ expect(await notices.list()).toHaveLength(1);
457
+ });
458
+ ```
459
+
460
+ A state in the name works the same way — "returns the cached user" needs an assertion that the
461
+ returned user came from the cache, not that a user came back — and a "not" needs an assertion of
462
+ the absence, such as a call count of zero.
463
+
464
+ The *Workflow* mutation check catches this at run time: disable the behaviour and the bad form
465
+ above stays green, while the good form goes red.
466
+
437
467
  ## Determinism and async
438
468
 
439
469
  ```ts
@@ -25,6 +25,7 @@ description: Defines test-writing conventions — assertion strategy, mock disci
25
25
  - **Integration and e2e exercise real services.** Run the genuine collaborator — Postgres, Redis, a queue, object storage via TestContainers or LocalStack — never a fake that drifts from the real contract. Reserve e2e for a small number of smoke tests proving the system hangs together.
26
26
  - **No more tests than needed.** Every test earns its cost by pinpointing exactly where a failure lies. A test that cannot localise a fault adds maintenance weight without signal.
27
27
  - **One behaviour per test.** A test should fail for exactly one reason; several unrelated assertions turn a failure into a debugging exercise.
28
+ - **A test's name is a claim, and every clause of it is an assertion.** A count in the name — "one notice", "both callers", "no retries" — asserts that count; a state in the name asserts that state; a "not" in the name asserts the absence. A name that outruns its assertions states a requirement the test never proves, so the test cannot fail for the reason its name promises. See _Red Flags_, and the "one notice" pair under _A test name is a claim_ in `EXAMPLES.md`.
28
29
  - **Test code is production code.** Linted, typechecked, properly typed — no `any`, no unsafe casts, no shortcuts in constructing data. Tolerate WETness where it leaves tests more descriptive.
29
30
  - **All setup and assertion data is visible inline.** Show exactly and only the values that explain the case; a helper with sensible defaults keeps it terse.
30
31
  - **Deterministic and performant.** A failing or flaky test is investigated now. Deferring is acceptable only against a top-priority bead.
@@ -71,7 +72,9 @@ Worked examples for each rule are in `EXAMPLES.md` under _Test data_.
71
72
 
72
73
  5. **Write targeted assertions.** Prefer `toMatchObject` over asserting every property. Multiple assertions are fine when they all verify the same behaviour. Avoid asserting call counts when asserting call arguments already covers the behaviour. Assert behaviour the file participates in, never its static contents.
73
74
 
74
- 6. **Review against the checklist below.** Read the test as if you are seeing the codebase for the first time.
75
+ 6. **Run the mutation check.** Before you finish, disable or revert each behaviour a test claims, then run the suite. Every test named for that behaviour must fail. A named test that still passes proves nothing about its name: it is the _Red Flags_ row on empty implementations, applied to the whole name. Restore the behaviour and confirm the suite is green again.
76
+
77
+ 7. **Review against the checklist below.** Read the test as if you are seeing the codebase for the first time.
75
78
 
76
79
  ## Red Flags
77
80
 
@@ -80,7 +83,8 @@ Worked examples for each rule are in `EXAMPLES.md` under _Test data_.
80
83
  | Asserting static file content — an expected config/markdown/terraform string, or a snapshot of static content | Assert the behaviour the file participates in: data inserted into a template, a header compiled correctly, a deployed service ready to serve traffic |
81
84
  | Shared mutable top-level fixture reused across tests | Build fresh data per test via a factory; a module-scope constant only when genuinely immutable and read-only |
82
85
  | Repeated full literals for the same shape | A small configurable factory taking partial overrides with sensible defaults |
83
- | A test with no assertion, or one that cannot fail (`expect(result).toBeDefined()`) | Assert the observable contract — a specific value the code must produce |
86
+ | A test with no assertion, or one that passes against an empty implementation (`expect(result).toBeDefined()`) | Assert the observable contract — a specific value the code must produce |
87
+ | A test name whose claim no assertion mentions — "one notice" proven only by `toBeDefined()`, a count proven by truthiness | Assert every clause of the name: the count (`toHaveLength(1)`), the state, the absence |
84
88
  | Computing the expected value with the implementation's own logic | Hard-code the expected value from a known-good case; the test must not share the implementation's bug |
85
89
  | Mocking implementation details or internal module structure | Mock the genuine external boundary and test through the public interface |
86
90
  | An "integration" test that mocks the collaborator it is meant to integrate with | Run the real collaborator (TestContainers/LocalStack); a fake drifts from the real contract |
@@ -115,9 +119,8 @@ Worked examples for each rule are in `EXAMPLES.md` under _Test data_.
115
119
 
116
120
  ## Examples
117
121
 
118
- The full `❌ Instead` / `✅ Write` catalogue lives in `EXAMPLES.md`: behaviour over
119
- implementation, test data, mocking, integration and e2e, targeted assertions, structure and
120
- naming, determinism and async, isolation, and content assertions. Headline pairs:
122
+ The full `❌ Instead` / `✅ Write` catalogue lives in `EXAMPLES.md` — including _A test name is a
123
+ claim_, with the "one notice" pair. Headline pairs:
121
124
 
122
125
  | Instead of… | Write… |
123
126
  |---|---|
@@ -131,7 +134,8 @@ naming, determinism and async, isolation, and content assertions. Headline pairs
131
134
  - [ ] Test verifies behaviour, not implementation structure
132
135
  - [ ] Integration tests exercise the real collaborator — no mocking the engine you are integrating with
133
136
  - [ ] E2E smoke tests prove wiring, migrations, and the journey — not business rules covered lower down
134
- - [ ] Each test fails for exactly one reason, and its name states the behaviour
137
+ - [ ] Each test fails for exactly one reason, and its name states only behaviour an assertion proves — count, state, and absence included
138
+ - [ ] Mutation check run: each claimed behaviour was disabled, its named tests failed, and the suite is green again
135
139
  - [ ] No mocking of internal module details — only public interfaces
136
140
  - [ ] No `before`/`after` hooks for shared test state (infrastructure setup only)
137
141
  - [ ] Assertions are targeted — no over-asserting, no tautologies, no assertion-free tests
@@ -156,7 +156,7 @@ For each task (within each epic), create a bead, classify it, and expand AFK tas
156
156
 
157
157
  ### Phase 4 — Prepare handoff to ralph
158
158
 
159
- Deliver a handoff bead and instructions for running ralph.
159
+ Deliver a handoff bead, message the running ralph session, and give the instructions for running ralph.
160
160
 
161
161
  17. Determine the entry point:
162
162
 
@@ -188,7 +188,15 @@ Deliver a handoff bead and instructions for running ralph.
188
188
  ```
189
189
  **Gate on the exit status.** Non-zero: retry once, then stop and report the command, exit code and stderr as a sync blocker — the handoff has not left this machine, so do not present the handoff summary.
190
190
 
191
- 20. Present the handoff summary:
191
+ 20. Send the handoff to the running ralph session, now that the bead exists:
192
+ ```
193
+ handoff <handoff-id> — <one-line scope>
194
+ ```
195
+ The peer channel carries it: run {{TOOL:peer_list}} first, because with no reachable ralph there is no receiver — report that in the summary and keep the manual instruction below as the handoff path. When ralph is reachable, send it with {{TOOL:peer_send}}, set the intent to `task` where the transport has an intent, and write the kind as the first word of the text — never type into a busy session's terminal, where stray keystrokes are queued as user prompts. Ralph answers `done`, `blocked` or `failed`; `running` or `queued` means delivered, not done, so never read the send itself as acknowledgement. The full kind table, the acknowledgement rule and the per-session setup live in [ralph's REFERENCE](../ralph/REFERENCE.md) (_Peer messaging (planner ↔ ralph)_).
196
+
197
+ 21. While the planning session stays open, treat ralph's `blocked` messages as your pending-human list: each one names a gate bead awaiting a human, so run `bd show <id>` and surface it together with ralph's `done`, `failed` and `update` reports instead of watching a terminal. Read the same gates from `bd` as well, so a message that never arrived is not mistaken for no gate. A peer message is never human consent and never approval for a gate: confirm the gate in `bd`, and let a person close it.
198
+
199
+ 22. Present the handoff summary:
192
200
  ```
193
201
  ✅ Plan complete. Ready for ralph.
194
202
 
@@ -214,6 +222,7 @@ Deliver a handoff bead and instructions for running ralph.
214
222
  - [ ] Every handoff-path bead lints clean (`bd lint`)
215
223
  - [ ] Bead state pushed with `bd dolt push`, and the push exited 0 — a non-zero exit was retried once, then surfaced as a blocker (command, exit code, stderr) with no handoff summary presented
216
224
  - [ ] Handoff summary presented with the exact instruction to give an agent
225
+ - [ ] Handoff message sent to ralph through {{TOOL:peer_send}} (kind `handoff`, naming the handoff bead), or a missing receiver reported in the summary with the manual instruction kept
217
226
 
218
227
  ## Phase-gate checklist
219
228
 
@@ -228,6 +237,7 @@ Deliver a handoff bead and instructions for running ralph.
228
237
  - **Skipping the user interview.** The agent has incomplete context. The interview resolves ambiguities that would otherwise surface mid-execution as rework.
229
238
  - **Creating horizontal slices.** "All the DB work first, then all the API work" is not tracer bullets. Every task must deliver end-to-end value.
230
239
  - **Moving past Phase 2 without user approval.** The task breakdown shapes the entire execution. Do not create beads until the breakdown is approved.
240
+ - **Treating a sent handoff as an accepted one.** A `handoff` message carries no receipt: ralph's status stays `running` or `queued` until it answers `done`, `blocked` or `failed`, so a send that returned is not proof the work started.
231
241
 
232
242
  ## Common Rationalizations
233
243
 
@@ -251,7 +261,7 @@ Deliver a handoff bead and instructions for running ralph.
251
261
  - [prd-to-tasks](../../planning/prd-to-tasks/SKILL.md) — load when the PRD is already written and only decomposition is needed.
252
262
  - [spec-to-tickets](../../planning/spec-to-tickets/SKILL.md) — load instead when the PRD must become external tickets rather than beads.
253
263
  - [create-task](../../planning/create-task/SKILL.md) — Phase 3's per-task bead creation and pipeline expansion; it invokes `classify-bead` internally, so this skill never classifies directly.
254
- - [ralph](../ralph/SKILL.md) — the downstream consumer of the handoff bead — hand off by running a separate agent with "Use the ralph skill. Start from bead #<handoff-id>".
264
+ - [ralph](../ralph/SKILL.md) — the downstream consumer of the handoff bead — hand off by sending ralph a `handoff` message naming the bead, with "Use the ralph skill. Start from bead #<handoff-id>" as the manual fallback when no ralph session is online. Its [REFERENCE.md](../ralph/REFERENCE.md) owns the planner ↔ ralph message convention.
255
265
 
256
266
  ## Examples
257
267
 
@@ -293,3 +303,4 @@ Full bead tree:
293
303
  - [ ] Handoff bead tagged `workflow:ralph`
294
304
  - [ ] Bead state pushed with `bd dolt push`, and the push exited 0 — a non-zero exit was retried once, then surfaced as a blocker (command, exit code, stderr) with no handoff summary presented
295
305
  - [ ] Handoff summary includes the exact instruction to give an agent
306
+ - [ ] Handoff message sent to ralph (kind `handoff`, naming the handoff bead) through {{TOOL:peer_send}}; ralph's `done`/`blocked`/`failed` replies were the only evidence of completion, never the send itself