@jaybeeuu/agent-cortex 2.7.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 +239 -0
- package/README.md +61 -5
- package/agents/README.md +2 -2
- package/agents/ralph/agent.md +17 -16
- package/agents/ralph/claude/wake.md +4 -4
- package/agents/ralph/pi/wake.md +15 -20
- package/bin/agent-cortex.mjs +20 -1
- 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 +42 -1
- package/lib/self-update.mjs +553 -0
- package/package.json +1 -1
- package/skills/engineering/implement-task/SKILL.md +124 -0
- package/skills/engineering/tdd/SKILL.md +2 -0
- package/skills/planning/create-task/REFERENCE.md +15 -6
- package/skills/planning/create-task/SKILL.md +1 -1
- package/skills/planning/create-task/pipeline.json +8 -0
- package/skills/planning/create-task/scripts/create-chores.mjs +27 -18
- 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 +8 -4
- 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/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 +124 -27
- package/skills/workflow/ralph/SKILL.md +24 -15
- package/skills/workflow/ralph/scripts/post-merge-check.mjs +651 -0
- package/skills/workflow/run-pipeline-stage/SKILL.md +37 -22
- 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 +1 -1
- 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 +55 -11
- 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 +27 -0
- package/token-map.json +10 -4
|
@@ -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
|
|
@@ -47,7 +47,7 @@ are fine.
|
|
|
47
47
|
|
|
48
48
|
## Expansion Guard
|
|
49
49
|
|
|
50
|
-
Stage chores are pre-created, never created on demand. `create-chores.mjs` (in `skills/planning/create-task/`) reads `pipeline.json` and creates the whole chain in one shot — `code` → `verify` → `review` → `document`, plus the `featurePrReview` HITL gate, plus the `housekeeping`
|
|
50
|
+
Stage chores are pre-created, never created on demand. `create-chores.mjs` (in `skills/planning/create-task/`) reads `pipeline.json` and creates the whole chain in one shot — `code` → `verify` → `review` → `document`, plus the `featurePrReview` HITL gate, plus the `post-merge` and `housekeeping` chores blocked behind that gate — with `blocks` deps that encode the order, and with the parent's priority and inherited labels. It is idempotent: a re-run adopts the chores that already exist under the parent, creates only the missing ones, and skips deps that are already wired. Ralph's dispatch is therefore always "claim what `bd ready -l execution:afk` returns".
|
|
51
51
|
|
|
52
52
|
A ready `execution:afk` task bead with **no** `stage:*` chore children has nothing to dispatch. Expand it, then dispatch the resulting `stage:code` chore:
|
|
53
53
|
|
|
@@ -70,9 +70,9 @@ A ready `execution:afk` task bead with **no** `stage:*` chore children has nothi
|
|
|
70
70
|
`<create-task-scripts>` is the absolute path to `skills/planning/create-task/scripts/`, resolved from wherever the skill was loaded — never hardcode one harness's install path. `<parent-priority>` is the parent's own priority from `bd show <parent-id>`, so chores match the task they belong to.
|
|
71
71
|
3. **Record it in `inflight`** so its completion is attributable to the parent and it stays in the wait set — it has no chore, so `choreId` is `null`:
|
|
72
72
|
```json
|
|
73
|
-
{ "kind": "expand", "choreId": null, "parentId": "<parent-id>", "title": "<parent title>", "agentId": "<agent-id>"
|
|
73
|
+
{ "kind": "expand", "choreId": null, "parentId": "<parent-id>", "title": "<parent title>", "agentId": "<agent-id>" }
|
|
74
74
|
```
|
|
75
|
-
4. **Report the mapping** the worker returns — `code`, `verify`, `review`, `document`, `housekeeping`, `featurePrReview` — and close nothing. The worker owns no bead on this run; the expansion is bookkeeping, not a stage.
|
|
75
|
+
4. **Report the mapping** the worker returns — `code`, `verify`, `review`, `document`, `post-merge`, `housekeeping`, `featurePrReview` — and close nothing. The worker owns no bead on this run; the expansion is bookkeeping, not a stage.
|
|
76
76
|
5. **Dispatch normally.** The next `bd ready -l execution:afk` pass picks up the `stage:code` chore, and the pre-created chain carries the rest.
|
|
77
77
|
6. **Handle the completion as an expansion completion**, not a stage completion — see below.
|
|
78
78
|
|
|
@@ -97,7 +97,7 @@ On completion, remove the entry from `inflight` and confirm the chore tree exist
|
|
|
97
97
|
bd children <parent-id> --json | grep -c '"stage:'
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
-
Expect one match per `stage:*` label in the chain —
|
|
100
|
+
Expect one match per `stage:*` label in the chain — six for the shipped `pipeline.json`; the
|
|
101
101
|
`featurePrReview` gate carries `lifecycle:feature-pr` instead, so it is not counted. A lower count
|
|
102
102
|
means the expansion did not finish: `0` created nothing, and a partial chain leaves stages that
|
|
103
103
|
will never become ready. Either way, surface the worker's output in chat, record the parent for the
|
|
@@ -130,7 +130,7 @@ Decompose it:
|
|
|
130
130
|
This is the shape `create-task` builds on its feature path (`SKILL.md` steps 5–6): an `execution:hitl` planning bead owned by the feature, whose impl children depend on it, so the dependency — not a label — is the readiness gate.
|
|
131
131
|
3. **Record it in `inflight`** so its completion is attributable to the feature and it stays in the wait set — it holds no chore, so `choreId` is `null`:
|
|
132
132
|
```json
|
|
133
|
-
{ "kind": "decompose", "choreId": null, "parentId": "<feature-id>", "title": "<feature title>", "agentId": "<agent-id>"
|
|
133
|
+
{ "kind": "decompose", "choreId": null, "parentId": "<feature-id>", "title": "<feature title>", "agentId": "<agent-id>" }
|
|
134
134
|
```
|
|
135
135
|
4. **Confirm the children exist** rather than trusting the report — the worker's IDs are a claim, the bead graph is the evidence:
|
|
136
136
|
```bash
|
|
@@ -157,8 +157,8 @@ Chores already exist, so dispatch never creates one:
|
|
|
157
157
|
- Parent task description from `bd show <parent-id>`
|
|
158
158
|
- For `fix` stage: `FILES_CHANGED` from the preceding stage's report (the required changes come from the fix bead's own description — read via `bd show <fix-id>`)
|
|
159
159
|
- For `verify`, `review`, `document` stages: `SUMMARY` and `FILES_CHANGED` from the preceding stage's report
|
|
160
|
-
-
|
|
161
|
-
7. **Call the {{TOOL:task}} tool** with the filled prompt as `prompt` and the worktree path as `cwd`. Store the returned agentId — you need it for step 8 and for
|
|
160
|
+
- The trail pointer: the worker records its own entries with `<run-pipeline-stage-scripts>/trail-entry.mjs`, so the prompt names that script rather than a log file (see _Reading the Trail_ below)
|
|
161
|
+
7. **Call the {{TOOL:task}} tool** with the filled prompt as `prompt` and the worktree path as `cwd`. Store the returned agentId — you need it for step 8 and for correlating the completion to its bead.
|
|
162
162
|
|
|
163
163
|
If this session has no subagent tool, **stop and say so**. Do not substitute a backgrounded CLI run (`pi -p … &`): that launch has no completion signal, so a finished worker and a killed one look identical, which is how stages came to be reported complete over uncommitted trees. Record the bead for the shutdown summary and tell the human the session needs restarting with the subagent extension loaded.
|
|
164
164
|
8. **Record the base commit** before the worker starts — you verify delivery against it (_Verified Completion_ below):
|
|
@@ -175,7 +175,7 @@ A worker's `---REPORT---` is a claim about the worktree, not evidence of it. Ver
|
|
|
175
175
|
node scripts/lib/verify-worker-tree.mjs --worktree .agent-cortex/worktrees/<parent-id> --base <base>
|
|
176
176
|
```
|
|
177
177
|
|
|
178
|
-
Exit code 0 means a clean tree with at least one commit ahead of the recorded base. A non-zero exit reports `worktree is dirty` or `no commit ahead of base`, and the stage did **not** deliver whatever its `OUTCOME` line says. Re-dispatch the same chore, or record the failure for the fix chain — never advance the parent, close the chore, or open a feature PR over uncommitted work.
|
|
178
|
+
Exit code 0 means a clean tree with at least one commit ahead of the recorded base. A non-zero exit reports `worktree is dirty` or `no commit ahead of base`, and the stage did **not** deliver whatever its `OUTCOME` line says. Re-dispatch the same chore, or record the failure for the fix chain — never advance the parent, close the chore, or open a feature PR over uncommitted work. `post-merge` and `housekeeping` deliver no commit, so this check does not apply to them.
|
|
179
179
|
|
|
180
180
|
---
|
|
181
181
|
|
|
@@ -204,6 +204,26 @@ For each parent task bead (`<parent-id>`):
|
|
|
204
204
|
|
|
205
205
|
---
|
|
206
206
|
|
|
207
|
+
## Post-merge Check
|
|
208
|
+
|
|
209
|
+
A merge is not verified by merging. The `stage:post-merge` chore reads the CI run that GitHub started for the merge commit on the base branch and reports whether it passed. `create-chores.mjs` pre-creates that chore, labelled `lifecycle:post-merge` and blocked behind the same `lifecycle:feature-pr` gate as `stage:housekeeping`, so it stays out of `bd ready` until a human closes the gate.
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
node skills/workflow/ralph/scripts/post-merge-check.mjs
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
The script is deterministic: no model, no tokens, zero dependencies, async only. It prints a JSON report and exits `0` when every run for the commit passed, `1` when a run failed, and `2` when the result is inconclusive.
|
|
216
|
+
|
|
217
|
+
- **Executor.** A dispatched worker runs it from the **main checkout**, like the housekeeping stage: the commit it reads landed on the base branch, not on the feature branch. It resolves the repository slug and base branch through `gh repo view`, so nothing about this repo's slug or `main` is baked in. `--base <branch>` and `--commit <sha>` override each; the wait window is `--wait <seconds>`, default 300. Any revision either flag supplies is expanded through `git rev-parse` first, because `gh run list --commit` matches the full 40-character SHA only — a caller that reaches `gh` with a short SHA gets an empty run list, which reads as `inconclusive` rather than as an error.
|
|
218
|
+
- **Exit `1` files one P1 escalation per open bead.** The bead carries the run URL and the failing job name, is labelled `lifecycle:post-merge-escalation` + `execution:hitl`, and carries a `post-merge-run:<id>` marker in its description. A second run finds the open bead carrying that marker and files nothing. The escalation has no parent, so the feature bead is never reopened and never gains a child.
|
|
219
|
+
- **The marker bounds open beads, not runs — a known limitation.** The marker is searched with `bd list`, which omits closed beads. A run id is immutable, so one bead per run forever is the intent, but the code delivers one **open** bead per run: once a human closes the escalation, a later check of the same run files a fresh bead for it. That repeats a page nobody wants; it never drops a signal. Fixing it means searching closed beads too, which is its own change.
|
|
220
|
+
- **Nothing is retried.** The stage never re-runs the workflow, never retries the check for a different answer, never reverts the merge, and builds no `stage:fix` chain — a commit that already merged is not repaired by a fix round. The chore stays **open** on exit `1` and `2`.
|
|
221
|
+
- **GitHub-only in v1.** A repository `gh` cannot resolve to a GitHub host, or a commit with no run on the base branch, is `inconclusive`: reported, never escalated.
|
|
222
|
+
- **A run still in flight is not a verdict.** The wait window belongs to the check; the wait past it belongs to the harness, and the stage playbook's _Watch transport_ section is the single place the per-harness mechanism is named. A session with no watcher runs the check once with a longer window instead — that fallback is prose, so it survives both renders.
|
|
223
|
+
- **Ordering against housekeeping.** The two stages are siblings on the gate and neither waits on the other. Chaining `housekeeping` behind `post-merge` was rejected: an inconclusive check that nobody resolves would strand the worktree reclamation housekeeping exists to do. Putting `post-merge` after `housekeeping` was rejected too — it would make the pruner a dependency of a check that reads GitHub state, which no worktree removal can affect.
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
207
227
|
## Pruning Worktrees
|
|
208
228
|
|
|
209
229
|
A worktree outlives its bead: ralph leaves the directory behind when a feature closes, and a crashed run leaves one with no bead at all. `skills/workflow/ralph/scripts/prune-worktrees.mjs` converges `.agent-cortex/worktrees/*` with bead state.
|
|
@@ -250,7 +270,7 @@ When the `document` chore for a parent task completes:
|
|
|
250
270
|
4. Find the child HITL task bead for this parent with label `lifecycle:feature-pr` and `execution:hitl` (created by `create-task`).
|
|
251
271
|
5. Update that HITL bead with the PR URL/status (comment or note) so the reviewer has the link.
|
|
252
272
|
6. Do not schedule new parent features while this gate is **unblocked** — an unblocked gate is a feature genuinely awaiting a human merge (see _Gate predicate_ below).
|
|
253
|
-
7. Once the PR is merged, a human closes the HITL PR gate bead, which unblocks the scheduling brake **and** the pre-created `stage:housekeeping`
|
|
273
|
+
7. Once the PR is merged, a human closes the HITL PR gate bead, which unblocks the scheduling brake **and** the pre-created `stage:post-merge` and `stage:housekeeping` chores. After that, close the parent feature bead:
|
|
254
274
|
```bash
|
|
255
275
|
bd close <parent-id>
|
|
256
276
|
```
|
|
@@ -300,24 +320,32 @@ Ralph never builds remediation. The worker that fails a stage creates its own `s
|
|
|
300
320
|
|
|
301
321
|
---
|
|
302
322
|
|
|
303
|
-
##
|
|
323
|
+
## Reading the Trail
|
|
304
324
|
|
|
305
|
-
|
|
325
|
+
Ralph never tails a file. The worker writes its own bead trail at each transition through
|
|
326
|
+
`skills/workflow/run-pipeline-stage/scripts/trail-entry.mjs`, so progress is read from the bead:
|
|
306
327
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
328
|
+
```bash
|
|
329
|
+
node <run-pipeline-stage-scripts>/recover-brief.mjs --bead <chore-id> # status, newest entries, refs, blockers, acceptance — bounded
|
|
330
|
+
bd show <chore-id> # every checkpoint entry: Outcome, Evidence, What is left
|
|
331
|
+
bd provenance log <chore-id> # the refs it recorded: git-sha, branch, pr, work-id
|
|
332
|
+
bd children <parent-id> # the whole chain's state, closed included
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
The brief is the first read: `bd show` returns the whole note — 89 lines and about 6.5 KB for a
|
|
336
|
+
bare bead carrying 20 entries, about 120 lines and about 8 KB for a realistic one with acceptance
|
|
337
|
+
criteria, a blocker, provenance refs and a description — and Ralph reads the tail, not the
|
|
338
|
+
history. Widen the brief's window (`--trail`) only when it left out the entry you need.
|
|
339
|
+
|
|
340
|
+
There is nothing to flush and no bookmark to keep in `state.json`: the record the orchestrator used
|
|
341
|
+
to poll is now the record the worker writes directly, so the per-parent log file and the line
|
|
342
|
+
counter that fed its tail are gone. On each completion:
|
|
343
|
+
|
|
344
|
+
1. **Read the completed worker's bead**: `bd show <chore-id>` for its entries, and
|
|
345
|
+
`bd provenance log <chore-id>` for the commit, branch and PR it recorded.
|
|
346
|
+
2. **Post a chat summary** of what is new since the last report for that bead — the stage
|
|
347
|
+
transition, the test and lint results, and the close reason. Not the entries verbatim.
|
|
348
|
+
3. **Post nothing when nothing new arrived** — do not spam the chat with empty polls.
|
|
321
349
|
|
|
322
350
|
---
|
|
323
351
|
|
|
@@ -389,6 +417,76 @@ When nothing is in flight AND nothing is dispatchable — `inflight` holds no en
|
|
|
389
417
|
|
|
390
418
|
---
|
|
391
419
|
|
|
420
|
+
## Peer messaging (planner ↔ ralph)
|
|
421
|
+
|
|
422
|
+
The planner session (`plan` skill) and ralph are separate sessions with no shared terminal. Delivery, discovery, idle wake and the message ledger all belong to the harness's own messaging mechanism — the published **pi-peer** package on PI, a built-in facility on Claude Code. **Never build a registry, inbox, watcher or transport in its place** — this section defines only the message vocabulary and the reply rule that sit on top of it, and it is the single place both skills read.
|
|
423
|
+
|
|
424
|
+
### Transport
|
|
425
|
+
|
|
426
|
+
Send with {{TOOL:peer_send}}; list the peers you can reach with {{TOOL:peer_list}}, and address a peer only by the name or address that list reports. Where the mechanism provides a per-message intent, an idle subscription or a reply wait, use it — but never make a send depend on one.
|
|
427
|
+
|
|
428
|
+
Send from a scripted call, **never** by typing text into a busy terminal session: during the `pi-peer` package's adoption spike, stray keystrokes aimed at a running session were queued as user prompts twice, so the receiving agent read them as new instructions.
|
|
429
|
+
|
|
430
|
+
A send does not prove receipt. An inbound message may be held for approval before the receiving agent sees it, and delivery stays unconfirmed until the receiver answers.
|
|
431
|
+
|
|
432
|
+
The mechanism is supplied by the harness, and neither harness is forced into the other's: a PI session takes it from the `pi-peer` package runbook below, and a Claude Code session has it built in. **Only this subsection differs between the two installs** — the message kinds and the acknowledgement rule that follow are the same bytes on both, so a message means the same thing whichever mechanism carries it.
|
|
433
|
+
|
|
434
|
+
### The `pi-peer` package (PI)
|
|
435
|
+
|
|
436
|
+
A PI session gets its transport from this package, which a consuming repository installs itself — it is never a dependency of this package. Its `peer_get` and `peer_await` tools have no Claude Code counterpart, so they are named here and nowhere else in this convention.
|
|
437
|
+
|
|
438
|
+
#### Adoption in a consuming repository
|
|
439
|
+
|
|
440
|
+
1. **Install once per repository:** `pi install npm:@cryptolibertus/pi-peer`.
|
|
441
|
+
2. **List both roles** in `.pi/peers.json`: `planner` and `ralph`. The peer list is configuration, so both entries are required — see _Discovery is two-way only when both roles are listed_ below.
|
|
442
|
+
3. **Start each session with its own identity:** `PI_PEER_ID=planner pi` for the planning session, `PI_PEER_ID=ralph pi` for ralph. The file holds a default `localPeerId`, and `PI_PEER_ID` overrides it for the session. Confirm with `peer_list` — it reports `1 online - <peer id>` when discovery works.
|
|
443
|
+
4. **Send and read with the package's own tools:** `peer_send` (or `/peer send <id> "<text>" --intent <intent> --no-await`), `peer_list`, `peer_get` for a peer's current state and `peer_await` for a reply.
|
|
444
|
+
5. **Ignore the state files** the package writes — see _Files a consuming repository gains_ below.
|
|
445
|
+
|
|
446
|
+
#### Discovery is two-way only when both roles are listed
|
|
447
|
+
|
|
448
|
+
`.pi/peers.json` is a **static peer list, not a live registry**: the runtime merges `.pi/settings.json`, `.pi/peers.json` and the `PI_PEER_*` environment, then polls only the peers the file names. `/peer setup` never overwrites an existing `.pi/peers.json`, so a setup run in the second session leaves the file alone.
|
|
449
|
+
|
|
450
|
+
A single-entry file therefore makes discovery one-way: the listed peer sees the other as online, while the unlisted peer reports `no peers` and its sends write no message record. That is configuration, not a fault. The missing step is `/peer setup --peer <peer-id>`, which adds a trusted peer to the existing file. Two entries plus `PI_PEER_ID` per session is the whole setup — and because a send is not proof of receipt, a message stays unverified until the receiver answers.
|
|
451
|
+
|
|
452
|
+
#### Files a consuming repository gains
|
|
453
|
+
|
|
454
|
+
| File | Purpose | Tracked? |
|
|
455
|
+
|------|---------|----------|
|
|
456
|
+
| `.pi/peers.json` | the peer list plus the default `localPeerId` that `PI_PEER_ID` overrides | no |
|
|
457
|
+
| `.pi/peer-messages.json` | message records with source, target, status and conversation id | no |
|
|
458
|
+
| `.pi/peer-control-ledger.jsonl` | the control ledger | no |
|
|
459
|
+
|
|
460
|
+
All three are machine state, not tracked content. Tell the consuming repository to gitignore them: ignore `.pi/peers.json` **and** `.pi/peer-*` together. The literal path covers the peer list, which the glob does not match, and the glob covers the message and ledger files plus any further `peer-*.json` the package adds.
|
|
461
|
+
|
|
462
|
+
### Message kinds
|
|
463
|
+
|
|
464
|
+
The kinds map **onto** the transport's own intents where it has them (PI's `pi-peer`: `ask`, `review`, `notify`, `coordinate`, `task`); they never replace or re-implement the transport protocol.
|
|
465
|
+
|
|
466
|
+
| Kind | Carries | Maps to intent | Direction |
|
|
467
|
+
|------|---------|----------------|-----------|
|
|
468
|
+
| `handoff` | bead id + one-line scope | `task` | planner → ralph |
|
|
469
|
+
| `done` | bead id + run summary | `notify` | ralph → planner |
|
|
470
|
+
| `blocked` | gate bead id awaiting a human | `notify` | ralph → planner |
|
|
471
|
+
| `failed` | bead id + reason | `notify` | ralph → planner |
|
|
472
|
+
| `update` | bead id + stage progress | `notify` | ralph → planner |
|
|
473
|
+
| `get` | peer id, asks for current state | `coordinate` | either → either |
|
|
474
|
+
| `ask` | question text, needs an answer | `ask` | either → either |
|
|
475
|
+
|
|
476
|
+
Write the kind as the first word of the message text, e.g. `handoff agnt-ctx-abc.1 — implement the retry budget`.
|
|
477
|
+
|
|
478
|
+
### Acknowledgement rule
|
|
479
|
+
|
|
480
|
+
Statuses stay `running` or `queued` until the receiver answers, so **a sender cannot tell "delivered" from "done"**. Every inbound `handoff`, `get` and `ask` must be answered with exactly one of three reply kinds:
|
|
481
|
+
|
|
482
|
+
- `done` — carried out; name the bead id and give a one-line result (for `get`, the requested state).
|
|
483
|
+
- `blocked` — cannot proceed; name the gate bead that is waiting for a human.
|
|
484
|
+
- `failed` — could not be carried out; name the bead and the reason.
|
|
485
|
+
|
|
486
|
+
`update`, `done`, `blocked` and `failed` are reports, so they need no reply. Never leave a `handoff`, `get` or `ask` unanswered — reply that it is queued rather than staying silent.
|
|
487
|
+
|
|
488
|
+
---
|
|
489
|
+
|
|
392
490
|
## State Files
|
|
393
491
|
|
|
394
492
|
Status is read from beads — there is no rendered status document to build or refresh:
|
|
@@ -401,8 +499,7 @@ bd show <id> # full detail, including the worker's notes
|
|
|
401
499
|
|
|
402
500
|
| File | Purpose |
|
|
403
501
|
|------|---------|
|
|
404
|
-
| `.agent-cortex/ralph/state.json` | Runtime bookkeeping only: the in-flight agent-ID→bead mapping. Shape: `{ "inflight": [ { "kind": "stage", "choreId": "...", "parentId": "...", "title": "...", "agentId": "..."
|
|
405
|
-
| `.agent-cortex/ralph/ralph-*.log` | Per-parent log files written by subagents — every stage chore of a feature appends to `.agent-cortex/ralph/ralph-<parent-id>.log` (e.g. `ralph-abc-123.log`). |
|
|
502
|
+
| `.agent-cortex/ralph/state.json` | Runtime bookkeeping only: the in-flight agent-ID→bead mapping. Shape: `{ "inflight": [ { "kind": "stage", "choreId": "...", "parentId": "...", "title": "...", "agentId": "..." } ] }`. A decomposition worker is recorded with `"kind": "decompose"` and an expansion worker with `"kind": "expand"`, both with `"choreId": null` — they hold no chore but still occupy the wait set and the in-flight check. Initialise at startup with `{ "inflight": [] }`. Update after every dispatch and every agent completion. **Never hand-edit.** |
|
|
406
503
|
|
|
407
504
|
All task state is derived from beads; `state.json` holds only runtime bookkeeping:
|
|
408
505
|
|