@os-eco/overstory-cli 0.7.0 → 0.7.3
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 +7 -6
- package/agents/builder.md +1 -1
- package/agents/coordinator.md +12 -11
- package/agents/lead.md +6 -6
- package/agents/monitor.md +4 -4
- package/agents/reviewer.md +1 -1
- package/agents/scout.md +5 -5
- package/agents/supervisor.md +36 -32
- package/package.json +1 -1
- package/src/agents/guard-rules.ts +97 -0
- package/src/agents/hooks-deployer.test.ts +6 -5
- package/src/agents/hooks-deployer.ts +7 -90
- package/src/agents/identity.test.ts +3 -2
- package/src/agents/manifest.test.ts +4 -3
- package/src/agents/overlay.test.ts +10 -9
- package/src/agents/overlay.ts +5 -5
- package/src/commands/agents.test.ts +10 -4
- package/src/commands/clean.test.ts +3 -0
- package/src/commands/completions.test.ts +8 -5
- package/src/commands/completions.ts +38 -2
- package/src/commands/coordinator.test.ts +1 -0
- package/src/commands/coordinator.ts +15 -11
- package/src/commands/costs.test.ts +9 -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/init.test.ts +1 -2
- package/src/commands/init.ts +1 -8
- package/src/commands/inspect.test.ts +17 -2
- package/src/commands/log.test.ts +262 -8
- package/src/commands/log.ts +232 -110
- package/src/commands/logs.test.ts +3 -2
- package/src/commands/mail.test.ts +8 -2
- package/src/commands/metrics.test.ts +4 -3
- package/src/commands/monitor.ts +15 -11
- package/src/commands/nudge.test.ts +4 -2
- package/src/commands/prime.test.ts +4 -2
- package/src/commands/prime.ts +6 -2
- package/src/commands/replay.test.ts +3 -2
- package/src/commands/run.test.ts +3 -1
- package/src/commands/sling.test.ts +142 -1
- package/src/commands/sling.ts +145 -24
- package/src/commands/status.test.ts +9 -8
- package/src/commands/stop.test.ts +1 -0
- package/src/commands/supervisor.ts +19 -12
- package/src/commands/trace.test.ts +4 -2
- package/src/commands/watch.test.ts +3 -2
- package/src/commands/worktree.test.ts +9 -0
- package/src/config.test.ts +3 -3
- package/src/config.ts +29 -0
- package/src/doctor/agents.test.ts +3 -2
- package/src/doctor/consistency.test.ts +14 -0
- package/src/doctor/logs.test.ts +3 -2
- package/src/doctor/structure.test.ts +3 -2
- package/src/e2e/init-sling-lifecycle.test.ts +3 -5
- 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/broadcast.test.ts +1 -0
- 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 +24 -5
- package/src/mulch/client.test.ts +63 -2
- package/src/mulch/client.ts +62 -1
- package/src/runtimes/claude.test.ts +5 -4
- package/src/runtimes/pi-guards.test.ts +457 -0
- package/src/runtimes/pi-guards.ts +349 -0
- package/src/runtimes/pi.test.ts +620 -0
- package/src/runtimes/pi.ts +244 -0
- package/src/runtimes/registry.test.ts +33 -0
- package/src/runtimes/registry.ts +15 -2
- package/src/runtimes/types.ts +63 -0
- package/src/schema-consistency.test.ts +5 -2
- package/src/sessions/compat.test.ts +3 -2
- package/src/sessions/compat.ts +1 -0
- package/src/sessions/store.test.ts +34 -2
- package/src/sessions/store.ts +37 -4
- package/src/test-helpers.ts +20 -1
- package/src/types.ts +17 -0
- package/src/watchdog/daemon.test.ts +11 -7
- package/src/watchdog/daemon.ts +1 -1
- package/src/watchdog/health.test.ts +1 -0
- package/src/watchdog/triage.test.ts +3 -2
- package/src/watchdog/triage.ts +14 -4
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
-
import { access, mkdtemp, readdir, readFile
|
|
2
|
+
import { access, mkdtemp, readdir, readFile } from "node:fs/promises";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
+
import { cleanupTempDir } from "../test-helpers.ts";
|
|
5
6
|
import type { LogEvent } from "../types.ts";
|
|
6
7
|
import { createLogger } from "./logger.ts";
|
|
7
8
|
|
|
@@ -15,7 +16,7 @@ describe("createLogger", () => {
|
|
|
15
16
|
});
|
|
16
17
|
|
|
17
18
|
afterEach(async () => {
|
|
18
|
-
await
|
|
19
|
+
await cleanupTempDir(tempDir);
|
|
19
20
|
});
|
|
20
21
|
|
|
21
22
|
async function readLogFile(filename: string): Promise<string> {
|
package/src/mail/client.test.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
-
import { mkdtemp
|
|
2
|
+
import { mkdtemp } from "node:fs/promises";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { MailError } from "../errors.ts";
|
|
6
|
+
import { cleanupTempDir } from "../test-helpers.ts";
|
|
6
7
|
import type { WorkerDonePayload } from "../types.ts";
|
|
7
8
|
import { createMailClient, type MailClient, parsePayload } from "./client.ts";
|
|
8
9
|
import { createMailStore, type MailStore } from "./store.ts";
|
|
@@ -20,7 +21,7 @@ describe("createMailClient", () => {
|
|
|
20
21
|
|
|
21
22
|
afterEach(async () => {
|
|
22
23
|
client.close();
|
|
23
|
-
await
|
|
24
|
+
await cleanupTempDir(tempDir);
|
|
24
25
|
});
|
|
25
26
|
|
|
26
27
|
describe("send", () => {
|
package/src/mail/store.test.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
-
import { mkdtemp
|
|
2
|
+
import { mkdtemp } from "node:fs/promises";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { MailError } from "../errors.ts";
|
|
6
|
+
import { cleanupTempDir } from "../test-helpers.ts";
|
|
6
7
|
import type { MailMessage } from "../types.ts";
|
|
7
8
|
import { createMailStore, type MailStore } from "./store.ts";
|
|
8
9
|
|
|
@@ -17,7 +18,7 @@ describe("createMailStore", () => {
|
|
|
17
18
|
|
|
18
19
|
afterEach(async () => {
|
|
19
20
|
store.close();
|
|
20
|
-
await
|
|
21
|
+
await cleanupTempDir(tempDir);
|
|
21
22
|
});
|
|
22
23
|
|
|
23
24
|
describe("insert", () => {
|
package/src/merge/queue.test.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Database } from "bun:sqlite";
|
|
2
2
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
3
|
-
import { mkdtemp
|
|
3
|
+
import { mkdtemp } from "node:fs/promises";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import { MergeError } from "../errors.ts";
|
|
7
|
+
import { cleanupTempDir } from "../test-helpers.ts";
|
|
7
8
|
import { createMergeQueue } from "./queue.ts";
|
|
8
9
|
|
|
9
10
|
describe("createMergeQueue", () => {
|
|
@@ -17,7 +18,7 @@ describe("createMergeQueue", () => {
|
|
|
17
18
|
});
|
|
18
19
|
|
|
19
20
|
afterEach(async () => {
|
|
20
|
-
await
|
|
21
|
+
await cleanupTempDir(tempDir);
|
|
21
22
|
});
|
|
22
23
|
|
|
23
24
|
function makeInput(
|
|
@@ -203,6 +203,9 @@ function createMockMulchClient(
|
|
|
203
203
|
action: "analyze",
|
|
204
204
|
};
|
|
205
205
|
},
|
|
206
|
+
async appendOutcome() {
|
|
207
|
+
// No-op stub: resolver tests don't exercise outcome appending
|
|
208
|
+
},
|
|
206
209
|
};
|
|
207
210
|
}
|
|
208
211
|
|
|
@@ -1440,6 +1443,42 @@ describe("createMergeResolver", () => {
|
|
|
1440
1443
|
});
|
|
1441
1444
|
});
|
|
1442
1445
|
|
|
1446
|
+
describe("queryConflictHistory uses sortByScore", () => {
|
|
1447
|
+
test("passes sortByScore: true to mulch search when querying conflict history", async () => {
|
|
1448
|
+
const repoDir = await createTempGitRepo();
|
|
1449
|
+
try {
|
|
1450
|
+
const defaultBranch = await getDefaultBranch(repoDir);
|
|
1451
|
+
await setupContentConflict(repoDir, defaultBranch);
|
|
1452
|
+
|
|
1453
|
+
const entry = makeTestEntry({
|
|
1454
|
+
branchName: "feature-branch",
|
|
1455
|
+
filesModified: ["src/test.ts"],
|
|
1456
|
+
});
|
|
1457
|
+
|
|
1458
|
+
// Capture search call options
|
|
1459
|
+
let capturedSearchOptions: unknown;
|
|
1460
|
+
const mockMulchClient = createMockMulchClient();
|
|
1461
|
+
mockMulchClient.search = async (_query, options) => {
|
|
1462
|
+
capturedSearchOptions = options;
|
|
1463
|
+
return "";
|
|
1464
|
+
};
|
|
1465
|
+
|
|
1466
|
+
const resolver = createMergeResolver({
|
|
1467
|
+
aiResolveEnabled: false,
|
|
1468
|
+
reimagineEnabled: false,
|
|
1469
|
+
mulchClient: mockMulchClient,
|
|
1470
|
+
});
|
|
1471
|
+
|
|
1472
|
+
await resolver.resolve(entry, defaultBranch, repoDir);
|
|
1473
|
+
|
|
1474
|
+
// Verify sortByScore was passed to search
|
|
1475
|
+
expect(capturedSearchOptions).toMatchObject({ sortByScore: true });
|
|
1476
|
+
} finally {
|
|
1477
|
+
await cleanupTempDir(repoDir);
|
|
1478
|
+
}
|
|
1479
|
+
});
|
|
1480
|
+
});
|
|
1481
|
+
|
|
1443
1482
|
describe("AI-resolve with history context", () => {
|
|
1444
1483
|
test("includes historical context in AI prompt when available", async () => {
|
|
1445
1484
|
const repoDir = await createTempGitRepo();
|
package/src/merge/resolver.ts
CHANGED
|
@@ -13,10 +13,12 @@
|
|
|
13
13
|
|
|
14
14
|
import { MergeError } from "../errors.ts";
|
|
15
15
|
import type { MulchClient } from "../mulch/client.ts";
|
|
16
|
+
import { getRuntime } from "../runtimes/registry.ts";
|
|
16
17
|
import type {
|
|
17
18
|
ConflictHistory,
|
|
18
19
|
MergeEntry,
|
|
19
20
|
MergeResult,
|
|
21
|
+
OverstoryConfig,
|
|
20
22
|
ParsedConflictPattern,
|
|
21
23
|
ResolutionTier,
|
|
22
24
|
} from "../types.ts";
|
|
@@ -243,6 +245,7 @@ async function tryAiResolve(
|
|
|
243
245
|
conflictFiles: string[],
|
|
244
246
|
repoRoot: string,
|
|
245
247
|
pastResolutions?: string[],
|
|
248
|
+
config?: OverstoryConfig,
|
|
246
249
|
): Promise<{ success: boolean; remainingConflicts: string[] }> {
|
|
247
250
|
const remainingConflicts: string[] = [];
|
|
248
251
|
|
|
@@ -265,7 +268,9 @@ async function tryAiResolve(
|
|
|
265
268
|
content,
|
|
266
269
|
].join(" ");
|
|
267
270
|
|
|
268
|
-
const
|
|
271
|
+
const runtime = getRuntime(config?.runtime?.printCommand ?? config?.runtime?.default, config);
|
|
272
|
+
const argv = runtime.buildPrintCommand(prompt);
|
|
273
|
+
const proc = Bun.spawn(argv, {
|
|
269
274
|
cwd: repoRoot,
|
|
270
275
|
stdout: "pipe",
|
|
271
276
|
stderr: "pipe",
|
|
@@ -315,6 +320,7 @@ async function tryReimagine(
|
|
|
315
320
|
entry: MergeEntry,
|
|
316
321
|
canonicalBranch: string,
|
|
317
322
|
repoRoot: string,
|
|
323
|
+
config?: OverstoryConfig,
|
|
318
324
|
): Promise<{ success: boolean }> {
|
|
319
325
|
// Abort the current merge
|
|
320
326
|
await runGit(repoRoot, ["merge", "--abort"]);
|
|
@@ -348,7 +354,9 @@ async function tryReimagine(
|
|
|
348
354
|
branchContent,
|
|
349
355
|
].join("");
|
|
350
356
|
|
|
351
|
-
const
|
|
357
|
+
const runtime = getRuntime(config?.runtime?.printCommand ?? config?.runtime?.default, config);
|
|
358
|
+
const argv = runtime.buildPrintCommand(prompt);
|
|
359
|
+
const proc = Bun.spawn(argv, {
|
|
352
360
|
cwd: repoRoot,
|
|
353
361
|
stdout: "pipe",
|
|
354
362
|
stderr: "pipe",
|
|
@@ -506,7 +514,7 @@ async function queryConflictHistory(
|
|
|
506
514
|
entry: MergeEntry,
|
|
507
515
|
): Promise<ConflictHistory> {
|
|
508
516
|
try {
|
|
509
|
-
const searchOutput = await mulchClient.search("merge-conflict");
|
|
517
|
+
const searchOutput = await mulchClient.search("merge-conflict", { sortByScore: true });
|
|
510
518
|
const patterns = parseConflictPatterns(searchOutput);
|
|
511
519
|
return buildConflictHistory(patterns, entry.filesModified);
|
|
512
520
|
} catch {
|
|
@@ -556,6 +564,7 @@ export function createMergeResolver(options: {
|
|
|
556
564
|
aiResolveEnabled: boolean;
|
|
557
565
|
reimagineEnabled: boolean;
|
|
558
566
|
mulchClient?: MulchClient;
|
|
567
|
+
config?: OverstoryConfig;
|
|
559
568
|
}): MergeResolver {
|
|
560
569
|
return {
|
|
561
570
|
async resolve(
|
|
@@ -632,7 +641,12 @@ export function createMergeResolver(options: {
|
|
|
632
641
|
// Tier 3: AI-resolve
|
|
633
642
|
if (options.aiResolveEnabled && !history.skipTiers.includes("ai-resolve")) {
|
|
634
643
|
lastTier = "ai-resolve";
|
|
635
|
-
const aiResult = await tryAiResolve(
|
|
644
|
+
const aiResult = await tryAiResolve(
|
|
645
|
+
conflictFiles,
|
|
646
|
+
repoRoot,
|
|
647
|
+
history.pastResolutions,
|
|
648
|
+
options.config,
|
|
649
|
+
);
|
|
636
650
|
if (aiResult.success) {
|
|
637
651
|
if (options.mulchClient) {
|
|
638
652
|
recordConflictPattern(options.mulchClient, entry, "ai-resolve", conflictFiles, true);
|
|
@@ -651,7 +665,12 @@ export function createMergeResolver(options: {
|
|
|
651
665
|
// Tier 4: Re-imagine
|
|
652
666
|
if (options.reimagineEnabled && !history.skipTiers.includes("reimagine")) {
|
|
653
667
|
lastTier = "reimagine";
|
|
654
|
-
const reimagineResult = await tryReimagine(
|
|
668
|
+
const reimagineResult = await tryReimagine(
|
|
669
|
+
entry,
|
|
670
|
+
canonicalBranch,
|
|
671
|
+
repoRoot,
|
|
672
|
+
options.config,
|
|
673
|
+
);
|
|
655
674
|
if (reimagineResult.success) {
|
|
656
675
|
if (options.mulchClient) {
|
|
657
676
|
recordConflictPattern(options.mulchClient, entry, "reimagine", conflictFiles, true);
|
package/src/mulch/client.test.ts
CHANGED
|
@@ -6,10 +6,11 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
9
|
-
import { mkdtemp
|
|
9
|
+
import { mkdtemp } from "node:fs/promises";
|
|
10
10
|
import { tmpdir } from "node:os";
|
|
11
11
|
import { join } from "node:path";
|
|
12
12
|
import { AgentError } from "../errors.ts";
|
|
13
|
+
import { cleanupTempDir } from "../test-helpers.ts";
|
|
13
14
|
import { createMulchClient } from "./client.ts";
|
|
14
15
|
|
|
15
16
|
// Check if mulch is available
|
|
@@ -30,7 +31,7 @@ describe("createMulchClient", () => {
|
|
|
30
31
|
});
|
|
31
32
|
|
|
32
33
|
afterEach(async () => {
|
|
33
|
-
await
|
|
34
|
+
await cleanupTempDir(tempDir);
|
|
34
35
|
});
|
|
35
36
|
|
|
36
37
|
/**
|
|
@@ -162,6 +163,33 @@ describe("createMulchClient", () => {
|
|
|
162
163
|
});
|
|
163
164
|
expect(typeof result).toBe("string");
|
|
164
165
|
});
|
|
166
|
+
|
|
167
|
+
test.skipIf(!hasMulch)("passes --sort-by-score flag in prime options", async () => {
|
|
168
|
+
await initMulch();
|
|
169
|
+
const client = createMulchClient(tempDir);
|
|
170
|
+
// mulch prime --sort-by-score may not be supported in older mulch versions;
|
|
171
|
+
// the interface and impl are forward-looking — test accepts both outcomes.
|
|
172
|
+
try {
|
|
173
|
+
const result = await client.prime([], "markdown", { sortByScore: true });
|
|
174
|
+
expect(typeof result).toBe("string");
|
|
175
|
+
} catch (error) {
|
|
176
|
+
expect(error).toBeInstanceOf(AgentError);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test.skipIf(!hasMulch)("passes --sort-by-score with --files together", async () => {
|
|
181
|
+
await initMulch();
|
|
182
|
+
const client = createMulchClient(tempDir);
|
|
183
|
+
try {
|
|
184
|
+
const result = await client.prime([], "markdown", {
|
|
185
|
+
files: ["src/config.ts"],
|
|
186
|
+
sortByScore: true,
|
|
187
|
+
});
|
|
188
|
+
expect(typeof result).toBe("string");
|
|
189
|
+
} catch (error) {
|
|
190
|
+
expect(error).toBeInstanceOf(AgentError);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
165
193
|
});
|
|
166
194
|
|
|
167
195
|
describe("status", () => {
|
|
@@ -452,6 +480,39 @@ describe("createMulchClient", () => {
|
|
|
452
480
|
expect(typeof result).toBe("string");
|
|
453
481
|
});
|
|
454
482
|
|
|
483
|
+
test.skipIf(!hasMulch)("passes --classification flag when provided", async () => {
|
|
484
|
+
await initMulch();
|
|
485
|
+
const client = createMulchClient(tempDir);
|
|
486
|
+
const result = await client.search("test", { classification: "foundational" });
|
|
487
|
+
expect(typeof result).toBe("string");
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
test.skipIf(!hasMulch)("passes --outcome-status flag when provided (success)", async () => {
|
|
491
|
+
await initMulch();
|
|
492
|
+
const client = createMulchClient(tempDir);
|
|
493
|
+
const result = await client.search("test", { outcomeStatus: "success" });
|
|
494
|
+
expect(typeof result).toBe("string");
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
test.skipIf(!hasMulch)("passes --outcome-status flag when provided (failure)", async () => {
|
|
498
|
+
await initMulch();
|
|
499
|
+
const client = createMulchClient(tempDir);
|
|
500
|
+
const result = await client.search("test", { outcomeStatus: "failure" });
|
|
501
|
+
expect(typeof result).toBe("string");
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
test.skipIf(!hasMulch)("passes all search filters together", async () => {
|
|
505
|
+
await initMulch();
|
|
506
|
+
const client = createMulchClient(tempDir);
|
|
507
|
+
const result = await client.search("test", {
|
|
508
|
+
classification: "tactical",
|
|
509
|
+
outcomeStatus: "success",
|
|
510
|
+
sortByScore: true,
|
|
511
|
+
file: "src/config.ts",
|
|
512
|
+
});
|
|
513
|
+
expect(typeof result).toBe("string");
|
|
514
|
+
});
|
|
515
|
+
|
|
455
516
|
test.skipIf(!hasMulch)("roundtrip: record via API then search and find it", async () => {
|
|
456
517
|
await initMulch();
|
|
457
518
|
const addProc = Bun.spawn(["ml", "add", "roundtrip"], {
|
package/src/mulch/client.ts
CHANGED
|
@@ -28,9 +28,22 @@ export interface MulchClient {
|
|
|
28
28
|
options?: {
|
|
29
29
|
files?: string[];
|
|
30
30
|
excludeDomain?: string[];
|
|
31
|
+
sortByScore?: boolean;
|
|
31
32
|
},
|
|
32
33
|
): Promise<string>;
|
|
33
34
|
|
|
35
|
+
/** Append an outcome entry to an existing record by ID in the given domain. */
|
|
36
|
+
appendOutcome(
|
|
37
|
+
domain: string,
|
|
38
|
+
id: string,
|
|
39
|
+
outcome: {
|
|
40
|
+
status: "success" | "failure" | "partial";
|
|
41
|
+
agent?: string;
|
|
42
|
+
notes?: string;
|
|
43
|
+
duration?: number;
|
|
44
|
+
},
|
|
45
|
+
): Promise<void>;
|
|
46
|
+
|
|
34
47
|
/** Show domain statistics. */
|
|
35
48
|
status(): Promise<MulchStatus>;
|
|
36
49
|
|
|
@@ -58,7 +71,15 @@ export interface MulchClient {
|
|
|
58
71
|
query(domain?: string): Promise<string>;
|
|
59
72
|
|
|
60
73
|
/** Search records across all domains. */
|
|
61
|
-
search(
|
|
74
|
+
search(
|
|
75
|
+
query: string,
|
|
76
|
+
options?: {
|
|
77
|
+
file?: string;
|
|
78
|
+
sortByScore?: boolean;
|
|
79
|
+
classification?: string;
|
|
80
|
+
outcomeStatus?: "success" | "failure";
|
|
81
|
+
},
|
|
82
|
+
): Promise<string>;
|
|
62
83
|
|
|
63
84
|
/** Show expertise record changes since a git ref. */
|
|
64
85
|
diff(options?: { since?: string }): Promise<MulchDiffResult>;
|
|
@@ -214,6 +235,8 @@ interface MulchProgrammaticApi {
|
|
|
214
235
|
type?: string;
|
|
215
236
|
tag?: string;
|
|
216
237
|
classification?: string;
|
|
238
|
+
outcomeStatus?: "success" | "failure";
|
|
239
|
+
sortByScore?: boolean;
|
|
217
240
|
file?: string;
|
|
218
241
|
cwd?: string;
|
|
219
242
|
},
|
|
@@ -222,6 +245,22 @@ interface MulchProgrammaticApi {
|
|
|
222
245
|
domain: string,
|
|
223
246
|
options?: { type?: string; classification?: string; file?: string; cwd?: string },
|
|
224
247
|
): Promise<MulchExpertiseRecord[]>;
|
|
248
|
+
appendOutcome(
|
|
249
|
+
domain: string,
|
|
250
|
+
id: string,
|
|
251
|
+
outcome: {
|
|
252
|
+
status: "success" | "failure" | "partial";
|
|
253
|
+
agent?: string;
|
|
254
|
+
notes?: string;
|
|
255
|
+
duration?: number;
|
|
256
|
+
recorded_at?: string;
|
|
257
|
+
},
|
|
258
|
+
options?: { cwd?: string },
|
|
259
|
+
): Promise<{
|
|
260
|
+
record: MulchExpertiseRecord;
|
|
261
|
+
outcome: { status: string; agent?: string; notes?: string; recorded_at?: string };
|
|
262
|
+
total_outcomes: number;
|
|
263
|
+
}>;
|
|
225
264
|
}
|
|
226
265
|
|
|
227
266
|
const MULCH_PKG = "@os-eco/mulch-cli";
|
|
@@ -406,6 +445,9 @@ export function createMulchClient(cwd: string): MulchClient {
|
|
|
406
445
|
if (options?.excludeDomain && options.excludeDomain.length > 0) {
|
|
407
446
|
args.push("--exclude-domain", ...options.excludeDomain);
|
|
408
447
|
}
|
|
448
|
+
if (options?.sortByScore) {
|
|
449
|
+
args.push("--sort-by-score");
|
|
450
|
+
}
|
|
409
451
|
const { stdout } = await runMulch(args, "prime");
|
|
410
452
|
return stdout;
|
|
411
453
|
},
|
|
@@ -472,6 +514,9 @@ export function createMulchClient(cwd: string): MulchClient {
|
|
|
472
514
|
const api = await loadMulchApi();
|
|
473
515
|
const results = await api.searchExpertise(query, {
|
|
474
516
|
file: options?.file,
|
|
517
|
+
classification: options?.classification,
|
|
518
|
+
outcomeStatus: options?.outcomeStatus,
|
|
519
|
+
sortByScore: options?.sortByScore,
|
|
475
520
|
cwd,
|
|
476
521
|
});
|
|
477
522
|
return formatSearchResults(results);
|
|
@@ -595,5 +640,21 @@ export function createMulchClient(cwd: string): MulchClient {
|
|
|
595
640
|
throw new AgentError(`Failed to parse JSON from mulch compact: ${trimmed.slice(0, 200)}`);
|
|
596
641
|
}
|
|
597
642
|
},
|
|
643
|
+
|
|
644
|
+
async appendOutcome(domain, id, outcome) {
|
|
645
|
+
const api = await loadMulchApi();
|
|
646
|
+
try {
|
|
647
|
+
await api.appendOutcome(
|
|
648
|
+
domain,
|
|
649
|
+
id,
|
|
650
|
+
{ ...outcome, recorded_at: new Date().toISOString() },
|
|
651
|
+
{ cwd },
|
|
652
|
+
);
|
|
653
|
+
} catch (error) {
|
|
654
|
+
throw new AgentError(
|
|
655
|
+
`mulch appendOutcome ${domain}/${id} failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
},
|
|
598
659
|
};
|
|
599
660
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
-
import { mkdtemp
|
|
2
|
+
import { mkdtemp } from "node:fs/promises";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
+
import { cleanupTempDir } from "../test-helpers.ts";
|
|
5
6
|
import type { ResolvedModel } from "../types.ts";
|
|
6
7
|
import { ClaudeRuntime } from "./claude.ts";
|
|
7
8
|
import type { SpawnOpts } from "./types.ts";
|
|
@@ -239,7 +240,7 @@ describe("ClaudeRuntime", () => {
|
|
|
239
240
|
});
|
|
240
241
|
|
|
241
242
|
afterEach(async () => {
|
|
242
|
-
await
|
|
243
|
+
await cleanupTempDir(tempDir);
|
|
243
244
|
});
|
|
244
245
|
|
|
245
246
|
test("writes overlay to .claude/CLAUDE.md when overlay is provided", async () => {
|
|
@@ -373,7 +374,7 @@ describe("ClaudeRuntime", () => {
|
|
|
373
374
|
});
|
|
374
375
|
|
|
375
376
|
afterEach(async () => {
|
|
376
|
-
await
|
|
377
|
+
await cleanupTempDir(tempDir);
|
|
377
378
|
});
|
|
378
379
|
|
|
379
380
|
test("returns null for non-existent file", async () => {
|
|
@@ -611,6 +612,6 @@ describe("ClaudeRuntime integration: registry resolves 'claude' as default", ()
|
|
|
611
612
|
test("getRuntime rejects unknown runtimes", async () => {
|
|
612
613
|
const { getRuntime } = await import("./registry.ts");
|
|
613
614
|
expect(() => getRuntime("codex")).toThrow('Unknown runtime: "codex"');
|
|
614
|
-
expect(() => getRuntime("
|
|
615
|
+
expect(() => getRuntime("opencode")).toThrow('Unknown runtime: "opencode"');
|
|
615
616
|
});
|
|
616
617
|
});
|