@os-eco/overstory-cli 0.6.4 → 0.6.6

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 (117) hide show
  1. package/README.md +61 -61
  2. package/agents/builder.md +16 -16
  3. package/agents/coordinator.md +57 -57
  4. package/agents/issue-reviews.md +71 -0
  5. package/agents/lead.md +43 -42
  6. package/agents/merger.md +15 -15
  7. package/agents/monitor.md +37 -37
  8. package/agents/pr-reviews.md +60 -0
  9. package/agents/prioritize.md +110 -0
  10. package/agents/release.md +56 -0
  11. package/agents/reviewer.md +15 -15
  12. package/agents/scout.md +18 -18
  13. package/agents/supervisor.md +78 -78
  14. package/package.json +1 -1
  15. package/src/agents/checkpoint.test.ts +2 -2
  16. package/src/agents/hooks-deployer.test.ts +59 -25
  17. package/src/agents/hooks-deployer.ts +24 -6
  18. package/src/agents/identity.test.ts +27 -27
  19. package/src/agents/identity.ts +10 -10
  20. package/src/agents/lifecycle.test.ts +6 -6
  21. package/src/agents/lifecycle.ts +2 -2
  22. package/src/agents/overlay.test.ts +14 -14
  23. package/src/agents/overlay.ts +14 -14
  24. package/src/commands/agents.test.ts +5 -5
  25. package/src/commands/agents.ts +10 -9
  26. package/src/commands/clean.test.ts +5 -5
  27. package/src/commands/clean.ts +5 -5
  28. package/src/commands/completions.test.ts +10 -10
  29. package/src/commands/completions.ts +26 -28
  30. package/src/commands/coordinator.test.ts +4 -4
  31. package/src/commands/coordinator.ts +13 -13
  32. package/src/commands/costs.test.ts +45 -45
  33. package/src/commands/costs.ts +1 -1
  34. package/src/commands/dashboard.ts +11 -11
  35. package/src/commands/doctor.ts +4 -4
  36. package/src/commands/errors.ts +1 -1
  37. package/src/commands/feed.ts +1 -1
  38. package/src/commands/group.ts +3 -3
  39. package/src/commands/hooks.test.ts +7 -7
  40. package/src/commands/hooks.ts +7 -7
  41. package/src/commands/init.test.ts +6 -2
  42. package/src/commands/init.ts +19 -19
  43. package/src/commands/inspect.test.ts +16 -16
  44. package/src/commands/inspect.ts +19 -19
  45. package/src/commands/log.test.ts +21 -21
  46. package/src/commands/log.ts +10 -10
  47. package/src/commands/logs.ts +1 -1
  48. package/src/commands/mail.test.ts +7 -7
  49. package/src/commands/mail.ts +28 -11
  50. package/src/commands/merge.test.ts +8 -8
  51. package/src/commands/merge.ts +15 -15
  52. package/src/commands/metrics.test.ts +7 -7
  53. package/src/commands/metrics.ts +3 -3
  54. package/src/commands/monitor.test.ts +5 -5
  55. package/src/commands/monitor.ts +5 -5
  56. package/src/commands/nudge.test.ts +1 -1
  57. package/src/commands/nudge.ts +1 -1
  58. package/src/commands/prime.test.ts +5 -5
  59. package/src/commands/prime.ts +8 -8
  60. package/src/commands/replay.ts +1 -1
  61. package/src/commands/run.test.ts +1 -1
  62. package/src/commands/run.ts +2 -2
  63. package/src/commands/sling.test.ts +89 -7
  64. package/src/commands/sling.ts +109 -18
  65. package/src/commands/spec.test.ts +2 -2
  66. package/src/commands/spec.ts +13 -14
  67. package/src/commands/status.test.ts +99 -3
  68. package/src/commands/status.ts +19 -20
  69. package/src/commands/stop.test.ts +1 -1
  70. package/src/commands/stop.ts +2 -2
  71. package/src/commands/supervisor.test.ts +10 -10
  72. package/src/commands/supervisor.ts +14 -14
  73. package/src/commands/trace.test.ts +7 -7
  74. package/src/commands/trace.ts +10 -10
  75. package/src/commands/watch.ts +5 -5
  76. package/src/commands/worktree.test.ts +208 -32
  77. package/src/commands/worktree.ts +56 -18
  78. package/src/doctor/consistency.test.ts +14 -14
  79. package/src/doctor/dependencies.test.ts +5 -5
  80. package/src/doctor/dependencies.ts +2 -2
  81. package/src/doctor/logs.ts +1 -1
  82. package/src/doctor/merge-queue.test.ts +4 -4
  83. package/src/doctor/structure.test.ts +1 -1
  84. package/src/doctor/structure.ts +1 -1
  85. package/src/doctor/version.test.ts +3 -3
  86. package/src/doctor/version.ts +1 -1
  87. package/src/e2e/init-sling-lifecycle.test.ts +8 -4
  88. package/src/errors.ts +1 -1
  89. package/src/index.ts +13 -11
  90. package/src/mail/broadcast.test.ts +1 -1
  91. package/src/mail/client.test.ts +7 -7
  92. package/src/mail/client.ts +2 -2
  93. package/src/mail/store.test.ts +3 -3
  94. package/src/merge/queue.test.ts +12 -12
  95. package/src/merge/queue.ts +2 -2
  96. package/src/merge/resolver.test.ts +159 -7
  97. package/src/merge/resolver.ts +46 -2
  98. package/src/metrics/store.test.ts +44 -44
  99. package/src/metrics/store.ts +2 -2
  100. package/src/metrics/summary.test.ts +35 -35
  101. package/src/mulch/client.test.ts +1 -1
  102. package/src/mulch/client.ts +1 -1
  103. package/src/sessions/compat.test.ts +3 -3
  104. package/src/sessions/compat.ts +1 -1
  105. package/src/sessions/store.test.ts +4 -4
  106. package/src/sessions/store.ts +2 -2
  107. package/src/types.ts +14 -14
  108. package/src/watchdog/daemon.test.ts +10 -10
  109. package/src/watchdog/daemon.ts +1 -1
  110. package/src/watchdog/health.test.ts +1 -1
  111. package/src/worktree/manager.test.ts +20 -20
  112. package/src/worktree/manager.ts +120 -4
  113. package/src/worktree/tmux.test.ts +8 -3
  114. package/src/worktree/tmux.ts +19 -18
  115. package/templates/CLAUDE.md.tmpl +27 -27
  116. package/templates/hooks.json.tmpl +15 -11
  117. package/templates/overlay.md.tmpl +7 -7
@@ -195,7 +195,7 @@ describe("checkConsistency", () => {
195
195
  capability: "builder",
196
196
  worktreePath: join(overstoryDir, "worktrees", "dead-agent"),
197
197
  branchName: "overstory/dead-agent/test-123",
198
- beadId: "test-123",
198
+ taskId: "test-123",
199
199
  tmuxSession: "overstory-testproject-dead-agent",
200
200
  state: "working",
201
201
  pid: 99999, // Non-existent PID
@@ -231,7 +231,7 @@ describe("checkConsistency", () => {
231
231
  capability: "builder",
232
232
  worktreePath: join(overstoryDir, "worktrees", "live-agent"),
233
233
  branchName: "overstory/live-agent/test-123",
234
- beadId: "test-123",
234
+ taskId: "test-123",
235
235
  tmuxSession: "overstory-testproject-live-agent",
236
236
  state: "working",
237
237
  pid: 12345,
@@ -266,7 +266,7 @@ describe("checkConsistency", () => {
266
266
  capability: "builder",
267
267
  worktreePath: missingWorktreePath,
268
268
  branchName: "overstory/missing-agent/test-123",
269
- beadId: "test-123",
269
+ taskId: "test-123",
270
270
  tmuxSession: "overstory-testproject-missing-agent",
271
271
  state: "working",
272
272
  pid: null,
@@ -302,7 +302,7 @@ describe("checkConsistency", () => {
302
302
  capability: "builder",
303
303
  worktreePath,
304
304
  branchName: "overstory/agent-without-tmux/test-123",
305
- beadId: "test-123",
305
+ taskId: "test-123",
306
306
  tmuxSession: "overstory-testproject-agent-without-tmux",
307
307
  state: "working",
308
308
  pid: null,
@@ -341,7 +341,7 @@ describe("checkConsistency", () => {
341
341
  capability: "builder",
342
342
  worktreePath,
343
343
  branchName: "overstory/consistent-agent/test-123",
344
- beadId: "test-123",
344
+ taskId: "test-123",
345
345
  tmuxSession: "overstory-testproject-consistent-agent",
346
346
  state: "working",
347
347
  pid: 12345,
@@ -414,7 +414,7 @@ describe("checkConsistency", () => {
414
414
  capability: "builder",
415
415
  worktreePath: join(overstoryDir, "worktrees", "builder-1"),
416
416
  branchName: "overstory/builder-1/test-123",
417
- beadId: "test-123",
417
+ taskId: "test-123",
418
418
  tmuxSession: "overstory-testproject-builder-1",
419
419
  state: "working",
420
420
  pid: null,
@@ -433,7 +433,7 @@ describe("checkConsistency", () => {
433
433
  capability: "builder",
434
434
  worktreePath: join(overstoryDir, "worktrees", "builder-2"),
435
435
  branchName: "overstory/builder-2/test-456",
436
- beadId: "test-456",
436
+ taskId: "test-456",
437
437
  tmuxSession: "overstory-testproject-builder-2",
438
438
  state: "working",
439
439
  pid: null,
@@ -469,7 +469,7 @@ describe("checkConsistency", () => {
469
469
  capability: "builder",
470
470
  worktreePath: join(overstoryDir, "worktrees", `builder-${i}`),
471
471
  branchName: `overstory/builder-${i}/test-${i}`,
472
- beadId: `test-${i}`,
472
+ taskId: `test-${i}`,
473
473
  tmuxSession: `overstory-testproject-builder-${i}`,
474
474
  state: "working",
475
475
  pid: null,
@@ -489,7 +489,7 @@ describe("checkConsistency", () => {
489
489
  capability: "reviewer",
490
490
  worktreePath: join(overstoryDir, "worktrees", "reviewer-1"),
491
491
  branchName: "overstory/reviewer-1/test-r1",
492
- beadId: "test-r1",
492
+ taskId: "test-r1",
493
493
  tmuxSession: "overstory-testproject-reviewer-1",
494
494
  state: "working",
495
495
  pid: null,
@@ -523,7 +523,7 @@ describe("checkConsistency", () => {
523
523
  capability: "builder",
524
524
  worktreePath: join(overstoryDir, "worktrees", `builder-${i}`),
525
525
  branchName: `overstory/builder-${i}/test-${i}`,
526
- beadId: `test-${i}`,
526
+ taskId: `test-${i}`,
527
527
  tmuxSession: `overstory-testproject-builder-${i}`,
528
528
  state: "working",
529
529
  pid: null,
@@ -542,7 +542,7 @@ describe("checkConsistency", () => {
542
542
  capability: "reviewer",
543
543
  worktreePath: join(overstoryDir, "worktrees", `reviewer-${i}`),
544
544
  branchName: `overstory/reviewer-${i}/test-r${i}`,
545
- beadId: `test-r${i}`,
545
+ taskId: `test-r${i}`,
546
546
  tmuxSession: `overstory-testproject-reviewer-${i}`,
547
547
  state: "working",
548
548
  pid: null,
@@ -585,7 +585,7 @@ describe("checkConsistency", () => {
585
585
  capability: "builder",
586
586
  worktreePath: join(overstoryDir, "worktrees", "builder-1"),
587
587
  branchName: "overstory/builder-1/test-1",
588
- beadId: "test-1",
588
+ taskId: "test-1",
589
589
  tmuxSession: "overstory-testproject-builder-1",
590
590
  state: "working",
591
591
  pid: null,
@@ -604,7 +604,7 @@ describe("checkConsistency", () => {
604
604
  capability: "reviewer",
605
605
  worktreePath: join(overstoryDir, "worktrees", "reviewer-1"),
606
606
  branchName: "overstory/reviewer-1/test-r1",
607
- beadId: "test-r1",
607
+ taskId: "test-r1",
608
608
  tmuxSession: "overstory-testproject-reviewer-1",
609
609
  state: "working",
610
610
  pid: null,
@@ -624,7 +624,7 @@ describe("checkConsistency", () => {
624
624
  capability: "builder",
625
625
  worktreePath: join(overstoryDir, "worktrees", "builder-2"),
626
626
  branchName: "overstory/builder-2/test-2",
627
- beadId: "test-2",
627
+ taskId: "test-2",
628
628
  tmuxSession: "overstory-testproject-builder-2",
629
629
  state: "working",
630
630
  pid: null,
@@ -66,7 +66,7 @@ describe("checkDependencies", () => {
66
66
  expect(toolNames).toContain("tmux availability");
67
67
  expect(toolNames).toContain("sd availability");
68
68
  expect(toolNames).toContain("mulch availability");
69
- expect(toolNames).toContain("overstory availability");
69
+ expect(toolNames).toContain("ov availability");
70
70
  expect(toolNames).toContain("cn availability");
71
71
  });
72
72
 
@@ -201,9 +201,9 @@ describe("checkDependencies", () => {
201
201
  expect(mlAlias?.category).toBe("dependencies");
202
202
  expect(["pass", "warn"]).toContain(mlAlias?.status ?? "");
203
203
  }
204
- const ovCheck = checks.find((c) => c.name === "overstory availability");
204
+ const ovCheck = checks.find((c) => c.name === "ov availability");
205
205
  if (ovCheck?.status === "pass") {
206
- const ovAlias = checks.find((c) => c.name === "ov alias");
206
+ const ovAlias = checks.find((c) => c.name === "overstory alias");
207
207
  expect(ovAlias).toBeDefined();
208
208
  expect(["pass", "warn"]).toContain(ovAlias?.status ?? "");
209
209
  }
@@ -229,9 +229,9 @@ describe("checkDependencies", () => {
229
229
  }
230
230
  });
231
231
 
232
- test("includes overstory availability check", async () => {
232
+ test("includes ov availability check", async () => {
233
233
  const checks = await checkDependencies(mockConfig, "/tmp/.overstory");
234
- const ovCheck = checks.find((c) => c.name === "overstory availability");
234
+ const ovCheck = checks.find((c) => c.name === "ov availability");
235
235
  expect(ovCheck).toBeDefined();
236
236
  expect(ovCheck?.category).toBe("dependencies");
237
237
  });
@@ -42,10 +42,10 @@ export const checkDependencies: DoctorCheckFn = async (
42
42
  installHint: "@os-eco/mulch-cli",
43
43
  },
44
44
  {
45
- name: "overstory",
45
+ name: "ov",
46
46
  versionFlag: "--version",
47
47
  required: true,
48
- alias: "ov",
48
+ alias: "overstory",
49
49
  installHint: "@os-eco/overstory-cli",
50
50
  },
51
51
  {
@@ -177,7 +177,7 @@ export const checkLogs: DoctorCheckFn = async (_config, overstoryDir): Promise<D
177
177
  totalBytes > DISK_USAGE_WARN_THRESHOLD
178
178
  ? [
179
179
  `Exceeds ${formatBytes(DISK_USAGE_WARN_THRESHOLD)} threshold`,
180
- "Consider running 'overstory worktree clean --completed' to remove old logs",
180
+ "Consider running 'ov worktree clean --completed' to remove old logs",
181
181
  ]
182
182
  : undefined,
183
183
  fixable: totalBytes > DISK_USAGE_WARN_THRESHOLD,
@@ -77,13 +77,13 @@ describe("checkMergeQueue", () => {
77
77
  const queue = createMergeQueue(dbPath);
78
78
  queue.enqueue({
79
79
  branchName: "feature/test",
80
- beadId: "beads-abc",
80
+ taskId: "beads-abc",
81
81
  agentName: "test-agent",
82
82
  filesModified: ["src/test.ts"],
83
83
  });
84
84
  queue.enqueue({
85
85
  branchName: "feature/another",
86
- beadId: "beads-def",
86
+ taskId: "beads-def",
87
87
  agentName: "another-agent",
88
88
  filesModified: ["src/another.ts"],
89
89
  });
@@ -194,13 +194,13 @@ describe("checkMergeQueue", () => {
194
194
  const queue = createMergeQueue(dbPath);
195
195
  queue.enqueue({
196
196
  branchName: "feature/duplicate",
197
- beadId: "beads-abc",
197
+ taskId: "beads-abc",
198
198
  agentName: "test-agent",
199
199
  filesModified: ["src/test.ts"],
200
200
  });
201
201
  queue.enqueue({
202
202
  branchName: "feature/duplicate",
203
- beadId: "beads-def",
203
+ taskId: "beads-def",
204
204
  agentName: "another-agent",
205
205
  filesModified: ["src/another.ts"],
206
206
  });
@@ -104,7 +104,7 @@ describe("checkStructure", () => {
104
104
  await Bun.write(
105
105
  join(overstoryDir, ".gitignore"),
106
106
  `# Wildcard+whitelist: ignore everything, whitelist tracked files
107
- # Auto-healed by overstory prime on each session start
107
+ # Auto-healed by ov prime on each session start
108
108
  *
109
109
  !.gitignore
110
110
  !config.yaml
@@ -32,7 +32,7 @@ export const checkStructure: DoctorCheckFn = async (
32
32
  category: "structure",
33
33
  status: overstoryDirExists ? "pass" : "fail",
34
34
  message: overstoryDirExists ? "Directory exists" : "Directory missing",
35
- details: overstoryDirExists ? undefined : ["Run 'overstory init' to create it"],
35
+ details: overstoryDirExists ? undefined : ["Run 'ov init' to create it"],
36
36
  fixable: !overstoryDirExists,
37
37
  });
38
38
 
@@ -70,7 +70,7 @@ describe("checkVersion", () => {
70
70
  const versionCheck = checks.find((c) => c.name === "version-current");
71
71
  expect(versionCheck).toBeDefined();
72
72
  expect(versionCheck?.status).toBeOneOf(["pass", "warn", "fail"]);
73
- expect(versionCheck?.message).toContain("overstory");
73
+ expect(versionCheck?.message).toContain("ov");
74
74
  });
75
75
 
76
76
  test("includes package-json-sync check", async () => {
@@ -88,8 +88,8 @@ describe("checkVersion", () => {
88
88
  expect(versionCheck).toBeDefined();
89
89
 
90
90
  if (versionCheck?.status === "pass") {
91
- // Message should contain version in format "overstory vX.Y.Z"
92
- expect(versionCheck.message).toMatch(/overstory v\d+\.\d+\.\d+/);
91
+ // Message should contain version in format "ov vX.Y.Z"
92
+ expect(versionCheck.message).toMatch(/ov v\d+\.\d+\.\d+/);
93
93
  }
94
94
  });
95
95
 
@@ -47,7 +47,7 @@ async function checkCurrentVersion(toolRoot: string): Promise<DoctorCheck> {
47
47
  name: "version-current",
48
48
  category: "version",
49
49
  status: "pass",
50
- message: `overstory v${packageJson.version}`,
50
+ message: `ov v${packageJson.version}`,
51
51
  };
52
52
  } catch (error) {
53
53
  return {
@@ -22,9 +22,13 @@ import type { OverlayConfig } from "../types.ts";
22
22
  const EXPECTED_AGENT_DEFS = [
23
23
  "builder.md",
24
24
  "coordinator.md",
25
+ "issue-reviews.md",
25
26
  "lead.md",
26
27
  "merger.md",
27
28
  "monitor.md",
29
+ "pr-reviews.md",
30
+ "prioritize.md",
31
+ "release.md",
28
32
  "reviewer.md",
29
33
  "scout.md",
30
34
  "supervisor.md",
@@ -109,7 +113,7 @@ describe("E2E: init→sling lifecycle on external project", () => {
109
113
  expect(config.project.name).toBeTruthy();
110
114
  });
111
115
 
112
- test("manifest loads successfully with all 8 agents", async () => {
116
+ test("manifest loads successfully with all 12 agents", async () => {
113
117
  await initCommand({});
114
118
 
115
119
  const manifestPath = join(tempDir, ".overstory", "agent-manifest.json");
@@ -174,7 +178,7 @@ describe("E2E: init→sling lifecycle on external project", () => {
174
178
 
175
179
  const overlayConfig: OverlayConfig = {
176
180
  agentName: "test-agent",
177
- beadId: "test-bead-001",
181
+ taskId: "test-bead-001",
178
182
  specPath: null,
179
183
  branchName: "overstory/test-agent/test-bead-001",
180
184
  worktreePath: join(tempDir, ".overstory", "worktrees", "test-agent"),
@@ -241,7 +245,7 @@ describe("E2E: init→sling lifecycle on external project", () => {
241
245
  const builderDef = await Bun.file(join(agentDefsDir, "builder.md")).text();
242
246
  const overlayConfig: OverlayConfig = {
243
247
  agentName: "lifecycle-builder",
244
- beadId: "lifecycle-001",
248
+ taskId: "lifecycle-001",
245
249
  specPath: join(tempDir, ".overstory", "specs", "lifecycle-001.md"),
246
250
  branchName: "overstory/lifecycle-builder/lifecycle-001",
247
251
  worktreePath: join(tempDir, ".overstory", "worktrees", "lifecycle-builder"),
@@ -269,7 +273,7 @@ describe("E2E: init→sling lifecycle on external project", () => {
269
273
  expect(overlayContent).toContain("orchestrator");
270
274
  expect(overlayContent).toContain("`src/main.ts`");
271
275
  expect(overlayContent).toContain("`src/utils.ts`");
272
- expect(overlayContent).toContain("mulch prime typescript");
276
+ expect(overlayContent).toContain("ml prime typescript");
273
277
 
274
278
  // No unresolved placeholders
275
279
  expect(overlayContent).not.toMatch(/\{\{[A-Z_]+\}\}/);
package/src/errors.ts CHANGED
@@ -152,7 +152,7 @@ export class MergeError extends OverstoryError {
152
152
 
153
153
  /**
154
154
  * Raised when input validation fails.
155
- * Examples: invalid agent names, malformed beadIds, bad CLI arguments.
155
+ * Examples: invalid agent names, malformed taskIds, bad CLI arguments.
156
156
  */
157
157
  export class ValidationError extends OverstoryError {
158
158
  readonly field: string | null;
package/src/index.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  * Overstory CLI — main entry point and command router.
5
5
  *
6
6
  * Routes subcommands to their respective handlers in src/commands/.
7
- * Usage: overstory <command> [args...]
7
+ * Usage: ov <command> [args...]
8
8
  */
9
9
 
10
10
  import { Command } from "commander";
@@ -42,7 +42,7 @@ import { createWorktreeCommand } from "./commands/worktree.ts";
42
42
  import { OverstoryError, WorktreeError } from "./errors.ts";
43
43
  import { setQuiet } from "./logging/color.ts";
44
44
 
45
- const VERSION = "0.6.4";
45
+ const VERSION = "0.6.6";
46
46
 
47
47
  const COMMANDS = [
48
48
  "agents",
@@ -113,9 +113,9 @@ function suggestCommand(input: string): string | undefined {
113
113
  const program = new Command();
114
114
 
115
115
  program
116
- .name("overstory")
116
+ .name("ov")
117
117
  .description("Multi-agent orchestration for Claude Code")
118
- .version(`overstory v${VERSION}`, "-v, --version")
118
+ .version(`ov v${VERSION}`, "-v, --version")
119
119
  .option("-q, --quiet", "Suppress non-error output")
120
120
  .option("--json", "JSON output")
121
121
  .option("--verbose", "Verbose output");
@@ -180,8 +180,8 @@ specCmd
180
180
  .argument("<bead-id>", "Task ID for the spec file")
181
181
  .option("--body <content>", "Spec content (or pipe via stdin)")
182
182
  .option("--agent <name>", "Agent writing the spec (for attribution)")
183
- .action(async (beadId, opts) => {
184
- await specWriteCommand(beadId, opts);
183
+ .action(async (taskId, opts) => {
184
+ await specWriteCommand(taskId, opts);
185
185
  });
186
186
 
187
187
  program
@@ -295,7 +295,7 @@ program.on("command:*", (operands) => {
295
295
  if (suggestion) {
296
296
  process.stderr.write(`Did you mean '${suggestion}'?\n`);
297
297
  }
298
- process.stderr.write("Run 'overstory --help' for usage.\n");
298
+ process.stderr.write("Run 'ov --help' for usage.\n");
299
299
  process.exit(1);
300
300
  });
301
301
 
@@ -307,20 +307,22 @@ if (import.meta.main)
307
307
  main().catch((err: unknown) => {
308
308
  // Friendly message when running outside a git repository
309
309
  if (err instanceof WorktreeError && err.message.includes("not a git repository")) {
310
- process.stderr.write("Not in an overstory project. Run 'overstory init' first.\n");
310
+ process.stderr.write("Not in an overstory project. Run 'ov init' first.\n");
311
311
  process.exit(1);
312
312
  }
313
313
  if (err instanceof OverstoryError) {
314
314
  process.stderr.write(`Error [${err.code}]: ${err.message}\n`);
315
- process.exit(1);
315
+ process.exitCode = 1;
316
+ return;
316
317
  }
317
318
  if (err instanceof Error) {
318
319
  process.stderr.write(`Error: ${err.message}\n`);
319
320
  if (process.argv.includes("--verbose")) {
320
321
  process.stderr.write(`${err.stack}\n`);
321
322
  }
322
- process.exit(1);
323
+ process.exitCode = 1;
324
+ return;
323
325
  }
324
326
  process.stderr.write(`Unknown error: ${String(err)}\n`);
325
- process.exit(1);
327
+ process.exitCode = 1;
326
328
  });
@@ -30,7 +30,7 @@ describe("resolveGroupAddress", () => {
30
30
  capability,
31
31
  worktreePath: `/worktrees/${agentName}`,
32
32
  branchName: `branch-${agentName}`,
33
- beadId: "bead-001",
33
+ taskId: "bead-001",
34
34
  tmuxSession: `overstory-test-${agentName}`,
35
35
  state: "working",
36
36
  pid: 12345,
@@ -225,7 +225,7 @@ describe("createMailClient", () => {
225
225
  });
226
226
 
227
227
  const result = client.checkInject("orchestrator");
228
- expect(result).toContain(`overstory mail reply ${id}`);
228
+ expect(result).toContain(`ov mail reply ${id}`);
229
229
  });
230
230
 
231
231
  test("formats multiple messages with correct count", () => {
@@ -598,7 +598,7 @@ describe("createMailClient", () => {
598
598
  describe("sendProtocol", () => {
599
599
  test("sends a worker_done message with serialized payload", () => {
600
600
  const payload: WorkerDonePayload = {
601
- beadId: "beads-abc",
601
+ taskId: "beads-abc",
602
602
  branch: "agent/builder-1",
603
603
  exitCode: 0,
604
604
  filesModified: ["src/foo.ts", "src/bar.ts"],
@@ -625,7 +625,7 @@ describe("createMailClient", () => {
625
625
  subject: "Merged",
626
626
  body: "Branch merged",
627
627
  type: "merged",
628
- payload: { branch: "agent/b1", beadId: "beads-xyz", tier: "clean-merge" as const },
628
+ payload: { branch: "agent/b1", taskId: "beads-xyz", tier: "clean-merge" as const },
629
629
  });
630
630
 
631
631
  const msg = store.getById(id);
@@ -640,7 +640,7 @@ describe("createMailClient", () => {
640
640
  body: "Build failing",
641
641
  type: "escalation",
642
642
  priority: "urgent",
643
- payload: { severity: "critical" as const, beadId: null, context: "OOM" },
643
+ payload: { severity: "critical" as const, taskId: null, context: "OOM" },
644
644
  });
645
645
 
646
646
  const msg = store.getById(id);
@@ -656,7 +656,7 @@ describe("createMailClient", () => {
656
656
  type: "assign",
657
657
  threadId: "thread-dispatch-1",
658
658
  payload: {
659
- beadId: "beads-123",
659
+ taskId: "beads-123",
660
660
  specPath: ".overstory/specs/beads-123.md",
661
661
  workerName: "builder-1",
662
662
  branch: "agent/builder-1",
@@ -671,7 +671,7 @@ describe("createMailClient", () => {
671
671
  describe("parsePayload", () => {
672
672
  test("parses a valid JSON payload", () => {
673
673
  const payload: WorkerDonePayload = {
674
- beadId: "beads-abc",
674
+ taskId: "beads-abc",
675
675
  branch: "agent/builder-1",
676
676
  exitCode: 0,
677
677
  filesModified: ["src/foo.ts"],
@@ -727,7 +727,7 @@ describe("createMailClient", () => {
727
727
  describe("checkInject with protocol messages", () => {
728
728
  test("includes payload in injection output for protocol messages", () => {
729
729
  const payload: WorkerDonePayload = {
730
- beadId: "beads-abc",
730
+ taskId: "beads-abc",
731
731
  branch: "agent/builder-1",
732
732
  exitCode: 0,
733
733
  filesModified: ["src/foo.ts"],
@@ -96,7 +96,7 @@ function formatForInjection(messages: MailMessage[]): string {
96
96
  }
97
97
 
98
98
  const lines: string[] = [
99
- `📬 You have ${messages.length} new message${messages.length === 1 ? "" : "s"}:`,
99
+ `You have ${messages.length} new message${messages.length === 1 ? "" : "s"}:`,
100
100
  "",
101
101
  ];
102
102
 
@@ -108,7 +108,7 @@ function formatForInjection(messages: MailMessage[]): string {
108
108
  if (msg.payload !== null && PROTOCOL_TYPES.has(msg.type)) {
109
109
  lines.push(`Payload: ${msg.payload}`);
110
110
  }
111
- lines.push(`[Reply with: overstory mail reply ${msg.id} --body "..."]`);
111
+ lines.push(`[Reply with: ov mail reply ${msg.id} --body "..."]`);
112
112
  lines.push("");
113
113
  }
114
114
 
@@ -638,7 +638,7 @@ describe("createMailStore", () => {
638
638
 
639
639
  test("stores JSON payload string", () => {
640
640
  const payload = JSON.stringify({
641
- beadId: "beads-abc",
641
+ taskId: "beads-abc",
642
642
  branch: "agent/builder-1",
643
643
  exitCode: 0,
644
644
  filesModified: ["src/foo.ts"],
@@ -661,7 +661,7 @@ describe("createMailStore", () => {
661
661
  });
662
662
 
663
663
  test("returns payload in getUnread results", () => {
664
- const payload = JSON.stringify({ severity: "critical", beadId: null, context: "OOM" });
664
+ const payload = JSON.stringify({ severity: "critical", taskId: null, context: "OOM" });
665
665
  store.insert({
666
666
  id: "msg-escalation",
667
667
  from: "builder-1",
@@ -682,7 +682,7 @@ describe("createMailStore", () => {
682
682
  test("returns payload in getAll results", () => {
683
683
  const payload = JSON.stringify({
684
684
  branch: "agent/b1",
685
- beadId: "beads-xyz",
685
+ taskId: "beads-xyz",
686
686
  tier: "clean-merge",
687
687
  });
688
688
  store.insert({
@@ -23,14 +23,14 @@ describe("createMergeQueue", () => {
23
23
  function makeInput(
24
24
  overrides?: Partial<{
25
25
  branchName: string;
26
- beadId: string;
26
+ taskId: string;
27
27
  agentName: string;
28
28
  filesModified: string[];
29
29
  }>,
30
30
  ) {
31
31
  return {
32
32
  branchName: overrides?.branchName ?? "overstory/test-agent/bead-123",
33
- beadId: overrides?.beadId ?? "bead-123",
33
+ taskId: overrides?.taskId ?? "bead-123",
34
34
  agentName: overrides?.agentName ?? "test-agent",
35
35
  filesModified: overrides?.filesModified ?? ["src/test.ts"],
36
36
  };
@@ -52,7 +52,7 @@ describe("createMergeQueue", () => {
52
52
  const after = new Date().toISOString();
53
53
 
54
54
  expect(entry.branchName).toBe("overstory/test-agent/bead-123");
55
- expect(entry.beadId).toBe("bead-123");
55
+ expect(entry.taskId).toBe("bead-123");
56
56
  expect(entry.agentName).toBe("test-agent");
57
57
  expect(entry.filesModified).toEqual(["src/test.ts"]);
58
58
  expect(entry.enqueuedAt).toBeDefined();
@@ -65,7 +65,7 @@ describe("createMergeQueue", () => {
65
65
  const queue = createMergeQueue(queuePath);
66
66
  const input = makeInput({
67
67
  branchName: "overstory/builder-1/bead-xyz",
68
- beadId: "bead-xyz",
68
+ taskId: "bead-xyz",
69
69
  agentName: "builder-1",
70
70
  filesModified: ["src/a.ts", "src/b.ts"],
71
71
  });
@@ -73,7 +73,7 @@ describe("createMergeQueue", () => {
73
73
  const entry = queue.enqueue(input);
74
74
 
75
75
  expect(entry.branchName).toBe("overstory/builder-1/bead-xyz");
76
- expect(entry.beadId).toBe("bead-xyz");
76
+ expect(entry.taskId).toBe("bead-xyz");
77
77
  expect(entry.agentName).toBe("builder-1");
78
78
  expect(entry.filesModified).toEqual(["src/a.ts", "src/b.ts"]);
79
79
  });
@@ -82,8 +82,8 @@ describe("createMergeQueue", () => {
82
82
  describe("dequeue", () => {
83
83
  test("returns first pending entry (FIFO)", () => {
84
84
  const queue = createMergeQueue(queuePath);
85
- queue.enqueue(makeInput({ branchName: "branch-a", beadId: "bead-a" }));
86
- queue.enqueue(makeInput({ branchName: "branch-b", beadId: "bead-b" }));
85
+ queue.enqueue(makeInput({ branchName: "branch-a", taskId: "bead-a" }));
86
+ queue.enqueue(makeInput({ branchName: "branch-b", taskId: "bead-b" }));
87
87
 
88
88
  const dequeued = queue.dequeue();
89
89
 
@@ -363,17 +363,17 @@ describe("createMergeQueue", () => {
363
363
  const entries = queue.list();
364
364
 
365
365
  expect(entries).toHaveLength(1);
366
- expect(entries[0]?.beadId).toBe("bead-1");
366
+ expect(entries[0]?.taskId).toBe("bead-1");
367
367
  expect(entries[0]?.branchName).toBe("overstory/test/bead-1");
368
368
 
369
369
  // New inserts should also work
370
370
  const newEntry = queue.enqueue({
371
371
  branchName: "overstory/test/bead-2",
372
- beadId: "bead-2",
372
+ taskId: "bead-2",
373
373
  agentName: "test",
374
374
  filesModified: ["src/b.ts"],
375
375
  });
376
- expect(newEntry.beadId).toBe("bead-2");
376
+ expect(newEntry.taskId).toBe("bead-2");
377
377
 
378
378
  queue.close();
379
379
  });
@@ -383,7 +383,7 @@ describe("createMergeQueue", () => {
383
383
  const queue1 = createMergeQueue(queuePath);
384
384
  queue1.enqueue({
385
385
  branchName: "overstory/test/bead-1",
386
- beadId: "bead-1",
386
+ taskId: "bead-1",
387
387
  agentName: "test",
388
388
  filesModified: [],
389
389
  });
@@ -394,7 +394,7 @@ describe("createMergeQueue", () => {
394
394
  const entries = queue2.list();
395
395
 
396
396
  expect(entries).toHaveLength(1);
397
- expect(entries[0]?.beadId).toBe("bead-1");
397
+ expect(entries[0]?.taskId).toBe("bead-1");
398
398
  queue2.close();
399
399
  });
400
400
  });
@@ -74,7 +74,7 @@ function rowToEntry(row: MergeQueueRow): MergeEntry {
74
74
 
75
75
  return {
76
76
  branchName: row.branch_name,
77
- beadId: row.task_id,
77
+ taskId: row.task_id,
78
78
  agentName: row.agent_name,
79
79
  filesModified,
80
80
  enqueuedAt: row.enqueued_at,
@@ -172,7 +172,7 @@ export function createMergeQueue(dbPath: string): MergeQueue {
172
172
 
173
173
  const row = insertStmt.get({
174
174
  $branch_name: input.branchName,
175
- $task_id: input.beadId,
175
+ $task_id: input.taskId,
176
176
  $agent_name: input.agentName,
177
177
  $files_modified: filesModifiedJson,
178
178
  $enqueued_at: enqueuedAt,