@jaybeeuu/agent-cortex 1.39.0 → 1.40.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 (41) hide show
  1. package/.agents/skills/style-versioning/SKILL.md +1 -1
  2. package/.github/workflows/ci.yml +47 -14
  3. package/AGENTS.md +19 -5
  4. package/CHANGELOG.md +12 -0
  5. package/README.md +111 -74
  6. package/agents/README.md +12 -11
  7. package/bin/agent-cortex.mjs +23 -10
  8. package/bin/installers/claude.mjs +518 -168
  9. package/bin/installers/copilot.mjs +22 -12
  10. package/bin/installers/pi.mjs +37 -34
  11. package/docs/claude-hooks.md +3 -3
  12. package/extensions/agent-modes/README.md +3 -2
  13. package/extensions/agent-modes/discover.test.ts +30 -7
  14. package/extensions/agent-modes/discover.ts +1 -1
  15. package/extensions/skill-stats/index.test.ts +3 -2
  16. package/lib/cli.mjs +22 -10
  17. package/package.json +4 -4
  18. package/plugin.json +1 -1
  19. package/scripts/build-copilot-agents.mjs +1 -1
  20. package/scripts/check-no-sync.mjs +49 -0
  21. package/scripts/lib/compose-agent.mjs +32 -14
  22. package/skills/engineering/grill-with-docs/SKILL.md +1 -1
  23. package/skills/workflow/hitl-collab/SKILL.md +1 -1
  24. package/test/claude-installer.test.mjs +199 -139
  25. package/test/cli.test.mjs +40 -30
  26. package/test/copilot-installer.test.mjs +92 -82
  27. package/test/helpers/fake-claude.mjs +152 -0
  28. package/test/installer.test.mjs +309 -57
  29. package/test/pi-installer.test.mjs +115 -99
  30. package/token-map.README.md +21 -12
  31. package/token-map.json +7 -6
  32. package/.claude-plugin/marketplace.json +0 -16
  33. package/claude/.claude-plugin/plugin.json +0 -11
  34. package/claude/agents/plan.md +0 -58
  35. package/claude/agents/ralph-plan.md +0 -121
  36. package/claude/agents/ralph.md +0 -193
  37. package/claude/agents/strategy.md +0 -111
  38. package/claude/hooks/scripts/notify.mjs +0 -54
  39. package/claude/hooks.json +0 -38
  40. /package/{claude → claude-extras}/.mcp.json +0 -0
  41. /package/{claude → claude-extras}/scripts/statusline-command.sh +0 -0
package/test/cli.test.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { describe, it } from "node:test";
2
2
  import assert from "node:assert/strict";
3
3
  import { execFile } from "node:child_process";
4
- import { mkdtempSync, readFileSync, rmSync } from "node:fs";
4
+ import { mkdtemp, readFile, rm } from "node:fs/promises";
5
5
  import { tmpdir } from "node:os";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { dirname, join } from "node:path";
@@ -10,6 +10,16 @@ import { parseArgs, buildHelpText, validateHarness } from "../lib/cli.mjs";
10
10
  const __dirname = dirname(fileURLToPath(import.meta.url));
11
11
  const CLI_PATH = join(__dirname, "..", "bin", "agent-cortex.mjs");
12
12
 
13
+ /** True when a path exists (stat succeeds). */
14
+ async function pathExists(p) {
15
+ try {
16
+ await stat(p);
17
+ return true;
18
+ } catch {
19
+ return false;
20
+ }
21
+ }
22
+
13
23
  function runCli(args) {
14
24
  return new Promise((resolve) => {
15
25
  execFile(process.execPath, [CLI_PATH, ...args], (error, stdout, stderr) => {
@@ -23,91 +33,91 @@ function runCli(args) {
23
33
  }
24
34
 
25
35
  describe("parseArgs", () => {
26
- it("returns { command: 'help' } when no arguments given", () => {
36
+ it("returns { command: 'help' } when no arguments given", async () => {
27
37
  const result = parseArgs([]);
28
38
  assert.deepStrictEqual(result, { command: "help" });
29
39
  });
30
40
 
31
- it("returns { command: 'help' } for --help flag", () => {
41
+ it("returns { command: 'help' } for --help flag", async () => {
32
42
  const result = parseArgs(["--help"]);
33
43
  assert.deepStrictEqual(result, { command: "help" });
34
44
  });
35
45
 
36
- it("returns { command: 'help' } for -h flag", () => {
46
+ it("returns { command: 'help' } for -h flag", async () => {
37
47
  const result = parseArgs(["-h"]);
38
48
  assert.deepStrictEqual(result, { command: "help" });
39
49
  });
40
50
 
41
- it("returns { command: 'install', harness: 'copilot' } for install copilot", () => {
51
+ it("returns { command: 'install', harness: 'copilot' } for install copilot", async () => {
42
52
  const result = parseArgs(["install", "copilot"]);
43
53
  assert.deepStrictEqual(result, { command: "install", harness: "copilot" });
44
54
  });
45
55
 
46
- it("returns { command: 'install', harness: 'claude' } for install claude", () => {
56
+ it("returns { command: 'install', harness: 'claude' } for install claude", async () => {
47
57
  const result = parseArgs(["install", "claude"]);
48
58
  assert.deepStrictEqual(result, { command: "install", harness: "claude" });
49
59
  });
50
60
 
51
- it("returns { command: 'install', harness: 'pi' } for install pi", () => {
61
+ it("returns { command: 'install', harness: 'pi' } for install pi", async () => {
52
62
  const result = parseArgs(["install", "pi"]);
53
63
  assert.deepStrictEqual(result, { command: "install", harness: "pi" });
54
64
  });
55
65
 
56
- it("returns { command: 'install', harness: null } when install has no harness", () => {
66
+ it("returns { command: 'install', harness: null } when install has no harness", async () => {
57
67
  const result = parseArgs(["install"]);
58
68
  assert.deepStrictEqual(result, { command: "install", harness: null });
59
69
  });
60
70
 
61
- it("returns output for install claude --output <dir>", () => {
71
+ it("returns output for install claude --output <dir>", async () => {
62
72
  const result = parseArgs(["install", "claude", "--output", "/tmp/x"]);
63
73
  assert.deepStrictEqual(result, { command: "install", harness: "claude", output: "/tmp/x" });
64
74
  });
65
75
 
66
- it("returns output for install claude --output=<dir>", () => {
76
+ it("returns output for install claude --output=<dir>", async () => {
67
77
  const result = parseArgs(["install", "claude", "--output=/tmp/x"]);
68
78
  assert.deepStrictEqual(result, { command: "install", harness: "claude", output: "/tmp/x" });
69
79
  });
70
80
 
71
- it("returns dryRun for install claude --dry-run", () => {
81
+ it("returns dryRun for install claude --dry-run", async () => {
72
82
  const result = parseArgs(["install", "claude", "--dry-run"]);
73
83
  assert.deepStrictEqual(result, { command: "install", harness: "claude", dryRun: true });
74
84
  });
75
85
 
76
- it("combines --dry-run with --output", () => {
86
+ it("combines --dry-run with --output", async () => {
77
87
  const result = parseArgs(["install", "claude", "--output", "/tmp/x", "--dry-run"]);
78
88
  assert.deepStrictEqual(result, { command: "install", harness: "claude", output: "/tmp/x", dryRun: true });
79
89
  });
80
90
 
81
- it("reports an unknown option for claude", () => {
91
+ it("reports an unknown option for claude", async () => {
82
92
  const result = parseArgs(["install", "claude", "--bogus"]);
83
93
  assert.equal(result.command, "install");
84
94
  assert.equal(result.harness, "claude");
85
95
  assert.ok(result.optionError.includes("--bogus"));
86
96
  });
87
97
 
88
- it("reports a missing value for --output for claude", () => {
98
+ it("reports a missing value for --output for claude", async () => {
89
99
  const result = parseArgs(["install", "claude", "--output"]);
90
100
  assert.equal(result.command, "install");
91
101
  assert.equal(result.harness, "claude");
92
102
  assert.ok(result.optionError.includes("--output"));
93
103
  });
94
104
 
95
- it("parses --dry-run for install", () => {
105
+ it("parses --dry-run for install", async () => {
96
106
  const result = parseArgs(["install", "pi", "--dry-run"]);
97
107
  assert.deepStrictEqual(result, { command: "install", harness: "pi", dryRun: true });
98
108
  });
99
109
 
100
- it("parses --output <dir> for install", () => {
110
+ it("parses --output <dir> for install", async () => {
101
111
  const result = parseArgs(["install", "pi", "--output", "/tmp/out"]);
102
112
  assert.deepStrictEqual(result, { command: "install", harness: "pi", output: "/tmp/out" });
103
113
  });
104
114
 
105
- it("parses --plugin-root <dir> for install", () => {
115
+ it("parses --plugin-root <dir> for install", async () => {
106
116
  const result = parseArgs(["install", "pi", "--plugin-root", "/tmp/plugin"]);
107
117
  assert.deepStrictEqual(result, { command: "install", harness: "pi", pluginRoot: "/tmp/plugin" });
108
118
  });
109
119
 
110
- it("combines multiple install options", () => {
120
+ it("combines multiple install options", async () => {
111
121
  const result = parseArgs(["install", "pi", "--dry-run", "--output", "/tmp/out", "--plugin-root", "/tmp/plugin"]);
112
122
  assert.deepStrictEqual(result, {
113
123
  command: "install",
@@ -118,32 +128,32 @@ describe("parseArgs", () => {
118
128
  });
119
129
  });
120
130
 
121
- it("reports a missing value for --output", () => {
131
+ it("reports a missing value for --output", async () => {
122
132
  const result = parseArgs(["install", "pi", "--output"]);
123
133
  assert.equal(result.command, "install");
124
134
  assert.equal(result.harness, "pi");
125
135
  assert.ok(result.optionError.includes("--output"));
126
136
  });
127
137
 
128
- it("reports an unknown option", () => {
138
+ it("reports an unknown option", async () => {
129
139
  const result = parseArgs(["install", "pi", "--bogus"]);
130
140
  assert.equal(result.command, "install");
131
141
  assert.equal(result.harness, "pi");
132
142
  assert.ok(result.optionError.includes("--bogus"));
133
143
  });
134
144
 
135
- it("keeps the old shape when no options are given", () => {
145
+ it("keeps the old shape when no options are given", async () => {
136
146
  assert.deepStrictEqual(parseArgs(["install", "pi"]), { command: "install", harness: "pi" });
137
147
  });
138
148
 
139
- it("returns { command: 'unknown', name: 'foo' } for unknown command", () => {
149
+ it("returns { command: 'unknown', name: 'foo' } for unknown command", async () => {
140
150
  const result = parseArgs(["foo"]);
141
151
  assert.deepStrictEqual(result, { command: "unknown", name: "foo" });
142
152
  });
143
153
  });
144
154
 
145
155
  describe("buildHelpText", () => {
146
- it("returns a string containing usage info", () => {
156
+ it("returns a string containing usage info", async () => {
147
157
  const text = buildHelpText();
148
158
  assert.ok(typeof text === "string");
149
159
  assert.ok(text.includes("agent-cortex"));
@@ -152,19 +162,19 @@ describe("buildHelpText", () => {
152
162
  });
153
163
 
154
164
  describe("validateHarness", () => {
155
- it("returns ok for supported harnesses", () => {
165
+ it("returns ok for supported harnesses", async () => {
156
166
  for (const h of ["copilot", "claude", "pi"]) {
157
167
  assert.deepStrictEqual(validateHarness(h), { ok: true });
158
168
  }
159
169
  });
160
170
 
161
- it("returns error when harness is null", () => {
171
+ it("returns error when harness is null", async () => {
162
172
  const result = validateHarness(null);
163
173
  assert.equal(result.ok, false);
164
174
  assert.ok(result.error.includes("Missing harness"));
165
175
  });
166
176
 
167
- it("returns error for unknown harness", () => {
177
+ it("returns error for unknown harness", async () => {
168
178
  const result = validateHarness("docker");
169
179
  assert.equal(result.ok, false);
170
180
  assert.ok(result.error.includes("Unknown harness"));
@@ -198,7 +208,7 @@ describe("CLI integration", () => {
198
208
  });
199
209
 
200
210
  it("exits 0 for install claude (into a temp output dir)", async () => {
201
- const dir = mkdtempSync(join(tmpdir(), "agent-cortex-cli-"));
211
+ const dir = await mkdtemp(join(tmpdir(), "agent-cortex-cli-"));
202
212
  const { exitCode, stdout } = await runCli(["install", "claude", "--output", dir]);
203
213
  assert.equal(exitCode, 0);
204
214
  assert.ok(stdout.includes("claude"));
@@ -236,15 +246,15 @@ describe("CLI integration", () => {
236
246
  });
237
247
 
238
248
  it("exits 0 for install pi --output <tmp> and writes agent files", async () => {
239
- const outDir = mkdtempSync(join(tmpdir(), "cli-pi-install-"));
249
+ const outDir = await mkdtemp(join(tmpdir(), "cli-pi-install-"));
240
250
  try {
241
251
  const { exitCode, stdout } = await runCli(["install", "pi", "--output", outDir]);
242
252
  assert.equal(exitCode, 0);
243
253
  assert.ok(stdout.includes("ralph.agent.md"));
244
- const ralph = readFileSync(join(outDir, "agents", "ralph.agent.md"), "utf-8");
254
+ const ralph = await readFile(join(outDir, "agents", "ralph.agent.md"), "utf-8");
245
255
  assert.ok(ralph.includes("agent-cortex:ralph"));
246
256
  } finally {
247
- rmSync(outDir, { recursive: true, force: true });
257
+ await rm(outDir, { recursive: true, force: true });
248
258
  }
249
259
  });
250
260
 
@@ -5,7 +5,7 @@
5
5
  import { describe, it } from "node:test";
6
6
  import assert from "node:assert/strict";
7
7
  import { execFile } from "node:child_process";
8
- import { mkdtempSync, mkdirSync, writeFileSync, rmSync, readFileSync, existsSync, readdirSync, statSync } from "node:fs";
8
+ import { mkdtemp, mkdir, writeFile, rm, readFile, readdir, stat } from "node:fs/promises";
9
9
  import { tmpdir } from "node:os";
10
10
  import { fileURLToPath } from "node:url";
11
11
  import { join, dirname } from "node:path";
@@ -15,6 +15,16 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
15
15
  const ROOT = join(__dirname, "..");
16
16
  const CLI_PATH = join(ROOT, "bin", "agent-cortex.mjs");
17
17
 
18
+ /** True when a path exists (stat succeeds). */
19
+ async function pathExists(p) {
20
+ try {
21
+ await stat(p);
22
+ return true;
23
+ } catch {
24
+ return false;
25
+ }
26
+ }
27
+
18
28
  function runCli(args) {
19
29
  return new Promise((resolve) => {
20
30
  execFile(process.execPath, [CLI_PATH, ...args], (error, stdout, stderr) => {
@@ -43,28 +53,28 @@ function tokenMap() {
43
53
  };
44
54
  }
45
55
 
46
- function makeFixture() {
47
- const root = mkdtempSync(join(tmpdir(), "copilot-installer-"));
56
+ async function makeFixture() {
57
+ const root = await mkdtemp(join(tmpdir(), "copilot-installer-"));
48
58
  return {
49
59
  root,
50
60
  output: join(root, "generated"),
51
- cleanup: () => rmSync(root, { recursive: true, force: true }),
61
+ cleanup: async () => rm(root, { recursive: true, force: true }),
52
62
  };
53
63
  }
54
64
 
55
- function writeFixture(fx, relPath, content) {
65
+ async function writeFixture(fx, relPath, content) {
56
66
  const p = join(fx.root, relPath);
57
- mkdirSync(dirname(p), { recursive: true });
58
- writeFileSync(p, content);
67
+ await mkdir(dirname(p), { recursive: true });
68
+ await writeFile(p, content);
59
69
  }
60
70
 
61
71
  /** Seed a minimal but realistic package tree (agents + token-map). */
62
- function seedCopilotPackage(fx, opts = {}) {
72
+ async function seedCopilotPackage(fx, opts = {}) {
63
73
  const { agents = {} } = opts;
64
- writeFixture(fx, "token-map.json", JSON.stringify(tokenMap()));
74
+ await writeFixture(fx, "token-map.json", JSON.stringify(tokenMap()));
65
75
  for (const [name, def] of Object.entries(agents)) {
66
- writeFixture(fx, `agents/${name}/agent.md`, def.body ?? `# ${name}\nBody of ${name}.`);
67
- writeFixture(
76
+ await writeFixture(fx, `agents/${name}/agent.md`, def.body ?? `# ${name}\nBody of ${name}.`);
77
+ await writeFixture(
68
78
  fx,
69
79
  `agents/${name}/copilot/frontmatter.json`,
70
80
  JSON.stringify(
@@ -77,34 +87,34 @@ function seedCopilotPackage(fx, opts = {}) {
77
87
  ),
78
88
  );
79
89
  for (const [section, content] of Object.entries(def.sections ?? {})) {
80
- writeFixture(fx, `agents/${name}/copilot/${section}.md`, content);
90
+ await writeFixture(fx, `agents/${name}/copilot/${section}.md`, content);
81
91
  }
82
92
  }
83
93
  }
84
94
 
85
95
  /** Deterministic snapshot of a directory: relative paths + byte content. */
86
- function snapshot(dir) {
96
+ async function snapshot(dir) {
87
97
  const entries = [];
88
- const walk = (p) => {
89
- for (const e of readdirSync(p, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
98
+ const walk = async (p) => {
99
+ for (const e of (await readdir(p, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name))) {
90
100
  const full = join(p, e.name);
91
101
  if (e.isDirectory()) {
92
102
  entries.push([e.name, "dir"]);
93
- walk(full);
94
- } else entries.push([e.name, "file", readFileSync(full, "utf-8")]);
103
+ await walk(full);
104
+ } else entries.push([e.name, "file", await readFile(full, "utf-8")]);
95
105
  }
96
106
  };
97
- walk(dir);
107
+ await walk(dir);
98
108
  return entries;
99
109
  }
100
110
 
101
111
  // ─── Agent composition ───────────────────────────────────────────────────────
102
112
 
103
113
  describe("installCopilot — agent composition", () => {
104
- it("writes one flat <name>.agent.md per composable agent dir with copilot frontmatter and no leftover tokens", () => {
105
- const fx = makeFixture();
114
+ it("writes one flat <name>.agent.md per composable agent dir with copilot frontmatter and no leftover tokens", async () => {
115
+ const fx = await makeFixture();
106
116
  try {
107
- seedCopilotPackage(fx, {
117
+ await seedCopilotPackage(fx, {
108
118
  agents: {
109
119
  alpha: {
110
120
  body: "# alpha\nUse {{TOOL:view}} to read.\n{{SECTION:polling}}\nRead {{PATH:agents_dir}}.",
@@ -119,11 +129,11 @@ describe("installCopilot — agent composition", () => {
119
129
  },
120
130
  });
121
131
 
122
- const result = installCopilot({ root: fx.root, output: fx.output });
132
+ const result = await installCopilot({ root: fx.root, output: fx.output });
123
133
 
124
134
  const file = join(fx.output, "alpha.agent.md");
125
- assert.ok(existsSync(file), "flat agent file written");
126
- const content = readFileSync(file, "utf-8");
135
+ assert.ok(await pathExists(file), "flat agent file written");
136
+ const content = await readFile(file, "utf-8");
127
137
  assert.ok(content.startsWith("---\n"), "yaml frontmatter");
128
138
  assert.ok(content.includes("# GENERATED from agents/alpha/ by scripts/build-copilot-agents.mjs — DO NOT EDIT."));
129
139
  // Committed flat-file field order: description, name, tools (YAML array), argument-hint.
@@ -138,64 +148,64 @@ describe("installCopilot — agent composition", () => {
138
148
  assert.deepEqual(result.agents, ["alpha"]);
139
149
  assert.equal(result.output, fx.output);
140
150
  } finally {
141
- fx.cleanup();
151
+ await fx.cleanup();
142
152
  }
143
153
  });
144
154
 
145
- it("skips non-composable entries in agents/ and never clobbers composable sources", () => {
146
- const fx = makeFixture();
155
+ it("skips non-composable entries in agents/ and never clobbers composable sources", async () => {
156
+ const fx = await makeFixture();
147
157
  try {
148
- seedCopilotPackage(fx, { agents: { alpha: { body: "# alpha" } } });
149
- writeFixture(fx, "agents/README.md", "# agents dir docs\n");
158
+ await seedCopilotPackage(fx, { agents: { alpha: { body: "# alpha" } } });
159
+ await writeFixture(fx, "agents/README.md", "# agents dir docs\n");
150
160
 
151
- const result = installCopilot({ root: fx.root, output: fx.output });
161
+ const result = await installCopilot({ root: fx.root, output: fx.output });
152
162
 
153
- assert.deepEqual(readdirSync(fx.output).sort(), ["alpha.agent.md"], "no README, no subdirectories written");
163
+ assert.deepEqual((await readdir(fx.output)).sort(), ["alpha.agent.md"], "no README, no subdirectories written");
154
164
  assert.deepEqual(result.agents, ["alpha"]);
155
165
  // The composable source dir next to the generated file stays untouched.
156
- assert.ok(existsSync(join(fx.root, "agents", "alpha", "agent.md")), "composable source preserved");
157
- assert.ok(existsSync(join(fx.root, "agents", "alpha", "copilot", "frontmatter.json")), "harness dir preserved");
166
+ assert.ok(await pathExists(join(fx.root, "agents", "alpha", "agent.md")), "composable source preserved");
167
+ assert.ok(await pathExists(join(fx.root, "agents", "alpha", "copilot", "frontmatter.json")), "harness dir preserved");
158
168
  } finally {
159
- fx.cleanup();
169
+ await fx.cleanup();
160
170
  }
161
171
  });
162
172
 
163
- it("throws when a composable dir lacks copilot/frontmatter.json", () => {
164
- const fx = makeFixture();
173
+ it("throws when a composable dir lacks copilot/frontmatter.json", async () => {
174
+ const fx = await makeFixture();
165
175
  try {
166
- seedCopilotPackage(fx, { agents: { alpha: { body: "# alpha" } } });
167
- rmSync(join(fx.root, "agents", "alpha", "copilot"), { recursive: true, force: true });
168
- assert.throws(
169
- () => installCopilot({ root: fx.root, output: fx.output }),
176
+ await seedCopilotPackage(fx, { agents: { alpha: { body: "# alpha" } } });
177
+ await rm(join(fx.root, "agents", "alpha", "copilot"), { recursive: true, force: true });
178
+ await assert.rejects(
179
+ installCopilot({ root: fx.root, output: fx.output }),
170
180
  /without copilot\/frontmatter\.json/,
171
181
  );
172
182
  } finally {
173
- fx.cleanup();
183
+ await fx.cleanup();
174
184
  }
175
185
  });
176
186
 
177
- it("defaults to the plugin-scanned agents/ dir of the package root (plugin.json \"agents\": \"agents/\")", () => {
178
- const fx = makeFixture();
187
+ it("defaults to the plugin-scanned agents/ dir of the package root (plugin.json \"agents\": \"agents/\")", async () => {
188
+ const fx = await makeFixture();
179
189
  try {
180
- seedCopilotPackage(fx, { agents: { alpha: { body: "# alpha" } } });
190
+ await seedCopilotPackage(fx, { agents: { alpha: { body: "# alpha" } } });
181
191
 
182
- const result = installCopilot({ root: fx.root });
192
+ const result = await installCopilot({ root: fx.root });
183
193
 
184
194
  assert.equal(result.output, join(fx.root, "agents"), "default output is the dir plugin.json scans");
185
- assert.ok(existsSync(join(fx.root, "agents", "alpha.agent.md")), "flat file lands next to the composable dir");
186
- assert.ok(existsSync(join(fx.root, "agents", "alpha", "agent.md")), "composable source untouched");
195
+ assert.ok(await pathExists(join(fx.root, "agents", "alpha.agent.md")), "flat file lands next to the composable dir");
196
+ assert.ok(await pathExists(join(fx.root, "agents", "alpha", "agent.md")), "composable source untouched");
187
197
  } finally {
188
- fx.cleanup();
198
+ await fx.cleanup();
189
199
  }
190
200
  });
191
201
 
192
- it("throws on an unknown tool token", () => {
193
- const fx = makeFixture();
202
+ it("throws on an unknown tool token", async () => {
203
+ const fx = await makeFixture();
194
204
  try {
195
- seedCopilotPackage(fx, { agents: { alpha: { body: "# alpha\nUse {{TOOL:nope}} now." } } });
196
- assert.throws(() => installCopilot({ root: fx.root, output: fx.output }), /unknown tool token {{TOOL:nope}}/);
205
+ await seedCopilotPackage(fx, { agents: { alpha: { body: "# alpha\nUse {{TOOL:nope}} now." } } });
206
+ await assert.rejects(installCopilot({ root: fx.root, output: fx.output }), /unknown tool token {{TOOL:nope}}/);
197
207
  } finally {
198
- fx.cleanup();
208
+ await fx.cleanup();
199
209
  }
200
210
  });
201
211
  });
@@ -203,37 +213,37 @@ describe("installCopilot — agent composition", () => {
203
213
  // ─── Output / dry-run ────────────────────────────────────────────────────────
204
214
 
205
215
  describe("installCopilot — output & dry-run", () => {
206
- it("dry-run reports the plan without writing anything", () => {
207
- const fx = makeFixture();
216
+ it("dry-run reports the plan without writing anything", async () => {
217
+ const fx = await makeFixture();
208
218
  try {
209
- seedCopilotPackage(fx, { agents: { alpha: { body: "# alpha" } } });
219
+ await seedCopilotPackage(fx, { agents: { alpha: { body: "# alpha" } } });
210
220
 
211
- const result = installCopilot({ root: fx.root, output: fx.output, dryRun: true });
221
+ const result = await installCopilot({ root: fx.root, output: fx.output, dryRun: true });
212
222
 
213
223
  assert.equal(result.dryRun, true);
214
- assert.equal(existsSync(fx.output), false, "no output dir created in dry-run");
224
+ assert.equal(await pathExists(fx.output), false, "no output dir created in dry-run");
215
225
  assert.deepEqual(result.agents, ["alpha"], "planned files still reported");
216
226
  } finally {
217
- fx.cleanup();
227
+ await fx.cleanup();
218
228
  }
219
229
  });
220
230
 
221
- it("is deterministic across repeated installs into the same output", () => {
222
- const fx = makeFixture();
231
+ it("is deterministic across repeated installs into the same output", async () => {
232
+ const fx = await makeFixture();
223
233
  try {
224
- seedCopilotPackage(fx, {
234
+ await seedCopilotPackage(fx, {
225
235
  agents: {
226
236
  alpha: { body: "# alpha\n{{SECTION:polling}}", sections: { polling: "Poll with {{TOOL:task}}." } },
227
237
  beta: { body: "# beta" },
228
238
  },
229
239
  });
230
240
 
231
- installCopilot({ root: fx.root, output: fx.output });
232
- const first = snapshot(fx.output);
233
- installCopilot({ root: fx.root, output: fx.output });
234
- assert.deepEqual(snapshot(fx.output), first);
241
+ await installCopilot({ root: fx.root, output: fx.output });
242
+ const first = await snapshot(fx.output);
243
+ await installCopilot({ root: fx.root, output: fx.output });
244
+ assert.deepEqual(await snapshot(fx.output), first);
235
245
  } finally {
236
- fx.cleanup();
246
+ await fx.cleanup();
237
247
  }
238
248
  });
239
249
  });
@@ -241,19 +251,19 @@ describe("installCopilot — output & dry-run", () => {
241
251
  // ─── Integration against the real repo ───────────────────────────────────────
242
252
 
243
253
  describe("installCopilot — real repo integration", () => {
244
- it("regenerates byte-identical copies of the committed agents/*.agent.md files", () => {
245
- const fx = makeFixture();
254
+ it("regenerates byte-identical copies of the committed agents/*.agent.md files", async () => {
255
+ const fx = await makeFixture();
246
256
  try {
247
- const result = installCopilot({ root: ROOT, output: fx.output });
257
+ const result = await installCopilot({ root: ROOT, output: fx.output });
248
258
 
249
259
  assert.deepEqual(result.agents.sort(), ["plan", "ralph", "ralph-plan", "strategy"]);
250
260
  for (const name of result.agents) {
251
- const generated = readFileSync(join(fx.output, `${name}.agent.md`), "utf-8");
252
- const committed = readFileSync(join(ROOT, "agents", `${name}.agent.md`), "utf-8");
261
+ const generated = await readFile(join(fx.output, `${name}.agent.md`), "utf-8");
262
+ const committed = await readFile(join(ROOT, "agents", `${name}.agent.md`), "utf-8");
253
263
  assert.equal(generated, committed, `${name}.agent.md must match the committed flat file (CI drift check)`);
254
264
  }
255
265
  } finally {
256
- fx.cleanup();
266
+ await fx.cleanup();
257
267
  }
258
268
  });
259
269
  });
@@ -262,37 +272,37 @@ describe("installCopilot — real repo integration", () => {
262
272
 
263
273
  describe("CLI integration", () => {
264
274
  it("exits 0 for install copilot --output <tmp> and writes the flat files", async () => {
265
- const fx = makeFixture();
275
+ const fx = await makeFixture();
266
276
  try {
267
277
  const { exitCode, stdout } = await runCli(["install", "copilot", "--output", fx.output]);
268
278
  assert.equal(exitCode, 0);
269
279
  assert.ok(stdout.includes("copilot"), "prints the harness name");
270
280
  assert.ok(stdout.includes("Generated"), "prints the generation summary");
271
- assert.ok(existsSync(join(fx.output, "ralph.agent.md")), "agent files written");
272
- assert.ok(existsSync(join(fx.output, "plan.agent.md")));
281
+ assert.ok(await pathExists(join(fx.output, "ralph.agent.md")), "agent files written");
282
+ assert.ok(await pathExists(join(fx.output, "plan.agent.md")));
273
283
  } finally {
274
- fx.cleanup();
284
+ await fx.cleanup();
275
285
  }
276
286
  });
277
287
 
278
288
  it("exits 0 for install copilot --dry-run and writes nothing", async () => {
279
- const fx = makeFixture();
289
+ const fx = await makeFixture();
280
290
  try {
281
291
  const { exitCode, stdout } = await runCli(["install", "copilot", "--output", fx.output, "--dry-run"]);
282
292
  assert.equal(exitCode, 0);
283
293
  assert.ok(/dry-run/i.test(stdout));
284
- assert.equal(existsSync(fx.output), false, "no files written in dry-run");
294
+ assert.equal(await pathExists(fx.output), false, "no files written in dry-run");
285
295
  } finally {
286
- fx.cleanup();
296
+ await fx.cleanup();
287
297
  }
288
298
  });
289
299
 
290
300
  it("exits 0 for install copilot (default output) without touching committed files", async () => {
291
- const before = readFileSync(join(ROOT, "agents", "ralph.agent.md"), "utf-8");
301
+ const before = await readFile(join(ROOT, "agents", "ralph.agent.md"), "utf-8");
292
302
  const { exitCode } = await runCli(["install", "copilot"]);
293
303
  assert.equal(exitCode, 0);
294
304
  // Default output regenerates the shared agents/ dir in place — output must
295
305
  // stay byte-identical to what is committed.
296
- assert.equal(readFileSync(join(ROOT, "agents", "ralph.agent.md"), "utf-8"), before);
306
+ assert.equal(await readFile(join(ROOT, "agents", "ralph.agent.md"), "utf-8"), before);
297
307
  });
298
308
  });