@mjasnikovs/pi-task 0.17.14 → 0.17.15

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.
@@ -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. A FAIL offers the user a boxed AUTOFIX / ACCEPT / dismiss
10
- * picker (the user always decides; AUTOFIX re-runs the implementation turn and
11
- * loops back to the gate uncapped).
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
@@ -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: offer the boxed AUTOFIX / ACCEPT / dismiss
64
- // picker. The USER always decides; AUTOFIX loops straight back to the gate
65
- // as many times as they keep choosing it (no attempt cap).
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
@@ -85,7 +98,23 @@ export async function runGatesForTask(ctxIn, deps, p) {
85
98
  await deps.recommend(active, p.cwd, p.title, p.taskId, failReason)
86
99
  : { recommend: 'autofix', rationale: failReason };
87
100
  await rec(`resolution: recommended ${recOutcome.recommend.toUpperCase()}`);
88
- const choice = await askVerifyResolution(active, p.title, failReason, recOutcome);
101
+ // AUTO-RESOLVE the AUTOFIX path: when the research says the work is
102
+ // genuinely wrong, re-run the fix WITHOUT prompting the user. The picker is
103
+ // reserved for the ACCEPT recommendation (the human decides whether to bless
104
+ // an artifact the gate FAILed) and for the bounded fallback: after
105
+ // MAX_AUTO_AUTOFIX consecutive unattended attempts that still FAIL, hand
106
+ // control back so a person can break a non-converging loop.
107
+ const autoFixNow = recOutcome.recommend === 'autofix' && autoFixCount < MAX_AUTO_AUTOFIX;
108
+ let choice;
109
+ if (autoFixNow) {
110
+ autoFixCount += 1;
111
+ await rec(`resolution: auto-AUTOFIX (recommended, unattended ${autoFixCount}/${MAX_AUTO_AUTOFIX})`);
112
+ active.ui.notify(`${p.tag}: verify FAIL on "${p.title}" — auto-fixing (recommended, ${autoFixCount}/${MAX_AUTO_AUTOFIX})…`, 'info');
113
+ choice = { action: 'autofix' };
114
+ }
115
+ else {
116
+ choice = await askVerifyResolution(active, p.title, failReason, recOutcome);
117
+ }
89
118
  if (choice.action === 'cancel') {
90
119
  await rec('resolution: user dismissed the verify-FAIL picker — paused');
91
120
  return { kind: 'paused', ctx: active, reason: failReason };
@@ -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 user ALWAYS makes the final call. This module only computes which of the
14
- * two cards is tinted RECOMMENDED, by handing the FAIL reason + the task spec to
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 is advisory: a missing/garbled verdict defaults to recommending
27
- * AUTOFIX, the conservative choice (re-do the work rather than bless it). The
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mjasnikovs/pi-task",
3
- "version": "0.17.14",
3
+ "version": "0.17.15",
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",