@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.
- package/README.md +61 -61
- package/agents/builder.md +16 -16
- package/agents/coordinator.md +57 -57
- package/agents/issue-reviews.md +71 -0
- package/agents/lead.md +43 -42
- package/agents/merger.md +15 -15
- package/agents/monitor.md +37 -37
- package/agents/pr-reviews.md +60 -0
- package/agents/prioritize.md +110 -0
- package/agents/release.md +56 -0
- package/agents/reviewer.md +15 -15
- package/agents/scout.md +18 -18
- package/agents/supervisor.md +78 -78
- package/package.json +1 -1
- package/src/agents/checkpoint.test.ts +2 -2
- package/src/agents/hooks-deployer.test.ts +59 -25
- package/src/agents/hooks-deployer.ts +24 -6
- package/src/agents/identity.test.ts +27 -27
- package/src/agents/identity.ts +10 -10
- package/src/agents/lifecycle.test.ts +6 -6
- package/src/agents/lifecycle.ts +2 -2
- package/src/agents/overlay.test.ts +14 -14
- package/src/agents/overlay.ts +14 -14
- package/src/commands/agents.test.ts +5 -5
- package/src/commands/agents.ts +10 -9
- package/src/commands/clean.test.ts +5 -5
- package/src/commands/clean.ts +5 -5
- package/src/commands/completions.test.ts +10 -10
- package/src/commands/completions.ts +26 -28
- package/src/commands/coordinator.test.ts +4 -4
- package/src/commands/coordinator.ts +13 -13
- package/src/commands/costs.test.ts +45 -45
- package/src/commands/costs.ts +1 -1
- package/src/commands/dashboard.ts +11 -11
- package/src/commands/doctor.ts +4 -4
- package/src/commands/errors.ts +1 -1
- package/src/commands/feed.ts +1 -1
- package/src/commands/group.ts +3 -3
- package/src/commands/hooks.test.ts +7 -7
- package/src/commands/hooks.ts +7 -7
- package/src/commands/init.test.ts +6 -2
- package/src/commands/init.ts +19 -19
- package/src/commands/inspect.test.ts +16 -16
- package/src/commands/inspect.ts +19 -19
- package/src/commands/log.test.ts +21 -21
- package/src/commands/log.ts +10 -10
- package/src/commands/logs.ts +1 -1
- package/src/commands/mail.test.ts +7 -7
- package/src/commands/mail.ts +28 -11
- package/src/commands/merge.test.ts +8 -8
- package/src/commands/merge.ts +15 -15
- package/src/commands/metrics.test.ts +7 -7
- package/src/commands/metrics.ts +3 -3
- package/src/commands/monitor.test.ts +5 -5
- package/src/commands/monitor.ts +5 -5
- package/src/commands/nudge.test.ts +1 -1
- package/src/commands/nudge.ts +1 -1
- package/src/commands/prime.test.ts +5 -5
- package/src/commands/prime.ts +8 -8
- package/src/commands/replay.ts +1 -1
- package/src/commands/run.test.ts +1 -1
- package/src/commands/run.ts +2 -2
- package/src/commands/sling.test.ts +89 -7
- package/src/commands/sling.ts +109 -18
- package/src/commands/spec.test.ts +2 -2
- package/src/commands/spec.ts +13 -14
- package/src/commands/status.test.ts +99 -3
- package/src/commands/status.ts +19 -20
- package/src/commands/stop.test.ts +1 -1
- package/src/commands/stop.ts +2 -2
- package/src/commands/supervisor.test.ts +10 -10
- package/src/commands/supervisor.ts +14 -14
- package/src/commands/trace.test.ts +7 -7
- package/src/commands/trace.ts +10 -10
- package/src/commands/watch.ts +5 -5
- package/src/commands/worktree.test.ts +208 -32
- package/src/commands/worktree.ts +56 -18
- package/src/doctor/consistency.test.ts +14 -14
- package/src/doctor/dependencies.test.ts +5 -5
- package/src/doctor/dependencies.ts +2 -2
- package/src/doctor/logs.ts +1 -1
- package/src/doctor/merge-queue.test.ts +4 -4
- package/src/doctor/structure.test.ts +1 -1
- package/src/doctor/structure.ts +1 -1
- package/src/doctor/version.test.ts +3 -3
- package/src/doctor/version.ts +1 -1
- package/src/e2e/init-sling-lifecycle.test.ts +8 -4
- package/src/errors.ts +1 -1
- package/src/index.ts +13 -11
- package/src/mail/broadcast.test.ts +1 -1
- package/src/mail/client.test.ts +7 -7
- package/src/mail/client.ts +2 -2
- package/src/mail/store.test.ts +3 -3
- package/src/merge/queue.test.ts +12 -12
- package/src/merge/queue.ts +2 -2
- package/src/merge/resolver.test.ts +159 -7
- package/src/merge/resolver.ts +46 -2
- package/src/metrics/store.test.ts +44 -44
- package/src/metrics/store.ts +2 -2
- package/src/metrics/summary.test.ts +35 -35
- package/src/mulch/client.test.ts +1 -1
- package/src/mulch/client.ts +1 -1
- package/src/sessions/compat.test.ts +3 -3
- package/src/sessions/compat.ts +1 -1
- package/src/sessions/store.test.ts +4 -4
- package/src/sessions/store.ts +2 -2
- package/src/types.ts +14 -14
- package/src/watchdog/daemon.test.ts +10 -10
- package/src/watchdog/daemon.ts +1 -1
- package/src/watchdog/health.test.ts +1 -1
- package/src/worktree/manager.test.ts +20 -20
- package/src/worktree/manager.ts +120 -4
- package/src/worktree/tmux.test.ts +8 -3
- package/src/worktree/tmux.ts +19 -18
- package/templates/CLAUDE.md.tmpl +27 -27
- package/templates/hooks.json.tmpl +15 -11
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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("
|
|
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 === "
|
|
204
|
+
const ovCheck = checks.find((c) => c.name === "ov availability");
|
|
205
205
|
if (ovCheck?.status === "pass") {
|
|
206
|
-
const ovAlias = checks.find((c) => c.name === "
|
|
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
|
|
232
|
+
test("includes ov availability check", async () => {
|
|
233
233
|
const checks = await checkDependencies(mockConfig, "/tmp/.overstory");
|
|
234
|
-
const ovCheck = checks.find((c) => c.name === "
|
|
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: "
|
|
45
|
+
name: "ov",
|
|
46
46
|
versionFlag: "--version",
|
|
47
47
|
required: true,
|
|
48
|
-
alias: "
|
|
48
|
+
alias: "overstory",
|
|
49
49
|
installHint: "@os-eco/overstory-cli",
|
|
50
50
|
},
|
|
51
51
|
{
|
package/src/doctor/logs.ts
CHANGED
|
@@ -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 '
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
107
|
+
# Auto-healed by ov prime on each session start
|
|
108
108
|
*
|
|
109
109
|
!.gitignore
|
|
110
110
|
!config.yaml
|
package/src/doctor/structure.ts
CHANGED
|
@@ -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 '
|
|
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("
|
|
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 "
|
|
92
|
-
expect(versionCheck.message).toMatch(/
|
|
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
|
|
package/src/doctor/version.ts
CHANGED
|
@@ -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: `
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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("
|
|
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
|
|
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:
|
|
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.
|
|
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("
|
|
116
|
+
.name("ov")
|
|
117
117
|
.description("Multi-agent orchestration for Claude Code")
|
|
118
|
-
.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 (
|
|
184
|
-
await specWriteCommand(
|
|
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 '
|
|
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 '
|
|
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.
|
|
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.
|
|
323
|
+
process.exitCode = 1;
|
|
324
|
+
return;
|
|
323
325
|
}
|
|
324
326
|
process.stderr.write(`Unknown error: ${String(err)}\n`);
|
|
325
|
-
process.
|
|
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
|
-
|
|
33
|
+
taskId: "bead-001",
|
|
34
34
|
tmuxSession: `overstory-test-${agentName}`,
|
|
35
35
|
state: "working",
|
|
36
36
|
pid: 12345,
|
package/src/mail/client.test.ts
CHANGED
|
@@ -225,7 +225,7 @@ describe("createMailClient", () => {
|
|
|
225
225
|
});
|
|
226
226
|
|
|
227
227
|
const result = client.checkInject("orchestrator");
|
|
228
|
-
expect(result).toContain(`
|
|
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
|
-
|
|
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",
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
730
|
+
taskId: "beads-abc",
|
|
731
731
|
branch: "agent/builder-1",
|
|
732
732
|
exitCode: 0,
|
|
733
733
|
filesModified: ["src/foo.ts"],
|
package/src/mail/client.ts
CHANGED
|
@@ -96,7 +96,7 @@ function formatForInjection(messages: MailMessage[]): string {
|
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
const lines: string[] = [
|
|
99
|
-
|
|
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:
|
|
111
|
+
lines.push(`[Reply with: ov mail reply ${msg.id} --body "..."]`);
|
|
112
112
|
lines.push("");
|
|
113
113
|
}
|
|
114
114
|
|
package/src/mail/store.test.ts
CHANGED
|
@@ -638,7 +638,7 @@ describe("createMailStore", () => {
|
|
|
638
638
|
|
|
639
639
|
test("stores JSON payload string", () => {
|
|
640
640
|
const payload = JSON.stringify({
|
|
641
|
-
|
|
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",
|
|
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
|
-
|
|
685
|
+
taskId: "beads-xyz",
|
|
686
686
|
tier: "clean-merge",
|
|
687
687
|
});
|
|
688
688
|
store.insert({
|
package/src/merge/queue.test.ts
CHANGED
|
@@ -23,14 +23,14 @@ describe("createMergeQueue", () => {
|
|
|
23
23
|
function makeInput(
|
|
24
24
|
overrides?: Partial<{
|
|
25
25
|
branchName: string;
|
|
26
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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",
|
|
86
|
-
queue.enqueue(makeInput({ branchName: "branch-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]?.
|
|
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
|
-
|
|
372
|
+
taskId: "bead-2",
|
|
373
373
|
agentName: "test",
|
|
374
374
|
filesModified: ["src/b.ts"],
|
|
375
375
|
});
|
|
376
|
-
expect(newEntry.
|
|
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
|
-
|
|
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]?.
|
|
397
|
+
expect(entries[0]?.taskId).toBe("bead-1");
|
|
398
398
|
queue2.close();
|
|
399
399
|
});
|
|
400
400
|
});
|
package/src/merge/queue.ts
CHANGED
|
@@ -74,7 +74,7 @@ function rowToEntry(row: MergeQueueRow): MergeEntry {
|
|
|
74
74
|
|
|
75
75
|
return {
|
|
76
76
|
branchName: row.branch_name,
|
|
77
|
-
|
|
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.
|
|
175
|
+
$task_id: input.taskId,
|
|
176
176
|
$agent_name: input.agentName,
|
|
177
177
|
$files_modified: filesModifiedJson,
|
|
178
178
|
$enqueued_at: enqueuedAt,
|