@jaybeeuu/agent-cortex 2.1.0 → 2.3.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 +411 -0
- package/README.md +33 -13
- package/agents/README.md +22 -10
- package/agents/plan/agent.md +2 -2
- package/agents/ralph/agent.md +126 -88
- package/agents/ralph/claude/frontmatter.json +3 -3
- package/agents/ralph/claude/wake.md +46 -0
- package/agents/ralph/pi/{polling.md → wake.md} +35 -20
- package/agents/ralph-plan/agent.md +23 -11
- package/bin/agent-cortex.mjs +66 -4
- package/bin/installers/claude.mjs +182 -68
- package/bin/installers/pi.mjs +200 -24
- package/extensions/agent-guardrails/README.md +1 -1
- package/extensions/agent-guardrails/guardrails.ts +1 -1
- package/extensions/agent-guardrails/index.ts +1 -1
- package/extensions/agent-modes/discover.test.ts +2 -2
- package/extensions/output-style/README.md +1 -1
- package/extensions/output-style/index.ts +2 -2
- package/extensions/output-style/output-style.ts +2 -2
- package/hooks/claude/scripts/notify.mjs +8 -1
- package/lib/cli.mjs +91 -2
- package/package.json +27 -3
- package/scripts/check-changeset.mjs +4 -0
- package/scripts/check-no-sync.mjs +5 -1
- package/scripts/lib/compose-agent.mjs +69 -9
- package/scripts/lib/fs.mjs +10 -2
- package/scripts/lib/migrate.mjs +736 -0
- package/scripts/lib/release-gates.mjs +21 -1
- package/scripts/lib/skill-frontmatter.mjs +23 -6
- package/scripts/lib/skill-links.mjs +52 -0
- package/scripts/lib/verify-worker-tree.mjs +25 -0
- package/scripts/resolve-publish-command.mjs +4 -0
- package/skills/engineering/design-an-interface/SKILL.md +3 -3
- package/skills/engineering/grill-with-docs/SKILL.md +3 -5
- package/skills/engineering/improve-codebase-architecture/SKILL.md +5 -4
- package/skills/engineering/tdd/SKILL.md +5 -7
- package/skills/engineering/technical-direction/SKILL.md +5 -5
- package/skills/planning/classify-bead/REFERENCE.md +3 -3
- package/skills/planning/classify-bead/SKILL.md +14 -9
- package/skills/planning/classify-bead/scripts/classify-bead.mjs +5 -4
- package/skills/planning/create-task/REFERENCE.md +52 -3
- package/skills/planning/create-task/SKILL.md +70 -26
- package/skills/planning/create-task/scripts/{create-chores.ts → create-chores.mjs} +48 -23
- package/skills/planning/flesh-out-idea/REFERENCE.md +116 -0
- package/skills/planning/flesh-out-idea/SKILL.md +146 -0
- package/skills/planning/prd-to-tasks/REFERENCE.md +54 -5
- package/skills/planning/prd-to-tasks/SKILL.md +22 -16
- package/skills/planning/record-idea/FORMAT.md +66 -17
- package/skills/planning/record-idea/SKILL.md +48 -24
- package/skills/planning/request-refactor-plan/SKILL.md +4 -4
- package/skills/planning/spec-to-tickets/SKILL.md +5 -5
- package/skills/planning/write-a-prd/SKILL.md +8 -9
- package/skills/planning/write-a-ticket/SKILL.md +4 -6
- package/skills/productivity/bd-tool/SKILL.md +15 -10
- package/skills/productivity/grill-me/SKILL.md +5 -0
- package/skills/productivity/init-beads/SKILL.md +3 -3
- package/skills/productivity/migration/REFERENCE.md +227 -0
- package/skills/productivity/migration/SKILL.md +188 -0
- package/skills/productivity/validate-agent-dir/scripts/{validate-agent-dir.ts → validate-agent-dir.mjs} +49 -28
- package/skills/productivity/write-a-skill/SKILL.md +5 -5
- package/skills/review/maintain-agent-docs/SKILL.md +5 -7
- package/skills/review/refactor-skill/SKILL.md +4 -3
- package/skills/review/review-security/SKILL.md +3 -5
- package/skills/style/style-code/SKILL.md +5 -10
- package/skills/style/style-comms/SKILL.md +5 -7
- package/skills/style/style-documentation/SKILL.md +5 -6
- package/skills/style/style-tests/SKILL.md +5 -7
- package/skills/workflow/git-workflow/SKILL.md +52 -26
- package/skills/workflow/hitl-collab/SKILL.md +4 -6
- package/skills/workflow/plan/SKILL.md +41 -30
- package/skills/workflow/ralph/REFERENCE.md +182 -51
- package/skills/workflow/ralph/SKILL.md +89 -50
- package/skills/workflow/run-pipeline-stage/SKILL.md +72 -71
- package/skills/workflow/run-pipeline-stage/playbooks/code.md +11 -0
- package/skills/workflow/run-pipeline-stage/playbooks/document.md +12 -0
- package/skills/workflow/run-pipeline-stage/playbooks/fix.md +12 -0
- package/skills/workflow/run-pipeline-stage/playbooks/review.md +11 -0
- package/skills/workflow/run-pipeline-stage/playbooks/verify.md +13 -0
- package/skills/workflow/run-pipeline-stage/prompts/stage-runner.md +73 -1
- package/token-map.README.md +9 -4
- package/token-map.json +13 -6
- package/.agents/skills/style-versioning/SKILL.md +0 -172
- package/.changeset/README.md +0 -8
- package/.changeset/config.json +0 -11
- package/.github/workflows/ci.yml +0 -302
- package/.junie/guidelines.md +0 -87
- package/.junie/mcp/mcp.json +0 -10
- package/AGENTS.md +0 -153
- package/agents/ralph/claude/polling.md +0 -31
- package/agents-native/ralph.md +0 -203
- package/docs/claude-hooks.md +0 -144
- package/docs/commit-signing.md +0 -231
- package/docs/ideas/auto-model-selection.md +0 -75
- package/docs/ideas/ban-content-assertion-tests.md +0 -75
- package/docs/ideas/bead-progress-notes-for-session-resumption.md +0 -75
- package/docs/ideas/classify-beads-via-deterministic-script-with-agent-fallback.md +0 -70
- package/docs/ideas/ctx-alias-version-and-update-commands.md +0 -65
- package/docs/ideas/ctx-feedback-command-for-capturing-ideas-from-any-machine.md +0 -67
- package/docs/ideas/dedicated-review-tests-skill.md +0 -71
- package/docs/ideas/deployment-beads-for-post-merge-pipelines.md +0 -36
- package/docs/ideas/design-and-delivery-as-separate-bead-trees/design-and-delivery-as-separate-bead-trees.md +0 -99
- package/docs/ideas/enable-signed-commit-enforcement-changesets-bot-commits-must-be-signed.md +0 -42
- package/docs/ideas/extension-manifests.md +0 -121
- package/docs/ideas/improve-pi-system-prompt.md +0 -43
- package/docs/ideas/map-every-cortex-tool-to-a-real-pi-equivalent.md +0 -95
- package/docs/ideas/model-selector.md +0 -40
- package/docs/ideas/pi-task-memory.md +0 -34
- package/docs/ideas/qa-extension.md +0 -43
- package/docs/ideas/record-idea-implementation-questions.md +0 -68
- package/docs/ideas/record-ideas-as-beads.md +0 -115
- package/docs/ideas/self-flushing-session.md +0 -72
- package/docs/ideas/state-in-beads-not-md.md +0 -69
- package/docs/ideas/subagent-visibility.md +0 -72
- package/docs/ideas/task-session-context.local.md +0 -95
- package/docs/ideas/task-session-context.md +0 -89
- package/docs/ideas/test-data-creation-helpers.md +0 -92
- package/docs/ideas/test-planning-stage-bead-in-feature-planning.md +0 -68
- package/docs/ideas/unified-ci-pipeline.md +0 -28
- package/docs/ideas/unify-the-ticket-and-task-decomposition-workflows.md +0 -71
- package/docs/ideas/worktree-housekeeping-for-closed-beads.md +0 -39
- package/docs/inspirations.md +0 -33
- package/docs/research/disable-model-invocation.md +0 -82
- package/docs/research/gas-city-research.md +0 -256
- package/docs/research/skill-design-research.md +0 -286
- package/docs/research/skill-improvements-analysis.md +0 -117
- package/docs/research/skill-patterns-research.md +0 -24
- package/docs/skills/skill-anatomy.md +0 -200
- package/docs/technical-direction/harness-config.md +0 -177
- package/docs/technical-direction/migration-execution-labels.md +0 -177
- package/docs/user-preferences.md +0 -17
- package/extensions/agent-guardrails/tsconfig.json +0 -12
- package/extensions/agent-modes/tsconfig.json +0 -12
- package/extensions/output-style/tsconfig.json +0 -12
- package/extensions/session-start/tsconfig.json +0 -12
- package/extensions/skill-stats/pnpm-lock.yaml +0 -329
- package/extensions/skill-stats/tsconfig.json +0 -12
- package/extensions/subagent/agents/planner.md +0 -37
- package/extensions/subagent/agents/reviewer.md +0 -35
- package/extensions/subagent/agents/scout.md +0 -50
- package/extensions/subagent/agents/worker.md +0 -24
- package/extensions/subagent/prompts/implement-and-review.md +0 -10
- package/extensions/subagent/prompts/implement.md +0 -10
- package/extensions/subagent/prompts/scout-and-plan.md +0 -9
- package/extensions/subagent/tsconfig.json +0 -12
- package/pi/keybindings.json +0 -3
- package/pi/settings.json +0 -34
- package/pnpm-workspace.yaml +0 -14
- package/skills/planning/create-task/scripts/package.json +0 -14
- package/skills/planning/create-task/scripts/pnpm-lock.yaml +0 -342
- package/skills/planning/create-task/scripts/tsconfig.json +0 -12
- package/skills/planning/record-idea/scripts/new-idea.sh +0 -89
- package/skills/productivity/validate-agent-dir/scripts/package.json +0 -15
- package/skills/productivity/validate-agent-dir/scripts/pnpm-lock.yaml +0 -329
- package/skills/productivity/validate-agent-dir/scripts/pnpm-workspace.yaml +0 -2
- package/skills/productivity/validate-agent-dir/scripts/tsconfig.json +0 -19
- package/skills/productivity/validate-agent-dir/scripts/validate-agent-dir.test.ts +0 -317
- package/skills/workflow/run-pipeline-stage/REFERENCE.md +0 -22
- package/skills/workflow/run-pipeline-stage/scripts/generate-progress.test.ts +0 -682
- package/skills/workflow/run-pipeline-stage/scripts/generate-progress.ts +0 -588
- package/skills/workflow/run-pipeline-stage/scripts/package.json +0 -15
- package/skills/workflow/run-pipeline-stage/scripts/pnpm-lock.yaml +0 -342
- package/skills/workflow/run-pipeline-stage/scripts/pnpm-workspace.yaml +0 -2
- package/skills/workflow/run-pipeline-stage/scripts/tsconfig.json +0 -12
- package/test/classify-bead.test.mjs +0 -277
- package/test/claude-installer.test.mjs +0 -308
- package/test/cli.test.mjs +0 -314
- package/test/create-chores.test.mjs +0 -85
- package/test/fake-bd.test.mjs +0 -72
- package/test/fs.test.mjs +0 -76
- package/test/helpers/fake-bd.mjs +0 -87
- package/test/helpers/fake-claude.mjs +0 -152
- package/test/helpers/fake-pi.mjs +0 -132
- package/test/installer.test.mjs +0 -806
- package/test/notify-hook.test.mjs +0 -63
- package/test/pending-gates.test.mjs +0 -147
- package/test/pi-installer.test.mjs +0 -785
- package/test/pi-register.test.mjs +0 -342
- package/test/pi-sweep.test.mjs +0 -619
- package/test/release-gates.test.mjs +0 -190
- package/test/skill-frontmatter.test.mjs +0 -137
- package/test/verify-worker-tree.test.mjs +0 -120
|
@@ -1,85 +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
|
-
|
|
9
|
-
import { makeFakeBd } from "./helpers/fake-bd.mjs";
|
|
10
|
-
|
|
11
|
-
const testDir = dirname(fileURLToPath(import.meta.url));
|
|
12
|
-
const CREATE_CHORES = join(testDir, "..", "skills", "planning", "create-task", "scripts", "create-chores.ts");
|
|
13
|
-
|
|
14
|
-
/** Run create-chores.ts against the fake `bd` with a parent that already
|
|
15
|
-
* carries implementation-type:afk — the label `bd create --parent` inherits.
|
|
16
|
-
* @param {string[]} parentLabels Labels seeded on the parent bead
|
|
17
|
-
* @returns {Promise<{ ids: Record<string, string>, beads: Array<{ id: string, title: string, labels: string[] }> }>} */
|
|
18
|
-
async function createChoresAgainstFakeBd(parentLabels) {
|
|
19
|
-
const dir = await mkdtemp(join(tmpdir(), "create-chores-"));
|
|
20
|
-
const { bin, state } = await makeFakeBd(dir, {
|
|
21
|
-
beads: [{ id: "parent-1", title: "Feature", labels: parentLabels }],
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
try {
|
|
25
|
-
const { exitCode, stdout, stderr } = await runScript(
|
|
26
|
-
["--parent", "parent-1"],
|
|
27
|
-
{ BD_PATH: bin, FAKE_BD_STATE: state },
|
|
28
|
-
);
|
|
29
|
-
assert.equal(exitCode, 0, stderr);
|
|
30
|
-
|
|
31
|
-
const ids = JSON.parse(stdout);
|
|
32
|
-
const { beads } = JSON.parse(await readFile(state, "utf8"));
|
|
33
|
-
return { ids, beads };
|
|
34
|
-
} finally {
|
|
35
|
-
await rm(dir, { recursive: true, force: true });
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** Run create-chores.ts with the given env overrides. */
|
|
40
|
-
function runScript(args, env) {
|
|
41
|
-
return new Promise((resolve) => {
|
|
42
|
-
execFile(
|
|
43
|
-
process.execPath,
|
|
44
|
-
[CREATE_CHORES, ...args],
|
|
45
|
-
{ env: { ...process.env, ...env } },
|
|
46
|
-
(error, stdout, stderr) => {
|
|
47
|
-
resolve({ exitCode: error ? error.code ?? 1 : 0, stdout, stderr });
|
|
48
|
-
},
|
|
49
|
-
);
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
describe("create-chores", () => {
|
|
54
|
-
it("gives the PR gate exactly its declared labels, without the parent's inherited implementation-type:afk", async () => {
|
|
55
|
-
const { ids, beads } = await createChoresAgainstFakeBd(["implementation-type:afk"]);
|
|
56
|
-
|
|
57
|
-
const gate = beads.find((b) => b.id === ids.featurePrReview);
|
|
58
|
-
assert.deepEqual(
|
|
59
|
-
[...gate.labels].sort(),
|
|
60
|
-
["implementation-type:hitl", "lifecycle:feature-pr"],
|
|
61
|
-
);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it("still lets stage chores inherit implementation-type:afk from the parent", async () => {
|
|
65
|
-
const { ids, beads } = await createChoresAgainstFakeBd(["implementation-type:afk"]);
|
|
66
|
-
|
|
67
|
-
const codeChore = beads.find((b) => b.id === ids.code);
|
|
68
|
-
assert.deepEqual(
|
|
69
|
-
[...codeChore.labels].sort(),
|
|
70
|
-
["implementation-type:afk", "stage:code"],
|
|
71
|
-
);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it("validates the PR gate task but not the stage chores", async () => {
|
|
75
|
-
const { ids, beads } = await createChoresAgainstFakeBd(["implementation-type:afk"]);
|
|
76
|
-
|
|
77
|
-
const gate = beads.find((b) => b.id === ids.featurePrReview);
|
|
78
|
-
assert.equal(gate.type, "task");
|
|
79
|
-
assert.equal(gate.validated, true);
|
|
80
|
-
assert.match(gate.description, /## Acceptance Criteria/);
|
|
81
|
-
|
|
82
|
-
const codeChore = beads.find((b) => b.id === ids.code);
|
|
83
|
-
assert.equal(codeChore.validated, false);
|
|
84
|
-
});
|
|
85
|
-
});
|
package/test/fake-bd.test.mjs
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
// Tests for the stateful fake `bd` helper's `--validate` model.
|
|
2
|
-
// The fake mirrors `bd create --validate`: a substantive bead whose description lacks the
|
|
3
|
-
// required section(s) for its type is rejected; chores have no requirements.
|
|
4
|
-
|
|
5
|
-
import { describe, it } from "node:test";
|
|
6
|
-
import assert from "node:assert/strict";
|
|
7
|
-
import { execFile } from "node:child_process";
|
|
8
|
-
import { mkdtemp, rm } from "node:fs/promises";
|
|
9
|
-
import { tmpdir } from "node:os";
|
|
10
|
-
import { join } from "node:path";
|
|
11
|
-
|
|
12
|
-
import { makeFakeBd } from "./helpers/fake-bd.mjs";
|
|
13
|
-
|
|
14
|
-
/** Run the fake `bd` binary with the given args. */
|
|
15
|
-
function runFake(bin, state, args) {
|
|
16
|
-
return new Promise((resolve) => {
|
|
17
|
-
execFile(bin, args, { env: { ...process.env, FAKE_BD_STATE: state } }, (error, stdout, stderr) => {
|
|
18
|
-
resolve({ exitCode: error ? error.code ?? 1 : 0, stdout, stderr });
|
|
19
|
-
});
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/** Create a fresh fake workspace, run `fn`, then remove it. */
|
|
24
|
-
async function withFake(fn) {
|
|
25
|
-
const dir = await mkdtemp(join(tmpdir(), "fake-bd-"));
|
|
26
|
-
try {
|
|
27
|
-
const { bin, state } = await makeFakeBd(dir, { beads: [] });
|
|
28
|
-
await fn(bin, state);
|
|
29
|
-
} finally {
|
|
30
|
-
await rm(dir, { recursive: true, force: true });
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
describe("fake bd --validate", () => {
|
|
35
|
-
it("rejects a section-less task", async () => {
|
|
36
|
-
await withFake(async (bin, state) => {
|
|
37
|
-
const { exitCode, stderr } = await runFake(bin, state, ["create", "empty", "--type", "task", "--validate"]);
|
|
38
|
-
assert.equal(exitCode, 1);
|
|
39
|
-
assert.match(stderr, /Acceptance Criteria/);
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it("accepts a task carrying ## Acceptance Criteria (case-insensitive)", async () => {
|
|
44
|
-
await withFake(async (bin, state) => {
|
|
45
|
-
const { exitCode } = await runFake(bin, state, [
|
|
46
|
-
"create", "ok", "--type", "task",
|
|
47
|
-
"--description", "## Acceptance criteria\n- [ ] done",
|
|
48
|
-
"--validate",
|
|
49
|
-
]);
|
|
50
|
-
assert.equal(exitCode, 0);
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it("requires ## Success Criteria for an epic", async () => {
|
|
55
|
-
await withFake(async (bin, state) => {
|
|
56
|
-
const { exitCode, stderr } = await runFake(bin, state, [
|
|
57
|
-
"create", "ep", "--type", "epic",
|
|
58
|
-
"--description", "## Acceptance Criteria\n- [ ] x",
|
|
59
|
-
"--validate",
|
|
60
|
-
]);
|
|
61
|
-
assert.equal(exitCode, 1);
|
|
62
|
-
assert.match(stderr, /Success Criteria/);
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it("does not require sections for a chore", async () => {
|
|
67
|
-
await withFake(async (bin, state) => {
|
|
68
|
-
const { exitCode } = await runFake(bin, state, ["create", "c", "--type", "chore", "--validate"]);
|
|
69
|
-
assert.equal(exitCode, 0);
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
});
|
package/test/fs.test.mjs
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
// Unit tests for scripts/lib/fs.mjs — the shared async stat wrappers. The
|
|
2
|
-
// helpers are exercised through their public API only; each case creates its
|
|
3
|
-
// own throwaway fixture under os.tmpdir().
|
|
4
|
-
|
|
5
|
-
import { describe, it } from "node:test";
|
|
6
|
-
import assert from "node:assert/strict";
|
|
7
|
-
import { mkdtemp, writeFile, rm } from "node:fs/promises";
|
|
8
|
-
import { tmpdir } from "node:os";
|
|
9
|
-
import { join } from "node:path";
|
|
10
|
-
import { isDirectory, isFile } from "../scripts/lib/fs.mjs";
|
|
11
|
-
|
|
12
|
-
async function makeTmp() {
|
|
13
|
-
return await mkdtemp(join(tmpdir(), "agent-cortex-fs-"));
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
describe("isFile", () => {
|
|
17
|
-
it("is true for an existing regular file", async () => {
|
|
18
|
-
const dir = await makeTmp();
|
|
19
|
-
try {
|
|
20
|
-
const file = join(dir, "file.txt");
|
|
21
|
-
await writeFile(file, "hello");
|
|
22
|
-
assert.equal(await isFile(file), true);
|
|
23
|
-
} finally {
|
|
24
|
-
await rm(dir, { recursive: true, force: true });
|
|
25
|
-
}
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it("is false for a directory", async () => {
|
|
29
|
-
const dir = await makeTmp();
|
|
30
|
-
try {
|
|
31
|
-
assert.equal(await isFile(dir), false);
|
|
32
|
-
} finally {
|
|
33
|
-
await rm(dir, { recursive: true, force: true });
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it("is false for a missing path", async () => {
|
|
38
|
-
const dir = await makeTmp();
|
|
39
|
-
try {
|
|
40
|
-
assert.equal(await isFile(join(dir, "nope.txt")), false);
|
|
41
|
-
} finally {
|
|
42
|
-
await rm(dir, { recursive: true, force: true });
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
describe("isDirectory", () => {
|
|
48
|
-
it("is true for an existing directory", async () => {
|
|
49
|
-
const dir = await makeTmp();
|
|
50
|
-
try {
|
|
51
|
-
assert.equal(await isDirectory(dir), true);
|
|
52
|
-
} finally {
|
|
53
|
-
await rm(dir, { recursive: true, force: true });
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it("is false for a regular file", async () => {
|
|
58
|
-
const dir = await makeTmp();
|
|
59
|
-
try {
|
|
60
|
-
const file = join(dir, "file.txt");
|
|
61
|
-
await writeFile(file, "hello");
|
|
62
|
-
assert.equal(await isDirectory(file), false);
|
|
63
|
-
} finally {
|
|
64
|
-
await rm(dir, { recursive: true, force: true });
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it("is false for a missing path", async () => {
|
|
69
|
-
const dir = await makeTmp();
|
|
70
|
-
try {
|
|
71
|
-
assert.equal(await isDirectory(join(dir, "nope")), false);
|
|
72
|
-
} finally {
|
|
73
|
-
await rm(dir, { recursive: true, force: true });
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
|
-
});
|
package/test/helpers/fake-bd.mjs
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
// Shared test helper: a stateful fake `bd` binary for create-chores.ts.
|
|
2
|
-
// `bd` is a genuine external system (its real database must never be touched
|
|
3
|
-
// by tests), so the script is driven against this fake via the BD_PATH
|
|
4
|
-
// override. The fake reproduces the real behaviours the tests depend on —
|
|
5
|
-
// `bd create --parent` label inheritance (unless `--no-inherit-labels` is
|
|
6
|
-
// passed) and `bd create --validate` section enforcement — and records every
|
|
7
|
-
// created bead to a JSON state file the test can read back.
|
|
8
|
-
//
|
|
9
|
-
// State lives in `state.json` next to the binary; the test seeds it with the
|
|
10
|
-
// parent bead and reads the children back after the script runs. Written async
|
|
11
|
-
// throughout — the repo's check-no-sync lint scans test/ (including helpers).
|
|
12
|
-
|
|
13
|
-
import { chmod, writeFile } from "node:fs/promises";
|
|
14
|
-
import { join } from "node:path";
|
|
15
|
-
|
|
16
|
-
const FAKE_BD_SCRIPT = [
|
|
17
|
-
"#!/usr/bin/env node",
|
|
18
|
-
"// Stateful fake for `bd create` (test/helpers/fake-bd.mjs).",
|
|
19
|
-
"const fs = require('node:fs/promises');",
|
|
20
|
-
"",
|
|
21
|
-
"const stateFile = process.env.FAKE_BD_STATE;",
|
|
22
|
-
"const VALUE_FLAGS = new Set(['--type', '--description', '--priority', '--labels', '--parent']);",
|
|
23
|
-
"const BOOL_FLAGS = new Set(['--silent', '--no-inherit-labels', '--validate']);",
|
|
24
|
-
"",
|
|
25
|
-
"(async () => {",
|
|
26
|
-
" const argv = process.argv.slice(2);",
|
|
27
|
-
" if (argv[0] === 'dep') process.exit(0);",
|
|
28
|
-
" if (argv[0] !== 'create') process.exit(1);",
|
|
29
|
-
"",
|
|
30
|
-
" const title = argv[1];",
|
|
31
|
-
" const flags = {};",
|
|
32
|
-
" const bools = new Set();",
|
|
33
|
-
" for (let i = 2; i < argv.length; i++) {",
|
|
34
|
-
" const arg = argv[i];",
|
|
35
|
-
" if (BOOL_FLAGS.has(arg)) { bools.add(arg); continue; }",
|
|
36
|
-
" if (VALUE_FLAGS.has(arg)) { flags[arg] = argv[++i]; continue; }",
|
|
37
|
-
" }",
|
|
38
|
-
"",
|
|
39
|
-
" const state = JSON.parse(await fs.readFile(stateFile, 'utf8'));",
|
|
40
|
-
"",
|
|
41
|
-
" // Model `bd create --validate`: reject a substantive bead whose description lacks the",
|
|
42
|
-
" // required section(s) for its type. Chores have no requirements and always pass.",
|
|
43
|
-
" if (bools.has('--validate')) {",
|
|
44
|
-
" const type = flags['--type'] || 'task';",
|
|
45
|
-
" const required = {",
|
|
46
|
-
" task: ['## Acceptance Criteria'],",
|
|
47
|
-
" feature: ['## Acceptance Criteria'],",
|
|
48
|
-
" bug: ['## Steps to Reproduce', '## Acceptance Criteria'],",
|
|
49
|
-
" epic: ['## Success Criteria'],",
|
|
50
|
-
" chore: [],",
|
|
51
|
-
" }[type] || ['## Acceptance Criteria'];",
|
|
52
|
-
" const description = flags['--description'] || '';",
|
|
53
|
-
" const missing = required.filter((section) => !description.toLowerCase().includes(section.toLowerCase()));",
|
|
54
|
-
" if (missing.length > 0) {",
|
|
55
|
-
" console.error('missing required sections for ' + type + ': ' + missing.join(', '));",
|
|
56
|
-
" process.exit(1);",
|
|
57
|
-
" }",
|
|
58
|
-
" }",
|
|
59
|
-
"",
|
|
60
|
-
" const explicit = (flags['--labels'] || '').split(',').filter(Boolean);",
|
|
61
|
-
" const parent = state.beads.find((b) => b.id === flags['--parent']);",
|
|
62
|
-
" const inherited = parent && !bools.has('--no-inherit-labels') ? parent.labels : [];",
|
|
63
|
-
" const labels = [...new Set([...inherited, ...explicit])];",
|
|
64
|
-
"",
|
|
65
|
-
" const id = 'bd-' + state.nextId++;",
|
|
66
|
-
" state.beads.push({ id, title, labels, parent: flags['--parent'] ?? null, type: flags['--type'] || 'task', description: flags['--description'] || '', validated: bools.has('--validate') });",
|
|
67
|
-
" await fs.writeFile(stateFile, JSON.stringify(state));",
|
|
68
|
-
" process.stdout.write(id + '\\n');",
|
|
69
|
-
"})().catch((err) => {",
|
|
70
|
-
" console.error(err);",
|
|
71
|
-
" process.exit(1);",
|
|
72
|
-
"});",
|
|
73
|
-
].join("\n");
|
|
74
|
-
|
|
75
|
-
/** Create a stateful fake `bd` binary in `dir`.
|
|
76
|
-
* @param {string} dir Directory to hold the binary and its state.json
|
|
77
|
-
* @param {{ beads: Array<{ id: string, title: string, labels: string[] }> }} seed
|
|
78
|
-
* Pre-existing beads (the parent) the fake inherits labels from
|
|
79
|
-
* @returns {Promise<{ bin: string, state: string }>} */
|
|
80
|
-
export async function makeFakeBd(dir, { beads }) {
|
|
81
|
-
const bin = join(dir, "bd");
|
|
82
|
-
const state = join(dir, "state.json");
|
|
83
|
-
await writeFile(bin, FAKE_BD_SCRIPT + "\n");
|
|
84
|
-
await chmod(bin, 0o755);
|
|
85
|
-
await writeFile(state, JSON.stringify({ beads, nextId: 1 }));
|
|
86
|
-
return { bin, state };
|
|
87
|
-
}
|
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
// Shared test helper: a stateful fake `claude` binary for the `claude plugin`
|
|
2
|
-
// CLI. The real CLI is a genuine external system (and its home config must
|
|
3
|
-
// never be touched by tests), so tests drive this fake instead. It records
|
|
4
|
-
// every invocation to `calls.log` (argv, space-joined, one line each) and
|
|
5
|
-
// simulates just enough real behaviour for idempotency assertions:
|
|
6
|
-
//
|
|
7
|
-
// plugin --help → exit 0 (availability probe)
|
|
8
|
-
// plugin marketplace list --json → registered marketplaces as JSON
|
|
9
|
-
// plugin marketplace add <root> → registers <root> (name read from its
|
|
10
|
-
// .claude-plugin/marketplace.json)
|
|
11
|
-
// plugin marketplace update <m> → exit 0
|
|
12
|
-
// plugin list --json → installed plugins as JSON
|
|
13
|
-
// plugin install <id> -y → records id at the version published by
|
|
14
|
-
// the marketplace source's plugin.json
|
|
15
|
-
// plugin update <name> -y → re-reads the version from the source
|
|
16
|
-
//
|
|
17
|
-
// State lives in `state.json` next to the binary, so a test can re-run the CLI
|
|
18
|
-
// against the same fake and observe the update path. `failWhen` makes any
|
|
19
|
-
// invocation whose argv contains the substring exit 1 (probe/command failures).
|
|
20
|
-
//
|
|
21
|
-
// Written async throughout (the fake is a child process and the helper's own
|
|
22
|
-
// I/O is test code) — the repo's check-no-sync lint scans test/ for *Sync.
|
|
23
|
-
|
|
24
|
-
import { chmod, writeFile } from "node:fs/promises";
|
|
25
|
-
import { join } from "node:path";
|
|
26
|
-
|
|
27
|
-
const FAKE_CLAUDE_SCRIPT = [
|
|
28
|
-
"#!/usr/bin/env node",
|
|
29
|
-
"// Stateful fake for the `claude plugin` CLI (test/helpers/fake-claude.mjs).",
|
|
30
|
-
"const fs = require('node:fs/promises');",
|
|
31
|
-
"const path = require('node:path');",
|
|
32
|
-
"",
|
|
33
|
-
"const dir = __dirname;",
|
|
34
|
-
"const stateFile = path.join(dir, 'state.json');",
|
|
35
|
-
"const logFile = path.join(dir, 'calls.log');",
|
|
36
|
-
"const failWhenFile = path.join(dir, 'failWhen.txt');",
|
|
37
|
-
"",
|
|
38
|
-
"(async () => {",
|
|
39
|
-
" const argv = process.argv.slice(2);",
|
|
40
|
-
" const joined = argv.join(' ');",
|
|
41
|
-
" await fs.appendFile(logFile, joined + '\\n');",
|
|
42
|
-
"",
|
|
43
|
-
" const failWhen = await fs.readFile(failWhenFile, 'utf8').catch(() => null);",
|
|
44
|
-
" if (failWhen && joined.includes(failWhen)) process.exit(1);",
|
|
45
|
-
"",
|
|
46
|
-
" if (argv[0] !== 'plugin') process.exit(0);",
|
|
47
|
-
"",
|
|
48
|
-
" const readState = async () => {",
|
|
49
|
-
" try {",
|
|
50
|
-
" return JSON.parse(await fs.readFile(stateFile, 'utf8'));",
|
|
51
|
-
" } catch {",
|
|
52
|
-
" return { markets: [], installed: [] };",
|
|
53
|
-
" }",
|
|
54
|
-
" };",
|
|
55
|
-
" const writeState = (state) => fs.writeFile(stateFile, JSON.stringify(state));",
|
|
56
|
-
"",
|
|
57
|
-
" // Real claude resolves the plugin version from the marketplace source's",
|
|
58
|
-
" // plugin.json; the fake mirrors that by reading <source>/claude/.claude-plugin/plugin.json.",
|
|
59
|
-
" const pluginJsonVersion = async (marketPath) => {",
|
|
60
|
-
" try {",
|
|
61
|
-
" return JSON.parse(await fs.readFile(path.join(marketPath, 'claude', '.claude-plugin', 'plugin.json'), 'utf8')).version;",
|
|
62
|
-
" } catch {",
|
|
63
|
-
" return undefined;",
|
|
64
|
-
" }",
|
|
65
|
-
" };",
|
|
66
|
-
" const marketplaceName = async (source) => {",
|
|
67
|
-
" try {",
|
|
68
|
-
" return JSON.parse(await fs.readFile(path.join(source, '.claude-plugin', 'marketplace.json'), 'utf8')).name;",
|
|
69
|
-
" } catch {",
|
|
70
|
-
" return undefined;",
|
|
71
|
-
" }",
|
|
72
|
-
" };",
|
|
73
|
-
"",
|
|
74
|
-
" const state = await readState();",
|
|
75
|
-
" const [sub, ...rest] = argv.slice(1);",
|
|
76
|
-
"",
|
|
77
|
-
" if (sub === '--help') process.exit(0);",
|
|
78
|
-
"",
|
|
79
|
-
" if (sub === 'marketplace') {",
|
|
80
|
-
" const [op, ...opArgs] = rest;",
|
|
81
|
-
" if (op === 'list') {",
|
|
82
|
-
" console.log(JSON.stringify(state.markets));",
|
|
83
|
-
" process.exit(0);",
|
|
84
|
-
" }",
|
|
85
|
-
" if (op === 'add') {",
|
|
86
|
-
" const source = path.resolve(opArgs[0]);",
|
|
87
|
-
" const name = (await marketplaceName(source)) ?? 'market-' + state.markets.length;",
|
|
88
|
-
" if (!state.markets.some((m) => m.name === name)) state.markets.push({ name, path: source });",
|
|
89
|
-
" await writeState(state);",
|
|
90
|
-
" process.exit(0);",
|
|
91
|
-
" }",
|
|
92
|
-
" if (op === 'update') process.exit(0);",
|
|
93
|
-
" process.exit(0);",
|
|
94
|
-
" }",
|
|
95
|
-
"",
|
|
96
|
-
" if (sub === 'list') {",
|
|
97
|
-
" console.log(JSON.stringify(state.installed));",
|
|
98
|
-
" process.exit(0);",
|
|
99
|
-
" }",
|
|
100
|
-
"",
|
|
101
|
-
" if (sub === 'install') {",
|
|
102
|
-
" const id = rest[0];",
|
|
103
|
-
" if (!state.installed.some((i) => i.id === id)) {",
|
|
104
|
-
" const [name, market] = id.split('@');",
|
|
105
|
-
" const m = state.markets.find((x) => x.name === market);",
|
|
106
|
-
" state.installed.push({ id, version: m ? (await pluginJsonVersion(m.path)) ?? '0.0.0' : '0.0.0' });",
|
|
107
|
-
" await writeState(state);",
|
|
108
|
-
" }",
|
|
109
|
-
" process.exit(0);",
|
|
110
|
-
" }",
|
|
111
|
-
"",
|
|
112
|
-
" if (sub === 'update') {",
|
|
113
|
-
" const name = rest[0];",
|
|
114
|
-
" const entry = state.installed.find((i) => i.id.startsWith(name + '@'));",
|
|
115
|
-
" if (entry) {",
|
|
116
|
-
" const m = state.markets.find((x) => x.name === entry.id.split('@')[1]);",
|
|
117
|
-
" const v = m ? await pluginJsonVersion(m.path) : undefined;",
|
|
118
|
-
" if (v) entry.version = v;",
|
|
119
|
-
" await writeState(state);",
|
|
120
|
-
" }",
|
|
121
|
-
" process.exit(0);",
|
|
122
|
-
" }",
|
|
123
|
-
"",
|
|
124
|
-
" process.exit(0);",
|
|
125
|
-
"})().catch((err) => {",
|
|
126
|
-
" console.error(err);",
|
|
127
|
-
" process.exit(1);",
|
|
128
|
-
"});",
|
|
129
|
-
].join("\n");
|
|
130
|
-
|
|
131
|
-
/** Create a stateful fake `claude` binary in `dir`.
|
|
132
|
-
* @param {string} dir Directory to hold bin, calls.log, state.json
|
|
133
|
-
* @param {{ failWhen?: string|null }} [options] Invocations whose argv contains
|
|
134
|
-
* failWhen exit 1
|
|
135
|
-
* @returns {Promise<{ bin: string, log: string, state: string }>} */
|
|
136
|
-
export async function makeFakeClaude(dir, { failWhen = null } = {}) {
|
|
137
|
-
const bin = join(dir, "claude");
|
|
138
|
-
const log = join(dir, "calls.log");
|
|
139
|
-
const state = join(dir, "state.json");
|
|
140
|
-
await writeFile(bin, FAKE_CLAUDE_SCRIPT + "\n");
|
|
141
|
-
await chmod(bin, 0o755);
|
|
142
|
-
await writeFile(state, JSON.stringify({ markets: [], installed: [] }));
|
|
143
|
-
if (failWhen) await writeFile(join(dir, "failWhen.txt"), failWhen);
|
|
144
|
-
return { bin, log, state };
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/** Registration actions from a fake call log: probes and state queries dropped.
|
|
148
|
-
* @param {string[]} lines Raw call-log lines
|
|
149
|
-
* @returns {string[]} Action lines (marketplace add/update, install, update) */
|
|
150
|
-
export function registrationActions(lines) {
|
|
151
|
-
return lines.filter((c) => !c.startsWith("plugin --help") && !c.includes("--json"));
|
|
152
|
-
}
|
package/test/helpers/fake-pi.mjs
DELETED
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
// Shared test helper: a stateful fake `pi` binary for pi's package commands.
|
|
2
|
-
// The real CLI is a genuine external system (and its user settings must never
|
|
3
|
-
// be touched by tests), so tests drive this fake instead. It records every
|
|
4
|
-
// invocation to `calls.log` (argv, space-joined, one line each) and simulates
|
|
5
|
-
// just enough real behaviour to assert registration state:
|
|
6
|
-
//
|
|
7
|
-
// --version → exit 0 (availability probe)
|
|
8
|
-
// list → configured package sources
|
|
9
|
-
// install <source> → adds <source> to settings.json `packages`, persisted
|
|
10
|
-
// relative to the settings file's directory (what real
|
|
11
|
-
// pi does) unless an entry already resolves to it
|
|
12
|
-
// remove <source> → drops the matching entry; exit 1 when none matches
|
|
13
|
-
//
|
|
14
|
-
// Local sources are resolved the way pi resolves them: relative to the
|
|
15
|
-
// directory holding settings.json. `failWhen` makes any invocation whose argv
|
|
16
|
-
// contains the substring exit 1 (command failures).
|
|
17
|
-
//
|
|
18
|
-
// Written async throughout (the fake is a child process and the helper's own
|
|
19
|
-
// I/O is test code) — the repo's check-no-sync lint scans test/ for *Sync.
|
|
20
|
-
|
|
21
|
-
import { chmod, writeFile } from "node:fs/promises";
|
|
22
|
-
import { join } from "node:path";
|
|
23
|
-
|
|
24
|
-
const FAKE_PI_SCRIPT = [
|
|
25
|
-
"#!/usr/bin/env node",
|
|
26
|
-
"// Stateful fake for the `pi` package CLI (test/helpers/fake-pi.mjs).",
|
|
27
|
-
"const fs = require('node:fs/promises');",
|
|
28
|
-
"const path = require('node:path');",
|
|
29
|
-
"const os = require('node:os');",
|
|
30
|
-
"",
|
|
31
|
-
"const dir = __dirname;",
|
|
32
|
-
"const settingsFile = path.join(dir, 'settings.json');",
|
|
33
|
-
"const logFile = path.join(dir, 'calls.log');",
|
|
34
|
-
"const failWhenFile = path.join(dir, 'failWhen.txt');",
|
|
35
|
-
"",
|
|
36
|
-
"const expand = (p) => (p === '~' ? os.homedir() : p.startsWith('~/') ? path.join(os.homedir(), p.slice(2)) : p);",
|
|
37
|
-
"const sourceOf = (entry) => (typeof entry === 'string' ? entry : entry && entry.source);",
|
|
38
|
-
"// pi resolves a configured local source against the settings file's directory.",
|
|
39
|
-
"const resolvedSource = (entry) => path.resolve(dir, expand(sourceOf(entry)));",
|
|
40
|
-
"const resolvedArgument = (arg) => path.resolve(dir, expand(arg));",
|
|
41
|
-
"",
|
|
42
|
-
"(async () => {",
|
|
43
|
-
" const argv = process.argv.slice(2);",
|
|
44
|
-
" const joined = argv.join(' ');",
|
|
45
|
-
" await fs.appendFile(logFile, joined + '\\n');",
|
|
46
|
-
"",
|
|
47
|
-
" const failWhen = await fs.readFile(failWhenFile, 'utf8').catch(() => null);",
|
|
48
|
-
" if (failWhen && joined.includes(failWhen)) process.exit(1);",
|
|
49
|
-
"",
|
|
50
|
-
" if (argv[0] === '--version' || argv[0] === '-v') {",
|
|
51
|
-
" console.log('1.4.0');",
|
|
52
|
-
" process.exit(0);",
|
|
53
|
-
" }",
|
|
54
|
-
"",
|
|
55
|
-
" const readSettings = async () => {",
|
|
56
|
-
" try {",
|
|
57
|
-
" return JSON.parse(await fs.readFile(settingsFile, 'utf8'));",
|
|
58
|
-
" } catch {",
|
|
59
|
-
" return {};",
|
|
60
|
-
" }",
|
|
61
|
-
" };",
|
|
62
|
-
" const writePackages = async (settings, packages) =>",
|
|
63
|
-
" fs.writeFile(settingsFile, JSON.stringify({ ...settings, packages }, null, 2) + '\\n');",
|
|
64
|
-
"",
|
|
65
|
-
" const [command, argument] = argv;",
|
|
66
|
-
" const settings = await readSettings();",
|
|
67
|
-
" const packages = settings.packages ?? [];",
|
|
68
|
-
"",
|
|
69
|
-
" if (command === 'list') {",
|
|
70
|
-
" if (packages.length === 0) {",
|
|
71
|
-
" console.log('No packages installed.');",
|
|
72
|
-
" process.exit(0);",
|
|
73
|
-
" }",
|
|
74
|
-
" console.log('User packages:');",
|
|
75
|
-
" for (const entry of packages) console.log(' ' + sourceOf(entry));",
|
|
76
|
-
" process.exit(0);",
|
|
77
|
-
" }",
|
|
78
|
-
"",
|
|
79
|
-
" if (command === 'install') {",
|
|
80
|
-
" const target = resolvedArgument(argument);",
|
|
81
|
-
" const persisted = path.relative(dir, target) || '.';",
|
|
82
|
-
" const index = packages.findIndex((entry) => resolvedSource(entry) === target);",
|
|
83
|
-
" if (index === -1) {",
|
|
84
|
-
" packages.push(persisted);",
|
|
85
|
-
" } else if (typeof packages[index] === 'string') {",
|
|
86
|
-
" packages[index] = persisted;",
|
|
87
|
-
" } else {",
|
|
88
|
-
" packages[index] = { ...packages[index], source: persisted };",
|
|
89
|
-
" }",
|
|
90
|
-
" await writePackages(settings, packages);",
|
|
91
|
-
" console.log('Installed ' + argument);",
|
|
92
|
-
" process.exit(0);",
|
|
93
|
-
" }",
|
|
94
|
-
"",
|
|
95
|
-
" if (command === 'remove') {",
|
|
96
|
-
" const target = resolvedArgument(argument);",
|
|
97
|
-
" const next = packages.filter((entry) => resolvedSource(entry) !== target);",
|
|
98
|
-
" if (next.length === packages.length) {",
|
|
99
|
-
" console.error('No matching package found for ' + argument);",
|
|
100
|
-
" process.exit(1);",
|
|
101
|
-
" }",
|
|
102
|
-
" await writePackages(settings, next);",
|
|
103
|
-
" console.log('Removed ' + argument);",
|
|
104
|
-
" process.exit(0);",
|
|
105
|
-
" }",
|
|
106
|
-
"",
|
|
107
|
-
" process.exit(0);",
|
|
108
|
-
"})().catch((err) => {",
|
|
109
|
-
" console.error(err);",
|
|
110
|
-
" process.exit(1);",
|
|
111
|
-
"});",
|
|
112
|
-
].join("\n");
|
|
113
|
-
|
|
114
|
-
/** Create a stateful fake `pi` binary in `dir`.
|
|
115
|
-
*
|
|
116
|
-
* The binary's own directory doubles as pi's settings directory, so a local
|
|
117
|
-
* package source is persisted relative to it exactly as real pi does.
|
|
118
|
-
* @param {string} dir Directory to hold bin, calls.log, settings.json
|
|
119
|
-
* @param {{ failWhen?: string|null, packages?: unknown[] }} [options]
|
|
120
|
-
* failWhen: invocations whose argv contains the substring exit 1;
|
|
121
|
-
* packages: initial `packages` array for settings.json
|
|
122
|
-
* @returns {Promise<{ bin: string, log: string, settingsPath: string }>} */
|
|
123
|
-
export async function makeFakePi(dir, { failWhen = null, packages = [] } = {}) {
|
|
124
|
-
const bin = join(dir, "pi");
|
|
125
|
-
const log = join(dir, "calls.log");
|
|
126
|
-
const settingsPath = join(dir, "settings.json");
|
|
127
|
-
await writeFile(bin, FAKE_PI_SCRIPT + "\n");
|
|
128
|
-
await chmod(bin, 0o755);
|
|
129
|
-
await writeFile(settingsPath, `${JSON.stringify({ packages }, null, 2)}\n`);
|
|
130
|
-
if (failWhen) await writeFile(join(dir, "failWhen.txt"), failWhen);
|
|
131
|
-
return { bin, log, settingsPath };
|
|
132
|
-
}
|