@mjasnikovs/pi-task 0.18.29 → 0.18.31
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/dist/config/config.d.ts +15 -0
- package/dist/config/config.js +8 -1
- package/dist/config/register.js +10 -0
- package/dist/task/accept-debt.d.ts +13 -1
- package/dist/task/accept-debt.js +18 -1
- package/dist/task/auto-orchestrator.d.ts +8 -0
- package/dist/task/auto-orchestrator.js +127 -22
- package/dist/task/final-gate-fix.d.ts +25 -0
- package/dist/task/final-gate-fix.js +33 -0
- package/dist/task/foreign-path.d.ts +96 -0
- package/dist/task/foreign-path.js +0 -0
- package/dist/task/gate-deps.js +143 -1
- package/dist/task/parsers.d.ts +18 -0
- package/dist/task/parsers.js +4 -3
- package/dist/task/phases.js +56 -10
- package/dist/task/runner-globs.d.ts +74 -0
- package/dist/task/runner-globs.js +155 -0
- package/dist/task/script-escape.d.ts +83 -0
- package/dist/task/script-escape.js +189 -0
- package/dist/task/task-gates.d.ts +8 -0
- package/dist/task/task-gates.js +26 -4
- package/dist/task/verify-work.d.ts +39 -1
- package/dist/task/verify-work.js +126 -2
- package/dist/task/yolo.d.ts +74 -0
- package/dist/task/yolo.js +112 -0
- package/package.json +1 -1
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* script-escape — deterministic detection of CHECK SCRIPTS THAT CANNOT FAIL.
|
|
3
|
+
*
|
|
4
|
+
* The failure this closes (mx5 run 13, PROMPT 4 item 4): the shipped package.json
|
|
5
|
+
* declared, verbatim,
|
|
6
|
+
* "lint": "prettier … && eslint --fix … && (tsc --noEmit 2>&1 | grep -qv 'TS18003' || true)"
|
|
7
|
+
* The typecheck is neutered twice over — its output is piped into an INVERTED grep
|
|
8
|
+
* (so the status becomes "some line did not match", never tsc's verdict), and the
|
|
9
|
+
* whole group is closed with `|| true` (so the script exits 0 unconditionally). Every
|
|
10
|
+
* consumer of that script — the repo-health verify gate, the final integration gate,
|
|
11
|
+
* a human reading a green CI line — is reading a constant, not a measurement.
|
|
12
|
+
*
|
|
13
|
+
* It was harmless in run 13 only by luck: tsc happened to be clean (validated). The
|
|
14
|
+
* class is not harmless — this is the same defect as run-8's F2 skip-escape, moved
|
|
15
|
+
* one level out. findSkipEscapes (skip-escape.ts) scans a spec's own VERIFY block;
|
|
16
|
+
* nothing scanned the SCRIPT DEFINITIONS those VERIFY blocks then invoke by name, so
|
|
17
|
+
* `bun run lint` could be authored into a no-op and every gate would salute it.
|
|
18
|
+
*
|
|
19
|
+
* TWO SHAPES, both crisp, both scoped to CHECK-CLASS script names:
|
|
20
|
+
*
|
|
21
|
+
* A. ALWAYS-ZERO TAIL — the script's last command cannot fail (`… || true`,
|
|
22
|
+
* `… || :`, `… || exit 0`, `…; exit 0`, a trailing `|| echo` fallback). A
|
|
23
|
+
* shell script's status is its last command's status, so this is a proof, not
|
|
24
|
+
* a heuristic: the script exits 0 no matter what the checker found.
|
|
25
|
+
*
|
|
26
|
+
* B. INVERTED-GREP LAUNDERING — a checker piped into `grep -qv` / `grep -vq`.
|
|
27
|
+
* "Some line of the output does NOT match X" is never a checker's verdict; it
|
|
28
|
+
* is true of virtually any non-empty output, including a wall of errors.
|
|
29
|
+
*
|
|
30
|
+
* Deliberately NOT flagged, because each is legitimate and FP-measured against the
|
|
31
|
+
* real corpus (pi-task, aiz-server, aiz-client, gofer, mx5):
|
|
32
|
+
* - `|| exit 1` — a HARDENING, the opposite of an escape (aiz-server).
|
|
33
|
+
* - any `||`/pipe in a NON-check script (`clean`, `dev`, `start`, `copy-fonts`) —
|
|
34
|
+
* a teardown `rm -rf dist || true` is correct and common.
|
|
35
|
+
* - pipes into formatters/reporters (`| tap-spec`, `| tee`) — those propagate
|
|
36
|
+
* status or are presentational; only inverted grep is unambiguous.
|
|
37
|
+
* - a plain `| grep -q "expected"` — that is a real assertion on output.
|
|
38
|
+
*/
|
|
39
|
+
/** One neutered check script. */
|
|
40
|
+
export interface ScriptEscapeFinding {
|
|
41
|
+
/** The script's name as declared (e.g. `lint`). */
|
|
42
|
+
name: string;
|
|
43
|
+
/** The script's body, verbatim. */
|
|
44
|
+
body: string;
|
|
45
|
+
/** Why it cannot fail (human- and prompt-readable). */
|
|
46
|
+
reason: string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Judge one script definition. Returns null when the script is not check-class, or
|
|
50
|
+
* is check-class but can genuinely fail.
|
|
51
|
+
*/
|
|
52
|
+
export declare function judgeScript(name: string, body: string): ScriptEscapeFinding | null;
|
|
53
|
+
/**
|
|
54
|
+
* Scan a parsed `scripts` map (package.json shape) for neutered check scripts.
|
|
55
|
+
* A non-object, or a map with no check-class scripts, yields no findings.
|
|
56
|
+
*/
|
|
57
|
+
export declare function findScriptEscapes(scripts: unknown): ScriptEscapeFinding[];
|
|
58
|
+
/**
|
|
59
|
+
* Scan a package.json's TEXT. Invalid JSON yields no findings — a manifest that does
|
|
60
|
+
* not parse is a different problem, and guessing at its contents is how a scanner
|
|
61
|
+
* earns false positives.
|
|
62
|
+
*/
|
|
63
|
+
export declare function findScriptEscapesInManifest(manifestText: string): ScriptEscapeFinding[];
|
|
64
|
+
/**
|
|
65
|
+
* Scan ARBITRARY text (a spec, a design doc) for script definitions written as JSON
|
|
66
|
+
* pairs — the form a spec uses when it dictates a script for the implementer to add
|
|
67
|
+
* (`"lint": "tsc --noEmit || true"`). This is what lets the critique catch the
|
|
68
|
+
* neutered script at SPEC time, before any task writes it into a manifest.
|
|
69
|
+
*
|
|
70
|
+
* Deliberately narrow: only `"name": "body"` pairs on one line, only check-class
|
|
71
|
+
* names. Prose describing a script in words extracts nothing.
|
|
72
|
+
*/
|
|
73
|
+
export declare function findScriptEscapesInText(text: string): ScriptEscapeFinding[];
|
|
74
|
+
/**
|
|
75
|
+
* Verify-child prompt lines (probe+rule pattern): one per neutered script, naming
|
|
76
|
+
* the script, its body, and why it cannot fail.
|
|
77
|
+
*/
|
|
78
|
+
export declare function scriptEscapeVerifyFindings(findings: ScriptEscapeFinding[]): string[];
|
|
79
|
+
/**
|
|
80
|
+
* Render findings as a critique/rewrite defect block — same shape as
|
|
81
|
+
* skipEscapeDefectText: a numbered list the rewrite must resolve.
|
|
82
|
+
*/
|
|
83
|
+
export declare function scriptEscapeDefectText(findings: ScriptEscapeFinding[]): string;
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* script-escape — deterministic detection of CHECK SCRIPTS THAT CANNOT FAIL.
|
|
3
|
+
*
|
|
4
|
+
* The failure this closes (mx5 run 13, PROMPT 4 item 4): the shipped package.json
|
|
5
|
+
* declared, verbatim,
|
|
6
|
+
* "lint": "prettier … && eslint --fix … && (tsc --noEmit 2>&1 | grep -qv 'TS18003' || true)"
|
|
7
|
+
* The typecheck is neutered twice over — its output is piped into an INVERTED grep
|
|
8
|
+
* (so the status becomes "some line did not match", never tsc's verdict), and the
|
|
9
|
+
* whole group is closed with `|| true` (so the script exits 0 unconditionally). Every
|
|
10
|
+
* consumer of that script — the repo-health verify gate, the final integration gate,
|
|
11
|
+
* a human reading a green CI line — is reading a constant, not a measurement.
|
|
12
|
+
*
|
|
13
|
+
* It was harmless in run 13 only by luck: tsc happened to be clean (validated). The
|
|
14
|
+
* class is not harmless — this is the same defect as run-8's F2 skip-escape, moved
|
|
15
|
+
* one level out. findSkipEscapes (skip-escape.ts) scans a spec's own VERIFY block;
|
|
16
|
+
* nothing scanned the SCRIPT DEFINITIONS those VERIFY blocks then invoke by name, so
|
|
17
|
+
* `bun run lint` could be authored into a no-op and every gate would salute it.
|
|
18
|
+
*
|
|
19
|
+
* TWO SHAPES, both crisp, both scoped to CHECK-CLASS script names:
|
|
20
|
+
*
|
|
21
|
+
* A. ALWAYS-ZERO TAIL — the script's last command cannot fail (`… || true`,
|
|
22
|
+
* `… || :`, `… || exit 0`, `…; exit 0`, a trailing `|| echo` fallback). A
|
|
23
|
+
* shell script's status is its last command's status, so this is a proof, not
|
|
24
|
+
* a heuristic: the script exits 0 no matter what the checker found.
|
|
25
|
+
*
|
|
26
|
+
* B. INVERTED-GREP LAUNDERING — a checker piped into `grep -qv` / `grep -vq`.
|
|
27
|
+
* "Some line of the output does NOT match X" is never a checker's verdict; it
|
|
28
|
+
* is true of virtually any non-empty output, including a wall of errors.
|
|
29
|
+
*
|
|
30
|
+
* Deliberately NOT flagged, because each is legitimate and FP-measured against the
|
|
31
|
+
* real corpus (pi-task, aiz-server, aiz-client, gofer, mx5):
|
|
32
|
+
* - `|| exit 1` — a HARDENING, the opposite of an escape (aiz-server).
|
|
33
|
+
* - any `||`/pipe in a NON-check script (`clean`, `dev`, `start`, `copy-fonts`) —
|
|
34
|
+
* a teardown `rm -rf dist || true` is correct and common.
|
|
35
|
+
* - pipes into formatters/reporters (`| tap-spec`, `| tee`) — those propagate
|
|
36
|
+
* status or are presentational; only inverted grep is unambiguous.
|
|
37
|
+
* - a plain `| grep -q "expected"` — that is a real assertion on output.
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* Script names whose whole purpose is to FAIL when something is wrong. A neutered
|
|
41
|
+
* `clean` or `dev` is nobody's business; a neutered `lint` silently disarms every
|
|
42
|
+
* gate that runs it. Suffixed variants (`test:ct`, `lint:fix`, `check-types`) match.
|
|
43
|
+
*/
|
|
44
|
+
const CHECK_SCRIPT_RE = /^(?:lint|test|check|verify|validate|typecheck|types?|tsc|ci|audit|coverage|e2e|build|compile|fmt:check|format:check)(?:[:_-].*)?$/i;
|
|
45
|
+
/** `|| exit 1` and friends HARDEN a script — never mistake one for an escape. */
|
|
46
|
+
const HARDENING_TAIL_RE = /\|\|\s*exit\s+[1-9]\d*\s*$/;
|
|
47
|
+
/** Tails that make the script's exit status unconditionally 0. */
|
|
48
|
+
const ALWAYS_ZERO_TAILS = [
|
|
49
|
+
{ re: /\|\|\s*true\s*$/, what: '`|| true`' },
|
|
50
|
+
{ re: /\|\|\s*:\s*$/, what: '`|| :`' },
|
|
51
|
+
{ re: /\|\|\s*exit\s+0\s*$/, what: '`|| exit 0`' },
|
|
52
|
+
{ re: /;\s*true\s*$/, what: '`; true`' },
|
|
53
|
+
{ re: /;\s*exit\s+0\s*$/, what: '`; exit 0`' },
|
|
54
|
+
{ re: /\|\|\s*echo\b[^|&;]*$/, what: 'a trailing `|| echo …` fallback' }
|
|
55
|
+
];
|
|
56
|
+
/** A checker piped into an INVERTED grep: status becomes "something didn't match". */
|
|
57
|
+
const INVERTED_GREP_RE = /\|\s*grep\s+(?:-\w*v\w*|-\w+\s+-\w*v\w*)/;
|
|
58
|
+
/**
|
|
59
|
+
* Strip trailing subshell/group closers and separators so the tail patterns see the
|
|
60
|
+
* real last command. mx5's script ends `… || true)` — without this the `)` hides it.
|
|
61
|
+
*/
|
|
62
|
+
function tailOf(body) {
|
|
63
|
+
let s = body.trim();
|
|
64
|
+
while (true) {
|
|
65
|
+
const next = s.replace(/[)\s;]+$/, '').trimEnd();
|
|
66
|
+
if (next === s)
|
|
67
|
+
return s;
|
|
68
|
+
s = next;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Judge one script definition. Returns null when the script is not check-class, or
|
|
73
|
+
* is check-class but can genuinely fail.
|
|
74
|
+
*/
|
|
75
|
+
export function judgeScript(name, body) {
|
|
76
|
+
if (!CHECK_SCRIPT_RE.test(name))
|
|
77
|
+
return null;
|
|
78
|
+
if (typeof body !== 'string' || body.trim().length === 0)
|
|
79
|
+
return null;
|
|
80
|
+
const reasons = [];
|
|
81
|
+
const tail = tailOf(body);
|
|
82
|
+
if (!HARDENING_TAIL_RE.test(tail)) {
|
|
83
|
+
for (const { re, what } of ALWAYS_ZERO_TAILS) {
|
|
84
|
+
if (re.test(tail)) {
|
|
85
|
+
reasons.push(`it ends in ${what}, so the script exits 0 whatever the check reports`);
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (INVERTED_GREP_RE.test(body)) {
|
|
91
|
+
reasons.push('it pipes a check into an INVERTED grep (`grep -v`), so the exit status becomes '
|
|
92
|
+
+ '"some output line did not match" — true of almost any output, including errors');
|
|
93
|
+
}
|
|
94
|
+
if (reasons.length === 0)
|
|
95
|
+
return null;
|
|
96
|
+
return {
|
|
97
|
+
name,
|
|
98
|
+
body: body.trim(),
|
|
99
|
+
reason: reasons.join('; ')
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Scan a parsed `scripts` map (package.json shape) for neutered check scripts.
|
|
104
|
+
* A non-object, or a map with no check-class scripts, yields no findings.
|
|
105
|
+
*/
|
|
106
|
+
export function findScriptEscapes(scripts) {
|
|
107
|
+
if (typeof scripts !== 'object' || scripts === null || Array.isArray(scripts))
|
|
108
|
+
return [];
|
|
109
|
+
const out = [];
|
|
110
|
+
for (const [name, body] of Object.entries(scripts)) {
|
|
111
|
+
const f = judgeScript(name, typeof body === 'string' ? body : '');
|
|
112
|
+
if (f)
|
|
113
|
+
out.push(f);
|
|
114
|
+
}
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Scan a package.json's TEXT. Invalid JSON yields no findings — a manifest that does
|
|
119
|
+
* not parse is a different problem, and guessing at its contents is how a scanner
|
|
120
|
+
* earns false positives.
|
|
121
|
+
*/
|
|
122
|
+
export function findScriptEscapesInManifest(manifestText) {
|
|
123
|
+
try {
|
|
124
|
+
const parsed = JSON.parse(manifestText);
|
|
125
|
+
if (typeof parsed !== 'object' || parsed === null)
|
|
126
|
+
return [];
|
|
127
|
+
return findScriptEscapes(parsed.scripts);
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return [];
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Scan ARBITRARY text (a spec, a design doc) for script definitions written as JSON
|
|
135
|
+
* pairs — the form a spec uses when it dictates a script for the implementer to add
|
|
136
|
+
* (`"lint": "tsc --noEmit || true"`). This is what lets the critique catch the
|
|
137
|
+
* neutered script at SPEC time, before any task writes it into a manifest.
|
|
138
|
+
*
|
|
139
|
+
* Deliberately narrow: only `"name": "body"` pairs on one line, only check-class
|
|
140
|
+
* names. Prose describing a script in words extracts nothing.
|
|
141
|
+
*/
|
|
142
|
+
export function findScriptEscapesInText(text) {
|
|
143
|
+
const out = [];
|
|
144
|
+
const seen = new Set();
|
|
145
|
+
for (const m of text.matchAll(/"([A-Za-z0-9:_-]{1,40})"\s*:\s*"((?:[^"\\]|\\.)*)"/g)) {
|
|
146
|
+
const name = m[1];
|
|
147
|
+
// Unescape the JSON string body so shell operators read normally.
|
|
148
|
+
let body;
|
|
149
|
+
try {
|
|
150
|
+
body = JSON.parse(`"${m[2]}"`);
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
const key = `${name}=${body}`;
|
|
156
|
+
if (seen.has(key))
|
|
157
|
+
continue;
|
|
158
|
+
const f = judgeScript(name, body);
|
|
159
|
+
if (!f)
|
|
160
|
+
continue;
|
|
161
|
+
seen.add(key);
|
|
162
|
+
out.push(f);
|
|
163
|
+
}
|
|
164
|
+
return out;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Verify-child prompt lines (probe+rule pattern): one per neutered script, naming
|
|
168
|
+
* the script, its body, and why it cannot fail.
|
|
169
|
+
*/
|
|
170
|
+
export function scriptEscapeVerifyFindings(findings) {
|
|
171
|
+
return findings.map(f => `\`${f.name}\`: ${f.body} — ${f.reason}`);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Render findings as a critique/rewrite defect block — same shape as
|
|
175
|
+
* skipEscapeDefectText: a numbered list the rewrite must resolve.
|
|
176
|
+
*/
|
|
177
|
+
export function scriptEscapeDefectText(findings) {
|
|
178
|
+
return [
|
|
179
|
+
'NEUTERED CHECK SCRIPT — this spec defines a check script that CANNOT FAIL, so every',
|
|
180
|
+
'gate that runs it reads a constant instead of a measurement (mx5 run 13 shipped',
|
|
181
|
+
'`"lint": "… && (tsc --noEmit 2>&1 | grep -qv \'TS18003\' || true)"` — the typecheck',
|
|
182
|
+
'was fully disarmed, and it went unnoticed only because tsc happened to be clean).',
|
|
183
|
+
"Rewrite each so it PROPAGATES the checker's exit status: drop the `|| true` /",
|
|
184
|
+
'`; exit 0` tail, and never launder a checker through an inverted grep. If a',
|
|
185
|
+
'specific diagnostic must genuinely be tolerated, suppress THAT diagnostic in the',
|
|
186
|
+
"checker's own config — never blanket-zero the whole script's exit code:",
|
|
187
|
+
...findings.map((f, i) => ` ${i + 1}. "${f.name}": "${f.body}" — ${f.reason}`)
|
|
188
|
+
].join('\n');
|
|
189
|
+
}
|
|
@@ -127,6 +127,14 @@ export interface GateDeps {
|
|
|
127
127
|
* and surfaces it if still open. Best-effort; absent in tests → no ledger written.
|
|
128
128
|
*/
|
|
129
129
|
recordAcceptDebt?: (cwd: string, taskId: string, reason: string) => Promise<void>;
|
|
130
|
+
/**
|
|
131
|
+
* Record a durable YOLO-ACCEPTED debt: the same ACCEPT branch, but reached by an
|
|
132
|
+
* unattended auto-pick (yolo mode) rather than a human. Separate dep — and
|
|
133
|
+
* separate ledger origin — because collapsing the two would let an auto-pick
|
|
134
|
+
* read as "a human blessed this" in the final gate's run-end report. Best-effort;
|
|
135
|
+
* absent in tests → no ledger written.
|
|
136
|
+
*/
|
|
137
|
+
recordYoloAcceptDebt?: (cwd: string, taskId: string, reason: string) => Promise<void>;
|
|
130
138
|
/**
|
|
131
139
|
* Record a durable ENFORCE-REVERT debt (mx5 run 10 item 3): the enforce re-verify
|
|
132
140
|
* FAILED and the enforce edits were reverted, but the FAIL indicts the ORIGINAL
|
package/dist/task/task-gates.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { resolutionOptions, classifyResolutionAnswer } from './verify-resolution.js';
|
|
2
2
|
import { SessionUI } from '../remote/bridge.js';
|
|
3
|
+
import { isYoloMode, yoloVerifyResolution, YOLO_STAMP } from './yolo.js';
|
|
3
4
|
/**
|
|
4
5
|
* How many times a verify FAIL may be auto-fixed UNATTENDED (the research
|
|
5
6
|
* recommended AUTOFIX, so pi re-runs the impl turn without prompting) before the
|
|
@@ -178,8 +179,19 @@ export async function runGatesForTask(ctxIn, deps, p) {
|
|
|
178
179
|
&& !isFrozenBlocked
|
|
179
180
|
&& recOutcome.recommend === 'autofix'
|
|
180
181
|
&& autoFixCount < MAX_AUTO_AUTOFIX;
|
|
182
|
+
// YOLO: the picker is unreachable with nobody watching, and by the time
|
|
183
|
+
// we are here the unattended AUTOFIX budget is ALREADY spent (autoFixNow
|
|
184
|
+
// is false) — so the only option left that terminates is ACCEPT, recorded
|
|
185
|
+
// as its own 'yolo-accepted' debt. Deliberately NOT a re-entry into
|
|
186
|
+
// autofix: MAX_AUTO_AUTOFIX exists to break a non-converging loop, and an
|
|
187
|
+
// auto-pick here would restart the budget from the site that proves it ran out.
|
|
188
|
+
const yoloChoice = autoFixNow ? null : yoloVerifyResolution(isYoloMode());
|
|
181
189
|
let choice;
|
|
182
|
-
if (
|
|
190
|
+
if (yoloChoice !== null) {
|
|
191
|
+
choice = yoloChoice;
|
|
192
|
+
await rec(`resolution: auto-ACCEPTED despite verify FAIL — autofix budget spent, nobody to ask ${YOLO_STAMP}`);
|
|
193
|
+
}
|
|
194
|
+
else if (autoFixNow) {
|
|
183
195
|
autoFixCount += 1;
|
|
184
196
|
await rec(`resolution: auto-AUTOFIX (recommended, unattended ${autoFixCount}/${MAX_AUTO_AUTOFIX})`);
|
|
185
197
|
active.ui.notify(`${p.tag}: verify FAIL on "${p.title}" — auto-fixing (recommended, ${autoFixCount}/${MAX_AUTO_AUTOFIX})…`, 'info');
|
|
@@ -193,7 +205,9 @@ export async function runGatesForTask(ctxIn, deps, p) {
|
|
|
193
205
|
return { kind: 'paused', ctx: active, reason: failReason };
|
|
194
206
|
}
|
|
195
207
|
if (choice.action === 'accept') {
|
|
196
|
-
|
|
208
|
+
const byYolo = yoloChoice !== null;
|
|
209
|
+
if (!byYolo)
|
|
210
|
+
await rec('resolution: user ACCEPTED the work despite verify FAIL');
|
|
197
211
|
// Durable debt: the human blessed a FAILing artifact as-is, so the
|
|
198
212
|
// defect ships and nothing else revisits it (mx5 run 4 B3 / run 8
|
|
199
213
|
// TASK_0012). Record it to the run ledger; the final integration gate
|
|
@@ -202,7 +216,15 @@ export async function runGatesForTask(ctxIn, deps, p) {
|
|
|
202
216
|
// the contradiction named) — don't double-enter it in the ledger.
|
|
203
217
|
if (!frozenDebtRecorded) {
|
|
204
218
|
try {
|
|
205
|
-
|
|
219
|
+
// Provenance splits here, mandatorily: an auto-pick writes the
|
|
220
|
+
// 'yolo-accepted' origin, never the plain 'accepted' one that
|
|
221
|
+
// asserts a human weighed the failing artifact.
|
|
222
|
+
if (byYolo) {
|
|
223
|
+
await deps.recordYoloAcceptDebt?.(p.cwd, p.taskId, failReason);
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
await deps.recordAcceptDebt?.(p.cwd, p.taskId, failReason);
|
|
227
|
+
}
|
|
206
228
|
}
|
|
207
229
|
catch {
|
|
208
230
|
// recording must never break the gate sequence
|
|
@@ -220,7 +242,7 @@ export async function runGatesForTask(ctxIn, deps, p) {
|
|
|
220
242
|
// recording must never break the gate sequence
|
|
221
243
|
}
|
|
222
244
|
}
|
|
223
|
-
active.ui.notify(`${p.tag}: accepted "${p.title}" despite verify FAIL (${failReason.slice(0, 120)}) — proceeding
|
|
245
|
+
active.ui.notify(`${p.tag}: accepted "${p.title}" despite verify FAIL (${failReason.slice(0, 120)}) — proceeding.${byYolo ? ` ${YOLO_STAMP}` : ''}`, 'warning');
|
|
224
246
|
break;
|
|
225
247
|
}
|
|
226
248
|
// AUTOFIX: re-run the implementation turn with the failure (and any typed
|
|
@@ -68,7 +68,20 @@ export declare function extractSpecForVerification(taskBody: string): string | n
|
|
|
68
68
|
* Guard: honest-clean fixture (prohibition in spec, probe silent) 5/5 PASS — no
|
|
69
69
|
* paranoia. Reverted-violation ≡ clean at the diff level (no entry → no finding).
|
|
70
70
|
*/
|
|
71
|
-
export declare function buildVerifyPrompt(spec: string, probeFindings?: string[], envNotes?: string, prohibitionFindings?: string[], skipEscapeFindings?: string[], contracts?: string, testAssemblyFindings?: string[], probeGamingFindings?: string[], crossTaskDeletionFindings?: string[]
|
|
71
|
+
export declare function buildVerifyPrompt(spec: string, probeFindings?: string[], envNotes?: string, prohibitionFindings?: string[], skipEscapeFindings?: string[], contracts?: string, testAssemblyFindings?: string[], probeGamingFindings?: string[], crossTaskDeletionFindings?: string[],
|
|
72
|
+
/**
|
|
73
|
+
* The mx5 run-13 (PROMPT 4) probes, grouped rather than appended as three more
|
|
74
|
+
* positional parameters — this signature was already at its limit. Each key is
|
|
75
|
+
* an independent finding list; absent/empty emits no block.
|
|
76
|
+
*/
|
|
77
|
+
projectSurface?: {
|
|
78
|
+
/** Sandbox-leaked absolute paths (rule 4e) — see foreign-path.ts. */
|
|
79
|
+
foreignPaths?: string[];
|
|
80
|
+
/** Check scripts that cannot fail (rule 4f) — see script-escape.ts. */
|
|
81
|
+
scriptEscapes?: string[];
|
|
82
|
+
/** Colliding test-runner globs (rule 4g) — see runner-globs.ts. */
|
|
83
|
+
runnerGlobs?: string[];
|
|
84
|
+
}): string;
|
|
72
85
|
/**
|
|
73
86
|
* Parse the child's verdict. Scans for the LAST `WORK-VERIFIED: PASS|FAIL|UNOBSERVED`
|
|
74
87
|
* marker (the model discusses before concluding, and bash output may echo the word
|
|
@@ -146,6 +159,31 @@ export interface VerificationDeps {
|
|
|
146
159
|
* 0/5), and carried structurally on a FAIL outcome so an ACCEPT records each as
|
|
147
160
|
* a durable debt. ABSENT or empty → no block. */
|
|
148
161
|
crossTaskDeletionProbe?: () => Promise<CrossTaskDeletion[]>;
|
|
162
|
+
/**
|
|
163
|
+
* DETERMINISTIC sandbox-path-leak probe (see foreign-path.ts, mx5 run 13
|
|
164
|
+
* PROMPT 4 item 1): absolute paths this task committed that exist only inside
|
|
165
|
+
* the authoring child's own environment — `/workspace/src/shared` in a vite
|
|
166
|
+
* alias — while the real file sits at `src/shared` here. The probe REPAIRS
|
|
167
|
+
* what it can deterministically first; only leaks it could not repair reach
|
|
168
|
+
* this hook, injected under rule 4e (MANDATORY + verdict-gating, the same
|
|
169
|
+
* shape as 4d — a leak makes the affected command fail to BUILD, so the
|
|
170
|
+
* checks that would notice never run at all). ABSENT or empty → no block. */
|
|
171
|
+
foreignPathProbe?: () => Promise<string[]>;
|
|
172
|
+
/**
|
|
173
|
+
* DETERMINISTIC neutered-check-script probe (see script-escape.ts, mx5 run 13
|
|
174
|
+
* PROMPT 4 item 4): check-class scripts in a manifest THIS task changed whose
|
|
175
|
+
* exit status cannot be non-zero (`… || true`, an inverted-grep launder). The
|
|
176
|
+
* damage is second-order — the script still "passes", so the gates that run it
|
|
177
|
+
* report success without measuring anything — which is exactly why the child
|
|
178
|
+
* cannot discover it by running the check. ABSENT or empty → no block. */
|
|
179
|
+
scriptEscapeProbe?: () => Promise<string[]>;
|
|
180
|
+
/**
|
|
181
|
+
* DETERMINISTIC test-runner glob-collision probe (see runner-globs.ts, mx5 runs
|
|
182
|
+
* 7 AND 13, PROMPT 4 item 2): the manifest declares two runners whose file sets
|
|
183
|
+
* are not provably disjoint, so the scanning one imports the other's specs and
|
|
184
|
+
* dies during COLLECTION. Injected under rule 4g. UNKNOWN (one runner, or
|
|
185
|
+
* disjointness proven) yields nothing. ABSENT or empty → no block. */
|
|
186
|
+
runnerGlobProbe?: () => Promise<string[]>;
|
|
149
187
|
/**
|
|
150
188
|
* Result of the git-state guard for the MOST RECENT runChild call (see
|
|
151
189
|
* git-state-guard.ts): did the child mutate repo state (stash/checkout/file
|
package/dist/task/verify-work.js
CHANGED
|
@@ -140,7 +140,14 @@ export function extractSpecForVerification(taskBody) {
|
|
|
140
140
|
* Guard: honest-clean fixture (prohibition in spec, probe silent) 5/5 PASS — no
|
|
141
141
|
* paranoia. Reverted-violation ≡ clean at the diff level (no entry → no finding).
|
|
142
142
|
*/
|
|
143
|
-
export function buildVerifyPrompt(spec, probeFindings, envNotes, prohibitionFindings, skipEscapeFindings, contracts, testAssemblyFindings, probeGamingFindings, crossTaskDeletionFindings
|
|
143
|
+
export function buildVerifyPrompt(spec, probeFindings, envNotes, prohibitionFindings, skipEscapeFindings, contracts, testAssemblyFindings, probeGamingFindings, crossTaskDeletionFindings,
|
|
144
|
+
/**
|
|
145
|
+
* The mx5 run-13 (PROMPT 4) probes, grouped rather than appended as three more
|
|
146
|
+
* positional parameters — this signature was already at its limit. Each key is
|
|
147
|
+
* an independent finding list; absent/empty emits no block.
|
|
148
|
+
*/
|
|
149
|
+
projectSurface = {}) {
|
|
150
|
+
const { foreignPaths: foreignPathFindings, scriptEscapes: scriptEscapeFindings, runnerGlobs: runnerGlobFindings } = projectSurface;
|
|
144
151
|
const probeBlock = probeFindings && probeFindings.length > 0 ?
|
|
145
152
|
[
|
|
146
153
|
'SELF-VERIFICATION NOTICE (deterministic, computed by the orchestrator from the diff):',
|
|
@@ -216,6 +223,57 @@ export function buildVerifyPrompt(spec, probeFindings, envNotes, prohibitionFind
|
|
|
216
223
|
''
|
|
217
224
|
]
|
|
218
225
|
: [];
|
|
226
|
+
const foreignPathBlock = foreignPathFindings && foreignPathFindings.length > 0 ?
|
|
227
|
+
[
|
|
228
|
+
'SANDBOX PATH LEAK NOTICE (deterministic, computed by the orchestrator by',
|
|
229
|
+
"resolving every absolute path in the task's diff against THIS machine): this",
|
|
230
|
+
'task committed absolute paths that do not exist here, while the real file they',
|
|
231
|
+
'name sits inside this repo:',
|
|
232
|
+
...foreignPathFindings.map(f => `- ${f}`),
|
|
233
|
+
"These are paths from the authoring agent's OWN environment, baked into a file",
|
|
234
|
+
'that ships. The command that reads such a path does not fail a check — it fails',
|
|
235
|
+
'to BUILD, so the checks that would have caught it never run and report nothing',
|
|
236
|
+
'(mx5 run 13: a leaked `/workspace` vite alias made `test:ct` collect 63 tests',
|
|
237
|
+
'and run 0, and the suite stayed dead for the rest of the run). A green or',
|
|
238
|
+
'EMPTY result from any command that reads these files is therefore NOT evidence.',
|
|
239
|
+
'Run the affected command yourself and confirm it actually EXECUTES work — count',
|
|
240
|
+
'the tests/steps that ran, not the exit code. Unless the leaked path resolves on',
|
|
241
|
+
'this machine, the verdict is FAIL naming the file and the path (rule 4e).',
|
|
242
|
+
''
|
|
243
|
+
]
|
|
244
|
+
: [];
|
|
245
|
+
const scriptEscapeBlock = scriptEscapeFindings && scriptEscapeFindings.length > 0 ?
|
|
246
|
+
[
|
|
247
|
+
'NEUTERED CHECK SCRIPT NOTICE (deterministic, computed by the orchestrator from',
|
|
248
|
+
'the manifest THIS task changed): these check scripts cannot report failure —',
|
|
249
|
+
'their exit status is 0 no matter what the checker finds:',
|
|
250
|
+
...scriptEscapeFindings.map(f => `- ${f}`),
|
|
251
|
+
'You CANNOT discover this by running the script: it passes, which is the whole',
|
|
252
|
+
'defect (mx5 run 13 shipped a `lint` whose typecheck was disarmed by an inverted',
|
|
253
|
+
'grep and a `|| true` tail; every gate that ran it reported success without',
|
|
254
|
+
'measuring anything). A green result from one of these scripts is NOT evidence',
|
|
255
|
+
'for any acceptance criterion. To judge the area it claims to cover, run the',
|
|
256
|
+
'underlying checker DIRECTLY and unmodified (e.g. `tsc --noEmit` rather than',
|
|
257
|
+
'`npm run lint`) and judge THAT output. Unless the task spec explicitly requires',
|
|
258
|
+
'the script to tolerate failure, the verdict is FAIL naming the script (rule 4f).',
|
|
259
|
+
''
|
|
260
|
+
]
|
|
261
|
+
: [];
|
|
262
|
+
const runnerGlobBlock = runnerGlobFindings && runnerGlobFindings.length > 0 ?
|
|
263
|
+
[
|
|
264
|
+
'TEST-RUNNER GLOB COLLISION NOTICE (deterministic, computed by the orchestrator',
|
|
265
|
+
"from the manifest's declared runners and their config): two test runners claim",
|
|
266
|
+
'the same files:',
|
|
267
|
+
...runnerGlobFindings.map(f => `- ${f}`),
|
|
268
|
+
"The scanning runner will import the other's spec files and abort on a module",
|
|
269
|
+
'loaded outside its own runner — so the suite dies WHOLESALE rather than',
|
|
270
|
+
'reporting failures (this is the THIRD occurrence: mx5 runs 7 and 13). Run both',
|
|
271
|
+
'test commands yourself and confirm each collects and runs its own files and only',
|
|
272
|
+
'its own. A run that errors during collection has verified nothing, whatever its',
|
|
273
|
+
'exit code says (rule 4g).',
|
|
274
|
+
''
|
|
275
|
+
]
|
|
276
|
+
: [];
|
|
219
277
|
const testAssemblyBlock = testAssemblyFindings && testAssemblyFindings.length > 0 ?
|
|
220
278
|
[
|
|
221
279
|
'TEST-ASSEMBLY NOTICE (deterministic, computed by the orchestrator from pure',
|
|
@@ -255,6 +313,9 @@ export function buildVerifyPrompt(spec, probeFindings, envNotes, prohibitionFind
|
|
|
255
313
|
...crossTaskDeletionBlock,
|
|
256
314
|
...probeGamingBlock,
|
|
257
315
|
...skipEscapeBlock,
|
|
316
|
+
...foreignPathBlock,
|
|
317
|
+
...scriptEscapeBlock,
|
|
318
|
+
...runnerGlobBlock,
|
|
258
319
|
...testAssemblyBlock,
|
|
259
320
|
'How to verify — verify the REAL, shipped deliverable exactly as an unaided fresh',
|
|
260
321
|
'checkout (or CI run) would experience it:',
|
|
@@ -388,6 +449,32 @@ export function buildVerifyPrompt(spec, probeFindings, envNotes, prohibitionFind
|
|
|
388
449
|
' tree). Otherwise the verdict is FAIL naming the deleted file and the task that',
|
|
389
450
|
' owns it.',
|
|
390
451
|
'',
|
|
452
|
+
'4e. AN ABSOLUTE PATH TO PROJECT FILES IS A DEFECT — a committed path like',
|
|
453
|
+
" `/workspace/src/shared` or `/home/<someone>/proj/src` names the authoring agent's",
|
|
454
|
+
' OWN machine, not this one. Its distinctive damage is that it breaks the run BEFORE',
|
|
455
|
+
' any check reports: a bad alias/config path makes the tool fail to RESOLVE or BUILD,',
|
|
456
|
+
' so a suite "passes" having executed nothing. Treat a command that reports no',
|
|
457
|
+
' failures but also no WORK — 0 tests run, 0 files emitted, an empty report — as',
|
|
458
|
+
' unverified, never as green. Confirm the count of things that actually ran. A path',
|
|
459
|
+
' to project-internal files must be relative to the file that carries it, or computed',
|
|
460
|
+
' at runtime; if one does not resolve here, the verdict is FAIL naming file and path.',
|
|
461
|
+
'',
|
|
462
|
+
'4f. A CHECK THAT CANNOT FAIL PROVES NOTHING — before you cite a check script',
|
|
463
|
+
" (`npm run lint`, `bun run test`) as evidence, read its DEFINITION in the project's",
|
|
464
|
+
' manifest. A script ending in `|| true`, `; exit 0`, or piping a checker into an',
|
|
465
|
+
' inverted grep exits 0 unconditionally: its green result is a constant, not a',
|
|
466
|
+
' measurement, and running it again only reproduces the constant. When a script is',
|
|
467
|
+
' built that way, run the underlying checker directly and judge its real output; and',
|
|
468
|
+
' unless the spec required that tolerance, the script itself is a defect — the',
|
|
469
|
+
' verdict is FAIL naming it.',
|
|
470
|
+
'',
|
|
471
|
+
'4g. TWO RUNNERS, ONE FILE SET, NO RESULTS — when a project declares more than one',
|
|
472
|
+
' test runner, check that each collects only its own files. A runner that scans for',
|
|
473
|
+
" `*.test.*` / `*.spec.*` project-wide will import another runner's specs and abort",
|
|
474
|
+
' during COLLECTION. That failure mode looks nothing like a test failure: you get an',
|
|
475
|
+
' import error, or a suite that reports zero tests. Always read how many tests each',
|
|
476
|
+
' command actually COLLECTED and RAN; zero collected is never a pass.',
|
|
477
|
+
'',
|
|
391
478
|
'5. The ONLY thing you may assume is already provided is a genuinely EXTERNAL running',
|
|
392
479
|
' service or network resource (a database server, an API host) that the project',
|
|
393
480
|
' documents as a prerequisite. Before you rely on that assumption, PROBE for the',
|
|
@@ -572,6 +659,39 @@ export async function runWorkVerification(deps) {
|
|
|
572
659
|
crossDeletions = [];
|
|
573
660
|
}
|
|
574
661
|
}
|
|
662
|
+
// Sandbox-path-leak findings the deterministic repair could NOT fix, injected
|
|
663
|
+
// under rule 4e. A probe failure must never block verification.
|
|
664
|
+
let foreignPaths = [];
|
|
665
|
+
if (deps.foreignPathProbe) {
|
|
666
|
+
try {
|
|
667
|
+
foreignPaths = await deps.foreignPathProbe();
|
|
668
|
+
}
|
|
669
|
+
catch {
|
|
670
|
+
foreignPaths = [];
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
// Neutered check scripts in a manifest this task changed, injected under rule
|
|
674
|
+
// 4f. A probe failure must never block verification.
|
|
675
|
+
let scriptEscapes = [];
|
|
676
|
+
if (deps.scriptEscapeProbe) {
|
|
677
|
+
try {
|
|
678
|
+
scriptEscapes = await deps.scriptEscapeProbe();
|
|
679
|
+
}
|
|
680
|
+
catch {
|
|
681
|
+
scriptEscapes = [];
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
// Colliding test-runner globs, injected under rule 4g. A probe failure must
|
|
685
|
+
// never block verification.
|
|
686
|
+
let runnerGlobs = [];
|
|
687
|
+
if (deps.runnerGlobProbe) {
|
|
688
|
+
try {
|
|
689
|
+
runnerGlobs = await deps.runnerGlobProbe();
|
|
690
|
+
}
|
|
691
|
+
catch {
|
|
692
|
+
runnerGlobs = [];
|
|
693
|
+
}
|
|
694
|
+
}
|
|
575
695
|
// Environment facts from earlier gate children (best-effort; a cache failure
|
|
576
696
|
// must never block verification).
|
|
577
697
|
let envNotes = '';
|
|
@@ -607,7 +727,11 @@ export async function runWorkVerification(deps) {
|
|
|
607
727
|
for (let attempt = 1;; attempt++) {
|
|
608
728
|
let text;
|
|
609
729
|
try {
|
|
610
|
-
text = await deps.runChild(VERIFY_TOOLS, buildVerifyPrompt(deps.spec, findings, envNotes, prohibitions, skipEscapes, contracts, testAssembly, probeGaming, crossTaskDeletionVerifyFindings(crossDeletions)
|
|
730
|
+
text = await deps.runChild(VERIFY_TOOLS, buildVerifyPrompt(deps.spec, findings, envNotes, prohibitions, skipEscapes, contracts, testAssembly, probeGaming, crossTaskDeletionVerifyFindings(crossDeletions), {
|
|
731
|
+
foreignPaths,
|
|
732
|
+
scriptEscapes,
|
|
733
|
+
runnerGlobs
|
|
734
|
+
}), deps.signal);
|
|
611
735
|
}
|
|
612
736
|
catch (err) {
|
|
613
737
|
if (err instanceof Error && err.message === USER_CANCELLED)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { AutoAnswer } from './parsers.js';
|
|
2
|
+
import type { ResolutionChoice } from './verify-resolution.js';
|
|
3
|
+
import type { FinalGateChoice } from './final-gate-fix.js';
|
|
4
|
+
/**
|
|
5
|
+
* Visible provenance marker on EVERY artifact an auto-pick writes (gate trails,
|
|
6
|
+
* task-file Q&A, the debt ledger's reason text). A later audit reading only the
|
|
7
|
+
* artifacts must never mistake an auto-pick for a human decision — that is the
|
|
8
|
+
* same confusion the accept-debt origins were introduced to prevent.
|
|
9
|
+
*/
|
|
10
|
+
export declare const YOLO_STAMP = "(YOLO)";
|
|
11
|
+
/** Is unattended auto-pick on for this run? The ONLY config read in this module. */
|
|
12
|
+
export declare function isYoloMode(): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* What YOLO does at a question site:
|
|
15
|
+
* - 'answer' — take this (the recommended option).
|
|
16
|
+
* - 'skip' — YOLO is on but there is nothing safe to take; leave the question
|
|
17
|
+
* unanswered and move on, with `note` recorded as the reason.
|
|
18
|
+
* - null — YOLO is off: ask the human exactly as before.
|
|
19
|
+
*/
|
|
20
|
+
export type YoloPick = {
|
|
21
|
+
kind: 'answer';
|
|
22
|
+
answer: string;
|
|
23
|
+
} | {
|
|
24
|
+
kind: 'skip';
|
|
25
|
+
note: string;
|
|
26
|
+
} | null;
|
|
27
|
+
/**
|
|
28
|
+
* The clarify/grill policy: take the RECOMMENDED option — which is positional,
|
|
29
|
+
* index 0 of the card list (question-box.ts tints it green), i.e. `suggested`,
|
|
30
|
+
* falling back to the B-side `alt` when a fork offers only that.
|
|
31
|
+
*
|
|
32
|
+
* `unsafe` is the step-aside channel: a caller that KNOWS the recommendation must
|
|
33
|
+
* not be auto-accepted passes why, and the question is skipped instead. Its one
|
|
34
|
+
* producer today is the anti-synthesis demotion — an answer proven to name a
|
|
35
|
+
* hallucinated API identifier. Auto-accepting that would re-promote exactly the
|
|
36
|
+
* invention the demotion was built to stop (it reached requirements AND the VERIFY
|
|
37
|
+
* block in mx5 run 13), so a machine may never take it; a human still can.
|
|
38
|
+
*/
|
|
39
|
+
export declare function yoloPickAnswer(enabled: boolean, opts: {
|
|
40
|
+
suggested?: string;
|
|
41
|
+
alt?: string;
|
|
42
|
+
unsafe?: string;
|
|
43
|
+
}): YoloPick;
|
|
44
|
+
/**
|
|
45
|
+
* The same policy expressed over an {@link AutoAnswer}, for the grill site. Only
|
|
46
|
+
* the ANTI-SYNTHESIS unknown is unsafe: the other two producers (an integration
|
|
47
|
+
* unknown research could not ground, a child that threw) carry an ordinary
|
|
48
|
+
* best-effort recommendation, which is precisely what a human would be shown as
|
|
49
|
+
* the green card. The variants are told apart by the union's `reason` tag — never
|
|
50
|
+
* by pattern-matching the answer text.
|
|
51
|
+
*/
|
|
52
|
+
export declare function yoloPickAutoAnswer(enabled: boolean, auto: AutoAnswer): YoloPick;
|
|
53
|
+
/**
|
|
54
|
+
* The verify-FAIL picker policy: ACCEPT (and write a debt), never AUTOFIX.
|
|
55
|
+
*
|
|
56
|
+
* Not a preference — a bound. Every branch that REACHES this picker has already
|
|
57
|
+
* spent its unattended budget: the loop auto-runs AUTOFIX while the research
|
|
58
|
+
* recommends it, up to MAX_AUTO_AUTOFIX consecutive failures, and only then hands
|
|
59
|
+
* over. Answering AUTOFIX here would restart that budget from a site whose whole
|
|
60
|
+
* purpose is that the budget ran out. So YOLO takes the terminal option and
|
|
61
|
+
* records the defect ('yolo-accepted' — an origin a human never produces).
|
|
62
|
+
*/
|
|
63
|
+
export declare function yoloVerifyResolution(enabled: boolean): ResolutionChoice | null;
|
|
64
|
+
/**
|
|
65
|
+
* The final-integration-gate policy: keep autofixing WHILE the picker still
|
|
66
|
+
* offers that card (the loop withdraws it after MAX_FINAL_GATE_AUTOFIX attempts),
|
|
67
|
+
* then leave the run FAILED.
|
|
68
|
+
*
|
|
69
|
+
* 'leave', not 'accept': an unattended run that cannot fix the whole-repo gate has
|
|
70
|
+
* not produced a working project, and the honest terminal state is a failed run a
|
|
71
|
+
* resume can re-enter — mx5 run 13 ended "FAIL accepted by user" on an app that
|
|
72
|
+
* 404'd at `/`, and that acceptance is what made the failure look like a success.
|
|
73
|
+
*/
|
|
74
|
+
export declare function yoloFinalGateChoice(enabled: boolean, canAutofix: boolean): FinalGateChoice | null;
|