@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
package/test/fs.test.mjs
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,152 @@
|
|
|
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
|
+
}
|