sequant 2.9.0 → 2.11.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 (209) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +20 -5
  4. package/dist/bin/cli.js +143 -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 +56 -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 +20 -7
  10. package/dist/marketplace/external_plugins/sequant/hooks/pre-tool.sh +75 -10
  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 +215 -390
  13. package/dist/marketplace/external_plugins/sequant/skills/assess/references/predicted-collision-detection.md +1 -1
  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 +3 -1
  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/release/SKILL.md +1 -1
  28. package/dist/marketplace/external_plugins/sequant/skills/setup/SKILL.md +5 -5
  29. package/dist/marketplace/external_plugins/sequant/skills/spec/SKILL.md +2 -0
  30. package/dist/marketplace/external_plugins/sequant/skills/test/SKILL.md +1 -1
  31. package/dist/src/commands/assess-render.d.ts +23 -0
  32. package/dist/src/commands/assess-render.js +60 -0
  33. package/dist/src/commands/doctor.js +8 -13
  34. package/dist/src/commands/init.js +13 -1
  35. package/dist/src/commands/locks.d.ts +20 -1
  36. package/dist/src/commands/locks.js +208 -4
  37. package/dist/src/commands/logs.js +14 -5
  38. package/dist/src/commands/merge.js +154 -2
  39. package/dist/src/commands/ready-tui-adapter.js +6 -1
  40. package/dist/src/commands/ready.d.ts +31 -3
  41. package/dist/src/commands/ready.js +53 -13
  42. package/dist/src/commands/resume.d.ts +113 -0
  43. package/dist/src/commands/resume.js +193 -0
  44. package/dist/src/commands/run-display.js +38 -4
  45. package/dist/src/commands/run-flags.d.ts +42 -1
  46. package/dist/src/commands/run-flags.js +53 -1
  47. package/dist/src/commands/run-progress.js +26 -1
  48. package/dist/src/commands/run.js +11 -10
  49. package/dist/src/commands/state.js +7 -0
  50. package/dist/src/commands/status.d.ts +9 -0
  51. package/dist/src/commands/status.js +24 -1
  52. package/dist/src/commands/sync.js +90 -19
  53. package/dist/src/commands/update.js +28 -5
  54. package/dist/src/commands/worktree.d.ts +31 -0
  55. package/dist/src/commands/worktree.js +95 -0
  56. package/dist/src/lib/ac-parser.d.ts +14 -0
  57. package/dist/src/lib/ac-parser.js +99 -5
  58. package/dist/src/lib/assess/index.d.ts +10 -0
  59. package/dist/src/lib/assess/index.js +9 -0
  60. package/dist/src/lib/assess/renderer.d.ts +64 -0
  61. package/dist/src/lib/assess/renderer.js +481 -0
  62. package/dist/src/lib/assess/types.d.ts +224 -0
  63. package/dist/src/lib/assess/types.js +241 -0
  64. package/dist/src/lib/ci/outputs.d.ts +15 -2
  65. package/dist/src/lib/ci/outputs.js +17 -5
  66. package/dist/src/lib/cli-flags.d.ts +56 -0
  67. package/dist/src/lib/cli-flags.js +89 -0
  68. package/dist/src/lib/cli-ui/run-renderer-types.d.ts +23 -7
  69. package/dist/src/lib/cli-ui/run-renderer.d.ts +10 -0
  70. package/dist/src/lib/cli-ui/run-renderer.js +69 -1
  71. package/dist/src/lib/errors.d.ts +78 -6
  72. package/dist/src/lib/errors.js +133 -15
  73. package/dist/src/lib/locks/checkout-lock.d.ts +193 -0
  74. package/dist/src/lib/locks/checkout-lock.js +389 -0
  75. package/dist/src/lib/locks/index.d.ts +7 -4
  76. package/dist/src/lib/locks/index.js +5 -3
  77. package/dist/src/lib/locks/lock-manager.d.ts +101 -3
  78. package/dist/src/lib/locks/lock-manager.js +277 -8
  79. package/dist/src/lib/locks/types.d.ts +106 -3
  80. package/dist/src/lib/locks/types.js +44 -0
  81. package/dist/src/lib/mcp-config.d.ts +45 -0
  82. package/dist/src/lib/mcp-config.js +77 -1
  83. package/dist/src/lib/merge-check/combined-branch-test.d.ts +21 -2
  84. package/dist/src/lib/merge-check/combined-branch-test.js +233 -110
  85. package/dist/src/lib/merge-check/command-result.d.ts +36 -0
  86. package/dist/src/lib/merge-check/command-result.js +56 -0
  87. package/dist/src/lib/merge-check/index.d.ts +4 -0
  88. package/dist/src/lib/merge-check/index.js +1 -1
  89. package/dist/src/lib/merge-check/report.js +5 -1
  90. package/dist/src/lib/merge-check/types.d.ts +9 -0
  91. package/dist/src/lib/merge-check/watch.d.ts +121 -0
  92. package/dist/src/lib/merge-check/watch.js +215 -0
  93. package/dist/src/lib/qa/infra-blocked-ci.d.ts +90 -0
  94. package/dist/src/lib/qa/infra-blocked-ci.js +80 -0
  95. package/dist/src/lib/settings.d.ts +87 -0
  96. package/dist/src/lib/settings.js +106 -8
  97. package/dist/src/lib/shutdown.d.ts +42 -2
  98. package/dist/src/lib/shutdown.js +75 -5
  99. package/dist/src/lib/skills-check.d.ts +26 -0
  100. package/dist/src/lib/skills-check.js +31 -0
  101. package/dist/src/lib/stacks.d.ts +110 -1
  102. package/dist/src/lib/stacks.js +244 -5
  103. package/dist/src/lib/templates.d.ts +74 -0
  104. package/dist/src/lib/templates.js +171 -16
  105. package/dist/src/lib/test-tautology-detector.d.ts +10 -2
  106. package/dist/src/lib/test-tautology-detector.js +213 -12
  107. package/dist/src/lib/version-check.d.ts +31 -0
  108. package/dist/src/lib/version-check.js +45 -2
  109. package/dist/src/lib/workflow/batch-executor.d.ts +125 -6
  110. package/dist/src/lib/workflow/batch-executor.js +552 -60
  111. package/dist/src/lib/workflow/chain-resume.d.ts +13 -11
  112. package/dist/src/lib/workflow/chain-resume.js +24 -20
  113. package/dist/src/lib/workflow/completed-status.d.ts +52 -0
  114. package/dist/src/lib/workflow/completed-status.js +57 -0
  115. package/dist/src/lib/workflow/config-resolver.d.ts +51 -0
  116. package/dist/src/lib/workflow/config-resolver.js +143 -2
  117. package/dist/src/lib/workflow/drivers/agent-driver.d.ts +22 -0
  118. package/dist/src/lib/workflow/drivers/aider.d.ts +2 -0
  119. package/dist/src/lib/workflow/drivers/aider.js +2 -0
  120. package/dist/src/lib/workflow/drivers/claude-code.d.ts +14 -0
  121. package/dist/src/lib/workflow/drivers/claude-code.js +25 -2
  122. package/dist/src/lib/workflow/effort-escalation.d.ts +73 -0
  123. package/dist/src/lib/workflow/effort-escalation.js +82 -0
  124. package/dist/src/lib/workflow/error-classifier.d.ts +4 -1
  125. package/dist/src/lib/workflow/error-classifier.js +4 -0
  126. package/dist/src/lib/workflow/git-diff-utils.d.ts +35 -3
  127. package/dist/src/lib/workflow/git-diff-utils.js +77 -4
  128. package/dist/src/lib/workflow/heartbeat.d.ts +38 -0
  129. package/dist/src/lib/workflow/heartbeat.js +90 -0
  130. package/dist/src/lib/workflow/log-writer.d.ts +54 -2
  131. package/dist/src/lib/workflow/log-writer.js +95 -6
  132. package/dist/src/lib/workflow/metrics-schema.d.ts +52 -9
  133. package/dist/src/lib/workflow/metrics-schema.js +33 -0
  134. package/dist/src/lib/workflow/metrics-writer.d.ts +11 -0
  135. package/dist/src/lib/workflow/phase-detection.d.ts +12 -0
  136. package/dist/src/lib/workflow/phase-detection.js +5 -1
  137. package/dist/src/lib/workflow/phase-executor.d.ts +203 -14
  138. package/dist/src/lib/workflow/phase-executor.js +467 -52
  139. package/dist/src/lib/workflow/phase-registry.js +2 -1
  140. package/dist/src/lib/workflow/platforms/github.d.ts +64 -0
  141. package/dist/src/lib/workflow/platforms/github.js +152 -0
  142. package/dist/src/lib/workflow/qa-cache.d.ts +6 -1
  143. package/dist/src/lib/workflow/qa-cache.js +22 -9
  144. package/dist/src/lib/workflow/ready-gate.d.ts +45 -4
  145. package/dist/src/lib/workflow/ready-gate.js +49 -12
  146. package/dist/src/lib/workflow/run-log-schema.d.ts +69 -0
  147. package/dist/src/lib/workflow/run-log-schema.js +78 -2
  148. package/dist/src/lib/workflow/run-orchestrator.d.ts +19 -0
  149. package/dist/src/lib/workflow/run-orchestrator.js +168 -20
  150. package/dist/src/lib/workflow/skills-preflight.d.ts +89 -0
  151. package/dist/src/lib/workflow/skills-preflight.js +112 -0
  152. package/dist/src/lib/workflow/spec-recommendation.d.ts +71 -0
  153. package/dist/src/lib/workflow/spec-recommendation.js +142 -0
  154. package/dist/src/lib/workflow/state-cleanup.js +9 -2
  155. package/dist/src/lib/workflow/state-manager.d.ts +33 -0
  156. package/dist/src/lib/workflow/state-manager.js +83 -0
  157. package/dist/src/lib/workflow/state-schema.d.ts +26 -0
  158. package/dist/src/lib/workflow/state-schema.js +47 -0
  159. package/dist/src/lib/workflow/types.d.ts +190 -4
  160. package/dist/src/lib/workflow/types.js +1 -0
  161. package/dist/src/lib/workflow/worktree-manager.d.ts +36 -3
  162. package/dist/src/lib/workflow/worktree-manager.js +111 -24
  163. package/dist/src/lib/workflow/worktree-resolver.d.ts +73 -0
  164. package/dist/src/lib/workflow/worktree-resolver.js +126 -0
  165. package/dist/src/mcp/tools/run.d.ts +9 -1
  166. package/dist/src/mcp/tools/run.js +20 -1
  167. package/dist/src/mcp/tools/status.js +6 -0
  168. package/dist/src/ui/tui/ElapsedTimer.d.ts +12 -3
  169. package/dist/src/ui/tui/ElapsedTimer.js +12 -10
  170. package/dist/src/ui/tui/IssueBox.js +10 -2
  171. package/dist/src/ui/tui/row-cap.js +2 -1
  172. package/dist/src/ui/tui/theme.d.ts +7 -0
  173. package/dist/src/ui/tui/theme.js +9 -0
  174. package/package.json +10 -8
  175. package/templates/hooks/parallel-marker.sh +50 -0
  176. package/templates/hooks/post-tool.sh +20 -7
  177. package/templates/hooks/pre-tool.sh +303 -10
  178. package/templates/scripts/cleanup-worktree.sh +217 -33
  179. package/templates/scripts/new-feature.sh +289 -35
  180. package/templates/skills/_shared/references/subagent-types.md +7 -18
  181. package/templates/skills/_shared/references/trust-model.md +18 -0
  182. package/templates/skills/assess/SKILL.md +220 -391
  183. package/templates/skills/assess/references/predicted-collision-detection.md +1 -1
  184. package/templates/skills/docs/SKILL.md +4 -4
  185. package/templates/skills/exec/SKILL.md +87 -13
  186. package/templates/skills/fullsolve/SKILL.md +130 -22
  187. package/templates/skills/loop/SKILL.md +94 -13
  188. package/templates/skills/merger/SKILL.md +100 -12
  189. package/templates/skills/qa/SKILL.md +396 -63
  190. package/templates/skills/qa/references/anti-pattern-detection.md +6 -6
  191. package/templates/skills/qa/references/call-site-review.md +2 -2
  192. package/templates/skills/qa/references/code-review-checklist.md +2 -2
  193. package/templates/skills/qa/references/fixtures/injection-issue-body.md +36 -0
  194. package/templates/skills/qa/references/quality-gates.md +21 -7
  195. package/templates/skills/qa/references/testing-requirements.md +1 -1
  196. package/templates/skills/qa/scripts/quality-checks.sh +11 -11
  197. package/templates/skills/release/SKILL.md +80 -1
  198. package/templates/skills/setup/SKILL.md +5 -5
  199. package/templates/skills/spec/SKILL.md +33 -15
  200. package/templates/skills/spec/references/recommended-workflow.md +14 -1
  201. package/templates/skills/test/SKILL.md +1 -1
  202. package/templates/skills/testgen/SKILL.md +23 -6
  203. package/dist/src/lib/phase-spinner.d.ts +0 -146
  204. package/dist/src/lib/phase-spinner.js +0 -255
  205. package/dist/src/lib/workflow/pr-operations.d.ts +0 -86
  206. package/dist/src/lib/workflow/pr-operations.js +0 -326
  207. package/dist/src/lib/workflow/run-summary.d.ts +0 -36
  208. package/dist/src/lib/workflow/run-summary.js +0 -142
  209. package/templates/agents/sequant-explorer.md +0 -24
@@ -2,28 +2,29 @@
2
2
  * Chain resume planning (#760).
3
3
  *
4
4
  * When a `--chain` run fails mid-way, earlier links may already be complete
5
- * (`ready_for_merge`) with a checkpoint commit on their feature branch
5
+ * (see `completed-status.ts`) with a checkpoint commit on their feature branch
6
6
  * (`createCheckpointCommit`, worktree-manager.ts). Re-running the same chain
7
7
  * should skip that completed prefix and resume at the first incomplete link,
8
8
  * rebased onto the last completed link's committed tip — NOT `main` (which is
9
9
  * the #748 wrong-base failure this reuses the #748 rebase path to avoid).
10
10
  *
11
- * The existing pre-flight guard (`run-orchestrator.ts`) already drops
12
- * `ready_for_merge`/`merged` issues from the run, but it is chain-unaware:
13
- * dropping the completed prefix leaves the first incomplete link at index 0,
14
- * where `executeSequential`'s successor-rebase never fires, so it silently
15
- * builds on `main`. This module computes a *chain-correct* resume plan that
16
- * preserves the completed prefix as the resume base.
11
+ * The non-chain pre-flight guard (`run-orchestrator.ts`) drops completed issues
12
+ * from the run using the same {@link isCompletedIssueStatus} predicate, but it
13
+ * is chain-unaware: dropping the completed prefix leaves the first incomplete
14
+ * link at index 0, where `executeSequential`'s successor-rebase never fires, so
15
+ * it silently builds on `main`. This module computes a *chain-correct* resume
16
+ * plan that preserves the completed prefix as the resume base.
17
17
  *
18
18
  * The planner is pure over an injected {@link CompletedLinkResolver} so the
19
19
  * skip/fail-fast state machine (AC-3) is unit-testable without real git; the
20
20
  * real-git rebase is covered by the integration test.
21
21
  */
22
+ import { type CompletedIssueStatus } from "./completed-status.js";
22
23
  /** A completed link that will be skipped (not re-executed) on resume. */
23
24
  export interface ChainResumeSkip {
24
25
  issueNumber: number;
25
26
  /** Why it was skipped — the terminal-ish status that made it complete. */
26
- status: "ready_for_merge" | "merged";
27
+ status: CompletedIssueStatus;
27
28
  /** The link's local feature branch (from state), if known. */
28
29
  branch?: string;
29
30
  /** The link's worktree path (from state), if known — used for the dirty check. */
@@ -45,9 +46,10 @@ export interface ChainResumePlan {
45
46
  /** First incomplete issue number (the resume point), if any. */
46
47
  resumeIssue?: number;
47
48
  /**
48
- * Set when resume cannot proceed safely (AC-3): a `ready_for_merge` link's
49
- * branch/checkpoint is gone and its tip is unreconstructable. The caller must
50
- * abort rather than silently execute the successor on the wrong base.
49
+ * Set when resume cannot proceed safely (AC-3): an unmerged completed link's
50
+ * (`ready_for_merge` or `waiting_for_human_merge`) branch/checkpoint is gone
51
+ * and its tip is unreconstructable. The caller must abort rather than
52
+ * silently execute the successor on the wrong base.
51
53
  */
52
54
  failFast?: string;
53
55
  /** True when every link in the chain is already complete. */
@@ -2,24 +2,24 @@
2
2
  * Chain resume planning (#760).
3
3
  *
4
4
  * When a `--chain` run fails mid-way, earlier links may already be complete
5
- * (`ready_for_merge`) with a checkpoint commit on their feature branch
5
+ * (see `completed-status.ts`) with a checkpoint commit on their feature branch
6
6
  * (`createCheckpointCommit`, worktree-manager.ts). Re-running the same chain
7
7
  * should skip that completed prefix and resume at the first incomplete link,
8
8
  * rebased onto the last completed link's committed tip — NOT `main` (which is
9
9
  * the #748 wrong-base failure this reuses the #748 rebase path to avoid).
10
10
  *
11
- * The existing pre-flight guard (`run-orchestrator.ts`) already drops
12
- * `ready_for_merge`/`merged` issues from the run, but it is chain-unaware:
13
- * dropping the completed prefix leaves the first incomplete link at index 0,
14
- * where `executeSequential`'s successor-rebase never fires, so it silently
15
- * builds on `main`. This module computes a *chain-correct* resume plan that
16
- * preserves the completed prefix as the resume base.
11
+ * The non-chain pre-flight guard (`run-orchestrator.ts`) drops completed issues
12
+ * from the run using the same {@link isCompletedIssueStatus} predicate, but it
13
+ * is chain-unaware: dropping the completed prefix leaves the first incomplete
14
+ * link at index 0, where `executeSequential`'s successor-rebase never fires, so
15
+ * it silently builds on `main`. This module computes a *chain-correct* resume
16
+ * plan that preserves the completed prefix as the resume base.
17
17
  *
18
18
  * The planner is pure over an injected {@link CompletedLinkResolver} so the
19
19
  * skip/fail-fast state machine (AC-3) is unit-testable without real git; the
20
20
  * real-git rebase is covered by the integration test.
21
21
  */
22
- const COMPLETED_STATUSES = new Set(["ready_for_merge", "merged"]);
22
+ import { isCompletedIssueStatus, } from "./completed-status.js";
23
23
  /**
24
24
  * Compute a chain-correct resume plan.
25
25
  *
@@ -37,7 +37,7 @@ export function computeChainResumePlan(orderedLinks, baseBranch, resolver) {
37
37
  const skipped = [];
38
38
  let firstIncomplete = 0;
39
39
  for (const link of orderedLinks) {
40
- if (link.status && COMPLETED_STATUSES.has(link.status)) {
40
+ if (isCompletedIssueStatus(link.status)) {
41
41
  skipped.push({
42
42
  issueNumber: link.issueNumber,
43
43
  status: link.status,
@@ -72,17 +72,18 @@ export function computeChainResumePlan(orderedLinks, baseBranch, resolver) {
72
72
  resumeBaseCommit = resolver.resolveBaseTip();
73
73
  }
74
74
  else {
75
- // ready_for_merge: the checkpoint tip lives only on the local feature
76
- // branch. If that branch is gone (worktree/branch destroyed mid-way), the
77
- // tip is unreconstructable — fail fast instead of wrong-basing the
78
- // successor onto main (which would miss the completed link's work).
75
+ // ready_for_merge / waiting_for_human_merge: the checkpoint tip lives only
76
+ // on the local feature branch. If that branch is gone (worktree/branch
77
+ // destroyed mid-way), the tip is unreconstructable — fail fast instead of
78
+ // wrong-basing the successor onto main (which would miss the completed
79
+ // link's work).
79
80
  if (!last.branch) {
80
81
  return {
81
82
  skipped,
82
83
  active,
83
84
  resumeIssue: active[0],
84
85
  allComplete: false,
85
- failFast: `#${last.issueNumber} is ready_for_merge but no branch is recorded in state — ` +
86
+ failFast: `#${last.issueNumber} is ${last.status} but no branch is recorded in state — ` +
86
87
  `cannot reconstruct the resume base. Re-run with --force to redo the chain from scratch.`,
87
88
  };
88
89
  }
@@ -93,24 +94,27 @@ export function computeChainResumePlan(orderedLinks, baseBranch, resolver) {
93
94
  active,
94
95
  resumeIssue: active[0],
95
96
  allComplete: false,
96
- failFast: `#${last.issueNumber} is ready_for_merge but its branch "${last.branch}" no longer exists — ` +
97
+ failFast: `#${last.issueNumber} is ${last.status} but its branch "${last.branch}" no longer exists — ` +
97
98
  `the resume base is unreconstructable. Re-run with --force to redo the chain from scratch.`,
98
99
  };
99
100
  }
100
101
  // The branch exists, but a tip is only a valid resume base if it actually
101
102
  // contains the link's work. `createCheckpointCommit` sweeps trailing
102
103
  // uncommitted changes into a checkpoint commit; when it fails (commit hook,
103
- // staging error, or unrelated dirty files) the status was *already* written
104
- // as ready_for_merge, so this link still reads as a completed prefix while
105
- // its tip is missing work. Rebasing the successor onto it would be exactly
106
- // the silent wrong-base execution AC-3 forbids fail fast instead.
104
+ // staging error, or unrelated dirty files) a completed status was *already*
105
+ // written — `ready_for_merge` on a plain run, or `waiting_for_human_merge`
106
+ // when #817's `--ready-gate` owned the terminal status (#837) so this link
107
+ // still reads as a completed prefix while its tip is missing work. Both
108
+ // statuses reach this branch, so the check covers gated links too. Rebasing
109
+ // the successor onto such a tip would be exactly the silent wrong-base
110
+ // execution AC-3 forbids — fail fast instead.
107
111
  if (last.worktree && resolver.isWorktreeDirty(last.worktree)) {
108
112
  return {
109
113
  skipped,
110
114
  active,
111
115
  resumeIssue: active[0],
112
116
  allComplete: false,
113
- failFast: `#${last.issueNumber} is ready_for_merge but its worktree has uncommitted changes — ` +
117
+ failFast: `#${last.issueNumber} is ${last.status} but its worktree has uncommitted changes — ` +
114
118
  `its checkpoint commit never landed, so branch "${last.branch}" is missing that work and ` +
115
119
  `resuming #${active[0]} here would build on an incomplete base. Commit them in ${last.worktree}, ` +
116
120
  `or re-run with --force to redo the chain from scratch.`,
@@ -0,0 +1,52 @@
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 declare const COMPLETED_ISSUE_STATUSES: readonly ["ready_for_merge", "merged", "waiting_for_human_merge"];
42
+ /** A persisted status that counts as completed work. */
43
+ export type CompletedIssueStatus = (typeof COMPLETED_ISSUE_STATUSES)[number];
44
+ /**
45
+ * True iff `status` marks the issue as already finished, so a re-run should
46
+ * skip it unless `--force` is passed.
47
+ *
48
+ * Accepts `string | undefined` because persisted state may predate the current
49
+ * schema or be absent entirely; an unknown or missing status is treated as
50
+ * incomplete (re-execute), never as complete.
51
+ */
52
+ export declare function isCompletedIssueStatus(status: string | undefined): status is CompletedIssueStatus;
@@ -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,57 @@ 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;
71
+ /** A single phase's resolved model/effort override (#914). */
72
+ export interface PhasePolicy {
73
+ model?: string;
74
+ effort?: string;
75
+ }
76
+ /**
77
+ * Parse a `--models`/`--efforts` CLI spec into a phase → value map.
78
+ *
79
+ * Grammar: a bare value (`"sonnet"`) applies to every phase and resolves to
80
+ * `{"*": "sonnet"}`; a comma list of `phase=value` pairs (`"spec=fable,exec=sonnet"`)
81
+ * resolves per phase. Mixing the two forms, an empty phase/value, or an
82
+ * unrecognized phase name all fail fast — this is the CLI boundary, so a
83
+ * malformed spec must never silently resolve to "nothing configured".
84
+ */
85
+ export declare function parsePhaseSpec(spec: string, phaseNames: string[]): Record<string, string>;
86
+ /**
87
+ * Resolve per-phase model/effort policies with CLI > settings > absent
88
+ * precedence.
89
+ *
90
+ * This is the single resolver both `buildExecutionConfig` (here) and
91
+ * `ready-gate.ts:buildPhaseConfig` call, so they cannot drift the way the
92
+ * two `phaseTimeout` producers did in #833 — see `positiveOr`'s doc comment
93
+ * for that history.
94
+ */
95
+ export declare function resolvePhasePolicies(cliModels: string | undefined, cliEfforts: string | undefined, settingsPhases: Record<string, PhasePolicy> | undefined, phaseNames: string[]): Record<string, PhasePolicy>;
45
96
  /**
46
97
  * Build an ExecutionConfig from merged RunOptions and settings.
47
98
  * Extracts the phase-timeout, MCP, retry, and mode resolution logic
@@ -8,6 +8,7 @@
8
8
  */
9
9
  import { DEFAULT_CONFIG, DEFAULT_PHASES, } from "./types.js";
10
10
  import { getEnvConfig } from "./batch-executor.js";
11
+ import { getPhaseNames } from "./phase-registry.js";
11
12
  /**
12
13
  * Coerce an env-var string to the type of the default value.
13
14
  * Returns the string as-is if no default exists for type inference.
@@ -97,6 +98,10 @@ export function normalizeCommanderOptions(options) {
97
98
  ...(raw.retry === false && { noRetry: true }),
98
99
  ...(raw.rebase === false && { noRebase: true }),
99
100
  ...(raw.pr === false && { noPr: true }),
101
+ // #804: map the flag-derived key onto the interface field. Guarded on
102
+ // `undefined` (not truthiness) so an explicit `--auto-wait 0` still
103
+ // overrides a non-zero setting.
104
+ ...(raw.autoWait !== undefined && { autoWaitMinutes: raw.autoWait }),
100
105
  };
101
106
  }
102
107
  /**
@@ -117,6 +122,7 @@ export function resolveRunOptions(cliOptions, settings) {
117
122
  qualityLoop: defined.qualityLoop ?? settings.run.qualityLoop,
118
123
  maxIterations: defined.maxIterations ?? settings.run.maxIterations,
119
124
  noSmartTests: defined.noSmartTests ?? !settings.run.smartTests,
125
+ autoWaitMinutes: defined.autoWaitMinutes ?? settings.run.autoWaitMinutes,
120
126
  // Agent settings
121
127
  isolateParallel: defined.isolateParallel ?? settings.agents.isolateParallel,
122
128
  // Env overrides
@@ -129,6 +135,116 @@ export function resolveRunOptions(cliOptions, settings) {
129
135
  merged.autoDetectPhases = autoDetectPhases;
130
136
  return merged;
131
137
  }
138
+ /**
139
+ * Fall back to `fallback` unless `value` is a usable positive number.
140
+ *
141
+ * `??` alone is not enough for these (#833). `NaN` is not nullish, so a
142
+ * malformed value survives `?? default` and flows on into `setTimeout`, which
143
+ * clamps a `NaN` delay to 0 and aborts the phase on its first tick, or into
144
+ * `while (iteration < maxIterations)`, which is false on entry and runs zero
145
+ * phases. Both read as a phase/agent fault rather than a bad input, which is
146
+ * what makes the silent version expensive. Non-finite and non-positive values
147
+ * therefore fall back *to* the default instead of through it.
148
+ *
149
+ * `bin/cli.ts` rejects these at the flag boundary with a message naming the
150
+ * flag — that is the user-facing fix. This is the structural backstop for
151
+ * programmatic callers, `settings.json`, and whatever calls this next.
152
+ *
153
+ * Exported because `phaseTimeout` has two producers, not one: this module and
154
+ * `commands/ready.ts`, whose value reaches the driver through
155
+ * `ready-gate.ts`'s own `buildPhaseConfig` and never passes through
156
+ * `buildExecutionConfig`. Guarding only here would have left that path open.
157
+ * Chain it to express the layering — CLI, then settings, then the default:
158
+ *
159
+ * ```ts
160
+ * positiveOr(options.timeout, positiveOr(settings.run.timeout, DEFAULT))
161
+ * ```
162
+ */
163
+ export function positiveOr(value, fallback) {
164
+ return typeof value === "number" && Number.isFinite(value) && value > 0
165
+ ? value
166
+ : fallback;
167
+ }
168
+ /**
169
+ * Parse a `--models`/`--efforts` CLI spec into a phase → value map.
170
+ *
171
+ * Grammar: a bare value (`"sonnet"`) applies to every phase and resolves to
172
+ * `{"*": "sonnet"}`; a comma list of `phase=value` pairs (`"spec=fable,exec=sonnet"`)
173
+ * resolves per phase. Mixing the two forms, an empty phase/value, or an
174
+ * unrecognized phase name all fail fast — this is the CLI boundary, so a
175
+ * malformed spec must never silently resolve to "nothing configured".
176
+ */
177
+ export function parsePhaseSpec(spec, phaseNames) {
178
+ const trimmed = spec.trim();
179
+ if (!trimmed) {
180
+ throw new Error("Malformed spec: value is empty.");
181
+ }
182
+ if (!trimmed.includes("=")) {
183
+ return { "*": trimmed };
184
+ }
185
+ const result = {};
186
+ for (const segment of trimmed.split(",")) {
187
+ const eq = segment.indexOf("=");
188
+ if (eq === -1) {
189
+ throw new Error(`Malformed spec segment '${segment}' — expected 'phase=value' (cannot mix a bare value with phase=value pairs).`);
190
+ }
191
+ const phase = segment.slice(0, eq).trim();
192
+ const value = segment.slice(eq + 1).trim();
193
+ if (!phase || !value) {
194
+ throw new Error(`Malformed spec segment '${segment}' — both phase and value are required.`);
195
+ }
196
+ if (!phaseNames.includes(phase)) {
197
+ throw new Error(`Unknown phase '${phase}'. Available phases: ${phaseNames.join(", ")}.`);
198
+ }
199
+ result[phase] = value;
200
+ }
201
+ return result;
202
+ }
203
+ /** Apply a parsed phase-spec map onto a policy accumulator for one field. */
204
+ function applyPhaseSpec(target, parsed, field, phaseNames) {
205
+ const wildcard = parsed["*"];
206
+ if (wildcard !== undefined) {
207
+ for (const phase of phaseNames) {
208
+ target[phase] = { ...target[phase], [field]: wildcard };
209
+ }
210
+ return;
211
+ }
212
+ for (const [phase, value] of Object.entries(parsed)) {
213
+ target[phase] = { ...target[phase], [field]: value };
214
+ }
215
+ }
216
+ /**
217
+ * Resolve per-phase model/effort policies with CLI > settings > absent
218
+ * precedence.
219
+ *
220
+ * This is the single resolver both `buildExecutionConfig` (here) and
221
+ * `ready-gate.ts:buildPhaseConfig` call, so they cannot drift the way the
222
+ * two `phaseTimeout` producers did in #833 — see `positiveOr`'s doc comment
223
+ * for that history.
224
+ */
225
+ export function resolvePhasePolicies(cliModels, cliEfforts, settingsPhases, phaseNames) {
226
+ const result = {};
227
+ // Layer 1 (lowest): settings.run.phases. Skip any phase name settings
228
+ // validation already didn't recognize — that's surfaced as a settings
229
+ // warning at load time (AC-1), not a resolver-time failure.
230
+ if (settingsPhases) {
231
+ for (const [phase, policy] of Object.entries(settingsPhases)) {
232
+ if (!phaseNames.includes(phase))
233
+ continue;
234
+ result[phase] = { ...policy };
235
+ }
236
+ }
237
+ // Layer 2 (highest): CLI --models/--efforts. Malformed specs throw here —
238
+ // callers at the CLI boundary (cli-flags.ts) turn that into a fail-fast
239
+ // InvalidArgumentError.
240
+ if (cliModels) {
241
+ applyPhaseSpec(result, parsePhaseSpec(cliModels, phaseNames), "model", phaseNames);
242
+ }
243
+ if (cliEfforts) {
244
+ applyPhaseSpec(result, parsePhaseSpec(cliEfforts, phaseNames), "effort", phaseNames);
245
+ }
246
+ return result;
247
+ }
132
248
  /**
133
249
  * Build an ExecutionConfig from merged RunOptions and settings.
134
250
  * Extracts the phase-timeout, MCP, retry, and mode resolution logic
@@ -157,15 +273,40 @@ export function buildExecutionConfig(mergedOptions, settings, issueCount) {
157
273
  parallel: isParallel,
158
274
  dryRun: mergedOptions.dryRun ?? false,
159
275
  verbose: mergedOptions.verbose ?? false,
160
- phaseTimeout: mergedOptions.timeout ?? DEFAULT_CONFIG.phaseTimeout,
276
+ // #833: chained so the layering is CLI → settings → default, matching
277
+ // `commands/ready.ts`. A malformed CLI value falls back to the user's
278
+ // configured setting rather than skipping past it to the hardcoded
279
+ // default; a malformed setting falls back to the default. In the normal
280
+ // path `mergedOptions` already carries the settings value, so this only
281
+ // differs when one of the two layers is unusable — which is the case that
282
+ // matters.
283
+ phaseTimeout: positiveOr(mergedOptions.timeout, positiveOr(settings.run.timeout, DEFAULT_CONFIG.phaseTimeout)),
161
284
  qualityLoop: mergedOptions.qualityLoop ?? false,
162
- maxIterations: mergedOptions.maxIterations ?? DEFAULT_CONFIG.maxIterations,
285
+ maxIterations: positiveOr(mergedOptions.maxIterations, positiveOr(settings.run.maxIterations, DEFAULT_CONFIG.maxIterations)),
163
286
  noSmartTests: mergedOptions.noSmartTests ?? false,
164
287
  mcp: mcpEnabled,
165
288
  retry: retryEnabled,
289
+ // #804: default 0 (off) — the whole regression contract for auto-wait is
290
+ // that an unset flag leaves the #761/#799 halt path untouched.
291
+ autoWaitMinutes: mergedOptions.autoWaitMinutes ??
292
+ settings.run.autoWaitMinutes ??
293
+ DEFAULT_CONFIG.autoWaitMinutes,
166
294
  agent: mergedOptions.agent ?? settings.run.agent,
167
295
  aiderSettings: settings.run.aider,
168
296
  isolateParallel: mergedOptions.isolateParallel,
169
297
  relayEnabled,
298
+ // #817: opt-in only, no settings backing (AC-4). A missing `--ready-gate`
299
+ // resolves to false, keeping the run path byte-identical (AC-5). This is the
300
+ // load-bearing wire the #795 inert-flag class guards against — the flag is
301
+ // useless if it stops reaching the executor here.
302
+ readyGate: mergedOptions.readyGate ?? false,
303
+ // #914: CLI > settings > absent, via the shared resolver both
304
+ // ExecutionConfig producers call (see `resolvePhasePolicies`'s doc
305
+ // comment for the #833 drift this guards against).
306
+ phasePolicies: resolvePhasePolicies(mergedOptions.models, mergedOptions.efforts, settings.run.phases, getPhaseNames()),
307
+ // #915: CLI > settings > default `false` — mirrors the `readyGate`
308
+ // precedent above. Both `ExecutionConfig` producers (here and
309
+ // `ready-gate.ts:buildPhaseConfig`) resolve this the same way (#833).
310
+ effortEscalation: mergedOptions.escalateEffort ?? settings.run.effortEscalation ?? false,
170
311
  };
171
312
  }
@@ -49,6 +49,21 @@ export interface AgentExecutionConfig {
49
49
  onStderr?: (text: string) => void;
50
50
  /** Relevant files for the phase (used by file-oriented drivers like Aider) */
51
51
  files?: string[];
52
+ /**
53
+ * Claude model to use for this phase (#914). Forwarded verbatim to the
54
+ * Agent SDK `query()` options by ClaudeCodeDriver; ignored by drivers
55
+ * without a model concept (Aider uses its own `AiderSettings.model`).
56
+ * Absent by default — the SDK falls back to the CLI default model.
57
+ */
58
+ model?: string;
59
+ /**
60
+ * Reasoning effort for this phase (#914). Forwarded verbatim to the Agent
61
+ * SDK `query()` options by ClaudeCodeDriver; ignored by drivers without an
62
+ * effort concept. Absent by default — the SDK defaults to `high`. Matches
63
+ * the SDK's own closed `EffortLevel` enum, not a bare `string`, so a value
64
+ * that reaches this field type-checks against `query()`'s options.
65
+ */
66
+ effort?: "low" | "medium" | "high" | "xhigh" | "max";
52
67
  }
53
68
  /**
54
69
  * Result returned by an agent after executing a phase.
@@ -98,6 +113,13 @@ export interface AgentPhaseResult {
98
113
  export interface AgentDriver {
99
114
  /** Human-readable name for logging */
100
115
  name: string;
116
+ /**
117
+ * True when this driver executes phases by resolving slash-command skills
118
+ * from the project's `.claude/skills/` directory (claude-code). Drivers
119
+ * whose prompts do the work inline (aider's `driverOverrides` templates)
120
+ * return false, and the `run` skills pre-flight (#813) is skipped for them.
121
+ */
122
+ resolvesSkills: boolean;
101
123
  /** Execute a phase prompt and return structured result */
102
124
  executePhase(prompt: string, config: AgentExecutionConfig): Promise<AgentPhaseResult>;
103
125
  /** 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,6 +44,18 @@ 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
  /**
@@ -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.
@@ -94,6 +96,11 @@ export class ClaudeCodeDriver {
94
96
  ...(resumeToken ? { resume: resumeToken } : {}),
95
97
  env: config.env,
96
98
  ...(mcpServers ? { mcpServers } : {}),
99
+ // #914: per-phase model/effort override. Omitted entirely when
100
+ // unset (not `undefined`-valued) so the SDK's own default
101
+ // resolution is untouched — see the AC-3 key-presence test.
102
+ ...(config.model ? { model: config.model } : {}),
103
+ ...(config.effort ? { effort: config.effort } : {}),
97
104
  stderr: (data) => {
98
105
  capturedStderr += data;
99
106
  // Split on newlines and push each line to the ring buffer
@@ -282,11 +289,27 @@ export class ClaudeCodeDriver {
282
289
  * RateLimitError would wrongly re-enable the retry / MCP-fallback path. When
283
290
  * the `rate_limit_event` is itself a billing failure its richer metadata
284
291
  * (`canUserPurchaseCredits`, etc.) is preserved.
292
+ *
293
+ * Counter-exception (#860): when the `rate_limit_event` carries the FULL
294
+ * captured subscription-window shape — waitable window evidence (recognized
295
+ * window type + future `resetsAt`) *plus* the `out_of_credits` marker — the
296
+ * richer structured signal wins over the bare `billing_error` enum. A
297
+ * subscription plan's five-hour exhaustion surfaces assistant-side as a
298
+ * billing error, and overriding on that enum here would strip the metadata
299
+ * `--auto-wait` needs and re-inert the feature this classification exists
300
+ * to enable. Deliberately no wider than the evidenced shape: a pure
301
+ * throttle event (window, no billing markers) beside a `billing_error`
302
+ * enum keeps the #732 rule — billing wins — because there the two signals
303
+ * genuinely conflict and no capture justifies trusting the throttle.
285
304
  */
286
305
  buildStructuredError(rateLimitInfo, assistantError, apiRetryError) {
287
306
  if (rateLimitInfo) {
288
307
  const err = createRateLimitError(rateLimitInfo);
289
- if (err instanceof RateLimitError && assistantError === "billing_error") {
308
+ const isCapturedSubscriptionShape = isWaitableWindow(rateLimitInfo) &&
309
+ rateLimitInfo.overageDisabledReason === "out_of_credits";
310
+ if (err instanceof RateLimitError &&
311
+ assistantError === "billing_error" &&
312
+ !isCapturedSubscriptionShape) {
290
313
  return new BillingError("Billing error");
291
314
  }
292
315
  return err;