@os-eco/overstory-cli 0.7.2 → 0.7.4
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 +21 -9
- package/agents/builder.md +6 -0
- package/agents/coordinator.md +2 -2
- package/agents/lead.md +4 -1
- package/agents/merger.md +3 -2
- package/agents/monitor.md +1 -1
- package/agents/reviewer.md +1 -0
- package/agents/scout.md +1 -0
- package/package.json +2 -2
- package/src/agents/hooks-deployer.test.ts +6 -5
- package/src/agents/identity.test.ts +3 -2
- package/src/agents/manifest.test.ts +4 -3
- package/src/agents/overlay.test.ts +3 -2
- package/src/commands/agents.test.ts +5 -4
- package/src/commands/agents.ts +18 -8
- package/src/commands/completions.test.ts +8 -5
- package/src/commands/completions.ts +37 -1
- package/src/commands/costs.test.ts +4 -3
- package/src/commands/dashboard.test.ts +265 -6
- package/src/commands/dashboard.ts +367 -64
- package/src/commands/doctor.test.ts +3 -2
- package/src/commands/errors.test.ts +3 -2
- package/src/commands/feed.test.ts +3 -2
- package/src/commands/feed.ts +2 -29
- package/src/commands/inspect.test.ts +3 -2
- package/src/commands/log.test.ts +248 -8
- package/src/commands/log.ts +193 -110
- package/src/commands/logs.test.ts +3 -2
- package/src/commands/mail.test.ts +3 -2
- package/src/commands/metrics.test.ts +4 -3
- package/src/commands/nudge.test.ts +3 -2
- package/src/commands/prime.test.ts +3 -2
- package/src/commands/prime.ts +1 -16
- package/src/commands/replay.test.ts +3 -2
- package/src/commands/run.test.ts +2 -1
- package/src/commands/sling.test.ts +127 -0
- package/src/commands/sling.ts +101 -3
- package/src/commands/status.test.ts +8 -8
- package/src/commands/trace.test.ts +3 -2
- package/src/commands/watch.test.ts +3 -2
- package/src/config.test.ts +3 -3
- package/src/doctor/agents.test.ts +3 -2
- package/src/doctor/logs.test.ts +3 -2
- package/src/doctor/structure.test.ts +3 -2
- package/src/index.ts +3 -1
- package/src/logging/color.ts +1 -1
- package/src/logging/format.test.ts +110 -0
- package/src/logging/format.ts +42 -1
- package/src/logging/logger.test.ts +3 -2
- package/src/mail/client.test.ts +3 -2
- package/src/mail/store.test.ts +3 -2
- package/src/merge/queue.test.ts +3 -2
- package/src/merge/resolver.test.ts +39 -0
- package/src/merge/resolver.ts +1 -1
- package/src/metrics/pricing.ts +80 -0
- package/src/metrics/transcript.test.ts +58 -1
- package/src/metrics/transcript.ts +9 -68
- package/src/mulch/client.test.ts +63 -2
- package/src/mulch/client.ts +62 -1
- package/src/runtimes/claude.test.ts +4 -3
- package/src/runtimes/pi-guards.test.ts +55 -2
- package/src/runtimes/pi-guards.ts +26 -9
- package/src/schema-consistency.test.ts +4 -2
- package/src/sessions/compat.test.ts +3 -2
- package/src/sessions/store.test.ts +3 -2
- package/src/test-helpers.ts +20 -1
- package/src/tracker/beads.test.ts +454 -0
- package/src/tracker/seeds.test.ts +461 -0
- package/src/watchdog/daemon.test.ts +4 -3
- package/src/watchdog/triage.test.ts +3 -2
|
@@ -10,10 +10,11 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
13
|
-
import { mkdtemp
|
|
13
|
+
import { mkdtemp } from "node:fs/promises";
|
|
14
14
|
import { tmpdir } from "node:os";
|
|
15
15
|
import { join } from "node:path";
|
|
16
16
|
import { ValidationError } from "../errors.ts";
|
|
17
|
+
import { cleanupTempDir } from "../test-helpers.ts";
|
|
17
18
|
import { doctorCommand } from "./doctor.ts";
|
|
18
19
|
|
|
19
20
|
describe("doctorCommand", () => {
|
|
@@ -47,7 +48,7 @@ describe("doctorCommand", () => {
|
|
|
47
48
|
afterEach(async () => {
|
|
48
49
|
process.stdout.write = originalWrite;
|
|
49
50
|
process.chdir(originalCwd);
|
|
50
|
-
await
|
|
51
|
+
await cleanupTempDir(tempDir);
|
|
51
52
|
});
|
|
52
53
|
|
|
53
54
|
function output(): string {
|
|
@@ -9,11 +9,12 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
12
|
-
import { mkdtemp
|
|
12
|
+
import { mkdtemp } from "node:fs/promises";
|
|
13
13
|
import { tmpdir } from "node:os";
|
|
14
14
|
import { join } from "node:path";
|
|
15
15
|
import { ValidationError } from "../errors.ts";
|
|
16
16
|
import { createEventStore } from "../events/store.ts";
|
|
17
|
+
import { cleanupTempDir } from "../test-helpers.ts";
|
|
17
18
|
import type { InsertEvent } from "../types.ts";
|
|
18
19
|
import { errorsCommand } from "./errors.ts";
|
|
19
20
|
|
|
@@ -64,7 +65,7 @@ describe("errorsCommand", () => {
|
|
|
64
65
|
afterEach(async () => {
|
|
65
66
|
process.stdout.write = originalWrite;
|
|
66
67
|
process.chdir(originalCwd);
|
|
67
|
-
await
|
|
68
|
+
await cleanupTempDir(tempDir);
|
|
68
69
|
});
|
|
69
70
|
|
|
70
71
|
function output(): string {
|
|
@@ -9,11 +9,12 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
12
|
-
import { mkdtemp
|
|
12
|
+
import { mkdtemp } from "node:fs/promises";
|
|
13
13
|
import { tmpdir } from "node:os";
|
|
14
14
|
import { join } from "node:path";
|
|
15
15
|
import { ValidationError } from "../errors.ts";
|
|
16
16
|
import { createEventStore } from "../events/store.ts";
|
|
17
|
+
import { cleanupTempDir } from "../test-helpers.ts";
|
|
17
18
|
import type { InsertEvent } from "../types.ts";
|
|
18
19
|
import { feedCommand } from "./feed.ts";
|
|
19
20
|
|
|
@@ -64,7 +65,7 @@ describe("feedCommand", () => {
|
|
|
64
65
|
afterEach(async () => {
|
|
65
66
|
process.stdout.write = originalWrite;
|
|
66
67
|
process.chdir(originalCwd);
|
|
67
|
-
await
|
|
68
|
+
await cleanupTempDir(tempDir);
|
|
68
69
|
});
|
|
69
70
|
|
|
70
71
|
function output(): string {
|
package/src/commands/feed.ts
CHANGED
|
@@ -13,14 +13,7 @@ import { ValidationError } from "../errors.ts";
|
|
|
13
13
|
import { createEventStore } from "../events/store.ts";
|
|
14
14
|
import { jsonOutput } from "../json.ts";
|
|
15
15
|
import type { ColorFn } from "../logging/color.ts";
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
buildAgentColorMap,
|
|
19
|
-
buildEventDetail,
|
|
20
|
-
extendAgentColorMap,
|
|
21
|
-
formatAbsoluteTime,
|
|
22
|
-
} from "../logging/format.ts";
|
|
23
|
-
import { eventLabel } from "../logging/theme.ts";
|
|
16
|
+
import { buildAgentColorMap, extendAgentColorMap, formatEventLine } from "../logging/format.ts";
|
|
24
17
|
import type { StoredEvent } from "../types.ts";
|
|
25
18
|
|
|
26
19
|
/**
|
|
@@ -28,27 +21,7 @@ import type { StoredEvent } from "../types.ts";
|
|
|
28
21
|
* HH:MM:SS LABEL agentname detail
|
|
29
22
|
*/
|
|
30
23
|
function printEvent(event: StoredEvent, colorMap: Map<string, ColorFn>): void {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const timeStr = formatAbsoluteTime(event.createdAt);
|
|
34
|
-
|
|
35
|
-
const label = eventLabel(event.eventType);
|
|
36
|
-
|
|
37
|
-
const levelColorFn =
|
|
38
|
-
event.level === "error" ? color.red : event.level === "warn" ? color.yellow : null;
|
|
39
|
-
const applyLevel = (text: string) => (levelColorFn ? levelColorFn(text) : text);
|
|
40
|
-
|
|
41
|
-
const detail = buildEventDetail(event, 60);
|
|
42
|
-
const detailSuffix = detail ? ` ${color.dim(detail)}` : "";
|
|
43
|
-
|
|
44
|
-
const agentColorFn = colorMap.get(event.agentName) ?? color.gray;
|
|
45
|
-
const agentLabel = ` ${agentColorFn(event.agentName.padEnd(15))}`;
|
|
46
|
-
|
|
47
|
-
w(
|
|
48
|
-
`${color.dim(timeStr)} ` +
|
|
49
|
-
`${applyLevel(label.color(color.bold(label.compact)))}` +
|
|
50
|
-
`${agentLabel}${detailSuffix}\n`,
|
|
51
|
-
);
|
|
24
|
+
process.stdout.write(`${formatEventLine(event, colorMap)}\n`);
|
|
52
25
|
}
|
|
53
26
|
|
|
54
27
|
interface FeedOpts {
|
|
@@ -9,13 +9,14 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
12
|
-
import { mkdtemp
|
|
12
|
+
import { mkdtemp } from "node:fs/promises";
|
|
13
13
|
import { tmpdir } from "node:os";
|
|
14
14
|
import { join } from "node:path";
|
|
15
15
|
import { ValidationError } from "../errors.ts";
|
|
16
16
|
import { createEventStore } from "../events/store.ts";
|
|
17
17
|
import { createMetricsStore } from "../metrics/store.ts";
|
|
18
18
|
import { createSessionStore } from "../sessions/store.ts";
|
|
19
|
+
import { cleanupTempDir } from "../test-helpers.ts";
|
|
19
20
|
import type { InsertEvent, SessionMetrics } from "../types.ts";
|
|
20
21
|
import { gatherInspectData, inspectCommand } from "./inspect.ts";
|
|
21
22
|
|
|
@@ -89,7 +90,7 @@ describe("inspectCommand", () => {
|
|
|
89
90
|
afterEach(async () => {
|
|
90
91
|
process.stdout.write = originalWrite;
|
|
91
92
|
process.chdir(originalCwd);
|
|
92
|
-
await
|
|
93
|
+
await cleanupTempDir(tempDir);
|
|
93
94
|
});
|
|
94
95
|
|
|
95
96
|
function output(): string {
|
package/src/commands/log.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
-
import { mkdtemp, readdir,
|
|
2
|
+
import { mkdir, mkdtemp, readdir, stat } from "node:fs/promises";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { ValidationError } from "../errors.ts";
|
|
@@ -9,8 +9,9 @@ import { createMailStore } from "../mail/store.ts";
|
|
|
9
9
|
import { createMetricsStore } from "../metrics/store.ts";
|
|
10
10
|
import type { MulchClient } from "../mulch/client.ts";
|
|
11
11
|
import { createRunStore, createSessionStore } from "../sessions/store.ts";
|
|
12
|
+
import { cleanupTempDir } from "../test-helpers.ts";
|
|
12
13
|
import type { AgentSession, MulchLearnResult, StoredEvent } from "../types.ts";
|
|
13
|
-
import { autoRecordExpertise, logCommand } from "./log.ts";
|
|
14
|
+
import { appendOutcomeToAppliedRecords, autoRecordExpertise, logCommand } from "./log.ts";
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Tests for `overstory log` command.
|
|
@@ -50,7 +51,7 @@ describe("logCommand", () => {
|
|
|
50
51
|
afterEach(async () => {
|
|
51
52
|
process.stdout.write = originalWrite;
|
|
52
53
|
process.chdir(originalCwd);
|
|
53
|
-
await
|
|
54
|
+
await cleanupTempDir(tempDir);
|
|
54
55
|
});
|
|
55
56
|
|
|
56
57
|
function output(): string {
|
|
@@ -58,18 +59,28 @@ describe("logCommand", () => {
|
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
/**
|
|
61
|
-
* Fake MulchClient for testing autoRecordExpertise.
|
|
62
|
-
* Only learn() and
|
|
62
|
+
* Fake MulchClient for testing autoRecordExpertise and appendOutcomeToAppliedRecords.
|
|
63
|
+
* Only learn(), record(), and appendOutcome() are implemented — other methods are stubs.
|
|
63
64
|
* Justified: we are testing orchestration logic, not the mulch CLI itself.
|
|
64
65
|
*/
|
|
65
66
|
function createFakeMulchClient(
|
|
66
67
|
learnResult: MulchLearnResult,
|
|
67
|
-
opts?: { recordShouldFail?: boolean },
|
|
68
|
+
opts?: { recordShouldFail?: boolean; appendOutcomeShouldFail?: boolean },
|
|
68
69
|
): {
|
|
69
70
|
client: MulchClient;
|
|
70
71
|
recordCalls: Array<{ domain: string; options: Record<string, unknown> }>;
|
|
72
|
+
appendOutcomeCalls: Array<{
|
|
73
|
+
domain: string;
|
|
74
|
+
id: string;
|
|
75
|
+
outcome: Record<string, unknown>;
|
|
76
|
+
}>;
|
|
71
77
|
} {
|
|
72
78
|
const recordCalls: Array<{ domain: string; options: Record<string, unknown> }> = [];
|
|
79
|
+
const appendOutcomeCalls: Array<{
|
|
80
|
+
domain: string;
|
|
81
|
+
id: string;
|
|
82
|
+
outcome: Record<string, unknown>;
|
|
83
|
+
}> = [];
|
|
73
84
|
const client = {
|
|
74
85
|
async learn() {
|
|
75
86
|
return learnResult;
|
|
@@ -80,8 +91,14 @@ describe("logCommand", () => {
|
|
|
80
91
|
}
|
|
81
92
|
recordCalls.push({ domain, options });
|
|
82
93
|
},
|
|
94
|
+
async appendOutcome(domain: string, id: string, outcome: Record<string, unknown>) {
|
|
95
|
+
if (opts?.appendOutcomeShouldFail) {
|
|
96
|
+
throw new Error("mulch appendOutcome failed");
|
|
97
|
+
}
|
|
98
|
+
appendOutcomeCalls.push({ domain, id, outcome });
|
|
99
|
+
},
|
|
83
100
|
} as unknown as MulchClient;
|
|
84
|
-
return { client, recordCalls };
|
|
101
|
+
return { client, recordCalls, appendOutcomeCalls };
|
|
85
102
|
}
|
|
86
103
|
|
|
87
104
|
test("--help flag shows help text", async () => {
|
|
@@ -785,6 +802,54 @@ describe("logCommand", () => {
|
|
|
785
802
|
expect(eventsContent).toContain("unknown");
|
|
786
803
|
});
|
|
787
804
|
|
|
805
|
+
test("tool-start writes to EventStore without --stdin flag (Pi runtime path)", async () => {
|
|
806
|
+
await logCommand(["tool-start", "--agent", "pi-agent", "--tool-name", "Read"]);
|
|
807
|
+
|
|
808
|
+
const eventsDbPath = join(tempDir, ".overstory", "events.db");
|
|
809
|
+
const eventStore = createEventStore(eventsDbPath);
|
|
810
|
+
const events = eventStore.getByAgent("pi-agent");
|
|
811
|
+
eventStore.close();
|
|
812
|
+
|
|
813
|
+
expect(events).toHaveLength(1);
|
|
814
|
+
expect(events[0]?.eventType).toBe("tool_start");
|
|
815
|
+
expect(events[0]?.toolName).toBe("Read");
|
|
816
|
+
expect(events[0]?.sessionId).toBeNull();
|
|
817
|
+
expect(events[0]?.agentName).toBe("pi-agent");
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
test("tool-end writes to EventStore without --stdin flag (Pi runtime path)", async () => {
|
|
821
|
+
await logCommand(["tool-start", "--agent", "pi-end-agent", "--tool-name", "Write"]);
|
|
822
|
+
await logCommand(["tool-end", "--agent", "pi-end-agent", "--tool-name", "Write"]);
|
|
823
|
+
|
|
824
|
+
const eventsDbPath = join(tempDir, ".overstory", "events.db");
|
|
825
|
+
const eventStore = createEventStore(eventsDbPath);
|
|
826
|
+
const events = eventStore.getByAgent("pi-end-agent");
|
|
827
|
+
eventStore.close();
|
|
828
|
+
|
|
829
|
+
expect(events).toHaveLength(2);
|
|
830
|
+
const startEv = events.find((e) => e.eventType === "tool_start");
|
|
831
|
+
const endEv = events.find((e) => e.eventType === "tool_end");
|
|
832
|
+
expect(startEv).toBeDefined();
|
|
833
|
+
expect(endEv).toBeDefined();
|
|
834
|
+
expect(startEv?.toolName).toBe("Write");
|
|
835
|
+
expect(endEv?.toolName).toBe("Write");
|
|
836
|
+
expect(startEv?.sessionId).toBeNull();
|
|
837
|
+
});
|
|
838
|
+
|
|
839
|
+
test("session-end writes to EventStore without --stdin flag (Pi runtime path)", async () => {
|
|
840
|
+
await logCommand(["session-end", "--agent", "pi-session-agent"]);
|
|
841
|
+
|
|
842
|
+
const eventsDbPath = join(tempDir, ".overstory", "events.db");
|
|
843
|
+
const eventStore = createEventStore(eventsDbPath);
|
|
844
|
+
const events = eventStore.getByAgent("pi-session-agent");
|
|
845
|
+
eventStore.close();
|
|
846
|
+
|
|
847
|
+
expect(events).toHaveLength(1);
|
|
848
|
+
expect(events[0]?.eventType).toBe("session_end");
|
|
849
|
+
expect(events[0]?.sessionId).toBeNull();
|
|
850
|
+
expect(events[0]?.agentName).toBe("pi-session-agent");
|
|
851
|
+
});
|
|
852
|
+
|
|
788
853
|
test("--help includes --stdin option in output", async () => {
|
|
789
854
|
await logCommand(["--help"]);
|
|
790
855
|
const out = output();
|
|
@@ -1182,7 +1247,7 @@ describe("logCommand --stdin integration", () => {
|
|
|
1182
1247
|
});
|
|
1183
1248
|
|
|
1184
1249
|
afterEach(async () => {
|
|
1185
|
-
await
|
|
1250
|
+
await cleanupTempDir(tempDir);
|
|
1186
1251
|
});
|
|
1187
1252
|
|
|
1188
1253
|
/**
|
|
@@ -1387,6 +1452,7 @@ try {
|
|
|
1387
1452
|
const scriptPath = join(tempDir, "_run-log-empty.ts");
|
|
1388
1453
|
const scriptContent = `
|
|
1389
1454
|
import { logCommand } from "${join(import.meta.dir, "log.ts").replace(/\\/g, "/")}";
|
|
1455
|
+
|
|
1390
1456
|
try {
|
|
1391
1457
|
await logCommand(["tool-start", "--agent", "empty-stdin-agent", "--stdin"]);
|
|
1392
1458
|
} catch (e) {
|
|
@@ -1461,3 +1527,177 @@ try {
|
|
|
1461
1527
|
// tool_result is not stored in EventStore (filtered out), but tool_name was parsed correctly
|
|
1462
1528
|
});
|
|
1463
1529
|
});
|
|
1530
|
+
|
|
1531
|
+
describe("appendOutcomeToAppliedRecords", () => {
|
|
1532
|
+
let tempDir: string;
|
|
1533
|
+
|
|
1534
|
+
/** Minimal fake MulchClient for appendOutcomeToAppliedRecords tests. */
|
|
1535
|
+
function makeOutcomeClient(opts?: { appendOutcomeShouldFail?: boolean }): {
|
|
1536
|
+
client: MulchClient;
|
|
1537
|
+
appendOutcomeCalls: Array<{ domain: string; id: string; outcome: Record<string, unknown> }>;
|
|
1538
|
+
} {
|
|
1539
|
+
const appendOutcomeCalls: Array<{
|
|
1540
|
+
domain: string;
|
|
1541
|
+
id: string;
|
|
1542
|
+
outcome: Record<string, unknown>;
|
|
1543
|
+
}> = [];
|
|
1544
|
+
const client = {
|
|
1545
|
+
async appendOutcome(domain: string, id: string, outcome: Record<string, unknown>) {
|
|
1546
|
+
if (opts?.appendOutcomeShouldFail) throw new Error("mulch appendOutcome failed");
|
|
1547
|
+
appendOutcomeCalls.push({ domain, id, outcome });
|
|
1548
|
+
},
|
|
1549
|
+
} as unknown as MulchClient;
|
|
1550
|
+
return { client, appendOutcomeCalls };
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
beforeEach(async () => {
|
|
1554
|
+
tempDir = await mkdtemp(join(tmpdir(), "outcome-test-"));
|
|
1555
|
+
});
|
|
1556
|
+
|
|
1557
|
+
afterEach(async () => {
|
|
1558
|
+
await cleanupTempDir(tempDir);
|
|
1559
|
+
});
|
|
1560
|
+
|
|
1561
|
+
test("returns 0 when applied-records.json does not exist (backward compat)", async () => {
|
|
1562
|
+
const { client } = makeOutcomeClient();
|
|
1563
|
+
const count = await appendOutcomeToAppliedRecords({
|
|
1564
|
+
mulchClient: client,
|
|
1565
|
+
agentName: "test-agent",
|
|
1566
|
+
capability: "builder",
|
|
1567
|
+
taskId: "bead-001",
|
|
1568
|
+
projectRoot: tempDir,
|
|
1569
|
+
});
|
|
1570
|
+
expect(count).toBe(0);
|
|
1571
|
+
});
|
|
1572
|
+
|
|
1573
|
+
test("returns 0 when records array is empty", async () => {
|
|
1574
|
+
const agentDir = join(tempDir, ".overstory", "agents", "test-agent");
|
|
1575
|
+
await mkdir(agentDir, { recursive: true });
|
|
1576
|
+
await Bun.write(
|
|
1577
|
+
join(agentDir, "applied-records.json"),
|
|
1578
|
+
JSON.stringify({
|
|
1579
|
+
taskId: "bead-001",
|
|
1580
|
+
agentName: "test-agent",
|
|
1581
|
+
capability: "builder",
|
|
1582
|
+
records: [],
|
|
1583
|
+
}),
|
|
1584
|
+
);
|
|
1585
|
+
|
|
1586
|
+
const { client } = makeOutcomeClient();
|
|
1587
|
+
const count = await appendOutcomeToAppliedRecords({
|
|
1588
|
+
mulchClient: client,
|
|
1589
|
+
agentName: "test-agent",
|
|
1590
|
+
capability: "builder",
|
|
1591
|
+
taskId: "bead-001",
|
|
1592
|
+
projectRoot: tempDir,
|
|
1593
|
+
});
|
|
1594
|
+
expect(count).toBe(0);
|
|
1595
|
+
});
|
|
1596
|
+
|
|
1597
|
+
test("calls appendOutcome for each record and returns count", async () => {
|
|
1598
|
+
const agentDir = join(tempDir, ".overstory", "agents", "test-agent");
|
|
1599
|
+
await mkdir(agentDir, { recursive: true });
|
|
1600
|
+
const records = [
|
|
1601
|
+
{ id: "mx-aaa111", domain: "agents" },
|
|
1602
|
+
{ id: "mx-bbb222", domain: "typescript" },
|
|
1603
|
+
];
|
|
1604
|
+
await Bun.write(
|
|
1605
|
+
join(agentDir, "applied-records.json"),
|
|
1606
|
+
JSON.stringify({
|
|
1607
|
+
taskId: "bead-001",
|
|
1608
|
+
agentName: "test-agent",
|
|
1609
|
+
capability: "builder",
|
|
1610
|
+
records,
|
|
1611
|
+
}),
|
|
1612
|
+
);
|
|
1613
|
+
|
|
1614
|
+
const { client, appendOutcomeCalls } = makeOutcomeClient();
|
|
1615
|
+
const count = await appendOutcomeToAppliedRecords({
|
|
1616
|
+
mulchClient: client,
|
|
1617
|
+
agentName: "test-agent",
|
|
1618
|
+
capability: "builder",
|
|
1619
|
+
taskId: "bead-001",
|
|
1620
|
+
projectRoot: tempDir,
|
|
1621
|
+
});
|
|
1622
|
+
|
|
1623
|
+
expect(count).toBe(2);
|
|
1624
|
+
expect(appendOutcomeCalls).toHaveLength(2);
|
|
1625
|
+
expect(appendOutcomeCalls[0]).toMatchObject({ id: "mx-aaa111", domain: "agents" });
|
|
1626
|
+
expect(appendOutcomeCalls[1]).toMatchObject({ id: "mx-bbb222", domain: "typescript" });
|
|
1627
|
+
expect(appendOutcomeCalls[0]?.outcome).toMatchObject({
|
|
1628
|
+
status: "success",
|
|
1629
|
+
agent: "test-agent",
|
|
1630
|
+
});
|
|
1631
|
+
});
|
|
1632
|
+
|
|
1633
|
+
test("cleans up applied-records.json after processing", async () => {
|
|
1634
|
+
const agentDir = join(tempDir, ".overstory", "agents", "test-agent");
|
|
1635
|
+
await mkdir(agentDir, { recursive: true });
|
|
1636
|
+
const appliedPath = join(agentDir, "applied-records.json");
|
|
1637
|
+
await Bun.write(
|
|
1638
|
+
appliedPath,
|
|
1639
|
+
JSON.stringify({
|
|
1640
|
+
taskId: "bead-001",
|
|
1641
|
+
agentName: "test-agent",
|
|
1642
|
+
capability: "builder",
|
|
1643
|
+
records: [{ id: "mx-abc123", domain: "agents" }],
|
|
1644
|
+
}),
|
|
1645
|
+
);
|
|
1646
|
+
|
|
1647
|
+
const { client } = makeOutcomeClient();
|
|
1648
|
+
await appendOutcomeToAppliedRecords({
|
|
1649
|
+
mulchClient: client,
|
|
1650
|
+
agentName: "test-agent",
|
|
1651
|
+
capability: "builder",
|
|
1652
|
+
taskId: "bead-001",
|
|
1653
|
+
projectRoot: tempDir,
|
|
1654
|
+
});
|
|
1655
|
+
|
|
1656
|
+
expect(await Bun.file(appliedPath).exists()).toBe(false);
|
|
1657
|
+
});
|
|
1658
|
+
|
|
1659
|
+
test("continues when individual appendOutcome calls fail (non-fatal per record)", async () => {
|
|
1660
|
+
const agentDir = join(tempDir, ".overstory", "agents", "test-agent");
|
|
1661
|
+
await mkdir(agentDir, { recursive: true });
|
|
1662
|
+
const records = [
|
|
1663
|
+
{ id: "mx-fail111", domain: "agents" },
|
|
1664
|
+
{ id: "mx-fail222", domain: "typescript" },
|
|
1665
|
+
];
|
|
1666
|
+
await Bun.write(
|
|
1667
|
+
join(agentDir, "applied-records.json"),
|
|
1668
|
+
JSON.stringify({
|
|
1669
|
+
taskId: "bead-002",
|
|
1670
|
+
agentName: "test-agent",
|
|
1671
|
+
capability: "builder",
|
|
1672
|
+
records,
|
|
1673
|
+
}),
|
|
1674
|
+
);
|
|
1675
|
+
|
|
1676
|
+
// appendOutcomeShouldFail=true makes all calls throw — should return 0 but not throw
|
|
1677
|
+
const { client } = makeOutcomeClient({ appendOutcomeShouldFail: true });
|
|
1678
|
+
const count = await appendOutcomeToAppliedRecords({
|
|
1679
|
+
mulchClient: client,
|
|
1680
|
+
agentName: "test-agent",
|
|
1681
|
+
capability: "builder",
|
|
1682
|
+
taskId: "bead-002",
|
|
1683
|
+
projectRoot: tempDir,
|
|
1684
|
+
});
|
|
1685
|
+
expect(count).toBe(0);
|
|
1686
|
+
});
|
|
1687
|
+
|
|
1688
|
+
test("returns 0 for malformed JSON", async () => {
|
|
1689
|
+
const agentDir = join(tempDir, ".overstory", "agents", "test-agent");
|
|
1690
|
+
await mkdir(agentDir, { recursive: true });
|
|
1691
|
+
await Bun.write(join(agentDir, "applied-records.json"), "not-valid-json{{{");
|
|
1692
|
+
|
|
1693
|
+
const { client } = makeOutcomeClient();
|
|
1694
|
+
const count = await appendOutcomeToAppliedRecords({
|
|
1695
|
+
mulchClient: client,
|
|
1696
|
+
agentName: "test-agent",
|
|
1697
|
+
capability: "builder",
|
|
1698
|
+
taskId: null,
|
|
1699
|
+
projectRoot: tempDir,
|
|
1700
|
+
});
|
|
1701
|
+
expect(count).toBe(0);
|
|
1702
|
+
});
|
|
1703
|
+
});
|