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 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
@@ -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
- const baseRef = baseBranch || "origin/main";
141
- // Fetch latest main to ensure worktree starts from fresh baseline (unless using local branch)
142
- if (!baseBranch) {
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 main...`));
159
+ console.log(chalk.gray(` 🔄 Fetching latest ${branchToFetch}...`));
145
160
  }
146
- const fetchResult = spawnSync("git", ["fetch", "origin", "main"], {
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/main, using local state`));
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
- console.log(chalk.blue("\n 📂 Preparing worktrees..."));
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
- console.log(chalk.blue("\n 🔗 Preparing chained worktrees..."));
243
- let previousBranch;
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
- console.log(chalk.gray(` Chained worktrees: ${created} created, ${reused} reused`));
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: origin/main → ${chainOrder}`));
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 configuration
2
+ * sequant status - Show version, configuration, and workflow state
3
3
  */
4
- export declare function statusCommand(): Promise<void>;
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>;