sequant 2.11.0 → 2.12.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +13 -0
- package/dist/marketplace/external_plugins/sequant/.claude-plugin/plugin.json +1 -1
- package/dist/marketplace/external_plugins/sequant/.mcp.json +1 -1
- package/dist/marketplace/external_plugins/sequant/hooks/pre-tool.sh +331 -12
- package/dist/marketplace/external_plugins/sequant/skills/_shared/references/subagent-types.md +7 -18
- package/dist/marketplace/external_plugins/sequant/skills/assess/SKILL.md +5 -1
- package/dist/marketplace/external_plugins/sequant/skills/exec/SKILL.md +62 -8
- package/dist/marketplace/external_plugins/sequant/skills/fullsolve/SKILL.md +187 -28
- package/dist/marketplace/external_plugins/sequant/skills/loop/SKILL.md +127 -23
- package/dist/marketplace/external_plugins/sequant/skills/merger/SKILL.md +130 -13
- package/dist/marketplace/external_plugins/sequant/skills/qa/SKILL.md +306 -8
- package/dist/marketplace/external_plugins/sequant/skills/release/SKILL.md +79 -0
- package/dist/marketplace/external_plugins/sequant/skills/spec/SKILL.md +40 -20
- package/dist/marketplace/external_plugins/sequant/skills/spec/references/recommended-workflow.md +14 -1
- package/dist/marketplace/external_plugins/sequant/skills/test/SKILL.md +1 -1
- package/dist/marketplace/external_plugins/sequant/skills/testgen/SKILL.md +23 -6
- package/dist/src/commands/doctor.js +20 -18
- package/dist/src/commands/ready.js +4 -0
- package/dist/src/lib/ac-linter.js +26 -0
- package/dist/src/lib/ac-parser.d.ts +40 -0
- package/dist/src/lib/ac-parser.js +202 -16
- package/dist/src/lib/markdown-fence.d.ts +24 -0
- package/dist/src/lib/markdown-fence.js +51 -0
- package/dist/src/lib/mcp-config.d.ts +24 -0
- package/dist/src/lib/mcp-config.js +51 -0
- package/dist/src/lib/scope/analyzer.d.ts +4 -0
- package/dist/src/lib/scope/analyzer.js +7 -1
- package/dist/src/lib/settings.d.ts +38 -1
- package/dist/src/lib/settings.js +14 -0
- package/dist/src/lib/system.d.ts +7 -3
- package/dist/src/lib/system.js +7 -3
- package/dist/src/lib/test-tautology-detector.js +50 -3
- package/dist/src/lib/workflow/batch-executor.d.ts +20 -1
- package/dist/src/lib/workflow/batch-executor.js +77 -5
- package/dist/src/lib/workflow/config-resolver.js +1 -0
- package/dist/src/lib/workflow/drivers/agent-driver.d.ts +7 -0
- package/dist/src/lib/workflow/drivers/claude-code.js +9 -3
- package/dist/src/lib/workflow/mutation-marker.d.ts +86 -0
- package/dist/src/lib/workflow/mutation-marker.js +97 -0
- package/dist/src/lib/workflow/phase-executor.d.ts +17 -0
- package/dist/src/lib/workflow/phase-executor.js +50 -4
- package/dist/src/lib/workflow/qa-gaps-marker.d.ts +38 -0
- package/dist/src/lib/workflow/qa-gaps-marker.js +66 -0
- package/dist/src/lib/workflow/ready-gate.d.ts +25 -1
- package/dist/src/lib/workflow/ready-gate.js +81 -11
- package/dist/src/lib/workflow/run-log-schema.d.ts +120 -0
- package/dist/src/lib/workflow/run-log-schema.js +40 -0
- package/dist/src/lib/workflow/state-schema.d.ts +5 -1
- package/dist/src/lib/workflow/state-schema.js +8 -1
- package/dist/src/lib/workflow/types.d.ts +14 -0
- package/package.json +2 -2
- package/templates/hooks/pre-tool.sh +108 -17
- package/templates/skills/exec/SKILL.md +1 -1
- package/templates/skills/fullsolve/SKILL.md +62 -9
- package/templates/skills/loop/SKILL.md +71 -12
- package/templates/skills/merger/SKILL.md +32 -3
- package/templates/skills/qa/SKILL.md +247 -2
- package/templates/skills/spec/SKILL.md +9 -5
- 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
|
|
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
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
|
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
|
-
#
|
|
119
|
-
|
|
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
|
|
123
|
-
1. Log a clear error: `"Warning: No QA comment found
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
226
|
-
|
|
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 "
|
|
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
|
-
|
|
290
|
-
|
|
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
|
-
|
|
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
|
|
502
|
-
4.
|
|
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
|