sequant 2.10.0 → 2.11.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.
Files changed (71) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +6 -2
  3. package/dist/bin/cli.js +47 -2
  4. package/dist/src/commands/locks.d.ts +20 -1
  5. package/dist/src/commands/locks.js +206 -4
  6. package/dist/src/commands/ready.d.ts +6 -0
  7. package/dist/src/commands/ready.js +15 -1
  8. package/dist/src/commands/run-display.js +1 -0
  9. package/dist/src/commands/worktree.d.ts +31 -0
  10. package/dist/src/commands/worktree.js +95 -0
  11. package/dist/src/lib/cli-flags.d.ts +23 -0
  12. package/dist/src/lib/cli-flags.js +43 -0
  13. package/dist/src/lib/cli-ui/run-renderer-types.d.ts +2 -0
  14. package/dist/src/lib/cli-ui/run-renderer.js +7 -1
  15. package/dist/src/lib/locks/checkout-lock.d.ts +193 -0
  16. package/dist/src/lib/locks/checkout-lock.js +389 -0
  17. package/dist/src/lib/locks/index.d.ts +6 -3
  18. package/dist/src/lib/locks/index.js +4 -2
  19. package/dist/src/lib/locks/lock-manager.d.ts +81 -1
  20. package/dist/src/lib/locks/lock-manager.js +230 -5
  21. package/dist/src/lib/locks/types.d.ts +72 -0
  22. package/dist/src/lib/locks/types.js +28 -0
  23. package/dist/src/lib/settings.d.ts +73 -0
  24. package/dist/src/lib/settings.js +45 -0
  25. package/dist/src/lib/test-tautology-detector.d.ts +4 -3
  26. package/dist/src/lib/test-tautology-detector.js +101 -41
  27. package/dist/src/lib/workflow/batch-executor.js +78 -19
  28. package/dist/src/lib/workflow/config-resolver.d.ts +25 -0
  29. package/dist/src/lib/workflow/config-resolver.js +89 -0
  30. package/dist/src/lib/workflow/drivers/agent-driver.d.ts +15 -0
  31. package/dist/src/lib/workflow/drivers/claude-code.js +5 -0
  32. package/dist/src/lib/workflow/effort-escalation.d.ts +73 -0
  33. package/dist/src/lib/workflow/effort-escalation.js +82 -0
  34. package/dist/src/lib/workflow/error-classifier.d.ts +4 -1
  35. package/dist/src/lib/workflow/error-classifier.js +4 -0
  36. package/dist/src/lib/workflow/log-writer.d.ts +10 -1
  37. package/dist/src/lib/workflow/log-writer.js +20 -0
  38. package/dist/src/lib/workflow/metrics-schema.d.ts +49 -6
  39. package/dist/src/lib/workflow/metrics-schema.js +33 -0
  40. package/dist/src/lib/workflow/metrics-writer.d.ts +11 -0
  41. package/dist/src/lib/workflow/phase-detection.d.ts +12 -0
  42. package/dist/src/lib/workflow/phase-detection.js +5 -1
  43. package/dist/src/lib/workflow/phase-executor.js +10 -0
  44. package/dist/src/lib/workflow/ready-gate.d.ts +28 -0
  45. package/dist/src/lib/workflow/ready-gate.js +24 -3
  46. package/dist/src/lib/workflow/run-log-schema.d.ts +55 -0
  47. package/dist/src/lib/workflow/run-log-schema.js +31 -1
  48. package/dist/src/lib/workflow/run-orchestrator.js +27 -0
  49. package/dist/src/lib/workflow/spec-recommendation.d.ts +71 -0
  50. package/dist/src/lib/workflow/spec-recommendation.js +142 -0
  51. package/dist/src/lib/workflow/types.d.ts +64 -0
  52. package/dist/src/lib/workflow/worktree-manager.d.ts +8 -1
  53. package/dist/src/lib/workflow/worktree-manager.js +9 -1
  54. package/dist/src/lib/workflow/worktree-resolver.d.ts +73 -0
  55. package/dist/src/lib/workflow/worktree-resolver.js +126 -0
  56. package/package.json +3 -2
  57. package/templates/hooks/pre-tool.sh +228 -0
  58. package/templates/scripts/cleanup-worktree.sh +36 -15
  59. package/templates/scripts/new-feature.sh +25 -19
  60. package/templates/skills/_shared/references/subagent-types.md +7 -18
  61. package/templates/skills/assess/SKILL.md +5 -1
  62. package/templates/skills/exec/SKILL.md +61 -7
  63. package/templates/skills/fullsolve/SKILL.md +127 -21
  64. package/templates/skills/loop/SKILL.md +56 -11
  65. package/templates/skills/merger/SKILL.md +98 -10
  66. package/templates/skills/qa/SKILL.md +59 -6
  67. package/templates/skills/release/SKILL.md +79 -0
  68. package/templates/skills/spec/SKILL.md +31 -15
  69. package/templates/skills/spec/references/recommended-workflow.md +14 -1
  70. package/templates/skills/testgen/SKILL.md +23 -6
  71. package/templates/agents/sequant-explorer.md +0 -24
@@ -77,12 +77,17 @@ RIGHT: "Spec complete. Proceeding to exec..." [invokes /exec immediately]
77
77
  ├─────────────────────────────────────────────────────────────┤
78
78
  │ │
79
79
  │ ┌─────────┐ │
80
- │ │ SPEC │ Plan implementation, extract AC
80
+ │ │ SPEC │ Plan implementation, extract AC (main repo)
81
81
  │ └────┬────┘ │
82
82
  │ │ │
83
83
  │ ▼ │
84
84
  │ ┌─────────┐ │
85
- │ │ EXEC Implement in feature worktree
85
+ │ │WORKTREE Create it here; export the resolved path
86
+ │ └────┬────┘ │
87
+ │ │ │
88
+ │ ▼ │
89
+ │ ┌─────────┐ │
90
+ │ │ EXEC │ Verify the path, then implement inside it │
86
91
  │ └────┬────┘ │
87
92
  │ │ │
88
93
  │ ▼ │
@@ -166,7 +171,7 @@ This skill acts as an **orchestrator** and sets environment variables for child
166
171
 
167
172
  **Child skills detect orchestration via `SEQUANT_ORCHESTRATOR` and adjust behavior:**
168
173
  - `/spec`: Runs normally (first phase, no prior context)
169
- - `/exec`: Skips worktree creation, uses provided path
174
+ - `/exec`: Skips worktree creation, but still verifies the provided path before using it
170
175
  - `/test`: Skips issue fetch, trusts orchestrator context
171
176
  - `/qa`: Skips pre-flight sync, defers GitHub updates
172
177
  - `/loop`: Uses provided worktree, defers GitHub updates
@@ -250,9 +255,30 @@ Before creating any files, check if they already exist:
250
255
 
251
256
  **If work already exists:** Skip to the appropriate phase (e.g., if implementation is done, go to Phase 3 or 4).
252
257
 
253
- ### 0.3 Acquire Concurrency Lock (#625)
258
+ ### 0.3 Acquire Concurrency Locks (#625, #901)
259
+
260
+ **Before invoking `/spec`**, claim the per-issue concurrency lock.
261
+
262
+ **Phase 0 always runs — including on a resumed run.** Smart Resumption (above) chooses only *which phase comes next after Phase 0*; it never skips Phase 0 itself. A resumed session that skipped the acquire below would hold neither lock while doing exactly the work the locks exist to protect, and would then run the release contract against a lock it never took.
254
263
 
255
- **Before invoking `/spec`**, claim the per-issue concurrency lock. This prevents a second session (another `/fullsolve`, an `npx sequant run`, or another `/fullsolve` in a different window) from racing on the same issue and producing zero-diff exec failures.
264
+ **Declare the issue for the guard.** `pre-tool.sh` decides whether *you* are the checkout's holder by `sessionId` when both sides have one, then by `SEQUANT_ISSUE`, and finally by the session→issue binding it records for itself.
265
+
266
+ ```bash
267
+ export SEQUANT_ISSUE=<issue-number>
268
+ ```
269
+
270
+ **You do not need that export to be recognized as the holder (#906).** `PreToolUse` runs *outside and before* your command's shell, so nothing a skill bash block exports is visible to it — not even an export prepended to the same block as the guarded command. Instead the hook watches for `locks checkout acquire --issue=<N>` and records the binding itself, keyed on the session id, which is the one identity that survives the shell boundary killing the acquiring PID. The `export` above still helps a parent process that launched you with it; it is not what unblocks you.
271
+
272
+ Consequences worth knowing:
273
+
274
+ - After the acquire in this phase, branch-mutating git **in the main checkout is allowed for you** and still refused for every other session.
275
+ - `git -C "$SEQUANT_WORKTREE" …` is exempt regardless, and is what every phase after 1.5 should be using anyway.
276
+ - Path-restore (`git checkout -- <path>`) is exempt whether or not the path is quoted.
277
+ - If the hook never observed your acquire (it was disabled, or the lock was taken by another tool), you are treated as a non-holder — release and re-acquire so the binding is recorded.
278
+
279
+ **What this lock covers — and what it does not (#901).** The per-issue lock is keyed on the *issue number*. It prevents a second session from working on **the same issue** — another `/fullsolve <same-issue>`, an `npx sequant run <same-issue>`, or the same issue in a different window — and producing zero-diff exec failures.
280
+
281
+ It is **not** a general concurrency guarantee. Two sessions working *different* issues take different lock files and never contend, even when they share one working tree. `git checkout`, `switch`, `reset`, `rebase`, `merge` and `cherry-pick` are global to a checkout, so the per-issue lock says nothing about them. That contention is covered by the separate **checkout lock** below.
256
282
 
257
283
  ```bash
258
284
  # Acquire lock for this issue. --skip-pid-check is required: the shell that
@@ -267,11 +293,26 @@ if ! npx sequant locks acquire <issue-number> \
267
293
  fi
268
294
  ```
269
295
 
270
- **Release contract:** Phase 5.5 releases the lock on the happy path. On ANY halt/abort branch (spec failure, exec exhausted, qa loop exhausted with AC_NOT_MET, unrecoverable error, stagnation halt), you MUST run `npx sequant locks release <issue-number> || true` **before** printing the halt message. The explicit release calls below cover the known branches; if you add a new abort path, add a release call there too.
296
+ **Checkout lock (#901).** Claim the working tree as well, so a session on a *different* issue cannot run branch-mutating git in the same checkout. `pre-tool.sh` enforces this: a foreign session's `git checkout`/`switch`/`reset`/`rebase`/`merge`/`cherry-pick` in the main checkout is refused with a message naming the holder and its issue.
297
+
298
+ ```bash
299
+ # Claim the shared working tree. Skip when you will work entirely inside a
300
+ # feature worktree (`git -C <worktree> ...` is never blocked).
301
+ npx sequant locks checkout acquire \
302
+ --issue=<issue-number> \
303
+ --command="/fullsolve <issue-number>" \
304
+ --skip-pid-check || true
305
+ ```
306
+
307
+ Release it alongside the per-issue lock: `npx sequant locks checkout release --issue=<issue-number> || true`. **`--issue` is mandatory** (#906) — it is what proves you are the holder. `--skip-pid-check` means the acquiring shell's PID is already dead, so PID identity is unavailable and a release without `--issue` is refused, not merely ineffective. Stale recovery is therefore age-based only for this lock: the 6h `SEQUANT_SKILL_LOCK_TTL_MS` and the 24h `SEQUANT_MAX_LOCK_AGE_MS` ceiling, *not* same-host dead-PID recovery, which `--skip-pid-check` disables by definition. An abandoned holder still cannot wedge the checkout permanently.
308
+
309
+ **Release contract:** Phase 5.5 releases both locks on the happy path. On ANY branch that **exits the workflow without reaching Phase 5** — spec failure, exec iterations exhausted, unrecoverable error — you MUST run `npx sequant locks release <issue-number> || true` and `npx sequant locks checkout release --issue=<issue-number> || true` **before** printing the halt message. The explicit release calls below cover the known branches; if you add a new early-exit path, add a release call there too.
310
+
311
+ **Do NOT release at a branch that continues to Phase 5.** QA-loop exhaustion and the stagnation halt both fall through to PR creation, which still runs git in this tree — releasing there would leave Phase 5 unprotected.
271
312
 
272
313
  **Backstop:** If a release is somehow missed, stale recovery clears the lock after 6h on the same host (`SEQUANT_SKILL_LOCK_TTL_MS` overrides). The user can also force-clear via `npx sequant locks clear <issue-number>`.
273
314
 
274
- **Orchestrator/MCP mode:** When `SEQUANT_ORCHESTRATOR` is set, `locks acquire` and `locks release` are no-ops (exit 0, no file touched). Safe to call unconditionally.
315
+ **Orchestrator/MCP mode:** When `SEQUANT_ORCHESTRATOR` is set, `locks acquire`, `locks release` and every `locks checkout` action are no-ops (exit 0, no file touched), and the `pre-tool.sh` checkout guard stands down. Safe to call unconditionally.
275
316
 
276
317
  ## Phase 1: Planning (SPEC)
277
318
 
@@ -290,20 +331,20 @@ The `/spec` skill will:
290
331
  - Extract acceptance criteria (AC-1, AC-2, etc.)
291
332
  - Create implementation plan (3-7 steps)
292
333
  - Post plan comment to the issue
293
- - Create feature worktree
334
+
335
+ `/spec` plans in the main repository and does **not** create a worktree — that
336
+ is this skill's job, in Phase 1.5 below.
294
337
 
295
338
  ### 1.2 Capture Spec Output
296
339
 
297
340
  After `/spec` completes, extract and store:
298
341
  - **AC Checklist:** List of acceptance criteria for tracking
299
- - **Worktree Path:** Location for subsequent phases
300
342
  - **Recommended Phases:** Whether `/test` is needed (UI features)
301
343
 
302
344
  ```markdown
303
345
  ## Spec Output Captured
304
346
 
305
347
  **Issue:** #<N>
306
- **Worktree:** ../worktrees/feature/<issue-number>-*/
307
348
  **AC Count:** <N> items
308
349
  **Needs Testing:** Yes/No (based on labels)
309
350
  ```
@@ -319,6 +360,7 @@ If `/spec` fails:
319
360
  ```bash
320
361
  # Release before halting — see Phase 0.3 release contract.
321
362
  npx sequant locks release <issue-number> || true
363
+ npx sequant locks checkout release --issue=<issue-number> || true
322
364
  ```
323
365
 
324
366
  ```markdown
@@ -333,7 +375,55 @@ Workflow halted. Fix the issue and re-run `/fullsolve <issue-number>`.
333
375
  **State after Phase 1:**
334
376
  - AC checklist defined
335
377
  - Implementation plan created (and posted to GitHub)
336
- - Feature worktree ready
378
+ - Still in the main repository — no worktree exists yet
379
+
380
+ **→ IMMEDIATELY proceed to Phase 1.5 (do not wait for user input)**
381
+
382
+ ## Phase 1.5: Create the Feature Worktree
383
+
384
+ <!-- BEGIN: worktree-creation (#899) -->
385
+
386
+ **This skill creates the worktree. Nothing upstream does it.** Every phase from
387
+ here on runs inside it, and `SEQUANT_WORKTREE` is what tells the child skills
388
+ where "here" is — so it must hold a **resolved absolute path**, never a glob.
389
+
390
+ ```bash
391
+ # 1. Create it. Idempotent: exits 0 with "Worktree already exists" on re-entry
392
+ # (resumed session, retried phase), so this is safe to run unconditionally.
393
+ ./scripts/new-feature.sh <issue-number>
394
+
395
+ # 2. Resolve the real path. `sequant worktree resolve` reads
396
+ # `git worktree list` in THIS repository and selects on the branch, so it
397
+ # can never return a sibling project's worktree — `../worktrees/` is one
398
+ # flat namespace shared by every repo under the same parent.
399
+ SEQUANT_WORKTREE="$(npx sequant worktree resolve <issue-number>)" || {
400
+ echo "❌ Could not resolve a worktree for #<issue-number> after creating one."
401
+ npx sequant locks release <issue-number> || true
402
+ npx sequant locks checkout release --issue=<issue-number> || true
403
+ exit 1
404
+ }
405
+ export SEQUANT_WORKTREE
406
+ echo "Worktree: $SEQUANT_WORKTREE"
407
+ ```
408
+
409
+ **Never substitute a glob for this step.** `../worktrees/feature/<issue>-*/`
410
+ is not a path: unquoted it may not expand at all, and where it does expand it
411
+ matches on the directory slug, which is shared across repositories and can
412
+ drift from its own branch after a rename.
413
+
414
+ **If `new-feature.sh` fails** (dirty tree, branch conflict), release the lock
415
+ and halt — do not proceed into Phase 2 in the main checkout:
416
+
417
+ ```bash
418
+ npx sequant locks release <issue-number> || true
419
+ npx sequant locks checkout release --issue=<issue-number> || true
420
+ ```
421
+
422
+ <!-- END: worktree-creation (#899) -->
423
+
424
+ **State after Phase 1.5:**
425
+ - Feature worktree created and verified to belong to this repository
426
+ - `SEQUANT_WORKTREE` exported as a resolved absolute path
337
427
 
338
428
  **→ IMMEDIATELY proceed to Phase 2 (do not wait for user input)**
339
429
 
@@ -363,12 +453,13 @@ Set environment variables before invoking `/exec` so it can optimize its behavio
363
453
  export SEQUANT_ORCHESTRATOR=fullsolve
364
454
  export SEQUANT_PHASE=exec
365
455
  export SEQUANT_ISSUE=<issue-number>
366
- export SEQUANT_WORKTREE=../worktrees/feature/<issue-number>-*/
456
+ # Absolute path resolved in Phase 1.5 — never a glob.
457
+ export SEQUANT_WORKTREE="$SEQUANT_WORKTREE"
367
458
  ```
368
459
 
369
460
  When `/exec` detects `SEQUANT_ORCHESTRATOR`, it:
370
- - Skips worktree creation (already done by `/spec`)
371
- - Uses the provided worktree path
461
+ - Skips worktree creation (already done in Phase 1.5)
462
+ - Verifies the provided path with `npx sequant worktree verify` before using it
372
463
  - Defers GitHub comment updates to orchestrator
373
464
 
374
465
  ### 2.3 Handle Exec Failures
@@ -393,6 +484,7 @@ while exec_iteration < MAX_EXEC_ITERATIONS:
393
484
  ```bash
394
485
  # Release before halting — see Phase 0.3 release contract.
395
486
  npx sequant locks release <issue-number> || true
487
+ npx sequant locks checkout release --issue=<issue-number> || true
396
488
  ```
397
489
 
398
490
  ```markdown
@@ -465,7 +557,8 @@ The `/test` skill will:
465
557
  export SEQUANT_ORCHESTRATOR=fullsolve
466
558
  export SEQUANT_PHASE=test
467
559
  export SEQUANT_ISSUE=<issue-number>
468
- export SEQUANT_WORKTREE=../worktrees/feature/<issue-number>-*/
560
+ # Absolute path resolved in Phase 1.5 — never a glob.
561
+ export SEQUANT_WORKTREE="$SEQUANT_WORKTREE"
469
562
  ```
470
563
 
471
564
  When `/test` detects `SEQUANT_ORCHESTRATOR`, it:
@@ -534,7 +627,8 @@ The `/qa` skill will:
534
627
  export SEQUANT_ORCHESTRATOR=fullsolve
535
628
  export SEQUANT_PHASE=qa
536
629
  export SEQUANT_ISSUE=<issue-number>
537
- export SEQUANT_WORKTREE=../worktrees/feature/<issue-number>-*/
630
+ # Absolute path resolved in Phase 1.5 — never a glob.
631
+ export SEQUANT_WORKTREE="$SEQUANT_WORKTREE"
538
632
  ```
539
633
 
540
634
  When `/qa` detects `SEQUANT_ORCHESTRATOR`, it:
@@ -632,6 +726,11 @@ echo "Current branch: $CURRENT_BRANCH"
632
726
 
633
727
  # HARD GATE: Must be on a feature branch, not main/master
634
728
  if [[ "$CURRENT_BRANCH" == "main" || "$CURRENT_BRANCH" == "master" ]]; then
729
+ # Release before halting — see Phase 0.3 release contract. This gate fires
730
+ # later than any other exit path, so a leak here wedges the tree for the
731
+ # longest (#906).
732
+ npx sequant locks release <issue-number> || true
733
+ npx sequant locks checkout release --issue=<issue-number> || true
635
734
  echo "❌ ERROR: On $CURRENT_BRANCH — commits must NOT land on main."
636
735
  echo " Fix: git checkout feature/<issue-number>-* or create a new branch."
637
736
  exit 1
@@ -699,16 +798,19 @@ npx sequant doctor
699
798
 
700
799
  If any command fails, fix immediately on main before continuing. This catches issues like ESM compatibility bugs that unit tests may miss.
701
800
 
702
- ### 5.5 Release Concurrency Lock (#625)
801
+ ### 5.5 Release Concurrency Locks (#625, #901)
703
802
 
704
- After the PR is created (or earlier if the workflow exits gracefully), release the lock so other sessions can claim it:
803
+ After the PR is created (or earlier if the workflow exits gracefully), release both locks so other sessions can claim them:
705
804
 
706
805
  ```bash
707
806
  npx sequant locks release <issue-number> || true
807
+ npx sequant locks checkout release --issue=<issue-number> || true
708
808
  ```
709
809
 
710
810
  `|| true` is intentional — release is idempotent; the lock may already have been cleared (orchestrator mode, age-based recovery, or manual `locks clear`). The exit code is informational only.
711
811
 
812
+ **But read it if you are debugging (#906).** A non-zero exit from `locks checkout release` no longer means only "nothing was held". It now also means **"held, but not by you"** — a refusal, printed with the holder's issue and the `clear --force` recovery command. If you see that after your own run, the usual cause is a missing or wrong `--issue`, not a stale lock.
813
+
712
814
  ## Iteration Tracking
713
815
 
714
816
  Track iterations to prevent infinite loops:
@@ -807,13 +909,16 @@ Ready for human review and merge.
807
909
 
808
910
  ## Error Recovery
809
911
 
810
- **Concurrency lock cleanup (#625, applies to every abort path below):**
912
+ **Concurrency lock cleanup (#625, #901, applies to every abort path below):**
811
913
 
812
914
  ```bash
813
915
  npx sequant locks release <issue-number> || true
916
+ npx sequant locks checkout release --issue=<issue-number> || true
814
917
  ```
815
918
 
816
- Run this BEFORE printing the halt/exit message in any of the branches below. The release call is idempotent (no-op when nothing is held, no-op in orchestrator mode), so calling it unconditionally on every error path is safe.
919
+ Run this BEFORE printing the halt/exit message in any branch below that **exits the workflow**. The release call is idempotent (no-op when nothing is held, no-op in orchestrator mode), so calling it unconditionally on a genuine exit path is safe.
920
+
921
+ Do **not** run it on the two branches below that continue to Phase 5 — "test loop exhausted" and "QA loop exhausted" both go on to create a PR, and releasing there hands the tree away while this session is still using it.
817
922
 
818
923
  **If spec fails:**
819
924
  - Check issue exists and is readable
@@ -930,7 +1035,8 @@ As an orchestrator, `/fullsolve` must:
930
1035
  export SEQUANT_ORCHESTRATOR=fullsolve
931
1036
  export SEQUANT_PHASE=<current-phase>
932
1037
  export SEQUANT_ISSUE=<issue-number>
933
- export SEQUANT_WORKTREE=<worktree-path>
1038
+ # Resolved in Phase 1.5 via `sequant worktree resolve` — absolute, never a glob.
1039
+ export SEQUANT_WORKTREE="$SEQUANT_WORKTREE"
934
1040
  ```
935
1041
 
936
1042
  2. **State tracking** is handled automatically by the orchestrator runtime when `SEQUANT_ORCHESTRATOR` is set. Child skills defer state management to the orchestrator to avoid duplicate updates.
@@ -19,6 +19,7 @@ allowed-tools:
19
19
  - mcp__context7__* # Library documentation - falls back to web search if unavailable
20
20
  - Bash(gh issue view:*)
21
21
  - Bash(gh issue comment:*)
22
+ - Bash(npx sequant worktree:*)
22
23
  - Bash(npm test:*)
23
24
  - Bash(npm run build:*)
24
25
  - Bash(git diff:*)
@@ -217,22 +218,64 @@ When the marker is absent this is a byte-identical pass-through, so unmarked QA
217
218
  ### Step 4: Locate Feature Worktree
218
219
 
219
220
  **If orchestrated (SEQUANT_WORKTREE is set):**
220
- - Use the provided worktree path directly: `cd $SEQUANT_WORKTREE`
221
- - Skip the lookup steps below
222
221
 
223
- **If standalone:**
222
+ <!-- BEGIN: worktree-existence-guard (#899) -->
223
+
224
+ **Verify the path before you use it. Never `cd` into it unchecked.** The value
225
+ can name a worktree that was never created, or one belonging to a *different
226
+ repository* — `../worktrees/` is one flat namespace shared by every repo under
227
+ the same parent, and issue numbers are per-repo. A bare `cd` fails silently,
228
+ and this skill **writes**: unguarded, it applies fixes and commits them in the
229
+ main checkout, on whatever branch happens to be there.
224
230
 
225
- Find the worktree for this issue:
226
231
  ```bash
227
- git worktree list | grep -E "feature.*<issue-number>" || true
232
+ npx sequant worktree verify "$SEQUANT_WORKTREE" --issue <issue-number> || {
233
+ echo "❌ HALT: SEQUANT_WORKTREE is not a usable worktree of this repository."
234
+ exit 1
235
+ }
236
+ cd "$SEQUANT_WORKTREE"
228
237
  ```
229
238
 
230
- Or check:
239
+ `verify` exits non-zero with one of these named errors. **Every one of them is
240
+ a halt** — report it and stop; never edit or commit from the current directory
241
+ as a fallback:
242
+
243
+ | Error | Meaning |
244
+ |-------|---------|
245
+ | `SEQUANT_WORKTREE_NOT_FOUND` | Path is empty, an unexpanded glob, or not an existing directory |
246
+ | `SEQUANT_WORKTREE_FOREIGN` | Real directory, but not a worktree of *this* repository (another project's, or stale) |
247
+ | `SEQUANT_WORKTREE_ISSUE_MISMATCH` | A worktree of this repo, but its branch belongs to a different issue |
248
+
249
+ Once verify passes, skip the lookup steps below.
250
+
251
+ <!-- END: worktree-existence-guard (#899) -->
252
+
253
+ **If standalone:**
254
+
255
+ <!-- BEGIN: worktree-standalone-lookup (#899) -->
256
+
257
+ Resolve the worktree through git, not the filesystem:
258
+
231
259
  ```bash
232
- ls ../worktrees/feature/<issue-number>-*/
260
+ WORKTREE="$(npx sequant worktree resolve <issue-number>)" || {
261
+ echo "❌ HALT: no worktree for #<issue-number> in this repository."
262
+ exit 1
263
+ }
264
+ cd "$WORKTREE"
233
265
  ```
234
266
 
235
- Navigate to the worktree directory for making fixes.
267
+ `sequant worktree resolve` reads `git worktree list` in the current repository
268
+ — which reports only *this* repo's worktrees — and selects on the **branch**
269
+ git reports, not the directory name.
270
+
271
+ **Do not glob `../worktrees/feature/<issue-number>-*`, and do not grep
272
+ `git worktree list` for the issue number.** The first matches across sibling
273
+ repositories, which share that directory; the second matches the printed path,
274
+ so it keys on the directory slug — and a slug can drift from its own branch
275
+ after a rename. Because this skill commits, landing in the wrong tree is
276
+ destructive rather than merely wrong.
277
+
278
+ <!-- END: worktree-standalone-lookup (#899) -->
236
279
 
237
280
  ### Step 5: Fix Identified Issues
238
281
 
@@ -511,12 +554,14 @@ Warning: No specific issues found in log.
511
554
  Recommend running /qa <N> for fresh assessment.
512
555
  ```
513
556
 
514
- **If worktree not found:**
557
+ **If worktree not found** (`sequant worktree resolve` exited non-zero):
515
558
  ```
516
- Error: Feature worktree not found for issue #<N>
517
- Expected: ../worktrees/feature/<N>-*/
559
+ Error: no worktree in this repository has a branch for issue #<N>
518
560
  Please run /exec <N> first to create the worktree.
519
561
  ```
562
+ Report the resolver's own message (`WORKTREE_NOT_FOUND` or
563
+ `WORKTREE_AMBIGUOUS`) rather than naming a filesystem path — the worktree is
564
+ identified by its branch, not by a directory under `../worktrees/`.
520
565
 
521
566
  ## Configuration
522
567
 
@@ -6,6 +6,7 @@ metadata:
6
6
  author: sequant
7
7
  version: "1.0"
8
8
  allowed-tools:
9
+ - Bash(npx sequant worktree:*)
9
10
  - Bash(git:*)
10
11
  - Bash(gh pr:*)
11
12
  - Bash(gh issue:*)
@@ -99,6 +100,54 @@ When processing multiple issues, determine the execution mode for validation che
99
100
 
100
101
  ## Workflow
101
102
 
103
+ ### Acquire the Checkout Lock (REQUIRED)
104
+
105
+ `/merger` runs branch-mutating git **in the main checkout**: `git checkout main`
106
+ for the baseline in Step 0, then `git checkout -b integrate/…` and `git merge` on
107
+ the integration path (Step 5), plus the mid-workflow `git checkout main` in
108
+ Steps 6–7. Those verbs are global to the working tree, so before the first one
109
+ you must claim the checkout lock (#901), exactly as `/fullsolve` does in its
110
+ Phase 0.3. Without it a session on another issue can interleave a `git checkout`
111
+ and land a merge on the wrong HEAD; and because the guard refuses a *non-holder*,
112
+ an unparticipating `/merger` is itself refused when someone else holds the tree.
113
+
114
+ `/merger` operates on several issues, so identify the holder by the **first issue
115
+ number** passed to it and use that same `<first-issue>` on the acquire and on
116
+ *every* release below — acquire and release must name the same holder or the
117
+ release is refused against your own lock (#906). Substitute the literal number
118
+ (e.g. `--issue=10` for `/merger 10 12`); a shell variable is invisible to the
119
+ `pre-tool.sh` guard, which reads the command text before the shell expands it.
120
+
121
+ ```bash
122
+ # Claim the shared working tree before the first branch-mutating verb.
123
+ npx sequant locks checkout acquire \
124
+ --issue=<first-issue> \
125
+ --command="/merger <issue-numbers>" \
126
+ --skip-pid-check || true
127
+ # Belt-and-suspenders for a parent that launched us with it; the guard's real
128
+ # binding comes from watching the acquire command above (#906).
129
+ export SEQUANT_ISSUE=<first-issue>
130
+ ```
131
+
132
+ **`--issue` is mandatory** (#906): it is what proves you are the holder on
133
+ release, since the acquiring shell's PID is gone by the next block
134
+ (`--skip-pid-check`). Stale recovery is therefore age-based only (the 6h
135
+ `SEQUANT_SKILL_LOCK_TTL_MS` and the 24h `SEQUANT_MAX_LOCK_AGE_MS` ceiling), not
136
+ same-host dead-PID recovery.
137
+
138
+ **Release contract:** release the checkout lock —
139
+ `npx sequant locks checkout release --issue=<first-issue> || true` — on **every**
140
+ path that exits `/merger`: happy-path completion (Step 8), the regression-gate
141
+ halt (Step 7), and any error/abort in "Error Handling". Do **NOT** release on a
142
+ branch that *continues* to a later branch-mutating verb (the #906 distinction):
143
+ the integration `git merge` in Step 5 and the `git checkout main` in Steps 6–7
144
+ run under the lock and must stay protected until the run ends.
145
+
146
+ **Orchestrator/MCP mode:** when `SEQUANT_ORCHESTRATOR` is set, every `locks
147
+ checkout` action is a no-op (exit 0, no file touched) and the `pre-tool.sh`
148
+ checkout guard stands down, so the acquire/release calls are safe to run
149
+ unconditionally.
150
+
102
151
  ### Step 0: Baseline Capture (REQUIRED)
103
152
 
104
153
  **Purpose:** Capture build error count and test pass/fail counts on main **before** any merge, so post-merge results can be compared to detect regressions.
@@ -157,8 +206,9 @@ fi
157
206
  For each issue specified:
158
207
 
159
208
  ```bash
160
- # Find the worktree for the issue
161
- git worktree list --porcelain | grep -A2 "feature/$ISSUE" || true
209
+ # Find the worktree for the issue. Resolve by branch (#899/#904) — grepping
210
+ # for "feature/$ISSUE" is a prefix match, so ISSUE=89 also hits feature/899-*.
211
+ worktree_path=$(npx sequant worktree resolve "$ISSUE") || echo "No worktree for #$ISSUE"
162
212
 
163
213
  # Check PR status
164
214
  gh pr list --head "feature/$ISSUE-*" --json number,state,title
@@ -222,10 +272,16 @@ gh pr merge <PR_NUMBER> --squash
222
272
 
223
273
  # Only clean up worktree AFTER merge succeeds
224
274
  # If merge fails, the worktree is preserved so work isn't lost
225
- worktree_path=$(git worktree list | grep "feature/$ISSUE" | awk '{print $1}' || true)
226
- if [[ -n "$worktree_path" ]]; then
275
+ #
276
+ # Resolve by BRANCH, never by grepping the printed path (#899/#904): the path
277
+ # line contains the directory slug, so `grep "feature/$ISSUE"` with ISSUE=89
278
+ # also matches `feature/899-...` — and the next line force-removes whatever it
279
+ # matched. `worktree resolve` matches the branch's issue number exactly, and
280
+ # exits non-zero rather than guessing when nothing (or more than one) matches.
281
+ if worktree_path=$(npx sequant worktree resolve "$ISSUE"); then
282
+ branch=$(git -C "$worktree_path" rev-parse --abbrev-ref HEAD)
227
283
  git worktree remove "$worktree_path" --force
228
- git branch -D "feature/$ISSUE-"* 2>/dev/null || true
284
+ git branch -D "$branch" 2>/dev/null || true
229
285
  fi
230
286
 
231
287
  # Delete remote branch (previously handled by --delete-branch)
@@ -285,17 +341,23 @@ Read(file_path=".sequant/state.json")
285
341
  **After each successful merge, ensure the worktree is removed:**
286
342
 
287
343
  ```bash
288
- # Find and remove worktree for the issue
289
- worktree_path=$(git worktree list | grep "feature/$ISSUE" | awk '{print $1}' || true)
290
- if [[ -n "$worktree_path" ]]; then
344
+ # Find and remove worktree for the issue.
345
+ # Resolve by branch, not by grepping the printed path see the note in the
346
+ # clean-merge block above. This one force-removes, so a slug collision here
347
+ # destroys an unrelated issue's worktree.
348
+ if worktree_path=$(npx sequant worktree resolve "$ISSUE"); then
291
349
  echo "Removing worktree: $worktree_path"
292
350
  git worktree remove "$worktree_path" --force
293
351
  else
294
352
  echo "No worktree found for #$ISSUE (already cleaned up)"
295
353
  fi
296
354
 
297
- # Verify worktree removal
298
- git worktree list | grep -q "feature/$ISSUE" && echo "WARNING: Worktree still exists" || echo "✅ Worktree removed"
355
+ # Verify worktree removal — resolve must now fail for this issue.
356
+ if npx sequant worktree resolve "$ISSUE" >/dev/null 2>&1; then
357
+ echo "WARNING: Worktree still exists"
358
+ else
359
+ echo "✅ Worktree removed"
360
+ fi
299
361
  ```
300
362
 
301
363
  **Why this matters:** Leftover worktrees waste disk space and can cause confusion when re-running `sequant run` on the same issues. The state guard (#305) prevents re-execution, but the worktree should still be cleaned up.
@@ -387,6 +449,9 @@ if [[ "$REGRESSION_DETECTED" == "true" ]]; then
387
449
  echo "⚠️ REGRESSION DETECTED but --force flag set. Proceeding with merge."
388
450
  echo "⚠️ Acknowledgment: Merging despite $BUILD_DELTA new build error(s) and $TEST_FAIL_DELTA new test failure(s)."
389
451
  else
452
+ # Terminal halt — release the checkout lock before stopping (#901/#906).
453
+ # This branch does NOT continue to Step 8, so the release lives here.
454
+ npx sequant locks checkout release --issue=<first-issue> || true
390
455
  echo "❌ REGRESSION DETECTED — merge is blocked."
391
456
  echo ""
392
457
  echo "New build errors: $BUILD_DELTA"
@@ -460,6 +525,21 @@ git diff HEAD~1 --stat
460
525
 
461
526
  **Important:** Regression detection does NOT trigger automatic rollback. It reports for human decision-making.
462
527
 
528
+ ### Step 8: Release the Checkout Lock (REQUIRED)
529
+
530
+ The merge is complete. Release the working-tree lock so other sessions can run
531
+ branch-mutating git again (#901). Run this on the happy path — a successful merge
532
+ and smoketest, or the `--force` proceed past a regression. It is the counterpart
533
+ to the acquire in "Acquire the Checkout Lock" above.
534
+
535
+ ```bash
536
+ npx sequant locks checkout release --issue=<first-issue> || true
537
+ ```
538
+
539
+ On a *failure* exit instead, the release is done at the halt site — the
540
+ regression gate (Step 7) or "Error Handling" — never both, so exactly one release
541
+ runs per invocation.
542
+
463
543
  ## Dependency Detection
464
544
 
465
545
  Parse dependencies from issue body or comments:
@@ -579,6 +659,14 @@ gh pr view <PR_NUMBER> --json baseRefName,body | \
579
659
 
580
660
  ## Error Handling
581
661
 
662
+ **Before halting on any failure below, release the checkout lock** (#901/#906) —
663
+ the acquire in "Acquire the Checkout Lock" claimed the tree, and every failure
664
+ here is a terminal exit that must hand it back before stopping:
665
+
666
+ ```bash
667
+ npx sequant locks checkout release --issue=<first-issue> || true
668
+ ```
669
+
582
670
  **If validation fails:**
583
671
  - Report which issues failed validation
584
672
  - Suggest corrective actions
@@ -6,6 +6,7 @@ metadata:
6
6
  author: sequant
7
7
  version: "1.0"
8
8
  allowed-tools:
9
+ - Bash(npx sequant worktree:*)
9
10
  - Bash(npm test:*)
10
11
  - Bash(npm run build:*)
11
12
  - Bash(git diff:*)
@@ -362,15 +363,64 @@ Include in QA output when branch is stale:
362
363
  **QA Phase:** Review code in the feature worktree.
363
364
 
364
365
  **If orchestrated (SEQUANT_WORKTREE is set):**
365
- - Use the provided worktree path directly: `cd $SEQUANT_WORKTREE`
366
- - Skip step 1 below (worktree location provided by orchestrator)
366
+
367
+ <!-- BEGIN: worktree-existence-guard (#899) -->
368
+
369
+ **Verify the path before you use it. Never `cd` into it unchecked.** The value
370
+ can name a worktree that was never created, or one belonging to a *different
371
+ repository* — `../worktrees/` is one flat namespace shared by every repo under
372
+ the same parent, and issue numbers are per-repo. A bare `cd` fails silently and
373
+ leaves you reviewing the main checkout while reporting on the feature branch.
374
+
375
+ ```bash
376
+ npx sequant worktree verify "$SEQUANT_WORKTREE" --issue <issue-number> || {
377
+ echo "❌ HALT: SEQUANT_WORKTREE is not a usable worktree of this repository."
378
+ exit 1
379
+ }
380
+ cd "$SEQUANT_WORKTREE"
381
+ ```
382
+
383
+ `verify` exits non-zero with one of these named errors. **Every one of them is
384
+ a halt** — report it and stop; do not fall back to reviewing the current
385
+ directory, and do not emit a verdict for a tree you did not actually read:
386
+
387
+ | Error | Meaning |
388
+ |-------|---------|
389
+ | `SEQUANT_WORKTREE_NOT_FOUND` | Path is empty, an unexpanded glob, or not an existing directory |
390
+ | `SEQUANT_WORKTREE_FOREIGN` | Real directory, but not a worktree of *this* repository (another project's, or stale) |
391
+ | `SEQUANT_WORKTREE_ISSUE_MISMATCH` | A worktree of this repo, but its branch belongs to a different issue |
392
+
393
+ Once verify passes, skip step 1 below (worktree location provided by orchestrator).
394
+
395
+ <!-- END: worktree-existence-guard (#899) -->
367
396
 
368
397
  **If standalone:**
369
398
 
399
+ <!-- BEGIN: worktree-standalone-lookup (#899) -->
400
+
370
401
  1. **Locate the worktree:**
371
- - The worktree should already exist from the execution phase (`/exec`)
372
- - Find the worktree: `git worktree list` or check `../worktrees/feature/` for directories matching the issue number
373
- - The worktree path will be: `../worktrees/feature/<issue-number>-<issue-title-slug>/`
402
+
403
+ The worktree should already exist from the execution phase (`/exec`).
404
+ Resolve it through git, not the filesystem:
405
+
406
+ ```bash
407
+ WORKTREE="$(npx sequant worktree resolve <issue-number>)" || {
408
+ echo "No worktree for #<issue-number> in this repository."
409
+ # Fall through to the "No Worktree Found" section below.
410
+ }
411
+ cd "$WORKTREE"
412
+ ```
413
+
414
+ `sequant worktree resolve` reads `git worktree list` in the current
415
+ repository — which reports only *this* repo's worktrees — and selects on the
416
+ **branch** git reports, not the directory name.
417
+
418
+ **Do not glob `../worktrees/feature/<issue-number>-*` to find it.** That
419
+ directory is shared by every sibling repository, so a name match may belong
420
+ to another project; and a directory slug can drift from its own branch after
421
+ a rename, so it proves nothing about which branch you would review.
422
+
423
+ <!-- END: worktree-standalone-lookup (#899) -->
374
424
 
375
425
  2. **Check implementation status:**
376
426
  - Navigate to worktree: `cd <worktree-path>`
@@ -425,7 +475,10 @@ If no feature worktree exists (work was done directly on main):
425
475
 
426
476
  ```bash
427
477
  # 1. Check for worktree (indicates work may have started)
428
- worktree_path=$(git worktree list | grep -i "<issue-number>" | awk '{print $1}' | head -1 || true)
478
+ # Resolve by branch, not by grepping the printed path (#899/#904): the path
479
+ # line carries the directory slug, so a bare number match is both cross-issue
480
+ # (89 matches 899-...) and slug-based rather than branch-based.
481
+ worktree_path=$(npx sequant worktree resolve "<issue-number>" 2>/dev/null || true)
429
482
 
430
483
  # 2. Check for commits on feature branch (vs main) — include ALL file types
431
484
  commits_exist=$(git log --oneline origin/main..HEAD 2>/dev/null | head -1)