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
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Structured resolution of the spec→run phase recommendation (#921).
3
+ *
4
+ * `parseRecommendedWorkflow` (phase-mapper.ts) regexes the spec agent's
5
+ * *ephemeral chat text* for a `## Recommended Workflow` section. When the
6
+ * spec agent posts its plan via a body file instead of restating it in
7
+ * chat, that regex has nothing to match and the run silently falls back to
8
+ * label-based phase detection — dropping any recommended phase the label
9
+ * fallback can never produce (e.g. `testgen`). See #814.
10
+ *
11
+ * This module resolves the recommendation through an ordered chain, each
12
+ * step falling through loudly to the next on failure:
13
+ *
14
+ * 1. `marker` — the durable `<!-- SEQUANT_SPEC: {json} --> `
15
+ * comment marker (this issue's fix)
16
+ * 2. `comment-prose` — the same regex as `chat`, applied to the spec
17
+ * plan's GitHub comment body instead of chat text
18
+ * 3. `chat` — `parseRecommendedWorkflow` over the agent's
19
+ * captured chat output (existing behavior)
20
+ * 4. `label-fallback` — `detectPhasesFromLabels` (existing behavior)
21
+ *
22
+ * The durable comment marker is the system's existing idiom — see
23
+ * `SEQUANT_PHASE` in `phase-detection.ts` and the `/assess` HTML markers in
24
+ * `assess-comment-parser.ts`.
25
+ */
26
+ import chalk from "chalk";
27
+ import { z } from "zod";
28
+ import { phaseRegistry } from "./phase-registry.js";
29
+ import { GitHubProvider } from "./platforms/github.js";
30
+ import { stripMarkdownCode } from "./phase-detection.js";
31
+ import { parseRecommendedWorkflow, detectPhasesFromLabels, } from "./phase-mapper.js";
32
+ /** Regex to extract the SEQUANT_SPEC marker JSON from an HTML comment. */
33
+ const SPEC_MARKER_REGEX = /<!-- SEQUANT_SPEC: (\{[^}]+\}) -->/g;
34
+ /**
35
+ * Structural shape of the marker JSON, before phase names are checked
36
+ * against the phase registry. `qualityLoop` defaults to `false` when
37
+ * omitted, matching `parseRecommendedWorkflow`'s prose-parsing default.
38
+ */
39
+ const SpecMarkerJsonSchema = z.object({
40
+ phases: z.array(z.string()).min(1),
41
+ qualityLoop: z.boolean().optional().default(false),
42
+ });
43
+ /**
44
+ * Extract and validate the latest `SEQUANT_SPEC` marker across a set of
45
+ * comment bodies (oldest-to-newest order, matching `gh`'s natural order).
46
+ *
47
+ * Returns `null` when no marker is present at all. Returns `null` and logs a
48
+ * visible warning when the latest marker is malformed JSON, fails schema
49
+ * validation, or names a phase the registry doesn't recognize — per AC-2,
50
+ * an invalid marker falls through to the next resolution step rather than
51
+ * silently dropping the recommendation or silently ignoring the bad phase.
52
+ *
53
+ * Only the *latest* marker is considered (last comment, last match within
54
+ * it) — a re-spec's marker supersedes an earlier one, matching how a human
55
+ * reader would interpret the thread.
56
+ */
57
+ export function parseSpecMarker(commentBodies) {
58
+ let latestRaw = null;
59
+ for (const body of commentBodies) {
60
+ const stripped = stripMarkdownCode(body);
61
+ SPEC_MARKER_REGEX.lastIndex = 0;
62
+ let match;
63
+ while ((match = SPEC_MARKER_REGEX.exec(stripped)) !== null) {
64
+ latestRaw = match[1];
65
+ }
66
+ }
67
+ if (latestRaw === null) {
68
+ return null;
69
+ }
70
+ let parsedJson;
71
+ try {
72
+ parsedJson = JSON.parse(latestRaw);
73
+ }
74
+ catch {
75
+ console.log(chalk.yellow(` ⚠ SEQUANT_SPEC marker is not valid JSON — falling through to comment prose`));
76
+ return null;
77
+ }
78
+ const result = SpecMarkerJsonSchema.safeParse(parsedJson);
79
+ if (!result.success) {
80
+ console.log(chalk.yellow(` ⚠ SEQUANT_SPEC marker failed schema validation — falling through to comment prose`));
81
+ return null;
82
+ }
83
+ const unknownPhases = result.data.phases.filter((name) => !phaseRegistry.has(name));
84
+ if (unknownPhases.length > 0) {
85
+ console.log(chalk.yellow(` ⚠ SEQUANT_SPEC marker names unknown phase(s): ${unknownPhases.join(", ")} — falling through to comment prose`));
86
+ return null;
87
+ }
88
+ return { phases: result.data.phases, qualityLoop: result.data.qualityLoop };
89
+ }
90
+ /**
91
+ * Find the most recently posted comment containing a `## Recommended
92
+ * Workflow` section and parse it with the existing prose regex.
93
+ *
94
+ * Scans newest-to-oldest so a later spec re-run's comment takes precedence
95
+ * over an earlier one — same intuition as the marker step.
96
+ */
97
+ function parseCommentProse(commentBodies) {
98
+ for (let i = commentBodies.length - 1; i >= 0; i--) {
99
+ const parsed = parseRecommendedWorkflow(commentBodies[i]);
100
+ if (parsed) {
101
+ return parsed;
102
+ }
103
+ }
104
+ return null;
105
+ }
106
+ /**
107
+ * Resolve the spec→run phase recommendation through the ordered chain:
108
+ * comment-marker → comment-prose → chat-text → label-fallback.
109
+ */
110
+ export function resolveSpecRecommendation(input) {
111
+ const github = input.githubProvider ?? new GitHubProvider();
112
+ const commentBodies = github.fetchIssueCommentBodiesSync(String(input.issueNumber));
113
+ let phases;
114
+ let qualityLoop;
115
+ let source;
116
+ const marker = parseSpecMarker(commentBodies);
117
+ const prose = marker ? null : parseCommentProse(commentBodies);
118
+ const chat = marker || prose || !input.chatOutput
119
+ ? null
120
+ : parseRecommendedWorkflow(input.chatOutput);
121
+ if (marker) {
122
+ ({ phases, qualityLoop } = marker);
123
+ source = "marker";
124
+ }
125
+ else if (prose) {
126
+ ({ phases, qualityLoop } = prose);
127
+ source = "comment-prose";
128
+ }
129
+ else if (chat) {
130
+ ({ phases, qualityLoop } = chat);
131
+ source = "chat";
132
+ }
133
+ else {
134
+ const fallback = detectPhasesFromLabels(input.labels);
135
+ phases = fallback.phases;
136
+ qualityLoop = fallback.qualityLoop;
137
+ source = "label-fallback";
138
+ }
139
+ // Spec already ran by the time this resolves — never re-include it,
140
+ // regardless of which step in the chain produced the result.
141
+ return { phases: phases.filter((p) => p !== "spec"), qualityLoop, source };
142
+ }
@@ -221,13 +221,20 @@ export async function reconcileStateAtStartup(options = {}) {
221
221
  const state = await manager.getState();
222
222
  const advanced = [];
223
223
  const stillPending = [];
224
- // Find issues in ready_for_merge, in_progress, or waiting_for_qa_gate state.
224
+ // Find issues whose PR may have merged since their status was written.
225
225
  // in_progress covers PRs merged outside this session (#592).
226
226
  // waiting_for_qa_gate covers PRs merged before the next QA-gate run (#606).
227
+ // waiting_for_human_merge covers #817's `--ready-gate` terminal: a gated
228
+ // issue never reaches ready_for_merge, so without it a gated issue whose PR
229
+ // a human then merged stayed here forever and never advanced to merged
230
+ // (#837). Note this list is deliberately WIDER than `isCompletedIssueStatus`
231
+ // — it asks "might this have a merged PR?", not "is this done?", which is
232
+ // why in_progress belongs here but not there.
227
233
  for (const [issueNumStr, issueState] of Object.entries(state.issues)) {
228
234
  if (issueState.status !== "ready_for_merge" &&
229
235
  issueState.status !== "in_progress" &&
230
- issueState.status !== "waiting_for_qa_gate") {
236
+ issueState.status !== "waiting_for_qa_gate" &&
237
+ issueState.status !== "waiting_for_human_merge") {
231
238
  continue;
232
239
  }
233
240
  const issueNum = parseInt(issueNumStr, 10);
@@ -126,6 +126,39 @@ export declare class StateManager {
126
126
  * Update PR information for an issue
127
127
  */
128
128
  updatePRInfo(issueNumber: number, pr: PRInfo): Promise<void>;
129
+ /**
130
+ * Record or clear an in-progress auto-wait (#860). `wakeAtMs` sets the
131
+ * pause marker (and which phase is paused); `null` clears it on wake.
132
+ *
133
+ * Unlike the sibling updaters this NEVER throws on an untracked issue —
134
+ * it is called from a liveness hook during a live wait, and a bookkeeping
135
+ * miss must not interrupt the pause it is describing.
136
+ */
137
+ updateAutoWait(issueNumber: number, phase: string, wakeAtMs: number | null): Promise<void>;
138
+ /**
139
+ * Record a durable waitable-window halt (#892 AC-1). Preserves an existing
140
+ * re-entry counter so repeated halts on a still-closed window keep counting
141
+ * toward the re-entry bound instead of resetting it.
142
+ *
143
+ * Like `updateAutoWait`, NEVER throws on an untracked issue — it runs on a
144
+ * halt path where bookkeeping must not mask the real failure.
145
+ */
146
+ updateWindowHalt(issueNumber: number, phase: string, resumeAtMs: number): Promise<void>;
147
+ /**
148
+ * Clear a stale window-halt record (#892) — on any phase success (the
149
+ * window demonstrably reopened; progress also resets the re-entry counter)
150
+ * or a non-window failure (the halt cause is no longer waitable, so an
151
+ * unattended re-entry must not fire on it). No-ops (no state write) when
152
+ * nothing is recorded; never throws on an untracked issue.
153
+ */
154
+ clearWindowHalt(issueNumber: number): Promise<void>;
155
+ /**
156
+ * Consume one re-entry attempt for a halted issue (#892 AC-3). Returns the
157
+ * new count, or `null` when the issue has no window-halt record (nothing to
158
+ * resume). Called by `sequant resume` immediately before re-entry so a
159
+ * re-entry that halts again (window still closed) is already counted.
160
+ */
161
+ incrementWindowHaltReentries(issueNumber: number): Promise<number | null>;
129
162
  /**
130
163
  * Update worktree information for an issue
131
164
  */
@@ -364,6 +364,89 @@ export class StateManager {
364
364
  console.log(`State: PR #${pr.number} linked to issue #${issueNumber}`);
365
365
  }
366
366
  }
367
+ /**
368
+ * Record or clear an in-progress auto-wait (#860). `wakeAtMs` sets the
369
+ * pause marker (and which phase is paused); `null` clears it on wake.
370
+ *
371
+ * Unlike the sibling updaters this NEVER throws on an untracked issue —
372
+ * it is called from a liveness hook during a live wait, and a bookkeeping
373
+ * miss must not interrupt the pause it is describing.
374
+ */
375
+ async updateAutoWait(issueNumber, phase, wakeAtMs) {
376
+ await this.withLock(async () => {
377
+ const state = await this.getState();
378
+ const issueState = state.issues[String(issueNumber)];
379
+ if (!issueState)
380
+ return;
381
+ issueState.autoWait =
382
+ wakeAtMs === null
383
+ ? undefined
384
+ : { wakeAt: new Date(wakeAtMs).toISOString(), phase };
385
+ issueState.lastActivity = new Date().toISOString();
386
+ await this.saveState(state);
387
+ });
388
+ }
389
+ /**
390
+ * Record a durable waitable-window halt (#892 AC-1). Preserves an existing
391
+ * re-entry counter so repeated halts on a still-closed window keep counting
392
+ * toward the re-entry bound instead of resetting it.
393
+ *
394
+ * Like `updateAutoWait`, NEVER throws on an untracked issue — it runs on a
395
+ * halt path where bookkeeping must not mask the real failure.
396
+ */
397
+ async updateWindowHalt(issueNumber, phase, resumeAtMs) {
398
+ await this.withLock(async () => {
399
+ const state = await this.getState();
400
+ const issueState = state.issues[String(issueNumber)];
401
+ if (!issueState)
402
+ return;
403
+ issueState.windowHalt = {
404
+ resumeAt: new Date(resumeAtMs).toISOString(),
405
+ phase,
406
+ reentries: issueState.windowHalt?.reentries ?? 0,
407
+ };
408
+ issueState.lastActivity = new Date().toISOString();
409
+ await this.saveState(state);
410
+ });
411
+ }
412
+ /**
413
+ * Clear a stale window-halt record (#892) — on any phase success (the
414
+ * window demonstrably reopened; progress also resets the re-entry counter)
415
+ * or a non-window failure (the halt cause is no longer waitable, so an
416
+ * unattended re-entry must not fire on it). No-ops (no state write) when
417
+ * nothing is recorded; never throws on an untracked issue.
418
+ */
419
+ async clearWindowHalt(issueNumber) {
420
+ await this.withLock(async () => {
421
+ const state = await this.getState();
422
+ const issueState = state.issues[String(issueNumber)];
423
+ if (!issueState?.windowHalt)
424
+ return;
425
+ issueState.windowHalt = undefined;
426
+ issueState.lastActivity = new Date().toISOString();
427
+ await this.saveState(state);
428
+ });
429
+ }
430
+ /**
431
+ * Consume one re-entry attempt for a halted issue (#892 AC-3). Returns the
432
+ * new count, or `null` when the issue has no window-halt record (nothing to
433
+ * resume). Called by `sequant resume` immediately before re-entry so a
434
+ * re-entry that halts again (window still closed) is already counted.
435
+ */
436
+ async incrementWindowHaltReentries(issueNumber) {
437
+ let newCount = null;
438
+ await this.withLock(async () => {
439
+ const state = await this.getState();
440
+ const issueState = state.issues[String(issueNumber)];
441
+ if (!issueState?.windowHalt)
442
+ return;
443
+ issueState.windowHalt.reentries += 1;
444
+ newCount = issueState.windowHalt.reentries;
445
+ issueState.lastActivity = new Date().toISOString();
446
+ await this.saveState(state);
447
+ });
448
+ return newCount;
449
+ }
367
450
  /**
368
451
  * Update worktree information for an issue
369
452
  */
@@ -213,6 +213,14 @@ export declare const RelayStateSchema: z.ZodObject<{
213
213
  messageCount: z.ZodNumber;
214
214
  }, z.core.$strip>;
215
215
  export type RelayState = z.infer<typeof RelayStateSchema>;
216
+ /**
217
+ * Maximum scheduler re-entries per window-halted issue (#892 AC-3). Mirrors
218
+ * `AUTO_WAIT_MAX_WAITS` (#804): both bound how often sequant returns to the
219
+ * same closed rate-limit window before declaring the halt terminal. Lives
220
+ * here — beside the `windowHalt.reentries` field it bounds — so display
221
+ * surfaces (`sequant status`) can import it without pulling in the run path.
222
+ */
223
+ export declare const MAX_RESUME_REENTRIES = 2;
216
224
  /**
217
225
  * Complete state for a single issue
218
226
  */
@@ -342,6 +350,15 @@ export declare const IssueStateSchema: z.ZodObject<{
342
350
  token: z.ZodString;
343
351
  originCwd: z.ZodString;
344
352
  }, z.core.$strip>>;
353
+ autoWait: z.ZodOptional<z.ZodObject<{
354
+ wakeAt: z.ZodString;
355
+ phase: z.ZodString;
356
+ }, z.core.$strip>>;
357
+ windowHalt: z.ZodOptional<z.ZodObject<{
358
+ resumeAt: z.ZodString;
359
+ phase: z.ZodString;
360
+ reentries: z.ZodNumber;
361
+ }, z.core.$strip>>;
345
362
  resolvedAt: z.ZodOptional<z.ZodString>;
346
363
  lastActivity: z.ZodString;
347
364
  createdAt: z.ZodString;
@@ -482,6 +499,15 @@ export declare const WorkflowStateSchema: z.ZodObject<{
482
499
  token: z.ZodString;
483
500
  originCwd: z.ZodString;
484
501
  }, z.core.$strip>>;
502
+ autoWait: z.ZodOptional<z.ZodObject<{
503
+ wakeAt: z.ZodString;
504
+ phase: z.ZodString;
505
+ }, z.core.$strip>>;
506
+ windowHalt: z.ZodOptional<z.ZodObject<{
507
+ resumeAt: z.ZodString;
508
+ phase: z.ZodString;
509
+ reentries: z.ZodNumber;
510
+ }, z.core.$strip>>;
485
511
  resolvedAt: z.ZodOptional<z.ZodString>;
486
512
  lastActivity: z.ZodString;
487
513
  createdAt: z.ZodString;
@@ -197,6 +197,14 @@ export const RelayStateSchema = z.object({
197
197
  startedAt: z.string().datetime(),
198
198
  messageCount: z.number().int().nonnegative(),
199
199
  });
200
+ /**
201
+ * Maximum scheduler re-entries per window-halted issue (#892 AC-3). Mirrors
202
+ * `AUTO_WAIT_MAX_WAITS` (#804): both bound how often sequant returns to the
203
+ * same closed rate-limit window before declaring the halt terminal. Lives
204
+ * here — beside the `windowHalt.reentries` field it bounds — so display
205
+ * surfaces (`sequant status`) can import it without pulling in the run path.
206
+ */
207
+ export const MAX_RESUME_REENTRIES = 2;
200
208
  /**
201
209
  * Complete state for a single issue
202
210
  */
@@ -248,6 +256,45 @@ export const IssueStateSchema = z.object({
248
256
  originCwd: z.string(),
249
257
  })
250
258
  .optional(),
259
+ /**
260
+ * An in-progress auto-wait (#860): the phase is deliberately paused until a
261
+ * rate-limit window reopens at `wakeAt`. Written at wait start and cleared
262
+ * on wake, so `sequant status` can say "waiting until 07:01" instead of
263
+ * showing an hours-stale in-progress phase indistinguishable from a hang
264
+ * (#856). The write itself also refreshes `state.json`'s mtime — the
265
+ * activity proxy several liveness surfaces poll.
266
+ */
267
+ autoWait: z
268
+ .object({
269
+ /** ISO timestamp of the scheduled wake (resetsAt + buffer). */
270
+ wakeAt: z.string().datetime(),
271
+ /** Phase that is paused. */
272
+ phase: z.string(),
273
+ })
274
+ .optional(),
275
+ /**
276
+ * A durable halt on an exhausted rate-limit window (#892): the run exited
277
+ * cleanly (lock released) and can be re-entered by `sequant resume` once
278
+ * the window reopens at `resumeAt`. Unlike `autoWait` (a live in-process
279
+ * pause), this record survives reboots — it is the machine-readable
280
+ * contract between the halt and an unattended scheduler re-entry.
281
+ * Written at a waitable-window halt, cleared on any phase success or a
282
+ * non-window failure (the halt cause is then no longer waitable).
283
+ */
284
+ windowHalt: z
285
+ .object({
286
+ /** ISO timestamp after which re-entry can proceed (resetsAt + buffer). */
287
+ resumeAt: z.string().datetime(),
288
+ /** Phase that halted. */
289
+ phase: z.string(),
290
+ /**
291
+ * Re-entry attempts consumed (#892 AC-3). Incremented by `sequant
292
+ * resume` before each re-entry; a window that never reopens is bounded
293
+ * by MAX_RESUME_REENTRIES instead of ping-ponging a scheduler.
294
+ */
295
+ reentries: z.number().int().min(0),
296
+ })
297
+ .optional(),
251
298
  /** When the issue transitioned to a terminal status (merged/abandoned/closed) */
252
299
  resolvedAt: z.string().datetime().optional(),
253
300
  /** Most recent activity timestamp */