@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 CHANGED
@@ -71,20 +71,28 @@ each step.
71
71
 
72
72
  ```
73
73
  Options:
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
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
- --step <id> Unblock a specific step before resuming
98
- --all Unblock all blocked steps (default behavior)
99
- --dry-run Preview what would be unblocked without making changes
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/ Active plan files (new and in-progress)
372
- reports/ Step-level execution logs and outcomes
373
- completed/ Successfully executed plans (archived)
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 | `.agent-work` |
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 status` | Shows the progress of current plans. |
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
- - Directory: `.agent-work/plans/`
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 .agent-work/plans/<plan>.yaml
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: .agent-work/plans/<date>-<plan-name>.yaml
416
+ Plan created: <plans-dir>/<date>-<plan-name>.yaml
409
417
 
410
418
  Next steps:
411
- - /refine-plan .agent-work/plans/<plan-file> — Analyze and improve the plan before execution
412
- - /simulate-plan .agent-work/plans/<plan-file> — Explore the plan through dialogue, ask "what if" questions
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 `.agent-work/` files before you start (marking steps `in_progress`, creating temp files). This is expected. **Ignore changes in `.agent-work/`** when checking git status - these are orchestrator bookkeeping files, not unexpected changes.
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 .agent-work/plans/my-plan.yaml).
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 .agent-work/plans/analytics-dashboard.yaml
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 .agent-work/plans/my-plan.yaml, /simulate-plan my-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 .agent-work/plans/analytics-dashboard.yaml
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 { updateStepsStatus } = require("../../orchestration/plan-loader");
5
- const { commit } = require("../../utils/git");
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
- // 1. Find plan for current branch
35
- let match;
36
- try {
37
- match = findPlanForCurrentBranch();
38
- } catch {
39
- console.error("Error detecting plan from current branch.");
40
- console.log(
41
- "Make sure you're on a work branch (e.g., plan/feature-name)."
42
- );
43
- process.exitCode = 1;
44
- return;
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
- if (!match) {
48
- console.error(
49
- "Not on a work branch. No plan found for current branch."
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
- console.log("\nTo resume a plan:");
52
- console.log(" 1. git checkout <work-branch>");
53
- console.log(" 2. orrery resume");
54
- process.exitCode = 1;
55
- return;
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
- const { planFile, plan } = match;
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({ resume: true });
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({ resume: true });
276
+ await orchestrate({
277
+ resume: true,
278
+ plan: options.plan,
279
+ onComplete: options.onComplete
280
+ });
145
281
  });
146
282
  };