sequant 1.16.0 → 1.17.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/plugin.json +1 -1
- package/README.md +6 -1
- package/dist/bin/cli.js +2 -1
- package/dist/src/commands/run.d.ts +6 -0
- package/dist/src/commands/run.js +26 -2
- package/dist/src/lib/upstream/assessment.js +6 -3
- package/dist/src/lib/upstream/relevance.d.ts +5 -0
- package/dist/src/lib/upstream/relevance.js +24 -0
- package/dist/src/lib/upstream/report.js +18 -46
- package/dist/src/lib/upstream/types.d.ts +2 -0
- package/dist/src/lib/workflow/pr-status.d.ts +47 -0
- package/dist/src/lib/workflow/pr-status.js +129 -0
- package/dist/src/lib/workflow/run-reflect.d.ts +32 -0
- package/dist/src/lib/workflow/run-reflect.js +191 -0
- package/dist/src/lib/workflow/state-cleanup.d.ts +79 -0
- package/dist/src/lib/workflow/state-cleanup.js +250 -0
- package/dist/src/lib/workflow/state-rebuild.d.ts +38 -0
- package/dist/src/lib/workflow/state-rebuild.js +140 -0
- package/dist/src/lib/workflow/state-utils.d.ts +14 -162
- package/dist/src/lib/workflow/state-utils.js +10 -677
- package/dist/src/lib/workflow/worktree-discovery.d.ts +61 -0
- package/dist/src/lib/workflow/worktree-discovery.js +229 -0
- package/package.json +4 -5
- package/templates/skills/exec/SKILL.md +2 -2
- package/templates/skills/fullsolve/SKILL.md +18 -4
- package/templates/skills/loop/SKILL.md +1 -1
- package/templates/skills/qa/SKILL.md +6 -6
- package/templates/skills/solve/SKILL.md +28 -6
- package/templates/skills/spec/SKILL.md +16 -15
- package/templates/skills/testgen/SKILL.md +1 -1
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worktree discovery for state bootstrapping
|
|
3
|
+
*
|
|
4
|
+
* @module worktree-discovery
|
|
5
|
+
* @example
|
|
6
|
+
* ```typescript
|
|
7
|
+
* import { discoverUntrackedWorktrees } from './worktree-discovery';
|
|
8
|
+
*
|
|
9
|
+
* // Discover worktrees not yet tracked in state
|
|
10
|
+
* const result = await discoverUntrackedWorktrees({ verbose: true });
|
|
11
|
+
* for (const worktree of result.discovered) {
|
|
12
|
+
* console.log(`Found: #${worktree.issueNumber} - ${worktree.title}`);
|
|
13
|
+
* }
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
import { type Phase } from "./state-schema.js";
|
|
17
|
+
export interface DiscoverOptions {
|
|
18
|
+
/** State file path (default: .sequant/state.json) */
|
|
19
|
+
statePath?: string;
|
|
20
|
+
/** Enable verbose logging */
|
|
21
|
+
verbose?: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface DiscoveredWorktree {
|
|
24
|
+
/** Issue number extracted from branch name */
|
|
25
|
+
issueNumber: number;
|
|
26
|
+
/** Issue title (fetched from GitHub or placeholder) */
|
|
27
|
+
title: string;
|
|
28
|
+
/** Full path to the worktree */
|
|
29
|
+
worktreePath: string;
|
|
30
|
+
/** Branch name */
|
|
31
|
+
branch: string;
|
|
32
|
+
/** Inferred current phase from logs (if available) */
|
|
33
|
+
inferredPhase?: Phase;
|
|
34
|
+
}
|
|
35
|
+
export interface SkippedWorktree {
|
|
36
|
+
/** Path to the worktree */
|
|
37
|
+
path: string;
|
|
38
|
+
/** Reason it was skipped */
|
|
39
|
+
reason: string;
|
|
40
|
+
}
|
|
41
|
+
export interface DiscoverResult {
|
|
42
|
+
/** Whether discovery was successful */
|
|
43
|
+
success: boolean;
|
|
44
|
+
/** Number of worktrees scanned */
|
|
45
|
+
worktreesScanned: number;
|
|
46
|
+
/** Number of worktrees already tracked */
|
|
47
|
+
alreadyTracked: number;
|
|
48
|
+
/** Discovered worktrees not yet in state */
|
|
49
|
+
discovered: DiscoveredWorktree[];
|
|
50
|
+
/** Worktrees that were skipped (not matching pattern, etc.) */
|
|
51
|
+
skipped: SkippedWorktree[];
|
|
52
|
+
/** Error message if failed */
|
|
53
|
+
error?: string;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Discover worktrees that are not yet tracked in state
|
|
57
|
+
*
|
|
58
|
+
* Scans all git worktrees, identifies those with issue-related branch names,
|
|
59
|
+
* and returns information about worktrees not yet in the state file.
|
|
60
|
+
*/
|
|
61
|
+
export declare function discoverUntrackedWorktrees(options?: DiscoverOptions): Promise<DiscoverResult>;
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worktree discovery for state bootstrapping
|
|
3
|
+
*
|
|
4
|
+
* @module worktree-discovery
|
|
5
|
+
* @example
|
|
6
|
+
* ```typescript
|
|
7
|
+
* import { discoverUntrackedWorktrees } from './worktree-discovery';
|
|
8
|
+
*
|
|
9
|
+
* // Discover worktrees not yet tracked in state
|
|
10
|
+
* const result = await discoverUntrackedWorktrees({ verbose: true });
|
|
11
|
+
* for (const worktree of result.discovered) {
|
|
12
|
+
* console.log(`Found: #${worktree.issueNumber} - ${worktree.title}`);
|
|
13
|
+
* }
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
import * as fs from "fs";
|
|
17
|
+
import * as path from "path";
|
|
18
|
+
import { spawnSync } from "child_process";
|
|
19
|
+
import { StateManager } from "./state-manager.js";
|
|
20
|
+
import { RunLogSchema, LOG_PATHS } from "./run-log-schema.js";
|
|
21
|
+
/**
|
|
22
|
+
* Parse issue number from a branch name
|
|
23
|
+
*
|
|
24
|
+
* Supports patterns:
|
|
25
|
+
* - feature/<number>-<slug>
|
|
26
|
+
* - issue-<number>
|
|
27
|
+
* - <number>-<slug>
|
|
28
|
+
*/
|
|
29
|
+
function parseIssueNumberFromBranch(branch) {
|
|
30
|
+
// Pattern: feature/123-description or feature/123
|
|
31
|
+
const featureMatch = branch.match(/^feature\/(\d+)(?:-|$)/);
|
|
32
|
+
if (featureMatch) {
|
|
33
|
+
return parseInt(featureMatch[1], 10);
|
|
34
|
+
}
|
|
35
|
+
// Pattern: issue-123
|
|
36
|
+
const issueMatch = branch.match(/^issue-(\d+)$/);
|
|
37
|
+
if (issueMatch) {
|
|
38
|
+
return parseInt(issueMatch[1], 10);
|
|
39
|
+
}
|
|
40
|
+
// Pattern: 123-description (bare number prefix)
|
|
41
|
+
const bareMatch = branch.match(/^(\d+)-/);
|
|
42
|
+
if (bareMatch) {
|
|
43
|
+
return parseInt(bareMatch[1], 10);
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Fetch issue title from GitHub using gh CLI
|
|
49
|
+
*
|
|
50
|
+
* Returns placeholder if gh is not available or fetch fails.
|
|
51
|
+
*/
|
|
52
|
+
function fetchIssueTitle(issueNumber) {
|
|
53
|
+
try {
|
|
54
|
+
const result = spawnSync("gh", ["issue", "view", String(issueNumber), "--json", "title", "-q", ".title"], { stdio: "pipe", timeout: 10000 });
|
|
55
|
+
if (result.status === 0 && result.stdout) {
|
|
56
|
+
const title = result.stdout.toString().trim();
|
|
57
|
+
if (title) {
|
|
58
|
+
return title;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// gh not available or error - use placeholder
|
|
64
|
+
}
|
|
65
|
+
return `(title unavailable for #${issueNumber})`;
|
|
66
|
+
}
|
|
67
|
+
function getWorktreeDetails() {
|
|
68
|
+
const result = spawnSync("git", ["worktree", "list", "--porcelain"], {
|
|
69
|
+
stdio: "pipe",
|
|
70
|
+
});
|
|
71
|
+
if (result.status !== 0) {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
const output = result.stdout.toString();
|
|
75
|
+
const worktrees = [];
|
|
76
|
+
let current = {};
|
|
77
|
+
for (const line of output.split("\n")) {
|
|
78
|
+
if (line.startsWith("worktree ")) {
|
|
79
|
+
// Start of new worktree entry
|
|
80
|
+
if (current.path) {
|
|
81
|
+
worktrees.push(current);
|
|
82
|
+
}
|
|
83
|
+
current = { path: line.substring(9) };
|
|
84
|
+
}
|
|
85
|
+
else if (line.startsWith("HEAD ")) {
|
|
86
|
+
current.head = line.substring(5);
|
|
87
|
+
}
|
|
88
|
+
else if (line.startsWith("branch refs/heads/")) {
|
|
89
|
+
current.branch = line.substring(18);
|
|
90
|
+
}
|
|
91
|
+
else if (line === "" && current.path) {
|
|
92
|
+
// End of entry
|
|
93
|
+
worktrees.push(current);
|
|
94
|
+
current = {};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Don't forget the last entry
|
|
98
|
+
if (current.path && current.branch) {
|
|
99
|
+
worktrees.push(current);
|
|
100
|
+
}
|
|
101
|
+
return worktrees;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Infer the current phase for an issue by checking logs
|
|
105
|
+
*/
|
|
106
|
+
function inferPhaseFromLogs(issueNumber) {
|
|
107
|
+
const logPath = LOG_PATHS.project;
|
|
108
|
+
if (!fs.existsSync(logPath)) {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
const files = fs.readdirSync(logPath).filter((f) => f.endsWith(".json"));
|
|
113
|
+
// Sort by timestamp (newest first)
|
|
114
|
+
files.sort().reverse();
|
|
115
|
+
for (const file of files) {
|
|
116
|
+
try {
|
|
117
|
+
const content = fs.readFileSync(path.join(logPath, file), "utf-8");
|
|
118
|
+
const logData = JSON.parse(content);
|
|
119
|
+
const log = RunLogSchema.safeParse(logData);
|
|
120
|
+
if (!log.success)
|
|
121
|
+
continue;
|
|
122
|
+
// Find this issue in the log
|
|
123
|
+
const issueLog = log.data.issues.find((i) => i.issueNumber === issueNumber);
|
|
124
|
+
if (issueLog && issueLog.phases.length > 0) {
|
|
125
|
+
// Return the last executed phase
|
|
126
|
+
const lastPhase = issueLog.phases[issueLog.phases.length - 1];
|
|
127
|
+
return lastPhase.phase;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
return undefined;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Discover worktrees that are not yet tracked in state
|
|
142
|
+
*
|
|
143
|
+
* Scans all git worktrees, identifies those with issue-related branch names,
|
|
144
|
+
* and returns information about worktrees not yet in the state file.
|
|
145
|
+
*/
|
|
146
|
+
export async function discoverUntrackedWorktrees(options = {}) {
|
|
147
|
+
try {
|
|
148
|
+
const worktrees = getWorktreeDetails();
|
|
149
|
+
const discovered = [];
|
|
150
|
+
const skipped = [];
|
|
151
|
+
let alreadyTracked = 0;
|
|
152
|
+
// Get existing state
|
|
153
|
+
const manager = new StateManager({
|
|
154
|
+
statePath: options.statePath,
|
|
155
|
+
verbose: options.verbose,
|
|
156
|
+
});
|
|
157
|
+
const state = await manager.getState();
|
|
158
|
+
const trackedIssues = new Set(Object.keys(state.issues).map((n) => parseInt(n, 10)));
|
|
159
|
+
for (const worktree of worktrees) {
|
|
160
|
+
// Skip if no branch (detached HEAD)
|
|
161
|
+
if (!worktree.branch) {
|
|
162
|
+
skipped.push({
|
|
163
|
+
path: worktree.path,
|
|
164
|
+
reason: "detached HEAD (no branch)",
|
|
165
|
+
});
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
// Skip main/master branches
|
|
169
|
+
if (worktree.branch === "main" || worktree.branch === "master") {
|
|
170
|
+
skipped.push({
|
|
171
|
+
path: worktree.path,
|
|
172
|
+
reason: "main/master branch (not a feature worktree)",
|
|
173
|
+
});
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
// Try to parse issue number from branch
|
|
177
|
+
const issueNumber = parseIssueNumberFromBranch(worktree.branch);
|
|
178
|
+
if (issueNumber === null) {
|
|
179
|
+
skipped.push({
|
|
180
|
+
path: worktree.path,
|
|
181
|
+
reason: `branch name doesn't match issue pattern: ${worktree.branch}`,
|
|
182
|
+
});
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
// Check if already tracked
|
|
186
|
+
if (trackedIssues.has(issueNumber)) {
|
|
187
|
+
alreadyTracked++;
|
|
188
|
+
if (options.verbose) {
|
|
189
|
+
console.log(` Already tracked: #${issueNumber} (${worktree.branch})`);
|
|
190
|
+
}
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
// Fetch title from GitHub
|
|
194
|
+
if (options.verbose) {
|
|
195
|
+
console.log(` Fetching title for #${issueNumber}...`);
|
|
196
|
+
}
|
|
197
|
+
const title = fetchIssueTitle(issueNumber);
|
|
198
|
+
// Try to infer phase from logs
|
|
199
|
+
const inferredPhase = inferPhaseFromLogs(issueNumber);
|
|
200
|
+
discovered.push({
|
|
201
|
+
issueNumber,
|
|
202
|
+
title,
|
|
203
|
+
worktreePath: worktree.path,
|
|
204
|
+
branch: worktree.branch,
|
|
205
|
+
inferredPhase,
|
|
206
|
+
});
|
|
207
|
+
if (options.verbose) {
|
|
208
|
+
console.log(` Discovered: #${issueNumber} - ${title}${inferredPhase ? ` (phase: ${inferredPhase})` : ""}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
success: true,
|
|
213
|
+
worktreesScanned: worktrees.length,
|
|
214
|
+
alreadyTracked,
|
|
215
|
+
discovered,
|
|
216
|
+
skipped,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
return {
|
|
221
|
+
success: false,
|
|
222
|
+
worktreesScanned: 0,
|
|
223
|
+
alreadyTracked: 0,
|
|
224
|
+
discovered: [],
|
|
225
|
+
skipped: [],
|
|
226
|
+
error: String(error),
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sequant",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.17.0",
|
|
4
4
|
"description": "Quantize your development workflow - Sequential AI phases with quality gates",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"bugs": {
|
|
51
51
|
"url": "https://github.com/sequant-io/sequant/issues"
|
|
52
52
|
},
|
|
53
|
-
"homepage": "https://
|
|
53
|
+
"homepage": "https://sequant.io",
|
|
54
54
|
"engines": {
|
|
55
55
|
"node": ">=18.0.0"
|
|
56
56
|
},
|
|
@@ -62,9 +62,9 @@
|
|
|
62
62
|
"chokidar": "^5.0.0",
|
|
63
63
|
"cli-table3": "^0.6.5",
|
|
64
64
|
"commander": "^12.1.0",
|
|
65
|
-
"diff": "^
|
|
65
|
+
"diff": "^8.0.3",
|
|
66
66
|
"gradient-string": "^3.0.0",
|
|
67
|
-
"hono": "^4.
|
|
67
|
+
"hono": "^4.12.1",
|
|
68
68
|
"inquirer": "^12.3.2",
|
|
69
69
|
"open": "^11.0.0",
|
|
70
70
|
"ora": "^8.2.0",
|
|
@@ -73,7 +73,6 @@
|
|
|
73
73
|
},
|
|
74
74
|
"devDependencies": {
|
|
75
75
|
"@eslint/js": "^9.39.2",
|
|
76
|
-
"@types/diff": "^7.0.0",
|
|
77
76
|
"@types/gradient-string": "^1.1.6",
|
|
78
77
|
"@types/inquirer": "^9.0.7",
|
|
79
78
|
"@types/node": "^22.10.5",
|
|
@@ -64,7 +64,7 @@ When invoked as `/exec`, your job is to:
|
|
|
64
64
|
```bash
|
|
65
65
|
# Check for existing phase markers
|
|
66
66
|
phase_data=$(gh issue view <issue-number> --json comments --jq '[.comments[].body]' | \
|
|
67
|
-
grep -o '{[^}]*}' | grep '"phase"' | tail -1)
|
|
67
|
+
grep -o '{[^}]*}' | grep '"phase"' | tail -1 || true)
|
|
68
68
|
|
|
69
69
|
if [[ -n "$phase_data" ]]; then
|
|
70
70
|
phase=$(echo "$phase_data" | jq -r '.phase')
|
|
@@ -127,7 +127,7 @@ git log --oneline -3 --stat
|
|
|
127
127
|
|
|
128
128
|
# Check for existing PRs or branches for this issue
|
|
129
129
|
gh pr list --search "<issue-number>"
|
|
130
|
-
git branch -a | grep -i "<issue-number>"
|
|
130
|
+
git branch -a | grep -i "<issue-number>" || true
|
|
131
131
|
```
|
|
132
132
|
|
|
133
133
|
**Why this matters:** After context restoration, PRs may have merged, branches may have changed, or work may already be complete. Always verify before creating duplicate work.
|
|
@@ -97,7 +97,7 @@ When invoked as `/fullsolve <issue-number>`, execute the complete issue resoluti
|
|
|
97
97
|
```bash
|
|
98
98
|
# Get all phase markers from issue comments
|
|
99
99
|
comments_json=$(gh issue view <issue-number> --json comments --jq '[.comments[].body]')
|
|
100
|
-
markers=$(echo "$comments_json" | grep -o '{[^}]*}' | grep '"phase"')
|
|
100
|
+
markers=$(echo "$comments_json" | grep -o '{[^}]*}' | grep '"phase"' || true)
|
|
101
101
|
|
|
102
102
|
if [[ -n "$markers" ]]; then
|
|
103
103
|
echo "Phase markers detected:"
|
|
@@ -147,7 +147,7 @@ When posting progress comments after each phase, append the appropriate marker:
|
|
|
147
147
|
git log --oneline -5 --stat
|
|
148
148
|
|
|
149
149
|
# Check for any existing work on this issue
|
|
150
|
-
git branch -a | grep -i "<issue-number>"
|
|
150
|
+
git branch -a | grep -i "<issue-number>" || true
|
|
151
151
|
gh pr list --search "<issue-number>"
|
|
152
152
|
```
|
|
153
153
|
|
|
@@ -345,10 +345,10 @@ while test_iteration < 3:
|
|
|
345
345
|
|
|
346
346
|
```bash
|
|
347
347
|
# Type safety
|
|
348
|
-
git diff main...HEAD | grep -E ":\s*any[,)]|as any"
|
|
348
|
+
git diff main...HEAD | grep -E ":\s*any[,)]|as any" || true
|
|
349
349
|
|
|
350
350
|
# Deleted tests
|
|
351
|
-
git diff main...HEAD --diff-filter=D --name-only | grep -E "\.test\."
|
|
351
|
+
git diff main...HEAD --diff-filter=D --name-only | grep -E "\.test\." || true
|
|
352
352
|
|
|
353
353
|
# Scope check
|
|
354
354
|
git diff main...HEAD --name-only | wc -l
|
|
@@ -636,6 +636,20 @@ For multiple issues, run `/fullsolve` on each sequentially:
|
|
|
636
636
|
|
|
637
637
|
Each issue gets its own worktree, PR, and quality validation.
|
|
638
638
|
|
|
639
|
+
### Post-Batch: Merge Verification
|
|
640
|
+
|
|
641
|
+
After processing a batch, run `sequant merge` to catch cross-issue integration gaps before merging:
|
|
642
|
+
|
|
643
|
+
```bash
|
|
644
|
+
/fullsolve 218
|
|
645
|
+
/fullsolve 219
|
|
646
|
+
/fullsolve 220
|
|
647
|
+
sequant merge --check # Verify no cross-issue conflicts
|
|
648
|
+
/merger 218 219 220 # Merge all issues
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
`sequant merge --check` detects merge conflicts, template mirroring gaps, and file overlaps at zero AI cost. See `docs/reference/merge-command.md`.
|
|
652
|
+
|
|
639
653
|
---
|
|
640
654
|
|
|
641
655
|
## Output Verification
|
|
@@ -46,13 +46,13 @@ When invoked as `/qa`, your job is to:
|
|
|
46
46
|
comments_json=$(gh issue view <issue-number> --json comments --jq '[.comments[].body]')
|
|
47
47
|
exec_completed=$(echo "$comments_json" | \
|
|
48
48
|
grep -o '{[^}]*}' | grep '"phase"' | \
|
|
49
|
-
jq -r 'select(.phase == "exec" and .status == "completed")' 2>/dev/null)
|
|
49
|
+
jq -r 'select(.phase == "exec" and .status == "completed")' 2>/dev/null || true)
|
|
50
50
|
|
|
51
51
|
if [[ -z "$exec_completed" ]]; then
|
|
52
52
|
# Check if any exec marker exists at all
|
|
53
53
|
exec_any=$(echo "$comments_json" | \
|
|
54
54
|
grep -o '{[^}]*}' | grep '"phase"' | \
|
|
55
|
-
jq -r 'select(.phase == "exec")' 2>/dev/null)
|
|
55
|
+
jq -r 'select(.phase == "exec")' 2>/dev/null || true)
|
|
56
56
|
|
|
57
57
|
if [[ -n "$exec_any" ]]; then
|
|
58
58
|
echo "⚠️ Exec phase not completed (status: $(echo "$exec_any" | jq -r '.status')). Run /exec first."
|
|
@@ -177,7 +177,7 @@ If no feature worktree exists (work was done directly on main):
|
|
|
177
177
|
|
|
178
178
|
**Add RLS check if admin files modified:**
|
|
179
179
|
```bash
|
|
180
|
-
admin_modified=$(git diff main...HEAD --name-only | grep -E "^app/admin/" | head -1)
|
|
180
|
+
admin_modified=$(git diff main...HEAD --name-only | grep -E "^app/admin/" | head -1 || true)
|
|
181
181
|
```
|
|
182
182
|
|
|
183
183
|
See [quality-gates.md](references/quality-gates.md) for detailed verdict synthesis.
|
|
@@ -306,10 +306,10 @@ See [quality-gates.md](references/quality-gates.md) for detailed verdict criteri
|
|
|
306
306
|
|
|
307
307
|
```bash
|
|
308
308
|
# Type safety
|
|
309
|
-
type_issues=$(git diff main...HEAD | grep -E ":\s*any[,)]|as any" | wc -l | xargs)
|
|
309
|
+
type_issues=$(git diff main...HEAD | grep -E ":\s*any[,)]|as any" | wc -l | xargs || echo "0")
|
|
310
310
|
|
|
311
311
|
# Deleted tests
|
|
312
|
-
deleted_tests=$(git diff main...HEAD --diff-filter=D --name-only | grep -E "\\.test\\.|\\spec\\." | wc -l | xargs)
|
|
312
|
+
deleted_tests=$(git diff main...HEAD --diff-filter=D --name-only | grep -E "\\.test\\.|\\spec\\." | wc -l | xargs || echo "0")
|
|
313
313
|
|
|
314
314
|
# Scope check
|
|
315
315
|
files_changed=$(git diff main...HEAD --name-only | wc -l | xargs)
|
|
@@ -355,7 +355,7 @@ If verdict is `READY_FOR_MERGE` or `AC_MET_BUT_NOT_A_PLUS`:
|
|
|
355
355
|
|
|
356
356
|
**Detection:**
|
|
357
357
|
```bash
|
|
358
|
-
scripts_changed=$(git diff main...HEAD --name-only | grep "^scripts/" | wc -l | xargs)
|
|
358
|
+
scripts_changed=$(git diff main...HEAD --name-only | grep "^scripts/" | wc -l | xargs || echo "0")
|
|
359
359
|
if [[ $scripts_changed -gt 0 ]]; then
|
|
360
360
|
echo "Script changes detected. Run /verify before READY_FOR_MERGE"
|
|
361
361
|
fi
|
|
@@ -100,10 +100,10 @@ When analyzing issues, check if `--base` flag should be recommended.
|
|
|
100
100
|
|
|
101
101
|
```bash
|
|
102
102
|
# Check for feature branch references in issue body
|
|
103
|
-
gh issue view <issue-number> --json body --jq '.body' | grep -iE "(feature/|branch from|based on|part of.*feature)"
|
|
103
|
+
gh issue view <issue-number> --json body --jq '.body' | grep -iE "(feature/|branch from|based on|part of.*feature)" || true
|
|
104
104
|
|
|
105
105
|
# Check issue labels for feature context
|
|
106
|
-
gh issue view <issue-number> --json labels --jq '.labels[].name' | grep -iE "(dashboard|feature-|epic-)"
|
|
106
|
+
gh issue view <issue-number> --json labels --jq '.labels[].name' | grep -iE "(dashboard|feature-|epic-)" || true
|
|
107
107
|
|
|
108
108
|
# Check if project has defaultBase configured
|
|
109
109
|
# Use the Read tool to check project settings
|
|
@@ -130,13 +130,13 @@ When analyzing multiple issues, determine if `--chain` flag should be recommende
|
|
|
130
130
|
|
|
131
131
|
```bash
|
|
132
132
|
# Check for dependency keywords in issue body
|
|
133
|
-
gh issue view <issue-number> --json body --jq '.body' | grep -iE "(depends on|blocked by|requires|after #|builds on)"
|
|
133
|
+
gh issue view <issue-number> --json body --jq '.body' | grep -iE "(depends on|blocked by|requires|after #|builds on)" || true
|
|
134
134
|
|
|
135
135
|
# Check for sequence labels
|
|
136
|
-
gh issue view <issue-number> --json labels --jq '.labels[].name' | grep -iE "(part-[0-9]|step-[0-9]|phase-[0-9])"
|
|
136
|
+
gh issue view <issue-number> --json labels --jq '.labels[].name' | grep -iE "(part-[0-9]|step-[0-9]|phase-[0-9])" || true
|
|
137
137
|
|
|
138
138
|
# Check for related issue references
|
|
139
|
-
gh issue view <issue-number> --json body --jq '.body' | grep -oE "#[0-9]+"
|
|
139
|
+
gh issue view <issue-number> --json body --jq '.body' | grep -oE "#[0-9]+" || true
|
|
140
140
|
```
|
|
141
141
|
|
|
142
142
|
**Recommend `--chain` when:**
|
|
@@ -201,7 +201,7 @@ Before generating output, check for in-flight work that may conflict:
|
|
|
201
201
|
|
|
202
202
|
```bash
|
|
203
203
|
# List open worktrees
|
|
204
|
-
git worktree list --porcelain 2>/dev/null | grep "^worktree" | cut -d' ' -f2
|
|
204
|
+
git worktree list --porcelain 2>/dev/null | grep "^worktree" | cut -d' ' -f2 || true
|
|
205
205
|
|
|
206
206
|
# For each worktree, get changed files
|
|
207
207
|
git -C <worktree-path> diff --name-only main...HEAD 2>/dev/null
|
|
@@ -477,6 +477,28 @@ npx sequant run 152 --quality-loop --max-iterations 5
|
|
|
477
477
|
npx sequant run 152 --dry-run
|
|
478
478
|
```
|
|
479
479
|
|
|
480
|
+
### Post-Run: Merge Verification
|
|
481
|
+
|
|
482
|
+
After batch execution, run merge checks before merging:
|
|
483
|
+
|
|
484
|
+
```bash
|
|
485
|
+
# Verify feature branches are safe to merge (auto-detects issues from last run)
|
|
486
|
+
npx sequant merge --check
|
|
487
|
+
|
|
488
|
+
# Full scan including residual pattern detection
|
|
489
|
+
npx sequant merge --scan
|
|
490
|
+
|
|
491
|
+
# Post results to each PR
|
|
492
|
+
npx sequant merge --check --post
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
**Recommended workflow:**
|
|
496
|
+
```bash
|
|
497
|
+
npx sequant run 152 153 154 # implement
|
|
498
|
+
npx sequant merge --check # verify cross-issue integration
|
|
499
|
+
/merger 152 153 154 # merge
|
|
500
|
+
```
|
|
501
|
+
|
|
480
502
|
### Custom Base Branch
|
|
481
503
|
|
|
482
504
|
The `--base` flag specifies which branch to create worktrees from:
|
|
@@ -37,7 +37,7 @@ When invoked as `/spec`, your job is to:
|
|
|
37
37
|
```bash
|
|
38
38
|
# Check for existing phase markers
|
|
39
39
|
phase_data=$(gh issue view <issue-number> --json comments --jq '[.comments[].body]' | \
|
|
40
|
-
grep -o '{[^}]*}' | grep '"phase"' | tail -1)
|
|
40
|
+
grep -o '{[^}]*}' | grep '"phase"' | tail -1 || true)
|
|
41
41
|
|
|
42
42
|
if [[ -n "$phase_data" ]]; then
|
|
43
43
|
phase=$(echo "$phase_data" | jq -r '.phase')
|
|
@@ -91,8 +91,8 @@ When called like `/spec <freeform description>`:
|
|
|
91
91
|
```bash
|
|
92
92
|
# Extract AC from issue body and store in state
|
|
93
93
|
npx tsx -e "
|
|
94
|
-
import { extractAcceptanceCriteria } from './src/lib/ac-parser.
|
|
95
|
-
import { StateManager } from './src/lib/workflow/state-manager.
|
|
94
|
+
import { extractAcceptanceCriteria } from './src/lib/ac-parser.ts';
|
|
95
|
+
import { StateManager } from './src/lib/workflow/state-manager.ts';
|
|
96
96
|
|
|
97
97
|
const issueBody = \`<ISSUE_BODY_HERE>\`;
|
|
98
98
|
const issueNumber = <ISSUE_NUMBER>;
|
|
@@ -103,13 +103,14 @@ console.log('Extracted AC:', JSON.stringify(ac, null, 2));
|
|
|
103
103
|
|
|
104
104
|
if (ac.items.length > 0) {
|
|
105
105
|
const manager = new StateManager();
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
106
|
+
(async () => {
|
|
107
|
+
const existing = await manager.getIssueState(issueNumber);
|
|
108
|
+
if (!existing) {
|
|
109
|
+
await manager.initializeIssue(issueNumber, issueTitle);
|
|
110
|
+
}
|
|
111
|
+
await manager.updateAcceptanceCriteria(issueNumber, ac);
|
|
112
|
+
console.log('AC stored in state for issue #' + issueNumber);
|
|
113
|
+
})();
|
|
113
114
|
}
|
|
114
115
|
"
|
|
115
116
|
```
|
|
@@ -136,8 +137,8 @@ The parser supports multiple formats:
|
|
|
136
137
|
```bash
|
|
137
138
|
# Lint AC for quality issues (skip if --skip-ac-lint flag is set)
|
|
138
139
|
npx tsx -e "
|
|
139
|
-
import { parseAcceptanceCriteria } from './src/lib/ac-parser.
|
|
140
|
-
import { lintAcceptanceCriteria, formatACLintResults } from './src/lib/ac-linter.
|
|
140
|
+
import { parseAcceptanceCriteria } from './src/lib/ac-parser.ts';
|
|
141
|
+
import { lintAcceptanceCriteria, formatACLintResults } from './src/lib/ac-linter.ts';
|
|
141
142
|
|
|
142
143
|
const issueBody = \`<ISSUE_BODY_HERE>\`;
|
|
143
144
|
|
|
@@ -230,12 +231,12 @@ Before creating the implementation plan, check if a custom base branch should be
|
|
|
230
231
|
|
|
231
232
|
1. **Check for feature branch references in issue body**:
|
|
232
233
|
```bash
|
|
233
|
-
gh issue view <issue> --json body --jq '.body' | grep -iE "(feature/|branch from|based on|part of.*feature)"
|
|
234
|
+
gh issue view <issue> --json body --jq '.body' | grep -iE "(feature/|branch from|based on|part of.*feature)" || true
|
|
234
235
|
```
|
|
235
236
|
|
|
236
237
|
2. **Check issue labels for feature context**:
|
|
237
238
|
```bash
|
|
238
|
-
gh issue view <issue> --json labels --jq '.labels[].name' | grep -iE "(dashboard|feature-|epic-)"
|
|
239
|
+
gh issue view <issue> --json labels --jq '.labels[].name' | grep -iE "(dashboard|feature-|epic-)" || true
|
|
239
240
|
```
|
|
240
241
|
|
|
241
242
|
3. **Check if project has defaultBase configured**:
|
|
@@ -409,7 +410,7 @@ First, check if a `/solve` comment already exists for this issue:
|
|
|
409
410
|
|
|
410
411
|
```bash
|
|
411
412
|
# Check issue comments for solve workflow
|
|
412
|
-
gh issue view <issue-number> --json comments --jq '.comments[].body' | grep -l "## Solve Workflow for Issues:"
|
|
413
|
+
gh issue view <issue-number> --json comments --jq '.comments[].body' | grep -l "## Solve Workflow for Issues:" || true
|
|
413
414
|
```
|
|
414
415
|
|
|
415
416
|
**If solve comment found:**
|
|
@@ -413,7 +413,7 @@ If an AC has verification method "N/A - Trivial", skip test generation and note
|
|
|
413
413
|
If generating file-based tests (Unit Test, Integration Test), find the worktree:
|
|
414
414
|
|
|
415
415
|
```bash
|
|
416
|
-
git worktree list | grep -E "feature.*<issue-number>"
|
|
416
|
+
git worktree list | grep -E "feature.*<issue-number>" || true
|
|
417
417
|
```
|
|
418
418
|
|
|
419
419
|
Or check:
|