@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
@@ -0,0 +1,124 @@
1
+ ---
2
+ name: implement-task
3
+ description: Owns the task itself — read the acceptance criteria, prove each one with evidence, stay in scope, and take only pragmatic boy-scout fixes. Use when implementing a tracked task, bead, or ticket that lists acceptance criteria, or when you must show each criterion is met before reporting done.
4
+ ---
5
+
6
+ # Implement Task
7
+
8
+ `tdd` owns the red-green loop and the `style-*` skills own the standards. This skill owns the task
9
+ around them: what the criteria are, what you touch, and what you can prove when you finish.
10
+
11
+ ## When to use
12
+
13
+ - Implementing a tracked task, bead, or ticket that carries acceptance criteria.
14
+ - Picking up a pipeline stage chore that must report against a criteria list.
15
+ - Asked to "implement this task", "work this bead", or "prove the criteria are met".
16
+
17
+ ## When NOT to use
18
+
19
+ - The loop inside one criterion — use `tdd`, which this skill invokes per criterion.
20
+ - Writing or reviewing a test itself — use `style-tests`.
21
+ - Naming, module boundaries, or refactoring shape — use `style-code`.
22
+ - Deciding what the work should be — use `plan` or `create-task` first.
23
+
24
+ ## Workflow
25
+
26
+ 1. **Read the criteria.** Extract the task's `## Acceptance Criteria` verbatim and complete. A task
27
+ with no criteria is not implementable: stop, and either ask for them or file the gap as a bead.
28
+ 2. **Build the table.** Write the criteria → evidence table before touching code (see
29
+ _Criteria to evidence_). A criterion you cannot name a check for is a blocker, not a guess.
30
+ 3. **Take one criterion at a time.** Drive it through `tdd`, with `style-tests` for the test and
31
+ `style-code` for the code. Fill its row when it lands, not at the end.
32
+ 4. **Hold the scope.** Change only what a criterion needs (see _Scope rule_). Record every
33
+ out-of-scope finding as a bead and change nothing for it.
34
+ 5. **Fix inside touched files only.** Apply the _Boy-scout rule_ to anything you notice outside the
35
+ criteria.
36
+ 6. **Finish.** Re-read the criteria, re-run every check the table names, apply the _Mutation check_
37
+ to each test that claims a behaviour, and report the filled table.
38
+
39
+ ## Criteria to evidence
40
+
41
+ The table is the contract between the request and the diff. Write it first, fill it last.
42
+
43
+ | # | Criterion (quoted from the task) | Check that proves it | Status |
44
+ |---|---|---|---|
45
+ | 1 | <criterion> | <command, test name, or commit> | todo / done / blocked |
46
+ | 2 | <criterion> | <command, test name, or commit> | todo / done / blocked |
47
+
48
+ - **A criterion with no check is a blocker, not a guess.** An unproven criterion is unfinished.
49
+ Name the criterion and the reason before writing code, rather than inventing a plausible proof
50
+ afterwards.
51
+ - **Evidence is observable.** A test named for the behaviour, the command and its result, or a
52
+ commit for a documentation change. Never "verified manually" or "looks right".
53
+ - **One row per criterion.** Fold a criterion you cannot separate from another into that row
54
+ instead of splitting it into vague halves.
55
+ - **The filled table is the report.** Hand back one row per criterion with its evidence. A claim
56
+ with no check in its row does not ship.
57
+
58
+ ## Scope rule
59
+
60
+ - **Change only what a criterion needs.** Each line in the diff answers a row in the table, or is
61
+ the smallest thing that lets a row pass.
62
+ - **An out-of-scope finding becomes a bead, not an edit.** Name what you found, where it is, and
63
+ why the task does not own it. File it, then continue.
64
+ - **A conflict is a stop, not a judgement call.** When a criterion needs a change that contradicts
65
+ another criterion or an existing rule, report both and stop. Do not pick one silently.
66
+ - **No drive-by refactor.** Rename, restructure, or reformat nothing the task did not require,
67
+ however tempting the neighbouring code looks.
68
+
69
+ ## Boy-scout rule
70
+
71
+ A boy-scout fix is allowed only inside a file the task already touches, and only when it serves the
72
+ task:
73
+
74
+ - **Serves the task** — it makes this criterion's own change correct or readable: a name the change
75
+ made wrong, a helper the criterion needs, a comment the change invalidated.
76
+ - **Serves nobody** — it improves code the criterion never reaches: an unrelated dead branch, a
77
+ different module's naming, a missing test elsewhere. File it and leave it.
78
+ - **Cost gate.** A fix must cost less to read in the diff than to follow in a bead. When in doubt,
79
+ file the bead.
80
+
81
+ ## Mutation check
82
+
83
+ `style-tests` owns the mutation-check rule; this skill applies it at Finish:
84
+
85
+ - Apply it to every test the task added or changed that claims a behaviour.
86
+ - Record the outcome in the evidence row of the criterion that test proves — which behaviour you
87
+ removed, which test failed, and that the suite is green again after restore.
88
+ - A claimed behaviour whose named test still passes under mutation leaves its criterion `blocked`,
89
+ whatever the suite says.
90
+
91
+ ## Red Flags
92
+
93
+ - Red flag: writing code before the criteria → evidence table exists.
94
+ - Red flag: a row whose evidence is a restated criterion instead of a check.
95
+ - Red flag: a diff line that no row in the table needs.
96
+ - Red flag: reporting `done` over a table with `todo` rows.
97
+ - Red flag: a "quick tidy" in a file the task never opens.
98
+
99
+ ## Common Rationalizations
100
+
101
+ | Rationalization | Rebuttal |
102
+ |---|---|
103
+ | "The criteria are obvious, the table is overhead" | The table is the only place the request and the diff meet. Without it, the checklist is memory and the report is opinion. |
104
+ | "This nearby bug is a two-line fix" | Then it is a two-line bead. Fixing it here hides an unrequested change inside a requested one. |
105
+ | "The test passes, so the criterion is proven" | A test that passes under mutation proves nothing. Run the mutation check or say the row is unproven. |
106
+ | "I refactored a little, it is still the same change" | It is a second change wearing the first one's name. Split it. |
107
+
108
+ ## See Also
109
+
110
+ - [tdd](../tdd/SKILL.md) — the red-green loop you run once per criterion.
111
+ - [style-tests](../../style/style-tests/SKILL.md) — assertion strategy, plus the mutation-check rule this skill applies.
112
+ - [style-code](../../style/style-code/SKILL.md) — the conventions the code under each criterion must meet.
113
+ - [run-pipeline-stage](../../workflow/run-pipeline-stage/SKILL.md) — the stage runner whose `code` playbook invokes this skill first.
114
+
115
+ ## Verification checklist
116
+
117
+ - [ ] Criteria → evidence table written before the first code change, one row per criterion
118
+ - [ ] No criterion left without a named check; any such gap reported as a blocker instead of guessed
119
+ - [ ] Each criterion taken through `tdd`; test and code conventions delegated to `style-tests` and `style-code`
120
+ - [ ] Every diff line traces to a table row; out-of-scope findings filed as beads, not edited
121
+ - [ ] Boy-scout fixes confined to files the task touches and to what the task needs
122
+ - [ ] Every check in the table re-run at Finish, with its result recorded
123
+ - [ ] Mutation check applied to each test claiming a behaviour, outcome recorded per row
124
+ - [ ] Table reported in full, with evidence per row
@@ -13,6 +13,7 @@ description: Test-driven development with red-green-refactor loop. Use when buil
13
13
 
14
14
  ## When NOT to use
15
15
 
16
+ - Owning the task itself — criteria, scope, and proof of each criterion — is [implement-task](../implement-task/SKILL.md); load it first and return here for the loop inside a criterion.
16
17
  - Prototyping, throwaway code, or exploratory spikes where speed of discovery matters more than correctness.
17
18
  - Changes so trivial that tests would duplicate type or lint coverage (rename, pure data shuffle).
18
19
  - When the test-to-implementation ratio would be absurdly high — a one-line change requiring extensive test setup.
@@ -93,6 +94,7 @@ After all tests pass, run `style-code` to guide the refactoring shape:
93
94
 
94
95
  ## See Also
95
96
 
97
+ - [implement-task](../implement-task/SKILL.md) — it owns the acceptance criteria, the scope and boy-scout rules, and the per-criterion evidence table that this loop fills one row at a time.
96
98
  - [style-tests](../../style/style-tests/SKILL.md) — load before writing the first test; it owns assertion strategy, mock discipline, and test-data construction.
97
99
  - [style-code](../../style/style-code/SKILL.md) — load at the refactor step for naming and module boundaries.
98
100
  - [grill-with-docs](../grill-with-docs/SKILL.md) — load when domain vocabulary is unsettled, so test names and seams use the project's own language.
@@ -16,12 +16,20 @@ node <skill-scripts>/create-chores.mjs --parent <bead-id> [--priority <0-4>]
16
16
 
17
17
  ## Stage chore beads
18
18
 
19
- One per stage in `pipeline.json` (currently `code`, `verify`, `review`, `document`):
19
+ One per stage in `pipeline.json` (currently `code`, `verify`, `review`, `document`, `post-merge`, `housekeeping`):
20
20
 
21
21
  - **Type**: `chore`
22
22
  - **Title**: `[<parent-id>] <stage title>` (e.g. `[abc-123] Code`)
23
- - **Labels**: `stage:<id>` (e.g. `stage:code`, `stage:verify`)
24
- - **Dependencies**: `parent-child` to the parent bead; `blocks` between stages per each stage's `dependsOn` (the script requires `dependsOn` targets to appear earlier in `pipeline.json`).
23
+ - **Description**: the stage's `description` text, carried verbatim. Expansion stores it and does nothing else with it: it never substitutes the `<ralph-scripts>` placeholder and never runs the backticked `node …` command inside it. Treat a description as text for the stage worker to read, never as something expansion acts on.
24
+ - **Labels**: `stage:<id>` (e.g. `stage:code`, `stage:verify`), plus any `labels` the stage declares — `post-merge` adds `lifecycle:post-merge`, `housekeeping` adds `lifecycle:housekeeping`
25
+ - **Dependencies**: `parent-child` to the parent bead; `blocks` between stages per each stage's `dependsOn` (the script requires `dependsOn` targets to appear earlier in `pipeline.json`)
26
+
27
+ **A stage may wait on the gate instead of a stage.** The reserved `dependsOn` target
28
+ `featurePrReview` means "blocked behind the PR gate bead". The gate is not a stage — it is created
29
+ after them — so the script wires those stages to it in a second pass, once the gate exists. Both
30
+ `post-merge` and `housekeeping` use it: each chore must not become ready until a human has merged
31
+ the PR and closed the gate, and the `blocks` edge keeps it out of `bd ready` while the feature is in
32
+ flight. The gate itself still waits on the last stage that does not wait on the gate (`document`).
25
33
 
26
34
  ## PR gate bead (`featurePrReview`)
27
35
 
@@ -87,9 +95,18 @@ The deliverable this feature produces and the problem it solves.
87
95
  | verify | Verify | code |
88
96
  | review | Review | verify |
89
97
  | document | Document | review |
98
+ | post-merge | Post-merge check | `featurePrReview` — the PR gate bead, not a stage |
99
+ | housekeeping | Housekeeping | `featurePrReview` — the PR gate bead, not a stage |
90
100
 
91
101
  `maxFixRounds: 4` — the maximum fix-retry rounds allowed for a stage's verify loop before the PR gate. Adjust stages only in `pipeline.json`; the script and templates follow it.
92
102
 
103
+ `post-merge` and `housekeeping` both wait on the gate and on nothing else: they become ready
104
+ together when a human closes it. Neither waits on the other. Chaining housekeeping behind
105
+ `post-merge` was rejected because a post-merge check that stays open — an inconclusive run nobody
106
+ resolves — would then strand the worktree reclamation housekeeping exists to do. Putting
107
+ `post-merge` after `housekeeping` was rejected too: it would make the pruner a dependency of a
108
+ check that reads GitHub state, which no worktree removal can affect.
109
+
93
110
  ## Stage templates
94
111
 
95
112
  `templates/` holds one prompt skeleton per stage; a dispatcher fills one to compose that stage's
@@ -141,7 +141,7 @@ Steps 2–6 create beads; step 7 expands one. A bead that already exists but has
141
141
  Input: title `Add export API to invoices`, description `…`, priority `2`, parent `feature-456`. Output: the bead ID plus the script's JSON mapping stage IDs → chore bead IDs, with `featurePrReview` as the HITL PR gate:
142
142
 
143
143
  ```json
144
- {"code": "abc123", "verify": "def456", "review": "ghi789", "document": "jkl012", "featurePrReview": "bd-mno345"}
144
+ {"code": "abc123", "verify": "def456", "review": "ghi789", "document": "jkl012", "post-merge": "mno345", "housekeeping": "pqr678", "featurePrReview": "bd-stu901"}
145
145
  ```
146
146
 
147
147
  Feature path input: "several endpoints, a UI, and a migration". Output: `feature-456` (`execution:hitl`), `plan-457` ("Plan: …", `execution:hitl`, owned by the feature, blocking every child), and one `execution:afk` impl child per deliverable, each expanded into its own stage chores plus PR gate.
@@ -28,6 +28,22 @@
28
28
  "description": "Review changes and update documentation if key decisions, behaviour changes, or constraints were introduced.",
29
29
  "template": "templates/document.md",
30
30
  "dependsOn": ["review"]
31
+ },
32
+ {
33
+ "id": "post-merge",
34
+ "title": "Post-merge check",
35
+ "description": "Run `node <ralph-scripts>/post-merge-check.mjs` from the main checkout. It reads `gh run list` for the base branch's merge commit and exits 0 when every run passed, 1 when a run failed, and 2 when the result is inconclusive. Exit 0: report the run URLs and close. Exit 1: the script has already filed one P1 escalation bead carrying the run URL and the failing job — report its id, leave this chore open, and close nothing else. Exit 2: report the reason it printed and leave this chore open. Never retry, never re-run the workflow, never reopen the parent.",
36
+ "template": "templates/post-merge.md",
37
+ "labels": ["lifecycle:post-merge"],
38
+ "dependsOn": ["featurePrReview"]
39
+ },
40
+ {
41
+ "id": "housekeeping",
42
+ "title": "Housekeeping",
43
+ "description": "Remove the worktrees of closed beads. Run `node <ralph-scripts>/prune-worktrees.mjs` from the main checkout and retry once if it exits non-zero. The exit status is 1 whenever any worktree survives — an open bead counts as survival — so fail this chore only when a closed-bead worktree is still refused after the retry, and leave it open. Never close it over a worktree the script did not remove.",
44
+ "template": "templates/housekeeping.md",
45
+ "labels": ["lifecycle:housekeeping"],
46
+ "dependsOn": ["featurePrReview"]
31
47
  }
32
48
  ]
33
49
  }
@@ -13,10 +13,11 @@
13
13
  //
14
14
  // Zero dependencies: run with `node create-chores.mjs --parent <bead-id>`.
15
15
 
16
- import { execSync } from 'node:child_process';
17
- import { readFileSync } from 'node:fs';
16
+ import { execFile } from 'node:child_process';
17
+ import { readFile } from 'node:fs/promises';
18
18
  import { resolve, dirname } from 'node:path';
19
19
  import { fileURLToPath } from 'node:url';
20
+ import { promisify } from 'node:util';
20
21
 
21
22
  // ─── Types ────────────────────────────────────────────────────────────────────
22
23
 
@@ -27,6 +28,7 @@ import { fileURLToPath } from 'node:url';
27
28
  * @property {string} description
28
29
  * @property {string} template
29
30
  * @property {string[]} dependsOn
31
+ * @property {string[]} [labels] Labels beside `stage:<id>`; e.g. `lifecycle:housekeeping`
30
32
  */
31
33
 
32
34
  /**
@@ -65,13 +67,20 @@ function parseArgs(args) {
65
67
  // Defaults to `bd`, resolved via PATH.
66
68
  const BD_PATH = process.env.BD_PATH || 'bd';
67
69
 
70
+ const execFileAsync = promisify(execFile);
71
+
68
72
  /**
73
+ * Run a command with its argv handed straight to the child process. The arguments are
74
+ * never joined into a command line for a shell: a stage description wraps a real command
75
+ * in backticks (`node <ralph-scripts>/post-merge-check.mjs`), and a shell would execute it
76
+ * and splice its output into the description instead of storing the text.
69
77
  * @param {string[]} args
70
- * @returns {string}
78
+ * @returns {Promise<string>}
71
79
  */
72
- function run(args) {
73
- const cmd = args[0] === 'bd' ? [BD_PATH, ...args.slice(1)] : args;
74
- return execSync(cmd.join(' '), { encoding: 'utf-8' }).trim();
80
+ async function run(args) {
81
+ const [command, ...rest] = args[0] === 'bd' ? [BD_PATH, ...args.slice(1)] : args;
82
+ const { stdout } = await execFileAsync(command, rest, { encoding: 'utf-8' });
83
+ return stdout.trim();
75
84
  }
76
85
 
77
86
  // ─── Existing children ────────────────────────────────────────────────────────
@@ -95,10 +104,10 @@ function run(args) {
95
104
  * is never adopted. When a role label appears more than once the first-listed child
96
105
  * wins, so a converged re-run is deterministic.
97
106
  * @param {string} parentId
98
- * @returns {Map<string, Adoptable>} Keyed by `stage:<id>` or `lifecycle:feature-pr`
107
+ * @returns {Promise<Map<string, Adoptable>>} Keyed by `stage:<id>` or `lifecycle:feature-pr`
99
108
  */
100
- function readAdoptable(parentId) {
101
- const raw = run(['bd', 'children', parentId, '--json']);
109
+ async function readAdoptable(parentId) {
110
+ const raw = await run(['bd', 'children', parentId, '--json']);
102
111
  /** @type {Array<{ id: string, labels?: string[], dependencies?: Dependency[] }>} */
103
112
  const children = raw === '' ? [] : JSON.parse(raw);
104
113
 
@@ -127,15 +136,20 @@ function readAdoptable(parentId) {
127
136
  * @param {string} beadId
128
137
  * @param {string} dependsOnId
129
138
  * @param {Set<string>} blocks Dependencies the bead already has; extended in place
139
+ * @returns {Promise<void>}
130
140
  */
131
- function wireBlocks(beadId, dependsOnId, blocks) {
141
+ async function wireBlocks(beadId, dependsOnId, blocks) {
132
142
  if (blocks.has(dependsOnId)) { return; }
133
- run(['bd', 'dep', 'add', beadId, dependsOnId, '--type', 'blocks']);
143
+ await run(['bd', 'dep', 'add', beadId, dependsOnId, '--type', 'blocks']);
134
144
  blocks.add(dependsOnId);
135
145
  }
136
146
 
137
147
  // ─── Main ─────────────────────────────────────────────────────────────────────
138
148
 
149
+ // Reserved `dependsOn` target: the PR gate bead. The gate is not a stage — it is
150
+ // created after them — so a stage that waits on it is wired in a second pass.
151
+ const GATE_ROLE = 'featurePrReview';
152
+
139
153
  const __filename = fileURLToPath(import.meta.url);
140
154
  const __dirname = dirname(__filename);
141
155
 
@@ -143,24 +157,28 @@ const { parentId, priority } = parseArgs(process.argv.slice(2));
143
157
 
144
158
  const pipelinePath = resolve(__dirname, '..', 'pipeline.json');
145
159
  /** @type {Pipeline} */
146
- const pipeline = JSON.parse(readFileSync(pipelinePath, 'utf-8'));
160
+ const pipeline = JSON.parse(await readFile(pipelinePath, 'utf-8'));
147
161
 
148
- const adoptable = readAdoptable(parentId);
162
+ const adoptable = await readAdoptable(parentId);
149
163
 
150
164
  /** @type {Map<string, string>} */
151
165
  const stageToBeadId = new Map();
152
166
 
167
+ /** Stages that wait on the gate rather than on another stage, held until it exists.
168
+ * @type {Array<{ beadId: string, blocks: Set<string> }>} */
169
+ const gateDependents = [];
170
+
153
171
  for (const stage of pipeline.stages) {
154
172
  const adopted = adoptable.get(`stage:${stage.id}`);
155
173
 
156
174
  const beadId = adopted
157
175
  ? adopted.id
158
- : run([
176
+ : await run([
159
177
  'bd', 'create', JSON.stringify(`[${parentId}] ${stage.title}`),
160
178
  '--type', 'chore',
161
179
  '--description', JSON.stringify(stage.description),
162
180
  '--priority', priority,
163
- '--labels', `stage:${stage.id}`,
181
+ '--labels', [`stage:${stage.id}`, ...(stage.labels ?? [])].join(','),
164
182
  '--parent', parentId,
165
183
  '--silent',
166
184
  ]);
@@ -171,22 +189,25 @@ for (const stage of pipeline.stages) {
171
189
  const blocks = adopted ? adopted.blocks : new Set();
172
190
 
173
191
  for (const depStageId of stage.dependsOn) {
192
+ if (depStageId === GATE_ROLE) { continue; }
174
193
  const depBeadId = stageToBeadId.get(depStageId);
175
194
  if (!depBeadId) {
176
195
  console.error(`Stage '${stage.id}' depends on '${depStageId}', but it has not been created yet. Check dependsOn ordering in pipeline.json.`);
177
196
  process.exit(1);
178
197
  }
179
- wireBlocks(beadId, depBeadId, blocks);
198
+ await wireBlocks(beadId, depBeadId, blocks);
180
199
  }
181
- }
182
200
 
183
- const finalStageId = pipeline.stages.at(-1)?.id;
184
- if (!finalStageId) {
185
- console.error('No pipeline stages found in pipeline.json.');
186
- process.exit(1);
201
+ if (stage.dependsOn.includes(GATE_ROLE)) { gateDependents.push({ beadId, blocks }); }
187
202
  }
188
203
 
189
- const documentStageBeadId = stageToBeadId.get('document') ?? stageToBeadId.get(finalStageId);
204
+ // The gate waits on the last stage that does not itself wait on the gate, so the
205
+ // chain stays acyclic once a stage can follow the gate.
206
+ const chainStageIds = pipeline.stages
207
+ .filter((stage) => !stage.dependsOn.includes(GATE_ROLE))
208
+ .map((stage) => stage.id);
209
+
210
+ const documentStageBeadId = stageToBeadId.get('document') ?? stageToBeadId.get(chainStageIds.at(-1) ?? '');
190
211
  if (!documentStageBeadId) {
191
212
  console.error('Could not resolve the final stage bead needed for PR gate dependency.');
192
213
  process.exit(1);
@@ -196,7 +217,7 @@ const adoptedGate = adoptable.get('lifecycle:feature-pr');
196
217
 
197
218
  const prGateBeadId = adoptedGate
198
219
  ? adoptedGate.id
199
- : run([
220
+ : await run([
200
221
  'bd', 'create', JSON.stringify(`[${parentId}] PR Review and Merge`),
201
222
  '--type', 'task',
202
223
  '--description', JSON.stringify(
@@ -218,7 +239,13 @@ const prGateBeadId = adoptedGate
218
239
 
219
240
  /** @type {Set<string>} */
220
241
  const gateBlocks = adoptedGate ? adoptedGate.blocks : new Set();
221
- wireBlocks(prGateBeadId, documentStageBeadId, gateBlocks);
242
+ await wireBlocks(prGateBeadId, documentStageBeadId, gateBlocks);
243
+
244
+ // Second pass: the stages that wait on the gate finally get their dep, now that the
245
+ // gate bead exists.
246
+ for (const { beadId, blocks } of gateDependents) {
247
+ await wireBlocks(beadId, prGateBeadId, blocks);
248
+ }
222
249
 
223
250
  /** @type {Record<string, string>} */
224
251
  const result = {
@@ -10,9 +10,14 @@
10
10
  ## Relevant files
11
11
  <list source files you read during context gathering>
12
12
 
13
- ## Progress log
14
- Write progress to: `.agent-cortex/ralph/ralph-<bead-id>.log`
15
- Log stage start, test results, build errors, and stage complete (see Progress Logging in run-pipeline-stage skill).
13
+ ## Trail entry
14
+ Record one entry at stage start and one at stage close through the shared writer (see Trail Entries in the `run-pipeline-stage` skill):
15
+ ```bash
16
+ node <run-pipeline-stage-scripts>/trail-entry.mjs \
17
+ --bead <bead-id> --stage code --outcome <done|blocked|partial> \
18
+ --evidence "<evidence>" --remaining "<what is left, or none>"
19
+ ```
20
+ Evidence for a code stage: the test and lint result, the stage commit, and any build error.
16
21
 
17
22
  ## Instructions
18
23
  Invoke the `style-code` skill before making any code changes.
@@ -10,9 +10,14 @@
10
10
  ## Files changed
11
11
  <FILES_CHANGED from the final coder/fixer REPORT>
12
12
 
13
- ## Progress log
14
- Write progress to: `.agent-cortex/ralph/ralph-<bead-id>.log`
15
- Log stage start and stage complete (see Progress Logging in run-pipeline-stage skill).
13
+ ## Trail entry
14
+ Record one entry at stage start and one at stage close through the shared writer (see Trail Entries in the `run-pipeline-stage` skill):
15
+ ```bash
16
+ node <run-pipeline-stage-scripts>/trail-entry.mjs \
17
+ --bead <bead-id> --stage document --outcome <done|blocked|partial> \
18
+ --evidence "<evidence>" --remaining "<what is left, or none>"
19
+ ```
20
+ Evidence for a document stage: the files updated and the stage commit.
16
21
 
17
22
  ## Instructions
18
23
  Invoke the `style-documentation` skill before making any documentation changes.
@@ -7,9 +7,14 @@
7
7
  ## Files to update
8
8
  <FILES_CHANGED from the previous coder/fixer's REPORT>
9
9
 
10
- ## Progress log
11
- Write progress to: `.agent-cortex/ralph/ralph-<bead-id>.log`
12
- Log stage start, test results, build errors, and stage complete (see Progress Logging in run-pipeline-stage skill).
10
+ ## Trail entry
11
+ Record one entry at stage start and one at stage close through the shared writer (see Trail Entries in the `run-pipeline-stage` skill):
12
+ ```bash
13
+ node <run-pipeline-stage-scripts>/trail-entry.mjs \
14
+ --bead <bead-id> --stage fix --outcome <done|blocked|partial> \
15
+ --evidence "<evidence>" --remaining "<what is left, or none>"
16
+ ```
17
+ Evidence for a fix stage: the test result after the repair, and the commit.
13
18
 
14
19
  ## Instructions
15
20
  Invoke the `style-code` skill before making any code changes.
@@ -0,0 +1,69 @@
1
+ ## Project context
2
+ <bd prime output — verbatim>
3
+
4
+ ## Task
5
+ <bd show <id> output — verbatim>
6
+
7
+ ## Trail entry
8
+ Record one entry at stage start and one at stage close through the shared writer (see Trail Entries in the `run-pipeline-stage` skill):
9
+ ```bash
10
+ node <run-pipeline-stage-scripts>/trail-entry.mjs \
11
+ --bead <bead-id> --stage housekeeping --outcome <done|blocked|partial> \
12
+ --evidence "<evidence>" --remaining "<what is left, or none>"
13
+ ```
14
+ Evidence for a housekeeping stage: each pruner run, with its exit status.
15
+
16
+ ## Instructions
17
+ This stage removes worktrees, so **do not work in the bead worktree**. Run every command from the
18
+ repository's main checkout — the directory that holds `.agent-cortex/worktrees/` — because the
19
+ worktree this run may remove can be your own.
20
+
21
+ 1. **Run the pruner once** from the main checkout:
22
+ ```bash
23
+ node <ralph-scripts>/prune-worktrees.mjs
24
+ ```
25
+ `<ralph-scripts>` is the absolute path to `skills/workflow/ralph/scripts/`, resolved from wherever
26
+ that skill was loaded. Read the per-worktree report it prints — it lists every scanned worktree,
27
+ its classification (`closed-bead`, `open-bead`, `orphan`, `unclassified`) and its outcome
28
+ (`removed`, `kept`, `refused`).
29
+
30
+ 2. **Retry once on a non-zero exit.** Exit status `1` means at least one scanned worktree survived
31
+ the run, and an open bead counts as survival — a normal run against a live backlog exits `1`. So
32
+ run the same command a second time when the first run exits non-zero. One retry, never more.
33
+
34
+ 3. **Decide the outcome from the report, not the exit status.** After the retry:
35
+ - **SUCCESS** — no worktree classified `closed-bead` is left in `refused` state. Report every
36
+ worktree the run removed and every one it kept, with the printed reason.
37
+ - **BLOCKED** — a `closed-bead` worktree is still `refused`, or the pruner could not run at all
38
+ (missing node, unreadable directory). A refused closed-bead worktree is a worktree this run did
39
+ not remove, whatever the reason printed.
40
+
41
+ 4. **Never close this chore over a worktree the script did not remove.** This stage is the one
42
+ exception to the close-on-both-outcomes rule, because this stage is last in the chain and nothing
43
+ waits on its close. Close it only when every `closed-bead` worktree is gone; the worktree removal
44
+ is the only thing this stage completes.
45
+
46
+ On BLOCKED, **do not close the chore**. Leave it open — return it to `open` status if your claim
47
+ moved it to `in_progress` — report the surviving worktrees with the reasons the pruner printed,
48
+ and hand the chore back to a human. Create no `stage:fix` chore and no fresh `stage:housekeeping`
49
+ chore: there is nothing for a fix round to repair, and no later stage to re-block. A `kept` or
50
+ `refused` entry for an open bead is a normal report, not a failure.
51
+
52
+ Do not delete a worktree by hand, do not pass `--remove-orphans`, and do not edit the pruner. This
53
+ stage reports what the script did.
54
+
55
+ End your response with a ---REPORT--- block. The `run-pipeline-stage` skill's
56
+ `prompts/stage-runner.md` holds the authoritative REPORT contract — if this block and that prompt
57
+ differ, follow the prompt and correct this template.
58
+ ---REPORT---
59
+ BEAD_ID: <id>
60
+ STAGE_COMPLETED: housekeeping
61
+ SUMMARY: <2–3 sentences: worktrees removed, worktrees kept and why>
62
+ FILES_CHANGED: none
63
+ COMMIT: none
64
+ PUSHED: no
65
+ PR: none
66
+ OUTCOME: <SUCCESS|BLOCKED>
67
+ BLOCKING_ISSUES: ← include only if OUTCOME is BLOCKED; one line per issue
68
+ - <worktree path and the reason the pruner printed>
69
+ ---
@@ -0,0 +1,67 @@
1
+ ## Project context
2
+ <bd prime output — verbatim>
3
+
4
+ ## Task
5
+ <bd show <id> output — verbatim>
6
+
7
+ ## Trail entry
8
+ Record one entry at stage start and one at stage close through the shared writer (see Trail Entries in the `run-pipeline-stage` skill):
9
+ ```bash
10
+ node <run-pipeline-stage-scripts>/trail-entry.mjs \
11
+ --bead <bead-id> --stage post-merge --outcome <done|blocked|partial> \
12
+ --evidence "<evidence>" --remaining "<what is left, or none>"
13
+ ```
14
+ Evidence for a post-merge stage: the check exit status, the commit, and the escalation bead id when one was filed.
15
+
16
+ ## Instructions
17
+ This stage verifies the merge, so **do not work in the bead worktree**. Run the check from the
18
+ repository's main checkout — the directory that holds `.agent-cortex/worktrees/` — because the
19
+ commit it reads landed on the base branch, not on the feature branch.
20
+
21
+ 1. **Run the check once** from the main checkout:
22
+ ```bash
23
+ node <ralph-scripts>/post-merge-check.mjs
24
+ ```
25
+ `<ralph-scripts>` is the absolute path to `skills/workflow/ralph/scripts/`, resolved from wherever
26
+ that skill was loaded. It prints a JSON report and exits `0`, `1`, or `2`. Pass `--base <branch>`
27
+ only when the base branch is not the repository's default branch, and `--commit <sha>` only when
28
+ the merge commit is not the base branch's tip. A run still in flight when the wait window closes
29
+ is a report, not a verdict: the playbook's _Watch transport_ section says how to wait for it, and
30
+ which mechanism your harness supplies.
31
+
32
+ 2. **Act on the exit status alone.** The status *is* the verdict; do not re-run the check to get a
33
+ different answer, do not retry the workflow, and never re-run the GitHub workflow yourself.
34
+
35
+ | Exit | Meaning | What you do |
36
+ |---|---|---|
37
+ | `0` | Every run for the merge commit passed | Report the run URLs and **close** this chore as `SUCCESS` |
38
+ | `1` | A run for the merge commit failed | The script has already filed **one** P1 escalation bead carrying the run URL and the failing job. Report its id and the run URL, leave this chore **open**, and close nothing else |
39
+ | `2` | Inconclusive: no run for the commit, a run still in flight past the wait window, a conclusion that is neither pass nor failure, or a repository `gh` cannot see as GitHub | Report the `reason` the report printed and leave this chore **open**. Nothing was filed and nothing needs filing |
40
+
41
+ 3. **Never repair the merge from here.** Reverting, re-running a workflow, editing the merged
42
+ commit, and reopening the parent feature bead are all outside this stage. Exit `1` hands the
43
+ failure to a human through the escalation bead; the pipeline fixes nothing it did not build.
44
+
45
+ 4. **Build no remediation chain.** A failure here is evidence about a commit that already landed,
46
+ not a fault in a file a `stage:fix` round can repair. Create no `stage:fix` chore and no fresh
47
+ stage chore on any exit status, and re-block no later stage.
48
+
49
+ 5. **Close only on `0`.** On `1` and `2` leave the chore open — return it to `open` status if your
50
+ claim moved it to `in_progress` — so the open chore is the record that the merge is unresolved.
51
+ Do not close the parent feature bead and do not edit the escalation bead the script filed.
52
+
53
+ End your response with a ---REPORT--- block. The `run-pipeline-stage` skill's
54
+ `prompts/stage-runner.md` holds the authoritative REPORT contract — if this block and that
55
+ prompt differ, follow the prompt and correct this template.
56
+ ---REPORT---
57
+ BEAD_ID: <id>
58
+ STAGE_COMPLETED: post-merge
59
+ SUMMARY: <2–3 sentences: the exit status, the runs read, and what was filed or left open>
60
+ FILES_CHANGED: none
61
+ COMMIT: none
62
+ PUSHED: no
63
+ PR: none
64
+ OUTCOME: <SUCCESS on exit 0; BLOCKED on exit 1 or 2>
65
+ BLOCKING_ISSUES: ← include only if OUTCOME is BLOCKED; one line per reason
66
+ - <exit status, the report's reason, and the run URL or the surviving uncertainty>
67
+ ---
@@ -10,9 +10,14 @@
10
10
  ## Files changed
11
11
  <FILES_CHANGED from the coder/fixer's REPORT block>
12
12
 
13
- ## Progress log
14
- Write progress to: `.agent-cortex/ralph/ralph-<bead-id>.log`
15
- Log stage start, security scan result, review outcome, and stage complete (see Progress Logging in run-pipeline-stage skill).
13
+ ## Trail entry
14
+ Record one entry at stage start and one at stage close through the shared writer (see Trail Entries in the `run-pipeline-stage` skill):
15
+ ```bash
16
+ node <run-pipeline-stage-scripts>/trail-entry.mjs \
17
+ --bead <bead-id> --stage review --outcome <done|blocked|partial> \
18
+ --evidence "<evidence>" --remaining "<what is left, or none>"
19
+ ```
20
+ Evidence for a review stage: the security scan result and the review outcome.
16
21
 
17
22
  ## Instructions
18
23
  **First**, invoke the `review-security` skill and run the scan against the changes. If the
@@ -10,9 +10,14 @@
10
10
  ## Files changed
11
11
  <FILES_CHANGED from the coder/fixer's REPORT block>
12
12
 
13
- ## Progress log
14
- Write progress to: `.agent-cortex/ralph/ralph-<bead-id>.log`
15
- Log stage start, each command run, test/lint result, and stage complete (see Progress Logging in run-pipeline-stage skill).
13
+ ## Trail entry
14
+ Record one entry at stage start and one at stage close through the shared writer (see Trail Entries in the `run-pipeline-stage` skill):
15
+ ```bash
16
+ node <run-pipeline-stage-scripts>/trail-entry.mjs \
17
+ --bead <bead-id> --stage verify --outcome <done|blocked|partial> \
18
+ --evidence "<evidence>" --remaining "<what is left, or none>"
19
+ ```
20
+ Evidence for a verify stage: each command run, with its test and lint result.
16
21
 
17
22
  ## Instructions
18
23
  Discover and run the project's test and lint commands, then report the outcome.