sequant 2.11.0 → 2.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.
Files changed (75) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +47 -3
  4. package/dist/dashboard/server.js +4 -0
  5. package/dist/marketplace/external_plugins/sequant/.claude-plugin/plugin.json +1 -1
  6. package/dist/marketplace/external_plugins/sequant/.mcp.json +1 -1
  7. package/dist/marketplace/external_plugins/sequant/README.md +9 -0
  8. package/dist/marketplace/external_plugins/sequant/hooks/pre-tool.sh +331 -12
  9. package/dist/marketplace/external_plugins/sequant/skills/_shared/references/subagent-types.md +7 -18
  10. package/dist/marketplace/external_plugins/sequant/skills/assess/SKILL.md +5 -1
  11. package/dist/marketplace/external_plugins/sequant/skills/exec/SKILL.md +62 -8
  12. package/dist/marketplace/external_plugins/sequant/skills/fullsolve/SKILL.md +187 -28
  13. package/dist/marketplace/external_plugins/sequant/skills/loop/SKILL.md +127 -23
  14. package/dist/marketplace/external_plugins/sequant/skills/merger/SKILL.md +130 -13
  15. package/dist/marketplace/external_plugins/sequant/skills/qa/SKILL.md +306 -8
  16. package/dist/marketplace/external_plugins/sequant/skills/release/SKILL.md +79 -0
  17. package/dist/marketplace/external_plugins/sequant/skills/spec/SKILL.md +42 -20
  18. package/dist/marketplace/external_plugins/sequant/skills/spec/references/recommended-workflow.md +14 -1
  19. package/dist/marketplace/external_plugins/sequant/skills/test/SKILL.md +1 -1
  20. package/dist/marketplace/external_plugins/sequant/skills/testgen/SKILL.md +23 -6
  21. package/dist/src/commands/doctor.js +20 -18
  22. package/dist/src/commands/ready.js +7 -1
  23. package/dist/src/commands/status.js +4 -0
  24. package/dist/src/lib/ac-linter.js +26 -0
  25. package/dist/src/lib/ac-parser.d.ts +40 -0
  26. package/dist/src/lib/ac-parser.js +202 -16
  27. package/dist/src/lib/markdown-fence.d.ts +24 -0
  28. package/dist/src/lib/markdown-fence.js +51 -0
  29. package/dist/src/lib/mcp-config.d.ts +24 -0
  30. package/dist/src/lib/mcp-config.js +51 -0
  31. package/dist/src/lib/scope/analyzer.d.ts +4 -0
  32. package/dist/src/lib/scope/analyzer.js +7 -1
  33. package/dist/src/lib/settings.d.ts +73 -14
  34. package/dist/src/lib/settings.js +45 -3
  35. package/dist/src/lib/system.d.ts +7 -3
  36. package/dist/src/lib/system.js +7 -3
  37. package/dist/src/lib/test-tautology-detector.js +50 -3
  38. package/dist/src/lib/workflow/batch-executor.d.ts +20 -1
  39. package/dist/src/lib/workflow/batch-executor.js +81 -6
  40. package/dist/src/lib/workflow/config-resolver.d.ts +30 -2
  41. package/dist/src/lib/workflow/config-resolver.js +59 -2
  42. package/dist/src/lib/workflow/drivers/agent-driver.d.ts +14 -0
  43. package/dist/src/lib/workflow/drivers/claude-code.js +36 -4
  44. package/dist/src/lib/workflow/metrics-schema.d.ts +10 -1
  45. package/dist/src/lib/workflow/metrics-schema.js +13 -1
  46. package/dist/src/lib/workflow/metrics-writer.d.ts +3 -1
  47. package/dist/src/lib/workflow/mutation-marker.d.ts +86 -0
  48. package/dist/src/lib/workflow/mutation-marker.js +97 -0
  49. package/dist/src/lib/workflow/phase-executor.d.ts +17 -0
  50. package/dist/src/lib/workflow/phase-executor.js +60 -6
  51. package/dist/src/lib/workflow/qa-gaps-marker.d.ts +38 -0
  52. package/dist/src/lib/workflow/qa-gaps-marker.js +66 -0
  53. package/dist/src/lib/workflow/ready-gate.d.ts +25 -1
  54. package/dist/src/lib/workflow/ready-gate.js +81 -11
  55. package/dist/src/lib/workflow/reconcile.js +4 -2
  56. package/dist/src/lib/workflow/run-log-schema.d.ts +120 -0
  57. package/dist/src/lib/workflow/run-log-schema.js +40 -0
  58. package/dist/src/lib/workflow/run-orchestrator.d.ts +18 -0
  59. package/dist/src/lib/workflow/run-orchestrator.js +38 -2
  60. package/dist/src/lib/workflow/state-cleanup.d.ts +4 -4
  61. package/dist/src/lib/workflow/state-cleanup.js +9 -5
  62. package/dist/src/lib/workflow/state-schema.d.ts +10 -1
  63. package/dist/src/lib/workflow/state-schema.js +13 -1
  64. package/dist/src/lib/workflow/types.d.ts +20 -0
  65. package/dist/src/mcp/tools/run.js +10 -1
  66. package/package.json +13 -12
  67. package/templates/hooks/pre-tool.sh +108 -17
  68. package/templates/memory/constitution.md +112 -45
  69. package/templates/skills/exec/SKILL.md +1 -1
  70. package/templates/skills/fullsolve/SKILL.md +62 -9
  71. package/templates/skills/loop/SKILL.md +71 -12
  72. package/templates/skills/merger/SKILL.md +32 -3
  73. package/templates/skills/qa/SKILL.md +247 -2
  74. package/templates/skills/spec/SKILL.md +11 -5
  75. package/templates/skills/test/SKILL.md +1 -1
@@ -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:*)
@@ -58,7 +59,7 @@ When running as part of an orchestrated workflow (e.g., `sequant run` or `/fulls
58
59
  1. **Use provided worktree** - Work in `SEQUANT_WORKTREE` path directly
59
60
  2. **Use `SEQUANT_ISSUE`** - Skip issue number parsing from invocation
60
61
  3. **Reduce GitHub comment frequency** - Defer updates to orchestrator
61
- 4. **Trust issue context** - Orchestrator has already validated issue
62
+ 4. **Trust embedded context** - when the orchestrator injected a `<!-- SEQUANT_PROMPT_CONTEXT -->` block into this invocation, it is the authoritative QA-findings source; do not re-fetch from GitHub (see Step 1A below)
62
63
 
63
64
  **Behavior when standalone (SEQUANT_ORCHESTRATOR is NOT set):**
64
65
 
@@ -80,10 +81,20 @@ When running as part of an orchestrated workflow (e.g., `sequant run` or `/fulls
80
81
 
81
82
  #### Step 1A: Orchestrated Mode (SEQUANT_ORCHESTRATOR is set)
82
83
 
83
- When `SEQUANT_ORCHESTRATOR` is set, read QA findings from the GitHub issue comments instead of a log file:
84
+ <!-- BEGIN: step-1a-context-source (#960) -->
85
+
86
+ **Check your own invocation first — before fetching anything.** The orchestrator (`ready-gate.ts` or `batch-executor.ts`, via `buildLoopContext`) may already have embedded the QA findings directly into the prompt that invoked this `/loop` run, wrapped in a `<!-- SEQUANT_PROMPT_CONTEXT -->` / `<!-- /SEQUANT_PROMPT_CONTEXT -->` sentinel pair (`getPhasePrompt`, `src/lib/workflow/phase-executor.ts`). When that sentinel is present in your own invocation text:
87
+
88
+ - Treat the text between the markers as the authoritative QA findings (`qa_comment` below).
89
+ - **Do not** fetch `gh issue view` for QA comments — skip straight to "Parsing QA comment" below, using the embedded text in place of the fetched comment.
90
+ - **The embedded block's gap list IS your `recommendations`.** The block is not QA-comment-shaped — the parsing snippets below (verdict grep aside) won't extract anything from it. Read the list under `Gaps to address:` (ready-gate) or `QA Gaps:` (batch-executor) directly as the findings to fix; the `QA Verdict:` line is the verdict.
91
+ - That gap list is already `fixableGaps`-filtered by the orchestrator (`selectFixableGaps` in `batch-executor.ts`, or ready-gate's own filter), so the `document`/`pause_for_human` exclusion in "Excluded Finding Classes" below is redundant for it — it exists for the fetched-comment fallback. But this applies **only to the gap list**: any `Suggestions:` or `Last output:` sections in the block (batch-executor only) are raw, unfiltered context — use them for understanding, never as additional findings to fix.
92
+
93
+ **Only when the sentinel is absent** from your invocation — a standalone-style dispatch, or an orchestrator that doesn't inject `promptContext` — fall back to reading QA findings from the GitHub issue comments instead of a log file:
84
94
 
85
95
  ```bash
86
- # Check if we're in orchestrated mode
96
+ # Fallback: no embedded SEQUANT_PROMPT_CONTEXT sentinel in the invocation.
97
+ # Fetch QA findings from GitHub comments instead.
87
98
  if [[ -n "$SEQUANT_ORCHESTRATOR" ]]; then
88
99
  echo "Orchestrated mode detected (orchestrator: $SEQUANT_ORCHESTRATOR)"
89
100
 
@@ -95,6 +106,8 @@ if [[ -n "$SEQUANT_ORCHESTRATOR" ]]; then
95
106
  fi
96
107
  ```
97
108
 
109
+ <!-- END: step-1a-context-source (#960) -->
110
+
98
111
  **How to identify QA comments:**
99
112
 
100
113
  | Pattern | Meaning |
@@ -102,7 +115,7 @@ fi
102
115
  | `## QA Review for Issue #N` | QA phase comment header |
103
116
  | `### Verdict:` | Contains AC_NOT_MET, AC_MET_BUT_NOT_A_PLUS, etc. |
104
117
  | `### AC Coverage` | Table with MET/NOT_MET/PARTIALLY_MET statuses |
105
- | `### Required Fixes` or `### Recommendations` | Actionable items to fix |
118
+ | `<!-- SEQUANT_QA_GAPS: {...} -->` | Structured findings the primary source for `recommendations` (#937; see below) |
106
119
 
107
120
  **Parsing QA comment:**
108
121
 
@@ -115,14 +128,58 @@ verdict=$(echo "$qa_comment" | grep -oE "Verdict:\s*\w+" | head -1 | awk '{print
115
128
  # Extract NOT_MET AC items
116
129
  not_met_acs=$(echo "$qa_comment" | grep -E "NOT_MET|PARTIALLY_MET" || true)
117
130
 
118
- # Extract recommendations section
119
- recommendations=$(echo "$qa_comment" | sed -n '/### Required Fixes/,/###/p' | head -n -1)
131
+ # #937: prefer the structured SEQUANT_QA_GAPS marker over prose scraping.
132
+ # `### Required Fixes` was removed no QA output template ever emitted that
133
+ # heading, so the old `sed -n '/### Required Fixes/,/###/p'` extraction was
134
+ # dead code that always produced an empty `recommendations`. The marker's
135
+ # payload is an array of objects, so a single node -e does the stripping,
136
+ # matching, AND JSON.parse/filter in one script — instead of hand-rolled
137
+ # jq/awk — mirroring the "do real parsing in TS/JS, keep shell to presence
138
+ # checks" split from the #871 drift-guard lesson (the same taxonomy filter
139
+ # also lives in `selectFixableGaps`, `src/lib/workflow/phase-executor.ts` —
140
+ # this is the shell-side mirror for standalone/orchestrated-comment-scrape
141
+ # mode, not a duplicate contract).
142
+ #
143
+ # Stripping fenced/inline code before matching mirrors `stripMarkdownCode`
144
+ # (src/lib/workflow/phase-detection.ts) — a bare grep/sed would mistake a
145
+ # marker quoted inside a code fence (e.g. this skill's own "QA Log Example"
146
+ # below) for a real one. Latest-wins if more than one marker is present,
147
+ # matching `parseQaGapsMarker`'s semantics.
148
+ recommendations=$(printf '%s' "$qa_comment" | node -e '
149
+ let input = "";
150
+ process.stdin.on("data", (d) => (input += d));
151
+ process.stdin.on("end", () => {
152
+ const stripped = input
153
+ .replace(/`{3,}[\s\S]*?`{3,}|~{3,}[\s\S]*?~{3,}/g, "")
154
+ .replace(/`[^`\n]+`/g, "");
155
+ const matches = [...stripped.matchAll(/<!-- SEQUANT_QA_GAPS: (\{[\s\S]*?\}) -->/g)];
156
+ if (matches.length === 0) return;
157
+ try {
158
+ const payload = JSON.parse(matches[matches.length - 1][1]);
159
+ const findings = Array.isArray(payload.findings) ? payload.findings : [];
160
+ for (const f of findings) {
161
+ if (f.recommendedAction === "document" || f.recommendedAction === "pause_for_human") continue;
162
+ console.log(`- ${f.description}`);
163
+ }
164
+ } catch {
165
+ // Malformed marker JSON — leave recommendations empty, not a crash.
166
+ }
167
+ });
168
+ ' || true)
169
+
170
+ # No marker (QA output predates #937, or emitted an empty findings array
171
+ # with everything filtered) — fall back to the AC-table NOT_MET/PARTIALLY_MET
172
+ # rows already captured above; there is no prose "Required Fixes" section to
173
+ # scrape.
174
+ if [[ -z "$recommendations" ]]; then
175
+ recommendations="$not_met_acs"
176
+ fi
120
177
  ```
121
178
 
122
- **If no QA comment found in orchestrated mode:**
123
- 1. Log a clear error: `"Warning: No QA comment found in issue #N"`
179
+ **If neither an embedded `SEQUANT_PROMPT_CONTEXT` block nor a matching GitHub QA comment is found in orchestrated mode:**
180
+ 1. Log a clear error: `"Warning: No embedded context or QA comment found for issue #N"`
124
181
  2. Fall back to Step 1B (log file) as a recovery mechanism
125
- 3. If log file also doesn't exist, exit with error
182
+ 3. If log file also doesn't exist, exit with error — do not silently report "no actionable issues"; a silent miss here is the exact failure mode this section exists to prevent (#960)
126
183
 
127
184
  #### Step 1B: Standalone Mode (no SEQUANT_ORCHESTRATOR)
128
185
 
@@ -177,13 +234,14 @@ Some QA findings are real but **not fixable by a code change**. Feeding them to
177
234
  | Class | Marker in the QA comment | Why it is not actionable |
178
235
  |-------|--------------------------|--------------------------|
179
236
  | Infra-blocked CI | `<!-- qa:ci-infra-blocked -->` | Every check failed without a runner ever starting (e.g. an Actions spending-limit lockout). The cause is account/infrastructure state; no diff can turn the checks green. See `qa/SKILL.md` § "Infra-Blocked CI Detection". |
237
+ | `document`/`pause_for_human` findings | `<!-- SEQUANT_QA_GAPS: {...} -->`, per-finding `recommendedAction` | Quality/polish gaps deliberately deferred (`document`), or findings needing a human decision the loop can't make on its own (`pause_for_human`) — see #937. Filtered directly out of the `recommendations` extraction above (Step 1A), not by rebinding `qa_comment` — the marker's structure makes a per-finding filter more precise than a text-range delete. |
180
238
 
181
239
  ```bash
182
240
  # Drop the marked findings when QA flagged CI as infra-blocked, so its
183
241
  # NEEDS_VERIFICATION AC items are never mistaken for actionable findings.
184
242
  # Range is EXCLUSIVE of the next `### ` header — a `sed '/marker/,/^### /d'`
185
243
  # range would delete that header too and orphan the following section's
186
- # content (verified: it silently swallows `### Required Fixes`).
244
+ # content (verified against this file's own `### AC Coverage` header).
187
245
  qa_comment_raw="$qa_comment" # keep the original so the cause can be reported verbatim
188
246
 
189
247
  if echo "$qa_comment" | grep -q '<!-- qa:ci-infra-blocked -->'; then
@@ -217,22 +275,64 @@ When the marker is absent this is a byte-identical pass-through, so unmarked QA
217
275
  ### Step 4: Locate Feature Worktree
218
276
 
219
277
  **If orchestrated (SEQUANT_WORKTREE is set):**
220
- - Use the provided worktree path directly: `cd $SEQUANT_WORKTREE`
221
- - Skip the lookup steps below
222
278
 
223
- **If standalone:**
279
+ <!-- BEGIN: worktree-existence-guard (#899) -->
280
+
281
+ **Verify the path before you use it. Never `cd` into it unchecked.** The value
282
+ can name a worktree that was never created, or one belonging to a *different
283
+ repository* — `../worktrees/` is one flat namespace shared by every repo under
284
+ the same parent, and issue numbers are per-repo. A bare `cd` fails silently,
285
+ and this skill **writes**: unguarded, it applies fixes and commits them in the
286
+ main checkout, on whatever branch happens to be there.
224
287
 
225
- Find the worktree for this issue:
226
288
  ```bash
227
- git worktree list | grep -E "feature.*<issue-number>" || true
289
+ npx sequant worktree verify "$SEQUANT_WORKTREE" --issue <issue-number> || {
290
+ echo "❌ HALT: SEQUANT_WORKTREE is not a usable worktree of this repository."
291
+ exit 1
292
+ }
293
+ cd "$SEQUANT_WORKTREE"
228
294
  ```
229
295
 
230
- Or check:
296
+ `verify` exits non-zero with one of these named errors. **Every one of them is
297
+ a halt** — report it and stop; never edit or commit from the current directory
298
+ as a fallback:
299
+
300
+ | Error | Meaning |
301
+ |-------|---------|
302
+ | `SEQUANT_WORKTREE_NOT_FOUND` | Path is empty, an unexpanded glob, or not an existing directory |
303
+ | `SEQUANT_WORKTREE_FOREIGN` | Real directory, but not a worktree of *this* repository (another project's, or stale) |
304
+ | `SEQUANT_WORKTREE_ISSUE_MISMATCH` | A worktree of this repo, but its branch belongs to a different issue |
305
+
306
+ Once verify passes, skip the lookup steps below.
307
+
308
+ <!-- END: worktree-existence-guard (#899) -->
309
+
310
+ **If standalone:**
311
+
312
+ <!-- BEGIN: worktree-standalone-lookup (#899) -->
313
+
314
+ Resolve the worktree through git, not the filesystem:
315
+
231
316
  ```bash
232
- ls ../worktrees/feature/<issue-number>-*/
317
+ WORKTREE="$(npx sequant worktree resolve <issue-number>)" || {
318
+ echo "❌ HALT: no worktree for #<issue-number> in this repository."
319
+ exit 1
320
+ }
321
+ cd "$WORKTREE"
233
322
  ```
234
323
 
235
- Navigate to the worktree directory for making fixes.
324
+ `sequant worktree resolve` reads `git worktree list` in the current repository
325
+ — which reports only *this* repo's worktrees — and selects on the **branch**
326
+ git reports, not the directory name.
327
+
328
+ **Do not glob `../worktrees/feature/<issue-number>-*`, and do not grep
329
+ `git worktree list` for the issue number.** The first matches across sibling
330
+ repositories, which share that directory; the second matches the printed path,
331
+ so it keys on the directory slug — and a slug can drift from its own branch
332
+ after a rename. Because this skill commits, landing in the wrong tree is
333
+ destructive rather than merely wrong.
334
+
335
+ <!-- END: worktree-standalone-lookup (#899) -->
236
336
 
237
337
  ### Step 5: Fix Identified Issues
238
338
 
@@ -470,13 +570,15 @@ For each iteration, output:
470
570
 
471
571
  ### Verdict: AC_NOT_MET
472
572
 
473
- ### Required Fixes
573
+ ### Next Steps
474
574
 
475
575
  1. Complete URL validation in ExternalUrlTab
476
576
  2. Add focal point persistence in updateArticleImage action
577
+
578
+ <!-- SEQUANT_QA_GAPS: {"findings":[{"category":"requirement_gap","evidence":"AC-3 row: PARTIALLY_MET — External URL validation incomplete","description":"Complete URL validation in ExternalUrlTab","recommendedAction":"fix_now","affectedAcs":["AC-3"]},{"category":"requirement_gap","evidence":"AC-4 row: NOT_MET — Focal point not persisted to database","description":"Add focal point persistence in updateArticleImage action","recommendedAction":"fix_now","affectedAcs":["AC-4"]}]} -->
477
579
  ```
478
580
 
479
- **Parsed Output:**
581
+ **Parsed Output** (marker-derived — this is the real emission shape, see §"How to build the marker" in `qa/SKILL.md`):
480
582
  - Last phase: `/qa`
481
583
  - Verdict: `AC_NOT_MET`
482
584
  - Issues to fix:
@@ -511,12 +613,14 @@ Warning: No specific issues found in log.
511
613
  Recommend running /qa <N> for fresh assessment.
512
614
  ```
513
615
 
514
- **If worktree not found:**
616
+ **If worktree not found** (`sequant worktree resolve` exited non-zero):
515
617
  ```
516
- Error: Feature worktree not found for issue #<N>
517
- Expected: ../worktrees/feature/<N>-*/
618
+ Error: no worktree in this repository has a branch for issue #<N>
518
619
  Please run /exec <N> first to create the worktree.
519
620
  ```
621
+ Report the resolver's own message (`WORKTREE_NOT_FOUND` or
622
+ `WORKTREE_AMBIGUOUS`) rather than naming a filesystem path — the worktree is
623
+ identified by its branch, not by a directory under `../worktrees/`.
520
624
 
521
625
  ## Configuration
522
626
 
@@ -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,47 @@ 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
+
543
+ <!-- BEGIN: named-set-boundary (#961) -->
544
+ ## Named-Set Boundary (REQUIRED)
545
+
546
+ `/merger <issue-numbers>` is the user's consent to merge **exactly those
547
+ issues** — invoking it is the explicit authorization, the same way `sequant
548
+ merge`/`sequant merge --watch` are explicit and human-triggered (#958's
549
+ Non-Goals). That consent does not extend past the named set: never let
550
+ Dependency Detection or Stacked PR Detection below pull an**other** issue's PR
551
+ into `gh pr merge` just because it happens to unblock or precede a named one.
552
+
553
+ **Rule:** if merge ordering requires a PR whose issue number is **not** in the
554
+ invocation's arg list, halt before merging anything and report it — do not
555
+ widen the merge silently, and do not treat the stacked-PR "Continue anyway?
556
+ (y/N)" confirmation as license to include an unnamed predecessor. Offer the
557
+ widened command for the user to re-run:
558
+
559
+ ```text
560
+ ❌ Out-of-named-set dependency
561
+ #101 depends on #100, but #100 was not in your /merger invocation.
562
+ Halting before any merge.
563
+ To include it: /merger 100 101
564
+ ```
565
+
566
+ This rule governs both sections below.
567
+ <!-- END: named-set-boundary (#961) -->
568
+
463
569
  ## Dependency Detection
464
570
 
465
571
  Parse dependencies from issue body or comments:
@@ -477,7 +583,9 @@ Labels: depends-on/10
477
583
  gh issue view <issue> --json body,labels | jq '.body, .labels[].name'
478
584
  ```
479
585
 
480
- If dependencies found, enforce merge order.
586
+ If dependencies found, enforce merge order **among the named set**. If a
587
+ dependency's issue number falls outside the named set, apply the Named-Set
588
+ Boundary rule above: halt and report instead of merging it.
481
589
 
482
590
  ### Stacked PR Detection (#605)
483
591
 
@@ -498,8 +606,9 @@ gh pr view <PR_NUMBER> --json baseRefName,body | \
498
606
 
499
607
  1. Extract the stack manifest from each PR's body (`Part of stack: #100 → #101 (this) → #102`).
500
608
  2. Treat the order in the manifest as the merge order — earlier entries land first.
501
- 3. If the user requests an out-of-order merge (e.g. `/merger 102 100 101` for the stack above), **warn before proceeding** and recommend the manifest order.
502
- 4. GitHub auto-updates the dependent PR's base when its predecessor merges, so once the order is correct no manual rebasing is needed.
609
+ 3. If **every** predecessor is in the named set but requested out of order (e.g. `/merger 102 100 101` for the stack above), **warn before proceeding** and recommend the manifest order.
610
+ 4. If a predecessor is **not** in the named set at all (e.g. `/merger 102` alone for that stack), the Named-Set Boundary rule applies: halt and report — do not fall through to the "Continue anyway? (y/N)" prompt, since that prompt is for reordering named issues, not for merging an unnamed one.
611
+ 5. GitHub auto-updates the dependent PR's base when its predecessor merges, so once the order is correct no manual rebasing is needed.
503
612
 
504
613
  **Warning template:**
505
614
 
@@ -579,6 +688,14 @@ gh pr view <PR_NUMBER> --json baseRefName,body | \
579
688
 
580
689
  ## Error Handling
581
690
 
691
+ **Before halting on any failure below, release the checkout lock** (#901/#906) —
692
+ the acquire in "Acquire the Checkout Lock" claimed the tree, and every failure
693
+ here is a terminal exit that must hand it back before stopping:
694
+
695
+ ```bash
696
+ npx sequant locks checkout release --issue=<first-issue> || true
697
+ ```
698
+
582
699
  **If validation fails:**
583
700
  - Report which issues failed validation
584
701
  - Suggest corrective actions