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
|
@@ -18,10 +18,17 @@ export function formatOutputs(result) {
|
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
20
|
* Format multiple issue results into combined outputs.
|
|
21
|
+
*
|
|
22
|
+
* @param wallClockDurationSeconds - The run's wall-clock duration, computed by
|
|
23
|
+
* whoever brackets the run (the orchestrator surfaces it as
|
|
24
|
+
* `RunResult.wallClockDurationSeconds`). It is a required argument rather than
|
|
25
|
+
* something derived here because the per-issue durations this function receives
|
|
26
|
+
* cannot express it: under `--parallel` they overlap, so summing them
|
|
27
|
+
* double-counts and over-reports by roughly the concurrency factor (#867).
|
|
21
28
|
*/
|
|
22
|
-
export function formatMultiOutputs(results) {
|
|
29
|
+
export function formatMultiOutputs(results, wallClockDurationSeconds) {
|
|
23
30
|
const allSuccess = results.every((r) => r.success);
|
|
24
|
-
const totalDuration =
|
|
31
|
+
const totalDuration = wallClockDurationSeconds;
|
|
25
32
|
const prUrls = results
|
|
26
33
|
.map((r) => r.prUrl)
|
|
27
34
|
.filter(Boolean)
|
|
@@ -54,8 +61,14 @@ export function outputCommands(outputs) {
|
|
|
54
61
|
}
|
|
55
62
|
/**
|
|
56
63
|
* Generate a GitHub Actions step summary (Markdown).
|
|
64
|
+
*
|
|
65
|
+
* @param wallClockDurationSeconds - The run's wall-clock duration. Required for
|
|
66
|
+
* the same reason as in {@link formatMultiOutputs}: the per-issue durations
|
|
67
|
+
* below overlap under `--parallel`, so the "Total duration" line cannot be
|
|
68
|
+
* derived by summing them (#867). Per-*phase* durations in the tables are
|
|
69
|
+
* unaffected — phases within an issue run sequentially.
|
|
57
70
|
*/
|
|
58
|
-
export function formatSummary(results) {
|
|
71
|
+
export function formatSummary(results, wallClockDurationSeconds) {
|
|
59
72
|
const lines = [];
|
|
60
73
|
lines.push("## Sequant Workflow Results\n");
|
|
61
74
|
for (const result of results) {
|
|
@@ -78,7 +91,6 @@ export function formatSummary(results) {
|
|
|
78
91
|
}
|
|
79
92
|
lines.push("");
|
|
80
93
|
}
|
|
81
|
-
|
|
82
|
-
lines.push(`**Total duration:** ${total}s`);
|
|
94
|
+
lines.push(`**Total duration:** ${wallClockDurationSeconds}s`);
|
|
83
95
|
return lines.join("\n");
|
|
84
96
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Commander argument coercions for numeric CLI flags.
|
|
3
|
+
*
|
|
4
|
+
* Lives here rather than inline in `bin/cli.ts` so it can be unit-tested
|
|
5
|
+
* directly — `bin/cli.ts` calls `program.parse()` at import time and cannot be
|
|
6
|
+
* imported from a test.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
/** Error-text shaping for a numeric flag. Affects messages only, not parsing. */
|
|
11
|
+
export interface WholeNumberOptions {
|
|
12
|
+
/** Smallest accepted value. Use 0 where 0 is a meaningful "off" setting. */
|
|
13
|
+
min: number;
|
|
14
|
+
/** Plural unit, e.g. `"seconds"`. Omit for a flag that reads better bare. */
|
|
15
|
+
unit?: string;
|
|
16
|
+
/** Singular unit, used when `min` is 1 ("at least 1 second"). */
|
|
17
|
+
unitSingular?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Build a commander coercion for a whole-number flag.
|
|
21
|
+
*
|
|
22
|
+
* A bare `parseInt` is unsafe for these: `parseInt("abc", 10)` is `NaN`, which
|
|
23
|
+
* is not nullish and so survives a `?? default`, and `parseInt("30m", 10)`
|
|
24
|
+
* silently yields `30` — the user asked for 30 minutes and got 30 seconds.
|
|
25
|
+
* Requiring the whole string to be digits rejects both instead of accepting a
|
|
26
|
+
* value that is unusable or quietly wrong (#818, #833).
|
|
27
|
+
*/
|
|
28
|
+
export declare function parseWholeNumber(flag: string, opts: WholeNumberOptions): (value: string) => number;
|
|
29
|
+
/**
|
|
30
|
+
* A positive-integer *seconds* flag — the #831 shape, preserved verbatim so the
|
|
31
|
+
* `merge --watch` messages this originally shipped with do not change.
|
|
32
|
+
*/
|
|
33
|
+
export declare function parsePositiveSeconds(flag: string): (value: string) => number;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Commander argument coercions for numeric CLI flags.
|
|
3
|
+
*
|
|
4
|
+
* Lives here rather than inline in `bin/cli.ts` so it can be unit-tested
|
|
5
|
+
* directly — `bin/cli.ts` calls `program.parse()` at import time and cannot be
|
|
6
|
+
* imported from a test.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import { InvalidArgumentError } from "commander";
|
|
11
|
+
/**
|
|
12
|
+
* Build a commander coercion for a whole-number flag.
|
|
13
|
+
*
|
|
14
|
+
* A bare `parseInt` is unsafe for these: `parseInt("abc", 10)` is `NaN`, which
|
|
15
|
+
* is not nullish and so survives a `?? default`, and `parseInt("30m", 10)`
|
|
16
|
+
* silently yields `30` — the user asked for 30 minutes and got 30 seconds.
|
|
17
|
+
* Requiring the whole string to be digits rejects both instead of accepting a
|
|
18
|
+
* value that is unusable or quietly wrong (#818, #833).
|
|
19
|
+
*/
|
|
20
|
+
export function parseWholeNumber(flag, opts) {
|
|
21
|
+
const { min, unit, unitSingular } = opts;
|
|
22
|
+
const ofUnit = unit ? ` of ${unit}` : "";
|
|
23
|
+
const minUnit = min === 1 ? (unitSingular ?? unit) : unit;
|
|
24
|
+
const minSuffix = minUnit ? ` ${minUnit}` : "";
|
|
25
|
+
return (value) => {
|
|
26
|
+
if (!/^\d+$/.test(value.trim())) {
|
|
27
|
+
throw new InvalidArgumentError(`${flag} expects a whole number${ofUnit} (got '${value}').`);
|
|
28
|
+
}
|
|
29
|
+
const parsed = Number(value);
|
|
30
|
+
if (!Number.isSafeInteger(parsed) || parsed < min) {
|
|
31
|
+
throw new InvalidArgumentError(`${flag} must be at least ${min}${minSuffix} (got '${value}').`);
|
|
32
|
+
}
|
|
33
|
+
return parsed;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* A positive-integer *seconds* flag — the #831 shape, preserved verbatim so the
|
|
38
|
+
* `merge --watch` messages this originally shipped with do not change.
|
|
39
|
+
*/
|
|
40
|
+
export function parsePositiveSeconds(flag) {
|
|
41
|
+
return parseWholeNumber(flag, {
|
|
42
|
+
min: 1,
|
|
43
|
+
unit: "seconds",
|
|
44
|
+
unitSingular: "second",
|
|
45
|
+
});
|
|
46
|
+
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* The renderer is event-driven: a `ProgressEvent` flows in and the renderer
|
|
5
5
|
* decides whether to update the live zone, append an event line, or both.
|
|
6
6
|
*/
|
|
7
|
-
export type ProgressEventKind = "start" | "complete" | "failed";
|
|
7
|
+
export type ProgressEventKind = "start" | "complete" | "failed" | "waiting";
|
|
8
8
|
/** Raw event from batch-executor `emitProgressLine` / `onProgress` callbacks. */
|
|
9
9
|
export interface ProgressEvent {
|
|
10
10
|
issue: number;
|
|
@@ -19,15 +19,34 @@ export interface ProgressEvent {
|
|
|
19
19
|
* cell as `loop N/M` (#624 Item 3).
|
|
20
20
|
*/
|
|
21
21
|
iteration?: number;
|
|
22
|
+
/**
|
|
23
|
+
* `"waiting"` (#804): display message for an in-progress auto-wait.
|
|
24
|
+
*/
|
|
25
|
+
text?: string;
|
|
26
|
+
/**
|
|
27
|
+
* `"waiting"` (#804): epoch ms at which the auto-wait ends. Absent on the
|
|
28
|
+
* terminal notice, which returns the phase to `running`.
|
|
29
|
+
*/
|
|
30
|
+
wakeAtMs?: number;
|
|
22
31
|
}
|
|
23
32
|
/** Per-phase status tracked inside the renderer state machine. */
|
|
24
33
|
export interface PhaseState {
|
|
25
34
|
name: string;
|
|
26
|
-
|
|
35
|
+
/**
|
|
36
|
+
* `"waiting"` (#804) is a *paused* running phase, not a terminal state: the
|
|
37
|
+
* phase spawn has failed on an exhausted rate-limit window and the executor
|
|
38
|
+
* is sleeping until it reopens. It returns to `"running"` on wake.
|
|
39
|
+
*/
|
|
40
|
+
status: "pending" | "running" | "done" | "failed" | "waiting";
|
|
27
41
|
startedAt?: number;
|
|
28
42
|
durationMs?: number;
|
|
29
43
|
/** Loop iteration label (e.g. "loop 2/3"). */
|
|
30
44
|
loopIteration?: number;
|
|
45
|
+
/**
|
|
46
|
+
* #804: epoch ms at which an in-progress auto-wait ends. Set only while
|
|
47
|
+
* `status === "waiting"`; cleared on resume.
|
|
48
|
+
*/
|
|
49
|
+
wakeAtMs?: number;
|
|
31
50
|
/**
|
|
32
51
|
* #624 Item 4: normalized signature of the most recent failure for THIS
|
|
33
52
|
* phase (ANSI-stripped, lowercased, first 80 chars, trimmed). Per-phase so
|
|
@@ -191,11 +210,6 @@ export interface RenderOptions {
|
|
|
191
210
|
* tracks the configured maximum instead of being hardcoded. Defaults to 3.
|
|
192
211
|
*/
|
|
193
212
|
maxLoopIterations?: number;
|
|
194
|
-
/**
|
|
195
|
-
* When true, `renderSummary` is rendered even if no issues were registered.
|
|
196
|
-
* Default: false (matches existing displaySummary behaviour).
|
|
197
|
-
*/
|
|
198
|
-
alwaysRenderSummary?: boolean;
|
|
199
213
|
/**
|
|
200
214
|
* #647: inject a `log-update` instance (typically built via
|
|
201
215
|
* `createLogUpdate(stream)` against a custom stream). Used by the
|
|
@@ -88,6 +88,8 @@ export declare class NonTTYRenderer extends BaseRenderer {
|
|
|
88
88
|
private readonly columnsOverride?;
|
|
89
89
|
private readonly maxLoopIterations;
|
|
90
90
|
private lastEventAt;
|
|
91
|
+
/** #860 AC-6: `issue:phase` keys whose auto-wait start line was emitted. */
|
|
92
|
+
private readonly announcedWaits;
|
|
91
93
|
constructor(options: RenderOptions);
|
|
92
94
|
private getColumns;
|
|
93
95
|
private startHeartbeat;
|
|
@@ -95,6 +97,14 @@ export declare class NonTTYRenderer extends BaseRenderer {
|
|
|
95
97
|
tickHeartbeatNow(): void;
|
|
96
98
|
private tickHeartbeat;
|
|
97
99
|
protected afterEvent(event: ProgressEvent, state: IssueState): void;
|
|
100
|
+
/**
|
|
101
|
+
* Announce auto-wait transitions (#860 AC-6). Only the first notice of a
|
|
102
|
+
* wait and the terminal notice that ends it produce lines — the ~15s ticks
|
|
103
|
+
* in between are absorbed here (the periodic signal is `tickHeartbeat`).
|
|
104
|
+
* Before this, waiting events fell through `emitEventLine`'s failure branch
|
|
105
|
+
* and printed a spurious `✘` line per tick.
|
|
106
|
+
*/
|
|
107
|
+
private emitWaitTransitionLine;
|
|
98
108
|
private emitEventLine;
|
|
99
109
|
/** Append a single `\n`-terminated line with `[HH:MM:SS]` prefix. */
|
|
100
110
|
private emitLine;
|
|
@@ -19,6 +19,8 @@ import chalk from "chalk";
|
|
|
19
19
|
import logUpdate from "log-update";
|
|
20
20
|
import stringWidth from "string-width";
|
|
21
21
|
import { formatElapsedTime, formatTimestamp } from "./format.js";
|
|
22
|
+
import { formatResetTime } from "../errors.js";
|
|
23
|
+
import { pipelineHasFailed } from "../workflow/status-derivation.js";
|
|
22
24
|
const DEFAULT_LIVE_TICK_MS = 1000;
|
|
23
25
|
const DEFAULT_NON_TTY_HEARTBEAT_MS = 60_000;
|
|
24
26
|
const NARROW_TERMINAL_THRESHOLD = 80;
|
|
@@ -228,6 +230,23 @@ class BaseRenderer {
|
|
|
228
230
|
state.subStatus = undefined;
|
|
229
231
|
return;
|
|
230
232
|
}
|
|
233
|
+
// #804 AC-7: an auto-wait pauses a phase rather than ending it. Presence of
|
|
234
|
+
// `wakeAtMs` distinguishes an in-progress wait from the terminal notice
|
|
235
|
+
// that resumes it, so the live zone never strands a "waiting" cell.
|
|
236
|
+
if (event.event === "waiting") {
|
|
237
|
+
if (event.wakeAtMs !== undefined) {
|
|
238
|
+
phase.status = "waiting";
|
|
239
|
+
phase.wakeAtMs = event.wakeAtMs;
|
|
240
|
+
state.subStatus = event.text;
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
if (phase.status === "waiting")
|
|
244
|
+
phase.status = "running";
|
|
245
|
+
phase.wakeAtMs = undefined;
|
|
246
|
+
state.subStatus = undefined;
|
|
247
|
+
}
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
231
250
|
if (event.event === "complete") {
|
|
232
251
|
phase.status = "done";
|
|
233
252
|
if (event.durationSeconds !== undefined) {
|
|
@@ -248,8 +267,13 @@ class BaseRenderer {
|
|
|
248
267
|
else if (phase.startedAt !== undefined) {
|
|
249
268
|
phase.durationMs = this.now() - phase.startedAt;
|
|
250
269
|
}
|
|
251
|
-
|
|
252
|
-
|
|
270
|
+
// #766: derive from the phase slots (loop excluded) instead of pinning
|
|
271
|
+
// `failed`, so a loop failure on an early quality-loop iteration doesn't
|
|
272
|
+
// stick after a later iteration recovers. Mirrors the orchestrator's card.
|
|
273
|
+
const nowFailed = pipelineHasFailed(state.phases);
|
|
274
|
+
state.status = nowFailed ? "failed" : "running";
|
|
275
|
+
if (nowFailed)
|
|
276
|
+
state.completedAt = this.now();
|
|
253
277
|
state.currentPhase = undefined;
|
|
254
278
|
if (event.error !== undefined)
|
|
255
279
|
state.failureReason = event.error;
|
|
@@ -267,20 +291,22 @@ class BaseRenderer {
|
|
|
267
291
|
}
|
|
268
292
|
/** Mark an issue done after PR is recorded — derived from phase completion. */
|
|
269
293
|
maybeMarkIssueDone(state) {
|
|
270
|
-
if (state.status === "failed")
|
|
271
|
-
return;
|
|
272
294
|
const allTerminal = state.phases.every((p) => p.status === "done" || p.status === "failed");
|
|
273
295
|
if (allTerminal && state.phases.length > 0) {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
296
|
+
// #766: derive the verdict (loop excluded) so a run that failed the loop
|
|
297
|
+
// on an early iteration and then recovered every planned phase resolves
|
|
298
|
+
// to `done`. No early `failed` guard: a stale loop failure must be able
|
|
299
|
+
// to de-escalate once the pipeline recovers.
|
|
300
|
+
state.status = pipelineHasFailed(state.phases) ? "failed" : "done";
|
|
277
301
|
state.completedAt = this.now();
|
|
278
302
|
}
|
|
279
303
|
}
|
|
280
304
|
// ------------ Hooks for subclasses ------------
|
|
281
305
|
afterEvent(_event, state) {
|
|
282
|
-
|
|
283
|
-
|
|
306
|
+
// #766: always re-derive — `maybeMarkIssueDone` guards internally and must
|
|
307
|
+
// run even when `state.status` is currently `failed` so a recovered loop
|
|
308
|
+
// failure can de-escalate to `done`.
|
|
309
|
+
this.maybeMarkIssueDone(state);
|
|
284
310
|
this.afterStateChange();
|
|
285
311
|
}
|
|
286
312
|
afterStateChange() {
|
|
@@ -321,6 +347,8 @@ export class NonTTYRenderer extends BaseRenderer {
|
|
|
321
347
|
columnsOverride;
|
|
322
348
|
maxLoopIterations;
|
|
323
349
|
lastEventAt;
|
|
350
|
+
/** #860 AC-6: `issue:phase` keys whose auto-wait start line was emitted. */
|
|
351
|
+
announcedWaits = new Set();
|
|
324
352
|
constructor(options) {
|
|
325
353
|
super(options);
|
|
326
354
|
this.heartbeatMs =
|
|
@@ -359,15 +387,53 @@ export class NonTTYRenderer extends BaseRenderer {
|
|
|
359
387
|
return;
|
|
360
388
|
const parts = running.map((s) => {
|
|
361
389
|
const elapsedSec = s.startedAt !== undefined ? (this.now() - s.startedAt) / 1000 : 0;
|
|
390
|
+
// #860 AC-6: a phase paused on an auto-wait names its wake time instead
|
|
391
|
+
// of masquerading as ordinary running time — this heartbeat is the only
|
|
392
|
+
// periodic signal a non-TTY (background) run gets during a multi-hour
|
|
393
|
+
// wait, and without the wake time it is indistinguishable from a hang.
|
|
394
|
+
const waiting = s.phases.find((p) => p.name === s.currentPhase && p.status === "waiting");
|
|
395
|
+
if (waiting?.wakeAtMs !== undefined) {
|
|
396
|
+
const leftSec = Math.max(0, (waiting.wakeAtMs - this.now()) / 1000);
|
|
397
|
+
return `#${s.issueNumber} ${s.currentPhase} ⏸ rate-limit window — resuming at ${formatResetTime(waiting.wakeAtMs)} (${formatElapsedTime(leftSec)} left)`;
|
|
398
|
+
}
|
|
362
399
|
return `#${s.issueNumber} ${s.currentPhase} (${formatElapsedTime(elapsedSec)})`;
|
|
363
400
|
});
|
|
364
401
|
this.emitLine(`⏱ still running: ${parts.join(", ")}`);
|
|
365
402
|
}
|
|
366
403
|
afterEvent(event, state) {
|
|
367
404
|
super.afterEvent(event, state);
|
|
405
|
+
// #860 AC-6: waiting notices are liveness, not progress. The wait emits a
|
|
406
|
+
// tick every ~15s, so counting them as events would hold `lastEventAt`
|
|
407
|
+
// fresh forever and silence the heartbeat for the entire multi-hour wait.
|
|
408
|
+
if (event.event === "waiting") {
|
|
409
|
+
this.emitWaitTransitionLine(event);
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
368
412
|
this.lastEventAt = this.now();
|
|
369
413
|
this.emitEventLine(event, state);
|
|
370
414
|
}
|
|
415
|
+
/**
|
|
416
|
+
* Announce auto-wait transitions (#860 AC-6). Only the first notice of a
|
|
417
|
+
* wait and the terminal notice that ends it produce lines — the ~15s ticks
|
|
418
|
+
* in between are absorbed here (the periodic signal is `tickHeartbeat`).
|
|
419
|
+
* Before this, waiting events fell through `emitEventLine`'s failure branch
|
|
420
|
+
* and printed a spurious `✘` line per tick.
|
|
421
|
+
*/
|
|
422
|
+
emitWaitTransitionLine(event) {
|
|
423
|
+
const key = `${event.issue}:${event.phase}`;
|
|
424
|
+
const c = colorize(this.noColor);
|
|
425
|
+
if (event.wakeAtMs !== undefined) {
|
|
426
|
+
if (this.announcedWaits.has(key))
|
|
427
|
+
return;
|
|
428
|
+
this.announcedWaits.add(key);
|
|
429
|
+
this.emitLine(`${c.yellow("⏸")} #${event.issue} ${event.phase} ${event.text ?? `rate-limit window — resuming at ${formatResetTime(event.wakeAtMs)}`}`);
|
|
430
|
+
}
|
|
431
|
+
else {
|
|
432
|
+
if (!this.announcedWaits.delete(key))
|
|
433
|
+
return;
|
|
434
|
+
this.emitLine(`${c.cyan("▸")} #${event.issue} ${event.phase} ${event.text ?? "auto-wait complete — resuming"}`);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
371
437
|
emitEventLine(event, state) {
|
|
372
438
|
const c = colorize(this.noColor);
|
|
373
439
|
const phase = state.phases.find((p) => p.name === event.phase);
|
|
@@ -1127,6 +1193,10 @@ export class TTYRenderer extends BaseRenderer {
|
|
|
1127
1193
|
return c.green(`${p.name} ✔${p.durationMs ? ` ${formatElapsedTime(p.durationMs / 1000)}` : ""}`);
|
|
1128
1194
|
if (p.status === "failed")
|
|
1129
1195
|
return c.red(`${p.name} ✘`);
|
|
1196
|
+
// #804: a paused phase reads as `qa waiting 14:33`, so the wake time
|
|
1197
|
+
// is visible in the live zone itself, not only the sub-status line.
|
|
1198
|
+
if (p.status === "waiting")
|
|
1199
|
+
return c.yellow(`${p.name} waiting${p.wakeAtMs ? ` ${formatResetTime(p.wakeAtMs)}` : ""}`);
|
|
1130
1200
|
if (p.status === "running")
|
|
1131
1201
|
return c.cyan(`${p.name} running`);
|
|
1132
1202
|
// #672 AC-3: pending cells render as `name –` (en dash) so the live
|
package/dist/src/lib/errors.d.ts
CHANGED
|
@@ -145,17 +145,87 @@ export interface RateLimitInfoLike {
|
|
|
145
145
|
hasChargeableSavedPaymentMethod?: boolean;
|
|
146
146
|
}
|
|
147
147
|
/**
|
|
148
|
-
* True when the rate-limit info
|
|
149
|
-
*
|
|
148
|
+
* True when the rate-limit info describes an exhausted *window* that will
|
|
149
|
+
* reopen at a known future time: a recognized window `rateLimitType` plus a
|
|
150
|
+
* `resetsAt` still in the future (#860 AC-1).
|
|
151
|
+
*
|
|
152
|
+
* This is the discriminator between "subscription window closed until 07:00"
|
|
153
|
+
* and "account needs credits". The fields that would answer that directly
|
|
154
|
+
* (`canUserPurchaseCredits`, `hasChargeableSavedPaymentMethod`) are absent
|
|
155
|
+
* from every real captured payload, so the window shape is the proxy — and
|
|
156
|
+
* any unrecognized shape returns false (fail closed, #860 AC-3).
|
|
157
|
+
*
|
|
158
|
+
* Explicitly informational statuses (`allowed` / `allowed_warning`) are never
|
|
159
|
+
* waitable. The driver retains marker-carrying warnings as failure-grade
|
|
160
|
+
* (pre-#732 semantics, unchanged), so a stale "you are nearing your limit"
|
|
161
|
+
* warning can be misattributed to an unrelated phase failure — classifying it
|
|
162
|
+
* waitable would upgrade that misattribution from a cheap immediate halt to a
|
|
163
|
+
* multi-hour sleep. The 26 real captures carry no `status` field at all, so
|
|
164
|
+
* absent status stays waitable; only a status that affirmatively says
|
|
165
|
+
* "not a rejection" is excluded.
|
|
166
|
+
*/
|
|
167
|
+
export declare function isWaitableWindow(info: RateLimitInfoLike, now?: number): boolean;
|
|
168
|
+
/**
|
|
169
|
+
* Vocabulary-drift telemetry for the #860 fail-closed path: returns a hint
|
|
170
|
+
* string when a payload was terminal ONLY because its `rateLimitType` is not
|
|
171
|
+
* in the recognized window allowlist — i.e. it carries `out_of_credits` plus a
|
|
172
|
+
* live future `resetsAt` and a *present but unrecognized* window type, and no
|
|
173
|
+
* explicit `credits_required`. If Anthropic renames or adds a window type,
|
|
174
|
+
* the halt message names the rejected type instead of silently reading as an
|
|
175
|
+
* ordinary wallet failure. A missing `rateLimitType` is NOT drift evidence
|
|
176
|
+
* (API-account payloads may legitimately omit it), so no hint fires there.
|
|
150
177
|
*/
|
|
151
|
-
export declare function
|
|
178
|
+
export declare function unrecognizedWindowHint(info: RateLimitInfoLike, now?: number): string | null;
|
|
179
|
+
/**
|
|
180
|
+
* True when the rate-limit info represents a billing/credits failure (which
|
|
181
|
+
* waiting cannot fix), rather than a transient throttle or an exhausted
|
|
182
|
+
* window.
|
|
183
|
+
*
|
|
184
|
+
* Narrowed by #860: a subscription plan hitting its five-hour cap with
|
|
185
|
+
* overage disabled emits `overageDisabledReason: "out_of_credits"` *plus* a
|
|
186
|
+
* window type and a live `resetsAt` — a pause, not a wallet failure. That
|
|
187
|
+
* shape is excluded here so it classifies as a retryable {@link RateLimitError}
|
|
188
|
+
* and `--auto-wait` (#804) can act on it. An explicit
|
|
189
|
+
* `errorCode: "credits_required"` stays terminal even alongside window
|
|
190
|
+
* evidence — it is the SDK's direct "purchase needed" signal (#860 AC-2).
|
|
191
|
+
* Anything short of the full recognized window shape remains terminal
|
|
192
|
+
* (fail closed, #860 AC-3).
|
|
193
|
+
*/
|
|
194
|
+
export declare function isBillingFailure(info: RateLimitInfoLike, now?: number): boolean;
|
|
152
195
|
/**
|
|
153
196
|
* True when the rate-limit info represents an actual failure (rejection or
|
|
154
197
|
* billing), as opposed to an informational `allowed` / `allowed_warning`
|
|
155
198
|
* event. The driver uses this to avoid mis-attributing a stale warning event
|
|
156
199
|
* to an unrelated phase failure.
|
|
200
|
+
*
|
|
201
|
+
* Built on the raw billing *markers*, not the #860-narrowed classification:
|
|
202
|
+
* the captured five-hour payloads cannot prove they carried
|
|
203
|
+
* `status: "rejected"`, so narrowing here could silently drop the very events
|
|
204
|
+
* #860 exists to keep (they'd fall back to a metadata-less assistant error and
|
|
205
|
+
* auto-wait would stay inert). Retention semantics are unchanged; only the
|
|
206
|
+
* billing-vs-waitable *classification* narrowed.
|
|
157
207
|
*/
|
|
158
208
|
export declare function isRateLimitFailureInfo(info: RateLimitInfoLike): boolean;
|
|
209
|
+
/**
|
|
210
|
+
* Normalize a `resetsAt` timestamp to milliseconds. The SDK does not pin the
|
|
211
|
+
* unit, so use the same heuristic everywhere a `resetsAt` is compared or
|
|
212
|
+
* displayed: values below ~1e12 are seconds, otherwise milliseconds.
|
|
213
|
+
*/
|
|
214
|
+
export declare function resetsAtToMs(resetsAt: number): number;
|
|
215
|
+
/**
|
|
216
|
+
* Format a Unix timestamp (seconds or ms) as a local time string.
|
|
217
|
+
*
|
|
218
|
+
* Bare `HH:MM` when the reset falls on the current local calendar day;
|
|
219
|
+
* date-qualified `MM-DD HH:MM` otherwise. Also used for #804's auto-wait wake
|
|
220
|
+
* time (a wake is `resetsAt + buffer`, already in ms, which `resetsAtToMs`
|
|
221
|
+
* passes through unchanged) so both render in one convention.
|
|
222
|
+
*
|
|
223
|
+
* Multi-day windows
|
|
224
|
+
* (`rateLimitType: seven_day*`) can reset days out — a bare `HH:MM` there reads
|
|
225
|
+
* as "later today" and misleads the user (#732 QA follow-up), so the date is
|
|
226
|
+
* included whenever the reset is not today.
|
|
227
|
+
*/
|
|
228
|
+
export declare function formatResetTime(resetsAt: number): string;
|
|
159
229
|
/**
|
|
160
230
|
* Build a user-facing message from rate-limit info, naming the real cause:
|
|
161
231
|
* - billing/credits → "Out of credits" (enriched with purchasable vs hard
|
|
@@ -163,14 +233,22 @@ export declare function isRateLimitFailureInfo(info: RateLimitInfoLike): boolean
|
|
|
163
233
|
* - transient throttle → "Rate limited — resets at HH:MM" (date-qualified as
|
|
164
234
|
* "MM-DD HH:MM" when the reset is not today; reset time omitted entirely when
|
|
165
235
|
* `resetsAt` is absent)
|
|
236
|
+
*
|
|
237
|
+
* `now` feeds the #860 waitable-window classification so message and error
|
|
238
|
+
* type are derived against the same instant (and tests can pin the clock).
|
|
239
|
+
* A waitable window renders through the rate-limited branch — its reset time
|
|
240
|
+
* is the actionable fact; "Out of credits" would misname a pause as a wallet
|
|
241
|
+
* failure.
|
|
166
242
|
*/
|
|
167
|
-
export declare function formatRateLimitMessage(info: RateLimitInfoLike): string;
|
|
243
|
+
export declare function formatRateLimitMessage(info: RateLimitInfoLike, now?: number): string;
|
|
168
244
|
/**
|
|
169
245
|
* Construct the appropriate typed error from structured rate-limit info.
|
|
170
246
|
* Billing/credits failures become a non-retryable {@link BillingError};
|
|
171
|
-
* transient throttles
|
|
247
|
+
* transient throttles AND exhausted-but-reopening windows (#860) become a
|
|
248
|
+
* retryable {@link RateLimitError}. `now` pins the waitable-window check to
|
|
249
|
+
* one instant across message and classification.
|
|
172
250
|
*/
|
|
173
|
-
export declare function createRateLimitError(info: RateLimitInfoLike): RateLimitError | BillingError;
|
|
251
|
+
export declare function createRateLimitError(info: RateLimitInfoLike, now?: number): RateLimitError | BillingError;
|
|
174
252
|
/**
|
|
175
253
|
* Map of error type names to their constructors.
|
|
176
254
|
* Used for deserialization from logs.
|