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
@@ -9,6 +9,7 @@ import type { ShutdownManager } from "../shutdown.js";
9
9
  import type { WorktreeInfo } from "./worktree-manager.js";
10
10
  import type { SequantError } from "../errors.js";
11
11
  import type { ErrorCategory } from "./error-classifier.js";
12
+ import type { ReadyResult } from "./ready-gate.js";
12
13
  export type { WorkflowEventEmitter, WorkflowEvents, WorkflowEventListener, IssueEventStatus, BaseEventPayload, RunEventPayload, PhaseStartedPayload, PhaseCompletedPayload, PhaseFailedPayload, IssueStatusChangedPayload, QaVerdictPayload, ProgressPayload, } from "./event-emitter.js";
13
14
  /**
14
15
  * Canonical Zod schema for all workflow phases.
@@ -131,6 +132,14 @@ export interface ExecutionConfig {
131
132
  * poll budget is preserved.
132
133
  */
133
134
  onActivity?: (text: string) => void;
135
+ /**
136
+ * Runtime callback invoked while an auto-wait (#804) is in progress, once
137
+ * per tick plus once when it ends. Used to keep the renderer and the `-q`
138
+ * heartbeat showing the wait and its wake time, so a multi-hour pause reads
139
+ * as deliberate rather than as the silent stall of #574. Not serialized —
140
+ * set per-call by the orchestrator, like {@link onActivity}.
141
+ */
142
+ onAutoWait?: (notice: AutoWaitNotice) => void;
134
143
  /**
135
144
  * Enable interactive relay (#383). When true, phase-executor sets
136
145
  * `SEQUANT_RELAY=true` in the agent environment so the PostToolUse hook
@@ -147,6 +156,75 @@ export interface ExecutionConfig {
147
156
  * QA pass does NOT skip the checks that catch the #318/#529/#570 class.
148
157
  */
149
158
  fullQa?: boolean;
159
+ /**
160
+ * Run the post-QA ready gate after the standard phases succeed (#817).
161
+ *
162
+ * When true, `runIssueWithLogging` invokes the existing `sequant ready`
163
+ * engine (`runReadyGate`) at the PR seam — before rebase/PR so the gate's
164
+ * auto-fix commits land in the PR — driving the issue to the configured
165
+ * `settings.ready.policy` threshold. It NEVER merges: the run terminates with
166
+ * the issue in `waiting_for_human_merge` (ready) or `blocked` (guard halt).
167
+ * Default off; when unset the run path is byte-identical to pre-#817.
168
+ */
169
+ readyGate?: boolean;
170
+ /**
171
+ * Total wait budget, in minutes, for auto-waiting out an exhausted
172
+ * rate-limit window (#804). `0` (the default) disables auto-wait entirely,
173
+ * preserving the #761/#799 halt behavior byte-for-byte.
174
+ *
175
+ * When a phase fails with a window-exhausted `RateLimitError` whose reset
176
+ * lies within the remaining budget, the executor sleeps until the reset and
177
+ * retries the phase instead of returning the failure. Named for the
178
+ * *behavior* (waiting), not the mechanism, so it survives a future move from
179
+ * in-process sleep to out-of-process rescheduling.
180
+ *
181
+ * This is a TOTAL budget across the issue, not a per-occurrence allowance —
182
+ * see {@link AutoWaitLedger}.
183
+ */
184
+ autoWaitMinutes?: number;
185
+ /**
186
+ * Resolved per-phase `model`/`effort` overrides (#914), keyed by phase
187
+ * name. Merged from `settings.run.phases` and the CLI's `--models`/
188
+ * `--efforts` flags via `resolvePhasePolicies` (CLI > settings > absent) —
189
+ * see `config-resolver.ts`. Absent/empty by default: `phase-executor.ts`
190
+ * only sets `AgentExecutionConfig.model`/`.effort` when a phase has an
191
+ * entry here, so an unconfigured run reaches the SDK unchanged.
192
+ */
193
+ phasePolicies?: Record<string, {
194
+ model?: string;
195
+ effort?: string;
196
+ }>;
197
+ /**
198
+ * Evidence-based effort escalation on quality-loop retries (#915). CLI >
199
+ * settings > absent (`false`), resolved by `buildExecutionConfig`
200
+ * (`config-resolver.ts`) and `ready-gate.ts`'s `buildPhaseConfig` — the same
201
+ * two producers `phasePolicies` uses, so this cannot drift from that one
202
+ * (#833 class). Consumed only at dispatch time by
203
+ * `effort-escalation.ts`'s `withEscalatedEffort`, never baked statically
204
+ * into `phasePolicies` here — escalation is per-execution, not per-run.
205
+ */
206
+ effortEscalation?: boolean;
207
+ }
208
+ /**
209
+ * A single liveness notice emitted during an auto-wait (#804 AC-7).
210
+ */
211
+ export interface AutoWaitNotice {
212
+ /** Issue the wait belongs to. */
213
+ issueNumber: number;
214
+ /** Phase whose retry is being waited for. */
215
+ phase: string;
216
+ /** Epoch ms at which the wait ends (reset + buffer). */
217
+ wakeAtMs: number;
218
+ /** Ms remaining at the time of this notice. `0` on the final notice. */
219
+ remainingMs: number;
220
+ /**
221
+ * Display string, already formatted. Built from `formatRateLimitMessage`
222
+ * plus the wake time — never by re-appending `resetsAt` to an
223
+ * already-formatted driver message (the #799 doubled-string trap).
224
+ */
225
+ message: string;
226
+ /** True on the terminal notice, once the wait has ended or been aborted. */
227
+ done: boolean;
150
228
  }
151
229
  /**
152
230
  * Default execution configuration
@@ -190,6 +268,16 @@ export interface PhaseResult {
190
268
  stdoutTail?: string[];
191
269
  /** Process exit code from the agent driver (#447) */
192
270
  exitCode?: number;
271
+ /**
272
+ * Set when this execution's effort was escalated one tier above its
273
+ * resolved base (#915) — a quality-loop retry with `effortEscalation`
274
+ * enabled. Additive/optional, same shape as `capped?`/`structuredError?`;
275
+ * absent on every non-escalated execution.
276
+ */
277
+ escalatedEffort?: {
278
+ base: string;
279
+ escalated: string;
280
+ };
193
281
  }
194
282
  /**
195
283
  * Result of executing all phases for an issue
@@ -205,6 +293,23 @@ export interface IssueResult {
205
293
  prNumber?: number;
206
294
  /** PR URL if created after successful QA */
207
295
  prUrl?: string;
296
+ /**
297
+ * Set when PR creation was attempted after passing QA but failed (#879).
298
+ * A failed `createPR` used to be a non-fatal yellow warning while the issue
299
+ * still reported success — so a run could "pass" while producing no PR. When
300
+ * present, `success` is false and the run summary counts the issue as failed
301
+ * with this string as the reason.
302
+ */
303
+ prCreationError?: string;
304
+ /**
305
+ * Set when PR creation was skipped because the branch carried zero commits
306
+ * ahead of its base (#920) — a phase-restricted run (e.g. `--phases spec`)
307
+ * that implemented nothing has no deliverable to open a PR for, and
308
+ * attempting one would only fail with GitHub's "No commits between main and
309
+ * …" error. Distinct from {@link IssueResult.prCreationError}: this is not
310
+ * a failure — `success` stays whatever the phases produced.
311
+ */
312
+ prSkippedReason?: string;
208
313
  /**
209
314
  * Set when the issue was skipped because another sequant session holds
210
315
  * the per-issue lock (#625). Surfaced in the summary as
@@ -232,6 +337,25 @@ export interface IssueResult {
232
337
  * privacy contract). Absent on success.
233
338
  */
234
339
  failureCategory?: ErrorCategory;
340
+ /**
341
+ * Outcome of the post-QA ready gate (#817), present only when the run was
342
+ * invoked with `--ready-gate` and the gate actually ran (i.e. the standard
343
+ * phases succeeded). Carries the terminal reason, the persisted issue status
344
+ * (`waiting_for_human_merge` / `blocked` — never merged), and the rendered
345
+ * report the summary and PR body surface. Absent on runs without the flag.
346
+ */
347
+ readyGate?: ReadyResult;
348
+ /**
349
+ * Why the ready gate did NOT run, when `--ready-gate` was requested but the
350
+ * gate threw (#817). Mutually exclusive with {@link IssueResult.readyGate}.
351
+ *
352
+ * A gate crash is deliberately non-fatal — the phase work is already
353
+ * committed and the PR still opens — but it must not be silent. Without this
354
+ * field a run whose gate died is byte-identical in the summary to one that
355
+ * never asked for a gate, so the user believes a second look happened when it
356
+ * did not. The summary renders this as an explicit "gate did not run" line.
357
+ */
358
+ readyGateError?: string;
235
359
  }
236
360
  /**
237
361
  * CLI options for the run command, merged with settings and env config.
@@ -263,8 +387,6 @@ export interface RunOptions {
263
387
  autoDetectPhases?: boolean;
264
388
  /** Enable automatic worktree creation for issue isolation */
265
389
  worktreeIsolation?: boolean;
266
- /** Reuse existing worktrees instead of creating new ones */
267
- reuseWorktrees?: boolean;
268
390
  /** Suppress version warnings and non-essential output */
269
391
  quiet?: boolean;
270
392
  /** Chain issues: each branches from previous (requires --sequential) */
@@ -299,6 +421,13 @@ export interface RunOptions {
299
421
  * Resolution priority: this CLI flag → settings.run.mcp → default (true)
300
422
  */
301
423
  noMcp?: boolean;
424
+ /**
425
+ * Total minutes willing to wait for an exhausted rate-limit window to reopen
426
+ * before halting (#804). `0` (default) keeps today's immediate halt.
427
+ * Resolution priority: this CLI flag → SEQUANT_AUTO_WAIT_MINUTES →
428
+ * settings.run.autoWaitMinutes → default (0).
429
+ */
430
+ autoWaitMinutes?: number;
302
431
  /**
303
432
  * Resume from last completed phase.
304
433
  * Reads phase markers from GitHub issue comments and skips completed phases.
@@ -324,7 +453,9 @@ export interface RunOptions {
324
453
  noPr?: boolean;
325
454
  /**
326
455
  * Force re-execution of issues even if they have completed status.
327
- * Bypasses the pre-flight state guard that skips ready_for_merge/merged issues.
456
+ * Bypasses the pre-flight state guard that skips completed issues — see
457
+ * `isCompletedIssueStatus` (completed-status.ts) for exactly which statuses
458
+ * that covers, and why `blocked` is not one of them.
328
459
  */
329
460
  force?: boolean;
330
461
  /**
@@ -361,6 +492,17 @@ export interface RunOptions {
361
492
  * #705: now a hidden no-op alias — the boxed Ink TUI is the default, so
362
493
  * `--experimental-tui` only parses for backward compatibility and no longer
363
494
  * gates rendering. Kept so existing scripts/muscle-memory don't break.
495
+ *
496
+ * INTENTIONALLY INERT — do not delete (#810). A dead-surface sweep of
497
+ * `RunOptions` will correctly observe that nothing branches on this field.
498
+ * That is the design, not a defect: the flag's whole job is to parse and do
499
+ * nothing, so scripts written against #705 keep working. Its inertness is
500
+ * asserted, not incidental — `run-flags.test.ts` ("--experimental-tui is a
501
+ * no-op"), `cli.integration.test.ts` (hidden from `--help`, still parses),
502
+ * and `run-tui.integration.test.ts` all pin it. Contrast `reuseWorktrees`,
503
+ * removed in #810: that field had no flag, no consumer, and no test, so it
504
+ * promised behavior nothing delivered. The distinction is a flag deliberately
505
+ * kept parseable versus a type field nobody could ever reach.
364
506
  */
365
507
  experimentalTui?: boolean;
366
508
  /**
@@ -375,6 +517,39 @@ export interface RunOptions {
375
517
  * Resolution priority: this CLI flag → settings.run.relay → default (true).
376
518
  */
377
519
  relay?: boolean;
520
+ /**
521
+ * Run the post-QA ready gate after the standard phases succeed (#817). Set via
522
+ * `--ready-gate`. When true, the run drives the issue to the configured
523
+ * `settings.ready.policy` threshold through the existing `sequant ready`
524
+ * engine before creating the PR, then STOPS at the human merge gate — it never
525
+ * merges. Off by default; opt-in only, so an unset flag leaves the run path
526
+ * (including the #749 break-to-PR behavior) unchanged. Reuses `ready`'s policy
527
+ * and bounds wholesale — no new settings accompany this flag.
528
+ */
529
+ readyGate?: boolean;
530
+ /**
531
+ * Per-phase model override (#914). Either a bare value applied to every
532
+ * phase (`--models sonnet`) or a comma list of `phase=model` pairs
533
+ * (`--models spec=fable,exec=sonnet`). Parsed and merged with
534
+ * `settings.run.phases` by `resolvePhasePolicies` (CLI > settings >
535
+ * absent) into `ExecutionConfig.phasePolicies`. Malformed specs fail fast
536
+ * at the Commander option boundary via `cli-flags.ts`.
537
+ */
538
+ models?: string;
539
+ /**
540
+ * Per-phase reasoning-effort override (#914). Same grammar as {@link
541
+ * models} (bare value or comma list of `phase=effort` pairs); each value
542
+ * validates against the SDK's closed `low|medium|high|xhigh|max` enum.
543
+ */
544
+ efforts?: string;
545
+ /**
546
+ * Evidence-based effort escalation on quality-loop retries (#915). Set via
547
+ * `--escalate-effort`. When true, a retried phase execution resolves one
548
+ * effort tier above its configured/inherited base for that execution only
549
+ * — see `effort-escalation.ts`. Default `false`: escalation raises token
550
+ * spend, so an unset flag leaves every run byte-identical to pre-#915.
551
+ */
552
+ escalateEffort?: boolean;
378
553
  }
379
554
  /**
380
555
  * CLI arguments for run command
@@ -415,12 +590,23 @@ export interface BatchResult {
415
590
  * `"activity"` (#543): sub-phase activity ping. `extra.text` carries a short
416
591
  * one-line snippet (e.g. last line of agent output) for the dashboard's
417
592
  * `nowLine`. Fires at most ~10 Hz from the phase executor.
593
+ *
594
+ * `"waiting"` (#804): an auto-wait for a rate-limit window is in progress.
595
+ * `extra.text` is the display message and `extra.wakeAtMs` the wake time;
596
+ * a `"waiting"` event WITHOUT `wakeAtMs` is the terminal notice that clears
597
+ * the waiting state. Unlike `"activity"`, this must reach both display paths
598
+ * — a multi-hour pause with no signal is the #574 complaint at 60x scale.
418
599
  */
419
- export type ProgressCallback = (issue: number, phase: string, event: "start" | "complete" | "failed" | "activity", extra?: {
600
+ export type ProgressCallback = (issue: number, phase: string, event: "start" | "complete" | "failed" | "activity" | "waiting", extra?: {
420
601
  durationSeconds?: number;
421
602
  error?: string;
422
603
  iteration?: number;
423
604
  text?: string;
605
+ /**
606
+ * `"waiting"` (#804): epoch ms at which an auto-wait ends. Absent on the
607
+ * terminal notice, which clears the waiting state.
608
+ */
609
+ wakeAtMs?: number;
424
610
  }) => void;
425
611
  /**
426
612
  * #672 AC-2: fired once per issue after the executor has resolved the final
@@ -45,4 +45,5 @@ export const DEFAULT_CONFIG = {
45
45
  dryRun: false,
46
46
  mcp: true,
47
47
  retry: true,
48
+ autoWaitMinutes: 0,
48
49
  };
@@ -104,8 +104,15 @@ export declare function checkWorktreeFreshness(worktreePath: string, verbose: bo
104
104
  export declare function removeStaleWorktree(existingPath: string, branch: string, verbose: boolean): boolean;
105
105
  /**
106
106
  * List all active worktrees with their branches
107
+ *
108
+ * `git worktree list` only ever reports worktrees belonging to the repository
109
+ * containing `cwd`, which is what makes this a repo-scoped lookup: a sibling
110
+ * project's worktree can never appear here, even though `../worktrees/` is a
111
+ * single directory shared by every repo under the same parent (#899).
112
+ *
113
+ * @param cwd - Directory to run git in. Defaults to the current process cwd.
107
114
  */
108
- export declare function listWorktrees(): Array<{
115
+ export declare function listWorktrees(cwd?: string): Array<{
109
116
  path: string;
110
117
  branch: string;
111
118
  issue: number | null;
@@ -145,6 +152,21 @@ export declare function filterResumedPhases(issueNumber: number, phases: Phase[]
145
152
  phases: Phase[];
146
153
  skipped: Phase[];
147
154
  };
155
+ /**
156
+ * Run the frozen dependency install for a freshly provisioned worktree and
157
+ * surface (rather than swallow) a failed install.
158
+ *
159
+ * Mirrors the status check in `reinstallIfLockfileChanged` (#846): a plain
160
+ * `npm install` self-heals a stale/absent lockfile, but the frozen `npm ci`
161
+ * hard-fails (exit 1, no node_modules). Without this check a failing install
162
+ * left a silently dependency-less worktree whose breakage only surfaced later
163
+ * as a confusing phase error. The warning names the resolved command so the
164
+ * user can rerun it by hand.
165
+ *
166
+ * @returns true if the install succeeded, false if it failed (warn-and-continue)
167
+ * @internal Exported for testing
168
+ */
169
+ export declare function installWorktreeDeps(worktreePath: string, packageManager: string | undefined, verbose: boolean): boolean;
148
170
  /**
149
171
  * Create or reuse a worktree for an issue
150
172
  * @param baseBranch - Optional branch to use as base instead of origin/main (for chain mode)
@@ -225,17 +247,25 @@ export declare function rebaseBeforePR(worktreePath: string, issueNumber: number
225
247
  * @param opts.qaVerdict QA verdict for the run; a non-A+ stopping state
226
248
  * (anything other than `READY_FOR_MERGE`) surfaces a note so a human reviewer
227
249
  * sees why the run broke to PR without reaching A+ (#749).
250
+ * @param opts.readyGateReport Rendered `sequant ready` gap report (#817). Set
251
+ * only when the run used `--ready-gate`; surfaces the gate outcome (threshold
252
+ * reached vs guard halt) in the PR body the same way `sequant ready` does.
228
253
  * @internal Exported for testing
229
254
  */
230
255
  export declare function buildAutomatedPRBody(issueNumber: number, opts?: {
231
256
  stackManifest?: string;
232
257
  qaVerdict?: string;
258
+ readyGateReport?: string;
233
259
  }): string;
234
260
  /**
235
261
  * Push branch and create a PR after successful QA.
236
262
  *
237
263
  * Handles both fresh PR creation and detection of existing PRs.
238
- * Failures are warnings — they don't fail the run.
264
+ *
265
+ * Returns `{ success: false, error }` on failure. As of #879 the caller
266
+ * (`runIssueWithLogging`) treats an attempted-but-failed PR as a run failure:
267
+ * a passing run that produced no PR is not a success. This function itself
268
+ * still only reports — it never throws — so relay teardown runs regardless.
239
269
  *
240
270
  * @param worktreePath Path to the worktree
241
271
  * @param issueNumber Issue number
@@ -246,10 +276,13 @@ export declare function buildAutomatedPRBody(issueNumber: number, opts?: {
246
276
  * @param stackOptions When set under --stacked, `prBase` overrides the default
247
277
  * PR target (otherwise gh defaults to the repo's default branch) and
248
278
  * `stackManifest` is appended to the PR body. (#605)
279
+ * @param qaVerdict QA verdict surfaced in the PR body when non-A+ (#749).
280
+ * @param readyGateReport Rendered `sequant ready` gap report, set only when the
281
+ * run used `--ready-gate` (#817).
249
282
  * @returns PRCreationResult with PR info or error
250
283
  * @internal Exported for testing
251
284
  */
252
285
  export declare function createPR(worktreePath: string, issueNumber: number, issueTitle: string, branch: string, verbose: boolean, labels?: string[], stackOptions?: {
253
286
  prBase?: string;
254
287
  stackManifest?: string;
255
- }, qaVerdict?: string): PRCreationResult;
288
+ }, qaVerdict?: string, readyGateReport?: string): PRCreationResult;
@@ -8,7 +8,8 @@ import chalk from "chalk";
8
8
  import { spawnSync } from "child_process";
9
9
  import { existsSync, readFileSync } from "fs";
10
10
  import path from "path";
11
- import { PM_CONFIG } from "../stacks.js";
11
+ import { resolvePackageManager, resolvePackageManagerConfig, } from "../stacks.js";
12
+ import { resolveDiffBase } from "./git-diff-utils.js";
12
13
  import { getResumablePhasesForIssue } from "./phase-detection.js";
13
14
  import { GitHubProvider } from "./platforms/github.js";
14
15
  /**
@@ -155,7 +156,11 @@ export function checkWorktreeFreshness(worktreePath, verbose, baseBranch = "main
155
156
  const countResult = spawnSync("git", ["-C", worktreePath, "rev-list", "--count", `${mergeBase}..${baseHead}`], { stdio: "pipe" });
156
157
  if (countResult.status === 0) {
157
158
  result.commitsBehind = parseInt(countResult.stdout.toString().trim(), 10);
158
- // Consider stale if more than 5 commits behind (configurable threshold)
159
+ // Consider stale if more than 5 commits behind. NOT configurable — the
160
+ // threshold is the literal below, and a stale-but-clean worktree is
161
+ // force-removed and rebuilt on the strength of it (see the recreate
162
+ // branch in `ensureWorktree`). Surfaced in #810; left as a literal here
163
+ // because making it configurable is a behavior change, not a comment fix.
159
164
  result.isStale = result.commitsBehind > 5;
160
165
  }
161
166
  }
@@ -200,10 +205,18 @@ export function removeStaleWorktree(existingPath, branch, verbose) {
200
205
  }
201
206
  /**
202
207
  * List all active worktrees with their branches
208
+ *
209
+ * `git worktree list` only ever reports worktrees belonging to the repository
210
+ * containing `cwd`, which is what makes this a repo-scoped lookup: a sibling
211
+ * project's worktree can never appear here, even though `../worktrees/` is a
212
+ * single directory shared by every repo under the same parent (#899).
213
+ *
214
+ * @param cwd - Directory to run git in. Defaults to the current process cwd.
203
215
  */
204
- export function listWorktrees() {
216
+ export function listWorktrees(cwd) {
205
217
  const result = spawnSync("git", ["worktree", "list", "--porcelain"], {
206
218
  stdio: "pipe",
219
+ ...(cwd ? { cwd } : {}),
207
220
  });
208
221
  if (result.status !== 0)
209
222
  return [];
@@ -231,7 +244,8 @@ export function listWorktrees() {
231
244
  * Get changed files in a worktree compared to main
232
245
  */
233
246
  export function getWorktreeChangedFiles(worktreePath) {
234
- const result = spawnSync("git", ["-C", worktreePath, "diff", "--name-only", "main...HEAD"], { stdio: "pipe" });
247
+ const diffBase = resolveDiffBase(worktreePath, "main");
248
+ const result = spawnSync("git", ["-C", worktreePath, "diff", "--name-only", `${diffBase}...HEAD`], { stdio: "pipe" });
235
249
  if (result.status !== 0)
236
250
  return [];
237
251
  return result.stdout
@@ -245,7 +259,8 @@ export function getWorktreeChangedFiles(worktreePath) {
245
259
  * Returns aggregate metrics only - no file paths to preserve privacy
246
260
  */
247
261
  export function getWorktreeDiffStats(worktreePath) {
248
- const result = spawnSync("git", ["-C", worktreePath, "diff", "--stat", "main...HEAD"], { stdio: "pipe" });
262
+ const diffBase = resolveDiffBase(worktreePath, "main");
263
+ const result = spawnSync("git", ["-C", worktreePath, "diff", "--stat", `${diffBase}...HEAD`], { stdio: "pipe" });
249
264
  if (result.status !== 0) {
250
265
  return { filesChanged: 0, linesAdded: 0 };
251
266
  }
@@ -314,6 +329,63 @@ export function filterResumedPhases(issueNumber, phases, resume) {
314
329
  const skipped = phases.filter((p) => !resumable.includes(p));
315
330
  return { phases: resumable, skipped };
316
331
  }
332
+ /**
333
+ * Run the frozen dependency install for a freshly provisioned worktree and
334
+ * surface (rather than swallow) a failed install.
335
+ *
336
+ * Mirrors the status check in `reinstallIfLockfileChanged` (#846): a plain
337
+ * `npm install` self-heals a stale/absent lockfile, but the frozen `npm ci`
338
+ * hard-fails (exit 1, no node_modules). Without this check a failing install
339
+ * left a silently dependency-less worktree whose breakage only surfaced later
340
+ * as a confusing phase error. The warning names the resolved command so the
341
+ * user can rerun it by hand.
342
+ *
343
+ * @returns true if the install succeeded, false if it failed (warn-and-continue)
344
+ * @internal Exported for testing
345
+ */
346
+ export function installWorktreeDeps(worktreePath, packageManager, verbose) {
347
+ if (verbose) {
348
+ console.log(chalk.gray(` Installing dependencies...`));
349
+ }
350
+ // Two independent questions, resolved in order against the worktree itself.
351
+ //
352
+ // WHICH manager: the manifest's packageManager is a snapshot and may be
353
+ // absent, so fall back to the worktree's own lockfile — that keeps this in
354
+ // agreement with what new-feature.sh would run for the same project (#870).
355
+ //
356
+ // WHICH commands for it: yarn's frozen install differs between classic
357
+ // (`--frozen-lockfile`) and berry (`--immutable`), and both majors use
358
+ // `yarn.lock`, so the manager's identity alone cannot say which (#871).
359
+ //
360
+ // The worktree is checked out by now, so its package.json / .yarnrc.yml /
361
+ // yarn.lock are all readable here.
362
+ const pmConfig = resolvePackageManagerConfig(resolvePackageManager(packageManager, worktreePath), worktreePath);
363
+ // ciInstall, not installSilent: a plain `npm install` normalizes and
364
+ // rewrites package-lock.json (observed: npm 10 strips the `libc` fields a
365
+ // newer npm committed), so every provisioned worktree started dirty. That
366
+ // one unstaged file cascaded: rebaseBeforePR refused to run, stale
367
+ // worktrees read as having "uncommitted changes" and were never recreated,
368
+ // and chain checkpoints skipped on an out-of-scope dirty file. A frozen
369
+ // install never touches the lockfile. Same substitution #803 made for
370
+ // merge-check's combined-branch test. The `!existsSync(node_modules)`
371
+ // guard at the call site means `npm ci`'s wipe-and-reinstall has nothing
372
+ // to wipe.
373
+ const [cmd, ...args] = pmConfig.ciInstall.split(" ");
374
+ const command = [cmd, ...args].join(" ");
375
+ const installResult = spawnSync(cmd, args, {
376
+ cwd: worktreePath,
377
+ stdio: "pipe",
378
+ });
379
+ if (installResult.status !== 0) {
380
+ // stderr may be empty on some failures — the resolved command alone is
381
+ // enough to act on, so always name it (AC-2).
382
+ const error = installResult.stderr?.toString().trim() ?? "";
383
+ const detail = error ? `: ${error}` : "";
384
+ console.log(chalk.yellow(` ! Dependency install failed (${command})${detail}`));
385
+ return false;
386
+ }
387
+ return true;
388
+ }
317
389
  /**
318
390
  * Create or reuse a worktree for an issue
319
391
  * @param baseBranch - Optional branch to use as base instead of origin/main (for chain mode)
@@ -513,17 +585,7 @@ export async function ensureWorktree(issueNumber, title, verbose, packageManager
513
585
  // Install dependencies if needed
514
586
  const nodeModulesPath = path.join(worktreePath, "node_modules");
515
587
  if (!existsSync(nodeModulesPath)) {
516
- if (verbose) {
517
- console.log(chalk.gray(` Installing dependencies...`));
518
- }
519
- // Use detected package manager or default to npm
520
- const pm = packageManager || "npm";
521
- const pmConfig = PM_CONFIG[pm];
522
- const [cmd, ...args] = pmConfig.installSilent.split(" ");
523
- spawnSync(cmd, args, {
524
- cwd: worktreePath,
525
- stdio: "pipe",
526
- });
588
+ installWorktreeDeps(worktreePath, packageManager, verbose);
527
589
  }
528
590
  if (verbose) {
529
591
  console.log(chalk.green(` ✔ Worktree ready: ${worktreePath}`));
@@ -742,9 +804,14 @@ export function reinstallIfLockfileChanged(worktreePath, packageManager, verbose
742
804
  }
743
805
  // Re-run install to sync node_modules with updated lockfile
744
806
  console.log(chalk.blue(` Reinstalling dependencies (lockfile changed)...`));
745
- const pm = packageManager || "npm";
746
- const pmConfig = PM_CONFIG[pm];
747
- const [cmd, ...args] = pmConfig.installSilent.split(" ");
807
+ // Same two-step resolution as installWorktreeDeps: manifest-or-lockfile for
808
+ // the manager (#870), then classic-vs-berry for its commands (#871).
809
+ const pmConfig = resolvePackageManagerConfig(resolvePackageManager(packageManager, worktreePath), worktreePath);
810
+ // ciInstall for the same reason as provisioning (see ensureWorktree): this
811
+ // reinstall exists because a rebase pulled in a NEW lockfile, so installing
812
+ // exactly what that lockfile says — never rewriting it — is the semantic
813
+ // the function's own name promises.
814
+ const [cmd, ...args] = pmConfig.ciInstall.split(" ");
748
815
  const installResult = spawnSync(cmd, args, {
749
816
  cwd: worktreePath,
750
817
  stdio: "pipe",
@@ -888,6 +955,9 @@ function qaVerdictNote(verdict) {
888
955
  * @param opts.qaVerdict QA verdict for the run; a non-A+ stopping state
889
956
  * (anything other than `READY_FOR_MERGE`) surfaces a note so a human reviewer
890
957
  * sees why the run broke to PR without reaching A+ (#749).
958
+ * @param opts.readyGateReport Rendered `sequant ready` gap report (#817). Set
959
+ * only when the run used `--ready-gate`; surfaces the gate outcome (threshold
960
+ * reached vs guard halt) in the PR body the same way `sequant ready` does.
891
961
  * @internal Exported for testing
892
962
  */
893
963
  export function buildAutomatedPRBody(issueNumber, opts) {
@@ -901,9 +971,18 @@ export function buildAutomatedPRBody(issueNumber, opts) {
901
971
  ];
902
972
  // #749: surface a non-A+ QA verdict in the PR body (not just the run log) so
903
973
  // a reviewer sees why the run broke to PR rather than reaching A+.
904
- const note = qaVerdictNote(opts?.qaVerdict);
905
- if (note) {
906
- bodyLines.push(note, ``);
974
+ //
975
+ // #817: when the ready gate ran, its report already carries the terminal
976
+ // verdict and stop reason (a superset of the #749 note), so prefer it and
977
+ // suppress the standalone note to avoid a contradictory double-headline.
978
+ if (opts?.readyGateReport) {
979
+ bodyLines.push(opts.readyGateReport, ``);
980
+ }
981
+ else {
982
+ const note = qaVerdictNote(opts?.qaVerdict);
983
+ if (note) {
984
+ bodyLines.push(note, ``);
985
+ }
907
986
  }
908
987
  // #605 AC-4: emit stack manifest before the trailer so reviewers see the
909
988
  // chain at the top of the body. Manifest is only set under --stacked.
@@ -917,7 +996,11 @@ export function buildAutomatedPRBody(issueNumber, opts) {
917
996
  * Push branch and create a PR after successful QA.
918
997
  *
919
998
  * Handles both fresh PR creation and detection of existing PRs.
920
- * Failures are warnings — they don't fail the run.
999
+ *
1000
+ * Returns `{ success: false, error }` on failure. As of #879 the caller
1001
+ * (`runIssueWithLogging`) treats an attempted-but-failed PR as a run failure:
1002
+ * a passing run that produced no PR is not a success. This function itself
1003
+ * still only reports — it never throws — so relay teardown runs regardless.
921
1004
  *
922
1005
  * @param worktreePath Path to the worktree
923
1006
  * @param issueNumber Issue number
@@ -928,10 +1011,13 @@ export function buildAutomatedPRBody(issueNumber, opts) {
928
1011
  * @param stackOptions When set under --stacked, `prBase` overrides the default
929
1012
  * PR target (otherwise gh defaults to the repo's default branch) and
930
1013
  * `stackManifest` is appended to the PR body. (#605)
1014
+ * @param qaVerdict QA verdict surfaced in the PR body when non-A+ (#749).
1015
+ * @param readyGateReport Rendered `sequant ready` gap report, set only when the
1016
+ * run used `--ready-gate` (#817).
931
1017
  * @returns PRCreationResult with PR info or error
932
1018
  * @internal Exported for testing
933
1019
  */
934
- export function createPR(worktreePath, issueNumber, issueTitle, branch, verbose, labels, stackOptions, qaVerdict) {
1020
+ export function createPR(worktreePath, issueNumber, issueTitle, branch, verbose, labels, stackOptions, qaVerdict, readyGateReport) {
935
1021
  const github = new GitHubProvider();
936
1022
  // Step 1: Check for existing PR on this branch
937
1023
  const existingPRInfo = github.viewPRByBranchSync(branch, worktreePath);
@@ -970,6 +1056,7 @@ export function createPR(worktreePath, issueNumber, issueTitle, branch, verbose,
970
1056
  const prBody = buildAutomatedPRBody(issueNumber, {
971
1057
  stackManifest: stackOptions?.stackManifest,
972
1058
  qaVerdict,
1059
+ readyGateReport,
973
1060
  });
974
1061
  const prResult = github.createPRCliSync(prTitle, prBody, branch, worktreePath, stackOptions?.prBase);
975
1062
  if (prResult.exitCode !== 0) {
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Repo-scoped worktree resolution and verification (#899).
3
+ *
4
+ * Skill-driven `/fullsolve` used to hand `/exec` an unexpanded glob
5
+ * (`../worktrees/feature/<issue>-` followed by a star and a slash) and `/exec`
6
+ * was instructed to trust it without checking. Two things went wrong:
7
+ *
8
+ * 1. When nothing had created the worktree, the `cd` failed and the agent
9
+ * silently kept working in the main checkout.
10
+ * 2. `../worktrees/` is one flat namespace shared by every repo under the same
11
+ * parent directory, and issue numbers are per-repo — so where the glob *did*
12
+ * match, it could match a sibling project's worktree.
13
+ *
14
+ * Both are fixed the same way: never resolve worktrees through the filesystem.
15
+ * `git worktree list` reports only the worktrees of the repository containing
16
+ * `cwd`, so scoping is structural rather than a filter that can be forgotten,
17
+ * and it reports the *branch*, which is the real identity — a worktree
18
+ * directory slug can drift from its branch after a rename.
19
+ */
20
+ /** Named failure modes for `resolveIssueWorktree`. */
21
+ export type ResolveErrorCode = "WORKTREE_NOT_FOUND" | "WORKTREE_AMBIGUOUS";
22
+ /** Named failure modes for `verifyWorktreePath`. */
23
+ export type VerifyErrorCode = "SEQUANT_WORKTREE_NOT_FOUND" | "SEQUANT_WORKTREE_FOREIGN" | "SEQUANT_WORKTREE_ISSUE_MISMATCH";
24
+ /** A worktree of the current repository, as reported by git. */
25
+ export interface ResolvedWorktree {
26
+ /** Absolute path, exactly as git reports it. */
27
+ path: string;
28
+ /** Branch checked out there, e.g. `feature/899-some-slug`. */
29
+ branch: string;
30
+ }
31
+ export type ResolveResult = ({
32
+ ok: true;
33
+ } & ResolvedWorktree) | {
34
+ ok: false;
35
+ error: ResolveErrorCode;
36
+ message: string;
37
+ /** Populated for WORKTREE_AMBIGUOUS so the caller can report candidates. */
38
+ candidates: ResolvedWorktree[];
39
+ };
40
+ export type VerifyResult = ({
41
+ ok: true;
42
+ } & ResolvedWorktree) | {
43
+ ok: false;
44
+ error: VerifyErrorCode;
45
+ message: string;
46
+ };
47
+ /**
48
+ * Find the worktree of the *current* repository that holds issue `issue`.
49
+ *
50
+ * Selection keys on the branch git reports, never on the directory name, so a
51
+ * worktree whose slug has drifted from its branch still resolves — and a
52
+ * directory that merely looks like a match (a sibling repo's worktree, or a
53
+ * stray directory) never does.
54
+ *
55
+ * @param issue - Issue number to look up.
56
+ * @param cwd - Directory inside the repository to search. Defaults to process cwd.
57
+ */
58
+ export declare function resolveIssueWorktree(issue: number, cwd?: string): ResolveResult;
59
+ /**
60
+ * Verify that a caller-supplied worktree path is safe to work in.
61
+ *
62
+ * This is the guard `/exec` runs before trusting `SEQUANT_WORKTREE`. It fails
63
+ * closed on every uncertain case — a bad path must halt the run, never degrade
64
+ * into "keep going in whatever directory we happen to be in".
65
+ *
66
+ * @param worktreePath - The path to check (typically `$SEQUANT_WORKTREE`).
67
+ * @param options.issue - When set, the branch must belong to this issue.
68
+ * @param options.cwd - Directory inside the repository to check against.
69
+ */
70
+ export declare function verifyWorktreePath(worktreePath: string, options?: {
71
+ issue?: number;
72
+ cwd?: string;
73
+ }): VerifyResult;