@jaybeeuu/agent-cortex 1.39.0 → 1.41.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 +35 -8
- package/CHANGELOG.md +60 -0
- package/README.md +174 -97
- package/agents/README.md +14 -11
- package/bin/agent-cortex.mjs +55 -10
- package/bin/installers/claude.mjs +674 -174
- package/bin/installers/copilot.mjs +13 -18
- package/bin/installers/pi.mjs +293 -51
- package/claude/settings.json +4 -0
- package/{claude → claude-extras}/scripts/statusline-command.sh +32 -4
- package/docs/claude-hooks.md +15 -3
- package/docs/ideas/ban-content-assertion-tests.md +73 -0
- package/docs/ideas/bead-progress-notes-for-session-resumption.md +75 -0
- package/docs/ideas/classify-beads-via-deterministic-script-with-agent-fallback.md +70 -0
- package/docs/ideas/ctx-alias-version-and-update-commands.md +65 -0
- package/docs/ideas/ctx-feedback-command-for-capturing-ideas-from-any-machine.md +67 -0
- package/docs/ideas/deployment-beads-for-post-merge-pipelines.md +36 -0
- package/docs/ideas/design-and-delivery-as-separate-bead-trees/design-and-delivery-as-separate-bead-trees.md +99 -0
- package/docs/ideas/enable-signed-commit-enforcement-changesets-bot-commits-must-be-signed.md +33 -0
- package/docs/ideas/extension-manifests.md +55 -14
- package/docs/ideas/improve-pi-system-prompt.md +5 -2
- package/docs/ideas/map-every-cortex-tool-to-a-real-pi-equivalent.md +95 -0
- package/docs/ideas/record-idea-implementation-questions.md +68 -0
- package/docs/ideas/self-flushing-session.md +72 -0
- package/docs/ideas/state-in-beads-not-md.md +69 -0
- package/docs/ideas/task-session-context.local.md +95 -0
- package/docs/ideas/test-data-creation-helpers.md +92 -0
- package/docs/ideas/test-planning-stage-bead-in-feature-planning.md +68 -0
- package/docs/ideas/worktree-housekeeping-for-closed-beads.md +39 -0
- package/docs/technical-direction/harness-config.md +161 -0
- package/docs/technical-direction/migration-execution-labels.md +60 -23
- package/extensions/agent-guardrails/README.md +45 -0
- package/extensions/agent-guardrails/guardrails.test.ts +42 -0
- package/extensions/agent-guardrails/guardrails.ts +66 -0
- package/extensions/agent-guardrails/index.ts +22 -0
- package/extensions/agent-guardrails/node_modules/.bin/pi +21 -0
- package/extensions/agent-guardrails/node_modules/.bin/tsc +21 -0
- package/extensions/agent-guardrails/node_modules/.bin/tsserver +21 -0
- package/extensions/agent-guardrails/node_modules/.bin/tsx +21 -0
- package/extensions/agent-guardrails/package.json +19 -0
- package/extensions/agent-guardrails/tsconfig.json +12 -0
- package/extensions/agent-modes/README.md +12 -5
- package/extensions/agent-modes/discover.test.ts +30 -7
- package/extensions/agent-modes/discover.ts +1 -1
- package/extensions/agent-modes/index.test.ts +208 -0
- package/extensions/agent-modes/index.ts +23 -6
- package/extensions/agent-modes/package.json +1 -1
- package/extensions/skill-stats/index.test.ts +3 -2
- package/lib/cli.mjs +30 -12
- package/lib/pi-packages.mjs +169 -0
- package/package.json +8 -4
- package/pi/settings.json +23 -12
- package/plugin.json +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/scripts/lib/fs.mjs +31 -0
- package/skills/engineering/grill-with-docs/SKILL.md +1 -1
- package/skills/planning/classify-bead/REFERENCE.md +85 -0
- package/skills/planning/classify-bead/SKILL.md +32 -36
- package/skills/planning/classify-bead/scripts/classify-bead.mjs +292 -0
- package/skills/planning/create-task/SKILL.md +19 -10
- package/skills/planning/write-a-prd/SKILL.md +2 -2
- package/skills/style/style-code/REFERENCE.md +32 -2
- package/skills/style/style-code/SKILL.md +5 -21
- package/skills/style/style-tests/SKILL.md +69 -17
- package/skills/workflow/hitl-collab/SKILL.md +1 -1
- package/skills/workflow/run-pipeline-stage/SKILL.md +13 -1
- package/skills/workflow/run-pipeline-stage/prompts/stage-runner.md +12 -0
- package/test/classify-bead.test.mjs +277 -0
- package/test/claude-installer.test.mjs +199 -139
- package/test/cli.test.mjs +95 -41
- package/test/copilot-installer.test.mjs +92 -82
- package/test/fs.test.mjs +76 -0
- package/test/helpers/fake-claude.mjs +152 -0
- package/test/installer.test.mjs +542 -57
- package/test/pi-installer.test.mjs +457 -102
- package/test/pi-packages.test.mjs +295 -0
- package/token-map.README.md +23 -12
- package/token-map.json +8 -7
- 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
|
@@ -1,71 +1,71 @@
|
|
|
1
1
|
// Tests for `agent-cortex install claude` runtime registration: after
|
|
2
|
-
//
|
|
3
|
-
// with Claude Code by driving the `claude plugin` CLI
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
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.
|
|
7
11
|
|
|
8
12
|
import { describe, it } from "node:test";
|
|
9
13
|
import assert from "node:assert/strict";
|
|
10
14
|
import { execFile } from "node:child_process";
|
|
11
|
-
import {
|
|
12
|
-
mkdtempSync,
|
|
13
|
-
mkdirSync,
|
|
14
|
-
writeFileSync,
|
|
15
|
-
rmSync,
|
|
16
|
-
readFileSync,
|
|
17
|
-
chmodSync,
|
|
18
|
-
existsSync,
|
|
19
|
-
} from "node:fs";
|
|
15
|
+
import { mkdtemp, mkdir, writeFile, rm, readFile, stat } from "node:fs/promises";
|
|
20
16
|
import { tmpdir } from "node:os";
|
|
21
17
|
import { fileURLToPath } from "node:url";
|
|
22
18
|
import { dirname, join } from "node:path";
|
|
23
19
|
import { registerClaude } from "../bin/installers/claude.mjs";
|
|
20
|
+
import { makeFakeClaude, registrationActions } from "./helpers/fake-claude.mjs";
|
|
24
21
|
|
|
25
22
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
26
23
|
const ROOT = join(__dirname, "..");
|
|
27
24
|
const CLI_PATH = join(ROOT, "bin", "agent-cortex.mjs");
|
|
28
25
|
|
|
29
|
-
function runCli(args) {
|
|
26
|
+
function runCli(args, env = {}) {
|
|
30
27
|
return new Promise((resolve) => {
|
|
31
|
-
execFile(
|
|
32
|
-
|
|
33
|
-
|
|
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
|
+
);
|
|
34
36
|
});
|
|
35
37
|
}
|
|
36
38
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const bin = join(dir, "claude");
|
|
46
|
-
const log = join(dir, "calls.log");
|
|
47
|
-
const script = [
|
|
48
|
-
"#!/bin/sh",
|
|
49
|
-
`echo "$*" >> "${log}"`,
|
|
50
|
-
...(failWhen
|
|
51
|
-
? [`if printf '%s' "$*" | grep -q -- "${failWhen}"; then exit 1; fi`]
|
|
52
|
-
: []),
|
|
53
|
-
"exit 0",
|
|
54
|
-
].join("\n");
|
|
55
|
-
writeFileSync(bin, `${script}\n`);
|
|
56
|
-
chmodSync(bin, 0o755);
|
|
57
|
-
return { bin, log };
|
|
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
|
+
}
|
|
58
47
|
}
|
|
59
48
|
|
|
60
49
|
function makeTmp() {
|
|
61
|
-
return
|
|
50
|
+
return mkdtemp(join(tmpdir(), "agent-cortex-claude-installer-"));
|
|
62
51
|
}
|
|
63
52
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
69
|
join(root, ".claude-plugin", "marketplace.json"),
|
|
70
70
|
JSON.stringify({
|
|
71
71
|
name,
|
|
@@ -73,150 +73,210 @@ function makeMarketplaceRoot({ name = "jaybeeuu", plugin = "agent-cortex" } = {}
|
|
|
73
73
|
plugins: [{ name: plugin, source: "./claude", description: "test plugin" }],
|
|
74
74
|
}),
|
|
75
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
|
+
);
|
|
76
81
|
return root;
|
|
77
82
|
}
|
|
78
83
|
|
|
79
84
|
// ─── registerClaude ──────────────────────────────────────────────────────────
|
|
80
85
|
|
|
81
86
|
describe("registerClaude", () => {
|
|
82
|
-
it("drives
|
|
83
|
-
const tmp = makeTmp();
|
|
84
|
-
const root = makeMarketplaceRoot();
|
|
85
|
-
const fake = makeFakeClaude(tmp);
|
|
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);
|
|
86
91
|
try {
|
|
87
|
-
const result = registerClaude({ root, claudeBin: fake.bin });
|
|
92
|
+
const result = await registerClaude({ root, claudeBin: fake.bin });
|
|
88
93
|
assert.equal(result.marketplace, "jaybeeuu");
|
|
89
94
|
assert.equal(result.plugin, "agent-cortex");
|
|
90
95
|
assert.equal(result.dryRun, false);
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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), [
|
|
97
104
|
`plugin marketplace add ${root}`,
|
|
98
105
|
"plugin install agent-cortex@jaybeeuu -y",
|
|
99
|
-
"plugin marketplace update jaybeeuu",
|
|
100
|
-
"plugin update agent-cortex -y",
|
|
101
106
|
]);
|
|
102
107
|
} finally {
|
|
103
|
-
|
|
104
|
-
|
|
108
|
+
await rm(tmp, { recursive: true, force: true });
|
|
109
|
+
await rm(root, { recursive: true, force: true });
|
|
105
110
|
}
|
|
106
111
|
});
|
|
107
112
|
|
|
108
|
-
it("derives marketplace and plugin names from the manifest", () => {
|
|
109
|
-
const tmp = makeTmp();
|
|
110
|
-
const root = makeMarketplaceRoot({ name: "scratchmkt", plugin: "scratchplug" });
|
|
111
|
-
const fake = makeFakeClaude(tmp);
|
|
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);
|
|
112
117
|
try {
|
|
113
|
-
const result = registerClaude({ root, claudeBin: fake.bin });
|
|
118
|
+
const result = await registerClaude({ root, claudeBin: fake.bin });
|
|
114
119
|
assert.equal(result.marketplace, "scratchmkt");
|
|
115
120
|
assert.equal(result.plugin, "scratchplug");
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
assert.ok(
|
|
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")));
|
|
120
125
|
} finally {
|
|
121
|
-
|
|
122
|
-
|
|
126
|
+
await rm(tmp, { recursive: true, force: true });
|
|
127
|
+
await rm(root, { recursive: true, force: true });
|
|
123
128
|
}
|
|
124
129
|
});
|
|
125
130
|
|
|
126
|
-
it("
|
|
127
|
-
const tmp = makeTmp();
|
|
128
|
-
const root = makeMarketplaceRoot();
|
|
129
|
-
const fake = makeFakeClaude(tmp);
|
|
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);
|
|
130
135
|
try {
|
|
131
|
-
|
|
132
|
-
assert.
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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" }),
|
|
141
177
|
);
|
|
142
|
-
|
|
143
|
-
|
|
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"]);
|
|
144
192
|
} finally {
|
|
145
|
-
|
|
146
|
-
|
|
193
|
+
await rm(tmp, { recursive: true, force: true });
|
|
194
|
+
await rm(root, { recursive: true, force: true });
|
|
147
195
|
}
|
|
148
196
|
});
|
|
149
197
|
|
|
150
|
-
it("
|
|
151
|
-
const
|
|
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);
|
|
152
202
|
try {
|
|
153
|
-
const result = registerClaude({ root, dryRun: true });
|
|
154
|
-
assert.equal(result.
|
|
155
|
-
assert.
|
|
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);
|
|
156
210
|
} finally {
|
|
157
|
-
|
|
211
|
+
await rm(tmp, { recursive: true, force: true });
|
|
212
|
+
await rm(root, { recursive: true, force: true });
|
|
158
213
|
}
|
|
159
214
|
});
|
|
160
215
|
|
|
161
|
-
it("
|
|
162
|
-
const
|
|
163
|
-
const root = makeMarketplaceRoot();
|
|
164
|
-
const fake = makeFakeClaude(tmp, { failWhen: "plugin --help" });
|
|
216
|
+
it("defaults the claude binary to `claude` from PATH (dry-run only — no spawn)", async () => {
|
|
217
|
+
const root = await makeMarketplaceRoot();
|
|
165
218
|
try {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
);
|
|
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);
|
|
170
223
|
} finally {
|
|
171
|
-
|
|
172
|
-
rmSync(root, { recursive: true, force: true });
|
|
224
|
+
await rm(root, { recursive: true, force: true });
|
|
173
225
|
}
|
|
174
226
|
});
|
|
175
227
|
|
|
176
|
-
it("
|
|
177
|
-
const tmp = makeTmp();
|
|
178
|
-
const root = makeMarketplaceRoot();
|
|
179
|
-
const fake = makeFakeClaude(tmp, { failWhen: "plugin
|
|
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" });
|
|
180
232
|
try {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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/,
|
|
184
244
|
);
|
|
185
245
|
} finally {
|
|
186
|
-
|
|
187
|
-
|
|
246
|
+
await rm(tmp, { recursive: true, force: true });
|
|
247
|
+
await rm(root, { recursive: true, force: true });
|
|
188
248
|
}
|
|
189
249
|
});
|
|
190
250
|
|
|
191
|
-
it("fails
|
|
192
|
-
const tmp = makeTmp();
|
|
193
|
-
const root =
|
|
194
|
-
const fake = makeFakeClaude(tmp);
|
|
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" });
|
|
195
255
|
try {
|
|
196
|
-
assert.
|
|
197
|
-
|
|
198
|
-
/
|
|
256
|
+
await assert.rejects(
|
|
257
|
+
registerClaude({ root, claudeBin: fake.bin }),
|
|
258
|
+
/claude plugin install agent-cortex@jaybeeuu -y/,
|
|
199
259
|
);
|
|
200
|
-
// The failed manifest read happens before any claude invocation.
|
|
201
|
-
assert.equal(existsSync(fake.log), false);
|
|
202
260
|
} finally {
|
|
203
|
-
|
|
204
|
-
|
|
261
|
+
await rm(tmp, { recursive: true, force: true });
|
|
262
|
+
await rm(root, { recursive: true, force: true });
|
|
205
263
|
}
|
|
206
264
|
});
|
|
207
265
|
|
|
208
|
-
it("
|
|
209
|
-
const tmp = makeTmp();
|
|
210
|
-
const
|
|
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);
|
|
211
270
|
try {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
assert.equal(
|
|
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);
|
|
218
277
|
} finally {
|
|
219
|
-
|
|
278
|
+
await rm(tmp, { recursive: true, force: true });
|
|
279
|
+
await rm(root, { recursive: true, force: true });
|
|
220
280
|
}
|
|
221
281
|
});
|
|
222
282
|
});
|
|
@@ -229,20 +289,20 @@ describe("install claude CLI: registration", () => {
|
|
|
229
289
|
assert.equal(exitCode, 0);
|
|
230
290
|
assert.ok(stdout.includes("would run: claude plugin marketplace add"));
|
|
231
291
|
assert.ok(stdout.includes("claude plugin install agent-cortex@jaybeeuu"));
|
|
232
|
-
assert.ok(stdout.includes("
|
|
233
|
-
assert.ok(stdout.includes("
|
|
292
|
+
assert.ok(stdout.includes("plugin marketplace update"));
|
|
293
|
+
assert.ok(stdout.includes("plugin update agent-cortex"));
|
|
234
294
|
assert.ok(/dry-run/i.test(stdout));
|
|
235
295
|
});
|
|
236
296
|
|
|
237
297
|
it("--output <dir> generates only — no registration plan is printed", async () => {
|
|
238
|
-
const out =
|
|
298
|
+
const out = await mkdtemp(join(tmpdir(), "agent-cortex-cli-out-"));
|
|
239
299
|
try {
|
|
240
300
|
const { exitCode, stdout } = await runCli(["install", "claude", "--output", out]);
|
|
241
301
|
assert.equal(exitCode, 0);
|
|
242
302
|
assert.doesNotMatch(stdout, /claude plugin/);
|
|
243
303
|
assert.ok(stdout.includes("Generated"));
|
|
244
304
|
} finally {
|
|
245
|
-
|
|
305
|
+
await rm(out, { recursive: true, force: true });
|
|
246
306
|
}
|
|
247
307
|
});
|
|
248
308
|
});
|