@caseyharalson/orrery 0.11.0 → 0.13.0
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/HELP.md +107 -14
- package/README.md +11 -8
- package/agent/skills/discovery/SKILL.md +13 -5
- package/agent/skills/orrery-execute/SKILL.md +1 -1
- package/agent/skills/refine-plan/SKILL.md +3 -2
- package/agent/skills/simulate-plan/SKILL.md +3 -2
- package/lib/cli/commands/orchestrate.js +66 -1
- package/lib/cli/commands/plans-dir.js +10 -0
- package/lib/cli/commands/resume.js +167 -31
- package/lib/cli/commands/status.js +83 -11
- package/lib/cli/index.js +2 -0
- package/lib/orchestration/index.js +830 -155
- package/lib/utils/git.js +52 -2
- package/lib/utils/lock.js +235 -0
- package/lib/utils/paths.js +46 -3
- package/package.json +3 -2
package/HELP.md
CHANGED
|
@@ -71,20 +71,28 @@ each step.
|
|
|
71
71
|
|
|
72
72
|
```
|
|
73
73
|
Options:
|
|
74
|
-
--plan <file>
|
|
75
|
-
--dry-run
|
|
76
|
-
--verbose
|
|
77
|
-
--resume
|
|
78
|
-
--review
|
|
79
|
-
--parallel
|
|
74
|
+
--plan <file> Process only a specific plan file
|
|
75
|
+
--dry-run Show what would be executed without running agents
|
|
76
|
+
--verbose Show detailed agent output
|
|
77
|
+
--resume Resume orchestration on the current work branch
|
|
78
|
+
--review Enable code review loop after each step
|
|
79
|
+
--parallel Enable parallel execution with git worktrees for isolation
|
|
80
|
+
--background Run orchestration as a detached background process
|
|
81
|
+
--on-complete <command> Run a shell command when the orchestrator finishes
|
|
80
82
|
```
|
|
81
83
|
|
|
84
|
+
A lock file prevents concurrent runs and is automatically cleaned up. Without
|
|
85
|
+
`--plan`, a global lock (`exec.lock`) allows only one execution at a time.
|
|
86
|
+
With `--plan`, each plan gets its own lock (`exec-<planId>.lock`) and runs in
|
|
87
|
+
an isolated worktree, so multiple plans can execute concurrently.
|
|
88
|
+
|
|
82
89
|
Example:
|
|
83
90
|
|
|
84
91
|
```bash
|
|
85
92
|
orrery exec
|
|
86
93
|
orrery exec --plan my-feature.yaml --review
|
|
87
94
|
orrery exec --parallel --verbose
|
|
95
|
+
orrery exec --background
|
|
88
96
|
```
|
|
89
97
|
|
|
90
98
|
#### `orrery resume`
|
|
@@ -94,15 +102,25 @@ work branch, resets blocked steps to pending, commits, and resumes.
|
|
|
94
102
|
|
|
95
103
|
```
|
|
96
104
|
Options:
|
|
97
|
-
--
|
|
98
|
-
--
|
|
99
|
-
--
|
|
105
|
+
--plan <file> Resume a specific plan file (skips branch auto-detection)
|
|
106
|
+
--step <id> Unblock a specific step before resuming
|
|
107
|
+
--all Unblock all blocked steps (default behavior)
|
|
108
|
+
--dry-run Preview what would be unblocked without making changes
|
|
109
|
+
--background Run resume as a detached background process
|
|
110
|
+
--on-complete <command> Run a shell command when the orchestrator finishes
|
|
100
111
|
```
|
|
101
112
|
|
|
113
|
+
When `--plan` is provided and a worktree exists for the plan, resume runs
|
|
114
|
+
directly inside the worktree. Otherwise, the plan's `work_branch` must match
|
|
115
|
+
the current branch. If the plan hasn't been dispatched yet (no `work_branch`),
|
|
116
|
+
use `orrery exec --plan` first.
|
|
117
|
+
|
|
102
118
|
Example:
|
|
103
119
|
|
|
104
120
|
```bash
|
|
105
121
|
orrery resume
|
|
122
|
+
orrery resume --plan my-feature.yaml
|
|
123
|
+
orrery resume --plan my-feature.yaml --background
|
|
106
124
|
orrery resume --step step-2
|
|
107
125
|
orrery resume --dry-run
|
|
108
126
|
```
|
|
@@ -112,7 +130,8 @@ orrery resume --dry-run
|
|
|
112
130
|
#### `orrery status`
|
|
113
131
|
|
|
114
132
|
Show orchestration status for plans in the current project. Auto-detects the
|
|
115
|
-
plan when on a work branch.
|
|
133
|
+
plan when on a work branch. Also shows whether an orchestration process is
|
|
134
|
+
currently running (via lock file detection) or if a stale lock exists.
|
|
116
135
|
|
|
117
136
|
```
|
|
118
137
|
Options:
|
|
@@ -250,6 +269,72 @@ The review agent inspects changes after each step. If issues are found, an edit
|
|
|
250
269
|
agent applies fixes and verification re-runs, repeating until approval or the
|
|
251
270
|
max iteration limit is reached (default: 3).
|
|
252
271
|
|
|
272
|
+
### Background Execution
|
|
273
|
+
|
|
274
|
+
Run orchestration or resume as a detached background process:
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
orrery exec --background
|
|
278
|
+
orrery exec --plan my-feature.yaml --background
|
|
279
|
+
orrery resume --plan my-feature.yaml --background
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
The process runs detached and logs output to `<work-dir>/exec.log` (or
|
|
283
|
+
`exec-<planId>.log` for per-plan execution). Use `orrery status` to check
|
|
284
|
+
progress. Each execution acquires its own lock file — a global `exec.lock` for non-plan
|
|
285
|
+
runs, or `exec-<planId>.lock` when `--plan` is used — so background per-plan
|
|
286
|
+
executions can run concurrently.
|
|
287
|
+
|
|
288
|
+
### Concurrent Plan Execution
|
|
289
|
+
|
|
290
|
+
Run multiple plans at the same time by using `--plan` in separate terminals.
|
|
291
|
+
Each plan executes in its own git worktree (`.worktrees/plan-<planId>`) with
|
|
292
|
+
an independent lock file, so plans do not interfere with each other.
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
# Terminal 1
|
|
296
|
+
orrery exec --plan auth-feature.yaml
|
|
297
|
+
|
|
298
|
+
# Terminal 2
|
|
299
|
+
orrery exec --plan search-feature.yaml
|
|
300
|
+
|
|
301
|
+
# Or run both in the background from a single terminal
|
|
302
|
+
orrery exec --plan auth-feature.yaml --background
|
|
303
|
+
orrery exec --plan search-feature.yaml --background
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Use `orrery status` to see all running plans at once. Resume a specific plan
|
|
307
|
+
with `orrery resume --plan <file>`, which automatically re-enters the plan's
|
|
308
|
+
existing worktree.
|
|
309
|
+
|
|
310
|
+
Note: Without `--plan`, execution uses a global lock and only one run is
|
|
311
|
+
allowed at a time.
|
|
312
|
+
|
|
313
|
+
### Completion Hook
|
|
314
|
+
|
|
315
|
+
Run a shell command when the orchestrator finishes using `--on-complete`:
|
|
316
|
+
|
|
317
|
+
```bash
|
|
318
|
+
orrery exec --on-complete "notify-send 'Plan finished'"
|
|
319
|
+
orrery resume --plan my-feature.yaml --on-complete "./scripts/on-done.sh"
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
The command receives plan context through environment variables:
|
|
323
|
+
|
|
324
|
+
| Variable | Description | Example |
|
|
325
|
+
| :----------------------- | :--------------------------------------------- | :----------------------------------------------------- |
|
|
326
|
+
| `ORRERY_PLAN_NAME` | Plan file name | `my-feature.yaml` |
|
|
327
|
+
| `ORRERY_PLAN_FILE` | Absolute path to the plan file | `/home/user/project/.agent-work/plans/my-feature.yaml` |
|
|
328
|
+
| `ORRERY_PLAN_OUTCOME` | Outcome: `success`, `partial`, or `incomplete` | `success` |
|
|
329
|
+
| `ORRERY_WORK_BRANCH` | Work branch name | `plan/my-feature` |
|
|
330
|
+
| `ORRERY_SOURCE_BRANCH` | Source branch name | `main` |
|
|
331
|
+
| `ORRERY_PR_URL` | PR URL (empty if not created) | `https://github.com/user/repo/pull/42` |
|
|
332
|
+
| `ORRERY_STEPS_TOTAL` | Total number of steps | `5` |
|
|
333
|
+
| `ORRERY_STEPS_COMPLETED` | Number of completed steps | `4` |
|
|
334
|
+
| `ORRERY_STEPS_BLOCKED` | Number of blocked steps | `1` |
|
|
335
|
+
|
|
336
|
+
Hook failures are logged but do not fail the orchestrator.
|
|
337
|
+
|
|
253
338
|
### Parallel Execution
|
|
254
339
|
|
|
255
340
|
Run independent steps concurrently using git worktrees for isolation:
|
|
@@ -368,11 +453,19 @@ Orrery maintains state in `.agent-work/` (configurable via `ORRERY_WORK_DIR`):
|
|
|
368
453
|
|
|
369
454
|
```
|
|
370
455
|
.agent-work/
|
|
371
|
-
plans/
|
|
372
|
-
reports/
|
|
373
|
-
completed/
|
|
456
|
+
plans/ Active plan files (new and in-progress)
|
|
457
|
+
reports/ Step-level execution logs and outcomes
|
|
458
|
+
completed/ Successfully executed plans (archived)
|
|
459
|
+
exec.lock Lock file when orchestration is running
|
|
460
|
+
exec-<planId>.lock Per-plan lock file for concurrent execution
|
|
461
|
+
exec.log Output log for background execution
|
|
462
|
+
exec-<planId>.log Per-plan output log for concurrent background execution
|
|
374
463
|
```
|
|
375
464
|
|
|
465
|
+
When `ORRERY_WORK_DIR` is set, Orrery automatically scopes to a project
|
|
466
|
+
subdirectory (`<project-name>-<hash>`) so multiple projects can share the same
|
|
467
|
+
work directory without plan conflicts.
|
|
468
|
+
|
|
376
469
|
---
|
|
377
470
|
|
|
378
471
|
## Environment Variables
|
|
@@ -385,7 +478,7 @@ Orrery maintains state in `.agent-work/` (configurable via `ORRERY_WORK_DIR`):
|
|
|
385
478
|
| `ORRERY_PARALLEL_MAX` | Maximum concurrent parallel agents | `3` |
|
|
386
479
|
| `ORRERY_REVIEW_ENABLED` | Enable the review loop | `false` |
|
|
387
480
|
| `ORRERY_REVIEW_MAX_ITERATIONS` | Maximum review-edit loop iterations | `3` |
|
|
388
|
-
| `ORRERY_WORK_DIR` | Override the work directory path
|
|
481
|
+
| `ORRERY_WORK_DIR` | Override the work directory path (project-scoped) | `.agent-work` |
|
|
389
482
|
|
|
390
483
|
---
|
|
391
484
|
|
package/README.md
CHANGED
|
@@ -88,6 +88,8 @@ For power users, Orrery offers additional capabilities:
|
|
|
88
88
|
- **External Plan Creation** - Import plans from other tools or LLMs
|
|
89
89
|
- **Review Loop** - Iterative code review after each step with automatic fixes
|
|
90
90
|
- **Parallel Execution** - Run independent steps concurrently with git worktree isolation
|
|
91
|
+
- **Background Execution** - Run orchestration as a detached process and poll status
|
|
92
|
+
- **Completion Hook** - Run a command when orchestration finishes
|
|
91
93
|
- **Handling Blocked Plans** - Recovery workflows when steps cannot complete
|
|
92
94
|
|
|
93
95
|
See [Advanced Workflows](docs/advanced-workflows.md) for details.
|
|
@@ -120,17 +122,18 @@ The Orchestrator (`orrery exec`) is the engine that drives the process. It loads
|
|
|
120
122
|
|
|
121
123
|
## Command Reference
|
|
122
124
|
|
|
123
|
-
| Command | Description
|
|
124
|
-
| :------------------- |
|
|
125
|
-
| `orrery` | Command reference.
|
|
126
|
-
| `orrery init` | Initialize Orrery: install skills to detected agents.
|
|
127
|
-
| `orrery manual` | Show the full CLI reference manual.
|
|
128
|
-
| `orrery orchestrate` | Executes the active plan. Use `--review` for review loop, `--parallel` for parallel execution. Alias: `exec`. |
|
|
129
|
-
| `orrery
|
|
125
|
+
| Command | Description |
|
|
126
|
+
| :------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
127
|
+
| `orrery` | Command reference. |
|
|
128
|
+
| `orrery init` | Initialize Orrery: install skills to detected agents. |
|
|
129
|
+
| `orrery manual` | Show the full CLI reference manual. |
|
|
130
|
+
| `orrery orchestrate` | Executes the active plan. Use `--review` for review loop, `--parallel` for parallel execution, `--background` for detached mode, `--on-complete` for a completion hook. Alias: `exec`. |
|
|
131
|
+
| `orrery resume` | Unblock steps and resume orchestration. Use `--plan` to target a specific plan, `--background` for detached mode. |
|
|
132
|
+
| `orrery status` | Shows the progress of current plans and active execution status. |
|
|
130
133
|
|
|
131
134
|
## Directory Structure
|
|
132
135
|
|
|
133
|
-
Orrery maintains its state in the `.agent-work/` directory (configurable via `ORRERY_WORK_DIR`).
|
|
136
|
+
Orrery maintains its state in the `.agent-work/` directory (configurable via `ORRERY_WORK_DIR`). When `ORRERY_WORK_DIR` is set, each project gets an isolated subdirectory so multiple projects can share the same work directory.
|
|
134
137
|
|
|
135
138
|
- `.agent-work/plans/`: **Active Plans.** New and in-progress plan files.
|
|
136
139
|
- `.agent-work/reports/`: **Reports.** Step-level execution logs and outcomes.
|
|
@@ -191,7 +191,15 @@ Use the schema defined in `./schemas/plan-schema.yaml`.
|
|
|
191
191
|
|
|
192
192
|
**Output Location:**
|
|
193
193
|
|
|
194
|
-
|
|
194
|
+
First, determine the plans directory by running:
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
orrery plans-dir
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
This prints the resolved plans directory (respects `ORRERY_WORK_DIR` when set).
|
|
201
|
+
|
|
202
|
+
- Directory: the path returned by `orrery plans-dir`
|
|
195
203
|
- Filename: `<date>-<plan-name>.yaml`
|
|
196
204
|
- Date format: YYYY-MM-DD (e.g., `2026-01-11`)
|
|
197
205
|
- Plan name: kebab-case description of the task (e.g., `fix-clone-agent-skills`)
|
|
@@ -215,7 +223,7 @@ Plans are automatically validated via the PostToolUse hook when written.
|
|
|
215
223
|
For manual validation, run:
|
|
216
224
|
|
|
217
225
|
```bash
|
|
218
|
-
orrery validate-plan
|
|
226
|
+
orrery validate-plan <plans-dir>/<plan>.yaml
|
|
219
227
|
```
|
|
220
228
|
|
|
221
229
|
This catches common YAML issues like unquoted colons and normalizes formatting.
|
|
@@ -405,11 +413,11 @@ Discovery is complete when:
|
|
|
405
413
|
When the plan is complete and validated, output the plan file path and present the user with their next options:
|
|
406
414
|
|
|
407
415
|
```
|
|
408
|
-
Plan created:
|
|
416
|
+
Plan created: <plans-dir>/<date>-<plan-name>.yaml
|
|
409
417
|
|
|
410
418
|
Next steps:
|
|
411
|
-
- /refine-plan
|
|
412
|
-
- /simulate-plan
|
|
419
|
+
- /refine-plan <plans-dir>/<plan-file> — Analyze and improve the plan before execution
|
|
420
|
+
- /simulate-plan <plans-dir>/<plan-file> — Explore the plan through dialogue, ask "what if" questions
|
|
413
421
|
- orrery exec — (Command run from the terminal) Execute the plan with the orrery orchestrator
|
|
414
422
|
```
|
|
415
423
|
|
|
@@ -39,7 +39,7 @@ These guidelines override generic examples in this skill. For example, if a guid
|
|
|
39
39
|
|
|
40
40
|
### Git State
|
|
41
41
|
|
|
42
|
-
The orchestrator modifies
|
|
42
|
+
The orchestrator modifies plan and work directory files before you start (marking steps `in_progress`, creating temp files). This is expected. **Ignore changes in the orchestrator work directory** (e.g., `.agent-work/` or the path from `orrery plans-dir`) when checking git status - these are orchestrator bookkeeping files, not unexpected changes.
|
|
43
43
|
|
|
44
44
|
### Step 1: Read the Plan
|
|
45
45
|
|
|
@@ -3,7 +3,8 @@ name: refine-plan
|
|
|
3
3
|
description: >
|
|
4
4
|
Analyze and improve an existing plan file. Reviews plan structure, dependencies,
|
|
5
5
|
context quality, and acceptance criteria, then implements improvements directly.
|
|
6
|
-
Requires a plan file argument (e.g., /refine-plan
|
|
6
|
+
Requires a plan file argument (e.g., /refine-plan my-plan.yaml).
|
|
7
|
+
Run `orrery plans-dir` to find the plans directory.
|
|
7
8
|
hooks:
|
|
8
9
|
PostToolUse:
|
|
9
10
|
- matcher: "Write"
|
|
@@ -273,7 +274,7 @@ Ready for execution.
|
|
|
273
274
|
## Example Dialogue
|
|
274
275
|
|
|
275
276
|
```
|
|
276
|
-
User: /refine-plan
|
|
277
|
+
User: /refine-plan analytics-dashboard.yaml
|
|
277
278
|
|
|
278
279
|
Agent: I've loaded the analytics dashboard plan. It has 6 steps delivering
|
|
279
280
|
two outcomes. Let me analyze it for improvements.
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
name: simulate-plan
|
|
3
3
|
description: >
|
|
4
4
|
Explore a plan through conversational dialogue before committing to execution.
|
|
5
|
-
Requires a plan file argument (e.g., /simulate-plan
|
|
5
|
+
Requires a plan file argument (e.g., /simulate-plan my-plan.yaml, /simulate-plan my-plan).
|
|
6
|
+
Run `orrery plans-dir` to find the plans directory.
|
|
6
7
|
Ask "what if" questions, trace dependencies, and build intuition about what you're building.
|
|
7
8
|
---
|
|
8
9
|
|
|
@@ -164,7 +165,7 @@ Only discuss what's in the plan. If asked about implementation details not cover
|
|
|
164
165
|
## Example Dialogue
|
|
165
166
|
|
|
166
167
|
```
|
|
167
|
-
User: /simulate
|
|
168
|
+
User: /simulate-plan analytics-dashboard.yaml
|
|
168
169
|
|
|
169
170
|
Agent: I've loaded the analytics dashboard plan. It has 6 steps delivering
|
|
170
171
|
two outcomes: "Users can see usage trends" and "Admins can export reports."
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
+
const { spawn } = require("child_process");
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
|
|
1
5
|
const { orchestrate } = require("../../orchestration");
|
|
6
|
+
const { getWorkDir } = require("../../utils/paths");
|
|
7
|
+
const { derivePlanId } = require("../../utils/git");
|
|
2
8
|
|
|
3
9
|
module.exports = function registerOrchestrateCommand(program) {
|
|
4
10
|
program
|
|
@@ -14,7 +20,65 @@ module.exports = function registerOrchestrateCommand(program) {
|
|
|
14
20
|
"--parallel",
|
|
15
21
|
"Enable parallel execution with git worktrees for isolation"
|
|
16
22
|
)
|
|
23
|
+
.option(
|
|
24
|
+
"--background",
|
|
25
|
+
"Run orchestration as a detached background process"
|
|
26
|
+
)
|
|
27
|
+
.option(
|
|
28
|
+
"--on-complete <command>",
|
|
29
|
+
"Run a shell command when the orchestrator finishes"
|
|
30
|
+
)
|
|
17
31
|
.action(async (options) => {
|
|
32
|
+
// Background mode: re-spawn as detached process
|
|
33
|
+
if (options.background) {
|
|
34
|
+
if (options.dryRun) {
|
|
35
|
+
console.log(
|
|
36
|
+
"Note: --background with --dry-run runs in foreground.\n"
|
|
37
|
+
);
|
|
38
|
+
// Fall through to normal execution
|
|
39
|
+
} else {
|
|
40
|
+
const args = [];
|
|
41
|
+
if (options.plan) args.push("--plan", options.plan);
|
|
42
|
+
if (options.verbose) args.push("--verbose");
|
|
43
|
+
if (options.resume) args.push("--resume");
|
|
44
|
+
if (options.review) args.push("--review");
|
|
45
|
+
if (options.parallel) args.push("--parallel");
|
|
46
|
+
if (options.onComplete)
|
|
47
|
+
args.push("--on-complete", options.onComplete);
|
|
48
|
+
|
|
49
|
+
let logFileName = "exec.log";
|
|
50
|
+
if (options.plan) {
|
|
51
|
+
const planId = derivePlanId(path.basename(options.plan));
|
|
52
|
+
logFileName = `exec-${planId}.log`;
|
|
53
|
+
}
|
|
54
|
+
const logFile = path.join(getWorkDir(), logFileName);
|
|
55
|
+
const logFd = fs.openSync(logFile, "a");
|
|
56
|
+
|
|
57
|
+
const binPath = path.join(
|
|
58
|
+
__dirname,
|
|
59
|
+
"..",
|
|
60
|
+
"..",
|
|
61
|
+
"..",
|
|
62
|
+
"bin",
|
|
63
|
+
"orrery.js"
|
|
64
|
+
);
|
|
65
|
+
const child = spawn(process.execPath, [binPath, "exec", ...args], {
|
|
66
|
+
detached: true,
|
|
67
|
+
stdio: ["ignore", logFd, logFd],
|
|
68
|
+
cwd: process.cwd(),
|
|
69
|
+
env: process.env
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
child.unref();
|
|
73
|
+
fs.closeSync(logFd);
|
|
74
|
+
|
|
75
|
+
console.log(`Background execution started (PID ${child.pid})`);
|
|
76
|
+
console.log(`Log file: ${logFile}`);
|
|
77
|
+
console.log("\nUse 'orrery status' to check progress.");
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
18
82
|
try {
|
|
19
83
|
await orchestrate({
|
|
20
84
|
plan: options.plan,
|
|
@@ -22,7 +86,8 @@ module.exports = function registerOrchestrateCommand(program) {
|
|
|
22
86
|
verbose: options.verbose,
|
|
23
87
|
resume: options.resume,
|
|
24
88
|
review: options.review,
|
|
25
|
-
parallel: options.parallel
|
|
89
|
+
parallel: options.parallel,
|
|
90
|
+
onComplete: options.onComplete
|
|
26
91
|
});
|
|
27
92
|
} catch (error) {
|
|
28
93
|
console.error(error && error.message ? error.message : error);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const { getPlansDir } = require("../../utils/paths");
|
|
2
|
+
|
|
3
|
+
module.exports = function registerPlansDirCommand(program) {
|
|
4
|
+
program
|
|
5
|
+
.command("plans-dir")
|
|
6
|
+
.description("Print the resolved plans directory path")
|
|
7
|
+
.action(() => {
|
|
8
|
+
console.log(getPlansDir());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
@@ -1,8 +1,14 @@
|
|
|
1
|
+
const { spawn } = require("child_process");
|
|
2
|
+
const fs = require("fs");
|
|
1
3
|
const path = require("path");
|
|
2
4
|
|
|
3
5
|
const { findPlanForCurrentBranch } = require("../../utils/plan-detect");
|
|
4
|
-
const {
|
|
5
|
-
|
|
6
|
+
const {
|
|
7
|
+
loadPlan,
|
|
8
|
+
updateStepsStatus
|
|
9
|
+
} = require("../../orchestration/plan-loader");
|
|
10
|
+
const { commit, getCurrentBranch, derivePlanId } = require("../../utils/git");
|
|
11
|
+
const { getPlansDir, getWorkDir } = require("../../utils/paths");
|
|
6
12
|
const { orchestrate } = require("../../orchestration");
|
|
7
13
|
|
|
8
14
|
function supportsColor() {
|
|
@@ -24,38 +30,162 @@ module.exports = function registerResumeCommand(program) {
|
|
|
24
30
|
program
|
|
25
31
|
.command("resume")
|
|
26
32
|
.description("Unblock steps and resume orchestration")
|
|
33
|
+
.option("--plan <file>", "Resume a specific plan file")
|
|
27
34
|
.option("--step <id>", "Unblock a specific step before resuming")
|
|
28
35
|
.option("--all", "Unblock all blocked steps (default behavior)")
|
|
29
36
|
.option(
|
|
30
37
|
"--dry-run",
|
|
31
38
|
"Preview what would be unblocked without making changes"
|
|
32
39
|
)
|
|
40
|
+
.option("--background", "Run resume as a detached background process")
|
|
41
|
+
.option(
|
|
42
|
+
"--on-complete <command>",
|
|
43
|
+
"Run a shell command when the orchestrator finishes"
|
|
44
|
+
)
|
|
33
45
|
.action(async (options) => {
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
46
|
+
// Background mode: re-spawn as detached process
|
|
47
|
+
if (options.background) {
|
|
48
|
+
if (options.dryRun) {
|
|
49
|
+
console.log(
|
|
50
|
+
"Note: --background with --dry-run runs in foreground.\n"
|
|
51
|
+
);
|
|
52
|
+
// Fall through to normal execution
|
|
53
|
+
} else {
|
|
54
|
+
const args = [];
|
|
55
|
+
if (options.plan) args.push("--plan", options.plan);
|
|
56
|
+
if (options.step) args.push("--step", options.step);
|
|
57
|
+
if (options.all) args.push("--all");
|
|
58
|
+
if (options.onComplete)
|
|
59
|
+
args.push("--on-complete", options.onComplete);
|
|
60
|
+
|
|
61
|
+
let logFileName = "exec.log";
|
|
62
|
+
if (options.plan) {
|
|
63
|
+
const planId = derivePlanId(path.basename(options.plan));
|
|
64
|
+
logFileName = `exec-${planId}.log`;
|
|
65
|
+
}
|
|
66
|
+
const logFile = path.join(getWorkDir(), logFileName);
|
|
67
|
+
const logFd = fs.openSync(logFile, "a");
|
|
68
|
+
|
|
69
|
+
const binPath = path.join(
|
|
70
|
+
__dirname,
|
|
71
|
+
"..",
|
|
72
|
+
"..",
|
|
73
|
+
"..",
|
|
74
|
+
"bin",
|
|
75
|
+
"orrery.js"
|
|
76
|
+
);
|
|
77
|
+
const child = spawn(process.execPath, [binPath, "resume", ...args], {
|
|
78
|
+
detached: true,
|
|
79
|
+
stdio: ["ignore", logFd, logFd],
|
|
80
|
+
cwd: process.cwd(),
|
|
81
|
+
env: process.env
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
child.unref();
|
|
85
|
+
fs.closeSync(logFd);
|
|
86
|
+
|
|
87
|
+
console.log(`Background resume started (PID ${child.pid})`);
|
|
88
|
+
console.log(`Log file: ${logFile}`);
|
|
89
|
+
console.log("\nUse 'orrery status' to check progress.");
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
45
92
|
}
|
|
46
93
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
94
|
+
let planFile;
|
|
95
|
+
let plan;
|
|
96
|
+
|
|
97
|
+
if (options.plan) {
|
|
98
|
+
// Resolve plan path (same pattern as status.js)
|
|
99
|
+
const planArg = options.plan;
|
|
100
|
+
let resolvedPath;
|
|
101
|
+
if (path.isAbsolute(planArg)) {
|
|
102
|
+
resolvedPath = planArg;
|
|
103
|
+
} else if (planArg.includes(path.sep)) {
|
|
104
|
+
resolvedPath = path.resolve(process.cwd(), planArg);
|
|
105
|
+
} else {
|
|
106
|
+
resolvedPath = path.join(getPlansDir(), planArg);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (!resolvedPath || !fs.existsSync(resolvedPath)) {
|
|
110
|
+
console.error(`Plan not found: ${planArg}`);
|
|
111
|
+
process.exitCode = 1;
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
plan = loadPlan(resolvedPath);
|
|
116
|
+
planFile = resolvedPath;
|
|
117
|
+
|
|
118
|
+
// Validate work_branch
|
|
119
|
+
if (!plan.metadata.work_branch) {
|
|
120
|
+
console.error(
|
|
121
|
+
"Plan has no work_branch — it hasn't been dispatched yet."
|
|
122
|
+
);
|
|
123
|
+
console.log(
|
|
124
|
+
"\nUse 'orrery exec --plan <file>' to dispatch the plan first."
|
|
125
|
+
);
|
|
126
|
+
process.exitCode = 1;
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Check for existing worktree — if found, skip branch validation
|
|
131
|
+
const planId = derivePlanId(path.basename(resolvedPath));
|
|
132
|
+
const worktreePath = path.join(
|
|
133
|
+
process.cwd(),
|
|
134
|
+
".worktrees",
|
|
135
|
+
`plan-${planId}`
|
|
50
136
|
);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
137
|
+
const hasWorktree = require("fs").existsSync(worktreePath);
|
|
138
|
+
|
|
139
|
+
if (!hasWorktree) {
|
|
140
|
+
// Verify current branch matches plan's work_branch
|
|
141
|
+
let currentBranch;
|
|
142
|
+
try {
|
|
143
|
+
currentBranch = getCurrentBranch(process.cwd());
|
|
144
|
+
} catch {
|
|
145
|
+
console.error("Error detecting current branch.");
|
|
146
|
+
process.exitCode = 1;
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (currentBranch !== plan.metadata.work_branch) {
|
|
151
|
+
console.error(
|
|
152
|
+
`Plan expects branch '${plan.metadata.work_branch}' but you are on '${currentBranch}'.`
|
|
153
|
+
);
|
|
154
|
+
console.log(`\nRun: git checkout ${plan.metadata.work_branch}`);
|
|
155
|
+
process.exitCode = 1;
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
} else {
|
|
160
|
+
// 1. Find plan for current branch (existing behavior)
|
|
161
|
+
let match;
|
|
162
|
+
try {
|
|
163
|
+
match = findPlanForCurrentBranch();
|
|
164
|
+
} catch {
|
|
165
|
+
console.error("Error detecting plan from current branch.");
|
|
166
|
+
console.log(
|
|
167
|
+
"Make sure you're on a work branch (e.g., plan/feature-name)."
|
|
168
|
+
);
|
|
169
|
+
process.exitCode = 1;
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
57
172
|
|
|
58
|
-
|
|
173
|
+
if (!match) {
|
|
174
|
+
console.error(
|
|
175
|
+
"Not on a work branch. No plan found for current branch."
|
|
176
|
+
);
|
|
177
|
+
console.log("\nTo resume a plan:");
|
|
178
|
+
console.log(" 1. git checkout <work-branch>");
|
|
179
|
+
console.log(" 2. orrery resume");
|
|
180
|
+
console.log("\nOr specify a plan directly:");
|
|
181
|
+
console.log(" orrery resume --plan <file>");
|
|
182
|
+
process.exitCode = 1;
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
planFile = match.planFile;
|
|
187
|
+
plan = match.plan;
|
|
188
|
+
}
|
|
59
189
|
const planFileName = path.basename(planFile);
|
|
60
190
|
console.log(`(detected plan: ${planFileName})\n`);
|
|
61
191
|
|
|
@@ -72,13 +202,15 @@ module.exports = function registerResumeCommand(program) {
|
|
|
72
202
|
}
|
|
73
203
|
|
|
74
204
|
console.log("Resuming orchestration...\n");
|
|
75
|
-
await orchestrate({
|
|
205
|
+
await orchestrate({
|
|
206
|
+
resume: true,
|
|
207
|
+
plan: options.plan,
|
|
208
|
+
onComplete: options.onComplete
|
|
209
|
+
});
|
|
76
210
|
return;
|
|
77
211
|
}
|
|
78
212
|
|
|
79
213
|
// 4. Determine which steps to unblock
|
|
80
|
-
let stepsToUnblock = [];
|
|
81
|
-
|
|
82
214
|
if (options.step) {
|
|
83
215
|
// Unblock specific step
|
|
84
216
|
const step = blockedSteps.find((s) => s.id === options.step);
|
|
@@ -93,12 +225,12 @@ module.exports = function registerResumeCommand(program) {
|
|
|
93
225
|
process.exitCode = 1;
|
|
94
226
|
return;
|
|
95
227
|
}
|
|
96
|
-
stepsToUnblock = [step];
|
|
97
|
-
} else {
|
|
98
|
-
// Default: unblock all (--all is implicit)
|
|
99
|
-
stepsToUnblock = blockedSteps;
|
|
100
228
|
}
|
|
101
229
|
|
|
230
|
+
const stepsToUnblock = options.step
|
|
231
|
+
? [blockedSteps.find((s) => s.id === options.step)]
|
|
232
|
+
: blockedSteps;
|
|
233
|
+
|
|
102
234
|
// 5. Dry-run mode: show preview
|
|
103
235
|
if (options.dryRun) {
|
|
104
236
|
console.log("Dry run - would unblock the following steps:\n");
|
|
@@ -141,6 +273,10 @@ module.exports = function registerResumeCommand(program) {
|
|
|
141
273
|
|
|
142
274
|
// 8. Resume orchestration
|
|
143
275
|
console.log("\nResuming orchestration...\n");
|
|
144
|
-
await orchestrate({
|
|
276
|
+
await orchestrate({
|
|
277
|
+
resume: true,
|
|
278
|
+
plan: options.plan,
|
|
279
|
+
onComplete: options.onComplete
|
|
280
|
+
});
|
|
145
281
|
});
|
|
146
282
|
};
|