@os-eco/overstory-cli 0.7.0 → 0.7.3

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/README.md +7 -6
  2. package/agents/builder.md +1 -1
  3. package/agents/coordinator.md +12 -11
  4. package/agents/lead.md +6 -6
  5. package/agents/monitor.md +4 -4
  6. package/agents/reviewer.md +1 -1
  7. package/agents/scout.md +5 -5
  8. package/agents/supervisor.md +36 -32
  9. package/package.json +1 -1
  10. package/src/agents/guard-rules.ts +97 -0
  11. package/src/agents/hooks-deployer.test.ts +6 -5
  12. package/src/agents/hooks-deployer.ts +7 -90
  13. package/src/agents/identity.test.ts +3 -2
  14. package/src/agents/manifest.test.ts +4 -3
  15. package/src/agents/overlay.test.ts +10 -9
  16. package/src/agents/overlay.ts +5 -5
  17. package/src/commands/agents.test.ts +10 -4
  18. package/src/commands/clean.test.ts +3 -0
  19. package/src/commands/completions.test.ts +8 -5
  20. package/src/commands/completions.ts +38 -2
  21. package/src/commands/coordinator.test.ts +1 -0
  22. package/src/commands/coordinator.ts +15 -11
  23. package/src/commands/costs.test.ts +9 -3
  24. package/src/commands/dashboard.test.ts +265 -6
  25. package/src/commands/dashboard.ts +367 -64
  26. package/src/commands/doctor.test.ts +3 -2
  27. package/src/commands/errors.test.ts +3 -2
  28. package/src/commands/feed.test.ts +3 -2
  29. package/src/commands/feed.ts +2 -29
  30. package/src/commands/init.test.ts +1 -2
  31. package/src/commands/init.ts +1 -8
  32. package/src/commands/inspect.test.ts +17 -2
  33. package/src/commands/log.test.ts +262 -8
  34. package/src/commands/log.ts +232 -110
  35. package/src/commands/logs.test.ts +3 -2
  36. package/src/commands/mail.test.ts +8 -2
  37. package/src/commands/metrics.test.ts +4 -3
  38. package/src/commands/monitor.ts +15 -11
  39. package/src/commands/nudge.test.ts +4 -2
  40. package/src/commands/prime.test.ts +4 -2
  41. package/src/commands/prime.ts +6 -2
  42. package/src/commands/replay.test.ts +3 -2
  43. package/src/commands/run.test.ts +3 -1
  44. package/src/commands/sling.test.ts +142 -1
  45. package/src/commands/sling.ts +145 -24
  46. package/src/commands/status.test.ts +9 -8
  47. package/src/commands/stop.test.ts +1 -0
  48. package/src/commands/supervisor.ts +19 -12
  49. package/src/commands/trace.test.ts +4 -2
  50. package/src/commands/watch.test.ts +3 -2
  51. package/src/commands/worktree.test.ts +9 -0
  52. package/src/config.test.ts +3 -3
  53. package/src/config.ts +29 -0
  54. package/src/doctor/agents.test.ts +3 -2
  55. package/src/doctor/consistency.test.ts +14 -0
  56. package/src/doctor/logs.test.ts +3 -2
  57. package/src/doctor/structure.test.ts +3 -2
  58. package/src/e2e/init-sling-lifecycle.test.ts +3 -5
  59. package/src/index.ts +3 -1
  60. package/src/logging/color.ts +1 -1
  61. package/src/logging/format.test.ts +110 -0
  62. package/src/logging/format.ts +42 -1
  63. package/src/logging/logger.test.ts +3 -2
  64. package/src/mail/broadcast.test.ts +1 -0
  65. package/src/mail/client.test.ts +3 -2
  66. package/src/mail/store.test.ts +3 -2
  67. package/src/merge/queue.test.ts +3 -2
  68. package/src/merge/resolver.test.ts +39 -0
  69. package/src/merge/resolver.ts +24 -5
  70. package/src/mulch/client.test.ts +63 -2
  71. package/src/mulch/client.ts +62 -1
  72. package/src/runtimes/claude.test.ts +5 -4
  73. package/src/runtimes/pi-guards.test.ts +457 -0
  74. package/src/runtimes/pi-guards.ts +349 -0
  75. package/src/runtimes/pi.test.ts +620 -0
  76. package/src/runtimes/pi.ts +244 -0
  77. package/src/runtimes/registry.test.ts +33 -0
  78. package/src/runtimes/registry.ts +15 -2
  79. package/src/runtimes/types.ts +63 -0
  80. package/src/schema-consistency.test.ts +5 -2
  81. package/src/sessions/compat.test.ts +3 -2
  82. package/src/sessions/compat.ts +1 -0
  83. package/src/sessions/store.test.ts +34 -2
  84. package/src/sessions/store.ts +37 -4
  85. package/src/test-helpers.ts +20 -1
  86. package/src/types.ts +17 -0
  87. package/src/watchdog/daemon.test.ts +11 -7
  88. package/src/watchdog/daemon.ts +1 -1
  89. package/src/watchdog/health.test.ts +1 -0
  90. package/src/watchdog/triage.test.ts +3 -2
  91. package/src/watchdog/triage.ts +14 -4
@@ -1,7 +1,8 @@
1
1
  import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
2
- import { access, mkdtemp, readdir, readFile, rm } from "node:fs/promises";
2
+ import { access, mkdtemp, readdir, readFile } from "node:fs/promises";
3
3
  import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
+ import { cleanupTempDir } from "../test-helpers.ts";
5
6
  import type { LogEvent } from "../types.ts";
6
7
  import { createLogger } from "./logger.ts";
7
8
 
@@ -15,7 +16,7 @@ describe("createLogger", () => {
15
16
  });
16
17
 
17
18
  afterEach(async () => {
18
- await rm(tempDir, { recursive: true, force: true });
19
+ await cleanupTempDir(tempDir);
19
20
  });
20
21
 
21
22
  async function readLogFile(filename: string): Promise<string> {
@@ -41,6 +41,7 @@ describe("resolveGroupAddress", () => {
41
41
  lastActivity: "2024-01-01T00:01:00Z",
42
42
  escalationLevel: 0,
43
43
  stalledSince: null,
44
+ transcriptPath: null,
44
45
  };
45
46
  }
46
47
 
@@ -1,8 +1,9 @@
1
1
  import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
- import { mkdtemp, rm } from "node:fs/promises";
2
+ import { mkdtemp } from "node:fs/promises";
3
3
  import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
5
  import { MailError } from "../errors.ts";
6
+ import { cleanupTempDir } from "../test-helpers.ts";
6
7
  import type { WorkerDonePayload } from "../types.ts";
7
8
  import { createMailClient, type MailClient, parsePayload } from "./client.ts";
8
9
  import { createMailStore, type MailStore } from "./store.ts";
@@ -20,7 +21,7 @@ describe("createMailClient", () => {
20
21
 
21
22
  afterEach(async () => {
22
23
  client.close();
23
- await rm(tempDir, { recursive: true, force: true });
24
+ await cleanupTempDir(tempDir);
24
25
  });
25
26
 
26
27
  describe("send", () => {
@@ -1,8 +1,9 @@
1
1
  import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
- import { mkdtemp, rm } from "node:fs/promises";
2
+ import { mkdtemp } from "node:fs/promises";
3
3
  import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
5
  import { MailError } from "../errors.ts";
6
+ import { cleanupTempDir } from "../test-helpers.ts";
6
7
  import type { MailMessage } from "../types.ts";
7
8
  import { createMailStore, type MailStore } from "./store.ts";
8
9
 
@@ -17,7 +18,7 @@ describe("createMailStore", () => {
17
18
 
18
19
  afterEach(async () => {
19
20
  store.close();
20
- await rm(tempDir, { recursive: true, force: true });
21
+ await cleanupTempDir(tempDir);
21
22
  });
22
23
 
23
24
  describe("insert", () => {
@@ -1,9 +1,10 @@
1
1
  import { Database } from "bun:sqlite";
2
2
  import { afterEach, beforeEach, describe, expect, test } from "bun:test";
3
- import { mkdtemp, rm } from "node:fs/promises";
3
+ import { mkdtemp } from "node:fs/promises";
4
4
  import { tmpdir } from "node:os";
5
5
  import { join } from "node:path";
6
6
  import { MergeError } from "../errors.ts";
7
+ import { cleanupTempDir } from "../test-helpers.ts";
7
8
  import { createMergeQueue } from "./queue.ts";
8
9
 
9
10
  describe("createMergeQueue", () => {
@@ -17,7 +18,7 @@ describe("createMergeQueue", () => {
17
18
  });
18
19
 
19
20
  afterEach(async () => {
20
- await rm(tempDir, { recursive: true, force: true });
21
+ await cleanupTempDir(tempDir);
21
22
  });
22
23
 
23
24
  function makeInput(
@@ -203,6 +203,9 @@ function createMockMulchClient(
203
203
  action: "analyze",
204
204
  };
205
205
  },
206
+ async appendOutcome() {
207
+ // No-op stub: resolver tests don't exercise outcome appending
208
+ },
206
209
  };
207
210
  }
208
211
 
@@ -1440,6 +1443,42 @@ describe("createMergeResolver", () => {
1440
1443
  });
1441
1444
  });
1442
1445
 
1446
+ describe("queryConflictHistory uses sortByScore", () => {
1447
+ test("passes sortByScore: true to mulch search when querying conflict history", async () => {
1448
+ const repoDir = await createTempGitRepo();
1449
+ try {
1450
+ const defaultBranch = await getDefaultBranch(repoDir);
1451
+ await setupContentConflict(repoDir, defaultBranch);
1452
+
1453
+ const entry = makeTestEntry({
1454
+ branchName: "feature-branch",
1455
+ filesModified: ["src/test.ts"],
1456
+ });
1457
+
1458
+ // Capture search call options
1459
+ let capturedSearchOptions: unknown;
1460
+ const mockMulchClient = createMockMulchClient();
1461
+ mockMulchClient.search = async (_query, options) => {
1462
+ capturedSearchOptions = options;
1463
+ return "";
1464
+ };
1465
+
1466
+ const resolver = createMergeResolver({
1467
+ aiResolveEnabled: false,
1468
+ reimagineEnabled: false,
1469
+ mulchClient: mockMulchClient,
1470
+ });
1471
+
1472
+ await resolver.resolve(entry, defaultBranch, repoDir);
1473
+
1474
+ // Verify sortByScore was passed to search
1475
+ expect(capturedSearchOptions).toMatchObject({ sortByScore: true });
1476
+ } finally {
1477
+ await cleanupTempDir(repoDir);
1478
+ }
1479
+ });
1480
+ });
1481
+
1443
1482
  describe("AI-resolve with history context", () => {
1444
1483
  test("includes historical context in AI prompt when available", async () => {
1445
1484
  const repoDir = await createTempGitRepo();
@@ -13,10 +13,12 @@
13
13
 
14
14
  import { MergeError } from "../errors.ts";
15
15
  import type { MulchClient } from "../mulch/client.ts";
16
+ import { getRuntime } from "../runtimes/registry.ts";
16
17
  import type {
17
18
  ConflictHistory,
18
19
  MergeEntry,
19
20
  MergeResult,
21
+ OverstoryConfig,
20
22
  ParsedConflictPattern,
21
23
  ResolutionTier,
22
24
  } from "../types.ts";
@@ -243,6 +245,7 @@ async function tryAiResolve(
243
245
  conflictFiles: string[],
244
246
  repoRoot: string,
245
247
  pastResolutions?: string[],
248
+ config?: OverstoryConfig,
246
249
  ): Promise<{ success: boolean; remainingConflicts: string[] }> {
247
250
  const remainingConflicts: string[] = [];
248
251
 
@@ -265,7 +268,9 @@ async function tryAiResolve(
265
268
  content,
266
269
  ].join(" ");
267
270
 
268
- const proc = Bun.spawn(["claude", "--print", "-p", prompt], {
271
+ const runtime = getRuntime(config?.runtime?.printCommand ?? config?.runtime?.default, config);
272
+ const argv = runtime.buildPrintCommand(prompt);
273
+ const proc = Bun.spawn(argv, {
269
274
  cwd: repoRoot,
270
275
  stdout: "pipe",
271
276
  stderr: "pipe",
@@ -315,6 +320,7 @@ async function tryReimagine(
315
320
  entry: MergeEntry,
316
321
  canonicalBranch: string,
317
322
  repoRoot: string,
323
+ config?: OverstoryConfig,
318
324
  ): Promise<{ success: boolean }> {
319
325
  // Abort the current merge
320
326
  await runGit(repoRoot, ["merge", "--abort"]);
@@ -348,7 +354,9 @@ async function tryReimagine(
348
354
  branchContent,
349
355
  ].join("");
350
356
 
351
- const proc = Bun.spawn(["claude", "--print", "-p", prompt], {
357
+ const runtime = getRuntime(config?.runtime?.printCommand ?? config?.runtime?.default, config);
358
+ const argv = runtime.buildPrintCommand(prompt);
359
+ const proc = Bun.spawn(argv, {
352
360
  cwd: repoRoot,
353
361
  stdout: "pipe",
354
362
  stderr: "pipe",
@@ -506,7 +514,7 @@ async function queryConflictHistory(
506
514
  entry: MergeEntry,
507
515
  ): Promise<ConflictHistory> {
508
516
  try {
509
- const searchOutput = await mulchClient.search("merge-conflict");
517
+ const searchOutput = await mulchClient.search("merge-conflict", { sortByScore: true });
510
518
  const patterns = parseConflictPatterns(searchOutput);
511
519
  return buildConflictHistory(patterns, entry.filesModified);
512
520
  } catch {
@@ -556,6 +564,7 @@ export function createMergeResolver(options: {
556
564
  aiResolveEnabled: boolean;
557
565
  reimagineEnabled: boolean;
558
566
  mulchClient?: MulchClient;
567
+ config?: OverstoryConfig;
559
568
  }): MergeResolver {
560
569
  return {
561
570
  async resolve(
@@ -632,7 +641,12 @@ export function createMergeResolver(options: {
632
641
  // Tier 3: AI-resolve
633
642
  if (options.aiResolveEnabled && !history.skipTiers.includes("ai-resolve")) {
634
643
  lastTier = "ai-resolve";
635
- const aiResult = await tryAiResolve(conflictFiles, repoRoot, history.pastResolutions);
644
+ const aiResult = await tryAiResolve(
645
+ conflictFiles,
646
+ repoRoot,
647
+ history.pastResolutions,
648
+ options.config,
649
+ );
636
650
  if (aiResult.success) {
637
651
  if (options.mulchClient) {
638
652
  recordConflictPattern(options.mulchClient, entry, "ai-resolve", conflictFiles, true);
@@ -651,7 +665,12 @@ export function createMergeResolver(options: {
651
665
  // Tier 4: Re-imagine
652
666
  if (options.reimagineEnabled && !history.skipTiers.includes("reimagine")) {
653
667
  lastTier = "reimagine";
654
- const reimagineResult = await tryReimagine(entry, canonicalBranch, repoRoot);
668
+ const reimagineResult = await tryReimagine(
669
+ entry,
670
+ canonicalBranch,
671
+ repoRoot,
672
+ options.config,
673
+ );
655
674
  if (reimagineResult.success) {
656
675
  if (options.mulchClient) {
657
676
  recordConflictPattern(options.mulchClient, entry, "reimagine", conflictFiles, true);
@@ -6,10 +6,11 @@
6
6
  */
7
7
 
8
8
  import { afterEach, beforeEach, describe, expect, test } from "bun:test";
9
- import { mkdtemp, rm } from "node:fs/promises";
9
+ import { mkdtemp } from "node:fs/promises";
10
10
  import { tmpdir } from "node:os";
11
11
  import { join } from "node:path";
12
12
  import { AgentError } from "../errors.ts";
13
+ import { cleanupTempDir } from "../test-helpers.ts";
13
14
  import { createMulchClient } from "./client.ts";
14
15
 
15
16
  // Check if mulch is available
@@ -30,7 +31,7 @@ describe("createMulchClient", () => {
30
31
  });
31
32
 
32
33
  afterEach(async () => {
33
- await rm(tempDir, { recursive: true, force: true });
34
+ await cleanupTempDir(tempDir);
34
35
  });
35
36
 
36
37
  /**
@@ -162,6 +163,33 @@ describe("createMulchClient", () => {
162
163
  });
163
164
  expect(typeof result).toBe("string");
164
165
  });
166
+
167
+ test.skipIf(!hasMulch)("passes --sort-by-score flag in prime options", async () => {
168
+ await initMulch();
169
+ const client = createMulchClient(tempDir);
170
+ // mulch prime --sort-by-score may not be supported in older mulch versions;
171
+ // the interface and impl are forward-looking — test accepts both outcomes.
172
+ try {
173
+ const result = await client.prime([], "markdown", { sortByScore: true });
174
+ expect(typeof result).toBe("string");
175
+ } catch (error) {
176
+ expect(error).toBeInstanceOf(AgentError);
177
+ }
178
+ });
179
+
180
+ test.skipIf(!hasMulch)("passes --sort-by-score with --files together", async () => {
181
+ await initMulch();
182
+ const client = createMulchClient(tempDir);
183
+ try {
184
+ const result = await client.prime([], "markdown", {
185
+ files: ["src/config.ts"],
186
+ sortByScore: true,
187
+ });
188
+ expect(typeof result).toBe("string");
189
+ } catch (error) {
190
+ expect(error).toBeInstanceOf(AgentError);
191
+ }
192
+ });
165
193
  });
166
194
 
167
195
  describe("status", () => {
@@ -452,6 +480,39 @@ describe("createMulchClient", () => {
452
480
  expect(typeof result).toBe("string");
453
481
  });
454
482
 
483
+ test.skipIf(!hasMulch)("passes --classification flag when provided", async () => {
484
+ await initMulch();
485
+ const client = createMulchClient(tempDir);
486
+ const result = await client.search("test", { classification: "foundational" });
487
+ expect(typeof result).toBe("string");
488
+ });
489
+
490
+ test.skipIf(!hasMulch)("passes --outcome-status flag when provided (success)", async () => {
491
+ await initMulch();
492
+ const client = createMulchClient(tempDir);
493
+ const result = await client.search("test", { outcomeStatus: "success" });
494
+ expect(typeof result).toBe("string");
495
+ });
496
+
497
+ test.skipIf(!hasMulch)("passes --outcome-status flag when provided (failure)", async () => {
498
+ await initMulch();
499
+ const client = createMulchClient(tempDir);
500
+ const result = await client.search("test", { outcomeStatus: "failure" });
501
+ expect(typeof result).toBe("string");
502
+ });
503
+
504
+ test.skipIf(!hasMulch)("passes all search filters together", async () => {
505
+ await initMulch();
506
+ const client = createMulchClient(tempDir);
507
+ const result = await client.search("test", {
508
+ classification: "tactical",
509
+ outcomeStatus: "success",
510
+ sortByScore: true,
511
+ file: "src/config.ts",
512
+ });
513
+ expect(typeof result).toBe("string");
514
+ });
515
+
455
516
  test.skipIf(!hasMulch)("roundtrip: record via API then search and find it", async () => {
456
517
  await initMulch();
457
518
  const addProc = Bun.spawn(["ml", "add", "roundtrip"], {
@@ -28,9 +28,22 @@ export interface MulchClient {
28
28
  options?: {
29
29
  files?: string[];
30
30
  excludeDomain?: string[];
31
+ sortByScore?: boolean;
31
32
  },
32
33
  ): Promise<string>;
33
34
 
35
+ /** Append an outcome entry to an existing record by ID in the given domain. */
36
+ appendOutcome(
37
+ domain: string,
38
+ id: string,
39
+ outcome: {
40
+ status: "success" | "failure" | "partial";
41
+ agent?: string;
42
+ notes?: string;
43
+ duration?: number;
44
+ },
45
+ ): Promise<void>;
46
+
34
47
  /** Show domain statistics. */
35
48
  status(): Promise<MulchStatus>;
36
49
 
@@ -58,7 +71,15 @@ export interface MulchClient {
58
71
  query(domain?: string): Promise<string>;
59
72
 
60
73
  /** Search records across all domains. */
61
- search(query: string, options?: { file?: string; sortByScore?: boolean }): Promise<string>;
74
+ search(
75
+ query: string,
76
+ options?: {
77
+ file?: string;
78
+ sortByScore?: boolean;
79
+ classification?: string;
80
+ outcomeStatus?: "success" | "failure";
81
+ },
82
+ ): Promise<string>;
62
83
 
63
84
  /** Show expertise record changes since a git ref. */
64
85
  diff(options?: { since?: string }): Promise<MulchDiffResult>;
@@ -214,6 +235,8 @@ interface MulchProgrammaticApi {
214
235
  type?: string;
215
236
  tag?: string;
216
237
  classification?: string;
238
+ outcomeStatus?: "success" | "failure";
239
+ sortByScore?: boolean;
217
240
  file?: string;
218
241
  cwd?: string;
219
242
  },
@@ -222,6 +245,22 @@ interface MulchProgrammaticApi {
222
245
  domain: string,
223
246
  options?: { type?: string; classification?: string; file?: string; cwd?: string },
224
247
  ): Promise<MulchExpertiseRecord[]>;
248
+ appendOutcome(
249
+ domain: string,
250
+ id: string,
251
+ outcome: {
252
+ status: "success" | "failure" | "partial";
253
+ agent?: string;
254
+ notes?: string;
255
+ duration?: number;
256
+ recorded_at?: string;
257
+ },
258
+ options?: { cwd?: string },
259
+ ): Promise<{
260
+ record: MulchExpertiseRecord;
261
+ outcome: { status: string; agent?: string; notes?: string; recorded_at?: string };
262
+ total_outcomes: number;
263
+ }>;
225
264
  }
226
265
 
227
266
  const MULCH_PKG = "@os-eco/mulch-cli";
@@ -406,6 +445,9 @@ export function createMulchClient(cwd: string): MulchClient {
406
445
  if (options?.excludeDomain && options.excludeDomain.length > 0) {
407
446
  args.push("--exclude-domain", ...options.excludeDomain);
408
447
  }
448
+ if (options?.sortByScore) {
449
+ args.push("--sort-by-score");
450
+ }
409
451
  const { stdout } = await runMulch(args, "prime");
410
452
  return stdout;
411
453
  },
@@ -472,6 +514,9 @@ export function createMulchClient(cwd: string): MulchClient {
472
514
  const api = await loadMulchApi();
473
515
  const results = await api.searchExpertise(query, {
474
516
  file: options?.file,
517
+ classification: options?.classification,
518
+ outcomeStatus: options?.outcomeStatus,
519
+ sortByScore: options?.sortByScore,
475
520
  cwd,
476
521
  });
477
522
  return formatSearchResults(results);
@@ -595,5 +640,21 @@ export function createMulchClient(cwd: string): MulchClient {
595
640
  throw new AgentError(`Failed to parse JSON from mulch compact: ${trimmed.slice(0, 200)}`);
596
641
  }
597
642
  },
643
+
644
+ async appendOutcome(domain, id, outcome) {
645
+ const api = await loadMulchApi();
646
+ try {
647
+ await api.appendOutcome(
648
+ domain,
649
+ id,
650
+ { ...outcome, recorded_at: new Date().toISOString() },
651
+ { cwd },
652
+ );
653
+ } catch (error) {
654
+ throw new AgentError(
655
+ `mulch appendOutcome ${domain}/${id} failed: ${error instanceof Error ? error.message : String(error)}`,
656
+ );
657
+ }
658
+ },
598
659
  };
599
660
  }
@@ -1,7 +1,8 @@
1
1
  import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
- import { mkdtemp, rm } from "node:fs/promises";
2
+ import { mkdtemp } from "node:fs/promises";
3
3
  import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
+ import { cleanupTempDir } from "../test-helpers.ts";
5
6
  import type { ResolvedModel } from "../types.ts";
6
7
  import { ClaudeRuntime } from "./claude.ts";
7
8
  import type { SpawnOpts } from "./types.ts";
@@ -239,7 +240,7 @@ describe("ClaudeRuntime", () => {
239
240
  });
240
241
 
241
242
  afterEach(async () => {
242
- await rm(tempDir, { recursive: true, force: true });
243
+ await cleanupTempDir(tempDir);
243
244
  });
244
245
 
245
246
  test("writes overlay to .claude/CLAUDE.md when overlay is provided", async () => {
@@ -373,7 +374,7 @@ describe("ClaudeRuntime", () => {
373
374
  });
374
375
 
375
376
  afterEach(async () => {
376
- await rm(tempDir, { recursive: true, force: true });
377
+ await cleanupTempDir(tempDir);
377
378
  });
378
379
 
379
380
  test("returns null for non-existent file", async () => {
@@ -611,6 +612,6 @@ describe("ClaudeRuntime integration: registry resolves 'claude' as default", ()
611
612
  test("getRuntime rejects unknown runtimes", async () => {
612
613
  const { getRuntime } = await import("./registry.ts");
613
614
  expect(() => getRuntime("codex")).toThrow('Unknown runtime: "codex"');
614
- expect(() => getRuntime("pi")).toThrow('Unknown runtime: "pi"');
615
+ expect(() => getRuntime("opencode")).toThrow('Unknown runtime: "opencode"');
615
616
  });
616
617
  });