@os-eco/overstory-cli 0.9.1 → 0.9.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 +21 -6
- package/agents/coordinator.md +34 -10
- package/agents/lead.md +11 -1
- package/package.json +1 -1
- package/src/agents/copilot-hooks-deployer.test.ts +162 -0
- package/src/agents/copilot-hooks-deployer.ts +93 -0
- package/src/agents/hooks-deployer.test.ts +9 -1
- package/src/agents/hooks-deployer.ts +2 -1
- package/src/agents/overlay.test.ts +26 -0
- package/src/agents/overlay.ts +18 -4
- package/src/beads/client.ts +31 -3
- package/src/commands/agents.ts +1 -1
- package/src/commands/clean.test.ts +3 -0
- package/src/commands/clean.ts +1 -58
- package/src/commands/completions.test.ts +18 -6
- package/src/commands/completions.ts +40 -1
- package/src/commands/coordinator.test.ts +77 -4
- package/src/commands/coordinator.ts +228 -125
- package/src/commands/dashboard.ts +50 -10
- package/src/commands/doctor.ts +3 -1
- package/src/commands/ecosystem.test.ts +126 -1
- package/src/commands/ecosystem.ts +7 -53
- package/src/commands/feed.test.ts +117 -2
- package/src/commands/feed.ts +46 -30
- package/src/commands/group.test.ts +274 -155
- package/src/commands/group.ts +11 -5
- package/src/commands/init.ts +50 -0
- package/src/commands/inspect.ts +8 -4
- package/src/commands/log.test.ts +35 -0
- package/src/commands/log.ts +10 -6
- package/src/commands/logs.test.ts +423 -1
- package/src/commands/logs.ts +99 -104
- package/src/commands/monitor.ts +8 -2
- package/src/commands/orchestrator.ts +42 -0
- package/src/commands/prime.test.ts +177 -2
- package/src/commands/prime.ts +4 -2
- package/src/commands/sling.ts +8 -3
- package/src/commands/upgrade.test.ts +2 -0
- package/src/commands/upgrade.ts +1 -17
- package/src/commands/watch.test.ts +67 -1
- package/src/commands/watch.ts +4 -79
- package/src/config.test.ts +250 -0
- package/src/config.ts +43 -0
- package/src/doctor/agents.test.ts +72 -5
- package/src/doctor/agents.ts +10 -10
- package/src/doctor/consistency.test.ts +35 -0
- package/src/doctor/consistency.ts +7 -3
- package/src/doctor/dependencies.test.ts +58 -1
- package/src/doctor/dependencies.ts +4 -2
- package/src/doctor/providers.test.ts +41 -5
- package/src/doctor/types.ts +2 -1
- package/src/doctor/version.test.ts +106 -2
- package/src/doctor/version.ts +4 -2
- package/src/doctor/watchdog.test.ts +167 -0
- package/src/doctor/watchdog.ts +158 -0
- package/src/e2e/init-sling-lifecycle.test.ts +2 -1
- package/src/errors.test.ts +350 -0
- package/src/events/tailer.test.ts +25 -0
- package/src/events/tailer.ts +8 -1
- package/src/index.ts +4 -1
- package/src/mail/store.test.ts +110 -0
- package/src/runtimes/aider.test.ts +124 -0
- package/src/runtimes/aider.ts +147 -0
- package/src/runtimes/amp.test.ts +164 -0
- package/src/runtimes/amp.ts +154 -0
- package/src/runtimes/claude.test.ts +4 -2
- package/src/runtimes/codex.test.ts +38 -1
- package/src/runtimes/codex.ts +22 -3
- package/src/runtimes/copilot.test.ts +213 -13
- package/src/runtimes/copilot.ts +93 -11
- package/src/runtimes/goose.test.ts +133 -0
- package/src/runtimes/goose.ts +157 -0
- package/src/runtimes/pi-guards.ts +2 -1
- package/src/runtimes/pi.test.ts +33 -9
- package/src/runtimes/pi.ts +10 -10
- package/src/runtimes/registry.test.ts +1 -1
- package/src/runtimes/registry.ts +13 -4
- package/src/runtimes/sapling.ts +2 -1
- package/src/runtimes/types.ts +9 -2
- package/src/tracker/factory.test.ts +10 -0
- package/src/tracker/factory.ts +3 -2
- package/src/types.ts +4 -0
- package/src/utils/bin.test.ts +10 -0
- package/src/utils/bin.ts +37 -0
- package/src/utils/fs.test.ts +119 -0
- package/src/utils/fs.ts +62 -0
- package/src/utils/pid.test.ts +68 -0
- package/src/utils/pid.ts +45 -0
- package/src/utils/time.test.ts +43 -0
- package/src/utils/time.ts +37 -0
- package/src/utils/version.test.ts +33 -0
- package/src/utils/version.ts +70 -0
- package/src/watchdog/daemon.test.ts +255 -1
- package/src/watchdog/daemon.ts +46 -9
- package/src/watchdog/health.test.ts +15 -1
- package/src/watchdog/health.ts +1 -1
- package/src/watchdog/triage.test.ts +49 -9
- package/src/watchdog/triage.ts +21 -5
- package/src/worktree/tmux.test.ts +166 -49
- package/src/worktree/tmux.ts +36 -37
- package/templates/copilot-hooks.json.tmpl +13 -0
package/src/beads/client.ts
CHANGED
|
@@ -194,9 +194,37 @@ export function createBeadsClient(cwd: string): BeadsClient {
|
|
|
194
194
|
if (options?.limit !== undefined) {
|
|
195
195
|
args.push("--limit", String(options.limit));
|
|
196
196
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
197
|
+
try {
|
|
198
|
+
const { stdout } = await runBd(args, "list");
|
|
199
|
+
const trimmed = stdout.trim();
|
|
200
|
+
if (trimmed === "") return [];
|
|
201
|
+
const parsed: unknown = JSON.parse(trimmed);
|
|
202
|
+
if (Array.isArray(parsed)) {
|
|
203
|
+
// Flat format: RawBeadIssue[]
|
|
204
|
+
return (parsed as RawBeadIssue[]).map(normalizeIssue);
|
|
205
|
+
}
|
|
206
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
207
|
+
// Tree format: { mol: RawBeadIssue[] } — flatten all groups
|
|
208
|
+
const tree = parsed as Record<string, unknown>;
|
|
209
|
+
const issues: BeadIssue[] = [];
|
|
210
|
+
for (const group of Object.values(tree)) {
|
|
211
|
+
if (Array.isArray(group)) {
|
|
212
|
+
for (const item of group) {
|
|
213
|
+
issues.push(normalizeIssue(item as RawBeadIssue));
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (issues.length > 0) return issues;
|
|
218
|
+
}
|
|
219
|
+
} catch {
|
|
220
|
+
// fall through to ready fallback
|
|
221
|
+
}
|
|
222
|
+
// Fallback: bd ready --json always returns a flat array
|
|
223
|
+
const { stdout: readyStdout } = await runBd(["ready", "--json"], "ready (list fallback)");
|
|
224
|
+
const readyTrimmed = readyStdout.trim();
|
|
225
|
+
if (readyTrimmed === "") return [];
|
|
226
|
+
const readyRaw = parseJsonOutput<RawBeadIssue[]>(readyTrimmed, "ready (list fallback)");
|
|
227
|
+
return readyRaw.map(normalizeIssue);
|
|
200
228
|
},
|
|
201
229
|
};
|
|
202
230
|
}
|
package/src/commands/agents.ts
CHANGED
|
@@ -223,7 +223,7 @@ export function createAgentsCommand(): Command {
|
|
|
223
223
|
.description("Find active agents by capability")
|
|
224
224
|
.option(
|
|
225
225
|
"--capability <type>",
|
|
226
|
-
"Filter by capability (builder, scout, reviewer, lead, merger, coordinator, supervisor)",
|
|
226
|
+
"Filter by capability (builder, scout, reviewer, lead, merger, orchestrator, coordinator, supervisor)",
|
|
227
227
|
)
|
|
228
228
|
.option("--all", "Include completed and zombie agents (default: active only)")
|
|
229
229
|
.option("--json", "Output as JSON")
|
package/src/commands/clean.ts
CHANGED
|
@@ -20,7 +20,6 @@
|
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
import { existsSync } from "node:fs";
|
|
23
|
-
import { readdir, rm, unlink } from "node:fs/promises";
|
|
24
23
|
import { join } from "node:path";
|
|
25
24
|
import { loadConfig } from "../config.ts";
|
|
26
25
|
import { AgentError, ValidationError } from "../errors.ts";
|
|
@@ -30,6 +29,7 @@ import { printHint, printSuccess } from "../logging/color.ts";
|
|
|
30
29
|
import { createMulchClient } from "../mulch/client.ts";
|
|
31
30
|
import { openSessionStore } from "../sessions/compat.ts";
|
|
32
31
|
import type { AgentSession, MulchDoctorResult, MulchPruneResult, MulchStatus } from "../types.ts";
|
|
32
|
+
import { clearDirectory, deleteFile, resetJsonFile, wipeSqliteDb } from "../utils/fs.ts";
|
|
33
33
|
import { listWorktrees, removeWorktree } from "../worktree/manager.ts";
|
|
34
34
|
import {
|
|
35
35
|
isProcessAlive,
|
|
@@ -274,63 +274,6 @@ async function deleteOrphanedBranches(root: string): Promise<number> {
|
|
|
274
274
|
return deleted;
|
|
275
275
|
}
|
|
276
276
|
|
|
277
|
-
/**
|
|
278
|
-
* Delete a SQLite database file and its WAL/SHM companions.
|
|
279
|
-
*/
|
|
280
|
-
async function wipeSqliteDb(dbPath: string): Promise<boolean> {
|
|
281
|
-
const extensions = ["", "-wal", "-shm"];
|
|
282
|
-
let wiped = false;
|
|
283
|
-
for (const ext of extensions) {
|
|
284
|
-
try {
|
|
285
|
-
await unlink(`${dbPath}${ext}`);
|
|
286
|
-
if (ext === "") wiped = true;
|
|
287
|
-
} catch {
|
|
288
|
-
// File may not exist
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
return wiped;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
/**
|
|
295
|
-
* Reset a JSON file to an empty array.
|
|
296
|
-
*/
|
|
297
|
-
async function resetJsonFile(path: string): Promise<boolean> {
|
|
298
|
-
const file = Bun.file(path);
|
|
299
|
-
if (await file.exists()) {
|
|
300
|
-
await Bun.write(path, "[]\n");
|
|
301
|
-
return true;
|
|
302
|
-
}
|
|
303
|
-
return false;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
/**
|
|
307
|
-
* Clear all entries inside a directory but keep the directory itself.
|
|
308
|
-
*/
|
|
309
|
-
async function clearDirectory(dirPath: string): Promise<boolean> {
|
|
310
|
-
try {
|
|
311
|
-
const entries = await readdir(dirPath);
|
|
312
|
-
for (const entry of entries) {
|
|
313
|
-
await rm(join(dirPath, entry), { recursive: true, force: true });
|
|
314
|
-
}
|
|
315
|
-
return entries.length > 0;
|
|
316
|
-
} catch {
|
|
317
|
-
// Directory may not exist
|
|
318
|
-
return false;
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
/**
|
|
323
|
-
* Delete a single file if it exists.
|
|
324
|
-
*/
|
|
325
|
-
async function deleteFile(path: string): Promise<boolean> {
|
|
326
|
-
try {
|
|
327
|
-
await unlink(path);
|
|
328
|
-
return true;
|
|
329
|
-
} catch {
|
|
330
|
-
return false;
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
|
|
334
277
|
/**
|
|
335
278
|
* Check mulch repository health and return diagnostic information.
|
|
336
279
|
*
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Tests for shell completion generation.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { describe, expect, it, mock } from "bun:test";
|
|
5
|
+
import { afterEach, describe, expect, it, mock } from "bun:test";
|
|
6
6
|
import {
|
|
7
7
|
COMMANDS,
|
|
8
8
|
completionsCommand,
|
|
@@ -11,9 +11,13 @@ import {
|
|
|
11
11
|
generateZsh,
|
|
12
12
|
} from "./completions.ts";
|
|
13
13
|
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
process.exitCode = undefined;
|
|
16
|
+
});
|
|
17
|
+
|
|
14
18
|
describe("COMMANDS array", () => {
|
|
15
|
-
it("should have exactly
|
|
16
|
-
expect(COMMANDS).toHaveLength(
|
|
19
|
+
it("should have exactly 34 commands", () => {
|
|
20
|
+
expect(COMMANDS).toHaveLength(34);
|
|
17
21
|
});
|
|
18
22
|
|
|
19
23
|
it("should include all expected command names", () => {
|
|
@@ -37,6 +41,7 @@ describe("COMMANDS array", () => {
|
|
|
37
41
|
expect(names).toContain("costs");
|
|
38
42
|
expect(names).toContain("metrics");
|
|
39
43
|
expect(names).toContain("spec");
|
|
44
|
+
expect(names).toContain("orchestrator");
|
|
40
45
|
expect(names).toContain("coordinator");
|
|
41
46
|
expect(names).toContain("supervisor");
|
|
42
47
|
expect(names).toContain("hooks");
|
|
@@ -62,7 +67,7 @@ describe("generateBash", () => {
|
|
|
62
67
|
expect(script).toContain("_init_completion");
|
|
63
68
|
});
|
|
64
69
|
|
|
65
|
-
it("should include all
|
|
70
|
+
it("should include all 34 command names", () => {
|
|
66
71
|
const script = generateBash();
|
|
67
72
|
for (const cmd of COMMANDS) {
|
|
68
73
|
expect(script).toContain(cmd.name);
|
|
@@ -96,7 +101,7 @@ describe("generateZsh", () => {
|
|
|
96
101
|
expect(script).toContain("_arguments");
|
|
97
102
|
});
|
|
98
103
|
|
|
99
|
-
it("should include all
|
|
104
|
+
it("should include all 34 command names", () => {
|
|
100
105
|
const script = generateZsh();
|
|
101
106
|
for (const cmd of COMMANDS) {
|
|
102
107
|
expect(script).toContain(cmd.name);
|
|
@@ -126,7 +131,7 @@ describe("generateFish", () => {
|
|
|
126
131
|
expect(script).toContain("__fish_use_subcommand");
|
|
127
132
|
});
|
|
128
133
|
|
|
129
|
-
it("should include all
|
|
134
|
+
it("should include all 34 command names", () => {
|
|
130
135
|
const script = generateFish();
|
|
131
136
|
for (const cmd of COMMANDS) {
|
|
132
137
|
expect(script).toContain(cmd.name);
|
|
@@ -148,6 +153,13 @@ describe("generateFish", () => {
|
|
|
148
153
|
expect(script).toContain("error");
|
|
149
154
|
expect(script).toContain("worker_done");
|
|
150
155
|
});
|
|
156
|
+
|
|
157
|
+
it("should include orchestrator command with start/stop/status subcommands", () => {
|
|
158
|
+
const orchestrator = COMMANDS.find((c) => c.name === "orchestrator");
|
|
159
|
+
expect(orchestrator).toBeDefined();
|
|
160
|
+
const subcommands = orchestrator?.subcommands?.map((s) => s.name);
|
|
161
|
+
expect(subcommands).toEqual(["start", "stop", "status"]);
|
|
162
|
+
});
|
|
151
163
|
});
|
|
152
164
|
|
|
153
165
|
describe("completionsCommand", () => {
|
|
@@ -44,7 +44,16 @@ export const COMMANDS: readonly CommandDef[] = [
|
|
|
44
44
|
name: "--capability",
|
|
45
45
|
desc: "Filter by capability",
|
|
46
46
|
takesValue: true,
|
|
47
|
-
values: [
|
|
47
|
+
values: [
|
|
48
|
+
"builder",
|
|
49
|
+
"scout",
|
|
50
|
+
"reviewer",
|
|
51
|
+
"lead",
|
|
52
|
+
"merger",
|
|
53
|
+
"orchestrator",
|
|
54
|
+
"coordinator",
|
|
55
|
+
"supervisor",
|
|
56
|
+
],
|
|
48
57
|
},
|
|
49
58
|
{ name: "--all", desc: "Include completed and zombie agents" },
|
|
50
59
|
{ name: "--json", desc: "JSON output" },
|
|
@@ -343,6 +352,36 @@ export const COMMANDS: readonly CommandDef[] = [
|
|
|
343
352
|
},
|
|
344
353
|
],
|
|
345
354
|
},
|
|
355
|
+
{
|
|
356
|
+
name: "orchestrator",
|
|
357
|
+
desc: "Persistent ecosystem orchestrator agent",
|
|
358
|
+
flags: [
|
|
359
|
+
{ name: "--json", desc: "JSON output" },
|
|
360
|
+
{ name: "--help", desc: "Show help" },
|
|
361
|
+
],
|
|
362
|
+
subcommands: [
|
|
363
|
+
{
|
|
364
|
+
name: "start",
|
|
365
|
+
desc: "Start orchestrator",
|
|
366
|
+
flags: [
|
|
367
|
+
{ name: "--attach", desc: "Attach to tmux session" },
|
|
368
|
+
{ name: "--no-attach", desc: "Do not attach to tmux session" },
|
|
369
|
+
{ name: "--watchdog", desc: "Auto-start watchdog daemon" },
|
|
370
|
+
{ name: "--json", desc: "JSON output" },
|
|
371
|
+
],
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
name: "stop",
|
|
375
|
+
desc: "Stop orchestrator",
|
|
376
|
+
flags: [{ name: "--json", desc: "JSON output" }],
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
name: "status",
|
|
380
|
+
desc: "Show orchestrator state",
|
|
381
|
+
flags: [{ name: "--json", desc: "JSON output" }],
|
|
382
|
+
},
|
|
383
|
+
],
|
|
384
|
+
},
|
|
346
385
|
{
|
|
347
386
|
name: "coordinator",
|
|
348
387
|
desc: "Persistent coordinator agent",
|
|
@@ -29,6 +29,11 @@ import {
|
|
|
29
29
|
createCoordinatorCommand,
|
|
30
30
|
resolveAttach,
|
|
31
31
|
} from "./coordinator.ts";
|
|
32
|
+
import {
|
|
33
|
+
buildOrchestratorBeacon,
|
|
34
|
+
createOrchestratorCommand,
|
|
35
|
+
orchestratorCommand,
|
|
36
|
+
} from "./orchestrator.ts";
|
|
32
37
|
|
|
33
38
|
// --- Fake Tmux ---
|
|
34
39
|
|
|
@@ -272,14 +277,26 @@ beforeEach(async () => {
|
|
|
272
277
|
canSpawn: true,
|
|
273
278
|
constraints: [],
|
|
274
279
|
},
|
|
280
|
+
orchestrator: {
|
|
281
|
+
file: "orchestrator.md",
|
|
282
|
+
model: "opus",
|
|
283
|
+
tools: ["Read", "Bash"],
|
|
284
|
+
capabilities: ["orchestrate", "coordinate"],
|
|
285
|
+
canSpawn: true,
|
|
286
|
+
constraints: [],
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
capabilityIndex: {
|
|
290
|
+
coordinate: ["coordinator", "orchestrator"],
|
|
291
|
+
orchestrate: ["orchestrator"],
|
|
275
292
|
},
|
|
276
|
-
capabilityIndex: { coordinate: ["coordinator"] },
|
|
277
293
|
};
|
|
278
294
|
await Bun.write(
|
|
279
295
|
join(overstoryDir, "agent-manifest.json"),
|
|
280
296
|
`${JSON.stringify(manifest, null, "\t")}\n`,
|
|
281
297
|
);
|
|
282
298
|
await Bun.write(join(agentDefsDir, "coordinator.md"), "# Coordinator\n");
|
|
299
|
+
await Bun.write(join(agentDefsDir, "orchestrator.md"), "# Orchestrator\n");
|
|
283
300
|
|
|
284
301
|
// Override cwd so coordinator commands find our temp project
|
|
285
302
|
process.chdir(tempDir);
|
|
@@ -1255,14 +1272,16 @@ describe("buildCoordinatorBeacon", () => {
|
|
|
1255
1272
|
|
|
1256
1273
|
test("includes hierarchy enforcement instruction", () => {
|
|
1257
1274
|
const beacon = buildCoordinatorBeacon();
|
|
1258
|
-
expect(beacon).toContain("
|
|
1259
|
-
expect(beacon).toContain("
|
|
1275
|
+
expect(beacon).toContain("Default to leads");
|
|
1276
|
+
expect(beacon).toContain("spawn scout/builder directly");
|
|
1277
|
+
expect(beacon).toContain("NEVER spawn reviewer or merger directly");
|
|
1260
1278
|
});
|
|
1261
1279
|
|
|
1262
1280
|
test("includes delegation instruction", () => {
|
|
1263
1281
|
const beacon = buildCoordinatorBeacon();
|
|
1264
1282
|
expect(beacon).toContain("DELEGATION");
|
|
1265
|
-
expect(beacon).toContain("spawn a lead who will
|
|
1283
|
+
expect(beacon).toContain("spawn a lead who will handle scouts/builders/reviewers");
|
|
1284
|
+
expect(beacon).toContain("--dispatch-max-agents 1/2");
|
|
1266
1285
|
});
|
|
1267
1286
|
|
|
1268
1287
|
test("parts are joined with em-dash separator", () => {
|
|
@@ -1273,6 +1292,60 @@ describe("buildCoordinatorBeacon", () => {
|
|
|
1273
1292
|
});
|
|
1274
1293
|
});
|
|
1275
1294
|
|
|
1295
|
+
describe("orchestratorCommand", () => {
|
|
1296
|
+
test("help shows orchestrator command name", async () => {
|
|
1297
|
+
const output = await captureStdout(() => orchestratorCommand(["--help"]));
|
|
1298
|
+
expect(output).toContain("orchestrator");
|
|
1299
|
+
});
|
|
1300
|
+
|
|
1301
|
+
test("start creates orchestrator session with orchestrator capability", async () => {
|
|
1302
|
+
const { deps, calls } = makeDeps({ "overstory-test-project-orchestrator": true });
|
|
1303
|
+
const originalSleep = Bun.sleep;
|
|
1304
|
+
Bun.sleep = (() => Promise.resolve()) as typeof Bun.sleep;
|
|
1305
|
+
|
|
1306
|
+
try {
|
|
1307
|
+
const output = await captureStdout(() =>
|
|
1308
|
+
orchestratorCommand(["start", "--no-attach", "--json"], deps),
|
|
1309
|
+
);
|
|
1310
|
+
const parsed = JSON.parse(output) as Record<string, unknown>;
|
|
1311
|
+
|
|
1312
|
+
expect(parsed.agentName).toBe("orchestrator");
|
|
1313
|
+
expect(parsed.capability).toBe("orchestrator");
|
|
1314
|
+
expect(parsed.tmuxSession).toBe("overstory-test-project-orchestrator");
|
|
1315
|
+
expect(calls.createSession[0]?.name).toBe("overstory-test-project-orchestrator");
|
|
1316
|
+
expect(calls.createSession[0]?.command).toContain("orchestrator.md");
|
|
1317
|
+
|
|
1318
|
+
const session = loadSessionsFromDb().find((entry) => entry.agentName === "orchestrator");
|
|
1319
|
+
expect(session?.capability).toBe("orchestrator");
|
|
1320
|
+
} finally {
|
|
1321
|
+
Bun.sleep = originalSleep;
|
|
1322
|
+
}
|
|
1323
|
+
});
|
|
1324
|
+
|
|
1325
|
+
test("command registration includes orchestrator start/stop/status", () => {
|
|
1326
|
+
const cmd = createOrchestratorCommand({});
|
|
1327
|
+
const subcommandNames = cmd.commands.map((c) => c.name());
|
|
1328
|
+
expect(subcommandNames).toContain("start");
|
|
1329
|
+
expect(subcommandNames).toContain("stop");
|
|
1330
|
+
expect(subcommandNames).toContain("status");
|
|
1331
|
+
expect(subcommandNames).not.toContain("check-complete");
|
|
1332
|
+
});
|
|
1333
|
+
});
|
|
1334
|
+
|
|
1335
|
+
describe("buildOrchestratorBeacon", () => {
|
|
1336
|
+
test("includes orchestrator identity in header", () => {
|
|
1337
|
+
const beacon = buildOrchestratorBeacon();
|
|
1338
|
+
expect(beacon).toContain("[OVERSTORY] orchestrator (orchestrator)");
|
|
1339
|
+
});
|
|
1340
|
+
|
|
1341
|
+
test("includes ecosystem startup instructions", () => {
|
|
1342
|
+
const beacon = buildOrchestratorBeacon("sd");
|
|
1343
|
+
expect(beacon).toContain("ov mail check --agent orchestrator");
|
|
1344
|
+
expect(beacon).toContain("sd ready");
|
|
1345
|
+
expect(beacon).toContain("inspect ecosystem status");
|
|
1346
|
+
});
|
|
1347
|
+
});
|
|
1348
|
+
|
|
1276
1349
|
describe("resolveAttach", () => {
|
|
1277
1350
|
test("--attach flag forces attach regardless of TTY", () => {
|
|
1278
1351
|
expect(resolveAttach(["--attach"], false)).toBe(true);
|