@os-eco/overstory-cli 0.6.8 → 0.6.10
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 +19 -5
- package/agents/builder.md +6 -15
- package/agents/lead.md +4 -6
- package/agents/merger.md +5 -13
- package/agents/reviewer.md +2 -9
- package/package.json +1 -1
- package/src/agents/hooks-deployer.test.ts +232 -0
- package/src/agents/hooks-deployer.ts +54 -8
- package/src/agents/overlay.test.ts +156 -1
- package/src/agents/overlay.ts +67 -7
- package/src/commands/agents.ts +9 -6
- package/src/commands/clean.ts +2 -1
- package/src/commands/completions.test.ts +8 -20
- package/src/commands/completions.ts +7 -6
- package/src/commands/coordinator.test.ts +8 -0
- package/src/commands/coordinator.ts +11 -8
- package/src/commands/costs.test.ts +48 -38
- package/src/commands/costs.ts +48 -38
- package/src/commands/dashboard.ts +7 -7
- package/src/commands/doctor.test.ts +8 -0
- package/src/commands/doctor.ts +96 -51
- package/src/commands/ecosystem.ts +291 -0
- package/src/commands/errors.test.ts +47 -40
- package/src/commands/errors.ts +5 -4
- package/src/commands/feed.test.ts +40 -33
- package/src/commands/feed.ts +5 -4
- package/src/commands/group.ts +23 -14
- package/src/commands/hooks.ts +2 -1
- package/src/commands/init.test.ts +104 -0
- package/src/commands/init.ts +11 -7
- package/src/commands/inspect.test.ts +2 -0
- package/src/commands/inspect.ts +9 -8
- package/src/commands/logs.test.ts +5 -6
- package/src/commands/logs.ts +2 -1
- package/src/commands/mail.test.ts +11 -10
- package/src/commands/mail.ts +11 -12
- package/src/commands/merge.ts +11 -12
- package/src/commands/metrics.test.ts +15 -2
- package/src/commands/metrics.ts +3 -2
- package/src/commands/monitor.ts +5 -4
- package/src/commands/nudge.ts +2 -3
- package/src/commands/prime.test.ts +1 -6
- package/src/commands/prime.ts +2 -3
- package/src/commands/replay.test.ts +62 -55
- package/src/commands/replay.ts +3 -2
- package/src/commands/run.ts +17 -20
- package/src/commands/sling.ts +3 -2
- package/src/commands/status.test.ts +2 -1
- package/src/commands/status.ts +7 -6
- package/src/commands/stop.test.ts +2 -0
- package/src/commands/stop.ts +10 -11
- package/src/commands/supervisor.ts +7 -6
- package/src/commands/trace.test.ts +52 -44
- package/src/commands/trace.ts +5 -4
- package/src/commands/upgrade.test.ts +46 -0
- package/src/commands/upgrade.ts +259 -0
- package/src/commands/watch.ts +8 -10
- package/src/commands/worktree.test.ts +21 -15
- package/src/commands/worktree.ts +10 -4
- package/src/doctor/databases.test.ts +38 -0
- package/src/doctor/databases.ts +7 -10
- package/src/doctor/ecosystem.test.ts +307 -0
- package/src/doctor/ecosystem.ts +155 -0
- package/src/doctor/merge-queue.test.ts +98 -0
- package/src/doctor/merge-queue.ts +23 -0
- package/src/doctor/structure.test.ts +130 -1
- package/src/doctor/structure.ts +87 -1
- package/src/doctor/types.ts +5 -2
- package/src/index.ts +25 -1
package/src/commands/sling.ts
CHANGED
|
@@ -27,6 +27,7 @@ import { writeOverlay } from "../agents/overlay.ts";
|
|
|
27
27
|
import { loadConfig } from "../config.ts";
|
|
28
28
|
import { AgentError, HierarchyError, ValidationError } from "../errors.ts";
|
|
29
29
|
import { inferDomain } from "../insights/analyzer.ts";
|
|
30
|
+
import { jsonOutput } from "../json.ts";
|
|
30
31
|
import { printSuccess } from "../logging/color.ts";
|
|
31
32
|
import { createMailClient } from "../mail/client.ts";
|
|
32
33
|
import { createMailStore } from "../mail/store.ts";
|
|
@@ -560,7 +561,7 @@ export async function slingCommand(taskId: string, opts: SlingOptions): Promise<
|
|
|
560
561
|
}
|
|
561
562
|
|
|
562
563
|
// 9. Deploy hooks config (capability-specific guards)
|
|
563
|
-
await deployHooks(worktreePath, name, capability);
|
|
564
|
+
await deployHooks(worktreePath, name, capability, config.project.qualityGates);
|
|
564
565
|
|
|
565
566
|
// 9b. Send auto-dispatch mail so it exists when SessionStart hook fires.
|
|
566
567
|
// This eliminates the race where coordinator sends dispatch AFTER agent boots.
|
|
@@ -708,7 +709,7 @@ export async function slingCommand(taskId: string, opts: SlingOptions): Promise<
|
|
|
708
709
|
};
|
|
709
710
|
|
|
710
711
|
if (opts.json ?? false) {
|
|
711
|
-
|
|
712
|
+
jsonOutput("sling", output);
|
|
712
713
|
} else {
|
|
713
714
|
printSuccess("Agent launched", name);
|
|
714
715
|
process.stdout.write(` Task: ${taskId}\n`);
|
|
@@ -2,6 +2,7 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
|
2
2
|
import { mkdir, mkdtemp, rm } from "node:fs/promises";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
+
import { stripAnsi } from "../logging/color.ts";
|
|
5
6
|
import { createSessionStore } from "../sessions/store.ts";
|
|
6
7
|
import { createTempGitRepo } from "../test-helpers.ts";
|
|
7
8
|
import type { AgentSession } from "../types.ts";
|
|
@@ -258,7 +259,7 @@ describe("run scoping", () => {
|
|
|
258
259
|
test("printStatus shows run ID when currentRunId is set", () => {
|
|
259
260
|
const data = makeStatusData({ currentRunId: "run-123" });
|
|
260
261
|
printStatus(data);
|
|
261
|
-
expect(output()).toContain("Run: run-123");
|
|
262
|
+
expect(stripAnsi(output())).toContain("Run: run-123");
|
|
262
263
|
});
|
|
263
264
|
|
|
264
265
|
test("printStatus does not show run line when currentRunId is undefined", () => {
|
package/src/commands/status.ts
CHANGED
|
@@ -9,7 +9,8 @@ import { join } from "node:path";
|
|
|
9
9
|
import { Command } from "commander";
|
|
10
10
|
import { loadConfig } from "../config.ts";
|
|
11
11
|
import { ValidationError } from "../errors.ts";
|
|
12
|
-
import {
|
|
12
|
+
import { jsonOutput } from "../json.ts";
|
|
13
|
+
import { accent, color } from "../logging/color.ts";
|
|
13
14
|
import { createMailStore } from "../mail/store.ts";
|
|
14
15
|
import { createMergeQueue } from "../merge/queue.ts";
|
|
15
16
|
import { createMetricsStore } from "../metrics/store.ts";
|
|
@@ -258,7 +259,7 @@ export function printStatus(data: StatusData): void {
|
|
|
258
259
|
w("Overstory Status\n");
|
|
259
260
|
w(`${"═".repeat(60)}\n\n`);
|
|
260
261
|
if (data.currentRunId) {
|
|
261
|
-
w(`Run: ${data.currentRunId}\n`);
|
|
262
|
+
w(`Run: ${accent(data.currentRunId)}\n`);
|
|
262
263
|
}
|
|
263
264
|
|
|
264
265
|
// Active agents
|
|
@@ -274,8 +275,8 @@ export function printStatus(data: StatusData): void {
|
|
|
274
275
|
const duration = formatDuration(endTime - new Date(agent.startedAt).getTime());
|
|
275
276
|
const tmuxAlive = tmuxSessionNames.has(agent.tmuxSession);
|
|
276
277
|
const aliveMarker = tmuxAlive ? color.green(">") : color.red("x");
|
|
277
|
-
w(` ${aliveMarker} ${agent.agentName} [${agent.capability}] `);
|
|
278
|
-
w(`${agent.state} | ${agent.taskId} | ${duration}\n`);
|
|
278
|
+
w(` ${aliveMarker} ${accent(agent.agentName)} [${agent.capability}] `);
|
|
279
|
+
w(`${agent.state} | ${accent(agent.taskId)} | ${duration}\n`);
|
|
279
280
|
|
|
280
281
|
const detail = data.verboseDetails?.[agent.agentName];
|
|
281
282
|
if (detail) {
|
|
@@ -357,7 +358,7 @@ async function executeStatus(opts: StatusOpts): Promise<void> {
|
|
|
357
358
|
process.stdout.write("\x1b[2J\x1b[H");
|
|
358
359
|
const data = await gatherStatus(root, agentName, verbose, runId);
|
|
359
360
|
if (json) {
|
|
360
|
-
|
|
361
|
+
jsonOutput("status", data as unknown as Record<string, unknown>);
|
|
361
362
|
} else {
|
|
362
363
|
printStatus(data);
|
|
363
364
|
}
|
|
@@ -366,7 +367,7 @@ async function executeStatus(opts: StatusOpts): Promise<void> {
|
|
|
366
367
|
} else {
|
|
367
368
|
const data = await gatherStatus(root, agentName, verbose, runId);
|
|
368
369
|
if (json) {
|
|
369
|
-
|
|
370
|
+
jsonOutput("status", data as unknown as Record<string, unknown>);
|
|
370
371
|
} else {
|
|
371
372
|
printStatus(data);
|
|
372
373
|
}
|
|
@@ -314,6 +314,8 @@ describe("stopCommand --json output", () => {
|
|
|
314
314
|
const output = await captureStdout(() => stopCommand("my-builder", { json: true }, deps));
|
|
315
315
|
|
|
316
316
|
const parsed = JSON.parse(output.trim()) as Record<string, unknown>;
|
|
317
|
+
expect(parsed.success).toBe(true);
|
|
318
|
+
expect(parsed.command).toBe("stop");
|
|
317
319
|
expect(parsed.stopped).toBe(true);
|
|
318
320
|
expect(parsed.agentName).toBe("my-builder");
|
|
319
321
|
expect(parsed.sessionId).toBe(session.id);
|
package/src/commands/stop.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import { join } from "node:path";
|
|
12
12
|
import { loadConfig } from "../config.ts";
|
|
13
13
|
import { AgentError, ValidationError } from "../errors.ts";
|
|
14
|
+
import { jsonOutput } from "../json.ts";
|
|
14
15
|
import { printSuccess, printWarning } from "../logging/color.ts";
|
|
15
16
|
import { openSessionStore } from "../sessions/compat.ts";
|
|
16
17
|
import { removeWorktree } from "../worktree/manager.ts";
|
|
@@ -109,17 +110,15 @@ export async function stopCommand(
|
|
|
109
110
|
}
|
|
110
111
|
|
|
111
112
|
if (json) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
})}\n`,
|
|
122
|
-
);
|
|
113
|
+
jsonOutput("stop", {
|
|
114
|
+
stopped: true,
|
|
115
|
+
agentName,
|
|
116
|
+
sessionId: session.id,
|
|
117
|
+
capability: session.capability,
|
|
118
|
+
tmuxKilled: alive,
|
|
119
|
+
worktreeRemoved,
|
|
120
|
+
force,
|
|
121
|
+
});
|
|
123
122
|
} else {
|
|
124
123
|
printSuccess("Agent stopped", agentName);
|
|
125
124
|
if (alive) {
|
|
@@ -20,6 +20,7 @@ import { createIdentity, loadIdentity } from "../agents/identity.ts";
|
|
|
20
20
|
import { createManifestLoader, resolveModel } from "../agents/manifest.ts";
|
|
21
21
|
import { loadConfig } from "../config.ts";
|
|
22
22
|
import { AgentError, ValidationError } from "../errors.ts";
|
|
23
|
+
import { jsonOutput } from "../json.ts";
|
|
23
24
|
import { printHint, printSuccess } from "../logging/color.ts";
|
|
24
25
|
import { openSessionStore } from "../sessions/compat.ts";
|
|
25
26
|
import { createTrackerClient, resolveBackend, trackerCliName } from "../tracker/factory.ts";
|
|
@@ -230,7 +231,7 @@ async function startSupervisor(opts: {
|
|
|
230
231
|
};
|
|
231
232
|
|
|
232
233
|
if (opts.json) {
|
|
233
|
-
|
|
234
|
+
jsonOutput("supervisor start", output);
|
|
234
235
|
} else {
|
|
235
236
|
printSuccess("Supervisor started", opts.name);
|
|
236
237
|
process.stdout.write(` Tmux: ${tmuxSession}\n`);
|
|
@@ -291,7 +292,7 @@ async function stopSupervisor(opts: { name: string; json: boolean }): Promise<vo
|
|
|
291
292
|
store.updateLastActivity(opts.name);
|
|
292
293
|
|
|
293
294
|
if (opts.json) {
|
|
294
|
-
|
|
295
|
+
jsonOutput("supervisor stop", { stopped: true, sessionId: session.id });
|
|
295
296
|
} else {
|
|
296
297
|
printSuccess("Supervisor stopped", opts.name);
|
|
297
298
|
}
|
|
@@ -325,7 +326,7 @@ async function statusSupervisor(opts: { name?: string; json: boolean }): Promise
|
|
|
325
326
|
session.state === "zombie"
|
|
326
327
|
) {
|
|
327
328
|
if (opts.json) {
|
|
328
|
-
|
|
329
|
+
jsonOutput("supervisor status", { running: false });
|
|
329
330
|
} else {
|
|
330
331
|
printHint("Supervisor not running");
|
|
331
332
|
}
|
|
@@ -357,7 +358,7 @@ async function statusSupervisor(opts: { name?: string; json: boolean }): Promise
|
|
|
357
358
|
};
|
|
358
359
|
|
|
359
360
|
if (opts.json) {
|
|
360
|
-
|
|
361
|
+
jsonOutput("supervisor status", status);
|
|
361
362
|
} else {
|
|
362
363
|
const stateLabel = alive ? "running" : session.state;
|
|
363
364
|
process.stdout.write(`Supervisor '${opts.name}': ${stateLabel}\n`);
|
|
@@ -377,7 +378,7 @@ async function statusSupervisor(opts: { name?: string; json: boolean }): Promise
|
|
|
377
378
|
|
|
378
379
|
if (supervisors.length === 0) {
|
|
379
380
|
if (opts.json) {
|
|
380
|
-
|
|
381
|
+
jsonOutput("supervisor status", { supervisors: [] });
|
|
381
382
|
} else {
|
|
382
383
|
printHint("No supervisor sessions found");
|
|
383
384
|
}
|
|
@@ -411,7 +412,7 @@ async function statusSupervisor(opts: { name?: string; json: boolean }): Promise
|
|
|
411
412
|
);
|
|
412
413
|
|
|
413
414
|
if (opts.json) {
|
|
414
|
-
|
|
415
|
+
jsonOutput("supervisor status", { supervisors: statuses });
|
|
415
416
|
} else {
|
|
416
417
|
process.stdout.write("Supervisor sessions:\n");
|
|
417
418
|
for (const status of statuses) {
|
|
@@ -14,6 +14,7 @@ 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 { stripAnsi } from "../logging/color.ts";
|
|
17
18
|
import { createSessionStore } from "../sessions/store.ts";
|
|
18
19
|
import type { InsertEvent } from "../types.ts";
|
|
19
20
|
import { traceCommand } from "./trace.ts";
|
|
@@ -149,8 +150,8 @@ describe("traceCommand", () => {
|
|
|
149
150
|
|
|
150
151
|
await traceCommand(["--json", "--limit", "50", "my-agent"]);
|
|
151
152
|
const out = output();
|
|
152
|
-
const parsed = JSON.parse(out.trim()) as unknown[];
|
|
153
|
-
expect(parsed).toHaveLength(1);
|
|
153
|
+
const parsed = JSON.parse(out.trim()) as { events: unknown[] };
|
|
154
|
+
expect(parsed.events).toHaveLength(1);
|
|
154
155
|
});
|
|
155
156
|
|
|
156
157
|
test("target is extracted correctly when flags come after", async () => {
|
|
@@ -161,8 +162,8 @@ describe("traceCommand", () => {
|
|
|
161
162
|
|
|
162
163
|
await traceCommand(["my-agent", "--json", "--limit", "50"]);
|
|
163
164
|
const out = output();
|
|
164
|
-
const parsed = JSON.parse(out.trim()) as unknown[];
|
|
165
|
-
expect(parsed).toHaveLength(1);
|
|
165
|
+
const parsed = JSON.parse(out.trim()) as { events: unknown[] };
|
|
166
|
+
expect(parsed.events).toHaveLength(1);
|
|
166
167
|
});
|
|
167
168
|
});
|
|
168
169
|
|
|
@@ -180,7 +181,14 @@ describe("traceCommand", () => {
|
|
|
180
181
|
await traceCommand(["builder-1", "--json"]);
|
|
181
182
|
const out = output();
|
|
182
183
|
|
|
183
|
-
|
|
184
|
+
const parsed = JSON.parse(out.trim()) as {
|
|
185
|
+
success: boolean;
|
|
186
|
+
command: string;
|
|
187
|
+
events: unknown[];
|
|
188
|
+
};
|
|
189
|
+
expect(parsed.success).toBe(true);
|
|
190
|
+
expect(parsed.command).toBe("trace");
|
|
191
|
+
expect(parsed.events).toEqual([]);
|
|
184
192
|
});
|
|
185
193
|
});
|
|
186
194
|
|
|
@@ -198,9 +206,9 @@ describe("traceCommand", () => {
|
|
|
198
206
|
await traceCommand(["builder-1", "--json"]);
|
|
199
207
|
const out = output();
|
|
200
208
|
|
|
201
|
-
const parsed = JSON.parse(out.trim()) as unknown[];
|
|
202
|
-
expect(parsed).toHaveLength(3);
|
|
203
|
-
expect(Array.isArray(parsed)).toBe(true);
|
|
209
|
+
const parsed = JSON.parse(out.trim()) as { events: unknown[] };
|
|
210
|
+
expect(parsed.events).toHaveLength(3);
|
|
211
|
+
expect(Array.isArray(parsed.events)).toBe(true);
|
|
204
212
|
});
|
|
205
213
|
|
|
206
214
|
test("JSON output includes expected fields", async () => {
|
|
@@ -219,9 +227,9 @@ describe("traceCommand", () => {
|
|
|
219
227
|
await traceCommand(["builder-1", "--json"]);
|
|
220
228
|
const out = output();
|
|
221
229
|
|
|
222
|
-
const parsed = JSON.parse(out.trim()) as Record<string, unknown>[];
|
|
223
|
-
expect(parsed).toHaveLength(1);
|
|
224
|
-
const event = parsed[0];
|
|
230
|
+
const parsed = JSON.parse(out.trim()) as { events: Record<string, unknown>[] };
|
|
231
|
+
expect(parsed.events).toHaveLength(1);
|
|
232
|
+
const event = parsed.events[0];
|
|
225
233
|
expect(event).toBeDefined();
|
|
226
234
|
expect(event?.agentName).toBe("builder-1");
|
|
227
235
|
expect(event?.eventType).toBe("tool_start");
|
|
@@ -239,8 +247,8 @@ describe("traceCommand", () => {
|
|
|
239
247
|
await traceCommand(["builder-1", "--json"]);
|
|
240
248
|
const out = output();
|
|
241
249
|
|
|
242
|
-
const parsed = JSON.parse(out.trim()) as unknown[];
|
|
243
|
-
expect(parsed).toEqual([]);
|
|
250
|
+
const parsed = JSON.parse(out.trim()) as { events: unknown[] };
|
|
251
|
+
expect(parsed.events).toEqual([]);
|
|
244
252
|
});
|
|
245
253
|
});
|
|
246
254
|
|
|
@@ -256,7 +264,7 @@ describe("traceCommand", () => {
|
|
|
256
264
|
await traceCommand(["builder-1"]);
|
|
257
265
|
const out = output();
|
|
258
266
|
|
|
259
|
-
expect(out).toContain("Timeline for builder-1");
|
|
267
|
+
expect(stripAnsi(out)).toContain("Timeline for builder-1");
|
|
260
268
|
});
|
|
261
269
|
|
|
262
270
|
test("shows event count", async () => {
|
|
@@ -413,8 +421,8 @@ describe("traceCommand", () => {
|
|
|
413
421
|
await traceCommand(["builder-1", "--json", "--limit", "3"]);
|
|
414
422
|
const out = output();
|
|
415
423
|
|
|
416
|
-
const parsed = JSON.parse(out.trim()) as unknown[];
|
|
417
|
-
expect(parsed).toHaveLength(3);
|
|
424
|
+
const parsed = JSON.parse(out.trim()) as { events: unknown[] };
|
|
425
|
+
expect(parsed.events).toHaveLength(3);
|
|
418
426
|
});
|
|
419
427
|
|
|
420
428
|
test("default limit is 100", async () => {
|
|
@@ -428,8 +436,8 @@ describe("traceCommand", () => {
|
|
|
428
436
|
await traceCommand(["builder-1", "--json"]);
|
|
429
437
|
const out = output();
|
|
430
438
|
|
|
431
|
-
const parsed = JSON.parse(out.trim()) as unknown[];
|
|
432
|
-
expect(parsed).toHaveLength(100);
|
|
439
|
+
const parsed = JSON.parse(out.trim()) as { events: unknown[] };
|
|
440
|
+
expect(parsed.events).toHaveLength(100);
|
|
433
441
|
});
|
|
434
442
|
});
|
|
435
443
|
|
|
@@ -448,8 +456,8 @@ describe("traceCommand", () => {
|
|
|
448
456
|
await traceCommand(["builder-1", "--json", "--since", "2099-01-01T00:00:00Z"]);
|
|
449
457
|
const out = output();
|
|
450
458
|
|
|
451
|
-
const parsed = JSON.parse(out.trim()) as unknown[];
|
|
452
|
-
expect(parsed).toEqual([]);
|
|
459
|
+
const parsed = JSON.parse(out.trim()) as { events: unknown[] };
|
|
460
|
+
expect(parsed.events).toEqual([]);
|
|
453
461
|
});
|
|
454
462
|
|
|
455
463
|
test("--since with past timestamp returns all events", async () => {
|
|
@@ -462,8 +470,8 @@ describe("traceCommand", () => {
|
|
|
462
470
|
await traceCommand(["builder-1", "--json", "--since", "2020-01-01T00:00:00Z"]);
|
|
463
471
|
const out = output();
|
|
464
472
|
|
|
465
|
-
const parsed = JSON.parse(out.trim()) as unknown[];
|
|
466
|
-
expect(parsed).toHaveLength(2);
|
|
473
|
+
const parsed = JSON.parse(out.trim()) as { events: unknown[] };
|
|
474
|
+
expect(parsed.events).toHaveLength(2);
|
|
467
475
|
});
|
|
468
476
|
|
|
469
477
|
test("--until with past timestamp returns no events", async () => {
|
|
@@ -475,8 +483,8 @@ describe("traceCommand", () => {
|
|
|
475
483
|
await traceCommand(["builder-1", "--json", "--until", "2000-01-01T00:00:00Z"]);
|
|
476
484
|
const out = output();
|
|
477
485
|
|
|
478
|
-
const parsed = JSON.parse(out.trim()) as unknown[];
|
|
479
|
-
expect(parsed).toEqual([]);
|
|
486
|
+
const parsed = JSON.parse(out.trim()) as { events: unknown[] };
|
|
487
|
+
expect(parsed.events).toEqual([]);
|
|
480
488
|
});
|
|
481
489
|
|
|
482
490
|
test("--since causes absolute timestamps in text mode", async () => {
|
|
@@ -518,9 +526,9 @@ describe("traceCommand", () => {
|
|
|
518
526
|
await traceCommand(["my-custom-agent", "--json"]);
|
|
519
527
|
const out = output();
|
|
520
528
|
|
|
521
|
-
const parsed = JSON.parse(out.trim()) as Record<string, unknown>[];
|
|
522
|
-
expect(parsed).toHaveLength(1);
|
|
523
|
-
expect(parsed[0]?.agentName).toBe("my-custom-agent");
|
|
529
|
+
const parsed = JSON.parse(out.trim()) as { events: Record<string, unknown>[] };
|
|
530
|
+
expect(parsed.events).toHaveLength(1);
|
|
531
|
+
expect(parsed.events[0]?.agentName).toBe("my-custom-agent");
|
|
524
532
|
});
|
|
525
533
|
|
|
526
534
|
test("task ID pattern is detected and resolved to agent name via SessionStore", async () => {
|
|
@@ -556,9 +564,9 @@ describe("traceCommand", () => {
|
|
|
556
564
|
await traceCommand(["overstory-rj1k", "--json"]);
|
|
557
565
|
const out = output();
|
|
558
566
|
|
|
559
|
-
const parsed = JSON.parse(out.trim()) as Record<string, unknown>[];
|
|
560
|
-
expect(parsed).toHaveLength(1);
|
|
561
|
-
expect(parsed[0]?.agentName).toBe("builder-for-task");
|
|
567
|
+
const parsed = JSON.parse(out.trim()) as { events: Record<string, unknown>[] };
|
|
568
|
+
expect(parsed.events).toHaveLength(1);
|
|
569
|
+
expect(parsed.events[0]?.agentName).toBe("builder-for-task");
|
|
562
570
|
});
|
|
563
571
|
|
|
564
572
|
test("unresolved task ID falls back to using task ID as agent name", async () => {
|
|
@@ -575,8 +583,8 @@ describe("traceCommand", () => {
|
|
|
575
583
|
await traceCommand(["myproj-abc1", "--json"]);
|
|
576
584
|
const out = output();
|
|
577
585
|
|
|
578
|
-
const parsed = JSON.parse(out.trim()) as unknown[];
|
|
579
|
-
expect(parsed).toEqual([]);
|
|
586
|
+
const parsed = JSON.parse(out.trim()) as { events: unknown[] };
|
|
587
|
+
expect(parsed.events).toEqual([]);
|
|
580
588
|
});
|
|
581
589
|
|
|
582
590
|
test("short agent names without task pattern are not resolved as task IDs", async () => {
|
|
@@ -589,9 +597,9 @@ describe("traceCommand", () => {
|
|
|
589
597
|
await traceCommand(["scout", "--json"]);
|
|
590
598
|
const out = output();
|
|
591
599
|
|
|
592
|
-
const parsed = JSON.parse(out.trim()) as Record<string, unknown>[];
|
|
593
|
-
expect(parsed).toHaveLength(1);
|
|
594
|
-
expect(parsed[0]?.agentName).toBe("scout");
|
|
600
|
+
const parsed = JSON.parse(out.trim()) as { events: Record<string, unknown>[] };
|
|
601
|
+
expect(parsed.events).toHaveLength(1);
|
|
602
|
+
expect(parsed.events[0]?.agentName).toBe("scout");
|
|
595
603
|
});
|
|
596
604
|
});
|
|
597
605
|
|
|
@@ -610,9 +618,9 @@ describe("traceCommand", () => {
|
|
|
610
618
|
await traceCommand(["builder-1", "--json"]);
|
|
611
619
|
const out = output();
|
|
612
620
|
|
|
613
|
-
const parsed = JSON.parse(out.trim()) as Record<string, unknown>[];
|
|
614
|
-
expect(parsed).toHaveLength(2);
|
|
615
|
-
for (const event of parsed) {
|
|
621
|
+
const parsed = JSON.parse(out.trim()) as { events: Record<string, unknown>[] };
|
|
622
|
+
expect(parsed.events).toHaveLength(2);
|
|
623
|
+
for (const event of parsed.events) {
|
|
616
624
|
expect(event.agentName).toBe("builder-1");
|
|
617
625
|
}
|
|
618
626
|
});
|
|
@@ -710,11 +718,11 @@ describe("traceCommand", () => {
|
|
|
710
718
|
await traceCommand(["builder-1", "--json"]);
|
|
711
719
|
const out = output();
|
|
712
720
|
|
|
713
|
-
const parsed = JSON.parse(out.trim()) as Record<string, unknown>[];
|
|
714
|
-
expect(parsed).toHaveLength(3);
|
|
715
|
-
expect(parsed[0]?.eventType).toBe("session_start");
|
|
716
|
-
expect(parsed[1]?.eventType).toBe("tool_start");
|
|
717
|
-
expect(parsed[2]?.eventType).toBe("session_end");
|
|
721
|
+
const parsed = JSON.parse(out.trim()) as { events: Record<string, unknown>[] };
|
|
722
|
+
expect(parsed.events).toHaveLength(3);
|
|
723
|
+
expect(parsed.events[0]?.eventType).toBe("session_start");
|
|
724
|
+
expect(parsed.events[1]?.eventType).toBe("tool_start");
|
|
725
|
+
expect(parsed.events[2]?.eventType).toBe("session_end");
|
|
718
726
|
});
|
|
719
727
|
|
|
720
728
|
test("handles event with all null optional fields", async () => {
|
|
@@ -738,7 +746,7 @@ describe("traceCommand", () => {
|
|
|
738
746
|
await traceCommand(["builder-1"]);
|
|
739
747
|
const out = output();
|
|
740
748
|
|
|
741
|
-
expect(out).toContain("Timeline for builder-1");
|
|
749
|
+
expect(stripAnsi(out)).toContain("Timeline for builder-1");
|
|
742
750
|
expect(out).toContain("1 event");
|
|
743
751
|
});
|
|
744
752
|
});
|
package/src/commands/trace.ts
CHANGED
|
@@ -10,8 +10,9 @@ import { Command } from "commander";
|
|
|
10
10
|
import { loadConfig } from "../config.ts";
|
|
11
11
|
import { ValidationError } from "../errors.ts";
|
|
12
12
|
import { createEventStore } from "../events/store.ts";
|
|
13
|
+
import { jsonOutput } from "../json.ts";
|
|
13
14
|
import type { ColorFn } from "../logging/color.ts";
|
|
14
|
-
import { color } from "../logging/color.ts";
|
|
15
|
+
import { accent, color } from "../logging/color.ts";
|
|
15
16
|
import { openSessionStore } from "../sessions/compat.ts";
|
|
16
17
|
import type { EventType, StoredEvent } from "../types.ts";
|
|
17
18
|
|
|
@@ -129,7 +130,7 @@ function buildEventDetail(event: StoredEvent): string {
|
|
|
129
130
|
function printTimeline(events: StoredEvent[], agentName: string, useAbsoluteTime: boolean): void {
|
|
130
131
|
const w = process.stdout.write.bind(process.stdout);
|
|
131
132
|
|
|
132
|
-
w(`${color.bold(`Timeline for ${agentName}`)}\n`);
|
|
133
|
+
w(`${color.bold(`Timeline for ${accent(agentName)}`)}\n`);
|
|
133
134
|
w(`${"=".repeat(70)}\n`);
|
|
134
135
|
|
|
135
136
|
if (events.length === 0) {
|
|
@@ -243,7 +244,7 @@ async function executeTrace(target: string, opts: TraceOpts): Promise<void> {
|
|
|
243
244
|
const eventsFile = Bun.file(eventsDbPath);
|
|
244
245
|
if (!(await eventsFile.exists())) {
|
|
245
246
|
if (json) {
|
|
246
|
-
|
|
247
|
+
jsonOutput("trace", { events: [] });
|
|
247
248
|
} else {
|
|
248
249
|
process.stdout.write("No events data yet.\n");
|
|
249
250
|
}
|
|
@@ -260,7 +261,7 @@ async function executeTrace(target: string, opts: TraceOpts): Promise<void> {
|
|
|
260
261
|
});
|
|
261
262
|
|
|
262
263
|
if (json) {
|
|
263
|
-
|
|
264
|
+
jsonOutput("trace", { events });
|
|
264
265
|
return;
|
|
265
266
|
}
|
|
266
267
|
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the ov upgrade command.
|
|
3
|
+
*
|
|
4
|
+
* Structural tests for CLI registration and option parsing.
|
|
5
|
+
* Network calls and subprocess execution happen via real implementations;
|
|
6
|
+
* this file tests command structure and output format rather than side effects.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, expect, test } from "bun:test";
|
|
10
|
+
import { createUpgradeCommand } from "./upgrade.ts";
|
|
11
|
+
|
|
12
|
+
describe("createUpgradeCommand — CLI structure", () => {
|
|
13
|
+
test("command has correct name", () => {
|
|
14
|
+
const cmd = createUpgradeCommand();
|
|
15
|
+
expect(cmd.name()).toBe("upgrade");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("description mentions overstory", () => {
|
|
19
|
+
const cmd = createUpgradeCommand();
|
|
20
|
+
expect(cmd.description().toLowerCase()).toContain("overstory");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("has --check option", () => {
|
|
24
|
+
const cmd = createUpgradeCommand();
|
|
25
|
+
const optionNames = cmd.options.map((o) => o.long);
|
|
26
|
+
expect(optionNames).toContain("--check");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("has --json option", () => {
|
|
30
|
+
const cmd = createUpgradeCommand();
|
|
31
|
+
const optionNames = cmd.options.map((o) => o.long);
|
|
32
|
+
expect(optionNames).toContain("--json");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("has --all option", () => {
|
|
36
|
+
const cmd = createUpgradeCommand();
|
|
37
|
+
const optionNames = cmd.options.map((o) => o.long);
|
|
38
|
+
expect(optionNames).toContain("--all");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("returns a Command instance", () => {
|
|
42
|
+
const cmd = createUpgradeCommand();
|
|
43
|
+
// Commander Command instances have a .parse method
|
|
44
|
+
expect(typeof cmd.parse).toBe("function");
|
|
45
|
+
});
|
|
46
|
+
});
|