@mjasnikovs/pi-task 0.42.2 → 0.42.4

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.
@@ -42,7 +42,8 @@ import { buildEnvNotesBlock, ENV_NOTE_EMIT_INSTRUCTION, extractEnvNotes } from '
42
42
  import { buildContractsVerifyBlock } from './contracts.js';
43
43
  import { findSkipEscapes, skipEscapeVerifyFindings } from './skip-escape.js';
44
44
  import { crossTaskDeletionVerifyFindings } from './task-provenance.js';
45
- import { classifyHealthDelta, inheritedHealthFindings } from './health-baseline.js';
45
+ import { classifyHealthDelta, inheritedHealthFindings, regressedCommands } from './health-baseline.js';
46
+ import { describeHealthFailures } from './repo-health-check.js';
46
47
  import { parseSpec, sliceSpecSection } from './spec-model.js';
47
48
  import { qaKindsFromRecord } from './qa-transcript.js';
48
49
  import { annotateConstraints, anyBinding, renderConstraintPolicy } from './constraint-policy.js';
@@ -72,6 +73,7 @@ const VERIFY_TOOLS = 'read,bash';
72
73
  export const VERIFY_FAIL_PREFIX = {
73
74
  'repo-health': 'repo health:',
74
75
  'static-checks': 'static checks:',
76
+ 'test-suite': 'test suite:',
75
77
  unobserved: 'work unobserved:',
76
78
  'model-verdict': 'work did not verify:',
77
79
  'harness-fault': 'verification pass could not run:'
@@ -103,6 +105,14 @@ export function failClassOfReason(reason) {
103
105
  export function isStaticClass(cls) {
104
106
  return cls === 'repo-health' || cls === 'static-checks';
105
107
  }
108
+ /** The class a red health result is minted under: the suite's when any test is red. */
109
+ function healthFailClass(failing) {
110
+ return failing.some(c => c.kind === 'test') ? 'test-suite' : 'repo-health';
111
+ }
112
+ /** Does this class name the deterministic whole-repo check, suite included? */
113
+ export function isHealthClass(cls) {
114
+ return isStaticClass(cls) || cls === 'test-suite';
115
+ }
106
116
  /**
107
117
  * The delivered spec's TEXT, for the children that must read its prose verbatim.
108
118
  * The slicing itself lives in spec-model.ts beside the parser, so "the spec
@@ -182,8 +192,8 @@ const PROBE_ADAPTERS = [
182
192
  ]
183
193
  }),
184
194
  /**
185
- * PRE-EXISTING repo health (see health-baseline.ts): static checks that were
186
- * ALREADY failing, the same way, before this task started. They used to be an
195
+ * PRE-EXISTING repo health (see health-baseline.ts): checks that were ALREADY
196
+ * failing, with the same exit code, before this task started. They used to be an
187
197
  * absolute FAIL that short-circuited the whole pass, so the task answered for
188
198
  * a sibling's defect and its own probe findings were never computed. As a row
189
199
  * they are stated to the child instead: judge this task's work, and do not
@@ -197,7 +207,7 @@ const PROBE_ADAPTERS = [
197
207
  ruleId: '4h',
198
208
  block: findings => [
199
209
  'INHERITED REPO-HEALTH NOTICE (deterministic, computed by the orchestrator by',
200
- "re-running the project's own static checks and comparing them against the",
210
+ "re-running the project's own checks and comparing their exit codes against the",
201
211
  'baseline taken before this task started): these checks were ALREADY failing,',
202
212
  'with the same exit code, before any of this work existed:',
203
213
  ...findings.map(f => `- ${f}`),
@@ -208,13 +218,16 @@ const PROBE_ADAPTERS = [
208
218
  ],
209
219
  rule: [
210
220
  "4h. AN INHERITED RED CHECK IS NOT THIS TASK'S FAIL, AND NOT ITS PROOF — when the",
211
- ' INHERITED REPO-HEALTH NOTICE above names a check, that check failed identically',
221
+ ' INHERITED REPO-HEALTH NOTICE above names a check, that check exited the same way',
212
222
  ' before this task ran. Do NOT fail this work for it: the defect belongs to',
213
223
  ' whatever put it there, it is recorded as durable debt, and the run-end gate',
214
224
  ' re-checks it. Do NOT lean on it either — a command that was already exiting',
215
225
  " non-zero tells you nothing about this task's behavior, so verify that behavior",
216
226
  ' another way. A check that is failing DIFFERENTLY, or one absent from the notice,',
217
- " is this task's to answer for in the ordinary way."
227
+ " is this task's to answer for in the ordinary way. A TEST command is the",
228
+ ' exception to the exit code: it exits the same way for one failing test or',
229
+ " fifty. Run it, and a test that fails because of THIS work is this task's FAIL,",
230
+ ' whatever the spec says about it.'
218
231
  ]
219
232
  }),
220
233
  /**
@@ -907,16 +920,33 @@ export async function runWorkVerification(deps) {
907
920
  const h = await deps.repoHealth();
908
921
  if (!h.ok) {
909
922
  const baseline = deps.healthBaseline ? await deps.healthBaseline() : null;
910
- if (classifyHealthDelta(baseline?.outcome ?? null, h) === 'regressed') {
923
+ const before = baseline?.outcome ?? null;
924
+ if (classifyHealthDelta(before, h) === 'regressed') {
925
+ // Named after what REGRESSED, not after whatever failed first: a lint
926
+ // red on arrival would otherwise stand in for the suite this task broke.
927
+ const regressed = regressedCommands(before, h);
928
+ if (regressed.length === 0) {
929
+ return {
930
+ ok: false,
931
+ failClass: 'repo-health',
932
+ reason: `repo health: ${h.reason}`,
933
+ health: h
934
+ };
935
+ }
936
+ const failClass = healthFailClass(regressed);
911
937
  return {
912
938
  ok: false,
913
- failClass: 'repo-health',
914
- reason: `repo health: ${h.reason}`,
915
- health: h
939
+ failClass,
940
+ reason: `${VERIFY_FAIL_PREFIX[failClass]} ${describeHealthFailures(regressed)}`,
941
+ health: { ...h, commands: regressed, output: regressed[0].output ?? h.output }
916
942
  };
917
943
  }
918
944
  pre.repoHealth = inheritedHealthFindings(h);
919
- inheritedHealth = `repo health: ${h.reason} already failing before this task`;
945
+ const failing = h.commands?.filter(c => c.outcome === 'fail') ?? [];
946
+ inheritedHealth =
947
+ failing.length > 0 ?
948
+ `${VERIFY_FAIL_PREFIX[healthFailClass(failing)]} ${describeHealthFailures(failing)} — already failing before this task`
949
+ : `repo health: ${h.reason} — already failing before this task`;
920
950
  }
921
951
  }
922
952
  const inherited = inheritedHealth === undefined ? {} : { inheritedHealth };
@@ -36,6 +36,10 @@ export type YoloPick = {
36
36
  * demotion — an answer proven to name an unverified API identifier. Auto-accepting
37
37
  * that would re-promote exactly the invention the demotion exists to stop, so a
38
38
  * machine may never take it; a human still can.
39
+ *
40
+ * An option that defers a breakage to an owner nobody is, is passed over here
41
+ * rather than at each caller: the clarify generator, the plan review and an
42
+ * UNKNOWN's own suggestion reach a machine with no guard in front of them.
39
43
  */
40
44
  export declare function yoloPickAnswer(enabled: boolean, opts: {
41
45
  suggested?: string;
@@ -44,11 +48,12 @@ export declare function yoloPickAnswer(enabled: boolean, opts: {
44
48
  }): YoloPick;
45
49
  /**
46
50
  * The same policy expressed over an {@link AutoAnswer}, for the grill site. Of the
47
- * four `reason` tags an unknown can carry — `api-synthesis`, `integration`,
48
- * `threw`, `model-unknown` — only ANTI-SYNTHESIS is unsafe. The other three carry
49
- * an ordinary best-effort recommendation, which is precisely what a human would be
50
- * shown as the green card. The variants are told apart by that tag, never by
51
- * pattern-matching the answer text.
51
+ * five `reason` tags an unknown can carry — `api-synthesis`, `deferred-breakage`,
52
+ * `integration`, `threw`, `model-unknown` — `api-synthesis` is unsafe by its tag:
53
+ * the answer names an API nobody verified. A `deferred-breakage` suggestion is the
54
+ * deferral itself, which `yoloPickAnswer` passes over for every site. The other
55
+ * three carry an ordinary best-effort recommendation, which is precisely what a
56
+ * human would be shown as the green card.
52
57
  */
53
58
  export declare function yoloPickAutoAnswer(enabled: boolean, auto: AutoAnswer): YoloPick;
54
59
  /**
package/dist/task/yolo.js CHANGED
@@ -29,6 +29,7 @@
29
29
  * the config.
30
30
  */
31
31
  import { getConfig } from '../config/config.js';
32
+ import { defersBreakage } from './deferred-breakage.js';
32
33
  /**
33
34
  * Visible provenance marker on the artifacts an auto-pick writes: gate trail lines
34
35
  * and the task file's Q&A record (qa-transcript.ts `QA_PROVENANCE`). A later audit
@@ -53,6 +54,10 @@ export function isYoloMode() {
53
54
  * demotion — an answer proven to name an unverified API identifier. Auto-accepting
54
55
  * that would re-promote exactly the invention the demotion exists to stop, so a
55
56
  * machine may never take it; a human still can.
57
+ *
58
+ * An option that defers a breakage to an owner nobody is, is passed over here
59
+ * rather than at each caller: the clarify generator, the plan review and an
60
+ * UNKNOWN's own suggestion reach a machine with no guard in front of them.
56
61
  */
57
62
  export function yoloPickAnswer(enabled, opts) {
58
63
  if (!enabled)
@@ -60,19 +65,26 @@ export function yoloPickAnswer(enabled, opts) {
60
65
  if (opts.unsafe !== undefined && opts.unsafe.length > 0) {
61
66
  return { kind: 'skip', note: opts.unsafe };
62
67
  }
63
- const pick = opts.suggested ?? opts.alt;
64
- if (pick === undefined || pick.trim().length === 0) {
68
+ const offered = [opts.suggested, opts.alt].filter((o) => o !== undefined && o.trim().length > 0);
69
+ if (offered.length === 0)
65
70
  return { kind: 'skip', note: 'no recommended option to take' };
71
+ const pick = offered.find(o => !defersBreakage(o));
72
+ if (pick === undefined) {
73
+ return {
74
+ kind: 'skip',
75
+ note: 'the recommended answer leaves a test or build failing for an owner that does not exist — needs a human'
76
+ };
66
77
  }
67
78
  return { kind: 'answer', answer: pick };
68
79
  }
69
80
  /**
70
81
  * The same policy expressed over an {@link AutoAnswer}, for the grill site. Of the
71
- * four `reason` tags an unknown can carry — `api-synthesis`, `integration`,
72
- * `threw`, `model-unknown` — only ANTI-SYNTHESIS is unsafe. The other three carry
73
- * an ordinary best-effort recommendation, which is precisely what a human would be
74
- * shown as the green card. The variants are told apart by that tag, never by
75
- * pattern-matching the answer text.
82
+ * five `reason` tags an unknown can carry — `api-synthesis`, `deferred-breakage`,
83
+ * `integration`, `threw`, `model-unknown` — `api-synthesis` is unsafe by its tag:
84
+ * the answer names an API nobody verified. A `deferred-breakage` suggestion is the
85
+ * deferral itself, which `yoloPickAnswer` passes over for every site. The other
86
+ * three carry an ordinary best-effort recommendation, which is precisely what a
87
+ * human would be shown as the green card.
76
88
  */
77
89
  export function yoloPickAutoAnswer(enabled, auto) {
78
90
  if (!enabled)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mjasnikovs/pi-task",
3
- "version": "0.42.2",
3
+ "version": "0.42.4",
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",