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
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Which persisted issue statuses mean "this issue is already done" (#837).
3
+ *
4
+ * Single source of truth for every pre-flight guard that skips finished work.
5
+ * It exists because the same predicate was previously spelled out inline at two
6
+ * sites with two different lists, and #817's `--ready-gate` extended the status
7
+ * vocabulary without either site following:
8
+ *
9
+ * - `chain-resume.ts` peels the completed prefix off a `--chain` run.
10
+ * - `run-orchestrator.ts` drops finished issues from a non-chain run.
11
+ *
12
+ * A gated issue deliberately terminates in `waiting_for_human_merge` (policy
13
+ * threshold reached) rather than `ready_for_merge`, since `ready_for_merge`
14
+ * would read as auto-merge-ready and defeat the human merge gate the feature
15
+ * exists to preserve. Both guards missed it, so re-running re-executed
16
+ * already-gated issues from phase 0 — a full spec/exec/qa pipeline plus another
17
+ * full-weight ready gate.
18
+ *
19
+ * `blocked` is deliberately NOT a completed status, for two reasons:
20
+ *
21
+ * 1. It is a *generic* member of `IssueStatusSchema` ("waiting on external input
22
+ * or dependency"), not a ready-gate-exclusive terminal. Admitting it here
23
+ * would silently apply to every other writer of that status, present and
24
+ * future.
25
+ * 2. A guard halt IS the human-attention signal. Skipping it as complete would
26
+ * report the issue as passed when it demonstrably did not.
27
+ *
28
+ * So a `blocked` issue is re-executed rather than skipped — the same
29
+ * conservative rule already applied to issues whose state lookup fails.
30
+ * Re-running wastes tokens; silently dropping an issue the user must look at is
31
+ * the worse failure. Once the user clears the blocker, the re-run is what lets
32
+ * work resume.
33
+ *
34
+ * NOTE: this is "done, do not re-run", which is NOT the same set as "might have
35
+ * a merged PR" — `state-cleanup.ts`'s merge-detection sweep deliberately keeps
36
+ * its own wider list (it also scans `in_progress`, #592, and
37
+ * `waiting_for_qa_gate`, #606). Do not unify the two.
38
+ *
39
+ * When adding a status to `IssueStatusSchema`, decide here whether it belongs.
40
+ */
41
+ export const COMPLETED_ISSUE_STATUSES = [
42
+ "ready_for_merge",
43
+ "merged",
44
+ "waiting_for_human_merge",
45
+ ];
46
+ const COMPLETED_ISSUE_STATUS_SET = new Set(COMPLETED_ISSUE_STATUSES);
47
+ /**
48
+ * True iff `status` marks the issue as already finished, so a re-run should
49
+ * skip it unless `--force` is passed.
50
+ *
51
+ * Accepts `string | undefined` because persisted state may predate the current
52
+ * schema or be absent entirely; an unknown or missing status is treated as
53
+ * incomplete (re-execute), never as complete.
54
+ */
55
+ export function isCompletedIssueStatus(status) {
56
+ return status !== undefined && COMPLETED_ISSUE_STATUS_SET.has(status);
57
+ }
@@ -42,6 +42,32 @@ export declare function normalizeCommanderOptions(options: RunOptions): RunOptio
42
42
  * This replaces the inline merging logic previously in run.ts.
43
43
  */
44
44
  export declare function resolveRunOptions(cliOptions: RunOptions, settings: SequantSettings): RunOptions;
45
+ /**
46
+ * Fall back to `fallback` unless `value` is a usable positive number.
47
+ *
48
+ * `??` alone is not enough for these (#833). `NaN` is not nullish, so a
49
+ * malformed value survives `?? default` and flows on into `setTimeout`, which
50
+ * clamps a `NaN` delay to 0 and aborts the phase on its first tick, or into
51
+ * `while (iteration < maxIterations)`, which is false on entry and runs zero
52
+ * phases. Both read as a phase/agent fault rather than a bad input, which is
53
+ * what makes the silent version expensive. Non-finite and non-positive values
54
+ * therefore fall back *to* the default instead of through it.
55
+ *
56
+ * `bin/cli.ts` rejects these at the flag boundary with a message naming the
57
+ * flag — that is the user-facing fix. This is the structural backstop for
58
+ * programmatic callers, `settings.json`, and whatever calls this next.
59
+ *
60
+ * Exported because `phaseTimeout` has two producers, not one: this module and
61
+ * `commands/ready.ts`, whose value reaches the driver through
62
+ * `ready-gate.ts`'s own `buildPhaseConfig` and never passes through
63
+ * `buildExecutionConfig`. Guarding only here would have left that path open.
64
+ * Chain it to express the layering — CLI, then settings, then the default:
65
+ *
66
+ * ```ts
67
+ * positiveOr(options.timeout, positiveOr(settings.run.timeout, DEFAULT))
68
+ * ```
69
+ */
70
+ export declare function positiveOr(value: number | undefined, fallback: number): number;
45
71
  /**
46
72
  * Build an ExecutionConfig from merged RunOptions and settings.
47
73
  * Extracts the phase-timeout, MCP, retry, and mode resolution logic
@@ -97,6 +97,10 @@ export function normalizeCommanderOptions(options) {
97
97
  ...(raw.retry === false && { noRetry: true }),
98
98
  ...(raw.rebase === false && { noRebase: true }),
99
99
  ...(raw.pr === false && { noPr: true }),
100
+ // #804: map the flag-derived key onto the interface field. Guarded on
101
+ // `undefined` (not truthiness) so an explicit `--auto-wait 0` still
102
+ // overrides a non-zero setting.
103
+ ...(raw.autoWait !== undefined && { autoWaitMinutes: raw.autoWait }),
100
104
  };
101
105
  }
102
106
  /**
@@ -117,6 +121,7 @@ export function resolveRunOptions(cliOptions, settings) {
117
121
  qualityLoop: defined.qualityLoop ?? settings.run.qualityLoop,
118
122
  maxIterations: defined.maxIterations ?? settings.run.maxIterations,
119
123
  noSmartTests: defined.noSmartTests ?? !settings.run.smartTests,
124
+ autoWaitMinutes: defined.autoWaitMinutes ?? settings.run.autoWaitMinutes,
120
125
  // Agent settings
121
126
  isolateParallel: defined.isolateParallel ?? settings.agents.isolateParallel,
122
127
  // Env overrides
@@ -129,6 +134,36 @@ export function resolveRunOptions(cliOptions, settings) {
129
134
  merged.autoDetectPhases = autoDetectPhases;
130
135
  return merged;
131
136
  }
137
+ /**
138
+ * Fall back to `fallback` unless `value` is a usable positive number.
139
+ *
140
+ * `??` alone is not enough for these (#833). `NaN` is not nullish, so a
141
+ * malformed value survives `?? default` and flows on into `setTimeout`, which
142
+ * clamps a `NaN` delay to 0 and aborts the phase on its first tick, or into
143
+ * `while (iteration < maxIterations)`, which is false on entry and runs zero
144
+ * phases. Both read as a phase/agent fault rather than a bad input, which is
145
+ * what makes the silent version expensive. Non-finite and non-positive values
146
+ * therefore fall back *to* the default instead of through it.
147
+ *
148
+ * `bin/cli.ts` rejects these at the flag boundary with a message naming the
149
+ * flag — that is the user-facing fix. This is the structural backstop for
150
+ * programmatic callers, `settings.json`, and whatever calls this next.
151
+ *
152
+ * Exported because `phaseTimeout` has two producers, not one: this module and
153
+ * `commands/ready.ts`, whose value reaches the driver through
154
+ * `ready-gate.ts`'s own `buildPhaseConfig` and never passes through
155
+ * `buildExecutionConfig`. Guarding only here would have left that path open.
156
+ * Chain it to express the layering — CLI, then settings, then the default:
157
+ *
158
+ * ```ts
159
+ * positiveOr(options.timeout, positiveOr(settings.run.timeout, DEFAULT))
160
+ * ```
161
+ */
162
+ export function positiveOr(value, fallback) {
163
+ return typeof value === "number" && Number.isFinite(value) && value > 0
164
+ ? value
165
+ : fallback;
166
+ }
132
167
  /**
133
168
  * Build an ExecutionConfig from merged RunOptions and settings.
134
169
  * Extracts the phase-timeout, MCP, retry, and mode resolution logic
@@ -157,15 +192,32 @@ export function buildExecutionConfig(mergedOptions, settings, issueCount) {
157
192
  parallel: isParallel,
158
193
  dryRun: mergedOptions.dryRun ?? false,
159
194
  verbose: mergedOptions.verbose ?? false,
160
- phaseTimeout: mergedOptions.timeout ?? DEFAULT_CONFIG.phaseTimeout,
195
+ // #833: chained so the layering is CLI → settings → default, matching
196
+ // `commands/ready.ts`. A malformed CLI value falls back to the user's
197
+ // configured setting rather than skipping past it to the hardcoded
198
+ // default; a malformed setting falls back to the default. In the normal
199
+ // path `mergedOptions` already carries the settings value, so this only
200
+ // differs when one of the two layers is unusable — which is the case that
201
+ // matters.
202
+ phaseTimeout: positiveOr(mergedOptions.timeout, positiveOr(settings.run.timeout, DEFAULT_CONFIG.phaseTimeout)),
161
203
  qualityLoop: mergedOptions.qualityLoop ?? false,
162
- maxIterations: mergedOptions.maxIterations ?? DEFAULT_CONFIG.maxIterations,
204
+ maxIterations: positiveOr(mergedOptions.maxIterations, positiveOr(settings.run.maxIterations, DEFAULT_CONFIG.maxIterations)),
163
205
  noSmartTests: mergedOptions.noSmartTests ?? false,
164
206
  mcp: mcpEnabled,
165
207
  retry: retryEnabled,
208
+ // #804: default 0 (off) — the whole regression contract for auto-wait is
209
+ // that an unset flag leaves the #761/#799 halt path untouched.
210
+ autoWaitMinutes: mergedOptions.autoWaitMinutes ??
211
+ settings.run.autoWaitMinutes ??
212
+ DEFAULT_CONFIG.autoWaitMinutes,
166
213
  agent: mergedOptions.agent ?? settings.run.agent,
167
214
  aiderSettings: settings.run.aider,
168
215
  isolateParallel: mergedOptions.isolateParallel,
169
216
  relayEnabled,
217
+ // #817: opt-in only, no settings backing (AC-4). A missing `--ready-gate`
218
+ // resolves to false, keeping the run path byte-identical (AC-5). This is the
219
+ // load-bearing wire the #795 inert-flag class guards against — the flag is
220
+ // useless if it stops reaching the executor here.
221
+ readyGate: mergedOptions.readyGate ?? false,
170
222
  };
171
223
  }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Shared parser for line-leading dependency markers in an issue body (#767).
3
+ *
4
+ * Two callers extract issue numbers from `depends on #N` / `blocked by #N`
5
+ * declarations:
6
+ *
7
+ * - `chain-preflight.ts:parseDeclaredBlockers` — warn-only pre-flight, honors
8
+ * BOTH `depends on` and `blocked by`.
9
+ * - `batch-executor.ts:parseDependencies` — feeds `sortByDependencies`, which
10
+ * *silently reorders the run*, so it honors ONLY `depends on`.
11
+ *
12
+ * The hardened mechanics (line anchoring, required `#`, code/comment stripping)
13
+ * were introduced for the pre-flight in #762/PR #764 and lived only in
14
+ * `chain-preflight.ts`. #767 promotes them here so the sorter can adopt the same
15
+ * hardening without the two regexes drifting — while keeping the **marker set
16
+ * per-caller** so sharing the parser does NOT make the sorter start reordering
17
+ * on `blocked by` (a new, unrequested silent-reorder class; #762 Open Q #3).
18
+ */
19
+ /** A dependency-declaration marker a caller opts into honoring. */
20
+ export type DepMarker = "depends on" | "blocked by";
21
+ /**
22
+ * Parse the issue numbers a body declares itself dependent on / blocked by,
23
+ * honoring only the requested `markers`. Deduped, order-preserving.
24
+ *
25
+ * Only line-leading markers count as declarations — see `buildMarkerRegex` for
26
+ * why mid-sentence prose mentions are deliberately ignored. Code blocks, inline
27
+ * code spans, and HTML comments are stripped first.
28
+ */
29
+ export declare function parseBodyDependencyMarkers(body: string, markers: DepMarker[]): number[];
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Shared parser for line-leading dependency markers in an issue body (#767).
3
+ *
4
+ * Two callers extract issue numbers from `depends on #N` / `blocked by #N`
5
+ * declarations:
6
+ *
7
+ * - `chain-preflight.ts:parseDeclaredBlockers` — warn-only pre-flight, honors
8
+ * BOTH `depends on` and `blocked by`.
9
+ * - `batch-executor.ts:parseDependencies` — feeds `sortByDependencies`, which
10
+ * *silently reorders the run*, so it honors ONLY `depends on`.
11
+ *
12
+ * The hardened mechanics (line anchoring, required `#`, code/comment stripping)
13
+ * were introduced for the pre-flight in #762/PR #764 and lived only in
14
+ * `chain-preflight.ts`. #767 promotes them here so the sorter can adopt the same
15
+ * hardening without the two regexes drifting — while keeping the **marker set
16
+ * per-caller** so sharing the parser does NOT make the sorter start reordering
17
+ * on `blocked by` (a new, unrequested silent-reorder class; #762 Open Q #3).
18
+ */
19
+ /**
20
+ * Build the anchored marker regex for the requested marker set.
21
+ *
22
+ * Matches a declared marker, optionally bold-wrapped, colon-separated, and/or
23
+ * written as a list item, e.g. `- **Depends on**: #123`.
24
+ *
25
+ * Anchored to line start because a *declaration* is a line about the issue's
26
+ * own dependencies, whereas prose that merely mentions the marker mid-sentence
27
+ * is not. #762's own body is the motivating case: it contains both `...when #39
28
+ * says blocked by #38` and `...real markers like "Blocked by #36"` as examples,
29
+ * and an unanchored match reported #762 as blocked by #38 and #36 — exactly the
30
+ * false inference #604 says is worse than none. That matters most under
31
+ * `--strict-preflight`, where a bogus warning hard-aborts a legitimate chain.
32
+ *
33
+ * The `#` is required (unlike the historic looser `#?` in `parseDependencies`)
34
+ * so a line such as `Blocked by 5 days of review` — or the prose
35
+ * `Issue 14 depends on 12+13` — cannot parse as an issue number.
36
+ */
37
+ function buildMarkerRegex(markers) {
38
+ const alternation = markers.map((m) => m.split(/\s+/).join("\\s+")).join("|");
39
+ return new RegExp(`^\\s*(?:[-*]\\s*)?\\*?\\*?(?:${alternation})\\*?\\*?:?\\s*#(\\d+)`, "gim");
40
+ }
41
+ /**
42
+ * Strip fenced code blocks, inline code spans, and HTML comments so markers
43
+ * inside quoted shell snippets, documentation examples, or commented-out drafts
44
+ * don't count as real declarations. Inline spans are matched within a single
45
+ * line so an unbalanced backtick cannot swallow the rest of the body.
46
+ *
47
+ * Deliberately diverges from `assess-collision-detect.ts:stripCodeBlocksAndComments`,
48
+ * which keeps inline spans: its PATH_REGEX only matches backtick-wrapped paths,
49
+ * so stripping them there would find nothing. Same syntax, opposite meaning —
50
+ * a backticked marker here is an example, a backticked path there is the target.
51
+ * Keep the two separate (see PR #770).
52
+ */
53
+ function stripCodeAndComments(body) {
54
+ return body
55
+ .replace(/```[\s\S]*?```/g, "")
56
+ .replace(/<!--[\s\S]*?-->/g, "")
57
+ .replace(/`[^`\n]*`/g, "");
58
+ }
59
+ /**
60
+ * Parse the issue numbers a body declares itself dependent on / blocked by,
61
+ * honoring only the requested `markers`. Deduped, order-preserving.
62
+ *
63
+ * Only line-leading markers count as declarations — see `buildMarkerRegex` for
64
+ * why mid-sentence prose mentions are deliberately ignored. Code blocks, inline
65
+ * code spans, and HTML comments are stripped first.
66
+ */
67
+ export function parseBodyDependencyMarkers(body, markers) {
68
+ if (markers.length === 0)
69
+ return [];
70
+ const cleaned = stripCodeAndComments(body);
71
+ const regex = buildMarkerRegex(markers);
72
+ const found = [];
73
+ for (const m of cleaned.matchAll(regex)) {
74
+ const n = parseInt(m[1], 10);
75
+ if (!isNaN(n) && !found.includes(n))
76
+ found.push(n);
77
+ }
78
+ return found;
79
+ }
@@ -98,6 +98,13 @@ export interface AgentPhaseResult {
98
98
  export interface AgentDriver {
99
99
  /** Human-readable name for logging */
100
100
  name: string;
101
+ /**
102
+ * True when this driver executes phases by resolving slash-command skills
103
+ * from the project's `.claude/skills/` directory (claude-code). Drivers
104
+ * whose prompts do the work inline (aider's `driverOverrides` templates)
105
+ * return false, and the `run` skills pre-flight (#813) is skipped for them.
106
+ */
107
+ resolvesSkills: boolean;
101
108
  /** Execute a phase prompt and return structured result */
102
109
  executePhase(prompt: string, config: AgentExecutionConfig): Promise<AgentPhaseResult>;
103
110
  /** Check if this driver is available/configured */
@@ -9,6 +9,8 @@ import type { AgentDriver, AgentExecutionConfig, AgentPhaseResult, ResumeHandle
9
9
  import type { AiderSettings } from "../../settings.js";
10
10
  export declare class AiderDriver implements AgentDriver {
11
11
  name: string;
12
+ /** Aider phases use inline `driverOverrides` prompts — no skills (#813). */
13
+ resolvesSkills: boolean;
12
14
  private settings?;
13
15
  constructor(settings?: AiderSettings);
14
16
  /**
@@ -10,6 +10,8 @@ import { execSync } from "child_process";
10
10
  import { RingBuffer } from "../ring-buffer.js";
11
11
  export class AiderDriver {
12
12
  name = "aider";
13
+ /** Aider phases use inline `driverOverrides` prompts — no skills (#813). */
14
+ resolvesSkills = false;
13
15
  settings;
14
16
  constructor(settings) {
15
17
  this.settings = settings;
@@ -7,6 +7,8 @@
7
7
  import type { AgentDriver, AgentExecutionConfig, AgentPhaseResult, ResumeHandle } from "./agent-driver.js";
8
8
  export declare class ClaudeCodeDriver implements AgentDriver {
9
9
  name: string;
10
+ /** Phases run slash-command skills from `.claude/skills/` (#813). */
11
+ resolvesSkills: boolean;
10
12
  /**
11
13
  * Track session ID across calls so callers can implement resume.
12
14
  * Set after each executePhase() call.
@@ -42,12 +44,31 @@ export declare class ClaudeCodeDriver implements AgentDriver {
42
44
  * RateLimitError would wrongly re-enable the retry / MCP-fallback path. When
43
45
  * the `rate_limit_event` is itself a billing failure its richer metadata
44
46
  * (`canUserPurchaseCredits`, etc.) is preserved.
47
+ *
48
+ * Counter-exception (#860): when the `rate_limit_event` carries the FULL
49
+ * captured subscription-window shape — waitable window evidence (recognized
50
+ * window type + future `resetsAt`) *plus* the `out_of_credits` marker — the
51
+ * richer structured signal wins over the bare `billing_error` enum. A
52
+ * subscription plan's five-hour exhaustion surfaces assistant-side as a
53
+ * billing error, and overriding on that enum here would strip the metadata
54
+ * `--auto-wait` needs and re-inert the feature this classification exists
55
+ * to enable. Deliberately no wider than the evidenced shape: a pure
56
+ * throttle event (window, no billing markers) beside a `billing_error`
57
+ * enum keeps the #732 rule — billing wins — because there the two signals
58
+ * genuinely conflict and no capture justifies trusting the throttle.
45
59
  */
46
60
  private buildStructuredError;
47
61
  /**
48
62
  * Map the SDK's assistant/api-retry error enum to a typed error. Only
49
63
  * rate-limit / billing variants are mapped; other variants (auth, etc.)
50
64
  * return undefined and defer to the existing classification path.
65
+ *
66
+ * The assistant-error channel carries no `resetsAt`/`rateLimitType` (the
67
+ * enum is the whole signal — `SDKAssistantMessageError` is a bare string),
68
+ * so the originating variant is recorded in metadata to keep `rate_limit`
69
+ * and `overloaded` distinguishable downstream (#761 AC-9). With no
70
+ * `resetsAt`, these errors are treated as transient by the executor's
71
+ * window-exhaustion check — the mandatory metadata-absent fallback.
51
72
  */
52
73
  private errorFromAssistantError;
53
74
  private buildResumeHandle;
@@ -6,10 +6,12 @@
6
6
  */
7
7
  import { query } from "@anthropic-ai/claude-agent-sdk";
8
8
  import { getMcpServersConfig } from "../../system.js";
9
- import { RateLimitError, BillingError, createRateLimitError, isRateLimitFailureInfo, } from "../../errors.js";
9
+ import { RateLimitError, BillingError, createRateLimitError, isWaitableWindow, isRateLimitFailureInfo, } from "../../errors.js";
10
10
  import { RingBuffer } from "../ring-buffer.js";
11
11
  export class ClaudeCodeDriver {
12
12
  name = "claude-code";
13
+ /** Phases run slash-command skills from `.claude/skills/` (#813). */
14
+ resolvesSkills = true;
13
15
  /**
14
16
  * Track session ID across calls so callers can implement resume.
15
17
  * Set after each executePhase() call.
@@ -228,10 +230,22 @@ export class ClaudeCodeDriver {
228
230
  clearTimeout(timeoutId);
229
231
  const error = err instanceof Error ? err.message : String(err);
230
232
  if (error.includes("abort") || error.includes("AbortError")) {
233
+ // A rate limit can manifest as a hang rather than a stream event
234
+ // reaching the result: the SDK stalls, the phase timeout fires, and
235
+ // the abort lands here. Only failure-grade signals are ever captured
236
+ // (see the stream loop), so attaching them cannot mask a genuine
237
+ // timeout — with no signal, `abortStructuredError` is undefined and
238
+ // the bare timeout message survives unchanged (#761 AC-1). Without
239
+ // this, the captured `rateLimitInfo` was discarded and downstream
240
+ // classification saw only `Timeout after Ns`, sending a closed
241
+ // rate-limit window into the full retry + MCP-fallback ladder.
242
+ const abortStructuredError = this.buildStructuredError(rateLimitInfo, assistantError, apiRetryError);
231
243
  return {
232
244
  success: false,
233
245
  output: capturedOutput,
234
- error: `Timeout after ${config.phaseTimeout}s`,
246
+ error: abortStructuredError?.message ??
247
+ `Timeout after ${config.phaseTimeout}s`,
248
+ structuredError: abortStructuredError,
235
249
  stderrTail: stderrBuffer.getLines(),
236
250
  stdoutTail: stdoutBuffer.getLines(),
237
251
  };
@@ -239,8 +253,7 @@ export class ClaudeCodeDriver {
239
253
  // If the stream surfaced a failure-grade rate-limit/billing signal before
240
254
  // throwing, prefer that typed cause (#732) over the raw thrown message — a
241
255
  // mid-stream throw after a *rejected* rate_limit_event is very likely the
242
- // proximate cause. Abort/timeout is handled above first, so a genuine
243
- // timeout is never masked by a stale rate-limit signal.
256
+ // proximate cause.
244
257
  const structuredError = this.buildStructuredError(rateLimitInfo, assistantError, apiRetryError);
245
258
  const stderrSuffix = capturedStderr
246
259
  ? `\nStderr: ${capturedStderr.slice(0, 500)}`
@@ -271,11 +284,27 @@ export class ClaudeCodeDriver {
271
284
  * RateLimitError would wrongly re-enable the retry / MCP-fallback path. When
272
285
  * the `rate_limit_event` is itself a billing failure its richer metadata
273
286
  * (`canUserPurchaseCredits`, etc.) is preserved.
287
+ *
288
+ * Counter-exception (#860): when the `rate_limit_event` carries the FULL
289
+ * captured subscription-window shape — waitable window evidence (recognized
290
+ * window type + future `resetsAt`) *plus* the `out_of_credits` marker — the
291
+ * richer structured signal wins over the bare `billing_error` enum. A
292
+ * subscription plan's five-hour exhaustion surfaces assistant-side as a
293
+ * billing error, and overriding on that enum here would strip the metadata
294
+ * `--auto-wait` needs and re-inert the feature this classification exists
295
+ * to enable. Deliberately no wider than the evidenced shape: a pure
296
+ * throttle event (window, no billing markers) beside a `billing_error`
297
+ * enum keeps the #732 rule — billing wins — because there the two signals
298
+ * genuinely conflict and no capture justifies trusting the throttle.
274
299
  */
275
300
  buildStructuredError(rateLimitInfo, assistantError, apiRetryError) {
276
301
  if (rateLimitInfo) {
277
302
  const err = createRateLimitError(rateLimitInfo);
278
- if (err instanceof RateLimitError && assistantError === "billing_error") {
303
+ const isCapturedSubscriptionShape = isWaitableWindow(rateLimitInfo) &&
304
+ rateLimitInfo.overageDisabledReason === "out_of_credits";
305
+ if (err instanceof RateLimitError &&
306
+ assistantError === "billing_error" &&
307
+ !isCapturedSubscriptionShape) {
279
308
  return new BillingError("Billing error");
280
309
  }
281
310
  return err;
@@ -287,15 +316,28 @@ export class ClaudeCodeDriver {
287
316
  * Map the SDK's assistant/api-retry error enum to a typed error. Only
288
317
  * rate-limit / billing variants are mapped; other variants (auth, etc.)
289
318
  * return undefined and defer to the existing classification path.
319
+ *
320
+ * The assistant-error channel carries no `resetsAt`/`rateLimitType` (the
321
+ * enum is the whole signal — `SDKAssistantMessageError` is a bare string),
322
+ * so the originating variant is recorded in metadata to keep `rate_limit`
323
+ * and `overloaded` distinguishable downstream (#761 AC-9). With no
324
+ * `resetsAt`, these errors are treated as transient by the executor's
325
+ * window-exhaustion check — the mandatory metadata-absent fallback.
290
326
  */
291
327
  errorFromAssistantError(error) {
292
328
  switch (error) {
293
329
  case "billing_error":
294
- return new BillingError("Billing error");
330
+ return new BillingError("Billing error", {
331
+ assistantError: "billing_error",
332
+ });
295
333
  case "rate_limit":
296
- return new RateLimitError("Rate limited");
334
+ return new RateLimitError("Rate limited", {
335
+ assistantError: "rate_limit",
336
+ });
297
337
  case "overloaded":
298
- return new RateLimitError("API overloaded");
338
+ return new RateLimitError("API overloaded", {
339
+ assistantError: "overloaded",
340
+ });
299
341
  default:
300
342
  return undefined;
301
343
  }
@@ -5,8 +5,15 @@
5
5
  * categories. Exit codes are the primary signal; stderr patterns are secondary.
6
6
  */
7
7
  import { SequantError } from "../errors.js";
8
- /** All recognized error categories (kept for backwards compatibility). */
9
- export declare const ERROR_CATEGORIES: readonly ["context_overflow", "api_error", "hook_failure", "build_error", "timeout", "unknown"];
8
+ /**
9
+ * All recognized error categories (kept for backwards compatibility).
10
+ *
11
+ * `rate_limit` / `billing` (#761 AC-6) only arise from the driver's structured
12
+ * errors — `classifyError` never produces them, since stderr text cannot
13
+ * distinguish a window-exhausted limit from a transient 429 (`api_error`).
14
+ * Keep in sync with the inline category enum in `run-log-schema.ts`.
15
+ */
16
+ export declare const ERROR_CATEGORIES: readonly ["context_overflow", "api_error", "hook_failure", "build_error", "timeout", "rate_limit", "billing", "unknown"];
10
17
  export type ErrorCategory = (typeof ERROR_CATEGORIES)[number];
11
18
  /**
12
19
  * Map from error type name to legacy category string.
@@ -5,13 +5,22 @@
5
5
  * categories. Exit codes are the primary signal; stderr patterns are secondary.
6
6
  */
7
7
  import { ContextOverflowError, ApiError, HookFailureError, BuildError, TimeoutError, SubprocessError, } from "../errors.js";
8
- /** All recognized error categories (kept for backwards compatibility). */
8
+ /**
9
+ * All recognized error categories (kept for backwards compatibility).
10
+ *
11
+ * `rate_limit` / `billing` (#761 AC-6) only arise from the driver's structured
12
+ * errors — `classifyError` never produces them, since stderr text cannot
13
+ * distinguish a window-exhausted limit from a transient 429 (`api_error`).
14
+ * Keep in sync with the inline category enum in `run-log-schema.ts`.
15
+ */
9
16
  export const ERROR_CATEGORIES = [
10
17
  "context_overflow",
11
18
  "api_error",
12
19
  "hook_failure",
13
20
  "build_error",
14
21
  "timeout",
22
+ "rate_limit",
23
+ "billing",
15
24
  "unknown",
16
25
  ];
17
26
  /**
@@ -30,6 +39,10 @@ export function errorTypeToCategory(error) {
30
39
  return "build_error";
31
40
  case "TimeoutError":
32
41
  return "timeout";
42
+ case "RateLimitError":
43
+ return "rate_limit";
44
+ case "BillingError":
45
+ return "billing";
33
46
  default:
34
47
  return "unknown";
35
48
  }
@@ -18,18 +18,50 @@ export interface GitDiffStatsResult {
18
18
  /** Total lines deleted across all files */
19
19
  totalDeletions: number;
20
20
  }
21
+ /**
22
+ * Resolve the ref diff stats should compare against (#878).
23
+ *
24
+ * Worktrees are created from `origin/<base>` (worktree-manager), but callers
25
+ * historically passed the bare branch name and the diff ran against the
26
+ * *local* ref. When local `<base>` lags the remote — routine, since nothing
27
+ * in the run path updates it — `<base>...HEAD` attributes commits the run
28
+ * never made (phantom filesModified).
29
+ *
30
+ * Candidates are the origin-qualified and bare forms of `baseBranch`
31
+ * (already-remote-qualified input keeps a single candidate). Among the
32
+ * candidates that resolve to a commit, pick the one nearest to HEAD
33
+ * (smallest `rev-list --count <cand>..HEAD`), preferring the
34
+ * origin-qualified form on a tie. Nearest-wins matches the worktree's
35
+ * actual creation point in both staleness directions: a stale local
36
+ * default branch (this issue) and a chain-mode worktree branched from a
37
+ * local base that is ahead of its pushed counterpart. Falls back to
38
+ * `baseBranch` verbatim when no candidate resolves (e.g. remote-less repo
39
+ * with a missing branch) — the diff then fails gracefully to empty, the
40
+ * pre-#878 behavior.
41
+ */
42
+ export declare function resolveDiffBase(worktreePath: string, baseBranch: string): string;
21
43
  /**
22
44
  * Get git commit SHA for a worktree (AC-2)
23
45
  *
46
+ * When `baseRef` is provided, returns undefined if HEAD has no commits
47
+ * unique to it (#878) — a branch that never moved off its base would
48
+ * otherwise log the base tip as if it were the phase's commit. Callers
49
+ * recording plain "where is HEAD" markers (run start/end) omit `baseRef`.
50
+ *
24
51
  * @param worktreePath - Path to the git worktree
25
- * @returns The current HEAD commit SHA, or undefined on error
52
+ * @param baseRef - Optional resolved base ref (see resolveDiffBase); when
53
+ * given, a HEAD with zero commits past it yields undefined
54
+ * @returns The current HEAD commit SHA, or undefined on error / no unique
55
+ * commits
26
56
  */
27
- export declare function getCommitHash(worktreePath: string): string | undefined;
57
+ export declare function getCommitHash(worktreePath: string, baseRef?: string): string | undefined;
28
58
  /**
29
59
  * Get git diff statistics for a worktree (AC-1, AC-3, AC-4)
30
60
  *
31
61
  * Efficiently captures both filesModified and fileDiffStats using
32
- * minimal git commands. Uses main...HEAD comparison by default.
62
+ * minimal git commands. The base is resolved via resolveDiffBase (#878) so
63
+ * the comparison targets the ref the worktree was actually created from
64
+ * (origin/<base> in the common case) rather than a possibly-stale local ref.
33
65
  *
34
66
  * @param worktreePath - Path to the git worktree
35
67
  * @param baseBranch - Branch to compare against (default: "main")