@mjasnikovs/pi-task 0.18.29 → 0.18.30
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.js +53 -16
- package/dist/task/gate-deps.js +2 -1
- package/dist/task/parsers.d.ts +18 -0
- package/dist/task/parsers.js +4 -3
- package/dist/task/phases.js +27 -3
- package/dist/task/task-gates.d.ts +8 -0
- package/dist/task/task-gates.js +26 -4
- package/dist/task/yolo.d.ts +74 -0
- package/dist/task/yolo.js +112 -0
- package/package.json +1 -1
package/dist/config/config.d.ts
CHANGED
|
@@ -61,6 +61,21 @@ export interface PiTaskConfig {
|
|
|
61
61
|
* a true hang doesn't cost half an hour of dead time.
|
|
62
62
|
*/
|
|
63
63
|
requestTimeoutMs: number;
|
|
64
|
+
/**
|
|
65
|
+
* UNATTENDED AUTO-PICK (see task/yolo.ts): wherever pi-task would stop and ask,
|
|
66
|
+
* take the option it already marks RECOMMENDED, stamp the artifact `(YOLO)` so
|
|
67
|
+
* an audit can tell a machine decided, and never notify. Lets a local model run
|
|
68
|
+
* a throwaway/test project end to end with nobody watching.
|
|
69
|
+
*
|
|
70
|
+
* Decided PER SITE, before the prompt is built — so the lone prompt notification
|
|
71
|
+
* (SessionUI.ask) is suppressed structurally, and the existing unattended budgets
|
|
72
|
+
* (MAX_AUTO_AUTOFIX, MAX_FINAL_GATE_AUTOFIX) still bound the loops they were
|
|
73
|
+
* written to bound. An auto-pick may cost time, never work: a question with no
|
|
74
|
+
* recommendation, or one the anti-synthesis guard demoted, is SKIPPED, not
|
|
75
|
+
* invented.
|
|
76
|
+
* DEFAULT OFF — this is never the behaviour of a normal, watched run.
|
|
77
|
+
*/
|
|
78
|
+
yoloMode: boolean;
|
|
64
79
|
}
|
|
65
80
|
/**
|
|
66
81
|
* The command-watchdog timeout choices offered by /task-config, newest-first in
|
package/dist/config/config.js
CHANGED
|
@@ -38,7 +38,9 @@ const DEFAULTS = {
|
|
|
38
38
|
researchCache: true,
|
|
39
39
|
searchProvider: 'exa',
|
|
40
40
|
extensionWhitelist: [],
|
|
41
|
-
requestTimeoutMs: DEFAULT_REQUEST_TIMEOUT_MS
|
|
41
|
+
requestTimeoutMs: DEFAULT_REQUEST_TIMEOUT_MS,
|
|
42
|
+
// OFF: auto-answering is for unattended throwaway runs only.
|
|
43
|
+
yoloMode: false
|
|
42
44
|
};
|
|
43
45
|
/**
|
|
44
46
|
* A hand-edited config can hold anything; keep only string entries so a stray
|
|
@@ -67,6 +69,11 @@ if (!G.loaded) {
|
|
|
67
69
|
delete parsed.searchProvider;
|
|
68
70
|
parsed.extensionWhitelist = sanitizeExtensionWhitelist(parsed.extensionWhitelist);
|
|
69
71
|
parsed.requestTimeoutMs = sanitizeRequestTimeoutMs(parsed.requestTimeoutMs);
|
|
72
|
+
// A hand-edited `"yoloMode": "false"` is a truthy string — it must not
|
|
73
|
+
// silently switch a watched run into unattended auto-pick. Only a real
|
|
74
|
+
// boolean counts; anything else falls back to the OFF default.
|
|
75
|
+
if (typeof parsed.yoloMode !== 'boolean')
|
|
76
|
+
delete parsed.yoloMode;
|
|
70
77
|
G.config = { ...DEFAULTS, ...parsed };
|
|
71
78
|
}
|
|
72
79
|
catch {
|
package/dist/config/register.js
CHANGED
|
@@ -107,6 +107,16 @@ const ITEMS = [
|
|
|
107
107
|
+ 'unbounded',
|
|
108
108
|
// Display human labels; the stored config value stays the ms number.
|
|
109
109
|
values: COMMAND_TIMEOUT_OPTIONS.map(o => o.label)
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
id: 'yoloMode',
|
|
113
|
+
label: 'yolo mode',
|
|
114
|
+
description: 'UNATTENDED: auto-answer every question with the option pi already recommends, '
|
|
115
|
+
+ 'and show no prompts at all — clarify/grill answers, the verify-FAIL picker '
|
|
116
|
+
+ '(auto-ACCEPT, recorded as a yolo debt), and the final-gate picker (autofix '
|
|
117
|
+
+ 'while the budget lasts, then leave the run FAILED). Every auto-pick is stamped '
|
|
118
|
+
+ '(YOLO) in the task file and debt ledger. For THROWAWAY/TEST projects you are '
|
|
119
|
+
+ 'not watching — a real run should decide these itself'
|
|
110
120
|
}
|
|
111
121
|
];
|
|
112
122
|
/** Human label for the stored command-timeout ms (falls back to the raw ms). */
|
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
* created files need a tsconfig registration every spec forbids). Cross-task
|
|
12
12
|
* contradiction: no unattended re-run can converge, so the gate loop records the
|
|
13
13
|
* defect and routes to the human picker instead of burning AUTOFIX rounds.
|
|
14
|
+
* - 'yolo-accepted' — YOLO MODE (unattended auto-pick, see yolo.ts) took the
|
|
15
|
+
* verify-FAIL picker's terminal option with nobody watching. It is NOT the
|
|
16
|
+
* 'accepted' class and must never collapse into it: 'accepted' asserts a HUMAN
|
|
17
|
+
* weighed this failing artifact and blessed it, which is exactly the assurance
|
|
18
|
+
* an auto-pick cannot give. Same re-check treatment, honest provenance.
|
|
14
19
|
* - 'cross-task-deletion' — the task's work DELETED a sibling task's committed
|
|
15
20
|
* deliverable (mx5 run 12 PROMPT 2: a fix child deleted TASK_0020's playwright ct
|
|
16
21
|
* files to green a lint) and the user ACCEPTed the verify-FAIL anyway, so the
|
|
@@ -18,7 +23,7 @@
|
|
|
18
23
|
* resolved iff the named file is back in the tree (a later task restored it),
|
|
19
24
|
* otherwise surfaced.
|
|
20
25
|
*/
|
|
21
|
-
export type DebtOrigin = 'accepted' | 'enforce-revert' | 'frozen-blocked' | 'cross-task-deletion';
|
|
26
|
+
export type DebtOrigin = 'accepted' | 'enforce-revert' | 'frozen-blocked' | 'cross-task-deletion' | 'yolo-accepted';
|
|
22
27
|
/** One recorded defect: the task, why its VERIFY failed, and how it was recorded. */
|
|
23
28
|
export interface AcceptDebt {
|
|
24
29
|
taskId: string;
|
|
@@ -77,6 +82,13 @@ export declare function recordCrossTaskDeletionDebt(cwd: string, taskId: string,
|
|
|
77
82
|
path: string;
|
|
78
83
|
owner: string;
|
|
79
84
|
}): Promise<void>;
|
|
85
|
+
/**
|
|
86
|
+
* Record a YOLO-ACCEPTED debt: unattended auto-pick took the verify-FAIL picker's
|
|
87
|
+
* ACCEPT branch because there was nobody to ask (yolo.ts). Its own origin — and
|
|
88
|
+
* therefore its own line in the final gate's surfaced report — so a later audit
|
|
89
|
+
* reading only the artifacts can never read it as "a human decided this".
|
|
90
|
+
*/
|
|
91
|
+
export declare function recordYoloAcceptDebt(cwd: string, taskId: string, reason: string): Promise<void>;
|
|
80
92
|
/**
|
|
81
93
|
* The deleted path a cross-task-deletion debt names (the fixed shape
|
|
82
94
|
* recordCrossTaskDeletionDebt writes). Null on any other reason text — an
|
package/dist/task/accept-debt.js
CHANGED
|
@@ -76,7 +76,8 @@ export function parseAcceptDebts(raw) {
|
|
|
76
76
|
reason: parts[1].trim(),
|
|
77
77
|
...((origin === 'enforce-revert'
|
|
78
78
|
|| origin === 'frozen-blocked'
|
|
79
|
-
|| origin === 'cross-task-deletion'
|
|
79
|
+
|| origin === 'cross-task-deletion'
|
|
80
|
+
|| origin === 'yolo-accepted') ?
|
|
80
81
|
{ origin: origin }
|
|
81
82
|
: {})
|
|
82
83
|
});
|
|
@@ -173,6 +174,19 @@ export async function recordCrossTaskDeletionDebt(cwd, taskId, deletion) {
|
|
|
173
174
|
origin: 'cross-task-deletion'
|
|
174
175
|
});
|
|
175
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* Record a YOLO-ACCEPTED debt: unattended auto-pick took the verify-FAIL picker's
|
|
179
|
+
* ACCEPT branch because there was nobody to ask (yolo.ts). Its own origin — and
|
|
180
|
+
* therefore its own line in the final gate's surfaced report — so a later audit
|
|
181
|
+
* reading only the artifacts can never read it as "a human decided this".
|
|
182
|
+
*/
|
|
183
|
+
export async function recordYoloAcceptDebt(cwd, taskId, reason) {
|
|
184
|
+
await appendDebt(cwd, {
|
|
185
|
+
taskId: taskId.trim(),
|
|
186
|
+
reason: normaliseReason(reason),
|
|
187
|
+
origin: 'yolo-accepted'
|
|
188
|
+
});
|
|
189
|
+
}
|
|
176
190
|
/**
|
|
177
191
|
* The deleted path a cross-task-deletion debt names (the fixed shape
|
|
178
192
|
* recordCrossTaskDeletionDebt writes). Null on any other reason text — an
|
|
@@ -324,5 +338,8 @@ export function describeDebt(d) {
|
|
|
324
338
|
if (d.origin === 'cross-task-deletion') {
|
|
325
339
|
return "a sibling task's committed deliverable was DELETED by this task's work and the deletion was accepted (still missing from the tree)";
|
|
326
340
|
}
|
|
341
|
+
if (d.origin === 'yolo-accepted') {
|
|
342
|
+
return 'auto-ACCEPTED by YOLO mode despite verify-FAIL (unattended — no human weighed this)';
|
|
343
|
+
}
|
|
327
344
|
return 'accepted despite verify-FAIL';
|
|
328
345
|
}
|
|
@@ -31,6 +31,7 @@ import { runFinalIntegrationGate } from './final-gate.js';
|
|
|
31
31
|
import { describeDebt } from './accept-debt.js';
|
|
32
32
|
import { classifyFinalGateAnswer, MAX_FINAL_GATE_AUTOFIX, FINAL_LEAVE_LABEL, FINAL_LEAVE_VALUE, FINAL_ACCEPT_LABEL, FINAL_ACCEPT_VALUE, FINAL_AUTOFIX_LABEL, FINAL_AUTOFIX_VALUE } from './final-gate-fix.js';
|
|
33
33
|
import { getConfig } from '../config/config.js';
|
|
34
|
+
import { isYoloMode, yoloPickAnswer, yoloFinalGateChoice, YOLO_STAMP } from './yolo.js';
|
|
34
35
|
import { configureResearchRun } from '../workers/research-cache.js';
|
|
35
36
|
import { CONTRACT_EXTRACT_PROMPT, parseContractLines, keepGroundedContracts, appendContracts } from './contracts.js';
|
|
36
37
|
import { reconcileTitleSources } from './decompose-fidelity.js';
|
|
@@ -409,6 +410,20 @@ export async function planAuto(ctx, cwd, feature, deps) {
|
|
|
409
410
|
// the recommended one tinted green; an open question shows the bare text
|
|
410
411
|
// prompt. No verbose "Recommended:" / "press Enter to accept" scaffolding.
|
|
411
412
|
const twoOption = plainSuggested !== undefined && plainAlt !== undefined;
|
|
413
|
+
// YOLO: take the recommended option (index 0 / the green card) without ever
|
|
414
|
+
// building the prompt. Clarify has no anti-synthesis channel — it runs before
|
|
415
|
+
// any research — so the only step-aside here is a question that carries no
|
|
416
|
+
// recommendation to take; that one is skipped rather than guessed.
|
|
417
|
+
const yolo = yoloPickAnswer(isYoloMode(), {
|
|
418
|
+
...(plainSuggested !== undefined && { suggested: plainSuggested }),
|
|
419
|
+
...(plainAlt !== undefined && { alt: plainAlt })
|
|
420
|
+
});
|
|
421
|
+
if (yolo !== null) {
|
|
422
|
+
const auto = yolo.kind === 'answer' ? yolo.answer : `(skipped — ${yolo.note})`;
|
|
423
|
+
answers.push(`Q${answers.length + 1}: ${plainQ}\n`
|
|
424
|
+
+ `A${answers.length + 1}: ${auto} ${YOLO_STAMP}`);
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
412
427
|
const options = twoOption ?
|
|
413
428
|
[
|
|
414
429
|
{
|
|
@@ -1005,21 +1020,41 @@ export async function runAutoLoop(ctx, cwd, id, deps) {
|
|
|
1005
1020
|
+ (fixAttempts > 0 ?
|
|
1006
1021
|
`\n\nAutofix attempts so far: ${fixAttempts}/${MAX_FINAL_GATE_AUTOFIX}.`
|
|
1007
1022
|
: '');
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
+
// YOLO: keep autofixing WHILE the card is still offered — the
|
|
1024
|
+
// loop withdraws it after MAX_FINAL_GATE_AUTOFIX, so the cap
|
|
1025
|
+
// that bounds a non-converging fix pass still bounds this —
|
|
1026
|
+
// then LEAVE the run failed. Never 'accept': an unattended run
|
|
1027
|
+
// that could not green the whole-repo gate has not produced a
|
|
1028
|
+
// working project, and mx5 run 13 shows what an accepted FAIL
|
|
1029
|
+
// looks like afterwards (a shipped app that 404s at `/`).
|
|
1030
|
+
const yoloFinal = yoloFinalGateChoice(isYoloMode(), canAutofix);
|
|
1031
|
+
if (yoloFinal !== null) {
|
|
1032
|
+
await recGate(`final-gate: auto-chose ${yoloFinal.action.toUpperCase()} ${YOLO_STAMP}`);
|
|
1033
|
+
}
|
|
1034
|
+
const answer = yoloFinal !== null ?
|
|
1035
|
+
yoloFinal.action === 'autofix' ?
|
|
1036
|
+
FINAL_AUTOFIX_VALUE
|
|
1037
|
+
: FINAL_LEAVE_VALUE
|
|
1038
|
+
: await new SessionUI(active).ask({
|
|
1039
|
+
localTitle: 'Final integration gate failed — how should pi proceed?',
|
|
1040
|
+
displayQuestion: question,
|
|
1041
|
+
question,
|
|
1042
|
+
recommended: FINAL_LEAVE_LABEL,
|
|
1043
|
+
recommended2: canAutofix ? FINAL_AUTOFIX_LABEL : FINAL_ACCEPT_LABEL,
|
|
1044
|
+
allowSkip: false,
|
|
1045
|
+
options: [
|
|
1046
|
+
{ label: FINAL_LEAVE_LABEL, value: FINAL_LEAVE_VALUE },
|
|
1047
|
+
...(canAutofix ?
|
|
1048
|
+
[
|
|
1049
|
+
{
|
|
1050
|
+
label: FINAL_AUTOFIX_LABEL,
|
|
1051
|
+
value: FINAL_AUTOFIX_VALUE
|
|
1052
|
+
}
|
|
1053
|
+
]
|
|
1054
|
+
: []),
|
|
1055
|
+
{ label: FINAL_ACCEPT_LABEL, value: FINAL_ACCEPT_VALUE }
|
|
1056
|
+
]
|
|
1057
|
+
});
|
|
1023
1058
|
const choice = classifyFinalGateAnswer(answer);
|
|
1024
1059
|
if (choice.action === 'accept') {
|
|
1025
1060
|
await recGate('final-gate: FAIL accepted by user');
|
|
@@ -1064,7 +1099,9 @@ export async function runAutoLoop(ctx, cwd, id, deps) {
|
|
|
1064
1099
|
}
|
|
1065
1100
|
// Leave failed — the dismissal default, unchanged from the
|
|
1066
1101
|
// two-option picker (an unavailable autofix demotes here too).
|
|
1067
|
-
await recGate(
|
|
1102
|
+
await recGate(yoloFinal !== null ?
|
|
1103
|
+
`final-gate: left failed — autofix budget spent, nobody to ask ${YOLO_STAMP}`
|
|
1104
|
+
: 'final-gate: left failed (user)');
|
|
1068
1105
|
await updateTaskFrontMatter(cwd, id, { state: 'failed' });
|
|
1069
1106
|
announceDone(active, `${id} finished all tasks but FAILED the final integration gate — ${fin.reason.slice(0, 200)} — fix and /task-auto-resume (the gate re-runs).`, 'error');
|
|
1070
1107
|
return;
|
package/dist/task/gate-deps.js
CHANGED
|
@@ -21,7 +21,7 @@ import { runGuidelineEnforcement, classifyEnforceChildFailure } from './enforce-
|
|
|
21
21
|
import { runWorkVerification, extractSpecForVerification } from './verify-work.js';
|
|
22
22
|
import { readEnvNotes, appendEnvNotes } from './env-notes.js';
|
|
23
23
|
import { readContracts } from './contracts.js';
|
|
24
|
-
import { recordAcceptDebt, recordEnforceRevertDebt, recordFrozenBlockedDebt, recordCrossTaskDeletionDebt } from './accept-debt.js';
|
|
24
|
+
import { recordAcceptDebt, recordEnforceRevertDebt, recordFrozenBlockedDebt, recordCrossTaskDeletionDebt, recordYoloAcceptDebt } from './accept-debt.js';
|
|
25
25
|
import { runRepoHealthCheck } from './repo-health-check.js';
|
|
26
26
|
import { runFinalIntegrationGate, discoverGateCommandLabels } from './final-gate.js';
|
|
27
27
|
import { runFinalGateAutofix } from './final-gate-fix.js';
|
|
@@ -337,6 +337,7 @@ export function buildGateDeps(params) {
|
|
|
337
337
|
// Durable ACCEPT-despite-verify-FAIL ledger under .pi-tasks/ (survives
|
|
338
338
|
// discardEdits): the final integration gate re-checks each debt at run end.
|
|
339
339
|
recordAcceptDebt: (cwd2, taskId, reason) => recordAcceptDebt(cwd2, taskId, reason),
|
|
340
|
+
recordYoloAcceptDebt: (cwd2, taskId, reason) => recordYoloAcceptDebt(cwd2, taskId, reason),
|
|
340
341
|
recordEnforceRevertDebt: (cwd2, taskId, reason) => recordEnforceRevertDebt(cwd2, taskId, reason),
|
|
341
342
|
// Durable cross-task-contradiction ledger (PROMPT 1 layer B): a repo-health
|
|
342
343
|
// FAIL whose only fix is an edit to a path this task's spec froze — recorded
|
package/dist/task/parsers.d.ts
CHANGED
|
@@ -3,6 +3,23 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Pure functions that parse raw model output into structured data.
|
|
5
5
|
*/
|
|
6
|
+
/**
|
|
7
|
+
* WHY an auto-answer came back `unknown` — the producers are otherwise
|
|
8
|
+
* indistinguishable at the call site, and they are NOT equivalent: only
|
|
9
|
+
* 'api-synthesis' marks a recommendation a machine must never take (see
|
|
10
|
+
* task/yolo.ts). Mechanical tag, never a text pattern-match:
|
|
11
|
+
* - 'model-unknown' — the child itself emitted UNKNOWN (or the parser salvaged a
|
|
12
|
+
* bare recommendation): a genuine open fork carrying a best-effort suggestion.
|
|
13
|
+
* - 'api-synthesis' — the answer was ANSWERED, then DEMOTED because it names an
|
|
14
|
+
* API identifier absent from the research and the question, in a namespace the
|
|
15
|
+
* research covers (mx5 run 13: an invented `Bun.mkdirSync` reached requirements
|
|
16
|
+
* AND the VERIFY block). The suggestion rides along for a HUMAN to judge.
|
|
17
|
+
* - 'integration' — an integration/build-wiring unknown no fetched doc grounded;
|
|
18
|
+
* a wrong guess is a structural landmine, so the model's answer is offered as
|
|
19
|
+
* a recommendation instead of being taken silently.
|
|
20
|
+
* - 'threw' — the child failed; there is no recommendation at all.
|
|
21
|
+
*/
|
|
22
|
+
export type AutoAnswerUnknownReason = 'model-unknown' | 'api-synthesis' | 'integration' | 'threw';
|
|
6
23
|
export type AutoAnswer = {
|
|
7
24
|
kind: 'answered';
|
|
8
25
|
text: string;
|
|
@@ -12,6 +29,7 @@ export type AutoAnswer = {
|
|
|
12
29
|
suggested?: string;
|
|
13
30
|
alt?: string;
|
|
14
31
|
raw: string;
|
|
32
|
+
reason?: AutoAnswerUnknownReason;
|
|
15
33
|
};
|
|
16
34
|
/** One /task-auto clarify question with its model-recommended default answer. */
|
|
17
35
|
export interface ClarifyQuestion {
|
package/dist/task/parsers.js
CHANGED
|
@@ -148,7 +148,8 @@ export function parseAutoAnswer(raw) {
|
|
|
148
148
|
kind: 'unknown',
|
|
149
149
|
...(suggested !== undefined && { suggested }),
|
|
150
150
|
...(alt !== undefined && { alt }),
|
|
151
|
-
raw
|
|
151
|
+
raw,
|
|
152
|
+
reason: 'model-unknown'
|
|
152
153
|
};
|
|
153
154
|
}
|
|
154
155
|
// Last-resort salvage: the model emitted no tag at all. Take the first line
|
|
@@ -159,8 +160,8 @@ export function parseAutoAnswer(raw) {
|
|
|
159
160
|
// answer than to pre-fill a meaningless preamble.
|
|
160
161
|
const salvaged = lines.find(l => !l.endsWith(':'));
|
|
161
162
|
if (salvaged)
|
|
162
|
-
return { kind: 'unknown', suggested: salvaged, raw };
|
|
163
|
-
return { kind: 'unknown', raw };
|
|
163
|
+
return { kind: 'unknown', suggested: salvaged, raw, reason: 'model-unknown' };
|
|
164
|
+
return { kind: 'unknown', raw, reason: 'model-unknown' };
|
|
164
165
|
}
|
|
165
166
|
// ─── Verify tooling output parser ────────────────────────────────────────────
|
|
166
167
|
export function parseVerifyToolingOutput(output) {
|
package/dist/task/phases.js
CHANGED
|
@@ -37,6 +37,7 @@ import { readContracts, buildContractsBlock, buildContractsVerifyBlock } from '.
|
|
|
37
37
|
import { readRequirements, buildRequirementsBlock } from './requirements.js';
|
|
38
38
|
import { runPhaseChild, runPhaseWithLoopGuard, runWithEmphasisRetry, prependHint, USER_CANCELLED } from './child-runner.js';
|
|
39
39
|
import { SessionUI } from '../remote/bridge.js';
|
|
40
|
+
import { isYoloMode, yoloPickAutoAnswer, YOLO_STAMP } from './yolo.js';
|
|
40
41
|
// ─── Re-export constants from their home modules ────────────────────────────
|
|
41
42
|
export { MAX_GRILL_QUESTIONS };
|
|
42
43
|
// ─── Tooling helpers ─────────────────────────────────────────────────────────
|
|
@@ -665,7 +666,15 @@ export async function phaseAutoAnswer(deps, refined, research, question, autoDep
|
|
|
665
666
|
const still = reasked ?? parsed;
|
|
666
667
|
const suggested = still.kind === 'answered' ? still.text : parsed.text;
|
|
667
668
|
deps.logDebug?.('grill-auto: answer still carries an unverified API — surfacing to user');
|
|
668
|
-
parsed = {
|
|
669
|
+
parsed = {
|
|
670
|
+
kind: 'unknown',
|
|
671
|
+
suggested,
|
|
672
|
+
raw: still.raw,
|
|
673
|
+
// Tagged so a call site can tell this producer from the other
|
|
674
|
+
// two: the suggestion is PROVEN to name an unverified API, so
|
|
675
|
+
// it may only be judged by a human (yolo.ts must not take it).
|
|
676
|
+
reason: 'api-synthesis'
|
|
677
|
+
};
|
|
669
678
|
}
|
|
670
679
|
else {
|
|
671
680
|
parsed = reasked;
|
|
@@ -683,13 +692,13 @@ export async function phaseAutoAnswer(deps, refined, research, question, autoDep
|
|
|
683
692
|
if (parsed.kind === 'answered' && !docResolved && isIntegrationUnknown(question)) {
|
|
684
693
|
deps.logDebug?.(`grill-auto: integration unknown unresolved by fetch — surfacing to user `
|
|
685
694
|
+ `instead of auto-answering: ${question.replace(/\s+/g, ' ').slice(0, 120)}`);
|
|
686
|
-
return { kind: 'unknown', suggested: parsed.text, raw: parsed.raw };
|
|
695
|
+
return { kind: 'unknown', suggested: parsed.text, raw: parsed.raw, reason: 'integration' };
|
|
687
696
|
}
|
|
688
697
|
return parsed;
|
|
689
698
|
}
|
|
690
699
|
catch (err) {
|
|
691
700
|
const msg = err instanceof Error ? err.message : String(err);
|
|
692
|
-
return { kind: 'unknown', raw: `(threw: ${msg})
|
|
701
|
+
return { kind: 'unknown', raw: `(threw: ${msg})`, reason: 'threw' };
|
|
693
702
|
}
|
|
694
703
|
}
|
|
695
704
|
export async function phaseGrill(deps, ctx, widgetState, refined, research) {
|
|
@@ -755,6 +764,21 @@ export async function phaseGrill(deps, ctx, widgetState, refined, research) {
|
|
|
755
764
|
// locally — each answer in its own bounding box, the recommended one
|
|
756
765
|
// tinted green; an open question shows the bare text prompt.
|
|
757
766
|
const twoOption = plainSuggested !== undefined && plainAlt !== undefined;
|
|
767
|
+
// YOLO: take the recommended option and never build the prompt (which
|
|
768
|
+
// is also what suppresses its notification — see yolo.ts). An answer the
|
|
769
|
+
// anti-synthesis guard demoted, or a question with no recommendation at
|
|
770
|
+
// all, is SKIPPED instead: costing the spec one unanswered fork is the
|
|
771
|
+
// guard direction, promoting a hallucination is not.
|
|
772
|
+
const yolo = yoloPickAutoAnswer(isYoloMode(), auto);
|
|
773
|
+
if (yolo !== null) {
|
|
774
|
+
answer =
|
|
775
|
+
yolo.kind === 'answer' ?
|
|
776
|
+
stripInlineMarkdown(yolo.answer)
|
|
777
|
+
: `(skipped — ${yolo.note})`;
|
|
778
|
+
out.push(`A${n + 1}: ${answer} ${YOLO_STAMP}`);
|
|
779
|
+
qa.push(`Q${n + 1}: ${plainQ}\nA${n + 1}: ${answer} ${YOLO_STAMP}`);
|
|
780
|
+
continue;
|
|
781
|
+
}
|
|
758
782
|
const options = twoOption ?
|
|
759
783
|
[
|
|
760
784
|
{
|
|
@@ -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
|
|
@@ -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;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* yolo — unattended auto-pick of the option pi-task already marks RECOMMENDED.
|
|
3
|
+
*
|
|
4
|
+
* GOAL: let a local model take a throwaway/test project end to end with nobody
|
|
5
|
+
* watching. Wherever pi-task would stop and ask, it takes the recommended option,
|
|
6
|
+
* STAMPS the artifact so a later audit can see a machine decided, and never
|
|
7
|
+
* notifies. OFF by default; never the behaviour of a normal run.
|
|
8
|
+
*
|
|
9
|
+
* WHY PER-SITE, NOT ONE HOOK (the trap this module exists to avoid): the single
|
|
10
|
+
* interactive choke point is SessionUI.ask() (remote/bridge.ts) — auto-picking
|
|
11
|
+
* "index 0" inside it would be one tiny patch, and it would be wrong. The
|
|
12
|
+
* verify-FAIL picker is reached ONLY AFTER MAX_AUTO_AUTOFIX unattended autofix
|
|
13
|
+
* attempts have already failed, yet it STILL tints AUTOFIX as recommended: a
|
|
14
|
+
* central hook would pick AUTOFIX forever and defeat the exact cap that exists to
|
|
15
|
+
* break a non-converging loop. So every site decides for itself, BEFORE ask() is
|
|
16
|
+
* called — which also means the prompt notification (the lone pushNotify in
|
|
17
|
+
* ask()) is suppressed structurally, with zero suppression code.
|
|
18
|
+
*
|
|
19
|
+
* Guard direction (repo constraint): an auto-pick may cost time, never work.
|
|
20
|
+
* Anything this module cannot stand behind — no recommendation to take, an answer
|
|
21
|
+
* the anti-synthesis guard proved unverifiable — steps ASIDE ('skip') rather than
|
|
22
|
+
* inventing a decision.
|
|
23
|
+
*
|
|
24
|
+
* The policy functions are PURE and take `enabled` explicitly, so each site's
|
|
25
|
+
* behaviour is unit-tested with the flag both ways; only {@link isYoloMode} reads
|
|
26
|
+
* the config.
|
|
27
|
+
*/
|
|
28
|
+
import { getConfig } from '../config/config.js';
|
|
29
|
+
/**
|
|
30
|
+
* Visible provenance marker on EVERY artifact an auto-pick writes (gate trails,
|
|
31
|
+
* task-file Q&A, the debt ledger's reason text). A later audit reading only the
|
|
32
|
+
* artifacts must never mistake an auto-pick for a human decision — that is the
|
|
33
|
+
* same confusion the accept-debt origins were introduced to prevent.
|
|
34
|
+
*/
|
|
35
|
+
export const YOLO_STAMP = '(YOLO)';
|
|
36
|
+
/** Is unattended auto-pick on for this run? The ONLY config read in this module. */
|
|
37
|
+
export function isYoloMode() {
|
|
38
|
+
return getConfig().yoloMode;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The clarify/grill policy: take the RECOMMENDED option — which is positional,
|
|
42
|
+
* index 0 of the card list (question-box.ts tints it green), i.e. `suggested`,
|
|
43
|
+
* falling back to the B-side `alt` when a fork offers only that.
|
|
44
|
+
*
|
|
45
|
+
* `unsafe` is the step-aside channel: a caller that KNOWS the recommendation must
|
|
46
|
+
* not be auto-accepted passes why, and the question is skipped instead. Its one
|
|
47
|
+
* producer today is the anti-synthesis demotion — an answer proven to name a
|
|
48
|
+
* hallucinated API identifier. Auto-accepting that would re-promote exactly the
|
|
49
|
+
* invention the demotion was built to stop (it reached requirements AND the VERIFY
|
|
50
|
+
* block in mx5 run 13), so a machine may never take it; a human still can.
|
|
51
|
+
*/
|
|
52
|
+
export function yoloPickAnswer(enabled, opts) {
|
|
53
|
+
if (!enabled)
|
|
54
|
+
return null;
|
|
55
|
+
if (opts.unsafe !== undefined && opts.unsafe.length > 0) {
|
|
56
|
+
return { kind: 'skip', note: opts.unsafe };
|
|
57
|
+
}
|
|
58
|
+
const pick = opts.suggested ?? opts.alt;
|
|
59
|
+
if (pick === undefined || pick.trim().length === 0) {
|
|
60
|
+
return { kind: 'skip', note: 'no recommended option to take' };
|
|
61
|
+
}
|
|
62
|
+
return { kind: 'answer', answer: pick };
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The same policy expressed over an {@link AutoAnswer}, for the grill site. Only
|
|
66
|
+
* the ANTI-SYNTHESIS unknown is unsafe: the other two producers (an integration
|
|
67
|
+
* unknown research could not ground, a child that threw) carry an ordinary
|
|
68
|
+
* best-effort recommendation, which is precisely what a human would be shown as
|
|
69
|
+
* the green card. The variants are told apart by the union's `reason` tag — never
|
|
70
|
+
* by pattern-matching the answer text.
|
|
71
|
+
*/
|
|
72
|
+
export function yoloPickAutoAnswer(enabled, auto) {
|
|
73
|
+
if (!enabled)
|
|
74
|
+
return null;
|
|
75
|
+
if (auto.kind === 'answered')
|
|
76
|
+
return { kind: 'answer', answer: auto.text };
|
|
77
|
+
return yoloPickAnswer(enabled, {
|
|
78
|
+
...(auto.suggested !== undefined && { suggested: auto.suggested }),
|
|
79
|
+
...(auto.alt !== undefined && { alt: auto.alt }),
|
|
80
|
+
...(auto.reason === 'api-synthesis' && {
|
|
81
|
+
unsafe: 'the suggested answer names an unverified API identifier — needs a human'
|
|
82
|
+
})
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The verify-FAIL picker policy: ACCEPT (and write a debt), never AUTOFIX.
|
|
87
|
+
*
|
|
88
|
+
* Not a preference — a bound. Every branch that REACHES this picker has already
|
|
89
|
+
* spent its unattended budget: the loop auto-runs AUTOFIX while the research
|
|
90
|
+
* recommends it, up to MAX_AUTO_AUTOFIX consecutive failures, and only then hands
|
|
91
|
+
* over. Answering AUTOFIX here would restart that budget from a site whose whole
|
|
92
|
+
* purpose is that the budget ran out. So YOLO takes the terminal option and
|
|
93
|
+
* records the defect ('yolo-accepted' — an origin a human never produces).
|
|
94
|
+
*/
|
|
95
|
+
export function yoloVerifyResolution(enabled) {
|
|
96
|
+
return enabled ? { action: 'accept' } : null;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The final-integration-gate policy: keep autofixing WHILE the picker still
|
|
100
|
+
* offers that card (the loop withdraws it after MAX_FINAL_GATE_AUTOFIX attempts),
|
|
101
|
+
* then leave the run FAILED.
|
|
102
|
+
*
|
|
103
|
+
* 'leave', not 'accept': an unattended run that cannot fix the whole-repo gate has
|
|
104
|
+
* not produced a working project, and the honest terminal state is a failed run a
|
|
105
|
+
* resume can re-enter — mx5 run 13 ended "FAIL accepted by user" on an app that
|
|
106
|
+
* 404'd at `/`, and that acceptance is what made the failure look like a success.
|
|
107
|
+
*/
|
|
108
|
+
export function yoloFinalGateChoice(enabled, canAutofix) {
|
|
109
|
+
if (!enabled)
|
|
110
|
+
return null;
|
|
111
|
+
return canAutofix ? { action: 'autofix' } : { action: 'leave' };
|
|
112
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mjasnikovs/pi-task",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.30",
|
|
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",
|