@mjasnikovs/pi-task 0.18.1 → 0.18.2

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.
@@ -45,6 +45,32 @@ function coverageRepromptHint(missing) {
45
45
  + 'task your previous list already had (reworded freely) PLUS tasks covering '
46
46
  + 'the areas above. Output every task, one "- [ ] " line each, nothing else.]');
47
47
  }
48
+ // Deterministic distrust floor for the coverage gate. The gate's judge is the
49
+ // same stochastic model as the decompose call it guards, and live (mx5 2026-07-08,
50
+ // A/B N=10) it rubber-stamps a 1-task plan for an 18KB spec 3/10 times — always
51
+ // as the bare "COVERAGE: COMPLETE" line, which is byte-identical to a legitimate
52
+ // verdict, so the rubber-stamp is NOT detectable from the judge's output. The
53
+ // distrust signal must come from the input: a plan this small for a spec this
54
+ // large is near-certainly the known degenerate-decompose flake (healthy runs on
55
+ // the same inputs produce 10–30 titles). The floor only ever forces a REGENERATION
56
+ // — it never rejects a plan on count alone (the v0.13.34 objection), so a model
57
+ // that insists twice still ships its small plan, with a warning.
58
+ const SUSPECT_PLAN_MAX_TITLES = 2;
59
+ const SUSPECT_PLAN_MIN_SPEC_CHARS = 4000;
60
+ function isSuspectPlan(titles, featureForModel) {
61
+ return (titles.length > 0
62
+ && titles.length <= SUSPECT_PLAN_MAX_TITLES
63
+ && featureForModel.length >= SUSPECT_PLAN_MIN_SPEC_CHARS);
64
+ }
65
+ /** Reprompt prefix for a suspect (degenerate-count) list; unlike
66
+ * coverageRepromptHint there is no judge verdict yet, so no missing areas. */
67
+ function suspectPlanHint(count) {
68
+ return (`[SYSTEM NOTE: Your previous answer contained only ${count} task(s), which `
69
+ + 'cannot decompose a feature specification of this size — it was almost '
70
+ + 'certainly an incomplete generation. Regenerate the FULL ordered checkbox '
71
+ + 'list for the ENTIRE feature, covering every part of the spec end to end. '
72
+ + 'Output every task, one "- [ ] " line each, nothing else.]');
73
+ }
48
74
  // Matches pi's @-file completion token (a path after @, until whitespace).
49
75
  const MENTION_RE = /(?:^|\s)@([^\s]+)/g;
50
76
  // Trailing punctuation a user naturally types AFTER an @-mention when it sits in
@@ -411,6 +437,20 @@ export async function planAuto(ctx, cwd, feature, deps) {
411
437
  const listRaw = await deps.runChild('auto-decompose', 'read', decomposePrompt);
412
438
  let planTitles = parseDecomposeList(listRaw);
413
439
  logPlanDebug(cwd, `decompose produced ${planTitles.length} title(s)`);
440
+ // Distrust floor (see isSuspectPlan): a ≤2-title plan for a multi-KB spec is
441
+ // regenerated once BEFORE the judge runs — the judge cannot be trusted to
442
+ // catch it (3/10 live false-pass) and a hinted retry heals it reliably
443
+ // (5/5 live). Longer list wins; a still-suspect plan falls through to the
444
+ // judge loop as before, so this never blocks planning.
445
+ if (isSuspectPlan(planTitles, featureForModel)) {
446
+ logPlanDebug(cwd, `decompose suspect (${planTitles.length} title(s) for a ${featureForModel.length}-char spec)`
447
+ + ` — raw output: ${listRaw.trim().slice(0, 300)}`);
448
+ const retryRaw = await deps.runChild('auto-decompose', 'read', prependHint(suspectPlanHint(planTitles.length), decomposePrompt));
449
+ const retryTitles = parseDecomposeList(retryRaw);
450
+ logPlanDebug(cwd, `decompose suspect-retry produced ${retryTitles.length} title(s)`);
451
+ if (retryTitles.length > planTitles.length)
452
+ planTitles = retryTitles;
453
+ }
414
454
  // Coverage gate: a stochastic degenerate completion (live mx5: ONE task +
415
455
  // natural EOS for an 18KB design doc) is nonempty, so the length guard below
416
456
  // never fires and the whole run "completes" after one task. Judge the list
@@ -442,6 +482,14 @@ export async function planAuto(ctx, cwd, feature, deps) {
442
482
  unresolvedMissing = null;
443
483
  logPlanDebug(cwd, `decompose-coverage round ${round + 1}: `
444
484
  + (verdict === null ? 'no verdict — accepting list' : 'COMPLETE'));
485
+ // A COMPLETE on a still-suspect plan is the judge's known live
486
+ // false-pass mode (bare verdict, indistinguishable from a real one).
487
+ // The plan still ships — the floor never rejects on count — but
488
+ // never silently: the user decides whether to trust it.
489
+ if (isSuspectPlan(planTitles, featureForModel)) {
490
+ ctx.ui.notify(`/task-auto: only ${planTitles.length} task(s) planned for a large spec`
491
+ + ' and the regeneration did not grow the list — review the plan before running.', 'warning');
492
+ }
445
493
  break;
446
494
  }
447
495
  unresolvedMissing = verdict.missing;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mjasnikovs/pi-task",
3
- "version": "0.18.1",
3
+ "version": "0.18.2",
4
4
  "description": "Deterministic task planning and spec-orchestration for local models — crash-safe /task pipelines with verify/enforce gates, a real-time remote web view, and web/docs/fetch/worker subagent tools.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",