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
|
@@ -2,28 +2,29 @@
|
|
|
2
2
|
* Chain resume planning (#760).
|
|
3
3
|
*
|
|
4
4
|
* When a `--chain` run fails mid-way, earlier links may already be complete
|
|
5
|
-
* (`
|
|
5
|
+
* (see `completed-status.ts`) with a checkpoint commit on their feature branch
|
|
6
6
|
* (`createCheckpointCommit`, worktree-manager.ts). Re-running the same chain
|
|
7
7
|
* should skip that completed prefix and resume at the first incomplete link,
|
|
8
8
|
* rebased onto the last completed link's committed tip — NOT `main` (which is
|
|
9
9
|
* the #748 wrong-base failure this reuses the #748 rebase path to avoid).
|
|
10
10
|
*
|
|
11
|
-
* The
|
|
12
|
-
*
|
|
13
|
-
* dropping the completed prefix leaves the first incomplete
|
|
14
|
-
* where `executeSequential`'s successor-rebase never fires, so
|
|
15
|
-
* builds on `main`. This module computes a *chain-correct* resume
|
|
16
|
-
* preserves the completed prefix as the resume base.
|
|
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
17
|
*
|
|
18
18
|
* The planner is pure over an injected {@link CompletedLinkResolver} so the
|
|
19
19
|
* skip/fail-fast state machine (AC-3) is unit-testable without real git; the
|
|
20
20
|
* real-git rebase is covered by the integration test.
|
|
21
21
|
*/
|
|
22
|
+
import { type CompletedIssueStatus } from "./completed-status.js";
|
|
22
23
|
/** A completed link that will be skipped (not re-executed) on resume. */
|
|
23
24
|
export interface ChainResumeSkip {
|
|
24
25
|
issueNumber: number;
|
|
25
26
|
/** Why it was skipped — the terminal-ish status that made it complete. */
|
|
26
|
-
status:
|
|
27
|
+
status: CompletedIssueStatus;
|
|
27
28
|
/** The link's local feature branch (from state), if known. */
|
|
28
29
|
branch?: string;
|
|
29
30
|
/** The link's worktree path (from state), if known — used for the dirty check. */
|
|
@@ -45,9 +46,10 @@ export interface ChainResumePlan {
|
|
|
45
46
|
/** First incomplete issue number (the resume point), if any. */
|
|
46
47
|
resumeIssue?: number;
|
|
47
48
|
/**
|
|
48
|
-
* Set when resume cannot proceed safely (AC-3):
|
|
49
|
-
* branch/checkpoint is gone
|
|
50
|
-
*
|
|
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.
|
|
51
53
|
*/
|
|
52
54
|
failFast?: string;
|
|
53
55
|
/** True when every link in the chain is already complete. */
|
|
@@ -2,24 +2,24 @@
|
|
|
2
2
|
* Chain resume planning (#760).
|
|
3
3
|
*
|
|
4
4
|
* When a `--chain` run fails mid-way, earlier links may already be complete
|
|
5
|
-
* (`
|
|
5
|
+
* (see `completed-status.ts`) with a checkpoint commit on their feature branch
|
|
6
6
|
* (`createCheckpointCommit`, worktree-manager.ts). Re-running the same chain
|
|
7
7
|
* should skip that completed prefix and resume at the first incomplete link,
|
|
8
8
|
* rebased onto the last completed link's committed tip — NOT `main` (which is
|
|
9
9
|
* the #748 wrong-base failure this reuses the #748 rebase path to avoid).
|
|
10
10
|
*
|
|
11
|
-
* The
|
|
12
|
-
*
|
|
13
|
-
* dropping the completed prefix leaves the first incomplete
|
|
14
|
-
* where `executeSequential`'s successor-rebase never fires, so
|
|
15
|
-
* builds on `main`. This module computes a *chain-correct* resume
|
|
16
|
-
* preserves the completed prefix as the resume base.
|
|
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
17
|
*
|
|
18
18
|
* The planner is pure over an injected {@link CompletedLinkResolver} so the
|
|
19
19
|
* skip/fail-fast state machine (AC-3) is unit-testable without real git; the
|
|
20
20
|
* real-git rebase is covered by the integration test.
|
|
21
21
|
*/
|
|
22
|
-
|
|
22
|
+
import { isCompletedIssueStatus, } from "./completed-status.js";
|
|
23
23
|
/**
|
|
24
24
|
* Compute a chain-correct resume plan.
|
|
25
25
|
*
|
|
@@ -37,7 +37,7 @@ export function computeChainResumePlan(orderedLinks, baseBranch, resolver) {
|
|
|
37
37
|
const skipped = [];
|
|
38
38
|
let firstIncomplete = 0;
|
|
39
39
|
for (const link of orderedLinks) {
|
|
40
|
-
if (
|
|
40
|
+
if (isCompletedIssueStatus(link.status)) {
|
|
41
41
|
skipped.push({
|
|
42
42
|
issueNumber: link.issueNumber,
|
|
43
43
|
status: link.status,
|
|
@@ -72,17 +72,18 @@ export function computeChainResumePlan(orderedLinks, baseBranch, resolver) {
|
|
|
72
72
|
resumeBaseCommit = resolver.resolveBaseTip();
|
|
73
73
|
}
|
|
74
74
|
else {
|
|
75
|
-
// ready_for_merge: the checkpoint tip lives only
|
|
76
|
-
// branch. If that branch is gone (worktree/branch
|
|
77
|
-
// tip is unreconstructable — fail fast instead of
|
|
78
|
-
// successor onto main (which would miss the completed
|
|
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).
|
|
79
80
|
if (!last.branch) {
|
|
80
81
|
return {
|
|
81
82
|
skipped,
|
|
82
83
|
active,
|
|
83
84
|
resumeIssue: active[0],
|
|
84
85
|
allComplete: false,
|
|
85
|
-
failFast: `#${last.issueNumber} is
|
|
86
|
+
failFast: `#${last.issueNumber} is ${last.status} but no branch is recorded in state — ` +
|
|
86
87
|
`cannot reconstruct the resume base. Re-run with --force to redo the chain from scratch.`,
|
|
87
88
|
};
|
|
88
89
|
}
|
|
@@ -93,24 +94,27 @@ export function computeChainResumePlan(orderedLinks, baseBranch, resolver) {
|
|
|
93
94
|
active,
|
|
94
95
|
resumeIssue: active[0],
|
|
95
96
|
allComplete: false,
|
|
96
|
-
failFast: `#${last.issueNumber} is
|
|
97
|
+
failFast: `#${last.issueNumber} is ${last.status} but its branch "${last.branch}" no longer exists — ` +
|
|
97
98
|
`the resume base is unreconstructable. Re-run with --force to redo the chain from scratch.`,
|
|
98
99
|
};
|
|
99
100
|
}
|
|
100
101
|
// The branch exists, but a tip is only a valid resume base if it actually
|
|
101
102
|
// contains the link's work. `createCheckpointCommit` sweeps trailing
|
|
102
103
|
// uncommitted changes into a checkpoint commit; when it fails (commit hook,
|
|
103
|
-
// staging error, or unrelated dirty files)
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
//
|
|
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.
|
|
107
111
|
if (last.worktree && resolver.isWorktreeDirty(last.worktree)) {
|
|
108
112
|
return {
|
|
109
113
|
skipped,
|
|
110
114
|
active,
|
|
111
115
|
resumeIssue: active[0],
|
|
112
116
|
allComplete: false,
|
|
113
|
-
failFast: `#${last.issueNumber} is
|
|
117
|
+
failFast: `#${last.issueNumber} is ${last.status} but its worktree has uncommitted changes — ` +
|
|
114
118
|
`its checkpoint commit never landed, so branch "${last.branch}" is missing that work and ` +
|
|
115
119
|
`resuming #${active[0]} here would build on an incomplete base. Commit them in ${last.worktree}, ` +
|
|
116
120
|
`or re-run with --force to redo the chain from scratch.`,
|
|
@@ -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;
|
|
@@ -0,0 +1,57 @@
|
|
|
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 const COMPLETED_ISSUE_STATUSES = [
|
|
42
|
+
"ready_for_merge",
|
|
43
|
+
"merged",
|
|
44
|
+
"waiting_for_human_merge",
|
|
45
|
+
];
|
|
46
|
+
const COMPLETED_ISSUE_STATUS_SET = new Set(COMPLETED_ISSUE_STATUSES);
|
|
47
|
+
/**
|
|
48
|
+
* True iff `status` marks the issue as already finished, so a re-run should
|
|
49
|
+
* skip it unless `--force` is passed.
|
|
50
|
+
*
|
|
51
|
+
* Accepts `string | undefined` because persisted state may predate the current
|
|
52
|
+
* schema or be absent entirely; an unknown or missing status is treated as
|
|
53
|
+
* incomplete (re-execute), never as complete.
|
|
54
|
+
*/
|
|
55
|
+
export function isCompletedIssueStatus(status) {
|
|
56
|
+
return status !== undefined && COMPLETED_ISSUE_STATUS_SET.has(status);
|
|
57
|
+
}
|
|
@@ -42,6 +42,57 @@ export declare function normalizeCommanderOptions(options: RunOptions): RunOptio
|
|
|
42
42
|
* This replaces the inline merging logic previously in run.ts.
|
|
43
43
|
*/
|
|
44
44
|
export declare function resolveRunOptions(cliOptions: RunOptions, settings: SequantSettings): RunOptions;
|
|
45
|
+
/**
|
|
46
|
+
* Fall back to `fallback` unless `value` is a usable positive number.
|
|
47
|
+
*
|
|
48
|
+
* `??` alone is not enough for these (#833). `NaN` is not nullish, so a
|
|
49
|
+
* malformed value survives `?? default` and flows on into `setTimeout`, which
|
|
50
|
+
* clamps a `NaN` delay to 0 and aborts the phase on its first tick, or into
|
|
51
|
+
* `while (iteration < maxIterations)`, which is false on entry and runs zero
|
|
52
|
+
* phases. Both read as a phase/agent fault rather than a bad input, which is
|
|
53
|
+
* what makes the silent version expensive. Non-finite and non-positive values
|
|
54
|
+
* therefore fall back *to* the default instead of through it.
|
|
55
|
+
*
|
|
56
|
+
* `bin/cli.ts` rejects these at the flag boundary with a message naming the
|
|
57
|
+
* flag — that is the user-facing fix. This is the structural backstop for
|
|
58
|
+
* programmatic callers, `settings.json`, and whatever calls this next.
|
|
59
|
+
*
|
|
60
|
+
* Exported because `phaseTimeout` has two producers, not one: this module and
|
|
61
|
+
* `commands/ready.ts`, whose value reaches the driver through
|
|
62
|
+
* `ready-gate.ts`'s own `buildPhaseConfig` and never passes through
|
|
63
|
+
* `buildExecutionConfig`. Guarding only here would have left that path open.
|
|
64
|
+
* Chain it to express the layering — CLI, then settings, then the default:
|
|
65
|
+
*
|
|
66
|
+
* ```ts
|
|
67
|
+
* positiveOr(options.timeout, positiveOr(settings.run.timeout, DEFAULT))
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export declare function positiveOr(value: number | undefined, fallback: number): number;
|
|
71
|
+
/** A single phase's resolved model/effort override (#914). */
|
|
72
|
+
export interface PhasePolicy {
|
|
73
|
+
model?: string;
|
|
74
|
+
effort?: string;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Parse a `--models`/`--efforts` CLI spec into a phase → value map.
|
|
78
|
+
*
|
|
79
|
+
* Grammar: a bare value (`"sonnet"`) applies to every phase and resolves to
|
|
80
|
+
* `{"*": "sonnet"}`; a comma list of `phase=value` pairs (`"spec=fable,exec=sonnet"`)
|
|
81
|
+
* resolves per phase. Mixing the two forms, an empty phase/value, or an
|
|
82
|
+
* unrecognized phase name all fail fast — this is the CLI boundary, so a
|
|
83
|
+
* malformed spec must never silently resolve to "nothing configured".
|
|
84
|
+
*/
|
|
85
|
+
export declare function parsePhaseSpec(spec: string, phaseNames: string[]): Record<string, string>;
|
|
86
|
+
/**
|
|
87
|
+
* Resolve per-phase model/effort policies with CLI > settings > absent
|
|
88
|
+
* precedence.
|
|
89
|
+
*
|
|
90
|
+
* This is the single resolver both `buildExecutionConfig` (here) and
|
|
91
|
+
* `ready-gate.ts:buildPhaseConfig` call, so they cannot drift the way the
|
|
92
|
+
* two `phaseTimeout` producers did in #833 — see `positiveOr`'s doc comment
|
|
93
|
+
* for that history.
|
|
94
|
+
*/
|
|
95
|
+
export declare function resolvePhasePolicies(cliModels: string | undefined, cliEfforts: string | undefined, settingsPhases: Record<string, PhasePolicy> | undefined, phaseNames: string[]): Record<string, PhasePolicy>;
|
|
45
96
|
/**
|
|
46
97
|
* Build an ExecutionConfig from merged RunOptions and settings.
|
|
47
98
|
* Extracts the phase-timeout, MCP, retry, and mode resolution logic
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { DEFAULT_CONFIG, DEFAULT_PHASES, } from "./types.js";
|
|
10
10
|
import { getEnvConfig } from "./batch-executor.js";
|
|
11
|
+
import { getPhaseNames } from "./phase-registry.js";
|
|
11
12
|
/**
|
|
12
13
|
* Coerce an env-var string to the type of the default value.
|
|
13
14
|
* Returns the string as-is if no default exists for type inference.
|
|
@@ -97,6 +98,10 @@ export function normalizeCommanderOptions(options) {
|
|
|
97
98
|
...(raw.retry === false && { noRetry: true }),
|
|
98
99
|
...(raw.rebase === false && { noRebase: true }),
|
|
99
100
|
...(raw.pr === false && { noPr: true }),
|
|
101
|
+
// #804: map the flag-derived key onto the interface field. Guarded on
|
|
102
|
+
// `undefined` (not truthiness) so an explicit `--auto-wait 0` still
|
|
103
|
+
// overrides a non-zero setting.
|
|
104
|
+
...(raw.autoWait !== undefined && { autoWaitMinutes: raw.autoWait }),
|
|
100
105
|
};
|
|
101
106
|
}
|
|
102
107
|
/**
|
|
@@ -117,6 +122,7 @@ export function resolveRunOptions(cliOptions, settings) {
|
|
|
117
122
|
qualityLoop: defined.qualityLoop ?? settings.run.qualityLoop,
|
|
118
123
|
maxIterations: defined.maxIterations ?? settings.run.maxIterations,
|
|
119
124
|
noSmartTests: defined.noSmartTests ?? !settings.run.smartTests,
|
|
125
|
+
autoWaitMinutes: defined.autoWaitMinutes ?? settings.run.autoWaitMinutes,
|
|
120
126
|
// Agent settings
|
|
121
127
|
isolateParallel: defined.isolateParallel ?? settings.agents.isolateParallel,
|
|
122
128
|
// Env overrides
|
|
@@ -129,6 +135,116 @@ export function resolveRunOptions(cliOptions, settings) {
|
|
|
129
135
|
merged.autoDetectPhases = autoDetectPhases;
|
|
130
136
|
return merged;
|
|
131
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* Fall back to `fallback` unless `value` is a usable positive number.
|
|
140
|
+
*
|
|
141
|
+
* `??` alone is not enough for these (#833). `NaN` is not nullish, so a
|
|
142
|
+
* malformed value survives `?? default` and flows on into `setTimeout`, which
|
|
143
|
+
* clamps a `NaN` delay to 0 and aborts the phase on its first tick, or into
|
|
144
|
+
* `while (iteration < maxIterations)`, which is false on entry and runs zero
|
|
145
|
+
* phases. Both read as a phase/agent fault rather than a bad input, which is
|
|
146
|
+
* what makes the silent version expensive. Non-finite and non-positive values
|
|
147
|
+
* therefore fall back *to* the default instead of through it.
|
|
148
|
+
*
|
|
149
|
+
* `bin/cli.ts` rejects these at the flag boundary with a message naming the
|
|
150
|
+
* flag — that is the user-facing fix. This is the structural backstop for
|
|
151
|
+
* programmatic callers, `settings.json`, and whatever calls this next.
|
|
152
|
+
*
|
|
153
|
+
* Exported because `phaseTimeout` has two producers, not one: this module and
|
|
154
|
+
* `commands/ready.ts`, whose value reaches the driver through
|
|
155
|
+
* `ready-gate.ts`'s own `buildPhaseConfig` and never passes through
|
|
156
|
+
* `buildExecutionConfig`. Guarding only here would have left that path open.
|
|
157
|
+
* Chain it to express the layering — CLI, then settings, then the default:
|
|
158
|
+
*
|
|
159
|
+
* ```ts
|
|
160
|
+
* positiveOr(options.timeout, positiveOr(settings.run.timeout, DEFAULT))
|
|
161
|
+
* ```
|
|
162
|
+
*/
|
|
163
|
+
export function positiveOr(value, fallback) {
|
|
164
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0
|
|
165
|
+
? value
|
|
166
|
+
: fallback;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Parse a `--models`/`--efforts` CLI spec into a phase → value map.
|
|
170
|
+
*
|
|
171
|
+
* Grammar: a bare value (`"sonnet"`) applies to every phase and resolves to
|
|
172
|
+
* `{"*": "sonnet"}`; a comma list of `phase=value` pairs (`"spec=fable,exec=sonnet"`)
|
|
173
|
+
* resolves per phase. Mixing the two forms, an empty phase/value, or an
|
|
174
|
+
* unrecognized phase name all fail fast — this is the CLI boundary, so a
|
|
175
|
+
* malformed spec must never silently resolve to "nothing configured".
|
|
176
|
+
*/
|
|
177
|
+
export function parsePhaseSpec(spec, phaseNames) {
|
|
178
|
+
const trimmed = spec.trim();
|
|
179
|
+
if (!trimmed) {
|
|
180
|
+
throw new Error("Malformed spec: value is empty.");
|
|
181
|
+
}
|
|
182
|
+
if (!trimmed.includes("=")) {
|
|
183
|
+
return { "*": trimmed };
|
|
184
|
+
}
|
|
185
|
+
const result = {};
|
|
186
|
+
for (const segment of trimmed.split(",")) {
|
|
187
|
+
const eq = segment.indexOf("=");
|
|
188
|
+
if (eq === -1) {
|
|
189
|
+
throw new Error(`Malformed spec segment '${segment}' — expected 'phase=value' (cannot mix a bare value with phase=value pairs).`);
|
|
190
|
+
}
|
|
191
|
+
const phase = segment.slice(0, eq).trim();
|
|
192
|
+
const value = segment.slice(eq + 1).trim();
|
|
193
|
+
if (!phase || !value) {
|
|
194
|
+
throw new Error(`Malformed spec segment '${segment}' — both phase and value are required.`);
|
|
195
|
+
}
|
|
196
|
+
if (!phaseNames.includes(phase)) {
|
|
197
|
+
throw new Error(`Unknown phase '${phase}'. Available phases: ${phaseNames.join(", ")}.`);
|
|
198
|
+
}
|
|
199
|
+
result[phase] = value;
|
|
200
|
+
}
|
|
201
|
+
return result;
|
|
202
|
+
}
|
|
203
|
+
/** Apply a parsed phase-spec map onto a policy accumulator for one field. */
|
|
204
|
+
function applyPhaseSpec(target, parsed, field, phaseNames) {
|
|
205
|
+
const wildcard = parsed["*"];
|
|
206
|
+
if (wildcard !== undefined) {
|
|
207
|
+
for (const phase of phaseNames) {
|
|
208
|
+
target[phase] = { ...target[phase], [field]: wildcard };
|
|
209
|
+
}
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
for (const [phase, value] of Object.entries(parsed)) {
|
|
213
|
+
target[phase] = { ...target[phase], [field]: value };
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Resolve per-phase model/effort policies with CLI > settings > absent
|
|
218
|
+
* precedence.
|
|
219
|
+
*
|
|
220
|
+
* This is the single resolver both `buildExecutionConfig` (here) and
|
|
221
|
+
* `ready-gate.ts:buildPhaseConfig` call, so they cannot drift the way the
|
|
222
|
+
* two `phaseTimeout` producers did in #833 — see `positiveOr`'s doc comment
|
|
223
|
+
* for that history.
|
|
224
|
+
*/
|
|
225
|
+
export function resolvePhasePolicies(cliModels, cliEfforts, settingsPhases, phaseNames) {
|
|
226
|
+
const result = {};
|
|
227
|
+
// Layer 1 (lowest): settings.run.phases. Skip any phase name settings
|
|
228
|
+
// validation already didn't recognize — that's surfaced as a settings
|
|
229
|
+
// warning at load time (AC-1), not a resolver-time failure.
|
|
230
|
+
if (settingsPhases) {
|
|
231
|
+
for (const [phase, policy] of Object.entries(settingsPhases)) {
|
|
232
|
+
if (!phaseNames.includes(phase))
|
|
233
|
+
continue;
|
|
234
|
+
result[phase] = { ...policy };
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
// Layer 2 (highest): CLI --models/--efforts. Malformed specs throw here —
|
|
238
|
+
// callers at the CLI boundary (cli-flags.ts) turn that into a fail-fast
|
|
239
|
+
// InvalidArgumentError.
|
|
240
|
+
if (cliModels) {
|
|
241
|
+
applyPhaseSpec(result, parsePhaseSpec(cliModels, phaseNames), "model", phaseNames);
|
|
242
|
+
}
|
|
243
|
+
if (cliEfforts) {
|
|
244
|
+
applyPhaseSpec(result, parsePhaseSpec(cliEfforts, phaseNames), "effort", phaseNames);
|
|
245
|
+
}
|
|
246
|
+
return result;
|
|
247
|
+
}
|
|
132
248
|
/**
|
|
133
249
|
* Build an ExecutionConfig from merged RunOptions and settings.
|
|
134
250
|
* Extracts the phase-timeout, MCP, retry, and mode resolution logic
|
|
@@ -157,15 +273,40 @@ export function buildExecutionConfig(mergedOptions, settings, issueCount) {
|
|
|
157
273
|
parallel: isParallel,
|
|
158
274
|
dryRun: mergedOptions.dryRun ?? false,
|
|
159
275
|
verbose: mergedOptions.verbose ?? false,
|
|
160
|
-
|
|
276
|
+
// #833: chained so the layering is CLI → settings → default, matching
|
|
277
|
+
// `commands/ready.ts`. A malformed CLI value falls back to the user's
|
|
278
|
+
// configured setting rather than skipping past it to the hardcoded
|
|
279
|
+
// default; a malformed setting falls back to the default. In the normal
|
|
280
|
+
// path `mergedOptions` already carries the settings value, so this only
|
|
281
|
+
// differs when one of the two layers is unusable — which is the case that
|
|
282
|
+
// matters.
|
|
283
|
+
phaseTimeout: positiveOr(mergedOptions.timeout, positiveOr(settings.run.timeout, DEFAULT_CONFIG.phaseTimeout)),
|
|
161
284
|
qualityLoop: mergedOptions.qualityLoop ?? false,
|
|
162
|
-
maxIterations: mergedOptions.maxIterations
|
|
285
|
+
maxIterations: positiveOr(mergedOptions.maxIterations, positiveOr(settings.run.maxIterations, DEFAULT_CONFIG.maxIterations)),
|
|
163
286
|
noSmartTests: mergedOptions.noSmartTests ?? false,
|
|
164
287
|
mcp: mcpEnabled,
|
|
165
288
|
retry: retryEnabled,
|
|
289
|
+
// #804: default 0 (off) — the whole regression contract for auto-wait is
|
|
290
|
+
// that an unset flag leaves the #761/#799 halt path untouched.
|
|
291
|
+
autoWaitMinutes: mergedOptions.autoWaitMinutes ??
|
|
292
|
+
settings.run.autoWaitMinutes ??
|
|
293
|
+
DEFAULT_CONFIG.autoWaitMinutes,
|
|
166
294
|
agent: mergedOptions.agent ?? settings.run.agent,
|
|
167
295
|
aiderSettings: settings.run.aider,
|
|
168
296
|
isolateParallel: mergedOptions.isolateParallel,
|
|
169
297
|
relayEnabled,
|
|
298
|
+
// #817: opt-in only, no settings backing (AC-4). A missing `--ready-gate`
|
|
299
|
+
// resolves to false, keeping the run path byte-identical (AC-5). This is the
|
|
300
|
+
// load-bearing wire the #795 inert-flag class guards against — the flag is
|
|
301
|
+
// useless if it stops reaching the executor here.
|
|
302
|
+
readyGate: mergedOptions.readyGate ?? false,
|
|
303
|
+
// #914: CLI > settings > absent, via the shared resolver both
|
|
304
|
+
// ExecutionConfig producers call (see `resolvePhasePolicies`'s doc
|
|
305
|
+
// comment for the #833 drift this guards against).
|
|
306
|
+
phasePolicies: resolvePhasePolicies(mergedOptions.models, mergedOptions.efforts, settings.run.phases, getPhaseNames()),
|
|
307
|
+
// #915: CLI > settings > default `false` — mirrors the `readyGate`
|
|
308
|
+
// precedent above. Both `ExecutionConfig` producers (here and
|
|
309
|
+
// `ready-gate.ts:buildPhaseConfig`) resolve this the same way (#833).
|
|
310
|
+
effortEscalation: mergedOptions.escalateEffort ?? settings.run.effortEscalation ?? false,
|
|
170
311
|
};
|
|
171
312
|
}
|
|
@@ -49,6 +49,21 @@ export interface AgentExecutionConfig {
|
|
|
49
49
|
onStderr?: (text: string) => void;
|
|
50
50
|
/** Relevant files for the phase (used by file-oriented drivers like Aider) */
|
|
51
51
|
files?: string[];
|
|
52
|
+
/**
|
|
53
|
+
* Claude model to use for this phase (#914). Forwarded verbatim to the
|
|
54
|
+
* Agent SDK `query()` options by ClaudeCodeDriver; ignored by drivers
|
|
55
|
+
* without a model concept (Aider uses its own `AiderSettings.model`).
|
|
56
|
+
* Absent by default — the SDK falls back to the CLI default model.
|
|
57
|
+
*/
|
|
58
|
+
model?: string;
|
|
59
|
+
/**
|
|
60
|
+
* Reasoning effort for this phase (#914). Forwarded verbatim to the Agent
|
|
61
|
+
* SDK `query()` options by ClaudeCodeDriver; ignored by drivers without an
|
|
62
|
+
* effort concept. Absent by default — the SDK defaults to `high`. Matches
|
|
63
|
+
* the SDK's own closed `EffortLevel` enum, not a bare `string`, so a value
|
|
64
|
+
* that reaches this field type-checks against `query()`'s options.
|
|
65
|
+
*/
|
|
66
|
+
effort?: "low" | "medium" | "high" | "xhigh" | "max";
|
|
52
67
|
}
|
|
53
68
|
/**
|
|
54
69
|
* Result returned by an agent after executing a phase.
|
|
@@ -98,6 +113,13 @@ export interface AgentPhaseResult {
|
|
|
98
113
|
export interface AgentDriver {
|
|
99
114
|
/** Human-readable name for logging */
|
|
100
115
|
name: string;
|
|
116
|
+
/**
|
|
117
|
+
* True when this driver executes phases by resolving slash-command skills
|
|
118
|
+
* from the project's `.claude/skills/` directory (claude-code). Drivers
|
|
119
|
+
* whose prompts do the work inline (aider's `driverOverrides` templates)
|
|
120
|
+
* return false, and the `run` skills pre-flight (#813) is skipped for them.
|
|
121
|
+
*/
|
|
122
|
+
resolvesSkills: boolean;
|
|
101
123
|
/** Execute a phase prompt and return structured result */
|
|
102
124
|
executePhase(prompt: string, config: AgentExecutionConfig): Promise<AgentPhaseResult>;
|
|
103
125
|
/** Check if this driver is available/configured */
|
|
@@ -9,6 +9,8 @@ import type { AgentDriver, AgentExecutionConfig, AgentPhaseResult, ResumeHandle
|
|
|
9
9
|
import type { AiderSettings } from "../../settings.js";
|
|
10
10
|
export declare class AiderDriver implements AgentDriver {
|
|
11
11
|
name: string;
|
|
12
|
+
/** Aider phases use inline `driverOverrides` prompts — no skills (#813). */
|
|
13
|
+
resolvesSkills: boolean;
|
|
12
14
|
private settings?;
|
|
13
15
|
constructor(settings?: AiderSettings);
|
|
14
16
|
/**
|
|
@@ -10,6 +10,8 @@ import { execSync } from "child_process";
|
|
|
10
10
|
import { RingBuffer } from "../ring-buffer.js";
|
|
11
11
|
export class AiderDriver {
|
|
12
12
|
name = "aider";
|
|
13
|
+
/** Aider phases use inline `driverOverrides` prompts — no skills (#813). */
|
|
14
|
+
resolvesSkills = false;
|
|
13
15
|
settings;
|
|
14
16
|
constructor(settings) {
|
|
15
17
|
this.settings = settings;
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
import type { AgentDriver, AgentExecutionConfig, AgentPhaseResult, ResumeHandle } from "./agent-driver.js";
|
|
8
8
|
export declare class ClaudeCodeDriver implements AgentDriver {
|
|
9
9
|
name: string;
|
|
10
|
+
/** Phases run slash-command skills from `.claude/skills/` (#813). */
|
|
11
|
+
resolvesSkills: boolean;
|
|
10
12
|
/**
|
|
11
13
|
* Track session ID across calls so callers can implement resume.
|
|
12
14
|
* Set after each executePhase() call.
|
|
@@ -42,6 +44,18 @@ export declare class ClaudeCodeDriver implements AgentDriver {
|
|
|
42
44
|
* RateLimitError would wrongly re-enable the retry / MCP-fallback path. When
|
|
43
45
|
* the `rate_limit_event` is itself a billing failure its richer metadata
|
|
44
46
|
* (`canUserPurchaseCredits`, etc.) is preserved.
|
|
47
|
+
*
|
|
48
|
+
* Counter-exception (#860): when the `rate_limit_event` carries the FULL
|
|
49
|
+
* captured subscription-window shape — waitable window evidence (recognized
|
|
50
|
+
* window type + future `resetsAt`) *plus* the `out_of_credits` marker — the
|
|
51
|
+
* richer structured signal wins over the bare `billing_error` enum. A
|
|
52
|
+
* subscription plan's five-hour exhaustion surfaces assistant-side as a
|
|
53
|
+
* billing error, and overriding on that enum here would strip the metadata
|
|
54
|
+
* `--auto-wait` needs and re-inert the feature this classification exists
|
|
55
|
+
* to enable. Deliberately no wider than the evidenced shape: a pure
|
|
56
|
+
* throttle event (window, no billing markers) beside a `billing_error`
|
|
57
|
+
* enum keeps the #732 rule — billing wins — because there the two signals
|
|
58
|
+
* genuinely conflict and no capture justifies trusting the throttle.
|
|
45
59
|
*/
|
|
46
60
|
private buildStructuredError;
|
|
47
61
|
/**
|
|
@@ -6,10 +6,12 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
8
8
|
import { getMcpServersConfig } from "../../system.js";
|
|
9
|
-
import { RateLimitError, BillingError, createRateLimitError, isRateLimitFailureInfo, } from "../../errors.js";
|
|
9
|
+
import { RateLimitError, BillingError, createRateLimitError, isWaitableWindow, isRateLimitFailureInfo, } from "../../errors.js";
|
|
10
10
|
import { RingBuffer } from "../ring-buffer.js";
|
|
11
11
|
export class ClaudeCodeDriver {
|
|
12
12
|
name = "claude-code";
|
|
13
|
+
/** Phases run slash-command skills from `.claude/skills/` (#813). */
|
|
14
|
+
resolvesSkills = true;
|
|
13
15
|
/**
|
|
14
16
|
* Track session ID across calls so callers can implement resume.
|
|
15
17
|
* Set after each executePhase() call.
|
|
@@ -94,6 +96,11 @@ export class ClaudeCodeDriver {
|
|
|
94
96
|
...(resumeToken ? { resume: resumeToken } : {}),
|
|
95
97
|
env: config.env,
|
|
96
98
|
...(mcpServers ? { mcpServers } : {}),
|
|
99
|
+
// #914: per-phase model/effort override. Omitted entirely when
|
|
100
|
+
// unset (not `undefined`-valued) so the SDK's own default
|
|
101
|
+
// resolution is untouched — see the AC-3 key-presence test.
|
|
102
|
+
...(config.model ? { model: config.model } : {}),
|
|
103
|
+
...(config.effort ? { effort: config.effort } : {}),
|
|
97
104
|
stderr: (data) => {
|
|
98
105
|
capturedStderr += data;
|
|
99
106
|
// Split on newlines and push each line to the ring buffer
|
|
@@ -282,11 +289,27 @@ export class ClaudeCodeDriver {
|
|
|
282
289
|
* RateLimitError would wrongly re-enable the retry / MCP-fallback path. When
|
|
283
290
|
* the `rate_limit_event` is itself a billing failure its richer metadata
|
|
284
291
|
* (`canUserPurchaseCredits`, etc.) is preserved.
|
|
292
|
+
*
|
|
293
|
+
* Counter-exception (#860): when the `rate_limit_event` carries the FULL
|
|
294
|
+
* captured subscription-window shape — waitable window evidence (recognized
|
|
295
|
+
* window type + future `resetsAt`) *plus* the `out_of_credits` marker — the
|
|
296
|
+
* richer structured signal wins over the bare `billing_error` enum. A
|
|
297
|
+
* subscription plan's five-hour exhaustion surfaces assistant-side as a
|
|
298
|
+
* billing error, and overriding on that enum here would strip the metadata
|
|
299
|
+
* `--auto-wait` needs and re-inert the feature this classification exists
|
|
300
|
+
* to enable. Deliberately no wider than the evidenced shape: a pure
|
|
301
|
+
* throttle event (window, no billing markers) beside a `billing_error`
|
|
302
|
+
* enum keeps the #732 rule — billing wins — because there the two signals
|
|
303
|
+
* genuinely conflict and no capture justifies trusting the throttle.
|
|
285
304
|
*/
|
|
286
305
|
buildStructuredError(rateLimitInfo, assistantError, apiRetryError) {
|
|
287
306
|
if (rateLimitInfo) {
|
|
288
307
|
const err = createRateLimitError(rateLimitInfo);
|
|
289
|
-
|
|
308
|
+
const isCapturedSubscriptionShape = isWaitableWindow(rateLimitInfo) &&
|
|
309
|
+
rateLimitInfo.overageDisabledReason === "out_of_credits";
|
|
310
|
+
if (err instanceof RateLimitError &&
|
|
311
|
+
assistantError === "billing_error" &&
|
|
312
|
+
!isCapturedSubscriptionShape) {
|
|
290
313
|
return new BillingError("Billing error");
|
|
291
314
|
}
|
|
292
315
|
return err;
|