sequant 2.8.0 → 2.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +23 -4
  4. package/dist/bin/cli.js +99 -18
  5. package/dist/marketplace/external_plugins/sequant/.claude-plugin/plugin.json +1 -1
  6. package/dist/marketplace/external_plugins/sequant/.mcp.json +1 -1
  7. package/dist/marketplace/external_plugins/sequant/README.md +58 -2
  8. package/dist/marketplace/external_plugins/sequant/hooks/parallel-marker.sh +50 -0
  9. package/dist/marketplace/external_plugins/sequant/hooks/post-tool.sh +38 -10
  10. package/dist/marketplace/external_plugins/sequant/hooks/pre-tool.sh +405 -67
  11. package/dist/marketplace/external_plugins/sequant/skills/_shared/references/trust-model.md +18 -0
  12. package/dist/marketplace/external_plugins/sequant/skills/assess/SKILL.md +250 -344
  13. package/dist/marketplace/external_plugins/sequant/skills/assess/references/predicted-collision-detection.md +10 -7
  14. package/dist/marketplace/external_plugins/sequant/skills/docs/SKILL.md +4 -4
  15. package/dist/marketplace/external_plugins/sequant/skills/exec/SKILL.md +26 -6
  16. package/dist/marketplace/external_plugins/sequant/skills/fullsolve/SKILL.md +4 -2
  17. package/dist/marketplace/external_plugins/sequant/skills/loop/SKILL.md +38 -2
  18. package/dist/marketplace/external_plugins/sequant/skills/merger/SKILL.md +2 -2
  19. package/dist/marketplace/external_plugins/sequant/skills/qa/SKILL.md +337 -57
  20. package/dist/marketplace/external_plugins/sequant/skills/qa/references/anti-pattern-detection.md +6 -6
  21. package/dist/marketplace/external_plugins/sequant/skills/qa/references/call-site-review.md +2 -2
  22. package/dist/marketplace/external_plugins/sequant/skills/qa/references/code-review-checklist.md +2 -2
  23. package/dist/marketplace/external_plugins/sequant/skills/qa/references/fixtures/injection-issue-body.md +36 -0
  24. package/dist/marketplace/external_plugins/sequant/skills/qa/references/quality-gates.md +21 -7
  25. package/dist/marketplace/external_plugins/sequant/skills/qa/references/testing-requirements.md +1 -1
  26. package/dist/marketplace/external_plugins/sequant/skills/qa/scripts/quality-checks.sh +11 -11
  27. package/dist/marketplace/external_plugins/sequant/skills/reflect/SKILL.md +27 -13
  28. package/dist/marketplace/external_plugins/sequant/skills/reflect/references/documentation-tiers.md +80 -68
  29. package/dist/marketplace/external_plugins/sequant/skills/reflect/references/phase-reflection.md +31 -15
  30. package/dist/marketplace/external_plugins/sequant/skills/release/SKILL.md +11 -3
  31. package/dist/marketplace/external_plugins/sequant/skills/setup/SKILL.md +5 -5
  32. package/dist/marketplace/external_plugins/sequant/skills/spec/SKILL.md +2 -0
  33. package/dist/marketplace/external_plugins/sequant/skills/spec/references/verification-criteria.md +1 -1
  34. package/dist/marketplace/external_plugins/sequant/skills/test/SKILL.md +1 -1
  35. package/dist/src/commands/assess-render.d.ts +23 -0
  36. package/dist/src/commands/assess-render.js +60 -0
  37. package/dist/src/commands/doctor.js +8 -13
  38. package/dist/src/commands/init.js +13 -1
  39. package/dist/src/commands/locks.js +2 -0
  40. package/dist/src/commands/logs.js +20 -6
  41. package/dist/src/commands/merge.js +154 -2
  42. package/dist/src/commands/ready-tui-adapter.js +6 -1
  43. package/dist/src/commands/ready.d.ts +25 -3
  44. package/dist/src/commands/ready.js +39 -13
  45. package/dist/src/commands/resume.d.ts +113 -0
  46. package/dist/src/commands/resume.js +193 -0
  47. package/dist/src/commands/run-display.d.ts +20 -0
  48. package/dist/src/commands/run-display.js +117 -5
  49. package/dist/src/commands/run-flags.d.ts +42 -1
  50. package/dist/src/commands/run-flags.js +53 -1
  51. package/dist/src/commands/run-progress.js +26 -1
  52. package/dist/src/commands/run.js +11 -10
  53. package/dist/src/commands/state.js +7 -0
  54. package/dist/src/commands/stats.js +47 -0
  55. package/dist/src/commands/status.d.ts +9 -0
  56. package/dist/src/commands/status.js +24 -1
  57. package/dist/src/commands/sync.js +90 -19
  58. package/dist/src/commands/update.js +28 -5
  59. package/dist/src/lib/ac-parser.d.ts +14 -0
  60. package/dist/src/lib/ac-parser.js +99 -5
  61. package/dist/src/lib/assess/index.d.ts +10 -0
  62. package/dist/src/lib/assess/index.js +9 -0
  63. package/dist/src/lib/assess/renderer.d.ts +64 -0
  64. package/dist/src/lib/assess/renderer.js +481 -0
  65. package/dist/src/lib/assess/types.d.ts +224 -0
  66. package/dist/src/lib/assess/types.js +241 -0
  67. package/dist/src/lib/assess-collision-detect.d.ts +19 -2
  68. package/dist/src/lib/assess-collision-detect.js +68 -4
  69. package/dist/src/lib/ci/outputs.d.ts +15 -2
  70. package/dist/src/lib/ci/outputs.js +17 -5
  71. package/dist/src/lib/cli-flags.d.ts +33 -0
  72. package/dist/src/lib/cli-flags.js +46 -0
  73. package/dist/src/lib/cli-ui/run-renderer-types.d.ts +21 -7
  74. package/dist/src/lib/cli-ui/run-renderer.d.ts +10 -0
  75. package/dist/src/lib/cli-ui/run-renderer.js +79 -9
  76. package/dist/src/lib/errors.d.ts +84 -6
  77. package/dist/src/lib/errors.js +142 -17
  78. package/dist/src/lib/locks/index.d.ts +3 -3
  79. package/dist/src/lib/locks/index.js +2 -2
  80. package/dist/src/lib/locks/lock-manager.d.ts +20 -2
  81. package/dist/src/lib/locks/lock-manager.js +47 -3
  82. package/dist/src/lib/locks/types.d.ts +34 -3
  83. package/dist/src/lib/locks/types.js +16 -0
  84. package/dist/src/lib/manifest.js +1 -17
  85. package/dist/src/lib/mcp-config.d.ts +45 -0
  86. package/dist/src/lib/mcp-config.js +77 -1
  87. package/dist/src/lib/merge-check/combined-branch-test.d.ts +21 -2
  88. package/dist/src/lib/merge-check/combined-branch-test.js +233 -110
  89. package/dist/src/lib/merge-check/command-result.d.ts +36 -0
  90. package/dist/src/lib/merge-check/command-result.js +56 -0
  91. package/dist/src/lib/merge-check/index.d.ts +4 -0
  92. package/dist/src/lib/merge-check/index.js +1 -1
  93. package/dist/src/lib/merge-check/report.js +5 -1
  94. package/dist/src/lib/merge-check/types.d.ts +9 -0
  95. package/dist/src/lib/merge-check/watch.d.ts +121 -0
  96. package/dist/src/lib/merge-check/watch.js +215 -0
  97. package/dist/src/lib/qa/infra-blocked-ci.d.ts +90 -0
  98. package/dist/src/lib/qa/infra-blocked-ci.js +80 -0
  99. package/dist/src/lib/settings.d.ts +14 -0
  100. package/dist/src/lib/settings.js +61 -8
  101. package/dist/src/lib/shutdown.d.ts +42 -2
  102. package/dist/src/lib/shutdown.js +75 -5
  103. package/dist/src/lib/skills-check.d.ts +26 -0
  104. package/dist/src/lib/skills-check.js +31 -0
  105. package/dist/src/lib/stacks.d.ts +110 -1
  106. package/dist/src/lib/stacks.js +244 -5
  107. package/dist/src/lib/templates.d.ts +74 -0
  108. package/dist/src/lib/templates.js +171 -16
  109. package/dist/src/lib/test-tautology-detector.d.ts +9 -2
  110. package/dist/src/lib/test-tautology-detector.js +153 -12
  111. package/dist/src/lib/version-check.d.ts +31 -0
  112. package/dist/src/lib/version-check.js +46 -7
  113. package/dist/src/lib/workflow/batch-executor.d.ts +138 -6
  114. package/dist/src/lib/workflow/batch-executor.js +550 -54
  115. package/dist/src/lib/workflow/chain-preflight.d.ts +89 -0
  116. package/dist/src/lib/workflow/chain-preflight.js +199 -0
  117. package/dist/src/lib/workflow/chain-resume.d.ts +118 -0
  118. package/dist/src/lib/workflow/chain-resume.js +170 -0
  119. package/dist/src/lib/workflow/completed-status.d.ts +52 -0
  120. package/dist/src/lib/workflow/completed-status.js +57 -0
  121. package/dist/src/lib/workflow/config-resolver.d.ts +26 -0
  122. package/dist/src/lib/workflow/config-resolver.js +54 -2
  123. package/dist/src/lib/workflow/dependency-markers.d.ts +29 -0
  124. package/dist/src/lib/workflow/dependency-markers.js +79 -0
  125. package/dist/src/lib/workflow/drivers/agent-driver.d.ts +7 -0
  126. package/dist/src/lib/workflow/drivers/aider.d.ts +2 -0
  127. package/dist/src/lib/workflow/drivers/aider.js +2 -0
  128. package/dist/src/lib/workflow/drivers/claude-code.d.ts +21 -0
  129. package/dist/src/lib/workflow/drivers/claude-code.js +50 -8
  130. package/dist/src/lib/workflow/error-classifier.d.ts +9 -2
  131. package/dist/src/lib/workflow/error-classifier.js +14 -1
  132. package/dist/src/lib/workflow/git-diff-utils.d.ts +35 -3
  133. package/dist/src/lib/workflow/git-diff-utils.js +77 -4
  134. package/dist/src/lib/workflow/heartbeat.d.ts +38 -0
  135. package/dist/src/lib/workflow/heartbeat.js +90 -0
  136. package/dist/src/lib/workflow/log-writer.d.ts +44 -1
  137. package/dist/src/lib/workflow/log-writer.js +81 -14
  138. package/dist/src/lib/workflow/metrics-schema.d.ts +42 -3
  139. package/dist/src/lib/workflow/metrics-schema.js +16 -0
  140. package/dist/src/lib/workflow/metrics-writer.d.ts +2 -1
  141. package/dist/src/lib/workflow/phase-executor.d.ts +234 -13
  142. package/dist/src/lib/workflow/phase-executor.js +533 -56
  143. package/dist/src/lib/workflow/phase-registry.js +2 -1
  144. package/dist/src/lib/workflow/platforms/github.d.ts +64 -0
  145. package/dist/src/lib/workflow/platforms/github.js +152 -0
  146. package/dist/src/lib/workflow/qa-cache.d.ts +6 -1
  147. package/dist/src/lib/workflow/qa-cache.js +22 -9
  148. package/dist/src/lib/workflow/ready-gate.d.ts +17 -4
  149. package/dist/src/lib/workflow/ready-gate.js +25 -9
  150. package/dist/src/lib/workflow/run-log-schema.d.ts +37 -0
  151. package/dist/src/lib/workflow/run-log-schema.js +92 -2
  152. package/dist/src/lib/workflow/run-orchestrator.d.ts +33 -0
  153. package/dist/src/lib/workflow/run-orchestrator.js +429 -47
  154. package/dist/src/lib/workflow/skills-preflight.d.ts +89 -0
  155. package/dist/src/lib/workflow/skills-preflight.js +112 -0
  156. package/dist/src/lib/workflow/state-cleanup.js +9 -2
  157. package/dist/src/lib/workflow/state-manager.d.ts +33 -0
  158. package/dist/src/lib/workflow/state-manager.js +83 -0
  159. package/dist/src/lib/workflow/state-schema.d.ts +26 -0
  160. package/dist/src/lib/workflow/state-schema.js +47 -0
  161. package/dist/src/lib/workflow/status-derivation.d.ts +30 -0
  162. package/dist/src/lib/workflow/status-derivation.js +27 -0
  163. package/dist/src/lib/workflow/types.d.ts +149 -4
  164. package/dist/src/lib/workflow/types.js +1 -0
  165. package/dist/src/lib/workflow/worktree-manager.d.ts +70 -2
  166. package/dist/src/lib/workflow/worktree-manager.js +201 -52
  167. package/dist/src/mcp/tools/run.d.ts +11 -1
  168. package/dist/src/mcp/tools/run.js +22 -1
  169. package/dist/src/mcp/tools/status.js +6 -0
  170. package/dist/src/ui/tui/ElapsedTimer.d.ts +12 -3
  171. package/dist/src/ui/tui/ElapsedTimer.js +12 -10
  172. package/dist/src/ui/tui/IssueBox.js +10 -2
  173. package/dist/src/ui/tui/row-cap.js +2 -1
  174. package/dist/src/ui/tui/theme.d.ts +7 -0
  175. package/dist/src/ui/tui/theme.js +9 -0
  176. package/package.json +9 -10
  177. package/templates/hooks/parallel-marker.sh +50 -0
  178. package/templates/hooks/post-tool.sh +38 -10
  179. package/templates/hooks/pre-tool.sh +405 -67
  180. package/templates/scripts/cleanup-worktree.sh +282 -30
  181. package/templates/scripts/new-feature.sh +264 -16
  182. package/templates/skills/_shared/references/trust-model.md +18 -0
  183. package/templates/skills/assess/SKILL.md +250 -344
  184. package/templates/skills/assess/references/predicted-collision-detection.md +10 -7
  185. package/templates/skills/docs/SKILL.md +4 -4
  186. package/templates/skills/exec/SKILL.md +26 -6
  187. package/templates/skills/fullsolve/SKILL.md +4 -2
  188. package/templates/skills/loop/SKILL.md +38 -2
  189. package/templates/skills/merger/SKILL.md +2 -2
  190. package/templates/skills/qa/SKILL.md +337 -57
  191. package/templates/skills/qa/references/anti-pattern-detection.md +6 -6
  192. package/templates/skills/qa/references/call-site-review.md +2 -2
  193. package/templates/skills/qa/references/code-review-checklist.md +2 -2
  194. package/templates/skills/qa/references/fixtures/injection-issue-body.md +36 -0
  195. package/templates/skills/qa/references/quality-gates.md +21 -7
  196. package/templates/skills/qa/references/testing-requirements.md +1 -1
  197. package/templates/skills/qa/scripts/quality-checks.sh +11 -11
  198. package/templates/skills/reflect/SKILL.md +27 -13
  199. package/templates/skills/reflect/references/documentation-tiers.md +80 -68
  200. package/templates/skills/reflect/references/phase-reflection.md +31 -15
  201. package/templates/skills/release/SKILL.md +11 -3
  202. package/templates/skills/setup/SKILL.md +5 -5
  203. package/templates/skills/spec/SKILL.md +2 -0
  204. package/templates/skills/spec/references/verification-criteria.md +1 -1
  205. package/templates/skills/test/SKILL.md +1 -1
  206. package/dist/src/lib/phase-spinner.d.ts +0 -146
  207. package/dist/src/lib/phase-spinner.js +0 -255
  208. package/dist/src/lib/workflow/pr-operations.d.ts +0 -86
  209. package/dist/src/lib/workflow/pr-operations.js +0 -326
  210. package/dist/src/lib/workflow/run-summary.d.ts +0 -36
  211. package/dist/src/lib/workflow/run-summary.js +0 -142
@@ -0,0 +1,113 @@
1
+ /**
2
+ * sequant resume — re-enter runs halted on a waitable rate-limit window
3
+ * (#892).
4
+ *
5
+ * The durable counterpart to `--auto-wait` (#804): when a run halts on an
6
+ * exhausted `five_hour`/`seven_day` window (#860 classification), the halt
7
+ * path writes `windowHalt.resumeAt` to issue state and exits cleanly with the
8
+ * per-issue lock released. This command is the re-entry: a no-op before
9
+ * `resumeAt` (clear message, exit 0 — safe to invoke from cron/launchd every
10
+ * few minutes), and after `resumeAt` it re-runs the halted issues through the
11
+ * normal `sequant run` path with `--resume` semantics, so completed phases
12
+ * (GitHub markers) and completed issues (#837 state guard) are skipped.
13
+ *
14
+ * Re-entries are bounded per issue by {@link MAX_RESUME_REENTRIES} (AC-3), so
15
+ * a window that never reopens cannot ping-pong a scheduler: the bound halts
16
+ * with the same labeled terminal message a spent auto-wait produces today.
17
+ */
18
+ import { StateManager } from "../lib/workflow/state-manager.js";
19
+ import { MAX_RESUME_REENTRIES } from "../lib/workflow/state-schema.js";
20
+ import type { IssueState } from "../lib/workflow/state-schema.js";
21
+ import type { RunOptions } from "../lib/workflow/types.js";
22
+ import type { LockFile } from "../lib/locks/index.js";
23
+ export { MAX_RESUME_REENTRIES };
24
+ /** One resumable (or not-yet / exhausted) issue in a {@link ResumePlan}. */
25
+ export interface ResumeCandidate {
26
+ issueNumber: number;
27
+ /** ISO timestamp after which re-entry can proceed. */
28
+ resumeAt: string;
29
+ /** Phase that halted. */
30
+ phase: string;
31
+ /** Re-entry attempts already consumed. */
32
+ reentries: number;
33
+ }
34
+ /**
35
+ * Partition of halted issues by what `sequant resume` should do with them.
36
+ * Pure data — computed by {@link planResume}, rendered/acted on by the
37
+ * command shell.
38
+ */
39
+ export interface ResumePlan {
40
+ /** Past `resumeAt` and under the re-entry bound: re-run these. */
41
+ due: ResumeCandidate[];
42
+ /** Before `resumeAt`: report and exit 0 (AC-2 no-op contract). */
43
+ notYet: ResumeCandidate[];
44
+ /** Re-entry bound consumed (AC-3): terminal, needs human attention. */
45
+ exhausted: ResumeCandidate[];
46
+ }
47
+ /**
48
+ * Decide what to do with every halted issue (#892 AC-2/AC-3).
49
+ *
50
+ * Pure so the before/after-`resumeAt` and bound edges are unit-testable with
51
+ * an injected clock. Issues whose persisted status is already completed
52
+ * (#837 vocabulary) are excluded even if a stale `windowHalt` survived — the
53
+ * run path would skip them anyway, so re-entering buys nothing.
54
+ *
55
+ * @param states All persisted issue states.
56
+ * @param requested Restrict to these issue numbers (empty = all halted).
57
+ * @param now Epoch ms clock, injectable for tests.
58
+ */
59
+ export declare function planResume(states: Record<number, IssueState>, requested: number[], now: number): ResumePlan;
60
+ /**
61
+ * Terminal message for an issue whose re-entry bound is spent (#892 AC-3).
62
+ * Mirrors today's labeled halt vocabulary (`Rate limited — resets at …`,
63
+ * `auto-wait N/M`): names the real cause, the consumed bound, and the manual
64
+ * way out.
65
+ *
66
+ * @internal Exported for testing
67
+ */
68
+ export declare function reentryBoundMessage(candidate: ResumeCandidate): string;
69
+ /**
70
+ * The delegated run options for a re-entry: `resume: true` (completed phases
71
+ * are skipped via GitHub markers) and nothing else. `RunOptions` is
72
+ * normalized so an absent field means "resolve from settings/defaults"
73
+ * (`config-resolver` only reacts to explicit `false` / `no*` fields), so a
74
+ * bare object behaves exactly like an attended `sequant run <issue> --resume`
75
+ * — MCP, retry ladder, rebase, and PR creation all follow the user's settings.
76
+ *
77
+ * @internal Exported for testing
78
+ */
79
+ export declare function buildReentryRunOptions(): RunOptions;
80
+ /** Options accepted by {@link resumeCommand}. */
81
+ export interface ResumeCommandOptions {
82
+ /** Print the plan without consuming a re-entry or spawning a run. */
83
+ dryRun?: boolean;
84
+ }
85
+ /**
86
+ * Injectable collaborators for {@link resumeCommand}.
87
+ *
88
+ * @internal For testing only — production callers (bin/cli.ts) omit this and
89
+ * get the real StateManager / LockManager / runCommand / clock. bin/cli.ts
90
+ * wraps the action in an arrow function so commander's third positional (the
91
+ * Command instance) can never land here.
92
+ */
93
+ export interface ResumeCommandDeps {
94
+ stateManager?: StateManager;
95
+ runFn?: (issues: string[], options: RunOptions) => Promise<void>;
96
+ /** Read-only lock probe; defaults to `LockManager.check`. */
97
+ checkLock?: (issue: number) => LockFile | null;
98
+ now?: () => number;
99
+ }
100
+ /**
101
+ * Command entry for `sequant resume [issues...]`.
102
+ *
103
+ * Exit contract (load-bearing for schedulers, AC-2):
104
+ * - nothing halted / nothing due yet → exit 0 (quiet no-op);
105
+ * - due issues → re-entry counter consumed, then delegates to the normal run
106
+ * path (its exit code stands);
107
+ * - a due issue whose lock is held by another session is skipped WITHOUT
108
+ * consuming a re-entry (the run path would skip it anyway; someone is
109
+ * already working on it) — exit 0 when that leaves nothing to run;
110
+ * - only exhausted issues → exit 1 with the labeled terminal message, so a
111
+ * wrapper can alert a human instead of silently looping forever.
112
+ */
113
+ export declare function resumeCommand(issues: string[], options?: ResumeCommandOptions, deps?: ResumeCommandDeps): Promise<void>;
@@ -0,0 +1,193 @@
1
+ /**
2
+ * sequant resume — re-enter runs halted on a waitable rate-limit window
3
+ * (#892).
4
+ *
5
+ * The durable counterpart to `--auto-wait` (#804): when a run halts on an
6
+ * exhausted `five_hour`/`seven_day` window (#860 classification), the halt
7
+ * path writes `windowHalt.resumeAt` to issue state and exits cleanly with the
8
+ * per-issue lock released. This command is the re-entry: a no-op before
9
+ * `resumeAt` (clear message, exit 0 — safe to invoke from cron/launchd every
10
+ * few minutes), and after `resumeAt` it re-runs the halted issues through the
11
+ * normal `sequant run` path with `--resume` semantics, so completed phases
12
+ * (GitHub markers) and completed issues (#837 state guard) are skipped.
13
+ *
14
+ * Re-entries are bounded per issue by {@link MAX_RESUME_REENTRIES} (AC-3), so
15
+ * a window that never reopens cannot ping-pong a scheduler: the bound halts
16
+ * with the same labeled terminal message a spent auto-wait produces today.
17
+ */
18
+ import chalk from "chalk";
19
+ import { StateManager } from "../lib/workflow/state-manager.js";
20
+ import { MAX_RESUME_REENTRIES } from "../lib/workflow/state-schema.js";
21
+ import { isCompletedIssueStatus } from "../lib/workflow/completed-status.js";
22
+ import { formatResetTime } from "../lib/errors.js";
23
+ import { LockManager, formatLockedMessage } from "../lib/locks/index.js";
24
+ import { runCommand } from "./run.js";
25
+ // Re-exported for callers that treat the resume command as the feature's
26
+ // entry point; the constant itself lives beside the schema field it bounds.
27
+ export { MAX_RESUME_REENTRIES };
28
+ /**
29
+ * Decide what to do with every halted issue (#892 AC-2/AC-3).
30
+ *
31
+ * Pure so the before/after-`resumeAt` and bound edges are unit-testable with
32
+ * an injected clock. Issues whose persisted status is already completed
33
+ * (#837 vocabulary) are excluded even if a stale `windowHalt` survived — the
34
+ * run path would skip them anyway, so re-entering buys nothing.
35
+ *
36
+ * @param states All persisted issue states.
37
+ * @param requested Restrict to these issue numbers (empty = all halted).
38
+ * @param now Epoch ms clock, injectable for tests.
39
+ */
40
+ export function planResume(states, requested, now) {
41
+ const plan = { due: [], notYet: [], exhausted: [] };
42
+ const requestedSet = new Set(requested);
43
+ for (const [key, state] of Object.entries(states)) {
44
+ const issueNumber = Number(key);
45
+ if (requested.length > 0 && !requestedSet.has(issueNumber))
46
+ continue;
47
+ if (!state.windowHalt)
48
+ continue;
49
+ if (isCompletedIssueStatus(state.status))
50
+ continue;
51
+ const candidate = {
52
+ issueNumber,
53
+ resumeAt: state.windowHalt.resumeAt,
54
+ phase: state.windowHalt.phase,
55
+ reentries: state.windowHalt.reentries,
56
+ };
57
+ if (candidate.reentries >= MAX_RESUME_REENTRIES) {
58
+ plan.exhausted.push(candidate);
59
+ }
60
+ else if (new Date(candidate.resumeAt).getTime() > now) {
61
+ plan.notYet.push(candidate);
62
+ }
63
+ else {
64
+ plan.due.push(candidate);
65
+ }
66
+ }
67
+ // Deterministic output order regardless of state-file key order.
68
+ for (const bucket of [plan.due, plan.notYet, plan.exhausted]) {
69
+ bucket.sort((a, b) => a.issueNumber - b.issueNumber);
70
+ }
71
+ return plan;
72
+ }
73
+ /**
74
+ * Terminal message for an issue whose re-entry bound is spent (#892 AC-3).
75
+ * Mirrors today's labeled halt vocabulary (`Rate limited — resets at …`,
76
+ * `auto-wait N/M`): names the real cause, the consumed bound, and the manual
77
+ * way out.
78
+ *
79
+ * @internal Exported for testing
80
+ */
81
+ export function reentryBoundMessage(candidate) {
82
+ const resetLabel = formatResetTime(new Date(candidate.resumeAt).getTime());
83
+ return (`Rate limited — re-entry bound reached ` +
84
+ `(${candidate.reentries}/${MAX_RESUME_REENTRIES}), window still closed ` +
85
+ `at last halt (${candidate.phase} phase, resumable at ${resetLabel}). ` +
86
+ `Re-run manually with: npx sequant run ${candidate.issueNumber} --resume`);
87
+ }
88
+ /**
89
+ * The delegated run options for a re-entry: `resume: true` (completed phases
90
+ * are skipped via GitHub markers) and nothing else. `RunOptions` is
91
+ * normalized so an absent field means "resolve from settings/defaults"
92
+ * (`config-resolver` only reacts to explicit `false` / `no*` fields), so a
93
+ * bare object behaves exactly like an attended `sequant run <issue> --resume`
94
+ * — MCP, retry ladder, rebase, and PR creation all follow the user's settings.
95
+ *
96
+ * @internal Exported for testing
97
+ */
98
+ export function buildReentryRunOptions() {
99
+ return { resume: true };
100
+ }
101
+ /**
102
+ * Command entry for `sequant resume [issues...]`.
103
+ *
104
+ * Exit contract (load-bearing for schedulers, AC-2):
105
+ * - nothing halted / nothing due yet → exit 0 (quiet no-op);
106
+ * - due issues → re-entry counter consumed, then delegates to the normal run
107
+ * path (its exit code stands);
108
+ * - a due issue whose lock is held by another session is skipped WITHOUT
109
+ * consuming a re-entry (the run path would skip it anyway; someone is
110
+ * already working on it) — exit 0 when that leaves nothing to run;
111
+ * - only exhausted issues → exit 1 with the labeled terminal message, so a
112
+ * wrapper can alert a human instead of silently looping forever.
113
+ */
114
+ export async function resumeCommand(issues, options = {}, deps = {}) {
115
+ const requested = issues.map((raw) => {
116
+ const n = parseInt(raw, 10);
117
+ if (isNaN(n) || n <= 0) {
118
+ console.log(chalk.red(`❌ Invalid issue number: ${raw}`));
119
+ process.exitCode = 1;
120
+ }
121
+ return n;
122
+ });
123
+ if (process.exitCode === 1)
124
+ return;
125
+ const stateManager = deps.stateManager ?? new StateManager();
126
+ const runFn = deps.runFn ?? runCommand;
127
+ const checkLock = deps.checkLock ?? ((issue) => new LockManager().check(issue));
128
+ const now = deps.now ?? Date.now;
129
+ const states = await stateManager.getAllIssueStates();
130
+ const plan = planResume(states, requested, now());
131
+ if (plan.due.length === 0 &&
132
+ plan.notYet.length === 0 &&
133
+ plan.exhausted.length === 0) {
134
+ console.log(chalk.gray("No halted issues to resume (no windowHalt in state)."));
135
+ return;
136
+ }
137
+ for (const candidate of plan.notYet) {
138
+ const resetLabel = formatResetTime(new Date(candidate.resumeAt).getTime());
139
+ console.log(chalk.yellow(`⏸ #${candidate.issueNumber} not yet resumable — window reopens at ` +
140
+ `${resetLabel} (${candidate.phase} phase halted)`));
141
+ }
142
+ for (const candidate of plan.exhausted) {
143
+ console.log(chalk.red(`❌ #${candidate.issueNumber}: ${reentryBoundMessage(candidate)}`));
144
+ }
145
+ if (plan.due.length === 0) {
146
+ // AC-2: pre-`resumeAt` is a clean no-op (exit 0). Only an exhausted-only
147
+ // outcome is terminal (exit 1) — there is nothing left for a scheduler to
148
+ // wait for.
149
+ if (plan.notYet.length === 0 && plan.exhausted.length > 0) {
150
+ process.exitCode = 1;
151
+ }
152
+ return;
153
+ }
154
+ if (options.dryRun) {
155
+ for (const candidate of plan.due) {
156
+ console.log(chalk.green(`▶ #${candidate.issueNumber} due for re-entry ` +
157
+ `(${candidate.reentries + 1}/${MAX_RESUME_REENTRIES}) — dry run, not started`));
158
+ }
159
+ return;
160
+ }
161
+ // Lock probe BEFORE consuming a re-entry: a due issue another session holds
162
+ // would be skipped by the run path's lock acquisition anyway, so consuming
163
+ // one of its bounded re-entries buys nothing. The probe is read-only and
164
+ // best-effort — a lock acquired in the window between this check and the
165
+ // run's own acquisition still costs the re-entry, which is the pre-existing
166
+ // (rare, recoverable) behavior, just with a much smaller race window.
167
+ const runnable = [];
168
+ for (const candidate of plan.due) {
169
+ const holder = checkLock(candidate.issueNumber);
170
+ if (holder) {
171
+ console.log(chalk.yellow(`⏭ #${candidate.issueNumber} skipped — re-entry not consumed. ` +
172
+ formatLockedMessage(candidate.issueNumber, holder)));
173
+ continue;
174
+ }
175
+ runnable.push(candidate);
176
+ }
177
+ if (runnable.length === 0) {
178
+ // Every due issue is being worked on by another session: nothing for a
179
+ // scheduler to do this tick, nothing terminal — exit 0 and try later.
180
+ return;
181
+ }
182
+ // Consume the re-entry BEFORE running (AC-3): a re-entry that halts again
183
+ // on a still-closed window must already be counted, or the bound never
184
+ // trips. A re-entry that makes progress clears the record (and counter)
185
+ // via the halt path's success handling.
186
+ for (const candidate of runnable) {
187
+ const count = await stateManager.incrementWindowHaltReentries(candidate.issueNumber);
188
+ console.log(chalk.green(`▶ Resuming #${candidate.issueNumber} ` +
189
+ `(re-entry ${count ?? candidate.reentries + 1}/${MAX_RESUME_REENTRIES}, ` +
190
+ `halted in ${candidate.phase})`));
191
+ }
192
+ await runFn(runnable.map((candidate) => String(candidate.issueNumber)), buildReentryRunOptions());
193
+ }
@@ -9,6 +9,7 @@
9
9
  */
10
10
  import type { RunRenderer } from "../lib/cli-ui/run-renderer-types.js";
11
11
  import type { ResolvedRun, RunResult } from "../lib/workflow/run-orchestrator.js";
12
+ import type { IssueResult } from "../lib/workflow/types.js";
12
13
  /**
13
14
  * Print pre-run config block.
14
15
  *
@@ -16,6 +17,25 @@ import type { ResolvedRun, RunResult } from "../lib/workflow/run-orchestrator.js
16
17
  * appear when non-default, matching the pre-#503 format.
17
18
  */
18
19
  export declare function displayConfig(r: ResolvedRun): void;
20
+ /**
21
+ * Detect a chain halted by a rate-limit/billing failure and build the summary
22
+ * notice for it (#761 AC-5). Returns null when the run wasn't a chain, no
23
+ * issue failed, or the halting failure wasn't rate-limit-classified.
24
+ *
25
+ * Extracted from `displaySummary` so the halt-and-print decision is testable
26
+ * standalone — the same treatment #760 gave `planChainResumeFromState` when it
27
+ * hit the executeSequential testability wall.
28
+ *
29
+ * The failing phase is found with the same reverse non-loop scan as
30
+ * `toIssueSummary` (#766): the classification must describe the LAST attempt,
31
+ * not a stale first-iteration failure.
32
+ *
33
+ * @internal Exported for testing
34
+ */
35
+ export declare function buildRateLimitHaltNotice(results: IssueResult[], chainEnabled: boolean): {
36
+ issueNumber: number;
37
+ label: string;
38
+ } | null;
19
39
  /**
20
40
  * Print post-run summary: per-issue grid, log path, reflection, tips.
21
41
  *
@@ -10,7 +10,9 @@
10
10
  import chalk from "chalk";
11
11
  import { ui, colors } from "../lib/cli-ui.js";
12
12
  import { renderRunSummary } from "../lib/cli-ui/run-renderer.js";
13
+ import { BillingError, RateLimitError, formatRateLimitMessage, isBillingFailure, } from "../lib/errors.js";
13
14
  import { analyzeRun, formatReflection } from "../lib/workflow/run-reflect.js";
15
+ import { LOOP_PHASE } from "../lib/workflow/status-derivation.js";
14
16
  /**
15
17
  * Print pre-run config block.
16
18
  *
@@ -59,15 +61,27 @@ export function displayConfig(r) {
59
61
  if (r.mergedOptions.chain) {
60
62
  console.log(chalk.gray(` Chain mode: enabled (each issue branches from previous)`));
61
63
  }
62
- if (r.mergedOptions.qaGate) {
63
- console.log(chalk.gray(` QA gate: enabled (chain waits for QA pass)`));
64
- }
64
+ // #795 deliberately prints nothing for `--qa-gate`: the old
65
+ // `QA gate: enabled (chain waits for QA pass)` line advertised gating the
66
+ // flag never performed. `runCommand` prints a deprecation notice instead.
65
67
  }
66
68
  /**
67
69
  * Convert workflow `IssueResult` to renderer `IssueSummary`.
68
70
  */
69
71
  function toIssueSummary(r) {
70
- const failedPhase = r.phaseResults.find((p) => !p.success);
72
+ // #766: the reason to show is the LAST failing attempt, not the first.
73
+ // `phaseResults` accumulates every attempt across every quality-loop
74
+ // iteration, so `.find()` (first-wins) rendered a stale first-iteration
75
+ // reason: #762's cell read `Timeout after 1800s` when its real last failure
76
+ // was an API drop. `verdict`/`unmetCount` below hang off the same entry, so
77
+ // they were stale for the same reason. `loop` is excluded on the same grounds
78
+ // the card and log exclude it (see `status-derivation.ts`) — it is auxiliary
79
+ // recovery, and a trailing loop failure would mask the phase that actually
80
+ // failed. Reverse scan rather than `findLast`: tsconfig pins `lib: ES2022`
81
+ // and `findLast` is ES2023.
82
+ const failedPhase = [...r.phaseResults]
83
+ .reverse()
84
+ .find((p) => !p.success && p.phase !== LOOP_PHASE);
71
85
  const summary = {
72
86
  issueNumber: r.issueNumber,
73
87
  success: r.success,
@@ -78,9 +92,12 @@ function toIssueSummary(r) {
78
92
  prUrl: r.prUrl,
79
93
  };
80
94
  if (!r.success) {
95
+ // #879: a PR-creation failure has no failed phase — fall back to its error
96
+ // so the summary cell reads the real reason, not the useless "phase failed".
81
97
  summary.failureReason =
82
98
  failedPhase?.error ??
83
99
  r.abortReason ??
100
+ r.prCreationError ??
84
101
  `${failedPhase?.phase ?? "phase"} failed`;
85
102
  if (failedPhase?.verdict) {
86
103
  summary.qaVerdict = String(failedPhase.verdict);
@@ -91,6 +108,48 @@ function toIssueSummary(r) {
91
108
  }
92
109
  return summary;
93
110
  }
111
+ /**
112
+ * Detect a chain halted by a rate-limit/billing failure and build the summary
113
+ * notice for it (#761 AC-5). Returns null when the run wasn't a chain, no
114
+ * issue failed, or the halting failure wasn't rate-limit-classified.
115
+ *
116
+ * Extracted from `displaySummary` so the halt-and-print decision is testable
117
+ * standalone — the same treatment #760 gave `planChainResumeFromState` when it
118
+ * hit the executeSequential testability wall.
119
+ *
120
+ * The failing phase is found with the same reverse non-loop scan as
121
+ * `toIssueSummary` (#766): the classification must describe the LAST attempt,
122
+ * not a stale first-iteration failure.
123
+ *
124
+ * @internal Exported for testing
125
+ */
126
+ export function buildRateLimitHaltNotice(results, chainEnabled) {
127
+ if (!chainEnabled)
128
+ return null;
129
+ // Chain mode halts at the first failed link, so at most one failed issue
130
+ // exists; scan defensively anyway.
131
+ for (const r of results) {
132
+ if (r.success)
133
+ continue;
134
+ const failedPhase = [...r.phaseResults]
135
+ .reverse()
136
+ .find((p) => !p.success && p.phase !== LOOP_PHASE);
137
+ const err = failedPhase?.structuredError;
138
+ if (err instanceof RateLimitError || err instanceof BillingError) {
139
+ // Event-derived errors get their message from formatRateLimitMessage, so
140
+ // re-deriving from metadata is exact and includes resetsAt. But errors
141
+ // from the assistant-error channel carry no billing/reset metadata —
142
+ // re-deriving there would mislabel a BillingError as "Rate limited"
143
+ // (isBillingFailure({}) is false) — so fall back to the typed message
144
+ // when the metadata carries no signal.
145
+ const label = err.metadata.resetsAt !== undefined || isBillingFailure(err.metadata)
146
+ ? formatRateLimitMessage(err.metadata)
147
+ : err.message;
148
+ return { issueNumber: r.issueNumber, label };
149
+ }
150
+ }
151
+ return null;
152
+ }
94
153
  /**
95
154
  * Print post-run summary: per-issue grid, log path, reflection, tips.
96
155
  *
@@ -103,7 +162,12 @@ export function displaySummary(result, renderer) {
103
162
  if (results.length === 0)
104
163
  return;
105
164
  const issueSummaries = results.map(toIssueSummary);
106
- const totalSeconds = results.reduce((sum, r) => sum + (r.durationSeconds ?? 0), 0);
165
+ // #867: run wall clock, computed once by the orchestrator (RunResult), NOT
166
+ // `results.reduce(sum + durationSeconds)`. Under --parallel that sum
167
+ // double-counts overlapping issues and over-reports by ~the concurrency
168
+ // factor (a 36m 18s run printed 1h 12m at concurrency 3). The orchestrator is
169
+ // the only component that brackets the whole run, so it owns this value.
170
+ const totalSeconds = result.wallClockDurationSeconds;
107
171
  if (renderer) {
108
172
  renderer.renderSummary({
109
173
  issues: issueSummaries,
@@ -120,6 +184,54 @@ export function displaySummary(result, renderer) {
120
184
  dryRun: config.dryRun,
121
185
  });
122
186
  }
187
+ // #817: when `--ready-gate` ran, surface each issue's gate outcome (threshold
188
+ // reached vs guard halt) in the summary the same way `sequant ready` reports
189
+ // it — the "never merged" contract and stop reason are the human's cue to
190
+ // review and merge manually. Only issues that actually ran the gate carry it.
191
+ //
192
+ // A gate that *failed* is listed here too. It is non-fatal (the PR still
193
+ // opens), but omitting it would make a run whose gate crashed look exactly
194
+ // like one that gated cleanly — the user opted into a second look and needs
195
+ // to know it never happened.
196
+ const gated = results.filter((r) => r.readyGate || r.readyGateError);
197
+ if (gated.length > 0) {
198
+ console.log(colors.muted(" Ready gate (#817) — never merged:"));
199
+ for (const r of gated) {
200
+ if (!r.readyGate) {
201
+ console.log(` #${r.issueNumber}: ${colors.warning("✗ gate did not run")} · ${r.readyGateError} · PR opened ungated — re-run \`sequant ready ${r.issueNumber}\``);
202
+ continue;
203
+ }
204
+ const g = r.readyGate;
205
+ const label = g.ready
206
+ ? colors.success(`✓ ${g.reason}`)
207
+ : colors.warning(`⚠️ ${g.reason}`);
208
+ console.log(` #${r.issueNumber}: ${label} · policy \`${g.policy}\` · ${g.iterations} QA pass(es) · status \`${g.issueStatus}\``);
209
+ }
210
+ console.log("");
211
+ }
212
+ // #760: a chain link whose checkpoint commit failed keeps its own work but
213
+ // loses the recovery point resume depends on, and the per-issue warning has
214
+ // long scrolled past by now on a multi-hour chain. Restate it at the summary,
215
+ // where the user is actually looking, so the next run's fail-fast is expected.
216
+ const checkpointFailures = results.filter((r) => r.checkpointFailed);
217
+ if (checkpointFailures.length > 0) {
218
+ console.log(colors.warning(` ⚠️ Checkpoint commit failed for ${checkpointFailures
219
+ .map((r) => `#${r.issueNumber}`)
220
+ .join(", ")} — uncommitted work is missing from the feature branch.`));
221
+ console.log(colors.muted(" Resuming this chain will stop at that link until the work is committed (or use --force)."));
222
+ console.log("");
223
+ }
224
+ // #761: a chain halted by a rate limit already stopped at the right link,
225
+ // but the labeled cause has long scrolled past by now (same rationale as the
226
+ // #760 restatement above), and #760 added no resume flag — resume IS
227
+ // re-running the identical command. Say both explicitly, or the halt reads
228
+ // as a bug and the resume path stays undiscovered.
229
+ const rateLimitHalt = buildRateLimitHaltNotice(results, mergedOptions.chain === true);
230
+ if (rateLimitHalt) {
231
+ console.log(colors.warning(` ⚠️ ${rateLimitHalt.label} — chain halted at #${rateLimitHalt.issueNumber}.`));
232
+ console.log(colors.muted(` Re-run the same command to resume from #${rateLimitHalt.issueNumber} (no flag needed; completed links are skipped).`));
233
+ console.log("");
234
+ }
123
235
  if (mergedOptions.reflect && results.length > 0) {
124
236
  const reflection = analyzeRun({
125
237
  results,
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * Run flag normalization (#705) — keeps run.ts thin (#503 AC-2: <200 LOC).
3
3
  *
4
- * Two pure resolvers for the `run` command's flag surface:
4
+ * Pure resolvers for the `run` command's flag surface:
5
5
  * - `normalizeQualityLoop`: ORs the hidden `-q` alias into `--quality-loop`.
6
6
  * - `resolveTuiEnabled`: decides whether the boxed Ink TUI mounts.
7
+ * - `deprecatedFlagNotices`: messages for flags kept only for compatibility.
7
8
  *
8
9
  * Extracted as pure functions so the flag behavior is unit-testable without
9
10
  * driving the full `runCommand` side effects.
@@ -29,3 +30,43 @@ export declare function normalizeQualityLoop(options: RunOptions): boolean;
29
30
  * it, so it is intentionally not consulted here.
30
31
  */
31
32
  export declare function resolveTuiEnabled(options: RunOptions, isTTY: boolean): boolean;
33
+ /**
34
+ * #795: notices for flags that still parse but no longer do anything.
35
+ *
36
+ * `--qa-gate` promised to "wait for QA pass before starting the next issue in
37
+ * chain", but the chain loop halts on any failed link unconditionally, so the
38
+ * flag never changed behavior and no runtime path ever wrote the
39
+ * `waiting_for_qa_gate` status it advertised. It is kept parseable so existing
40
+ * scripts do not hard-error — the previous `--qa-gate requires --chain` check
41
+ * aborted the whole run, which is exactly the breakage a deprecation window
42
+ * exists to avoid — and it no longer requires `--chain`.
43
+ *
44
+ * Returned rather than printed so the wording is unit-testable without driving
45
+ * `runCommand`'s side effects.
46
+ */
47
+ export declare function deprecatedFlagNotices(options: RunOptions): string[];
48
+ /**
49
+ * Prints every notice from {@link deprecatedFlagNotices}.
50
+ *
51
+ * Lives here rather than inline in `run.ts` to keep that adapter under the
52
+ * #503 AC-2 200-LOC budget, alongside the pure resolver it wraps.
53
+ *
54
+ * Two deliberate choices, both flagged because a future reader may mistake
55
+ * them for oversights:
56
+ *
57
+ * 1. **stderr, not stdout.** Deprecation notices are warnings, and warnings
58
+ * go on stderr (same as `abort.ts`). This keeps a script's piped stdout
59
+ * clean while still surfacing the notice on a terminal.
60
+ * 2. **Not gated on `options.quiet`.** `--quiet` suppresses progress and
61
+ * version chatter; it is not a warning switch. The scripts most likely to
62
+ * still pass `--qa-gate` are CI scripts, which are also the most likely to
63
+ * pass `--quiet` — suppressing there would defeat the deprecation window
64
+ * for exactly its target audience. Silencing warnings should be an
65
+ * explicit opt-out, not a side effect of asking for less progress output.
66
+ *
67
+ * `runCommand` calls this immediately after the header box, ahead of the
68
+ * manifest and settings checks: a dead flag is a fact about argv, not about
69
+ * project state, so it should be reported even from an uninitialized
70
+ * directory where those checks would return early.
71
+ */
72
+ export declare function warnDeprecatedFlags(options: RunOptions): void;
@@ -1,13 +1,15 @@
1
1
  /**
2
2
  * Run flag normalization (#705) — keeps run.ts thin (#503 AC-2: <200 LOC).
3
3
  *
4
- * Two pure resolvers for the `run` command's flag surface:
4
+ * Pure resolvers for the `run` command's flag surface:
5
5
  * - `normalizeQualityLoop`: ORs the hidden `-q` alias into `--quality-loop`.
6
6
  * - `resolveTuiEnabled`: decides whether the boxed Ink TUI mounts.
7
+ * - `deprecatedFlagNotices`: messages for flags kept only for compatibility.
7
8
  *
8
9
  * Extracted as pure functions so the flag behavior is unit-testable without
9
10
  * driving the full `runCommand` side effects.
10
11
  */
12
+ import chalk from "chalk";
11
13
  /**
12
14
  * #705: `-q` is a hidden alias for the quality loop (it no longer maps to
13
15
  * `--quiet`, which moved to `-s`). Returns the effective quality-loop flag so
@@ -32,3 +34,53 @@ export function normalizeQualityLoop(options) {
32
34
  export function resolveTuiEnabled(options, isTTY) {
33
35
  return options.tui !== false && isTTY && !options.quiet;
34
36
  }
37
+ /**
38
+ * #795: notices for flags that still parse but no longer do anything.
39
+ *
40
+ * `--qa-gate` promised to "wait for QA pass before starting the next issue in
41
+ * chain", but the chain loop halts on any failed link unconditionally, so the
42
+ * flag never changed behavior and no runtime path ever wrote the
43
+ * `waiting_for_qa_gate` status it advertised. It is kept parseable so existing
44
+ * scripts do not hard-error — the previous `--qa-gate requires --chain` check
45
+ * aborted the whole run, which is exactly the breakage a deprecation window
46
+ * exists to avoid — and it no longer requires `--chain`.
47
+ *
48
+ * Returned rather than printed so the wording is unit-testable without driving
49
+ * `runCommand`'s side effects.
50
+ */
51
+ export function deprecatedFlagNotices(options) {
52
+ const notices = [];
53
+ if (options.qaGate) {
54
+ notices.push("--qa-gate is deprecated and has no effect (#795). --chain already halts the chain on any failed issue, QA included. Remove the flag; it will be deleted in a future major release.");
55
+ }
56
+ return notices;
57
+ }
58
+ /**
59
+ * Prints every notice from {@link deprecatedFlagNotices}.
60
+ *
61
+ * Lives here rather than inline in `run.ts` to keep that adapter under the
62
+ * #503 AC-2 200-LOC budget, alongside the pure resolver it wraps.
63
+ *
64
+ * Two deliberate choices, both flagged because a future reader may mistake
65
+ * them for oversights:
66
+ *
67
+ * 1. **stderr, not stdout.** Deprecation notices are warnings, and warnings
68
+ * go on stderr (same as `abort.ts`). This keeps a script's piped stdout
69
+ * clean while still surfacing the notice on a terminal.
70
+ * 2. **Not gated on `options.quiet`.** `--quiet` suppresses progress and
71
+ * version chatter; it is not a warning switch. The scripts most likely to
72
+ * still pass `--qa-gate` are CI scripts, which are also the most likely to
73
+ * pass `--quiet` — suppressing there would defeat the deprecation window
74
+ * for exactly its target audience. Silencing warnings should be an
75
+ * explicit opt-out, not a side effect of asking for less progress output.
76
+ *
77
+ * `runCommand` calls this immediately after the header box, ahead of the
78
+ * manifest and settings checks: a dead flag is a fact about argv, not about
79
+ * project state, so it should be reported even from an uninitialized
80
+ * directory where those checks would return early.
81
+ */
82
+ export function warnDeprecatedFlags(options) {
83
+ for (const notice of deprecatedFlagNotices(options)) {
84
+ console.error(chalk.yellow(` ! ${notice}`));
85
+ }
86
+ }