@jaybeeuu/agent-cortex 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/CHANGELOG.md +387 -0
  2. package/README.md +33 -13
  3. package/agents/README.md +22 -10
  4. package/agents/plan/agent.md +2 -2
  5. package/agents/ralph/agent.md +126 -88
  6. package/agents/ralph/claude/frontmatter.json +3 -3
  7. package/agents/ralph/claude/wake.md +46 -0
  8. package/agents/ralph/pi/{polling.md → wake.md} +35 -20
  9. package/agents/ralph-plan/agent.md +23 -11
  10. package/bin/agent-cortex.mjs +66 -4
  11. package/bin/installers/claude.mjs +182 -68
  12. package/bin/installers/pi.mjs +200 -24
  13. package/extensions/agent-guardrails/README.md +1 -1
  14. package/extensions/agent-guardrails/guardrails.ts +1 -1
  15. package/extensions/agent-guardrails/index.ts +1 -1
  16. package/extensions/agent-modes/discover.test.ts +2 -2
  17. package/hooks/claude/scripts/notify.mjs +8 -1
  18. package/lib/cli.mjs +91 -2
  19. package/package.json +27 -3
  20. package/scripts/check-changeset.mjs +4 -0
  21. package/scripts/check-no-sync.mjs +5 -1
  22. package/scripts/lib/compose-agent.mjs +69 -9
  23. package/scripts/lib/fs.mjs +10 -2
  24. package/scripts/lib/migrate.mjs +736 -0
  25. package/scripts/lib/release-gates.mjs +21 -1
  26. package/scripts/lib/skill-frontmatter.mjs +23 -6
  27. package/scripts/lib/skill-links.mjs +52 -0
  28. package/scripts/lib/verify-worker-tree.mjs +25 -0
  29. package/scripts/resolve-publish-command.mjs +4 -0
  30. package/skills/engineering/design-an-interface/SKILL.md +3 -3
  31. package/skills/engineering/grill-with-docs/SKILL.md +3 -5
  32. package/skills/engineering/improve-codebase-architecture/SKILL.md +5 -4
  33. package/skills/engineering/tdd/SKILL.md +5 -7
  34. package/skills/engineering/technical-direction/SKILL.md +5 -5
  35. package/skills/planning/classify-bead/REFERENCE.md +3 -3
  36. package/skills/planning/classify-bead/SKILL.md +14 -9
  37. package/skills/planning/classify-bead/scripts/classify-bead.mjs +5 -4
  38. package/skills/planning/create-task/REFERENCE.md +52 -3
  39. package/skills/planning/create-task/SKILL.md +70 -26
  40. package/skills/planning/create-task/scripts/{create-chores.ts → create-chores.mjs} +48 -23
  41. package/skills/planning/flesh-out-idea/REFERENCE.md +116 -0
  42. package/skills/planning/flesh-out-idea/SKILL.md +146 -0
  43. package/skills/planning/prd-to-tasks/REFERENCE.md +54 -5
  44. package/skills/planning/prd-to-tasks/SKILL.md +22 -16
  45. package/skills/planning/record-idea/FORMAT.md +66 -17
  46. package/skills/planning/record-idea/SKILL.md +48 -24
  47. package/skills/planning/request-refactor-plan/SKILL.md +4 -4
  48. package/skills/planning/spec-to-tickets/SKILL.md +5 -5
  49. package/skills/planning/write-a-prd/SKILL.md +8 -9
  50. package/skills/planning/write-a-ticket/SKILL.md +4 -6
  51. package/skills/productivity/bd-tool/SKILL.md +15 -10
  52. package/skills/productivity/grill-me/SKILL.md +5 -0
  53. package/skills/productivity/init-beads/SKILL.md +3 -3
  54. package/skills/productivity/migration/REFERENCE.md +227 -0
  55. package/skills/productivity/migration/SKILL.md +188 -0
  56. package/skills/productivity/validate-agent-dir/scripts/{validate-agent-dir.ts → validate-agent-dir.mjs} +49 -28
  57. package/skills/productivity/write-a-skill/SKILL.md +5 -5
  58. package/skills/review/maintain-agent-docs/SKILL.md +5 -7
  59. package/skills/review/refactor-skill/SKILL.md +4 -3
  60. package/skills/review/review-security/SKILL.md +3 -5
  61. package/skills/style/style-code/SKILL.md +5 -10
  62. package/skills/style/style-comms/SKILL.md +5 -7
  63. package/skills/style/style-documentation/SKILL.md +5 -6
  64. package/skills/style/style-tests/SKILL.md +5 -7
  65. package/skills/workflow/git-workflow/SKILL.md +28 -4
  66. package/skills/workflow/hitl-collab/SKILL.md +4 -6
  67. package/skills/workflow/plan/SKILL.md +41 -30
  68. package/skills/workflow/ralph/REFERENCE.md +182 -51
  69. package/skills/workflow/ralph/SKILL.md +89 -50
  70. package/skills/workflow/run-pipeline-stage/SKILL.md +72 -71
  71. package/skills/workflow/run-pipeline-stage/playbooks/code.md +11 -0
  72. package/skills/workflow/run-pipeline-stage/playbooks/document.md +12 -0
  73. package/skills/workflow/run-pipeline-stage/playbooks/fix.md +12 -0
  74. package/skills/workflow/run-pipeline-stage/playbooks/review.md +11 -0
  75. package/skills/workflow/run-pipeline-stage/playbooks/verify.md +13 -0
  76. package/skills/workflow/run-pipeline-stage/prompts/stage-runner.md +73 -1
  77. package/token-map.README.md +9 -4
  78. package/token-map.json +13 -6
  79. package/.agents/skills/style-versioning/SKILL.md +0 -172
  80. package/.changeset/README.md +0 -8
  81. package/.changeset/config.json +0 -11
  82. package/.github/workflows/ci.yml +0 -302
  83. package/.junie/guidelines.md +0 -87
  84. package/.junie/mcp/mcp.json +0 -10
  85. package/AGENTS.md +0 -153
  86. package/agents/ralph/claude/polling.md +0 -31
  87. package/agents-native/ralph.md +0 -203
  88. package/docs/claude-hooks.md +0 -144
  89. package/docs/commit-signing.md +0 -231
  90. package/docs/ideas/auto-model-selection.md +0 -75
  91. package/docs/ideas/ban-content-assertion-tests.md +0 -75
  92. package/docs/ideas/bead-progress-notes-for-session-resumption.md +0 -75
  93. package/docs/ideas/classify-beads-via-deterministic-script-with-agent-fallback.md +0 -70
  94. package/docs/ideas/ctx-alias-version-and-update-commands.md +0 -65
  95. package/docs/ideas/ctx-feedback-command-for-capturing-ideas-from-any-machine.md +0 -67
  96. package/docs/ideas/dedicated-review-tests-skill.md +0 -71
  97. package/docs/ideas/deployment-beads-for-post-merge-pipelines.md +0 -36
  98. package/docs/ideas/design-and-delivery-as-separate-bead-trees/design-and-delivery-as-separate-bead-trees.md +0 -99
  99. package/docs/ideas/enable-signed-commit-enforcement-changesets-bot-commits-must-be-signed.md +0 -42
  100. package/docs/ideas/extension-manifests.md +0 -121
  101. package/docs/ideas/improve-pi-system-prompt.md +0 -43
  102. package/docs/ideas/map-every-cortex-tool-to-a-real-pi-equivalent.md +0 -95
  103. package/docs/ideas/model-selector.md +0 -40
  104. package/docs/ideas/pi-task-memory.md +0 -34
  105. package/docs/ideas/qa-extension.md +0 -43
  106. package/docs/ideas/record-idea-implementation-questions.md +0 -68
  107. package/docs/ideas/record-ideas-as-beads.md +0 -115
  108. package/docs/ideas/self-flushing-session.md +0 -72
  109. package/docs/ideas/state-in-beads-not-md.md +0 -69
  110. package/docs/ideas/subagent-visibility.md +0 -72
  111. package/docs/ideas/task-session-context.local.md +0 -95
  112. package/docs/ideas/task-session-context.md +0 -89
  113. package/docs/ideas/test-data-creation-helpers.md +0 -92
  114. package/docs/ideas/test-planning-stage-bead-in-feature-planning.md +0 -68
  115. package/docs/ideas/unified-ci-pipeline.md +0 -28
  116. package/docs/ideas/unify-the-ticket-and-task-decomposition-workflows.md +0 -71
  117. package/docs/ideas/worktree-housekeeping-for-closed-beads.md +0 -39
  118. package/docs/inspirations.md +0 -33
  119. package/docs/research/disable-model-invocation.md +0 -82
  120. package/docs/research/gas-city-research.md +0 -256
  121. package/docs/research/skill-design-research.md +0 -286
  122. package/docs/research/skill-improvements-analysis.md +0 -117
  123. package/docs/research/skill-patterns-research.md +0 -24
  124. package/docs/skills/skill-anatomy.md +0 -200
  125. package/docs/technical-direction/harness-config.md +0 -177
  126. package/docs/technical-direction/migration-execution-labels.md +0 -177
  127. package/docs/user-preferences.md +0 -17
  128. package/extensions/agent-guardrails/tsconfig.json +0 -12
  129. package/extensions/agent-modes/tsconfig.json +0 -12
  130. package/extensions/output-style/tsconfig.json +0 -12
  131. package/extensions/session-start/tsconfig.json +0 -12
  132. package/extensions/skill-stats/pnpm-lock.yaml +0 -329
  133. package/extensions/skill-stats/tsconfig.json +0 -12
  134. package/extensions/subagent/agents/planner.md +0 -37
  135. package/extensions/subagent/agents/reviewer.md +0 -35
  136. package/extensions/subagent/agents/scout.md +0 -50
  137. package/extensions/subagent/agents/worker.md +0 -24
  138. package/extensions/subagent/prompts/implement-and-review.md +0 -10
  139. package/extensions/subagent/prompts/implement.md +0 -10
  140. package/extensions/subagent/prompts/scout-and-plan.md +0 -9
  141. package/extensions/subagent/tsconfig.json +0 -12
  142. package/pi/keybindings.json +0 -3
  143. package/pi/settings.json +0 -34
  144. package/pnpm-workspace.yaml +0 -14
  145. package/skills/planning/create-task/scripts/package.json +0 -14
  146. package/skills/planning/create-task/scripts/pnpm-lock.yaml +0 -342
  147. package/skills/planning/create-task/scripts/tsconfig.json +0 -12
  148. package/skills/planning/record-idea/scripts/new-idea.sh +0 -89
  149. package/skills/productivity/validate-agent-dir/scripts/package.json +0 -15
  150. package/skills/productivity/validate-agent-dir/scripts/pnpm-lock.yaml +0 -329
  151. package/skills/productivity/validate-agent-dir/scripts/pnpm-workspace.yaml +0 -2
  152. package/skills/productivity/validate-agent-dir/scripts/tsconfig.json +0 -19
  153. package/skills/productivity/validate-agent-dir/scripts/validate-agent-dir.test.ts +0 -317
  154. package/skills/workflow/run-pipeline-stage/REFERENCE.md +0 -22
  155. package/skills/workflow/run-pipeline-stage/scripts/generate-progress.test.ts +0 -682
  156. package/skills/workflow/run-pipeline-stage/scripts/generate-progress.ts +0 -588
  157. package/skills/workflow/run-pipeline-stage/scripts/package.json +0 -15
  158. package/skills/workflow/run-pipeline-stage/scripts/pnpm-lock.yaml +0 -342
  159. package/skills/workflow/run-pipeline-stage/scripts/pnpm-workspace.yaml +0 -2
  160. package/skills/workflow/run-pipeline-stage/scripts/tsconfig.json +0 -12
  161. package/test/classify-bead.test.mjs +0 -277
  162. package/test/claude-installer.test.mjs +0 -308
  163. package/test/cli.test.mjs +0 -314
  164. package/test/create-chores.test.mjs +0 -85
  165. package/test/fake-bd.test.mjs +0 -72
  166. package/test/fs.test.mjs +0 -76
  167. package/test/helpers/fake-bd.mjs +0 -87
  168. package/test/helpers/fake-claude.mjs +0 -152
  169. package/test/helpers/fake-pi.mjs +0 -132
  170. package/test/installer.test.mjs +0 -806
  171. package/test/notify-hook.test.mjs +0 -63
  172. package/test/pending-gates.test.mjs +0 -147
  173. package/test/pi-installer.test.mjs +0 -785
  174. package/test/pi-register.test.mjs +0 -342
  175. package/test/pi-sweep.test.mjs +0 -619
  176. package/test/release-gates.test.mjs +0 -190
  177. package/test/skill-frontmatter.test.mjs +0 -137
  178. package/test/verify-worker-tree.test.mjs +0 -120
@@ -1,308 +0,0 @@
1
- // Tests for `agent-cortex install claude` runtime registration: after
2
- // materialising the plugin into the home install root, the plain install
3
- // registers the plugin with Claude Code by driving the `claude plugin` CLI.
4
- // Registration is
5
- // idempotent by STATE, not by exit code: `marketplace list --json` decides
6
- // add-vs-update and `plugin list --json` decides install-vs-update against the
7
- // materialised version. The real CLI is a genuine external system (and its
8
- // home config must never be touched), so these tests drive the stateful fake
9
- // from test/helpers/fake-claude.mjs; fixture manifests (makeMarketplaceRoot)
10
- // stand in for the home install root's generated marketplace manifest.
11
-
12
- import { describe, it } from "node:test";
13
- import assert from "node:assert/strict";
14
- import { execFile } from "node:child_process";
15
- import { mkdtemp, mkdir, writeFile, rm, readFile, stat } from "node:fs/promises";
16
- import { tmpdir } from "node:os";
17
- import { fileURLToPath } from "node:url";
18
- import { dirname, join } from "node:path";
19
- import { registerClaude } from "../bin/installers/claude.mjs";
20
- import { makeFakeClaude, registrationActions } from "./helpers/fake-claude.mjs";
21
-
22
- const __dirname = dirname(fileURLToPath(import.meta.url));
23
- const ROOT = join(__dirname, "..");
24
- const CLI_PATH = join(ROOT, "bin", "agent-cortex.mjs");
25
-
26
- function runCli(args, env = {}) {
27
- return new Promise((resolve) => {
28
- execFile(
29
- process.execPath,
30
- [CLI_PATH, ...args],
31
- { env: { ...process.env, ...env } },
32
- (error, stdout, stderr) => {
33
- resolve({ exitCode: error ? error.code ?? 1 : 0, stdout, stderr });
34
- },
35
- );
36
- });
37
- }
38
-
39
- /** True when a path exists (stat succeeds). */
40
- async function pathExists(p) {
41
- try {
42
- await stat(p);
43
- return true;
44
- } catch {
45
- return false;
46
- }
47
- }
48
-
49
- function makeTmp() {
50
- return mkdtemp(join(tmpdir(), "agent-cortex-claude-installer-"));
51
- }
52
-
53
- async function callLines(log) {
54
- return (await readFile(log, "utf-8")).trim().split("\n").filter(Boolean);
55
- }
56
-
57
- /** Registration actions logged at/after line `since` (0 = whole log). */
58
- async function actionsSince(log, since = 0) {
59
- return registrationActions((await callLines(log)).slice(since));
60
- }
61
-
62
- /** A minimal marketplace tree (as seen at the real install root): the
63
- * marketplace manifest exposing ./claude plus the materialised plugin.json the
64
- * fake reads for the installed version. */
65
- async function makeMarketplaceRoot({ name = "jaybeeuu", plugin = "agent-cortex", version = "1.0.0" } = {}) {
66
- const root = await mkdtemp(join(tmpdir(), "agent-cortex-marketplace-"));
67
- await mkdir(join(root, ".claude-plugin"), { recursive: true });
68
- await writeFile(
69
- join(root, ".claude-plugin", "marketplace.json"),
70
- JSON.stringify({
71
- name,
72
- owner: { name: "tester" },
73
- plugins: [{ name: plugin, source: "./claude", description: "test plugin" }],
74
- }),
75
- );
76
- await mkdir(join(root, "claude", ".claude-plugin"), { recursive: true });
77
- await writeFile(
78
- join(root, "claude", ".claude-plugin", "plugin.json"),
79
- JSON.stringify({ name: plugin, version, description: "test plugin" }),
80
- );
81
- return root;
82
- }
83
-
84
- // ─── registerClaude ──────────────────────────────────────────────────────────
85
-
86
- describe("registerClaude", () => {
87
- it("drives marketplace add → install on a fresh registration, skipping the market update/plugin update no-ops", async () => {
88
- const tmp = await makeTmp();
89
- const root = await makeMarketplaceRoot();
90
- const fake = await makeFakeClaude(tmp);
91
- try {
92
- const result = await registerClaude({ root, claudeBin: fake.bin });
93
- assert.equal(result.marketplace, "jaybeeuu");
94
- assert.equal(result.plugin, "agent-cortex");
95
- assert.equal(result.dryRun, false);
96
- assert.equal(result.registered, true);
97
- assert.deepEqual(result.commands, [
98
- ["plugin", "marketplace", "add", root],
99
- ["plugin", "install", "agent-cortex@jaybeeuu", "-y"],
100
- ]);
101
- // The availability probe (`plugin --help`) and the state queries
102
- // (`marketplace list --json`, `plugin list --json`) are not actions.
103
- assert.deepEqual(await actionsSince(fake.log), [
104
- `plugin marketplace add ${root}`,
105
- "plugin install agent-cortex@jaybeeuu -y",
106
- ]);
107
- } finally {
108
- await rm(tmp, { recursive: true, force: true });
109
- await rm(root, { recursive: true, force: true });
110
- }
111
- });
112
-
113
- it("derives marketplace and plugin names from the manifest", async () => {
114
- const tmp = await makeTmp();
115
- const root = await makeMarketplaceRoot({ name: "scratchmkt", plugin: "scratchplug" });
116
- const fake = await makeFakeClaude(tmp);
117
- try {
118
- const result = await registerClaude({ root, claudeBin: fake.bin });
119
- assert.equal(result.marketplace, "scratchmkt");
120
- assert.equal(result.plugin, "scratchplug");
121
- const actions = await actionsSince(fake.log);
122
- assert.ok(actions.some((c) => c === "plugin install scratchplug@scratchmkt -y"));
123
- // Fresh registration has no update commands.
124
- assert.ok(!actions.some((c) => c.startsWith("plugin update")));
125
- } finally {
126
- await rm(tmp, { recursive: true, force: true });
127
- await rm(root, { recursive: true, force: true });
128
- }
129
- });
130
-
131
- it("re-running at the same materialised version is the update path: marketplace update only, no re-add or double-install", async () => {
132
- const tmp = await makeTmp();
133
- const root = await makeMarketplaceRoot();
134
- const fake = await makeFakeClaude(tmp);
135
- try {
136
- await registerClaude({ root, claudeBin: fake.bin });
137
- assert.deepEqual(await actionsSince(fake.log), [
138
- `plugin marketplace add ${root}`,
139
- "plugin install agent-cortex@jaybeeuu -y",
140
- ]);
141
- const run1End = (await callLines(fake.log)).length;
142
-
143
- const second = await registerClaude({ root, claudeBin: fake.bin });
144
- assert.equal(second.registered, true);
145
- // Second run: the marketplace is present so only the update path runs;
146
- // the plugin is already at the materialised version so nothing installs.
147
- assert.deepEqual(await actionsSince(fake.log, run1End), ["plugin marketplace update jaybeeuu"]);
148
- assert.ok(second.skipped.some((s) => s.includes("already installed")));
149
-
150
- // Across both runs the marketplace was added exactly once and the
151
- // plugin installed exactly once.
152
- const all = await actionsSince(fake.log);
153
- assert.equal(all.filter((c) => c.startsWith("plugin marketplace add")).length, 1);
154
- assert.equal(all.filter((c) => c === "plugin install agent-cortex@jaybeeuu -y").length, 1);
155
- } finally {
156
- await rm(tmp, { recursive: true, force: true });
157
- await rm(root, { recursive: true, force: true });
158
- }
159
- });
160
-
161
- it("runs plugin update when a newer version is materialised, refreshing the installed version", async () => {
162
- const tmp = await makeTmp();
163
- const root = await makeMarketplaceRoot({ version: "1.0.0" });
164
- const fake = await makeFakeClaude(tmp);
165
- try {
166
- await registerClaude({ root, claudeBin: fake.bin });
167
- assert.deepEqual(await actionsSince(fake.log), [
168
- `plugin marketplace add ${root}`,
169
- "plugin install agent-cortex@jaybeeuu -y",
170
- ]);
171
- const run1End = (await callLines(fake.log)).length;
172
-
173
- // A new version is materialised into the source the marketplace exposes.
174
- await writeFile(
175
- join(root, "claude", ".claude-plugin", "plugin.json"),
176
- JSON.stringify({ name: "agent-cortex", version: "2.0.0", description: "test plugin" }),
177
- );
178
-
179
- const updated = await registerClaude({ root, claudeBin: fake.bin });
180
- assert.deepEqual(await actionsSince(fake.log, run1End), [
181
- "plugin marketplace update jaybeeuu",
182
- "plugin update agent-cortex -y",
183
- ]);
184
- assert.ok(updated.skipped.some((s) => s.includes("already installed")));
185
- const state = JSON.parse(await readFile(fake.state, "utf-8"));
186
- assert.equal(state.installed[0].version, "2.0.0", "plugin update refreshed the installed version");
187
-
188
- // At the new version another run is back to update-path-only.
189
- const run2End = (await callLines(fake.log)).length;
190
- await registerClaude({ root, claudeBin: fake.bin });
191
- assert.deepEqual(await actionsSince(fake.log, run2End), ["plugin marketplace update jaybeeuu"]);
192
- } finally {
193
- await rm(tmp, { recursive: true, force: true });
194
- await rm(root, { recursive: true, force: true });
195
- }
196
- });
197
-
198
- it("--dry-run plans the commands without spawning claude at all", async () => {
199
- const tmp = await makeTmp();
200
- const root = await makeMarketplaceRoot();
201
- const fake = await makeFakeClaude(tmp);
202
- try {
203
- const result = await registerClaude({ root, claudeBin: fake.bin, dryRun: true });
204
- assert.equal(result.dryRun, true);
205
- assert.equal(result.registered, true);
206
- // Dry-run cannot know live state without spawning — no commands decided.
207
- assert.deepEqual(result.commands, []);
208
- // No registration spawns happened at all in dry-run mode.
209
- assert.equal(await pathExists(fake.log), false);
210
- } finally {
211
- await rm(tmp, { recursive: true, force: true });
212
- await rm(root, { recursive: true, force: true });
213
- }
214
- });
215
-
216
- it("defaults the claude binary to `claude` from PATH (dry-run only — no spawn)", async () => {
217
- const root = await makeMarketplaceRoot();
218
- try {
219
- const result = await registerClaude({ root, dryRun: true });
220
- assert.equal(result.marketplace, "jaybeeuu");
221
- assert.equal(result.plugin, "agent-cortex");
222
- assert.equal(result.dryRun, true);
223
- } finally {
224
- await rm(root, { recursive: true, force: true });
225
- }
226
- });
227
-
228
- it("warns with manual commands when the claude binary lacks the plugin subcommand; --require-register fails the run", async () => {
229
- const tmp = await makeTmp();
230
- const root = await makeMarketplaceRoot();
231
- const fake = await makeFakeClaude(tmp, { failWhen: "plugin --help" });
232
- try {
233
- const warnings = [];
234
- const result = await registerClaude({ root, claudeBin: fake.bin, warn: (m) => warnings.push(m) });
235
- assert.equal(result.registered, false);
236
- const manual = warnings.join("\n");
237
- assert.match(manual, /NOT registered/);
238
- assert.match(manual, new RegExp(`claude plugin marketplace add ${root.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`));
239
- assert.match(manual, /claude plugin install agent-cortex@jaybeeuu -y/);
240
-
241
- await assert.rejects(
242
- registerClaude({ root, claudeBin: fake.bin, requireRegister: true }),
243
- /require-register/,
244
- );
245
- } finally {
246
- await rm(tmp, { recursive: true, force: true });
247
- await rm(root, { recursive: true, force: true });
248
- }
249
- });
250
-
251
- it("fails, naming the failing command, when a claude plugin command errors", async () => {
252
- const tmp = await makeTmp();
253
- const root = await makeMarketplaceRoot();
254
- const fake = await makeFakeClaude(tmp, { failWhen: "plugin install" });
255
- try {
256
- await assert.rejects(
257
- registerClaude({ root, claudeBin: fake.bin }),
258
- /claude plugin install agent-cortex@jaybeeuu -y/,
259
- );
260
- } finally {
261
- await rm(tmp, { recursive: true, force: true });
262
- await rm(root, { recursive: true, force: true });
263
- }
264
- });
265
-
266
- it("fails when the checkout has no marketplace manifest", async () => {
267
- const tmp = await makeTmp();
268
- const root = await mkdtemp(join(tmpdir(), "agent-cortex-no-market-"));
269
- const fake = await makeFakeClaude(tmp);
270
- try {
271
- await assert.rejects(
272
- registerClaude({ root, claudeBin: fake.bin }),
273
- /marketplace\.json/,
274
- );
275
- // The failed manifest read happens before any claude invocation.
276
- assert.equal(await pathExists(fake.log), false);
277
- } finally {
278
- await rm(tmp, { recursive: true, force: true });
279
- await rm(root, { recursive: true, force: true });
280
- }
281
- });
282
- });
283
-
284
- // ─── CLI wiring ───────────────────────────────────────────────────────────────
285
-
286
- describe("install claude CLI: registration", () => {
287
- it("--dry-run without --output prints the registration plan and writes nothing", async () => {
288
- const { exitCode, stdout } = await runCli(["install", "claude", "--dry-run"]);
289
- assert.equal(exitCode, 0);
290
- assert.ok(stdout.includes("would run: claude plugin marketplace add"));
291
- assert.ok(stdout.includes("claude plugin install agent-cortex@jaybeeuu"));
292
- assert.ok(stdout.includes("plugin marketplace update"));
293
- assert.ok(stdout.includes("plugin update agent-cortex"));
294
- assert.ok(/dry-run/i.test(stdout));
295
- });
296
-
297
- it("--output <dir> generates only — no registration plan is printed", async () => {
298
- const out = await mkdtemp(join(tmpdir(), "agent-cortex-cli-out-"));
299
- try {
300
- const { exitCode, stdout } = await runCli(["install", "claude", "--output", out]);
301
- assert.equal(exitCode, 0);
302
- assert.doesNotMatch(stdout, /claude plugin/);
303
- assert.ok(stdout.includes("Generated"));
304
- } finally {
305
- await rm(out, { recursive: true, force: true });
306
- }
307
- });
308
- });
package/test/cli.test.mjs DELETED
@@ -1,314 +0,0 @@
1
- import { describe, it } from "node:test";
2
- import assert from "node:assert/strict";
3
- import { execFile } from "node:child_process";
4
- import { mkdtemp, readFile, rm } from "node:fs/promises";
5
- import { tmpdir } from "node:os";
6
- import { fileURLToPath } from "node:url";
7
- import { dirname, join } from "node:path";
8
- import { parseArgs, buildHelpText, validateHarness } from "../lib/cli.mjs";
9
-
10
- const __dirname = dirname(fileURLToPath(import.meta.url));
11
- const CLI_PATH = join(__dirname, "..", "bin", "agent-cortex.mjs");
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
-
23
- /** Run the CLI; optionally pass a custom env (e.g. a fake HOME). */
24
- function runCli(args, env = {}) {
25
- return new Promise((resolve) => {
26
- execFile(
27
- process.execPath,
28
- [CLI_PATH, ...args],
29
- { env: { ...process.env, ...env } },
30
- (error, stdout, stderr) => {
31
- resolve({
32
- exitCode: error ? error.code ?? 1 : 0,
33
- stdout: stdout,
34
- stderr: stderr,
35
- });
36
- },
37
- );
38
- });
39
- }
40
-
41
- describe("parseArgs", () => {
42
- it("returns { command: 'help' } when no arguments given", async () => {
43
- const result = parseArgs([]);
44
- assert.deepStrictEqual(result, { command: "help" });
45
- });
46
-
47
- it("returns { command: 'help' } for --help flag", async () => {
48
- const result = parseArgs(["--help"]);
49
- assert.deepStrictEqual(result, { command: "help" });
50
- });
51
-
52
- it("returns { command: 'help' } for -h flag", async () => {
53
- const result = parseArgs(["-h"]);
54
- assert.deepStrictEqual(result, { command: "help" });
55
- });
56
-
57
- it("returns { command: 'install', harness: 'claude' } for install claude", async () => {
58
- const result = parseArgs(["install", "claude"]);
59
- assert.deepStrictEqual(result, { command: "install", harness: "claude" });
60
- });
61
-
62
- it("returns { command: 'install', harness: 'pi' } for install pi", async () => {
63
- const result = parseArgs(["install", "pi"]);
64
- assert.deepStrictEqual(result, { command: "install", harness: "pi" });
65
- });
66
-
67
- it("returns { command: 'install', harness: null } when install has no harness", async () => {
68
- const result = parseArgs(["install"]);
69
- assert.deepStrictEqual(result, { command: "install", harness: null });
70
- });
71
-
72
- it("returns output for install claude --output <dir>", async () => {
73
- const result = parseArgs(["install", "claude", "--output", "/tmp/x"]);
74
- assert.deepStrictEqual(result, { command: "install", harness: "claude", output: "/tmp/x" });
75
- });
76
-
77
- it("returns output for install claude --output=<dir>", async () => {
78
- const result = parseArgs(["install", "claude", "--output=/tmp/x"]);
79
- assert.deepStrictEqual(result, { command: "install", harness: "claude", output: "/tmp/x" });
80
- });
81
-
82
- it("returns dryRun for install claude --dry-run", async () => {
83
- const result = parseArgs(["install", "claude", "--dry-run"]);
84
- assert.deepStrictEqual(result, { command: "install", harness: "claude", dryRun: true });
85
- });
86
-
87
- it("combines --dry-run with --output", async () => {
88
- const result = parseArgs(["install", "claude", "--output", "/tmp/x", "--dry-run"]);
89
- assert.deepStrictEqual(result, { command: "install", harness: "claude", output: "/tmp/x", dryRun: true });
90
- });
91
-
92
- it("reports an unknown option for claude", async () => {
93
- const result = parseArgs(["install", "claude", "--bogus"]);
94
- assert.equal(result.command, "install");
95
- assert.equal(result.harness, "claude");
96
- assert.ok(result.optionError.includes("--bogus"));
97
- });
98
-
99
- it("reports a missing value for --output for claude", async () => {
100
- const result = parseArgs(["install", "claude", "--output"]);
101
- assert.equal(result.command, "install");
102
- assert.equal(result.harness, "claude");
103
- assert.ok(result.optionError.includes("--output"));
104
- });
105
-
106
- it("parses --dry-run for install", async () => {
107
- const result = parseArgs(["install", "pi", "--dry-run"]);
108
- assert.deepStrictEqual(result, { command: "install", harness: "pi", dryRun: true });
109
- });
110
-
111
- it("parses --output <dir> for install", async () => {
112
- const result = parseArgs(["install", "pi", "--output", "/tmp/out"]);
113
- assert.deepStrictEqual(result, { command: "install", harness: "pi", output: "/tmp/out" });
114
- });
115
-
116
- it("parses --plugin-root <dir> for install", async () => {
117
- const result = parseArgs(["install", "pi", "--plugin-root", "/tmp/plugin"]);
118
- assert.deepStrictEqual(result, { command: "install", harness: "pi", pluginRoot: "/tmp/plugin" });
119
- });
120
-
121
- it("combines multiple install options", async () => {
122
- const result = parseArgs(["install", "pi", "--dry-run", "--output", "/tmp/out", "--plugin-root", "/tmp/plugin"]);
123
- assert.deepStrictEqual(result, {
124
- command: "install",
125
- harness: "pi",
126
- dryRun: true,
127
- output: "/tmp/out",
128
- pluginRoot: "/tmp/plugin",
129
- });
130
- });
131
-
132
- it("reports a missing value for --output", async () => {
133
- const result = parseArgs(["install", "pi", "--output"]);
134
- assert.equal(result.command, "install");
135
- assert.equal(result.harness, "pi");
136
- assert.ok(result.optionError.includes("--output"));
137
- });
138
-
139
- it("reports an unknown option", async () => {
140
- const result = parseArgs(["install", "pi", "--bogus"]);
141
- assert.equal(result.command, "install");
142
- assert.equal(result.harness, "pi");
143
- assert.ok(result.optionError.includes("--bogus"));
144
- });
145
-
146
- it("keeps the old shape when no options are given", async () => {
147
- assert.deepStrictEqual(parseArgs(["install", "pi"]), { command: "install", harness: "pi" });
148
- });
149
-
150
- it("returns { command: 'unknown', name: 'foo' } for unknown command", async () => {
151
- const result = parseArgs(["foo"]);
152
- assert.deepStrictEqual(result, { command: "unknown", name: "foo" });
153
- });
154
- });
155
-
156
- describe("buildHelpText", () => {
157
- it("returns a string containing usage info", async () => {
158
- const text = buildHelpText();
159
- assert.ok(typeof text === "string");
160
- assert.ok(text.includes("agent-cortex"));
161
- assert.ok(text.includes("install"));
162
- });
163
- });
164
-
165
- describe("validateHarness", () => {
166
- it("returns ok for supported harnesses", async () => {
167
- for (const h of ["claude", "pi"]) {
168
- assert.deepStrictEqual(validateHarness(h), { ok: true });
169
- }
170
- });
171
-
172
- it("returns error for copilot, which is no longer a deploy target", async () => {
173
- const result = validateHarness("copilot");
174
- assert.equal(result.ok, false);
175
- assert.ok(result.error.includes("Unknown harness"));
176
- });
177
-
178
- it("returns error when harness is null", async () => {
179
- const result = validateHarness(null);
180
- assert.equal(result.ok, false);
181
- assert.ok(result.error.includes("Missing harness"));
182
- });
183
-
184
- it("returns error for unknown harness", async () => {
185
- const result = validateHarness("docker");
186
- assert.equal(result.ok, false);
187
- assert.ok(result.error.includes("Unknown harness"));
188
- });
189
- });
190
-
191
- describe("CLI integration", () => {
192
- it("exits 0 and prints help when run with no arguments", async () => {
193
- const { exitCode, stdout } = await runCli([]);
194
- assert.equal(exitCode, 0);
195
- assert.ok(stdout.includes("agent-cortex"));
196
- assert.ok(stdout.includes("install"));
197
- });
198
-
199
- it("exits 0 and prints help for --help", async () => {
200
- const { exitCode, stdout } = await runCli(["--help"]);
201
- assert.equal(exitCode, 0);
202
- assert.ok(stdout.includes("Usage:"));
203
- });
204
-
205
- it("exits 0 and prints help for -h", async () => {
206
- const { exitCode, stdout } = await runCli(["-h"]);
207
- assert.equal(exitCode, 0);
208
- assert.ok(stdout.includes("Usage:"));
209
- });
210
-
211
- it("exits 1 for install copilot, which is no longer a deploy target", async () => {
212
- const { exitCode, stderr } = await runCli(["install", "copilot"]);
213
- assert.equal(exitCode, 1);
214
- assert.ok(stderr.includes("Unknown harness"));
215
- });
216
-
217
- it("exits 0 for install claude (into a temp output dir)", async () => {
218
- const dir = await mkdtemp(join(tmpdir(), "agent-cortex-cli-"));
219
- const { exitCode, stdout } = await runCli(["install", "claude", "--output", dir]);
220
- assert.equal(exitCode, 0);
221
- assert.ok(stdout.includes("claude"));
222
- });
223
-
224
- it("exits 0 for install pi (into a temp output dir)", async () => {
225
- const outDir = await mkdtemp(join(tmpdir(), "cli-pi-default-"));
226
- try {
227
- const { exitCode, stdout } = await runCli(["install", "pi", "--output", outDir]);
228
- assert.equal(exitCode, 0);
229
- assert.ok(stdout.includes("pi"));
230
- } finally {
231
- await rm(outDir, { recursive: true, force: true });
232
- }
233
- });
234
-
235
- it("exits 1 and prints error for install with no harness", async () => {
236
- const { exitCode, stderr } = await runCli(["install"]);
237
- assert.equal(exitCode, 1);
238
- assert.ok(stderr.includes("Missing harness"));
239
- });
240
-
241
- it("exits 1 and prints error for install with an unknown option", async () => {
242
- const { exitCode, stderr } = await runCli(["install", "pi", "--bogus"]);
243
- assert.equal(exitCode, 1);
244
- assert.ok(stderr.includes("--bogus"));
245
- });
246
-
247
- it("exits 1 for the removed --no-provision flag", async () => {
248
- const { exitCode, stderr } = await runCli(["install", "pi", "--no-provision"]);
249
- assert.equal(exitCode, 1);
250
- assert.ok(stderr.includes("--no-provision"), "the removed flag is named in the error");
251
- });
252
-
253
- it("exits 1 and prints error for install with a missing option value", async () => {
254
- const { exitCode, stderr } = await runCli(["install", "pi", "--output"]);
255
- assert.equal(exitCode, 1);
256
- assert.ok(stderr.includes("--output"));
257
- });
258
-
259
- it("exits 0 for install pi --dry-run and writes nothing", async () => {
260
- // A throwaway HOME keeps the registration plan (which reads pi's settings)
261
- // off the developer's real ~/.pi/agent.
262
- const fakeHome = await mkdtemp(join(tmpdir(), "cli-pi-dry-run-home-"));
263
- try {
264
- const { exitCode, stdout } = await runCli(["install", "pi", "--dry-run"], { HOME: fakeHome });
265
- assert.equal(exitCode, 0);
266
- assert.ok(stdout.includes("ralph.agent.md"));
267
- assert.ok(stdout.includes("dry-run"));
268
- } finally {
269
- await rm(fakeHome, { recursive: true, force: true });
270
- }
271
- });
272
-
273
- it("install pi --dry-run reports the bundled pi packages it would stage", async () => {
274
- const fakeHome = await mkdtemp(join(tmpdir(), "cli-pi-home-"));
275
- try {
276
- const { exitCode, stdout } = await runCli(["install", "pi", "--dry-run"], { HOME: fakeHome });
277
- assert.equal(exitCode, 0);
278
- assert.ok(stdout.includes("Bundled pi package pi-questions"), "pi-questions staged");
279
- assert.ok(stdout.includes("Bundled pi package pi-web-access"), "pi-web-access staged");
280
- assert.ok(stdout.includes(join(fakeHome, ".agent-cortex", "pi")), "default output is the home staging dir");
281
- } finally {
282
- await rm(fakeHome, { recursive: true, force: true });
283
- }
284
- });
285
-
286
- it("exits 0 for install pi --output <tmp> and stages a loadable pi package", async () => {
287
- const outDir = await mkdtemp(join(tmpdir(), "cli-pi-install-"));
288
- try {
289
- const { exitCode, stdout } = await runCli(["install", "pi", "--output", outDir]);
290
- assert.equal(exitCode, 0);
291
- assert.ok(stdout.includes("ralph.agent.md"));
292
- const ralph = await readFile(join(outDir, "agents", "ralph.agent.md"), "utf-8");
293
- assert.ok(ralph.includes("agent-cortex:ralph"));
294
- const manifest = JSON.parse(await readFile(join(outDir, "package.json"), "utf-8"));
295
- assert.deepEqual(manifest.keywords, ["pi-package"]);
296
- assert.ok(manifest.pi.extensions.includes("./extensions"));
297
- assert.ok(manifest.pi.skills.includes("./skills"));
298
- } finally {
299
- await rm(outDir, { recursive: true, force: true });
300
- }
301
- });
302
-
303
- it("exits 1 and prints error for install with unknown harness", async () => {
304
- const { exitCode, stderr } = await runCli(["install", "docker"]);
305
- assert.equal(exitCode, 1);
306
- assert.ok(stderr.includes("Unknown harness"));
307
- });
308
-
309
- it("exits 1 and prints error for unknown command", async () => {
310
- const { exitCode, stderr } = await runCli(["bogus"]);
311
- assert.equal(exitCode, 1);
312
- assert.ok(stderr.includes("Unknown command"));
313
- });
314
- });