@mjasnikovs/pi-task 0.18.21 → 0.18.23

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.
@@ -1,3 +1,4 @@
1
+ import { type CrossTaskDeletion } from './task-provenance.js';
1
2
  /**
2
3
  * The verification child gets exactly two tools: `read` and `bash`.
3
4
  *
@@ -27,6 +28,12 @@ export interface VerifyOutcome {
27
28
  * AUTOFIX re-run, which cannot provision a missing tool. Only meaningful when
28
29
  * ok === false. */
29
30
  unobserved?: boolean;
31
+ /** The deterministic cross-task deletion findings (see task-provenance.ts) that
32
+ * were live when this verdict was produced: sibling tasks' committed
33
+ * deliverables this task's diff deletes. Carried on a FAIL so the gate loop
34
+ * can record each as a durable debt if the user ACCEPTs anyway — the deletion
35
+ * then ships in the next commit and the final gate must re-check it. */
36
+ crossTaskDeletions?: CrossTaskDeletion[];
30
37
  }
31
38
  /**
32
39
  * Slice the delivered spec (GOAL / CONSTRAINTS / ACCEPTANCE / VERIFY) out of a
@@ -61,7 +68,7 @@ export declare function extractSpecForVerification(taskBody: string): string | n
61
68
  * Guard: honest-clean fixture (prohibition in spec, probe silent) 5/5 PASS — no
62
69
  * paranoia. Reverted-violation ≡ clean at the diff level (no entry → no finding).
63
70
  */
64
- export declare function buildVerifyPrompt(spec: string, probeFindings?: string[], envNotes?: string, prohibitionFindings?: string[], skipEscapeFindings?: string[], contracts?: string, testAssemblyFindings?: string[], probeGamingFindings?: string[]): string;
71
+ export declare function buildVerifyPrompt(spec: string, probeFindings?: string[], envNotes?: string, prohibitionFindings?: string[], skipEscapeFindings?: string[], contracts?: string, testAssemblyFindings?: string[], probeGamingFindings?: string[], crossTaskDeletionFindings?: string[]): string;
65
72
  /**
66
73
  * Parse the child's verdict. Scans for the LAST `WORK-VERIFIED: PASS|FAIL|UNOBSERVED`
67
74
  * marker (the model discusses before concluding, and bash output may echo the word
@@ -130,6 +137,15 @@ export interface VerificationDeps {
130
137
  * underlying requirement is genuinely met rather than trusting the green check.
131
138
  * Pure diff-text analysis; ABSENT or empty → no probe block. */
132
139
  probeGamingProbe?: () => Promise<string[]>;
140
+ /**
141
+ * DETERMINISTIC cross-task deletion probe (see task-provenance.ts, mx5 run 12
142
+ * PROMPT 2): tracked files the task's diff DELETES whose introducing task (git
143
+ * provenance) differs from the current task — a sibling's committed deliverable
144
+ * destroyed, typically to green a check. Injected as prompt findings under rule
145
+ * 4d (MANDATORY + verdict-gating, the A/B-proven shape — buried rules score
146
+ * 0/5), and carried structurally on a FAIL outcome so an ACCEPT records each as
147
+ * a durable debt. ABSENT or empty → no block. */
148
+ crossTaskDeletionProbe?: () => Promise<CrossTaskDeletion[]>;
133
149
  /**
134
150
  * Result of the git-state guard for the MOST RECENT runChild call (see
135
151
  * git-state-guard.ts): did the child mutate repo state (stash/checkout/file
@@ -71,6 +71,7 @@ import { USER_CANCELLED } from './child-runner.js';
71
71
  import { buildEnvNotesBlock, ENV_NOTE_EMIT_INSTRUCTION, extractEnvNotes } from './env-notes.js';
72
72
  import { buildContractsVerifyBlock } from './contracts.js';
73
73
  import { findSkipEscapes, skipEscapeVerifyFindings } from './skip-escape.js';
74
+ import { crossTaskDeletionVerifyFindings } from './task-provenance.js';
74
75
  /**
75
76
  * The verification child gets exactly two tools: `read` and `bash`.
76
77
  *
@@ -139,7 +140,7 @@ export function extractSpecForVerification(taskBody) {
139
140
  * Guard: honest-clean fixture (prohibition in spec, probe silent) 5/5 PASS — no
140
141
  * paranoia. Reverted-violation ≡ clean at the diff level (no entry → no finding).
141
142
  */
142
- export function buildVerifyPrompt(spec, probeFindings, envNotes, prohibitionFindings, skipEscapeFindings, contracts, testAssemblyFindings, probeGamingFindings) {
143
+ export function buildVerifyPrompt(spec, probeFindings, envNotes, prohibitionFindings, skipEscapeFindings, contracts, testAssemblyFindings, probeGamingFindings, crossTaskDeletionFindings) {
143
144
  const probeBlock = probeFindings && probeFindings.length > 0 ?
144
145
  [
145
146
  'SELF-VERIFICATION NOTICE (deterministic, computed by the orchestrator from the diff):',
@@ -166,6 +167,22 @@ export function buildVerifyPrompt(spec, probeFindings, envNotes, prohibitionFind
166
167
  ''
167
168
  ]
168
169
  : [];
170
+ const crossTaskDeletionBlock = crossTaskDeletionFindings && crossTaskDeletionFindings.length > 0 ?
171
+ [
172
+ 'CROSS-TASK DELETION NOTICE (deterministic, computed by the orchestrator from',
173
+ "git provenance over the task's diff): this task's work DELETED committed",
174
+ 'deliverable(s) that a DIFFERENT, already-completed task introduced:',
175
+ ...crossTaskDeletionFindings.map(f => `- ${f}`),
176
+ "A sibling task's verified, committed deliverable is not this task's to remove.",
177
+ 'Deleting the file a checker complains about is the cheapest way to turn a red',
178
+ 'check green — the finding vanishes with the file — and it destroys finished',
179
+ 'work (rule 4d). A green check suite on the shrunken tree is NOT a waiver.',
180
+ "Unless THIS task's spec explicitly REQUIRES removing exactly that file, the",
181
+ 'verdict is FAIL naming the deleted file and its owning task — even if every',
182
+ 'check passes and the deletion made them pass.',
183
+ ''
184
+ ]
185
+ : [];
169
186
  const probeGamingBlock = probeGamingFindings && probeGamingFindings.length > 0 ?
170
187
  [
171
188
  'CHECK-GAMING NOTICE (deterministic, computed by the orchestrator from the',
@@ -235,6 +252,7 @@ export function buildVerifyPrompt(spec, probeFindings, envNotes, prohibitionFind
235
252
  ...contractsBlock,
236
253
  ...probeBlock,
237
254
  ...prohibitionBlock,
255
+ ...crossTaskDeletionBlock,
238
256
  ...probeGamingBlock,
239
257
  ...skipEscapeBlock,
240
258
  ...testAssemblyBlock,
@@ -359,6 +377,17 @@ export function buildVerifyPrompt(spec, probeFindings, envNotes, prohibitionFind
359
377
  ' check, not implementing the behavior). If the requirement is genuinely unmet while',
360
378
  ' the check passes, the verdict is FAIL naming the gamed check and the real gap.',
361
379
  '',
380
+ "4d. A SIBLING TASK'S COMMITTED DELIVERABLE IS NOT THIS TASK'S TO DELETE — check the",
381
+ " task's own diff (git) for DELETED tracked files. A file that a DIFFERENT completed",
382
+ " task introduced and committed, deleted by this task's work, is finished work",
383
+ ' destroyed — usually to make a red check go green (the file the checker complains',
384
+ ' about simply disappears; that is quieting the messenger, rule 4c, not fixing the',
385
+ ' defect). A passing check suite on the shrunken tree is not a waiver. Only two',
386
+ " outcomes are not a FAIL: THIS task's spec explicitly requires that removal, or",
387
+ ' the deletion is a genuine relocation (the same file lives on elsewhere in the',
388
+ ' tree). Otherwise the verdict is FAIL naming the deleted file and the task that',
389
+ ' owns it.',
390
+ '',
362
391
  '5. The ONLY thing you may assume is already provided is a genuinely EXTERNAL running',
363
392
  ' service or network resource (a database server, an API host) that the project',
364
393
  ' documents as a prerequisite. Before you rely on that assumption, PROBE for the',
@@ -531,6 +560,18 @@ export async function runWorkVerification(deps) {
531
560
  probeGaming = [];
532
561
  }
533
562
  }
563
+ // Cross-task deletion findings feed the prompt (rule 4d) AND ride on a FAIL
564
+ // outcome (structured) so an ACCEPT can record them as durable debts. A probe
565
+ // failure must never block verification.
566
+ let crossDeletions = [];
567
+ if (deps.crossTaskDeletionProbe) {
568
+ try {
569
+ crossDeletions = await deps.crossTaskDeletionProbe();
570
+ }
571
+ catch {
572
+ crossDeletions = [];
573
+ }
574
+ }
534
575
  // Environment facts from earlier gate children (best-effort; a cache failure
535
576
  // must never block verification).
536
577
  let envNotes = '';
@@ -566,7 +607,7 @@ export async function runWorkVerification(deps) {
566
607
  for (let attempt = 1;; attempt++) {
567
608
  let text;
568
609
  try {
569
- text = await deps.runChild(VERIFY_TOOLS, buildVerifyPrompt(deps.spec, findings, envNotes, prohibitions, skipEscapes, contracts, testAssembly, probeGaming), deps.signal);
610
+ text = await deps.runChild(VERIFY_TOOLS, buildVerifyPrompt(deps.spec, findings, envNotes, prohibitions, skipEscapes, contracts, testAssembly, probeGaming, crossTaskDeletionVerifyFindings(crossDeletions)), deps.signal);
570
611
  }
571
612
  catch (err) {
572
613
  if (err instanceof Error && err.message === USER_CANCELLED)
@@ -603,15 +644,25 @@ export async function runWorkVerification(deps) {
603
644
  return { ok: true };
604
645
  if (verdict.detail === 'no verdict emitted' && attempt === 1)
605
646
  continue;
647
+ // Structured cross-task deletion findings ride on every FAIL outcome: if the
648
+ // human ACCEPTs the failing artifact, the deletions ship in the next commit
649
+ // and the gate loop must record each as a durable debt (see task-gates.ts).
650
+ const deletions = crossDeletions.length > 0 ? { crossTaskDeletions: crossDeletions } : {};
606
651
  // UNOBSERVED (rule 5c): a spec-required behavioral check could not run because
607
652
  // its tooling is absent. Block like any FAIL, but flag it so the gate hands it
608
653
  // straight to the human — re-running the impl turn cannot install a missing tool.
609
654
  if (verdict.unobserved) {
610
- return { ok: false, unobserved: true, reason: `work unobserved: ${verdict.detail}` };
655
+ return {
656
+ ok: false,
657
+ unobserved: true,
658
+ reason: `work unobserved: ${verdict.detail}`,
659
+ ...deletions
660
+ };
611
661
  }
612
662
  return {
613
663
  ok: false,
614
- reason: `work did not verify: ${verdict.detail}${verdict.detail === 'no verdict emitted' ? ' (after verify retry)' : ''}`
664
+ reason: `work did not verify: ${verdict.detail}${verdict.detail === 'no verdict emitted' ? ' (after verify retry)' : ''}`,
665
+ ...deletions
615
666
  };
616
667
  }
617
668
  }
@@ -36,6 +36,16 @@ export interface TreeChangeSummary {
36
36
  * without a repo.
37
37
  */
38
38
  export declare function parseTreeChanges(porcelain: string): TreeChangeSummary;
39
+ /**
40
+ * Parse `git diff --name-status` output (tab-separated: `M\tpath`, `A\tpath`,
41
+ * `D\tpath`, `R100\told\tnew`, `C75\told\tnew`) into the same change summary. Used
42
+ * where the working tree is already clean and the last COMMIT's diff is the record
43
+ * of the task's changes (the post-enforce re-verify fallback). A rename entry
44
+ * contributes its source to `deleted` and its target to `added`, matching
45
+ * parseTreeChanges so the relocation allowance below reads both shapes identically;
46
+ * a copy's source still exists, so only its target counts (as added).
47
+ */
48
+ export declare function parseNameStatusChanges(nameStatus: string): TreeChangeSummary;
39
49
  /**
40
50
  * The deletions a fix pass may NOT make: every deleted tracked file whose name does
41
51
  * not reappear among the added files (a relocation keeps the file, under the same
@@ -75,6 +75,46 @@ export function parseTreeChanges(porcelain) {
75
75
  }
76
76
  return { modified: [...modified], deleted: [...deleted], added: [...added] };
77
77
  }
78
+ /**
79
+ * Parse `git diff --name-status` output (tab-separated: `M\tpath`, `A\tpath`,
80
+ * `D\tpath`, `R100\told\tnew`, `C75\told\tnew`) into the same change summary. Used
81
+ * where the working tree is already clean and the last COMMIT's diff is the record
82
+ * of the task's changes (the post-enforce re-verify fallback). A rename entry
83
+ * contributes its source to `deleted` and its target to `added`, matching
84
+ * parseTreeChanges so the relocation allowance below reads both shapes identically;
85
+ * a copy's source still exists, so only its target counts (as added).
86
+ */
87
+ export function parseNameStatusChanges(nameStatus) {
88
+ const modified = new Set();
89
+ const deleted = new Set();
90
+ const added = new Set();
91
+ for (const raw of nameStatus.split('\n')) {
92
+ const parts = raw.split('\t').map(s => s.trim());
93
+ const code = parts[0] ?? '';
94
+ if (code.length === 0 || !parts[1])
95
+ continue;
96
+ if (code.startsWith('R')) {
97
+ deleted.add(parts[1]);
98
+ if (parts[2])
99
+ added.add(parts[2]);
100
+ continue;
101
+ }
102
+ if (code.startsWith('C')) {
103
+ added.add(parts[2] ?? parts[1]);
104
+ continue;
105
+ }
106
+ if (code.startsWith('D')) {
107
+ deleted.add(parts[1]);
108
+ continue;
109
+ }
110
+ if (code.startsWith('A')) {
111
+ added.add(parts[1]);
112
+ continue;
113
+ }
114
+ modified.add(parts[1]);
115
+ }
116
+ return { modified: [...modified], deleted: [...deleted], added: [...added] };
117
+ }
78
118
  const basename = (p) => {
79
119
  const i = p.lastIndexOf('/');
80
120
  return i === -1 ? p : p.slice(i + 1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mjasnikovs/pi-task",
3
- "version": "0.18.21",
3
+ "version": "0.18.23",
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",