@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.
Files changed (91) hide show
  1. package/.agents/skills/style-versioning/SKILL.md +1 -1
  2. package/.github/workflows/ci.yml +47 -14
  3. package/AGENTS.md +35 -8
  4. package/CHANGELOG.md +60 -0
  5. package/README.md +174 -97
  6. package/agents/README.md +14 -11
  7. package/bin/agent-cortex.mjs +55 -10
  8. package/bin/installers/claude.mjs +674 -174
  9. package/bin/installers/copilot.mjs +13 -18
  10. package/bin/installers/pi.mjs +293 -51
  11. package/claude/settings.json +4 -0
  12. package/{claude → claude-extras}/scripts/statusline-command.sh +32 -4
  13. package/docs/claude-hooks.md +15 -3
  14. package/docs/ideas/ban-content-assertion-tests.md +73 -0
  15. package/docs/ideas/bead-progress-notes-for-session-resumption.md +75 -0
  16. package/docs/ideas/classify-beads-via-deterministic-script-with-agent-fallback.md +70 -0
  17. package/docs/ideas/ctx-alias-version-and-update-commands.md +65 -0
  18. package/docs/ideas/ctx-feedback-command-for-capturing-ideas-from-any-machine.md +67 -0
  19. package/docs/ideas/deployment-beads-for-post-merge-pipelines.md +36 -0
  20. package/docs/ideas/design-and-delivery-as-separate-bead-trees/design-and-delivery-as-separate-bead-trees.md +99 -0
  21. package/docs/ideas/enable-signed-commit-enforcement-changesets-bot-commits-must-be-signed.md +33 -0
  22. package/docs/ideas/extension-manifests.md +55 -14
  23. package/docs/ideas/improve-pi-system-prompt.md +5 -2
  24. package/docs/ideas/map-every-cortex-tool-to-a-real-pi-equivalent.md +95 -0
  25. package/docs/ideas/record-idea-implementation-questions.md +68 -0
  26. package/docs/ideas/self-flushing-session.md +72 -0
  27. package/docs/ideas/state-in-beads-not-md.md +69 -0
  28. package/docs/ideas/task-session-context.local.md +95 -0
  29. package/docs/ideas/test-data-creation-helpers.md +92 -0
  30. package/docs/ideas/test-planning-stage-bead-in-feature-planning.md +68 -0
  31. package/docs/ideas/worktree-housekeeping-for-closed-beads.md +39 -0
  32. package/docs/technical-direction/harness-config.md +161 -0
  33. package/docs/technical-direction/migration-execution-labels.md +60 -23
  34. package/extensions/agent-guardrails/README.md +45 -0
  35. package/extensions/agent-guardrails/guardrails.test.ts +42 -0
  36. package/extensions/agent-guardrails/guardrails.ts +66 -0
  37. package/extensions/agent-guardrails/index.ts +22 -0
  38. package/extensions/agent-guardrails/node_modules/.bin/pi +21 -0
  39. package/extensions/agent-guardrails/node_modules/.bin/tsc +21 -0
  40. package/extensions/agent-guardrails/node_modules/.bin/tsserver +21 -0
  41. package/extensions/agent-guardrails/node_modules/.bin/tsx +21 -0
  42. package/extensions/agent-guardrails/package.json +19 -0
  43. package/extensions/agent-guardrails/tsconfig.json +12 -0
  44. package/extensions/agent-modes/README.md +12 -5
  45. package/extensions/agent-modes/discover.test.ts +30 -7
  46. package/extensions/agent-modes/discover.ts +1 -1
  47. package/extensions/agent-modes/index.test.ts +208 -0
  48. package/extensions/agent-modes/index.ts +23 -6
  49. package/extensions/agent-modes/package.json +1 -1
  50. package/extensions/skill-stats/index.test.ts +3 -2
  51. package/lib/cli.mjs +30 -12
  52. package/lib/pi-packages.mjs +169 -0
  53. package/package.json +8 -4
  54. package/pi/settings.json +23 -12
  55. package/plugin.json +1 -1
  56. package/scripts/build-copilot-agents.mjs +1 -1
  57. package/scripts/check-no-sync.mjs +49 -0
  58. package/scripts/lib/compose-agent.mjs +32 -14
  59. package/scripts/lib/fs.mjs +31 -0
  60. package/skills/engineering/grill-with-docs/SKILL.md +1 -1
  61. package/skills/planning/classify-bead/REFERENCE.md +85 -0
  62. package/skills/planning/classify-bead/SKILL.md +32 -36
  63. package/skills/planning/classify-bead/scripts/classify-bead.mjs +292 -0
  64. package/skills/planning/create-task/SKILL.md +19 -10
  65. package/skills/planning/write-a-prd/SKILL.md +2 -2
  66. package/skills/style/style-code/REFERENCE.md +32 -2
  67. package/skills/style/style-code/SKILL.md +5 -21
  68. package/skills/style/style-tests/SKILL.md +69 -17
  69. package/skills/workflow/hitl-collab/SKILL.md +1 -1
  70. package/skills/workflow/run-pipeline-stage/SKILL.md +13 -1
  71. package/skills/workflow/run-pipeline-stage/prompts/stage-runner.md +12 -0
  72. package/test/classify-bead.test.mjs +277 -0
  73. package/test/claude-installer.test.mjs +199 -139
  74. package/test/cli.test.mjs +95 -41
  75. package/test/copilot-installer.test.mjs +92 -82
  76. package/test/fs.test.mjs +76 -0
  77. package/test/helpers/fake-claude.mjs +152 -0
  78. package/test/installer.test.mjs +542 -57
  79. package/test/pi-installer.test.mjs +457 -102
  80. package/test/pi-packages.test.mjs +295 -0
  81. package/token-map.README.md +23 -12
  82. package/token-map.json +8 -7
  83. package/.claude-plugin/marketplace.json +0 -16
  84. package/claude/.claude-plugin/plugin.json +0 -11
  85. package/claude/agents/plan.md +0 -58
  86. package/claude/agents/ralph-plan.md +0 -121
  87. package/claude/agents/ralph.md +0 -193
  88. package/claude/agents/strategy.md +0 -111
  89. package/claude/hooks/scripts/notify.mjs +0 -54
  90. package/claude/hooks.json +0 -38
  91. /package/{claude → claude-extras}/.mcp.json +0 -0
@@ -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
+ }