@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
|
@@ -22,7 +22,7 @@ import { costsCommand } from "./costs.ts";
|
|
|
22
22
|
function makeMetrics(overrides: Partial<SessionMetrics> = {}): SessionMetrics {
|
|
23
23
|
return {
|
|
24
24
|
agentName: "builder-1",
|
|
25
|
-
|
|
25
|
+
taskId: "task-001",
|
|
26
26
|
capability: "builder",
|
|
27
27
|
startedAt: new Date().toISOString(),
|
|
28
28
|
completedAt: new Date().toISOString(),
|
|
@@ -142,8 +142,8 @@ describe("costsCommand", () => {
|
|
|
142
142
|
test("outputs valid JSON array with sessions", async () => {
|
|
143
143
|
const dbPath = join(tempDir, ".overstory", "metrics.db");
|
|
144
144
|
const store = createMetricsStore(dbPath);
|
|
145
|
-
store.recordSession(makeMetrics({ agentName: "builder-1",
|
|
146
|
-
store.recordSession(makeMetrics({ agentName: "scout-1",
|
|
145
|
+
store.recordSession(makeMetrics({ agentName: "builder-1", taskId: "t1" }));
|
|
146
|
+
store.recordSession(makeMetrics({ agentName: "scout-1", taskId: "t2", capability: "scout" }));
|
|
147
147
|
store.close();
|
|
148
148
|
|
|
149
149
|
await costsCommand(["--json"]);
|
|
@@ -160,7 +160,7 @@ describe("costsCommand", () => {
|
|
|
160
160
|
store.recordSession(
|
|
161
161
|
makeMetrics({
|
|
162
162
|
agentName: "builder-1",
|
|
163
|
-
|
|
163
|
+
taskId: "t1",
|
|
164
164
|
inputTokens: 100,
|
|
165
165
|
outputTokens: 50,
|
|
166
166
|
cacheReadTokens: 30,
|
|
@@ -187,7 +187,7 @@ describe("costsCommand", () => {
|
|
|
187
187
|
test("JSON output returns empty array when no sessions match", async () => {
|
|
188
188
|
const dbPath = join(tempDir, ".overstory", "metrics.db");
|
|
189
189
|
const store = createMetricsStore(dbPath);
|
|
190
|
-
store.recordSession(makeMetrics({ agentName: "builder-1",
|
|
190
|
+
store.recordSession(makeMetrics({ agentName: "builder-1", taskId: "t1" }));
|
|
191
191
|
store.close();
|
|
192
192
|
|
|
193
193
|
await costsCommand(["--json", "--agent", "nonexistent"]);
|
|
@@ -203,7 +203,7 @@ describe("costsCommand", () => {
|
|
|
203
203
|
store.recordSession(
|
|
204
204
|
makeMetrics({
|
|
205
205
|
agentName: "builder-1",
|
|
206
|
-
|
|
206
|
+
taskId: "t1",
|
|
207
207
|
capability: "builder",
|
|
208
208
|
inputTokens: 100,
|
|
209
209
|
}),
|
|
@@ -211,7 +211,7 @@ describe("costsCommand", () => {
|
|
|
211
211
|
store.recordSession(
|
|
212
212
|
makeMetrics({
|
|
213
213
|
agentName: "scout-1",
|
|
214
|
-
|
|
214
|
+
taskId: "t2",
|
|
215
215
|
capability: "scout",
|
|
216
216
|
inputTokens: 50,
|
|
217
217
|
}),
|
|
@@ -238,7 +238,7 @@ describe("costsCommand", () => {
|
|
|
238
238
|
test("shows Cost Summary header", async () => {
|
|
239
239
|
const dbPath = join(tempDir, ".overstory", "metrics.db");
|
|
240
240
|
const store = createMetricsStore(dbPath);
|
|
241
|
-
store.recordSession(makeMetrics({ agentName: "builder-1",
|
|
241
|
+
store.recordSession(makeMetrics({ agentName: "builder-1", taskId: "t1" }));
|
|
242
242
|
store.close();
|
|
243
243
|
|
|
244
244
|
await costsCommand([]);
|
|
@@ -250,7 +250,7 @@ describe("costsCommand", () => {
|
|
|
250
250
|
test("shows column headers", async () => {
|
|
251
251
|
const dbPath = join(tempDir, ".overstory", "metrics.db");
|
|
252
252
|
const store = createMetricsStore(dbPath);
|
|
253
|
-
store.recordSession(makeMetrics({ agentName: "builder-1",
|
|
253
|
+
store.recordSession(makeMetrics({ agentName: "builder-1", taskId: "t1" }));
|
|
254
254
|
store.close();
|
|
255
255
|
|
|
256
256
|
await costsCommand([]);
|
|
@@ -270,7 +270,7 @@ describe("costsCommand", () => {
|
|
|
270
270
|
store.recordSession(
|
|
271
271
|
makeMetrics({
|
|
272
272
|
agentName: "builder-1",
|
|
273
|
-
|
|
273
|
+
taskId: "t1",
|
|
274
274
|
capability: "builder",
|
|
275
275
|
}),
|
|
276
276
|
);
|
|
@@ -286,7 +286,7 @@ describe("costsCommand", () => {
|
|
|
286
286
|
test("shows separator line", async () => {
|
|
287
287
|
const dbPath = join(tempDir, ".overstory", "metrics.db");
|
|
288
288
|
const store = createMetricsStore(dbPath);
|
|
289
|
-
store.recordSession(makeMetrics({ agentName: "builder-1",
|
|
289
|
+
store.recordSession(makeMetrics({ agentName: "builder-1", taskId: "t1" }));
|
|
290
290
|
store.close();
|
|
291
291
|
|
|
292
292
|
await costsCommand([]);
|
|
@@ -298,7 +298,7 @@ describe("costsCommand", () => {
|
|
|
298
298
|
test("shows Total row", async () => {
|
|
299
299
|
const dbPath = join(tempDir, ".overstory", "metrics.db");
|
|
300
300
|
const store = createMetricsStore(dbPath);
|
|
301
|
-
store.recordSession(makeMetrics({ agentName: "builder-1",
|
|
301
|
+
store.recordSession(makeMetrics({ agentName: "builder-1", taskId: "t1" }));
|
|
302
302
|
store.close();
|
|
303
303
|
|
|
304
304
|
await costsCommand([]);
|
|
@@ -329,7 +329,7 @@ describe("costsCommand", () => {
|
|
|
329
329
|
store.recordSession(
|
|
330
330
|
makeMetrics({
|
|
331
331
|
agentName: "builder-1",
|
|
332
|
-
|
|
332
|
+
taskId: "t1",
|
|
333
333
|
inputTokens: 12345,
|
|
334
334
|
outputTokens: 5678,
|
|
335
335
|
}),
|
|
@@ -349,7 +349,7 @@ describe("costsCommand", () => {
|
|
|
349
349
|
store.recordSession(
|
|
350
350
|
makeMetrics({
|
|
351
351
|
agentName: "builder-1",
|
|
352
|
-
|
|
352
|
+
taskId: "t1",
|
|
353
353
|
estimatedCostUsd: 0.42,
|
|
354
354
|
}),
|
|
355
355
|
);
|
|
@@ -367,7 +367,7 @@ describe("costsCommand", () => {
|
|
|
367
367
|
store.recordSession(
|
|
368
368
|
makeMetrics({
|
|
369
369
|
agentName: "builder-1",
|
|
370
|
-
|
|
370
|
+
taskId: "t1",
|
|
371
371
|
inputTokens: 0,
|
|
372
372
|
outputTokens: 0,
|
|
373
373
|
cacheReadTokens: 0,
|
|
@@ -389,7 +389,7 @@ describe("costsCommand", () => {
|
|
|
389
389
|
store.recordSession(
|
|
390
390
|
makeMetrics({
|
|
391
391
|
agentName: "builder-1",
|
|
392
|
-
|
|
392
|
+
taskId: "t1",
|
|
393
393
|
estimatedCostUsd: null,
|
|
394
394
|
}),
|
|
395
395
|
);
|
|
@@ -408,8 +408,8 @@ describe("costsCommand", () => {
|
|
|
408
408
|
test("filters sessions by agent name", async () => {
|
|
409
409
|
const dbPath = join(tempDir, ".overstory", "metrics.db");
|
|
410
410
|
const store = createMetricsStore(dbPath);
|
|
411
|
-
store.recordSession(makeMetrics({ agentName: "builder-1",
|
|
412
|
-
store.recordSession(makeMetrics({ agentName: "scout-1",
|
|
411
|
+
store.recordSession(makeMetrics({ agentName: "builder-1", taskId: "t1", inputTokens: 100 }));
|
|
412
|
+
store.recordSession(makeMetrics({ agentName: "scout-1", taskId: "t2", inputTokens: 200 }));
|
|
413
413
|
store.close();
|
|
414
414
|
|
|
415
415
|
await costsCommand(["--json", "--agent", "builder-1"]);
|
|
@@ -423,7 +423,7 @@ describe("costsCommand", () => {
|
|
|
423
423
|
test("returns empty for non-existent agent", async () => {
|
|
424
424
|
const dbPath = join(tempDir, ".overstory", "metrics.db");
|
|
425
425
|
const store = createMetricsStore(dbPath);
|
|
426
|
-
store.recordSession(makeMetrics({ agentName: "builder-1",
|
|
426
|
+
store.recordSession(makeMetrics({ agentName: "builder-1", taskId: "t1" }));
|
|
427
427
|
store.close();
|
|
428
428
|
|
|
429
429
|
await costsCommand(["--json", "--agent", "nonexistent"]);
|
|
@@ -441,12 +441,12 @@ describe("costsCommand", () => {
|
|
|
441
441
|
const dbPath = join(tempDir, ".overstory", "metrics.db");
|
|
442
442
|
const store = createMetricsStore(dbPath);
|
|
443
443
|
store.recordSession(
|
|
444
|
-
makeMetrics({ agentName: "builder-1",
|
|
444
|
+
makeMetrics({ agentName: "builder-1", taskId: "task-001", runId: "run-2026-01-01" }),
|
|
445
445
|
);
|
|
446
446
|
store.recordSession(
|
|
447
447
|
makeMetrics({
|
|
448
448
|
agentName: "scout-1",
|
|
449
|
-
|
|
449
|
+
taskId: "task-002",
|
|
450
450
|
capability: "scout",
|
|
451
451
|
runId: "run-other",
|
|
452
452
|
}),
|
|
@@ -465,7 +465,7 @@ describe("costsCommand", () => {
|
|
|
465
465
|
const dbPath = join(tempDir, ".overstory", "metrics.db");
|
|
466
466
|
const store = createMetricsStore(dbPath);
|
|
467
467
|
store.recordSession(
|
|
468
|
-
makeMetrics({ agentName: "builder-1",
|
|
468
|
+
makeMetrics({ agentName: "builder-1", taskId: "t1", runId: "run-2026-01-01" }),
|
|
469
469
|
);
|
|
470
470
|
store.close();
|
|
471
471
|
|
|
@@ -484,7 +484,7 @@ describe("costsCommand", () => {
|
|
|
484
484
|
const dbPath = join(tempDir, ".overstory", "metrics.db");
|
|
485
485
|
const store = createMetricsStore(dbPath);
|
|
486
486
|
store.recordSession(
|
|
487
|
-
makeMetrics({ agentName: "builder-1",
|
|
487
|
+
makeMetrics({ agentName: "builder-1", taskId: "t1", capability: "builder" }),
|
|
488
488
|
);
|
|
489
489
|
store.close();
|
|
490
490
|
|
|
@@ -498,7 +498,7 @@ describe("costsCommand", () => {
|
|
|
498
498
|
const dbPath = join(tempDir, ".overstory", "metrics.db");
|
|
499
499
|
const store = createMetricsStore(dbPath);
|
|
500
500
|
store.recordSession(
|
|
501
|
-
makeMetrics({ agentName: "builder-1",
|
|
501
|
+
makeMetrics({ agentName: "builder-1", taskId: "t1", capability: "builder" }),
|
|
502
502
|
);
|
|
503
503
|
store.close();
|
|
504
504
|
|
|
@@ -514,7 +514,7 @@ describe("costsCommand", () => {
|
|
|
514
514
|
store.recordSession(
|
|
515
515
|
makeMetrics({
|
|
516
516
|
agentName: "builder-1",
|
|
517
|
-
|
|
517
|
+
taskId: "t1",
|
|
518
518
|
capability: "builder",
|
|
519
519
|
inputTokens: 1000,
|
|
520
520
|
}),
|
|
@@ -522,7 +522,7 @@ describe("costsCommand", () => {
|
|
|
522
522
|
store.recordSession(
|
|
523
523
|
makeMetrics({
|
|
524
524
|
agentName: "builder-2",
|
|
525
|
-
|
|
525
|
+
taskId: "t2",
|
|
526
526
|
capability: "builder",
|
|
527
527
|
inputTokens: 2000,
|
|
528
528
|
}),
|
|
@@ -530,7 +530,7 @@ describe("costsCommand", () => {
|
|
|
530
530
|
store.recordSession(
|
|
531
531
|
makeMetrics({
|
|
532
532
|
agentName: "scout-1",
|
|
533
|
-
|
|
533
|
+
taskId: "t3",
|
|
534
534
|
capability: "scout",
|
|
535
535
|
inputTokens: 500,
|
|
536
536
|
}),
|
|
@@ -548,10 +548,10 @@ describe("costsCommand", () => {
|
|
|
548
548
|
test("shows correct session count per capability", async () => {
|
|
549
549
|
const dbPath = join(tempDir, ".overstory", "metrics.db");
|
|
550
550
|
const store = createMetricsStore(dbPath);
|
|
551
|
-
store.recordSession(makeMetrics({ agentName: "b1",
|
|
552
|
-
store.recordSession(makeMetrics({ agentName: "b2",
|
|
553
|
-
store.recordSession(makeMetrics({ agentName: "b3",
|
|
554
|
-
store.recordSession(makeMetrics({ agentName: "s1",
|
|
551
|
+
store.recordSession(makeMetrics({ agentName: "b1", taskId: "t1", capability: "builder" }));
|
|
552
|
+
store.recordSession(makeMetrics({ agentName: "b2", taskId: "t2", capability: "builder" }));
|
|
553
|
+
store.recordSession(makeMetrics({ agentName: "b3", taskId: "t3", capability: "builder" }));
|
|
554
|
+
store.recordSession(makeMetrics({ agentName: "s1", taskId: "t4", capability: "scout" }));
|
|
555
555
|
store.close();
|
|
556
556
|
|
|
557
557
|
await costsCommand(["--json", "--by-capability"]);
|
|
@@ -584,7 +584,7 @@ describe("costsCommand", () => {
|
|
|
584
584
|
const dbPath = join(tempDir, ".overstory", "metrics.db");
|
|
585
585
|
const store = createMetricsStore(dbPath);
|
|
586
586
|
for (let i = 0; i < 10; i++) {
|
|
587
|
-
store.recordSession(makeMetrics({ agentName: `agent-${i}`,
|
|
587
|
+
store.recordSession(makeMetrics({ agentName: `agent-${i}`, taskId: `t-${i}` }));
|
|
588
588
|
}
|
|
589
589
|
store.close();
|
|
590
590
|
|
|
@@ -599,7 +599,7 @@ describe("costsCommand", () => {
|
|
|
599
599
|
const dbPath = join(tempDir, ".overstory", "metrics.db");
|
|
600
600
|
const store = createMetricsStore(dbPath);
|
|
601
601
|
for (let i = 0; i < 25; i++) {
|
|
602
|
-
store.recordSession(makeMetrics({ agentName: `agent-${i}`,
|
|
602
|
+
store.recordSession(makeMetrics({ agentName: `agent-${i}`, taskId: `t-${i}` }));
|
|
603
603
|
}
|
|
604
604
|
store.close();
|
|
605
605
|
|
|
@@ -620,7 +620,7 @@ describe("costsCommand", () => {
|
|
|
620
620
|
store.recordSession(
|
|
621
621
|
makeMetrics({
|
|
622
622
|
agentName: "builder-1",
|
|
623
|
-
|
|
623
|
+
taskId: "t1",
|
|
624
624
|
inputTokens: 0,
|
|
625
625
|
outputTokens: 0,
|
|
626
626
|
cacheReadTokens: 0,
|
|
@@ -644,7 +644,7 @@ describe("costsCommand", () => {
|
|
|
644
644
|
store.recordSession(
|
|
645
645
|
makeMetrics({
|
|
646
646
|
agentName: "builder-1",
|
|
647
|
-
|
|
647
|
+
taskId: "t1",
|
|
648
648
|
estimatedCostUsd: null,
|
|
649
649
|
}),
|
|
650
650
|
);
|
|
@@ -664,7 +664,7 @@ describe("costsCommand", () => {
|
|
|
664
664
|
store.recordSession(
|
|
665
665
|
makeMetrics({
|
|
666
666
|
agentName: "builder-1",
|
|
667
|
-
|
|
667
|
+
taskId: "t1",
|
|
668
668
|
cacheReadTokens: 8000,
|
|
669
669
|
cacheCreationTokens: 901,
|
|
670
670
|
}),
|
|
@@ -684,7 +684,7 @@ describe("costsCommand", () => {
|
|
|
684
684
|
store.recordSession(
|
|
685
685
|
makeMetrics({
|
|
686
686
|
agentName: "builder-1",
|
|
687
|
-
|
|
687
|
+
taskId: "t1",
|
|
688
688
|
inputTokens: 100,
|
|
689
689
|
outputTokens: 50,
|
|
690
690
|
estimatedCostUsd: 0.1,
|
|
@@ -693,7 +693,7 @@ describe("costsCommand", () => {
|
|
|
693
693
|
store.recordSession(
|
|
694
694
|
makeMetrics({
|
|
695
695
|
agentName: "scout-1",
|
|
696
|
-
|
|
696
|
+
taskId: "t2",
|
|
697
697
|
capability: "scout",
|
|
698
698
|
inputTokens: 200,
|
|
699
699
|
outputTokens: 100,
|
|
@@ -716,10 +716,10 @@ describe("costsCommand", () => {
|
|
|
716
716
|
const dbPath = join(tempDir, ".overstory", "metrics.db");
|
|
717
717
|
const store = createMetricsStore(dbPath);
|
|
718
718
|
store.recordSession(
|
|
719
|
-
makeMetrics({ agentName: "builder-1",
|
|
719
|
+
makeMetrics({ agentName: "builder-1", taskId: "t1", capability: "builder" }),
|
|
720
720
|
);
|
|
721
721
|
store.recordSession(
|
|
722
|
-
makeMetrics({ agentName: "builder-2",
|
|
722
|
+
makeMetrics({ agentName: "builder-2", taskId: "t2", capability: "builder" }),
|
|
723
723
|
);
|
|
724
724
|
store.close();
|
|
725
725
|
|
|
@@ -759,7 +759,7 @@ describe("costsCommand", () => {
|
|
|
759
759
|
capability: "builder",
|
|
760
760
|
worktreePath: "/tmp/wt1",
|
|
761
761
|
branchName: "feat/task1",
|
|
762
|
-
|
|
762
|
+
taskId: "task-001",
|
|
763
763
|
tmuxSession: "tmux-001",
|
|
764
764
|
state: "working",
|
|
765
765
|
pid: 12345,
|
|
@@ -815,7 +815,7 @@ describe("costsCommand", () => {
|
|
|
815
815
|
capability: "builder",
|
|
816
816
|
worktreePath: "/tmp/wt1",
|
|
817
817
|
branchName: "feat/task1",
|
|
818
|
-
|
|
818
|
+
taskId: "task-001",
|
|
819
819
|
tmuxSession: "tmux-001",
|
|
820
820
|
state: "working",
|
|
821
821
|
pid: 12345,
|
|
@@ -879,7 +879,7 @@ describe("costsCommand", () => {
|
|
|
879
879
|
capability: "builder",
|
|
880
880
|
worktreePath: "/tmp/wt1",
|
|
881
881
|
branchName: "feat/task1",
|
|
882
|
-
|
|
882
|
+
taskId: "task-001",
|
|
883
883
|
tmuxSession: "tmux-001",
|
|
884
884
|
state: "working",
|
|
885
885
|
pid: 12345,
|
|
@@ -897,7 +897,7 @@ describe("costsCommand", () => {
|
|
|
897
897
|
capability: "scout",
|
|
898
898
|
worktreePath: "/tmp/wt2",
|
|
899
899
|
branchName: "feat/task2",
|
|
900
|
-
|
|
900
|
+
taskId: "task-002",
|
|
901
901
|
tmuxSession: "tmux-002",
|
|
902
902
|
state: "working",
|
|
903
903
|
pid: 12346,
|
|
@@ -956,7 +956,7 @@ describe("costsCommand", () => {
|
|
|
956
956
|
capability: "builder",
|
|
957
957
|
worktreePath: "/tmp/wt1",
|
|
958
958
|
branchName: "feat/task1",
|
|
959
|
-
|
|
959
|
+
taskId: "task-001",
|
|
960
960
|
tmuxSession: "tmux-001",
|
|
961
961
|
state: "working",
|
|
962
962
|
pid: 12345,
|
package/src/commands/costs.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* CLI command:
|
|
2
|
+
* CLI command: ov costs [--agent <name>] [--run <id>] [--by-capability] [--last <n>] [--self] [--json]
|
|
3
3
|
*
|
|
4
4
|
* Shows token/cost analysis and breakdown for agent sessions.
|
|
5
5
|
* Data source: metrics.db via createMetricsStore().
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* CLI command:
|
|
2
|
+
* CLI command: ov dashboard [--interval <ms>] [--all]
|
|
3
3
|
*
|
|
4
4
|
* Rich terminal dashboard using raw ANSI escape codes (zero runtime deps).
|
|
5
5
|
* Polls existing data sources and renders multi-panel layout with agent status,
|
|
@@ -402,7 +402,7 @@ async function loadDashboardData(
|
|
|
402
402
|
* Render the header bar (line 1).
|
|
403
403
|
*/
|
|
404
404
|
function renderHeader(width: number, interval: number, currentRunId?: string | null): string {
|
|
405
|
-
const left = color.bold(`
|
|
405
|
+
const left = color.bold(`ov dashboard v${PKG_VERSION}`);
|
|
406
406
|
const now = new Date().toLocaleTimeString();
|
|
407
407
|
const scope = currentRunId ? ` [run: ${currentRunId.slice(0, 8)}]` : " [all runs]";
|
|
408
408
|
const right = `${now}${scope} | refresh: ${interval}ms`;
|
|
@@ -438,15 +438,15 @@ function getStateColor(state: string): ColorFn {
|
|
|
438
438
|
function getStateIcon(state: string): string {
|
|
439
439
|
switch (state) {
|
|
440
440
|
case "working":
|
|
441
|
-
return "
|
|
441
|
+
return ">";
|
|
442
442
|
case "booting":
|
|
443
|
-
return "
|
|
443
|
+
return "-";
|
|
444
444
|
case "stalled":
|
|
445
|
-
return "
|
|
445
|
+
return "!";
|
|
446
446
|
case "zombie":
|
|
447
|
-
return "
|
|
447
|
+
return "x";
|
|
448
448
|
case "completed":
|
|
449
|
-
return "
|
|
449
|
+
return "x";
|
|
450
450
|
default:
|
|
451
451
|
return "?";
|
|
452
452
|
}
|
|
@@ -470,7 +470,7 @@ function renderAgentPanel(
|
|
|
470
470
|
output += `${CURSOR.cursorTo(startRow, 1)}${headerLine}${headerPadding}${BOX.vertical}\n`;
|
|
471
471
|
|
|
472
472
|
// Column headers
|
|
473
|
-
const colHeaders = `${BOX.vertical} St Name Capability State
|
|
473
|
+
const colHeaders = `${BOX.vertical} St Name Capability State Task ID Duration Tmux ${BOX.vertical}`;
|
|
474
474
|
output += `${CURSOR.cursorTo(startRow + 1, 1)}${colHeaders}\n`;
|
|
475
475
|
|
|
476
476
|
// Separator
|
|
@@ -500,7 +500,7 @@ function renderAgentPanel(
|
|
|
500
500
|
const name = pad(truncate(agent.agentName, 15), 15);
|
|
501
501
|
const capability = pad(truncate(agent.capability, 12), 12);
|
|
502
502
|
const state = pad(agent.state, 10);
|
|
503
|
-
const
|
|
503
|
+
const taskId = pad(truncate(agent.taskId, 16), 16);
|
|
504
504
|
const endTime =
|
|
505
505
|
agent.state === "completed" || agent.state === "zombie"
|
|
506
506
|
? new Date(agent.lastActivity).getTime()
|
|
@@ -508,9 +508,9 @@ function renderAgentPanel(
|
|
|
508
508
|
const duration = formatDuration(endTime - new Date(agent.startedAt).getTime());
|
|
509
509
|
const durationPadded = pad(duration, 9);
|
|
510
510
|
const tmuxAlive = data.status.tmuxSessions.some((s) => s.name === agent.tmuxSession);
|
|
511
|
-
const tmuxDot = tmuxAlive ? color.green("
|
|
511
|
+
const tmuxDot = tmuxAlive ? color.green(">") : color.red("x");
|
|
512
512
|
|
|
513
|
-
const line = `${BOX.vertical} ${stateColor(icon)} ${name} ${capability} ${stateColor(state)} ${
|
|
513
|
+
const line = `${BOX.vertical} ${stateColor(icon)} ${name} ${capability} ${stateColor(state)} ${taskId} ${durationPadded} ${tmuxDot} ${BOX.vertical}`;
|
|
514
514
|
output += `${CURSOR.cursorTo(startRow + 3 + i, 1)}${line}\n`;
|
|
515
515
|
}
|
|
516
516
|
|
package/src/commands/doctor.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* CLI command:
|
|
2
|
+
* CLI command: ov doctor [options]
|
|
3
3
|
*
|
|
4
4
|
* Runs health checks on overstory subsystems and reports problems.
|
|
5
5
|
*/
|
|
@@ -77,10 +77,10 @@ function printHumanReadable(
|
|
|
77
77
|
|
|
78
78
|
const icon =
|
|
79
79
|
check.status === "pass"
|
|
80
|
-
? color.green("
|
|
80
|
+
? color.green("-")
|
|
81
81
|
: check.status === "warn"
|
|
82
|
-
? color.yellow("
|
|
83
|
-
: color.red("
|
|
82
|
+
? color.yellow("!")
|
|
83
|
+
: color.red("x");
|
|
84
84
|
|
|
85
85
|
w(` ${icon} ${check.message}\n`);
|
|
86
86
|
|
package/src/commands/errors.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* CLI command:
|
|
2
|
+
* CLI command: ov errors [--agent <name>] [--run <id>] [--json] [--since <ts>] [--until <ts>] [--limit <n>]
|
|
3
3
|
*
|
|
4
4
|
* Shows aggregated error-level events across all agents.
|
|
5
5
|
* Errors can be filtered by agent name, run ID, or time range.
|
package/src/commands/feed.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* CLI command:
|
|
2
|
+
* CLI command: ov feed [--follow] [--agent <name>...] [--run <id>]
|
|
3
3
|
* [--since <ts>] [--limit <n>] [--interval <ms>] [--json]
|
|
4
4
|
*
|
|
5
5
|
* Unified real-time event stream across all agents — like `tail -f` for the fleet.
|
package/src/commands/group.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* CLI command:
|
|
2
|
+
* CLI command: ov group create|status|add|remove|list
|
|
3
3
|
*
|
|
4
4
|
* Manages TaskGroups for batch work coordination. Groups track collections
|
|
5
5
|
* of beads issues and auto-close when all member issues are closed.
|
|
@@ -297,7 +297,7 @@ function printGroupProgress(progress: TaskGroupProgress): void {
|
|
|
297
297
|
}
|
|
298
298
|
|
|
299
299
|
/**
|
|
300
|
-
* Create the Commander command for `
|
|
300
|
+
* Create the Commander command for `ov group`.
|
|
301
301
|
*/
|
|
302
302
|
export function createGroupCommand(): Command {
|
|
303
303
|
const cmd = new Command("group").description("Manage task groups for batch coordination");
|
|
@@ -472,7 +472,7 @@ export function createGroupCommand(): Command {
|
|
|
472
472
|
}
|
|
473
473
|
|
|
474
474
|
/**
|
|
475
|
-
* Entry point for `
|
|
475
|
+
* Entry point for `ov group <subcommand>`.
|
|
476
476
|
*/
|
|
477
477
|
export async function groupCommand(args: string[]): Promise<void> {
|
|
478
478
|
const cmd = createGroupCommand();
|
|
@@ -22,13 +22,13 @@ const SAMPLE_HOOKS = {
|
|
|
22
22
|
SessionStart: [
|
|
23
23
|
{
|
|
24
24
|
matcher: "",
|
|
25
|
-
hooks: [{ type: "command", command: "
|
|
25
|
+
hooks: [{ type: "command", command: "ov prime --agent orchestrator" }],
|
|
26
26
|
},
|
|
27
27
|
],
|
|
28
28
|
Stop: [
|
|
29
29
|
{
|
|
30
30
|
matcher: "",
|
|
31
|
-
hooks: [{ type: "command", command: "
|
|
31
|
+
hooks: [{ type: "command", command: "ov log session-end --agent orchestrator" }],
|
|
32
32
|
},
|
|
33
33
|
],
|
|
34
34
|
},
|
|
@@ -106,7 +106,7 @@ describe("hooks install", () => {
|
|
|
106
106
|
const content = await Bun.file(targetPath).text();
|
|
107
107
|
const parsed = JSON.parse(content) as Record<string, unknown>;
|
|
108
108
|
expect(parsed.hooks).toBeDefined();
|
|
109
|
-
expect(content).toContain("
|
|
109
|
+
expect(content).toContain("ov prime");
|
|
110
110
|
});
|
|
111
111
|
|
|
112
112
|
test("preserves existing non-hooks keys in settings.local.json", async () => {
|
|
@@ -182,7 +182,7 @@ describe("hooks install", () => {
|
|
|
182
182
|
// Existing user hook is preserved
|
|
183
183
|
expect(content).toContain("user-hook");
|
|
184
184
|
// Overstory hooks are added
|
|
185
|
-
expect(content).toContain("
|
|
185
|
+
expect(content).toContain("ov prime");
|
|
186
186
|
});
|
|
187
187
|
|
|
188
188
|
test("throws when .overstory/hooks.json does not exist", async () => {
|
|
@@ -284,7 +284,7 @@ describe("hooks install merge behavior", () => {
|
|
|
284
284
|
// User's PreToolUse hook preserved
|
|
285
285
|
expect(content).toContain("user-write-hook");
|
|
286
286
|
// Overstory's SessionStart hook added
|
|
287
|
-
expect(content).toContain("
|
|
287
|
+
expect(content).toContain("ov prime");
|
|
288
288
|
// Both event types present
|
|
289
289
|
expect(parsed.hooks.PreToolUse).toBeDefined();
|
|
290
290
|
expect(parsed.hooks.SessionStart).toBeDefined();
|
|
@@ -361,7 +361,7 @@ describe("hooks install merge behavior", () => {
|
|
|
361
361
|
const parsed = JSON.parse(content) as { hooks: Record<string, unknown[]> };
|
|
362
362
|
expect(parsed.hooks.SessionStart).toBeDefined();
|
|
363
363
|
expect(parsed.hooks.Stop).toBeDefined();
|
|
364
|
-
expect(content).toContain("
|
|
364
|
+
expect(content).toContain("ov prime");
|
|
365
365
|
});
|
|
366
366
|
|
|
367
367
|
describe("mergeHooksByEventType unit tests", () => {
|
|
@@ -424,7 +424,7 @@ describe("hooks status", () => {
|
|
|
424
424
|
const output = await captureStdout(() => hooksCommand(["status"]));
|
|
425
425
|
expect(output).toContain("present");
|
|
426
426
|
expect(output).toContain("no");
|
|
427
|
-
expect(output).toContain("
|
|
427
|
+
expect(output).toContain("ov hooks install");
|
|
428
428
|
});
|
|
429
429
|
|
|
430
430
|
test("reports installed:true when hooks present in .claude/", async () => {
|
package/src/commands/hooks.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* CLI command:
|
|
2
|
+
* CLI command: ov hooks install|uninstall|status
|
|
3
3
|
*
|
|
4
4
|
* Manages orchestrator hooks in .claude/settings.local.json.
|
|
5
|
-
* Hooks are sourced from .overstory/hooks.json (generated by
|
|
5
|
+
* Hooks are sourced from .overstory/hooks.json (generated by ov init).
|
|
6
6
|
*
|
|
7
7
|
* This keeps the canonical hook configuration in .overstory/ while placing
|
|
8
8
|
* a minimal copy in .claude/ only when the user explicitly opts in.
|
|
9
|
-
* Running `
|
|
10
|
-
* run `
|
|
9
|
+
* Running `ov init` alone does NOT modify .claude/ — the user must
|
|
10
|
+
* run `ov hooks install` as a separate step.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { mkdir, unlink } from "node:fs/promises";
|
|
@@ -80,7 +80,7 @@ async function installHooks(force: boolean): Promise<void> {
|
|
|
80
80
|
const sourcePath = join(projectRoot, ".overstory", "hooks.json");
|
|
81
81
|
const sourceFile = Bun.file(sourcePath);
|
|
82
82
|
if (!(await sourceFile.exists())) {
|
|
83
|
-
throw new ValidationError("No hooks.json found in .overstory/. Run '
|
|
83
|
+
throw new ValidationError("No hooks.json found in .overstory/. Run 'ov init' first.", {
|
|
84
84
|
field: "source",
|
|
85
85
|
});
|
|
86
86
|
}
|
|
@@ -199,7 +199,7 @@ async function statusHooks(json: boolean): Promise<void> {
|
|
|
199
199
|
`Hooks installed (.claude/settings.local.json): ${installed ? "yes" : "no"}\n`,
|
|
200
200
|
);
|
|
201
201
|
if (!installed && sourceExists) {
|
|
202
|
-
process.stdout.write(`\nRun '
|
|
202
|
+
process.stdout.write(`\nRun 'ov hooks install' to install.\n`);
|
|
203
203
|
}
|
|
204
204
|
}
|
|
205
205
|
}
|
|
@@ -234,7 +234,7 @@ export function createHooksCommand(): Command {
|
|
|
234
234
|
}
|
|
235
235
|
|
|
236
236
|
/**
|
|
237
|
-
* Entry point for `
|
|
237
|
+
* Entry point for `ov hooks <subcommand>`.
|
|
238
238
|
*/
|
|
239
239
|
export async function hooksCommand(args: string[]): Promise<void> {
|
|
240
240
|
const cmd = createHooksCommand();
|
|
@@ -20,6 +20,10 @@ const AGENT_DEF_FILES = [
|
|
|
20
20
|
"supervisor.md",
|
|
21
21
|
"coordinator.md",
|
|
22
22
|
"monitor.md",
|
|
23
|
+
"issue-reviews.md",
|
|
24
|
+
"pr-reviews.md",
|
|
25
|
+
"prioritize.md",
|
|
26
|
+
"release.md",
|
|
23
27
|
];
|
|
24
28
|
|
|
25
29
|
/** Resolve the source agents directory (same logic as init.ts). */
|
|
@@ -46,7 +50,7 @@ describe("initCommand: agent-defs deployment", () => {
|
|
|
46
50
|
await cleanupTempDir(tempDir);
|
|
47
51
|
});
|
|
48
52
|
|
|
49
|
-
test("creates .overstory/agent-defs/ with all
|
|
53
|
+
test("creates .overstory/agent-defs/ with all 12 agent definition files", async () => {
|
|
50
54
|
await initCommand({});
|
|
51
55
|
|
|
52
56
|
const agentDefsDir = join(tempDir, ".overstory", "agent-defs");
|
|
@@ -100,7 +104,7 @@ describe("initCommand: agent-defs deployment", () => {
|
|
|
100
104
|
const stopHooks = parsed.hooks.Stop[0].hooks;
|
|
101
105
|
|
|
102
106
|
expect(stopHooks.length).toBe(2);
|
|
103
|
-
expect(stopHooks[0].command).toContain("
|
|
107
|
+
expect(stopHooks[0].command).toContain("ov log session-end");
|
|
104
108
|
expect(stopHooks[1].command).toBe("mulch learn");
|
|
105
109
|
});
|
|
106
110
|
|