@jaybeeuu/agent-cortex 2.4.2 → 2.5.1
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 +59 -0
- package/README.md +18 -6
- package/agents/README.md +3 -1
- package/agents/ralph/agent.md +6 -4
- package/bin/agent-cortex.mjs +12 -7
- package/bin/installers/claude.mjs +55 -11
- package/bin/installers/pi.mjs +35 -1
- package/extensions/agent-guardrails/package.json +1 -1
- package/extensions/agent-modes/index.test.ts +13 -0
- package/extensions/agent-modes/package.json +1 -1
- package/extensions/lib/tiny-model.ts +7 -6
- package/extensions/output-style/package.json +1 -1
- package/extensions/session-start/package.json +1 -1
- package/extensions/skill-stats/package.json +1 -1
- package/extensions/subagent/package.json +3 -3
- package/lib/cli.mjs +29 -7
- package/package.json +4 -2
- package/scripts/lib/migrate.mjs +31 -2
- package/{skills/productivity/validate-agent-dir/scripts → scripts}/validate-agent-dir.mjs +20 -14
- package/skills/planning/create-task/REFERENCE.md +15 -2
- package/skills/planning/create-task/SKILL.md +5 -2
- package/skills/planning/create-task/scripts/create-chores.mjs +111 -32
- package/skills/planning/create-task/templates/code.md +13 -2
- package/skills/planning/create-task/templates/document.md +13 -2
- package/skills/planning/create-task/templates/fix.md +13 -2
- package/skills/planning/create-task/templates/verify.md +14 -8
- package/skills/productivity/migration/REFERENCE.md +10 -3
- package/skills/workflow/ralph/REFERENCE.md +12 -5
- package/skills/workflow/ralph/SKILL.md +6 -3
- package/skills/workflow/run-pipeline-stage/SKILL.md +3 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jaybeeuu/agent-cortex",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.1",
|
|
4
4
|
"description": "Personal PI package with custom agents, skills, and extensions",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package"
|
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
"url": "https://github.com/jaybeeuu/agent-cortex"
|
|
12
12
|
},
|
|
13
13
|
"bin": {
|
|
14
|
-
"agent-cortex": "./bin/agent-cortex.mjs"
|
|
14
|
+
"agent-cortex": "./bin/agent-cortex.mjs",
|
|
15
|
+
"cortex": "./bin/agent-cortex.mjs",
|
|
16
|
+
"ctx": "./bin/agent-cortex.mjs"
|
|
15
17
|
},
|
|
16
18
|
"files": [
|
|
17
19
|
"bin/",
|
package/scripts/lib/migrate.mjs
CHANGED
|
@@ -40,6 +40,15 @@ const EXECUTION_LABEL_PREFIX = "execution:";
|
|
|
40
40
|
*/
|
|
41
41
|
const V1_LABEL_VALUES = ["afk", "hitl"];
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Ids per `bd label add` / `bd label remove` call. bd applies a label change id
|
|
45
|
+
* by id, so one call carrying every id in a large workspace takes minutes and
|
|
46
|
+
* can report success having persisted only part of the list — which is how a
|
|
47
|
+
* 710-bead rename silently left 280 beads unrenamed. A chunk keeps each call
|
|
48
|
+
* small enough to apply in full.
|
|
49
|
+
*/
|
|
50
|
+
const LABEL_BATCH_SIZE = 20;
|
|
51
|
+
|
|
43
52
|
/**
|
|
44
53
|
* Where loose idea records live, relative to the working directory. This is the
|
|
45
54
|
* one path the command assumes, and it is deliberately the directory every
|
|
@@ -262,9 +271,27 @@ async function findLegacyLabelBeads({ bd }) {
|
|
|
262
271
|
return pending.sort((a, b) => a.id.localeCompare(b.id));
|
|
263
272
|
}
|
|
264
273
|
|
|
274
|
+
/**
|
|
275
|
+
* Split `items` into consecutive runs of at most `size` — one `bd` call's worth.
|
|
276
|
+
*
|
|
277
|
+
* `size` must be positive: a size of 0 never advances the loop.
|
|
278
|
+
*
|
|
279
|
+
* @template T
|
|
280
|
+
* @param {T[]} items
|
|
281
|
+
* @param {number} size
|
|
282
|
+
* @returns {T[][]}
|
|
283
|
+
*/
|
|
284
|
+
function chunkItems(items, size) {
|
|
285
|
+
/** @type {T[][]} */
|
|
286
|
+
const chunks = [];
|
|
287
|
+
for (let start = 0; start < items.length; start += size) chunks.push(items.slice(start, start + size));
|
|
288
|
+
return chunks;
|
|
289
|
+
}
|
|
290
|
+
|
|
265
291
|
/**
|
|
266
292
|
* Apply the label rename in batches: add the target label for a group of beads,
|
|
267
293
|
* then remove the legacy one, so a bead is never left without its axis label.
|
|
294
|
+
* Both writes carry one chunk of ids, never the whole workspace.
|
|
268
295
|
*
|
|
269
296
|
* @param {StepContext} ctx
|
|
270
297
|
* @param {Pending[]} pending
|
|
@@ -283,8 +310,10 @@ async function renameLegacyLabels({ bd }, pending) {
|
|
|
283
310
|
|
|
284
311
|
for (const from of [...groups.keys()].sort()) {
|
|
285
312
|
const { to, ids } = /** @type {{ to: string, ids: string[] }} */ (groups.get(from));
|
|
286
|
-
|
|
287
|
-
|
|
313
|
+
for (const chunk of chunkItems(ids, LABEL_BATCH_SIZE)) {
|
|
314
|
+
await bd(["label", "add", ...chunk, to]);
|
|
315
|
+
await bd(["label", "remove", ...chunk, from]);
|
|
316
|
+
}
|
|
288
317
|
}
|
|
289
318
|
}
|
|
290
319
|
|
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
// Validator for the composable agent directory layout: `agent.md` plus at
|
|
3
3
|
// least one harness subdirectory with a valid `frontmatter.json`.
|
|
4
4
|
//
|
|
5
|
-
//
|
|
6
|
-
// `
|
|
5
|
+
// Repo tooling, not a shipped skill: the composable layout exists only in this
|
|
6
|
+
// repository's `agents/` tree, and installs carry flat rendered agents. Run the
|
|
7
|
+
// test suite at `test/validate-agent-dir.test.mjs`.
|
|
7
8
|
|
|
8
|
-
import {
|
|
9
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
9
10
|
import { join } from "node:path";
|
|
10
11
|
|
|
12
|
+
import { isDirectory, isFile } from "./lib/fs.mjs";
|
|
13
|
+
|
|
11
14
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
12
15
|
|
|
13
16
|
/**
|
|
@@ -96,28 +99,31 @@ export function validateFrontmatter(fm) {
|
|
|
96
99
|
|
|
97
100
|
/**
|
|
98
101
|
* @param {string} agentDirPath
|
|
99
|
-
* @returns {ValidationResult}
|
|
102
|
+
* @returns {Promise<ValidationResult>}
|
|
100
103
|
*/
|
|
101
|
-
export function validateAgentDir(agentDirPath) {
|
|
104
|
+
export async function validateAgentDir(agentDirPath) {
|
|
102
105
|
/** @type {string[]} */
|
|
103
106
|
const errors = [];
|
|
104
107
|
|
|
105
108
|
// Check agent.md exists
|
|
106
109
|
const agentMdPath = join(agentDirPath, "agent.md");
|
|
107
|
-
if (!
|
|
110
|
+
if (!(await isFile(agentMdPath))) {
|
|
108
111
|
errors.push("missing agent.md");
|
|
109
112
|
}
|
|
110
113
|
|
|
111
114
|
// Check for at least one harness subdirectory with frontmatter.json
|
|
112
|
-
if (!
|
|
115
|
+
if (!(await isDirectory(agentDirPath))) {
|
|
113
116
|
return { ok: false, errors: ["agent directory does not exist"] };
|
|
114
117
|
}
|
|
115
118
|
|
|
116
|
-
const entries =
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
119
|
+
const entries = await readdir(agentDirPath);
|
|
120
|
+
/** @type {string[]} */
|
|
121
|
+
const harnessDirs = [];
|
|
122
|
+
for (const entry of entries) {
|
|
123
|
+
if (entry !== "node_modules" && (await isDirectory(join(agentDirPath, entry)))) {
|
|
124
|
+
harnessDirs.push(entry);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
121
127
|
|
|
122
128
|
if (harnessDirs.length === 0) {
|
|
123
129
|
errors.push("no harness subdirectories found (need at least one with frontmatter.json)");
|
|
@@ -126,12 +132,12 @@ export function validateAgentDir(agentDirPath) {
|
|
|
126
132
|
// Validate each harness directory
|
|
127
133
|
for (const harnessDir of harnessDirs) {
|
|
128
134
|
const frontmatterPath = join(agentDirPath, harnessDir, "frontmatter.json");
|
|
129
|
-
if (!
|
|
135
|
+
if (!(await isFile(frontmatterPath))) {
|
|
130
136
|
continue; // Skip directories without frontmatter.json
|
|
131
137
|
}
|
|
132
138
|
|
|
133
139
|
try {
|
|
134
|
-
const content =
|
|
140
|
+
const content = await readFile(frontmatterPath, "utf-8");
|
|
135
141
|
const fm = JSON.parse(content);
|
|
136
142
|
const result = validateFrontmatter(fm);
|
|
137
143
|
if (!result.ok) {
|
|
@@ -10,7 +10,9 @@ node <skill-scripts>/create-chores.mjs --parent <bead-id> [--priority <0-4>]
|
|
|
10
10
|
|
|
11
11
|
- Reads `<skill>/pipeline.json`; creates each stage chore via `bd create` and wires dependencies via `bd dep add`.
|
|
12
12
|
- Prints a JSON object mapping each stage ID to its chore bead ID, plus `featurePrReview` for the PR gate.
|
|
13
|
-
- Expands a bead that already exists — it never creates a parent.
|
|
13
|
+
- Expands a bead that already exists — it never creates a parent.
|
|
14
|
+
- **Idempotent**: it reads that parent's children once (`bd children <parent-id> --json`) and adopts an existing child by its role label — `stage:<id>` for a stage chore, `lifecycle:feature-pr` for the gate — creating only what is missing. Deps that are already wired are skipped. A re-run on an expanded bead creates nothing and prints the same mapping, so re-running after a failed expansion is the recovery path.
|
|
15
|
+
- Adoption ignores bead status and reads only labels: a chore a later stage already closed is still adopted and reprinted in the mapping, which keeps a re-run convergent instead of creating a duplicate. When a role label appears twice under one parent the first-listed child wins. Adoption is scoped to that parent's children and keyed on labels, never titles, so a stray chore from another bead is never adopted.
|
|
14
16
|
|
|
15
17
|
## Stage chore beads
|
|
16
18
|
|
|
@@ -86,4 +88,15 @@ The deliverable this feature produces and the problem it solves.
|
|
|
86
88
|
| review | Review | verify |
|
|
87
89
|
| document | Document | review |
|
|
88
90
|
|
|
89
|
-
`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.
|
|
91
|
+
`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
|
+
|
|
93
|
+
## Stage templates
|
|
94
|
+
|
|
95
|
+
`templates/` holds one prompt skeleton per stage; a dispatcher fills one to compose that stage's
|
|
96
|
+
chore prompt. The REPORT contract is not defined here —
|
|
97
|
+
`skills/workflow/run-pipeline-stage/prompts/stage-runner.md` owns it and every template mirrors it.
|
|
98
|
+
Drift between the two fails silently: a worker that follows a drifted template emits a well-formed
|
|
99
|
+
block with keys no one reads, so routing stops at that stage. Drift has already broken routing
|
|
100
|
+
twice. Compare a template with the authority whenever either changes. The intended guard is a lint
|
|
101
|
+
parity check beside `scripts/check-skill-frontmatter.mjs`, not a test — a test here would assert on
|
|
102
|
+
file text, which is a content inventory, and `style-tests` bans that shape.
|
|
@@ -81,7 +81,7 @@ Turn a piece of work into a tracked bead, classify it for autonomous (AFK) or hu
|
|
|
81
81
|
|
|
82
82
|
The planning bead is cold-start ready — what and why, decisions already made, open questions and risks, references — and needs `## Acceptance Criteria`, which `--validate` requires for type `task` (template in `REFERENCE.md` → Hierarchy). A deliverable that genuinely needs a human — visual sign-off, credentials, hardware — is created `-l execution:hitl` and stops there.
|
|
83
83
|
|
|
84
|
-
7. **Expand into pipeline stage chores.** Run the `create-chores` script, which reads this skill's `pipeline.json` and
|
|
84
|
+
7. **Expand into pipeline stage chores.** Run the `create-chores` script, which reads this skill's `pipeline.json` and creates the stage chores plus the PR gate bead — adopting any the parent already has, so a re-run converges on the existing tree instead of duplicating it:
|
|
85
85
|
|
|
86
86
|
```bash
|
|
87
87
|
# Run from the TARGET project's directory — bd resolves its beads DB from cwd;
|
|
@@ -92,13 +92,15 @@ Turn a piece of work into a tracked bead, classify it for autonomous (AFK) or hu
|
|
|
92
92
|
|
|
93
93
|
`<skill-scripts>` is the absolute path to this skill's own `scripts/` directory, resolved from wherever this skill was loaded — it varies by harness and install location, so never hardcode one harness's path.
|
|
94
94
|
|
|
95
|
+
The script is idempotent: it adopts an existing child by its `stage:<id>` or `lifecycle:feature-pr` label, creates only what is missing, and skips deps that are already wired. If an expansion fails partway, re-run this command — it completes the partial chain and prints the full mapping.
|
|
96
|
+
|
|
95
97
|
On the feature path, run it **once per AFK impl child**, with `--parent <impl-task-id>` — never the feature ID: stage chores inherit their parent's labels, the script checks only that it was handed a parent ID (`REFERENCE.md` → PR gate bead), so an `execution:hitl` feature parent yields a chore tree no executor routes. A deliverable created `execution:hitl` in step 6 is not expanded.
|
|
96
98
|
|
|
97
99
|
8. **Report.** Return the bead ID (or, on the feature path, the feature ID plus every planning and impl child ID), the classification of each expanded bead (`AFK`), and the created stage chore IDs from the script output (including `featurePrReview`) — one mapping per expanded impl child on the feature path.
|
|
98
100
|
|
|
99
101
|
### Expanding an existing bead
|
|
100
102
|
|
|
101
|
-
Steps 2–6 create beads; step 7 expands one. A bead that already exists but has no `stage:*` chore children — an AFK task filed before the pipeline existed, or one ralph found chore-less — is a supported entry point: run step 7's `create-chores.mjs --parent <existing-id> --priority <its-own-priority>` directly, then report the mapping. No create steps, no new description, no re-classification.
|
|
103
|
+
Steps 2–6 create beads; step 7 expands one. A bead that already exists but has no `stage:*` chore children — an AFK task filed before the pipeline existed, or one ralph found chore-less — is a supported entry point: run step 7's `create-chores.mjs --parent <existing-id> --priority <its-own-priority>` directly, then report the mapping. No create steps, no new description, no re-classification. A partial chain from a failed run is repaired the same way: re-running adopts the chores that exist, creates the missing ones, and reparents nothing.
|
|
102
104
|
|
|
103
105
|
**Never re-run `create-task` for an existing bead.** It creates a bead first and expands second, so the run leaves a **second parent** beside the original, which stays chore-less — the duplicate ralph's expansion guard exists to prevent. `create-chores.mjs` expands a bead that already exists; only step 2 creates a bead.
|
|
104
106
|
|
|
@@ -110,6 +112,7 @@ Steps 2–6 create beads; step 7 expands one. A bead that already exists but has
|
|
|
110
112
|
- **Skipping the classifier and classifying by hand.** The rubric is a subagent's job; a hand-rolled guess drifts from it.
|
|
111
113
|
- **Expanding an HITL bead.** If classification is HITL, stop — creating a chore tree for human-driven work is wrong.
|
|
112
114
|
- **Expanding an existing bead by re-running `create-task`.** That step creates a bead, so the original stays chore-less and gains a second parent beside it. Run `create-chores.mjs --parent <existing-id>` instead.
|
|
115
|
+
- **Treating a failed expansion as unrecoverable.** `create-chores.mjs` is idempotent: re-run it and it adopts the chores that exist, creates the rest, and reports the whole mapping.
|
|
113
116
|
- **Hardcoding `<skill-scripts>`.** The install path differs per harness (plugin cache, installed-plugins, dev checkout); resolve it at runtime.
|
|
114
117
|
- **Creating a feature with no planning bead.** A feature that goes straight to impl children is a feature nobody designed. The planning bead keeps the children blocked until the design is confirmed.
|
|
115
118
|
- **Expressing ownership as a dependency.** `bd dep add <child> <parent>` mixes hierarchy into the readiness gate; pass `--parent` at create time instead.
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
// Pre-create the pipeline stage chores for a task bead, plus the feature PR
|
|
3
3
|
// review gate, from `pipeline.json`.
|
|
4
4
|
//
|
|
5
|
+
// Idempotent: a re-run converges on the tree that already exists instead of adding a
|
|
6
|
+
// second chain beside it, so re-running after a failed expansion is the recovery path.
|
|
7
|
+
// Existing children are adopted by their role label (`stage:<id>`, `lifecycle:feature-pr`)
|
|
8
|
+
// under the given parent — never by title, and never across parents.
|
|
9
|
+
//
|
|
5
10
|
// The parent is an `execution:afk` bead. Stage chores inherit that label from it, so
|
|
6
11
|
// create-chores does not classify: a parent that is hitl or unlabelled silently yields
|
|
7
12
|
// a chore tree no executor routes. Check the parent's labels before invoking this.
|
|
@@ -69,6 +74,66 @@ function run(args) {
|
|
|
69
74
|
return execSync(cmd.join(' '), { encoding: 'utf-8' }).trim();
|
|
70
75
|
}
|
|
71
76
|
|
|
77
|
+
// ─── Existing children ────────────────────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @typedef {object} Dependency
|
|
81
|
+
* @property {string} depends_on_id
|
|
82
|
+
* @property {string} type
|
|
83
|
+
*/
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* A child of the parent that already fills one of this script's roles.
|
|
87
|
+
* @typedef {object} Adoptable
|
|
88
|
+
* @property {string} id
|
|
89
|
+
* @property {Set<string>} blocks Beads it already depends on with a `blocks` dep
|
|
90
|
+
*/
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Read the parent's children once and index the ones this script owns by their role
|
|
94
|
+
* label. Scoped to that parent's children, so a stray chore belonging to another bead
|
|
95
|
+
* is never adopted. When a role label appears more than once the first-listed child
|
|
96
|
+
* wins, so a converged re-run is deterministic.
|
|
97
|
+
* @param {string} parentId
|
|
98
|
+
* @returns {Map<string, Adoptable>} Keyed by `stage:<id>` or `lifecycle:feature-pr`
|
|
99
|
+
*/
|
|
100
|
+
function readAdoptable(parentId) {
|
|
101
|
+
const raw = run(['bd', 'children', parentId, '--json']);
|
|
102
|
+
/** @type {Array<{ id: string, labels?: string[], dependencies?: Dependency[] }>} */
|
|
103
|
+
const children = raw === '' ? [] : JSON.parse(raw);
|
|
104
|
+
|
|
105
|
+
/** @type {Map<string, Adoptable>} */
|
|
106
|
+
const adoptable = new Map();
|
|
107
|
+
|
|
108
|
+
for (const child of children) {
|
|
109
|
+
const blocks = new Set(
|
|
110
|
+
(child.dependencies ?? [])
|
|
111
|
+
.filter((dep) => dep.type === 'blocks')
|
|
112
|
+
.map((dep) => dep.depends_on_id),
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
for (const label of child.labels ?? []) {
|
|
116
|
+
if (label !== 'lifecycle:feature-pr' && !label.startsWith('stage:')) { continue; }
|
|
117
|
+
if (!adoptable.has(label)) { adoptable.set(label, { id: child.id, blocks }); }
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return adoptable;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Wire a `blocks` dependency unless the bead already carries it. Re-adding an existing
|
|
126
|
+
* dep is the failure mode a re-run must not hit, so existing deps are skipped.
|
|
127
|
+
* @param {string} beadId
|
|
128
|
+
* @param {string} dependsOnId
|
|
129
|
+
* @param {Set<string>} blocks Dependencies the bead already has; extended in place
|
|
130
|
+
*/
|
|
131
|
+
function wireBlocks(beadId, dependsOnId, blocks) {
|
|
132
|
+
if (blocks.has(dependsOnId)) { return; }
|
|
133
|
+
run(['bd', 'dep', 'add', beadId, dependsOnId, '--type', 'blocks']);
|
|
134
|
+
blocks.add(dependsOnId);
|
|
135
|
+
}
|
|
136
|
+
|
|
72
137
|
// ─── Main ─────────────────────────────────────────────────────────────────────
|
|
73
138
|
|
|
74
139
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -80,31 +145,38 @@ const pipelinePath = resolve(__dirname, '..', 'pipeline.json');
|
|
|
80
145
|
/** @type {Pipeline} */
|
|
81
146
|
const pipeline = JSON.parse(readFileSync(pipelinePath, 'utf-8'));
|
|
82
147
|
|
|
148
|
+
const adoptable = readAdoptable(parentId);
|
|
149
|
+
|
|
83
150
|
/** @type {Map<string, string>} */
|
|
84
151
|
const stageToBeadId = new Map();
|
|
85
152
|
|
|
86
153
|
for (const stage of pipeline.stages) {
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
const beadId =
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
154
|
+
const adopted = adoptable.get(`stage:${stage.id}`);
|
|
155
|
+
|
|
156
|
+
const beadId = adopted
|
|
157
|
+
? adopted.id
|
|
158
|
+
: run([
|
|
159
|
+
'bd', 'create', JSON.stringify(`[${parentId}] ${stage.title}`),
|
|
160
|
+
'--type', 'chore',
|
|
161
|
+
'--description', JSON.stringify(stage.description),
|
|
162
|
+
'--priority', priority,
|
|
163
|
+
'--labels', `stage:${stage.id}`,
|
|
164
|
+
'--parent', parentId,
|
|
165
|
+
'--silent',
|
|
166
|
+
]);
|
|
98
167
|
|
|
99
168
|
stageToBeadId.set(stage.id, beadId);
|
|
100
169
|
|
|
170
|
+
/** @type {Set<string>} */
|
|
171
|
+
const blocks = adopted ? adopted.blocks : new Set();
|
|
172
|
+
|
|
101
173
|
for (const depStageId of stage.dependsOn) {
|
|
102
174
|
const depBeadId = stageToBeadId.get(depStageId);
|
|
103
175
|
if (!depBeadId) {
|
|
104
176
|
console.error(`Stage '${stage.id}' depends on '${depStageId}', but it has not been created yet. Check dependsOn ordering in pipeline.json.`);
|
|
105
177
|
process.exit(1);
|
|
106
178
|
}
|
|
107
|
-
|
|
179
|
+
wireBlocks(beadId, depBeadId, blocks);
|
|
108
180
|
}
|
|
109
181
|
}
|
|
110
182
|
|
|
@@ -120,26 +192,33 @@ if (!documentStageBeadId) {
|
|
|
120
192
|
process.exit(1);
|
|
121
193
|
}
|
|
122
194
|
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
195
|
+
const adoptedGate = adoptable.get('lifecycle:feature-pr');
|
|
196
|
+
|
|
197
|
+
const prGateBeadId = adoptedGate
|
|
198
|
+
? adoptedGate.id
|
|
199
|
+
: run([
|
|
200
|
+
'bd', 'create', JSON.stringify(`[${parentId}] PR Review and Merge`),
|
|
201
|
+
'--type', 'task',
|
|
202
|
+
'--description', JSON.stringify(
|
|
203
|
+
'Human review gate: review and merge the agent PR into the feature branch before closing this feature.\n\n## Acceptance Criteria\n\n- [ ] Agent PR reviewed and merged into the feature branch.',
|
|
204
|
+
),
|
|
205
|
+
// The gate is type `task`, so `--validate` enforces its `## Acceptance Criteria`
|
|
206
|
+
// section. Stage chores require no sections and deliberately omit the flag.
|
|
207
|
+
'--validate',
|
|
208
|
+
'--priority', priority,
|
|
209
|
+
'--labels', 'execution:hitl,lifecycle:feature-pr',
|
|
210
|
+
'--parent', parentId,
|
|
211
|
+
// The gate declares its own execution class (it is human-owned review work), so opt
|
|
212
|
+
// out of bd's create-time parent-label inheritance and its explicit :hitl is never
|
|
213
|
+
// joined by an inherited :afk. Stage chores deliberately keep inheriting the parent's
|
|
214
|
+
// :afk -- do not add this flag there.
|
|
215
|
+
'--no-inherit-labels',
|
|
216
|
+
'--silent',
|
|
217
|
+
]);
|
|
218
|
+
|
|
219
|
+
/** @type {Set<string>} */
|
|
220
|
+
const gateBlocks = adoptedGate ? adoptedGate.blocks : new Set();
|
|
221
|
+
wireBlocks(prGateBeadId, documentStageBeadId, gateBlocks);
|
|
143
222
|
|
|
144
223
|
/** @type {Record<string, string>} */
|
|
145
224
|
const result = {
|
|
@@ -31,10 +31,21 @@ Implement the task described above using a test-driven approach:
|
|
|
31
31
|
|
|
32
32
|
Make only the changes required to complete this task — do not refactor unrelated code.
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
Commit and push the stage's work before reporting (see _Deliver before reporting_ in
|
|
35
|
+
`prompts/stage-runner.md`). A dirty worktree counts as a failed stage.
|
|
36
|
+
|
|
37
|
+
End your response with a ---REPORT--- block. The `run-pipeline-stage` skill's
|
|
38
|
+
`prompts/stage-runner.md` holds the authoritative REPORT contract — if this block and that
|
|
39
|
+
prompt differ, follow the prompt and correct this template.
|
|
35
40
|
---REPORT---
|
|
36
41
|
BEAD_ID: <id>
|
|
37
|
-
STAGE_COMPLETED:
|
|
42
|
+
STAGE_COMPLETED: code
|
|
38
43
|
SUMMARY: <2–3 sentence summary of what was done>
|
|
39
44
|
FILES_CHANGED: <comma-separated list, or "none">
|
|
45
|
+
COMMIT: <sha of the stage commit, or "none">
|
|
46
|
+
PUSHED: <yes|no>
|
|
47
|
+
PR: none
|
|
48
|
+
OUTCOME: <SUCCESS|BLOCKED>
|
|
49
|
+
BLOCKING_ISSUES: ← include only if OUTCOME is BLOCKED; one line per issue
|
|
50
|
+
- <what blocks the stage and what would unblock it>
|
|
40
51
|
---
|
|
@@ -31,10 +31,21 @@ Review the changes made and compare them against the existing documentation in t
|
|
|
31
31
|
when no decisions or macro behaviour changed.
|
|
32
32
|
6. Do not modify source code — documentation only.
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
Commit and push the docs, and open or update the feature PR before reporting (see _Deliver
|
|
35
|
+
before reporting_ in `prompts/stage-runner.md`). A dirty worktree counts as a failed stage.
|
|
36
|
+
|
|
37
|
+
End your response with a ---REPORT--- block. The `run-pipeline-stage` skill's
|
|
38
|
+
`prompts/stage-runner.md` holds the authoritative REPORT contract — if this block and that
|
|
39
|
+
prompt differ, follow the prompt and correct this template.
|
|
35
40
|
---REPORT---
|
|
36
41
|
BEAD_ID: <id>
|
|
37
|
-
STAGE_COMPLETED:
|
|
42
|
+
STAGE_COMPLETED: document
|
|
38
43
|
SUMMARY: <2–3 sentence summary of what was documented, or "No documentation changes required">
|
|
39
44
|
FILES_CHANGED: <comma-separated list of docs touched, or "none">
|
|
45
|
+
COMMIT: <sha of the stage commit, or "none">
|
|
46
|
+
PUSHED: <yes|no>
|
|
47
|
+
PR: <feature PR url>
|
|
48
|
+
OUTCOME: <SUCCESS|BLOCKED>
|
|
49
|
+
BLOCKING_ISSUES: ← include only if OUTCOME is BLOCKED; one line per issue
|
|
50
|
+
- <what blocks the stage and what would unblock it>
|
|
40
51
|
---
|
|
@@ -18,10 +18,21 @@ Apply only the changes described in the Task section above. Do not make any othe
|
|
|
18
18
|
|
|
19
19
|
**Use context7** when working with any third-party library — fetch live docs to ensure you use the correct, up-to-date API for the version in this project.
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
Commit and push the fix before reporting (see _Deliver before reporting_ in
|
|
22
|
+
`prompts/stage-runner.md`). A dirty worktree counts as a failed stage.
|
|
23
|
+
|
|
24
|
+
End your response with a ---REPORT--- block. The `run-pipeline-stage` skill's
|
|
25
|
+
`prompts/stage-runner.md` holds the authoritative REPORT contract — if this block and that
|
|
26
|
+
prompt differ, follow the prompt and correct this template.
|
|
22
27
|
---REPORT---
|
|
23
28
|
BEAD_ID: <id>
|
|
24
|
-
STAGE_COMPLETED:
|
|
29
|
+
STAGE_COMPLETED: fix
|
|
25
30
|
SUMMARY: <2–3 sentence summary of what was changed>
|
|
26
31
|
FILES_CHANGED: <comma-separated list, or "none">
|
|
32
|
+
COMMIT: <sha of the stage commit, or "none">
|
|
33
|
+
PUSHED: <yes|no>
|
|
34
|
+
PR: none
|
|
35
|
+
OUTCOME: <SUCCESS|BLOCKED>
|
|
36
|
+
BLOCKING_ISSUES: ← include only if OUTCOME is BLOCKED; one line per issue
|
|
37
|
+
- <what remains unresolved and what would unblock it>
|
|
27
38
|
---
|
|
@@ -21,23 +21,29 @@ Discover and run the project's test and lint commands, then report the outcome.
|
|
|
21
21
|
1. `package.json` `scripts` — look for keys named `test`, `lint`, `check`, `typecheck`, or similar.
|
|
22
22
|
2. `Makefile` — look for targets named `test`, `lint`, `check`.
|
|
23
23
|
3. Other well-known config files (e.g. `pyproject.toml`, `Cargo.toml`) — look for test/lint targets.
|
|
24
|
-
4. If no commands are found, report
|
|
24
|
+
4. If no commands are found, report `OUTCOME: SUCCESS` with summary "No test or lint commands found".
|
|
25
25
|
|
|
26
26
|
**Step 2 — Run each discovered command** in the project root. Capture stdout and stderr.
|
|
27
27
|
|
|
28
28
|
**Step 3 — Evaluate**:
|
|
29
|
-
- If all commands exit 0: `
|
|
30
|
-
- If any command exits non-zero: `
|
|
29
|
+
- If all commands exit 0: `OUTCOME: SUCCESS`.
|
|
30
|
+
- If any command exits non-zero: `OUTCOME: BLOCKED`. List each failure concisely under
|
|
31
|
+
`BLOCKING_ISSUES`.
|
|
31
32
|
|
|
32
33
|
Do not fix failures — report them only.
|
|
33
34
|
|
|
34
|
-
End your response with a ---REPORT--- block
|
|
35
|
+
End your response with a ---REPORT--- block. The `run-pipeline-stage` skill's
|
|
36
|
+
`prompts/stage-runner.md` holds the authoritative REPORT contract — if this block and that
|
|
37
|
+
prompt differ, follow the prompt and correct this template.
|
|
35
38
|
---REPORT---
|
|
36
39
|
BEAD_ID: <id>
|
|
37
|
-
STAGE_COMPLETED:
|
|
40
|
+
STAGE_COMPLETED: verify
|
|
38
41
|
SUMMARY: <what commands were run and overall outcome>
|
|
39
42
|
FILES_CHANGED: none
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
COMMIT: <sha, or "none" if this stage added no commit>
|
|
44
|
+
PUSHED: <yes|no>
|
|
45
|
+
PR: none
|
|
46
|
+
OUTCOME: <SUCCESS|BLOCKED>
|
|
47
|
+
BLOCKING_ISSUES: ← include only if OUTCOME is BLOCKED; one line per failure
|
|
48
|
+
- <command, its exit status, and the failure summary>
|
|
43
49
|
---
|
|
@@ -77,10 +77,16 @@ Mechanics the plan reflects:
|
|
|
77
77
|
2. Group bead ids by label value. A bead carrying both `afk` and `hitl` appears in both groups;
|
|
78
78
|
that is a data problem worth inspecting, not a conflict the tool resolves for you.
|
|
79
79
|
3. Per group, on `--apply`: `bd label add <ids…> execution:<value>` then
|
|
80
|
-
`bd label remove <ids…> implementation-type:<value
|
|
81
|
-
present at every instant, so nothing is unroutable mid-flight.
|
|
80
|
+
`bd label remove <ids…> implementation-type:<value>`, at most 20 ids per call. Add-then-remove
|
|
81
|
+
keeps the routing label present at every instant, so nothing is unroutable mid-flight.
|
|
82
82
|
4. Re-running after convergence finds zero targets — the no-op that proves idempotence.
|
|
83
83
|
|
|
84
|
+
The 20-id ceiling is a `bd` limit, not a preference. `bd label add|remove` applies a list id by
|
|
85
|
+
id and exits 0 even when it persisted only a prefix of it: one call carrying 710 ids left 280
|
|
86
|
+
beads unrenamed while reporting success. Every call in this skill therefore carries one chunk of
|
|
87
|
+
20 ids, and the per-step verification re-detects from the export instead of trusting the exit
|
|
88
|
+
code. Any hand-run recipe below needs the same ceiling.
|
|
89
|
+
|
|
84
90
|
Verification commands:
|
|
85
91
|
|
|
86
92
|
```bash
|
|
@@ -153,7 +159,8 @@ inversion.
|
|
|
153
159
|
|
|
154
160
|
Every `bd label` call in this skill passes issue ids first and the label last, matching the CLI's
|
|
155
161
|
`bd label add [issue-id...] [label[,label...]]`. Reversing that order fails: the ids are resolved
|
|
156
|
-
before the trailing argument is read as a label.
|
|
162
|
+
before the trailing argument is read as a label. Keep each hand-run call to about 20 ids for the
|
|
163
|
+
same reason the command chunks: a longer list exits 0 having applied only part of itself.
|
|
157
164
|
|
|
158
165
|
```bash
|
|
159
166
|
# Invert the v1 rename for a specific group
|
|
@@ -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 — with `blocks` deps that encode the order, and with the parent's priority and inherited labels. Ralph's dispatch is therefore always "claim what `bd ready -l execution:afk` returns".
|
|
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 — 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
|
|
|
@@ -57,7 +57,11 @@ A ready `execution:afk` task bead with **no** `stage:*` chore children has nothi
|
|
|
57
57
|
```
|
|
58
58
|
Expect `0` printed. `grep -c` exits `1` when it counts zero matches, so add `|| true` when the
|
|
59
59
|
check runs under `set -e` or inside an `&&` chain — the printed count is the answer, not the exit
|
|
60
|
-
status.
|
|
60
|
+
status. A bead with a full chain — one `stage:*` chore per stage in `pipeline.json` (four for the
|
|
61
|
+
shipped pipeline) plus the `lifecycle:feature-pr` gate — never needs an expansion. The check
|
|
62
|
+
answers "no chores at all", so a **partial** chain (a lower count from a failed run) passes it
|
|
63
|
+
and its missing stages never arrive. Nothing detects that automatically: re-run step 2's command
|
|
64
|
+
against that parent by hand, which is idempotent.
|
|
61
65
|
2. **Dispatch an expansion worker** in the feature worktree (`.agent-cortex/worktrees/<parent-id>`) whose whole job is to run the pre-creator against the existing bead and report the JSON it printed:
|
|
62
66
|
```bash
|
|
63
67
|
node <create-task-scripts>/create-chores.mjs \
|
|
@@ -93,8 +97,11 @@ On completion, remove the entry from `inflight` and confirm the chore tree exist
|
|
|
93
97
|
bd children <parent-id> --json | grep -c '"stage:'
|
|
94
98
|
```
|
|
95
99
|
|
|
96
|
-
Expect
|
|
97
|
-
|
|
100
|
+
Expect one match per `stage:*` label in the chain — four for the shipped `pipeline.json`; the
|
|
101
|
+
`featurePrReview` gate carries `lifecycle:feature-pr` instead, so it is not counted. A lower count
|
|
102
|
+
means the expansion did not finish: `0` created nothing, and a partial chain leaves stages that
|
|
103
|
+
will never become ready. Either way, surface the worker's output in chat, record the parent for the
|
|
104
|
+
shutdown summary, and re-dispatch the expansion: `create-chores.mjs` is idempotent, so the second run adopts the chores that already exist, creates only the missing ones, and prints the full mapping — re-running is the recovery path. Then continue at the normal dispatch pass: either way nothing is created and nothing is closed by ralph.
|
|
98
105
|
|
|
99
106
|
---
|
|
100
107
|
|
|
@@ -130,7 +137,7 @@ Decompose it:
|
|
|
130
137
|
bd children <feature-id> --json
|
|
131
138
|
```
|
|
132
139
|
Surface the created IDs in chat. An empty list means the decomposition failed: record the feature for the shutdown summary and continue — do not re-dispatch.
|
|
133
|
-
5. **Never re-dispatch a decomposition.** The bead creation it runs is not idempotent, so a second run duplicates the planning bead and every implementation child beside the first. This
|
|
140
|
+
5. **Never re-dispatch a decomposition.** The bead creation it runs is not idempotent, so a second run duplicates the planning bead and every implementation child beside the first. This hazard belongs to `create-task`'s bead creation alone — `create-chores.mjs` is idempotent, so re-running an expansion is its recovery path.
|
|
134
141
|
6. **Go idle.** After decomposition nothing is dispatchable for that feature: `bd ready -l execution:afk` still lists the feature itself (it now has children, so it is skipped — never decomposed again) but none of its children until a human closes the planning bead. Returning to the event loop with *nothing* to dispatch is the correct outcome, not a failure to re-expand — that is Q5b working as designed.
|
|
135
142
|
|
|
136
143
|
---
|