@mjasnikovs/pi-task 0.13.30 → 0.13.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.
|
@@ -15,7 +15,7 @@ import { isDuplicateQuestion, MAX_DUP_STRIKES, DUP_REPROMPT_HINT } from './quest
|
|
|
15
15
|
import { allocateAutoId, buildAutoBody, parseDecomposeList, parseTaskList, checkOffTask, stampTaskInProgress, findResumableAuto } from './auto-io.js';
|
|
16
16
|
import { writeTaskFile, readTaskFile, updateTaskFrontMatter, taskFilePath } from './task-io.js';
|
|
17
17
|
import { gitCommitAll } from './auto-commit.js';
|
|
18
|
-
import { runGuidelineEnforcement, ENFORCE_TIMEOUT_MS } from './enforce-guidelines.js';
|
|
18
|
+
import { runGuidelineEnforcement, classifyEnforceChildFailure, ENFORCE_TIMEOUT_MS } from './enforce-guidelines.js';
|
|
19
19
|
import { runWorker } from '../workers/pi-worker-core.js';
|
|
20
20
|
import { runPhaseChild, prependHint, USER_CANCELLED } from './child-runner.js';
|
|
21
21
|
import { SessionUI, registerBridgeCommand } from '../remote/bridge.js';
|
|
@@ -306,7 +306,7 @@ function defaultDeps(ctx, cwd, signal, title) {
|
|
|
306
306
|
commit: (cwd2, message) => getConfig().autoCommit ?
|
|
307
307
|
gitCommitAll(cwd2, message, signal)
|
|
308
308
|
: Promise.resolve({ committed: false, reason: 'auto-commit disabled' }),
|
|
309
|
-
enforce: (cwd2,
|
|
309
|
+
enforce: (cwd2, taskTitle) => {
|
|
310
310
|
if (!getConfig().enforceGuidelines) {
|
|
311
311
|
return Promise.resolve({ ok: true, reason: 'disabled' });
|
|
312
312
|
}
|
|
@@ -315,30 +315,49 @@ function defaultDeps(ctx, cwd, signal, title) {
|
|
|
315
315
|
signal,
|
|
316
316
|
// Reuse the timeout- and loop-guarded worker child. It runs the
|
|
317
317
|
// same local model with edit tools so it can fix violations in
|
|
318
|
-
// place.
|
|
319
|
-
//
|
|
320
|
-
//
|
|
318
|
+
// place. classifyEnforceChildFailure turns any non-clean exit
|
|
319
|
+
// (loop kill, timeout, leaked tool call, non-zero) into a thrown
|
|
320
|
+
// error so the pass becomes a blocking outcome rather than
|
|
321
|
+
// trusting a partial transcript.
|
|
321
322
|
runChild: async (tools, prompt, sig) => {
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
323
|
+
// The enforcement child is a slow local-model pass with edit
|
|
324
|
+
// tools; show the same /task-auto status block as planning so
|
|
325
|
+
// it isn't silent — head · enforcing guidelines/elapsed · ↳
|
|
326
|
+
// last line. (runWorker surfaces no context usage, so that
|
|
327
|
+
// line is just omitted.)
|
|
328
|
+
lastLine = undefined;
|
|
329
|
+
contextUsage = undefined;
|
|
330
|
+
const startedAt = Date.now();
|
|
331
|
+
const stopLoader = startAutoLoader(ctx, () => ({
|
|
332
|
+
title: taskTitle,
|
|
333
|
+
kind: 'enforce',
|
|
334
|
+
step: 'guidelines',
|
|
335
|
+
stepNum: 1,
|
|
336
|
+
stepTotal: 1,
|
|
337
|
+
startedAt,
|
|
338
|
+
lastLine,
|
|
339
|
+
contextUsage
|
|
340
|
+
}));
|
|
341
|
+
try {
|
|
342
|
+
const r = await runWorker({
|
|
343
|
+
prompt,
|
|
344
|
+
cwd: cwd2,
|
|
345
|
+
signal: sig,
|
|
346
|
+
tools,
|
|
347
|
+
timeoutMs: ENFORCE_TIMEOUT_MS,
|
|
348
|
+
onLine: line => {
|
|
349
|
+
lastLine = line;
|
|
350
|
+
phaseDeps.logDebug?.(`enforce: ${line}`);
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
const failure = classifyEnforceChildFailure(r);
|
|
354
|
+
if (failure)
|
|
355
|
+
throw new Error(failure);
|
|
356
|
+
return r.text;
|
|
357
|
+
}
|
|
358
|
+
finally {
|
|
359
|
+
stopLoader();
|
|
340
360
|
}
|
|
341
|
-
return r.text;
|
|
342
361
|
}
|
|
343
362
|
});
|
|
344
363
|
}
|
|
@@ -46,6 +46,26 @@ export declare function parseEnforceVerdict(text: string): {
|
|
|
46
46
|
clean: boolean;
|
|
47
47
|
detail: string;
|
|
48
48
|
};
|
|
49
|
+
/** The subset of a runWorker result the enforcement-child mapping reads. */
|
|
50
|
+
export interface EnforceChildResult {
|
|
51
|
+
text: string;
|
|
52
|
+
exitCode: number;
|
|
53
|
+
aborted: boolean;
|
|
54
|
+
timedOut?: boolean;
|
|
55
|
+
loopHit?: unknown;
|
|
56
|
+
leakedToolCall?: unknown;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Map the enforcement child's runWorker result to a fatal error message, or null
|
|
60
|
+
* when it finished cleanly enough to parse a verdict from its text.
|
|
61
|
+
*
|
|
62
|
+
* The order is load-bearing. A loop-kill AND a wall-clock timeout BOTH also set
|
|
63
|
+
* `aborted` — killProc flips it on every kill path — so the specific causes
|
|
64
|
+
* (timeout, loop, leaked tool call) must be named BEFORE the generic
|
|
65
|
+
* `aborted → user-cancel` mapping. Checking `aborted` first (as the original
|
|
66
|
+
* inline code did) mislabels a loop-killed enforcement child as a user cancel.
|
|
67
|
+
*/
|
|
68
|
+
export declare function classifyEnforceChildFailure(r: EnforceChildResult): string | null;
|
|
49
69
|
/**
|
|
50
70
|
* Capture the work to verify as text: the tracked diff against HEAD plus the
|
|
51
71
|
* names of any new (untracked) files. Non-destructive — it does not touch the
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
import * as fsp from 'node:fs/promises';
|
|
20
20
|
import * as path from 'node:path';
|
|
21
21
|
import { runChildDefault } from '../shared/child-process.js';
|
|
22
|
+
import { USER_CANCELLED } from './child-runner.js';
|
|
22
23
|
/** Filenames discovered in the working directory (cwd only — no tree walk). */
|
|
23
24
|
export const GUIDELINE_FILENAMES = ['AGENTS.md', 'CLAUDE.md'];
|
|
24
25
|
/** Tools the fix pass is allowed: read/search to inspect, edit/write to fix. */
|
|
@@ -99,6 +100,29 @@ export function parseEnforceVerdict(text) {
|
|
|
99
100
|
const clean = last[1].toUpperCase() === 'CLEAN';
|
|
100
101
|
return { clean, detail: clean ? '' : last[2].trim() || 'unspecified violation' };
|
|
101
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Map the enforcement child's runWorker result to a fatal error message, or null
|
|
105
|
+
* when it finished cleanly enough to parse a verdict from its text.
|
|
106
|
+
*
|
|
107
|
+
* The order is load-bearing. A loop-kill AND a wall-clock timeout BOTH also set
|
|
108
|
+
* `aborted` — killProc flips it on every kill path — so the specific causes
|
|
109
|
+
* (timeout, loop, leaked tool call) must be named BEFORE the generic
|
|
110
|
+
* `aborted → user-cancel` mapping. Checking `aborted` first (as the original
|
|
111
|
+
* inline code did) mislabels a loop-killed enforcement child as a user cancel.
|
|
112
|
+
*/
|
|
113
|
+
export function classifyEnforceChildFailure(r) {
|
|
114
|
+
if (r.timedOut)
|
|
115
|
+
return 'enforcement child timed out';
|
|
116
|
+
if (r.loopHit)
|
|
117
|
+
return 'enforcement child looped';
|
|
118
|
+
if (r.leakedToolCall)
|
|
119
|
+
return 'enforcement child leaked a tool call';
|
|
120
|
+
if (r.aborted)
|
|
121
|
+
return USER_CANCELLED;
|
|
122
|
+
if (r.exitCode !== 0)
|
|
123
|
+
return `enforcement child exited ${r.exitCode}`;
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
102
126
|
/**
|
|
103
127
|
* Capture the work to verify as text: the tracked diff against HEAD plus the
|
|
104
128
|
* names of any new (untracked) files. Non-destructive — it does not touch the
|
package/dist/task/widget.d.ts
CHANGED
|
@@ -49,6 +49,10 @@ export interface AutoLoaderState {
|
|
|
49
49
|
startedAt: number;
|
|
50
50
|
lastLine?: string;
|
|
51
51
|
contextUsage?: ContextSnapshot;
|
|
52
|
+
/** Which /task-auto stage this loader is for. Defaults to 'planning' (the
|
|
53
|
+
* numbered clarify/decompose steps); 'enforce' is the per-task guideline
|
|
54
|
+
* pass, which has no step numbering. */
|
|
55
|
+
kind?: 'planning' | 'enforce';
|
|
52
56
|
}
|
|
53
57
|
export declare function buildAutoLoaderLines(s: AutoLoaderState, theme?: WidgetTheme): string[];
|
|
54
58
|
/**
|
package/dist/task/widget.js
CHANGED
|
@@ -121,7 +121,9 @@ export function startWidget(ctx, getState) {
|
|
|
121
121
|
export function buildAutoLoaderLines(s, theme) {
|
|
122
122
|
const elapsed = formatDuration(Date.now() - s.startedAt);
|
|
123
123
|
const head = `/task-auto · ${s.title}`;
|
|
124
|
-
let detail =
|
|
124
|
+
let detail = s.kind === 'enforce' ?
|
|
125
|
+
`enforcing guidelines · ${elapsed}`
|
|
126
|
+
: `planning ${s.stepNum}/${s.stepTotal} ${s.step} · ${elapsed}`;
|
|
125
127
|
if (s.contextUsage) {
|
|
126
128
|
const ctxDetail = formatContextDetail(s.contextUsage, theme);
|
|
127
129
|
if (ctxDetail)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mjasnikovs/pi-task",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.31",
|
|
4
4
|
"description": "Deterministic spec-orchestration for local models, with a bundled real-time remote web view and web/docs/fetch/worker subagent tools.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|