sequant 2.8.0 → 2.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.
Files changed (211) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +23 -4
  4. package/dist/bin/cli.js +99 -18
  5. package/dist/marketplace/external_plugins/sequant/.claude-plugin/plugin.json +1 -1
  6. package/dist/marketplace/external_plugins/sequant/.mcp.json +1 -1
  7. package/dist/marketplace/external_plugins/sequant/README.md +58 -2
  8. package/dist/marketplace/external_plugins/sequant/hooks/parallel-marker.sh +50 -0
  9. package/dist/marketplace/external_plugins/sequant/hooks/post-tool.sh +38 -10
  10. package/dist/marketplace/external_plugins/sequant/hooks/pre-tool.sh +405 -67
  11. package/dist/marketplace/external_plugins/sequant/skills/_shared/references/trust-model.md +18 -0
  12. package/dist/marketplace/external_plugins/sequant/skills/assess/SKILL.md +250 -344
  13. package/dist/marketplace/external_plugins/sequant/skills/assess/references/predicted-collision-detection.md +10 -7
  14. package/dist/marketplace/external_plugins/sequant/skills/docs/SKILL.md +4 -4
  15. package/dist/marketplace/external_plugins/sequant/skills/exec/SKILL.md +26 -6
  16. package/dist/marketplace/external_plugins/sequant/skills/fullsolve/SKILL.md +4 -2
  17. package/dist/marketplace/external_plugins/sequant/skills/loop/SKILL.md +38 -2
  18. package/dist/marketplace/external_plugins/sequant/skills/merger/SKILL.md +2 -2
  19. package/dist/marketplace/external_plugins/sequant/skills/qa/SKILL.md +337 -57
  20. package/dist/marketplace/external_plugins/sequant/skills/qa/references/anti-pattern-detection.md +6 -6
  21. package/dist/marketplace/external_plugins/sequant/skills/qa/references/call-site-review.md +2 -2
  22. package/dist/marketplace/external_plugins/sequant/skills/qa/references/code-review-checklist.md +2 -2
  23. package/dist/marketplace/external_plugins/sequant/skills/qa/references/fixtures/injection-issue-body.md +36 -0
  24. package/dist/marketplace/external_plugins/sequant/skills/qa/references/quality-gates.md +21 -7
  25. package/dist/marketplace/external_plugins/sequant/skills/qa/references/testing-requirements.md +1 -1
  26. package/dist/marketplace/external_plugins/sequant/skills/qa/scripts/quality-checks.sh +11 -11
  27. package/dist/marketplace/external_plugins/sequant/skills/reflect/SKILL.md +27 -13
  28. package/dist/marketplace/external_plugins/sequant/skills/reflect/references/documentation-tiers.md +80 -68
  29. package/dist/marketplace/external_plugins/sequant/skills/reflect/references/phase-reflection.md +31 -15
  30. package/dist/marketplace/external_plugins/sequant/skills/release/SKILL.md +11 -3
  31. package/dist/marketplace/external_plugins/sequant/skills/setup/SKILL.md +5 -5
  32. package/dist/marketplace/external_plugins/sequant/skills/spec/SKILL.md +2 -0
  33. package/dist/marketplace/external_plugins/sequant/skills/spec/references/verification-criteria.md +1 -1
  34. package/dist/marketplace/external_plugins/sequant/skills/test/SKILL.md +1 -1
  35. package/dist/src/commands/assess-render.d.ts +23 -0
  36. package/dist/src/commands/assess-render.js +60 -0
  37. package/dist/src/commands/doctor.js +8 -13
  38. package/dist/src/commands/init.js +13 -1
  39. package/dist/src/commands/locks.js +2 -0
  40. package/dist/src/commands/logs.js +20 -6
  41. package/dist/src/commands/merge.js +154 -2
  42. package/dist/src/commands/ready-tui-adapter.js +6 -1
  43. package/dist/src/commands/ready.d.ts +25 -3
  44. package/dist/src/commands/ready.js +39 -13
  45. package/dist/src/commands/resume.d.ts +113 -0
  46. package/dist/src/commands/resume.js +193 -0
  47. package/dist/src/commands/run-display.d.ts +20 -0
  48. package/dist/src/commands/run-display.js +117 -5
  49. package/dist/src/commands/run-flags.d.ts +42 -1
  50. package/dist/src/commands/run-flags.js +53 -1
  51. package/dist/src/commands/run-progress.js +26 -1
  52. package/dist/src/commands/run.js +11 -10
  53. package/dist/src/commands/state.js +7 -0
  54. package/dist/src/commands/stats.js +47 -0
  55. package/dist/src/commands/status.d.ts +9 -0
  56. package/dist/src/commands/status.js +24 -1
  57. package/dist/src/commands/sync.js +90 -19
  58. package/dist/src/commands/update.js +28 -5
  59. package/dist/src/lib/ac-parser.d.ts +14 -0
  60. package/dist/src/lib/ac-parser.js +99 -5
  61. package/dist/src/lib/assess/index.d.ts +10 -0
  62. package/dist/src/lib/assess/index.js +9 -0
  63. package/dist/src/lib/assess/renderer.d.ts +64 -0
  64. package/dist/src/lib/assess/renderer.js +481 -0
  65. package/dist/src/lib/assess/types.d.ts +224 -0
  66. package/dist/src/lib/assess/types.js +241 -0
  67. package/dist/src/lib/assess-collision-detect.d.ts +19 -2
  68. package/dist/src/lib/assess-collision-detect.js +68 -4
  69. package/dist/src/lib/ci/outputs.d.ts +15 -2
  70. package/dist/src/lib/ci/outputs.js +17 -5
  71. package/dist/src/lib/cli-flags.d.ts +33 -0
  72. package/dist/src/lib/cli-flags.js +46 -0
  73. package/dist/src/lib/cli-ui/run-renderer-types.d.ts +21 -7
  74. package/dist/src/lib/cli-ui/run-renderer.d.ts +10 -0
  75. package/dist/src/lib/cli-ui/run-renderer.js +79 -9
  76. package/dist/src/lib/errors.d.ts +84 -6
  77. package/dist/src/lib/errors.js +142 -17
  78. package/dist/src/lib/locks/index.d.ts +3 -3
  79. package/dist/src/lib/locks/index.js +2 -2
  80. package/dist/src/lib/locks/lock-manager.d.ts +20 -2
  81. package/dist/src/lib/locks/lock-manager.js +47 -3
  82. package/dist/src/lib/locks/types.d.ts +34 -3
  83. package/dist/src/lib/locks/types.js +16 -0
  84. package/dist/src/lib/manifest.js +1 -17
  85. package/dist/src/lib/mcp-config.d.ts +45 -0
  86. package/dist/src/lib/mcp-config.js +77 -1
  87. package/dist/src/lib/merge-check/combined-branch-test.d.ts +21 -2
  88. package/dist/src/lib/merge-check/combined-branch-test.js +233 -110
  89. package/dist/src/lib/merge-check/command-result.d.ts +36 -0
  90. package/dist/src/lib/merge-check/command-result.js +56 -0
  91. package/dist/src/lib/merge-check/index.d.ts +4 -0
  92. package/dist/src/lib/merge-check/index.js +1 -1
  93. package/dist/src/lib/merge-check/report.js +5 -1
  94. package/dist/src/lib/merge-check/types.d.ts +9 -0
  95. package/dist/src/lib/merge-check/watch.d.ts +121 -0
  96. package/dist/src/lib/merge-check/watch.js +215 -0
  97. package/dist/src/lib/qa/infra-blocked-ci.d.ts +90 -0
  98. package/dist/src/lib/qa/infra-blocked-ci.js +80 -0
  99. package/dist/src/lib/settings.d.ts +14 -0
  100. package/dist/src/lib/settings.js +61 -8
  101. package/dist/src/lib/shutdown.d.ts +42 -2
  102. package/dist/src/lib/shutdown.js +75 -5
  103. package/dist/src/lib/skills-check.d.ts +26 -0
  104. package/dist/src/lib/skills-check.js +31 -0
  105. package/dist/src/lib/stacks.d.ts +110 -1
  106. package/dist/src/lib/stacks.js +244 -5
  107. package/dist/src/lib/templates.d.ts +74 -0
  108. package/dist/src/lib/templates.js +171 -16
  109. package/dist/src/lib/test-tautology-detector.d.ts +9 -2
  110. package/dist/src/lib/test-tautology-detector.js +153 -12
  111. package/dist/src/lib/version-check.d.ts +31 -0
  112. package/dist/src/lib/version-check.js +46 -7
  113. package/dist/src/lib/workflow/batch-executor.d.ts +138 -6
  114. package/dist/src/lib/workflow/batch-executor.js +550 -54
  115. package/dist/src/lib/workflow/chain-preflight.d.ts +89 -0
  116. package/dist/src/lib/workflow/chain-preflight.js +199 -0
  117. package/dist/src/lib/workflow/chain-resume.d.ts +118 -0
  118. package/dist/src/lib/workflow/chain-resume.js +170 -0
  119. package/dist/src/lib/workflow/completed-status.d.ts +52 -0
  120. package/dist/src/lib/workflow/completed-status.js +57 -0
  121. package/dist/src/lib/workflow/config-resolver.d.ts +26 -0
  122. package/dist/src/lib/workflow/config-resolver.js +54 -2
  123. package/dist/src/lib/workflow/dependency-markers.d.ts +29 -0
  124. package/dist/src/lib/workflow/dependency-markers.js +79 -0
  125. package/dist/src/lib/workflow/drivers/agent-driver.d.ts +7 -0
  126. package/dist/src/lib/workflow/drivers/aider.d.ts +2 -0
  127. package/dist/src/lib/workflow/drivers/aider.js +2 -0
  128. package/dist/src/lib/workflow/drivers/claude-code.d.ts +21 -0
  129. package/dist/src/lib/workflow/drivers/claude-code.js +50 -8
  130. package/dist/src/lib/workflow/error-classifier.d.ts +9 -2
  131. package/dist/src/lib/workflow/error-classifier.js +14 -1
  132. package/dist/src/lib/workflow/git-diff-utils.d.ts +35 -3
  133. package/dist/src/lib/workflow/git-diff-utils.js +77 -4
  134. package/dist/src/lib/workflow/heartbeat.d.ts +38 -0
  135. package/dist/src/lib/workflow/heartbeat.js +90 -0
  136. package/dist/src/lib/workflow/log-writer.d.ts +44 -1
  137. package/dist/src/lib/workflow/log-writer.js +81 -14
  138. package/dist/src/lib/workflow/metrics-schema.d.ts +42 -3
  139. package/dist/src/lib/workflow/metrics-schema.js +16 -0
  140. package/dist/src/lib/workflow/metrics-writer.d.ts +2 -1
  141. package/dist/src/lib/workflow/phase-executor.d.ts +234 -13
  142. package/dist/src/lib/workflow/phase-executor.js +533 -56
  143. package/dist/src/lib/workflow/phase-registry.js +2 -1
  144. package/dist/src/lib/workflow/platforms/github.d.ts +64 -0
  145. package/dist/src/lib/workflow/platforms/github.js +152 -0
  146. package/dist/src/lib/workflow/qa-cache.d.ts +6 -1
  147. package/dist/src/lib/workflow/qa-cache.js +22 -9
  148. package/dist/src/lib/workflow/ready-gate.d.ts +17 -4
  149. package/dist/src/lib/workflow/ready-gate.js +25 -9
  150. package/dist/src/lib/workflow/run-log-schema.d.ts +37 -0
  151. package/dist/src/lib/workflow/run-log-schema.js +92 -2
  152. package/dist/src/lib/workflow/run-orchestrator.d.ts +33 -0
  153. package/dist/src/lib/workflow/run-orchestrator.js +429 -47
  154. package/dist/src/lib/workflow/skills-preflight.d.ts +89 -0
  155. package/dist/src/lib/workflow/skills-preflight.js +112 -0
  156. package/dist/src/lib/workflow/state-cleanup.js +9 -2
  157. package/dist/src/lib/workflow/state-manager.d.ts +33 -0
  158. package/dist/src/lib/workflow/state-manager.js +83 -0
  159. package/dist/src/lib/workflow/state-schema.d.ts +26 -0
  160. package/dist/src/lib/workflow/state-schema.js +47 -0
  161. package/dist/src/lib/workflow/status-derivation.d.ts +30 -0
  162. package/dist/src/lib/workflow/status-derivation.js +27 -0
  163. package/dist/src/lib/workflow/types.d.ts +149 -4
  164. package/dist/src/lib/workflow/types.js +1 -0
  165. package/dist/src/lib/workflow/worktree-manager.d.ts +70 -2
  166. package/dist/src/lib/workflow/worktree-manager.js +201 -52
  167. package/dist/src/mcp/tools/run.d.ts +11 -1
  168. package/dist/src/mcp/tools/run.js +22 -1
  169. package/dist/src/mcp/tools/status.js +6 -0
  170. package/dist/src/ui/tui/ElapsedTimer.d.ts +12 -3
  171. package/dist/src/ui/tui/ElapsedTimer.js +12 -10
  172. package/dist/src/ui/tui/IssueBox.js +10 -2
  173. package/dist/src/ui/tui/row-cap.js +2 -1
  174. package/dist/src/ui/tui/theme.d.ts +7 -0
  175. package/dist/src/ui/tui/theme.js +9 -0
  176. package/package.json +9 -10
  177. package/templates/hooks/parallel-marker.sh +50 -0
  178. package/templates/hooks/post-tool.sh +38 -10
  179. package/templates/hooks/pre-tool.sh +405 -67
  180. package/templates/scripts/cleanup-worktree.sh +282 -30
  181. package/templates/scripts/new-feature.sh +264 -16
  182. package/templates/skills/_shared/references/trust-model.md +18 -0
  183. package/templates/skills/assess/SKILL.md +250 -344
  184. package/templates/skills/assess/references/predicted-collision-detection.md +10 -7
  185. package/templates/skills/docs/SKILL.md +4 -4
  186. package/templates/skills/exec/SKILL.md +26 -6
  187. package/templates/skills/fullsolve/SKILL.md +4 -2
  188. package/templates/skills/loop/SKILL.md +38 -2
  189. package/templates/skills/merger/SKILL.md +2 -2
  190. package/templates/skills/qa/SKILL.md +337 -57
  191. package/templates/skills/qa/references/anti-pattern-detection.md +6 -6
  192. package/templates/skills/qa/references/call-site-review.md +2 -2
  193. package/templates/skills/qa/references/code-review-checklist.md +2 -2
  194. package/templates/skills/qa/references/fixtures/injection-issue-body.md +36 -0
  195. package/templates/skills/qa/references/quality-gates.md +21 -7
  196. package/templates/skills/qa/references/testing-requirements.md +1 -1
  197. package/templates/skills/qa/scripts/quality-checks.sh +11 -11
  198. package/templates/skills/reflect/SKILL.md +27 -13
  199. package/templates/skills/reflect/references/documentation-tiers.md +80 -68
  200. package/templates/skills/reflect/references/phase-reflection.md +31 -15
  201. package/templates/skills/release/SKILL.md +11 -3
  202. package/templates/skills/setup/SKILL.md +5 -5
  203. package/templates/skills/spec/SKILL.md +2 -0
  204. package/templates/skills/spec/references/verification-criteria.md +1 -1
  205. package/templates/skills/test/SKILL.md +1 -1
  206. package/dist/src/lib/phase-spinner.d.ts +0 -146
  207. package/dist/src/lib/phase-spinner.js +0 -255
  208. package/dist/src/lib/workflow/pr-operations.d.ts +0 -86
  209. package/dist/src/lib/workflow/pr-operations.js +0 -326
  210. package/dist/src/lib/workflow/run-summary.d.ts +0 -36
  211. package/dist/src/lib/workflow/run-summary.js +0 -142
@@ -20,6 +20,36 @@
20
20
  * shutdown.dispose();
21
21
  * ```
22
22
  */
23
+ /**
24
+ * Why the process is shutting down, handed to every cleanup task so they can
25
+ * record the cause rather than silently persisting a truncated record (#856).
26
+ */
27
+ export interface AbortContext {
28
+ /** Signal that triggered the shutdown, e.g. `SIGTERM`. */
29
+ signal: string;
30
+ /** Human-readable cause, from `describeSignalCause`. */
31
+ reason: string;
32
+ }
33
+ /**
34
+ * Conventional shell exit code for death-by-signal: `128 + signum`
35
+ * (SIGINT → 130, SIGTERM → 143). Falls back to 1 for anything unmapped.
36
+ *
37
+ * #856: this used to be a flat `0`. A run killed from outside therefore
38
+ * exited *successfully*, so nothing downstream — CI, a wrapping script, the
39
+ * user reading `$?` — could tell a completed run from a terminated one.
40
+ */
41
+ export declare function exitCodeForSignal(signal: string): number;
42
+ /**
43
+ * Human-readable cause for a termination signal (#856, AC-4).
44
+ *
45
+ * SIGINT is the user pressing Ctrl+C — self-explanatory. SIGTERM is not:
46
+ * no interactive user sends it by hand, so it always means something else
47
+ * killed the run, and the user needs to be told that rather than left to
48
+ * infer it from truncated output. The known offender on macOS is Claude
49
+ * Code's `[bg-pty]` host-dead watchdog, which group-SIGKILLs a process tree
50
+ * containing the run; see `docs/incidents/856/`.
51
+ */
52
+ export declare function describeSignalCause(signal: string): string;
23
53
  /**
24
54
  * Options for ShutdownManager
25
55
  */
@@ -46,6 +76,8 @@ export interface ShutdownManagerOptions {
46
76
  export declare class ShutdownManager {
47
77
  private cleanupTasks;
48
78
  private _isShuttingDown;
79
+ /** Cause of the in-progress shutdown; null until a signal arrives (#856). */
80
+ private _abortContext;
49
81
  /** Active abort controllers — supports concurrent phase execution (#404) */
50
82
  private abortControllers;
51
83
  private forceExitTimeout;
@@ -63,6 +95,12 @@ export declare class ShutdownManager {
63
95
  * Alias for isShuttingDown (matches proposed API)
64
96
  */
65
97
  get shuttingDown(): boolean;
98
+ /**
99
+ * Why the process is shutting down, or `null` if it isn't (#856).
100
+ * Lets code outside a cleanup task — e.g. a `finally` block that also
101
+ * finalizes state — record the same cause the cleanup tasks saw.
102
+ */
103
+ get abortContext(): AbortContext | null;
66
104
  /**
67
105
  * Register an abort controller for a running phase.
68
106
  *
@@ -91,9 +129,11 @@ export declare class ShutdownManager {
91
129
  * This allows dependent cleanup to happen in correct order.
92
130
  *
93
131
  * @param name - Human-readable name for user feedback
94
- * @param task - Async function to execute during cleanup
132
+ * @param task - Async function to execute during cleanup. Receives the
133
+ * `AbortContext` when the shutdown was signal-triggered, or `null` on a
134
+ * programmatic teardown. Tasks that don't care may ignore the argument.
95
135
  */
96
- registerCleanup(name: string, task: () => Promise<void>): void;
136
+ registerCleanup(name: string, task: (abort: AbortContext | null) => Promise<void>): void;
97
137
  /**
98
138
  * Unregister a cleanup task by name
99
139
  *
@@ -21,6 +21,43 @@
21
21
  * ```
22
22
  */
23
23
  import chalk from "chalk";
24
+ /** POSIX signal numbers for the signals we install handlers for. */
25
+ const SIGNAL_NUMBERS = {
26
+ SIGINT: 2,
27
+ SIGTERM: 15,
28
+ };
29
+ /**
30
+ * Conventional shell exit code for death-by-signal: `128 + signum`
31
+ * (SIGINT → 130, SIGTERM → 143). Falls back to 1 for anything unmapped.
32
+ *
33
+ * #856: this used to be a flat `0`. A run killed from outside therefore
34
+ * exited *successfully*, so nothing downstream — CI, a wrapping script, the
35
+ * user reading `$?` — could tell a completed run from a terminated one.
36
+ */
37
+ export function exitCodeForSignal(signal) {
38
+ const num = SIGNAL_NUMBERS[signal];
39
+ return num === undefined ? 1 : 128 + num;
40
+ }
41
+ /**
42
+ * Human-readable cause for a termination signal (#856, AC-4).
43
+ *
44
+ * SIGINT is the user pressing Ctrl+C — self-explanatory. SIGTERM is not:
45
+ * no interactive user sends it by hand, so it always means something else
46
+ * killed the run, and the user needs to be told that rather than left to
47
+ * infer it from truncated output. The known offender on macOS is Claude
48
+ * Code's `[bg-pty]` host-dead watchdog, which group-SIGKILLs a process tree
49
+ * containing the run; see `docs/incidents/856/`.
50
+ */
51
+ export function describeSignalCause(signal) {
52
+ switch (signal) {
53
+ case "SIGINT":
54
+ return "interrupted by user (Ctrl+C)";
55
+ case "SIGTERM":
56
+ return "terminated by an external SIGTERM (not sent by sequant)";
57
+ default:
58
+ return `terminated by ${signal}`;
59
+ }
60
+ }
24
61
  /**
25
62
  * Manages graceful shutdown for sequant run
26
63
  *
@@ -34,6 +71,8 @@ import chalk from "chalk";
34
71
  export class ShutdownManager {
35
72
  cleanupTasks = [];
36
73
  _isShuttingDown = false;
74
+ /** Cause of the in-progress shutdown; null until a signal arrives (#856). */
75
+ _abortContext = null;
37
76
  /** Active abort controllers — supports concurrent phase execution (#404) */
38
77
  abortControllers = new Set();
39
78
  forceExitTimeout;
@@ -67,6 +106,14 @@ export class ShutdownManager {
67
106
  get shuttingDown() {
68
107
  return this._isShuttingDown;
69
108
  }
109
+ /**
110
+ * Why the process is shutting down, or `null` if it isn't (#856).
111
+ * Lets code outside a cleanup task — e.g. a `finally` block that also
112
+ * finalizes state — record the same cause the cleanup tasks saw.
113
+ */
114
+ get abortContext() {
115
+ return this._abortContext;
116
+ }
70
117
  /**
71
118
  * Register an abort controller for a running phase.
72
119
  *
@@ -104,7 +151,9 @@ export class ShutdownManager {
104
151
  * This allows dependent cleanup to happen in correct order.
105
152
  *
106
153
  * @param name - Human-readable name for user feedback
107
- * @param task - Async function to execute during cleanup
154
+ * @param task - Async function to execute during cleanup. Receives the
155
+ * `AbortContext` when the shutdown was signal-triggered, or `null` on a
156
+ * programmatic teardown. Tasks that don't care may ignore the argument.
108
157
  */
109
158
  registerCleanup(name, task) {
110
159
  this.cleanupTasks.push({ name, task });
@@ -138,6 +187,13 @@ export class ShutdownManager {
138
187
  return;
139
188
  }
140
189
  this._isShuttingDown = true;
190
+ // #856 AC-4: name the cause up front. A truncated run whose last words
191
+ // were "shutting down gracefully" reads as an orderly stop; it isn't one.
192
+ const abort = {
193
+ signal,
194
+ reason: describeSignalCause(signal),
195
+ };
196
+ this._abortContext = abort;
141
197
  this.output(chalk.yellow(`\n! Received ${signal}, shutting down gracefully...`));
142
198
  // Abort all in-flight phases immediately
143
199
  if (this.abortControllers.size > 0) {
@@ -157,7 +213,7 @@ export class ShutdownManager {
157
213
  const tasksToRun = [...this.cleanupTasks].reverse();
158
214
  for (const { name, task } of tasksToRun) {
159
215
  try {
160
- await task();
216
+ await task(abort);
161
217
  this.output(chalk.green(`✓ ${name}`));
162
218
  }
163
219
  catch (err) {
@@ -166,9 +222,22 @@ export class ShutdownManager {
166
222
  }
167
223
  }
168
224
  clearTimeout(forceExitTimer);
169
- // Print summary
170
- this.output(chalk.yellow("\nInterrupted. Cleanup complete."));
171
- this.exit(0);
225
+ // #856 AC-4: report the abort AS an abort, with its cause and a non-zero
226
+ // exit code. The old banner ("Interrupted. Cleanup complete.") plus
227
+ // `exit(0)` made an externally-killed run indistinguishable from a
228
+ // successful one — and because this path exits the process, the normal
229
+ // `displaySummary` / `abortReason` rendering in run-display.ts is never
230
+ // reached, so this banner is the only thing the user sees.
231
+ const code = exitCodeForSignal(signal);
232
+ this.errorOutput(chalk.red(`\n✗ Run aborted — ${abort.reason}.`));
233
+ this.output(chalk.yellow(` Cleanup completed. No phase results beyond this point were recorded.`));
234
+ if (signal === "SIGTERM") {
235
+ this.output(chalk.gray(` sequant does not send itself SIGTERM. If this run was launched as a\n` +
236
+ ` backgrounded task inside an interactive Claude Code session, see\n` +
237
+ ` docs/incidents/856/ — relaunch from a plain terminal instead.`));
238
+ }
239
+ this.output(chalk.gray(` Exit code: ${code}`));
240
+ this.exit(code);
172
241
  }
173
242
  /**
174
243
  * Remove signal handlers and clean up
@@ -181,6 +250,7 @@ export class ShutdownManager {
181
250
  process.removeListener("SIGTERM", this.sigtermHandler);
182
251
  this.cleanupTasks = [];
183
252
  this.abortControllers.clear();
253
+ this._abortContext = null;
184
254
  }
185
255
  }
186
256
  /**
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Skills installation check — single source of truth for the
3
+ * `.claude/skills/<skill>/SKILL.md` layout.
4
+ *
5
+ * Shared by `doctor` (Checks 2+3) and the `run` pre-flight (#813) so the two
6
+ * cannot drift. The claude-code driver loads skills from project scope only
7
+ * (`settingSources: ["project"]`, see #19 AC-3 / #711), which makes
8
+ * `.claude/skills/` a hard runtime dependency: a phase prompt like
9
+ * "Run the /spec workflow" can never resolve without it.
10
+ */
11
+ /** Directory (relative to a project root) that skills are resolved from. */
12
+ export declare const SKILLS_DIR = ".claude/skills";
13
+ export interface SkillsCheckResult {
14
+ /** True when the `.claude/skills/` directory exists. */
15
+ skillsDirExists: boolean;
16
+ /** Requested skills whose `SKILL.md` is missing, in input order. */
17
+ missingSkills: string[];
18
+ }
19
+ /**
20
+ * Check that each requested skill is installed at
21
+ * `<cwd>/.claude/skills/<skill>/SKILL.md`.
22
+ *
23
+ * @param skills - Skill directory names to require (e.g. `["spec", "exec"]`).
24
+ * @param cwd - Project root to check under (default: `process.cwd()`).
25
+ */
26
+ export declare function checkSkillsInstalled(skills: string[], cwd?: string): Promise<SkillsCheckResult>;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Skills installation check — single source of truth for the
3
+ * `.claude/skills/<skill>/SKILL.md` layout.
4
+ *
5
+ * Shared by `doctor` (Checks 2+3) and the `run` pre-flight (#813) so the two
6
+ * cannot drift. The claude-code driver loads skills from project scope only
7
+ * (`settingSources: ["project"]`, see #19 AC-3 / #711), which makes
8
+ * `.claude/skills/` a hard runtime dependency: a phase prompt like
9
+ * "Run the /spec workflow" can never resolve without it.
10
+ */
11
+ import { join } from "path";
12
+ import { fileExists } from "./fs.js";
13
+ /** Directory (relative to a project root) that skills are resolved from. */
14
+ export const SKILLS_DIR = ".claude/skills";
15
+ /**
16
+ * Check that each requested skill is installed at
17
+ * `<cwd>/.claude/skills/<skill>/SKILL.md`.
18
+ *
19
+ * @param skills - Skill directory names to require (e.g. `["spec", "exec"]`).
20
+ * @param cwd - Project root to check under (default: `process.cwd()`).
21
+ */
22
+ export async function checkSkillsInstalled(skills, cwd = process.cwd()) {
23
+ const skillsDirExists = await fileExists(join(cwd, SKILLS_DIR));
24
+ const missingSkills = [];
25
+ for (const skill of skills) {
26
+ if (!(await fileExists(join(cwd, SKILLS_DIR, skill, "SKILL.md")))) {
27
+ missingSkills.push(skill);
28
+ }
29
+ }
30
+ return { skillsDirExists, missingSkills };
31
+ }
@@ -37,6 +37,7 @@ export interface PackageManagerConfig {
37
37
  exec: string;
38
38
  install: string;
39
39
  installSilent: string;
40
+ ciInstall: string;
40
41
  addPkg: string;
41
42
  removePkg: string;
42
43
  updatePkg: string;
@@ -56,12 +57,120 @@ export declare function detectPackageManager(): Promise<PackageManager | null>;
56
57
  /**
57
58
  * Synchronous version of detectPackageManager for use in startup code.
58
59
  * Only checks JS lockfiles (not Python) since sequant is a Node.js tool.
60
+ *
61
+ * @param root Directory to look for lockfiles in. Defaults to the process cwd,
62
+ * which is what startup code wants; callers that operate on a specific
63
+ * repository (e.g. merge-check) must pass that repo's root instead.
64
+ */
65
+ export declare function detectPackageManagerSync(root?: string): PackageManager;
66
+ /**
67
+ * Resolve the package manager for a directory, preferring a declared value and
68
+ * falling back to live lockfile detection.
69
+ *
70
+ * The declared value is a *snapshot* — the worktree manifest records whatever
71
+ * `sequant init` saw, so it is absent on manifest-less or pre-init trees. The
72
+ * old spelling at those call sites was `(declared as keyof typeof PM_CONFIG)
73
+ * || "npm"`, which assumed npm whenever the snapshot was missing while the
74
+ * shell provisioning path (`templates/scripts/new-feature.sh`, PM-aware since
75
+ * #847) detected live from the lockfile. On a pnpm/yarn/bun project without a
76
+ * manifest the two paths therefore disagreed about the project's package
77
+ * manager — the dual-producer drift class of #833. Detecting here makes the
78
+ * TypeScript path agree with the shell path by construction (#870).
79
+ *
80
+ * The own-property membership test also retires that unchecked cast: a
81
+ * declared value outside `PackageManager` (a hand-edited manifest, a future
82
+ * `"npm@10"` spelling) used to index `PM_CONFIG` to `undefined` and throw on
83
+ * the next property access. Such a value now routes to detection instead.
84
+ * Python managers (`pip`/`poetry`/`uv`) are `PM_CONFIG` keys, so a declared
85
+ * Python manager still wins over the JS-only detector.
86
+ *
87
+ * The test is `hasOwnProperty`, deliberately not `in`: `in` walks the
88
+ * prototype chain, so `"toString"`, `"constructor"`, and `"__proto__"` would
89
+ * all pass it and be returned as if they named a package manager. Indexing
90
+ * `PM_CONFIG` with one yields an inherited function (or `Object.prototype`)
91
+ * whose `ciInstall` is `undefined`, crashing the caller on `.split(" ")` —
92
+ * reintroducing the exact failure this guard exists to prevent.
93
+ *
94
+ * @param declared Package manager recorded in the manifest, if any
95
+ * @param root Directory whose lockfiles decide the fallback
59
96
  */
60
- export declare function detectPackageManagerSync(): PackageManager;
97
+ export declare function resolvePackageManager(declared: string | undefined, root: string): PackageManager;
61
98
  /**
62
99
  * Get package manager command configuration
63
100
  */
64
101
  export declare function getPackageManagerCommands(pm: PackageManager): PackageManagerConfig;
102
+ /**
103
+ * Frozen install understood by Yarn 1 (classic).
104
+ *
105
+ * Yarn 2+ (berry) renamed this flag to `--immutable`, which is what
106
+ * `PM_CONFIG.yarn.ciInstall` carries. Both yarn spellings therefore exist, and
107
+ * both must appear in `new-feature.sh`'s `pm_ci_install()` table — the drift
108
+ * guard in `__tests__/new-feature-frozen-install.integration.test.ts` asserts
109
+ * exactly that, which is why this is a named export rather than a literal
110
+ * buried in `resolvePackageManagerConfig` (#871).
111
+ */
112
+ export declare const YARN_CLASSIC_CI_INSTALL = "yarn install --frozen-lockfile";
113
+ /**
114
+ * Detect whether a directory is a Yarn 1 (classic) or Yarn 2+ (berry) project.
115
+ *
116
+ * Yarn 1 and berry both use `yarn.lock`, so `detectPackageManagerSync` cannot
117
+ * tell them apart — it answers "yarn", and this answers "which yarn". They are
118
+ * separate axes on purpose: adding a `yarn1` member to `PackageManager` would
119
+ * leak a synthetic name into settings (`pmRun`), doctor output, and templates
120
+ * for what is, today, a one-flag difference.
121
+ *
122
+ * Signals, in order of authority. The ordering follows a single rule: **flag
123
+ * acceptance is decided by the yarn binary that runs, not by the lockfile it
124
+ * reads.** So a Corepack pin outranks the lockfile header — a yarn-1 lockfile
125
+ * under `packageManager: "yarn@4"` still gets berry's `--immutable`, because
126
+ * yarn 4 is what will execute and yarn 4 rejects `--frozen-lockfile`.
127
+ *
128
+ * 1. `packageManager: "yarn@<major>"` in `package.json` — Corepack pins the
129
+ * binary.
130
+ * 2. `.yarnrc.yml` — a berry-only config file; yarn 1 reads `.yarnrc`.
131
+ * 3. `yarn.lock` header — yarn 1 writes `# yarn lockfile v1`; berry writes
132
+ * `__metadata:` instead.
133
+ * 4. Nothing recognizable → berry, the pre-#871 assumption. Staying with it
134
+ * keeps a contentless or unreadable `yarn.lock` behaving exactly as it did
135
+ * before this function existed.
136
+ *
137
+ * Mirrored by `detect_yarn_major` in `templates/scripts/new-feature.sh` (#871).
138
+ *
139
+ * @internal Exported for testing — production code should go through
140
+ * {@link resolvePackageManagerConfig}, whose whole job is to apply this answer.
141
+ * The export exists so the cross-path agreement test can run this and the shell
142
+ * mirror against the same fixtures, which is the gate keeping the two in step.
143
+ *
144
+ * @param root Directory to inspect. Defaults to the process cwd.
145
+ * @returns 1 for Yarn 1 (classic), 2 for Yarn 2+ (berry)
146
+ */
147
+ export declare function detectYarnMajor(root?: string): 1 | 2;
148
+ /**
149
+ * Package manager commands for `pm`, with directory-dependent commands resolved
150
+ * against `root`.
151
+ *
152
+ * A drop-in replacement for `PM_CONFIG[pm]` at every call site that knows which
153
+ * project directory it is acting on. `PM_CONFIG.yarn` is the berry baseline, so
154
+ * for a Yarn 1 project this substitutes every field whose berry spelling is
155
+ * wrong on classic — `ciInstall`, `exec`, and `updatePkg` (see
156
+ * {@link YARN_CLASSIC_OVERRIDES}). Every other package manager, and every
157
+ * major-agnostic yarn field, is returned untouched.
158
+ *
159
+ * Returning the whole config rather than one resolved string is what makes that
160
+ * cheap: call sites that thread a `PackageManagerConfig` onward — merge-check's
161
+ * combined-branch test reads `ciInstall` for three commands and two user-facing
162
+ * messages — stay correct with a single-line change, and each newly discovered
163
+ * classic/berry split is one entry in the overrides table rather than a new
164
+ * call-site edit.
165
+ *
166
+ * Resolve against the tree you are about to act on, not against wherever the
167
+ * process started: `root` decides the answer, and in merge-check the combined
168
+ * (post-merge) state can disagree with the pre-merge one.
169
+ *
170
+ * @param pm Package manager identity, e.g. from `detectPackageManagerSync`
171
+ * @param root Directory whose files decide the directory-dependent commands
172
+ */
173
+ export declare function resolvePackageManagerConfig(pm: PackageManager, root: string): PackageManagerConfig;
65
174
  export interface StackConfig {
66
175
  name: string;
67
176
  displayName: string;