@mjasnikovs/pi-task 0.28.1 → 0.28.2
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.
|
@@ -55,6 +55,25 @@ export interface AutoDeps extends GateDeps {
|
|
|
55
55
|
* handling is skipped entirely (prior behavior).
|
|
56
56
|
*/
|
|
57
57
|
pendingChanges?: (cwd: string) => Promise<string[]>;
|
|
58
|
+
/**
|
|
59
|
+
* Re-derive the still-open ACCEPT-debt ledger against the tree AS IT IS NOW
|
|
60
|
+
* (final-gate.ts `deriveOpenDebts`). Needed because the run's "N recorded
|
|
61
|
+
* verify-FAIL defect(s) are STILL unresolved" report used to be built from the
|
|
62
|
+
* FIRST gate result and the converged-autofix path then rebuilt the gate
|
|
63
|
+
* outcome as a bare `{ok, reason}` — so `openDebts` was not merely un-actioned,
|
|
64
|
+
* it was GONE from the value, and no code path could ever clear, re-check or
|
|
65
|
+
* act on it (mx5 run 18: four defects reported STILL OPEN at 14:58, one of them
|
|
66
|
+
* fixed by the autofix that converged at 15:03, and the report never moved).
|
|
67
|
+
*
|
|
68
|
+
* `staticOk` is the caller's PROOF about the current statics, never a guess:
|
|
69
|
+
* pass true only where the gate itself just passed them. Absent (tests) → the
|
|
70
|
+
* post-autofix re-check is skipped and the pre-autofix report stands, exactly
|
|
71
|
+
* the prior behavior.
|
|
72
|
+
*/
|
|
73
|
+
recheckOpenDebts?: (cwd: string, staticOk: boolean) => Promise<{
|
|
74
|
+
openDebts: AcceptDebt[];
|
|
75
|
+
debtNote?: string;
|
|
76
|
+
}>;
|
|
58
77
|
}
|
|
59
78
|
/**
|
|
60
79
|
* Expand any @file references in the feature text by appending each referenced
|
|
@@ -33,7 +33,7 @@ import { getParentContextWindow, resolveContextUsage } from './context-usage.js'
|
|
|
33
33
|
import { buildGateDeps, collectTreeChanges } from './gate-deps.js';
|
|
34
34
|
import { runGatesForTask } from './task-gates.js';
|
|
35
35
|
import { gitUnmergedPaths, gitStashRef } from './auto-commit.js';
|
|
36
|
-
import { runFinalIntegrationGate } from './final-gate.js';
|
|
36
|
+
import { runFinalIntegrationGate, deriveOpenDebts } from './final-gate.js';
|
|
37
37
|
import { describeDebt, recordFinalGateUnobservedDebt } from './accept-debt.js';
|
|
38
38
|
import { applyDemotions, isNonProgress, normalizeFailureDetail, rankedFirstFailure, unobservedDebtReason } from './final-gate-progress.js';
|
|
39
39
|
import { classifyFinalGateAnswer, MAX_FINAL_GATE_AUTOFIX, FINAL_LEAVE_LABEL, FINAL_LEAVE_VALUE, FINAL_ACCEPT_LABEL, FINAL_ACCEPT_VALUE, FINAL_AUTOFIX_LABEL, FINAL_AUTOFIX_VALUE, STRANDED_FIX_COMMIT, strandedFixNote } from './final-gate-fix.js';
|
|
@@ -1103,7 +1103,11 @@ function defaultDeps(ctx, cwd, signal, title) {
|
|
|
1103
1103
|
pendingChanges: async (cwd2) => {
|
|
1104
1104
|
const changes = await collectTreeChanges(cwd2, signal);
|
|
1105
1105
|
return [...changes.modified, ...changes.added, ...changes.deleted].sort();
|
|
1106
|
-
}
|
|
1106
|
+
},
|
|
1107
|
+
// Re-derive the debt ledger against the FINAL tree after a converged
|
|
1108
|
+
// autofix (nexttask 6). Only ever reached from inside the gate's own
|
|
1109
|
+
// resolution loop, so it needs no `verify work` switch of its own.
|
|
1110
|
+
recheckOpenDebts: (cwd2, staticOk) => deriveOpenDebts(cwd2, staticOk)
|
|
1107
1111
|
};
|
|
1108
1112
|
}
|
|
1109
1113
|
// ─── Loop ────────────────────────────────────────────────────────────────────
|
|
@@ -1222,12 +1226,79 @@ export async function runAutoLoop(ctx, cwd, id, deps) {
|
|
|
1222
1226
|
// gate moment — on PASS or FAIL — so a run never completes silently
|
|
1223
1227
|
// carrying an accepted defect. Informational: the per-task ACCEPT was
|
|
1224
1228
|
// already a human decision, so this reports, it does not re-fail.
|
|
1225
|
-
|
|
1226
|
-
|
|
1229
|
+
const debtKey = (d) => `${d.taskId}\t${d.reason}`;
|
|
1230
|
+
const surfaceOpenDebts = async (debts) => {
|
|
1231
|
+
if (debts.length === 0)
|
|
1232
|
+
return;
|
|
1233
|
+
for (const d of debts) {
|
|
1227
1234
|
await recGate(`defect STILL OPEN — ${d.taskId || '(unknown task)'}: ${describeDebt(d)}: ${d.reason.slice(0, 240)}${d.conflict ? ` [CONFLICTING CLAIM — ${d.conflict}]` : ''}`);
|
|
1228
1235
|
}
|
|
1229
|
-
active.ui.notify(`${id}: ${
|
|
1230
|
-
}
|
|
1236
|
+
active.ui.notify(`${id}: ${debts.length} recorded verify-FAIL defect(s) are STILL unresolved at run end — see the gate trail.`, 'warning');
|
|
1237
|
+
};
|
|
1238
|
+
// What was REPORTED, so a post-autofix re-derivation can be
|
|
1239
|
+
// compared against it rather than blindly re-printed.
|
|
1240
|
+
let reportedDebts = fin.openDebts ?? [];
|
|
1241
|
+
await surfaceOpenDebts(reportedDebts);
|
|
1242
|
+
/**
|
|
1243
|
+
* nexttask 6 (mx5 run 18). The lines above are emitted from the
|
|
1244
|
+
* FIRST gate result; the converged-autofix paths below used to
|
|
1245
|
+
* rebuild `fin` as `{ok, reason}`, so `openDebts` did not survive
|
|
1246
|
+
* the fix pass — the run's last word on its own defects was a
|
|
1247
|
+
* snapshot of a tree that no longer existed, and no code path
|
|
1248
|
+
* could clear, re-check or act on it. Re-derive here, against the
|
|
1249
|
+
* tree the run actually ends with, and correct the record.
|
|
1250
|
+
*
|
|
1251
|
+
* FP-safe by inheritance: `deriveOpenDebts` auto-closes only what
|
|
1252
|
+
* a deterministic check can stand behind (a static-class debt when
|
|
1253
|
+
* the statics provably pass, a cross-task-deletion whose file is
|
|
1254
|
+
* back). Anything model-judged or behavioral STAYS OPEN —
|
|
1255
|
+
* `inv-no-false-clear`. `staticOk` is therefore only ever passed
|
|
1256
|
+
* true where the gate itself just passed the statics.
|
|
1257
|
+
*/
|
|
1258
|
+
const reconcileDebts = async (staticOk) => {
|
|
1259
|
+
if (!deps.recheckOpenDebts)
|
|
1260
|
+
return;
|
|
1261
|
+
let fresh;
|
|
1262
|
+
try {
|
|
1263
|
+
fresh = await deps.recheckOpenDebts(cwd, staticOk);
|
|
1264
|
+
}
|
|
1265
|
+
catch {
|
|
1266
|
+
// A ledger read fault is inconclusive: say nothing rather
|
|
1267
|
+
// than imply the defects cleared.
|
|
1268
|
+
return;
|
|
1269
|
+
}
|
|
1270
|
+
fin = {
|
|
1271
|
+
...fin,
|
|
1272
|
+
openDebts: fresh.openDebts,
|
|
1273
|
+
...(fresh.debtNote ? { debtNote: fresh.debtNote } : {})
|
|
1274
|
+
};
|
|
1275
|
+
// Identity is (task, origin, reason), but a RESOLUTION claim
|
|
1276
|
+
// needs more than a key miss: a ledger entry whose TEXT changed
|
|
1277
|
+
// is the same defect re-recorded, never a fix. So a debt counts
|
|
1278
|
+
// as closed only when nothing for that (task, origin) survives.
|
|
1279
|
+
const slot = (d) => `${d.taskId}\t${d.origin ?? ''}`;
|
|
1280
|
+
const before = new Set(reportedDebts.map(debtKey));
|
|
1281
|
+
const after = new Set(fresh.openDebts.map(debtKey));
|
|
1282
|
+
const beforeSlots = new Set(reportedDebts.map(slot));
|
|
1283
|
+
const afterSlots = new Set(fresh.openDebts.map(slot));
|
|
1284
|
+
const closed = reportedDebts.filter(d => !after.has(debtKey(d)) && !afterSlots.has(slot(d)));
|
|
1285
|
+
const added = fresh.openDebts.filter(d => !before.has(debtKey(d)) && !beforeSlots.has(slot(d)));
|
|
1286
|
+
if (closed.length === 0 && added.length === 0) {
|
|
1287
|
+
if (reportedDebts.length > 0) {
|
|
1288
|
+
await recGate(`defect re-check after autofix: all ${reportedDebts.length} defect(s) `
|
|
1289
|
+
+ 'above re-derived against the FINAL tree and still open');
|
|
1290
|
+
}
|
|
1291
|
+
return;
|
|
1292
|
+
}
|
|
1293
|
+
for (const d of closed) {
|
|
1294
|
+
await recGate(`defect RESOLVED by the final-gate autofix — ${d.taskId || '(unknown task)'}: `
|
|
1295
|
+
+ `${d.reason.slice(0, 240)}`);
|
|
1296
|
+
}
|
|
1297
|
+
await surfaceOpenDebts(added);
|
|
1298
|
+
reportedDebts = fresh.openDebts;
|
|
1299
|
+
await recGate(`defect re-check after autofix: ${closed.length} resolved, `
|
|
1300
|
+
+ `${fresh.openDebts.length} still open (re-derived against the FINAL tree)`);
|
|
1301
|
+
};
|
|
1231
1302
|
// Resolution loop: Leave-failed (recommended) / Autofix (bounded,
|
|
1232
1303
|
// model-driven fix pass + gate re-run — run 7's gap: the picker
|
|
1233
1304
|
// had NO automated fix path) / Accept. The user always decides;
|
|
@@ -1371,6 +1442,9 @@ export async function runAutoLoop(ctx, cwd, id, deps) {
|
|
|
1371
1442
|
await recGate(`final-gate: autofix ${fix.unobserved ? 'ended UNOBSERVED' : 'converged'} — ${fix.reason.slice(0, 200)}`);
|
|
1372
1443
|
active.ui.notify(`${id}: final integration gate ${fix.unobserved ? 'is UNOBSERVED' : 'PASSES'} after autofix — ${fix.reason.slice(0, 140)}`, fix.unobserved ? 'warning' : 'info');
|
|
1373
1444
|
fin = { ok: true, reason: fix.reason };
|
|
1445
|
+
// The gate itself just passed, statics included, so
|
|
1446
|
+
// `staticOk` here is proof rather than assumption.
|
|
1447
|
+
await reconcileDebts(true);
|
|
1374
1448
|
break;
|
|
1375
1449
|
}
|
|
1376
1450
|
await recGate(`final-gate: autofix attempt ${fixAttempts} failed — ${fix.reason.slice(0, 200)}`);
|
|
@@ -1434,6 +1508,12 @@ export async function runAutoLoop(ctx, cwd, id, deps) {
|
|
|
1434
1508
|
await recGate(`final-gate: ${converged}`);
|
|
1435
1509
|
active.ui.notify(`${id}: final integration gate converged — ${converged}.`, 'warning');
|
|
1436
1510
|
fin = { ok: true, reason: converged };
|
|
1511
|
+
// Converged on the REMAINING checks only: one or
|
|
1512
|
+
// more were DEMOTED as unfalsifiable here, and the
|
|
1513
|
+
// statics may be among them. No proof ⇒ pass false,
|
|
1514
|
+
// so nothing static-class can auto-close on this
|
|
1515
|
+
// door (inv-no-false-clear).
|
|
1516
|
+
await reconcileDebts(false);
|
|
1437
1517
|
break;
|
|
1438
1518
|
}
|
|
1439
1519
|
}
|
|
@@ -446,6 +446,30 @@ export declare function bootSkipVerdict(args: {
|
|
|
446
446
|
expectServer: boolean;
|
|
447
447
|
}): string | null;
|
|
448
448
|
export { taskThatIntroduced };
|
|
449
|
+
/**
|
|
450
|
+
* ACCEPT-debt re-check (mx5 run 4 B3 / run 8 TASK_0012): read the ledger of tasks
|
|
451
|
+
* the user accepted despite a verify-FAIL and re-check each against the CURRENT
|
|
452
|
+
* tree. A static-class debt whose statics now pass is provably RESOLVED (a later
|
|
453
|
+
* task fixed it) and pruned from the ledger; every other debt cannot be proven
|
|
454
|
+
* resolved deterministically, so it stays OPEN and is surfaced — a run may not
|
|
455
|
+
* complete silently carrying an accepted defect. FP-safe by construction (see
|
|
456
|
+
* accept-debt.ts). Best-effort: a ledger read/write failure must never break the
|
|
457
|
+
* caller.
|
|
458
|
+
*
|
|
459
|
+
* FACTORED OUT of runFinalIntegrationGate (nexttask 6): the derivation has to be
|
|
460
|
+
* runnable at a SECOND moment — after a converged final-gate autofix, where the
|
|
461
|
+
* orchestrator used to rebuild its gate outcome as a bare `{ok, reason}` and drop
|
|
462
|
+
* `openDebts` entirely. The report a run ends on has to be derived from the tree
|
|
463
|
+
* the run ends with, not from the tree as it was before the fix pass.
|
|
464
|
+
*
|
|
465
|
+
* `staticOk` is the caller's claim about the CURRENT statics, and it is the only
|
|
466
|
+
* thing that can auto-close a static-class debt — so a caller that does not know
|
|
467
|
+
* must pass `false` (unprovable ⇒ stays open), never a guess.
|
|
468
|
+
*/
|
|
469
|
+
export declare function deriveOpenDebts(cwd: string, staticOk: boolean): Promise<{
|
|
470
|
+
openDebts: AcceptDebt[];
|
|
471
|
+
debtNote?: string;
|
|
472
|
+
}>;
|
|
449
473
|
/**
|
|
450
474
|
* Run the final gate: static analysis first, then the lockfile consistency
|
|
451
475
|
* checks, then the discovered integration commands, then one boot exercise of
|
package/dist/task/final-gate.js
CHANGED
|
@@ -1132,6 +1132,44 @@ async function recoverOrphanPort(cwd, boot, first, bootGraceMs, deps, expectServ
|
|
|
1132
1132
|
// PROMPT 2 extracted it for the cross-task deletion guards); re-exported so
|
|
1133
1133
|
// existing importers keep working.
|
|
1134
1134
|
export { taskThatIntroduced };
|
|
1135
|
+
/**
|
|
1136
|
+
* ACCEPT-debt re-check (mx5 run 4 B3 / run 8 TASK_0012): read the ledger of tasks
|
|
1137
|
+
* the user accepted despite a verify-FAIL and re-check each against the CURRENT
|
|
1138
|
+
* tree. A static-class debt whose statics now pass is provably RESOLVED (a later
|
|
1139
|
+
* task fixed it) and pruned from the ledger; every other debt cannot be proven
|
|
1140
|
+
* resolved deterministically, so it stays OPEN and is surfaced — a run may not
|
|
1141
|
+
* complete silently carrying an accepted defect. FP-safe by construction (see
|
|
1142
|
+
* accept-debt.ts). Best-effort: a ledger read/write failure must never break the
|
|
1143
|
+
* caller.
|
|
1144
|
+
*
|
|
1145
|
+
* FACTORED OUT of runFinalIntegrationGate (nexttask 6): the derivation has to be
|
|
1146
|
+
* runnable at a SECOND moment — after a converged final-gate autofix, where the
|
|
1147
|
+
* orchestrator used to rebuild its gate outcome as a bare `{ok, reason}` and drop
|
|
1148
|
+
* `openDebts` entirely. The report a run ends on has to be derived from the tree
|
|
1149
|
+
* the run ends with, not from the tree as it was before the fix pass.
|
|
1150
|
+
*
|
|
1151
|
+
* `staticOk` is the caller's claim about the CURRENT statics, and it is the only
|
|
1152
|
+
* thing that can auto-close a static-class debt — so a caller that does not know
|
|
1153
|
+
* must pass `false` (unprovable ⇒ stays open), never a guess.
|
|
1154
|
+
*/
|
|
1155
|
+
export async function deriveOpenDebts(cwd, staticOk) {
|
|
1156
|
+
const { open: openRaw, resolved } = recheckAcceptDebts(await readAcceptDebts(cwd), {
|
|
1157
|
+
staticOk,
|
|
1158
|
+
// Cross-task-deletion debts auto-close iff the deleted file is back in the
|
|
1159
|
+
// tree — a deterministic existence check, corroborating the per-file
|
|
1160
|
+
// provenance the record already carries.
|
|
1161
|
+
fileExists: rel => existsSync(path.join(cwd, rel))
|
|
1162
|
+
});
|
|
1163
|
+
if (resolved.length > 0)
|
|
1164
|
+
await writeAcceptDebts(cwd, openRaw);
|
|
1165
|
+
// Conflicting-claim annotation (mx5 run 11): an existence-as-failure debt whose
|
|
1166
|
+
// named file is another task's committed deliverable is a plan defect — surface
|
|
1167
|
+
// the contradiction with the debt so nobody (human or child) treats the claim as
|
|
1168
|
+
// a deletion instruction. Pure git-history lookup; degrades to no annotation.
|
|
1169
|
+
const openDebts = annotateDebtConflicts(openRaw, p => taskThatIntroduced(cwd, p));
|
|
1170
|
+
const debtNote = buildAcceptDebtNote(openDebts);
|
|
1171
|
+
return { openDebts, ...(debtNote ? { debtNote } : {}) };
|
|
1172
|
+
}
|
|
1135
1173
|
/**
|
|
1136
1174
|
* Run the final gate: static analysis first, then the lockfile consistency
|
|
1137
1175
|
* checks, then the discovered integration commands, then one boot exercise of
|
|
@@ -1150,29 +1188,7 @@ export { taskThatIntroduced };
|
|
|
1150
1188
|
*/
|
|
1151
1189
|
export async function runFinalIntegrationGate(cwd, timeoutMs = 900_000, bootGraceMs = 10_000, bootDeps = {}, planText) {
|
|
1152
1190
|
const stat = runRepoHealthCheck(cwd);
|
|
1153
|
-
|
|
1154
|
-
// the user accepted despite a verify-FAIL and re-check each against the current
|
|
1155
|
-
// tree. A static-class debt whose statics now pass is provably RESOLVED (a later
|
|
1156
|
-
// task fixed it) and pruned; every other debt cannot be proven resolved
|
|
1157
|
-
// deterministically, so it stays OPEN and is surfaced in this gate's report — a
|
|
1158
|
-
// run may not complete silently carrying an accepted defect. FP-safe by
|
|
1159
|
-
// construction (see accept-debt.ts). Best-effort: a ledger read/write failure
|
|
1160
|
-
// must never break the gate.
|
|
1161
|
-
const { open: openRaw, resolved } = recheckAcceptDebts(await readAcceptDebts(cwd), {
|
|
1162
|
-
staticOk: stat.ok,
|
|
1163
|
-
// Cross-task-deletion debts auto-close iff the deleted file is back in the
|
|
1164
|
-
// tree — a deterministic existence check, corroborating the per-file
|
|
1165
|
-
// provenance the record already carries.
|
|
1166
|
-
fileExists: rel => existsSync(path.join(cwd, rel))
|
|
1167
|
-
});
|
|
1168
|
-
if (resolved.length > 0)
|
|
1169
|
-
await writeAcceptDebts(cwd, openRaw);
|
|
1170
|
-
// Conflicting-claim annotation (mx5 run 11): an existence-as-failure debt whose
|
|
1171
|
-
// named file is another task's committed deliverable is a plan defect — surface
|
|
1172
|
-
// the contradiction with the debt so nobody (human or child) treats the claim as
|
|
1173
|
-
// a deletion instruction. Pure git-history lookup; degrades to no annotation.
|
|
1174
|
-
const openDebts = annotateDebtConflicts(openRaw, p => taskThatIntroduced(cwd, p));
|
|
1175
|
-
const debtNote = buildAcceptDebtNote(openDebts);
|
|
1191
|
+
const { openDebts, debtNote } = await deriveOpenDebts(cwd, stat.ok);
|
|
1176
1192
|
// The debt note rides in its OWN field: `reason` stays the mechanical failure
|
|
1177
1193
|
// because it seeds the autofix child's prompt (see FinalGateOutcome.reason —
|
|
1178
1194
|
// run 11's fix child executed a recorded claim as an instruction).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mjasnikovs/pi-task",
|
|
3
|
-
"version": "0.28.
|
|
3
|
+
"version": "0.28.2",
|
|
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",
|