@mjasnikovs/pi-task 0.17.4 → 0.17.6
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.
|
@@ -8,14 +8,16 @@
|
|
|
8
8
|
import * as fsp from 'node:fs/promises';
|
|
9
9
|
import * as path from 'node:path';
|
|
10
10
|
import { gateRunTask } from './orchestrator.js';
|
|
11
|
-
import { parseClarifyList, deriveTitle } from './parsers.js';
|
|
11
|
+
import { parseClarifyList, parseAutoAnswer, autoAnswerHasTag, deriveTitle } from './parsers.js';
|
|
12
12
|
import { renderInlineMarkdown, stripInlineMarkdown } from './inline-markdown.js';
|
|
13
13
|
import { AUTO_CLARIFY_PROMPT, AUTO_DECOMPOSE_PROMPT } from './auto-prompts.js';
|
|
14
|
+
import { GRILL_AUTO_ANSWER_PROMPT, GRILL_AUTO_FORMAT_HINT } from './prompts.js';
|
|
14
15
|
import { isDuplicateQuestion, MAX_DUP_STRIKES, DUP_REPROMPT_HINT } from './question-dedup.js';
|
|
15
16
|
import { allocateAutoId, buildAutoBody, parseDecomposeList, parseTaskList, checkOffTask, stampTaskInProgress, findResumableAuto } from './auto-io.js';
|
|
16
17
|
import { writeTaskFile, readTaskFile, updateTaskFrontMatter, taskFilePath, tasksDir } from './task-io.js';
|
|
17
18
|
import { findPhantomImports, rewritePhantomSpecifiers } from '../workers/phantom-imports.js';
|
|
18
19
|
import { runPhaseChild, prependHint, USER_CANCELLED } from './child-runner.js';
|
|
20
|
+
import { refineExistingFilesBlock } from './phases.js';
|
|
19
21
|
import { SessionUI, registerBridgeCommand, publishLifecycleNotice } from '../remote/bridge.js';
|
|
20
22
|
import { pushNotify } from '../remote/push.js';
|
|
21
23
|
import { startAutoLoader } from './widget.js';
|
|
@@ -54,6 +56,66 @@ function logPlanDebug(cwd, msg) {
|
|
|
54
56
|
.then(() => fsp.appendFile(path.join(dir, 'plan-debug.log'), line))
|
|
55
57
|
.catch(() => { });
|
|
56
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Clarify's answer-side TRIAGE — the second stage /task-auto's clarify gate was
|
|
61
|
+
* missing that /task's grill already had. /task-auto's clarify was single-stage:
|
|
62
|
+
* it generated a question and went straight to the user, with the gen prompt's
|
|
63
|
+
* own "do not re-ask what the spec settles" prose as the only guard — and a weak
|
|
64
|
+
* local model ignores that prose, surfacing questions the inlined spec already
|
|
65
|
+
* answers (the "use Hono's Bun adapter or node adapter?" question when the spec
|
|
66
|
+
* pins the Bun adapter outright).
|
|
67
|
+
*
|
|
68
|
+
* grill is gen → AUTO-ANSWER triage → user; clarify was gen → user. This runs
|
|
69
|
+
* grill's exact triage (ALREADY-DECIDED → FUNCTIONAL-REQUIREMENT → reversibility,
|
|
70
|
+
* see GRILL_AUTO_ANSWER_PROMPT) against the inlined spec BEFORE a question reaches
|
|
71
|
+
* the user. If the spec already settles the answer (or it's a cheap-to-undo
|
|
72
|
+
* default the user would accept without thinking), the question is auto-resolved
|
|
73
|
+
* and never shown — the resolved value still flows into the clarify transcript so
|
|
74
|
+
* decompose sees the decision and the next gen call won't re-ask it. Only a
|
|
75
|
+
* genuine UNKNOWN — a fork the spec leaves open — survives to the user.
|
|
76
|
+
*
|
|
77
|
+
* Returns the auto-resolved answer string when the question is settled, or null
|
|
78
|
+
* to surface it. The source fed to the triage is the feature spec itself; there
|
|
79
|
+
* is no per-feature research blob at this pre-decomposition stage (each task does
|
|
80
|
+
* its own research downstream), so the "Research" slot is a stub. Best-effort: a
|
|
81
|
+
* throw or an untagged reply falls back to surfacing the question (the prior
|
|
82
|
+
* behavior), never dropping it silently.
|
|
83
|
+
*/
|
|
84
|
+
async function triageClarifyQuestion(deps, cwd, featureForModel, existingFilesBlock, question) {
|
|
85
|
+
try {
|
|
86
|
+
// Prepend the existing-files block (refine's REFINE_PRESERVE_DIRECTIVE +
|
|
87
|
+
// manifest/config content) so a "scaffold/create/from scratch" question is
|
|
88
|
+
// auto-resolved as an in-place UPDATE that PRESERVES what is on disk —
|
|
89
|
+
// instead of "greenfield, from scratch", which the spec-only triage emitted
|
|
90
|
+
// 13/15 of the time and would mint a destructive decompose decision that can
|
|
91
|
+
// outrank refine's preserve directive (A/B live: 2/15 → 14/15 preserve).
|
|
92
|
+
// Empty (greenfield repo / orientation off) → byte-identical to before.
|
|
93
|
+
const source = existingFilesBlock.length > 0 ?
|
|
94
|
+
`${existingFilesBlock}\n\n${featureForModel}`
|
|
95
|
+
: featureForModel;
|
|
96
|
+
const basePrompt = GRILL_AUTO_ANSWER_PROMPT(source, '(none — clarify runs before decomposition; each task researches itself later)', question);
|
|
97
|
+
let text = await deps.runChild('clarify-triage', 'read', basePrompt);
|
|
98
|
+
if (!autoAnswerHasTag(text)) {
|
|
99
|
+
// No ANSWER/UNKNOWN/ALT tag — the model wrote prose. Reprompt once for
|
|
100
|
+
// the tagged form before trusting parseAutoAnswer's lenient salvage,
|
|
101
|
+
// exactly as phaseAutoAnswer does, so a preamble line can't masquerade
|
|
102
|
+
// as a decision.
|
|
103
|
+
text = await deps.runChild('clarify-triage', 'read', prependHint(GRILL_AUTO_FORMAT_HINT, basePrompt));
|
|
104
|
+
}
|
|
105
|
+
const parsed = parseAutoAnswer(text);
|
|
106
|
+
if (parsed.kind === 'answered') {
|
|
107
|
+
logPlanDebug(cwd, `clarify-triage auto-resolved (spec-settled): ${question.replace(/\s+/g, ' ').slice(0, 100)}`
|
|
108
|
+
+ ` → ${parsed.text.replace(/\s+/g, ' ').slice(0, 100)}`);
|
|
109
|
+
return parsed.text;
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// Triage is a best-effort filter, not a gate: on any failure, surface the
|
|
115
|
+
// question rather than silently dropping it.
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
57
119
|
/**
|
|
58
120
|
* Expand any @file references in the feature text by appending each referenced
|
|
59
121
|
* file's contents, so the planning children (clarify, decompose) always see the
|
|
@@ -186,8 +248,10 @@ export async function planAuto(ctx, cwd, feature, deps) {
|
|
|
186
248
|
// otherwise the model's recommendation is shown as the input placeholder and
|
|
187
249
|
// in the title. Nothing is pre-filled into the editor — submitting an empty
|
|
188
250
|
// field is what accepts the recommendation (see the typed.length === 0 branch
|
|
189
|
-
// below); typing overrides it.
|
|
190
|
-
//
|
|
251
|
+
// below); typing overrides it. Each generated question first runs the
|
|
252
|
+
// answer-side TRIAGE (triageClarifyQuestion): a question the inlined spec
|
|
253
|
+
// already settles is auto-resolved and never shown — only genuine open forks
|
|
254
|
+
// reach the user. The model emits NONE when nothing remains.
|
|
191
255
|
const theme = ctx.ui.theme;
|
|
192
256
|
const ui = new SessionUI(ctx);
|
|
193
257
|
// Inline any @file spec the user referenced so clarify/decompose reason over
|
|
@@ -208,6 +272,15 @@ export async function planAuto(ctx, cwd, feature, deps) {
|
|
|
208
272
|
if (planPhantoms.length > 0) {
|
|
209
273
|
logPlanDebug(cwd, `phantom specifiers rewritten in plan spec: ${planPhantoms.map(x => x.spec).join(', ')}`);
|
|
210
274
|
}
|
|
275
|
+
// Existing manifest/config on disk (REFINE_PRESERVE_DIRECTIVE + tier 0–1
|
|
276
|
+
// content), computed ONCE and fed to every triage call so a "scaffold X"
|
|
277
|
+
// question resolves to an in-place update instead of "greenfield". '' for a
|
|
278
|
+
// greenfield/non-git repo or orientation off → triage stays spec-only.
|
|
279
|
+
const existingFilesBlock = await refineExistingFilesBlock({
|
|
280
|
+
cwd,
|
|
281
|
+
taskId: '',
|
|
282
|
+
signal: new AbortController().signal
|
|
283
|
+
}).catch(() => '');
|
|
211
284
|
const answers = [];
|
|
212
285
|
// Plain text of every question already shown, for the duplicate backstop.
|
|
213
286
|
const askedQuestions = [];
|
|
@@ -242,6 +315,16 @@ export async function planAuto(ctx, cwd, feature, deps) {
|
|
|
242
315
|
const shownQ = renderInlineMarkdown(question, theme);
|
|
243
316
|
const plainQ = stripInlineMarkdown(question);
|
|
244
317
|
askedQuestions.push(plainQ);
|
|
318
|
+
// Answer-side triage (grill parity): if the inlined spec already settles
|
|
319
|
+
// this question, auto-resolve it and never show it. The resolved value is
|
|
320
|
+
// recorded so decompose sees the decision and the next gen call's priorQA
|
|
321
|
+
// won't re-ask it.
|
|
322
|
+
const autoResolved = await triageClarifyQuestion(deps, cwd, featureForModel, existingFilesBlock, plainQ);
|
|
323
|
+
if (autoResolved !== null) {
|
|
324
|
+
answers.push(`Q${answers.length + 1}: ${plainQ}\n`
|
|
325
|
+
+ `A${answers.length + 1}: ${autoResolved} (auto-resolved — already settled by the spec)`);
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
245
328
|
const plainSuggested = suggested === undefined ? undefined : stripInlineMarkdown(suggested);
|
|
246
329
|
const plainAlt = alt === undefined ? undefined : stripInlineMarkdown(alt);
|
|
247
330
|
// Identical to /task's grill dialog: a recommendation (or A/B fork)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mjasnikovs/pi-task",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.6",
|
|
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",
|