sequant 2.9.0 → 2.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +20 -5
- package/dist/bin/cli.js +143 -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 +56 -2
- package/dist/marketplace/external_plugins/sequant/hooks/parallel-marker.sh +50 -0
- package/dist/marketplace/external_plugins/sequant/hooks/post-tool.sh +20 -7
- package/dist/marketplace/external_plugins/sequant/hooks/pre-tool.sh +75 -10
- package/dist/marketplace/external_plugins/sequant/skills/_shared/references/trust-model.md +18 -0
- package/dist/marketplace/external_plugins/sequant/skills/assess/SKILL.md +215 -390
- package/dist/marketplace/external_plugins/sequant/skills/assess/references/predicted-collision-detection.md +1 -1
- 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 +3 -1
- 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/release/SKILL.md +1 -1
- 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/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.d.ts +20 -1
- package/dist/src/commands/locks.js +208 -4
- package/dist/src/commands/logs.js +14 -5
- 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 +31 -3
- package/dist/src/commands/ready.js +53 -13
- package/dist/src/commands/resume.d.ts +113 -0
- package/dist/src/commands/resume.js +193 -0
- package/dist/src/commands/run-display.js +38 -4
- 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/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/commands/worktree.d.ts +31 -0
- package/dist/src/commands/worktree.js +95 -0
- 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/ci/outputs.d.ts +15 -2
- package/dist/src/lib/ci/outputs.js +17 -5
- package/dist/src/lib/cli-flags.d.ts +56 -0
- package/dist/src/lib/cli-flags.js +89 -0
- package/dist/src/lib/cli-ui/run-renderer-types.d.ts +23 -7
- package/dist/src/lib/cli-ui/run-renderer.d.ts +10 -0
- package/dist/src/lib/cli-ui/run-renderer.js +69 -1
- package/dist/src/lib/errors.d.ts +78 -6
- package/dist/src/lib/errors.js +133 -15
- package/dist/src/lib/locks/checkout-lock.d.ts +193 -0
- package/dist/src/lib/locks/checkout-lock.js +389 -0
- package/dist/src/lib/locks/index.d.ts +7 -4
- package/dist/src/lib/locks/index.js +5 -3
- package/dist/src/lib/locks/lock-manager.d.ts +101 -3
- package/dist/src/lib/locks/lock-manager.js +277 -8
- package/dist/src/lib/locks/types.d.ts +106 -3
- package/dist/src/lib/locks/types.js +44 -0
- 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 +87 -0
- package/dist/src/lib/settings.js +106 -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 +10 -2
- package/dist/src/lib/test-tautology-detector.js +213 -12
- package/dist/src/lib/version-check.d.ts +31 -0
- package/dist/src/lib/version-check.js +45 -2
- package/dist/src/lib/workflow/batch-executor.d.ts +125 -6
- package/dist/src/lib/workflow/batch-executor.js +552 -60
- package/dist/src/lib/workflow/chain-resume.d.ts +13 -11
- package/dist/src/lib/workflow/chain-resume.js +24 -20
- 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 +51 -0
- package/dist/src/lib/workflow/config-resolver.js +143 -2
- package/dist/src/lib/workflow/drivers/agent-driver.d.ts +22 -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 +14 -0
- package/dist/src/lib/workflow/drivers/claude-code.js +25 -2
- package/dist/src/lib/workflow/effort-escalation.d.ts +73 -0
- package/dist/src/lib/workflow/effort-escalation.js +82 -0
- package/dist/src/lib/workflow/error-classifier.d.ts +4 -1
- package/dist/src/lib/workflow/error-classifier.js +4 -0
- 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 +54 -2
- package/dist/src/lib/workflow/log-writer.js +95 -6
- package/dist/src/lib/workflow/metrics-schema.d.ts +52 -9
- package/dist/src/lib/workflow/metrics-schema.js +33 -0
- package/dist/src/lib/workflow/metrics-writer.d.ts +11 -0
- package/dist/src/lib/workflow/phase-detection.d.ts +12 -0
- package/dist/src/lib/workflow/phase-detection.js +5 -1
- package/dist/src/lib/workflow/phase-executor.d.ts +203 -14
- package/dist/src/lib/workflow/phase-executor.js +467 -52
- 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 +45 -4
- package/dist/src/lib/workflow/ready-gate.js +49 -12
- package/dist/src/lib/workflow/run-log-schema.d.ts +69 -0
- package/dist/src/lib/workflow/run-log-schema.js +78 -2
- package/dist/src/lib/workflow/run-orchestrator.d.ts +19 -0
- package/dist/src/lib/workflow/run-orchestrator.js +168 -20
- 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/spec-recommendation.d.ts +71 -0
- package/dist/src/lib/workflow/spec-recommendation.js +142 -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/types.d.ts +190 -4
- package/dist/src/lib/workflow/types.js +1 -0
- package/dist/src/lib/workflow/worktree-manager.d.ts +36 -3
- package/dist/src/lib/workflow/worktree-manager.js +111 -24
- package/dist/src/lib/workflow/worktree-resolver.d.ts +73 -0
- package/dist/src/lib/workflow/worktree-resolver.js +126 -0
- package/dist/src/mcp/tools/run.d.ts +9 -1
- package/dist/src/mcp/tools/run.js +20 -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 +10 -8
- package/templates/hooks/parallel-marker.sh +50 -0
- package/templates/hooks/post-tool.sh +20 -7
- package/templates/hooks/pre-tool.sh +303 -10
- package/templates/scripts/cleanup-worktree.sh +217 -33
- package/templates/scripts/new-feature.sh +289 -35
- package/templates/skills/_shared/references/subagent-types.md +7 -18
- package/templates/skills/_shared/references/trust-model.md +18 -0
- package/templates/skills/assess/SKILL.md +220 -391
- package/templates/skills/assess/references/predicted-collision-detection.md +1 -1
- package/templates/skills/docs/SKILL.md +4 -4
- package/templates/skills/exec/SKILL.md +87 -13
- package/templates/skills/fullsolve/SKILL.md +130 -22
- package/templates/skills/loop/SKILL.md +94 -13
- package/templates/skills/merger/SKILL.md +100 -12
- package/templates/skills/qa/SKILL.md +396 -63
- 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/release/SKILL.md +80 -1
- package/templates/skills/setup/SKILL.md +5 -5
- package/templates/skills/spec/SKILL.md +33 -15
- package/templates/skills/spec/references/recommended-workflow.md +14 -1
- package/templates/skills/test/SKILL.md +1 -1
- package/templates/skills/testgen/SKILL.md +23 -6
- 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
- package/templates/agents/sequant-explorer.md +0 -24
|
@@ -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
|
+
}
|
|
@@ -61,9 +61,9 @@ export function displayConfig(r) {
|
|
|
61
61
|
if (r.mergedOptions.chain) {
|
|
62
62
|
console.log(chalk.gray(` Chain mode: enabled (each issue branches from previous)`));
|
|
63
63
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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.
|
|
67
67
|
}
|
|
68
68
|
/**
|
|
69
69
|
* Convert workflow `IssueResult` to renderer `IssueSummary`.
|
|
@@ -90,11 +90,15 @@ function toIssueSummary(r) {
|
|
|
90
90
|
loopTriggered: r.loopTriggered,
|
|
91
91
|
prNumber: r.prNumber,
|
|
92
92
|
prUrl: r.prUrl,
|
|
93
|
+
prSkippedReason: r.prSkippedReason,
|
|
93
94
|
};
|
|
94
95
|
if (!r.success) {
|
|
96
|
+
// #879: a PR-creation failure has no failed phase — fall back to its error
|
|
97
|
+
// so the summary cell reads the real reason, not the useless "phase failed".
|
|
95
98
|
summary.failureReason =
|
|
96
99
|
failedPhase?.error ??
|
|
97
100
|
r.abortReason ??
|
|
101
|
+
r.prCreationError ??
|
|
98
102
|
`${failedPhase?.phase ?? "phase"} failed`;
|
|
99
103
|
if (failedPhase?.verdict) {
|
|
100
104
|
summary.qaVerdict = String(failedPhase.verdict);
|
|
@@ -159,7 +163,12 @@ export function displaySummary(result, renderer) {
|
|
|
159
163
|
if (results.length === 0)
|
|
160
164
|
return;
|
|
161
165
|
const issueSummaries = results.map(toIssueSummary);
|
|
162
|
-
|
|
166
|
+
// #867: run wall clock, computed once by the orchestrator (RunResult), NOT
|
|
167
|
+
// `results.reduce(sum + durationSeconds)`. Under --parallel that sum
|
|
168
|
+
// double-counts overlapping issues and over-reports by ~the concurrency
|
|
169
|
+
// factor (a 36m 18s run printed 1h 12m at concurrency 3). The orchestrator is
|
|
170
|
+
// the only component that brackets the whole run, so it owns this value.
|
|
171
|
+
const totalSeconds = result.wallClockDurationSeconds;
|
|
163
172
|
if (renderer) {
|
|
164
173
|
renderer.renderSummary({
|
|
165
174
|
issues: issueSummaries,
|
|
@@ -176,6 +185,31 @@ export function displaySummary(result, renderer) {
|
|
|
176
185
|
dryRun: config.dryRun,
|
|
177
186
|
});
|
|
178
187
|
}
|
|
188
|
+
// #817: when `--ready-gate` ran, surface each issue's gate outcome (threshold
|
|
189
|
+
// reached vs guard halt) in the summary the same way `sequant ready` reports
|
|
190
|
+
// it — the "never merged" contract and stop reason are the human's cue to
|
|
191
|
+
// review and merge manually. Only issues that actually ran the gate carry it.
|
|
192
|
+
//
|
|
193
|
+
// A gate that *failed* is listed here too. It is non-fatal (the PR still
|
|
194
|
+
// opens), but omitting it would make a run whose gate crashed look exactly
|
|
195
|
+
// like one that gated cleanly — the user opted into a second look and needs
|
|
196
|
+
// to know it never happened.
|
|
197
|
+
const gated = results.filter((r) => r.readyGate || r.readyGateError);
|
|
198
|
+
if (gated.length > 0) {
|
|
199
|
+
console.log(colors.muted(" Ready gate (#817) — never merged:"));
|
|
200
|
+
for (const r of gated) {
|
|
201
|
+
if (!r.readyGate) {
|
|
202
|
+
console.log(` #${r.issueNumber}: ${colors.warning("✗ gate did not run")} · ${r.readyGateError} · PR opened ungated — re-run \`sequant ready ${r.issueNumber}\``);
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
const g = r.readyGate;
|
|
206
|
+
const label = g.ready
|
|
207
|
+
? colors.success(`✓ ${g.reason}`)
|
|
208
|
+
: colors.warning(`⚠️ ${g.reason}`);
|
|
209
|
+
console.log(` #${r.issueNumber}: ${label} · policy \`${g.policy}\` · ${g.iterations} QA pass(es) · status \`${g.issueStatus}\``);
|
|
210
|
+
}
|
|
211
|
+
console.log("");
|
|
212
|
+
}
|
|
179
213
|
// #760: a chain link whose checkpoint commit failed keeps its own work but
|
|
180
214
|
// loses the recovery point resume depends on, and the per-issue warning has
|
|
181
215
|
// long scrolled past by now on a multi-hour chain. Restate it at the summary,
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Run flag normalization (#705) — keeps run.ts thin (#503 AC-2: <200 LOC).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
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
|
-
*
|
|
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
|
+
}
|
|
@@ -58,6 +58,18 @@ export function buildProgressWiring(args) {
|
|
|
58
58
|
// #543: activity events only feed the TUI's nowLine — skip the line renderer.
|
|
59
59
|
if (event === "activity")
|
|
60
60
|
return;
|
|
61
|
+
// #804 AC-7: unlike activity, a waiting event MUST reach the renderer —
|
|
62
|
+
// it is the only signal during a pause that can last hours.
|
|
63
|
+
if (event === "waiting") {
|
|
64
|
+
renderer.onEvent({
|
|
65
|
+
issue,
|
|
66
|
+
phase,
|
|
67
|
+
event,
|
|
68
|
+
text: extra?.text,
|
|
69
|
+
wakeAtMs: extra?.wakeAtMs,
|
|
70
|
+
});
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
61
73
|
// #624 Item 3: pass the outer-loop iteration through so the renderer can
|
|
62
74
|
// render `(attempt N/M)` / `loop N/M`.
|
|
63
75
|
renderer.onEvent({
|
|
@@ -71,9 +83,22 @@ export function buildProgressWiring(args) {
|
|
|
71
83
|
};
|
|
72
84
|
}
|
|
73
85
|
else if (heartbeat) {
|
|
74
|
-
onProgress = (issue, phase, event) => {
|
|
86
|
+
onProgress = (issue, phase, event, extra) => {
|
|
75
87
|
if (event === "activity")
|
|
76
88
|
return;
|
|
89
|
+
// #804 AC-7: mark/clear the auto-wait so the heartbeat reports the wait
|
|
90
|
+
// and, critically, does not report it as a stall. Must be handled before
|
|
91
|
+
// the `else` below, which would otherwise `stop()` the phase entirely and
|
|
92
|
+
// leave a multi-hour wait with no liveness signal at all.
|
|
93
|
+
if (event === "waiting") {
|
|
94
|
+
if (extra?.wakeAtMs !== undefined) {
|
|
95
|
+
heartbeat.pauseForWait({ issueNumber: issue, phase }, extra.wakeAtMs);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
heartbeat.resumeFromWait({ issueNumber: issue, phase });
|
|
99
|
+
}
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
77
102
|
if (event === "start")
|
|
78
103
|
heartbeat.start({
|
|
79
104
|
issueNumber: issue,
|
package/dist/src/commands/run.js
CHANGED
|
@@ -8,15 +8,21 @@ import { parseBatches } from "../lib/workflow/batch-executor.js";
|
|
|
8
8
|
import { RunOrchestrator } from "../lib/workflow/run-orchestrator.js";
|
|
9
9
|
import { displayConfig, displaySummary } from "./run-display.js";
|
|
10
10
|
import { buildProgressWiring } from "./run-progress.js";
|
|
11
|
-
import { normalizeQualityLoop, resolveTuiEnabled } from "./run-flags.js";
|
|
11
|
+
import { normalizeQualityLoop, resolveTuiEnabled, warnDeprecatedFlags, } from "./run-flags.js";
|
|
12
12
|
// Re-export public API for backwards compatibility
|
|
13
13
|
export * from "./run-compat.js";
|
|
14
|
+
// #848: red pre-flight rejection + non-zero exit (process.exitCode, not exit()).
|
|
15
|
+
function rejectPreflight(message) {
|
|
16
|
+
console.log(chalk.red(message));
|
|
17
|
+
process.exitCode = 1;
|
|
18
|
+
}
|
|
14
19
|
/** Parse CLI args → validate → delegate to RunOrchestrator.run() → display summary. */
|
|
15
20
|
export async function runCommand(issues, options) {
|
|
16
21
|
// #705: fold the hidden `-q` alias into qualityLoop before any consumer reads
|
|
17
22
|
// it (`-q` no longer maps to --quiet, which moved to `-s`). See run-flags.ts.
|
|
18
23
|
options.qualityLoop = normalizeQualityLoop(options);
|
|
19
24
|
console.log(ui.headerBox("SEQUANT WORKFLOW"));
|
|
25
|
+
warnDeprecatedFlags(options);
|
|
20
26
|
if (!options.quiet) {
|
|
21
27
|
try {
|
|
22
28
|
const v = await checkVersionCached();
|
|
@@ -31,31 +37,26 @@ export async function runCommand(issues, options) {
|
|
|
31
37
|
}
|
|
32
38
|
const manifest = await getManifest();
|
|
33
39
|
if (!manifest) {
|
|
34
|
-
|
|
40
|
+
rejectPreflight("❌ Sequant is not initialized. Run `sequant init` first.");
|
|
35
41
|
return;
|
|
36
42
|
}
|
|
37
43
|
const settings = await getSettings();
|
|
38
44
|
// #605: --stacked implies --chain; reject explicit --no-chain combo before
|
|
39
45
|
// we evaluate any --chain-dependent constraint below.
|
|
40
46
|
if (options.stacked && options.chain === false) {
|
|
41
|
-
|
|
47
|
+
rejectPreflight("❌ --stacked cannot be combined with --no-chain");
|
|
42
48
|
return;
|
|
43
49
|
}
|
|
44
50
|
if (options.stacked) {
|
|
45
51
|
options.chain = true;
|
|
46
52
|
}
|
|
47
|
-
// Validate constraints
|
|
48
53
|
if (options.chain && options.batch?.length) {
|
|
49
|
-
|
|
54
|
+
rejectPreflight("❌ --chain cannot be used with --batch");
|
|
50
55
|
return;
|
|
51
56
|
}
|
|
52
57
|
if (options.concurrency !== undefined &&
|
|
53
58
|
(options.concurrency < 1 || !Number.isInteger(options.concurrency))) {
|
|
54
|
-
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
if (options.qaGate && !options.chain) {
|
|
58
|
-
console.log(chalk.red("❌ --qa-gate requires --chain flag"));
|
|
59
|
+
rejectPreflight(`❌ Invalid --concurrency value: ${options.concurrency}. Must be a positive integer.`);
|
|
59
60
|
return;
|
|
60
61
|
}
|
|
61
62
|
let batches = null;
|
|
@@ -27,10 +27,13 @@ export async function stateInitCommand(options = {}) {
|
|
|
27
27
|
const result = await discoverUntrackedWorktrees(discoverOptions);
|
|
28
28
|
if (options.json) {
|
|
29
29
|
console.log(JSON.stringify(result, null, 2));
|
|
30
|
+
if (!result.success)
|
|
31
|
+
process.exitCode = 1;
|
|
30
32
|
return;
|
|
31
33
|
}
|
|
32
34
|
if (!result.success) {
|
|
33
35
|
console.log(chalk.red(`✗ Discovery failed: ${result.error}`));
|
|
36
|
+
process.exitCode = 1;
|
|
34
37
|
return;
|
|
35
38
|
}
|
|
36
39
|
if (result.discovered.length === 0) {
|
|
@@ -106,6 +109,7 @@ export async function stateRebuildCommand(options = {}) {
|
|
|
106
109
|
else {
|
|
107
110
|
console.log(chalk.red(`✗ Log rebuild failed: ${logResult.error}`));
|
|
108
111
|
}
|
|
112
|
+
process.exitCode = 1;
|
|
109
113
|
return;
|
|
110
114
|
}
|
|
111
115
|
// Step 2: Discover and add untracked worktrees
|
|
@@ -204,10 +208,13 @@ export async function stateCleanCommand(options = {}) {
|
|
|
204
208
|
});
|
|
205
209
|
if (options.json) {
|
|
206
210
|
console.log(JSON.stringify(result, null, 2));
|
|
211
|
+
if (!result.success)
|
|
212
|
+
process.exitCode = 1;
|
|
207
213
|
return;
|
|
208
214
|
}
|
|
209
215
|
if (!result.success) {
|
|
210
216
|
console.log(chalk.red(`✗ Cleanup failed: ${result.error}`));
|
|
217
|
+
process.exitCode = 1;
|
|
211
218
|
return;
|
|
212
219
|
}
|
|
213
220
|
const orphanedCount = result.orphaned.length;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* sequant status - Show version, configuration, and workflow state
|
|
3
3
|
*/
|
|
4
|
+
import { type IssueState } from "../lib/workflow/state-schema.js";
|
|
4
5
|
export interface StatusCommandOptions {
|
|
5
6
|
/** Show only issues state */
|
|
6
7
|
issues?: boolean;
|
|
@@ -21,4 +22,12 @@ export interface StatusCommandOptions {
|
|
|
21
22
|
/** Skip GitHub API queries (offline mode) */
|
|
22
23
|
offline?: boolean;
|
|
23
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Format a single issue state for display
|
|
27
|
+
*
|
|
28
|
+
* @internal Exported for testing only (#892: the windowHalt/autoWait pause
|
|
29
|
+
* lines are display contract — a halted issue must read as resumable, not as
|
|
30
|
+
* a bare failure).
|
|
31
|
+
*/
|
|
32
|
+
export declare function formatIssueState(issue: IssueState): string;
|
|
24
33
|
export declare function statusCommand(options?: StatusCommandOptions): Promise<void>;
|