@os-eco/overstory-cli 0.6.1 → 0.6.5
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 +8 -7
- package/package.json +12 -4
- package/src/agents/checkpoint.test.ts +2 -2
- package/src/agents/hooks-deployer.test.ts +131 -16
- package/src/agents/hooks-deployer.ts +33 -1
- 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/manifest.test.ts +86 -0
- package/src/agents/overlay.test.ts +9 -9
- package/src/agents/overlay.ts +4 -4
- package/src/commands/agents.test.ts +8 -8
- package/src/commands/agents.ts +62 -91
- package/src/commands/clean.test.ts +36 -51
- package/src/commands/clean.ts +28 -49
- package/src/commands/completions.ts +14 -0
- package/src/commands/coordinator.test.ts +133 -26
- package/src/commands/coordinator.ts +101 -64
- package/src/commands/costs.test.ts +47 -47
- package/src/commands/costs.ts +96 -75
- package/src/commands/dashboard.test.ts +2 -2
- package/src/commands/dashboard.ts +75 -95
- package/src/commands/doctor.test.ts +2 -2
- package/src/commands/doctor.ts +92 -79
- package/src/commands/errors.test.ts +2 -2
- package/src/commands/errors.ts +56 -50
- package/src/commands/feed.test.ts +2 -2
- package/src/commands/feed.ts +86 -83
- package/src/commands/group.ts +167 -177
- package/src/commands/hooks.test.ts +2 -2
- package/src/commands/hooks.ts +52 -42
- package/src/commands/init.test.ts +19 -19
- package/src/commands/init.ts +7 -16
- package/src/commands/inspect.test.ts +18 -18
- package/src/commands/inspect.ts +55 -58
- package/src/commands/log.test.ts +26 -31
- package/src/commands/log.ts +97 -91
- package/src/commands/logs.test.ts +1 -1
- package/src/commands/logs.ts +101 -104
- package/src/commands/mail.test.ts +5 -5
- package/src/commands/mail.ts +157 -169
- package/src/commands/merge.test.ts +28 -66
- package/src/commands/merge.ts +21 -51
- package/src/commands/metrics.test.ts +8 -8
- package/src/commands/metrics.ts +34 -35
- package/src/commands/monitor.test.ts +3 -3
- package/src/commands/monitor.ts +57 -62
- package/src/commands/nudge.test.ts +1 -1
- package/src/commands/nudge.ts +41 -89
- package/src/commands/prime.test.ts +19 -51
- package/src/commands/prime.ts +13 -50
- package/src/commands/replay.test.ts +2 -2
- package/src/commands/replay.ts +79 -86
- package/src/commands/run.test.ts +1 -1
- package/src/commands/run.ts +97 -77
- package/src/commands/sling.test.ts +201 -5
- package/src/commands/sling.ts +37 -64
- package/src/commands/spec.test.ts +14 -40
- package/src/commands/spec.ts +32 -101
- package/src/commands/status.test.ts +97 -1
- package/src/commands/status.ts +63 -58
- package/src/commands/stop.test.ts +22 -40
- package/src/commands/stop.ts +18 -33
- package/src/commands/supervisor.test.ts +12 -14
- package/src/commands/supervisor.ts +144 -165
- package/src/commands/trace.test.ts +15 -15
- package/src/commands/trace.ts +59 -82
- package/src/commands/watch.test.ts +2 -2
- package/src/commands/watch.ts +38 -45
- package/src/commands/worktree.test.ts +213 -37
- package/src/commands/worktree.ts +110 -55
- package/src/config.test.ts +96 -0
- package/src/doctor/consistency.test.ts +14 -14
- package/src/doctor/databases.test.ts +22 -2
- package/src/doctor/databases.ts +16 -0
- package/src/doctor/dependencies.test.ts +55 -1
- package/src/doctor/dependencies.ts +113 -18
- package/src/doctor/merge-queue.test.ts +4 -4
- package/src/e2e/init-sling-lifecycle.test.ts +8 -8
- package/src/errors.ts +1 -1
- package/src/index.ts +223 -213
- package/src/logging/color.test.ts +74 -91
- package/src/logging/color.ts +52 -46
- package/src/logging/reporter.test.ts +10 -10
- package/src/logging/reporter.ts +6 -5
- package/src/mail/broadcast.test.ts +1 -1
- package/src/mail/client.test.ts +6 -6
- package/src/mail/store.test.ts +3 -3
- package/src/merge/queue.test.ts +73 -7
- package/src/merge/queue.ts +17 -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/schema-consistency.test.ts +239 -0
- package/src/sessions/compat.test.ts +3 -3
- package/src/sessions/compat.ts +2 -2
- package/src/sessions/store.test.ts +41 -4
- package/src/sessions/store.ts +13 -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 +98 -9
- package/src/worktree/tmux.ts +18 -0
package/src/commands/log.test.ts
CHANGED
|
@@ -88,7 +88,7 @@ describe("logCommand", () => {
|
|
|
88
88
|
await logCommand(["--help"]);
|
|
89
89
|
const out = output();
|
|
90
90
|
|
|
91
|
-
expect(out).toContain("
|
|
91
|
+
expect(out).toContain("log");
|
|
92
92
|
expect(out).toContain("tool-start");
|
|
93
93
|
expect(out).toContain("tool-end");
|
|
94
94
|
expect(out).toContain("session-end");
|
|
@@ -99,23 +99,18 @@ describe("logCommand", () => {
|
|
|
99
99
|
await logCommand(["-h"]);
|
|
100
100
|
const out = output();
|
|
101
101
|
|
|
102
|
-
expect(out).toContain("
|
|
102
|
+
expect(out).toContain("log");
|
|
103
103
|
expect(out).toContain("tool-start");
|
|
104
104
|
expect(out).toContain("tool-end");
|
|
105
105
|
expect(out).toContain("session-end");
|
|
106
106
|
expect(out).toContain("--agent");
|
|
107
107
|
});
|
|
108
108
|
|
|
109
|
-
test("missing event
|
|
110
|
-
//
|
|
111
|
-
// Note: the implementation checks for undefined event
|
|
109
|
+
test("missing event argument throws when required argument missing", async () => {
|
|
110
|
+
// Commander throws when a required positional argument is missing
|
|
112
111
|
await expect(async () => {
|
|
113
112
|
await logCommand([]);
|
|
114
|
-
}).toThrow(
|
|
115
|
-
|
|
116
|
-
await expect(async () => {
|
|
117
|
-
await logCommand([]);
|
|
118
|
-
}).toThrow("Event is required");
|
|
113
|
+
}).toThrow();
|
|
119
114
|
});
|
|
120
115
|
|
|
121
116
|
test("invalid event name throws ValidationError", async () => {
|
|
@@ -223,7 +218,7 @@ describe("logCommand", () => {
|
|
|
223
218
|
capability: "builder",
|
|
224
219
|
worktreePath: "/tmp/test",
|
|
225
220
|
branchName: "test-branch",
|
|
226
|
-
|
|
221
|
+
taskId: "bead-001",
|
|
227
222
|
tmuxSession: "test-tmux",
|
|
228
223
|
state: "working",
|
|
229
224
|
pid: 12345,
|
|
@@ -278,7 +273,7 @@ describe("logCommand", () => {
|
|
|
278
273
|
capability: "scout",
|
|
279
274
|
worktreePath: "/tmp/metrics",
|
|
280
275
|
branchName: "metrics-branch",
|
|
281
|
-
|
|
276
|
+
taskId: "bead-002",
|
|
282
277
|
tmuxSession: "metrics-tmux",
|
|
283
278
|
state: "working",
|
|
284
279
|
pid: 54321,
|
|
@@ -304,7 +299,7 @@ describe("logCommand", () => {
|
|
|
304
299
|
|
|
305
300
|
expect(metrics).toHaveLength(1);
|
|
306
301
|
expect(metrics[0]?.agentName).toBe("metrics-agent");
|
|
307
|
-
expect(metrics[0]?.
|
|
302
|
+
expect(metrics[0]?.taskId).toBe("bead-002");
|
|
308
303
|
expect(metrics[0]?.capability).toBe("scout");
|
|
309
304
|
expect(metrics[0]?.parentAgent).toBe("parent-agent");
|
|
310
305
|
});
|
|
@@ -318,7 +313,7 @@ describe("logCommand", () => {
|
|
|
318
313
|
capability: "coordinator",
|
|
319
314
|
worktreePath: tempDir,
|
|
320
315
|
branchName: "main",
|
|
321
|
-
|
|
316
|
+
taskId: "",
|
|
322
317
|
tmuxSession: "overstory-coordinator",
|
|
323
318
|
state: "working",
|
|
324
319
|
pid: 11111,
|
|
@@ -357,7 +352,7 @@ describe("logCommand", () => {
|
|
|
357
352
|
capability: "monitor",
|
|
358
353
|
worktreePath: tempDir,
|
|
359
354
|
branchName: "main",
|
|
360
|
-
|
|
355
|
+
taskId: "",
|
|
361
356
|
tmuxSession: "overstory-monitor",
|
|
362
357
|
state: "working",
|
|
363
358
|
pid: 22222,
|
|
@@ -394,7 +389,7 @@ describe("logCommand", () => {
|
|
|
394
389
|
capability: "coordinator",
|
|
395
390
|
worktreePath: tempDir,
|
|
396
391
|
branchName: "main",
|
|
397
|
-
|
|
392
|
+
taskId: "",
|
|
398
393
|
tmuxSession: "overstory-coordinator",
|
|
399
394
|
state: "working",
|
|
400
395
|
pid: 11111,
|
|
@@ -451,7 +446,7 @@ describe("logCommand", () => {
|
|
|
451
446
|
capability: "coordinator",
|
|
452
447
|
worktreePath: tempDir,
|
|
453
448
|
branchName: "main",
|
|
454
|
-
|
|
449
|
+
taskId: "",
|
|
455
450
|
tmuxSession: "overstory-coordinator-no-run",
|
|
456
451
|
state: "working",
|
|
457
452
|
pid: 11112,
|
|
@@ -481,7 +476,7 @@ describe("logCommand", () => {
|
|
|
481
476
|
capability: "builder",
|
|
482
477
|
worktreePath: tempDir,
|
|
483
478
|
branchName: "builder-branch",
|
|
484
|
-
|
|
479
|
+
taskId: "bead-builder-001",
|
|
485
480
|
tmuxSession: "overstory-builder",
|
|
486
481
|
state: "working",
|
|
487
482
|
pid: 11113,
|
|
@@ -535,7 +530,7 @@ describe("logCommand", () => {
|
|
|
535
530
|
capability: "coordinator",
|
|
536
531
|
worktreePath: tempDir,
|
|
537
532
|
branchName: "main",
|
|
538
|
-
|
|
533
|
+
taskId: "",
|
|
539
534
|
tmuxSession: "overstory-coordinator-completed",
|
|
540
535
|
state: "working",
|
|
541
536
|
pid: 11114,
|
|
@@ -588,7 +583,7 @@ describe("logCommand", () => {
|
|
|
588
583
|
capability: "lead",
|
|
589
584
|
worktreePath: tempDir,
|
|
590
585
|
branchName: "lead-alpha-branch",
|
|
591
|
-
|
|
586
|
+
taskId: "bead-lead-001",
|
|
592
587
|
tmuxSession: "overstory-lead-alpha",
|
|
593
588
|
state: "working",
|
|
594
589
|
pid: 33333,
|
|
@@ -628,7 +623,7 @@ describe("logCommand", () => {
|
|
|
628
623
|
capability: "builder",
|
|
629
624
|
worktreePath: tempDir,
|
|
630
625
|
branchName: "builder-beta-branch",
|
|
631
|
-
|
|
626
|
+
taskId: "bead-builder-001",
|
|
632
627
|
tmuxSession: "overstory-builder-beta",
|
|
633
628
|
state: "working",
|
|
634
629
|
pid: 44444,
|
|
@@ -670,7 +665,7 @@ describe("logCommand", () => {
|
|
|
670
665
|
capability: "builder",
|
|
671
666
|
worktreePath: "/tmp/activity",
|
|
672
667
|
branchName: "activity-branch",
|
|
673
|
-
|
|
668
|
+
taskId: "bead-003",
|
|
674
669
|
tmuxSession: "activity-tmux",
|
|
675
670
|
state: "working",
|
|
676
671
|
pid: 99999,
|
|
@@ -709,7 +704,7 @@ describe("logCommand", () => {
|
|
|
709
704
|
capability: "builder",
|
|
710
705
|
worktreePath: "/tmp/booting",
|
|
711
706
|
branchName: "booting-branch",
|
|
712
|
-
|
|
707
|
+
taskId: "bead-004",
|
|
713
708
|
tmuxSession: "booting-tmux",
|
|
714
709
|
state: "booting",
|
|
715
710
|
pid: 11111,
|
|
@@ -794,7 +789,7 @@ describe("logCommand", () => {
|
|
|
794
789
|
capability: "builder",
|
|
795
790
|
worktreePath: tempDir,
|
|
796
791
|
branchName: "mulch-fail-branch",
|
|
797
|
-
|
|
792
|
+
taskId: "bead-mulch-001",
|
|
798
793
|
tmuxSession: "overstory-mulch-fail",
|
|
799
794
|
state: "working",
|
|
800
795
|
pid: 55555,
|
|
@@ -833,7 +828,7 @@ describe("logCommand", () => {
|
|
|
833
828
|
capability: "coordinator",
|
|
834
829
|
worktreePath: tempDir,
|
|
835
830
|
branchName: "main",
|
|
836
|
-
|
|
831
|
+
taskId: "",
|
|
837
832
|
tmuxSession: "overstory-coordinator-mulch",
|
|
838
833
|
state: "working",
|
|
839
834
|
pid: 66666,
|
|
@@ -880,7 +875,7 @@ describe("logCommand", () => {
|
|
|
880
875
|
mulchClient: client,
|
|
881
876
|
agentName: "test-builder",
|
|
882
877
|
capability: "builder",
|
|
883
|
-
|
|
878
|
+
taskId: "bead-123",
|
|
884
879
|
mailDbPath,
|
|
885
880
|
parentAgent: "parent-lead",
|
|
886
881
|
projectRoot: tempDir,
|
|
@@ -913,7 +908,7 @@ describe("logCommand", () => {
|
|
|
913
908
|
mulchClient: client,
|
|
914
909
|
agentName: "test-builder",
|
|
915
910
|
capability: "builder",
|
|
916
|
-
|
|
911
|
+
taskId: "bead-456",
|
|
917
912
|
mailDbPath,
|
|
918
913
|
parentAgent: "parent-lead",
|
|
919
914
|
projectRoot: tempDir,
|
|
@@ -945,7 +940,7 @@ describe("logCommand", () => {
|
|
|
945
940
|
mulchClient: client,
|
|
946
941
|
agentName: "test-builder",
|
|
947
942
|
capability: "builder",
|
|
948
|
-
|
|
943
|
+
taskId: null,
|
|
949
944
|
mailDbPath,
|
|
950
945
|
parentAgent: null,
|
|
951
946
|
projectRoot: tempDir,
|
|
@@ -973,7 +968,7 @@ describe("logCommand", () => {
|
|
|
973
968
|
mulchClient: client,
|
|
974
969
|
agentName: "test-builder",
|
|
975
970
|
capability: "builder",
|
|
976
|
-
|
|
971
|
+
taskId: null,
|
|
977
972
|
mailDbPath,
|
|
978
973
|
parentAgent: null,
|
|
979
974
|
projectRoot: tempDir,
|
|
@@ -1052,7 +1047,7 @@ describe("logCommand", () => {
|
|
|
1052
1047
|
mulchClient: client,
|
|
1053
1048
|
agentName: "insight-agent",
|
|
1054
1049
|
capability: "builder",
|
|
1055
|
-
|
|
1050
|
+
taskId: "bead-insight",
|
|
1056
1051
|
mailDbPath,
|
|
1057
1052
|
parentAgent: "parent-agent",
|
|
1058
1053
|
projectRoot: tempDir,
|
|
@@ -1132,7 +1127,7 @@ describe("logCommand", () => {
|
|
|
1132
1127
|
mulchClient: client,
|
|
1133
1128
|
agentName: "mail-insight-agent",
|
|
1134
1129
|
capability: "scout",
|
|
1135
|
-
|
|
1130
|
+
taskId: "bead-mail",
|
|
1136
1131
|
mailDbPath,
|
|
1137
1132
|
parentAgent: "parent-agent",
|
|
1138
1133
|
projectRoot: tempDir,
|
package/src/commands/log.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { join } from "node:path";
|
|
14
|
+
import { Command } from "commander";
|
|
14
15
|
import { updateIdentity } from "../agents/identity.ts";
|
|
15
16
|
import { loadConfig } from "../config.ts";
|
|
16
17
|
import { ValidationError } from "../errors.ts";
|
|
@@ -27,17 +28,6 @@ import { openSessionStore } from "../sessions/compat.ts";
|
|
|
27
28
|
import { createRunStore } from "../sessions/store.ts";
|
|
28
29
|
import type { AgentSession } from "../types.ts";
|
|
29
30
|
|
|
30
|
-
/**
|
|
31
|
-
* Parse a named flag value from args.
|
|
32
|
-
*/
|
|
33
|
-
function getFlag(args: string[], flag: string): string | undefined {
|
|
34
|
-
const idx = args.indexOf(flag);
|
|
35
|
-
if (idx === -1 || idx + 1 >= args.length) {
|
|
36
|
-
return undefined;
|
|
37
|
-
}
|
|
38
|
-
return args[idx + 1];
|
|
39
|
-
}
|
|
40
|
-
|
|
41
31
|
/**
|
|
42
32
|
* Get or create a session timestamp directory for the agent.
|
|
43
33
|
* Uses a file-based marker to track the current session directory.
|
|
@@ -237,7 +227,7 @@ export async function autoRecordExpertise(params: {
|
|
|
237
227
|
mulchClient: MulchClient;
|
|
238
228
|
agentName: string;
|
|
239
229
|
capability: string;
|
|
240
|
-
|
|
230
|
+
taskId: string | null;
|
|
241
231
|
mailDbPath: string;
|
|
242
232
|
parentAgent: string | null;
|
|
243
233
|
projectRoot: string;
|
|
@@ -257,7 +247,7 @@ export async function autoRecordExpertise(params: {
|
|
|
257
247
|
type: "reference",
|
|
258
248
|
description: `${params.capability} agent ${params.agentName} completed work in this domain. Files: ${filesList}`,
|
|
259
249
|
tags: ["auto-session-end", params.capability],
|
|
260
|
-
evidenceBead: params.
|
|
250
|
+
evidenceBead: params.taskId ?? undefined,
|
|
261
251
|
});
|
|
262
252
|
recordedDomains.push(domain);
|
|
263
253
|
} catch {
|
|
@@ -296,7 +286,7 @@ export async function autoRecordExpertise(params: {
|
|
|
296
286
|
type: insight.type,
|
|
297
287
|
description: insight.description,
|
|
298
288
|
tags: insight.tags,
|
|
299
|
-
evidenceBead: params.
|
|
289
|
+
evidenceBead: params.taskId ?? undefined,
|
|
300
290
|
});
|
|
301
291
|
if (!recordedDomains.includes(insight.domain)) {
|
|
302
292
|
recordedDomains.push(insight.domain);
|
|
@@ -342,63 +332,32 @@ export async function autoRecordExpertise(params: {
|
|
|
342
332
|
}
|
|
343
333
|
|
|
344
334
|
/**
|
|
345
|
-
*
|
|
335
|
+
* Core implementation for the log command.
|
|
346
336
|
*/
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
Options:
|
|
355
|
-
--agent <name> Agent name (required)
|
|
356
|
-
--tool-name <name> Tool name (for tool-start/tool-end events, legacy)
|
|
357
|
-
--transcript <path> Path to Claude Code transcript JSONL (for session-end, legacy)
|
|
358
|
-
--stdin Read hook payload JSON from stdin (preferred)
|
|
359
|
-
--help, -h Show this help`;
|
|
360
|
-
|
|
361
|
-
export async function logCommand(args: string[]): Promise<void> {
|
|
362
|
-
if (args.includes("--help") || args.includes("-h")) {
|
|
363
|
-
process.stdout.write(`${LOG_HELP}\n`);
|
|
364
|
-
return;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
const event = args.find((a) => !a.startsWith("--"));
|
|
368
|
-
const agentName = getFlag(args, "--agent");
|
|
369
|
-
const useStdin = args.includes("--stdin");
|
|
370
|
-
const toolNameFlag = getFlag(args, "--tool-name") ?? "unknown";
|
|
371
|
-
const transcriptPathFlag = getFlag(args, "--transcript");
|
|
372
|
-
|
|
373
|
-
if (!event) {
|
|
374
|
-
throw new ValidationError("Event is required: overstory log <event> --agent <name>", {
|
|
375
|
-
field: "event",
|
|
376
|
-
});
|
|
377
|
-
}
|
|
378
|
-
|
|
337
|
+
async function runLog(opts: {
|
|
338
|
+
event: string;
|
|
339
|
+
agent: string;
|
|
340
|
+
toolName: string;
|
|
341
|
+
transcript: string | undefined;
|
|
342
|
+
stdin: boolean;
|
|
343
|
+
}): Promise<void> {
|
|
379
344
|
const validEvents = ["tool-start", "tool-end", "session-end"];
|
|
380
|
-
if (!validEvents.includes(event)) {
|
|
381
|
-
throw new ValidationError(`Invalid event "${event}". Valid: ${validEvents.join(", ")}`, {
|
|
345
|
+
if (!validEvents.includes(opts.event)) {
|
|
346
|
+
throw new ValidationError(`Invalid event "${opts.event}". Valid: ${validEvents.join(", ")}`, {
|
|
382
347
|
field: "event",
|
|
383
|
-
value: event,
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
if (!agentName) {
|
|
388
|
-
throw new ValidationError("--agent is required for log command", {
|
|
389
|
-
field: "agent",
|
|
348
|
+
value: opts.event,
|
|
390
349
|
});
|
|
391
350
|
}
|
|
392
351
|
|
|
393
352
|
// Read stdin payload if --stdin flag is set
|
|
394
353
|
let stdinPayload: Record<string, unknown> | null = null;
|
|
395
|
-
if (
|
|
354
|
+
if (opts.stdin) {
|
|
396
355
|
stdinPayload = await readStdinJson();
|
|
397
356
|
}
|
|
398
357
|
|
|
399
358
|
// Extract fields from stdin payload (preferred) or fall back to flags
|
|
400
359
|
const toolName =
|
|
401
|
-
typeof stdinPayload?.tool_name === "string" ? stdinPayload.tool_name :
|
|
360
|
+
typeof stdinPayload?.tool_name === "string" ? stdinPayload.tool_name : opts.toolName;
|
|
402
361
|
const toolInput =
|
|
403
362
|
stdinPayload?.tool_input !== undefined &&
|
|
404
363
|
stdinPayload?.tool_input !== null &&
|
|
@@ -409,28 +368,28 @@ export async function logCommand(args: string[]): Promise<void> {
|
|
|
409
368
|
const transcriptPath =
|
|
410
369
|
typeof stdinPayload?.transcript_path === "string"
|
|
411
370
|
? stdinPayload.transcript_path
|
|
412
|
-
:
|
|
371
|
+
: opts.transcript;
|
|
413
372
|
|
|
414
373
|
const cwd = process.cwd();
|
|
415
374
|
const config = await loadConfig(cwd);
|
|
416
375
|
const logsBase = join(config.project.root, ".overstory", "logs");
|
|
417
|
-
const sessionDir = await getSessionDir(logsBase,
|
|
376
|
+
const sessionDir = await getSessionDir(logsBase, opts.agent);
|
|
418
377
|
|
|
419
378
|
const logger = createLogger({
|
|
420
379
|
logDir: sessionDir,
|
|
421
|
-
agentName,
|
|
380
|
+
agentName: opts.agent,
|
|
422
381
|
verbose: config.logging.verbose,
|
|
423
382
|
redactSecrets: config.logging.redactSecrets,
|
|
424
383
|
});
|
|
425
384
|
|
|
426
|
-
switch (event) {
|
|
385
|
+
switch (opts.event) {
|
|
427
386
|
case "tool-start": {
|
|
428
387
|
// Backward compatibility: always write to per-agent log files
|
|
429
388
|
logger.toolStart(toolName, toolInput ?? {});
|
|
430
|
-
updateLastActivity(config.project.root,
|
|
389
|
+
updateLastActivity(config.project.root, opts.agent);
|
|
431
390
|
|
|
432
391
|
// When --stdin is used, also write to EventStore for structured observability
|
|
433
|
-
if (
|
|
392
|
+
if (opts.stdin) {
|
|
434
393
|
try {
|
|
435
394
|
const eventsDbPath = join(config.project.root, ".overstory", "events.db");
|
|
436
395
|
const eventStore = createEventStore(eventsDbPath);
|
|
@@ -439,7 +398,7 @@ export async function logCommand(args: string[]): Promise<void> {
|
|
|
439
398
|
: { args: {}, summary: toolName };
|
|
440
399
|
eventStore.insert({
|
|
441
400
|
runId: null,
|
|
442
|
-
agentName,
|
|
401
|
+
agentName: opts.agent,
|
|
443
402
|
sessionId,
|
|
444
403
|
eventType: "tool_start",
|
|
445
404
|
toolName,
|
|
@@ -458,10 +417,10 @@ export async function logCommand(args: string[]): Promise<void> {
|
|
|
458
417
|
case "tool-end": {
|
|
459
418
|
// Backward compatibility: always write to per-agent log files
|
|
460
419
|
logger.toolEnd(toolName, 0);
|
|
461
|
-
updateLastActivity(config.project.root,
|
|
420
|
+
updateLastActivity(config.project.root, opts.agent);
|
|
462
421
|
|
|
463
422
|
// When --stdin is used, write to EventStore and correlate with tool-start
|
|
464
|
-
if (
|
|
423
|
+
if (opts.stdin) {
|
|
465
424
|
try {
|
|
466
425
|
const eventsDbPath = join(config.project.root, ".overstory", "events.db");
|
|
467
426
|
const eventStore = createEventStore(eventsDbPath);
|
|
@@ -470,7 +429,7 @@ export async function logCommand(args: string[]): Promise<void> {
|
|
|
470
429
|
: { args: {}, summary: toolName };
|
|
471
430
|
eventStore.insert({
|
|
472
431
|
runId: null,
|
|
473
|
-
agentName,
|
|
432
|
+
agentName: opts.agent,
|
|
474
433
|
sessionId,
|
|
475
434
|
eventType: "tool_end",
|
|
476
435
|
toolName,
|
|
@@ -479,7 +438,7 @@ export async function logCommand(args: string[]): Promise<void> {
|
|
|
479
438
|
level: "info",
|
|
480
439
|
data: JSON.stringify({ summary: filtered.summary }),
|
|
481
440
|
});
|
|
482
|
-
const correlation = eventStore.correlateToolEnd(
|
|
441
|
+
const correlation = eventStore.correlateToolEnd(opts.agent, toolName);
|
|
483
442
|
if (correlation) {
|
|
484
443
|
logger.toolEnd(toolName, correlation.durationMs);
|
|
485
444
|
}
|
|
@@ -492,7 +451,7 @@ export async function logCommand(args: string[]): Promise<void> {
|
|
|
492
451
|
if (sessionId) {
|
|
493
452
|
try {
|
|
494
453
|
// Throttle check
|
|
495
|
-
const snapshotMarkerPath = join(logsBase,
|
|
454
|
+
const snapshotMarkerPath = join(logsBase, opts.agent, ".last-snapshot");
|
|
496
455
|
const SNAPSHOT_INTERVAL_MS = 30_000;
|
|
497
456
|
const snapshotMarkerFile = Bun.file(snapshotMarkerPath);
|
|
498
457
|
let shouldSnapshot = true;
|
|
@@ -505,19 +464,19 @@ export async function logCommand(args: string[]): Promise<void> {
|
|
|
505
464
|
}
|
|
506
465
|
|
|
507
466
|
if (shouldSnapshot) {
|
|
508
|
-
const
|
|
467
|
+
const resolvedTranscriptPath = await resolveTranscriptPath(
|
|
509
468
|
config.project.root,
|
|
510
469
|
sessionId,
|
|
511
470
|
logsBase,
|
|
512
|
-
|
|
471
|
+
opts.agent,
|
|
513
472
|
);
|
|
514
|
-
if (
|
|
515
|
-
const usage = await parseTranscriptUsage(
|
|
473
|
+
if (resolvedTranscriptPath) {
|
|
474
|
+
const usage = await parseTranscriptUsage(resolvedTranscriptPath);
|
|
516
475
|
const cost = estimateCost(usage);
|
|
517
476
|
const metricsDbPath = join(config.project.root, ".overstory", "metrics.db");
|
|
518
477
|
const metricsStore = createMetricsStore(metricsDbPath);
|
|
519
478
|
metricsStore.recordSnapshot({
|
|
520
|
-
agentName,
|
|
479
|
+
agentName: opts.agent,
|
|
521
480
|
inputTokens: usage.inputTokens,
|
|
522
481
|
outputTokens: usage.outputTokens,
|
|
523
482
|
cacheReadTokens: usage.cacheReadTokens,
|
|
@@ -538,20 +497,20 @@ export async function logCommand(args: string[]): Promise<void> {
|
|
|
538
497
|
break;
|
|
539
498
|
}
|
|
540
499
|
case "session-end":
|
|
541
|
-
logger.info("session.end", { agentName });
|
|
500
|
+
logger.info("session.end", { agentName: opts.agent });
|
|
542
501
|
// Transition agent state to completed
|
|
543
|
-
transitionToCompleted(config.project.root,
|
|
502
|
+
transitionToCompleted(config.project.root, opts.agent);
|
|
544
503
|
// Look up agent session for identity update and metrics recording
|
|
545
504
|
{
|
|
546
|
-
const agentSession = getAgentSession(config.project.root,
|
|
547
|
-
const
|
|
505
|
+
const agentSession = getAgentSession(config.project.root, opts.agent);
|
|
506
|
+
const taskId = agentSession?.taskId ?? null;
|
|
548
507
|
|
|
549
508
|
// Update agent identity with completed session
|
|
550
509
|
const identityBaseDir = join(config.project.root, ".overstory", "agents");
|
|
551
510
|
try {
|
|
552
|
-
await updateIdentity(identityBaseDir,
|
|
511
|
+
await updateIdentity(identityBaseDir, opts.agent, {
|
|
553
512
|
sessionsCompleted: 1,
|
|
554
|
-
completedTask:
|
|
513
|
+
completedTask: taskId ? { taskId, summary: `Completed task ${taskId}` } : undefined,
|
|
555
514
|
});
|
|
556
515
|
} catch {
|
|
557
516
|
// Non-fatal: identity may not exist for this agent
|
|
@@ -567,10 +526,10 @@ export async function logCommand(args: string[]): Promise<void> {
|
|
|
567
526
|
await mkdir(nudgesDir, { recursive: true });
|
|
568
527
|
const markerPath = join(nudgesDir, "coordinator.json");
|
|
569
528
|
const marker = {
|
|
570
|
-
from:
|
|
529
|
+
from: opts.agent,
|
|
571
530
|
reason: "lead_completed",
|
|
572
|
-
subject: `Lead ${
|
|
573
|
-
messageId: `auto-nudge-${
|
|
531
|
+
subject: `Lead ${opts.agent} completed — check mail for merge_ready/worker_done`,
|
|
532
|
+
messageId: `auto-nudge-${opts.agent}-${Date.now()}`,
|
|
574
533
|
createdAt: new Date().toISOString(),
|
|
575
534
|
};
|
|
576
535
|
await Bun.write(markerPath, `${JSON.stringify(marker, null, "\t")}\n`);
|
|
@@ -641,8 +600,8 @@ export async function logCommand(args: string[]): Promise<void> {
|
|
|
641
600
|
}
|
|
642
601
|
|
|
643
602
|
metricsStore.recordSession({
|
|
644
|
-
agentName,
|
|
645
|
-
|
|
603
|
+
agentName: opts.agent,
|
|
604
|
+
taskId: agentSession.taskId,
|
|
646
605
|
capability: agentSession.capability,
|
|
647
606
|
startedAt: agentSession.startedAt,
|
|
648
607
|
completedAt: now,
|
|
@@ -671,9 +630,9 @@ export async function logCommand(args: string[]): Promise<void> {
|
|
|
671
630
|
const mailDbPath = join(config.project.root, ".overstory", "mail.db");
|
|
672
631
|
await autoRecordExpertise({
|
|
673
632
|
mulchClient,
|
|
674
|
-
agentName,
|
|
633
|
+
agentName: opts.agent,
|
|
675
634
|
capability: agentSession.capability,
|
|
676
|
-
|
|
635
|
+
taskId,
|
|
677
636
|
mailDbPath,
|
|
678
637
|
parentAgent: agentSession.parentAgent,
|
|
679
638
|
projectRoot: config.project.root,
|
|
@@ -686,13 +645,13 @@ export async function logCommand(args: string[]): Promise<void> {
|
|
|
686
645
|
}
|
|
687
646
|
|
|
688
647
|
// Write session-end event to EventStore when --stdin is used
|
|
689
|
-
if (
|
|
648
|
+
if (opts.stdin) {
|
|
690
649
|
try {
|
|
691
650
|
const eventsDbPath = join(config.project.root, ".overstory", "events.db");
|
|
692
651
|
const eventStore = createEventStore(eventsDbPath);
|
|
693
652
|
eventStore.insert({
|
|
694
653
|
runId: null,
|
|
695
|
-
agentName,
|
|
654
|
+
agentName: opts.agent,
|
|
696
655
|
sessionId,
|
|
697
656
|
eventType: "session_end",
|
|
698
657
|
toolName: null,
|
|
@@ -709,7 +668,7 @@ export async function logCommand(args: string[]): Promise<void> {
|
|
|
709
668
|
}
|
|
710
669
|
// Clear the current session marker
|
|
711
670
|
{
|
|
712
|
-
const markerPath = join(logsBase,
|
|
671
|
+
const markerPath = join(logsBase, opts.agent, ".current-session");
|
|
713
672
|
try {
|
|
714
673
|
const { unlink } = await import("node:fs/promises");
|
|
715
674
|
await unlink(markerPath);
|
|
@@ -722,3 +681,50 @@ export async function logCommand(args: string[]): Promise<void> {
|
|
|
722
681
|
|
|
723
682
|
logger.close();
|
|
724
683
|
}
|
|
684
|
+
|
|
685
|
+
export function createLogCommand(): Command {
|
|
686
|
+
return new Command("log")
|
|
687
|
+
.description("Log a hook event")
|
|
688
|
+
.argument("<event>", "Event type: tool-start, tool-end, session-end")
|
|
689
|
+
.option("--agent <name>", "Agent name (required)")
|
|
690
|
+
.option("--tool-name <name>", "Tool name (for tool-start/tool-end events, legacy)")
|
|
691
|
+
.option("--transcript <path>", "Path to Claude Code transcript JSONL (for session-end, legacy)")
|
|
692
|
+
.option("--stdin", "Read hook payload JSON from stdin (preferred)")
|
|
693
|
+
.action(
|
|
694
|
+
async (
|
|
695
|
+
event: string,
|
|
696
|
+
opts: { agent?: string; toolName?: string; transcript?: string; stdin?: boolean },
|
|
697
|
+
) => {
|
|
698
|
+
if (!opts.agent) {
|
|
699
|
+
throw new ValidationError("--agent is required for log command", { field: "agent" });
|
|
700
|
+
}
|
|
701
|
+
await runLog({
|
|
702
|
+
event,
|
|
703
|
+
agent: opts.agent,
|
|
704
|
+
toolName: opts.toolName ?? "unknown",
|
|
705
|
+
transcript: opts.transcript,
|
|
706
|
+
stdin: opts.stdin ?? false,
|
|
707
|
+
});
|
|
708
|
+
},
|
|
709
|
+
);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
/**
|
|
713
|
+
* Entry point for `overstory log <event> --agent <name>`.
|
|
714
|
+
*/
|
|
715
|
+
export async function logCommand(args: string[]): Promise<void> {
|
|
716
|
+
const cmd = createLogCommand();
|
|
717
|
+
cmd.exitOverride();
|
|
718
|
+
|
|
719
|
+
try {
|
|
720
|
+
await cmd.parseAsync(args, { from: "user" });
|
|
721
|
+
} catch (err: unknown) {
|
|
722
|
+
if (err && typeof err === "object" && "code" in err) {
|
|
723
|
+
const code = (err as { code: string }).code;
|
|
724
|
+
if (code === "commander.helpDisplayed" || code === "commander.version") {
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
throw err;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
@@ -95,7 +95,7 @@ describe("logsCommand", () => {
|
|
|
95
95
|
await logsCommand(["--help"]);
|
|
96
96
|
});
|
|
97
97
|
|
|
98
|
-
expect(output).toContain("
|
|
98
|
+
expect(output).toContain("logs");
|
|
99
99
|
expect(output).toContain("--agent");
|
|
100
100
|
expect(output).toContain("--level");
|
|
101
101
|
expect(output).toContain("--since");
|