@mjasnikovs/pi-task 0.17.3 → 0.17.5

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,9 +8,10 @@
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';
@@ -54,6 +55,56 @@ function logPlanDebug(cwd, msg) {
54
55
  .then(() => fsp.appendFile(path.join(dir, 'plan-debug.log'), line))
55
56
  .catch(() => { });
56
57
  }
58
+ /**
59
+ * Clarify's answer-side TRIAGE — the second stage /task-auto's clarify gate was
60
+ * missing that /task's grill already had. /task-auto's clarify was single-stage:
61
+ * it generated a question and went straight to the user, with the gen prompt's
62
+ * own "do not re-ask what the spec settles" prose as the only guard — and a weak
63
+ * local model ignores that prose, surfacing questions the inlined spec already
64
+ * answers (the "use Hono's Bun adapter or node adapter?" question when the spec
65
+ * pins the Bun adapter outright).
66
+ *
67
+ * grill is gen → AUTO-ANSWER triage → user; clarify was gen → user. This runs
68
+ * grill's exact triage (ALREADY-DECIDED → FUNCTIONAL-REQUIREMENT → reversibility,
69
+ * see GRILL_AUTO_ANSWER_PROMPT) against the inlined spec BEFORE a question reaches
70
+ * the user. If the spec already settles the answer (or it's a cheap-to-undo
71
+ * default the user would accept without thinking), the question is auto-resolved
72
+ * and never shown — the resolved value still flows into the clarify transcript so
73
+ * decompose sees the decision and the next gen call won't re-ask it. Only a
74
+ * genuine UNKNOWN — a fork the spec leaves open — survives to the user.
75
+ *
76
+ * Returns the auto-resolved answer string when the question is settled, or null
77
+ * to surface it. The source fed to the triage is the feature spec itself; there
78
+ * is no per-feature research blob at this pre-decomposition stage (each task does
79
+ * its own research downstream), so the "Research" slot is a stub. Best-effort: a
80
+ * throw or an untagged reply falls back to surfacing the question (the prior
81
+ * behavior), never dropping it silently.
82
+ */
83
+ async function triageClarifyQuestion(deps, cwd, featureForModel, question) {
84
+ try {
85
+ const basePrompt = GRILL_AUTO_ANSWER_PROMPT(featureForModel, '(none — clarify runs before decomposition; each task researches itself later)', question);
86
+ let text = await deps.runChild('clarify-triage', 'read', basePrompt);
87
+ if (!autoAnswerHasTag(text)) {
88
+ // No ANSWER/UNKNOWN/ALT tag — the model wrote prose. Reprompt once for
89
+ // the tagged form before trusting parseAutoAnswer's lenient salvage,
90
+ // exactly as phaseAutoAnswer does, so a preamble line can't masquerade
91
+ // as a decision.
92
+ text = await deps.runChild('clarify-triage', 'read', prependHint(GRILL_AUTO_FORMAT_HINT, basePrompt));
93
+ }
94
+ const parsed = parseAutoAnswer(text);
95
+ if (parsed.kind === 'answered') {
96
+ logPlanDebug(cwd, `clarify-triage auto-resolved (spec-settled): ${question.replace(/\s+/g, ' ').slice(0, 100)}`
97
+ + ` → ${parsed.text.replace(/\s+/g, ' ').slice(0, 100)}`);
98
+ return parsed.text;
99
+ }
100
+ return null;
101
+ }
102
+ catch {
103
+ // Triage is a best-effort filter, not a gate: on any failure, surface the
104
+ // question rather than silently dropping it.
105
+ return null;
106
+ }
107
+ }
57
108
  /**
58
109
  * Expand any @file references in the feature text by appending each referenced
59
110
  * file's contents, so the planning children (clarify, decompose) always see the
@@ -186,8 +237,10 @@ export async function planAuto(ctx, cwd, feature, deps) {
186
237
  // otherwise the model's recommendation is shown as the input placeholder and
187
238
  // in the title. Nothing is pre-filled into the editor — submitting an empty
188
239
  // field is what accepts the recommendation (see the typed.length === 0 branch
189
- // below); typing overrides it. We never auto-answer; the model emits NONE when
190
- // nothing remains.
240
+ // below); typing overrides it. Each generated question first runs the
241
+ // answer-side TRIAGE (triageClarifyQuestion): a question the inlined spec
242
+ // already settles is auto-resolved and never shown — only genuine open forks
243
+ // reach the user. The model emits NONE when nothing remains.
191
244
  const theme = ctx.ui.theme;
192
245
  const ui = new SessionUI(ctx);
193
246
  // Inline any @file spec the user referenced so clarify/decompose reason over
@@ -242,6 +295,16 @@ export async function planAuto(ctx, cwd, feature, deps) {
242
295
  const shownQ = renderInlineMarkdown(question, theme);
243
296
  const plainQ = stripInlineMarkdown(question);
244
297
  askedQuestions.push(plainQ);
298
+ // Answer-side triage (grill parity): if the inlined spec already settles
299
+ // this question, auto-resolve it and never show it. The resolved value is
300
+ // recorded so decompose sees the decision and the next gen call's priorQA
301
+ // won't re-ask it.
302
+ const autoResolved = await triageClarifyQuestion(deps, cwd, featureForModel, plainQ);
303
+ if (autoResolved !== null) {
304
+ answers.push(`Q${answers.length + 1}: ${plainQ}\n`
305
+ + `A${answers.length + 1}: ${autoResolved} (auto-resolved — already settled by the spec)`);
306
+ continue;
307
+ }
245
308
  const plainSuggested = suggested === undefined ? undefined : stripInlineMarkdown(suggested);
246
309
  const plainAlt = alt === undefined ? undefined : stripInlineMarkdown(alt);
247
310
  // Identical to /task's grill dialog: a recommendation (or A/B fork)
@@ -222,6 +222,14 @@ LIVE-DATA RULE:
222
222
  - "### freshness-check skipped" → tag UNKNOWN and say current state needs verification.
223
223
  - No npm block + question is about latest/current version → tag UNKNOWN (training data goes stale).
224
224
 
225
+ TRIAGE — run these checks IN ORDER first. The REVERSIBILITY TEST below applies ONLY to a question that survives all checks as a genuine preference.
226
+
227
+ 1. ALREADY-DECIDED CHECK — scan the refined task and research for a value, shape, response body, schema, route, or requirement that ALREADY determines the answer. If one does, this is a fact, not a preference. Emit "ANSWER: <value taken from that source>". If your instinct or a "nicer" alternative contradicts that source, the SOURCE WINS — never override a stated contract with a preferred default. (E.g. a stated response shape { items, total, page, pageSize } already answers a pagination question — page/offset — you may NOT answer "cursor".)
228
+
229
+ 2. FUNCTIONAL-REQUIREMENT CHECK — if the question is whether to include or defer a package, config file, or setup that something THIS task configures needs in order to FUNCTION (a build plugin's engine or required peer dependency, an entry file the build reads, a runtime module an import resolves to), then a "minimize / keep it minimal / defer to the step that uses it" preference does NOT override that functional requirement. A tool you wire up this step must have its required pieces present this step or the build/step is broken. Emit "ANSWER: <include it now, because configuring X requires it>". Do not defer something the step's own configuration depends on.
230
+
231
+ 3. PREFERENCE — only if neither check fires (a genuine choice the sources do not determine), apply the REVERSIBILITY TEST.
232
+
225
233
  REVERSIBILITY TEST:
226
234
  ANSWER: cheap to undo (output style, policy, report format, obvious scope, standard convention).
227
235
  UNKNOWN: costly to reverse (file mutations, tool/dependency choice, approach/algorithm, format that shapes downstream artifacts).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mjasnikovs/pi-task",
3
- "version": "0.17.3",
3
+ "version": "0.17.5",
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",