@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.
- package/CHANGELOG.md +269 -0
- package/README.md +102 -8
- package/agents/README.md +2 -2
- package/agents/ralph/agent.md +19 -16
- package/agents/ralph/claude/wake.md +4 -4
- package/agents/ralph/pi/wake.md +15 -20
- package/bin/agent-cortex.mjs +176 -67
- package/bin/installers/claude.mjs +33 -8
- package/bin/installers/pi.mjs +11 -1
- package/docs/skills/skill-anatomy.md +212 -0
- package/extensions/post-merge-watch/README.md +133 -0
- package/extensions/post-merge-watch/index.test.ts +247 -0
- package/extensions/post-merge-watch/index.ts +123 -0
- package/extensions/post-merge-watch/package.json +16 -0
- package/extensions/post-merge-watch/watch.test.ts +469 -0
- package/extensions/post-merge-watch/watch.ts +431 -0
- package/extensions/session-clear/README.md +74 -0
- package/extensions/session-clear/index.test.ts +524 -0
- package/extensions/session-clear/index.ts +159 -0
- package/extensions/session-clear/package.json +16 -0
- package/lib/cli.mjs +88 -1
- package/lib/harness-update.mjs +254 -0
- package/lib/harness-versions.mjs +36 -2
- package/lib/self-update.mjs +553 -0
- package/package.json +2 -1
- package/scripts/lib/docs.mjs +67 -0
- package/skills/engineering/implement-task/SKILL.md +124 -0
- package/skills/engineering/tdd/SKILL.md +2 -0
- package/skills/planning/create-task/REFERENCE.md +20 -3
- package/skills/planning/create-task/SKILL.md +1 -1
- package/skills/planning/create-task/pipeline.json +16 -0
- package/skills/planning/create-task/scripts/create-chores.mjs +51 -24
- package/skills/planning/create-task/templates/code.md +8 -3
- package/skills/planning/create-task/templates/document.md +8 -3
- package/skills/planning/create-task/templates/fix.md +8 -3
- package/skills/planning/create-task/templates/housekeeping.md +69 -0
- package/skills/planning/create-task/templates/post-merge.md +67 -0
- package/skills/planning/create-task/templates/review.md +8 -3
- package/skills/planning/create-task/templates/verify.md +8 -3
- package/skills/productivity/bd-tool/SKILL.md +55 -0
- package/skills/productivity/write-a-skill/SKILL.md +2 -2
- package/skills/review/refactor-skill/SKILL.md +2 -2
- package/skills/review/review-defects/EXAMPLES.md +122 -0
- package/skills/review/review-defects/SKILL.md +91 -0
- package/skills/style/style-tests/EXAMPLES.md +30 -0
- package/skills/style/style-tests/SKILL.md +10 -6
- package/skills/workflow/plan/SKILL.md +14 -3
- package/skills/workflow/ralph/REFERENCE.md +137 -30
- package/skills/workflow/ralph/SKILL.md +26 -16
- package/skills/workflow/ralph/scripts/post-merge-check.mjs +651 -0
- package/skills/workflow/run-pipeline-stage/SKILL.md +41 -23
- package/skills/workflow/run-pipeline-stage/playbooks/code.md +14 -8
- package/skills/workflow/run-pipeline-stage/playbooks/document.md +2 -2
- package/skills/workflow/run-pipeline-stage/playbooks/fix.md +2 -2
- package/skills/workflow/run-pipeline-stage/playbooks/housekeeping.md +58 -0
- package/skills/workflow/run-pipeline-stage/playbooks/post-merge.md +54 -0
- package/skills/workflow/run-pipeline-stage/playbooks/review.md +34 -3
- package/skills/workflow/run-pipeline-stage/playbooks/verify.md +2 -2
- package/skills/workflow/run-pipeline-stage/prompts/stage-runner.md +66 -10
- package/skills/workflow/run-pipeline-stage/scripts/recover-brief.mjs +805 -0
- package/skills/workflow/run-pipeline-stage/scripts/trail-entry.mjs +702 -0
- package/skills/workflow/using-agent-skills/SKILL.md +15 -7
- package/token-map.README.md +41 -4
- 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 `
|
|
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
|
-
- [ ] `
|
|
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 `
|
|
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
|
|
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. **
|
|
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
|
|
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
|
|
119
|
-
|
|
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
|
|
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.
|
|
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
|
|
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
|