@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
|
@@ -3,6 +3,13 @@ import { dirname, join } from "node:path";
|
|
|
3
3
|
import { DEFAULT_QUALITY_GATES } from "../config.ts";
|
|
4
4
|
import { AgentError } from "../errors.ts";
|
|
5
5
|
import type { QualityGate } from "../types.ts";
|
|
6
|
+
import {
|
|
7
|
+
DANGEROUS_BASH_PATTERNS,
|
|
8
|
+
INTERACTIVE_TOOLS,
|
|
9
|
+
NATIVE_TEAM_TOOLS,
|
|
10
|
+
SAFE_BASH_PREFIXES,
|
|
11
|
+
WRITE_TOOLS,
|
|
12
|
+
} from "./guard-rules.ts";
|
|
6
13
|
|
|
7
14
|
/**
|
|
8
15
|
* Capabilities that must never modify project files.
|
|
@@ -31,96 +38,6 @@ const COORDINATION_CAPABILITIES = new Set(["coordinator", "supervisor", "monitor
|
|
|
31
38
|
*/
|
|
32
39
|
const COORDINATION_SAFE_PREFIXES = ["git add", "git commit"];
|
|
33
40
|
|
|
34
|
-
/**
|
|
35
|
-
* Claude Code native team/task tools that bypass overstory orchestration.
|
|
36
|
-
* All overstory agents must use `overstory sling` for delegation, not these.
|
|
37
|
-
*/
|
|
38
|
-
const NATIVE_TEAM_TOOLS = [
|
|
39
|
-
"Task",
|
|
40
|
-
"TeamCreate",
|
|
41
|
-
"TeamDelete",
|
|
42
|
-
"SendMessage",
|
|
43
|
-
"TaskCreate",
|
|
44
|
-
"TaskUpdate",
|
|
45
|
-
"TaskList",
|
|
46
|
-
"TaskGet",
|
|
47
|
-
"TaskOutput",
|
|
48
|
-
"TaskStop",
|
|
49
|
-
];
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Tools that require human interaction and block indefinitely in non-interactive
|
|
53
|
-
* tmux sessions. Agents run non-interactively and must never call these tools.
|
|
54
|
-
* Use overstory mail (--type question) to escalate to the orchestrator instead.
|
|
55
|
-
*/
|
|
56
|
-
const INTERACTIVE_TOOLS = ["AskUserQuestion", "EnterPlanMode", "EnterWorktree"];
|
|
57
|
-
|
|
58
|
-
/** Tools that non-implementation agents must not use. */
|
|
59
|
-
const WRITE_TOOLS = ["Write", "Edit", "NotebookEdit"];
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Bash commands that modify files and must be blocked for non-implementation agents.
|
|
63
|
-
* Each pattern is a regex fragment used inside a grep -qE check.
|
|
64
|
-
*/
|
|
65
|
-
const DANGEROUS_BASH_PATTERNS = [
|
|
66
|
-
"sed\\s+-i",
|
|
67
|
-
"sed\\s+--in-place",
|
|
68
|
-
"echo\\s+.*>",
|
|
69
|
-
"printf\\s+.*>",
|
|
70
|
-
"cat\\s+.*>",
|
|
71
|
-
"tee\\s",
|
|
72
|
-
"\\bvim\\b",
|
|
73
|
-
"\\bnano\\b",
|
|
74
|
-
"\\bvi\\b",
|
|
75
|
-
"\\bmv\\s",
|
|
76
|
-
"\\bcp\\s",
|
|
77
|
-
"\\brm\\s",
|
|
78
|
-
"\\bmkdir\\s",
|
|
79
|
-
"\\btouch\\s",
|
|
80
|
-
"\\bchmod\\s",
|
|
81
|
-
"\\bchown\\s",
|
|
82
|
-
">>",
|
|
83
|
-
"\\bgit\\s+add\\b",
|
|
84
|
-
"\\bgit\\s+commit\\b",
|
|
85
|
-
"\\bgit\\s+merge\\b",
|
|
86
|
-
"\\bgit\\s+push\\b",
|
|
87
|
-
"\\bgit\\s+reset\\b",
|
|
88
|
-
"\\bgit\\s+checkout\\b",
|
|
89
|
-
"\\bgit\\s+rebase\\b",
|
|
90
|
-
"\\bgit\\s+stash\\b",
|
|
91
|
-
"\\bnpm\\s+install\\b",
|
|
92
|
-
"\\bbun\\s+install\\b",
|
|
93
|
-
"\\bbun\\s+add\\b",
|
|
94
|
-
// Runtime eval flags — bypass shell pattern guards by executing JS/Python directly
|
|
95
|
-
"\\bbun\\s+-e\\b",
|
|
96
|
-
"\\bbun\\s+--eval\\b",
|
|
97
|
-
"\\bnode\\s+-e\\b",
|
|
98
|
-
"\\bnode\\s+--eval\\b",
|
|
99
|
-
"\\bdeno\\s+eval\\b",
|
|
100
|
-
"\\bpython3?\\s+-c\\b",
|
|
101
|
-
"\\bperl\\s+-e\\b",
|
|
102
|
-
"\\bruby\\s+-e\\b",
|
|
103
|
-
];
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Bash commands that are always safe for non-implementation agents.
|
|
107
|
-
* If a command starts with any of these prefixes, it bypasses the dangerous command check.
|
|
108
|
-
* This whitelist is checked BEFORE the blocklist.
|
|
109
|
-
*/
|
|
110
|
-
const SAFE_BASH_PREFIXES = [
|
|
111
|
-
"ov ",
|
|
112
|
-
"overstory ",
|
|
113
|
-
"bd ",
|
|
114
|
-
"sd ",
|
|
115
|
-
"git status",
|
|
116
|
-
"git log",
|
|
117
|
-
"git diff",
|
|
118
|
-
"git show",
|
|
119
|
-
"git blame",
|
|
120
|
-
"git branch",
|
|
121
|
-
"mulch ",
|
|
122
|
-
];
|
|
123
|
-
|
|
124
41
|
/**
|
|
125
42
|
* Extract command prefixes from quality gate configurations.
|
|
126
43
|
*
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
-
import { mkdir, mkdtemp
|
|
2
|
+
import { mkdir, mkdtemp } from "node:fs/promises";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { AgentError } from "../errors.ts";
|
|
6
|
+
import { cleanupTempDir } from "../test-helpers.ts";
|
|
6
7
|
import type { AgentIdentity } from "../types.ts";
|
|
7
8
|
import { createIdentity, loadIdentity, updateIdentity } from "./identity.ts";
|
|
8
9
|
|
|
@@ -14,7 +15,7 @@ describe("identity", () => {
|
|
|
14
15
|
});
|
|
15
16
|
|
|
16
17
|
afterEach(async () => {
|
|
17
|
-
await
|
|
18
|
+
await cleanupTempDir(tempDir);
|
|
18
19
|
});
|
|
19
20
|
|
|
20
21
|
describe("createIdentity", () => {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
-
import { mkdir, mkdtemp
|
|
2
|
+
import { mkdir, mkdtemp } from "node:fs/promises";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { AgentError } from "../errors.ts";
|
|
6
|
+
import { cleanupTempDir } from "../test-helpers.ts";
|
|
6
7
|
import type { AgentManifest, OverstoryConfig } from "../types.ts";
|
|
7
8
|
import { createManifestLoader, resolveModel, resolveProviderEnv } from "./manifest.ts";
|
|
8
9
|
|
|
@@ -41,7 +42,7 @@ describe("createManifestLoader", () => {
|
|
|
41
42
|
});
|
|
42
43
|
|
|
43
44
|
afterEach(async () => {
|
|
44
|
-
await
|
|
45
|
+
await cleanupTempDir(tempDir);
|
|
45
46
|
});
|
|
46
47
|
|
|
47
48
|
/** Write the manifest JSON and create matching .md files. */
|
|
@@ -806,7 +807,7 @@ describe("manifest validation accepts arbitrary model strings", () => {
|
|
|
806
807
|
});
|
|
807
808
|
|
|
808
809
|
afterEach(async () => {
|
|
809
|
-
await
|
|
810
|
+
await cleanupTempDir(tempDir);
|
|
810
811
|
});
|
|
811
812
|
|
|
812
813
|
test("accepts provider-prefixed model string", async () => {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
-
import { mkdir, mkdtemp
|
|
2
|
+
import { mkdir, mkdtemp } from "node:fs/promises";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { AgentError } from "../errors.ts";
|
|
6
|
+
import { cleanupTempDir } from "../test-helpers.ts";
|
|
6
7
|
import type { OverlayConfig, QualityGate } from "../types.ts";
|
|
7
8
|
import {
|
|
8
9
|
formatQualityGatesBash,
|
|
@@ -265,7 +266,7 @@ describe("generateOverlay", () => {
|
|
|
265
266
|
expect(output).not.toContain("bun run lint");
|
|
266
267
|
});
|
|
267
268
|
|
|
268
|
-
test("scout completion section includes
|
|
269
|
+
test("scout completion section includes sd close and mail send", async () => {
|
|
269
270
|
const config = makeConfig({
|
|
270
271
|
capability: "scout",
|
|
271
272
|
agentName: "recon-1",
|
|
@@ -274,7 +275,7 @@ describe("generateOverlay", () => {
|
|
|
274
275
|
});
|
|
275
276
|
const output = await generateOverlay(config);
|
|
276
277
|
|
|
277
|
-
expect(output).toContain("
|
|
278
|
+
expect(output).toContain("sd close overstory-task1");
|
|
278
279
|
expect(output).toContain("ov mail send --to lead-alpha");
|
|
279
280
|
});
|
|
280
281
|
|
|
@@ -426,14 +427,14 @@ describe("generateOverlay", () => {
|
|
|
426
427
|
expect(output).not.toContain("Quality Gates");
|
|
427
428
|
});
|
|
428
429
|
|
|
429
|
-
test("default trackerCli renders as
|
|
430
|
+
test("default trackerCli renders as sd in quality gates", async () => {
|
|
430
431
|
const config = makeConfig({ capability: "builder", taskId: "overstory-task1" });
|
|
431
432
|
const output = await generateOverlay(config);
|
|
432
433
|
|
|
433
|
-
expect(output).toContain("
|
|
434
|
+
expect(output).toContain("sd close overstory-task1");
|
|
434
435
|
});
|
|
435
436
|
|
|
436
|
-
test("custom trackerCli replaces
|
|
437
|
+
test("custom trackerCli replaces sd in quality gates", async () => {
|
|
437
438
|
const config = makeConfig({
|
|
438
439
|
capability: "builder",
|
|
439
440
|
trackerCli: "sd",
|
|
@@ -489,11 +490,11 @@ describe("generateOverlay", () => {
|
|
|
489
490
|
expect(output).not.toContain("{{TRACKER_NAME}}");
|
|
490
491
|
});
|
|
491
492
|
|
|
492
|
-
test("defaults
|
|
493
|
+
test("defaults: no trackerCli/trackerName produces sd/seeds", async () => {
|
|
493
494
|
const config = makeConfig({ capability: "builder", taskId: "overstory-back" });
|
|
494
495
|
const output = await generateOverlay(config);
|
|
495
496
|
|
|
496
|
-
expect(output).toContain("
|
|
497
|
+
expect(output).toContain("sd close overstory-back");
|
|
497
498
|
});
|
|
498
499
|
|
|
499
500
|
test("dispatch overrides: skipReview injects SKIP REVIEW directive for leads", async () => {
|
|
@@ -584,7 +585,7 @@ describe("writeOverlay", () => {
|
|
|
584
585
|
});
|
|
585
586
|
|
|
586
587
|
afterEach(async () => {
|
|
587
|
-
await
|
|
588
|
+
await cleanupTempDir(tempDir);
|
|
588
589
|
});
|
|
589
590
|
|
|
590
591
|
test("creates .claude/CLAUDE.md in worktree directory", async () => {
|
package/src/agents/overlay.ts
CHANGED
|
@@ -182,7 +182,7 @@ function formatQualityGates(config: OverlayConfig): string {
|
|
|
182
182
|
"Before reporting completion:",
|
|
183
183
|
"",
|
|
184
184
|
`1. **Record mulch learnings:** \`ml record <domain> --type <convention|pattern|reference> --description "..."\` — capture reusable knowledge from your work`,
|
|
185
|
-
`2. **Close issue:** \`${config.trackerCli ?? "
|
|
185
|
+
`2. **Close issue:** \`${config.trackerCli ?? "sd"} close ${config.taskId} --reason "summary of findings"\``,
|
|
186
186
|
`3. **Send results:** \`ov mail send --to ${config.parentAgent ?? "coordinator"} --subject "done" --body "Summary" --type result --agent ${config.agentName}\``,
|
|
187
187
|
"",
|
|
188
188
|
"You are a read-only agent. Do NOT commit, modify files, or run quality gates.",
|
|
@@ -207,7 +207,7 @@ function formatQualityGates(config: OverlayConfig): string {
|
|
|
207
207
|
`${gateLines.length + 1}. **Commit:** all changes committed to your branch (${config.branchName})`,
|
|
208
208
|
`${gateLines.length + 2}. **Record mulch learnings:** \`ml record <domain> --type <convention|pattern|failure|decision> --description "..." --outcome-status success --outcome-agent ${config.agentName}\` — capture insights from your work`,
|
|
209
209
|
`${gateLines.length + 3}. **Signal completion:** send \`worker_done\` mail to ${config.parentAgent ?? "coordinator"}: \`ov mail send --to ${config.parentAgent ?? "coordinator"} --subject "Worker done: ${config.taskId}" --body "Quality gates passed." --type worker_done --agent ${config.agentName}\``,
|
|
210
|
-
`${gateLines.length + 4}. **Close issue:** \`${config.trackerCli ?? "
|
|
210
|
+
`${gateLines.length + 4}. **Close issue:** \`${config.trackerCli ?? "sd"} close ${config.taskId} --reason "summary of changes"\``,
|
|
211
211
|
"",
|
|
212
212
|
"Do NOT push to the canonical branch. Your work will be merged by the",
|
|
213
213
|
"coordinator via `ov merge`.",
|
|
@@ -225,7 +225,7 @@ function formatConstraints(config: OverlayConfig): string {
|
|
|
225
225
|
"",
|
|
226
226
|
"- You are **read-only**: do NOT modify, create, or delete any files",
|
|
227
227
|
"- Do NOT commit, push, or make any git state changes",
|
|
228
|
-
`- Report completion via \`${config.trackerCli ?? "
|
|
228
|
+
`- Report completion via \`${config.trackerCli ?? "sd"} close\` AND \`ov mail send --type result\``,
|
|
229
229
|
"- If you encounter a blocking issue, send mail with `--priority urgent --type error`",
|
|
230
230
|
].join("\n");
|
|
231
231
|
}
|
|
@@ -238,7 +238,7 @@ function formatConstraints(config: OverlayConfig): string {
|
|
|
238
238
|
"- Only modify files in your File Scope",
|
|
239
239
|
`- Commit only to your branch: ${config.branchName}`,
|
|
240
240
|
"- Never push to the canonical branch",
|
|
241
|
-
`- Report completion via \`${config.trackerCli ?? "
|
|
241
|
+
`- Report completion via \`${config.trackerCli ?? "sd"} close\` AND \`ov mail send --type result\``,
|
|
242
242
|
"- If you encounter a blocking issue, send mail with `--priority urgent --type error`",
|
|
243
243
|
].join("\n");
|
|
244
244
|
}
|
|
@@ -318,7 +318,7 @@ export async function generateOverlay(config: OverlayConfig): Promise<string> {
|
|
|
318
318
|
"{{QUALITY_GATE_STEPS}}": formatQualityGatesSteps(config.qualityGates),
|
|
319
319
|
"{{QUALITY_GATE_BASH}}": formatQualityGatesBash(config.qualityGates),
|
|
320
320
|
"{{QUALITY_GATE_CAPABILITIES}}": formatQualityGatesCapabilities(config.qualityGates),
|
|
321
|
-
"{{TRACKER_CLI}}": config.trackerCli ?? "
|
|
321
|
+
"{{TRACKER_CLI}}": config.trackerCli ?? "sd",
|
|
322
322
|
"{{TRACKER_NAME}}": config.trackerName ?? "seeds",
|
|
323
323
|
};
|
|
324
324
|
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { afterEach, beforeEach, describe, expect, it, mock } from "bun:test";
|
|
6
|
-
import { mkdtemp
|
|
6
|
+
import { mkdtemp } from "node:fs/promises";
|
|
7
7
|
import { tmpdir } from "node:os";
|
|
8
8
|
import { join } from "node:path";
|
|
9
9
|
import { createSessionStore } from "../sessions/store.ts";
|
|
10
|
+
import { cleanupTempDir } from "../test-helpers.ts";
|
|
10
11
|
import type { AgentSession } from "../types.ts";
|
|
11
12
|
import { agentsCommand, discoverAgents, extractFileScope } from "./agents.ts";
|
|
12
13
|
|
|
@@ -65,7 +66,7 @@ Some expertise here.
|
|
|
65
66
|
});
|
|
66
67
|
|
|
67
68
|
afterEach(async () => {
|
|
68
|
-
await
|
|
69
|
+
await cleanupTempDir(tempDir);
|
|
69
70
|
});
|
|
70
71
|
});
|
|
71
72
|
|
|
@@ -108,6 +109,7 @@ describe("discoverAgents", () => {
|
|
|
108
109
|
lastActivity: "2024-01-01T00:01:00Z",
|
|
109
110
|
escalationLevel: 0,
|
|
110
111
|
stalledSince: null,
|
|
112
|
+
transcriptPath: null,
|
|
111
113
|
};
|
|
112
114
|
|
|
113
115
|
store.upsert(session);
|
|
@@ -140,6 +142,7 @@ describe("discoverAgents", () => {
|
|
|
140
142
|
lastActivity: "2024-01-01T00:01:00Z",
|
|
141
143
|
escalationLevel: 0,
|
|
142
144
|
stalledSince: null,
|
|
145
|
+
transcriptPath: null,
|
|
143
146
|
};
|
|
144
147
|
|
|
145
148
|
const scout: AgentSession = {
|
|
@@ -159,6 +162,7 @@ describe("discoverAgents", () => {
|
|
|
159
162
|
lastActivity: "2024-01-01T00:01:00Z",
|
|
160
163
|
escalationLevel: 0,
|
|
161
164
|
stalledSince: null,
|
|
165
|
+
transcriptPath: null,
|
|
162
166
|
};
|
|
163
167
|
|
|
164
168
|
store.upsert(builder);
|
|
@@ -191,6 +195,7 @@ describe("discoverAgents", () => {
|
|
|
191
195
|
lastActivity: "2024-01-01T00:01:00Z",
|
|
192
196
|
escalationLevel: 0,
|
|
193
197
|
stalledSince: null,
|
|
198
|
+
transcriptPath: null,
|
|
194
199
|
};
|
|
195
200
|
|
|
196
201
|
const completed: AgentSession = {
|
|
@@ -210,6 +215,7 @@ describe("discoverAgents", () => {
|
|
|
210
215
|
lastActivity: "2024-01-01T00:02:00Z",
|
|
211
216
|
escalationLevel: 0,
|
|
212
217
|
stalledSince: null,
|
|
218
|
+
transcriptPath: null,
|
|
213
219
|
};
|
|
214
220
|
|
|
215
221
|
store.upsert(working);
|
|
@@ -230,7 +236,7 @@ describe("discoverAgents", () => {
|
|
|
230
236
|
});
|
|
231
237
|
|
|
232
238
|
afterEach(async () => {
|
|
233
|
-
await
|
|
239
|
+
await cleanupTempDir(tempDir);
|
|
234
240
|
});
|
|
235
241
|
});
|
|
236
242
|
|
|
@@ -317,6 +323,6 @@ logging:
|
|
|
317
323
|
afterEach(async () => {
|
|
318
324
|
process.stdout.write = originalStdoutWrite;
|
|
319
325
|
process.chdir(originalCwd);
|
|
320
|
-
await
|
|
326
|
+
await cleanupTempDir(tempDir);
|
|
321
327
|
});
|
|
322
328
|
});
|
|
@@ -159,6 +159,7 @@ describe("--all", () => {
|
|
|
159
159
|
lastActivity: new Date().toISOString(),
|
|
160
160
|
escalationLevel: 0,
|
|
161
161
|
stalledSince: null,
|
|
162
|
+
transcriptPath: null,
|
|
162
163
|
});
|
|
163
164
|
store.close();
|
|
164
165
|
|
|
@@ -303,6 +304,7 @@ describe("individual flags", () => {
|
|
|
303
304
|
lastActivity: new Date().toISOString(),
|
|
304
305
|
escalationLevel: 0,
|
|
305
306
|
stalledSince: null,
|
|
307
|
+
transcriptPath: null,
|
|
306
308
|
});
|
|
307
309
|
store.close();
|
|
308
310
|
|
|
@@ -424,6 +426,7 @@ describe("synthetic session-end events", () => {
|
|
|
424
426
|
lastActivity: new Date().toISOString(),
|
|
425
427
|
escalationLevel: 0,
|
|
426
428
|
stalledSince: null,
|
|
429
|
+
transcriptPath: null,
|
|
427
430
|
...overrides,
|
|
428
431
|
};
|
|
429
432
|
}
|
|
@@ -12,8 +12,8 @@ import {
|
|
|
12
12
|
} from "./completions.ts";
|
|
13
13
|
|
|
14
14
|
describe("COMMANDS array", () => {
|
|
15
|
-
it("should have exactly
|
|
16
|
-
expect(COMMANDS).toHaveLength(
|
|
15
|
+
it("should have exactly 33 commands", () => {
|
|
16
|
+
expect(COMMANDS).toHaveLength(33);
|
|
17
17
|
});
|
|
18
18
|
|
|
19
19
|
it("should include all expected command names", () => {
|
|
@@ -48,6 +48,9 @@ describe("COMMANDS array", () => {
|
|
|
48
48
|
expect(names).toContain("feed");
|
|
49
49
|
expect(names).toContain("logs");
|
|
50
50
|
expect(names).toContain("stop");
|
|
51
|
+
expect(names).toContain("ecosystem");
|
|
52
|
+
expect(names).toContain("upgrade");
|
|
53
|
+
expect(names).toContain("completions");
|
|
51
54
|
});
|
|
52
55
|
});
|
|
53
56
|
|
|
@@ -59,7 +62,7 @@ describe("generateBash", () => {
|
|
|
59
62
|
expect(script).toContain("_init_completion");
|
|
60
63
|
});
|
|
61
64
|
|
|
62
|
-
it("should include all
|
|
65
|
+
it("should include all 33 command names", () => {
|
|
63
66
|
const script = generateBash();
|
|
64
67
|
for (const cmd of COMMANDS) {
|
|
65
68
|
expect(script).toContain(cmd.name);
|
|
@@ -93,7 +96,7 @@ describe("generateZsh", () => {
|
|
|
93
96
|
expect(script).toContain("_arguments");
|
|
94
97
|
});
|
|
95
98
|
|
|
96
|
-
it("should include all
|
|
99
|
+
it("should include all 33 command names", () => {
|
|
97
100
|
const script = generateZsh();
|
|
98
101
|
for (const cmd of COMMANDS) {
|
|
99
102
|
expect(script).toContain(cmd.name);
|
|
@@ -123,7 +126,7 @@ describe("generateFish", () => {
|
|
|
123
126
|
expect(script).toContain("__fish_use_subcommand");
|
|
124
127
|
});
|
|
125
128
|
|
|
126
|
-
it("should include all
|
|
129
|
+
it("should include all 33 command names", () => {
|
|
127
130
|
const script = generateFish();
|
|
128
131
|
for (const cmd of COMMANDS) {
|
|
129
132
|
expect(script).toContain(cmd.name);
|
|
@@ -58,6 +58,9 @@ export const COMMANDS: readonly CommandDef[] = [
|
|
|
58
58
|
desc: "Initialize .overstory/ in current project",
|
|
59
59
|
flags: [
|
|
60
60
|
{ name: "--force", desc: "Overwrite existing configuration" },
|
|
61
|
+
{ name: "--yes", desc: "Accept all defaults without prompting" },
|
|
62
|
+
{ name: "-y", desc: "Alias for --yes" },
|
|
63
|
+
{ name: "--name", desc: "Project name", takesValue: true },
|
|
61
64
|
{ name: "--help", desc: "Show help" },
|
|
62
65
|
],
|
|
63
66
|
},
|
|
@@ -76,7 +79,13 @@ export const COMMANDS: readonly CommandDef[] = [
|
|
|
76
79
|
{ name: "--files", desc: "Exclusive file scope (comma-separated)", takesValue: true },
|
|
77
80
|
{ name: "--parent", desc: "Parent agent name", takesValue: true },
|
|
78
81
|
{ name: "--depth", desc: "Current hierarchy depth", takesValue: true },
|
|
82
|
+
{ name: "--skip-scout", desc: "Skip scout phase for lead agents" },
|
|
83
|
+
{ name: "--skip-review", desc: "Skip review phase for lead agents" },
|
|
84
|
+
{ name: "--skip-task-check", desc: "Skip task existence validation" },
|
|
79
85
|
{ name: "--force-hierarchy", desc: "Bypass hierarchy validation" },
|
|
86
|
+
{ name: "--max-agents", desc: "Max children per lead", takesValue: true },
|
|
87
|
+
{ name: "--dispatch-max-agents", desc: "Per-lead max agents ceiling", takesValue: true },
|
|
88
|
+
{ name: "--runtime", desc: "Runtime adapter", takesValue: true },
|
|
80
89
|
{ name: "--json", desc: "JSON output" },
|
|
81
90
|
{ name: "--help", desc: "Show help" },
|
|
82
91
|
],
|
|
@@ -107,6 +116,7 @@ export const COMMANDS: readonly CommandDef[] = [
|
|
|
107
116
|
{ name: "--json", desc: "JSON output" },
|
|
108
117
|
{ name: "--verbose", desc: "Extra per-agent detail" },
|
|
109
118
|
{ name: "--agent", desc: "Filter by agent", takesValue: true },
|
|
119
|
+
{ name: "--all", desc: "Show sessions from all runs" },
|
|
110
120
|
{ name: "--watch", desc: "Watch mode" },
|
|
111
121
|
{ name: "--interval", desc: "Poll interval in ms", takesValue: true },
|
|
112
122
|
{ name: "--help", desc: "Show help" },
|
|
@@ -138,6 +148,7 @@ export const COMMANDS: readonly CommandDef[] = [
|
|
|
138
148
|
flags: [
|
|
139
149
|
{ name: "--branch", desc: "Specific branch to merge", takesValue: true },
|
|
140
150
|
{ name: "--all", desc: "All completed branches" },
|
|
151
|
+
{ name: "--into", desc: "Target branch to merge into", takesValue: true },
|
|
141
152
|
{ name: "--dry-run", desc: "Check for conflicts only" },
|
|
142
153
|
{ name: "--json", desc: "JSON output" },
|
|
143
154
|
{ name: "--help", desc: "Show help" },
|
|
@@ -190,8 +201,10 @@ export const COMMANDS: readonly CommandDef[] = [
|
|
|
190
201
|
"merge",
|
|
191
202
|
"logs",
|
|
192
203
|
"version",
|
|
204
|
+
"ecosystem",
|
|
193
205
|
],
|
|
194
206
|
},
|
|
207
|
+
{ name: "--fix", desc: "Attempt to auto-fix issues" },
|
|
195
208
|
{ name: "--help", desc: "Show help" },
|
|
196
209
|
],
|
|
197
210
|
},
|
|
@@ -356,7 +369,7 @@ export const COMMANDS: readonly CommandDef[] = [
|
|
|
356
369
|
},
|
|
357
370
|
{
|
|
358
371
|
name: "supervisor",
|
|
359
|
-
desc: "Per-project supervisor agent",
|
|
372
|
+
desc: "[DEPRECATED] Per-project supervisor agent",
|
|
360
373
|
flags: [
|
|
361
374
|
{ name: "--json", desc: "JSON output" },
|
|
362
375
|
{ name: "--help", desc: "Show help" },
|
|
@@ -606,6 +619,29 @@ export const COMMANDS: readonly CommandDef[] = [
|
|
|
606
619
|
},
|
|
607
620
|
],
|
|
608
621
|
},
|
|
622
|
+
{
|
|
623
|
+
name: "ecosystem",
|
|
624
|
+
desc: "Show a summary dashboard of all installed os-eco tools",
|
|
625
|
+
flags: [
|
|
626
|
+
{ name: "--json", desc: "JSON output" },
|
|
627
|
+
{ name: "--help", desc: "Show help" },
|
|
628
|
+
],
|
|
629
|
+
},
|
|
630
|
+
{
|
|
631
|
+
name: "upgrade",
|
|
632
|
+
desc: "Upgrade overstory to the latest version",
|
|
633
|
+
flags: [
|
|
634
|
+
{ name: "--check", desc: "Compare current vs latest without installing" },
|
|
635
|
+
{ name: "--all", desc: "Upgrade all os-eco tools" },
|
|
636
|
+
{ name: "--json", desc: "JSON output" },
|
|
637
|
+
{ name: "--help", desc: "Show help" },
|
|
638
|
+
],
|
|
639
|
+
},
|
|
640
|
+
{
|
|
641
|
+
name: "completions",
|
|
642
|
+
desc: "Generate shell completions",
|
|
643
|
+
flags: [{ name: "--help", desc: "Show help" }],
|
|
644
|
+
},
|
|
609
645
|
] as const;
|
|
610
646
|
|
|
611
647
|
export function generateBash(): string {
|
|
@@ -618,7 +654,7 @@ export function generateBash(): string {
|
|
|
618
654
|
" local cur prev words cword",
|
|
619
655
|
" _init_completion || return",
|
|
620
656
|
"",
|
|
621
|
-
" local commands='init sling prime stop status dashboard inspect merge nudge clean doctor log logs watch trace errors feed replay costs metrics spec coordinator supervisor hooks monitor mail group worktree run'",
|
|
657
|
+
" local commands='agents init sling prime stop status dashboard inspect merge nudge clean doctor log logs watch trace errors feed replay costs metrics spec coordinator supervisor hooks monitor mail group worktree run ecosystem upgrade completions'",
|
|
622
658
|
"",
|
|
623
659
|
" # Top-level completion",
|
|
624
660
|
" if [[ $cword -eq 1 ]]; then",
|
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
import { mkdir, unlink } 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";
|
|
@@ -320,13 +319,26 @@ async function startCoordinator(
|
|
|
320
319
|
store.updateState(COORDINATOR_NAME, "completed");
|
|
321
320
|
}
|
|
322
321
|
|
|
322
|
+
// Resolve model and runtime early (needed for deployConfig and spawn)
|
|
323
|
+
const manifestLoader = createManifestLoader(
|
|
324
|
+
join(projectRoot, config.agents.manifestPath),
|
|
325
|
+
join(projectRoot, config.agents.baseDir),
|
|
326
|
+
);
|
|
327
|
+
const manifest = await manifestLoader.load();
|
|
328
|
+
const resolvedModel = resolveModel(config, manifest, "coordinator", "opus");
|
|
329
|
+
const runtime = getRuntime(undefined, config);
|
|
330
|
+
|
|
323
331
|
// Deploy hooks to the project root so the coordinator gets event logging,
|
|
324
332
|
// mail check --inject, and activity tracking via the standard hook pipeline.
|
|
325
333
|
// The ENV_GUARD prefix on all hooks (both template and generated guards)
|
|
326
334
|
// ensures they only activate when OVERSTORY_AGENT_NAME is set (i.e. for
|
|
327
335
|
// the coordinator's tmux session), so the user's own Claude Code session
|
|
328
336
|
// at the project root is unaffected.
|
|
329
|
-
await
|
|
337
|
+
await runtime.deployConfig(projectRoot, undefined, {
|
|
338
|
+
agentName: COORDINATOR_NAME,
|
|
339
|
+
capability: "coordinator",
|
|
340
|
+
worktreePath: projectRoot,
|
|
341
|
+
});
|
|
330
342
|
|
|
331
343
|
// Create coordinator identity if first run
|
|
332
344
|
const identityBaseDir = join(projectRoot, ".overstory", "agents");
|
|
@@ -343,15 +355,6 @@ async function startCoordinator(
|
|
|
343
355
|
});
|
|
344
356
|
}
|
|
345
357
|
|
|
346
|
-
// Resolve model from config > manifest > fallback
|
|
347
|
-
const manifestLoader = createManifestLoader(
|
|
348
|
-
join(projectRoot, config.agents.manifestPath),
|
|
349
|
-
join(projectRoot, config.agents.baseDir),
|
|
350
|
-
);
|
|
351
|
-
const manifest = await manifestLoader.load();
|
|
352
|
-
const resolvedModel = resolveModel(config, manifest, "coordinator", "opus");
|
|
353
|
-
const runtime = getRuntime(undefined, config);
|
|
354
|
-
|
|
355
358
|
// Preflight: verify tmux is installed before attempting to spawn.
|
|
356
359
|
// Without this check, a missing tmux leads to cryptic errors later.
|
|
357
360
|
await tmux.ensureTmuxAvailable();
|
|
@@ -402,6 +405,7 @@ async function startCoordinator(
|
|
|
402
405
|
lastActivity: new Date().toISOString(),
|
|
403
406
|
escalationLevel: 0,
|
|
404
407
|
stalledSince: null,
|
|
408
|
+
transcriptPath: null,
|
|
405
409
|
};
|
|
406
410
|
|
|
407
411
|
store.upsert(session);
|
|
@@ -9,12 +9,13 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
12
|
-
import { mkdir, mkdtemp
|
|
12
|
+
import { mkdir, 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 { createMetricsStore } from "../metrics/store.ts";
|
|
17
17
|
import { createSessionStore } from "../sessions/store.ts";
|
|
18
|
+
import { cleanupTempDir } from "../test-helpers.ts";
|
|
18
19
|
import type { SessionMetrics } from "../types.ts";
|
|
19
20
|
import { costsCommand } from "./costs.ts";
|
|
20
21
|
|
|
@@ -72,7 +73,7 @@ describe("costsCommand", () => {
|
|
|
72
73
|
afterEach(async () => {
|
|
73
74
|
process.stdout.write = originalWrite;
|
|
74
75
|
process.chdir(originalCwd);
|
|
75
|
-
await
|
|
76
|
+
await cleanupTempDir(tempDir);
|
|
76
77
|
});
|
|
77
78
|
|
|
78
79
|
function output(): string {
|
|
@@ -780,6 +781,7 @@ describe("costsCommand", () => {
|
|
|
780
781
|
lastActivity: new Date().toISOString(),
|
|
781
782
|
escalationLevel: 0,
|
|
782
783
|
stalledSince: null,
|
|
784
|
+
transcriptPath: null,
|
|
783
785
|
});
|
|
784
786
|
sessionStore.close();
|
|
785
787
|
|
|
@@ -836,6 +838,7 @@ describe("costsCommand", () => {
|
|
|
836
838
|
lastActivity: new Date().toISOString(),
|
|
837
839
|
escalationLevel: 0,
|
|
838
840
|
stalledSince: null,
|
|
841
|
+
transcriptPath: null,
|
|
839
842
|
});
|
|
840
843
|
sessionStore.close();
|
|
841
844
|
|
|
@@ -900,6 +903,7 @@ describe("costsCommand", () => {
|
|
|
900
903
|
lastActivity: new Date().toISOString(),
|
|
901
904
|
escalationLevel: 0,
|
|
902
905
|
stalledSince: null,
|
|
906
|
+
transcriptPath: null,
|
|
903
907
|
});
|
|
904
908
|
sessionStore.upsert({
|
|
905
909
|
id: "sess-002",
|
|
@@ -918,6 +922,7 @@ describe("costsCommand", () => {
|
|
|
918
922
|
lastActivity: new Date().toISOString(),
|
|
919
923
|
escalationLevel: 0,
|
|
920
924
|
stalledSince: null,
|
|
925
|
+
transcriptPath: null,
|
|
921
926
|
});
|
|
922
927
|
sessionStore.close();
|
|
923
928
|
|
|
@@ -977,6 +982,7 @@ describe("costsCommand", () => {
|
|
|
977
982
|
lastActivity: new Date().toISOString(),
|
|
978
983
|
escalationLevel: 0,
|
|
979
984
|
stalledSince: null,
|
|
985
|
+
transcriptPath: null,
|
|
980
986
|
});
|
|
981
987
|
sessionStore.close();
|
|
982
988
|
|
|
@@ -1047,7 +1053,7 @@ describe("costsCommand", () => {
|
|
|
1047
1053
|
|
|
1048
1054
|
afterEach(async () => {
|
|
1049
1055
|
process.env.HOME = originalHome;
|
|
1050
|
-
await
|
|
1056
|
+
await cleanupTempDir(tempHome);
|
|
1051
1057
|
});
|
|
1052
1058
|
|
|
1053
1059
|
test("--self shows orchestrator cost when transcript exists", async () => {
|