sequant 2.9.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 (188) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +14 -3
  4. package/dist/bin/cli.js +97 -17
  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.js +2 -0
  36. package/dist/src/commands/logs.js +14 -5
  37. package/dist/src/commands/merge.js +154 -2
  38. package/dist/src/commands/ready-tui-adapter.js +6 -1
  39. package/dist/src/commands/ready.d.ts +25 -3
  40. package/dist/src/commands/ready.js +39 -13
  41. package/dist/src/commands/resume.d.ts +113 -0
  42. package/dist/src/commands/resume.js +193 -0
  43. package/dist/src/commands/run-display.js +37 -4
  44. package/dist/src/commands/run-flags.d.ts +42 -1
  45. package/dist/src/commands/run-flags.js +53 -1
  46. package/dist/src/commands/run-progress.js +26 -1
  47. package/dist/src/commands/run.js +11 -10
  48. package/dist/src/commands/state.js +7 -0
  49. package/dist/src/commands/status.d.ts +9 -0
  50. package/dist/src/commands/status.js +24 -1
  51. package/dist/src/commands/sync.js +90 -19
  52. package/dist/src/commands/update.js +28 -5
  53. package/dist/src/lib/ac-parser.d.ts +14 -0
  54. package/dist/src/lib/ac-parser.js +99 -5
  55. package/dist/src/lib/assess/index.d.ts +10 -0
  56. package/dist/src/lib/assess/index.js +9 -0
  57. package/dist/src/lib/assess/renderer.d.ts +64 -0
  58. package/dist/src/lib/assess/renderer.js +481 -0
  59. package/dist/src/lib/assess/types.d.ts +224 -0
  60. package/dist/src/lib/assess/types.js +241 -0
  61. package/dist/src/lib/ci/outputs.d.ts +15 -2
  62. package/dist/src/lib/ci/outputs.js +17 -5
  63. package/dist/src/lib/cli-flags.d.ts +33 -0
  64. package/dist/src/lib/cli-flags.js +46 -0
  65. package/dist/src/lib/cli-ui/run-renderer-types.d.ts +21 -7
  66. package/dist/src/lib/cli-ui/run-renderer.d.ts +10 -0
  67. package/dist/src/lib/cli-ui/run-renderer.js +62 -0
  68. package/dist/src/lib/errors.d.ts +78 -6
  69. package/dist/src/lib/errors.js +133 -15
  70. package/dist/src/lib/locks/index.d.ts +3 -3
  71. package/dist/src/lib/locks/index.js +2 -2
  72. package/dist/src/lib/locks/lock-manager.d.ts +20 -2
  73. package/dist/src/lib/locks/lock-manager.js +47 -3
  74. package/dist/src/lib/locks/types.d.ts +34 -3
  75. package/dist/src/lib/locks/types.js +16 -0
  76. package/dist/src/lib/mcp-config.d.ts +45 -0
  77. package/dist/src/lib/mcp-config.js +77 -1
  78. package/dist/src/lib/merge-check/combined-branch-test.d.ts +21 -2
  79. package/dist/src/lib/merge-check/combined-branch-test.js +233 -110
  80. package/dist/src/lib/merge-check/command-result.d.ts +36 -0
  81. package/dist/src/lib/merge-check/command-result.js +56 -0
  82. package/dist/src/lib/merge-check/index.d.ts +4 -0
  83. package/dist/src/lib/merge-check/index.js +1 -1
  84. package/dist/src/lib/merge-check/report.js +5 -1
  85. package/dist/src/lib/merge-check/types.d.ts +9 -0
  86. package/dist/src/lib/merge-check/watch.d.ts +121 -0
  87. package/dist/src/lib/merge-check/watch.js +215 -0
  88. package/dist/src/lib/qa/infra-blocked-ci.d.ts +90 -0
  89. package/dist/src/lib/qa/infra-blocked-ci.js +80 -0
  90. package/dist/src/lib/settings.d.ts +14 -0
  91. package/dist/src/lib/settings.js +61 -8
  92. package/dist/src/lib/shutdown.d.ts +42 -2
  93. package/dist/src/lib/shutdown.js +75 -5
  94. package/dist/src/lib/skills-check.d.ts +26 -0
  95. package/dist/src/lib/skills-check.js +31 -0
  96. package/dist/src/lib/stacks.d.ts +110 -1
  97. package/dist/src/lib/stacks.js +244 -5
  98. package/dist/src/lib/templates.d.ts +74 -0
  99. package/dist/src/lib/templates.js +171 -16
  100. package/dist/src/lib/test-tautology-detector.d.ts +9 -2
  101. package/dist/src/lib/test-tautology-detector.js +153 -12
  102. package/dist/src/lib/version-check.d.ts +31 -0
  103. package/dist/src/lib/version-check.js +45 -2
  104. package/dist/src/lib/workflow/batch-executor.d.ts +125 -6
  105. package/dist/src/lib/workflow/batch-executor.js +477 -44
  106. package/dist/src/lib/workflow/chain-resume.d.ts +13 -11
  107. package/dist/src/lib/workflow/chain-resume.js +24 -20
  108. package/dist/src/lib/workflow/completed-status.d.ts +52 -0
  109. package/dist/src/lib/workflow/completed-status.js +57 -0
  110. package/dist/src/lib/workflow/config-resolver.d.ts +26 -0
  111. package/dist/src/lib/workflow/config-resolver.js +54 -2
  112. package/dist/src/lib/workflow/drivers/agent-driver.d.ts +7 -0
  113. package/dist/src/lib/workflow/drivers/aider.d.ts +2 -0
  114. package/dist/src/lib/workflow/drivers/aider.js +2 -0
  115. package/dist/src/lib/workflow/drivers/claude-code.d.ts +14 -0
  116. package/dist/src/lib/workflow/drivers/claude-code.js +20 -2
  117. package/dist/src/lib/workflow/git-diff-utils.d.ts +35 -3
  118. package/dist/src/lib/workflow/git-diff-utils.js +77 -4
  119. package/dist/src/lib/workflow/heartbeat.d.ts +38 -0
  120. package/dist/src/lib/workflow/heartbeat.js +90 -0
  121. package/dist/src/lib/workflow/log-writer.d.ts +44 -1
  122. package/dist/src/lib/workflow/log-writer.js +75 -6
  123. package/dist/src/lib/workflow/metrics-schema.d.ts +3 -3
  124. package/dist/src/lib/workflow/phase-executor.d.ts +203 -14
  125. package/dist/src/lib/workflow/phase-executor.js +457 -52
  126. package/dist/src/lib/workflow/phase-registry.js +2 -1
  127. package/dist/src/lib/workflow/platforms/github.d.ts +64 -0
  128. package/dist/src/lib/workflow/platforms/github.js +152 -0
  129. package/dist/src/lib/workflow/qa-cache.d.ts +6 -1
  130. package/dist/src/lib/workflow/qa-cache.js +22 -9
  131. package/dist/src/lib/workflow/ready-gate.d.ts +17 -4
  132. package/dist/src/lib/workflow/ready-gate.js +25 -9
  133. package/dist/src/lib/workflow/run-log-schema.d.ts +14 -0
  134. package/dist/src/lib/workflow/run-log-schema.js +47 -1
  135. package/dist/src/lib/workflow/run-orchestrator.d.ts +19 -0
  136. package/dist/src/lib/workflow/run-orchestrator.js +141 -20
  137. package/dist/src/lib/workflow/skills-preflight.d.ts +89 -0
  138. package/dist/src/lib/workflow/skills-preflight.js +112 -0
  139. package/dist/src/lib/workflow/state-cleanup.js +9 -2
  140. package/dist/src/lib/workflow/state-manager.d.ts +33 -0
  141. package/dist/src/lib/workflow/state-manager.js +83 -0
  142. package/dist/src/lib/workflow/state-schema.d.ts +26 -0
  143. package/dist/src/lib/workflow/state-schema.js +47 -0
  144. package/dist/src/lib/workflow/types.d.ts +126 -4
  145. package/dist/src/lib/workflow/types.js +1 -0
  146. package/dist/src/lib/workflow/worktree-manager.d.ts +28 -2
  147. package/dist/src/lib/workflow/worktree-manager.js +102 -23
  148. package/dist/src/mcp/tools/run.d.ts +9 -1
  149. package/dist/src/mcp/tools/run.js +20 -1
  150. package/dist/src/mcp/tools/status.js +6 -0
  151. package/dist/src/ui/tui/ElapsedTimer.d.ts +12 -3
  152. package/dist/src/ui/tui/ElapsedTimer.js +12 -10
  153. package/dist/src/ui/tui/IssueBox.js +10 -2
  154. package/dist/src/ui/tui/row-cap.js +2 -1
  155. package/dist/src/ui/tui/theme.d.ts +7 -0
  156. package/dist/src/ui/tui/theme.js +9 -0
  157. package/package.json +8 -7
  158. package/templates/hooks/parallel-marker.sh +50 -0
  159. package/templates/hooks/post-tool.sh +20 -7
  160. package/templates/hooks/pre-tool.sh +75 -10
  161. package/templates/scripts/cleanup-worktree.sh +182 -19
  162. package/templates/scripts/new-feature.sh +264 -16
  163. package/templates/skills/_shared/references/trust-model.md +18 -0
  164. package/templates/skills/assess/SKILL.md +215 -390
  165. package/templates/skills/assess/references/predicted-collision-detection.md +1 -1
  166. package/templates/skills/docs/SKILL.md +4 -4
  167. package/templates/skills/exec/SKILL.md +26 -6
  168. package/templates/skills/fullsolve/SKILL.md +3 -1
  169. package/templates/skills/loop/SKILL.md +38 -2
  170. package/templates/skills/merger/SKILL.md +2 -2
  171. package/templates/skills/qa/SKILL.md +337 -57
  172. package/templates/skills/qa/references/anti-pattern-detection.md +6 -6
  173. package/templates/skills/qa/references/call-site-review.md +2 -2
  174. package/templates/skills/qa/references/code-review-checklist.md +2 -2
  175. package/templates/skills/qa/references/fixtures/injection-issue-body.md +36 -0
  176. package/templates/skills/qa/references/quality-gates.md +21 -7
  177. package/templates/skills/qa/references/testing-requirements.md +1 -1
  178. package/templates/skills/qa/scripts/quality-checks.sh +11 -11
  179. package/templates/skills/release/SKILL.md +1 -1
  180. package/templates/skills/setup/SKILL.md +5 -5
  181. package/templates/skills/spec/SKILL.md +2 -0
  182. package/templates/skills/test/SKILL.md +1 -1
  183. package/dist/src/lib/phase-spinner.d.ts +0 -146
  184. package/dist/src/lib/phase-spinner.js +0 -255
  185. package/dist/src/lib/workflow/pr-operations.d.ts +0 -86
  186. package/dist/src/lib/workflow/pr-operations.js +0 -326
  187. package/dist/src/lib/workflow/run-summary.d.ts +0 -36
  188. package/dist/src/lib/workflow/run-summary.js +0 -142
@@ -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,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
  }
@@ -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,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.
@@ -282,11 +284,27 @@ export class ClaudeCodeDriver {
282
284
  * RateLimitError would wrongly re-enable the retry / MCP-fallback path. When
283
285
  * the `rate_limit_event` is itself a billing failure its richer metadata
284
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.
285
299
  */
286
300
  buildStructuredError(rateLimitInfo, assistantError, apiRetryError) {
287
301
  if (rateLimitInfo) {
288
302
  const err = createRateLimitError(rateLimitInfo);
289
- 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) {
290
308
  return new BillingError("Billing error");
291
309
  }
292
310
  return err;
@@ -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")