@mjasnikovs/pi-task 0.13.37 → 0.13.38

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.
@@ -17,7 +17,7 @@ import { writeTaskFile, readTaskFile, updateTaskFrontMatter, taskFilePath, tasks
17
17
  import { gitCommitAll } from './auto-commit.js';
18
18
  import { runGuidelineEnforcement, classifyEnforceChildFailure } from './enforce-guidelines.js';
19
19
  import { runWorker } from '../workers/pi-worker-core.js';
20
- import { runPhaseChild, prependHint, USER_CANCELLED } from './child-runner.js';
20
+ import { runPhaseChild, prependHint, formatLoopHint, USER_CANCELLED } from './child-runner.js';
21
21
  import { SessionUI, registerBridgeCommand } from '../remote/bridge.js';
22
22
  import { pushNotify } from '../remote/push.js';
23
23
  import { getConfig } from '../config/config.js';
@@ -362,7 +362,15 @@ function defaultDeps(ctx, cwd, signal, title) {
362
362
  signal: sig,
363
363
  tools,
364
364
  timeoutMs: 0, // no wall-clock timeout — run to completion
365
- loop: false, // no loop guard revisiting one file IS the job
365
+ // Exact-match loop guard only: pathThreshold Infinity
366
+ // disables the path-revisit heuristic, so revisiting one
367
+ // file (which IS this pass's job) never trips — only a
368
+ // literally-identical call repeated past threshold does
369
+ // (e.g. the same grep fired 900× in enforce-debug.log).
370
+ // A hit nudges via the normal restart-with-hint; a loop
371
+ // that survives the nudges is WARNED, not blocked (see
372
+ // r.loopHit handling below and classifyEnforceChildFailure).
373
+ loop: { pathThreshold: Number.POSITIVE_INFINITY },
366
374
  onLine: line => {
367
375
  lastLine = line;
368
376
  logEnforce(line);
@@ -371,6 +379,13 @@ function defaultDeps(ctx, cwd, signal, title) {
371
379
  contextUsage = resolveContextUsage(snapshot, contextUsage, parentContextWindow);
372
380
  }
373
381
  });
382
+ // A loop that survived the restart-with-hint nudges is a
383
+ // warning, not a failure: log it and tell the user, but let
384
+ // the verdict gate (below) be the only thing that can block.
385
+ if (r.loopHit) {
386
+ logEnforce(`=== enforce LOOP WARNING — ${formatLoopHint(r.loopHit)} ===`);
387
+ enforceCtx.ui.notify(`${taskTitle}: enforce worker looped past the nudges — continuing (not blocked).`, 'warning');
388
+ }
374
389
  const failure = classifyEnforceChildFailure(r);
375
390
  logEnforce(failure ?
376
391
  `=== enforce end: FAIL — ${failure} ===`
@@ -56,10 +56,17 @@ export interface EnforceChildResult {
56
56
  * when it finished cleanly enough to parse a verdict from its text.
57
57
  *
58
58
  * The order is load-bearing. A loop-kill AND a wall-clock timeout BOTH also set
59
- * `aborted` — killProc flips it on every kill path — so the specific causes
60
- * (timeout, loop, leaked tool call) must be named BEFORE the generic
61
- * `aborted → user-cancel` mapping. Checking `aborted` first (as the original
62
- * inline code did) mislabels a loop-killed enforcement child as a user cancel.
59
+ * `aborted` (and a non-zero exit) — killProc flips it on every kill path — so the
60
+ * specific causes (timeout, loop, leaked tool call) must be handled BEFORE the
61
+ * generic `aborted → user-cancel` and `exitCode` mappings. Checking `aborted`
62
+ * first (as the original inline code did) mislabels a loop-killed enforcement
63
+ * child as a user cancel.
64
+ *
65
+ * A loop is NOT fatal: enforce attaches the detector in nudge-then-warn mode, so a
66
+ * loop that survived its restart-with-hint nudges returns null here (the caller
67
+ * logs/notifies it as a warning) and the verdict gate alone decides the outcome.
68
+ * It still has to be matched before `aborted`/`exitCode` so the kill's side
69
+ * effects don't get re-classified as a user cancel or a crash.
63
70
  */
64
71
  export declare function classifyEnforceChildFailure(r: EnforceChildResult): string | null;
65
72
  /**
@@ -101,16 +101,23 @@ export function parseEnforceVerdict(text) {
101
101
  * when it finished cleanly enough to parse a verdict from its text.
102
102
  *
103
103
  * The order is load-bearing. A loop-kill AND a wall-clock timeout BOTH also set
104
- * `aborted` — killProc flips it on every kill path — so the specific causes
105
- * (timeout, loop, leaked tool call) must be named BEFORE the generic
106
- * `aborted → user-cancel` mapping. Checking `aborted` first (as the original
107
- * inline code did) mislabels a loop-killed enforcement child as a user cancel.
104
+ * `aborted` (and a non-zero exit) — killProc flips it on every kill path — so the
105
+ * specific causes (timeout, loop, leaked tool call) must be handled BEFORE the
106
+ * generic `aborted → user-cancel` and `exitCode` mappings. Checking `aborted`
107
+ * first (as the original inline code did) mislabels a loop-killed enforcement
108
+ * child as a user cancel.
109
+ *
110
+ * A loop is NOT fatal: enforce attaches the detector in nudge-then-warn mode, so a
111
+ * loop that survived its restart-with-hint nudges returns null here (the caller
112
+ * logs/notifies it as a warning) and the verdict gate alone decides the outcome.
113
+ * It still has to be matched before `aborted`/`exitCode` so the kill's side
114
+ * effects don't get re-classified as a user cancel or a crash.
108
115
  */
109
116
  export function classifyEnforceChildFailure(r) {
110
117
  if (r.timedOut)
111
118
  return 'enforcement child timed out';
112
119
  if (r.loopHit)
113
- return 'enforcement child looped';
120
+ return null; // looped past the nudges → warning, handled by caller
114
121
  if (r.leakedToolCall)
115
122
  return 'enforcement child leaked a tool call';
116
123
  if (r.aborted)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mjasnikovs/pi-task",
3
- "version": "0.13.37",
3
+ "version": "0.13.38",
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",