@mjasnikovs/pi-task 0.32.0 → 0.33.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/dist/task/task-gates.d.ts +23 -0
- package/dist/task/task-gates.js +74 -12
- package/package.json +1 -1
|
@@ -285,6 +285,29 @@ export type GateResult = {
|
|
|
285
285
|
* regardless of this count (blessing an artifact as-is is a human's call).
|
|
286
286
|
*/
|
|
287
287
|
export declare const MAX_AUTO_AUTOFIX = 3;
|
|
288
|
+
/** Which of the four disjoint branches sent a FAIL to the terminal YOLO ACCEPT. */
|
|
289
|
+
export interface YoloAcceptContext {
|
|
290
|
+
/** Rule 5c: the spec-required check could not run (tooling absent). */
|
|
291
|
+
isUnobserved: boolean;
|
|
292
|
+
/** Cross-task contradiction: the repo-health fix needs a spec-frozen path. */
|
|
293
|
+
isFrozenBlocked: boolean;
|
|
294
|
+
/** What the resolution research recommended, when it was consulted at all. */
|
|
295
|
+
recommend: ResolutionOutcome['recommend'];
|
|
296
|
+
/** Unattended AUTOFIX attempts already spent on this task. */
|
|
297
|
+
autoFixCount: number;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* The reason an auto-ACCEPT is being written — NAMED, not assumed.
|
|
301
|
+
*
|
|
302
|
+
* The line this replaces asserted "autofix budget spent" on every branch. It was
|
|
303
|
+
* measured false in 2709 of 2709 recorded accepts (scripts/yolo-accept-baserate.ts):
|
|
304
|
+
* the budget has never once reached MAX_AUTO_AUTOFIX anywhere in the corpus — 30%
|
|
305
|
+
* of accepts are UNOBSERVED (the research is never even consulted) and 70% are an
|
|
306
|
+
* ACCEPT recommendation with the budget fully untouched. A durable trail that
|
|
307
|
+
* misstates why a defect shipped is worse than no trail: mx5 run 19's TASK_0009
|
|
308
|
+
* reads as an exhausted fixer when nothing was ever attempted.
|
|
309
|
+
*/
|
|
310
|
+
export declare function yoloAcceptReason(c: YoloAcceptContext): string;
|
|
288
311
|
/**
|
|
289
312
|
* Show the boxed two-choice picker after a verify FAIL and return what the user
|
|
290
313
|
* decided. The model-recommended card is placed first so the renderer tints it
|
package/dist/task/task-gates.js
CHANGED
|
@@ -13,6 +13,29 @@ import { attributeEnforceFailure } from './enforce-attribution.js';
|
|
|
13
13
|
* regardless of this count (blessing an artifact as-is is a human's call).
|
|
14
14
|
*/
|
|
15
15
|
export const MAX_AUTO_AUTOFIX = 3;
|
|
16
|
+
/**
|
|
17
|
+
* The reason an auto-ACCEPT is being written — NAMED, not assumed.
|
|
18
|
+
*
|
|
19
|
+
* The line this replaces asserted "autofix budget spent" on every branch. It was
|
|
20
|
+
* measured false in 2709 of 2709 recorded accepts (scripts/yolo-accept-baserate.ts):
|
|
21
|
+
* the budget has never once reached MAX_AUTO_AUTOFIX anywhere in the corpus — 30%
|
|
22
|
+
* of accepts are UNOBSERVED (the research is never even consulted) and 70% are an
|
|
23
|
+
* ACCEPT recommendation with the budget fully untouched. A durable trail that
|
|
24
|
+
* misstates why a defect shipped is worse than no trail: mx5 run 19's TASK_0009
|
|
25
|
+
* reads as an exhausted fixer when nothing was ever attempted.
|
|
26
|
+
*/
|
|
27
|
+
export function yoloAcceptReason(c) {
|
|
28
|
+
if (c.isUnobserved)
|
|
29
|
+
return 'verify UNOBSERVED — tooling absent, an unattended re-run cannot provision it';
|
|
30
|
+
if (c.isFrozenBlocked)
|
|
31
|
+
return 'repo-health blocked by a spec-frozen path — an impl re-run under the same freeze cannot converge';
|
|
32
|
+
if (c.recommend === 'autofix') {
|
|
33
|
+
return `autofix budget spent (${c.autoFixCount}/${MAX_AUTO_AUTOFIX})`;
|
|
34
|
+
}
|
|
35
|
+
return c.autoFixCount === 0 ?
|
|
36
|
+
`judge recommended ACCEPT (autofix budget 0/${MAX_AUTO_AUTOFIX} unused)`
|
|
37
|
+
: `judge recommended ACCEPT (autofix budget ${c.autoFixCount}/${MAX_AUTO_AUTOFIX} already spent)`;
|
|
38
|
+
}
|
|
16
39
|
/**
|
|
17
40
|
* Bound a captured health-check output before it is embedded in a gate-trail line.
|
|
18
41
|
* appendGateRecord flattens newlines to spaces, so the trail stays one line per
|
|
@@ -134,6 +157,8 @@ export async function runGatesForTask(ctxIn, deps, p) {
|
|
|
134
157
|
// defect is recorded as a durable debt for the final gate.
|
|
135
158
|
let frozenContradiction = null;
|
|
136
159
|
let frozenDebtRecorded = false;
|
|
160
|
+
// YOLO only: has the one-attempt rescue below already been spent on this task?
|
|
161
|
+
let yoloRescueUsed = false;
|
|
137
162
|
while (!verified.ok) {
|
|
138
163
|
const failReason = verified.reason ?? 'did not verify';
|
|
139
164
|
// GRADUATED resolution: a repo-health FAIL (pure static findings) gets ONE
|
|
@@ -213,23 +238,54 @@ export async function runGatesForTask(ctxIn, deps, p) {
|
|
|
213
238
|
const autoFixNow = !isUnobserved
|
|
214
239
|
&& !isFrozenBlocked
|
|
215
240
|
&& recOutcome.recommend === 'autofix'
|
|
216
|
-
&& autoFixCount < MAX_AUTO_AUTOFIX
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
//
|
|
241
|
+
&& autoFixCount < MAX_AUTO_AUTOFIX
|
|
242
|
+
// A rescue attempt that still FAILed is terminal under YOLO: the
|
|
243
|
+
// recommendation that got us here was ACCEPT, so a later flip to
|
|
244
|
+
// AUTOFIX must not bootstrap the full budget from it.
|
|
245
|
+
&& !yoloRescueUsed;
|
|
246
|
+
// YOLO, THE RESCUE BRANCH: the recommendation is ACCEPT, nobody can be
|
|
247
|
+
// asked, and the unattended budget is UNTOUCHED. Accepting here ships a
|
|
248
|
+
// defect having attempted nothing — mx5 run 19 did exactly that twice,
|
|
249
|
+
// and the final-gate autofix later fixed one of the two in a single pass
|
|
250
|
+
// (`0 pass 130 fail` → `121 pass 0 fail`). So spend ONE attempt first.
|
|
251
|
+
// Bounded by construction: one, not MAX_AUTO_AUTOFIX, so an ACCEPT
|
|
252
|
+
// recommendation can never restart a full loop; if it still FAILs the
|
|
253
|
+
// next turn falls through to the same auto-ACCEPT and the same debt.
|
|
254
|
+
const yoloRescueNow = isYoloMode()
|
|
255
|
+
&& !yoloRescueUsed
|
|
256
|
+
&& !isUnobserved
|
|
257
|
+
&& !isFrozenBlocked
|
|
258
|
+
&& recOutcome.recommend === 'accept'
|
|
259
|
+
&& autoFixCount === 0;
|
|
260
|
+
// YOLO: the picker is unreachable with nobody watching, and every
|
|
261
|
+
// unattended attempt this task may make has been made — so the only
|
|
262
|
+
// option left that terminates is ACCEPT, recorded as its own
|
|
263
|
+
// 'yolo-accepted' debt. Deliberately NOT a re-entry into autofix:
|
|
264
|
+
// MAX_AUTO_AUTOFIX exists to break a non-converging loop, and an
|
|
222
265
|
// auto-pick here would restart the budget from the site that proves it ran out.
|
|
223
|
-
const yoloChoice = autoFixNow ? null : yoloVerifyResolution(isYoloMode());
|
|
266
|
+
const yoloChoice = autoFixNow || yoloRescueNow ? null : yoloVerifyResolution(isYoloMode());
|
|
224
267
|
let choice;
|
|
225
268
|
if (yoloChoice !== null) {
|
|
226
269
|
choice = yoloChoice;
|
|
227
|
-
|
|
270
|
+
// NAME the branch. This line is the durable record of why a defect
|
|
271
|
+
// shipped; asserting a spent budget on all four branches made run
|
|
272
|
+
// 19's zero-attempt accepts read as an exhausted fixer.
|
|
273
|
+
await rec(`resolution: auto-ACCEPTED despite verify FAIL — ${yoloAcceptReason({
|
|
274
|
+
isUnobserved,
|
|
275
|
+
isFrozenBlocked,
|
|
276
|
+
recommend: recOutcome.recommend,
|
|
277
|
+
autoFixCount
|
|
278
|
+
})}, nobody to ask ${YOLO_STAMP}`);
|
|
228
279
|
}
|
|
229
|
-
else if (autoFixNow) {
|
|
280
|
+
else if (autoFixNow || yoloRescueNow) {
|
|
230
281
|
autoFixCount += 1;
|
|
231
|
-
|
|
232
|
-
|
|
282
|
+
if (yoloRescueNow)
|
|
283
|
+
yoloRescueUsed = true;
|
|
284
|
+
await rec(yoloRescueNow ?
|
|
285
|
+
`resolution: auto-AUTOFIX (${YOLO_STAMP} rescue — judge recommended ACCEPT with the unattended `
|
|
286
|
+
+ `budget unspent; one attempt, ${autoFixCount}/${MAX_AUTO_AUTOFIX})`
|
|
287
|
+
: `resolution: auto-AUTOFIX (recommended, unattended ${autoFixCount}/${MAX_AUTO_AUTOFIX})`);
|
|
288
|
+
active.ui.notify(`${p.tag}: verify FAIL on "${p.title}" — auto-fixing (${yoloRescueNow ? `${YOLO_STAMP} one attempt before accepting` : 'recommended'}, ${autoFixCount}/${MAX_AUTO_AUTOFIX})…`, 'info');
|
|
233
289
|
choice = { action: 'autofix' };
|
|
234
290
|
}
|
|
235
291
|
else {
|
|
@@ -292,7 +348,13 @@ export async function runGatesForTask(ctxIn, deps, p) {
|
|
|
292
348
|
// hand that diagnosis to the re-run so it fixes the located cause
|
|
293
349
|
// instead of re-deriving it from the bare FAIL line. Skipped when there
|
|
294
350
|
// is no researched rationale beyond the failure text itself.
|
|
295
|
-
|
|
351
|
+
// Only the picker branch may claim a person chose this: the two
|
|
352
|
+
// unattended branches already recorded themselves one line above, and a
|
|
353
|
+
// trail that says "user chose" when nobody was asked is the same lie the
|
|
354
|
+
// accept line used to tell.
|
|
355
|
+
if (!autoFixNow && !yoloRescueNow) {
|
|
356
|
+
await rec('resolution: user chose AUTOFIX — re-running the implementation turn');
|
|
357
|
+
}
|
|
296
358
|
active.ui.notify(`${p.tag}: autofixing "${p.title}"…`, 'info');
|
|
297
359
|
const diagnosis = (recOutcome.recommend === 'autofix'
|
|
298
360
|
&& recOutcome.rationale.length > 0
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mjasnikovs/pi-task",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.33.0",
|
|
4
4
|
"description": "Deterministic task planning and spec-orchestration for local models — crash-safe /task pipelines with verify/enforce gates, a real-time remote web view, and web/docs/fetch/worker subagent tools.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|