@mjasnikovs/pi-task 0.17.16 → 0.17.18
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-io.d.ts +11 -0
- package/dist/task/auto-io.js +23 -0
- package/dist/task/auto-orchestrator.js +49 -5
- package/dist/task/auto-prompts.d.ts +10 -0
- package/dist/task/auto-prompts.js +34 -0
- package/dist/task/verify-work.js +13 -0
- package/package.json +1 -1
package/dist/task/auto-io.d.ts
CHANGED
|
@@ -7,6 +7,17 @@ export interface TaskEntry {
|
|
|
7
7
|
export declare function allocateAutoId(cwd: string): Promise<string>;
|
|
8
8
|
/** Parse a decompose-phase model output into a clean list of titles. */
|
|
9
9
|
export declare function parseDecomposeList(raw: string): string[];
|
|
10
|
+
/** Parsed DECOMPOSE_COVERAGE_PROMPT verdict. */
|
|
11
|
+
export interface CoverageVerdict {
|
|
12
|
+
kind: 'complete' | 'incomplete';
|
|
13
|
+
missing: string[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Parse the coverage-triage child's verdict. Returns null when no COVERAGE tag
|
|
17
|
+
* is present (the model wrote prose) — the caller treats that as "accept the
|
|
18
|
+
* list as-is", so a malformed judgment can never block planning.
|
|
19
|
+
*/
|
|
20
|
+
export declare function parseCoverageVerdict(raw: string): CoverageVerdict | null;
|
|
10
21
|
/** Parse the "## tasks" checkbox list. */
|
|
11
22
|
export declare function parseTaskList(body: string): TaskEntry[];
|
|
12
23
|
/** Build the initial AUTO-file body. */
|
package/dist/task/auto-io.js
CHANGED
|
@@ -39,6 +39,29 @@ export function parseDecomposeList(raw) {
|
|
|
39
39
|
}
|
|
40
40
|
return out;
|
|
41
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Parse the coverage-triage child's verdict. Returns null when no COVERAGE tag
|
|
44
|
+
* is present (the model wrote prose) — the caller treats that as "accept the
|
|
45
|
+
* list as-is", so a malformed judgment can never block planning.
|
|
46
|
+
*/
|
|
47
|
+
export function parseCoverageVerdict(raw) {
|
|
48
|
+
const tag = /^\s*COVERAGE:\s*(COMPLETE|INCOMPLETE)\s*$/im.exec(raw);
|
|
49
|
+
if (!tag)
|
|
50
|
+
return null;
|
|
51
|
+
if (tag[1].toUpperCase() === 'COMPLETE')
|
|
52
|
+
return { kind: 'complete', missing: [] };
|
|
53
|
+
const missing = [];
|
|
54
|
+
for (const line of raw.split('\n')) {
|
|
55
|
+
const m = /^\s*MISSING:\s*(.+?)\s*$/i.exec(line);
|
|
56
|
+
if (m && m[1].length > 0)
|
|
57
|
+
missing.push(m[1]);
|
|
58
|
+
if (missing.length >= 8)
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
// INCOMPLETE with no MISSING lines carries no actionable signal to reprompt
|
|
62
|
+
// with — treat it like an unparseable verdict rather than looping blind.
|
|
63
|
+
return missing.length === 0 ? null : { kind: 'incomplete', missing };
|
|
64
|
+
}
|
|
42
65
|
const CHECKBOX_RE = /^- \[([ xX])\]\s+(.+?)\s*$/;
|
|
43
66
|
const PRODUCED_ID_RE = /^(TASK_\d{4,})\s{2,}(.+)$/;
|
|
44
67
|
/** Parse the "## tasks" checkbox list. */
|
|
@@ -10,10 +10,10 @@ import * as path from 'node:path';
|
|
|
10
10
|
import { gateRunTask } from './orchestrator.js';
|
|
11
11
|
import { parseClarifyList, parseAutoAnswer, autoAnswerHasTag, deriveTitle } from './parsers.js';
|
|
12
12
|
import { renderInlineMarkdown, stripInlineMarkdown } from './inline-markdown.js';
|
|
13
|
-
import { AUTO_CLARIFY_PROMPT, AUTO_DECOMPOSE_PROMPT } from './auto-prompts.js';
|
|
13
|
+
import { AUTO_CLARIFY_PROMPT, AUTO_DECOMPOSE_PROMPT, DECOMPOSE_COVERAGE_PROMPT } from './auto-prompts.js';
|
|
14
14
|
import { GRILL_AUTO_ANSWER_PROMPT, GRILL_AUTO_FORMAT_HINT } from './prompts.js';
|
|
15
15
|
import { isDuplicateQuestion, MAX_DUP_STRIKES, DUP_REPROMPT_HINT } from './question-dedup.js';
|
|
16
|
-
import { allocateAutoId, buildAutoBody, parseDecomposeList, parseTaskList, checkOffTask, stampTaskInProgress, findResumableAuto } from './auto-io.js';
|
|
16
|
+
import { allocateAutoId, buildAutoBody, parseDecomposeList, parseCoverageVerdict, parseTaskList, checkOffTask, stampTaskInProgress, findResumableAuto } from './auto-io.js';
|
|
17
17
|
import { writeTaskFile, readTaskFile, updateTaskFrontMatter, taskFilePath, tasksDir } from './task-io.js';
|
|
18
18
|
import { readTextFile } from '../shared/fs-text.js';
|
|
19
19
|
import { findPhantomImports, rewritePhantomSpecifiers } from '../workers/phantom-imports.js';
|
|
@@ -29,6 +29,18 @@ import { runGatesForTask } from './task-gates.js';
|
|
|
29
29
|
// when the model emits NONE), but a model that never says NONE would otherwise
|
|
30
30
|
// barrage the user — the real mx5 run asked 10, several of them redundant.
|
|
31
31
|
const MAX_CLARIFY_QUESTIONS = 8;
|
|
32
|
+
// Bounded coverage-triage rounds after decompose: judge → reprompt-with-missing
|
|
33
|
+
// → judge again, at most. Two rounds so one flaky retry doesn't end the gate,
|
|
34
|
+
// while a judge that keeps flagging can't loop the plan phase forever.
|
|
35
|
+
const MAX_COVERAGE_ROUNDS = 2;
|
|
36
|
+
/** Reprompt prefix when the coverage triage found feature areas no task covers. */
|
|
37
|
+
function coverageRepromptHint(missing) {
|
|
38
|
+
return ('[SYSTEM NOTE: Your previous task list was INCOMPLETE — no task covered: '
|
|
39
|
+
+ missing.join('; ')
|
|
40
|
+
+ '. Regenerate the FULL ordered checkbox list for the ENTIRE feature: every '
|
|
41
|
+
+ 'task your previous list already had (reworded freely) PLUS tasks covering '
|
|
42
|
+
+ 'the areas above. Output every task, one "- [ ] " line each, nothing else.]');
|
|
43
|
+
}
|
|
32
44
|
// Matches pi's @-file completion token (a path after @, until whitespace).
|
|
33
45
|
const MENTION_RE = /(?:^|\s)@([^\s]+)/g;
|
|
34
46
|
// Trailing punctuation a user naturally types AFTER an @-mention when it sits in
|
|
@@ -391,12 +403,43 @@ export async function planAuto(ctx, cwd, feature, deps) {
|
|
|
391
403
|
}
|
|
392
404
|
const clarifications = answers.join('\n');
|
|
393
405
|
// decompose
|
|
394
|
-
const
|
|
406
|
+
const decomposePrompt = AUTO_DECOMPOSE_PROMPT(featureForModel, clarifications);
|
|
407
|
+
const listRaw = await deps.runChild('auto-decompose', 'read', decomposePrompt);
|
|
408
|
+
let planTitles = parseDecomposeList(listRaw);
|
|
409
|
+
logPlanDebug(cwd, `decompose produced ${planTitles.length} title(s)`);
|
|
410
|
+
// Coverage gate: a stochastic degenerate completion (live mx5: ONE task +
|
|
411
|
+
// natural EOS for an 18KB design doc) is nonempty, so the length guard below
|
|
412
|
+
// never fires and the whole run "completes" after one task. Judge the list
|
|
413
|
+
// against the feature with a no-tools child; on INCOMPLETE, re-run decompose
|
|
414
|
+
// with the missing areas as a hint. Longest-list-wins so a flaky retry can
|
|
415
|
+
// never replace a better list; best-effort so a triage fault never blocks
|
|
416
|
+
// planning (mirrors triageClarifyQuestion).
|
|
417
|
+
for (let round = 0; round < MAX_COVERAGE_ROUNDS && planTitles.length > 0; round++) {
|
|
418
|
+
let verdict;
|
|
419
|
+
try {
|
|
420
|
+
verdict = parseCoverageVerdict(await deps.runChild('decompose-coverage', '', DECOMPOSE_COVERAGE_PROMPT(featureForModel, clarifications, planTitles)));
|
|
421
|
+
}
|
|
422
|
+
catch {
|
|
423
|
+
break;
|
|
424
|
+
}
|
|
425
|
+
if (verdict === null || verdict.kind === 'complete') {
|
|
426
|
+
logPlanDebug(cwd, `decompose-coverage round ${round + 1}: `
|
|
427
|
+
+ (verdict === null ? 'no verdict — accepting list' : 'COMPLETE'));
|
|
428
|
+
break;
|
|
429
|
+
}
|
|
430
|
+
logPlanDebug(cwd, `decompose-coverage round ${round + 1}: INCOMPLETE — missing: `
|
|
431
|
+
+ verdict.missing.join('; ').slice(0, 300));
|
|
432
|
+
const retryRaw = await deps.runChild('auto-decompose', 'read', prependHint(coverageRepromptHint(verdict.missing), decomposePrompt));
|
|
433
|
+
const retryTitles = parseDecomposeList(retryRaw);
|
|
434
|
+
logPlanDebug(cwd, `decompose retry produced ${retryTitles.length} title(s)`);
|
|
435
|
+
if (retryTitles.length > planTitles.length)
|
|
436
|
+
planTitles = retryTitles;
|
|
437
|
+
}
|
|
395
438
|
// Thread the feature's spec doc(s) into every title so each per-task
|
|
396
439
|
// pipeline — which only ever sees its title — reads the real spec instead of
|
|
397
440
|
// a lossy one-line paraphrase of it.
|
|
398
441
|
const refs = await readableMentions(cwd, feature);
|
|
399
|
-
const titles = attachSpecRefs(
|
|
442
|
+
const titles = attachSpecRefs(planTitles, refs);
|
|
400
443
|
if (titles.length === 0) {
|
|
401
444
|
announceDone(ctx, '/task-auto: no tasks produced from the feature.', 'warning');
|
|
402
445
|
return null;
|
|
@@ -418,7 +461,8 @@ export async function planAuto(ctx, cwd, feature, deps) {
|
|
|
418
461
|
/** The two feature-level planning children, shown as steps in the loader. */
|
|
419
462
|
const AUTO_PLAN_STEPS = {
|
|
420
463
|
'auto-clarify': { step: 'clarify', stepNum: 1 },
|
|
421
|
-
'auto-decompose': { step: 'decompose', stepNum: 2 }
|
|
464
|
+
'auto-decompose': { step: 'decompose', stepNum: 2 },
|
|
465
|
+
'decompose-coverage': { step: 'coverage', stepNum: 2 }
|
|
422
466
|
};
|
|
423
467
|
const AUTO_PLAN_STEP_TOTAL = 2;
|
|
424
468
|
function defaultDeps(ctx, cwd, signal, title) {
|
|
@@ -14,3 +14,13 @@ export declare const AUTO_CLARIFY_PROMPT: (feature: string, priorQA: string) =>
|
|
|
14
14
|
* Decompose: output a markdown checkbox list of task titles (one line each).
|
|
15
15
|
*/
|
|
16
16
|
export declare const AUTO_DECOMPOSE_PROMPT: (feature: string, clarifications: string) => string;
|
|
17
|
+
/**
|
|
18
|
+
* Coverage triage: judge whether a decomposed task list covers the whole
|
|
19
|
+
* feature. Guards the plan — the highest-leverage artifact in /task-auto —
|
|
20
|
+
* against a degenerate-but-nonempty decompose completion (live mx5: the model
|
|
21
|
+
* emitted ONE task for an 18KB design doc with a natural EOS, and the only
|
|
22
|
+
* existing gate was `titles.length === 0`, so the run "completed" after one
|
|
23
|
+
* task). Pure judgment over text already in hand, so it runs with --no-tools.
|
|
24
|
+
* Output MUST match parseCoverageVerdict.
|
|
25
|
+
*/
|
|
26
|
+
export declare const DECOMPOSE_COVERAGE_PROMPT: (feature: string, clarifications: string, list: string[]) => string;
|
|
@@ -89,3 +89,37 @@ RULES:
|
|
|
89
89
|
doc; phrase them as imperative directives (e.g. "use Bun's built-in bundler, do
|
|
90
90
|
not add vite"). Do NOT invent decisions — only restate ones from CLARIFICATIONS.
|
|
91
91
|
- Output the checkbox list and NOTHING else (no preamble, no numbering).`;
|
|
92
|
+
/**
|
|
93
|
+
* Coverage triage: judge whether a decomposed task list covers the whole
|
|
94
|
+
* feature. Guards the plan — the highest-leverage artifact in /task-auto —
|
|
95
|
+
* against a degenerate-but-nonempty decompose completion (live mx5: the model
|
|
96
|
+
* emitted ONE task for an 18KB design doc with a natural EOS, and the only
|
|
97
|
+
* existing gate was `titles.length === 0`, so the run "completed" after one
|
|
98
|
+
* task). Pure judgment over text already in hand, so it runs with --no-tools.
|
|
99
|
+
* Output MUST match parseCoverageVerdict.
|
|
100
|
+
*/
|
|
101
|
+
export const DECOMPOSE_COVERAGE_PROMPT = (feature, clarifications, list) => `You are auditing a task decomposition for completeness. Below is a FEATURE
|
|
102
|
+
REQUEST (with any referenced design spec inlined), the CLARIFICATIONS the user
|
|
103
|
+
settled, and the proposed ordered TASK LIST that is supposed to implement ALL
|
|
104
|
+
of it. Each listed task will later get its own research and spec — so judge
|
|
105
|
+
coverage only, not wording or level of detail.
|
|
106
|
+
|
|
107
|
+
FEATURE REQUEST:
|
|
108
|
+
${feature.trim()}
|
|
109
|
+
|
|
110
|
+
CLARIFICATIONS:
|
|
111
|
+
${clarifications.trim() || '(none)'}
|
|
112
|
+
|
|
113
|
+
TASK LIST:
|
|
114
|
+
${list.map((t, i) => `${i + 1}. ${t}`).join('\n')}
|
|
115
|
+
|
|
116
|
+
Judge ONE thing: taken together, do these tasks cover the whole feature end to
|
|
117
|
+
end? An area is MISSING only when NO task plausibly covers it — do not flag
|
|
118
|
+
wording, ordering, task size, or detail a task's own research will fill in.
|
|
119
|
+
|
|
120
|
+
Output — no preamble, no markdown:
|
|
121
|
+
COVERAGE: COMPLETE
|
|
122
|
+
or
|
|
123
|
+
COVERAGE: INCOMPLETE
|
|
124
|
+
MISSING: <feature area no task covers>
|
|
125
|
+
(one MISSING line per uncovered area, most important first, at most 8)`;
|
package/dist/task/verify-work.js
CHANGED
|
@@ -188,6 +188,19 @@ export function buildVerifyPrompt(spec, probeFindings) {
|
|
|
188
188
|
' shipped artifact directly (the real app / module / entry point). If the real artifact',
|
|
189
189
|
' fails where the tests pass, report FAIL and name the bypass.',
|
|
190
190
|
'',
|
|
191
|
+
'3c. TEST-AUTHORED REPAIR: rule 2 applies INSIDE the shipped test suite too. Before',
|
|
192
|
+
' trusting a green suite, READ its setup/bootstrap (beforeAll / fixtures / helpers /',
|
|
193
|
+
' conftest). Seeding ordinary test DATA is fine — but if the setup changes the',
|
|
194
|
+
" product's STRUCTURE or wiring to make the code runnable (ALTER TABLE / adding",
|
|
195
|
+
" columns, tables, or config that the project's own migrations and setup files never",
|
|
196
|
+
' create; monkey-patching or stubbing shipped code), the suite is repairing the',
|
|
197
|
+
' product in order to pass: its green result proves the repaired copy, NOT the',
|
|
198
|
+
" shipped artifact. Cross-check against the project's own provisioning: apply ONLY",
|
|
199
|
+
" the project's own setup commands (its migrations / seed scripts) to a scratch",
|
|
200
|
+
' database and check whether the tested behaviors are possible on what THEY produce.',
|
|
201
|
+
' If the tests patched a gap, report FAIL and name the missing production-side',
|
|
202
|
+
' change (e.g. the column no migration creates).',
|
|
203
|
+
'',
|
|
191
204
|
'4. Treat the ACCEPTANCE criteria as the bar. If a command fails, or its real output',
|
|
192
205
|
' contradicts an ACCEPTANCE criterion, the work has NOT verified.',
|
|
193
206
|
'',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mjasnikovs/pi-task",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.18",
|
|
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",
|