@mjasnikovs/pi-task 0.18.12 → 0.18.14

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.
@@ -127,6 +127,14 @@ export interface GateDeps {
127
127
  * and surfaces it if still open. Best-effort; absent in tests → no ledger written.
128
128
  */
129
129
  recordAcceptDebt?: (cwd: string, taskId: string, reason: string) => Promise<void>;
130
+ /**
131
+ * Record a durable ENFORCE-REVERT debt (mx5 run 10 item 3): the enforce re-verify
132
+ * FAILED and the enforce edits were reverted, but the FAIL indicts the ORIGINAL
133
+ * work (run 10 TASK_0004: "Missing server entry point … the Hono server cannot be
134
+ * started"). Without this the diagnosis dies with the revert; recorded, the final
135
+ * gate re-checks and surfaces it like an accept-debt. Best-effort; absent in tests.
136
+ */
137
+ recordEnforceRevertDebt?: (cwd: string, taskId: string, reason: string) => Promise<void>;
130
138
  /**
131
139
  * The concrete paths this task's spec forbids modifying (its `Do NOT modify`
132
140
  * CONSTRAINTS — see frozen-path-guard.ts / prohibition-probe.ts). Used to
@@ -332,6 +332,13 @@ export async function runGatesForTask(ctxIn, deps, p) {
332
332
  if (deps.revert)
333
333
  await deps.revert(p.cwd);
334
334
  await rec(`enforce: fixes committed but re-verify FAILED (${(after.reason ?? 'now fails').slice(0, 200)}) — ${deps.revert ? 'REVERTED' : 'left in place (no revert available)'}`);
335
+ // Persist the FAIL as a durable defect (mx5 run 10 item 3). The
336
+ // revert restores the tree the ORIGINAL verify already blessed, so
337
+ // this re-verify caught a defect that verify's earlier PASS missed —
338
+ // erasing it with the enforce edits buried the terminal fault 8.5h
339
+ // before run end. The final gate re-checks/surfaces it (static-class
340
+ // auto-closes if a later task fixed the statics; else stays open).
341
+ await deps.recordEnforceRevertDebt?.(p.cwd, p.taskId, after.reason ?? 'enforce re-verify failed');
335
342
  active.ui.notify(`${p.tag}: guideline fixes regressed verification on "${p.title}" (${(after.reason ?? 'now fails').slice(0, 120)}) — ${deps.revert ? 'reverted them, kept the verified work' : 'left in place (no revert available)'}.`, 'warning');
336
343
  }
337
344
  else {
@@ -8,7 +8,8 @@ import { resolvePackage as defaultResolvePackage, ResolveError, detectTypesRedir
8
8
  import { retrieveChunks as defaultRetrieveChunks } from './docs-retrieve.js';
9
9
  import { npmVersionLookup as defaultNpmVersionLookup } from './npm-version.js';
10
10
  import { getPiInvocation } from '../shared/pi-invocation.js';
11
- import { CHILD_BASE_ARGS, runChild } from '../shared/child-process.js';
11
+ import { runChild } from '../shared/child-process.js';
12
+ import { childBaseArgs } from '../shared/child-extensions.js';
12
13
  import { parseChildOutput, isExcerptInContent, formatResultText as formatResultTextShared } from '../shared/child-output.js';
13
14
  const DEFAULT_LIMIT = 8;
14
15
  const DEFAULT_BUDGET = 24_000;
@@ -16,7 +17,7 @@ const NO_CACHE_HEAD = 25_000;
16
17
  const NO_CACHE_TAIL = 5_000;
17
18
  const NO_CACHE_TOTAL = NO_CACHE_HEAD + NO_CACHE_TAIL;
18
19
  const NO_CACHE_MARKER = '\n\n[...content continues, truncated...]\n\n';
19
- const CHILD_ARGS = [...CHILD_BASE_ARGS, '--no-tools'];
20
+ const childArgs = () => [...childBaseArgs(), '--no-tools'];
20
21
  export function extractParentPackage(moduleName) {
21
22
  if (moduleName.startsWith('@')) {
22
23
  const parts = moduleName.split('/');
@@ -423,7 +424,7 @@ export async function docsFocused(input) {
423
424
  const { pkg, chunks, hitCache, indexingMs } = rawResult;
424
425
  const concatenated = chunks.map(c => c.content).join('\n\n');
425
426
  const prompt = buildPrompt(pkg, input.query, concatenated);
426
- const invocation = getPiInvocation([...CHILD_ARGS], prompt);
427
+ const invocation = getPiInvocation(childArgs(), prompt);
427
428
  const child = await runChild(spawn, invocation, input.cwd, input.signal);
428
429
  const parsed = parseChildOutput(child.stdout);
429
430
  const excerptVerified = parsed.excerpt ? isExcerptInContent(parsed.excerpt, concatenated) : undefined;
@@ -1,13 +1,14 @@
1
1
  import { spawn as defaultSpawn } from 'node:child_process';
2
2
  import { fetchAndClean as defaultFetchAndClean } from './html-clean.js';
3
3
  import { getPiInvocation } from '../shared/pi-invocation.js';
4
- import { CHILD_BASE_ARGS, runChild } from '../shared/child-process.js';
4
+ import { runChild } from '../shared/child-process.js';
5
+ import { childBaseArgs } from '../shared/child-extensions.js';
5
6
  import { parseChildOutput, isExcerptInContent, formatResultText as formatResultTextShared } from '../shared/child-output.js';
6
7
  const CONTENT_BUDGET = 30_000;
7
8
  const HEAD_CHARS = 25_000;
8
9
  const TAIL_CHARS = 5_000;
9
10
  const TRUNCATION_MARKER = '\n\n[...page continues, truncated...]\n\n';
10
- const CHILD_ARGS = [...CHILD_BASE_ARGS, '--no-tools'];
11
+ const childArgs = () => [...childBaseArgs(), '--no-tools'];
11
12
  export async function fetchRaw(input) {
12
13
  const fetchAndCleanFn = input.fetchAndClean ?? defaultFetchAndClean;
13
14
  const cleaned = await fetchAndCleanFn(input.url, { signal: input.signal });
@@ -24,7 +25,7 @@ export async function fetchFocused(input) {
24
25
  title: cleaned.title,
25
26
  content: truncated
26
27
  });
27
- const invocation = getPiInvocation([...CHILD_ARGS], prompt);
28
+ const invocation = getPiInvocation(childArgs(), prompt);
28
29
  const childResult = await runChild(spawnFn, invocation, input.cwd, input.signal);
29
30
  if (childResult.aborted) {
30
31
  return {
@@ -7,10 +7,17 @@ export interface RunWorkerInput {
7
7
  spawn?: SpawnFn;
8
8
  /** Comma-separated tool whitelist passed to `pi --tools`. Defaults to read,grep,find,ls. */
9
9
  tools?: string;
10
- /** Extension entry-point paths to load via `-e <path>` before CHILD_BASE_ARGS. */
10
+ /** Internal extension entry-point paths to load via `-e <path>` (see childBaseArgs). */
11
11
  extensions?: string[];
12
12
  /** Called for each tool execution start and text-writing event inside the worker. */
13
13
  onLine?: (line: string) => void;
14
+ /** Called when a tool call FINISHES, with its (truncatable) result — lets a caller
15
+ * log tool OUTPUTS, not just the command (mx5 run 10 item 6). */
16
+ onToolResult?: (result: {
17
+ name: string;
18
+ isError: boolean;
19
+ text: string;
20
+ }) => void;
14
21
  /**
15
22
  * Called for each context_usage snapshot the child emits (same `--mode json`
16
23
  * stream the phase children parse). Lets a caller's status widget show the
@@ -1,5 +1,6 @@
1
1
  import { getPiInvocation } from '../shared/pi-invocation.js';
2
- import { CHILD_BASE_ARGS, runChildDefault } from '../shared/child-process.js';
2
+ import { runChildDefault } from '../shared/child-process.js';
3
+ import { childBaseArgs } from '../shared/child-extensions.js';
3
4
  import { LoopDetector } from '../task/loop-detector.js';
4
5
  import { LOOP_WINDOW, LOOP_THRESHOLD, MAX_LOOP_RESTARTS, formatLoopHint } from '../task/child-runner.js';
5
6
  import { detectLeakedToolCall, leakedToolCallHint, MAX_LEAK_RETRIES } from '../shared/leaked-tool-call.js';
@@ -70,8 +71,7 @@ function workerTimeout(external, ms) {
70
71
  }
71
72
  export async function runWorker(input) {
72
73
  const tools = input.tools ?? DEFAULT_TOOLS;
73
- const extensionArgs = (input.extensions ?? []).flatMap(e => ['-e', e]);
74
- const baseArgs = [...extensionArgs, ...CHILD_BASE_ARGS, '--mode', 'json', '--tools', tools];
74
+ const baseArgs = [...childBaseArgs(input.extensions ?? []), '--mode', 'json', '--tools', tools];
75
75
  const timeoutMs = input.timeoutMs ?? RESEARCH_WORKER_TIMEOUT_MS;
76
76
  let hint = null;
77
77
  // Loop-kill and timeout share one restart budget, mirroring
@@ -124,6 +124,7 @@ export async function runWorker(input) {
124
124
  return hit;
125
125
  },
126
126
  onLine: input.onLine,
127
+ onToolResult: input.onToolResult,
127
128
  onContextUsage: input.onContextUsage
128
129
  }, input.spawn);
129
130
  }
@@ -4,13 +4,14 @@ import { openCache as defaultOpenCache } from './docs-cache.js';
4
4
  import { retrieveChunks as defaultRetrieveChunks } from './docs-retrieve.js';
5
5
  import { docsRaw, formatResultText, buildPrompt, buildVersionBanner } from './docs-core.js';
6
6
  import { formatNpmVersionSection } from './npm-version.js';
7
- import { runChild, CHILD_BASE_ARGS } from '../shared/child-process.js';
7
+ import { runChild } from '../shared/child-process.js';
8
+ import { childBaseArgs } from '../shared/child-extensions.js';
8
9
  import { parseChildOutput, isExcerptInContent } from '../shared/child-output.js';
9
10
  import { getPiInvocation } from '../shared/pi-invocation.js';
10
11
  import { formatChildFailure, makeWorkerTool } from './shared.js';
11
12
  import { normalizeQuery } from './research-cache.js';
12
13
  import { projectDocsRaw, buildProjectPrompt } from './docs-project.js';
13
- const CHILD_ARGS = [...CHILD_BASE_ARGS, '--no-tools'];
14
+ const childArgs = () => [...childBaseArgs(), '--no-tools'];
14
15
  const RENDER_QUERY_MAX = 100;
15
16
  const Params = Type.Object({
16
17
  module: Type.String({
@@ -105,7 +106,7 @@ export function registerPiWorkerDocs(pi, internals = {}) {
105
106
  };
106
107
  const concatenated = chunks.map(c => c.content).join('\n\n');
107
108
  const prompt = buildProjectPrompt(projectName, params.query, concatenated);
108
- const invocation = getPiInvocation([...CHILD_ARGS], prompt);
109
+ const invocation = getPiInvocation(childArgs(), prompt);
109
110
  const child = await runChild(spawn, invocation, ctx.cwd, signal);
110
111
  const failure = formatChildFailure(child, 'Project docs lookup aborted.');
111
112
  if (failure !== null) {
@@ -207,7 +208,7 @@ export function registerPiWorkerDocs(pi, internals = {}) {
207
208
  };
208
209
  const concatenated = chunks.map(c => c.content).join('\n\n');
209
210
  const prompt = buildPrompt(pkg, params.query, concatenated);
210
- const invocation = getPiInvocation([...CHILD_ARGS], prompt);
211
+ const invocation = getPiInvocation(childArgs(), prompt);
211
212
  const child = await runChild(spawn, invocation, ctx.cwd, signal);
212
213
  const failure = formatChildFailure(child, 'Docs lookup aborted.');
213
214
  if (failure !== null) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mjasnikovs/pi-task",
3
- "version": "0.18.12",
3
+ "version": "0.18.14",
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",