@mjasnikovs/pi-task 0.17.14 → 0.17.16
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/auto-commit.d.ts +11 -0
- package/dist/task/auto-commit.js +31 -1
- package/dist/task/lint-fix.js +53 -17
- package/dist/task/repo-health-check.js +5 -0
- package/dist/task/task-gates.d.ts +15 -3
- package/dist/task/task-gates.js +45 -7
- package/dist/task/verify-resolution.js +8 -5
- package/dist/task/verify-work.js +24 -13
- package/package.json +1 -1
|
@@ -11,7 +11,18 @@ export interface CommitResult {
|
|
|
11
11
|
committed: boolean;
|
|
12
12
|
/** Short, human-readable reason when committed === false. */
|
|
13
13
|
reason?: string;
|
|
14
|
+
/** Set when the commit needed a fallback (e.g. self-supplied identity). */
|
|
15
|
+
note?: string;
|
|
14
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Does this git stderr describe a missing author identity? Seen live (mx5 run 4):
|
|
19
|
+
* the headless docker container has no HOME gitconfig, so EVERY per-task commit
|
|
20
|
+
* failed "Author identity unknown" — which silently disabled enforce and every
|
|
21
|
+
* commit-based differential guard for the whole run.
|
|
22
|
+
*/
|
|
23
|
+
export declare function isIdentityFailure(stderr: string): boolean;
|
|
24
|
+
/** Fallback identity for environments with no git config (headless containers). */
|
|
25
|
+
export declare const FALLBACK_IDENTITY_ARGS: readonly ["-c", "user.name=pi-task", "-c", "user.email=pi-task@local"];
|
|
15
26
|
export declare function git(cwd: string, args: string[], signal: AbortSignal | undefined, spawnFn?: SpawnFn): Promise<{
|
|
16
27
|
stdout: string;
|
|
17
28
|
stderr: string;
|
package/dist/task/auto-commit.js
CHANGED
|
@@ -7,6 +7,22 @@
|
|
|
7
7
|
* reason and let the loop continue (the task already succeeded).
|
|
8
8
|
*/
|
|
9
9
|
import { runChildDefault } from '../shared/child-process.js';
|
|
10
|
+
/**
|
|
11
|
+
* Does this git stderr describe a missing author identity? Seen live (mx5 run 4):
|
|
12
|
+
* the headless docker container has no HOME gitconfig, so EVERY per-task commit
|
|
13
|
+
* failed "Author identity unknown" — which silently disabled enforce and every
|
|
14
|
+
* commit-based differential guard for the whole run.
|
|
15
|
+
*/
|
|
16
|
+
export function isIdentityFailure(stderr) {
|
|
17
|
+
return /identity unknown|unable to auto-detect email|user\.(name|email)/i.test(stderr);
|
|
18
|
+
}
|
|
19
|
+
/** Fallback identity for environments with no git config (headless containers). */
|
|
20
|
+
export const FALLBACK_IDENTITY_ARGS = [
|
|
21
|
+
'-c',
|
|
22
|
+
'user.name=pi-task',
|
|
23
|
+
'-c',
|
|
24
|
+
'user.email=pi-task@local'
|
|
25
|
+
];
|
|
10
26
|
function firstLine(s) {
|
|
11
27
|
const line = s.split('\n').find(l => l.trim().length > 0);
|
|
12
28
|
return (line ?? s).trim();
|
|
@@ -42,11 +58,25 @@ export async function gitCommitAll(cwd, message, signal, spawnFn) {
|
|
|
42
58
|
return { committed: false, reason: 'cancelled' };
|
|
43
59
|
if (diff.exitCode === 0)
|
|
44
60
|
return { committed: false, reason: 'nothing to commit' };
|
|
45
|
-
// 4. Commit. A failure here is usually missing user.name/user.email config
|
|
61
|
+
// 4. Commit. A failure here is usually missing user.name/user.email config —
|
|
62
|
+
// retry once with a self-supplied identity rather than losing the snapshot
|
|
63
|
+
// (and with it enforce + every differential guard) for the whole run.
|
|
46
64
|
const commit = await git(cwd, ['commit', '-m', message], signal, spawnFn);
|
|
47
65
|
if (commit.aborted)
|
|
48
66
|
return { committed: false, reason: 'cancelled' };
|
|
49
67
|
if (commit.exitCode !== 0) {
|
|
68
|
+
if (isIdentityFailure(commit.stderr || commit.stdout)) {
|
|
69
|
+
const retry = await git(cwd, [...FALLBACK_IDENTITY_ARGS, 'commit', '-m', message], signal, spawnFn);
|
|
70
|
+
if (retry.aborted)
|
|
71
|
+
return { committed: false, reason: 'cancelled' };
|
|
72
|
+
if (retry.exitCode === 0) {
|
|
73
|
+
return { committed: true, note: 'no git identity configured — used pi-task fallback' };
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
committed: false,
|
|
77
|
+
reason: `git commit failed: ${firstLine(retry.stderr || retry.stdout)}`
|
|
78
|
+
};
|
|
79
|
+
}
|
|
50
80
|
return {
|
|
51
81
|
committed: false,
|
|
52
82
|
reason: `git commit failed: ${firstLine(commit.stderr || commit.stdout)}`
|
package/dist/task/lint-fix.js
CHANGED
|
@@ -74,10 +74,17 @@ export function revertGuardViolations(preDirty, stillDirty) {
|
|
|
74
74
|
return preDirty.filter(f => !stillDirty.has(f));
|
|
75
75
|
}
|
|
76
76
|
const EXCLUDE_TASKS_DIR = ':(exclude).pi-tasks';
|
|
77
|
+
/**
|
|
78
|
+
* Files differing from HEAD, or null when git itself failed. The distinction is
|
|
79
|
+
* load-bearing: mx5 run 4 rolled back two GOOD converged fixes because a git
|
|
80
|
+
* failure after the child read as "[] files still dirty" → every pre-existing work
|
|
81
|
+
* file looked reverted → false "discarded work". A git error is INCONCLUSIVE, not
|
|
82
|
+
* evidence of destruction.
|
|
83
|
+
*/
|
|
77
84
|
async function dirtyFiles(deps) {
|
|
78
85
|
const r = await deps.git(['diff', '--name-only', 'HEAD', '--', '.', EXCLUDE_TASKS_DIR]);
|
|
79
86
|
if (r.exitCode !== 0)
|
|
80
|
-
return
|
|
87
|
+
return null;
|
|
81
88
|
return r.stdout
|
|
82
89
|
.split('\n')
|
|
83
90
|
.map(l => l.trim())
|
|
@@ -89,12 +96,24 @@ async function dirtyFiles(deps) {
|
|
|
89
96
|
*/
|
|
90
97
|
export async function runBoundedLintFix(deps) {
|
|
91
98
|
// Snapshot the full working state as a tree object (includes untracked files),
|
|
92
|
-
// then unstage so the child sees the tree exactly as it was.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
99
|
+
// then unstage so the child sees the tree exactly as it was. A git failure at
|
|
100
|
+
// snapshot time leaves the guard without a baseline — it then runs vacuously
|
|
101
|
+
// (nothing to compare) rather than inventing violations.
|
|
102
|
+
const preDirty = (await dirtyFiles(deps)) ?? [];
|
|
103
|
+
const preUntrackedRes = await deps.git([
|
|
104
|
+
'ls-files',
|
|
105
|
+
'--others',
|
|
106
|
+
'--exclude-standard',
|
|
107
|
+
'--',
|
|
108
|
+
'.',
|
|
109
|
+
EXCLUDE_TASKS_DIR
|
|
110
|
+
]);
|
|
111
|
+
const preUntracked = preUntrackedRes.exitCode !== 0 ?
|
|
112
|
+
[]
|
|
113
|
+
: preUntrackedRes.stdout
|
|
114
|
+
.split('\n')
|
|
115
|
+
.map(l => l.trim())
|
|
116
|
+
.filter(l => l.length > 0);
|
|
98
117
|
let snapshot = null;
|
|
99
118
|
const add = await deps.git(['add', '-A']);
|
|
100
119
|
if (add.exitCode === 0) {
|
|
@@ -115,15 +134,32 @@ export async function runBoundedLintFix(deps) {
|
|
|
115
134
|
}
|
|
116
135
|
// REVERT-GUARD: every pre-existing work file must still differ from HEAD, and
|
|
117
136
|
// every pre-existing untracked file must still exist. Trip → restore snapshot.
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
137
|
+
// Every comparison requires git to have actually SUCCEEDED: a git error after
|
|
138
|
+
// the child is inconclusive (proven live: it flagged the whole untracked file
|
|
139
|
+
// list as "discarded" while the child had verifiably edited only lint findings)
|
|
140
|
+
// — then the guard steps aside and the converge check below still decides.
|
|
141
|
+
const stillDirtyList = await dirtyFiles(deps);
|
|
142
|
+
let guardNote;
|
|
143
|
+
const violations = [];
|
|
144
|
+
if (stillDirtyList === null) {
|
|
145
|
+
guardNote = 'revert-guard inconclusive (git failed after fix) — converge check decides';
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
const stillDirty = new Set(stillDirtyList);
|
|
149
|
+
violations.push(...revertGuardViolations(preDirty, stillDirty));
|
|
150
|
+
for (const f of preUntracked) {
|
|
151
|
+
const probe = await deps.git(['ls-files', '--others', '--exclude-standard', '--', f]);
|
|
152
|
+
if (probe.exitCode !== 0) {
|
|
153
|
+
// git error → unknown, not "gone"; note it once and move on.
|
|
154
|
+
guardNote ??= 'revert-guard partly inconclusive (git probe failed)';
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
const gone = probe.stdout.trim().length === 0
|
|
158
|
+
// ...unless the child legitimately made it tracked-dirty (it edited it).
|
|
159
|
+
&& !stillDirty.has(f);
|
|
160
|
+
if (gone)
|
|
161
|
+
violations.push(f);
|
|
162
|
+
}
|
|
127
163
|
}
|
|
128
164
|
if (violations.length > 0) {
|
|
129
165
|
if (snapshot) {
|
|
@@ -142,5 +178,5 @@ export async function runBoundedLintFix(deps) {
|
|
|
142
178
|
if (!health.ok) {
|
|
143
179
|
return { ok: false, reason: `did not converge: ${health.reason}` };
|
|
144
180
|
}
|
|
145
|
-
return { ok: true };
|
|
181
|
+
return { ok: true, reason: guardNote };
|
|
146
182
|
}
|
|
@@ -110,6 +110,11 @@ export function runRepoHealthCheck(cwd, timeoutMs = 600_000) {
|
|
|
110
110
|
// Tool missing (ENOENT) or killed by timeout → cannot conclude; skip it.
|
|
111
111
|
if (r.error || r.status === null)
|
|
112
112
|
continue;
|
|
113
|
+
// Exit 127 = "command not found" INSIDE the script chain (e.g. `bun run lint`
|
|
114
|
+
// before node_modules exists — seen live failing TASK_0001's first verify).
|
|
115
|
+
// Same environment gap as ENOENT, just surfaced through the runner's shell.
|
|
116
|
+
if (r.status === 127)
|
|
117
|
+
continue;
|
|
113
118
|
if (r.status !== 0) {
|
|
114
119
|
return {
|
|
115
120
|
ok: false,
|
|
@@ -6,9 +6,11 @@
|
|
|
6
6
|
* just-finished work:
|
|
7
7
|
*
|
|
8
8
|
* 1. VERIFY — RUN the composed spec's VERIFY block in the real workspace and
|
|
9
|
-
* judge a PASS/FAIL.
|
|
10
|
-
*
|
|
11
|
-
* loops back to the gate
|
|
9
|
+
* judge a PASS/FAIL. On a FAIL a fresh read-only child recommends AUTOFIX or
|
|
10
|
+
* ACCEPT: an AUTOFIX recommendation re-runs the implementation turn UNATTENDED
|
|
11
|
+
* (no prompt) and loops back to the gate, bounded by MAX_AUTO_AUTOFIX; the user
|
|
12
|
+
* is shown the boxed picker only when the recommendation is ACCEPT (blessing the
|
|
13
|
+
* artifact as-is) or when that unattended-autofix cap is reached.
|
|
12
14
|
* 2. ENFORCE — hold the committed work to the project's AGENTS.md / CLAUDE.md
|
|
13
15
|
* rules. Runs in `edit` mode (fix in place) only when the verify gate produced
|
|
14
16
|
* a genuine clean pass to guard the edits against; otherwise `flag` mode
|
|
@@ -166,6 +168,16 @@ export type GateResult = {
|
|
|
166
168
|
ctx: ExtensionCommandContext;
|
|
167
169
|
reason?: string;
|
|
168
170
|
};
|
|
171
|
+
/**
|
|
172
|
+
* How many times a verify FAIL may be auto-fixed UNATTENDED (the research
|
|
173
|
+
* recommended AUTOFIX, so pi re-runs the impl turn without prompting) before the
|
|
174
|
+
* loop falls back to the human picker. Each AUTOFIX is a full implementation
|
|
175
|
+
* re-run, so a non-converging loop must not run forever with nobody able to break
|
|
176
|
+
* it — after this many consecutive auto attempts that still FAIL, the picker is
|
|
177
|
+
* shown so a person can decide. A recommendation to ACCEPT always shows the picker
|
|
178
|
+
* regardless of this count (blessing an artifact as-is is a human's call).
|
|
179
|
+
*/
|
|
180
|
+
export declare const MAX_AUTO_AUTOFIX = 3;
|
|
169
181
|
/**
|
|
170
182
|
* Show the boxed two-choice picker after a verify FAIL and return what the user
|
|
171
183
|
* decided. The model-recommended card is placed first so the renderer tints it
|
package/dist/task/task-gates.js
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import { resolutionOptions, classifyResolutionAnswer } from './verify-resolution.js';
|
|
2
2
|
import { SessionUI } from '../remote/bridge.js';
|
|
3
|
+
/**
|
|
4
|
+
* How many times a verify FAIL may be auto-fixed UNATTENDED (the research
|
|
5
|
+
* recommended AUTOFIX, so pi re-runs the impl turn without prompting) before the
|
|
6
|
+
* loop falls back to the human picker. Each AUTOFIX is a full implementation
|
|
7
|
+
* re-run, so a non-converging loop must not run forever with nobody able to break
|
|
8
|
+
* it — after this many consecutive auto attempts that still FAIL, the picker is
|
|
9
|
+
* shown so a person can decide. A recommendation to ACCEPT always shows the picker
|
|
10
|
+
* regardless of this count (blessing an artifact as-is is a human's call).
|
|
11
|
+
*/
|
|
12
|
+
export const MAX_AUTO_AUTOFIX = 3;
|
|
3
13
|
/**
|
|
4
14
|
* Show the boxed two-choice picker after a verify FAIL and return what the user
|
|
5
15
|
* decided. The model-recommended card is placed first so the renderer tints it
|
|
@@ -60,10 +70,13 @@ export async function runGatesForTask(ctxIn, deps, p) {
|
|
|
60
70
|
active.ui.notify(`${p.tag}: verifying "${p.title}"…`, 'info');
|
|
61
71
|
let verified = await deps.verify(active, p.cwd, p.title, p.taskId);
|
|
62
72
|
await rec(verdictLine(verified));
|
|
63
|
-
// A FAIL no longer dead-stops
|
|
64
|
-
//
|
|
65
|
-
//
|
|
73
|
+
// A FAIL no longer dead-stops. When the research recommends AUTOFIX, pi
|
|
74
|
+
// re-runs the impl turn UNATTENDED (no picker) — the human is consulted only
|
|
75
|
+
// when the recommendation is ACCEPT (blessing the artifact as-is). The
|
|
76
|
+
// unattended fix is BOUNDED by MAX_AUTO_AUTOFIX: once that many consecutive
|
|
77
|
+
// auto attempts still FAIL, the picker returns so a person can break the loop.
|
|
66
78
|
let lintFixAttempted = false;
|
|
79
|
+
let autoFixCount = 0;
|
|
67
80
|
while (!verified.ok) {
|
|
68
81
|
const failReason = verified.reason ?? 'did not verify';
|
|
69
82
|
// GRADUATED resolution: a repo-health FAIL (pure static findings) gets ONE
|
|
@@ -74,7 +87,9 @@ export async function runGatesForTask(ctxIn, deps, p) {
|
|
|
74
87
|
lintFixAttempted = true;
|
|
75
88
|
active.ui.notify(`${p.tag}: static findings on "${p.title}" — attempting bounded lint fix…`, 'info');
|
|
76
89
|
const fix = await deps.lintFix(active, p.cwd, p.title, failReason);
|
|
77
|
-
await rec(`lint-fix: ${fix.ok ?
|
|
90
|
+
await rec(`lint-fix: ${fix.ok ?
|
|
91
|
+
`applied${fix.reason ? ` (${fix.reason})` : ''} — re-verifying`
|
|
92
|
+
: `not applied (${fix.reason ?? 'failed'})`}`);
|
|
78
93
|
if (fix.ok) {
|
|
79
94
|
verified = await deps.verify(active, p.cwd, p.title, p.taskId);
|
|
80
95
|
await rec(verdictLine(verified));
|
|
@@ -85,7 +100,23 @@ export async function runGatesForTask(ctxIn, deps, p) {
|
|
|
85
100
|
await deps.recommend(active, p.cwd, p.title, p.taskId, failReason)
|
|
86
101
|
: { recommend: 'autofix', rationale: failReason };
|
|
87
102
|
await rec(`resolution: recommended ${recOutcome.recommend.toUpperCase()}`);
|
|
88
|
-
|
|
103
|
+
// AUTO-RESOLVE the AUTOFIX path: when the research says the work is
|
|
104
|
+
// genuinely wrong, re-run the fix WITHOUT prompting the user. The picker is
|
|
105
|
+
// reserved for the ACCEPT recommendation (the human decides whether to bless
|
|
106
|
+
// an artifact the gate FAILed) and for the bounded fallback: after
|
|
107
|
+
// MAX_AUTO_AUTOFIX consecutive unattended attempts that still FAIL, hand
|
|
108
|
+
// control back so a person can break a non-converging loop.
|
|
109
|
+
const autoFixNow = recOutcome.recommend === 'autofix' && autoFixCount < MAX_AUTO_AUTOFIX;
|
|
110
|
+
let choice;
|
|
111
|
+
if (autoFixNow) {
|
|
112
|
+
autoFixCount += 1;
|
|
113
|
+
await rec(`resolution: auto-AUTOFIX (recommended, unattended ${autoFixCount}/${MAX_AUTO_AUTOFIX})`);
|
|
114
|
+
active.ui.notify(`${p.tag}: verify FAIL on "${p.title}" — auto-fixing (recommended, ${autoFixCount}/${MAX_AUTO_AUTOFIX})…`, 'info');
|
|
115
|
+
choice = { action: 'autofix' };
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
choice = await askVerifyResolution(active, p.title, failReason, recOutcome);
|
|
119
|
+
}
|
|
89
120
|
if (choice.action === 'cancel') {
|
|
90
121
|
await rec('resolution: user dismissed the verify-FAIL picker — paused');
|
|
91
122
|
return { kind: 'paused', ctx: active, reason: failReason };
|
|
@@ -128,12 +159,19 @@ export async function runGatesForTask(ctxIn, deps, p) {
|
|
|
128
159
|
// so a passing task is durably recorded no matter what enforcement later finds.
|
|
129
160
|
const commit = await deps.commit(p.cwd, `task: ${p.title} (${p.taskId})`);
|
|
130
161
|
if (commit.committed) {
|
|
131
|
-
await rec(
|
|
162
|
+
await rec(`commit: task snapshot committed${commit.note ? ` (${commit.note})` : ''}`);
|
|
132
163
|
active.ui.notify(`${p.tag}: committed "${p.title}".`, 'info');
|
|
133
164
|
}
|
|
134
165
|
else {
|
|
135
166
|
await rec(`commit: skipped (${commit.reason ?? 'unknown'})`);
|
|
136
|
-
|
|
167
|
+
// A benign skip ("nothing to commit", auto-commit off) is a warning. A real
|
|
168
|
+
// git failure is louder: it silently disables enforce AND every commit-based
|
|
169
|
+
// guard — mx5 run 4 lost all 10 commits (no container git identity) with only
|
|
170
|
+
// per-task warnings to show for it.
|
|
171
|
+
const gitFailure = /^git (commit|add) failed/.test(commit.reason ?? '');
|
|
172
|
+
active.ui.notify(gitFailure ?
|
|
173
|
+
`${p.tag}: COMMIT FAILED (${commit.reason}) — enforce and revert guards are disabled for this task.`
|
|
174
|
+
: `${p.tag}: not committed (${commit.reason ?? 'unknown'}) — continuing.`, gitFailure ? 'error' : 'warning');
|
|
137
175
|
}
|
|
138
176
|
// With the task committed, hold its work to AGENTS.md / CLAUDE.md — but as a step
|
|
139
177
|
// INSIDE the validation gate, gated by the verify signal (see GateDeps.enforce).
|
|
@@ -10,8 +10,12 @@
|
|
|
10
10
|
* • Accept — treat the artifact as good, override the gate, and proceed
|
|
11
11
|
* (check off + commit) exactly as a PASS would.
|
|
12
12
|
*
|
|
13
|
-
* The
|
|
14
|
-
*
|
|
13
|
+
* The recommendation now also GATES whether the user is prompted at all: an
|
|
14
|
+
* AUTOFIX recommendation is auto-applied unattended (bounded — see
|
|
15
|
+
* task-gates.ts MAX_AUTO_AUTOFIX), and the picker is shown only when the
|
|
16
|
+
* recommendation is ACCEPT (blessing the artifact is a human's call) or when the
|
|
17
|
+
* unattended-autofix cap is reached. This module only computes which of the two
|
|
18
|
+
* cards is tinted RECOMMENDED, by handing the FAIL reason + the task spec to
|
|
15
19
|
* a fresh read+bash child of the SAME local model and letting it INVESTIGATE the
|
|
16
20
|
* real workspace: is the FAIL a genuine defect in the work (→ recommend AUTOFIX),
|
|
17
21
|
* or a false alarm / an acceptable artifact the gate misjudged (→ recommend
|
|
@@ -23,9 +27,8 @@
|
|
|
23
27
|
* through the full implementation runner, not this pass.) A read-only research
|
|
24
28
|
* step can never itself change the tree it is judging.
|
|
25
29
|
*
|
|
26
|
-
* The verdict
|
|
27
|
-
*
|
|
28
|
-
* picker is shown regardless; this only moves the green tint.
|
|
30
|
+
* The verdict defaults to AUTOFIX when missing/garbled — the conservative choice
|
|
31
|
+
* (re-do the work rather than bless it), which is also the auto-applied path.
|
|
29
32
|
*/
|
|
30
33
|
import { USER_CANCELLED } from './child-runner.js';
|
|
31
34
|
/** Same read-only contract as the verify pass: observe, never edit. */
|
package/dist/task/verify-work.js
CHANGED
|
@@ -281,19 +281,30 @@ export async function runWorkVerification(deps) {
|
|
|
281
281
|
findings = [];
|
|
282
282
|
}
|
|
283
283
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
284
|
+
// A child that emits NO verdict never judged the work (budget/context death mid-
|
|
285
|
+
// investigation — seen live: an 11-minute verify wandered, died verdict-less, and
|
|
286
|
+
// the resulting FAIL burned a full implementation re-run on an unjudged artifact).
|
|
287
|
+
// That is a verify-side fault, so retry the VERIFY once before reporting a FAIL.
|
|
288
|
+
for (let attempt = 1;; attempt++) {
|
|
289
|
+
let text;
|
|
290
|
+
try {
|
|
291
|
+
text = await deps.runChild(VERIFY_TOOLS, buildVerifyPrompt(deps.spec, findings), deps.signal);
|
|
292
|
+
}
|
|
293
|
+
catch (err) {
|
|
294
|
+
if (err instanceof Error && err.message === USER_CANCELLED)
|
|
295
|
+
throw err;
|
|
296
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
297
|
+
return { ok: false, reason: `verification pass could not run: ${msg}` };
|
|
298
|
+
}
|
|
299
|
+
const verdict = parseVerifyVerdict(text);
|
|
300
|
+
if (verdict.pass)
|
|
301
|
+
return { ok: true };
|
|
302
|
+
if (verdict.detail === 'no verdict emitted' && attempt === 1)
|
|
303
|
+
continue;
|
|
304
|
+
return {
|
|
305
|
+
ok: false,
|
|
306
|
+
reason: `work did not verify: ${verdict.detail}${verdict.detail === 'no verdict emitted' ? ' (after verify retry)' : ''}`
|
|
307
|
+
};
|
|
293
308
|
}
|
|
294
|
-
const verdict = parseVerifyVerdict(text);
|
|
295
|
-
if (verdict.pass)
|
|
296
|
-
return { ok: true };
|
|
297
|
-
return { ok: false, reason: `work did not verify: ${verdict.detail}` };
|
|
298
309
|
}
|
|
299
310
|
export { VERIFY_TOOLS };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mjasnikovs/pi-task",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.16",
|
|
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",
|