@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
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
import { mkdir } from "node:fs/promises";
|
|
16
16
|
import { join } from "node:path";
|
|
17
17
|
import { Command } from "commander";
|
|
18
|
-
import { deployHooks } from "../agents/hooks-deployer.ts";
|
|
19
18
|
import { createIdentity, loadIdentity } from "../agents/identity.ts";
|
|
20
19
|
import { createManifestLoader, resolveModel } from "../agents/manifest.ts";
|
|
21
20
|
import { loadConfig } from "../config.ts";
|
|
@@ -137,8 +136,21 @@ async function startSupervisor(opts: {
|
|
|
137
136
|
store.updateState(opts.name, "completed");
|
|
138
137
|
}
|
|
139
138
|
|
|
139
|
+
// Resolve model and runtime early (needed for deployConfig and spawn)
|
|
140
|
+
const manifestLoader = createManifestLoader(
|
|
141
|
+
join(projectRoot, config.agents.manifestPath),
|
|
142
|
+
join(projectRoot, config.agents.baseDir),
|
|
143
|
+
);
|
|
144
|
+
const manifest = await manifestLoader.load();
|
|
145
|
+
const resolvedModel = resolveModel(config, manifest, "supervisor", "opus");
|
|
146
|
+
const runtime = getRuntime(undefined, config);
|
|
147
|
+
|
|
140
148
|
// Deploy supervisor-specific hooks to the project root's .claude/ directory.
|
|
141
|
-
await
|
|
149
|
+
await runtime.deployConfig(projectRoot, undefined, {
|
|
150
|
+
agentName: opts.name,
|
|
151
|
+
capability: "supervisor",
|
|
152
|
+
worktreePath: projectRoot,
|
|
153
|
+
});
|
|
142
154
|
|
|
143
155
|
// Create supervisor identity if first run
|
|
144
156
|
const identityBaseDir = join(projectRoot, ".overstory", "agents");
|
|
@@ -155,15 +167,6 @@ async function startSupervisor(opts: {
|
|
|
155
167
|
});
|
|
156
168
|
}
|
|
157
169
|
|
|
158
|
-
// Resolve model from config > manifest > fallback
|
|
159
|
-
const manifestLoader = createManifestLoader(
|
|
160
|
-
join(projectRoot, config.agents.manifestPath),
|
|
161
|
-
join(projectRoot, config.agents.baseDir),
|
|
162
|
-
);
|
|
163
|
-
const manifest = await manifestLoader.load();
|
|
164
|
-
const resolvedModel = resolveModel(config, manifest, "supervisor", "opus");
|
|
165
|
-
const runtime = getRuntime(undefined, config);
|
|
166
|
-
|
|
167
170
|
// Spawn tmux session at project root with Claude Code (interactive mode).
|
|
168
171
|
// Inject the supervisor base definition via --append-system-prompt.
|
|
169
172
|
const tmuxSession = `overstory-${config.project.name}-supervisor-${opts.name}`;
|
|
@@ -225,6 +228,7 @@ async function startSupervisor(opts: {
|
|
|
225
228
|
lastActivity: new Date().toISOString(),
|
|
226
229
|
escalationLevel: 0,
|
|
227
230
|
stalledSince: null,
|
|
231
|
+
transcriptPath: null,
|
|
228
232
|
};
|
|
229
233
|
|
|
230
234
|
store.upsert(session);
|
|
@@ -442,7 +446,7 @@ async function statusSupervisor(opts: { name?: string; json: boolean }): Promise
|
|
|
442
446
|
* Create the Commander command for `ov supervisor`.
|
|
443
447
|
*/
|
|
444
448
|
export function createSupervisorCommand(): Command {
|
|
445
|
-
const cmd = new Command("supervisor").description("
|
|
449
|
+
const cmd = new Command("supervisor").description("[DEPRECATED] Per-project supervisor agent");
|
|
446
450
|
|
|
447
451
|
cmd
|
|
448
452
|
.command("start")
|
|
@@ -460,6 +464,9 @@ export function createSupervisorCommand(): Command {
|
|
|
460
464
|
depth: string;
|
|
461
465
|
json?: boolean;
|
|
462
466
|
}) => {
|
|
467
|
+
console.error(
|
|
468
|
+
"[DEPRECATED] ov supervisor is deprecated. Use 'ov sling --capability lead' instead.",
|
|
469
|
+
);
|
|
463
470
|
await startSupervisor({
|
|
464
471
|
task: opts.task,
|
|
465
472
|
name: opts.name,
|
|
@@ -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 { stripAnsi } from "../logging/color.ts";
|
|
18
18
|
import { createSessionStore } from "../sessions/store.ts";
|
|
19
|
+
import { cleanupTempDir } from "../test-helpers.ts";
|
|
19
20
|
import type { InsertEvent } from "../types.ts";
|
|
20
21
|
import { traceCommand } from "./trace.ts";
|
|
21
22
|
|
|
@@ -66,7 +67,7 @@ describe("traceCommand", () => {
|
|
|
66
67
|
afterEach(async () => {
|
|
67
68
|
process.stdout.write = originalWrite;
|
|
68
69
|
process.chdir(originalCwd);
|
|
69
|
-
await
|
|
70
|
+
await cleanupTempDir(tempDir);
|
|
70
71
|
});
|
|
71
72
|
|
|
72
73
|
function output(): string {
|
|
@@ -551,6 +552,7 @@ describe("traceCommand", () => {
|
|
|
551
552
|
lastActivity: new Date().toISOString(),
|
|
552
553
|
escalationLevel: 0,
|
|
553
554
|
stalledSince: null,
|
|
555
|
+
transcriptPath: null,
|
|
554
556
|
});
|
|
555
557
|
sessionStore.close();
|
|
556
558
|
|
|
@@ -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 { watchCommand } from "./watch.ts";
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -66,7 +67,7 @@ describe("watchCommand", () => {
|
|
|
66
67
|
process.stderr.write = originalStderrWrite;
|
|
67
68
|
process.exitCode = originalExitCode;
|
|
68
69
|
process.chdir(originalCwd);
|
|
69
|
-
await
|
|
70
|
+
await cleanupTempDir(tempDir);
|
|
70
71
|
});
|
|
71
72
|
|
|
72
73
|
function output(): string {
|
|
@@ -79,6 +79,7 @@ describe("worktreeCommand", () => {
|
|
|
79
79
|
lastActivity: new Date().toISOString(),
|
|
80
80
|
escalationLevel: 0,
|
|
81
81
|
stalledSince: null,
|
|
82
|
+
transcriptPath: null,
|
|
82
83
|
...overrides,
|
|
83
84
|
};
|
|
84
85
|
}
|
|
@@ -167,6 +168,7 @@ describe("worktreeCommand", () => {
|
|
|
167
168
|
lastActivity: new Date().toISOString(),
|
|
168
169
|
escalationLevel: 0,
|
|
169
170
|
stalledSince: null,
|
|
171
|
+
transcriptPath: null,
|
|
170
172
|
},
|
|
171
173
|
]);
|
|
172
174
|
|
|
@@ -214,6 +216,7 @@ describe("worktreeCommand", () => {
|
|
|
214
216
|
lastActivity: new Date().toISOString(),
|
|
215
217
|
escalationLevel: 0,
|
|
216
218
|
stalledSince: null,
|
|
219
|
+
transcriptPath: null,
|
|
217
220
|
},
|
|
218
221
|
]);
|
|
219
222
|
|
|
@@ -308,6 +311,7 @@ describe("worktreeCommand", () => {
|
|
|
308
311
|
lastActivity: new Date().toISOString(),
|
|
309
312
|
escalationLevel: 0,
|
|
310
313
|
stalledSince: null,
|
|
314
|
+
transcriptPath: null,
|
|
311
315
|
},
|
|
312
316
|
]);
|
|
313
317
|
|
|
@@ -363,6 +367,7 @@ describe("worktreeCommand", () => {
|
|
|
363
367
|
lastActivity: new Date().toISOString(),
|
|
364
368
|
escalationLevel: 0,
|
|
365
369
|
stalledSince: null,
|
|
370
|
+
transcriptPath: null,
|
|
366
371
|
},
|
|
367
372
|
]);
|
|
368
373
|
|
|
@@ -401,6 +406,7 @@ describe("worktreeCommand", () => {
|
|
|
401
406
|
lastActivity: new Date().toISOString(),
|
|
402
407
|
escalationLevel: 0,
|
|
403
408
|
stalledSince: null,
|
|
409
|
+
transcriptPath: null,
|
|
404
410
|
},
|
|
405
411
|
]);
|
|
406
412
|
|
|
@@ -455,6 +461,7 @@ describe("worktreeCommand", () => {
|
|
|
455
461
|
lastActivity: new Date().toISOString(),
|
|
456
462
|
escalationLevel: 0,
|
|
457
463
|
stalledSince: new Date().toISOString(),
|
|
464
|
+
transcriptPath: null,
|
|
458
465
|
},
|
|
459
466
|
]);
|
|
460
467
|
|
|
@@ -616,6 +623,7 @@ describe("worktreeCommand", () => {
|
|
|
616
623
|
lastActivity: new Date().toISOString(),
|
|
617
624
|
escalationLevel: 0,
|
|
618
625
|
stalledSince: null,
|
|
626
|
+
transcriptPath: null,
|
|
619
627
|
},
|
|
620
628
|
{
|
|
621
629
|
id: "session-2",
|
|
@@ -634,6 +642,7 @@ describe("worktreeCommand", () => {
|
|
|
634
642
|
lastActivity: new Date().toISOString(),
|
|
635
643
|
escalationLevel: 0,
|
|
636
644
|
stalledSince: null,
|
|
645
|
+
transcriptPath: null,
|
|
637
646
|
},
|
|
638
647
|
]);
|
|
639
648
|
|
package/src/config.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
-
import { mkdir, mkdtemp, realpath
|
|
2
|
+
import { mkdir, mkdtemp, realpath } from "node:fs/promises";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { DEFAULT_CONFIG, DEFAULT_QUALITY_GATES, loadConfig, resolveProjectRoot } from "./config.ts";
|
|
@@ -14,7 +14,7 @@ describe("loadConfig", () => {
|
|
|
14
14
|
});
|
|
15
15
|
|
|
16
16
|
afterEach(async () => {
|
|
17
|
-
await
|
|
17
|
+
await cleanupTempDir(tempDir);
|
|
18
18
|
});
|
|
19
19
|
|
|
20
20
|
async function writeConfig(yaml: string): Promise<void> {
|
|
@@ -428,7 +428,7 @@ describe("validateConfig", () => {
|
|
|
428
428
|
});
|
|
429
429
|
|
|
430
430
|
afterEach(async () => {
|
|
431
|
-
await
|
|
431
|
+
await cleanupTempDir(tempDir);
|
|
432
432
|
});
|
|
433
433
|
|
|
434
434
|
async function writeConfig(yaml: string): Promise<void> {
|
package/src/config.ts
CHANGED
|
@@ -64,6 +64,14 @@ export const DEFAULT_CONFIG: OverstoryConfig = {
|
|
|
64
64
|
},
|
|
65
65
|
runtime: {
|
|
66
66
|
default: "claude",
|
|
67
|
+
pi: {
|
|
68
|
+
provider: "anthropic",
|
|
69
|
+
modelMap: {
|
|
70
|
+
opus: "anthropic/claude-opus-4-6",
|
|
71
|
+
sonnet: "anthropic/claude-sonnet-4-6",
|
|
72
|
+
haiku: "anthropic/claude-haiku-4-5",
|
|
73
|
+
},
|
|
74
|
+
},
|
|
67
75
|
},
|
|
68
76
|
};
|
|
69
77
|
|
|
@@ -635,6 +643,27 @@ function validateConfig(config: OverstoryConfig): void {
|
|
|
635
643
|
);
|
|
636
644
|
}
|
|
637
645
|
|
|
646
|
+
// runtime.pi: validate provider and modelMap if present
|
|
647
|
+
if (config.runtime?.pi) {
|
|
648
|
+
const pi = config.runtime.pi;
|
|
649
|
+
if (!pi.provider || typeof pi.provider !== "string") {
|
|
650
|
+
throw new ValidationError("runtime.pi.provider must be a non-empty string", {
|
|
651
|
+
field: "runtime.pi.provider",
|
|
652
|
+
value: pi.provider,
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
if (pi.modelMap && typeof pi.modelMap === "object") {
|
|
656
|
+
for (const [alias, qualified] of Object.entries(pi.modelMap)) {
|
|
657
|
+
if (!qualified || typeof qualified !== "string") {
|
|
658
|
+
throw new ValidationError(`runtime.pi.modelMap.${alias} must be a non-empty string`, {
|
|
659
|
+
field: `runtime.pi.modelMap.${alias}`,
|
|
660
|
+
value: qualified,
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
638
667
|
// models: validate each value — accepts aliases and provider-prefixed refs
|
|
639
668
|
const validAliases = ["sonnet", "opus", "haiku"];
|
|
640
669
|
const toolHeavyRoles = ["builder", "scout"];
|
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
9
|
-
import { mkdir, mkdtemp
|
|
9
|
+
import { mkdir, mkdtemp } from "node:fs/promises";
|
|
10
10
|
import { tmpdir } from "node:os";
|
|
11
11
|
import { join } from "node:path";
|
|
12
|
+
import { cleanupTempDir } from "../test-helpers.ts";
|
|
12
13
|
import type { OverstoryConfig } from "../types.ts";
|
|
13
14
|
import { checkAgents } from "./agents.ts";
|
|
14
15
|
|
|
@@ -74,7 +75,7 @@ describe("checkAgents", () => {
|
|
|
74
75
|
});
|
|
75
76
|
|
|
76
77
|
afterEach(async () => {
|
|
77
|
-
await
|
|
78
|
+
await cleanupTempDir(tempDir);
|
|
78
79
|
});
|
|
79
80
|
|
|
80
81
|
test("fails when manifest is missing", async () => {
|
|
@@ -207,6 +207,7 @@ describe("checkConsistency", () => {
|
|
|
207
207
|
lastActivity: new Date().toISOString(),
|
|
208
208
|
escalationLevel: 0,
|
|
209
209
|
stalledSince: null,
|
|
210
|
+
transcriptPath: null,
|
|
210
211
|
});
|
|
211
212
|
store.close();
|
|
212
213
|
|
|
@@ -243,6 +244,7 @@ describe("checkConsistency", () => {
|
|
|
243
244
|
lastActivity: new Date().toISOString(),
|
|
244
245
|
escalationLevel: 0,
|
|
245
246
|
stalledSince: null,
|
|
247
|
+
transcriptPath: null,
|
|
246
248
|
});
|
|
247
249
|
store.close();
|
|
248
250
|
|
|
@@ -278,6 +280,7 @@ describe("checkConsistency", () => {
|
|
|
278
280
|
lastActivity: new Date().toISOString(),
|
|
279
281
|
escalationLevel: 0,
|
|
280
282
|
stalledSince: null,
|
|
283
|
+
transcriptPath: null,
|
|
281
284
|
});
|
|
282
285
|
store.close();
|
|
283
286
|
|
|
@@ -314,6 +317,7 @@ describe("checkConsistency", () => {
|
|
|
314
317
|
lastActivity: new Date().toISOString(),
|
|
315
318
|
escalationLevel: 0,
|
|
316
319
|
stalledSince: null,
|
|
320
|
+
transcriptPath: null,
|
|
317
321
|
});
|
|
318
322
|
store.close();
|
|
319
323
|
|
|
@@ -353,6 +357,7 @@ describe("checkConsistency", () => {
|
|
|
353
357
|
lastActivity: new Date().toISOString(),
|
|
354
358
|
escalationLevel: 0,
|
|
355
359
|
stalledSince: null,
|
|
360
|
+
transcriptPath: null,
|
|
356
361
|
});
|
|
357
362
|
store.close();
|
|
358
363
|
|
|
@@ -426,6 +431,7 @@ describe("checkConsistency", () => {
|
|
|
426
431
|
lastActivity: new Date().toISOString(),
|
|
427
432
|
escalationLevel: 0,
|
|
428
433
|
stalledSince: null,
|
|
434
|
+
transcriptPath: null,
|
|
429
435
|
});
|
|
430
436
|
|
|
431
437
|
store.upsert({
|
|
@@ -445,6 +451,7 @@ describe("checkConsistency", () => {
|
|
|
445
451
|
lastActivity: new Date().toISOString(),
|
|
446
452
|
escalationLevel: 0,
|
|
447
453
|
stalledSince: null,
|
|
454
|
+
transcriptPath: null,
|
|
448
455
|
});
|
|
449
456
|
store.close();
|
|
450
457
|
|
|
@@ -481,6 +488,7 @@ describe("checkConsistency", () => {
|
|
|
481
488
|
lastActivity: new Date().toISOString(),
|
|
482
489
|
escalationLevel: 0,
|
|
483
490
|
stalledSince: null,
|
|
491
|
+
transcriptPath: null,
|
|
484
492
|
});
|
|
485
493
|
}
|
|
486
494
|
|
|
@@ -501,6 +509,7 @@ describe("checkConsistency", () => {
|
|
|
501
509
|
lastActivity: new Date().toISOString(),
|
|
502
510
|
escalationLevel: 0,
|
|
503
511
|
stalledSince: null,
|
|
512
|
+
transcriptPath: null,
|
|
504
513
|
});
|
|
505
514
|
store.close();
|
|
506
515
|
|
|
@@ -535,6 +544,7 @@ describe("checkConsistency", () => {
|
|
|
535
544
|
lastActivity: new Date().toISOString(),
|
|
536
545
|
escalationLevel: 0,
|
|
537
546
|
stalledSince: null,
|
|
547
|
+
transcriptPath: null,
|
|
538
548
|
});
|
|
539
549
|
|
|
540
550
|
store.upsert({
|
|
@@ -554,6 +564,7 @@ describe("checkConsistency", () => {
|
|
|
554
564
|
lastActivity: new Date().toISOString(),
|
|
555
565
|
escalationLevel: 0,
|
|
556
566
|
stalledSince: null,
|
|
567
|
+
transcriptPath: null,
|
|
557
568
|
});
|
|
558
569
|
}
|
|
559
570
|
store.close();
|
|
@@ -597,6 +608,7 @@ describe("checkConsistency", () => {
|
|
|
597
608
|
lastActivity: new Date().toISOString(),
|
|
598
609
|
escalationLevel: 0,
|
|
599
610
|
stalledSince: null,
|
|
611
|
+
transcriptPath: null,
|
|
600
612
|
});
|
|
601
613
|
|
|
602
614
|
store.upsert({
|
|
@@ -616,6 +628,7 @@ describe("checkConsistency", () => {
|
|
|
616
628
|
lastActivity: new Date().toISOString(),
|
|
617
629
|
escalationLevel: 0,
|
|
618
630
|
stalledSince: null,
|
|
631
|
+
transcriptPath: null,
|
|
619
632
|
});
|
|
620
633
|
|
|
621
634
|
// Lead-2 has builders only (bad)
|
|
@@ -636,6 +649,7 @@ describe("checkConsistency", () => {
|
|
|
636
649
|
lastActivity: new Date().toISOString(),
|
|
637
650
|
escalationLevel: 0,
|
|
638
651
|
stalledSince: null,
|
|
652
|
+
transcriptPath: null,
|
|
639
653
|
});
|
|
640
654
|
store.close();
|
|
641
655
|
|
package/src/doctor/logs.test.ts
CHANGED
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
9
|
-
import { mkdir, mkdtemp,
|
|
9
|
+
import { mkdir, mkdtemp, writeFile } from "node:fs/promises";
|
|
10
10
|
import { tmpdir } from "node:os";
|
|
11
11
|
import { join } from "node:path";
|
|
12
|
+
import { cleanupTempDir } from "../test-helpers.ts";
|
|
12
13
|
import type { OverstoryConfig } from "../types.ts";
|
|
13
14
|
import { checkLogs } from "./logs.ts";
|
|
14
15
|
|
|
@@ -77,7 +78,7 @@ describe("checkLogs", () => {
|
|
|
77
78
|
});
|
|
78
79
|
|
|
79
80
|
afterEach(async () => {
|
|
80
|
-
await
|
|
81
|
+
await cleanupTempDir(tempDir);
|
|
81
82
|
});
|
|
82
83
|
|
|
83
84
|
test("warns when logs/ directory does not exist", async () => {
|
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
9
|
-
import { mkdir, mkdtemp,
|
|
9
|
+
import { mkdir, mkdtemp, utimes } from "node:fs/promises";
|
|
10
10
|
import { tmpdir } from "node:os";
|
|
11
11
|
import { join } from "node:path";
|
|
12
|
+
import { cleanupTempDir } from "../test-helpers.ts";
|
|
12
13
|
import type { OverstoryConfig } from "../types.ts";
|
|
13
14
|
import { checkStructure } from "./structure.ts";
|
|
14
15
|
|
|
@@ -73,7 +74,7 @@ describe("checkStructure", () => {
|
|
|
73
74
|
});
|
|
74
75
|
|
|
75
76
|
afterEach(async () => {
|
|
76
|
-
await
|
|
77
|
+
await cleanupTempDir(tempDir);
|
|
77
78
|
});
|
|
78
79
|
|
|
79
80
|
test("fails when .overstory/ directory does not exist", async () => {
|
|
@@ -27,7 +27,6 @@ const EXPECTED_AGENT_DEFS = [
|
|
|
27
27
|
"monitor.md",
|
|
28
28
|
"reviewer.md",
|
|
29
29
|
"scout.md",
|
|
30
|
-
"supervisor.md",
|
|
31
30
|
];
|
|
32
31
|
|
|
33
32
|
describe("E2E: init→sling lifecycle on external project", () => {
|
|
@@ -77,7 +76,7 @@ describe("E2E: init→sling lifecycle on external project", () => {
|
|
|
77
76
|
const gitignoreFile = Bun.file(join(overstoryDir, ".gitignore"));
|
|
78
77
|
expect(await gitignoreFile.exists()).toBe(true);
|
|
79
78
|
|
|
80
|
-
// agent-defs/ contains all
|
|
79
|
+
// agent-defs/ contains all 7 agent definition files (supervisor deprecated)
|
|
81
80
|
const agentDefsDir = join(overstoryDir, "agent-defs");
|
|
82
81
|
const agentDefFiles = (await readdir(agentDefsDir)).filter((f) => f.endsWith(".md")).sort();
|
|
83
82
|
expect(agentDefFiles).toEqual(EXPECTED_AGENT_DEFS);
|
|
@@ -109,7 +108,7 @@ describe("E2E: init→sling lifecycle on external project", () => {
|
|
|
109
108
|
expect(config.project.name).toBeTruthy();
|
|
110
109
|
});
|
|
111
110
|
|
|
112
|
-
test("manifest loads successfully with all
|
|
111
|
+
test("manifest loads successfully with all 7 agents (supervisor deprecated)", async () => {
|
|
113
112
|
await initCommand({});
|
|
114
113
|
|
|
115
114
|
const manifestPath = join(tempDir, ".overstory", "agent-manifest.json");
|
|
@@ -118,7 +117,7 @@ describe("E2E: init→sling lifecycle on external project", () => {
|
|
|
118
117
|
|
|
119
118
|
const manifest = await loader.load();
|
|
120
119
|
|
|
121
|
-
// All
|
|
120
|
+
// All 7 agents present (supervisor removed: deprecated, use lead instead)
|
|
122
121
|
const agentNames = Object.keys(manifest.agents).sort();
|
|
123
122
|
expect(agentNames).toEqual([
|
|
124
123
|
"builder",
|
|
@@ -128,7 +127,6 @@ describe("E2E: init→sling lifecycle on external project", () => {
|
|
|
128
127
|
"monitor",
|
|
129
128
|
"reviewer",
|
|
130
129
|
"scout",
|
|
131
|
-
"supervisor",
|
|
132
130
|
]);
|
|
133
131
|
|
|
134
132
|
// Each agent has a valid file reference
|
package/src/index.ts
CHANGED
|
@@ -45,7 +45,7 @@ import { OverstoryError, WorktreeError } from "./errors.ts";
|
|
|
45
45
|
import { jsonError } from "./json.ts";
|
|
46
46
|
import { brand, chalk, muted, setQuiet } from "./logging/color.ts";
|
|
47
47
|
|
|
48
|
-
export const VERSION = "0.7.
|
|
48
|
+
export const VERSION = "0.7.3";
|
|
49
49
|
|
|
50
50
|
const rawArgs = process.argv.slice(2);
|
|
51
51
|
|
|
@@ -96,6 +96,7 @@ const COMMANDS = [
|
|
|
96
96
|
"costs",
|
|
97
97
|
"metrics",
|
|
98
98
|
"upgrade",
|
|
99
|
+
"completions",
|
|
99
100
|
];
|
|
100
101
|
|
|
101
102
|
function editDistance(a: string, b: string): number {
|
|
@@ -254,6 +255,7 @@ program
|
|
|
254
255
|
.option("--force-hierarchy", "Bypass hierarchy validation")
|
|
255
256
|
.option("--max-agents <n>", "Max children per lead (overrides config)")
|
|
256
257
|
.option("--skip-review", "Skip review phase for lead agents")
|
|
258
|
+
.option("--no-scout-check", "Suppress the parentHasScouts scout-before-build warning")
|
|
257
259
|
.option("--dispatch-max-agents <n>", "Per-lead max agents ceiling (injected into overlay)")
|
|
258
260
|
.option("--runtime <name>", "Runtime adapter (default: config or claude)")
|
|
259
261
|
.option("--json", "Output result as JSON")
|
package/src/logging/color.ts
CHANGED
|
@@ -10,7 +10,7 @@ import chalk from "chalk";
|
|
|
10
10
|
// --- Brand palette (os-eco brand colors) ---
|
|
11
11
|
|
|
12
12
|
/** Forest green — Overstory primary brand color. */
|
|
13
|
-
export const brand = chalk.rgb(
|
|
13
|
+
export const brand = chalk.rgb(46, 125, 50);
|
|
14
14
|
|
|
15
15
|
/** Amber — highlights, warnings. */
|
|
16
16
|
export const accent = chalk.rgb(255, 183, 77);
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import type { StoredEvent } from "../types.ts";
|
|
3
|
+
import { stripAnsi } from "./color.ts";
|
|
4
|
+
import { formatEventLine, numericPriorityColor } from "./format.ts";
|
|
5
|
+
|
|
6
|
+
// Minimal StoredEvent fixture for testing formatEventLine
|
|
7
|
+
const BASE_EVENT: StoredEvent = {
|
|
8
|
+
id: 1,
|
|
9
|
+
runId: "run-001",
|
|
10
|
+
agentName: "agent1",
|
|
11
|
+
sessionId: null,
|
|
12
|
+
eventType: "tool_start",
|
|
13
|
+
level: "info",
|
|
14
|
+
toolName: "bash",
|
|
15
|
+
toolArgs: null,
|
|
16
|
+
toolDurationMs: null,
|
|
17
|
+
data: null,
|
|
18
|
+
createdAt: "2024-01-15T10:30:45.000Z",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
describe("numericPriorityColor", () => {
|
|
22
|
+
test("returns a function for priority 1", () => {
|
|
23
|
+
const fn = numericPriorityColor(1);
|
|
24
|
+
expect(typeof fn).toBe("function");
|
|
25
|
+
// Function must accept a string and return a string
|
|
26
|
+
expect(typeof fn("x")).toBe("string");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("returns a function for priority 2", () => {
|
|
30
|
+
const fn = numericPriorityColor(2);
|
|
31
|
+
expect(typeof fn).toBe("function");
|
|
32
|
+
expect(typeof fn("x")).toBe("string");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("priority 3 is identity (returns input unchanged)", () => {
|
|
36
|
+
const fn = numericPriorityColor(3);
|
|
37
|
+
// Priority 3 = normal = (text) => text, always identity regardless of chalk level
|
|
38
|
+
expect(fn("x")).toBe("x");
|
|
39
|
+
expect(fn("hello world")).toBe("hello world");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("returns a function for priority 4", () => {
|
|
43
|
+
const fn = numericPriorityColor(4);
|
|
44
|
+
expect(typeof fn).toBe("function");
|
|
45
|
+
expect(typeof fn("x")).toBe("string");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("unknown priority returns identity function", () => {
|
|
49
|
+
const fn = numericPriorityColor(99);
|
|
50
|
+
expect(typeof fn).toBe("function");
|
|
51
|
+
expect(fn("x")).toBe("x");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("all priority functions preserve input text (visible content after strip)", () => {
|
|
55
|
+
for (const p of [1, 2, 3, 4]) {
|
|
56
|
+
const fn = numericPriorityColor(p);
|
|
57
|
+
expect(stripAnsi(fn("hello"))).toBe("hello");
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe("formatEventLine", () => {
|
|
63
|
+
test("returns a non-empty string", () => {
|
|
64
|
+
const colorMap = new Map<string, (t: string) => string>();
|
|
65
|
+
const result = formatEventLine(BASE_EVENT, colorMap);
|
|
66
|
+
expect(result.length).toBeGreaterThan(0);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("includes agent name in the result", () => {
|
|
70
|
+
const colorMap = new Map<string, (t: string) => string>();
|
|
71
|
+
const result = formatEventLine(BASE_EVENT, colorMap);
|
|
72
|
+
// Strip ANSI so we can do plain text comparison
|
|
73
|
+
expect(stripAnsi(result)).toContain("agent1");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("includes time portion (10:30:45) in the result", () => {
|
|
77
|
+
const colorMap = new Map<string, (t: string) => string>();
|
|
78
|
+
const result = formatEventLine(BASE_EVENT, colorMap);
|
|
79
|
+
expect(stripAnsi(result)).toContain("10:30:45");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("result does NOT end with a newline", () => {
|
|
83
|
+
const colorMap = new Map<string, (t: string) => string>();
|
|
84
|
+
const result = formatEventLine(BASE_EVENT, colorMap);
|
|
85
|
+
expect(result.endsWith("\n")).toBe(false);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("error level result is a non-empty string", () => {
|
|
89
|
+
const colorMap = new Map<string, (t: string) => string>();
|
|
90
|
+
const errorEvent: StoredEvent = { ...BASE_EVENT, level: "error" };
|
|
91
|
+
const result = formatEventLine(errorEvent, colorMap);
|
|
92
|
+
expect(result.length).toBeGreaterThan(0);
|
|
93
|
+
expect(result.endsWith("\n")).toBe(false);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("uses color from colorMap when agent is registered", () => {
|
|
97
|
+
const blueColor = (t: string) => `\x1b[34m${t}\x1b[39m`;
|
|
98
|
+
const colorMap = new Map<string, (t: string) => string>([["agent1", blueColor]]);
|
|
99
|
+
const result = formatEventLine(BASE_EVENT, colorMap);
|
|
100
|
+
// The colored agent name should appear in the output
|
|
101
|
+
expect(result).toContain("\x1b[34m");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("falls back to gray when agent is not in colorMap", () => {
|
|
105
|
+
const colorMap = new Map<string, (t: string) => string>();
|
|
106
|
+
// Should not throw — gray fallback is used
|
|
107
|
+
const result = formatEventLine(BASE_EVENT, colorMap);
|
|
108
|
+
expect(result.length).toBeGreaterThan(0);
|
|
109
|
+
});
|
|
110
|
+
});
|
package/src/logging/format.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import type { StoredEvent } from "../types.ts";
|
|
9
9
|
import type { ColorFn } from "./color.ts";
|
|
10
10
|
import { color, noColor } from "./color.ts";
|
|
11
|
-
import { AGENT_COLORS } from "./theme.ts";
|
|
11
|
+
import { AGENT_COLORS, eventLabel } from "./theme.ts";
|
|
12
12
|
|
|
13
13
|
// === Duration ===
|
|
14
14
|
|
|
@@ -175,6 +175,25 @@ export function priorityColor(priority: string): ColorFn {
|
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
+
/**
|
|
179
|
+
* Returns a color function for a numeric tracker priority.
|
|
180
|
+
* 1=urgent (red), 2=high (yellow), 3=normal (identity), 4=low (dim)
|
|
181
|
+
*/
|
|
182
|
+
export function numericPriorityColor(priority: number): ColorFn {
|
|
183
|
+
switch (priority) {
|
|
184
|
+
case 1:
|
|
185
|
+
return color.red;
|
|
186
|
+
case 2:
|
|
187
|
+
return color.yellow;
|
|
188
|
+
case 3:
|
|
189
|
+
return (text: string) => text;
|
|
190
|
+
case 4:
|
|
191
|
+
return color.dim;
|
|
192
|
+
default:
|
|
193
|
+
return (text: string) => text;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
178
197
|
/**
|
|
179
198
|
* Returns a color function for a log level string.
|
|
180
199
|
* debug=gray, info=blue, warn=yellow, error=red
|
|
@@ -212,3 +231,25 @@ export function logLevelLabel(level: string): string {
|
|
|
212
231
|
return level.slice(0, 3).toUpperCase();
|
|
213
232
|
}
|
|
214
233
|
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Format a single event as a compact feed line.
|
|
237
|
+
* Returns the formatted string WITHOUT a trailing newline.
|
|
238
|
+
* Used by both ov feed and the dashboard Feed panel.
|
|
239
|
+
*/
|
|
240
|
+
export function formatEventLine(event: StoredEvent, colorMap: Map<string, ColorFn>): string {
|
|
241
|
+
const timeStr = formatAbsoluteTime(event.createdAt);
|
|
242
|
+
const label = eventLabel(event.eventType);
|
|
243
|
+
const levelColorFn =
|
|
244
|
+
event.level === "error" ? color.red : event.level === "warn" ? color.yellow : null;
|
|
245
|
+
const applyLevel = (text: string) => (levelColorFn ? levelColorFn(text) : text);
|
|
246
|
+
const detail = buildEventDetail(event, 60);
|
|
247
|
+
const detailSuffix = detail ? ` ${color.dim(detail)}` : "";
|
|
248
|
+
const agentColorFn = colorMap.get(event.agentName) ?? color.gray;
|
|
249
|
+
const agentLabel = ` ${agentColorFn(event.agentName.padEnd(15))}`;
|
|
250
|
+
return (
|
|
251
|
+
`${color.dim(timeStr)} ` +
|
|
252
|
+
`${applyLevel(label.color(color.bold(label.compact)))}` +
|
|
253
|
+
`${agentLabel}${detailSuffix}`
|
|
254
|
+
);
|
|
255
|
+
}
|