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
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* Async interface methods delegate to the sync implementations.
|
|
10
10
|
*/
|
|
11
11
|
import type { PlatformProvider, Issue, CreatePROptions, PRInfo, PRStatus, Comment } from "./platform-provider.js";
|
|
12
|
+
import type { AnnotatedCheck } from "../../qa/infra-blocked-ci.js";
|
|
12
13
|
/**
|
|
13
14
|
* PR merge status values (matches the casing returned by `gh pr view`).
|
|
14
15
|
*/
|
|
@@ -55,6 +56,30 @@ export interface BatchGitHubResult {
|
|
|
55
56
|
pullRequests: Record<number, BatchPRInfo>;
|
|
56
57
|
error?: string;
|
|
57
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* A single entry in a PR's `statusCheckRollup`.
|
|
61
|
+
*
|
|
62
|
+
* `gh pr view N --json statusCheckRollup` returns a heterogeneous array of two
|
|
63
|
+
* shapes, discriminated by `__typename`:
|
|
64
|
+
* - `CheckRun` (GitHub Actions / Checks API): progress in `status`
|
|
65
|
+
* (QUEUED | IN_PROGRESS | COMPLETED) with the outcome in `conclusion`.
|
|
66
|
+
* - `StatusContext` (legacy commit statuses): combined state in `state`
|
|
67
|
+
* (EXPECTED | PENDING | SUCCESS | FAILURE | ERROR).
|
|
68
|
+
* Only the fields the watch loop consumes are typed; unknown fields are ignored.
|
|
69
|
+
*/
|
|
70
|
+
export interface RollupEntry {
|
|
71
|
+
__typename?: string;
|
|
72
|
+
name?: string;
|
|
73
|
+
context?: string;
|
|
74
|
+
/** CheckRun progress. */
|
|
75
|
+
status?: string;
|
|
76
|
+
/** CheckRun outcome. */
|
|
77
|
+
conclusion?: string;
|
|
78
|
+
/** StatusContext combined state. */
|
|
79
|
+
state?: string;
|
|
80
|
+
}
|
|
81
|
+
/** PR mergeability as reported by `gh pr view --json mergeable`. */
|
|
82
|
+
export type MergeableState = "MERGEABLE" | "CONFLICTING" | "UNKNOWN";
|
|
58
83
|
export declare class GitHubProvider implements PlatformProvider {
|
|
59
84
|
name: string;
|
|
60
85
|
/**
|
|
@@ -151,6 +176,45 @@ export declare class GitHubProvider implements PlatformProvider {
|
|
|
151
176
|
* Used by upstream/issues.ts.
|
|
152
177
|
*/
|
|
153
178
|
commentOnIssueWithBodyFileSync(repo: string, issueNumber: number, bodyFile: string): boolean;
|
|
179
|
+
/**
|
|
180
|
+
* Fetch a PR's status-check rollup via `statusCheckRollup`.
|
|
181
|
+
*
|
|
182
|
+
* Uses `statusCheckRollup`, NOT the known-broken `--json checks` field
|
|
183
|
+
* (#443 / #818). Returns the raw heterogeneous entries; callers classify
|
|
184
|
+
* terminal-ness with the helpers in `merge-check/watch.ts`. Returns `[]` on
|
|
185
|
+
* any error so a transient `gh` failure degrades to "no checks yet" rather
|
|
186
|
+
* than crashing a poll loop.
|
|
187
|
+
*/
|
|
188
|
+
getStatusCheckRollupSync(prNumber: number): RollupEntry[];
|
|
189
|
+
/**
|
|
190
|
+
* Fetch a PR's mergeability state via `--json mergeable`.
|
|
191
|
+
*
|
|
192
|
+
* Right after a push GitHub reports `UNKNOWN` while it recomputes, then
|
|
193
|
+
* settles to `MERGEABLE` or `CONFLICTING`. `merge --watch` treats
|
|
194
|
+
* `CONFLICTING` as a dispatch block: CI never starts against an unmergeable
|
|
195
|
+
* ref. Returns `UNKNOWN` on any error — the safe default that keeps the loop
|
|
196
|
+
* polling rather than falsely reporting a conflict.
|
|
197
|
+
*/
|
|
198
|
+
getMergeableStateSync(prNumber: number): MergeableState;
|
|
199
|
+
/**
|
|
200
|
+
* Get a PR's head commit SHA via `--json headRefOid`.
|
|
201
|
+
*
|
|
202
|
+
* Needed to query the commit's check-run annotations for the billing-lockout
|
|
203
|
+
* signature. Returns `null` on error.
|
|
204
|
+
*/
|
|
205
|
+
getPRHeadShaSync(prNumber: number): string | null;
|
|
206
|
+
/**
|
|
207
|
+
* Fetch check-run annotations for a commit, shaped for `detectInfraBlockedCi`.
|
|
208
|
+
*
|
|
209
|
+
* Lists the commit's check runs (per_page=100, no pagination needed for the
|
|
210
|
+
* billing-lockout case — every run carries the same annotation, so the first
|
|
211
|
+
* page is representative), then fetches annotations for each run that reports
|
|
212
|
+
* any. Used only when the whole board is failing, to recognise the "job was
|
|
213
|
+
* not started" signature (#820). Degrades to `[]` on any error (404, rate
|
|
214
|
+
* limit, revoked token) so a broken-CI situation never crashes the watch
|
|
215
|
+
* loop — `detectInfraBlockedCi` then reads that as "not infra-blocked".
|
|
216
|
+
*/
|
|
217
|
+
getCheckRunAnnotationsSync(headSha: string): AnnotatedCheck[];
|
|
154
218
|
fetchIssue(id: string): Promise<Issue>;
|
|
155
219
|
postComment(issueId: string, body: string): Promise<void>;
|
|
156
220
|
addLabel(issueId: string, label: string): Promise<void>;
|
|
@@ -414,6 +414,158 @@ export class GitHubProvider {
|
|
|
414
414
|
return false;
|
|
415
415
|
}
|
|
416
416
|
}
|
|
417
|
+
// ─── Watch / CI-rollup helpers (for `merge --watch`, #818) ─────────
|
|
418
|
+
/**
|
|
419
|
+
* Fetch a PR's status-check rollup via `statusCheckRollup`.
|
|
420
|
+
*
|
|
421
|
+
* Uses `statusCheckRollup`, NOT the known-broken `--json checks` field
|
|
422
|
+
* (#443 / #818). Returns the raw heterogeneous entries; callers classify
|
|
423
|
+
* terminal-ness with the helpers in `merge-check/watch.ts`. Returns `[]` on
|
|
424
|
+
* any error so a transient `gh` failure degrades to "no checks yet" rather
|
|
425
|
+
* than crashing a poll loop.
|
|
426
|
+
*/
|
|
427
|
+
getStatusCheckRollupSync(prNumber) {
|
|
428
|
+
try {
|
|
429
|
+
const result = spawnSync("gh", [
|
|
430
|
+
"pr",
|
|
431
|
+
"view",
|
|
432
|
+
String(prNumber),
|
|
433
|
+
"--json",
|
|
434
|
+
"statusCheckRollup",
|
|
435
|
+
"-q",
|
|
436
|
+
".statusCheckRollup",
|
|
437
|
+
], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 15000 });
|
|
438
|
+
if (result.status !== 0 || !result.stdout)
|
|
439
|
+
return [];
|
|
440
|
+
const parsed = JSON.parse(result.stdout);
|
|
441
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
442
|
+
}
|
|
443
|
+
catch {
|
|
444
|
+
return [];
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Fetch a PR's mergeability state via `--json mergeable`.
|
|
449
|
+
*
|
|
450
|
+
* Right after a push GitHub reports `UNKNOWN` while it recomputes, then
|
|
451
|
+
* settles to `MERGEABLE` or `CONFLICTING`. `merge --watch` treats
|
|
452
|
+
* `CONFLICTING` as a dispatch block: CI never starts against an unmergeable
|
|
453
|
+
* ref. Returns `UNKNOWN` on any error — the safe default that keeps the loop
|
|
454
|
+
* polling rather than falsely reporting a conflict.
|
|
455
|
+
*/
|
|
456
|
+
getMergeableStateSync(prNumber) {
|
|
457
|
+
try {
|
|
458
|
+
const result = spawnSync("gh", [
|
|
459
|
+
"pr",
|
|
460
|
+
"view",
|
|
461
|
+
String(prNumber),
|
|
462
|
+
"--json",
|
|
463
|
+
"mergeable",
|
|
464
|
+
"-q",
|
|
465
|
+
".mergeable",
|
|
466
|
+
], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 10000 });
|
|
467
|
+
if (result.status === 0 && result.stdout) {
|
|
468
|
+
const state = result.stdout.toString().trim().toUpperCase();
|
|
469
|
+
if (state === "MERGEABLE" ||
|
|
470
|
+
state === "CONFLICTING" ||
|
|
471
|
+
state === "UNKNOWN") {
|
|
472
|
+
return state;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
catch {
|
|
477
|
+
// fall through to UNKNOWN
|
|
478
|
+
}
|
|
479
|
+
return "UNKNOWN";
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Get a PR's head commit SHA via `--json headRefOid`.
|
|
483
|
+
*
|
|
484
|
+
* Needed to query the commit's check-run annotations for the billing-lockout
|
|
485
|
+
* signature. Returns `null` on error.
|
|
486
|
+
*/
|
|
487
|
+
getPRHeadShaSync(prNumber) {
|
|
488
|
+
try {
|
|
489
|
+
const result = spawnSync("gh", [
|
|
490
|
+
"pr",
|
|
491
|
+
"view",
|
|
492
|
+
String(prNumber),
|
|
493
|
+
"--json",
|
|
494
|
+
"headRefOid",
|
|
495
|
+
"-q",
|
|
496
|
+
".headRefOid",
|
|
497
|
+
], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 10000 });
|
|
498
|
+
if (result.status === 0 && result.stdout) {
|
|
499
|
+
const sha = result.stdout.toString().trim();
|
|
500
|
+
return sha || null;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
catch {
|
|
504
|
+
// fall through to null
|
|
505
|
+
}
|
|
506
|
+
return null;
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Fetch check-run annotations for a commit, shaped for `detectInfraBlockedCi`.
|
|
510
|
+
*
|
|
511
|
+
* Lists the commit's check runs (per_page=100, no pagination needed for the
|
|
512
|
+
* billing-lockout case — every run carries the same annotation, so the first
|
|
513
|
+
* page is representative), then fetches annotations for each run that reports
|
|
514
|
+
* any. Used only when the whole board is failing, to recognise the "job was
|
|
515
|
+
* not started" signature (#820). Degrades to `[]` on any error (404, rate
|
|
516
|
+
* limit, revoked token) so a broken-CI situation never crashes the watch
|
|
517
|
+
* loop — `detectInfraBlockedCi` then reads that as "not infra-blocked".
|
|
518
|
+
*/
|
|
519
|
+
getCheckRunAnnotationsSync(headSha) {
|
|
520
|
+
try {
|
|
521
|
+
const listResult = spawnSync("gh", [
|
|
522
|
+
"api",
|
|
523
|
+
`repos/{owner}/{repo}/commits/${headSha}/check-runs?per_page=100`,
|
|
524
|
+
"-q",
|
|
525
|
+
".check_runs",
|
|
526
|
+
], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 15000 });
|
|
527
|
+
if (listResult.status !== 0 || !listResult.stdout)
|
|
528
|
+
return [];
|
|
529
|
+
const runs = JSON.parse(listResult.stdout);
|
|
530
|
+
if (!Array.isArray(runs))
|
|
531
|
+
return [];
|
|
532
|
+
const annotated = [];
|
|
533
|
+
for (const run of runs) {
|
|
534
|
+
const checkName = typeof run?.name === "string" ? run.name : "unknown";
|
|
535
|
+
const id = run?.id;
|
|
536
|
+
if (typeof id !== "number" && typeof id !== "string")
|
|
537
|
+
continue;
|
|
538
|
+
// Skip runs that explicitly report zero annotations; keep fetching when
|
|
539
|
+
// the count is unknown, so a missing field never hides the signature.
|
|
540
|
+
const count = run?.output?.annotations_count;
|
|
541
|
+
if (typeof count === "number" && count === 0)
|
|
542
|
+
continue;
|
|
543
|
+
const annResult = spawnSync("gh", ["api", `repos/{owner}/{repo}/check-runs/${id}/annotations`], {
|
|
544
|
+
encoding: "utf-8",
|
|
545
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
546
|
+
timeout: 15000,
|
|
547
|
+
});
|
|
548
|
+
if (annResult.status !== 0 || !annResult.stdout) {
|
|
549
|
+
annotated.push({ checkName, annotations: [] });
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
try {
|
|
553
|
+
const annotations = JSON.parse(annResult.stdout);
|
|
554
|
+
annotated.push({
|
|
555
|
+
checkName,
|
|
556
|
+
annotations: Array.isArray(annotations) ? annotations : [],
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
catch {
|
|
560
|
+
annotated.push({ checkName, annotations: [] });
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
return annotated;
|
|
564
|
+
}
|
|
565
|
+
catch {
|
|
566
|
+
return [];
|
|
567
|
+
}
|
|
568
|
+
}
|
|
417
569
|
// ─── Async interface methods (PlatformProvider) ────────────────────
|
|
418
570
|
async fetchIssue(id) {
|
|
419
571
|
const result = spawnSync("gh", ["issue", "view", id, "--json", "number,title,body,labels,state"], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 15000 });
|
|
@@ -130,6 +130,8 @@ export interface QACacheOptions {
|
|
|
130
130
|
defaultTtl?: number;
|
|
131
131
|
/** Enable verbose logging */
|
|
132
132
|
verbose?: boolean;
|
|
133
|
+
/** Repository directory for git operations (default: process.cwd()) */
|
|
134
|
+
cwd?: string;
|
|
133
135
|
}
|
|
134
136
|
/**
|
|
135
137
|
* Result of cache lookup
|
|
@@ -153,6 +155,7 @@ export declare class QACache {
|
|
|
153
155
|
private cacheDir;
|
|
154
156
|
private defaultTtl;
|
|
155
157
|
private verbose;
|
|
158
|
+
private cwd;
|
|
156
159
|
private cachedState;
|
|
157
160
|
constructor(options?: QACacheOptions);
|
|
158
161
|
/**
|
|
@@ -160,9 +163,11 @@ export declare class QACache {
|
|
|
160
163
|
*/
|
|
161
164
|
getCachePath(): string;
|
|
162
165
|
/**
|
|
163
|
-
* Compute hash of git diff between
|
|
166
|
+
* Compute hash of git diff between the resolved base and HEAD
|
|
164
167
|
*
|
|
165
168
|
* This provides a content-based cache key that changes when code changes.
|
|
169
|
+
* The base is resolved via resolveDiffBase so a stale local main does not
|
|
170
|
+
* leak merged-upstream content into the cache key (#890).
|
|
166
171
|
*/
|
|
167
172
|
computeDiffHash(): string;
|
|
168
173
|
/**
|
|
@@ -27,8 +27,9 @@
|
|
|
27
27
|
import * as fs from "fs";
|
|
28
28
|
import * as path from "path";
|
|
29
29
|
import * as crypto from "crypto";
|
|
30
|
-
import {
|
|
30
|
+
import { execFileSync } from "child_process";
|
|
31
31
|
import { z } from "zod";
|
|
32
|
+
import { resolveDiffBase } from "./git-diff-utils.js";
|
|
32
33
|
/**
|
|
33
34
|
* Check types that can be cached
|
|
34
35
|
*/
|
|
@@ -74,7 +75,7 @@ export const CachedCheckResultSchema = z.object({
|
|
|
74
75
|
export const QARunContextSchema = z.object({
|
|
75
76
|
/** Git HEAD SHA at the time of QA completion */
|
|
76
77
|
lastQACommitSHA: z.string(),
|
|
77
|
-
/** Diff hash (
|
|
78
|
+
/** Diff hash (resolved base...HEAD) at the time of QA */
|
|
78
79
|
lastQADiffHash: z.string(),
|
|
79
80
|
/** Map of AC ID → status from the last QA run */
|
|
80
81
|
acStatuses: z.record(z.string(), z.enum(["met", "not_met", "partially_met", "pending", "blocked"])),
|
|
@@ -139,11 +140,13 @@ export class QACache {
|
|
|
139
140
|
cacheDir;
|
|
140
141
|
defaultTtl;
|
|
141
142
|
verbose;
|
|
143
|
+
cwd;
|
|
142
144
|
cachedState = null;
|
|
143
145
|
constructor(options = {}) {
|
|
144
146
|
this.cacheDir = options.cacheDir ?? DEFAULT_CACHE_DIR;
|
|
145
147
|
this.defaultTtl = options.defaultTtl ?? DEFAULT_TTL;
|
|
146
148
|
this.verbose = options.verbose ?? false;
|
|
149
|
+
this.cwd = options.cwd ?? process.cwd();
|
|
147
150
|
}
|
|
148
151
|
/**
|
|
149
152
|
* Get the cache file path
|
|
@@ -152,14 +155,18 @@ export class QACache {
|
|
|
152
155
|
return path.join(this.cacheDir, "cache.json");
|
|
153
156
|
}
|
|
154
157
|
/**
|
|
155
|
-
* Compute hash of git diff between
|
|
158
|
+
* Compute hash of git diff between the resolved base and HEAD
|
|
156
159
|
*
|
|
157
160
|
* This provides a content-based cache key that changes when code changes.
|
|
161
|
+
* The base is resolved via resolveDiffBase so a stale local main does not
|
|
162
|
+
* leak merged-upstream content into the cache key (#890).
|
|
158
163
|
*/
|
|
159
164
|
computeDiffHash() {
|
|
160
165
|
try {
|
|
161
|
-
// Get the diff content between
|
|
162
|
-
const
|
|
166
|
+
// Get the diff content between the resolved base and HEAD
|
|
167
|
+
const base = resolveDiffBase(this.cwd, "main");
|
|
168
|
+
const diff = execFileSync("git", ["diff", `${base}...HEAD`], {
|
|
169
|
+
cwd: this.cwd,
|
|
163
170
|
encoding: "utf-8",
|
|
164
171
|
maxBuffer: 10 * 1024 * 1024, // 10MB buffer
|
|
165
172
|
});
|
|
@@ -236,7 +243,9 @@ export class QACache {
|
|
|
236
243
|
*/
|
|
237
244
|
async checkGlobalInvalidation() {
|
|
238
245
|
try {
|
|
239
|
-
const
|
|
246
|
+
const base = resolveDiffBase(this.cwd, "main");
|
|
247
|
+
const changedFiles = execFileSync("git", ["diff", `${base}...HEAD`, "--name-only"], {
|
|
248
|
+
cwd: this.cwd,
|
|
240
249
|
encoding: "utf-8",
|
|
241
250
|
})
|
|
242
251
|
.trim()
|
|
@@ -263,7 +272,9 @@ export class QACache {
|
|
|
263
272
|
return false;
|
|
264
273
|
}
|
|
265
274
|
try {
|
|
266
|
-
const
|
|
275
|
+
const base = resolveDiffBase(this.cwd, "main");
|
|
276
|
+
const changedFiles = execFileSync("git", ["diff", `${base}...HEAD`, "--name-only"], {
|
|
277
|
+
cwd: this.cwd,
|
|
267
278
|
encoding: "utf-8",
|
|
268
279
|
})
|
|
269
280
|
.trim()
|
|
@@ -432,7 +443,8 @@ export class QACache {
|
|
|
432
443
|
return null;
|
|
433
444
|
}
|
|
434
445
|
try {
|
|
435
|
-
const diff =
|
|
446
|
+
const diff = execFileSync("git", ["diff", `${sinceCommit}...HEAD`], {
|
|
447
|
+
cwd: this.cwd,
|
|
436
448
|
encoding: "utf-8",
|
|
437
449
|
maxBuffer: 10 * 1024 * 1024,
|
|
438
450
|
});
|
|
@@ -460,7 +472,8 @@ export class QACache {
|
|
|
460
472
|
return null;
|
|
461
473
|
}
|
|
462
474
|
try {
|
|
463
|
-
const output =
|
|
475
|
+
const output = execFileSync("git", ["diff", `${sinceCommit}...HEAD`, "--name-only"], {
|
|
476
|
+
cwd: this.cwd,
|
|
464
477
|
encoding: "utf-8",
|
|
465
478
|
});
|
|
466
479
|
return output.trim().split("\n").filter(Boolean);
|
|
@@ -22,10 +22,12 @@
|
|
|
22
22
|
* lives in `src/commands/ready.ts`.
|
|
23
23
|
*/
|
|
24
24
|
import type { ExecutionConfig, PhaseResult, ProgressCallback } from "./types.js";
|
|
25
|
+
import { type EscalationRecord } from "./effort-escalation.js";
|
|
25
26
|
import type { QaVerdict } from "./run-log-schema.js";
|
|
26
27
|
import type { ReadyPolicy } from "../settings.js";
|
|
27
28
|
import type { IssueStatus } from "./state-schema.js";
|
|
28
29
|
import { type LoopProgressSnapshot } from "./qa-stagnation.js";
|
|
30
|
+
import { type ExecChangeState } from "./phase-executor.js";
|
|
29
31
|
export type { ReadyPolicy } from "../settings.js";
|
|
30
32
|
/**
|
|
31
33
|
* Why the gate stopped. Drives `ready`, the persisted issue status, and the
|
|
@@ -44,8 +46,20 @@ export type ReadyTerminalReason =
|
|
|
44
46
|
| "LOOP_NO_DIFF"
|
|
45
47
|
/** Guard: `/loop` phase itself failed. Needs human. */
|
|
46
48
|
| "LOOP_FAILED"
|
|
47
|
-
/** #534: zero-diff exec
|
|
48
|
-
| "NO_IMPLEMENTATION"
|
|
49
|
+
/** #534: zero-diff exec worktree — nothing was built. Not ready. */
|
|
50
|
+
| "NO_IMPLEMENTATION"
|
|
51
|
+
/**
|
|
52
|
+
* #879: the worktree is dirty but has no commits. Distinct from
|
|
53
|
+
* NO_IMPLEMENTATION — work exists, but uncommitted work cannot rebase, push,
|
|
54
|
+
* or become a PR, so it is not certifiable as-is. Re-run once it is committed.
|
|
55
|
+
*/
|
|
56
|
+
| "UNCOMMITTED_ONLY"
|
|
57
|
+
/**
|
|
58
|
+
* #853: QA ran but produced no verdict (deferred its one-shot turn, or
|
|
59
|
+
* output was unparseable). Distinct from NO_IMPLEMENTATION — the
|
|
60
|
+
* implementation may be complete; it is the *review* that is missing.
|
|
61
|
+
*/
|
|
62
|
+
| "NO_VERDICT";
|
|
49
63
|
/** A single gap surfaced by QA, classified for the report. */
|
|
50
64
|
export interface ReadyGapItem {
|
|
51
65
|
/** Gap description as surfaced by QA. */
|
|
@@ -77,6 +91,12 @@ export interface ReadyResult {
|
|
|
77
91
|
tokensUsed: number;
|
|
78
92
|
/** Human-readable markdown gap report (AC-4). */
|
|
79
93
|
report: string;
|
|
94
|
+
/**
|
|
95
|
+
* Effort escalations applied during this gate's QA-pass loop (#915),
|
|
96
|
+
* base+escalated tier per escalated `qa`/`loop` dispatch. Empty when
|
|
97
|
+
* `effortEscalation` is off or no dispatch escalated.
|
|
98
|
+
*/
|
|
99
|
+
effortEscalations: EscalationRecord[];
|
|
80
100
|
}
|
|
81
101
|
/**
|
|
82
102
|
* Thin phase-runner abstraction so the engine can be unit-tested without the
|
|
@@ -110,10 +130,31 @@ export interface RunReadyGateOptions {
|
|
|
110
130
|
onProgress?: ProgressCallback;
|
|
111
131
|
/** Injectable token reader — defaults to reading `<worktree>/.sequant`. */
|
|
112
132
|
readTokensUsed?: (worktreePath: string) => number;
|
|
113
|
-
/** Injectable change
|
|
114
|
-
|
|
133
|
+
/** Injectable change classifier — defaults to {@link classifyExecChanges}. */
|
|
134
|
+
classifyChangesFn?: (cwd: string) => ExecChangeState;
|
|
115
135
|
/** Injectable loop-progress snapshot — defaults to {@link snapshotLoopProgress}. */
|
|
116
136
|
snapshotFn?: (cwd: string) => LoopProgressSnapshot;
|
|
137
|
+
/**
|
|
138
|
+
* Resolved per-phase `model`/`effort` overrides (#914), keyed by phase
|
|
139
|
+
* name. Callers (e.g. `commands/ready.ts`) resolve this via
|
|
140
|
+
* `resolvePhasePolicies` — the same shared resolver `buildExecutionConfig`
|
|
141
|
+
* uses — so this producer cannot drift from that one (#833 class).
|
|
142
|
+
* `buildPhaseConfig` spreads it onto every `ExecutionConfig` it builds;
|
|
143
|
+
* `phase-executor.ts` applies the entry for the phase actually running.
|
|
144
|
+
*/
|
|
145
|
+
phasePolicies?: Record<string, {
|
|
146
|
+
model?: string;
|
|
147
|
+
effort?: string;
|
|
148
|
+
}>;
|
|
149
|
+
/**
|
|
150
|
+
* Evidence-based effort escalation on quality-loop retries (#915). Callers
|
|
151
|
+
* (e.g. `commands/ready.ts`) resolve this CLI > settings > `false`, the same
|
|
152
|
+
* precedence `buildExecutionConfig` uses for the `run` path (#833 class).
|
|
153
|
+
* `buildPhaseConfig` spreads it onto every `ExecutionConfig` it builds;
|
|
154
|
+
* `withEscalatedEffort` (`effort-escalation.ts`) reads it at each QA-pass
|
|
155
|
+
* dispatch to decide whether that specific `qa`/`loop` call escalates.
|
|
156
|
+
*/
|
|
157
|
+
effortEscalation?: boolean;
|
|
117
158
|
}
|
|
118
159
|
/**
|
|
119
160
|
* Pure exit predicate. Given a policy and a QA verdict, has the loop reached
|
|
@@ -21,8 +21,9 @@
|
|
|
21
21
|
* (out of scope for #683) can reuse `runReadyGate` directly. The command shell
|
|
22
22
|
* lives in `src/commands/ready.ts`.
|
|
23
23
|
*/
|
|
24
|
+
import { withEscalatedEffort, } from "./effort-escalation.js";
|
|
24
25
|
import { snapshotLoopProgress, compareLoopProgress, } from "./qa-stagnation.js";
|
|
25
|
-
import {
|
|
26
|
+
import { classifyExecChanges } from "./phase-executor.js";
|
|
26
27
|
import { readTokenUsageFiles, aggregateTokenUsage, TOKEN_USAGE_DIR, } from "./token-utils.js";
|
|
27
28
|
import * as path from "path";
|
|
28
29
|
/**
|
|
@@ -167,6 +168,11 @@ function buildPhaseConfig(opts, extra) {
|
|
|
167
168
|
dryRun: false,
|
|
168
169
|
mcp: opts.mcp,
|
|
169
170
|
retry: true,
|
|
171
|
+
// #914: producer 2 (see the doc comment on RunReadyGateOptions.phasePolicies
|
|
172
|
+
// for why this can't drift from buildExecutionConfig's own assignment).
|
|
173
|
+
phasePolicies: opts.phasePolicies,
|
|
174
|
+
// #915: producer 2 (see RunReadyGateOptions.effortEscalation).
|
|
175
|
+
effortEscalation: opts.effortEscalation,
|
|
170
176
|
...extra,
|
|
171
177
|
};
|
|
172
178
|
}
|
|
@@ -180,7 +186,11 @@ export function formatReadyReport(result) {
|
|
|
180
186
|
? "✅ READY — awaiting human merge decision"
|
|
181
187
|
: result.reason === "NO_IMPLEMENTATION"
|
|
182
188
|
? "⛔ NOT READY — no implementation detected"
|
|
183
|
-
:
|
|
189
|
+
: result.reason === "UNCOMMITTED_ONLY"
|
|
190
|
+
? "⛔ NOT READY — work is uncommitted"
|
|
191
|
+
: result.reason === "NO_VERDICT"
|
|
192
|
+
? "⛔ NOT READY — QA produced no verdict"
|
|
193
|
+
: "⚠️ NOT READY — needs human intervention";
|
|
184
194
|
const reasonText = {
|
|
185
195
|
AC_MET: "Acceptance Criteria are objectively met (no AC_NOT_MET). Remaining gaps are quality-only and reported below (policy `ac` does not auto-fix them).",
|
|
186
196
|
READY_FOR_MERGE: "QA returned READY_FOR_MERGE.",
|
|
@@ -188,7 +198,9 @@ export function formatReadyReport(result) {
|
|
|
188
198
|
TOKEN_BUDGET: "Token budget exhausted before reaching the policy threshold. A human should review the remaining gaps.",
|
|
189
199
|
LOOP_NO_DIFF: "The fix loop made no diff (stagnation guard). Manual intervention required.",
|
|
190
200
|
LOOP_FAILED: "The fix loop phase failed. A human should investigate before merging.",
|
|
191
|
-
NO_IMPLEMENTATION: "Zero-diff worktree
|
|
201
|
+
NO_IMPLEMENTATION: "Zero-diff worktree — there is nothing to certify (#534 guard).",
|
|
202
|
+
UNCOMMITTED_ONLY: "The worktree has uncommitted changes but no commits (#879 guard). Uncommitted work cannot rebase, push, or become a PR — commit it, then re-run the gate.",
|
|
203
|
+
NO_VERDICT: "QA ran but produced no verdict (deferred one-shot turn or unparseable output) — there is nothing to certify (#534/#853 guard). The implementation may exist; re-run the gate once QA emits a verdict.",
|
|
192
204
|
};
|
|
193
205
|
const lines = [];
|
|
194
206
|
lines.push(`## sequant ready — Issue #${result.issueNumber}`);
|
|
@@ -231,7 +243,7 @@ export function formatReadyReport(result) {
|
|
|
231
243
|
export async function runReadyGate(opts) {
|
|
232
244
|
const { issueNumber, worktreePath, policy, maxIterations, tokenBudget, nonGoals = [], } = opts;
|
|
233
245
|
const readTokensUsed = opts.readTokensUsed ?? defaultReadTokensUsed;
|
|
234
|
-
const
|
|
246
|
+
const classifyChangesFn = opts.classifyChangesFn ?? classifyExecChanges;
|
|
235
247
|
const snapshotFn = opts.snapshotFn ?? snapshotLoopProgress;
|
|
236
248
|
// #697: run a phase while emitting live-progress events around it. `iteration`
|
|
237
249
|
// is the 1-based QA-pass index so the renderer can render `loop N/M`. Emits
|
|
@@ -262,6 +274,9 @@ export async function runReadyGate(opts) {
|
|
|
262
274
|
const autoFixed = [];
|
|
263
275
|
let remaining = [];
|
|
264
276
|
let tokensUsed = 0;
|
|
277
|
+
// #915: escalated (base, escalated) tiers, one entry per QA-pass dispatch
|
|
278
|
+
// that actually escalated. Populated at the two dispatch sites below.
|
|
279
|
+
const effortEscalations = [];
|
|
265
280
|
const finish = (reason) => {
|
|
266
281
|
const ready = reason === "AC_MET" || reason === "READY_FOR_MERGE";
|
|
267
282
|
const issueStatus = ready
|
|
@@ -279,6 +294,7 @@ export async function runReadyGate(opts) {
|
|
|
279
294
|
remaining,
|
|
280
295
|
tokensUsed,
|
|
281
296
|
report: "",
|
|
297
|
+
effortEscalations,
|
|
282
298
|
};
|
|
283
299
|
result.report = formatReadyReport(result);
|
|
284
300
|
return result;
|
|
@@ -293,18 +309,33 @@ export async function runReadyGate(opts) {
|
|
|
293
309
|
return finish("TOKEN_BUDGET");
|
|
294
310
|
}
|
|
295
311
|
iterations++;
|
|
296
|
-
|
|
312
|
+
// #915: iterations > 1 means this QA pass is a retry of a prior
|
|
313
|
+
// unsatisfied verdict — the ready-gate's retry signal.
|
|
314
|
+
const qaEscalation = withEscalatedEffort(buildPhaseConfig(opts, { fullQa: true }), "qa", iterations > 1);
|
|
315
|
+
if (qaEscalation.record)
|
|
316
|
+
effortEscalations.push(qaEscalation.record);
|
|
317
|
+
const qaResult = await runPhaseTracked("qa", qaEscalation.config, iterations);
|
|
297
318
|
tokensUsed = readTokensUsed(worktreePath);
|
|
298
319
|
const verdict = qaResult.verdict ?? null;
|
|
299
|
-
// #534 guard: a null
|
|
320
|
+
// #534 guard: a null verdict is never "ready". #853: report it as
|
|
321
|
+
// NO_VERDICT, not NO_IMPLEMENTATION — a deferred/unparseable QA turn says
|
|
322
|
+
// nothing about whether an implementation exists, and the misleading label
|
|
323
|
+
// sent debuggers to the wrong place (same class as the phase-executor
|
|
324
|
+
// "unparseable verdict" split).
|
|
300
325
|
if (!verdict) {
|
|
301
|
-
return finish("
|
|
326
|
+
return finish("NO_VERDICT");
|
|
302
327
|
}
|
|
303
|
-
// #534 guard:
|
|
304
|
-
//
|
|
305
|
-
|
|
328
|
+
// #534/#879 guard: a worktree with no commits is never "ready". Split by
|
|
329
|
+
// cause so the report is not misleading: `none` is the empty-branch class
|
|
330
|
+
// (#529/#570), `uncommitted` is a dirty tree whose work cannot become a PR
|
|
331
|
+
// until committed (#879). `commits`/`unknown` continue (unknown fails open).
|
|
332
|
+
const changes = classifyChangesFn(worktreePath);
|
|
333
|
+
if (changes.kind === "none") {
|
|
306
334
|
return finish("NO_IMPLEMENTATION");
|
|
307
335
|
}
|
|
336
|
+
if (changes.kind === "uncommitted") {
|
|
337
|
+
return finish("UNCOMMITTED_ONLY");
|
|
338
|
+
}
|
|
308
339
|
finalVerdict = verdict;
|
|
309
340
|
const gaps = qaResult.summary?.gaps ?? [];
|
|
310
341
|
remaining = classifyGaps(gaps, nonGoals);
|
|
@@ -327,11 +358,17 @@ export async function runReadyGate(opts) {
|
|
|
327
358
|
.filter((g) => !g.nonGoal)
|
|
328
359
|
.map((g) => g.description);
|
|
329
360
|
const before = snapshotFn(worktreePath);
|
|
330
|
-
|
|
361
|
+
// #915: iterations > 1 means this fix pass follows a QA pass that was
|
|
362
|
+
// itself a retry — the same ready-gate retry signal as the qa dispatch
|
|
363
|
+
// above.
|
|
364
|
+
const loopEscalation = withEscalatedEffort(buildPhaseConfig(opts, {
|
|
331
365
|
lastVerdict: verdict,
|
|
332
366
|
failedAcs: fixableGaps.join("; ") || undefined,
|
|
333
367
|
promptContext: buildLoopContext(policy, verdict, fixableGaps),
|
|
334
|
-
}), iterations);
|
|
368
|
+
}), "loop", iterations > 1);
|
|
369
|
+
if (loopEscalation.record)
|
|
370
|
+
effortEscalations.push(loopEscalation.record);
|
|
371
|
+
const loopResult = await runPhaseTracked("loop", loopEscalation.config, iterations);
|
|
335
372
|
tokensUsed = readTokensUsed(worktreePath);
|
|
336
373
|
if (!loopResult.success) {
|
|
337
374
|
return finish("LOOP_FAILED");
|