@os-eco/overstory-cli 0.8.6 → 0.8.7
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 +11 -8
- package/package.json +1 -1
- package/src/agents/hooks-deployer.test.ts +185 -12
- package/src/agents/hooks-deployer.ts +57 -1
- package/src/commands/coordinator.test.ts +74 -5
- package/src/commands/coordinator.ts +27 -3
- package/src/commands/dashboard.ts +84 -18
- package/src/commands/ecosystem.test.ts +101 -0
- package/src/commands/init.test.ts +74 -0
- package/src/commands/init.ts +36 -14
- package/src/commands/sling.test.ts +33 -0
- package/src/commands/sling.ts +106 -38
- package/src/commands/supervisor.ts +2 -0
- package/src/index.ts +1 -1
- package/src/merge/resolver.test.ts +141 -7
- package/src/merge/resolver.ts +61 -8
- package/src/runtimes/claude.test.ts +32 -7
- package/src/runtimes/claude.ts +19 -4
- package/src/runtimes/codex.test.ts +13 -0
- package/src/runtimes/codex.ts +18 -2
- package/src/runtimes/copilot.ts +3 -0
- package/src/runtimes/cursor.test.ts +497 -0
- package/src/runtimes/cursor.ts +205 -0
- package/src/runtimes/gemini.ts +3 -0
- package/src/runtimes/opencode.ts +3 -0
- package/src/runtimes/pi.test.ts +1 -1
- package/src/runtimes/pi.ts +3 -0
- package/src/runtimes/registry.test.ts +21 -1
- package/src/runtimes/registry.ts +3 -0
- package/src/runtimes/sapling.ts +3 -0
- package/src/runtimes/types.ts +5 -0
- package/src/schema-consistency.test.ts +1 -0
- package/src/sessions/store.test.ts +178 -0
- package/src/sessions/store.ts +44 -8
- package/src/types.ts +8 -1
- package/src/worktree/tmux.test.ts +150 -0
- package/src/worktree/tmux.ts +126 -23
|
@@ -220,14 +220,16 @@ describe("ClaudeRuntime", () => {
|
|
|
220
220
|
expect(state).toEqual({ phase: "ready" });
|
|
221
221
|
});
|
|
222
222
|
|
|
223
|
-
test("returns
|
|
223
|
+
test("returns loading for prompt indicator + shift+tab (no bypass permissions)", () => {
|
|
224
|
+
// shift+tab appears in ALL Claude Code sessions — it must NOT trigger ready
|
|
224
225
|
const state = runtime.detectReady("Claude Code\n\u276f\nshift+tab to chat");
|
|
225
|
-
expect(state).toEqual({ phase: "
|
|
226
|
+
expect(state).toEqual({ phase: "loading" });
|
|
226
227
|
});
|
|
227
228
|
|
|
228
|
-
test('returns
|
|
229
|
+
test('returns loading for Try " + shift+tab (no bypass permissions)', () => {
|
|
230
|
+
// False-positive scenario: shift+tab alone is not a reliable readiness signal
|
|
229
231
|
const state = runtime.detectReady('Try "help"\nshift+tab');
|
|
230
|
-
expect(state).toEqual({ phase: "
|
|
232
|
+
expect(state).toEqual({ phase: "loading" });
|
|
231
233
|
});
|
|
232
234
|
|
|
233
235
|
test("returns dialog for trust dialog", () => {
|
|
@@ -235,6 +237,20 @@ describe("ClaudeRuntime", () => {
|
|
|
235
237
|
expect(state).toEqual({ phase: "dialog", action: "Enter" });
|
|
236
238
|
});
|
|
237
239
|
|
|
240
|
+
test("returns dialog for bypass permissions confirmation", () => {
|
|
241
|
+
const state = runtime.detectReady(
|
|
242
|
+
"WARNING: Claude Code running in Bypass Permissions mode\n❯ 1. No, exit\n2. Yes, I accept",
|
|
243
|
+
);
|
|
244
|
+
expect(state).toEqual({ phase: "dialog", action: "type:2" });
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test("bypass permissions confirmation takes precedence over ready indicators", () => {
|
|
248
|
+
const state = runtime.detectReady(
|
|
249
|
+
"WARNING: Claude Code running in Bypass Permissions mode\n❯ 1. No, exit\n2. Yes, I accept\nbypass permissions",
|
|
250
|
+
);
|
|
251
|
+
expect(state).toEqual({ phase: "dialog", action: "type:2" });
|
|
252
|
+
});
|
|
253
|
+
|
|
238
254
|
test("trust dialog takes precedence over ready indicators", () => {
|
|
239
255
|
const state = runtime.detectReady("trust this folder\n\u276f\nbypass permissions");
|
|
240
256
|
expect(state).toEqual({ phase: "dialog", action: "Enter" });
|
|
@@ -577,9 +593,10 @@ describe("ClaudeRuntime integration: detectReady matches pre-refactor tmux behav
|
|
|
577
593
|
expect(state.phase).toBe("ready");
|
|
578
594
|
});
|
|
579
595
|
|
|
580
|
-
test("
|
|
596
|
+
test("loading: 'Try \"help\"' + 'shift+tab' (no bypass permissions — false-positive fix)", () => {
|
|
597
|
+
// shift+tab appears in all Claude Code sessions, must not trigger ready without bypass permissions
|
|
581
598
|
const state = runtime.detectReady('Try "help"\nshift+tab');
|
|
582
|
-
expect(state.phase).toBe("
|
|
599
|
+
expect(state.phase).toBe("loading");
|
|
583
600
|
});
|
|
584
601
|
|
|
585
602
|
test("not ready: only prompt (no status bar)", () => {
|
|
@@ -597,6 +614,14 @@ describe("ClaudeRuntime integration: detectReady matches pre-refactor tmux behav
|
|
|
597
614
|
expect(state.phase).toBe("dialog");
|
|
598
615
|
expect((state as { phase: "dialog"; action: string }).action).toBe("Enter");
|
|
599
616
|
});
|
|
617
|
+
|
|
618
|
+
test("dialog: bypass permissions confirmation", () => {
|
|
619
|
+
const state = runtime.detectReady(
|
|
620
|
+
"WARNING: Claude Code running in Bypass Permissions mode\n❯ 1. No, exit\n2. Yes, I accept",
|
|
621
|
+
);
|
|
622
|
+
expect(state.phase).toBe("dialog");
|
|
623
|
+
expect((state as { phase: "dialog"; action: string }).action).toBe("type:2");
|
|
624
|
+
});
|
|
600
625
|
});
|
|
601
626
|
|
|
602
627
|
describe("ClaudeRuntime integration: buildEnv matches pre-refactor env injection", () => {
|
|
@@ -652,6 +677,6 @@ describe("ClaudeRuntime integration: registry resolves 'claude' as default", ()
|
|
|
652
677
|
test("getRuntime rejects unknown runtimes", async () => {
|
|
653
678
|
const { getRuntime } = await import("./registry.ts");
|
|
654
679
|
expect(() => getRuntime("aider")).toThrow('Unknown runtime: "aider"');
|
|
655
|
-
expect(() => getRuntime("
|
|
680
|
+
expect(() => getRuntime("nonexistent")).toThrow('Unknown runtime: "nonexistent"');
|
|
656
681
|
});
|
|
657
682
|
});
|
package/src/runtimes/claude.ts
CHANGED
|
@@ -31,6 +31,9 @@ export class ClaudeRuntime implements AgentRuntime {
|
|
|
31
31
|
/** Unique identifier for this runtime. */
|
|
32
32
|
readonly id = "claude";
|
|
33
33
|
|
|
34
|
+
/** Stability level. Claude Code is the primary runtime. */
|
|
35
|
+
readonly stability = "stable" as const;
|
|
36
|
+
|
|
34
37
|
/** Relative path to the instruction file within a worktree. */
|
|
35
38
|
readonly instructionPath = ".claude/CLAUDE.md";
|
|
36
39
|
|
|
@@ -136,14 +139,25 @@ export class ClaudeRuntime implements AgentRuntime {
|
|
|
136
139
|
*
|
|
137
140
|
* Detection phases:
|
|
138
141
|
* - Trust dialog: "trust this folder" detected → `{ phase: "dialog", action: "Enter" }`
|
|
139
|
-
* - Ready: prompt indicator (❯ or 'Try "') AND status bar ("bypass permissions"
|
|
140
|
-
*
|
|
142
|
+
* - Ready: prompt indicator (❯ or 'Try "') AND status bar ("bypass permissions")
|
|
143
|
+
* both present → `{ phase: "ready" }`
|
|
141
144
|
* - Otherwise → `{ phase: "loading" }`
|
|
142
145
|
*
|
|
143
146
|
* @param paneContent - Captured tmux pane content to analyze
|
|
144
147
|
* @returns Current readiness phase
|
|
145
148
|
*/
|
|
146
149
|
detectReady(paneContent: string): ReadyState {
|
|
150
|
+
// Claude Code v2.1.71+ shows a dedicated bypass confirmation screen.
|
|
151
|
+
// It already contains both a prompt marker and the phrase "bypass permissions",
|
|
152
|
+
// so it must be detected before the normal ready heuristics.
|
|
153
|
+
if (
|
|
154
|
+
paneContent.includes("WARNING: Claude Code running in Bypass Permissions mode") &&
|
|
155
|
+
paneContent.includes("1. No, exit") &&
|
|
156
|
+
paneContent.includes("2. Yes, I accept")
|
|
157
|
+
) {
|
|
158
|
+
return { phase: "dialog", action: "type:2" };
|
|
159
|
+
}
|
|
160
|
+
|
|
147
161
|
// Trust dialog takes precedence — it replaces the normal TUI temporarily.
|
|
148
162
|
// The caller should send the action key to dismiss it.
|
|
149
163
|
if (paneContent.includes("trust this folder")) {
|
|
@@ -155,8 +169,9 @@ export class ClaudeRuntime implements AgentRuntime {
|
|
|
155
169
|
const hasPrompt = paneContent.includes("\u276f") || paneContent.includes('Try "');
|
|
156
170
|
|
|
157
171
|
// Phase 2: status bar text confirms full TUI render.
|
|
158
|
-
|
|
159
|
-
|
|
172
|
+
// Only match 'bypass permissions' — 'shift+tab' appears in ALL Claude Code sessions
|
|
173
|
+
// regardless of permission mode and would cause false-positive ready detection.
|
|
174
|
+
const hasStatusBar = paneContent.includes("bypass permissions");
|
|
160
175
|
|
|
161
176
|
if (hasPrompt && hasStatusBar) {
|
|
162
177
|
return { phase: "ready" };
|
|
@@ -151,6 +151,19 @@ describe("CodexRuntime", () => {
|
|
|
151
151
|
expect(cmd).not.toContain("This inline content should be ignored");
|
|
152
152
|
});
|
|
153
153
|
|
|
154
|
+
test("sharedWritableDirs are exposed with --add-dir", () => {
|
|
155
|
+
const opts: SpawnOpts = {
|
|
156
|
+
model: "gpt-5-codex",
|
|
157
|
+
permissionMode: "bypass",
|
|
158
|
+
cwd: "/tmp/worktree",
|
|
159
|
+
sharedWritableDirs: ["/project/.overstory", "/project/.git"],
|
|
160
|
+
env: {},
|
|
161
|
+
};
|
|
162
|
+
const cmd = runtime.buildSpawnCommand(opts);
|
|
163
|
+
expect(cmd).toContain("--add-dir '/project/.overstory'");
|
|
164
|
+
expect(cmd).toContain("--add-dir '/project/.git'");
|
|
165
|
+
});
|
|
166
|
+
|
|
154
167
|
test("without appendSystemPrompt uses default AGENTS.md prompt", () => {
|
|
155
168
|
const opts: SpawnOpts = {
|
|
156
169
|
model: "gpt-5-codex",
|
package/src/runtimes/codex.ts
CHANGED
|
@@ -37,6 +37,9 @@ export class CodexRuntime implements AgentRuntime {
|
|
|
37
37
|
/** Unique identifier for this runtime. */
|
|
38
38
|
readonly id = "codex";
|
|
39
39
|
|
|
40
|
+
/** Stability level. Codex adapter is experimental — not fully validated. */
|
|
41
|
+
readonly stability = "experimental" as const;
|
|
42
|
+
|
|
40
43
|
/** Relative path to the instruction file within a worktree. */
|
|
41
44
|
readonly instructionPath = "AGENTS.md";
|
|
42
45
|
|
|
@@ -46,6 +49,16 @@ export class CodexRuntime implements AgentRuntime {
|
|
|
46
49
|
*/
|
|
47
50
|
private static readonly MANIFEST_ALIASES = new Set(["sonnet", "opus", "haiku"]);
|
|
48
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Escape a directory path for use in a single-quoted shell argument.
|
|
54
|
+
*
|
|
55
|
+
* @param path - Absolute directory path
|
|
56
|
+
* @returns POSIX shell-safe path string
|
|
57
|
+
*/
|
|
58
|
+
private static shellEscape(path: string): string {
|
|
59
|
+
return path.replace(/'/g, "'\\''");
|
|
60
|
+
}
|
|
61
|
+
|
|
49
62
|
/**
|
|
50
63
|
* Build the shell command string to spawn a Codex agent in a tmux pane.
|
|
51
64
|
*
|
|
@@ -68,16 +81,19 @@ export class CodexRuntime implements AgentRuntime {
|
|
|
68
81
|
if (!CodexRuntime.MANIFEST_ALIASES.has(opts.model)) {
|
|
69
82
|
cmd += ` --model ${opts.model}`;
|
|
70
83
|
}
|
|
84
|
+
for (const dir of opts.sharedWritableDirs ?? []) {
|
|
85
|
+
cmd += ` --add-dir '${CodexRuntime.shellEscape(dir)}'`;
|
|
86
|
+
}
|
|
71
87
|
|
|
72
88
|
if (opts.appendSystemPromptFile) {
|
|
73
89
|
// Read role definition from file at shell expansion time — avoids tmux
|
|
74
90
|
// IPC message size limits. Append the "read AGENTS.md" instruction.
|
|
75
|
-
const escaped = opts.appendSystemPromptFile
|
|
91
|
+
const escaped = CodexRuntime.shellEscape(opts.appendSystemPromptFile);
|
|
76
92
|
cmd += ` "$(cat '${escaped}')"' Read AGENTS.md for your task assignment and begin immediately.'`;
|
|
77
93
|
} else if (opts.appendSystemPrompt) {
|
|
78
94
|
// Inline role definition + instruction to read AGENTS.md.
|
|
79
95
|
const prompt = `${opts.appendSystemPrompt}\n\nRead AGENTS.md for your task assignment and begin immediately.`;
|
|
80
|
-
const escaped =
|
|
96
|
+
const escaped = CodexRuntime.shellEscape(prompt);
|
|
81
97
|
cmd += ` '${escaped}'`;
|
|
82
98
|
} else {
|
|
83
99
|
cmd += ` 'Read AGENTS.md for your task assignment and begin immediately.'`;
|
package/src/runtimes/copilot.ts
CHANGED
|
@@ -28,6 +28,9 @@ export class CopilotRuntime implements AgentRuntime {
|
|
|
28
28
|
/** Unique identifier for this runtime. */
|
|
29
29
|
readonly id = "copilot";
|
|
30
30
|
|
|
31
|
+
/** Stability level. Copilot adapter is experimental — not fully validated. */
|
|
32
|
+
readonly stability = "experimental" as const;
|
|
33
|
+
|
|
31
34
|
/** Relative path to the instruction file within a worktree. */
|
|
32
35
|
readonly instructionPath = ".github/copilot-instructions.md";
|
|
33
36
|
|