sequant 1.4.0 → 1.5.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.
Files changed (153) hide show
  1. package/README.md +29 -0
  2. package/dist/bin/cli.d.ts +0 -1
  3. package/dist/bin/cli.js +0 -1
  4. package/dist/src/commands/doctor.d.ts +1 -5
  5. package/dist/src/commands/doctor.js +1 -2
  6. package/dist/src/commands/init.d.ts +0 -1
  7. package/dist/src/commands/init.js +0 -3
  8. package/dist/src/commands/logs.d.ts +0 -1
  9. package/dist/src/commands/logs.js +0 -1
  10. package/dist/src/commands/run.d.ts +0 -1
  11. package/dist/src/commands/run.js +161 -97
  12. package/dist/src/commands/stats.d.ts +0 -1
  13. package/dist/src/commands/stats.js +0 -1
  14. package/dist/src/commands/status.d.ts +0 -1
  15. package/dist/src/commands/status.js +0 -1
  16. package/dist/src/commands/update.d.ts +0 -1
  17. package/dist/src/commands/update.js +0 -1
  18. package/dist/src/index.d.ts +0 -1
  19. package/dist/src/index.js +0 -1
  20. package/dist/src/lib/config.d.ts +0 -1
  21. package/dist/src/lib/config.js +0 -1
  22. package/dist/src/lib/fs.d.ts +0 -1
  23. package/dist/src/lib/fs.js +0 -1
  24. package/dist/src/lib/manifest.d.ts +0 -1
  25. package/dist/src/lib/manifest.js +0 -1
  26. package/dist/src/lib/settings.d.ts +26 -1
  27. package/dist/src/lib/settings.js +12 -1
  28. package/dist/src/lib/shutdown.d.ts +117 -0
  29. package/dist/src/lib/shutdown.js +173 -0
  30. package/dist/src/lib/stacks.d.ts +0 -1
  31. package/dist/src/lib/stacks.js +0 -1
  32. package/dist/src/lib/system.d.ts +0 -1
  33. package/dist/src/lib/system.js +0 -1
  34. package/dist/src/lib/templates.d.ts +0 -1
  35. package/dist/src/lib/templates.js +0 -1
  36. package/dist/src/lib/tty.d.ts +0 -1
  37. package/dist/src/lib/tty.js +0 -1
  38. package/dist/src/lib/wizard.d.ts +0 -1
  39. package/dist/src/lib/wizard.js +0 -1
  40. package/dist/src/lib/workflow/log-rotation.d.ts +0 -1
  41. package/dist/src/lib/workflow/log-rotation.js +0 -2
  42. package/dist/src/lib/workflow/log-writer.d.ts +0 -1
  43. package/dist/src/lib/workflow/log-writer.js +0 -1
  44. package/dist/src/lib/workflow/run-log-schema.d.ts +4 -5
  45. package/dist/src/lib/workflow/run-log-schema.js +0 -1
  46. package/dist/src/lib/workflow/types.d.ts +0 -1
  47. package/dist/src/lib/workflow/types.js +0 -1
  48. package/package.json +8 -2
  49. package/templates/skills/assess/SKILL.md +31 -0
  50. package/templates/skills/exec/SKILL.md +164 -15
  51. package/templates/skills/fullsolve/SKILL.md +61 -10
  52. package/templates/skills/loop/SKILL.md +48 -3
  53. package/templates/skills/spec/SKILL.md +97 -2
  54. package/dist/bin/cli.d.ts.map +0 -1
  55. package/dist/bin/cli.js.map +0 -1
  56. package/dist/src/commands/doctor.d.ts.map +0 -1
  57. package/dist/src/commands/doctor.js.map +0 -1
  58. package/dist/src/commands/doctor.test.d.ts +0 -2
  59. package/dist/src/commands/doctor.test.d.ts.map +0 -1
  60. package/dist/src/commands/doctor.test.js +0 -285
  61. package/dist/src/commands/doctor.test.js.map +0 -1
  62. package/dist/src/commands/init.d.ts.map +0 -1
  63. package/dist/src/commands/init.js.map +0 -1
  64. package/dist/src/commands/init.test.d.ts +0 -2
  65. package/dist/src/commands/init.test.d.ts.map +0 -1
  66. package/dist/src/commands/init.test.js +0 -313
  67. package/dist/src/commands/init.test.js.map +0 -1
  68. package/dist/src/commands/logs.d.ts.map +0 -1
  69. package/dist/src/commands/logs.js.map +0 -1
  70. package/dist/src/commands/run.d.ts.map +0 -1
  71. package/dist/src/commands/run.js.map +0 -1
  72. package/dist/src/commands/run.test.d.ts +0 -2
  73. package/dist/src/commands/run.test.d.ts.map +0 -1
  74. package/dist/src/commands/run.test.js +0 -320
  75. package/dist/src/commands/run.test.js.map +0 -1
  76. package/dist/src/commands/stats.d.ts.map +0 -1
  77. package/dist/src/commands/stats.js.map +0 -1
  78. package/dist/src/commands/stats.test.d.ts +0 -7
  79. package/dist/src/commands/stats.test.d.ts.map +0 -1
  80. package/dist/src/commands/stats.test.js +0 -218
  81. package/dist/src/commands/stats.test.js.map +0 -1
  82. package/dist/src/commands/status.d.ts.map +0 -1
  83. package/dist/src/commands/status.js.map +0 -1
  84. package/dist/src/commands/update.d.ts.map +0 -1
  85. package/dist/src/commands/update.js.map +0 -1
  86. package/dist/src/index.d.ts.map +0 -1
  87. package/dist/src/index.js.map +0 -1
  88. package/dist/src/lib/config.d.ts.map +0 -1
  89. package/dist/src/lib/config.js.map +0 -1
  90. package/dist/src/lib/fs.d.ts.map +0 -1
  91. package/dist/src/lib/fs.js.map +0 -1
  92. package/dist/src/lib/manifest.d.ts.map +0 -1
  93. package/dist/src/lib/manifest.js.map +0 -1
  94. package/dist/src/lib/settings.d.ts.map +0 -1
  95. package/dist/src/lib/settings.js.map +0 -1
  96. package/dist/src/lib/stacks.d.ts.map +0 -1
  97. package/dist/src/lib/stacks.js.map +0 -1
  98. package/dist/src/lib/stacks.test.d.ts +0 -2
  99. package/dist/src/lib/stacks.test.d.ts.map +0 -1
  100. package/dist/src/lib/stacks.test.js +0 -487
  101. package/dist/src/lib/stacks.test.js.map +0 -1
  102. package/dist/src/lib/system.d.ts.map +0 -1
  103. package/dist/src/lib/system.js.map +0 -1
  104. package/dist/src/lib/system.test.d.ts +0 -2
  105. package/dist/src/lib/system.test.d.ts.map +0 -1
  106. package/dist/src/lib/system.test.js +0 -107
  107. package/dist/src/lib/system.test.js.map +0 -1
  108. package/dist/src/lib/templates.d.ts.map +0 -1
  109. package/dist/src/lib/templates.js.map +0 -1
  110. package/dist/src/lib/tty.d.ts.map +0 -1
  111. package/dist/src/lib/tty.js.map +0 -1
  112. package/dist/src/lib/tty.test.d.ts +0 -2
  113. package/dist/src/lib/tty.test.d.ts.map +0 -1
  114. package/dist/src/lib/tty.test.js +0 -269
  115. package/dist/src/lib/tty.test.js.map +0 -1
  116. package/dist/src/lib/wizard.d.ts.map +0 -1
  117. package/dist/src/lib/wizard.js.map +0 -1
  118. package/dist/src/lib/wizard.test.d.ts +0 -2
  119. package/dist/src/lib/wizard.test.d.ts.map +0 -1
  120. package/dist/src/lib/wizard.test.js +0 -268
  121. package/dist/src/lib/wizard.test.js.map +0 -1
  122. package/dist/src/lib/workflow/cli-args.d.ts +0 -138
  123. package/dist/src/lib/workflow/cli-args.d.ts.map +0 -1
  124. package/dist/src/lib/workflow/cli-args.js +0 -210
  125. package/dist/src/lib/workflow/cli-args.js.map +0 -1
  126. package/dist/src/lib/workflow/execute-issues.d.ts +0 -42
  127. package/dist/src/lib/workflow/execute-issues.d.ts.map +0 -1
  128. package/dist/src/lib/workflow/execute-issues.js +0 -463
  129. package/dist/src/lib/workflow/execute-issues.js.map +0 -1
  130. package/dist/src/lib/workflow/log-rotation.d.ts.map +0 -1
  131. package/dist/src/lib/workflow/log-rotation.js.map +0 -1
  132. package/dist/src/lib/workflow/log-rotation.test.d.ts +0 -7
  133. package/dist/src/lib/workflow/log-rotation.test.d.ts.map +0 -1
  134. package/dist/src/lib/workflow/log-rotation.test.js +0 -248
  135. package/dist/src/lib/workflow/log-rotation.test.js.map +0 -1
  136. package/dist/src/lib/workflow/log-writer.d.ts.map +0 -1
  137. package/dist/src/lib/workflow/log-writer.js.map +0 -1
  138. package/dist/src/lib/workflow/log-writer.test.d.ts +0 -7
  139. package/dist/src/lib/workflow/log-writer.test.d.ts.map +0 -1
  140. package/dist/src/lib/workflow/log-writer.test.js +0 -454
  141. package/dist/src/lib/workflow/log-writer.test.js.map +0 -1
  142. package/dist/src/lib/workflow/logger.d.ts +0 -168
  143. package/dist/src/lib/workflow/logger.d.ts.map +0 -1
  144. package/dist/src/lib/workflow/logger.js +0 -249
  145. package/dist/src/lib/workflow/logger.js.map +0 -1
  146. package/dist/src/lib/workflow/run-log-schema.d.ts.map +0 -1
  147. package/dist/src/lib/workflow/run-log-schema.js.map +0 -1
  148. package/dist/src/lib/workflow/run-log-schema.test.d.ts +0 -2
  149. package/dist/src/lib/workflow/run-log-schema.test.d.ts.map +0 -1
  150. package/dist/src/lib/workflow/run-log-schema.test.js +0 -455
  151. package/dist/src/lib/workflow/run-log-schema.test.js.map +0 -1
  152. package/dist/src/lib/workflow/types.d.ts.map +0 -1
  153. package/dist/src/lib/workflow/types.js.map +0 -1
package/README.md CHANGED
@@ -311,10 +311,16 @@ Configure `sequant run` defaults in `.sequant/settings.json`:
311
311
  "qualityLoop": false,
312
312
  "maxIterations": 3,
313
313
  "smartTests": true
314
+ },
315
+ "agents": {
316
+ "parallel": false,
317
+ "model": "haiku"
314
318
  }
315
319
  }
316
320
  ```
317
321
 
322
+ #### Run Settings
323
+
318
324
  | Option | Default | Description |
319
325
  |--------|---------|-------------|
320
326
  | `logJson` | `true` | Enable JSON logging for run sessions |
@@ -326,6 +332,29 @@ Configure `sequant run` defaults in `.sequant/settings.json`:
326
332
  | `maxIterations` | `3` | Max quality loop iterations |
327
333
  | `smartTests` | `true` | Auto-detect test commands |
328
334
 
335
+ #### Agent Settings
336
+
337
+ | Option | Default | Description |
338
+ |--------|---------|-------------|
339
+ | `parallel` | `false` | Run sub-agents in parallel (faster but higher token usage) |
340
+ | `model` | `"haiku"` | Default model for sub-agents (`haiku`, `sonnet`, or `opus`) |
341
+
342
+ ##### Cost vs Speed Trade-offs
343
+
344
+ Skills like `/qa`, `/merger`, and `/fullsolve` spawn sub-agents for quality checks. The `agents` settings control how these agents execute:
345
+
346
+ | Mode | Token Usage | Speed | Best For |
347
+ |------|-------------|-------|----------|
348
+ | Sequential (`parallel: false`) | 1x (baseline) | Slower | Limited API plans, cost-conscious users |
349
+ | Parallel (`parallel: true`) | ~2-3x | ~50% faster | Unlimited plans, batch operations |
350
+
351
+ **Override per invocation:**
352
+
353
+ ```bash
354
+ /qa 123 --parallel # Force parallel (faster)
355
+ /qa 123 --sequential # Force sequential (cheaper)
356
+ ```
357
+
329
358
  CLI flags override settings file values.
330
359
 
331
360
  ## Directory Structure
package/dist/bin/cli.d.ts CHANGED
@@ -5,4 +5,3 @@
5
5
  * Sequential AI phases with quality gates for any codebase.
6
6
  */
7
7
  export {};
8
- //# sourceMappingURL=cli.d.ts.map
package/dist/bin/cli.js CHANGED
@@ -123,4 +123,3 @@ if (!process.argv.slice(2).length) {
123
123
  `));
124
124
  program.help();
125
125
  }
126
- //# sourceMappingURL=cli.js.map
@@ -1,8 +1,4 @@
1
1
  /**
2
2
  * sequant doctor - Check installation health
3
3
  */
4
- interface DoctorOptions {
5
- }
6
- export declare function doctorCommand(options: DoctorOptions): Promise<void>;
7
- export {};
8
- //# sourceMappingURL=doctor.d.ts.map
4
+ export declare function doctorCommand(): Promise<void>;
@@ -5,7 +5,7 @@ import chalk from "chalk";
5
5
  import { fileExists, isExecutable } from "../lib/fs.js";
6
6
  import { getManifest } from "../lib/manifest.js";
7
7
  import { commandExists, isGhAuthenticated, isNativeWindows, isWSL, checkOptionalMcpServers, OPTIONAL_MCP_SERVERS, } from "../lib/system.js";
8
- export async function doctorCommand(options) {
8
+ export async function doctorCommand() {
9
9
  console.log(chalk.blue("\n🔍 Running health checks...\n"));
10
10
  const checks = [];
11
11
  // Check 1: Manifest exists
@@ -277,4 +277,3 @@ export async function doctorCommand(options) {
277
277
  console.log(chalk.green("\n✅ All checks passed!"));
278
278
  }
279
279
  }
280
- //# sourceMappingURL=doctor.js.map
@@ -10,4 +10,3 @@ interface InitOptions {
10
10
  }
11
11
  export declare function initCommand(options: InitOptions): Promise<void>;
12
12
  export {};
13
- //# sourceMappingURL=init.d.ts.map
@@ -47,10 +47,8 @@ const GITIGNORE_ENTRIES = [
47
47
  async function updateGitignore() {
48
48
  const gitignorePath = ".gitignore";
49
49
  let content = "";
50
- let existed = false;
51
50
  if (await fileExists(gitignorePath)) {
52
51
  content = await readFile(gitignorePath);
53
- existed = true;
54
52
  // Check if already has .sequant/
55
53
  if (content.includes(".sequant/")) {
56
54
  return false; // Already configured
@@ -300,4 +298,3 @@ ${chalk.bold("Documentation:")}
300
298
  https://github.com/admarble/sequant#readme
301
299
  `));
302
300
  }
303
- //# sourceMappingURL=init.js.map
@@ -17,4 +17,3 @@ interface LogsOptions {
17
17
  */
18
18
  export declare function logsCommand(options: LogsOptions): Promise<void>;
19
19
  export {};
20
- //# sourceMappingURL=logs.d.ts.map
@@ -226,4 +226,3 @@ export async function logsCommand(options) {
226
226
  `));
227
227
  }
228
228
  }
229
- //# sourceMappingURL=logs.js.map
@@ -62,4 +62,3 @@ interface RunOptions {
62
62
  */
63
63
  export declare function runCommand(issues: string[], options: RunOptions): Promise<void>;
64
64
  export {};
65
- //# sourceMappingURL=run.d.ts.map
@@ -14,6 +14,7 @@ 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
16
  import { DEFAULT_PHASES, DEFAULT_CONFIG, } from "../lib/workflow/types.js";
17
+ import { ShutdownManager } from "../lib/shutdown.js";
17
18
  /**
18
19
  * Slugify a title for branch naming
19
20
  */
@@ -358,7 +359,7 @@ const ISOLATED_PHASES = ["exec", "test", "qa"];
358
359
  /**
359
360
  * Execute a single phase for an issue using Claude Agent SDK
360
361
  */
361
- async function executePhase(issueNumber, phase, config, sessionId, worktreePath) {
362
+ async function executePhase(issueNumber, phase, config, sessionId, worktreePath, shutdownManager) {
362
363
  const startTime = Date.now();
363
364
  if (config.dryRun) {
364
365
  // Dry run - just simulate
@@ -382,11 +383,24 @@ async function executePhase(issueNumber, phase, config, sessionId, worktreePath)
382
383
  const shouldUseWorktree = worktreePath && ISOLATED_PHASES.includes(phase);
383
384
  const cwd = shouldUseWorktree ? worktreePath : process.cwd();
384
385
  try {
386
+ // Check if shutdown is in progress
387
+ if (shutdownManager?.shuttingDown) {
388
+ return {
389
+ phase,
390
+ success: false,
391
+ durationSeconds: 0,
392
+ error: "Shutdown in progress",
393
+ };
394
+ }
385
395
  // Create abort controller for timeout
386
396
  const abortController = new AbortController();
387
397
  const timeoutId = setTimeout(() => {
388
398
  abortController.abort();
389
399
  }, config.phaseTimeout * 1000);
400
+ // Register abort controller with shutdown manager for graceful shutdown
401
+ if (shutdownManager) {
402
+ shutdownManager.setAbortController(abortController);
403
+ }
390
404
  let resultSessionId;
391
405
  let resultMessage;
392
406
  let lastError;
@@ -455,6 +469,10 @@ async function executePhase(issueNumber, phase, config, sessionId, worktreePath)
455
469
  }
456
470
  }
457
471
  clearTimeout(timeoutId);
472
+ // Clear abort controller from shutdown manager
473
+ if (shutdownManager) {
474
+ shutdownManager.clearAbortController();
475
+ }
458
476
  const durationSeconds = (Date.now() - startTime) / 1000;
459
477
  // Check result status
460
478
  if (resultMessage) {
@@ -651,7 +669,7 @@ function hasUILabels(labels) {
651
669
  * Determine phases to run based on options and issue labels
652
670
  */
653
671
  function determinePhasesForIssue(basePhases, labels, options) {
654
- let phases = [...basePhases];
672
+ const phases = [...basePhases];
655
673
  // Add testgen phase after spec if requested
656
674
  if (options.testgen && phases.includes("spec")) {
657
675
  const specIndex = phases.indexOf("spec");
@@ -797,6 +815,15 @@ export async function runCommand(issues, options) {
797
815
  });
798
816
  await logWriter.initialize(runConfig);
799
817
  }
818
+ // Initialize shutdown manager for graceful interruption handling
819
+ const shutdown = new ShutdownManager();
820
+ // Register log writer finalization as cleanup task
821
+ if (logWriter) {
822
+ const writer = logWriter; // Capture for closure
823
+ shutdown.registerCleanup("Finalize run logs", async () => {
824
+ await writer.finalize();
825
+ });
826
+ }
800
827
  // Display configuration
801
828
  console.log(chalk.gray(` Stack: ${manifest.stack}`));
802
829
  if (autoDetectPhases) {
@@ -840,112 +867,149 @@ export async function runCommand(issues, options) {
840
867
  title: issueInfoMap.get(num)?.title || `Issue #${num}`,
841
868
  }));
842
869
  worktreeMap = await ensureWorktrees(issueData, config.verbose, manifest.packageManager);
843
- }
844
- // Execute
845
- const results = [];
846
- if (batches) {
847
- // Batch execution: run batches sequentially, issues within batch based on mode
848
- for (let batchIdx = 0; batchIdx < batches.length; batchIdx++) {
849
- const batch = batches[batchIdx];
850
- console.log(chalk.blue(`\n Batch ${batchIdx + 1}/${batches.length}: Issues ${batch.map((n) => `#${n}`).join(", ")}`));
851
- const batchResults = await executeBatch(batch, config, logWriter, mergedOptions, issueInfoMap, worktreeMap);
852
- results.push(...batchResults);
853
- // Check if batch failed and we should stop
854
- const batchFailed = batchResults.some((r) => !r.success);
855
- if (batchFailed && config.sequential) {
856
- console.log(chalk.yellow(`\n ⚠️ Batch ${batchIdx + 1} failed, stopping batch execution`));
857
- break;
870
+ // Register cleanup tasks for newly created worktrees (not pre-existing ones)
871
+ for (const [issueNum, worktree] of worktreeMap.entries()) {
872
+ if (!worktree.existed) {
873
+ shutdown.registerCleanup(`Cleanup worktree for #${issueNum}`, async () => {
874
+ // Remove worktree (leaves branch intact for recovery)
875
+ const result = spawnSync("git", ["worktree", "remove", "--force", worktree.path], {
876
+ stdio: "pipe",
877
+ });
878
+ if (result.status !== 0 && config.verbose) {
879
+ console.log(chalk.yellow(` Warning: Could not remove worktree ${worktree.path}`));
880
+ }
881
+ });
858
882
  }
859
883
  }
860
884
  }
861
- else if (config.sequential) {
862
- // Sequential execution
863
- for (const issueNumber of issueNumbers) {
864
- const issueInfo = issueInfoMap.get(issueNumber) ?? {
865
- title: `Issue #${issueNumber}`,
866
- labels: [],
867
- };
868
- const worktreeInfo = worktreeMap.get(issueNumber);
869
- // Start issue logging
870
- if (logWriter) {
871
- logWriter.startIssue(issueNumber, issueInfo.title, issueInfo.labels);
872
- }
873
- const result = await runIssueWithLogging(issueNumber, config, logWriter, issueInfo.labels, mergedOptions, worktreeInfo?.path);
874
- results.push(result);
875
- // Complete issue logging
876
- if (logWriter) {
877
- logWriter.completeIssue();
878
- }
879
- if (!result.success) {
880
- console.log(chalk.yellow(`\n ⚠️ Issue #${issueNumber} failed, stopping sequential execution`));
881
- break;
885
+ // Execute with graceful shutdown handling
886
+ const results = [];
887
+ let exitCode = 0;
888
+ try {
889
+ if (batches) {
890
+ // Batch execution: run batches sequentially, issues within batch based on mode
891
+ for (let batchIdx = 0; batchIdx < batches.length; batchIdx++) {
892
+ const batch = batches[batchIdx];
893
+ console.log(chalk.blue(`\n Batch ${batchIdx + 1}/${batches.length}: Issues ${batch.map((n) => `#${n}`).join(", ")}`));
894
+ const batchResults = await executeBatch(batch, config, logWriter, mergedOptions, issueInfoMap, worktreeMap, shutdown);
895
+ results.push(...batchResults);
896
+ // Check if batch failed and we should stop
897
+ const batchFailed = batchResults.some((r) => !r.success);
898
+ if (batchFailed && config.sequential) {
899
+ console.log(chalk.yellow(`\n ⚠️ Batch ${batchIdx + 1} failed, stopping batch execution`));
900
+ break;
901
+ }
882
902
  }
883
903
  }
884
- }
885
- else {
886
- // Parallel execution (for now, just run sequentially but don't stop on failure)
887
- // TODO: Add proper parallel execution with listr2
888
- for (const issueNumber of issueNumbers) {
889
- const issueInfo = issueInfoMap.get(issueNumber) ?? {
890
- title: `Issue #${issueNumber}`,
891
- labels: [],
892
- };
893
- const worktreeInfo = worktreeMap.get(issueNumber);
894
- // Start issue logging
895
- if (logWriter) {
896
- logWriter.startIssue(issueNumber, issueInfo.title, issueInfo.labels);
904
+ else if (config.sequential) {
905
+ // Sequential execution
906
+ for (const issueNumber of issueNumbers) {
907
+ const issueInfo = issueInfoMap.get(issueNumber) ?? {
908
+ title: `Issue #${issueNumber}`,
909
+ labels: [],
910
+ };
911
+ const worktreeInfo = worktreeMap.get(issueNumber);
912
+ // Start issue logging
913
+ if (logWriter) {
914
+ logWriter.startIssue(issueNumber, issueInfo.title, issueInfo.labels);
915
+ }
916
+ const result = await runIssueWithLogging(issueNumber, config, logWriter, issueInfo.labels, mergedOptions, worktreeInfo?.path, shutdown);
917
+ results.push(result);
918
+ // Complete issue logging
919
+ if (logWriter) {
920
+ logWriter.completeIssue();
921
+ }
922
+ // Check if shutdown was triggered
923
+ if (shutdown.shuttingDown) {
924
+ break;
925
+ }
926
+ if (!result.success) {
927
+ console.log(chalk.yellow(`\n ⚠️ Issue #${issueNumber} failed, stopping sequential execution`));
928
+ break;
929
+ }
897
930
  }
898
- const result = await runIssueWithLogging(issueNumber, config, logWriter, issueInfo.labels, mergedOptions, worktreeInfo?.path);
899
- results.push(result);
900
- // Complete issue logging
901
- if (logWriter) {
902
- logWriter.completeIssue();
931
+ }
932
+ else {
933
+ // Parallel execution (for now, just run sequentially but don't stop on failure)
934
+ // TODO: Add proper parallel execution with listr2
935
+ for (const issueNumber of issueNumbers) {
936
+ // Check if shutdown was triggered
937
+ if (shutdown.shuttingDown) {
938
+ break;
939
+ }
940
+ const issueInfo = issueInfoMap.get(issueNumber) ?? {
941
+ title: `Issue #${issueNumber}`,
942
+ labels: [],
943
+ };
944
+ const worktreeInfo = worktreeMap.get(issueNumber);
945
+ // Start issue logging
946
+ if (logWriter) {
947
+ logWriter.startIssue(issueNumber, issueInfo.title, issueInfo.labels);
948
+ }
949
+ const result = await runIssueWithLogging(issueNumber, config, logWriter, issueInfo.labels, mergedOptions, worktreeInfo?.path, shutdown);
950
+ results.push(result);
951
+ // Complete issue logging
952
+ if (logWriter) {
953
+ logWriter.completeIssue();
954
+ }
903
955
  }
904
956
  }
905
- }
906
- // Finalize log
907
- let logPath = null;
908
- if (logWriter) {
909
- logPath = await logWriter.finalize();
910
- }
911
- // Summary
912
- console.log(chalk.blue("\n" + "━".repeat(50)));
913
- console.log(chalk.blue(" Summary"));
914
- console.log(chalk.blue("━".repeat(50)));
915
- const passed = results.filter((r) => r.success).length;
916
- const failed = results.filter((r) => !r.success).length;
917
- console.log(chalk.gray(`\n Results: ${chalk.green(`${passed} passed`)}, ${chalk.red(`${failed} failed`)}`));
918
- for (const result of results) {
919
- const status = result.success ? chalk.green("✓") : chalk.red("✗");
920
- const duration = result.durationSeconds
921
- ? chalk.gray(` (${formatDuration(result.durationSeconds)})`)
922
- : "";
923
- const phases = result.phaseResults
924
- .map((p) => (p.success ? chalk.green(p.phase) : chalk.red(p.phase)))
925
- .join(" ");
926
- const loopInfo = result.loopTriggered ? chalk.yellow(" [loop]") : "";
927
- console.log(` ${status} #${result.issueNumber}: ${phases}${loopInfo}${duration}`);
928
- }
929
- console.log("");
930
- if (logPath) {
931
- console.log(chalk.gray(` 📝 Log: ${logPath}`));
957
+ // Finalize log
958
+ let logPath = null;
959
+ if (logWriter) {
960
+ logPath = await logWriter.finalize();
961
+ }
962
+ // Summary
963
+ console.log(chalk.blue("\n" + "━".repeat(50)));
964
+ console.log(chalk.blue(" Summary"));
965
+ console.log(chalk.blue("".repeat(50)));
966
+ const passed = results.filter((r) => r.success).length;
967
+ const failed = results.filter((r) => !r.success).length;
968
+ console.log(chalk.gray(`\n Results: ${chalk.green(`${passed} passed`)}, ${chalk.red(`${failed} failed`)}`));
969
+ for (const result of results) {
970
+ const status = result.success ? chalk.green("✓") : chalk.red("✗");
971
+ const duration = result.durationSeconds
972
+ ? chalk.gray(` (${formatDuration(result.durationSeconds)})`)
973
+ : "";
974
+ const phases = result.phaseResults
975
+ .map((p) => (p.success ? chalk.green(p.phase) : chalk.red(p.phase)))
976
+ .join(" ");
977
+ const loopInfo = result.loopTriggered ? chalk.yellow(" [loop]") : "";
978
+ console.log(` ${status} #${result.issueNumber}: ${phases}${loopInfo}${duration}`);
979
+ }
932
980
  console.log("");
981
+ if (logPath) {
982
+ console.log(chalk.gray(` 📝 Log: ${logPath}`));
983
+ console.log("");
984
+ }
985
+ if (config.dryRun) {
986
+ console.log(chalk.yellow(" ℹ️ This was a dry run. Use without --dry-run to execute."));
987
+ console.log("");
988
+ }
989
+ // Set exit code if any failed
990
+ if (failed > 0 && !config.dryRun) {
991
+ exitCode = 1;
992
+ }
933
993
  }
934
- if (config.dryRun) {
935
- console.log(chalk.yellow(" ℹ️ This was a dry run. Use without --dry-run to execute."));
936
- console.log("");
994
+ finally {
995
+ // Always dispose shutdown manager to clean up signal handlers
996
+ shutdown.dispose();
937
997
  }
938
- // Exit with error if any failed
939
- if (failed > 0 && !config.dryRun) {
940
- process.exit(1);
998
+ // Exit with error if any failed (outside try/finally so dispose() runs first)
999
+ if (exitCode !== 0) {
1000
+ process.exit(exitCode);
941
1001
  }
942
1002
  }
943
1003
  /**
944
1004
  * Execute a batch of issues
945
1005
  */
946
- async function executeBatch(issueNumbers, config, logWriter, options, issueInfoMap, worktreeMap) {
1006
+ async function executeBatch(issueNumbers, config, logWriter, options, issueInfoMap, worktreeMap, shutdownManager) {
947
1007
  const results = [];
948
1008
  for (const issueNumber of issueNumbers) {
1009
+ // Check if shutdown was triggered
1010
+ if (shutdownManager?.shuttingDown) {
1011
+ break;
1012
+ }
949
1013
  const issueInfo = issueInfoMap.get(issueNumber) ?? {
950
1014
  title: `Issue #${issueNumber}`,
951
1015
  labels: [],
@@ -955,7 +1019,7 @@ async function executeBatch(issueNumbers, config, logWriter, options, issueInfoM
955
1019
  if (logWriter) {
956
1020
  logWriter.startIssue(issueNumber, issueInfo.title, issueInfo.labels);
957
1021
  }
958
- const result = await runIssueWithLogging(issueNumber, config, logWriter, issueInfo.labels, options, worktreeInfo?.path);
1022
+ const result = await runIssueWithLogging(issueNumber, config, logWriter, issueInfo.labels, options, worktreeInfo?.path, shutdownManager);
959
1023
  results.push(result);
960
1024
  // Complete issue logging
961
1025
  if (logWriter) {
@@ -967,7 +1031,7 @@ async function executeBatch(issueNumbers, config, logWriter, options, issueInfoM
967
1031
  /**
968
1032
  * Execute all phases for a single issue with logging and quality loop
969
1033
  */
970
- async function runIssueWithLogging(issueNumber, config, logWriter, labels, options, worktreePath) {
1034
+ async function runIssueWithLogging(issueNumber, config, logWriter, labels, options, worktreePath, shutdownManager) {
971
1035
  const startTime = Date.now();
972
1036
  const phaseResults = [];
973
1037
  let loopTriggered = false;
@@ -995,7 +1059,8 @@ async function runIssueWithLogging(issueNumber, config, logWriter, labels, optio
995
1059
  console.log(chalk.gray(` ⏳ spec...`));
996
1060
  const specStartTime = new Date();
997
1061
  // Note: spec runs in main repo (not worktree) for planning
998
- const specResult = await executePhase(issueNumber, "spec", config, sessionId, worktreePath);
1062
+ const specResult = await executePhase(issueNumber, "spec", config, sessionId, worktreePath, // Will be ignored for spec (non-isolated phase)
1063
+ shutdownManager);
999
1064
  const specEndTime = new Date();
1000
1065
  if (specResult.sessionId) {
1001
1066
  sessionId = specResult.sessionId;
@@ -1027,7 +1092,7 @@ async function runIssueWithLogging(issueNumber, config, logWriter, labels, optio
1027
1092
  : "";
1028
1093
  console.log(chalk.green(` ✓ spec${duration}`));
1029
1094
  // Parse recommended workflow from spec output
1030
- let parsedWorkflow = specResult.output
1095
+ const parsedWorkflow = specResult.output
1031
1096
  ? parseRecommendedWorkflow(specResult.output)
1032
1097
  : null;
1033
1098
  if (parsedWorkflow) {
@@ -1082,7 +1147,7 @@ async function runIssueWithLogging(issueNumber, config, logWriter, labels, optio
1082
1147
  for (const phase of phases) {
1083
1148
  console.log(chalk.gray(` ⏳ ${phase}...`));
1084
1149
  const phaseStartTime = new Date();
1085
- const result = await executePhase(issueNumber, phase, config, sessionId, worktreePath);
1150
+ const result = await executePhase(issueNumber, phase, config, sessionId, worktreePath, shutdownManager);
1086
1151
  const phaseEndTime = new Date();
1087
1152
  // Capture session ID for subsequent phases
1088
1153
  if (result.sessionId) {
@@ -1110,7 +1175,7 @@ async function runIssueWithLogging(issueNumber, config, logWriter, labels, optio
1110
1175
  // If quality loop enabled, run loop phase to fix issues
1111
1176
  if (useQualityLoop && iteration < maxIterations) {
1112
1177
  console.log(chalk.yellow(` Running /loop to fix issues...`));
1113
- const loopResult = await executePhase(issueNumber, "loop", config, sessionId, worktreePath);
1178
+ const loopResult = await executePhase(issueNumber, "loop", config, sessionId, worktreePath, shutdownManager);
1114
1179
  phaseResults.push(loopResult);
1115
1180
  if (loopResult.sessionId) {
1116
1181
  sessionId = loopResult.sessionId;
@@ -1150,4 +1215,3 @@ async function runIssueWithLogging(issueNumber, config, logWriter, labels, optio
1150
1215
  loopTriggered,
1151
1216
  };
1152
1217
  }
1153
- //# sourceMappingURL=run.js.map
@@ -13,4 +13,3 @@ interface StatsOptions {
13
13
  */
14
14
  export declare function statsCommand(options: StatsOptions): Promise<void>;
15
15
  export {};
16
- //# sourceMappingURL=stats.d.ts.map
@@ -268,4 +268,3 @@ export async function statsCommand(options) {
268
268
  const stats = calculateStats(logs);
269
269
  displayStats(stats, logDir);
270
270
  }
271
- //# sourceMappingURL=stats.js.map
@@ -2,4 +2,3 @@
2
2
  * sequant status - Show version and configuration
3
3
  */
4
4
  export declare function statusCommand(): Promise<void>;
5
- //# sourceMappingURL=status.d.ts.map
@@ -42,4 +42,3 @@ export async function statusCommand() {
42
42
  }
43
43
  console.log(chalk.gray("\nRun `sequant doctor` for detailed health check."));
44
44
  }
45
- //# sourceMappingURL=status.js.map
@@ -7,4 +7,3 @@ interface UpdateOptions {
7
7
  }
8
8
  export declare function updateCommand(options: UpdateOptions): Promise<void>;
9
9
  export {};
10
- //# sourceMappingURL=update.d.ts.map
@@ -201,4 +201,3 @@ export async function updateCommand(options) {
201
201
  }
202
202
  }
203
203
  }
204
- //# sourceMappingURL=update.js.map
@@ -14,4 +14,3 @@ export { copyTemplates, listTemplateFiles, getTemplateContent, processTemplate,
14
14
  export type { StackConfig } from "./lib/stacks.js";
15
15
  export type { Manifest } from "./lib/manifest.js";
16
16
  export type { SequantConfig } from "./lib/config.js";
17
- //# sourceMappingURL=index.d.ts.map
package/dist/src/index.js CHANGED
@@ -11,4 +11,3 @@ export { detectStack, getStackConfig, STACKS } from "./lib/stacks.js";
11
11
  export { getManifest, createManifest, updateManifest } from "./lib/manifest.js";
12
12
  export { getConfig, saveConfig } from "./lib/config.js";
13
13
  export { copyTemplates, listTemplateFiles, getTemplateContent, processTemplate, } from "./lib/templates.js";
14
- //# sourceMappingURL=index.js.map
@@ -16,4 +16,3 @@ export declare function getConfig(): Promise<SequantConfig | null>;
16
16
  * Save the sequant configuration
17
17
  */
18
18
  export declare function saveConfig(config: SequantConfig): Promise<void>;
19
- //# sourceMappingURL=config.d.ts.map
@@ -28,4 +28,3 @@ export async function saveConfig(config) {
28
28
  await ensureDir(dirname(CONFIG_PATH));
29
29
  await writeFile(CONFIG_PATH, JSON.stringify(config, null, 2));
30
30
  }
31
- //# sourceMappingURL=config.js.map
@@ -7,4 +7,3 @@ export declare function ensureDir(path: string): Promise<void>;
7
7
  export declare function readFile(path: string): Promise<string>;
8
8
  export declare function writeFile(path: string, content: string): Promise<void>;
9
9
  export declare function getFileStats(path: string): Promise<import("fs").Stats>;
10
- //# sourceMappingURL=fs.d.ts.map
@@ -41,4 +41,3 @@ export async function writeFile(path, content) {
41
41
  export async function getFileStats(path) {
42
42
  return stat(path);
43
43
  }
44
- //# sourceMappingURL=fs.js.map
@@ -13,4 +13,3 @@ export interface Manifest {
13
13
  export declare function getManifest(): Promise<Manifest | null>;
14
14
  export declare function createManifest(stack: string, packageManager?: PackageManager): Promise<void>;
15
15
  export declare function updateManifest(): Promise<void>;
16
- //# sourceMappingURL=manifest.d.ts.map
@@ -60,4 +60,3 @@ export async function updateManifest() {
60
60
  manifest.updatedAt = new Date().toISOString();
61
61
  await writeFile(MANIFEST_PATH, JSON.stringify(manifest, null, 2));
62
62
  }
63
- //# sourceMappingURL=manifest.js.map
@@ -25,6 +25,26 @@ export interface RotationSettings {
25
25
  /** Maximum file count before rotation (default: 100) */
26
26
  maxFiles: number;
27
27
  }
28
+ /**
29
+ * Agent execution settings
30
+ *
31
+ * Controls how sub-agents are spawned in multi-issue skills.
32
+ * Affects token usage and execution speed.
33
+ */
34
+ export interface AgentSettings {
35
+ /**
36
+ * Run agents in parallel (faster, higher token usage).
37
+ * When false, agents run sequentially (slower, lower token usage).
38
+ * Default: false (cost-optimized)
39
+ */
40
+ parallel: boolean;
41
+ /**
42
+ * Default model for sub-agents.
43
+ * Options: "haiku" (cheapest), "sonnet" (balanced), "opus" (most capable)
44
+ * Default: "haiku"
45
+ */
46
+ model: "haiku" | "sonnet" | "opus";
47
+ }
28
48
  /**
29
49
  * Run command settings
30
50
  */
@@ -56,11 +76,17 @@ export interface SequantSettings {
56
76
  version: string;
57
77
  /** Run command settings */
58
78
  run: RunSettings;
79
+ /** Agent execution settings */
80
+ agents: AgentSettings;
59
81
  }
60
82
  /**
61
83
  * Default rotation settings
62
84
  */
63
85
  export declare const DEFAULT_ROTATION_SETTINGS: RotationSettings;
86
+ /**
87
+ * Default agent settings (cost-optimized)
88
+ */
89
+ export declare const DEFAULT_AGENT_SETTINGS: AgentSettings;
64
90
  /**
65
91
  * Default settings
66
92
  */
@@ -83,4 +109,3 @@ export declare function settingsExist(): Promise<boolean>;
83
109
  * Create default settings file
84
110
  */
85
111
  export declare function createDefaultSettings(): Promise<void>;
86
- //# sourceMappingURL=settings.d.ts.map