sequant 1.8.0 → 1.10.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/README.md +4 -1
- package/dist/bin/cli.js +10 -0
- package/dist/src/commands/run.d.ts +13 -0
- package/dist/src/commands/run.js +280 -22
- 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
|
@@ -10,6 +10,7 @@ import { fileURLToPath } from "url";
|
|
|
10
10
|
import { dirname, resolve } from "path";
|
|
11
11
|
import { readFileSync } from "fs";
|
|
12
12
|
import { initCommand } from "../src/commands/init.js";
|
|
13
|
+
import { isLocalNodeModulesInstall } from "../src/lib/version-check.js";
|
|
13
14
|
// Read version from package.json dynamically
|
|
14
15
|
// Works from both source (bin/) and compiled (dist/bin/) locations
|
|
15
16
|
function getVersion() {
|
|
@@ -43,6 +44,13 @@ const program = new Command();
|
|
|
43
44
|
if (process.argv.includes("--no-color")) {
|
|
44
45
|
process.env.FORCE_COLOR = "0";
|
|
45
46
|
}
|
|
47
|
+
// Warn if running from local node_modules (not npx cache or global)
|
|
48
|
+
// This helps users who accidentally have a stale local install
|
|
49
|
+
if (!process.argv.includes("--quiet") && isLocalNodeModulesInstall()) {
|
|
50
|
+
console.warn(chalk.yellow("⚠️ Running sequant from local node_modules\n" +
|
|
51
|
+
" For latest version: npx sequant@latest\n" +
|
|
52
|
+
" To remove local: npm uninstall sequant\n"));
|
|
53
|
+
}
|
|
46
54
|
program
|
|
47
55
|
.name("sequant")
|
|
48
56
|
.description("Quantize your development workflow - Sequential AI phases with quality gates")
|
|
@@ -90,6 +98,8 @@ program
|
|
|
90
98
|
.option("--no-smart-tests", "Disable smart test detection")
|
|
91
99
|
.option("--testgen", "Run testgen phase after spec")
|
|
92
100
|
.option("--quiet", "Suppress version warnings and non-essential output")
|
|
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)")
|
|
93
103
|
.action(runCommand);
|
|
94
104
|
program
|
|
95
105
|
.command("logs")
|
|
@@ -17,6 +17,12 @@ export declare function listWorktrees(): Array<{
|
|
|
17
17
|
* Get changed files in a worktree compared to main
|
|
18
18
|
*/
|
|
19
19
|
export declare function getWorktreeChangedFiles(worktreePath: string): string[];
|
|
20
|
+
/**
|
|
21
|
+
* Create a checkpoint commit in the worktree after QA passes
|
|
22
|
+
* This allows recovery in case later issues in the chain fail
|
|
23
|
+
* @internal Exported for testing
|
|
24
|
+
*/
|
|
25
|
+
export declare function createCheckpointCommit(worktreePath: string, issueNumber: number, verbose: boolean): boolean;
|
|
20
26
|
/**
|
|
21
27
|
* Detect phases based on issue labels (like /solve logic)
|
|
22
28
|
*/
|
|
@@ -58,6 +64,13 @@ interface RunOptions {
|
|
|
58
64
|
reuseWorktrees?: boolean;
|
|
59
65
|
/** Suppress version warnings and non-essential output */
|
|
60
66
|
quiet?: boolean;
|
|
67
|
+
/** Chain issues: each branches from previous (requires --sequential) */
|
|
68
|
+
chain?: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Base branch for worktree creation.
|
|
71
|
+
* Resolution priority: this CLI flag → settings.run.defaultBase → 'main'
|
|
72
|
+
*/
|
|
73
|
+
base?: string;
|
|
61
74
|
}
|
|
62
75
|
/**
|
|
63
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";
|
|
@@ -105,8 +106,9 @@ export function getWorktreeChangedFiles(worktreePath) {
|
|
|
105
106
|
}
|
|
106
107
|
/**
|
|
107
108
|
* Create or reuse a worktree for an issue
|
|
109
|
+
* @param baseBranch - Optional branch to use as base instead of origin/main (for chain mode)
|
|
108
110
|
*/
|
|
109
|
-
async function ensureWorktree(issueNumber, title, verbose, packageManager) {
|
|
111
|
+
async function ensureWorktree(issueNumber, title, verbose, packageManager, baseBranch) {
|
|
110
112
|
const gitRoot = getGitRoot();
|
|
111
113
|
if (!gitRoot) {
|
|
112
114
|
console.log(chalk.red(" ❌ Not in a git repository"));
|
|
@@ -135,15 +137,34 @@ async function ensureWorktree(issueNumber, title, verbose, packageManager) {
|
|
|
135
137
|
if (verbose) {
|
|
136
138
|
console.log(chalk.gray(` 🌿 Creating worktree for #${issueNumber}...`));
|
|
137
139
|
}
|
|
138
|
-
//
|
|
139
|
-
if
|
|
140
|
-
|
|
140
|
+
// Determine the base for the new branch
|
|
141
|
+
// For custom base branches, use origin/<branch> if it's a remote-style reference
|
|
142
|
+
// For local branches (chain mode), use as-is
|
|
143
|
+
const isLocalBranch = baseBranch && !baseBranch.startsWith("origin/") && baseBranch !== "main";
|
|
144
|
+
const baseRef = baseBranch
|
|
145
|
+
? isLocalBranch
|
|
146
|
+
? baseBranch
|
|
147
|
+
: baseBranch.startsWith("origin/")
|
|
148
|
+
? baseBranch
|
|
149
|
+
: `origin/${baseBranch}`
|
|
150
|
+
: "origin/main";
|
|
151
|
+
// Fetch the base branch to ensure worktree starts from fresh baseline
|
|
152
|
+
const branchToFetch = baseBranch
|
|
153
|
+
? baseBranch.replace(/^origin\//, "")
|
|
154
|
+
: "main";
|
|
155
|
+
if (!isLocalBranch) {
|
|
156
|
+
if (verbose) {
|
|
157
|
+
console.log(chalk.gray(` 🔄 Fetching latest ${branchToFetch}...`));
|
|
158
|
+
}
|
|
159
|
+
const fetchResult = spawnSync("git", ["fetch", "origin", branchToFetch], {
|
|
160
|
+
stdio: "pipe",
|
|
161
|
+
});
|
|
162
|
+
if (fetchResult.status !== 0 && verbose) {
|
|
163
|
+
console.log(chalk.yellow(` ⚠️ Could not fetch origin/${branchToFetch}, using local state`));
|
|
164
|
+
}
|
|
141
165
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
});
|
|
145
|
-
if (fetchResult.status !== 0 && verbose) {
|
|
146
|
-
console.log(chalk.yellow(` ⚠️ Could not fetch origin/main, using local state`));
|
|
166
|
+
else if (verbose) {
|
|
167
|
+
console.log(chalk.gray(` 🔗 Chaining from branch: ${baseBranch}`));
|
|
147
168
|
}
|
|
148
169
|
// Ensure worktrees directory exists
|
|
149
170
|
if (!existsSync(worktreesDir)) {
|
|
@@ -158,8 +179,8 @@ async function ensureWorktree(issueNumber, title, verbose, packageManager) {
|
|
|
158
179
|
});
|
|
159
180
|
}
|
|
160
181
|
else {
|
|
161
|
-
// Create new branch from origin/main
|
|
162
|
-
createResult = spawnSync("git", ["worktree", "add", worktreePath, "-b", branch,
|
|
182
|
+
// Create new branch from base reference (origin/main or previous branch in chain)
|
|
183
|
+
createResult = spawnSync("git", ["worktree", "add", worktreePath, "-b", branch, baseRef], { stdio: "pipe" });
|
|
163
184
|
}
|
|
164
185
|
if (createResult.status !== 0) {
|
|
165
186
|
const error = createResult.stderr.toString();
|
|
@@ -208,12 +229,14 @@ async function ensureWorktree(issueNumber, title, verbose, packageManager) {
|
|
|
208
229
|
}
|
|
209
230
|
/**
|
|
210
231
|
* Ensure worktrees exist for all issues before execution
|
|
232
|
+
* @param baseBranch - Optional base branch for worktree creation (default: main)
|
|
211
233
|
*/
|
|
212
|
-
async function ensureWorktrees(issues, verbose, packageManager) {
|
|
234
|
+
async function ensureWorktrees(issues, verbose, packageManager, baseBranch) {
|
|
213
235
|
const worktrees = new Map();
|
|
214
|
-
|
|
236
|
+
const baseDisplay = baseBranch || "main";
|
|
237
|
+
console.log(chalk.blue(`\n 📂 Preparing worktrees from ${baseDisplay}...`));
|
|
215
238
|
for (const issue of issues) {
|
|
216
|
-
const worktree = await ensureWorktree(issue.number, issue.title, verbose, packageManager);
|
|
239
|
+
const worktree = await ensureWorktree(issue.number, issue.title, verbose, packageManager, baseBranch);
|
|
217
240
|
if (worktree) {
|
|
218
241
|
worktrees.set(issue.number, worktree);
|
|
219
242
|
}
|
|
@@ -225,6 +248,93 @@ async function ensureWorktrees(issues, verbose, packageManager) {
|
|
|
225
248
|
}
|
|
226
249
|
return worktrees;
|
|
227
250
|
}
|
|
251
|
+
/**
|
|
252
|
+
* Ensure worktrees exist for all issues in chain mode
|
|
253
|
+
* Each issue branches from the previous issue's branch
|
|
254
|
+
* @param baseBranch - Optional starting base branch for the chain (default: main)
|
|
255
|
+
*/
|
|
256
|
+
async function ensureWorktreesChain(issues, verbose, packageManager, baseBranch) {
|
|
257
|
+
const worktrees = new Map();
|
|
258
|
+
const baseDisplay = baseBranch || "main";
|
|
259
|
+
console.log(chalk.blue(`\n 🔗 Preparing chained worktrees from ${baseDisplay}...`));
|
|
260
|
+
// First issue starts from the specified base branch (or main)
|
|
261
|
+
let previousBranch = baseBranch;
|
|
262
|
+
for (const issue of issues) {
|
|
263
|
+
const worktree = await ensureWorktree(issue.number, issue.title, verbose, packageManager, previousBranch);
|
|
264
|
+
if (worktree) {
|
|
265
|
+
worktrees.set(issue.number, worktree);
|
|
266
|
+
previousBranch = worktree.branch; // Next issue will branch from this
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
// If worktree creation fails, stop the chain
|
|
270
|
+
console.log(chalk.red(` ❌ Chain broken: could not create worktree for #${issue.number}`));
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
const created = Array.from(worktrees.values()).filter((w) => !w.existed).length;
|
|
275
|
+
const reused = Array.from(worktrees.values()).filter((w) => w.existed).length;
|
|
276
|
+
if (created > 0 || reused > 0) {
|
|
277
|
+
console.log(chalk.gray(` Chained worktrees: ${created} created, ${reused} reused`));
|
|
278
|
+
}
|
|
279
|
+
// Show chain structure
|
|
280
|
+
if (worktrees.size > 0) {
|
|
281
|
+
const chainOrder = issues
|
|
282
|
+
.filter((i) => worktrees.has(i.number))
|
|
283
|
+
.map((i) => `#${i.number}`)
|
|
284
|
+
.join(" → ");
|
|
285
|
+
console.log(chalk.gray(` Chain: ${baseDisplay} → ${chainOrder}`));
|
|
286
|
+
}
|
|
287
|
+
return worktrees;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Create a checkpoint commit in the worktree after QA passes
|
|
291
|
+
* This allows recovery in case later issues in the chain fail
|
|
292
|
+
* @internal Exported for testing
|
|
293
|
+
*/
|
|
294
|
+
export function createCheckpointCommit(worktreePath, issueNumber, verbose) {
|
|
295
|
+
// Check if there are uncommitted changes
|
|
296
|
+
const statusResult = spawnSync("git", ["-C", worktreePath, "status", "--porcelain"], { stdio: "pipe" });
|
|
297
|
+
if (statusResult.status !== 0) {
|
|
298
|
+
if (verbose) {
|
|
299
|
+
console.log(chalk.yellow(` ⚠️ Could not check git status for checkpoint`));
|
|
300
|
+
}
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
const hasChanges = statusResult.stdout.toString().trim().length > 0;
|
|
304
|
+
if (!hasChanges) {
|
|
305
|
+
if (verbose) {
|
|
306
|
+
console.log(chalk.gray(` 📌 No changes to checkpoint (already committed)`));
|
|
307
|
+
}
|
|
308
|
+
return true;
|
|
309
|
+
}
|
|
310
|
+
// Stage all changes
|
|
311
|
+
const addResult = spawnSync("git", ["-C", worktreePath, "add", "-A"], {
|
|
312
|
+
stdio: "pipe",
|
|
313
|
+
});
|
|
314
|
+
if (addResult.status !== 0) {
|
|
315
|
+
if (verbose) {
|
|
316
|
+
console.log(chalk.yellow(` ⚠️ Could not stage changes for checkpoint`));
|
|
317
|
+
}
|
|
318
|
+
return false;
|
|
319
|
+
}
|
|
320
|
+
// Create checkpoint commit
|
|
321
|
+
const commitMessage = `checkpoint(#${issueNumber}): QA passed
|
|
322
|
+
|
|
323
|
+
This is an automatic checkpoint commit created after issue #${issueNumber}
|
|
324
|
+
passed QA in chain mode. It serves as a recovery point if later issues fail.
|
|
325
|
+
|
|
326
|
+
Co-Authored-By: Sequant <noreply@sequant.dev>`;
|
|
327
|
+
const commitResult = spawnSync("git", ["-C", worktreePath, "commit", "-m", commitMessage], { stdio: "pipe" });
|
|
328
|
+
if (commitResult.status !== 0) {
|
|
329
|
+
const error = commitResult.stderr.toString();
|
|
330
|
+
if (verbose) {
|
|
331
|
+
console.log(chalk.yellow(` ⚠️ Could not create checkpoint commit: ${error}`));
|
|
332
|
+
}
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
console.log(chalk.green(` 📌 Checkpoint commit created for #${issueNumber}`));
|
|
336
|
+
return true;
|
|
337
|
+
}
|
|
228
338
|
/**
|
|
229
339
|
* Natural language prompts for each phase
|
|
230
340
|
* These prompts will invoke the corresponding skills via natural language
|
|
@@ -777,6 +887,8 @@ export async function runCommand(issues, options) {
|
|
|
777
887
|
// Determine if we should auto-detect phases from labels
|
|
778
888
|
const autoDetectPhases = !options.phases && settings.run.autoDetectPhases;
|
|
779
889
|
mergedOptions.autoDetectPhases = autoDetectPhases;
|
|
890
|
+
// Resolve base branch: CLI flag → settings.run.defaultBase → 'main'
|
|
891
|
+
const resolvedBaseBranch = options.base ?? settings.run.defaultBase ?? undefined;
|
|
780
892
|
// Parse issue numbers (or use batch mode)
|
|
781
893
|
let issueNumbers;
|
|
782
894
|
let batches = null;
|
|
@@ -793,8 +905,30 @@ export async function runCommand(issues, options) {
|
|
|
793
905
|
console.log(chalk.gray("\nUsage: npx sequant run <issues...> [options]"));
|
|
794
906
|
console.log(chalk.gray("Example: npx sequant run 1 2 3 --sequential"));
|
|
795
907
|
console.log(chalk.gray('Batch example: npx sequant run --batch "1 2" --batch "3"'));
|
|
908
|
+
console.log(chalk.gray("Chain example: npx sequant run 1 2 3 --sequential --chain"));
|
|
796
909
|
return;
|
|
797
910
|
}
|
|
911
|
+
// Validate chain mode requirements
|
|
912
|
+
if (mergedOptions.chain) {
|
|
913
|
+
if (!mergedOptions.sequential) {
|
|
914
|
+
console.log(chalk.red("❌ --chain requires --sequential flag"));
|
|
915
|
+
console.log(chalk.gray(" Chain mode executes issues sequentially, each branching from the previous."));
|
|
916
|
+
console.log(chalk.gray(" Usage: npx sequant run 1 2 3 --sequential --chain"));
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
if (batches) {
|
|
920
|
+
console.log(chalk.red("❌ --chain cannot be used with --batch"));
|
|
921
|
+
console.log(chalk.gray(" Chain mode creates a linear dependency chain between issues."));
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
// Warn about long chains
|
|
925
|
+
if (issueNumbers.length > 5) {
|
|
926
|
+
console.log(chalk.yellow(` ⚠️ Warning: Chain has ${issueNumbers.length} issues (recommended max: 5)`));
|
|
927
|
+
console.log(chalk.yellow(" Long chains increase merge complexity and review difficulty."));
|
|
928
|
+
console.log(chalk.yellow(" Consider breaking into smaller chains or using batch mode."));
|
|
929
|
+
console.log("");
|
|
930
|
+
}
|
|
931
|
+
}
|
|
798
932
|
// Sort issues by dependencies (if more than one issue)
|
|
799
933
|
if (issueNumbers.length > 1 && !batches) {
|
|
800
934
|
const originalOrder = [...issueNumbers];
|
|
@@ -839,6 +973,12 @@ export async function runCommand(issues, options) {
|
|
|
839
973
|
});
|
|
840
974
|
await logWriter.initialize(runConfig);
|
|
841
975
|
}
|
|
976
|
+
// Initialize state manager for persistent workflow state tracking
|
|
977
|
+
// State tracking is always enabled (unless dry run)
|
|
978
|
+
let stateManager = null;
|
|
979
|
+
if (!config.dryRun) {
|
|
980
|
+
stateManager = new StateManager({ verbose: config.verbose });
|
|
981
|
+
}
|
|
842
982
|
// Initialize shutdown manager for graceful interruption handling
|
|
843
983
|
const shutdown = new ShutdownManager();
|
|
844
984
|
// Register log writer finalization as cleanup task
|
|
@@ -872,12 +1012,21 @@ export async function runCommand(issues, options) {
|
|
|
872
1012
|
if (logWriter) {
|
|
873
1013
|
console.log(chalk.gray(` Logging: JSON (run ${logWriter.getRunId()?.slice(0, 8)}...)`));
|
|
874
1014
|
}
|
|
1015
|
+
if (stateManager) {
|
|
1016
|
+
console.log(chalk.gray(` State tracking: enabled`));
|
|
1017
|
+
}
|
|
875
1018
|
console.log(chalk.gray(` Issues: ${issueNumbers.map((n) => `#${n}`).join(", ")}`));
|
|
876
1019
|
// Worktree isolation is enabled by default for multi-issue runs
|
|
877
1020
|
const useWorktreeIsolation = mergedOptions.worktreeIsolation !== false && issueNumbers.length > 0;
|
|
878
1021
|
if (useWorktreeIsolation) {
|
|
879
1022
|
console.log(chalk.gray(` Worktree isolation: enabled`));
|
|
880
1023
|
}
|
|
1024
|
+
if (resolvedBaseBranch) {
|
|
1025
|
+
console.log(chalk.gray(` Base branch: ${resolvedBaseBranch}`));
|
|
1026
|
+
}
|
|
1027
|
+
if (mergedOptions.chain) {
|
|
1028
|
+
console.log(chalk.gray(` Chain mode: enabled (each issue branches from previous)`));
|
|
1029
|
+
}
|
|
881
1030
|
// Fetch issue info for all issues first
|
|
882
1031
|
const issueInfoMap = new Map();
|
|
883
1032
|
for (const issueNumber of issueNumbers) {
|
|
@@ -890,7 +1039,13 @@ export async function runCommand(issues, options) {
|
|
|
890
1039
|
number: num,
|
|
891
1040
|
title: issueInfoMap.get(num)?.title || `Issue #${num}`,
|
|
892
1041
|
}));
|
|
893
|
-
|
|
1042
|
+
// Use chain mode or standard worktree creation
|
|
1043
|
+
if (mergedOptions.chain) {
|
|
1044
|
+
worktreeMap = await ensureWorktreesChain(issueData, config.verbose, manifest.packageManager, resolvedBaseBranch);
|
|
1045
|
+
}
|
|
1046
|
+
else {
|
|
1047
|
+
worktreeMap = await ensureWorktrees(issueData, config.verbose, manifest.packageManager, resolvedBaseBranch);
|
|
1048
|
+
}
|
|
894
1049
|
// Register cleanup tasks for newly created worktrees (not pre-existing ones)
|
|
895
1050
|
for (const [issueNum, worktree] of worktreeMap.entries()) {
|
|
896
1051
|
if (!worktree.existed) {
|
|
@@ -915,7 +1070,7 @@ export async function runCommand(issues, options) {
|
|
|
915
1070
|
for (let batchIdx = 0; batchIdx < batches.length; batchIdx++) {
|
|
916
1071
|
const batch = batches[batchIdx];
|
|
917
1072
|
console.log(chalk.blue(`\n Batch ${batchIdx + 1}/${batches.length}: Issues ${batch.map((n) => `#${n}`).join(", ")}`));
|
|
918
|
-
const batchResults = await executeBatch(batch, config, logWriter, mergedOptions, issueInfoMap, worktreeMap, shutdown);
|
|
1073
|
+
const batchResults = await executeBatch(batch, config, logWriter, stateManager, mergedOptions, issueInfoMap, worktreeMap, shutdown);
|
|
919
1074
|
results.push(...batchResults);
|
|
920
1075
|
// Check if batch failed and we should stop
|
|
921
1076
|
const batchFailed = batchResults.some((r) => !r.success);
|
|
@@ -937,7 +1092,7 @@ export async function runCommand(issues, options) {
|
|
|
937
1092
|
if (logWriter) {
|
|
938
1093
|
logWriter.startIssue(issueNumber, issueInfo.title, issueInfo.labels);
|
|
939
1094
|
}
|
|
940
|
-
const result = await runIssueWithLogging(issueNumber, config, logWriter, issueInfo.labels, mergedOptions, worktreeInfo?.path, shutdown);
|
|
1095
|
+
const result = await runIssueWithLogging(issueNumber, config, logWriter, stateManager, issueInfo.title, issueInfo.labels, mergedOptions, worktreeInfo?.path, worktreeInfo?.branch, shutdown, mergedOptions.chain);
|
|
941
1096
|
results.push(result);
|
|
942
1097
|
// Complete issue logging
|
|
943
1098
|
if (logWriter) {
|
|
@@ -948,7 +1103,8 @@ export async function runCommand(issues, options) {
|
|
|
948
1103
|
break;
|
|
949
1104
|
}
|
|
950
1105
|
if (!result.success) {
|
|
951
|
-
|
|
1106
|
+
const chainInfo = mergedOptions.chain ? " (chain stopped)" : "";
|
|
1107
|
+
console.log(chalk.yellow(`\n ⚠️ Issue #${issueNumber} failed, stopping sequential execution${chainInfo}`));
|
|
952
1108
|
break;
|
|
953
1109
|
}
|
|
954
1110
|
}
|
|
@@ -970,7 +1126,7 @@ export async function runCommand(issues, options) {
|
|
|
970
1126
|
if (logWriter) {
|
|
971
1127
|
logWriter.startIssue(issueNumber, issueInfo.title, issueInfo.labels);
|
|
972
1128
|
}
|
|
973
|
-
const result = await runIssueWithLogging(issueNumber, config, logWriter, issueInfo.labels, mergedOptions, worktreeInfo?.path, shutdown);
|
|
1129
|
+
const result = await runIssueWithLogging(issueNumber, config, logWriter, stateManager, issueInfo.title, issueInfo.labels, mergedOptions, worktreeInfo?.path, worktreeInfo?.branch, shutdown, false);
|
|
974
1130
|
results.push(result);
|
|
975
1131
|
// Complete issue logging
|
|
976
1132
|
if (logWriter) {
|
|
@@ -1027,7 +1183,7 @@ export async function runCommand(issues, options) {
|
|
|
1027
1183
|
/**
|
|
1028
1184
|
* Execute a batch of issues
|
|
1029
1185
|
*/
|
|
1030
|
-
async function executeBatch(issueNumbers, config, logWriter, options, issueInfoMap, worktreeMap, shutdownManager) {
|
|
1186
|
+
async function executeBatch(issueNumbers, config, logWriter, stateManager, options, issueInfoMap, worktreeMap, shutdownManager) {
|
|
1031
1187
|
const results = [];
|
|
1032
1188
|
for (const issueNumber of issueNumbers) {
|
|
1033
1189
|
// Check if shutdown was triggered
|
|
@@ -1043,7 +1199,7 @@ async function executeBatch(issueNumbers, config, logWriter, options, issueInfoM
|
|
|
1043
1199
|
if (logWriter) {
|
|
1044
1200
|
logWriter.startIssue(issueNumber, issueInfo.title, issueInfo.labels);
|
|
1045
1201
|
}
|
|
1046
|
-
const result = await runIssueWithLogging(issueNumber, config, logWriter, issueInfo.labels, options, worktreeInfo?.path, shutdownManager);
|
|
1202
|
+
const result = await runIssueWithLogging(issueNumber, config, logWriter, stateManager, issueInfo.title, issueInfo.labels, options, worktreeInfo?.path, worktreeInfo?.branch, shutdownManager, false);
|
|
1047
1203
|
results.push(result);
|
|
1048
1204
|
// Complete issue logging
|
|
1049
1205
|
if (logWriter) {
|
|
@@ -1055,7 +1211,7 @@ async function executeBatch(issueNumbers, config, logWriter, options, issueInfoM
|
|
|
1055
1211
|
/**
|
|
1056
1212
|
* Execute all phases for a single issue with logging and quality loop
|
|
1057
1213
|
*/
|
|
1058
|
-
async function runIssueWithLogging(issueNumber, config, logWriter, labels, options, worktreePath, shutdownManager) {
|
|
1214
|
+
async function runIssueWithLogging(issueNumber, config, logWriter, stateManager, issueTitle, labels, options, worktreePath, branch, shutdownManager, chainMode) {
|
|
1059
1215
|
const startTime = Date.now();
|
|
1060
1216
|
const phaseResults = [];
|
|
1061
1217
|
let loopTriggered = false;
|
|
@@ -1064,6 +1220,32 @@ async function runIssueWithLogging(issueNumber, config, logWriter, labels, optio
|
|
|
1064
1220
|
if (worktreePath) {
|
|
1065
1221
|
console.log(chalk.gray(` Worktree: ${worktreePath}`));
|
|
1066
1222
|
}
|
|
1223
|
+
// Initialize state tracking for this issue
|
|
1224
|
+
if (stateManager) {
|
|
1225
|
+
try {
|
|
1226
|
+
const existingState = await stateManager.getIssueState(issueNumber);
|
|
1227
|
+
if (!existingState) {
|
|
1228
|
+
await stateManager.initializeIssue(issueNumber, issueTitle, {
|
|
1229
|
+
worktree: worktreePath,
|
|
1230
|
+
branch,
|
|
1231
|
+
qualityLoop: config.qualityLoop,
|
|
1232
|
+
maxIterations: config.maxIterations,
|
|
1233
|
+
});
|
|
1234
|
+
}
|
|
1235
|
+
else {
|
|
1236
|
+
// Update worktree info if it changed
|
|
1237
|
+
if (worktreePath && branch) {
|
|
1238
|
+
await stateManager.updateWorktreeInfo(issueNumber, worktreePath, branch);
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
catch (error) {
|
|
1243
|
+
// State tracking errors shouldn't stop execution
|
|
1244
|
+
if (config.verbose) {
|
|
1245
|
+
console.log(chalk.yellow(` ⚠️ State tracking error: ${error}`));
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1067
1249
|
// Determine phases for this specific issue
|
|
1068
1250
|
let phases;
|
|
1069
1251
|
let detectedQualityLoop = false;
|
|
@@ -1081,6 +1263,15 @@ async function runIssueWithLogging(issueNumber, config, logWriter, labels, optio
|
|
|
1081
1263
|
// Run spec first to get recommended workflow
|
|
1082
1264
|
console.log(chalk.gray(` Running spec to determine workflow...`));
|
|
1083
1265
|
console.log(chalk.gray(` ⏳ spec...`));
|
|
1266
|
+
// Track spec phase start in state
|
|
1267
|
+
if (stateManager) {
|
|
1268
|
+
try {
|
|
1269
|
+
await stateManager.updatePhaseStatus(issueNumber, "spec", "in_progress");
|
|
1270
|
+
}
|
|
1271
|
+
catch {
|
|
1272
|
+
// State tracking errors shouldn't stop execution
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1084
1275
|
const specStartTime = new Date();
|
|
1085
1276
|
// Note: spec runs in main repo (not worktree) for planning
|
|
1086
1277
|
const specResult = await executePhase(issueNumber, "spec", config, sessionId, worktreePath, // Will be ignored for spec (non-isolated phase)
|
|
@@ -1088,6 +1279,15 @@ async function runIssueWithLogging(issueNumber, config, logWriter, labels, optio
|
|
|
1088
1279
|
const specEndTime = new Date();
|
|
1089
1280
|
if (specResult.sessionId) {
|
|
1090
1281
|
sessionId = specResult.sessionId;
|
|
1282
|
+
// Update session ID in state for resume capability
|
|
1283
|
+
if (stateManager) {
|
|
1284
|
+
try {
|
|
1285
|
+
await stateManager.updateSessionId(issueNumber, specResult.sessionId);
|
|
1286
|
+
}
|
|
1287
|
+
catch {
|
|
1288
|
+
// State tracking errors shouldn't stop execution
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1091
1291
|
}
|
|
1092
1292
|
phaseResults.push(specResult);
|
|
1093
1293
|
specAlreadyRan = true;
|
|
@@ -1100,6 +1300,18 @@ async function runIssueWithLogging(issueNumber, config, logWriter, labels, optio
|
|
|
1100
1300
|
: "failure", { error: specResult.error });
|
|
1101
1301
|
logWriter.logPhase(phaseLog);
|
|
1102
1302
|
}
|
|
1303
|
+
// Track spec phase completion in state
|
|
1304
|
+
if (stateManager) {
|
|
1305
|
+
try {
|
|
1306
|
+
const phaseStatus = specResult.success ? "completed" : "failed";
|
|
1307
|
+
await stateManager.updatePhaseStatus(issueNumber, "spec", phaseStatus, {
|
|
1308
|
+
error: specResult.error,
|
|
1309
|
+
});
|
|
1310
|
+
}
|
|
1311
|
+
catch {
|
|
1312
|
+
// State tracking errors shouldn't stop execution
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1103
1315
|
if (!specResult.success) {
|
|
1104
1316
|
console.log(chalk.red(` ✗ spec: ${specResult.error}`));
|
|
1105
1317
|
const durationSeconds = (Date.now() - startTime) / 1000;
|
|
@@ -1170,12 +1382,30 @@ async function runIssueWithLogging(issueNumber, config, logWriter, labels, optio
|
|
|
1170
1382
|
let phasesFailed = false;
|
|
1171
1383
|
for (const phase of phases) {
|
|
1172
1384
|
console.log(chalk.gray(` ⏳ ${phase}...`));
|
|
1385
|
+
// Track phase start in state
|
|
1386
|
+
if (stateManager) {
|
|
1387
|
+
try {
|
|
1388
|
+
await stateManager.updatePhaseStatus(issueNumber, phase, "in_progress");
|
|
1389
|
+
}
|
|
1390
|
+
catch {
|
|
1391
|
+
// State tracking errors shouldn't stop execution
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1173
1394
|
const phaseStartTime = new Date();
|
|
1174
1395
|
const result = await executePhase(issueNumber, phase, config, sessionId, worktreePath, shutdownManager);
|
|
1175
1396
|
const phaseEndTime = new Date();
|
|
1176
1397
|
// Capture session ID for subsequent phases
|
|
1177
1398
|
if (result.sessionId) {
|
|
1178
1399
|
sessionId = result.sessionId;
|
|
1400
|
+
// Update session ID in state for resume capability
|
|
1401
|
+
if (stateManager) {
|
|
1402
|
+
try {
|
|
1403
|
+
await stateManager.updateSessionId(issueNumber, result.sessionId);
|
|
1404
|
+
}
|
|
1405
|
+
catch {
|
|
1406
|
+
// State tracking errors shouldn't stop execution
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1179
1409
|
}
|
|
1180
1410
|
phaseResults.push(result);
|
|
1181
1411
|
// Log phase result
|
|
@@ -1187,6 +1417,20 @@ async function runIssueWithLogging(issueNumber, config, logWriter, labels, optio
|
|
|
1187
1417
|
: "failure", { error: result.error });
|
|
1188
1418
|
logWriter.logPhase(phaseLog);
|
|
1189
1419
|
}
|
|
1420
|
+
// Track phase completion in state
|
|
1421
|
+
if (stateManager) {
|
|
1422
|
+
try {
|
|
1423
|
+
const phaseStatus = result.success
|
|
1424
|
+
? "completed"
|
|
1425
|
+
: result.error?.includes("Timeout")
|
|
1426
|
+
? "failed"
|
|
1427
|
+
: "failed";
|
|
1428
|
+
await stateManager.updatePhaseStatus(issueNumber, phase, phaseStatus, { error: result.error });
|
|
1429
|
+
}
|
|
1430
|
+
catch {
|
|
1431
|
+
// State tracking errors shouldn't stop execution
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1190
1434
|
if (result.success) {
|
|
1191
1435
|
const duration = result.durationSeconds
|
|
1192
1436
|
? ` (${formatDuration(result.durationSeconds)})`
|
|
@@ -1231,6 +1475,20 @@ async function runIssueWithLogging(issueNumber, config, logWriter, labels, optio
|
|
|
1231
1475
|
// Success is determined by whether all phases completed in any iteration,
|
|
1232
1476
|
// not whether all accumulated phase results passed (which would fail after loop recovery)
|
|
1233
1477
|
const success = completedSuccessfully;
|
|
1478
|
+
// Update final issue status in state
|
|
1479
|
+
if (stateManager) {
|
|
1480
|
+
try {
|
|
1481
|
+
const finalStatus = success ? "ready_for_merge" : "in_progress";
|
|
1482
|
+
await stateManager.updateIssueStatus(issueNumber, finalStatus);
|
|
1483
|
+
}
|
|
1484
|
+
catch {
|
|
1485
|
+
// State tracking errors shouldn't stop execution
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
// Create checkpoint commit in chain mode after QA passes
|
|
1489
|
+
if (success && chainMode && worktreePath) {
|
|
1490
|
+
createCheckpointCommit(worktreePath, issueNumber, config.verbose);
|
|
1491
|
+
}
|
|
1234
1492
|
return {
|
|
1235
1493
|
issueNumber,
|
|
1236
1494
|
success,
|
|
@@ -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>;
|