@jaybeeuu/agent-cortex 2.6.0 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +269 -0
- package/README.md +102 -8
- package/agents/README.md +2 -2
- package/agents/ralph/agent.md +19 -16
- package/agents/ralph/claude/wake.md +4 -4
- package/agents/ralph/pi/wake.md +15 -20
- package/bin/agent-cortex.mjs +176 -67
- package/bin/installers/claude.mjs +33 -8
- package/bin/installers/pi.mjs +11 -1
- package/docs/skills/skill-anatomy.md +212 -0
- package/extensions/post-merge-watch/README.md +133 -0
- package/extensions/post-merge-watch/index.test.ts +247 -0
- package/extensions/post-merge-watch/index.ts +123 -0
- package/extensions/post-merge-watch/package.json +16 -0
- package/extensions/post-merge-watch/watch.test.ts +469 -0
- package/extensions/post-merge-watch/watch.ts +431 -0
- package/extensions/session-clear/README.md +74 -0
- package/extensions/session-clear/index.test.ts +524 -0
- package/extensions/session-clear/index.ts +159 -0
- package/extensions/session-clear/package.json +16 -0
- package/lib/cli.mjs +88 -1
- package/lib/harness-update.mjs +254 -0
- package/lib/harness-versions.mjs +36 -2
- package/lib/self-update.mjs +553 -0
- package/package.json +2 -1
- package/scripts/lib/docs.mjs +67 -0
- package/skills/engineering/implement-task/SKILL.md +124 -0
- package/skills/engineering/tdd/SKILL.md +2 -0
- package/skills/planning/create-task/REFERENCE.md +20 -3
- package/skills/planning/create-task/SKILL.md +1 -1
- package/skills/planning/create-task/pipeline.json +16 -0
- package/skills/planning/create-task/scripts/create-chores.mjs +51 -24
- package/skills/planning/create-task/templates/code.md +8 -3
- package/skills/planning/create-task/templates/document.md +8 -3
- package/skills/planning/create-task/templates/fix.md +8 -3
- package/skills/planning/create-task/templates/housekeeping.md +69 -0
- package/skills/planning/create-task/templates/post-merge.md +67 -0
- package/skills/planning/create-task/templates/review.md +8 -3
- package/skills/planning/create-task/templates/verify.md +8 -3
- package/skills/productivity/bd-tool/SKILL.md +55 -0
- package/skills/productivity/write-a-skill/SKILL.md +2 -2
- package/skills/review/refactor-skill/SKILL.md +2 -2
- package/skills/review/review-defects/EXAMPLES.md +122 -0
- package/skills/review/review-defects/SKILL.md +91 -0
- package/skills/style/style-tests/EXAMPLES.md +30 -0
- package/skills/style/style-tests/SKILL.md +10 -6
- package/skills/workflow/plan/SKILL.md +14 -3
- package/skills/workflow/ralph/REFERENCE.md +137 -30
- package/skills/workflow/ralph/SKILL.md +26 -16
- package/skills/workflow/ralph/scripts/post-merge-check.mjs +651 -0
- package/skills/workflow/run-pipeline-stage/SKILL.md +41 -23
- package/skills/workflow/run-pipeline-stage/playbooks/code.md +14 -8
- package/skills/workflow/run-pipeline-stage/playbooks/document.md +2 -2
- package/skills/workflow/run-pipeline-stage/playbooks/fix.md +2 -2
- package/skills/workflow/run-pipeline-stage/playbooks/housekeeping.md +58 -0
- package/skills/workflow/run-pipeline-stage/playbooks/post-merge.md +54 -0
- package/skills/workflow/run-pipeline-stage/playbooks/review.md +34 -3
- package/skills/workflow/run-pipeline-stage/playbooks/verify.md +2 -2
- package/skills/workflow/run-pipeline-stage/prompts/stage-runner.md +66 -10
- package/skills/workflow/run-pipeline-stage/scripts/recover-brief.mjs +805 -0
- package/skills/workflow/run-pipeline-stage/scripts/trail-entry.mjs +702 -0
- package/skills/workflow/using-agent-skills/SKILL.md +15 -7
- package/token-map.README.md +41 -4
- package/token-map.json +16 -5
package/agents/ralph/pi/wake.md
CHANGED
|
@@ -46,30 +46,25 @@ Initialize `.agent-cortex/ralph/state.json` as `{ "timerShellId": null, "infligh
|
|
|
46
46
|
- **inflight[].agentId** — the ID `task` returned for that worker; it is the argument `wait_for_agents` takes, the identifier for a `read_agent` fallback, and the key the completion is correlated to its bead.
|
|
47
47
|
- **timerShellId** — stays `null` on PI forever: the blocking wait replaces a poll timer, so nothing is ever recorded there.
|
|
48
48
|
|
|
49
|
-
###
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
coding → verifying (or whatever transition)
|
|
63
|
-
Tests: 12 passed, 0 failed
|
|
64
|
-
```
|
|
65
|
-
- Update `logLine` in `state.json` for that entry.
|
|
66
|
-
4. If no entry had new lines, post nothing — do not spam the chat with empty polls.
|
|
49
|
+
### Reading the trail
|
|
50
|
+
|
|
51
|
+
There is no log file and nothing to flush. A worker appends its own checkpoints to its bead with
|
|
52
|
+
`trail-entry.mjs`, so progress is read from the bead itself when a wait returns:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
bd show <chore-id> # entries: Outcome, Evidence, What is left
|
|
56
|
+
bd provenance log <chore-id> # refs: git-sha, branch, pr, work-id
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Read each completed worker's bead once, post a chat summary of what is new — the stage transition,
|
|
60
|
+
the test and lint results, the close reason — and post nothing when nothing new arrived. Nothing in
|
|
61
|
+
`state.json` needs a bookmark, because the trail is not a stream to follow.
|
|
67
62
|
|
|
68
63
|
### Wait loop lifecycle
|
|
69
64
|
|
|
70
65
|
- **Dispatch-and-wait** — during initialization, dispatch up to 5 AFK chore subagents with `task` (or an expansion worker for a chore-less AFK bead) and hold their agent IDs (recorded in `state.json`'s `inflight` list; expansion entries carry `kind: "expand"` and `choreId: null`).
|
|
71
66
|
- **Wait** — call `wait_for_agents` with the agent IDs of every in-flight agent, stage worker or expansion worker (`timeout` defaults to 120s). The tool blocks until at least one finishes. For each completed agent:
|
|
72
|
-
1.
|
|
67
|
+
1. Read its bead trail (see _Reading the trail_ above) to surface progress to chat.
|
|
73
68
|
2. Apply the shared completion handling described under _Completion arrival_ above.
|
|
74
69
|
3. Drop the completed agent ID from the wait set.
|
|
75
70
|
- **Repeat** — dispatch replacements for completed work (up to 5 in flight), add their new IDs to the wait set, and call `wait_for_agents` again with the remaining + new IDs. A completed expansion leaves a slot to fill with its parent's newly ready `stage:code` chore.
|
|
@@ -86,4 +81,4 @@ Nothing to kill: stopping means simply **stop calling `wait_for_agents`**. There
|
|
|
86
81
|
- **Always** keep a `wait_for_agents` call outstanding while agents are in-flight — stage workers and expansion workers alike. Never let running agent work stall without a wait active.
|
|
87
82
|
- **Never** re-dispatch an agent that is merely STILL RUNNING — wait again on the same ID.
|
|
88
83
|
- **Never** start `sleep` / background timer loops for completion detection on PI — `wait_for_agents` replaces them, and there is nothing to kill on HITL Pause.
|
|
89
|
-
- **Never** post empty poll updates to chat — only surface
|
|
84
|
+
- **Never** post empty poll updates to chat — only surface what a bead actually reports.
|
package/bin/agent-cortex.mjs
CHANGED
|
@@ -1,14 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { parseArgs, buildHelpText, resolveProgramName, validateHarness } from "../lib/cli.mjs";
|
|
3
|
+
import { parseArgs, buildHelpText, resolveProgramName, validateHarness, SUPPORTED_HARNESSES } from "../lib/cli.mjs";
|
|
4
4
|
import {
|
|
5
5
|
collectVersionReport,
|
|
6
|
+
collectHarnessStatus,
|
|
6
7
|
formatVersionReport,
|
|
7
8
|
serialiseVersionReport,
|
|
8
9
|
} from "../lib/harness-versions.mjs";
|
|
10
|
+
import {
|
|
11
|
+
describeClaudeArtifacts,
|
|
12
|
+
describePiArtifacts,
|
|
13
|
+
formatHarnessTable,
|
|
14
|
+
formatUpdateFlags,
|
|
15
|
+
formatUpdateReport,
|
|
16
|
+
promptHarnessSelection,
|
|
17
|
+
runUpdate,
|
|
18
|
+
selectHarnesses,
|
|
19
|
+
} from "../lib/harness-update.mjs";
|
|
9
20
|
import { join, dirname } from "node:path";
|
|
10
21
|
import { fileURLToPath } from "node:url";
|
|
11
|
-
import { readFile } from "node:fs/promises";
|
|
22
|
+
import { readFile, realpath } from "node:fs/promises";
|
|
23
|
+
import { runSelfUpdate } from "../lib/self-update.mjs";
|
|
12
24
|
import { installPi, registerPi, sweepLegacyPiUserDirs } from "./installers/pi.mjs";
|
|
13
25
|
|
|
14
26
|
/** @typedef {import("./installers/pi.mjs").InstallPiResult} InstallPiResult */
|
|
@@ -90,50 +102,76 @@ if (parsed.command === "install") {
|
|
|
90
102
|
process.exit(1);
|
|
91
103
|
}
|
|
92
104
|
|
|
93
|
-
|
|
105
|
+
try {
|
|
106
|
+
await installHarness(/** @type {"pi" | "claude"} */ (parsed.harness), {
|
|
107
|
+
dryRun: parsed.dryRun ?? false,
|
|
108
|
+
...(parsed.output ? { output: parsed.output } : {}),
|
|
109
|
+
...(parsed.pluginRoot ? { pluginRoot: parsed.pluginRoot } : {}),
|
|
110
|
+
requireRegister: parsed.requireRegister ?? false,
|
|
111
|
+
});
|
|
112
|
+
} catch (err) {
|
|
113
|
+
process.stderr.write(`Install failed: ${/** @type {Error} */ (err).message}\n`);
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
process.exit(0);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (parsed.command === "update") {
|
|
120
|
+
process.exitCode = await runUpdateCommand(parsed, programName);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Run one harness's installer and, for a plain install, its registration.
|
|
125
|
+
* `install <harness>` and `update` both come through here, so update-vs-install
|
|
126
|
+
* stays the installer's decision and neither command grows a second install path.
|
|
127
|
+
* @param {"pi" | "claude"} harness
|
|
128
|
+
* @param {object} [options]
|
|
129
|
+
* @param {boolean} [options.dryRun]
|
|
130
|
+
* @param {string} [options.output]
|
|
131
|
+
* @param {string} [options.pluginRoot]
|
|
132
|
+
* @param {boolean} [options.requireRegister]
|
|
133
|
+
* @returns {Promise<string[]>} What the installer wrote, for the update report
|
|
134
|
+
*/
|
|
135
|
+
async function installHarness(harness, { dryRun = false, output, pluginRoot, requireRegister = false } = {}) {
|
|
136
|
+
if (harness === "pi") {
|
|
94
137
|
// A plain `agent-cortex install pi` stages the package into the home
|
|
95
138
|
// install root (~/.agent-cortex/pi) and registers that directory with pi
|
|
96
139
|
// through pi's own `pi install` (idempotent by state: a re-run is a no-op
|
|
97
140
|
// update); `--output <dir>` is the stage-only form (tests/CI) and prints
|
|
98
141
|
// the registration command instead.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
142
|
+
const result = await installPi({
|
|
143
|
+
dryRun,
|
|
144
|
+
warn: () => {}, // warnings surface once in the printed summary
|
|
145
|
+
...(output ? { output } : {}),
|
|
146
|
+
...(pluginRoot ? { pluginRoot } : {}),
|
|
147
|
+
});
|
|
148
|
+
printPiInstall(result);
|
|
149
|
+
if (output === undefined) {
|
|
150
|
+
const registration = await registerPi({
|
|
151
|
+
output: result.output,
|
|
152
|
+
dryRun,
|
|
153
|
+
requireRegister,
|
|
154
|
+
warn: (msg) => process.stdout.write(` ⚠ ${msg}\n`),
|
|
105
155
|
});
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
// depend on this guard staying in place.
|
|
118
|
-
if (registration.registered && !(parsed.dryRun ?? false)) {
|
|
119
|
-
printPiSweep(
|
|
120
|
-
await sweepLegacyPiUserDirs({
|
|
121
|
-
piUserDir: dirname(registration.settingsPath),
|
|
122
|
-
dryRun: parsed.dryRun ?? false,
|
|
123
|
-
}),
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
} else {
|
|
127
|
-
process.stdout.write(` → register it with pi: pi install ${result.output}\n`);
|
|
156
|
+
// The old installer's rendered copies are only stale once the staged
|
|
157
|
+
// package is what pi loads — and nothing is deleted on a dry run. The
|
|
158
|
+
// flag is passed through as well, so the sweep's own guarantee does not
|
|
159
|
+
// depend on this guard staying in place.
|
|
160
|
+
if (registration.registered && !dryRun) {
|
|
161
|
+
printPiSweep(
|
|
162
|
+
await sweepLegacyPiUserDirs({
|
|
163
|
+
piUserDir: dirname(registration.settingsPath),
|
|
164
|
+
dryRun,
|
|
165
|
+
}),
|
|
166
|
+
);
|
|
128
167
|
}
|
|
129
|
-
}
|
|
130
|
-
process.
|
|
131
|
-
process.exit(1);
|
|
168
|
+
} else {
|
|
169
|
+
process.stdout.write(` → register it with pi: pi install ${result.output}\n`);
|
|
132
170
|
}
|
|
133
|
-
|
|
171
|
+
return describePiArtifacts(result);
|
|
134
172
|
}
|
|
135
173
|
|
|
136
|
-
process.stdout.write(`Installing agent-cortex for "${
|
|
174
|
+
process.stdout.write(`Installing agent-cortex for "${harness}" harness…\n`);
|
|
137
175
|
|
|
138
176
|
// claude is the first wired harness: a plain `agent-cortex install claude`
|
|
139
177
|
// materialises the plugin into the home install root (~/.agent-cortex/claude)
|
|
@@ -144,40 +182,90 @@ if (parsed.command === "install") {
|
|
|
144
182
|
// (marketplace add → install → update); `--output <dir>` is the generate-only
|
|
145
183
|
// form (tests/CI validation), and the generated claude/ plugin subtree is
|
|
146
184
|
// never committed — only the settings template is.
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
185
|
+
|
|
186
|
+
// Avoid loading the installer on the help/summary paths and for other harnesses.
|
|
187
|
+
const { installClaude, registerClaude } = await import("./installers/claude.mjs");
|
|
188
|
+
const result = await installClaude({
|
|
189
|
+
dryRun,
|
|
190
|
+
warn: () => {}, // warnings surface once via result.warnings
|
|
191
|
+
...(output ? { output } : {}),
|
|
192
|
+
});
|
|
193
|
+
for (const warning of result.warnings) {
|
|
194
|
+
process.stdout.write(` ⚠ ${warning}\n`);
|
|
195
|
+
}
|
|
196
|
+
// Runtime registration happens only for the plain install — `--output`
|
|
197
|
+
// keeps generating only (documented in --help).
|
|
198
|
+
if (output === undefined) {
|
|
199
|
+
await registerClaude({
|
|
200
|
+
// A default install always materialises the marketplace manifest; the
|
|
201
|
+
// generate-only form (--output) is the only path where it is null, and
|
|
202
|
+
// this branch is not that path.
|
|
203
|
+
root: /** @type {string} */ (result.marketplaceRoot),
|
|
204
|
+
manifest: /** @type {object} */ (result.marketplaceManifest),
|
|
205
|
+
pluginVersion: result.pluginVersion,
|
|
206
|
+
// The enablement check reads the same file this install just merged:
|
|
207
|
+
// a plugin Claude Code does not enable loads nothing.
|
|
208
|
+
...(result.settings ? { settingsPath: result.settings.path } : {}),
|
|
209
|
+
dryRun,
|
|
210
|
+
requireRegister,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
return describeClaudeArtifacts(result);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* `ctx update`: choose the harnesses, refresh each through its installer, report
|
|
218
|
+
* what changed. Every write happens inside `installHarness`'s call into the
|
|
219
|
+
* harness installer — this command adds selection and reporting only.
|
|
220
|
+
* @param {import("../lib/cli.mjs").ParsedArgs} parsed
|
|
221
|
+
* @param {string} programName
|
|
222
|
+
* @returns {Promise<number>} The exit code
|
|
223
|
+
*/
|
|
224
|
+
async function runUpdateCommand(parsed, programName) {
|
|
225
|
+
if (parsed.optionError) {
|
|
226
|
+
process.stderr.write(`${parsed.optionError}\n`);
|
|
227
|
+
return 1;
|
|
228
|
+
}
|
|
229
|
+
try {
|
|
230
|
+
const cliVersion = await readPackageVersion();
|
|
231
|
+
const statuses = await collectHarnessStatus({ cli: cliVersion });
|
|
232
|
+
const all = parsed.all ?? false;
|
|
233
|
+
let harnesses = parsed.harnesses ?? [];
|
|
234
|
+
|
|
235
|
+
// Bare `ctx update` asks; a non-interactive shell gets the same table plus
|
|
236
|
+
// the flags that select without a prompt, and a non-zero exit so a script
|
|
237
|
+
// never mistakes "nothing chosen" for "nothing to do".
|
|
238
|
+
if (harnesses.length === 0 && !all) {
|
|
239
|
+
if (process.stdin.isTTY !== true || process.stdout.isTTY !== true) {
|
|
240
|
+
process.stdout.write(`${formatHarnessTable(statuses)}\n\n${formatUpdateFlags(programName)}\n`);
|
|
241
|
+
return 1;
|
|
158
242
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
// A default install always materialises the marketplace manifest; the
|
|
164
|
-
// generate-only form (--output) is the only path where it is null, and
|
|
165
|
-
// this branch is not that path.
|
|
166
|
-
root: /** @type {string} */ (result.marketplaceRoot),
|
|
167
|
-
manifest: /** @type {object} */ (result.marketplaceManifest),
|
|
168
|
-
pluginVersion: result.pluginVersion,
|
|
169
|
-
// The enablement check reads the same file this install just merged:
|
|
170
|
-
// a plugin Claude Code does not enable loads nothing.
|
|
171
|
-
...(result.settings ? { settingsPath: result.settings.path } : {}),
|
|
172
|
-
dryRun: parsed.dryRun ?? false,
|
|
173
|
-
requireRegister: parsed.requireRegister ?? false,
|
|
174
|
-
});
|
|
243
|
+
harnesses = await promptHarnessSelection({ statuses });
|
|
244
|
+
if (harnesses.length === 0) {
|
|
245
|
+
process.stdout.write("No harness selected; nothing updated.\n");
|
|
246
|
+
return 0;
|
|
175
247
|
}
|
|
176
|
-
process.exit(0);
|
|
177
|
-
} catch (err) {
|
|
178
|
-
process.stderr.write(`Install failed: ${/** @type {Error} */ (err).message}\n`);
|
|
179
|
-
process.exit(1);
|
|
180
248
|
}
|
|
249
|
+
|
|
250
|
+
const plan = selectHarnesses({ statuses, harnesses, all, force: parsed.force ?? false });
|
|
251
|
+
if (plan.unknown.length > 0) {
|
|
252
|
+
const names = plan.unknown.map((name) => `"${name}"`).join(", ");
|
|
253
|
+
process.stderr.write(
|
|
254
|
+
`Unknown harness ${names}. Supported harnesses: ${SUPPORTED_HARNESSES.join(", ")}\nRun "${programName} --help" for usage.\n`,
|
|
255
|
+
);
|
|
256
|
+
return 1;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const entries = await runUpdate({
|
|
260
|
+
cli: cliVersion,
|
|
261
|
+
targets: plan.targets,
|
|
262
|
+
refresh: (harness) => installHarness(/** @type {"pi" | "claude"} */ (harness), {}),
|
|
263
|
+
});
|
|
264
|
+
process.stdout.write(`${formatUpdateReport({ cli: cliVersion, entries, skipped: plan.skipped })}\n`);
|
|
265
|
+
return 0;
|
|
266
|
+
} catch (err) {
|
|
267
|
+
process.stderr.write(`Update failed: ${/** @type {Error} */ (err).message}\n`);
|
|
268
|
+
return 1;
|
|
181
269
|
}
|
|
182
270
|
}
|
|
183
271
|
|
|
@@ -192,6 +280,24 @@ function printPiSweep({ removed }) {
|
|
|
192
280
|
}
|
|
193
281
|
}
|
|
194
282
|
|
|
283
|
+
if (parsed.command === "self-update") {
|
|
284
|
+
if (parsed.optionError) {
|
|
285
|
+
process.stderr.write(`${parsed.optionError}\n`);
|
|
286
|
+
process.exit(1);
|
|
287
|
+
}
|
|
288
|
+
try {
|
|
289
|
+
// The real path follows the global bin shim into the layout of the manager
|
|
290
|
+
// that installed this copy, which is what identifies the owner. Taking it
|
|
291
|
+
// from this file's own URL keeps detection on the running entry point, not
|
|
292
|
+
// on the cwd or on PATH.
|
|
293
|
+
const entryPoint = await realpath(fileURLToPath(import.meta.url));
|
|
294
|
+
process.exitCode = await runSelfUpdate({ entryPoint, programName, check: parsed.check ?? false });
|
|
295
|
+
} catch (err) {
|
|
296
|
+
process.stderr.write(`${/** @type {Error} */ (err).message}\n`);
|
|
297
|
+
process.exit(1);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
195
301
|
if (parsed.command === "migrate") {
|
|
196
302
|
if (parsed.optionError) {
|
|
197
303
|
process.stderr.write(`${parsed.optionError}\n`);
|
|
@@ -217,6 +323,9 @@ function printPiInstall(result) {
|
|
|
217
323
|
process.stdout.write(` ✓ ${agent.name}.agent.md → ${agent.filePath}\n`);
|
|
218
324
|
}
|
|
219
325
|
process.stdout.write(` ✓ Substituted ${result.skills.md} markdown file(s) across ${result.skills.skills} skill(s) → ${result.skills.dir}\n`);
|
|
326
|
+
if (result.docs.files.length > 0) {
|
|
327
|
+
process.stdout.write(` ✓ Copied ${result.docs.files.length} doc(s) → ${result.docs.dir}\n`);
|
|
328
|
+
}
|
|
220
329
|
process.stdout.write(` ✓ Copied ${result.extensions.files} extension file(s) → ${result.extensions.dir}\n`);
|
|
221
330
|
for (const name of result.packages.bundled) {
|
|
222
331
|
process.stdout.write(` ✓ Bundled pi package ${name} → ${join(result.packages.dir, name)}\n`);
|
|
@@ -22,6 +22,12 @@
|
|
|
22
22
|
// layout (scripts/lib/skill-links.mjs),
|
|
23
23
|
// so a grouped-tree handoff still
|
|
24
24
|
// resolves in the plugin.
|
|
25
|
+
// <output>/docs/... documents the skills read after
|
|
26
|
+
// install (docs/skills/skill-anatomy.md),
|
|
27
|
+
// copied verbatim once per install — a
|
|
28
|
+
// skill names them by {{PATH:key}}, so
|
|
29
|
+
// the token map decides both location
|
|
30
|
+
// and shipment
|
|
25
31
|
// <output>/hooks.json + hooks/<scripts> from hooks/claude/ (support files
|
|
26
32
|
// referenced via ${CLAUDE_PLUGIN_ROOT})
|
|
27
33
|
// <output>/.mcp.json + scripts/ hand-authored extras copied from
|
|
@@ -67,6 +73,7 @@ import { join, dirname, basename, resolve } from "node:path";
|
|
|
67
73
|
import { fileURLToPath } from "node:url";
|
|
68
74
|
import { homedir } from "node:os";
|
|
69
75
|
import { composeAgent, loadTokenMap, substituteTokens } from "../../scripts/lib/compose-agent.mjs";
|
|
76
|
+
import { installDocs } from "../../scripts/lib/docs.mjs";
|
|
70
77
|
import { isDirectory, isFile } from "../../scripts/lib/fs.mjs";
|
|
71
78
|
import { flattenSkillLinks } from "../../scripts/lib/skill-links.mjs";
|
|
72
79
|
|
|
@@ -633,6 +640,26 @@ async function isSymlink(filePath) {
|
|
|
633
640
|
}
|
|
634
641
|
}
|
|
635
642
|
|
|
643
|
+
/**
|
|
644
|
+
* Result of generating (or planning) the Claude Code plugin.
|
|
645
|
+
* @typedef {object} InstallClaudeResult
|
|
646
|
+
* @property {string} output
|
|
647
|
+
* @property {string | null} marketplaceRoot
|
|
648
|
+
* @property {string | null} manifestPath
|
|
649
|
+
* @property {object | null} marketplaceManifest
|
|
650
|
+
* @property {string[]} agents
|
|
651
|
+
* @property {{ names: string[], md: number, files: number, dir: string }} skills
|
|
652
|
+
* @property {{ files: string[], dir: string }} docs
|
|
653
|
+
* @property {boolean} hooks
|
|
654
|
+
* @property {string[]} hookFiles
|
|
655
|
+
* @property {string[]} handAuthored
|
|
656
|
+
* @property {{ path: string, action: string, removedSymlink: boolean } | null} settings
|
|
657
|
+
* @property {{ path: string, action: string, removedSymlink: boolean } | null} instructions
|
|
658
|
+
* @property {string} pluginVersion
|
|
659
|
+
* @property {string[]} warnings
|
|
660
|
+
* @property {boolean} dryRun
|
|
661
|
+
*/
|
|
662
|
+
|
|
636
663
|
/**
|
|
637
664
|
* Generate the Claude Code plugin subtree.
|
|
638
665
|
*
|
|
@@ -654,13 +681,7 @@ async function isSymlink(filePath) {
|
|
|
654
681
|
* explicitly materialises the section even
|
|
655
682
|
* with --output — the test/CI seam.
|
|
656
683
|
* @param {(msg: string) => void} [options.warn] Warning sink, also collected in `warnings`.
|
|
657
|
-
* @returns {Promise<
|
|
658
|
-
* marketplaceManifest: object|null, agents: string[],
|
|
659
|
-
* skills: {names: string[], md: number, files: number, dir: string},
|
|
660
|
-
* hooks: boolean, hookFiles: string[], handAuthored: string[],
|
|
661
|
-
* settings: {path: string, action: string, removedSymlink: boolean}|null,
|
|
662
|
-
* instructions: {path: string, action: string, removedSymlink: boolean}|null,
|
|
663
|
-
* pluginVersion: string, warnings: string[], dryRun: boolean }>}
|
|
684
|
+
* @returns {Promise<InstallClaudeResult>}
|
|
664
685
|
*/
|
|
665
686
|
export async function installClaude({ root = PACKAGE_ROOT, output, dryRun = false, pluginRoot, settingsPath, instructionsPath, warn = DEFAULT_WARN } = {}) {
|
|
666
687
|
const tokenMap = await loadTokenMap(root);
|
|
@@ -690,7 +711,7 @@ export async function installClaude({ root = PACKAGE_ROOT, output, dryRun = fals
|
|
|
690
711
|
// or a tmp dir, never claude-extras/, but clearing the output first would
|
|
691
712
|
// still destroy sources if they ever collided). They are re-copied fresh in
|
|
692
713
|
// the write phase. In dry-run nothing is cleaned or written.
|
|
693
|
-
const cleanable = ["agents", "skills", ".claude-plugin", "hooks", "scripts", "hooks.json", ".mcp.json"];
|
|
714
|
+
const cleanable = ["agents", "skills", "docs", ".claude-plugin", "hooks", "scripts", "hooks.json", ".mcp.json"];
|
|
694
715
|
const handAuthored = await buildHandAuthored(root);
|
|
695
716
|
|
|
696
717
|
// Regenerate the plugin children (removes stale output from earlier flows,
|
|
@@ -721,6 +742,7 @@ export async function installClaude({ root = PACKAGE_ROOT, output, dryRun = fals
|
|
|
721
742
|
const agents = await buildAgents(root);
|
|
722
743
|
const skillOut = join(out, "skills");
|
|
723
744
|
const skills = await buildSkills(root, skillOut, { dryRun, pluginRoot: rootPlugin, tokenMap, warn: warnSink });
|
|
745
|
+
const docs = await installDocs({ root, output: out, dryRun, tokenMap });
|
|
724
746
|
const pluginJson = await buildPluginJson(root);
|
|
725
747
|
const hooksSrc = join(root, "hooks", "claude", "hooks.json");
|
|
726
748
|
const hooksJson = (await isFile(hooksSrc)) ? await readFile(hooksSrc, "utf-8") : null;
|
|
@@ -735,6 +757,7 @@ export async function installClaude({ root = PACKAGE_ROOT, output, dryRun = fals
|
|
|
735
757
|
pluginVersion: JSON.parse(pluginJson).version,
|
|
736
758
|
agents: agents.files.map((f) => f.file.replace(/\.md$/, "")),
|
|
737
759
|
skills: { names: skills.names, md: skills.md, files: skills.files, dir: skillOut },
|
|
760
|
+
docs,
|
|
738
761
|
hooks: hooksJson !== null,
|
|
739
762
|
hookFiles: hookFiles.map((f) => f.rel),
|
|
740
763
|
handAuthored: handAuthored.map((f) => f.rel),
|
|
@@ -748,6 +771,7 @@ export async function installClaude({ root = PACKAGE_ROOT, output, dryRun = fals
|
|
|
748
771
|
console.log(`DRY-RUN — would generate into ${out}, no files written:`);
|
|
749
772
|
console.log(` agents: ${summary.agents.join(", ")}`);
|
|
750
773
|
console.log(` skills: ${summary.skills.names.length} copied → ${skillOut}/<name>/ (token-substituted)`);
|
|
774
|
+
if (docs.files.length) console.log(` docs: ${docs.files.join(", ")} → ${docs.dir}/ (verbatim copies)`);
|
|
751
775
|
console.log(` .claude-plugin/plugin.json (version ${JSON.parse(pluginJson).version})`);
|
|
752
776
|
if (isDefaultInstall && manifestPath) {
|
|
753
777
|
console.log(` marketplace manifest → ${manifestPath} (plugin agent-cortex @ ./${basename(out)})`);
|
|
@@ -778,6 +802,7 @@ export async function installClaude({ root = PACKAGE_ROOT, output, dryRun = fals
|
|
|
778
802
|
console.log(
|
|
779
803
|
`Copied ${summary.skills.names.length} skill(s) → ${skillOut}/<name>/ (token-substituted, no symlinks)`,
|
|
780
804
|
);
|
|
805
|
+
if (docs.files.length) console.log(`Copied ${docs.files.length} doc(s) → ${docs.dir}/ (verbatim)`);
|
|
781
806
|
|
|
782
807
|
await mkdir(join(out, ".claude-plugin"), { recursive: true });
|
|
783
808
|
await writeFile(join(out, ".claude-plugin", "plugin.json"), pluginJson);
|
package/bin/installers/pi.mjs
CHANGED
|
@@ -22,6 +22,11 @@
|
|
|
22
22
|
// <extension>/../../agents, i.e. this dir
|
|
23
23
|
// <output>/skills/<group>/<name>/ token-substituted skill copies — real
|
|
24
24
|
// files, never symlinks
|
|
25
|
+
// <output>/docs/... documents the skills read after install
|
|
26
|
+
// (docs/skills/skill-anatomy.md), copied
|
|
27
|
+
// verbatim once per install — a skill names
|
|
28
|
+
// them by {{PATH:key}}, so the token map
|
|
29
|
+
// decides both location and shipment
|
|
25
30
|
// <output>/extensions/ copy of the repo extensions/ tree (loaded
|
|
26
31
|
// by pi's package discovery)
|
|
27
32
|
// <output>/token-map.json the map agent-modes reads from the package
|
|
@@ -115,6 +120,7 @@ import { join, dirname, resolve } from "node:path";
|
|
|
115
120
|
import { fileURLToPath } from "node:url";
|
|
116
121
|
import { homedir } from "node:os";
|
|
117
122
|
import { composeAgent, loadTokenMap, substituteTokens, translateToolList } from "../../scripts/lib/compose-agent.mjs";
|
|
123
|
+
import { installDocs } from "../../scripts/lib/docs.mjs";
|
|
118
124
|
import { isDirectory, isFile } from "../../scripts/lib/fs.mjs";
|
|
119
125
|
|
|
120
126
|
const PI = "pi";
|
|
@@ -141,6 +147,7 @@ const PI = "pi";
|
|
|
141
147
|
* @property {string} output
|
|
142
148
|
* @property {{ name: string, filePath: string }[]} agents
|
|
143
149
|
* @property {{ skills: number, md: number, files: number, dir: string }} skills
|
|
150
|
+
* @property {{ files: string[], dir: string }} docs
|
|
144
151
|
* @property {{ files: number, dir: string }} extensions
|
|
145
152
|
* @property {{ dir: string, bundled: string[], staged: string[], resources: PackageResources }} packages
|
|
146
153
|
* @property {{ path: string, manifest: PiManifest }} manifest
|
|
@@ -181,7 +188,7 @@ const STAGED_NAME = "@jaybeeuu/agent-cortex-pi";
|
|
|
181
188
|
// before every write so re-staging cannot leave stale copies behind (a skill
|
|
182
189
|
// deleted in the repo, a package dropped from the manifest). Anything else in
|
|
183
190
|
// the output directory is left alone.
|
|
184
|
-
const OWNED_CHILDREN = ["package.json", "token-map.json", "agents", "skills", "extensions", "node_modules"];
|
|
191
|
+
const OWNED_CHILDREN = ["package.json", "token-map.json", "agents", "skills", "docs", "extensions", "node_modules"];
|
|
185
192
|
|
|
186
193
|
const GENERATED_MARKER =
|
|
187
194
|
"GENERATED by `agent-cortex install pi` — DO NOT EDIT; re-run the installer to update.";
|
|
@@ -242,6 +249,7 @@ const DEFAULT_LEGACY_SWEEP_RECORD = join(homedir(), ".agent-cortex", "legacy-swe
|
|
|
242
249
|
* @param {(msg: string) => void} [options.warn] Warning sink, also collected in `warnings`
|
|
243
250
|
* @returns {Promise<{ output: string, agents: {name: string, filePath: string}[],
|
|
244
251
|
* skills: {skills: number, md: number, files: number, dir: string},
|
|
252
|
+
* docs: {files: string[], dir: string},
|
|
245
253
|
* extensions: {files: number, dir: string},
|
|
246
254
|
* packages: {dir: string, bundled: string[], staged: string[], resources: {extensions: string[], skills: string[]}},
|
|
247
255
|
* manifest: {path: string, manifest: PiManifest},
|
|
@@ -285,6 +293,7 @@ export async function installPi(options = {}) {
|
|
|
285
293
|
|
|
286
294
|
const agents = await installAgents({ root, agentsDir: join(output, "agents"), dryRun, pluginRoot, tokenMap, warn });
|
|
287
295
|
const skills = await installSkills({ root, skillsDir: join(output, "skills"), dryRun, pluginRoot, tokenMap, warn });
|
|
296
|
+
const docs = await installDocs({ root, output, dryRun, tokenMap });
|
|
288
297
|
const extensions = await installExtensions({ root, output, dryRun });
|
|
289
298
|
const tokenMapFile = await installTokenMap({ output, tokenMap, dryRun });
|
|
290
299
|
const packages = await stagePiPackages({ dependencies, output, resolvePackage, dryRun, warn });
|
|
@@ -301,6 +310,7 @@ export async function installPi(options = {}) {
|
|
|
301
310
|
output,
|
|
302
311
|
agents,
|
|
303
312
|
skills,
|
|
313
|
+
docs,
|
|
304
314
|
extensions,
|
|
305
315
|
packages,
|
|
306
316
|
manifest,
|