sequant 1.9.0 → 1.10.1
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/README.md +4 -1
- package/dist/bin/cli.js +1 -0
- package/dist/src/commands/run.d.ts +5 -0
- package/dist/src/commands/run.js +201 -24
- package/dist/src/commands/status.d.ts +18 -2
- package/dist/src/commands/status.js +321 -2
- package/dist/src/index.d.ts +9 -0
- package/dist/src/index.js +5 -0
- package/dist/src/lib/settings.d.ts +6 -0
- package/dist/src/lib/workflow/state-hook.d.ts +69 -0
- package/dist/src/lib/workflow/state-hook.js +166 -0
- package/dist/src/lib/workflow/state-manager.d.ts +136 -0
- package/dist/src/lib/workflow/state-manager.js +329 -0
- package/dist/src/lib/workflow/state-schema.d.ts +224 -0
- package/dist/src/lib/workflow/state-schema.js +190 -0
- package/dist/src/lib/workflow/state-utils.d.ts +66 -0
- package/dist/src/lib/workflow/state-utils.js +243 -0
- package/package.json +1 -1
- package/templates/scripts/new-feature.sh +23 -13
package/README.md
CHANGED
|
@@ -84,6 +84,7 @@ Run without Claude Code UI:
|
|
|
84
84
|
npx sequant run 123 # Single issue
|
|
85
85
|
npx sequant run 1 2 3 # Batch (parallel)
|
|
86
86
|
npx sequant run 123 --quality-loop
|
|
87
|
+
npx sequant run 123 --base feature/dashboard # Custom base branch
|
|
87
88
|
```
|
|
88
89
|
|
|
89
90
|
---
|
|
@@ -148,7 +149,8 @@ See [Run Command Options](docs/run-command.md) for advanced usage.
|
|
|
148
149
|
{
|
|
149
150
|
"run": {
|
|
150
151
|
"qualityLoop": false,
|
|
151
|
-
"maxIterations": 3
|
|
152
|
+
"maxIterations": 3,
|
|
153
|
+
"defaultBase": "feature/dashboard" // Optional: custom default base branch
|
|
152
154
|
}
|
|
153
155
|
}
|
|
154
156
|
```
|
|
@@ -173,6 +175,7 @@ See [Customization Guide](docs/customization.md) for all options.
|
|
|
173
175
|
- [Getting Started](docs/getting-started/installation.md)
|
|
174
176
|
- [Workflow Concepts](docs/concepts/workflow-phases.md)
|
|
175
177
|
- [Run Command](docs/run-command.md)
|
|
178
|
+
- [Feature Branch Workflows](docs/feature-branch-workflow.md)
|
|
176
179
|
- [Customization](docs/customization.md)
|
|
177
180
|
- [Troubleshooting](docs/troubleshooting.md)
|
|
178
181
|
|
package/dist/bin/cli.js
CHANGED
|
@@ -99,6 +99,7 @@ program
|
|
|
99
99
|
.option("--testgen", "Run testgen phase after spec")
|
|
100
100
|
.option("--quiet", "Suppress version warnings and non-essential output")
|
|
101
101
|
.option("--chain", "Chain issues: each branches from previous (requires --sequential)")
|
|
102
|
+
.option("--base <branch>", "Base branch for worktree creation (default: main or settings.run.defaultBase)")
|
|
102
103
|
.action(runCommand);
|
|
103
104
|
program
|
|
104
105
|
.command("logs")
|
|
@@ -66,6 +66,11 @@ interface RunOptions {
|
|
|
66
66
|
quiet?: boolean;
|
|
67
67
|
/** Chain issues: each branches from previous (requires --sequential) */
|
|
68
68
|
chain?: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Base branch for worktree creation.
|
|
71
|
+
* Resolution priority: this CLI flag → settings.run.defaultBase → 'main'
|
|
72
|
+
*/
|
|
73
|
+
base?: string;
|
|
69
74
|
}
|
|
70
75
|
/**
|
|
71
76
|
* Main run command
|
package/dist/src/commands/run.js
CHANGED
|
@@ -13,6 +13,7 @@ import { getManifest } from "../lib/manifest.js";
|
|
|
13
13
|
import { getSettings } from "../lib/settings.js";
|
|
14
14
|
import { PM_CONFIG } from "../lib/stacks.js";
|
|
15
15
|
import { LogWriter, createPhaseLogFromTiming, } from "../lib/workflow/log-writer.js";
|
|
16
|
+
import { StateManager, } from "../lib/workflow/state-manager.js";
|
|
16
17
|
import { DEFAULT_PHASES, DEFAULT_CONFIG, } from "../lib/workflow/types.js";
|
|
17
18
|
import { ShutdownManager } from "../lib/shutdown.js";
|
|
18
19
|
import { checkVersionCached, getVersionWarning } from "../lib/version-check.js";
|
|
@@ -106,8 +107,9 @@ export function getWorktreeChangedFiles(worktreePath) {
|
|
|
106
107
|
/**
|
|
107
108
|
* Create or reuse a worktree for an issue
|
|
108
109
|
* @param baseBranch - Optional branch to use as base instead of origin/main (for chain mode)
|
|
110
|
+
* @param chainMode - If true and branch exists, rebase onto baseBranch instead of using as-is
|
|
109
111
|
*/
|
|
110
|
-
async function ensureWorktree(issueNumber, title, verbose, packageManager, baseBranch) {
|
|
112
|
+
async function ensureWorktree(issueNumber, title, verbose, packageManager, baseBranch, chainMode) {
|
|
111
113
|
const gitRoot = getGitRoot();
|
|
112
114
|
if (!gitRoot) {
|
|
113
115
|
console.log(chalk.red(" ❌ Not in a git repository"));
|
|
@@ -128,6 +130,7 @@ async function ensureWorktree(issueNumber, title, verbose, packageManager, baseB
|
|
|
128
130
|
path: existingPath,
|
|
129
131
|
branch,
|
|
130
132
|
existed: true,
|
|
133
|
+
rebased: false,
|
|
131
134
|
};
|
|
132
135
|
}
|
|
133
136
|
// Check if branch exists (but no worktree)
|
|
@@ -137,17 +140,29 @@ async function ensureWorktree(issueNumber, title, verbose, packageManager, baseB
|
|
|
137
140
|
console.log(chalk.gray(` 🌿 Creating worktree for #${issueNumber}...`));
|
|
138
141
|
}
|
|
139
142
|
// Determine the base for the new branch
|
|
140
|
-
|
|
141
|
-
//
|
|
142
|
-
|
|
143
|
+
// For custom base branches, use origin/<branch> if it's a remote-style reference
|
|
144
|
+
// For local branches (chain mode), use as-is
|
|
145
|
+
const isLocalBranch = baseBranch && !baseBranch.startsWith("origin/") && baseBranch !== "main";
|
|
146
|
+
const baseRef = baseBranch
|
|
147
|
+
? isLocalBranch
|
|
148
|
+
? baseBranch
|
|
149
|
+
: baseBranch.startsWith("origin/")
|
|
150
|
+
? baseBranch
|
|
151
|
+
: `origin/${baseBranch}`
|
|
152
|
+
: "origin/main";
|
|
153
|
+
// Fetch the base branch to ensure worktree starts from fresh baseline
|
|
154
|
+
const branchToFetch = baseBranch
|
|
155
|
+
? baseBranch.replace(/^origin\//, "")
|
|
156
|
+
: "main";
|
|
157
|
+
if (!isLocalBranch) {
|
|
143
158
|
if (verbose) {
|
|
144
|
-
console.log(chalk.gray(` 🔄 Fetching latest
|
|
159
|
+
console.log(chalk.gray(` 🔄 Fetching latest ${branchToFetch}...`));
|
|
145
160
|
}
|
|
146
|
-
const fetchResult = spawnSync("git", ["fetch", "origin",
|
|
161
|
+
const fetchResult = spawnSync("git", ["fetch", "origin", branchToFetch], {
|
|
147
162
|
stdio: "pipe",
|
|
148
163
|
});
|
|
149
164
|
if (fetchResult.status !== 0 && verbose) {
|
|
150
|
-
console.log(chalk.yellow(` ⚠️ Could not fetch origin
|
|
165
|
+
console.log(chalk.yellow(` ⚠️ Could not fetch origin/${branchToFetch}, using local state`));
|
|
151
166
|
}
|
|
152
167
|
}
|
|
153
168
|
else if (verbose) {
|
|
@@ -159,11 +174,16 @@ async function ensureWorktree(issueNumber, title, verbose, packageManager, baseB
|
|
|
159
174
|
}
|
|
160
175
|
// Create the worktree
|
|
161
176
|
let createResult;
|
|
177
|
+
let needsRebase = false;
|
|
162
178
|
if (branchExists) {
|
|
163
179
|
// Use existing branch
|
|
164
180
|
createResult = spawnSync("git", ["worktree", "add", worktreePath, branch], {
|
|
165
181
|
stdio: "pipe",
|
|
166
182
|
});
|
|
183
|
+
// In chain mode with existing branch, mark for rebase onto previous chain link
|
|
184
|
+
if (chainMode && baseBranch) {
|
|
185
|
+
needsRebase = true;
|
|
186
|
+
}
|
|
167
187
|
}
|
|
168
188
|
else {
|
|
169
189
|
// Create new branch from base reference (origin/main or previous branch in chain)
|
|
@@ -174,6 +194,39 @@ async function ensureWorktree(issueNumber, title, verbose, packageManager, baseB
|
|
|
174
194
|
console.log(chalk.red(` ❌ Failed to create worktree: ${error}`));
|
|
175
195
|
return null;
|
|
176
196
|
}
|
|
197
|
+
// Rebase existing branch onto chain base if needed
|
|
198
|
+
let rebased = false;
|
|
199
|
+
if (needsRebase) {
|
|
200
|
+
if (verbose) {
|
|
201
|
+
console.log(chalk.gray(` 🔄 Rebasing existing branch onto previous chain link (${baseRef})...`));
|
|
202
|
+
}
|
|
203
|
+
const rebaseResult = spawnSync("git", ["-C", worktreePath, "rebase", baseRef], {
|
|
204
|
+
stdio: "pipe",
|
|
205
|
+
});
|
|
206
|
+
if (rebaseResult.status !== 0) {
|
|
207
|
+
const rebaseError = rebaseResult.stderr.toString();
|
|
208
|
+
// Check if it's a conflict
|
|
209
|
+
if (rebaseError.includes("CONFLICT") ||
|
|
210
|
+
rebaseError.includes("could not apply")) {
|
|
211
|
+
console.log(chalk.yellow(` ⚠️ Rebase conflict detected. Aborting rebase and keeping original branch state.`));
|
|
212
|
+
console.log(chalk.yellow(` ℹ️ Branch ${branch} is not properly chained. Manual rebase may be required.`));
|
|
213
|
+
// Abort the rebase to restore branch state
|
|
214
|
+
spawnSync("git", ["-C", worktreePath, "rebase", "--abort"], {
|
|
215
|
+
stdio: "pipe",
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
console.log(chalk.yellow(` ⚠️ Rebase failed: ${rebaseError.trim()}`));
|
|
220
|
+
console.log(chalk.yellow(` ℹ️ Continuing with branch in its original state.`));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
rebased = true;
|
|
225
|
+
if (verbose) {
|
|
226
|
+
console.log(chalk.green(` ✅ Branch rebased onto ${baseRef}`));
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
177
230
|
// Copy .env.local if it exists
|
|
178
231
|
const envLocalSrc = path.join(gitRoot, ".env.local");
|
|
179
232
|
const envLocalDst = path.join(worktreePath, ".env.local");
|
|
@@ -212,16 +265,19 @@ async function ensureWorktree(issueNumber, title, verbose, packageManager, baseB
|
|
|
212
265
|
path: worktreePath,
|
|
213
266
|
branch,
|
|
214
267
|
existed: false,
|
|
268
|
+
rebased,
|
|
215
269
|
};
|
|
216
270
|
}
|
|
217
271
|
/**
|
|
218
272
|
* Ensure worktrees exist for all issues before execution
|
|
273
|
+
* @param baseBranch - Optional base branch for worktree creation (default: main)
|
|
219
274
|
*/
|
|
220
|
-
async function ensureWorktrees(issues, verbose, packageManager) {
|
|
275
|
+
async function ensureWorktrees(issues, verbose, packageManager, baseBranch) {
|
|
221
276
|
const worktrees = new Map();
|
|
222
|
-
|
|
277
|
+
const baseDisplay = baseBranch || "main";
|
|
278
|
+
console.log(chalk.blue(`\n 📂 Preparing worktrees from ${baseDisplay}...`));
|
|
223
279
|
for (const issue of issues) {
|
|
224
|
-
const worktree = await ensureWorktree(issue.number, issue.title, verbose, packageManager);
|
|
280
|
+
const worktree = await ensureWorktree(issue.number, issue.title, verbose, packageManager, baseBranch, false);
|
|
225
281
|
if (worktree) {
|
|
226
282
|
worktrees.set(issue.number, worktree);
|
|
227
283
|
}
|
|
@@ -236,13 +292,17 @@ async function ensureWorktrees(issues, verbose, packageManager) {
|
|
|
236
292
|
/**
|
|
237
293
|
* Ensure worktrees exist for all issues in chain mode
|
|
238
294
|
* Each issue branches from the previous issue's branch
|
|
295
|
+
* @param baseBranch - Optional starting base branch for the chain (default: main)
|
|
239
296
|
*/
|
|
240
|
-
async function ensureWorktreesChain(issues, verbose, packageManager) {
|
|
297
|
+
async function ensureWorktreesChain(issues, verbose, packageManager, baseBranch) {
|
|
241
298
|
const worktrees = new Map();
|
|
242
|
-
|
|
243
|
-
|
|
299
|
+
const baseDisplay = baseBranch || "main";
|
|
300
|
+
console.log(chalk.blue(`\n 🔗 Preparing chained worktrees from ${baseDisplay}...`));
|
|
301
|
+
// First issue starts from the specified base branch (or main)
|
|
302
|
+
let previousBranch = baseBranch;
|
|
244
303
|
for (const issue of issues) {
|
|
245
|
-
const worktree = await ensureWorktree(issue.number, issue.title, verbose, packageManager, previousBranch)
|
|
304
|
+
const worktree = await ensureWorktree(issue.number, issue.title, verbose, packageManager, previousBranch, // Chain from previous branch (or base branch for first issue)
|
|
305
|
+
true);
|
|
246
306
|
if (worktree) {
|
|
247
307
|
worktrees.set(issue.number, worktree);
|
|
248
308
|
previousBranch = worktree.branch; // Next issue will branch from this
|
|
@@ -255,8 +315,13 @@ async function ensureWorktreesChain(issues, verbose, packageManager) {
|
|
|
255
315
|
}
|
|
256
316
|
const created = Array.from(worktrees.values()).filter((w) => !w.existed).length;
|
|
257
317
|
const reused = Array.from(worktrees.values()).filter((w) => w.existed).length;
|
|
318
|
+
const rebased = Array.from(worktrees.values()).filter((w) => w.rebased).length;
|
|
258
319
|
if (created > 0 || reused > 0) {
|
|
259
|
-
|
|
320
|
+
let msg = ` Chained worktrees: ${created} created, ${reused} reused`;
|
|
321
|
+
if (rebased > 0) {
|
|
322
|
+
msg += `, ${rebased} rebased`;
|
|
323
|
+
}
|
|
324
|
+
console.log(chalk.gray(msg));
|
|
260
325
|
}
|
|
261
326
|
// Show chain structure
|
|
262
327
|
if (worktrees.size > 0) {
|
|
@@ -264,7 +329,7 @@ async function ensureWorktreesChain(issues, verbose, packageManager) {
|
|
|
264
329
|
.filter((i) => worktrees.has(i.number))
|
|
265
330
|
.map((i) => `#${i.number}`)
|
|
266
331
|
.join(" → ");
|
|
267
|
-
console.log(chalk.gray(` Chain:
|
|
332
|
+
console.log(chalk.gray(` Chain: ${baseDisplay} → ${chainOrder}`));
|
|
268
333
|
}
|
|
269
334
|
return worktrees;
|
|
270
335
|
}
|
|
@@ -869,6 +934,8 @@ export async function runCommand(issues, options) {
|
|
|
869
934
|
// Determine if we should auto-detect phases from labels
|
|
870
935
|
const autoDetectPhases = !options.phases && settings.run.autoDetectPhases;
|
|
871
936
|
mergedOptions.autoDetectPhases = autoDetectPhases;
|
|
937
|
+
// Resolve base branch: CLI flag → settings.run.defaultBase → 'main'
|
|
938
|
+
const resolvedBaseBranch = options.base ?? settings.run.defaultBase ?? undefined;
|
|
872
939
|
// Parse issue numbers (or use batch mode)
|
|
873
940
|
let issueNumbers;
|
|
874
941
|
let batches = null;
|
|
@@ -953,6 +1020,12 @@ export async function runCommand(issues, options) {
|
|
|
953
1020
|
});
|
|
954
1021
|
await logWriter.initialize(runConfig);
|
|
955
1022
|
}
|
|
1023
|
+
// Initialize state manager for persistent workflow state tracking
|
|
1024
|
+
// State tracking is always enabled (unless dry run)
|
|
1025
|
+
let stateManager = null;
|
|
1026
|
+
if (!config.dryRun) {
|
|
1027
|
+
stateManager = new StateManager({ verbose: config.verbose });
|
|
1028
|
+
}
|
|
956
1029
|
// Initialize shutdown manager for graceful interruption handling
|
|
957
1030
|
const shutdown = new ShutdownManager();
|
|
958
1031
|
// Register log writer finalization as cleanup task
|
|
@@ -986,12 +1059,18 @@ export async function runCommand(issues, options) {
|
|
|
986
1059
|
if (logWriter) {
|
|
987
1060
|
console.log(chalk.gray(` Logging: JSON (run ${logWriter.getRunId()?.slice(0, 8)}...)`));
|
|
988
1061
|
}
|
|
1062
|
+
if (stateManager) {
|
|
1063
|
+
console.log(chalk.gray(` State tracking: enabled`));
|
|
1064
|
+
}
|
|
989
1065
|
console.log(chalk.gray(` Issues: ${issueNumbers.map((n) => `#${n}`).join(", ")}`));
|
|
990
1066
|
// Worktree isolation is enabled by default for multi-issue runs
|
|
991
1067
|
const useWorktreeIsolation = mergedOptions.worktreeIsolation !== false && issueNumbers.length > 0;
|
|
992
1068
|
if (useWorktreeIsolation) {
|
|
993
1069
|
console.log(chalk.gray(` Worktree isolation: enabled`));
|
|
994
1070
|
}
|
|
1071
|
+
if (resolvedBaseBranch) {
|
|
1072
|
+
console.log(chalk.gray(` Base branch: ${resolvedBaseBranch}`));
|
|
1073
|
+
}
|
|
995
1074
|
if (mergedOptions.chain) {
|
|
996
1075
|
console.log(chalk.gray(` Chain mode: enabled (each issue branches from previous)`));
|
|
997
1076
|
}
|
|
@@ -1009,10 +1088,10 @@ export async function runCommand(issues, options) {
|
|
|
1009
1088
|
}));
|
|
1010
1089
|
// Use chain mode or standard worktree creation
|
|
1011
1090
|
if (mergedOptions.chain) {
|
|
1012
|
-
worktreeMap = await ensureWorktreesChain(issueData, config.verbose, manifest.packageManager);
|
|
1091
|
+
worktreeMap = await ensureWorktreesChain(issueData, config.verbose, manifest.packageManager, resolvedBaseBranch);
|
|
1013
1092
|
}
|
|
1014
1093
|
else {
|
|
1015
|
-
worktreeMap = await ensureWorktrees(issueData, config.verbose, manifest.packageManager);
|
|
1094
|
+
worktreeMap = await ensureWorktrees(issueData, config.verbose, manifest.packageManager, resolvedBaseBranch);
|
|
1016
1095
|
}
|
|
1017
1096
|
// Register cleanup tasks for newly created worktrees (not pre-existing ones)
|
|
1018
1097
|
for (const [issueNum, worktree] of worktreeMap.entries()) {
|
|
@@ -1038,7 +1117,7 @@ export async function runCommand(issues, options) {
|
|
|
1038
1117
|
for (let batchIdx = 0; batchIdx < batches.length; batchIdx++) {
|
|
1039
1118
|
const batch = batches[batchIdx];
|
|
1040
1119
|
console.log(chalk.blue(`\n Batch ${batchIdx + 1}/${batches.length}: Issues ${batch.map((n) => `#${n}`).join(", ")}`));
|
|
1041
|
-
const batchResults = await executeBatch(batch, config, logWriter, mergedOptions, issueInfoMap, worktreeMap, shutdown);
|
|
1120
|
+
const batchResults = await executeBatch(batch, config, logWriter, stateManager, mergedOptions, issueInfoMap, worktreeMap, shutdown);
|
|
1042
1121
|
results.push(...batchResults);
|
|
1043
1122
|
// Check if batch failed and we should stop
|
|
1044
1123
|
const batchFailed = batchResults.some((r) => !r.success);
|
|
@@ -1060,7 +1139,7 @@ export async function runCommand(issues, options) {
|
|
|
1060
1139
|
if (logWriter) {
|
|
1061
1140
|
logWriter.startIssue(issueNumber, issueInfo.title, issueInfo.labels);
|
|
1062
1141
|
}
|
|
1063
|
-
const result = await runIssueWithLogging(issueNumber, config, logWriter, issueInfo.labels, mergedOptions, worktreeInfo?.path, shutdown, mergedOptions.chain);
|
|
1142
|
+
const result = await runIssueWithLogging(issueNumber, config, logWriter, stateManager, issueInfo.title, issueInfo.labels, mergedOptions, worktreeInfo?.path, worktreeInfo?.branch, shutdown, mergedOptions.chain);
|
|
1064
1143
|
results.push(result);
|
|
1065
1144
|
// Complete issue logging
|
|
1066
1145
|
if (logWriter) {
|
|
@@ -1094,7 +1173,7 @@ export async function runCommand(issues, options) {
|
|
|
1094
1173
|
if (logWriter) {
|
|
1095
1174
|
logWriter.startIssue(issueNumber, issueInfo.title, issueInfo.labels);
|
|
1096
1175
|
}
|
|
1097
|
-
const result = await runIssueWithLogging(issueNumber, config, logWriter, issueInfo.labels, mergedOptions, worktreeInfo?.path, shutdown, false);
|
|
1176
|
+
const result = await runIssueWithLogging(issueNumber, config, logWriter, stateManager, issueInfo.title, issueInfo.labels, mergedOptions, worktreeInfo?.path, worktreeInfo?.branch, shutdown, false);
|
|
1098
1177
|
results.push(result);
|
|
1099
1178
|
// Complete issue logging
|
|
1100
1179
|
if (logWriter) {
|
|
@@ -1151,7 +1230,7 @@ export async function runCommand(issues, options) {
|
|
|
1151
1230
|
/**
|
|
1152
1231
|
* Execute a batch of issues
|
|
1153
1232
|
*/
|
|
1154
|
-
async function executeBatch(issueNumbers, config, logWriter, options, issueInfoMap, worktreeMap, shutdownManager) {
|
|
1233
|
+
async function executeBatch(issueNumbers, config, logWriter, stateManager, options, issueInfoMap, worktreeMap, shutdownManager) {
|
|
1155
1234
|
const results = [];
|
|
1156
1235
|
for (const issueNumber of issueNumbers) {
|
|
1157
1236
|
// Check if shutdown was triggered
|
|
@@ -1167,7 +1246,7 @@ async function executeBatch(issueNumbers, config, logWriter, options, issueInfoM
|
|
|
1167
1246
|
if (logWriter) {
|
|
1168
1247
|
logWriter.startIssue(issueNumber, issueInfo.title, issueInfo.labels);
|
|
1169
1248
|
}
|
|
1170
|
-
const result = await runIssueWithLogging(issueNumber, config, logWriter, issueInfo.labels, options, worktreeInfo?.path, shutdownManager, false);
|
|
1249
|
+
const result = await runIssueWithLogging(issueNumber, config, logWriter, stateManager, issueInfo.title, issueInfo.labels, options, worktreeInfo?.path, worktreeInfo?.branch, shutdownManager, false);
|
|
1171
1250
|
results.push(result);
|
|
1172
1251
|
// Complete issue logging
|
|
1173
1252
|
if (logWriter) {
|
|
@@ -1179,7 +1258,7 @@ async function executeBatch(issueNumbers, config, logWriter, options, issueInfoM
|
|
|
1179
1258
|
/**
|
|
1180
1259
|
* Execute all phases for a single issue with logging and quality loop
|
|
1181
1260
|
*/
|
|
1182
|
-
async function runIssueWithLogging(issueNumber, config, logWriter, labels, options, worktreePath, shutdownManager, chainMode) {
|
|
1261
|
+
async function runIssueWithLogging(issueNumber, config, logWriter, stateManager, issueTitle, labels, options, worktreePath, branch, shutdownManager, chainMode) {
|
|
1183
1262
|
const startTime = Date.now();
|
|
1184
1263
|
const phaseResults = [];
|
|
1185
1264
|
let loopTriggered = false;
|
|
@@ -1188,6 +1267,32 @@ async function runIssueWithLogging(issueNumber, config, logWriter, labels, optio
|
|
|
1188
1267
|
if (worktreePath) {
|
|
1189
1268
|
console.log(chalk.gray(` Worktree: ${worktreePath}`));
|
|
1190
1269
|
}
|
|
1270
|
+
// Initialize state tracking for this issue
|
|
1271
|
+
if (stateManager) {
|
|
1272
|
+
try {
|
|
1273
|
+
const existingState = await stateManager.getIssueState(issueNumber);
|
|
1274
|
+
if (!existingState) {
|
|
1275
|
+
await stateManager.initializeIssue(issueNumber, issueTitle, {
|
|
1276
|
+
worktree: worktreePath,
|
|
1277
|
+
branch,
|
|
1278
|
+
qualityLoop: config.qualityLoop,
|
|
1279
|
+
maxIterations: config.maxIterations,
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
else {
|
|
1283
|
+
// Update worktree info if it changed
|
|
1284
|
+
if (worktreePath && branch) {
|
|
1285
|
+
await stateManager.updateWorktreeInfo(issueNumber, worktreePath, branch);
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
catch (error) {
|
|
1290
|
+
// State tracking errors shouldn't stop execution
|
|
1291
|
+
if (config.verbose) {
|
|
1292
|
+
console.log(chalk.yellow(` ⚠️ State tracking error: ${error}`));
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1191
1296
|
// Determine phases for this specific issue
|
|
1192
1297
|
let phases;
|
|
1193
1298
|
let detectedQualityLoop = false;
|
|
@@ -1205,6 +1310,15 @@ async function runIssueWithLogging(issueNumber, config, logWriter, labels, optio
|
|
|
1205
1310
|
// Run spec first to get recommended workflow
|
|
1206
1311
|
console.log(chalk.gray(` Running spec to determine workflow...`));
|
|
1207
1312
|
console.log(chalk.gray(` ⏳ spec...`));
|
|
1313
|
+
// Track spec phase start in state
|
|
1314
|
+
if (stateManager) {
|
|
1315
|
+
try {
|
|
1316
|
+
await stateManager.updatePhaseStatus(issueNumber, "spec", "in_progress");
|
|
1317
|
+
}
|
|
1318
|
+
catch {
|
|
1319
|
+
// State tracking errors shouldn't stop execution
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1208
1322
|
const specStartTime = new Date();
|
|
1209
1323
|
// Note: spec runs in main repo (not worktree) for planning
|
|
1210
1324
|
const specResult = await executePhase(issueNumber, "spec", config, sessionId, worktreePath, // Will be ignored for spec (non-isolated phase)
|
|
@@ -1212,6 +1326,15 @@ async function runIssueWithLogging(issueNumber, config, logWriter, labels, optio
|
|
|
1212
1326
|
const specEndTime = new Date();
|
|
1213
1327
|
if (specResult.sessionId) {
|
|
1214
1328
|
sessionId = specResult.sessionId;
|
|
1329
|
+
// Update session ID in state for resume capability
|
|
1330
|
+
if (stateManager) {
|
|
1331
|
+
try {
|
|
1332
|
+
await stateManager.updateSessionId(issueNumber, specResult.sessionId);
|
|
1333
|
+
}
|
|
1334
|
+
catch {
|
|
1335
|
+
// State tracking errors shouldn't stop execution
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1215
1338
|
}
|
|
1216
1339
|
phaseResults.push(specResult);
|
|
1217
1340
|
specAlreadyRan = true;
|
|
@@ -1224,6 +1347,18 @@ async function runIssueWithLogging(issueNumber, config, logWriter, labels, optio
|
|
|
1224
1347
|
: "failure", { error: specResult.error });
|
|
1225
1348
|
logWriter.logPhase(phaseLog);
|
|
1226
1349
|
}
|
|
1350
|
+
// Track spec phase completion in state
|
|
1351
|
+
if (stateManager) {
|
|
1352
|
+
try {
|
|
1353
|
+
const phaseStatus = specResult.success ? "completed" : "failed";
|
|
1354
|
+
await stateManager.updatePhaseStatus(issueNumber, "spec", phaseStatus, {
|
|
1355
|
+
error: specResult.error,
|
|
1356
|
+
});
|
|
1357
|
+
}
|
|
1358
|
+
catch {
|
|
1359
|
+
// State tracking errors shouldn't stop execution
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1227
1362
|
if (!specResult.success) {
|
|
1228
1363
|
console.log(chalk.red(` ✗ spec: ${specResult.error}`));
|
|
1229
1364
|
const durationSeconds = (Date.now() - startTime) / 1000;
|
|
@@ -1294,12 +1429,30 @@ async function runIssueWithLogging(issueNumber, config, logWriter, labels, optio
|
|
|
1294
1429
|
let phasesFailed = false;
|
|
1295
1430
|
for (const phase of phases) {
|
|
1296
1431
|
console.log(chalk.gray(` ⏳ ${phase}...`));
|
|
1432
|
+
// Track phase start in state
|
|
1433
|
+
if (stateManager) {
|
|
1434
|
+
try {
|
|
1435
|
+
await stateManager.updatePhaseStatus(issueNumber, phase, "in_progress");
|
|
1436
|
+
}
|
|
1437
|
+
catch {
|
|
1438
|
+
// State tracking errors shouldn't stop execution
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1297
1441
|
const phaseStartTime = new Date();
|
|
1298
1442
|
const result = await executePhase(issueNumber, phase, config, sessionId, worktreePath, shutdownManager);
|
|
1299
1443
|
const phaseEndTime = new Date();
|
|
1300
1444
|
// Capture session ID for subsequent phases
|
|
1301
1445
|
if (result.sessionId) {
|
|
1302
1446
|
sessionId = result.sessionId;
|
|
1447
|
+
// Update session ID in state for resume capability
|
|
1448
|
+
if (stateManager) {
|
|
1449
|
+
try {
|
|
1450
|
+
await stateManager.updateSessionId(issueNumber, result.sessionId);
|
|
1451
|
+
}
|
|
1452
|
+
catch {
|
|
1453
|
+
// State tracking errors shouldn't stop execution
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1303
1456
|
}
|
|
1304
1457
|
phaseResults.push(result);
|
|
1305
1458
|
// Log phase result
|
|
@@ -1311,6 +1464,20 @@ async function runIssueWithLogging(issueNumber, config, logWriter, labels, optio
|
|
|
1311
1464
|
: "failure", { error: result.error });
|
|
1312
1465
|
logWriter.logPhase(phaseLog);
|
|
1313
1466
|
}
|
|
1467
|
+
// Track phase completion in state
|
|
1468
|
+
if (stateManager) {
|
|
1469
|
+
try {
|
|
1470
|
+
const phaseStatus = result.success
|
|
1471
|
+
? "completed"
|
|
1472
|
+
: result.error?.includes("Timeout")
|
|
1473
|
+
? "failed"
|
|
1474
|
+
: "failed";
|
|
1475
|
+
await stateManager.updatePhaseStatus(issueNumber, phase, phaseStatus, { error: result.error });
|
|
1476
|
+
}
|
|
1477
|
+
catch {
|
|
1478
|
+
// State tracking errors shouldn't stop execution
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1314
1481
|
if (result.success) {
|
|
1315
1482
|
const duration = result.durationSeconds
|
|
1316
1483
|
? ` (${formatDuration(result.durationSeconds)})`
|
|
@@ -1355,6 +1522,16 @@ async function runIssueWithLogging(issueNumber, config, logWriter, labels, optio
|
|
|
1355
1522
|
// Success is determined by whether all phases completed in any iteration,
|
|
1356
1523
|
// not whether all accumulated phase results passed (which would fail after loop recovery)
|
|
1357
1524
|
const success = completedSuccessfully;
|
|
1525
|
+
// Update final issue status in state
|
|
1526
|
+
if (stateManager) {
|
|
1527
|
+
try {
|
|
1528
|
+
const finalStatus = success ? "ready_for_merge" : "in_progress";
|
|
1529
|
+
await stateManager.updateIssueStatus(issueNumber, finalStatus);
|
|
1530
|
+
}
|
|
1531
|
+
catch {
|
|
1532
|
+
// State tracking errors shouldn't stop execution
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1358
1535
|
// Create checkpoint commit in chain mode after QA passes
|
|
1359
1536
|
if (success && chainMode && worktreePath) {
|
|
1360
1537
|
createCheckpointCommit(worktreePath, issueNumber, config.verbose);
|
|
@@ -1,4 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* sequant status - Show version and
|
|
2
|
+
* sequant status - Show version, configuration, and workflow state
|
|
3
3
|
*/
|
|
4
|
-
export
|
|
4
|
+
export interface StatusCommandOptions {
|
|
5
|
+
/** Show only issues state */
|
|
6
|
+
issues?: boolean;
|
|
7
|
+
/** Show details for a specific issue */
|
|
8
|
+
issue?: number;
|
|
9
|
+
/** Output as JSON */
|
|
10
|
+
json?: boolean;
|
|
11
|
+
/** Rebuild state from run logs */
|
|
12
|
+
rebuild?: boolean;
|
|
13
|
+
/** Clean up stale/orphaned entries */
|
|
14
|
+
cleanup?: boolean;
|
|
15
|
+
/** Only show what would be cleaned (used with --cleanup) */
|
|
16
|
+
dryRun?: boolean;
|
|
17
|
+
/** Remove entries older than this many days (used with --cleanup) */
|
|
18
|
+
maxAge?: number;
|
|
19
|
+
}
|
|
20
|
+
export declare function statusCommand(options?: StatusCommandOptions): Promise<void>;
|