@jaybeeuu/agent-cortex 1.38.1 → 1.40.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/.agents/skills/style-versioning/SKILL.md +1 -1
- package/.github/workflows/ci.yml +47 -14
- package/AGENTS.md +19 -3
- package/CHANGELOG.md +42 -0
- package/README.md +118 -63
- package/agents/README.md +13 -10
- package/agents/ralph-plan/agent.md +1 -1
- package/agents/ralph-plan.agent.md +1 -1
- package/bin/agent-cortex.mjs +29 -8
- package/bin/installers/claude.mjs +569 -105
- package/bin/installers/copilot.mjs +22 -12
- package/bin/installers/pi.mjs +37 -34
- package/docs/claude-hooks.md +3 -3
- package/docs/ideas/extension-manifests.md +75 -0
- package/docs/ideas/record-ideas-as-beads.md +115 -0
- package/docs/ideas/subagent-visibility.md +72 -0
- package/docs/ideas/task-session-context.md +89 -0
- package/docs/research/skill-design-research.md +4 -4
- package/docs/research/skill-improvements-analysis.md +1 -1
- package/docs/skills/skill-anatomy.md +38 -2
- package/docs/technical-direction/migration-execution-labels.md +132 -0
- package/extensions/agent-modes/README.md +3 -2
- package/extensions/agent-modes/discover.test.ts +30 -7
- package/extensions/agent-modes/discover.ts +1 -1
- package/extensions/skill-stats/index.test.ts +3 -2
- package/lib/cli.mjs +24 -6
- package/package.json +4 -4
- package/plugin.json +1 -1
- package/pnpm-workspace.yaml +1 -1
- package/scripts/build-copilot-agents.mjs +1 -1
- package/scripts/check-no-sync.mjs +49 -0
- package/scripts/lib/compose-agent.mjs +32 -14
- package/skills/engineering/design-an-interface/SKILL.md +78 -58
- package/skills/engineering/grill-with-docs/REFERENCE.md +78 -0
- package/skills/engineering/grill-with-docs/SKILL.md +66 -63
- package/skills/engineering/improve-codebase-architecture/SKILL.md +66 -48
- package/skills/engineering/technical-direction/FORMAT.md +38 -0
- package/skills/engineering/technical-direction/SKILL.md +70 -77
- package/skills/planning/classify-bead/SKILL.md +59 -35
- package/skills/planning/create-task/REFERENCE.md +40 -0
- package/skills/planning/create-task/SKILL.md +70 -66
- package/skills/planning/create-task/scripts/create-chores.ts +6 -1
- package/skills/planning/create-task/scripts/node_modules/.bin/tsc +21 -0
- package/skills/planning/create-task/scripts/node_modules/.bin/tsserver +21 -0
- package/skills/planning/create-task/scripts/node_modules/.bin/tsx +21 -0
- package/skills/planning/prd-to-tasks/REFERENCE.md +66 -0
- package/skills/planning/prd-to-tasks/SKILL.md +86 -170
- package/skills/planning/record-idea/FORMAT.md +44 -0
- package/skills/planning/record-idea/SKILL.md +70 -53
- package/skills/planning/request-refactor-plan/FORMAT.md +45 -0
- package/skills/planning/request-refactor-plan/SKILL.md +57 -46
- package/skills/planning/write-a-prd/FORMAT.md +54 -0
- package/skills/planning/write-a-prd/SKILL.md +49 -48
- package/skills/planning/write-a-ticket/SKILL.md +68 -53
- package/skills/productivity/handoff/SKILL.md +12 -0
- package/skills/productivity/init-beads/SKILL.md +48 -40
- package/skills/productivity/validate-agent-dir/scripts/node_modules/.bin/tsc +21 -0
- package/skills/productivity/validate-agent-dir/scripts/node_modules/.bin/tsserver +21 -0
- package/skills/productivity/validate-agent-dir/scripts/node_modules/.bin/tsx +21 -0
- package/skills/productivity/write-a-skill/SKILL.md +2 -1
- package/skills/review/maintain-agent-docs/SKILL.md +71 -51
- package/skills/workflow/hitl-collab/SKILL.md +86 -17
- package/skills/workflow/ralph/REFERENCE.md +22 -17
- package/skills/workflow/ralph/SKILL.md +88 -74
- package/skills/workflow/run-pipeline-stage/REFERENCE.md +22 -0
- package/skills/workflow/run-pipeline-stage/SKILL.md +91 -93
- package/skills/workflow/run-pipeline-stage/scripts/generate-progress.test.ts +6 -107
- package/skills/workflow/run-pipeline-stage/scripts/node_modules/.bin/tsc +21 -0
- package/skills/workflow/run-pipeline-stage/scripts/node_modules/.bin/tsserver +21 -0
- package/skills/workflow/run-pipeline-stage/scripts/node_modules/.bin/tsx +21 -0
- package/test/claude-installer.test.mjs +308 -0
- package/test/cli.test.mjs +40 -30
- package/test/copilot-installer.test.mjs +92 -82
- package/test/helpers/fake-claude.mjs +152 -0
- package/test/installer.test.mjs +309 -57
- package/test/pi-installer.test.mjs +115 -99
- package/token-map.README.md +21 -10
- package/token-map.json +7 -6
- package/.claude-plugin/marketplace.json +0 -16
- package/claude/.claude-plugin/plugin.json +0 -11
- package/claude/agents/plan.md +0 -58
- package/claude/agents/ralph-plan.md +0 -121
- package/claude/agents/ralph.md +0 -193
- package/claude/agents/strategy.md +0 -111
- package/claude/hooks/scripts/notify.mjs +0 -54
- package/claude/hooks.json +0 -38
- /package/{claude → claude-extras}/.mcp.json +0 -0
- /package/{claude → claude-extras}/scripts/statusline-command.sh +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, it } from "node:test";
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
|
-
import {
|
|
3
|
+
import { mkdtemp, mkdir, writeFile, rm, readFile, readdir, stat } from "node:fs/promises";
|
|
4
4
|
import { join, dirname } from "node:path";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
@@ -10,6 +10,16 @@ import { installPi } from "../bin/installers/pi.mjs";
|
|
|
10
10
|
// works against the canonical sources (agents/, skills/, token-map.json).
|
|
11
11
|
const REPO_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
12
12
|
|
|
13
|
+
/** True when a path exists (stat succeeds). */
|
|
14
|
+
async function pathExists(p) {
|
|
15
|
+
try {
|
|
16
|
+
await stat(p);
|
|
17
|
+
return true;
|
|
18
|
+
} catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
13
23
|
// ─── Fixtures ────────────────────────────────────────────────────────────────
|
|
14
24
|
|
|
15
25
|
const TOOL_MAP = {
|
|
@@ -37,29 +47,29 @@ function tokenMap(extra = {}) {
|
|
|
37
47
|
};
|
|
38
48
|
}
|
|
39
49
|
|
|
40
|
-
function makeFixture() {
|
|
41
|
-
const root =
|
|
50
|
+
async function makeFixture() {
|
|
51
|
+
const root = await mkdtemp(join(tmpdir(), "pi-installer-"));
|
|
42
52
|
const output = join(root, "out");
|
|
43
53
|
return {
|
|
44
54
|
root,
|
|
45
55
|
output,
|
|
46
|
-
cleanup: () =>
|
|
56
|
+
cleanup: async () => rm(root, { recursive: true, force: true }),
|
|
47
57
|
};
|
|
48
58
|
}
|
|
49
59
|
|
|
50
|
-
function writeFixture(fx, relPath, content) {
|
|
60
|
+
async function writeFixture(fx, relPath, content) {
|
|
51
61
|
const p = join(fx.root, relPath);
|
|
52
|
-
|
|
53
|
-
|
|
62
|
+
await mkdir(dirname(p), { recursive: true });
|
|
63
|
+
await writeFile(p, content);
|
|
54
64
|
}
|
|
55
65
|
|
|
56
66
|
/** Seed a minimal but realistic package tree (agents + token-map + skills). */
|
|
57
|
-
function seedPackage(fx, opts = {}) {
|
|
67
|
+
async function seedPackage(fx, opts = {}) {
|
|
58
68
|
const { agents = {}, skills = {}, tokenMap: map, version } = opts;
|
|
59
|
-
writeFixture(fx, "token-map.json", JSON.stringify(map ?? tokenMap(version ? { version } : {})));
|
|
69
|
+
await writeFixture(fx, "token-map.json", JSON.stringify(map ?? tokenMap(version ? { version } : {})));
|
|
60
70
|
for (const [name, def] of Object.entries(agents)) {
|
|
61
|
-
writeFixture(fx, `agents/${name}/agent.md`, def.body ?? `# ${name}\nBody of ${name}.`);
|
|
62
|
-
writeFixture(
|
|
71
|
+
await writeFixture(fx, `agents/${name}/agent.md`, def.body ?? `# ${name}\nBody of ${name}.`);
|
|
72
|
+
await writeFixture(
|
|
63
73
|
fx,
|
|
64
74
|
`agents/${name}/pi/frontmatter.json`,
|
|
65
75
|
JSON.stringify(
|
|
@@ -72,20 +82,20 @@ function seedPackage(fx, opts = {}) {
|
|
|
72
82
|
),
|
|
73
83
|
);
|
|
74
84
|
for (const [section, content] of Object.entries(def.sections ?? {})) {
|
|
75
|
-
writeFixture(fx, `agents/${name}/pi/${section}.md`, content);
|
|
85
|
+
await writeFixture(fx, `agents/${name}/pi/${section}.md`, content);
|
|
76
86
|
}
|
|
77
87
|
}
|
|
78
88
|
for (const [path, content] of Object.entries(skills)) {
|
|
79
|
-
writeFixture(fx, `skills/${path}`, content);
|
|
89
|
+
await writeFixture(fx, `skills/${path}`, content);
|
|
80
90
|
}
|
|
81
91
|
}
|
|
82
92
|
|
|
83
|
-
function listFiles(dir) {
|
|
84
|
-
if (!
|
|
93
|
+
async function listFiles(dir) {
|
|
94
|
+
if (!(await pathExists(dir))) return [];
|
|
85
95
|
const out = [];
|
|
86
|
-
for (const entry of
|
|
96
|
+
for (const entry of await readdir(dir, { withFileTypes: true })) {
|
|
87
97
|
const p = join(dir, entry.name);
|
|
88
|
-
if (entry.isDirectory()) out.push(...listFiles(p).map((f) => join(entry.name, f)));
|
|
98
|
+
if (entry.isDirectory()) out.push(...(await listFiles(p)).map((f) => join(entry.name, f)));
|
|
89
99
|
else out.push(entry.name);
|
|
90
100
|
}
|
|
91
101
|
return out;
|
|
@@ -94,10 +104,10 @@ function listFiles(dir) {
|
|
|
94
104
|
// ─── Agent composition ───────────────────────────────────────────────────────
|
|
95
105
|
|
|
96
106
|
describe("installPi — agent composition", () => {
|
|
97
|
-
it("writes one .agent.md per composable agent dir with pi frontmatter and no leftover tokens", () => {
|
|
98
|
-
const fx = makeFixture();
|
|
107
|
+
it("writes one .agent.md per composable agent dir with pi frontmatter and no leftover tokens", async () => {
|
|
108
|
+
const fx = await makeFixture();
|
|
99
109
|
try {
|
|
100
|
-
seedPackage(fx, {
|
|
110
|
+
await seedPackage(fx, {
|
|
101
111
|
agents: {
|
|
102
112
|
alpha: {
|
|
103
113
|
body: "# alpha\nUse {{TOOL:view}} to read.\n{{SECTION:polling}}\nRead {{PATH:skills_dir}}.",
|
|
@@ -112,11 +122,11 @@ describe("installPi — agent composition", () => {
|
|
|
112
122
|
},
|
|
113
123
|
});
|
|
114
124
|
|
|
115
|
-
const result = installPi({ root: fx.root, output: fx.output });
|
|
125
|
+
const result = await installPi({ root: fx.root, output: fx.output });
|
|
116
126
|
|
|
117
127
|
const file = join(fx.output, "agents", "alpha.agent.md");
|
|
118
|
-
assert.ok(
|
|
119
|
-
const content =
|
|
128
|
+
assert.ok(await pathExists(file), "agent file written");
|
|
129
|
+
const content = await readFile(file, "utf-8");
|
|
120
130
|
assert.ok(content.startsWith("---\n"), "yaml frontmatter");
|
|
121
131
|
assert.match(content, /name: "agent-cortex:alpha"/);
|
|
122
132
|
assert.match(content, /description: "Reads things\."/);
|
|
@@ -128,55 +138,55 @@ describe("installPi — agent composition", () => {
|
|
|
128
138
|
assert.equal(result.agents.length, 1);
|
|
129
139
|
assert.deepEqual(result.agents[0], { name: "alpha", filePath: file });
|
|
130
140
|
} finally {
|
|
131
|
-
fx.cleanup();
|
|
141
|
+
await fx.cleanup();
|
|
132
142
|
}
|
|
133
143
|
});
|
|
134
144
|
|
|
135
|
-
it("drops null-mapped tool tokens in prose and reports a warning", () => {
|
|
136
|
-
const fx = makeFixture();
|
|
145
|
+
it("drops null-mapped tool tokens in prose and reports a warning", async () => {
|
|
146
|
+
const fx = await makeFixture();
|
|
137
147
|
try {
|
|
138
|
-
seedPackage(fx, {
|
|
148
|
+
await seedPackage(fx, {
|
|
139
149
|
agents: {
|
|
140
150
|
alpha: { body: "# alpha\nAsk the user with {{TOOL:ask_user}}, then use {{TOOL:bash}}." },
|
|
141
151
|
},
|
|
142
152
|
});
|
|
143
153
|
|
|
144
|
-
const result = installPi({ root: fx.root, output: fx.output });
|
|
145
|
-
const content =
|
|
154
|
+
const result = await installPi({ root: fx.root, output: fx.output });
|
|
155
|
+
const content = await readFile(join(fx.output, "agents", "alpha.agent.md"), "utf-8");
|
|
146
156
|
|
|
147
157
|
assert.ok(!content.includes("ask_user"), "null-mapped token dropped from prose");
|
|
148
158
|
assert.ok(content.includes("use bash"), "mapped token substituted");
|
|
149
159
|
assert.ok(!/{{/.test(content));
|
|
150
160
|
assert.ok(result.warnings.some((w) => w.includes("{{TOOL:ask_user}}")), "drop reported as warning");
|
|
151
161
|
} finally {
|
|
152
|
-
fx.cleanup();
|
|
162
|
+
await fx.cleanup();
|
|
153
163
|
}
|
|
154
164
|
});
|
|
155
165
|
|
|
156
|
-
it("throws on an unknown tool token", () => {
|
|
157
|
-
const fx = makeFixture();
|
|
166
|
+
it("throws on an unknown tool token", async () => {
|
|
167
|
+
const fx = await makeFixture();
|
|
158
168
|
try {
|
|
159
|
-
seedPackage(fx, { agents: { alpha: { body: "# alpha\nUse {{TOOL:nope}} now." } } });
|
|
160
|
-
assert.
|
|
169
|
+
await seedPackage(fx, { agents: { alpha: { body: "# alpha\nUse {{TOOL:nope}} now." } } });
|
|
170
|
+
await assert.rejects(installPi({ root: fx.root, output: fx.output }), /unknown tool token {{TOOL:nope}}/);
|
|
161
171
|
} finally {
|
|
162
|
-
fx.cleanup();
|
|
172
|
+
await fx.cleanup();
|
|
163
173
|
}
|
|
164
174
|
});
|
|
165
175
|
|
|
166
|
-
it("throws when a section file referenced by {{SECTION:...}} is missing", () => {
|
|
167
|
-
const fx = makeFixture();
|
|
176
|
+
it("throws when a section file referenced by {{SECTION:...}} is missing", async () => {
|
|
177
|
+
const fx = await makeFixture();
|
|
168
178
|
try {
|
|
169
|
-
seedPackage(fx, { agents: { alpha: { body: "# alpha\n{{SECTION:missing}}" } } });
|
|
170
|
-
assert.
|
|
179
|
+
await seedPackage(fx, { agents: { alpha: { body: "# alpha\n{{SECTION:missing}}" } } });
|
|
180
|
+
await assert.rejects(installPi({ root: fx.root, output: fx.output }), /missing section "missing"/);
|
|
171
181
|
} finally {
|
|
172
|
-
fx.cleanup();
|
|
182
|
+
await fx.cleanup();
|
|
173
183
|
}
|
|
174
184
|
});
|
|
175
185
|
|
|
176
|
-
it("resolves named and relative {{PATH:...}} tokens against the plugin root", () => {
|
|
177
|
-
const fx = makeFixture();
|
|
186
|
+
it("resolves named and relative {{PATH:...}} tokens against the plugin root", async () => {
|
|
187
|
+
const fx = await makeFixture();
|
|
178
188
|
try {
|
|
179
|
-
seedPackage(fx, {
|
|
189
|
+
await seedPackage(fx, {
|
|
180
190
|
agents: {
|
|
181
191
|
alpha: {
|
|
182
192
|
body: "# alpha\nNamed: {{PATH:skills_dir}}\nRelative: {{PATH:skills/x/SKILL.md}}",
|
|
@@ -184,8 +194,8 @@ describe("installPi — agent composition", () => {
|
|
|
184
194
|
},
|
|
185
195
|
});
|
|
186
196
|
|
|
187
|
-
const result = installPi({ root: fx.root, output: fx.output });
|
|
188
|
-
const content =
|
|
197
|
+
const result = await installPi({ root: fx.root, output: fx.output });
|
|
198
|
+
const content = await readFile(join(fx.output, "agents", "alpha.agent.md"), "utf-8");
|
|
189
199
|
|
|
190
200
|
assert.ok(
|
|
191
201
|
content.includes("Named: ~/.pi/agent/npm/node_modules/@jaybeeuu/agent-cortex/skills"),
|
|
@@ -197,38 +207,38 @@ describe("installPi — agent composition", () => {
|
|
|
197
207
|
);
|
|
198
208
|
assert.equal(result.warnings.length, 0);
|
|
199
209
|
} finally {
|
|
200
|
-
fx.cleanup();
|
|
210
|
+
await fx.cleanup();
|
|
201
211
|
}
|
|
202
212
|
});
|
|
203
213
|
|
|
204
|
-
it("honours the --plugin-root override for path resolution", () => {
|
|
205
|
-
const fx = makeFixture();
|
|
214
|
+
it("honours the --plugin-root override for path resolution", async () => {
|
|
215
|
+
const fx = await makeFixture();
|
|
206
216
|
try {
|
|
207
|
-
seedPackage(fx, {
|
|
217
|
+
await seedPackage(fx, {
|
|
208
218
|
agents: {
|
|
209
219
|
alpha: { body: "# alpha\n{{PATH:skills/x/SKILL.md}}" },
|
|
210
220
|
},
|
|
211
221
|
});
|
|
212
222
|
|
|
213
223
|
const pluginRoot = join(fx.root, "checkout");
|
|
214
|
-
installPi({ root: fx.root, output: fx.output, pluginRoot });
|
|
215
|
-
const content =
|
|
224
|
+
await installPi({ root: fx.root, output: fx.output, pluginRoot });
|
|
225
|
+
const content = await readFile(join(fx.output, "agents", "alpha.agent.md"), "utf-8");
|
|
216
226
|
assert.ok(content.includes(join(pluginRoot, "skills", "x", "SKILL.md")), "plugin root override used");
|
|
217
227
|
} finally {
|
|
218
|
-
fx.cleanup();
|
|
228
|
+
await fx.cleanup();
|
|
219
229
|
}
|
|
220
230
|
});
|
|
221
231
|
|
|
222
|
-
it("rejects token-map.json versions newer than the implemented contract", () => {
|
|
223
|
-
const fx = makeFixture();
|
|
232
|
+
it("rejects token-map.json versions newer than the implemented contract", async () => {
|
|
233
|
+
const fx = await makeFixture();
|
|
224
234
|
try {
|
|
225
|
-
seedPackage(fx, {
|
|
235
|
+
await seedPackage(fx, {
|
|
226
236
|
version: 99,
|
|
227
237
|
agents: { alpha: { body: "# alpha" } },
|
|
228
238
|
});
|
|
229
|
-
assert.
|
|
239
|
+
await assert.rejects(installPi({ root: fx.root, output: fx.output }), /version 99/);
|
|
230
240
|
} finally {
|
|
231
|
-
fx.cleanup();
|
|
241
|
+
await fx.cleanup();
|
|
232
242
|
}
|
|
233
243
|
});
|
|
234
244
|
});
|
|
@@ -236,10 +246,10 @@ describe("installPi — agent composition", () => {
|
|
|
236
246
|
// ─── Skills ──────────────────────────────────────────────────────────────────
|
|
237
247
|
|
|
238
248
|
describe("installPi — skills", () => {
|
|
239
|
-
it("copies skills into <output>/skills preserving the grouped layout with substituted tokens", () => {
|
|
240
|
-
const fx = makeFixture();
|
|
249
|
+
it("copies skills into <output>/skills preserving the grouped layout with substituted tokens", async () => {
|
|
250
|
+
const fx = await makeFixture();
|
|
241
251
|
try {
|
|
242
|
-
seedPackage(fx, {
|
|
252
|
+
await seedPackage(fx, {
|
|
243
253
|
agents: { alpha: { body: "# alpha" } },
|
|
244
254
|
skills: {
|
|
245
255
|
"engineering/echo/SKILL.md":
|
|
@@ -248,11 +258,11 @@ describe("installPi — skills", () => {
|
|
|
248
258
|
},
|
|
249
259
|
});
|
|
250
260
|
|
|
251
|
-
installPi({ root: fx.root, output: fx.output });
|
|
261
|
+
await installPi({ root: fx.root, output: fx.output });
|
|
252
262
|
|
|
253
263
|
const skillFile = join(fx.output, "skills", "engineering", "echo", "SKILL.md");
|
|
254
|
-
assert.ok(
|
|
255
|
-
const content =
|
|
264
|
+
assert.ok(await pathExists(skillFile), "skill copied");
|
|
265
|
+
const content = await readFile(skillFile, "utf-8");
|
|
256
266
|
assert.ok(content.includes("description: Echoes read."), "token substituted in skill frontmatter");
|
|
257
267
|
assert.ok(content.includes("Use read to read and for help."), "null-mapped {{TOOL:skill}} dropped");
|
|
258
268
|
assert.ok(
|
|
@@ -263,10 +273,10 @@ describe("installPi — skills", () => {
|
|
|
263
273
|
);
|
|
264
274
|
assert.ok(!/{{/.test(content), "no literal tokens remain in skill");
|
|
265
275
|
|
|
266
|
-
const script =
|
|
276
|
+
const script = await readFile(join(fx.output, "skills", "engineering", "echo", "scripts", "run.sh"), "utf-8");
|
|
267
277
|
assert.equal(script, "#!/bin/sh\necho hi\n", "non-md files copied verbatim");
|
|
268
278
|
} finally {
|
|
269
|
-
fx.cleanup();
|
|
279
|
+
await fx.cleanup();
|
|
270
280
|
}
|
|
271
281
|
});
|
|
272
282
|
});
|
|
@@ -274,37 +284,37 @@ describe("installPi — skills", () => {
|
|
|
274
284
|
// ─── Output / dry-run ────────────────────────────────────────────────────────
|
|
275
285
|
|
|
276
286
|
describe("installPi — output & dry-run", () => {
|
|
277
|
-
it("dry-run writes nothing but reports the planned output", () => {
|
|
278
|
-
const fx = makeFixture();
|
|
287
|
+
it("dry-run writes nothing but reports the planned output", async () => {
|
|
288
|
+
const fx = await makeFixture();
|
|
279
289
|
try {
|
|
280
|
-
seedPackage(fx, {
|
|
290
|
+
await seedPackage(fx, {
|
|
281
291
|
agents: { alpha: { body: "# alpha" } },
|
|
282
292
|
skills: { "engineering/echo/SKILL.md": "---\nname: echo\ndescription: d\n---\n\nBody." },
|
|
283
293
|
});
|
|
284
294
|
|
|
285
|
-
const result = installPi({ root: fx.root, output: fx.output, dryRun: true });
|
|
295
|
+
const result = await installPi({ root: fx.root, output: fx.output, dryRun: true });
|
|
286
296
|
|
|
287
297
|
assert.equal(result.dryRun, true);
|
|
288
|
-
assert.equal(listFiles(fx.output).length, 0, "no files written in dry-run");
|
|
298
|
+
assert.equal((await listFiles(fx.output)).length, 0, "no files written in dry-run");
|
|
289
299
|
assert.equal(result.agents.length, 1);
|
|
290
300
|
assert.equal(result.agents[0].name, "alpha");
|
|
291
301
|
assert.equal(result.skills.skills, 1);
|
|
292
302
|
// Planned paths are still reported so the user can see what would happen.
|
|
293
303
|
assert.ok(result.agents[0].filePath.startsWith(fx.output));
|
|
294
304
|
} finally {
|
|
295
|
-
fx.cleanup();
|
|
305
|
+
await fx.cleanup();
|
|
296
306
|
}
|
|
297
307
|
});
|
|
298
308
|
|
|
299
|
-
it("creates the output directory hierarchy when needed", () => {
|
|
300
|
-
const fx = makeFixture();
|
|
309
|
+
it("creates the output directory hierarchy when needed", async () => {
|
|
310
|
+
const fx = await makeFixture();
|
|
301
311
|
try {
|
|
302
|
-
seedPackage(fx, { agents: { alpha: { body: "# alpha" } } });
|
|
312
|
+
await seedPackage(fx, { agents: { alpha: { body: "# alpha" } } });
|
|
303
313
|
const deep = join(fx.output, "nested", "deep");
|
|
304
|
-
installPi({ root: fx.root, output: deep });
|
|
305
|
-
assert.ok(
|
|
314
|
+
await installPi({ root: fx.root, output: deep });
|
|
315
|
+
assert.ok(await pathExists(join(deep, "agents", "alpha.agent.md")));
|
|
306
316
|
} finally {
|
|
307
|
-
fx.cleanup();
|
|
317
|
+
await fx.cleanup();
|
|
308
318
|
}
|
|
309
319
|
});
|
|
310
320
|
});
|
|
@@ -312,60 +322,66 @@ describe("installPi — output & dry-run", () => {
|
|
|
312
322
|
// ─── Integration against the real repo ───────────────────────────────────────
|
|
313
323
|
|
|
314
324
|
describe("installPi — real repo integration", () => {
|
|
315
|
-
it("installs every canonical agent with pi tool names and no leftover tokens", () => {
|
|
316
|
-
const fx = makeFixture();
|
|
325
|
+
it("installs every canonical agent with pi tool names and no leftover tokens", async () => {
|
|
326
|
+
const fx = await makeFixture();
|
|
317
327
|
try {
|
|
318
|
-
const result = installPi({ root: REPO_ROOT, output: fx.output });
|
|
328
|
+
const result = await installPi({ root: REPO_ROOT, output: fx.output });
|
|
319
329
|
|
|
320
330
|
const names = result.agents.map((a) => a.name).sort();
|
|
321
331
|
assert.deepEqual(names, ["plan", "ralph", "ralph-plan", "strategy"]);
|
|
322
332
|
|
|
323
333
|
// ralph pi frontmatter: ["bash", "view", "rg", "glob", "task", "read_agent", "wait_for_agents"]
|
|
324
|
-
const ralph =
|
|
334
|
+
const ralph = await readFile(join(fx.output, "agents", "ralph.agent.md"), "utf-8");
|
|
325
335
|
assert.match(ralph, /name: "agent-cortex:ralph"/);
|
|
326
336
|
assert.match(ralph, /tools: "bash read grep find task read_agent wait_for_agents"/);
|
|
327
337
|
assert.ok(ralph.includes("## Spawning subagents (PI)"), "{{SECTION:polling}} composed from pi/polling.md");
|
|
328
338
|
const bodyTokens = ralph.split("---\n").slice(2).join("---\n");
|
|
329
339
|
assert.ok(!/{{/.test(bodyTokens), "no literal tokens in ralph body");
|
|
330
340
|
|
|
331
|
-
// plan body carries the relative {{PATH:...}} resolved against the plugin root
|
|
332
|
-
|
|
333
|
-
|
|
341
|
+
// plan body carries the relative {{PATH:...}} resolved against the plugin root.
|
|
342
|
+
// Tool list preserves authoring order (composer translateToolList, unlike the
|
|
343
|
+
// runtime agent-modes translateTools which sorts): ask_user → ask_questions,
|
|
344
|
+
// skill → read (last position).
|
|
345
|
+
const plan = await readFile(join(fx.output, "agents", "plan.agent.md"), "utf-8");
|
|
346
|
+
assert.match(plan, /tools: "bash read edit write grep find ask_questions task read_agent read"/);
|
|
334
347
|
assert.ok(
|
|
335
348
|
plan.includes("~/.pi/agent/npm/node_modules/@jaybeeuu/agent-cortex/skills/workflow/plan/SKILL.md"),
|
|
336
349
|
"plan PATH token resolved against token-map plugin root",
|
|
337
350
|
);
|
|
338
351
|
|
|
339
|
-
// ralph-plan prose {{TOOL:ask_user}}
|
|
340
|
-
const ralphPlan =
|
|
341
|
-
assert.ok(!ralphPlan.includes("ask_user"), "
|
|
352
|
+
// ralph-plan prose {{TOOL:ask_user}} now substitutes to ask_questions
|
|
353
|
+
const ralphPlan = await readFile(join(fx.output, "agents", "ralph-plan.agent.md"), "utf-8");
|
|
354
|
+
assert.ok(!ralphPlan.includes("ask_user"), "ask_user never appears in ralph-plan body");
|
|
355
|
+
assert.ok(ralphPlan.includes("ask_questions"), "{{TOOL:ask_user}} substituted to ask_questions");
|
|
342
356
|
assert.ok(
|
|
343
|
-
result.warnings.some((w) => w.includes("ask_user")),
|
|
344
|
-
"ralph-plan
|
|
357
|
+
!result.warnings.some((w) => w.includes("ask_user")),
|
|
358
|
+
"no null-tool warning for ralph-plan (ask_user now maps to ask_questions)",
|
|
345
359
|
);
|
|
346
360
|
|
|
347
|
-
// strategy: web_fetch → fetch_content; ask_user
|
|
348
|
-
|
|
349
|
-
|
|
361
|
+
// strategy: web_fetch → fetch_content; ask_user → ask_questions; skill → read
|
|
362
|
+
// (authoring order preserved: bash, view→read, rg→grep, glob→find, ask_user→
|
|
363
|
+
// ask_questions, web_fetch→fetch_content, skill→read, edit, create)
|
|
364
|
+
const strategy = await readFile(join(fx.output, "agents", "strategy.agent.md"), "utf-8");
|
|
365
|
+
assert.match(strategy, /tools: "bash read grep find ask_questions fetch_content read edit write"/);
|
|
350
366
|
} finally {
|
|
351
|
-
fx.cleanup();
|
|
367
|
+
await fx.cleanup();
|
|
352
368
|
}
|
|
353
369
|
});
|
|
354
370
|
|
|
355
|
-
it("substitutes tokens in every installed skill markdown", () => {
|
|
356
|
-
const fx = makeFixture();
|
|
371
|
+
it("substitutes tokens in every installed skill markdown", async () => {
|
|
372
|
+
const fx = await makeFixture();
|
|
357
373
|
try {
|
|
358
|
-
const result = installPi({ root: REPO_ROOT, output: fx.output });
|
|
374
|
+
const result = await installPi({ root: REPO_ROOT, output: fx.output });
|
|
359
375
|
assert.ok(result.skills.skills > 20, `expected the full skill tree, got ${result.skills.skills}`);
|
|
360
376
|
|
|
361
|
-
const files = listFiles(join(fx.output, "skills")).filter((f) => f.endsWith(".md"));
|
|
377
|
+
const files = (await listFiles(join(fx.output, "skills"))).filter((f) => f.endsWith(".md"));
|
|
362
378
|
assert.ok(files.length > 0);
|
|
363
379
|
for (const f of files) {
|
|
364
|
-
const content =
|
|
380
|
+
const content = await readFile(join(fx.output, "skills", f), "utf-8");
|
|
365
381
|
assert.ok(!/{{/.test(content), `no literal tokens in skill ${f}`);
|
|
366
382
|
}
|
|
367
383
|
} finally {
|
|
368
|
-
fx.cleanup();
|
|
384
|
+
await fx.cleanup();
|
|
369
385
|
}
|
|
370
386
|
});
|
|
371
387
|
});
|
package/token-map.README.md
CHANGED
|
@@ -16,7 +16,7 @@ decisions behind the map.
|
|
|
16
16
|
|---|---|---|---|
|
|
17
17
|
| `copilot` | GitHub Copilot CLI | `agents/<name>/` composable dirs → generated `agents/*.agent.md` | `~/.copilot/installed-plugins/_direct/agent-cortex` |
|
|
18
18
|
| `pi` | pi coding agent | `agents/<name>/` composable dirs (composed at runtime by agent-modes, or installed install-time by `agent-cortex install pi`) | `~/.pi/agent/npm/node_modules/@jaybeeuu/agent-cortex` |
|
|
19
|
-
| `claude` | Claude Code |
|
|
19
|
+
| `claude` | Claude Code | `~/.agent-cortex/claude/agents/*.md` (materialised home install) | `${CLAUDE_PLUGIN_ROOT}` (env var) |
|
|
20
20
|
|
|
21
21
|
The harness ids match the values accepted by `agent-cortex install <harness>` in
|
|
22
22
|
`lib/cli.mjs` (`SUPPORTED_HARNESSES`). The pi `plugin_root` row is the npm-layout
|
|
@@ -48,11 +48,19 @@ rather than inventing a dotted form:
|
|
|
48
48
|
harness. Source of truth for the copilot → claude half is `token-map.json`'s own claude
|
|
49
49
|
column, consumed by `bin/installers/claude.mjs` (the install-time generator — PR #91, #92); the pi column is grounded
|
|
50
50
|
in pi's documented built-in tool surface (`read`, `bash`, `edit`, `write`, `grep`, `find`,
|
|
51
|
-
`ls`) plus `
|
|
51
|
+
`ls`) plus `ask_questions` — added by the pi-questions package (mapped from
|
|
52
|
+
`ask_user`) — and `task` / `read_agent` / `wait_for_agents` subagent tools; `skill` maps
|
|
53
|
+
to `read` (skills load by reading the listed SKILL.md path).
|
|
52
54
|
- **`paths`** — `plugin_root`, `agents_dir`, and `skills_dir`. These are the install-time
|
|
53
55
|
variables that differ per harness. `plugin_root` is the anchor; the other two resolve
|
|
54
56
|
relative to it (`{base, relative}` specs) so a harness only overrides the single root it
|
|
55
57
|
actually installs into.
|
|
58
|
+
Claude's `plugin_root` stays the `${CLAUDE_PLUGIN_ROOT}` env-var token, never a literal
|
|
59
|
+
`~/.agent-cortex/claude`: Claude Code **copies** marketplace-installed plugins — including
|
|
60
|
+
local directory sources like `~/.agent-cortex/claude` — into its own cache, so only the
|
|
61
|
+
runtime env var (expanded per context by Claude Code) points at wherever the plugin
|
|
62
|
+
actually runs from. A literal home path in substituted files would break under
|
|
63
|
+
cache-copy, so the installer passes the env form through verbatim.
|
|
56
64
|
- **`agents`** — the `agent-cortex:` name-prefix convention per harness (claude strips the
|
|
57
65
|
prefix during conversion) and the deferred-agent exception (claude's `ralph` is authored
|
|
58
66
|
natively, never generated). Agent names themselves do not get per-row mappings because
|
|
@@ -83,8 +91,10 @@ The pi install-time installer (`bin/installers/pi.mjs`) follows the strict contr
|
|
|
83
91
|
One consumer deviates deliberately: the pi `agent-modes` extension reads the map at
|
|
84
92
|
**runtime** (not install time) to compose agent prompts from the composable agents
|
|
85
93
|
format. For it, an unmapped tool name passes through unchanged — it may be a
|
|
86
|
-
native PI tool — while a mapped-null tool
|
|
87
|
-
|
|
94
|
+
native PI tool — while a mapped-null tool is omitted with a warning. The pi column
|
|
95
|
+
currently maps every canonical tool (`ask_user` → `ask_questions`, `skill` → `read`),
|
|
96
|
+
so the warn-and-omit path applies only to future null entries. See
|
|
97
|
+
`extensions/agent-modes/README.md`.
|
|
88
98
|
|
|
89
99
|
### 4. Token substitution contract
|
|
90
100
|
|
|
@@ -103,9 +113,9 @@ The full contract is embedded in `token-map.json` under `contract`. In summary,
|
|
|
103
113
|
- Write tokens with the **canonical (copilot) names**: `Use {{TOOL:task}} to spawn
|
|
104
114
|
subagents`, `Read {{PATH:skills/workflow/plan/SKILL.md}}`.
|
|
105
115
|
- Never embed a harness-specific name in the canonical sources (`agents/<name>/agent.md`,
|
|
106
|
-
`skills/**/SKILL.md`) — that defeats the map. The
|
|
107
|
-
generated `agents/*.agent.md` files, and any installed
|
|
108
|
-
substituted names appear.
|
|
116
|
+
`skills/**/SKILL.md`) — that defeats the map. The materialised Claude plugin
|
|
117
|
+
(`~/.agent-cortex/claude`), the generated `agents/*.agent.md` files, and any installed
|
|
118
|
+
copies are the only places substituted names appear.
|
|
109
119
|
- When you add a new tool to an agent's frontmatter, add a row to `tools` in
|
|
110
120
|
`token-map.json` in the same change — the contract treats unknown tools as errors, so a
|
|
111
121
|
missing row breaks the install pipeline loudly instead of silently.
|
|
@@ -119,9 +129,10 @@ The full contract is embedded in `token-map.json` under `contract`. In summary,
|
|
|
119
129
|
`{{PATH:...}}` against the map and `{{SECTION:...}}` from the per-harness section
|
|
120
130
|
files; `bin/installers/copilot.mjs` is the shared generator both `pnpm build:copilot`
|
|
121
131
|
and `agent-cortex install copilot` run.
|
|
122
|
-
- `bin/installers/claude.mjs` — the single Claude generator
|
|
123
|
-
`agent-cortex install claude`
|
|
124
|
-
|
|
132
|
+
- `bin/installers/claude.mjs` — the single Claude generator: plain
|
|
133
|
+
`agent-cortex install claude` materialises the plugin into `~/.agent-cortex/claude`
|
|
134
|
+
with token-substituted skills and registers it with Claude Code; `--output <dir>` is the
|
|
135
|
+
generate-only form (tests/CI structural checks). The repo commits no `claude/` output.
|
|
125
136
|
- `bin/installers/pi.mjs` — the pi harness installer (`agent-cortex install pi`), an
|
|
126
137
|
install-time consumer of the map's contract.
|
|
127
138
|
- `lib/cli.mjs` — `SUPPORTED_HARNESSES`, the install command surface the harness ids come
|
package/token-map.json
CHANGED
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
"grep": { "copilot": "grep", "claude": "Grep", "pi": "grep" },
|
|
21
21
|
"rg": { "copilot": "rg", "claude": "Grep", "pi": "grep" },
|
|
22
22
|
"glob": { "copilot": "glob", "claude": "Glob", "pi": "find" },
|
|
23
|
-
"ask_user": { "copilot": "ask_user", "claude": "AskUserQuestion", "pi":
|
|
23
|
+
"ask_user": { "copilot": "ask_user", "claude": "AskUserQuestion", "pi": "ask_questions" },
|
|
24
24
|
"web_fetch": { "copilot": "web_fetch", "claude": "WebFetch", "pi": "fetch_content" },
|
|
25
|
-
"skill": { "copilot": "skill", "claude": "Skill", "pi":
|
|
25
|
+
"skill": { "copilot": "skill", "claude": "Skill", "pi": "read" },
|
|
26
26
|
"task": { "copilot": "task", "claude": "Task", "pi": "task" },
|
|
27
27
|
"read_agent": { "copilot": "read_agent", "claude": null, "pi": "read_agent" },
|
|
28
28
|
"wait_for_agents": { "copilot": null, "claude": null, "pi": "wait_for_agents" }
|
|
@@ -56,21 +56,22 @@
|
|
|
56
56
|
"sources": {
|
|
57
57
|
"copilot": "agents/<name>/ composable dirs (canonical) — composed to agents/*.agent.md by scripts/build-copilot-agents.mjs",
|
|
58
58
|
"pi": "agents/<name>/ composable dirs (canonical) — composed at runtime by the agent-modes extension",
|
|
59
|
-
"claude": "agents/<name>/ composable dirs (canonical) — composed by bin/installers/claude.mjs (install-time generator
|
|
59
|
+
"claude": "agents/<name>/ composable dirs (canonical) — composed by bin/installers/claude.mjs (install-time generator, the only generator — the repo commits no claude/ output), plus agents-native/*.md for deferred agents"
|
|
60
60
|
}
|
|
61
61
|
},
|
|
62
62
|
"notes": {
|
|
63
63
|
"tools": {
|
|
64
64
|
"rg": "Copilot alias for grep. Claude and pi collapse it onto their single search tool (Grep / grep) — same collapse rule as the build script.",
|
|
65
65
|
"glob": "Claude has a dedicated Glob tool. pi's built-in pattern-search tool is find (hypa_find when the pi-hypa extension is installed).",
|
|
66
|
-
"ask_user": "Copilot (ask_user) and Claude (AskUserQuestion) prompt the user mid-run. pi
|
|
66
|
+
"ask_user": "Copilot (ask_user) and Claude (AskUserQuestion) prompt the user mid-run. pi's equivalent is ask_questions, provided by the pi-questions package (pi install npm:pi-questions; declared in pi/settings.json) — the genuine interactive-questioning tool, restoring grilling in plan/ralph-plan/strategy modes. Like fetch_content (pi-web-access), it is only present when its package is installed, so the mapping depends on that package (see the extension-manifests idea). pi still intentionally ships no permission popups; ask_questions is the interactive mid-run question tool, not a popup.",
|
|
67
67
|
"web_fetch": "pi's fetch_content comes from the pi-web-access package (declared in pi/settings.json).",
|
|
68
|
-
"skill": "Copilot (skill) and Claude (Skill) expose skills as a tool. pi
|
|
68
|
+
"skill": "Copilot (skill) and Claude (Skill) expose skills as a tool. pi has no dedicated skill tool — skills are surfaced in the always-present 'Available skills' context block and loaded by reading the listed SKILL.md path ({{PATH:<skill>/SKILL.md}}). The pi mapping is read, the tool used to load a skill's file; a dedicated skill tool is a follow-on design candidate if prompting proves insufficient.",
|
|
69
69
|
"read_agent": "Claude's Task returns results inline — there is no separate read step, so read_agent drops on claude (null). Copilot uses the task + read_agent poll loop; pi blocks on wait_for_agents and keeps read_agent as a fallback.",
|
|
70
70
|
"wait_for_agents": "PI-only blocking wait over background subagent runs (the task tool): returns the results of completed agents and STILL RUNNING for the rest. Copilot has no equivalent tool (its task + read_agent poll loop stays) and Claude's Task delivers results inline with no wait step — both null."
|
|
71
71
|
},
|
|
72
72
|
"paths": {
|
|
73
|
-
"pi": "plugin_root defaults to the npm-package install layout (~/.pi/agent/npm/node_modules/@jaybeeuu/agent-cortex). Local-checkout installs (pi/settings.json packages entry referencing a local src path) or symlinked installs differ — installers must compute plugin_root from the actual resolution and can override it."
|
|
73
|
+
"pi": "plugin_root defaults to the npm-package install layout (~/.pi/agent/npm/node_modules/@jaybeeuu/agent-cortex). Local-checkout installs (pi/settings.json packages entry referencing a local src path) or symlinked installs differ — installers must compute plugin_root from the actual resolution and can override it.",
|
|
74
|
+
"claude": "plugin_root stays ${CLAUDE_PLUGIN_ROOT} (env var) for the materialised plugin — never a literal ~/.agent-cortex/claude. Claude Code copies marketplace-installed plugins (including local directory sources like ~/.agent-cortex/claude) into its own cache and expands the env var per context, so a literal home path in substituted files would break under cache-copy; the installer passes the env form through verbatim."
|
|
74
75
|
},
|
|
75
76
|
"agents": {
|
|
76
77
|
"prefix": "Claude strips the agent-cortex: name prefix during conversion (NAME_PREFIX in bin/installers/claude.mjs): copilot/pi agents are agent-cortex:<slug>, claude agents are <slug>.",
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "jaybeeuu",
|
|
3
|
-
"owner": {
|
|
4
|
-
"name": "jaybeeuu"
|
|
5
|
-
},
|
|
6
|
-
"metadata": {
|
|
7
|
-
"description": "jaybeeuu's personal Claude Code plugins."
|
|
8
|
-
},
|
|
9
|
-
"plugins": [
|
|
10
|
-
{
|
|
11
|
-
"name": "agent-cortex",
|
|
12
|
-
"source": "./claude",
|
|
13
|
-
"description": "Personal Claude Code plugin with custom agents and skills"
|
|
14
|
-
}
|
|
15
|
-
]
|
|
16
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "agent-cortex",
|
|
3
|
-
"version": "1.38.1",
|
|
4
|
-
"description": "Personal Claude Code plugin with custom agents and skills",
|
|
5
|
-
"author": {
|
|
6
|
-
"name": "jaybeeuu"
|
|
7
|
-
},
|
|
8
|
-
"license": "MIT",
|
|
9
|
-
"repository": "https://github.com/jaybeeuu/agent-cortex",
|
|
10
|
-
"hooks": "./hooks.json"
|
|
11
|
-
}
|
package/claude/agents/plan.md
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
# GENERATED from agents/plan/ by bin/installers/claude.mjs — DO NOT EDIT.
|
|
3
|
-
name: plan
|
|
4
|
-
description: "Guide a feature from first discussion through PRD and task breakdown to classified beads ready for ralph execution. Full end-to-end planning workflow using the plan skill. Use when starting a new feature, scoping work to hand off to ralph, or saying 'plan this'."
|
|
5
|
-
tools: Bash, Read, Edit, Write, Grep, Glob, AskUserQuestion, Task, Skill
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
# plan — end-to-end feature planning agent
|
|
9
|
-
|
|
10
|
-
You are a planning agent. Your job is to guide a feature from first discussion through PRD and task breakdown to classified beads ready for ralph execution.
|
|
11
|
-
|
|
12
|
-
You follow the **plan** skill workflow. The skill is defined in `skills/workflow/plan/SKILL.md`. Read it now to load the full instructions.
|
|
13
|
-
|
|
14
|
-
## Workflow summary
|
|
15
|
-
|
|
16
|
-
### Phase 1 — Understand and specify
|
|
17
|
-
1. Ask the user to describe the problem, goal, or feature.
|
|
18
|
-
2. Explore the codebase to verify assertions and understand current state.
|
|
19
|
-
3. Interview the user relentlessly — scope, data models, module boundaries, trade-offs.
|
|
20
|
-
4. Sketch major modules.
|
|
21
|
-
5. Draft a PRD using the `write-a-prd` template.
|
|
22
|
-
6. Present the PRD for user approval.
|
|
23
|
-
7. Create an epic bead from the approved PRD.
|
|
24
|
-
|
|
25
|
-
### Phase 2 — Break into tasks
|
|
26
|
-
8. Review PRD stories and identify vertical-slice phases.
|
|
27
|
-
9. Define each phase (title, stories, what to build, acceptance criteria).
|
|
28
|
-
10. Create epics for multi-phase work.
|
|
29
|
-
11. Present the breakdown for user approval.
|
|
30
|
-
|
|
31
|
-
### Phase 3 — Create and classify beads
|
|
32
|
-
13. Create task beads via `create-task` for each task.
|
|
33
|
-
14. Tag parent epics with aggregate classification.
|
|
34
|
-
15. Present the full bead tree.
|
|
35
|
-
|
|
36
|
-
### Phase 4 — Prepare handoff to ralph
|
|
37
|
-
16. Determine the handoff bead (epic or meta-epic).
|
|
38
|
-
17. Tag it `workflow:ralph`.
|
|
39
|
-
18. Push bead state with `bd dolt push`.
|
|
40
|
-
19. Present the handoff summary.
|
|
41
|
-
|
|
42
|
-
## Permitted writes
|
|
43
|
-
|
|
44
|
-
| Location | Purpose |
|
|
45
|
-
|---|---|
|
|
46
|
-
| `.agent-cortex/working-docs/` | Research notes, decisions, exploration findings — gitignored |
|
|
47
|
-
| Bead fields (`description`, `design`) | Plans and specifications |
|
|
48
|
-
| `docs/prd/<topic>.md` | PRD documents |
|
|
49
|
-
|
|
50
|
-
Do **not** modify source code or tracked documentation outside these locations.
|
|
51
|
-
|
|
52
|
-
## Constraints
|
|
53
|
-
|
|
54
|
-
- Never modify source code.
|
|
55
|
-
- Always get user approval before moving between phases.
|
|
56
|
-
- Each phase has a gate — do not skip it.
|
|
57
|
-
- Work in `.agent-cortex/working-docs/` for scratchpad notes.
|
|
58
|
-
- Use `bd` commands for all bead operations.
|