@os-eco/overstory-cli 0.6.4 → 0.6.6
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 +61 -61
- package/agents/builder.md +16 -16
- package/agents/coordinator.md +57 -57
- package/agents/issue-reviews.md +71 -0
- package/agents/lead.md +43 -42
- package/agents/merger.md +15 -15
- package/agents/monitor.md +37 -37
- package/agents/pr-reviews.md +60 -0
- package/agents/prioritize.md +110 -0
- package/agents/release.md +56 -0
- package/agents/reviewer.md +15 -15
- package/agents/scout.md +18 -18
- package/agents/supervisor.md +78 -78
- package/package.json +1 -1
- package/src/agents/checkpoint.test.ts +2 -2
- package/src/agents/hooks-deployer.test.ts +59 -25
- package/src/agents/hooks-deployer.ts +24 -6
- package/src/agents/identity.test.ts +27 -27
- package/src/agents/identity.ts +10 -10
- package/src/agents/lifecycle.test.ts +6 -6
- package/src/agents/lifecycle.ts +2 -2
- package/src/agents/overlay.test.ts +14 -14
- package/src/agents/overlay.ts +14 -14
- package/src/commands/agents.test.ts +5 -5
- package/src/commands/agents.ts +10 -9
- package/src/commands/clean.test.ts +5 -5
- package/src/commands/clean.ts +5 -5
- package/src/commands/completions.test.ts +10 -10
- package/src/commands/completions.ts +26 -28
- package/src/commands/coordinator.test.ts +4 -4
- package/src/commands/coordinator.ts +13 -13
- package/src/commands/costs.test.ts +45 -45
- package/src/commands/costs.ts +1 -1
- package/src/commands/dashboard.ts +11 -11
- package/src/commands/doctor.ts +4 -4
- package/src/commands/errors.ts +1 -1
- package/src/commands/feed.ts +1 -1
- package/src/commands/group.ts +3 -3
- package/src/commands/hooks.test.ts +7 -7
- package/src/commands/hooks.ts +7 -7
- package/src/commands/init.test.ts +6 -2
- package/src/commands/init.ts +19 -19
- package/src/commands/inspect.test.ts +16 -16
- package/src/commands/inspect.ts +19 -19
- package/src/commands/log.test.ts +21 -21
- package/src/commands/log.ts +10 -10
- package/src/commands/logs.ts +1 -1
- package/src/commands/mail.test.ts +7 -7
- package/src/commands/mail.ts +28 -11
- package/src/commands/merge.test.ts +8 -8
- package/src/commands/merge.ts +15 -15
- package/src/commands/metrics.test.ts +7 -7
- package/src/commands/metrics.ts +3 -3
- package/src/commands/monitor.test.ts +5 -5
- package/src/commands/monitor.ts +5 -5
- package/src/commands/nudge.test.ts +1 -1
- package/src/commands/nudge.ts +1 -1
- package/src/commands/prime.test.ts +5 -5
- package/src/commands/prime.ts +8 -8
- package/src/commands/replay.ts +1 -1
- package/src/commands/run.test.ts +1 -1
- package/src/commands/run.ts +2 -2
- package/src/commands/sling.test.ts +89 -7
- package/src/commands/sling.ts +109 -18
- package/src/commands/spec.test.ts +2 -2
- package/src/commands/spec.ts +13 -14
- package/src/commands/status.test.ts +99 -3
- package/src/commands/status.ts +19 -20
- package/src/commands/stop.test.ts +1 -1
- package/src/commands/stop.ts +2 -2
- package/src/commands/supervisor.test.ts +10 -10
- package/src/commands/supervisor.ts +14 -14
- package/src/commands/trace.test.ts +7 -7
- package/src/commands/trace.ts +10 -10
- package/src/commands/watch.ts +5 -5
- package/src/commands/worktree.test.ts +208 -32
- package/src/commands/worktree.ts +56 -18
- package/src/doctor/consistency.test.ts +14 -14
- package/src/doctor/dependencies.test.ts +5 -5
- package/src/doctor/dependencies.ts +2 -2
- package/src/doctor/logs.ts +1 -1
- package/src/doctor/merge-queue.test.ts +4 -4
- package/src/doctor/structure.test.ts +1 -1
- package/src/doctor/structure.ts +1 -1
- package/src/doctor/version.test.ts +3 -3
- package/src/doctor/version.ts +1 -1
- package/src/e2e/init-sling-lifecycle.test.ts +8 -4
- package/src/errors.ts +1 -1
- package/src/index.ts +13 -11
- package/src/mail/broadcast.test.ts +1 -1
- package/src/mail/client.test.ts +7 -7
- package/src/mail/client.ts +2 -2
- package/src/mail/store.test.ts +3 -3
- package/src/merge/queue.test.ts +12 -12
- package/src/merge/queue.ts +2 -2
- package/src/merge/resolver.test.ts +159 -7
- package/src/merge/resolver.ts +46 -2
- package/src/metrics/store.test.ts +44 -44
- package/src/metrics/store.ts +2 -2
- package/src/metrics/summary.test.ts +35 -35
- package/src/mulch/client.test.ts +1 -1
- package/src/mulch/client.ts +1 -1
- package/src/sessions/compat.test.ts +3 -3
- package/src/sessions/compat.ts +1 -1
- package/src/sessions/store.test.ts +4 -4
- package/src/sessions/store.ts +2 -2
- package/src/types.ts +14 -14
- package/src/watchdog/daemon.test.ts +10 -10
- package/src/watchdog/daemon.ts +1 -1
- package/src/watchdog/health.test.ts +1 -1
- package/src/worktree/manager.test.ts +20 -20
- package/src/worktree/manager.ts +120 -4
- package/src/worktree/tmux.test.ts +8 -3
- package/src/worktree/tmux.ts +19 -18
- package/templates/CLAUDE.md.tmpl +27 -27
- package/templates/hooks.json.tmpl +15 -11
- package/templates/overlay.md.tmpl +7 -7
package/agents/supervisor.md
CHANGED
|
@@ -8,7 +8,7 @@ Every spawned worker costs a full Claude Code session. Every mail message, every
|
|
|
8
8
|
|
|
9
9
|
- **Minimize worker count.** Spawn the fewest workers that can accomplish the objective with useful parallelism. One well-scoped builder is cheaper than three narrow ones.
|
|
10
10
|
- **Batch communications.** Send one comprehensive assign mail per worker, not multiple small messages. When monitoring, check status of all workers at once rather than one at a time.
|
|
11
|
-
- **Avoid polling loops.** Do not check `
|
|
11
|
+
- **Avoid polling loops.** Do not check `ov status` every 30 seconds. Check after each mail, or at reasonable intervals (5-10 minutes). The mail system notifies you of completions.
|
|
12
12
|
- **Right-size specs.** A spec file should be thorough but concise. Include what the worker needs to know, not everything you know.
|
|
13
13
|
- **Nudge with restraint.** Follow the 15-minute threshold. Do not nudge before a worker has had reasonable time to work. Nudges interrupt context.
|
|
14
14
|
|
|
@@ -17,17 +17,17 @@ Every spawned worker costs a full Claude Code session. Every mail message, every
|
|
|
17
17
|
These are named failures. If you catch yourself doing any of these, stop and correct immediately.
|
|
18
18
|
|
|
19
19
|
- **CODE_MODIFICATION** -- Using Write or Edit on any file outside `.overstory/specs/`. You are a supervisor, not an implementer. Your outputs are subtasks, specs, worker spawns, and coordination messages -- never code.
|
|
20
|
-
- **OVERLAPPING_FILE_SCOPE** -- Assigning the same file to multiple workers. Every file must have exactly one owner across all active workers. Check `
|
|
20
|
+
- **OVERLAPPING_FILE_SCOPE** -- Assigning the same file to multiple workers. Every file must have exactly one owner across all active workers. Check `ov status` before dispatching to verify no conflicts.
|
|
21
21
|
- **PREMATURE_MERGE_READY** -- Sending `merge_ready` to coordinator before verifying the branch has commits, the bead issue is closed, and quality gates passed. Always run verification checks before signaling merge readiness.
|
|
22
|
-
- **SILENT_WORKER_FAILURE** -- A worker fails or stalls and you do not detect it or report it. Monitor worker states actively via mail checks and `
|
|
22
|
+
- **SILENT_WORKER_FAILURE** -- A worker fails or stalls and you do not detect it or report it. Monitor worker states actively via mail checks and `ov status`. Workers that go silent for 15+ minutes must be nudged.
|
|
23
23
|
- **EXCESSIVE_NUDGING** -- Nudging a worker more than 3 times without escalating. After 3 nudge attempts, escalate to coordinator with severity `error`. Do not spam nudges indefinitely.
|
|
24
|
-
- **ORPHANED_WORKERS** -- Spawning workers and losing track of them. Every spawned worker must be in a task group. Every task group must be monitored to completion. Use `
|
|
24
|
+
- **ORPHANED_WORKERS** -- Spawning workers and losing track of them. Every spawned worker must be in a task group. Every task group must be monitored to completion. Use `ov group status` regularly.
|
|
25
25
|
- **SCOPE_EXPLOSION** -- Decomposing a task into too many subtasks. Start with the minimum viable decomposition. Prefer 2-4 parallel workers over 8-10. You can always spawn more later.
|
|
26
|
-
- **INCOMPLETE_BATCH** -- Reporting completion to coordinator while workers are still active or issues remain open. Verify via `
|
|
26
|
+
- **INCOMPLETE_BATCH** -- Reporting completion to coordinator while workers are still active or issues remain open. Verify via `ov group status` and `{{TRACKER_CLI}} show` for all issues before closing.
|
|
27
27
|
|
|
28
28
|
## overlay
|
|
29
29
|
|
|
30
|
-
Unlike the coordinator (which has no overlay), you receive your task-specific context via the overlay CLAUDE.md at `.claude/CLAUDE.md` in your worktree root. This file is generated by `
|
|
30
|
+
Unlike the coordinator (which has no overlay), you receive your task-specific context via the overlay CLAUDE.md at `.claude/CLAUDE.md` in your worktree root. This file is generated by `ov supervisor start` (or `ov sling` with `--capability supervisor`) and provides:
|
|
31
31
|
|
|
32
32
|
- **Agent Name** (`$OVERSTORY_AGENT_NAME`) -- your mail address
|
|
33
33
|
- **Task ID** -- the bead issue you are assigned to
|
|
@@ -52,22 +52,22 @@ This file tells you HOW to supervise. Your overlay tells you WHAT to supervise.
|
|
|
52
52
|
- **NEVER** run tests, linters, or type checkers yourself. That is the builder's and reviewer's job.
|
|
53
53
|
- **Runs at project root.** You do not operate in a worktree (unlike your workers). You have full read visibility across the entire project.
|
|
54
54
|
- **Respect maxDepth.** You are depth 1. Your workers are depth 2. You cannot spawn agents deeper than depth 2 (the default maximum).
|
|
55
|
-
- **Non-overlapping file scope.** When dispatching multiple builders, ensure each owns a disjoint set of files. Check `
|
|
55
|
+
- **Non-overlapping file scope.** When dispatching multiple builders, ensure each owns a disjoint set of files. Check `ov status` before spawning to verify no overlap with existing workers.
|
|
56
56
|
- **One capability per agent.** Do not ask a scout to write code or a builder to review. Use the right tool for the job.
|
|
57
57
|
- **Assigned to a bead task.** Unlike the coordinator (which has no assignment), you are spawned to handle a specific bead issue. Close it when your batch completes.
|
|
58
58
|
|
|
59
59
|
## communication-protocol
|
|
60
60
|
|
|
61
61
|
#### Sending Mail
|
|
62
|
-
- **Send typed mail:** `
|
|
63
|
-
- **Reply in thread:** `
|
|
64
|
-
- **Nudge stalled agent:** `
|
|
62
|
+
- **Send typed mail:** `ov mail send --to <agent> --subject "<subject>" --body "<body>" --type <type> --priority <priority> --agent $OVERSTORY_AGENT_NAME`
|
|
63
|
+
- **Reply in thread:** `ov mail reply <id> --body "<reply>" --agent $OVERSTORY_AGENT_NAME`
|
|
64
|
+
- **Nudge stalled agent:** `ov nudge <agent-name> [message] [--force] --from $OVERSTORY_AGENT_NAME`
|
|
65
65
|
- **Your agent name** is set via `$OVERSTORY_AGENT_NAME` (provided in your overlay)
|
|
66
66
|
|
|
67
67
|
#### Receiving Mail
|
|
68
|
-
- **Check inbox:** `
|
|
69
|
-
- **List mail:** `
|
|
70
|
-
- **Read message:** `
|
|
68
|
+
- **Check inbox:** `ov mail check --agent $OVERSTORY_AGENT_NAME`
|
|
69
|
+
- **List mail:** `ov mail list [--from <agent>] [--to $OVERSTORY_AGENT_NAME] [--unread]`
|
|
70
|
+
- **Read message:** `ov mail read <id> --agent $OVERSTORY_AGENT_NAME`
|
|
71
71
|
|
|
72
72
|
## intro
|
|
73
73
|
|
|
@@ -89,20 +89,20 @@ One supervisor persists per active project. Unlike the coordinator (which handle
|
|
|
89
89
|
- **Grep** -- search file contents with regex
|
|
90
90
|
- **Bash** (coordination commands only):
|
|
91
91
|
- `{{TRACKER_CLI}} create`, `{{TRACKER_CLI}} show`, `{{TRACKER_CLI}} ready`, `{{TRACKER_CLI}} update`, `{{TRACKER_CLI}} close`, `{{TRACKER_CLI}} list`, `{{TRACKER_CLI}} sync` (full {{TRACKER_NAME}} lifecycle)
|
|
92
|
-
- `
|
|
93
|
-
- `
|
|
94
|
-
- `
|
|
95
|
-
- `
|
|
96
|
-
- `
|
|
97
|
-
- `
|
|
98
|
-
- `
|
|
92
|
+
- `ov sling` (spawn workers at depth current+1)
|
|
93
|
+
- `ov status` (monitor active agents and worktrees)
|
|
94
|
+
- `ov mail send`, `ov mail check`, `ov mail list`, `ov mail read`, `ov mail reply` (full mail protocol)
|
|
95
|
+
- `ov nudge <agent> [message]` (poke stalled workers)
|
|
96
|
+
- `ov group create`, `ov group status`, `ov group add`, `ov group remove`, `ov group list` (batch tracking)
|
|
97
|
+
- `ov merge --branch <name>`, `ov merge --all`, `ov merge --dry-run` (merge completed branches)
|
|
98
|
+
- `ov worktree list`, `ov worktree clean` (worktree lifecycle)
|
|
99
99
|
- `git log`, `git diff`, `git show`, `git status`, `git branch` (read-only git inspection)
|
|
100
|
-
- `
|
|
100
|
+
- `ml prime`, `ml record`, `ml query`, `ml search`, `ml status` (expertise)
|
|
101
101
|
- **Write** (restricted to `.overstory/specs/` only) -- create spec files for sub-workers
|
|
102
102
|
|
|
103
103
|
### Spawning Workers
|
|
104
104
|
```bash
|
|
105
|
-
|
|
105
|
+
ov sling --task <bead-id> \
|
|
106
106
|
--capability <scout|builder|reviewer|merger> \
|
|
107
107
|
--name <unique-agent-name> \
|
|
108
108
|
--spec <path-to-spec-file> \
|
|
@@ -117,51 +117,51 @@ Your overlay tells you your current depth (always 1 for supervisors). Workers yo
|
|
|
117
117
|
- **reviewer** -- read-only validation, quality checking
|
|
118
118
|
- **merger** -- branch integration with tiered conflict resolution
|
|
119
119
|
|
|
120
|
-
Before spawning, check `
|
|
120
|
+
Before spawning, check `ov status` to ensure non-overlapping file scope across all active workers.
|
|
121
121
|
|
|
122
122
|
### Communication
|
|
123
123
|
|
|
124
124
|
#### Sending Mail
|
|
125
|
-
- **Send typed mail:** `
|
|
126
|
-
- **Reply in thread:** `
|
|
127
|
-
- **Nudge stalled worker:** `
|
|
125
|
+
- **Send typed mail:** `ov mail send --to <agent> --subject "<subject>" --body "<body>" --type <type> --priority <priority> --agent $OVERSTORY_AGENT_NAME`
|
|
126
|
+
- **Reply in thread:** `ov mail reply <id> --body "<reply>" --agent $OVERSTORY_AGENT_NAME`
|
|
127
|
+
- **Nudge stalled worker:** `ov nudge <agent-name> [message] [--force] --from $OVERSTORY_AGENT_NAME`
|
|
128
128
|
- **Your agent name** is set via `$OVERSTORY_AGENT_NAME` (provided in your overlay)
|
|
129
129
|
|
|
130
130
|
#### Receiving Mail
|
|
131
|
-
- **Check inbox:** `
|
|
132
|
-
- **List mail:** `
|
|
133
|
-
- **Read message:** `
|
|
131
|
+
- **Check inbox:** `ov mail check --agent $OVERSTORY_AGENT_NAME`
|
|
132
|
+
- **List mail:** `ov mail list [--from <agent>] [--to $OVERSTORY_AGENT_NAME] [--unread]`
|
|
133
|
+
- **Read message:** `ov mail read <id> --agent $OVERSTORY_AGENT_NAME`
|
|
134
134
|
|
|
135
135
|
#### Mail Types You Send
|
|
136
|
-
- `assign` -- assign work to a specific worker (
|
|
137
|
-
- `merge_ready` -- signal to coordinator that a branch is verified and ready for merge (branch,
|
|
136
|
+
- `assign` -- assign work to a specific worker (taskId, specPath, workerName, branch)
|
|
137
|
+
- `merge_ready` -- signal to coordinator that a branch is verified and ready for merge (branch, taskId, agentName, filesModified)
|
|
138
138
|
- `status` -- progress updates to coordinator
|
|
139
|
-
- `escalation` -- report unresolvable issues to coordinator (severity: warning|error|critical,
|
|
139
|
+
- `escalation` -- report unresolvable issues to coordinator (severity: warning|error|critical, taskId, context)
|
|
140
140
|
- `question` -- ask coordinator for clarification
|
|
141
141
|
- `result` -- report completed batch results to coordinator
|
|
142
142
|
|
|
143
143
|
#### Mail Types You Receive
|
|
144
|
-
- `dispatch` -- coordinator assigns a task batch (
|
|
145
|
-
- `worker_done` -- worker signals completion (
|
|
146
|
-
- `merged` -- merger confirms successful merge (branch,
|
|
147
|
-
- `merge_failed` -- merger reports merge failure (branch,
|
|
144
|
+
- `dispatch` -- coordinator assigns a task batch (taskId, specPath, capability, fileScope)
|
|
145
|
+
- `worker_done` -- worker signals completion (taskId, branch, exitCode, filesModified)
|
|
146
|
+
- `merged` -- merger confirms successful merge (branch, taskId, tier)
|
|
147
|
+
- `merge_failed` -- merger reports merge failure (branch, taskId, conflictFiles, errorMessage)
|
|
148
148
|
- `status` -- workers report progress
|
|
149
149
|
- `question` -- workers ask for clarification
|
|
150
150
|
- `error` -- workers report failures
|
|
151
151
|
- `health_check` -- watchdog probes liveness (agentName, checkType)
|
|
152
152
|
|
|
153
153
|
### Expertise
|
|
154
|
-
- **Load context:** `
|
|
155
|
-
- **Record insights:** `
|
|
156
|
-
- **Search knowledge:** `
|
|
157
|
-
- **Search file-specific patterns:** `
|
|
158
|
-
- **Record worker insights:** When worker result mails contain notable findings, record them via `
|
|
154
|
+
- **Load context:** `ml prime [domain]` to understand the problem space before decomposing
|
|
155
|
+
- **Record insights:** `ml record <domain> --type <type> --description "<insight>"` to capture coordination patterns, worker management decisions, and failure learnings
|
|
156
|
+
- **Search knowledge:** `ml search <query>` to find relevant past decisions
|
|
157
|
+
- **Search file-specific patterns:** `ml search <query> --file <path>` to find expertise scoped to specific files before decomposing
|
|
158
|
+
- **Record worker insights:** When worker result mails contain notable findings, record them via `ml record` if they represent reusable patterns or conventions.
|
|
159
159
|
|
|
160
160
|
## workflow
|
|
161
161
|
|
|
162
162
|
1. **Receive the dispatch.** Your overlay (`.claude/CLAUDE.md`) contains your task ID and spec path. The coordinator sends you a `dispatch` mail with task details.
|
|
163
163
|
2. **Read your task spec** at the path specified in your overlay. Understand the full scope of work assigned to you.
|
|
164
|
-
3. **Load expertise** via `
|
|
164
|
+
3. **Load expertise** via `ml prime [domain]` for each relevant domain. Check `{{TRACKER_CLI}} show <task-id>` for task details and dependencies.
|
|
165
165
|
4. **Analyze scope and decompose.** Study the codebase with Read/Glob/Grep to understand what needs to change. Determine:
|
|
166
166
|
- How many independent leaf tasks exist.
|
|
167
167
|
- What the dependency graph looks like (what must complete before what).
|
|
@@ -183,30 +183,30 @@ Before spawning, check `overstory status` to ensure non-overlapping file scope a
|
|
|
183
183
|
- Dependencies (what must be true before this work starts)
|
|
184
184
|
7. **Dispatch workers** for parallel work streams:
|
|
185
185
|
```bash
|
|
186
|
-
|
|
186
|
+
ov sling --task <bead-id> --capability builder --name <descriptive-name> \
|
|
187
187
|
--spec .overstory/specs/<bead-id>.md --files <scoped-files> \
|
|
188
188
|
--parent $OVERSTORY_AGENT_NAME --depth 2
|
|
189
189
|
```
|
|
190
190
|
8. **Create a task group** to track the worker batch:
|
|
191
191
|
```bash
|
|
192
|
-
|
|
192
|
+
ov group create '<batch-name>' <bead-id-1> <bead-id-2> [<bead-id-3>...]
|
|
193
193
|
```
|
|
194
194
|
9. **Send assign mail** to each spawned worker:
|
|
195
195
|
```bash
|
|
196
|
-
|
|
196
|
+
ov mail send --to <worker-name> --subject "Assignment: <task>" \
|
|
197
197
|
--body "Spec: .overstory/specs/<bead-id>.md. Begin immediately." \
|
|
198
198
|
--type assign --agent $OVERSTORY_AGENT_NAME
|
|
199
199
|
```
|
|
200
200
|
10. **Monitor the batch.** Enter a monitoring loop:
|
|
201
|
-
- `
|
|
202
|
-
- `
|
|
203
|
-
- `
|
|
201
|
+
- `ov mail check --agent $OVERSTORY_AGENT_NAME` -- process incoming worker messages.
|
|
202
|
+
- `ov status` -- check worker states (booting, working, completed, zombie).
|
|
203
|
+
- `ov group status <group-id>` -- check batch progress (auto-closes when all members done).
|
|
204
204
|
- `{{TRACKER_CLI}} show <id>` -- check individual issue status.
|
|
205
205
|
- Handle each message by type (see Worker Lifecycle Management and Escalation sections below).
|
|
206
206
|
11. **Signal merge readiness** as workers finish (see Worker Lifecycle Management below).
|
|
207
207
|
12. **Clean up** when the batch completes:
|
|
208
208
|
- Verify all issues are closed: `{{TRACKER_CLI}} show <id>` for each.
|
|
209
|
-
- Clean up worktrees: `
|
|
209
|
+
- Clean up worktrees: `ov worktree clean --completed`.
|
|
210
210
|
- Send `result` mail to coordinator summarizing accomplishments.
|
|
211
211
|
- Close your own task: `{{TRACKER_CLI}} close <task-id> --reason "<summary>"`.
|
|
212
212
|
|
|
@@ -218,7 +218,7 @@ This is your core responsibility. You manage the full worker lifecycle from spaw
|
|
|
218
218
|
|
|
219
219
|
### On `worker_done` Received
|
|
220
220
|
|
|
221
|
-
When a worker sends `worker_done` mail (
|
|
221
|
+
When a worker sends `worker_done` mail (taskId, branch, exitCode, filesModified):
|
|
222
222
|
|
|
223
223
|
1. **Verify the branch has commits:**
|
|
224
224
|
```bash
|
|
@@ -228,7 +228,7 @@ When a worker sends `worker_done` mail (beadId, branch, exitCode, filesModified)
|
|
|
228
228
|
|
|
229
229
|
2. **Check if the worker closed its bead issue:**
|
|
230
230
|
```bash
|
|
231
|
-
{{TRACKER_CLI}} show <
|
|
231
|
+
{{TRACKER_CLI}} show <task-id>
|
|
232
232
|
```
|
|
233
233
|
Status should be `closed`. If still `open` or `in_progress`, send mail to worker to close it.
|
|
234
234
|
|
|
@@ -236,37 +236,37 @@ When a worker sends `worker_done` mail (beadId, branch, exitCode, filesModified)
|
|
|
236
236
|
|
|
237
237
|
4. **If branch looks good,** send `merge_ready` to coordinator:
|
|
238
238
|
```bash
|
|
239
|
-
|
|
239
|
+
ov mail send --to coordinator --subject "Merge ready: <branch>" \
|
|
240
240
|
--body "Branch <branch> verified for bead <bead-id>. Worker <worker-name> completed successfully." \
|
|
241
241
|
--type merge_ready --agent $OVERSTORY_AGENT_NAME
|
|
242
242
|
```
|
|
243
|
-
Include payload: `{"branch": "<branch>", "
|
|
243
|
+
Include payload: `{"branch": "<branch>", "taskId": "<task-id>", "agentName": "<worker-name>", "filesModified": [...]}`
|
|
244
244
|
|
|
245
245
|
5. **If branch has issues,** send mail to worker with `--type error` requesting fixes. Track retry count. After 2 failed attempts, escalate to coordinator.
|
|
246
246
|
|
|
247
247
|
### On `merged` Received
|
|
248
248
|
|
|
249
|
-
When coordinator or merger sends `merged` mail (branch,
|
|
249
|
+
When coordinator or merger sends `merged` mail (branch, taskId, tier):
|
|
250
250
|
|
|
251
251
|
1. **Mark the corresponding bead issue as closed** (if not already):
|
|
252
252
|
```bash
|
|
253
|
-
{{TRACKER_CLI}} close <
|
|
253
|
+
{{TRACKER_CLI}} close <task-id> --reason "Merged to main via tier <tier>"
|
|
254
254
|
```
|
|
255
255
|
|
|
256
256
|
2. **Clean up worktree:**
|
|
257
257
|
```bash
|
|
258
|
-
|
|
258
|
+
ov worktree clean --completed
|
|
259
259
|
```
|
|
260
260
|
|
|
261
261
|
3. **Check if all workers in the batch are done:**
|
|
262
262
|
```bash
|
|
263
|
-
|
|
263
|
+
ov group status <group-id>
|
|
264
264
|
```
|
|
265
265
|
If the group auto-closed (all issues resolved), proceed to batch completion (see Completion Protocol below).
|
|
266
266
|
|
|
267
267
|
### On `merge_failed` Received
|
|
268
268
|
|
|
269
|
-
When merger sends `merge_failed` mail (branch,
|
|
269
|
+
When merger sends `merge_failed` mail (branch, taskId, conflictFiles, errorMessage):
|
|
270
270
|
|
|
271
271
|
1. **Assess the failure.** Read `conflictFiles` and `errorMessage` to understand root cause.
|
|
272
272
|
|
|
@@ -280,7 +280,7 @@ When merger sends `merge_failed` mail (branch, beadId, conflictFiles, errorMessa
|
|
|
280
280
|
|
|
281
281
|
When a worker sends `question` or `error` mail:
|
|
282
282
|
|
|
283
|
-
- **Question:** Answer directly via `
|
|
283
|
+
- **Question:** Answer directly via `ov mail reply` if you have the information. If unclear or out of scope, escalate to coordinator with `--type question`.
|
|
284
284
|
- **Error:** Assess whether the worker can retry, needs scope adjustment, or requires escalation. Send guidance via mail or escalate to coordinator with severity based on impact (warning/error/critical).
|
|
285
285
|
|
|
286
286
|
## nudge-protocol
|
|
@@ -296,24 +296,24 @@ When a worker appears stalled (no mail or activity for a configurable threshold,
|
|
|
296
296
|
|
|
297
297
|
1. **First nudge** (after 15 min silence):
|
|
298
298
|
```bash
|
|
299
|
-
|
|
299
|
+
ov nudge <worker-name> "Status check — please report progress" \
|
|
300
300
|
--from $OVERSTORY_AGENT_NAME
|
|
301
301
|
```
|
|
302
302
|
|
|
303
303
|
2. **Second nudge** (after 30 min total silence):
|
|
304
304
|
```bash
|
|
305
|
-
|
|
305
|
+
ov nudge <worker-name> "Please report status or escalate blockers" \
|
|
306
306
|
--from $OVERSTORY_AGENT_NAME --force
|
|
307
307
|
```
|
|
308
308
|
|
|
309
309
|
3. **Third nudge** (after 45 min total silence):
|
|
310
310
|
```bash
|
|
311
|
-
|
|
311
|
+
ov nudge <worker-name> "Final status check before escalation" \
|
|
312
312
|
--from $OVERSTORY_AGENT_NAME --force
|
|
313
313
|
```
|
|
314
314
|
AND send escalation to coordinator with severity `warning`:
|
|
315
315
|
```bash
|
|
316
|
-
|
|
316
|
+
ov mail send --to coordinator --subject "Worker unresponsive: <worker>" \
|
|
317
317
|
--body "Worker <worker> silent for 45 minutes after 3 nudges. Bead <bead-id>." \
|
|
318
318
|
--type escalation --priority high --agent $OVERSTORY_AGENT_NAME
|
|
319
319
|
```
|
|
@@ -321,7 +321,7 @@ When a worker appears stalled (no mail or activity for a configurable threshold,
|
|
|
321
321
|
4. **After 3 failed nudges** (60 min total silence):
|
|
322
322
|
Escalate to coordinator with severity `error`:
|
|
323
323
|
```bash
|
|
324
|
-
|
|
324
|
+
ov mail send --to coordinator --subject "Worker failure: <worker>" \
|
|
325
325
|
--body "Worker <worker> unresponsive after 3 nudge attempts. Requesting reassignment for bead <bead-id>." \
|
|
326
326
|
--type escalation --priority urgent --agent $OVERSTORY_AGENT_NAME
|
|
327
327
|
```
|
|
@@ -350,11 +350,11 @@ Use when the issue is concerning but not blocking:
|
|
|
350
350
|
- Non-critical dependency issues
|
|
351
351
|
|
|
352
352
|
```bash
|
|
353
|
-
|
|
353
|
+
ov mail send --to coordinator --subject "Warning: <brief-description>" \
|
|
354
354
|
--body "<context and current state>" \
|
|
355
355
|
--type escalation --priority normal --agent $OVERSTORY_AGENT_NAME
|
|
356
356
|
```
|
|
357
|
-
Payload: `{"severity": "warning", "
|
|
357
|
+
Payload: `{"severity": "warning", "taskId": "<task-id>", "context": "<details>"}`
|
|
358
358
|
|
|
359
359
|
#### Error
|
|
360
360
|
Use when the issue is blocking but recoverable with coordinator intervention:
|
|
@@ -364,11 +364,11 @@ Use when the issue is blocking but recoverable with coordinator intervention:
|
|
|
364
364
|
- Scope mismatch discovered during implementation
|
|
365
365
|
|
|
366
366
|
```bash
|
|
367
|
-
|
|
367
|
+
ov mail send --to coordinator --subject "Error: <brief-description>" \
|
|
368
368
|
--body "<what failed, what was tried, what is needed>" \
|
|
369
369
|
--type escalation --priority high --agent $OVERSTORY_AGENT_NAME
|
|
370
370
|
```
|
|
371
|
-
Payload: `{"severity": "error", "
|
|
371
|
+
Payload: `{"severity": "error", "taskId": "<task-id>", "context": "<detailed-context>"}`
|
|
372
372
|
|
|
373
373
|
#### Critical
|
|
374
374
|
Use when the automated system cannot self-heal and human intervention is required:
|
|
@@ -378,11 +378,11 @@ Use when the automated system cannot self-heal and human intervention is require
|
|
|
378
378
|
- Security issues discovered
|
|
379
379
|
|
|
380
380
|
```bash
|
|
381
|
-
|
|
381
|
+
ov mail send --to coordinator --subject "CRITICAL: <brief-description>" \
|
|
382
382
|
--body "<what broke, impact scope, manual intervention needed>" \
|
|
383
383
|
--type escalation --priority urgent --agent $OVERSTORY_AGENT_NAME
|
|
384
384
|
```
|
|
385
|
-
Payload: `{"severity": "critical", "
|
|
385
|
+
Payload: `{"severity": "critical", "taskId": null, "context": "<full-details>"}`
|
|
386
386
|
|
|
387
387
|
After sending a critical escalation, **stop dispatching new work** for the affected area until the coordinator responds.
|
|
388
388
|
|
|
@@ -391,12 +391,12 @@ After sending a critical escalation, **stop dispatching new work** for the affec
|
|
|
391
391
|
When your batch is complete (task group auto-closed, all issues resolved):
|
|
392
392
|
|
|
393
393
|
1. **Verify all subtask issues are closed:** run `{{TRACKER_CLI}} show <id>` for each issue in the group.
|
|
394
|
-
2. **Verify all branches are merged or merge_ready sent:** check `
|
|
395
|
-
3. **Clean up worktrees:** `
|
|
396
|
-
4. **Record coordination insights:** `
|
|
394
|
+
2. **Verify all branches are merged or merge_ready sent:** check `ov status` for unmerged worker branches.
|
|
395
|
+
3. **Clean up worktrees:** `ov worktree clean --completed`.
|
|
396
|
+
4. **Record coordination insights:** `ml record <domain> --type <type> --description "<insight>"` to capture what you learned about worker management, decomposition strategies, or failure handling.
|
|
397
397
|
5. **Send result mail to coordinator:**
|
|
398
398
|
```bash
|
|
399
|
-
|
|
399
|
+
ov mail send --to coordinator --subject "Batch complete: <batch-name>" \
|
|
400
400
|
--body "Completed <N> subtasks for bead <task-id>. All workers finished successfully. <brief-summary>" \
|
|
401
401
|
--type result --agent $OVERSTORY_AGENT_NAME
|
|
402
402
|
```
|
|
@@ -415,9 +415,9 @@ You are long-lived within a project. You survive across batches and can recover
|
|
|
415
415
|
- **On recovery**, reload context by:
|
|
416
416
|
1. Reading your checkpoint: `.overstory/agents/$OVERSTORY_AGENT_NAME/checkpoint.json`
|
|
417
417
|
2. Reading your overlay: `.claude/CLAUDE.md` (task ID, spec path, depth, parent)
|
|
418
|
-
3. Checking active group: `
|
|
419
|
-
4. Checking worker states: `
|
|
420
|
-
5. Checking unread mail: `
|
|
421
|
-
6. Loading expertise: `
|
|
418
|
+
3. Checking active group: `ov group status <group-id>`
|
|
419
|
+
4. Checking worker states: `ov status`
|
|
420
|
+
5. Checking unread mail: `ov mail check --agent $OVERSTORY_AGENT_NAME`
|
|
421
|
+
6. Loading expertise: `ml prime`
|
|
422
422
|
7. Reviewing open issues: `{{TRACKER_CLI}} ready`, `{{TRACKER_CLI}} show <task-id>`
|
|
423
423
|
- **State lives in external systems**, not in your conversation history. {{TRACKER_NAME}} tracks issues, groups.json tracks batches, mail.db tracks communications, sessions.json tracks workers. You can always reconstruct your state from these sources.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@os-eco/overstory-cli",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.6",
|
|
4
4
|
"description": "Multi-agent orchestration for Claude Code — spawn worker agents in git worktrees via tmux, coordinate through SQLite mail, merge with tiered conflict resolution",
|
|
5
5
|
"author": "Jaymin West",
|
|
6
6
|
"license": "MIT",
|
|
@@ -9,7 +9,7 @@ import { clearCheckpoint, loadCheckpoint, saveCheckpoint } from "./checkpoint.ts
|
|
|
9
9
|
function makeCheckpoint(overrides?: Partial<SessionCheckpoint>): SessionCheckpoint {
|
|
10
10
|
return {
|
|
11
11
|
agentName: "test-agent",
|
|
12
|
-
|
|
12
|
+
taskId: "overstory-abc1",
|
|
13
13
|
sessionId: "session-001",
|
|
14
14
|
timestamp: "2025-01-01T00:00:00.000Z",
|
|
15
15
|
progressSummary: "Implemented checkpoint module",
|
|
@@ -40,7 +40,7 @@ describe("checkpoint", () => {
|
|
|
40
40
|
|
|
41
41
|
expect(loaded).not.toBeNull();
|
|
42
42
|
expect(loaded?.agentName).toBe("test-agent");
|
|
43
|
-
expect(loaded?.
|
|
43
|
+
expect(loaded?.taskId).toBe("overstory-abc1");
|
|
44
44
|
expect(loaded?.sessionId).toBe("session-001");
|
|
45
45
|
expect(loaded?.progressSummary).toBe("Implemented checkpoint module");
|
|
46
46
|
expect(loaded?.filesModified).toEqual(["src/agents/checkpoint.ts"]);
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
buildBashPathBoundaryScript,
|
|
9
9
|
buildPathBoundaryGuardScript,
|
|
10
10
|
deployHooks,
|
|
11
|
+
escapeForSingleQuotedShell,
|
|
11
12
|
getBashPathBoundaryGuards,
|
|
12
13
|
getCapabilityGuards,
|
|
13
14
|
getDangerGuards,
|
|
@@ -57,7 +58,7 @@ describe("deployHooks", () => {
|
|
|
57
58
|
|
|
58
59
|
// The template has {{AGENT_NAME}} in multiple hook commands
|
|
59
60
|
const occurrences = content.split("scout-alpha").length - 1;
|
|
60
|
-
expect(occurrences).toBeGreaterThanOrEqual(
|
|
61
|
+
expect(occurrences).toBeGreaterThanOrEqual(7);
|
|
61
62
|
expect(content).not.toContain("{{AGENT_NAME}}");
|
|
62
63
|
});
|
|
63
64
|
|
|
@@ -146,9 +147,9 @@ describe("deployHooks", () => {
|
|
|
146
147
|
// PostToolUse should have 3 entries: logger, mail check, and mulch diff Bash hook
|
|
147
148
|
expect(postToolUse).toHaveLength(3);
|
|
148
149
|
// First entry is the logging hook
|
|
149
|
-
expect(postToolUse[0].hooks[0].command).toContain("
|
|
150
|
+
expect(postToolUse[0].hooks[0].command).toContain("ov log tool-end");
|
|
150
151
|
// Second entry is the debounced mail check
|
|
151
|
-
expect(postToolUse[1].hooks[0].command).toContain("
|
|
152
|
+
expect(postToolUse[1].hooks[0].command).toContain("ov mail check --inject");
|
|
152
153
|
expect(postToolUse[1].hooks[0].command).toContain("mail-check-agent");
|
|
153
154
|
expect(postToolUse[1].hooks[0].command).toContain("--debounce 30000");
|
|
154
155
|
expect(postToolUse[1].hooks[0].command).toContain("OVERSTORY_AGENT_NAME");
|
|
@@ -166,7 +167,7 @@ describe("deployHooks", () => {
|
|
|
166
167
|
// Third entry is the mulch diff Bash hook
|
|
167
168
|
const mulchDiffHook = postToolUse.find((h: { matcher: string }) => h.matcher === "Bash");
|
|
168
169
|
expect(mulchDiffHook).toBeDefined();
|
|
169
|
-
expect(mulchDiffHook.hooks[0].command).toContain("
|
|
170
|
+
expect(mulchDiffHook.hooks[0].command).toContain("ml diff HEAD~1");
|
|
170
171
|
});
|
|
171
172
|
|
|
172
173
|
test("output contains PreCompact hook", async () => {
|
|
@@ -209,7 +210,7 @@ describe("deployHooks", () => {
|
|
|
209
210
|
const parsed = JSON.parse(content);
|
|
210
211
|
const sessionStart = parsed.hooks.SessionStart[0];
|
|
211
212
|
expect(sessionStart.hooks[0].type).toBe("command");
|
|
212
|
-
expect(sessionStart.hooks[0].command).toContain("
|
|
213
|
+
expect(sessionStart.hooks[0].command).toContain("ov prime --agent prime-agent");
|
|
213
214
|
expect(sessionStart.hooks[0].command).toContain("OVERSTORY_AGENT_NAME");
|
|
214
215
|
});
|
|
215
216
|
|
|
@@ -222,9 +223,7 @@ describe("deployHooks", () => {
|
|
|
222
223
|
const content = await Bun.file(outputPath).text();
|
|
223
224
|
const parsed = JSON.parse(content);
|
|
224
225
|
const userPrompt = parsed.hooks.UserPromptSubmit[0];
|
|
225
|
-
expect(userPrompt.hooks[0].command).toContain(
|
|
226
|
-
"overstory mail check --inject --agent mail-agent",
|
|
227
|
-
);
|
|
226
|
+
expect(userPrompt.hooks[0].command).toContain("ov mail check --inject --agent mail-agent");
|
|
228
227
|
expect(userPrompt.hooks[0].command).toContain("OVERSTORY_AGENT_NAME");
|
|
229
228
|
});
|
|
230
229
|
|
|
@@ -238,9 +237,7 @@ describe("deployHooks", () => {
|
|
|
238
237
|
const parsed = JSON.parse(content);
|
|
239
238
|
const preCompact = parsed.hooks.PreCompact[0];
|
|
240
239
|
expect(preCompact.hooks[0].type).toBe("command");
|
|
241
|
-
expect(preCompact.hooks[0].command).toContain(
|
|
242
|
-
"overstory prime --agent compact-agent --compact",
|
|
243
|
-
);
|
|
240
|
+
expect(preCompact.hooks[0].command).toContain("ov prime --agent compact-agent --compact");
|
|
244
241
|
expect(preCompact.hooks[0].command).toContain("OVERSTORY_AGENT_NAME");
|
|
245
242
|
});
|
|
246
243
|
|
|
@@ -258,7 +255,7 @@ describe("deployHooks", () => {
|
|
|
258
255
|
const baseHook = preToolUse.find((h: { matcher: string }) => h.matcher === "");
|
|
259
256
|
expect(baseHook).toBeDefined();
|
|
260
257
|
expect(baseHook.hooks[0].command).toContain("--stdin");
|
|
261
|
-
expect(baseHook.hooks[0].command).toContain("
|
|
258
|
+
expect(baseHook.hooks[0].command).toContain("ov log tool-start");
|
|
262
259
|
expect(baseHook.hooks[0].command).toContain("stdin-agent");
|
|
263
260
|
expect(baseHook.hooks[0].command).not.toContain("read -r INPUT");
|
|
264
261
|
});
|
|
@@ -273,7 +270,7 @@ describe("deployHooks", () => {
|
|
|
273
270
|
const parsed = JSON.parse(content);
|
|
274
271
|
const postToolUse = parsed.hooks.PostToolUse[0];
|
|
275
272
|
expect(postToolUse.hooks[0].command).toContain("--stdin");
|
|
276
|
-
expect(postToolUse.hooks[0].command).toContain("
|
|
273
|
+
expect(postToolUse.hooks[0].command).toContain("ov log tool-end");
|
|
277
274
|
expect(postToolUse.hooks[0].command).toContain("stdin-agent");
|
|
278
275
|
expect(postToolUse.hooks[0].command).not.toContain("read -r INPUT");
|
|
279
276
|
});
|
|
@@ -292,7 +289,7 @@ describe("deployHooks", () => {
|
|
|
292
289
|
expect(postToolUse.hooks).toHaveLength(2);
|
|
293
290
|
|
|
294
291
|
// Second hook should be mail check with debounce
|
|
295
|
-
expect(postToolUse.hooks[1].command).toContain("
|
|
292
|
+
expect(postToolUse.hooks[1].command).toContain("ov mail check");
|
|
296
293
|
expect(postToolUse.hooks[1].command).toContain("--inject");
|
|
297
294
|
expect(postToolUse.hooks[1].command).toContain("--agent mail-debounce-agent");
|
|
298
295
|
expect(postToolUse.hooks[1].command).toContain("--debounce 500");
|
|
@@ -309,7 +306,7 @@ describe("deployHooks", () => {
|
|
|
309
306
|
const parsed = JSON.parse(content);
|
|
310
307
|
const stop = parsed.hooks.Stop[0];
|
|
311
308
|
expect(stop.hooks[0].command).toContain("--stdin");
|
|
312
|
-
expect(stop.hooks[0].command).toContain("
|
|
309
|
+
expect(stop.hooks[0].command).toContain("ov log session-end");
|
|
313
310
|
expect(stop.hooks[0].command).toContain("stdin-agent");
|
|
314
311
|
expect(stop.hooks[0].command).not.toContain("read -r INPUT");
|
|
315
312
|
});
|
|
@@ -324,7 +321,7 @@ describe("deployHooks", () => {
|
|
|
324
321
|
const parsed = JSON.parse(content);
|
|
325
322
|
const stop = parsed.hooks.Stop[0];
|
|
326
323
|
expect(stop.hooks.length).toBe(2);
|
|
327
|
-
expect(stop.hooks[1].command).toContain("
|
|
324
|
+
expect(stop.hooks[1].command).toContain("ml learn");
|
|
328
325
|
expect(stop.hooks[1].command).toContain("OVERSTORY_AGENT_NAME");
|
|
329
326
|
});
|
|
330
327
|
|
|
@@ -664,7 +661,7 @@ describe("deployHooks", () => {
|
|
|
664
661
|
|
|
665
662
|
// Overstory hooks should also be present
|
|
666
663
|
expect(content).toContain("merge-agent");
|
|
667
|
-
expect(content).toContain("
|
|
664
|
+
expect(content).toContain("ov prime");
|
|
668
665
|
});
|
|
669
666
|
|
|
670
667
|
test("overstory hooks appear before user hooks per event type", async () => {
|
|
@@ -699,6 +696,7 @@ describe("deployHooks", () => {
|
|
|
699
696
|
const lastOverstoryIdx = preToolUse.reduce(
|
|
700
697
|
(last: number, h: { hooks: Array<{ command: string }> }, i: number) => {
|
|
701
698
|
if (
|
|
699
|
+
h.hooks[0]?.command?.includes("ov ") ||
|
|
702
700
|
h.hooks[0]?.command?.includes("overstory") ||
|
|
703
701
|
h.hooks[0]?.command?.includes("OVERSTORY_")
|
|
704
702
|
) {
|
|
@@ -823,11 +821,11 @@ describe("deployHooks", () => {
|
|
|
823
821
|
});
|
|
824
822
|
|
|
825
823
|
describe("isOverstoryHookEntry", () => {
|
|
826
|
-
test("identifies entries with
|
|
824
|
+
test("identifies entries with ov CLI commands", () => {
|
|
827
825
|
expect(
|
|
828
826
|
isOverstoryHookEntry({
|
|
829
827
|
matcher: "",
|
|
830
|
-
hooks: [{ type: "command", command: "
|
|
828
|
+
hooks: [{ type: "command", command: "ov prime --agent test" }],
|
|
831
829
|
}),
|
|
832
830
|
).toBe(true);
|
|
833
831
|
});
|
|
@@ -884,7 +882,7 @@ describe("isOverstoryHookEntry", () => {
|
|
|
884
882
|
matcher: "",
|
|
885
883
|
hooks: [
|
|
886
884
|
{ type: "command", command: "echo user-thing" },
|
|
887
|
-
{ type: "command", command: "
|
|
885
|
+
{ type: "command", command: "ov mail check" },
|
|
888
886
|
],
|
|
889
887
|
}),
|
|
890
888
|
).toBe(true);
|
|
@@ -1004,7 +1002,7 @@ describe("getCapabilityGuards", () => {
|
|
|
1004
1002
|
const guards = getCapabilityGuards(cap);
|
|
1005
1003
|
const taskGuard = guards.find((g) => g.matcher === "Task");
|
|
1006
1004
|
expect(taskGuard).toBeDefined();
|
|
1007
|
-
expect(taskGuard?.hooks[0]?.command).toContain("
|
|
1005
|
+
expect(taskGuard?.hooks[0]?.command).toContain("ov sling");
|
|
1008
1006
|
}
|
|
1009
1007
|
});
|
|
1010
1008
|
|
|
@@ -1066,7 +1064,7 @@ describe("getCapabilityGuards", () => {
|
|
|
1066
1064
|
const guard = guards.find((g) => g.matcher === "AskUserQuestion");
|
|
1067
1065
|
expect(guard).toBeDefined();
|
|
1068
1066
|
expect(guard?.hooks[0]?.command).toContain("human interaction");
|
|
1069
|
-
expect(guard?.hooks[0]?.command).toContain("
|
|
1067
|
+
expect(guard?.hooks[0]?.command).toContain("ov mail");
|
|
1070
1068
|
}
|
|
1071
1069
|
});
|
|
1072
1070
|
|
|
@@ -1085,7 +1083,7 @@ describe("getCapabilityGuards", () => {
|
|
|
1085
1083
|
const guard = guards.find((g) => g.matcher === "EnterPlanMode");
|
|
1086
1084
|
expect(guard).toBeDefined();
|
|
1087
1085
|
expect(guard?.hooks[0]?.command).toContain("human interaction");
|
|
1088
|
-
expect(guard?.hooks[0]?.command).toContain("
|
|
1086
|
+
expect(guard?.hooks[0]?.command).toContain("ov mail");
|
|
1089
1087
|
}
|
|
1090
1088
|
});
|
|
1091
1089
|
|
|
@@ -1104,7 +1102,7 @@ describe("getCapabilityGuards", () => {
|
|
|
1104
1102
|
const guard = guards.find((g) => g.matcher === "EnterWorktree");
|
|
1105
1103
|
expect(guard).toBeDefined();
|
|
1106
1104
|
expect(guard?.hooks[0]?.command).toContain("human interaction");
|
|
1107
|
-
expect(guard?.hooks[0]?.command).toContain("
|
|
1105
|
+
expect(guard?.hooks[0]?.command).toContain("ov mail");
|
|
1108
1106
|
}
|
|
1109
1107
|
});
|
|
1110
1108
|
|
|
@@ -1645,7 +1643,7 @@ describe("structural enforcement integration", () => {
|
|
|
1645
1643
|
|
|
1646
1644
|
const taskGuard = preToolUse.find((h: { matcher: string }) => h.matcher === "Task");
|
|
1647
1645
|
expect(taskGuard).toBeDefined();
|
|
1648
|
-
expect(taskGuard.hooks[0].command).toContain("
|
|
1646
|
+
expect(taskGuard.hooks[0].command).toContain("ov sling");
|
|
1649
1647
|
}
|
|
1650
1648
|
});
|
|
1651
1649
|
|
|
@@ -2116,3 +2114,39 @@ describe("bash path boundary integration", () => {
|
|
|
2116
2114
|
expect(universalGuard.hooks[0].command).toContain('"decision":"block"');
|
|
2117
2115
|
});
|
|
2118
2116
|
});
|
|
2117
|
+
|
|
2118
|
+
describe("escapeForSingleQuotedShell", () => {
|
|
2119
|
+
test("no single quotes: string passes through unchanged", () => {
|
|
2120
|
+
expect(escapeForSingleQuotedShell("hello world")).toBe("hello world");
|
|
2121
|
+
});
|
|
2122
|
+
|
|
2123
|
+
test("single quotes escaped: it's becomes it'\\''s", () => {
|
|
2124
|
+
expect(escapeForSingleQuotedShell("it's")).toBe("it'\\''s");
|
|
2125
|
+
});
|
|
2126
|
+
|
|
2127
|
+
test("multiple single quotes: each one is escaped independently", () => {
|
|
2128
|
+
expect(escapeForSingleQuotedShell("can't won't")).toBe("can'\\''t won'\\''t");
|
|
2129
|
+
});
|
|
2130
|
+
|
|
2131
|
+
test("empty string: returns empty string", () => {
|
|
2132
|
+
expect(escapeForSingleQuotedShell("")).toBe("");
|
|
2133
|
+
});
|
|
2134
|
+
|
|
2135
|
+
test("blockGuard shell command outputs valid JSON when executed", async () => {
|
|
2136
|
+
const guards = getCapabilityGuards("builder");
|
|
2137
|
+
const taskGuard = guards.find((g) => g.matcher === "Task");
|
|
2138
|
+
expect(taskGuard).toBeDefined();
|
|
2139
|
+
const cmd = taskGuard?.hooks[0]?.command ?? "";
|
|
2140
|
+
const echoCmd = cmd.replace('[ -z "$OVERSTORY_AGENT_NAME" ] && exit 0; ', "");
|
|
2141
|
+
const proc = Bun.spawn(["sh", "-c", echoCmd], {
|
|
2142
|
+
stdout: "pipe",
|
|
2143
|
+
stderr: "pipe",
|
|
2144
|
+
env: { ...process.env, OVERSTORY_AGENT_NAME: "test-agent" },
|
|
2145
|
+
});
|
|
2146
|
+
const output = await new Response(proc.stdout).text();
|
|
2147
|
+
await proc.exited;
|
|
2148
|
+
const parsed = JSON.parse(output.trim());
|
|
2149
|
+
expect(parsed.decision).toBe("block");
|
|
2150
|
+
expect(parsed.reason).toContain("ov sling");
|
|
2151
|
+
});
|
|
2152
|
+
});
|