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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +23 -4
- package/dist/bin/cli.js +99 -18
- package/dist/marketplace/external_plugins/sequant/.claude-plugin/plugin.json +1 -1
- package/dist/marketplace/external_plugins/sequant/.mcp.json +1 -1
- package/dist/marketplace/external_plugins/sequant/README.md +58 -2
- package/dist/marketplace/external_plugins/sequant/hooks/parallel-marker.sh +50 -0
- package/dist/marketplace/external_plugins/sequant/hooks/post-tool.sh +38 -10
- package/dist/marketplace/external_plugins/sequant/hooks/pre-tool.sh +405 -67
- package/dist/marketplace/external_plugins/sequant/skills/_shared/references/trust-model.md +18 -0
- package/dist/marketplace/external_plugins/sequant/skills/assess/SKILL.md +250 -344
- package/dist/marketplace/external_plugins/sequant/skills/assess/references/predicted-collision-detection.md +10 -7
- package/dist/marketplace/external_plugins/sequant/skills/docs/SKILL.md +4 -4
- package/dist/marketplace/external_plugins/sequant/skills/exec/SKILL.md +26 -6
- package/dist/marketplace/external_plugins/sequant/skills/fullsolve/SKILL.md +4 -2
- package/dist/marketplace/external_plugins/sequant/skills/loop/SKILL.md +38 -2
- package/dist/marketplace/external_plugins/sequant/skills/merger/SKILL.md +2 -2
- package/dist/marketplace/external_plugins/sequant/skills/qa/SKILL.md +337 -57
- package/dist/marketplace/external_plugins/sequant/skills/qa/references/anti-pattern-detection.md +6 -6
- package/dist/marketplace/external_plugins/sequant/skills/qa/references/call-site-review.md +2 -2
- package/dist/marketplace/external_plugins/sequant/skills/qa/references/code-review-checklist.md +2 -2
- package/dist/marketplace/external_plugins/sequant/skills/qa/references/fixtures/injection-issue-body.md +36 -0
- package/dist/marketplace/external_plugins/sequant/skills/qa/references/quality-gates.md +21 -7
- package/dist/marketplace/external_plugins/sequant/skills/qa/references/testing-requirements.md +1 -1
- package/dist/marketplace/external_plugins/sequant/skills/qa/scripts/quality-checks.sh +11 -11
- package/dist/marketplace/external_plugins/sequant/skills/reflect/SKILL.md +27 -13
- package/dist/marketplace/external_plugins/sequant/skills/reflect/references/documentation-tiers.md +80 -68
- package/dist/marketplace/external_plugins/sequant/skills/reflect/references/phase-reflection.md +31 -15
- package/dist/marketplace/external_plugins/sequant/skills/release/SKILL.md +11 -3
- package/dist/marketplace/external_plugins/sequant/skills/setup/SKILL.md +5 -5
- package/dist/marketplace/external_plugins/sequant/skills/spec/SKILL.md +2 -0
- package/dist/marketplace/external_plugins/sequant/skills/spec/references/verification-criteria.md +1 -1
- package/dist/marketplace/external_plugins/sequant/skills/test/SKILL.md +1 -1
- package/dist/src/commands/assess-render.d.ts +23 -0
- package/dist/src/commands/assess-render.js +60 -0
- package/dist/src/commands/doctor.js +8 -13
- package/dist/src/commands/init.js +13 -1
- package/dist/src/commands/locks.js +2 -0
- package/dist/src/commands/logs.js +20 -6
- package/dist/src/commands/merge.js +154 -2
- package/dist/src/commands/ready-tui-adapter.js +6 -1
- package/dist/src/commands/ready.d.ts +25 -3
- package/dist/src/commands/ready.js +39 -13
- package/dist/src/commands/resume.d.ts +113 -0
- package/dist/src/commands/resume.js +193 -0
- package/dist/src/commands/run-display.d.ts +20 -0
- package/dist/src/commands/run-display.js +117 -5
- package/dist/src/commands/run-flags.d.ts +42 -1
- package/dist/src/commands/run-flags.js +53 -1
- package/dist/src/commands/run-progress.js +26 -1
- package/dist/src/commands/run.js +11 -10
- package/dist/src/commands/state.js +7 -0
- package/dist/src/commands/stats.js +47 -0
- package/dist/src/commands/status.d.ts +9 -0
- package/dist/src/commands/status.js +24 -1
- package/dist/src/commands/sync.js +90 -19
- package/dist/src/commands/update.js +28 -5
- package/dist/src/lib/ac-parser.d.ts +14 -0
- package/dist/src/lib/ac-parser.js +99 -5
- package/dist/src/lib/assess/index.d.ts +10 -0
- package/dist/src/lib/assess/index.js +9 -0
- package/dist/src/lib/assess/renderer.d.ts +64 -0
- package/dist/src/lib/assess/renderer.js +481 -0
- package/dist/src/lib/assess/types.d.ts +224 -0
- package/dist/src/lib/assess/types.js +241 -0
- package/dist/src/lib/assess-collision-detect.d.ts +19 -2
- package/dist/src/lib/assess-collision-detect.js +68 -4
- package/dist/src/lib/ci/outputs.d.ts +15 -2
- package/dist/src/lib/ci/outputs.js +17 -5
- package/dist/src/lib/cli-flags.d.ts +33 -0
- package/dist/src/lib/cli-flags.js +46 -0
- package/dist/src/lib/cli-ui/run-renderer-types.d.ts +21 -7
- package/dist/src/lib/cli-ui/run-renderer.d.ts +10 -0
- package/dist/src/lib/cli-ui/run-renderer.js +79 -9
- package/dist/src/lib/errors.d.ts +84 -6
- package/dist/src/lib/errors.js +142 -17
- package/dist/src/lib/locks/index.d.ts +3 -3
- package/dist/src/lib/locks/index.js +2 -2
- package/dist/src/lib/locks/lock-manager.d.ts +20 -2
- package/dist/src/lib/locks/lock-manager.js +47 -3
- package/dist/src/lib/locks/types.d.ts +34 -3
- package/dist/src/lib/locks/types.js +16 -0
- package/dist/src/lib/manifest.js +1 -17
- package/dist/src/lib/mcp-config.d.ts +45 -0
- package/dist/src/lib/mcp-config.js +77 -1
- package/dist/src/lib/merge-check/combined-branch-test.d.ts +21 -2
- package/dist/src/lib/merge-check/combined-branch-test.js +233 -110
- package/dist/src/lib/merge-check/command-result.d.ts +36 -0
- package/dist/src/lib/merge-check/command-result.js +56 -0
- package/dist/src/lib/merge-check/index.d.ts +4 -0
- package/dist/src/lib/merge-check/index.js +1 -1
- package/dist/src/lib/merge-check/report.js +5 -1
- package/dist/src/lib/merge-check/types.d.ts +9 -0
- package/dist/src/lib/merge-check/watch.d.ts +121 -0
- package/dist/src/lib/merge-check/watch.js +215 -0
- package/dist/src/lib/qa/infra-blocked-ci.d.ts +90 -0
- package/dist/src/lib/qa/infra-blocked-ci.js +80 -0
- package/dist/src/lib/settings.d.ts +14 -0
- package/dist/src/lib/settings.js +61 -8
- package/dist/src/lib/shutdown.d.ts +42 -2
- package/dist/src/lib/shutdown.js +75 -5
- package/dist/src/lib/skills-check.d.ts +26 -0
- package/dist/src/lib/skills-check.js +31 -0
- package/dist/src/lib/stacks.d.ts +110 -1
- package/dist/src/lib/stacks.js +244 -5
- package/dist/src/lib/templates.d.ts +74 -0
- package/dist/src/lib/templates.js +171 -16
- package/dist/src/lib/test-tautology-detector.d.ts +9 -2
- package/dist/src/lib/test-tautology-detector.js +153 -12
- package/dist/src/lib/version-check.d.ts +31 -0
- package/dist/src/lib/version-check.js +46 -7
- package/dist/src/lib/workflow/batch-executor.d.ts +138 -6
- package/dist/src/lib/workflow/batch-executor.js +550 -54
- package/dist/src/lib/workflow/chain-preflight.d.ts +89 -0
- package/dist/src/lib/workflow/chain-preflight.js +199 -0
- package/dist/src/lib/workflow/chain-resume.d.ts +118 -0
- package/dist/src/lib/workflow/chain-resume.js +170 -0
- package/dist/src/lib/workflow/completed-status.d.ts +52 -0
- package/dist/src/lib/workflow/completed-status.js +57 -0
- package/dist/src/lib/workflow/config-resolver.d.ts +26 -0
- package/dist/src/lib/workflow/config-resolver.js +54 -2
- package/dist/src/lib/workflow/dependency-markers.d.ts +29 -0
- package/dist/src/lib/workflow/dependency-markers.js +79 -0
- package/dist/src/lib/workflow/drivers/agent-driver.d.ts +7 -0
- package/dist/src/lib/workflow/drivers/aider.d.ts +2 -0
- package/dist/src/lib/workflow/drivers/aider.js +2 -0
- package/dist/src/lib/workflow/drivers/claude-code.d.ts +21 -0
- package/dist/src/lib/workflow/drivers/claude-code.js +50 -8
- package/dist/src/lib/workflow/error-classifier.d.ts +9 -2
- package/dist/src/lib/workflow/error-classifier.js +14 -1
- package/dist/src/lib/workflow/git-diff-utils.d.ts +35 -3
- package/dist/src/lib/workflow/git-diff-utils.js +77 -4
- package/dist/src/lib/workflow/heartbeat.d.ts +38 -0
- package/dist/src/lib/workflow/heartbeat.js +90 -0
- package/dist/src/lib/workflow/log-writer.d.ts +44 -1
- package/dist/src/lib/workflow/log-writer.js +81 -14
- package/dist/src/lib/workflow/metrics-schema.d.ts +42 -3
- package/dist/src/lib/workflow/metrics-schema.js +16 -0
- package/dist/src/lib/workflow/metrics-writer.d.ts +2 -1
- package/dist/src/lib/workflow/phase-executor.d.ts +234 -13
- package/dist/src/lib/workflow/phase-executor.js +533 -56
- package/dist/src/lib/workflow/phase-registry.js +2 -1
- package/dist/src/lib/workflow/platforms/github.d.ts +64 -0
- package/dist/src/lib/workflow/platforms/github.js +152 -0
- package/dist/src/lib/workflow/qa-cache.d.ts +6 -1
- package/dist/src/lib/workflow/qa-cache.js +22 -9
- package/dist/src/lib/workflow/ready-gate.d.ts +17 -4
- package/dist/src/lib/workflow/ready-gate.js +25 -9
- package/dist/src/lib/workflow/run-log-schema.d.ts +37 -0
- package/dist/src/lib/workflow/run-log-schema.js +92 -2
- package/dist/src/lib/workflow/run-orchestrator.d.ts +33 -0
- package/dist/src/lib/workflow/run-orchestrator.js +429 -47
- package/dist/src/lib/workflow/skills-preflight.d.ts +89 -0
- package/dist/src/lib/workflow/skills-preflight.js +112 -0
- package/dist/src/lib/workflow/state-cleanup.js +9 -2
- package/dist/src/lib/workflow/state-manager.d.ts +33 -0
- package/dist/src/lib/workflow/state-manager.js +83 -0
- package/dist/src/lib/workflow/state-schema.d.ts +26 -0
- package/dist/src/lib/workflow/state-schema.js +47 -0
- package/dist/src/lib/workflow/status-derivation.d.ts +30 -0
- package/dist/src/lib/workflow/status-derivation.js +27 -0
- package/dist/src/lib/workflow/types.d.ts +149 -4
- package/dist/src/lib/workflow/types.js +1 -0
- package/dist/src/lib/workflow/worktree-manager.d.ts +70 -2
- package/dist/src/lib/workflow/worktree-manager.js +201 -52
- package/dist/src/mcp/tools/run.d.ts +11 -1
- package/dist/src/mcp/tools/run.js +22 -1
- package/dist/src/mcp/tools/status.js +6 -0
- package/dist/src/ui/tui/ElapsedTimer.d.ts +12 -3
- package/dist/src/ui/tui/ElapsedTimer.js +12 -10
- package/dist/src/ui/tui/IssueBox.js +10 -2
- package/dist/src/ui/tui/row-cap.js +2 -1
- package/dist/src/ui/tui/theme.d.ts +7 -0
- package/dist/src/ui/tui/theme.js +9 -0
- package/package.json +9 -10
- package/templates/hooks/parallel-marker.sh +50 -0
- package/templates/hooks/post-tool.sh +38 -10
- package/templates/hooks/pre-tool.sh +405 -67
- package/templates/scripts/cleanup-worktree.sh +282 -30
- package/templates/scripts/new-feature.sh +264 -16
- package/templates/skills/_shared/references/trust-model.md +18 -0
- package/templates/skills/assess/SKILL.md +250 -344
- package/templates/skills/assess/references/predicted-collision-detection.md +10 -7
- package/templates/skills/docs/SKILL.md +4 -4
- package/templates/skills/exec/SKILL.md +26 -6
- package/templates/skills/fullsolve/SKILL.md +4 -2
- package/templates/skills/loop/SKILL.md +38 -2
- package/templates/skills/merger/SKILL.md +2 -2
- package/templates/skills/qa/SKILL.md +337 -57
- package/templates/skills/qa/references/anti-pattern-detection.md +6 -6
- package/templates/skills/qa/references/call-site-review.md +2 -2
- package/templates/skills/qa/references/code-review-checklist.md +2 -2
- package/templates/skills/qa/references/fixtures/injection-issue-body.md +36 -0
- package/templates/skills/qa/references/quality-gates.md +21 -7
- package/templates/skills/qa/references/testing-requirements.md +1 -1
- package/templates/skills/qa/scripts/quality-checks.sh +11 -11
- package/templates/skills/reflect/SKILL.md +27 -13
- package/templates/skills/reflect/references/documentation-tiers.md +80 -68
- package/templates/skills/reflect/references/phase-reflection.md +31 -15
- package/templates/skills/release/SKILL.md +11 -3
- package/templates/skills/setup/SKILL.md +5 -5
- package/templates/skills/spec/SKILL.md +2 -0
- package/templates/skills/spec/references/verification-criteria.md +1 -1
- package/templates/skills/test/SKILL.md +1 -1
- package/dist/src/lib/phase-spinner.d.ts +0 -146
- package/dist/src/lib/phase-spinner.js +0 -255
- package/dist/src/lib/workflow/pr-operations.d.ts +0 -86
- package/dist/src/lib/workflow/pr-operations.js +0 -326
- package/dist/src/lib/workflow/run-summary.d.ts +0 -36
- 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;
|