@os-eco/overstory-cli 0.6.11 → 0.7.2
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 +12 -13
- package/agents/builder.md +1 -1
- package/agents/coordinator.md +12 -11
- package/agents/lead.md +25 -24
- 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 +5 -3
- package/src/agents/guard-rules.ts +97 -0
- package/src/agents/hooks-deployer.ts +7 -90
- package/src/agents/overlay.test.ts +30 -7
- package/src/agents/overlay.ts +10 -9
- package/src/commands/agents.test.ts +5 -0
- package/src/commands/clean.test.ts +3 -0
- package/src/commands/completions.ts +1 -1
- package/src/commands/coordinator.test.ts +1 -0
- package/src/commands/coordinator.ts +34 -18
- package/src/commands/costs.test.ts +6 -1
- package/src/commands/costs.ts +13 -20
- package/src/commands/dashboard.ts +38 -138
- package/src/commands/doctor.test.ts +1 -1
- package/src/commands/doctor.ts +2 -2
- package/src/commands/ecosystem.ts +2 -1
- package/src/commands/errors.test.ts +4 -5
- package/src/commands/errors.ts +4 -62
- package/src/commands/feed.test.ts +2 -2
- package/src/commands/feed.ts +12 -106
- package/src/commands/init.test.ts +1 -2
- package/src/commands/init.ts +1 -8
- package/src/commands/inspect.test.ts +14 -0
- package/src/commands/inspect.ts +10 -44
- package/src/commands/log.test.ts +14 -0
- package/src/commands/log.ts +39 -0
- package/src/commands/logs.ts +7 -63
- package/src/commands/mail.test.ts +5 -0
- package/src/commands/metrics.test.ts +2 -2
- package/src/commands/metrics.ts +3 -17
- package/src/commands/monitor.ts +30 -16
- package/src/commands/nudge.test.ts +1 -0
- package/src/commands/prime.test.ts +2 -0
- package/src/commands/prime.ts +6 -2
- package/src/commands/replay.test.ts +2 -2
- package/src/commands/replay.ts +12 -135
- package/src/commands/run.test.ts +1 -0
- package/src/commands/run.ts +7 -23
- package/src/commands/sling.test.ts +68 -1
- package/src/commands/sling.ts +62 -24
- package/src/commands/status.test.ts +1 -0
- package/src/commands/status.ts +4 -17
- package/src/commands/stop.test.ts +1 -0
- package/src/commands/supervisor.ts +35 -18
- package/src/commands/trace.test.ts +6 -6
- package/src/commands/trace.ts +11 -109
- package/src/commands/worktree.test.ts +9 -0
- package/src/config.ts +39 -0
- package/src/doctor/consistency.test.ts +14 -0
- package/src/e2e/init-sling-lifecycle.test.ts +3 -5
- package/src/index.ts +2 -1
- package/src/logging/format.ts +214 -0
- package/src/logging/theme.ts +132 -0
- package/src/mail/broadcast.test.ts +1 -0
- package/src/merge/resolver.ts +23 -4
- package/src/metrics/store.test.ts +46 -0
- package/src/metrics/store.ts +11 -0
- package/src/mulch/client.test.ts +20 -0
- package/src/mulch/client.ts +312 -45
- package/src/runtimes/claude.test.ts +616 -0
- package/src/runtimes/claude.ts +218 -0
- package/src/runtimes/pi-guards.test.ts +433 -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 +86 -0
- package/src/runtimes/registry.ts +46 -0
- package/src/runtimes/types.ts +188 -0
- package/src/schema-consistency.test.ts +1 -0
- package/src/sessions/compat.ts +1 -0
- package/src/sessions/store.test.ts +31 -0
- package/src/sessions/store.ts +37 -4
- package/src/types.ts +21 -0
- package/src/watchdog/daemon.test.ts +7 -4
- package/src/watchdog/daemon.ts +1 -1
- package/src/watchdog/health.test.ts +1 -0
- package/src/watchdog/triage.ts +14 -4
- package/src/worktree/tmux.test.ts +28 -13
- package/src/worktree/tmux.ts +14 -28
|
@@ -399,6 +399,52 @@ describe("getSessionsByRun", () => {
|
|
|
399
399
|
});
|
|
400
400
|
});
|
|
401
401
|
|
|
402
|
+
// === countSessions ===
|
|
403
|
+
|
|
404
|
+
describe("countSessions", () => {
|
|
405
|
+
test("returns 0 for empty database", () => {
|
|
406
|
+
expect(store.countSessions()).toBe(0);
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
test("returns total count of sessions", () => {
|
|
410
|
+
store.recordSession(makeSession({ agentName: "a1", taskId: "t1" }));
|
|
411
|
+
store.recordSession(makeSession({ agentName: "a2", taskId: "t2" }));
|
|
412
|
+
store.recordSession(makeSession({ agentName: "a3", taskId: "t3" }));
|
|
413
|
+
|
|
414
|
+
expect(store.countSessions()).toBe(3);
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
test("returns accurate count beyond getRecentSessions default limit", () => {
|
|
418
|
+
// Insert 25 sessions (more than the default limit of 20)
|
|
419
|
+
for (let i = 0; i < 25; i++) {
|
|
420
|
+
store.recordSession(
|
|
421
|
+
makeSession({
|
|
422
|
+
agentName: `agent-${i}`,
|
|
423
|
+
taskId: `task-${i}`,
|
|
424
|
+
startedAt: new Date(Date.now() + i * 1000).toISOString(),
|
|
425
|
+
}),
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// getRecentSessions is capped at 20 by default
|
|
430
|
+
expect(store.getRecentSessions().length).toBe(20);
|
|
431
|
+
// countSessions returns the true total without a cap
|
|
432
|
+
expect(store.countSessions()).toBe(25);
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
test("count updates after purge", () => {
|
|
436
|
+
store.recordSession(makeSession({ agentName: "a1", taskId: "t1" }));
|
|
437
|
+
store.recordSession(makeSession({ agentName: "a2", taskId: "t2" }));
|
|
438
|
+
expect(store.countSessions()).toBe(2);
|
|
439
|
+
|
|
440
|
+
store.purge({ agent: "a1" });
|
|
441
|
+
expect(store.countSessions()).toBe(1);
|
|
442
|
+
|
|
443
|
+
store.purge({ all: true });
|
|
444
|
+
expect(store.countSessions()).toBe(0);
|
|
445
|
+
});
|
|
446
|
+
});
|
|
447
|
+
|
|
402
448
|
// === purge ===
|
|
403
449
|
|
|
404
450
|
describe("purge", () => {
|
package/src/metrics/store.ts
CHANGED
|
@@ -14,6 +14,8 @@ export interface MetricsStore {
|
|
|
14
14
|
getSessionsByAgent(agentName: string): SessionMetrics[];
|
|
15
15
|
getSessionsByRun(runId: string): SessionMetrics[];
|
|
16
16
|
getAverageDuration(capability?: string): number;
|
|
17
|
+
/** Count the total number of sessions in the database (no limit cap). */
|
|
18
|
+
countSessions(): number;
|
|
17
19
|
/** Delete metrics matching the given criteria. Returns the number of rows deleted. */
|
|
18
20
|
purge(options: { all?: boolean; agent?: string }): number;
|
|
19
21
|
/** Record a token usage snapshot for a running agent. */
|
|
@@ -252,6 +254,10 @@ export function createMetricsStore(dbPath: string): MetricsStore {
|
|
|
252
254
|
SELECT AVG(duration_ms) AS avg_duration FROM sessions WHERE completed_at IS NOT NULL
|
|
253
255
|
`);
|
|
254
256
|
|
|
257
|
+
const countSessionsStmt = db.prepare<{ cnt: number }, Record<string, never>>(`
|
|
258
|
+
SELECT COUNT(*) as cnt FROM sessions
|
|
259
|
+
`);
|
|
260
|
+
|
|
255
261
|
const avgDurationByCapStmt = db.prepare<
|
|
256
262
|
{ avg_duration: number | null },
|
|
257
263
|
{ $capability: string }
|
|
@@ -345,6 +351,11 @@ export function createMetricsStore(dbPath: string): MetricsStore {
|
|
|
345
351
|
return row?.avg_duration ?? 0;
|
|
346
352
|
},
|
|
347
353
|
|
|
354
|
+
countSessions(): number {
|
|
355
|
+
const row = countSessionsStmt.get({});
|
|
356
|
+
return row?.cnt ?? 0;
|
|
357
|
+
},
|
|
358
|
+
|
|
348
359
|
purge(options: { all?: boolean; agent?: string }): number {
|
|
349
360
|
if (options.all) {
|
|
350
361
|
const countRow = db
|
package/src/mulch/client.test.ts
CHANGED
|
@@ -451,6 +451,26 @@ describe("createMulchClient", () => {
|
|
|
451
451
|
const result = await client.search("test", { file: "src/config.ts", sortByScore: true });
|
|
452
452
|
expect(typeof result).toBe("string");
|
|
453
453
|
});
|
|
454
|
+
|
|
455
|
+
test.skipIf(!hasMulch)("roundtrip: record via API then search and find it", async () => {
|
|
456
|
+
await initMulch();
|
|
457
|
+
const addProc = Bun.spawn(["ml", "add", "roundtrip"], {
|
|
458
|
+
cwd: tempDir,
|
|
459
|
+
stdout: "pipe",
|
|
460
|
+
stderr: "pipe",
|
|
461
|
+
});
|
|
462
|
+
await addProc.exited;
|
|
463
|
+
|
|
464
|
+
const client = createMulchClient(tempDir);
|
|
465
|
+
await client.record("roundtrip", {
|
|
466
|
+
type: "convention",
|
|
467
|
+
description: "unique-roundtrip-marker-xyz",
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
const result = await client.search("unique-roundtrip-marker-xyz");
|
|
471
|
+
expect(typeof result).toBe("string");
|
|
472
|
+
expect(result).toContain("unique-roundtrip-marker-xyz");
|
|
473
|
+
});
|
|
454
474
|
});
|
|
455
475
|
|
|
456
476
|
describe("diff", () => {
|
package/src/mulch/client.ts
CHANGED
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
* Mulch CLI client.
|
|
3
3
|
*
|
|
4
4
|
* Wraps the `mulch` command-line tool for structured expertise operations.
|
|
5
|
-
*
|
|
5
|
+
* record(), search(), and query() use the @os-eco/mulch-cli programmatic API
|
|
6
|
+
* via a variable-based dynamic import so tsc cannot statically resolve the
|
|
7
|
+
* module (avoiding type errors in mulch's raw .ts source files).
|
|
8
|
+
* Remaining methods (prime, status, diff, learn, prune, doctor, ready, compact)
|
|
9
|
+
* remain as Bun.spawn CLI wrappers.
|
|
6
10
|
*/
|
|
7
11
|
|
|
8
12
|
import { AgentError } from "../errors.ts";
|
|
@@ -87,6 +91,149 @@ export interface MulchClient {
|
|
|
87
91
|
): Promise<MulchCompactResult>;
|
|
88
92
|
}
|
|
89
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Local type matching @os-eco/mulch-cli ExpertiseRecord.
|
|
96
|
+
* Defined locally to avoid tsc following into mulch's raw .ts source
|
|
97
|
+
* (which conflicts with our noUncheckedIndexedAccess setting).
|
|
98
|
+
*/
|
|
99
|
+
type MulchClassification = "foundational" | "tactical" | "observational";
|
|
100
|
+
|
|
101
|
+
interface MulchEvidence {
|
|
102
|
+
commit?: string;
|
|
103
|
+
date?: string;
|
|
104
|
+
issue?: string;
|
|
105
|
+
file?: string;
|
|
106
|
+
bead?: string;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
interface MulchOutcome {
|
|
110
|
+
status: "success" | "failure" | "partial";
|
|
111
|
+
duration?: number;
|
|
112
|
+
test_results?: string;
|
|
113
|
+
agent?: string;
|
|
114
|
+
notes?: string;
|
|
115
|
+
recorded_at?: string;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
type MulchExpertiseRecord =
|
|
119
|
+
| {
|
|
120
|
+
type: "convention";
|
|
121
|
+
content: string;
|
|
122
|
+
classification: MulchClassification;
|
|
123
|
+
recorded_at: string;
|
|
124
|
+
id?: string;
|
|
125
|
+
tags?: string[];
|
|
126
|
+
evidence?: MulchEvidence;
|
|
127
|
+
outcomes?: MulchOutcome[];
|
|
128
|
+
relates_to?: string[];
|
|
129
|
+
supersedes?: string[];
|
|
130
|
+
}
|
|
131
|
+
| {
|
|
132
|
+
type: "pattern";
|
|
133
|
+
name: string;
|
|
134
|
+
description: string;
|
|
135
|
+
files?: string[];
|
|
136
|
+
classification: MulchClassification;
|
|
137
|
+
recorded_at: string;
|
|
138
|
+
id?: string;
|
|
139
|
+
tags?: string[];
|
|
140
|
+
evidence?: MulchEvidence;
|
|
141
|
+
outcomes?: MulchOutcome[];
|
|
142
|
+
relates_to?: string[];
|
|
143
|
+
supersedes?: string[];
|
|
144
|
+
}
|
|
145
|
+
| {
|
|
146
|
+
type: "failure";
|
|
147
|
+
description: string;
|
|
148
|
+
resolution: string;
|
|
149
|
+
classification: MulchClassification;
|
|
150
|
+
recorded_at: string;
|
|
151
|
+
id?: string;
|
|
152
|
+
tags?: string[];
|
|
153
|
+
evidence?: MulchEvidence;
|
|
154
|
+
outcomes?: MulchOutcome[];
|
|
155
|
+
relates_to?: string[];
|
|
156
|
+
supersedes?: string[];
|
|
157
|
+
}
|
|
158
|
+
| {
|
|
159
|
+
type: "decision";
|
|
160
|
+
title: string;
|
|
161
|
+
rationale: string;
|
|
162
|
+
classification: MulchClassification;
|
|
163
|
+
recorded_at: string;
|
|
164
|
+
id?: string;
|
|
165
|
+
tags?: string[];
|
|
166
|
+
evidence?: MulchEvidence;
|
|
167
|
+
outcomes?: MulchOutcome[];
|
|
168
|
+
relates_to?: string[];
|
|
169
|
+
supersedes?: string[];
|
|
170
|
+
}
|
|
171
|
+
| {
|
|
172
|
+
type: "reference";
|
|
173
|
+
name: string;
|
|
174
|
+
description: string;
|
|
175
|
+
files?: string[];
|
|
176
|
+
classification: MulchClassification;
|
|
177
|
+
recorded_at: string;
|
|
178
|
+
id?: string;
|
|
179
|
+
tags?: string[];
|
|
180
|
+
evidence?: MulchEvidence;
|
|
181
|
+
outcomes?: MulchOutcome[];
|
|
182
|
+
relates_to?: string[];
|
|
183
|
+
supersedes?: string[];
|
|
184
|
+
}
|
|
185
|
+
| {
|
|
186
|
+
type: "guide";
|
|
187
|
+
name: string;
|
|
188
|
+
description: string;
|
|
189
|
+
classification: MulchClassification;
|
|
190
|
+
recorded_at: string;
|
|
191
|
+
id?: string;
|
|
192
|
+
tags?: string[];
|
|
193
|
+
evidence?: MulchEvidence;
|
|
194
|
+
outcomes?: MulchOutcome[];
|
|
195
|
+
relates_to?: string[];
|
|
196
|
+
supersedes?: string[];
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Interface for mulch programmatic API functions.
|
|
201
|
+
* Uses a dynamic import with a variable specifier so tsc cannot statically
|
|
202
|
+
* resolve the module (avoiding type errors in mulch's raw .ts source files).
|
|
203
|
+
*/
|
|
204
|
+
interface MulchProgrammaticApi {
|
|
205
|
+
recordExpertise(
|
|
206
|
+
domain: string,
|
|
207
|
+
record: MulchExpertiseRecord,
|
|
208
|
+
options?: { force?: boolean; cwd?: string },
|
|
209
|
+
): Promise<{ action: "created" | "updated" | "skipped"; record: MulchExpertiseRecord }>;
|
|
210
|
+
searchExpertise(
|
|
211
|
+
query: string,
|
|
212
|
+
options?: {
|
|
213
|
+
domain?: string;
|
|
214
|
+
type?: string;
|
|
215
|
+
tag?: string;
|
|
216
|
+
classification?: string;
|
|
217
|
+
file?: string;
|
|
218
|
+
cwd?: string;
|
|
219
|
+
},
|
|
220
|
+
): Promise<Array<{ domain: string; records: MulchExpertiseRecord[] }>>;
|
|
221
|
+
queryDomain(
|
|
222
|
+
domain: string,
|
|
223
|
+
options?: { type?: string; classification?: string; file?: string; cwd?: string },
|
|
224
|
+
): Promise<MulchExpertiseRecord[]>;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const MULCH_PKG = "@os-eco/mulch-cli";
|
|
228
|
+
let _mulchApi: MulchProgrammaticApi | undefined;
|
|
229
|
+
|
|
230
|
+
async function loadMulchApi(): Promise<MulchProgrammaticApi> {
|
|
231
|
+
if (!_mulchApi) {
|
|
232
|
+
_mulchApi = (await import(MULCH_PKG)) as MulchProgrammaticApi;
|
|
233
|
+
}
|
|
234
|
+
return _mulchApi;
|
|
235
|
+
}
|
|
236
|
+
|
|
90
237
|
/**
|
|
91
238
|
* Run a shell command and capture its output.
|
|
92
239
|
*/
|
|
@@ -105,6 +252,127 @@ async function runCommand(
|
|
|
105
252
|
return { stdout, stderr, exitCode };
|
|
106
253
|
}
|
|
107
254
|
|
|
255
|
+
/**
|
|
256
|
+
* Build an ExpertiseRecord from record() options.
|
|
257
|
+
*
|
|
258
|
+
* CRITICAL MAPPING: --description maps to record.content for convention records,
|
|
259
|
+
* but to record.description for all other types.
|
|
260
|
+
*/
|
|
261
|
+
function buildExpertiseRecord(options: {
|
|
262
|
+
type: string;
|
|
263
|
+
name?: string;
|
|
264
|
+
description?: string;
|
|
265
|
+
title?: string;
|
|
266
|
+
rationale?: string;
|
|
267
|
+
tags?: string[];
|
|
268
|
+
classification?: string;
|
|
269
|
+
evidenceBead?: string;
|
|
270
|
+
outcomeStatus?: "success" | "failure";
|
|
271
|
+
outcomeDuration?: number;
|
|
272
|
+
outcomeTestResults?: string;
|
|
273
|
+
outcomeAgent?: string;
|
|
274
|
+
}): MulchExpertiseRecord {
|
|
275
|
+
const base = {
|
|
276
|
+
classification: (options.classification ?? "tactical") as
|
|
277
|
+
| "foundational"
|
|
278
|
+
| "tactical"
|
|
279
|
+
| "observational",
|
|
280
|
+
recorded_at: new Date().toISOString(),
|
|
281
|
+
tags: options.tags,
|
|
282
|
+
evidence: options.evidenceBead ? { bead: options.evidenceBead } : undefined,
|
|
283
|
+
outcomes: options.outcomeStatus
|
|
284
|
+
? [
|
|
285
|
+
{
|
|
286
|
+
status: options.outcomeStatus as "success" | "failure" | "partial",
|
|
287
|
+
duration: options.outcomeDuration,
|
|
288
|
+
test_results: options.outcomeTestResults,
|
|
289
|
+
agent: options.outcomeAgent,
|
|
290
|
+
recorded_at: new Date().toISOString(),
|
|
291
|
+
},
|
|
292
|
+
]
|
|
293
|
+
: undefined,
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
switch (options.type) {
|
|
297
|
+
case "convention":
|
|
298
|
+
return { ...base, type: "convention", content: options.description ?? "" };
|
|
299
|
+
case "pattern":
|
|
300
|
+
return {
|
|
301
|
+
...base,
|
|
302
|
+
type: "pattern",
|
|
303
|
+
name: options.name ?? "",
|
|
304
|
+
description: options.description ?? "",
|
|
305
|
+
};
|
|
306
|
+
case "failure":
|
|
307
|
+
return {
|
|
308
|
+
...base,
|
|
309
|
+
type: "failure",
|
|
310
|
+
description: options.description ?? "",
|
|
311
|
+
resolution: "",
|
|
312
|
+
};
|
|
313
|
+
case "decision":
|
|
314
|
+
return {
|
|
315
|
+
...base,
|
|
316
|
+
type: "decision",
|
|
317
|
+
title: options.title ?? "",
|
|
318
|
+
rationale: options.rationale ?? "",
|
|
319
|
+
};
|
|
320
|
+
case "reference":
|
|
321
|
+
return {
|
|
322
|
+
...base,
|
|
323
|
+
type: "reference",
|
|
324
|
+
name: options.name ?? "",
|
|
325
|
+
description: options.description ?? "",
|
|
326
|
+
};
|
|
327
|
+
case "guide":
|
|
328
|
+
return {
|
|
329
|
+
...base,
|
|
330
|
+
type: "guide",
|
|
331
|
+
name: options.name ?? "",
|
|
332
|
+
description: options.description ?? "",
|
|
333
|
+
};
|
|
334
|
+
default:
|
|
335
|
+
return {
|
|
336
|
+
...base,
|
|
337
|
+
type: "convention",
|
|
338
|
+
content: options.description ?? "",
|
|
339
|
+
} as MulchExpertiseRecord;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Format search/query results as a plain string for callers that expect string output.
|
|
345
|
+
* Preserves behavior for parseConflictPatterns regex in resolver.ts.
|
|
346
|
+
*/
|
|
347
|
+
function formatSearchResults(
|
|
348
|
+
results: Array<{ domain: string; records: MulchExpertiseRecord[] }>,
|
|
349
|
+
): string {
|
|
350
|
+
const lines: string[] = [];
|
|
351
|
+
for (const result of results) {
|
|
352
|
+
for (const record of result.records) {
|
|
353
|
+
lines.push(formatRecordText(record));
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return lines.join("\n");
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function formatRecordText(record: MulchExpertiseRecord): string {
|
|
360
|
+
switch (record.type) {
|
|
361
|
+
case "convention":
|
|
362
|
+
return record.content;
|
|
363
|
+
case "pattern":
|
|
364
|
+
return record.description;
|
|
365
|
+
case "failure":
|
|
366
|
+
return record.description;
|
|
367
|
+
case "decision":
|
|
368
|
+
return `${record.title}: ${record.rationale}`;
|
|
369
|
+
case "reference":
|
|
370
|
+
return record.description;
|
|
371
|
+
case "guide":
|
|
372
|
+
return record.description;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
108
376
|
/**
|
|
109
377
|
* Create a MulchClient bound to the given working directory.
|
|
110
378
|
*
|
|
@@ -158,61 +426,60 @@ export function createMulchClient(cwd: string): MulchClient {
|
|
|
158
426
|
},
|
|
159
427
|
|
|
160
428
|
async record(domain, options) {
|
|
161
|
-
|
|
162
|
-
if (options.name) {
|
|
163
|
-
args.push("--name", options.name);
|
|
164
|
-
}
|
|
165
|
-
if (options.description) {
|
|
166
|
-
args.push("--description", options.description);
|
|
167
|
-
}
|
|
168
|
-
if (options.title) {
|
|
169
|
-
args.push("--title", options.title);
|
|
170
|
-
}
|
|
171
|
-
if (options.rationale) {
|
|
172
|
-
args.push("--rationale", options.rationale);
|
|
173
|
-
}
|
|
174
|
-
if (options.tags && options.tags.length > 0) {
|
|
175
|
-
args.push("--tags", options.tags.join(","));
|
|
176
|
-
}
|
|
177
|
-
if (options.classification) {
|
|
178
|
-
args.push("--classification", options.classification);
|
|
179
|
-
}
|
|
429
|
+
// stdin mode: no programmatic API equivalent, fall back to CLI
|
|
180
430
|
if (options.stdin) {
|
|
431
|
+
const args = ["record", domain, "--type", options.type];
|
|
432
|
+
if (options.description) args.push("--description", options.description);
|
|
181
433
|
args.push("--stdin");
|
|
434
|
+
await runMulch(args, `record ${domain}`);
|
|
435
|
+
return;
|
|
182
436
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
437
|
+
|
|
438
|
+
const expertiseRecord = buildExpertiseRecord(options);
|
|
439
|
+
const api = await loadMulchApi();
|
|
440
|
+
try {
|
|
441
|
+
await api.recordExpertise(domain, expertiseRecord, { cwd });
|
|
442
|
+
} catch (error) {
|
|
443
|
+
if (error instanceof Error && error.message.includes("not found in config")) {
|
|
444
|
+
// Auto-create domain (matching mulch CLI 0.6.1+ behavior)
|
|
445
|
+
await runMulch(["add", domain], `add ${domain}`);
|
|
446
|
+
await api.recordExpertise(domain, expertiseRecord, { cwd });
|
|
447
|
+
} else {
|
|
448
|
+
throw new AgentError(
|
|
449
|
+
`mulch record ${domain} failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
450
|
+
);
|
|
451
|
+
}
|
|
197
452
|
}
|
|
198
|
-
await runMulch(args, `record ${domain}`);
|
|
199
453
|
},
|
|
200
454
|
|
|
201
455
|
async query(domain) {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
456
|
+
if (!domain) {
|
|
457
|
+
throw new AgentError("mulch query failed (exit 1): domain argument required");
|
|
458
|
+
}
|
|
459
|
+
try {
|
|
460
|
+
const api = await loadMulchApi();
|
|
461
|
+
const records = await api.queryDomain(domain, { cwd });
|
|
462
|
+
return formatSearchResults([{ domain, records }]);
|
|
463
|
+
} catch (error) {
|
|
464
|
+
throw new AgentError(
|
|
465
|
+
`mulch query failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
466
|
+
);
|
|
205
467
|
}
|
|
206
|
-
const { stdout } = await runMulch(args, "query");
|
|
207
|
-
return stdout;
|
|
208
468
|
},
|
|
209
469
|
|
|
210
470
|
async search(query, options) {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
471
|
+
try {
|
|
472
|
+
const api = await loadMulchApi();
|
|
473
|
+
const results = await api.searchExpertise(query, {
|
|
474
|
+
file: options?.file,
|
|
475
|
+
cwd,
|
|
476
|
+
});
|
|
477
|
+
return formatSearchResults(results);
|
|
478
|
+
} catch (error) {
|
|
479
|
+
throw new AgentError(
|
|
480
|
+
`mulch search failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
481
|
+
);
|
|
482
|
+
}
|
|
216
483
|
},
|
|
217
484
|
|
|
218
485
|
async diff(options) {
|