@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
@@ -761,7 +761,7 @@ describe("mailCommand", () => {
761
761
  capability: "coordinator",
762
762
  worktreePath: "/worktrees/orchestrator",
763
763
  branchName: "main",
764
- beadId: "bead-001",
764
+ taskId: "bead-001",
765
765
  tmuxSession: "overstory-test-orchestrator",
766
766
  state: "working" as const,
767
767
  pid: 12345,
@@ -779,7 +779,7 @@ describe("mailCommand", () => {
779
779
  capability: "builder",
780
780
  worktreePath: "/worktrees/builder-1",
781
781
  branchName: "builder-1",
782
- beadId: "bead-002",
782
+ taskId: "bead-002",
783
783
  tmuxSession: "overstory-test-builder-1",
784
784
  state: "working" as const,
785
785
  pid: 12346,
@@ -797,7 +797,7 @@ describe("mailCommand", () => {
797
797
  capability: "builder",
798
798
  worktreePath: "/worktrees/builder-2",
799
799
  branchName: "builder-2",
800
- beadId: "bead-003",
800
+ taskId: "bead-003",
801
801
  tmuxSession: "overstory-test-builder-2",
802
802
  state: "working" as const,
803
803
  pid: 12347,
@@ -815,7 +815,7 @@ describe("mailCommand", () => {
815
815
  capability: "scout",
816
816
  worktreePath: "/worktrees/scout-1",
817
817
  branchName: "scout-1",
818
- beadId: "bead-004",
818
+ taskId: "bead-004",
819
819
  tmuxSession: "overstory-test-scout-1",
820
820
  state: "working" as const,
821
821
  pid: 12348,
@@ -1135,7 +1135,7 @@ describe("mailCommand", () => {
1135
1135
  | "monitor",
1136
1136
  worktreePath: `/worktrees/${session.agentName}`,
1137
1137
  branchName: session.agentName,
1138
- beadId: `bead-${idx}`,
1138
+ taskId: `bead-${idx}`,
1139
1139
  tmuxSession: `overstory-test-${session.agentName}`,
1140
1140
  state: "working" as const,
1141
1141
  pid: 10000 + idx,
@@ -1175,7 +1175,7 @@ describe("mailCommand", () => {
1175
1175
  ]);
1176
1176
 
1177
1177
  // Verify warning on stderr
1178
- expect(stderrOutput).toContain("WARNING");
1178
+ expect(stderrOutput).toContain("Warning:");
1179
1179
  expect(stderrOutput).toContain("NO reviewer sessions found");
1180
1180
  expect(stderrOutput).toContain("lead-1");
1181
1181
  expect(stderrOutput).toContain("2 builder(s)");
@@ -1208,7 +1208,7 @@ describe("mailCommand", () => {
1208
1208
  ]);
1209
1209
 
1210
1210
  // Verify note on stderr
1211
- expect(stderrOutput).toContain("NOTE");
1211
+ expect(stderrOutput).toContain("Note:");
1212
1212
  expect(stderrOutput).toContain("Only 1 reviewer(s) for 3 builder(s)");
1213
1213
  expect(stderrOutput).toContain("review-verified");
1214
1214
  });
@@ -373,7 +373,7 @@ async function handleSend(opts: SendOpts, cwd: string): Promise<void> {
373
373
  );
374
374
  } else {
375
375
  process.stdout.write(
376
- `📢 Broadcast sent to ${recipients.length} recipient${recipients.length === 1 ? "" : "s"} (${to})\n`,
376
+ `Broadcast sent to ${recipients.length} recipient${recipients.length === 1 ? "" : "s"} (${to})\n`,
377
377
  );
378
378
  for (let i = 0; i < recipients.length; i++) {
379
379
  const recipient = recipients[i];
@@ -429,7 +429,7 @@ async function handleSend(opts: SendOpts, cwd: string): Promise<void> {
429
429
  if (opts.json) {
430
430
  process.stdout.write(`${JSON.stringify({ id })}\n`);
431
431
  } else {
432
- process.stdout.write(`✉️ Sent message ${id} to ${to}\n`);
432
+ process.stdout.write(`Sent message ${id} to ${to}\n`);
433
433
  }
434
434
 
435
435
  // Auto-nudge: write a pending nudge marker instead of sending tmux keys.
@@ -449,11 +449,28 @@ async function handleSend(opts: SendOpts, cwd: string): Promise<void> {
449
449
  });
450
450
  if (!opts.json) {
451
451
  process.stdout.write(
452
- `📢 Queued nudge for "${to}" (${nudgeReason}, delivered on next prompt)\n`,
452
+ `Queued nudge for "${to}" (${nudgeReason}, delivered on next prompt)\n`,
453
453
  );
454
454
  }
455
455
  }
456
456
 
457
+ // For dispatch messages, also send an immediate tmux nudge.
458
+ // Dispatch targets newly spawned agents that may be idle at the welcome
459
+ // screen where file-based nudges can't reach (no hook fires on idle agents).
460
+ // The I/O corruption concern (overstory-ii1o) only applies during active
461
+ // tool execution — newly spawned agents are idle, so sendKeys is safe.
462
+ if (type === "dispatch") {
463
+ try {
464
+ const { nudgeAgent } = await import("./nudge.ts");
465
+ const nudgeMessage = `[DISPATCH] ${subject}: ${body.slice(0, 500)}`;
466
+ // Small delay to let the agent's TUI stabilize after sling
467
+ await Bun.sleep(3_000);
468
+ await nudgeAgent(cwd, to, nudgeMessage, true); // force=true to skip debounce
469
+ } catch {
470
+ // Non-fatal: the file-based nudge is the fallback
471
+ }
472
+ }
473
+
457
474
  // Reviewer coverage check for merge_ready (advisory warning)
458
475
  if (type === "merge_ready") {
459
476
  try {
@@ -469,13 +486,13 @@ async function handleSend(opts: SendOpts, cwd: string): Promise<void> {
469
486
  );
470
487
  if (myBuilders.length > 0 && myReviewers.length === 0) {
471
488
  process.stderr.write(
472
- `\n⚠️ WARNING: merge_ready sent but NO reviewer sessions found for "${from}".\n` +
473
- `⚠️ ${myBuilders.length} builder(s) completed without review. This violates the review-before-merge requirement.\n` +
474
- `⚠️ Spawn reviewers for each builder before merge. See REVIEW_SKIP in agents/lead.md.\n\n`,
489
+ `\nWarning: merge_ready sent but NO reviewer sessions found for "${from}".\n` +
490
+ `${myBuilders.length} builder(s) completed without review. This violates the review-before-merge requirement.\n` +
491
+ `Spawn reviewers for each builder before merge. See REVIEW_SKIP in agents/lead.md.\n\n`,
475
492
  );
476
493
  } else if (myReviewers.length > 0 && myReviewers.length < myBuilders.length) {
477
494
  process.stderr.write(
478
- `\n⚠️ NOTE: Only ${myReviewers.length} reviewer(s) for ${myBuilders.length} builder(s). Ensure all builder work is review-verified.\n\n`,
495
+ `\nNote: Only ${myReviewers.length} reviewer(s) for ${myBuilders.length} builder(s). Ensure all builder work is review-verified.\n\n`,
479
496
  );
480
497
  }
481
498
  } finally {
@@ -528,7 +545,7 @@ async function handleCheck(opts: CheckOpts, cwd: string): Promise<void> {
528
545
 
529
546
  // Prepend a priority banner if there's a pending nudge
530
547
  if (pendingNudge) {
531
- const banner = `🚨 PRIORITY: ${pendingNudge.reason} message from ${pendingNudge.from} — "${pendingNudge.subject}"\n\n`;
548
+ const banner = `PRIORITY: ${pendingNudge.reason} message from ${pendingNudge.from} — "${pendingNudge.subject}"\n\n`;
532
549
  process.stdout.write(banner);
533
550
  }
534
551
 
@@ -544,7 +561,7 @@ async function handleCheck(opts: CheckOpts, cwd: string): Promise<void> {
544
561
  process.stdout.write("No new messages.\n");
545
562
  } else {
546
563
  process.stdout.write(
547
- `📬 ${messages.length} new message${messages.length === 1 ? "" : "s"}:\n\n`,
564
+ `${messages.length} new message${messages.length === 1 ? "" : "s"}:\n\n`,
548
565
  );
549
566
  for (const msg of messages) {
550
567
  process.stdout.write(`${formatMessage(msg)}\n\n`);
@@ -617,7 +634,7 @@ function handleReply(id: string, opts: ReplyOpts, cwd: string): void {
617
634
  if (opts.json) {
618
635
  process.stdout.write(`${JSON.stringify({ id: replyId })}\n`);
619
636
  } else {
620
- process.stdout.write(`✉️ Reply sent: ${replyId}\n`);
637
+ process.stdout.write(`Reply sent: ${replyId}\n`);
621
638
  }
622
639
  } finally {
623
640
  client.close();
@@ -677,7 +694,7 @@ export async function mailCommand(args: string[]): Promise<void> {
677
694
  const root = await resolveProjectRoot(process.cwd());
678
695
 
679
696
  const program = new Command();
680
- program.name("overstory mail").description("Agent messaging system").exitOverride();
697
+ program.name("ov mail").description("Agent messaging system").exitOverride();
681
698
 
682
699
  program
683
700
  .command("send")
@@ -207,7 +207,7 @@ merge:
207
207
 
208
208
  const parsed = JSON.parse(output);
209
209
  expect(parsed.agentName).toBe("my-builder");
210
- expect(parsed.beadId).toBe("bead-xyz");
210
+ expect(parsed.taskId).toBe("bead-xyz");
211
211
  });
212
212
  });
213
213
 
@@ -264,13 +264,13 @@ merge:
264
264
  const queue = createMergeQueue(queuePath);
265
265
  queue.enqueue({
266
266
  branchName: branch1,
267
- beadId: "bead-001",
267
+ taskId: "bead-001",
268
268
  agentName: "agent1",
269
269
  filesModified: [`src/${branch1}.ts`],
270
270
  });
271
271
  queue.enqueue({
272
272
  branchName: branch2,
273
- beadId: "bead-002",
273
+ taskId: "bead-002",
274
274
  agentName: "agent2",
275
275
  filesModified: [`src/${branch2}.ts`],
276
276
  });
@@ -306,13 +306,13 @@ merge:
306
306
  const queue = createMergeQueue(queuePath);
307
307
  queue.enqueue({
308
308
  branchName: branch1,
309
- beadId: "bead-100",
309
+ taskId: "bead-100",
310
310
  agentName: "builder1",
311
311
  filesModified: [`src/${branch1}.ts`],
312
312
  });
313
313
  queue.enqueue({
314
314
  branchName: branch2,
315
- beadId: "bead-200",
315
+ taskId: "bead-200",
316
316
  agentName: "builder2",
317
317
  filesModified: [`src/${branch2}.ts`],
318
318
  });
@@ -351,7 +351,7 @@ merge:
351
351
  const queue = createMergeQueue(queuePath);
352
352
  queue.enqueue({
353
353
  branchName: branch1,
354
- beadId: "bead-300",
354
+ taskId: "bead-300",
355
355
  agentName: "builder3",
356
356
  filesModified: [`src/${branch1}.ts`],
357
357
  });
@@ -449,13 +449,13 @@ merge:
449
449
  const queue = createMergeQueue(queuePath);
450
450
  queue.enqueue({
451
451
  branchName: branch1,
452
- beadId: "bead-into-all-1",
452
+ taskId: "bead-into-all-1",
453
453
  agentName: "agent1",
454
454
  filesModified: [`src/${branch1}.ts`],
455
455
  });
456
456
  queue.enqueue({
457
457
  branchName: branch2,
458
- beadId: "bead-into-all-2",
458
+ taskId: "bead-into-all-2",
459
459
  agentName: "agent2",
460
460
  filesModified: [`src/${branch2}.ts`],
461
461
  });
@@ -1,14 +1,14 @@
1
1
  /**
2
- * CLI command: overstory merge
2
+ * CLI command: ov merge
3
3
  *
4
4
  * Merges agent branches back to the canonical branch using
5
5
  * the merge queue and tiered conflict resolver.
6
6
  *
7
7
  * Usage:
8
- * overstory merge --branch <name> Merge a specific branch
9
- * overstory merge --all Merge all pending branches
10
- * overstory merge --dry-run Check for conflicts without merging
11
- * overstory merge --json Output results as JSON
8
+ * ov merge --branch <name> Merge a specific branch
9
+ * ov merge --all Merge all pending branches
10
+ * ov merge --dry-run Check for conflicts without merging
11
+ * ov merge --json Output results as JSON
12
12
  */
13
13
 
14
14
  import { join } from "node:path";
@@ -29,7 +29,7 @@ export interface MergeOptions {
29
29
 
30
30
  /**
31
31
  * Extract agent name from a branch following the overstory naming convention.
32
- * Pattern: overstory/{agentName}/{beadId}
32
+ * Pattern: overstory/{agentName}/{taskId}
33
33
  * Falls back to "unknown" if the pattern does not match.
34
34
  */
35
35
  function parseAgentName(branchName: string): string {
@@ -41,8 +41,8 @@ function parseAgentName(branchName: string): string {
41
41
  }
42
42
 
43
43
  /**
44
- * Extract bead ID from a branch following the overstory naming convention.
45
- * Pattern: overstory/{agentName}/{beadId}
44
+ * Extract task ID from a branch following the overstory naming convention.
45
+ * Pattern: overstory/{agentName}/{taskId}
46
46
  * Falls back to "unknown" if the pattern does not match.
47
47
  */
48
48
  function parseBeadId(branchName: string): string {
@@ -89,7 +89,7 @@ function formatResult(result: MergeResult): string {
89
89
  const statusIcon = result.success ? "Merged" : "Failed";
90
90
  const lines: string[] = [
91
91
  `Merging branch: ${result.entry.branchName}`,
92
- ` Agent: ${result.entry.agentName} | Task: ${result.entry.beadId}`,
92
+ ` Agent: ${result.entry.agentName} | Task: ${result.entry.taskId}`,
93
93
  ` Files: ${result.entry.filesModified.length} modified`,
94
94
  ` Result: ${statusIcon} (tier: ${result.tier})`,
95
95
  ];
@@ -109,7 +109,7 @@ function formatResult(result: MergeResult): string {
109
109
  function formatDryRun(entry: MergeEntry): string {
110
110
  const lines: string[] = [
111
111
  `[dry-run] Branch: ${entry.branchName}`,
112
- ` Agent: ${entry.agentName} | Task: ${entry.beadId}`,
112
+ ` Agent: ${entry.agentName} | Task: ${entry.taskId}`,
113
113
  ` Status: ${entry.status}`,
114
114
  ` Files: ${entry.filesModified.length} modified`,
115
115
  ];
@@ -124,7 +124,7 @@ function formatDryRun(entry: MergeEntry): string {
124
124
  }
125
125
 
126
126
  /**
127
- * Entry point for `overstory merge [flags]`.
127
+ * Entry point for `ov merge [flags]`.
128
128
  *
129
129
  * @param opts - Command options
130
130
  */
@@ -136,7 +136,7 @@ export async function mergeCommand(opts: MergeOptions): Promise<void> {
136
136
  const json = opts.json ?? false;
137
137
 
138
138
  if (!branchName && !all) {
139
- throw new ValidationError("Either --branch <name> or --all is required for overstory merge", {
139
+ throw new ValidationError("Either --branch <name> or --all is required for ov merge", {
140
140
  field: "branch|all",
141
141
  });
142
142
  }
@@ -176,7 +176,7 @@ export async function mergeCommand(opts: MergeOptions): Promise<void> {
176
176
  /**
177
177
  * Handle merging a specific branch.
178
178
  * If the branch is not in the queue, creates a new entry by detecting
179
- * agent name, bead ID, and modified files from git.
179
+ * agent name, task ID, and modified files from git.
180
180
  */
181
181
  async function handleBranch(
182
182
  branchName: string,
@@ -211,12 +211,12 @@ async function handleBranch(
211
211
  }
212
212
 
213
213
  const agentName = parseAgentName(branchName);
214
- const beadId = parseBeadId(branchName);
214
+ const taskId = parseBeadId(branchName);
215
215
  const filesModified = await detectModifiedFiles(repoRoot, canonicalBranch, branchName);
216
216
 
217
217
  entry = queue.enqueue({
218
218
  branchName,
219
- beadId,
219
+ taskId,
220
220
  agentName,
221
221
  filesModified,
222
222
  });
@@ -54,7 +54,7 @@ describe("metricsCommand", () => {
54
54
  function makeSession(overrides: Partial<SessionMetrics> = {}): SessionMetrics {
55
55
  return {
56
56
  agentName: "test-agent",
57
- beadId: "bead-001",
57
+ taskId: "bead-001",
58
58
  capability: "builder",
59
59
  startedAt: new Date(Date.now() - 120_000).toISOString(),
60
60
  completedAt: new Date().toISOString(),
@@ -151,7 +151,7 @@ describe("metricsCommand", () => {
151
151
  const out = output();
152
152
 
153
153
  // Check summary stats
154
- expect(out).toContain("📈 Session Metrics");
154
+ expect(out).toContain("Session Metrics");
155
155
  expect(out).toContain("Total sessions: 3");
156
156
  expect(out).toContain("Completed: 2");
157
157
  expect(out).toContain("Avg duration:");
@@ -177,7 +177,7 @@ describe("metricsCommand", () => {
177
177
  store.recordSession(
178
178
  makeSession({
179
179
  agentName: "test-builder",
180
- beadId: "bead-123",
180
+ taskId: "bead-123",
181
181
  capability: "builder",
182
182
  }),
183
183
  );
@@ -190,7 +190,7 @@ describe("metricsCommand", () => {
190
190
  const parsed = JSON.parse(out.trim()) as { sessions: SessionMetrics[] };
191
191
  expect(parsed.sessions).toHaveLength(1);
192
192
  expect(parsed.sessions[0]?.agentName).toBe("test-builder");
193
- expect(parsed.sessions[0]?.beadId).toBe("bead-123");
193
+ expect(parsed.sessions[0]?.taskId).toBe("bead-123");
194
194
  expect(parsed.sessions[0]?.capability).toBe("builder");
195
195
  });
196
196
 
@@ -203,7 +203,7 @@ describe("metricsCommand", () => {
203
203
  store.recordSession(
204
204
  makeSession({
205
205
  agentName: `agent-${i}`,
206
- beadId: `bead-${i}`,
206
+ taskId: `bead-${i}`,
207
207
  startedAt: new Date(Date.now() - (5 - i) * 1000).toISOString(),
208
208
  }),
209
209
  );
@@ -227,7 +227,7 @@ describe("metricsCommand", () => {
227
227
  store.recordSession(
228
228
  makeSession({
229
229
  agentName: `agent-${i}`,
230
- beadId: `bead-${i}`,
230
+ taskId: `bead-${i}`,
231
231
  }),
232
232
  );
233
233
  }
@@ -363,7 +363,7 @@ describe("formatDuration helper", () => {
363
363
  function makeSession(durationMs: number): SessionMetrics {
364
364
  return {
365
365
  agentName: "test-agent",
366
- beadId: "bead-001",
366
+ taskId: "bead-001",
367
367
  capability: "builder",
368
368
  startedAt: new Date(Date.now() - durationMs).toISOString(),
369
369
  completedAt: new Date().toISOString(),
@@ -1,5 +1,5 @@
1
1
  /**
2
- * CLI command: overstory metrics [--last <n>] [--json]
2
+ * CLI command: ov metrics [--last <n>] [--json]
3
3
  *
4
4
  * Shows metrics summary from SQLite store: session durations, success rates,
5
5
  * merge tier distribution, agent utilization.
@@ -63,7 +63,7 @@ async function executeMetrics(opts: MetricsOpts): Promise<void> {
63
63
  return;
64
64
  }
65
65
 
66
- process.stdout.write("📈 Session Metrics\n");
66
+ process.stdout.write("Session Metrics\n");
67
67
  process.stdout.write(`${"═".repeat(60)}\n\n`);
68
68
 
69
69
  // Summary stats
@@ -107,7 +107,7 @@ async function executeMetrics(opts: MetricsOpts): Promise<void> {
107
107
  const status = s.completedAt ? "done" : "running";
108
108
  const duration = formatDuration(s.durationMs);
109
109
  process.stdout.write(
110
- ` ${s.agentName} [${s.capability}] ${s.beadId} | ${status} | ${duration}\n`,
110
+ ` ${s.agentName} [${s.capability}] ${s.taskId} | ${status} | ${duration}\n`,
111
111
  );
112
112
  }
113
113
  } finally {
@@ -39,14 +39,14 @@ describe("buildMonitorBeacon", () => {
39
39
  expect(beacon).toContain("mulch prime");
40
40
  });
41
41
 
42
- test("contains startup instruction: overstory status --json", () => {
42
+ test("contains startup instruction: ov status --json", () => {
43
43
  const beacon = buildMonitorBeacon();
44
- expect(beacon).toContain("overstory status --json");
44
+ expect(beacon).toContain("ov status --json");
45
45
  });
46
46
 
47
- test("contains startup instruction: overstory mail check --agent monitor", () => {
47
+ test("contains startup instruction: ov mail check --agent monitor", () => {
48
48
  const beacon = buildMonitorBeacon();
49
- expect(beacon).toContain("overstory mail check --agent monitor");
49
+ expect(beacon).toContain("ov mail check --agent monitor");
50
50
  });
51
51
 
52
52
  test("contains startup instruction: patrol loop", () => {
@@ -86,7 +86,7 @@ describe("monitorCommand", () => {
86
86
  stdoutSpy.mockRestore();
87
87
  });
88
88
 
89
- test("--help prints help text containing 'overstory monitor'", async () => {
89
+ test("--help prints help text containing 'monitor'", async () => {
90
90
  await monitorCommand(["--help"]);
91
91
  const output = stdoutWrites.join("");
92
92
  expect(output).toContain("monitor");
@@ -1,5 +1,5 @@
1
1
  /**
2
- * CLI command: overstory monitor start|stop|status
2
+ * CLI command: ov monitor start|stop|status
3
3
  *
4
4
  * Manages the persistent Tier 2 monitor agent lifecycle. The monitor runs
5
5
  * at the project root (NOT in a worktree), continuously patrols the agent
@@ -9,7 +9,7 @@
9
9
  * Unlike regular agents spawned by sling, the monitor:
10
10
  * - Has no worktree (operates on the main working tree)
11
11
  * - Has no bead assignment (it monitors, not implements)
12
- * - Has no overlay CLAUDE.md (context comes via overstory status + mail)
12
+ * - Has no overlay CLAUDE.md (context comes via ov status + mail)
13
13
  * - Persists across patrol cycles
14
14
  */
15
15
 
@@ -46,7 +46,7 @@ export function buildMonitorBeacon(): string {
46
46
  const parts = [
47
47
  `[OVERSTORY] ${MONITOR_NAME} (monitor/tier-2) ${timestamp}`,
48
48
  "Depth: 0 | Parent: none | Role: continuous fleet patrol",
49
- `Startup: run mulch prime, check fleet (overstory status --json), check mail (overstory mail check --agent ${MONITOR_NAME}), then begin patrol loop`,
49
+ `Startup: run mulch prime, check fleet (ov status --json), check mail (ov mail check --agent ${MONITOR_NAME}), then begin patrol loop`,
50
50
  ];
51
51
  return parts.join(" — ");
52
52
  }
@@ -156,7 +156,7 @@ async function startMonitor(opts: { json: boolean; attach: boolean }): Promise<v
156
156
  capability: "monitor",
157
157
  worktreePath: projectRoot, // Monitor uses project root, not a worktree
158
158
  branchName: config.project.canonicalBranch, // Operates on canonical branch
159
- beadId: "", // No specific bead assignment
159
+ taskId: "", // No specific bead assignment
160
160
  tmuxSession,
161
161
  state: "booting",
162
162
  pid,
@@ -356,7 +356,7 @@ export function createMonitorCommand(): Command {
356
356
  }
357
357
 
358
358
  /**
359
- * Entry point for `overstory monitor <subcommand>`.
359
+ * Entry point for `ov monitor <subcommand>`.
360
360
  */
361
361
  export async function monitorCommand(args: string[]): Promise<void> {
362
362
  const cmd = createMonitorCommand();
@@ -46,7 +46,7 @@ function makeSession(overrides: Partial<AgentSession> = {}): AgentSession {
46
46
  capability: "builder",
47
47
  worktreePath: "/tmp/wt",
48
48
  branchName: "overstory/test-agent/task-1",
49
- beadId: "task-1",
49
+ taskId: "task-1",
50
50
  tmuxSession: "overstory-test-agent",
51
51
  state: "working",
52
52
  pid: 12345,
@@ -283,7 +283,7 @@ export async function nudgeAgent(
283
283
  export async function nudgeCommand(args: string[]): Promise<void> {
284
284
  const program = new Command();
285
285
  program
286
- .name("overstory nudge")
286
+ .name("ov nudge")
287
287
  .description("Send a text nudge to an agent")
288
288
  .argument("<agent-name>", "Name of the agent to nudge")
289
289
  .argument("[message...]", "Text to send (default: check mail prompt)")
@@ -134,7 +134,7 @@ expertiseDomains:
134
134
  - typescript
135
135
  - testing
136
136
  recentTasks:
137
- - beadId: task-001
137
+ - taskId: task-001
138
138
  summary: "Implemented feature X"
139
139
  completedAt: "2026-01-10T12:00:00Z"
140
140
  `,
@@ -161,7 +161,7 @@ recentTasks:
161
161
  capability: "builder",
162
162
  worktreePath: join(tempDir, ".overstory", "worktrees", "active-builder"),
163
163
  branchName: "overstory/active-builder/task-001",
164
- beadId: "task-001",
164
+ taskId: "task-001",
165
165
  tmuxSession: "overstory-active-builder",
166
166
  state: "working",
167
167
  pid: 12345,
@@ -198,7 +198,7 @@ recentTasks:
198
198
  capability: "builder",
199
199
  worktreePath: join(tempDir, ".overstory", "worktrees", "completed-builder"),
200
200
  branchName: "overstory/completed-builder/task-002",
201
- beadId: "task-002",
201
+ taskId: "task-002",
202
202
  tmuxSession: "overstory-completed-builder",
203
203
  state: "completed",
204
204
  pid: null,
@@ -233,7 +233,7 @@ recentTasks:
233
233
  `${JSON.stringify(
234
234
  {
235
235
  agentName: "recovery-agent",
236
- beadId: "task-003",
236
+ taskId: "task-003",
237
237
  sessionId: "session-003",
238
238
  timestamp: new Date().toISOString(),
239
239
  progressSummary: "Implemented initial tests for prime command",
@@ -361,7 +361,7 @@ recentTasks: []
361
361
 
362
362
  describe("Gitignore auto-heal", () => {
363
363
  const expectedGitignore = `# Wildcard+whitelist: ignore everything, whitelist tracked files
364
- # Auto-healed by overstory prime on each session start
364
+ # Auto-healed by ov prime on each session start
365
365
  *
366
366
  !.gitignore
367
367
  !config.yaml
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `overstory prime` command.
2
+ * `ov prime` command.
3
3
  *
4
4
  * Loads context for the orchestrator or a specific agent and outputs it
5
5
  * to stdout for injection into Claude Code's context via hooks.
@@ -24,7 +24,7 @@ import { getCurrentSessionName } from "../worktree/tmux.ts";
24
24
  * Wildcard+whitelist pattern: ignore everything except tracked config files.
25
25
  */
26
26
  const OVERSTORY_GITIGNORE = `# Wildcard+whitelist: ignore everything, whitelist tracked files
27
- # Auto-healed by overstory prime on each session start
27
+ # Auto-healed by ov prime on each session start
28
28
  *
29
29
  !.gitignore
30
30
  !config.yaml
@@ -65,7 +65,7 @@ function formatMetrics(sessions: SessionMetrics[]): string {
65
65
  const status = s.completedAt !== null ? "completed" : "in-progress";
66
66
  const duration = s.durationMs > 0 ? ` (${Math.round(s.durationMs / 1000)}s)` : "";
67
67
  const merge = s.mergeResult !== null ? ` [${s.mergeResult}]` : "";
68
- lines.push(`- ${s.agentName} (${s.capability}): ${s.beadId} — ${status}${duration}${merge}`);
68
+ lines.push(`- ${s.agentName} (${s.capability}): ${s.taskId} — ${status}${duration}${merge}`);
69
69
  }
70
70
  return lines.join("\n");
71
71
  }
@@ -86,7 +86,7 @@ function formatIdentity(identity: AgentIdentity): string {
86
86
  if (identity.recentTasks.length > 0) {
87
87
  lines.push("Recent tasks:");
88
88
  for (const task of identity.recentTasks) {
89
- lines.push(` - ${task.beadId}: ${task.summary} (${task.completedAt})`);
89
+ lines.push(` - ${task.taskId}: ${task.summary} (${task.completedAt})`);
90
90
  }
91
91
  }
92
92
 
@@ -184,7 +184,7 @@ async function outputAgentContext(
184
184
  const overstoryDir = join(config.project.root, ".overstory");
185
185
  const { store } = openSessionStore(overstoryDir);
186
186
  let sessionExists = false;
187
- let boundSession: { beadId: string } | null = null;
187
+ let boundSession: { taskId: string } | null = null;
188
188
  try {
189
189
  const agentSession = store.getByName(agentName);
190
190
  sessionExists = agentSession !== null;
@@ -192,9 +192,9 @@ async function outputAgentContext(
192
192
  agentSession &&
193
193
  agentSession.state !== "completed" &&
194
194
  agentSession.state !== "zombie" &&
195
- agentSession.beadId
195
+ agentSession.taskId
196
196
  ) {
197
- boundSession = { beadId: agentSession.beadId };
197
+ boundSession = { taskId: agentSession.taskId };
198
198
  }
199
199
  } finally {
200
200
  store.close();
@@ -226,7 +226,7 @@ async function outputAgentContext(
226
226
  // Activation context: if agent has a bound task, inject it
227
227
  if (boundSession) {
228
228
  sections.push("\n## Activation");
229
- sections.push(`You have a bound task: **${boundSession.beadId}**`);
229
+ sections.push(`You have a bound task: **${boundSession.taskId}**`);
230
230
  sections.push("Read your overlay at `.claude/CLAUDE.md` and begin working immediately.");
231
231
  sections.push("Do not wait for dispatch mail. Your assignment was bound at spawn time.");
232
232
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * CLI command: overstory replay [--run <id>] [--agent <name>...] [--json]
2
+ * CLI command: ov replay [--run <id>] [--agent <name>...] [--json]
3
3
  * [--since <ts>] [--until <ts>] [--limit <n>]
4
4
  *
5
5
  * Shows an interleaved chronological replay of events across multiple agents.
@@ -68,7 +68,7 @@ function makeSession(overrides: Partial<AgentSession> = {}): AgentSession {
68
68
  capability: "builder",
69
69
  worktreePath: "/tmp/worktrees/test-agent",
70
70
  branchName: "overstory/test-agent/task-1",
71
- beadId: "task-1",
71
+ taskId: "task-1",
72
72
  tmuxSession: "overstory-test-agent",
73
73
  state: "working",
74
74
  pid: 12345,
@@ -354,13 +354,13 @@ export function createRunCommand(): Command {
354
354
  }
355
355
 
356
356
  export async function runCommand(args: string[]): Promise<void> {
357
- const program = new Command("overstory").exitOverride().configureOutput({
357
+ const program = new Command("ov").exitOverride().configureOutput({
358
358
  writeOut: (str) => process.stdout.write(str),
359
359
  writeErr: (str) => process.stderr.write(str),
360
360
  });
361
361
  program.addCommand(createRunCommand());
362
362
  try {
363
- await program.parseAsync(["node", "overstory", "run", ...args]);
363
+ await program.parseAsync(["node", "ov", "run", ...args]);
364
364
  } catch (err: unknown) {
365
365
  if (err instanceof CommanderError) {
366
366
  if (err.code === "commander.helpDisplayed" || err.code === "commander.version") return;