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,89 @@
1
+ /**
2
+ * Lightweight content pre-flight for `--chain` runs (#762).
3
+ *
4
+ * Chain flag-validation only checks flag *combinations* (see `run.ts`); nothing
5
+ * inspects the *content* of the issues being chained. This module adds a fast,
6
+ * warn-by-default pre-flight that runs before the first worktree is provisioned
7
+ * and surfaces four cheap content-level problems:
8
+ *
9
+ * 1. An issue has no (or an empty) Acceptance Criteria section.
10
+ * 2. An issue declares a blocker (`blocked by #N` / `depends on #N`) that runs
11
+ * *after* it in the CLI order — the order contradicts the declaration.
12
+ * 3. Two chained issues are predicted to modify the same file, but the CLI
13
+ * order contradicts the predicted (ascending) land order.
14
+ * 4. An issue is CLOSED on GitHub — chaining a closed/merged issue is almost
15
+ * certainly unintended.
16
+ *
17
+ * The design follows the #604 philosophy: **suggest, never auto-decide**. False
18
+ * dependency inference is worse than none, so warnings are non-fatal by default;
19
+ * `--strict-preflight` opts in to a hard stop.
20
+ *
21
+ * Overlap prediction is delegated to `assess-collision-detect` (AC-3) rather
22
+ * than reimplemented. The pure `computePreflightWarnings` function is the unit
23
+ * surface (AC-1/AC-5); `runChainPreflight` adds the `gh` fetch and warn-degrades
24
+ * if a fetch fails — the pre-flight must never be the thing that breaks a run.
25
+ */
26
+ /** The class of content problem a warning describes. */
27
+ export type PreflightWarningKind = "missing-ac" | "dependency-order" | "file-overlap-order" | "closed-issue";
28
+ /** A single content-level pre-flight warning. */
29
+ export interface PreflightWarning {
30
+ /** Primary issue the warning is attached to. */
31
+ issue: number;
32
+ /** The class of problem (one warning per class per AC-1). */
33
+ kind: PreflightWarningKind;
34
+ /** Human-readable, ready to print after a `⚠` prefix. */
35
+ message: string;
36
+ }
37
+ /** Fetched issue content the pure computation operates over. */
38
+ export interface PreflightIssue {
39
+ number: number;
40
+ /** Raw issue body markdown. */
41
+ body: string;
42
+ /** GitHub issue state, e.g. `"OPEN"` / `"CLOSED"`. */
43
+ state: string;
44
+ /** Issue title (for message context). */
45
+ title: string;
46
+ }
47
+ /**
48
+ * Parse the issue numbers a body declares itself blocked by / dependent on.
49
+ * Catches both `depends on #N` and `blocked by #N` (deduped, order-preserving).
50
+ * Only line-leading markers count as declarations — see the shared
51
+ * `dependency-markers.ts` parser for why mid-sentence prose mentions are
52
+ * deliberately ignored.
53
+ *
54
+ * The pre-flight honors BOTH markers (unlike `batch-executor.ts`, which honors
55
+ * only `depends on` so the sorter's ordering semantics stay untouched — #762
56
+ * Open Q #3). The hardened mechanics are shared; the marker set is per-caller.
57
+ */
58
+ export declare function parseDeclaredBlockers(body: string): number[];
59
+ /**
60
+ * Detect whether a body has a non-empty Acceptance Criteria section.
61
+ *
62
+ * Fast path reuses `hasAcceptanceCriteria` (ac-parser) so `AC-N:`-prefixed
63
+ * checklists are recognized directly. The fallback catches AC sections written
64
+ * as bare checkboxes under an "Acceptance Criteria" heading (e.g. this very
65
+ * issue, #762) — those are legitimately non-empty AC sections that the
66
+ * `AC-N:` patterns don't match, and warning on them would be a false positive
67
+ * (worse than no warning, per #604).
68
+ */
69
+ export declare function hasNonEmptyAcSection(body: string): boolean;
70
+ /**
71
+ * Compute chain pre-flight warnings over already-fetched issue content.
72
+ *
73
+ * Pure and deterministic — this is the unit-test surface (AC-1/AC-5). Issues
74
+ * absent from `issues` (e.g. a `gh` fetch failed for them) are silently skipped
75
+ * so a fetch error degrades to fewer checks rather than aborting the run.
76
+ *
77
+ * @param cliOrder Raw CLI issue order (NOT dep-sorted — see #762 Open Q #1).
78
+ * @param issues Map of issue number → fetched content.
79
+ */
80
+ export declare function computePreflightWarnings(cliOrder: number[], issues: Map<number, PreflightIssue>): PreflightWarning[];
81
+ /**
82
+ * Run the chain content pre-flight: fetch each issue's body/state/title, then
83
+ * compute warnings. Fetch failures warn-degrade (a gray note is printed and the
84
+ * issue's checks are skipped) — the pre-flight never aborts a run on its own.
85
+ *
86
+ * @param cliOrder Raw CLI issue order (NOT dep-sorted).
87
+ * @returns The list of content warnings (empty when everything looks consistent).
88
+ */
89
+ export declare function runChainPreflight(cliOrder: number[]): Promise<PreflightWarning[]>;
@@ -0,0 +1,199 @@
1
+ /**
2
+ * Lightweight content pre-flight for `--chain` runs (#762).
3
+ *
4
+ * Chain flag-validation only checks flag *combinations* (see `run.ts`); nothing
5
+ * inspects the *content* of the issues being chained. This module adds a fast,
6
+ * warn-by-default pre-flight that runs before the first worktree is provisioned
7
+ * and surfaces four cheap content-level problems:
8
+ *
9
+ * 1. An issue has no (or an empty) Acceptance Criteria section.
10
+ * 2. An issue declares a blocker (`blocked by #N` / `depends on #N`) that runs
11
+ * *after* it in the CLI order — the order contradicts the declaration.
12
+ * 3. Two chained issues are predicted to modify the same file, but the CLI
13
+ * order contradicts the predicted (ascending) land order.
14
+ * 4. An issue is CLOSED on GitHub — chaining a closed/merged issue is almost
15
+ * certainly unintended.
16
+ *
17
+ * The design follows the #604 philosophy: **suggest, never auto-decide**. False
18
+ * dependency inference is worse than none, so warnings are non-fatal by default;
19
+ * `--strict-preflight` opts in to a hard stop.
20
+ *
21
+ * Overlap prediction is delegated to `assess-collision-detect` (AC-3) rather
22
+ * than reimplemented. The pure `computePreflightWarnings` function is the unit
23
+ * surface (AC-1/AC-5); `runChainPreflight` adds the `gh` fetch and warn-degrades
24
+ * if a fetch fails — the pre-flight must never be the thing that breaks a run.
25
+ */
26
+ import { spawnSync } from "child_process";
27
+ import chalk from "chalk";
28
+ import { hasAcceptanceCriteria } from "../ac-parser.js";
29
+ import { extractPathsFromIssueBody, detectFileCollisions, } from "../assess-collision-detect.js";
30
+ import { parseBodyDependencyMarkers } from "./dependency-markers.js";
31
+ /**
32
+ * Parse the issue numbers a body declares itself blocked by / dependent on.
33
+ * Catches both `depends on #N` and `blocked by #N` (deduped, order-preserving).
34
+ * Only line-leading markers count as declarations — see the shared
35
+ * `dependency-markers.ts` parser for why mid-sentence prose mentions are
36
+ * deliberately ignored.
37
+ *
38
+ * The pre-flight honors BOTH markers (unlike `batch-executor.ts`, which honors
39
+ * only `depends on` so the sorter's ordering semantics stay untouched — #762
40
+ * Open Q #3). The hardened mechanics are shared; the marker set is per-caller.
41
+ */
42
+ export function parseDeclaredBlockers(body) {
43
+ return parseBodyDependencyMarkers(body, ["depends on", "blocked by"]);
44
+ }
45
+ /**
46
+ * Detect whether a body has a non-empty Acceptance Criteria section.
47
+ *
48
+ * Fast path reuses `hasAcceptanceCriteria` (ac-parser) so `AC-N:`-prefixed
49
+ * checklists are recognized directly. The fallback catches AC sections written
50
+ * as bare checkboxes under an "Acceptance Criteria" heading (e.g. this very
51
+ * issue, #762) — those are legitimately non-empty AC sections that the
52
+ * `AC-N:` patterns don't match, and warning on them would be a false positive
53
+ * (worse than no warning, per #604).
54
+ */
55
+ export function hasNonEmptyAcSection(body) {
56
+ if (hasAcceptanceCriteria(body))
57
+ return true;
58
+ const lines = body.split("\n");
59
+ let inSection = false;
60
+ for (const line of lines) {
61
+ if (/^#{1,6}\s+.*acceptance\s+criteria/i.test(line)) {
62
+ inSection = true;
63
+ continue;
64
+ }
65
+ // A subsequent heading closes the AC section.
66
+ if (inSection && /^#{1,6}\s+/.test(line))
67
+ break;
68
+ // A checkbox item inside the section proves it is non-empty.
69
+ if (inSection && /^\s*-\s*\[[x\s]\]/i.test(line))
70
+ return true;
71
+ }
72
+ return false;
73
+ }
74
+ /**
75
+ * Compute chain pre-flight warnings over already-fetched issue content.
76
+ *
77
+ * Pure and deterministic — this is the unit-test surface (AC-1/AC-5). Issues
78
+ * absent from `issues` (e.g. a `gh` fetch failed for them) are silently skipped
79
+ * so a fetch error degrades to fewer checks rather than aborting the run.
80
+ *
81
+ * @param cliOrder Raw CLI issue order (NOT dep-sorted — see #762 Open Q #1).
82
+ * @param issues Map of issue number → fetched content.
83
+ */
84
+ export function computePreflightWarnings(cliOrder, issues) {
85
+ const warnings = [];
86
+ const positionOf = new Map();
87
+ cliOrder.forEach((n, i) => positionOf.set(n, i));
88
+ // Per-issue checks, in CLI order for deterministic output.
89
+ for (const num of cliOrder) {
90
+ const issue = issues.get(num);
91
+ if (!issue)
92
+ continue;
93
+ // AC-4: closed/merged issue check (consistent with the #305 state guard).
94
+ if (issue.state.toUpperCase() === "CLOSED") {
95
+ warnings.push({
96
+ issue: num,
97
+ kind: "closed-issue",
98
+ message: `#${num} is CLOSED on GitHub — chaining a closed/merged issue is ` +
99
+ `likely unintended (consistent with the #305 ready_for_merge/merged ` +
100
+ `guard; the #592 in_progress-but-merged gap is not covered here).`,
101
+ });
102
+ }
103
+ // AC-1: missing/empty Acceptance Criteria section.
104
+ if (!hasNonEmptyAcSection(issue.body)) {
105
+ warnings.push({
106
+ issue: num,
107
+ kind: "missing-ac",
108
+ message: `#${num} has no non-empty Acceptance Criteria section — the chain ` +
109
+ `cannot verify it is ready to implement.`,
110
+ });
111
+ }
112
+ // AC-1: CLI order contradicts a declared dependency marker.
113
+ for (const blocker of parseDeclaredBlockers(issue.body)) {
114
+ const blockerPos = positionOf.get(blocker);
115
+ // Only meaningful if the blocker is itself in this chain.
116
+ if (blockerPos === undefined)
117
+ continue;
118
+ if (blockerPos > positionOf.get(num)) {
119
+ warnings.push({
120
+ issue: num,
121
+ kind: "dependency-order",
122
+ message: `#${num} declares it is blocked by / depends on #${blocker}, but ` +
123
+ `#${blocker} runs AFTER #${num} in the chain order — reorder so ` +
124
+ `#${blocker} comes first.`,
125
+ });
126
+ }
127
+ }
128
+ }
129
+ // AC-1/AC-3: CLI order contradicts predicted file-overlap order.
130
+ const issuePaths = new Map();
131
+ for (const num of cliOrder) {
132
+ const issue = issues.get(num);
133
+ if (issue)
134
+ issuePaths.set(num, extractPathsFromIssueBody(issue.body));
135
+ }
136
+ for (const collision of detectFileCollisions(issuePaths)) {
137
+ // `collision.issues` is ascending issue-number order = the predicted land
138
+ // order used by /assess. Restrict to issues actually in the chain.
139
+ const predicted = collision.issues.filter((n) => positionOf.has(n));
140
+ if (predicted.length < 2)
141
+ continue;
142
+ const cliRelative = [...predicted].sort((a, b) => positionOf.get(a) - positionOf.get(b));
143
+ const contradicts = predicted.some((n, i) => n !== cliRelative[i]);
144
+ if (contradicts) {
145
+ warnings.push({
146
+ issue: predicted[0],
147
+ kind: "file-overlap-order",
148
+ message: `#${predicted.join(", #")} are predicted to modify ${collision.file}; ` +
149
+ `predicted land order is #${predicted.join(" → #")} but the CLI order ` +
150
+ `is #${cliRelative.join(" → #")} — landing out of order risks the ` +
151
+ `downstream-staleness class (#133).`,
152
+ });
153
+ }
154
+ }
155
+ return warnings;
156
+ }
157
+ /**
158
+ * Fetch a single issue's pre-flight content via `gh`. Returns `null` on any
159
+ * failure so the caller can warn-degrade (skip that issue's checks) rather than
160
+ * abort the run.
161
+ */
162
+ function fetchPreflightIssue(issueNumber) {
163
+ try {
164
+ const result = spawnSync("gh", ["issue", "view", String(issueNumber), "--json", "body,state,title"], { stdio: "pipe" });
165
+ if (result.status !== 0)
166
+ return null;
167
+ const data = JSON.parse(result.stdout.toString());
168
+ return {
169
+ number: issueNumber,
170
+ body: typeof data.body === "string" ? data.body : "",
171
+ state: typeof data.state === "string" ? data.state : "",
172
+ title: typeof data.title === "string" ? data.title : `Issue #${issueNumber}`,
173
+ };
174
+ }
175
+ catch {
176
+ return null;
177
+ }
178
+ }
179
+ /**
180
+ * Run the chain content pre-flight: fetch each issue's body/state/title, then
181
+ * compute warnings. Fetch failures warn-degrade (a gray note is printed and the
182
+ * issue's checks are skipped) — the pre-flight never aborts a run on its own.
183
+ *
184
+ * @param cliOrder Raw CLI issue order (NOT dep-sorted).
185
+ * @returns The list of content warnings (empty when everything looks consistent).
186
+ */
187
+ export async function runChainPreflight(cliOrder) {
188
+ const issues = new Map();
189
+ for (const num of cliOrder) {
190
+ const fetched = fetchPreflightIssue(num);
191
+ if (fetched) {
192
+ issues.set(num, fetched);
193
+ }
194
+ else {
195
+ console.log(chalk.gray(` (pre-flight: could not fetch #${num} — skipping its content checks)`));
196
+ }
197
+ }
198
+ return computePreflightWarnings(cliOrder, issues);
199
+ }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Chain resume planning (#760).
3
+ *
4
+ * When a `--chain` run fails mid-way, earlier links may already be complete
5
+ * (see `completed-status.ts`) with a checkpoint commit on their feature branch
6
+ * (`createCheckpointCommit`, worktree-manager.ts). Re-running the same chain
7
+ * should skip that completed prefix and resume at the first incomplete link,
8
+ * rebased onto the last completed link's committed tip — NOT `main` (which is
9
+ * the #748 wrong-base failure this reuses the #748 rebase path to avoid).
10
+ *
11
+ * The non-chain pre-flight guard (`run-orchestrator.ts`) drops completed issues
12
+ * from the run using the same {@link isCompletedIssueStatus} predicate, but it
13
+ * is chain-unaware: dropping the completed prefix leaves the first incomplete
14
+ * link at index 0, where `executeSequential`'s successor-rebase never fires, so
15
+ * it silently builds on `main`. This module computes a *chain-correct* resume
16
+ * plan that preserves the completed prefix as the resume base.
17
+ *
18
+ * The planner is pure over an injected {@link CompletedLinkResolver} so the
19
+ * skip/fail-fast state machine (AC-3) is unit-testable without real git; the
20
+ * real-git rebase is covered by the integration test.
21
+ */
22
+ import { type CompletedIssueStatus } from "./completed-status.js";
23
+ /** A completed link that will be skipped (not re-executed) on resume. */
24
+ export interface ChainResumeSkip {
25
+ issueNumber: number;
26
+ /** Why it was skipped — the terminal-ish status that made it complete. */
27
+ status: CompletedIssueStatus;
28
+ /** The link's local feature branch (from state), if known. */
29
+ branch?: string;
30
+ /** The link's worktree path (from state), if known — used for the dirty check. */
31
+ worktree?: string;
32
+ }
33
+ /** The computed plan for resuming a partially-completed chain. */
34
+ export interface ChainResumePlan {
35
+ /** Contiguous completed prefix that will be skipped (not re-executed). */
36
+ skipped: ChainResumeSkip[];
37
+ /** Issue numbers to actually execute (the incomplete tail). */
38
+ active: number[];
39
+ /**
40
+ * Local branch (or base ref) the first active link must be provisioned from
41
+ * and rebased onto. Undefined on a fresh run (no completed prefix).
42
+ */
43
+ resumeBase?: string;
44
+ /** Commit the resume base resolves to — reported so the user sees the point. */
45
+ resumeBaseCommit?: string;
46
+ /** First incomplete issue number (the resume point), if any. */
47
+ resumeIssue?: number;
48
+ /**
49
+ * Set when resume cannot proceed safely (AC-3): an unmerged completed link's
50
+ * (`ready_for_merge` or `waiting_for_human_merge`) branch/checkpoint is gone
51
+ * and its tip is unreconstructable. The caller must abort rather than
52
+ * silently execute the successor on the wrong base.
53
+ */
54
+ failFast?: string;
55
+ /** True when every link in the chain is already complete. */
56
+ allComplete: boolean;
57
+ }
58
+ /** Resolves the git refs a resume base depends on. Injected for testability. */
59
+ export interface CompletedLinkResolver {
60
+ /**
61
+ * Resolve a local branch ref to its tip commit SHA, or undefined if the
62
+ * branch does not exist (destroyed worktree/branch — the AC-3 fail-fast case).
63
+ */
64
+ resolveBranchTip(branch: string): string | undefined;
65
+ /** Resolve the base branch tip (for merged-resume reporting). */
66
+ resolveBaseTip(): string | undefined;
67
+ /**
68
+ * True iff the worktree has uncommitted changes (tracked or non-ignored
69
+ * untracked). Used to detect a resume base whose checkpoint never landed —
70
+ * see the dirty-tip fail-fast in {@link computeChainResumePlan}. Must return
71
+ * false (not throw) when the worktree is gone or unreadable; branch-tip
72
+ * resolution governs that case instead.
73
+ */
74
+ isWorktreeDirty(worktreePath: string): boolean;
75
+ }
76
+ /** An ordered chain link with its persisted state (status + branch + worktree). */
77
+ export interface ChainLinkState {
78
+ issueNumber: number;
79
+ status?: string;
80
+ branch?: string;
81
+ worktree?: string;
82
+ }
83
+ /**
84
+ * Compute a chain-correct resume plan.
85
+ *
86
+ * Peels the *contiguous* completed prefix off the front of the chain (chain
87
+ * mode breaks on the first failure, so completed links always form a prefix).
88
+ * A completed link that appears *after* an incomplete one is left in `active`
89
+ * and re-executed — the conservative choice, since automatic skipping must not
90
+ * silently drop an issue the user intended to redo.
91
+ *
92
+ * @param orderedLinks Chain links in execution order, with persisted state.
93
+ * @param baseBranch The run's base branch (resume base when the prefix merged).
94
+ * @param resolver Git-ref resolver (injected for tests).
95
+ */
96
+ export declare function computeChainResumePlan(orderedLinks: ChainLinkState[], baseBranch: string, resolver: CompletedLinkResolver): ChainResumePlan;
97
+ /** The subset of persisted issue state the resume planner reads. */
98
+ export interface PersistedLinkState {
99
+ status?: string;
100
+ branch?: string;
101
+ worktree?: string;
102
+ }
103
+ /**
104
+ * Read each link's persisted state and compute the resume plan.
105
+ *
106
+ * Split out of `run-orchestrator.ts` so the state-reading half of resume — in
107
+ * particular "a state lookup failure must treat the link as *incomplete*", the
108
+ * conservative choice that re-executes rather than skipping on bad data — is
109
+ * testable without standing up an orchestrator.
110
+ *
111
+ * @param issueNumbers Chain issues in execution order.
112
+ * @param baseBranch The run's base branch.
113
+ * @param getIssueState Reads persisted state for one issue (may reject).
114
+ * @param resolver Git-ref resolver.
115
+ * @param onStateError Called when a lookup throws; the link is then treated as
116
+ * incomplete (no status), so it stays in `active`.
117
+ */
118
+ export declare function planChainResumeFromState(issueNumbers: number[], baseBranch: string, getIssueState: (issueNumber: number) => Promise<PersistedLinkState | null | undefined>, resolver: CompletedLinkResolver, onStateError?: (issueNumber: number, error: unknown) => void): Promise<ChainResumePlan>;
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Chain resume planning (#760).
3
+ *
4
+ * When a `--chain` run fails mid-way, earlier links may already be complete
5
+ * (see `completed-status.ts`) with a checkpoint commit on their feature branch
6
+ * (`createCheckpointCommit`, worktree-manager.ts). Re-running the same chain
7
+ * should skip that completed prefix and resume at the first incomplete link,
8
+ * rebased onto the last completed link's committed tip — NOT `main` (which is
9
+ * the #748 wrong-base failure this reuses the #748 rebase path to avoid).
10
+ *
11
+ * The non-chain pre-flight guard (`run-orchestrator.ts`) drops completed issues
12
+ * from the run using the same {@link isCompletedIssueStatus} predicate, but it
13
+ * is chain-unaware: dropping the completed prefix leaves the first incomplete
14
+ * link at index 0, where `executeSequential`'s successor-rebase never fires, so
15
+ * it silently builds on `main`. This module computes a *chain-correct* resume
16
+ * plan that preserves the completed prefix as the resume base.
17
+ *
18
+ * The planner is pure over an injected {@link CompletedLinkResolver} so the
19
+ * skip/fail-fast state machine (AC-3) is unit-testable without real git; the
20
+ * real-git rebase is covered by the integration test.
21
+ */
22
+ import { isCompletedIssueStatus, } from "./completed-status.js";
23
+ /**
24
+ * Compute a chain-correct resume plan.
25
+ *
26
+ * Peels the *contiguous* completed prefix off the front of the chain (chain
27
+ * mode breaks on the first failure, so completed links always form a prefix).
28
+ * A completed link that appears *after* an incomplete one is left in `active`
29
+ * and re-executed — the conservative choice, since automatic skipping must not
30
+ * silently drop an issue the user intended to redo.
31
+ *
32
+ * @param orderedLinks Chain links in execution order, with persisted state.
33
+ * @param baseBranch The run's base branch (resume base when the prefix merged).
34
+ * @param resolver Git-ref resolver (injected for tests).
35
+ */
36
+ export function computeChainResumePlan(orderedLinks, baseBranch, resolver) {
37
+ const skipped = [];
38
+ let firstIncomplete = 0;
39
+ for (const link of orderedLinks) {
40
+ if (isCompletedIssueStatus(link.status)) {
41
+ skipped.push({
42
+ issueNumber: link.issueNumber,
43
+ status: link.status,
44
+ branch: link.branch,
45
+ worktree: link.worktree,
46
+ });
47
+ firstIncomplete++;
48
+ }
49
+ else {
50
+ break;
51
+ }
52
+ }
53
+ const allNumbers = orderedLinks.map((l) => l.issueNumber);
54
+ // No completed prefix → fresh run, nothing to resume.
55
+ if (skipped.length === 0) {
56
+ return { skipped: [], active: allNumbers, allComplete: false };
57
+ }
58
+ // Every link already complete → nothing to execute.
59
+ if (firstIncomplete >= orderedLinks.length) {
60
+ return { skipped, active: [], allComplete: true };
61
+ }
62
+ const active = allNumbers.slice(firstIncomplete);
63
+ const last = skipped[skipped.length - 1];
64
+ let resumeBase;
65
+ let resumeBaseCommit;
66
+ if (last.status === "merged") {
67
+ // The completed prefix's work is in the merged base (origin/main). Provision
68
+ // the first incomplete link from the base branch — the same base a fresh
69
+ // first link would use — rather than a local tip that squash-merge may have
70
+ // orphaned (never rebase a successor onto a stale post-merge local tip).
71
+ resumeBase = baseBranch;
72
+ resumeBaseCommit = resolver.resolveBaseTip();
73
+ }
74
+ else {
75
+ // ready_for_merge / waiting_for_human_merge: the checkpoint tip lives only
76
+ // on the local feature branch. If that branch is gone (worktree/branch
77
+ // destroyed mid-way), the tip is unreconstructable — fail fast instead of
78
+ // wrong-basing the successor onto main (which would miss the completed
79
+ // link's work).
80
+ if (!last.branch) {
81
+ return {
82
+ skipped,
83
+ active,
84
+ resumeIssue: active[0],
85
+ allComplete: false,
86
+ failFast: `#${last.issueNumber} is ${last.status} but no branch is recorded in state — ` +
87
+ `cannot reconstruct the resume base. Re-run with --force to redo the chain from scratch.`,
88
+ };
89
+ }
90
+ const tip = resolver.resolveBranchTip(last.branch);
91
+ if (!tip) {
92
+ return {
93
+ skipped,
94
+ active,
95
+ resumeIssue: active[0],
96
+ allComplete: false,
97
+ failFast: `#${last.issueNumber} is ${last.status} but its branch "${last.branch}" no longer exists — ` +
98
+ `the resume base is unreconstructable. Re-run with --force to redo the chain from scratch.`,
99
+ };
100
+ }
101
+ // The branch exists, but a tip is only a valid resume base if it actually
102
+ // contains the link's work. `createCheckpointCommit` sweeps trailing
103
+ // uncommitted changes into a checkpoint commit; when it fails (commit hook,
104
+ // staging error, or unrelated dirty files) a completed status was *already*
105
+ // written — `ready_for_merge` on a plain run, or `waiting_for_human_merge`
106
+ // when #817's `--ready-gate` owned the terminal status (#837) — so this link
107
+ // still reads as a completed prefix while its tip is missing work. Both
108
+ // statuses reach this branch, so the check covers gated links too. Rebasing
109
+ // the successor onto such a tip would be exactly the silent wrong-base
110
+ // execution AC-3 forbids — fail fast instead.
111
+ if (last.worktree && resolver.isWorktreeDirty(last.worktree)) {
112
+ return {
113
+ skipped,
114
+ active,
115
+ resumeIssue: active[0],
116
+ allComplete: false,
117
+ failFast: `#${last.issueNumber} is ${last.status} but its worktree has uncommitted changes — ` +
118
+ `its checkpoint commit never landed, so branch "${last.branch}" is missing that work and ` +
119
+ `resuming #${active[0]} here would build on an incomplete base. Commit them in ${last.worktree}, ` +
120
+ `or re-run with --force to redo the chain from scratch.`,
121
+ };
122
+ }
123
+ resumeBase = last.branch;
124
+ resumeBaseCommit = tip;
125
+ }
126
+ return {
127
+ skipped,
128
+ active,
129
+ resumeBase,
130
+ resumeBaseCommit,
131
+ resumeIssue: active[0],
132
+ allComplete: false,
133
+ };
134
+ }
135
+ /**
136
+ * Read each link's persisted state and compute the resume plan.
137
+ *
138
+ * Split out of `run-orchestrator.ts` so the state-reading half of resume — in
139
+ * particular "a state lookup failure must treat the link as *incomplete*", the
140
+ * conservative choice that re-executes rather than skipping on bad data — is
141
+ * testable without standing up an orchestrator.
142
+ *
143
+ * @param issueNumbers Chain issues in execution order.
144
+ * @param baseBranch The run's base branch.
145
+ * @param getIssueState Reads persisted state for one issue (may reject).
146
+ * @param resolver Git-ref resolver.
147
+ * @param onStateError Called when a lookup throws; the link is then treated as
148
+ * incomplete (no status), so it stays in `active`.
149
+ */
150
+ export async function planChainResumeFromState(issueNumbers, baseBranch, getIssueState, resolver, onStateError) {
151
+ const orderedLinks = [];
152
+ for (const issueNumber of issueNumbers) {
153
+ let status;
154
+ let branch;
155
+ let worktree;
156
+ try {
157
+ const issueState = await getIssueState(issueNumber);
158
+ status = issueState?.status;
159
+ branch = issueState?.branch;
160
+ worktree = issueState?.worktree;
161
+ }
162
+ catch (error) {
163
+ // Unknown state → treat as incomplete and re-execute. Skipping on a
164
+ // failed lookup could silently drop an issue the user meant to run.
165
+ onStateError?.(issueNumber, error);
166
+ }
167
+ orderedLinks.push({ issueNumber, status, branch, worktree });
168
+ }
169
+ return computeChainResumePlan(orderedLinks, baseBranch, resolver);
170
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Which persisted issue statuses mean "this issue is already done" (#837).
3
+ *
4
+ * Single source of truth for every pre-flight guard that skips finished work.
5
+ * It exists because the same predicate was previously spelled out inline at two
6
+ * sites with two different lists, and #817's `--ready-gate` extended the status
7
+ * vocabulary without either site following:
8
+ *
9
+ * - `chain-resume.ts` peels the completed prefix off a `--chain` run.
10
+ * - `run-orchestrator.ts` drops finished issues from a non-chain run.
11
+ *
12
+ * A gated issue deliberately terminates in `waiting_for_human_merge` (policy
13
+ * threshold reached) rather than `ready_for_merge`, since `ready_for_merge`
14
+ * would read as auto-merge-ready and defeat the human merge gate the feature
15
+ * exists to preserve. Both guards missed it, so re-running re-executed
16
+ * already-gated issues from phase 0 — a full spec/exec/qa pipeline plus another
17
+ * full-weight ready gate.
18
+ *
19
+ * `blocked` is deliberately NOT a completed status, for two reasons:
20
+ *
21
+ * 1. It is a *generic* member of `IssueStatusSchema` ("waiting on external input
22
+ * or dependency"), not a ready-gate-exclusive terminal. Admitting it here
23
+ * would silently apply to every other writer of that status, present and
24
+ * future.
25
+ * 2. A guard halt IS the human-attention signal. Skipping it as complete would
26
+ * report the issue as passed when it demonstrably did not.
27
+ *
28
+ * So a `blocked` issue is re-executed rather than skipped — the same
29
+ * conservative rule already applied to issues whose state lookup fails.
30
+ * Re-running wastes tokens; silently dropping an issue the user must look at is
31
+ * the worse failure. Once the user clears the blocker, the re-run is what lets
32
+ * work resume.
33
+ *
34
+ * NOTE: this is "done, do not re-run", which is NOT the same set as "might have
35
+ * a merged PR" — `state-cleanup.ts`'s merge-detection sweep deliberately keeps
36
+ * its own wider list (it also scans `in_progress`, #592, and
37
+ * `waiting_for_qa_gate`, #606). Do not unify the two.
38
+ *
39
+ * When adding a status to `IssueStatusSchema`, decide here whether it belongs.
40
+ */
41
+ export declare const COMPLETED_ISSUE_STATUSES: readonly ["ready_for_merge", "merged", "waiting_for_human_merge"];
42
+ /** A persisted status that counts as completed work. */
43
+ export type CompletedIssueStatus = (typeof COMPLETED_ISSUE_STATUSES)[number];
44
+ /**
45
+ * True iff `status` marks the issue as already finished, so a re-run should
46
+ * skip it unless `--force` is passed.
47
+ *
48
+ * Accepts `string | undefined` because persisted state may predate the current
49
+ * schema or be absent entirely; an unknown or missing status is treated as
50
+ * incomplete (re-execute), never as complete.
51
+ */
52
+ export declare function isCompletedIssueStatus(status: string | undefined): status is CompletedIssueStatus;