@mjasnikovs/pi-task 0.42.5 → 0.42.6
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.
- package/dist/task/deferred-breakage.js +33 -21
- package/dist/task/health-baseline.d.ts +4 -1
- package/dist/task/health-baseline.js +6 -3
- package/dist/task/health-repair.d.ts +6 -2
- package/dist/task/health-repair.js +7 -2
- package/dist/task/repo-health-check.d.ts +2 -0
- package/dist/task/repo-health-check.js +5 -1
- package/dist/task/task-gates.js +9 -4
- package/dist/task/verify-work.d.ts +4 -1
- package/dist/task/verify-work.js +5 -1
- package/package.json +1 -1
|
@@ -24,11 +24,11 @@ const FAILURE = /\b(?:fail\w*|red|broken|breaks?|breakage|errors?)\b/i;
|
|
|
24
24
|
/** Before a phrase in its clause: the phrase is rejected. */
|
|
25
25
|
const NOT_A_DECISION = /\b(?:not|never|no|don't|do not|doesn't|does not|rather than|instead of|isn't|is not|without|avoid|avoiding)\b/i;
|
|
26
26
|
/**
|
|
27
|
-
* A modal cancels a phrase only where the
|
|
28
|
-
* weigh: "IF NOT EXISTS would still leave the test
|
|
29
|
-
* rejected option does. A bare hedge does not — "I
|
|
30
|
-
* issue" is the decision, and treating every modal as
|
|
31
|
-
* be rephrased away.
|
|
27
|
+
* A modal cancels a phrase only where the condition's own half of the sentence
|
|
28
|
+
* poses an option for it to weigh: "IF NOT EXISTS would still leave the test
|
|
29
|
+
* failing" describes what a rejected option does. A bare hedge does not — "I
|
|
30
|
+
* would flag it as a known issue" is the decision, and treating every modal as
|
|
31
|
+
* hypothetical let the guard be rephrased away.
|
|
32
32
|
*/
|
|
33
33
|
const MODAL = /\b(?:would|could|might)\b/i;
|
|
34
34
|
const HYPOTHETICAL = /\b(?:if|unless|either|whether|option|alternative|otherwise)\b/i;
|
|
@@ -54,7 +54,13 @@ const PHRASES = [
|
|
|
54
54
|
// Handing the work to an unnamed someone is the deferral itself, whatever the
|
|
55
55
|
// clause is about; bare `whoever` below still needs a check to be one.
|
|
56
56
|
{ re: /\bwhoever\s+(?:owns|revisits|maintains|touches)\b/i, needs: 'alone' },
|
|
57
|
-
|
|
57
|
+
// Ownership handed to a ROLE is handed to nobody. Handed to a named team it is
|
|
58
|
+
// handed to someone, so it counts only where the clause is about a check.
|
|
59
|
+
{
|
|
60
|
+
re: /\bownership\s+(?:belongs|lies|rests)\s+(?:to|with)\s+(?:whoever|someone|somebody|another\b|a\s+later\b|the\s+(?:\w+\s+)?(?:owners?|maintainers?)\b)/i,
|
|
61
|
+
needs: 'alone'
|
|
62
|
+
},
|
|
63
|
+
{ re: /\bownership\s+(?:belongs|lies|rests)\s+(?:to|with)\b/i, needs: 'check' },
|
|
58
64
|
{ re: /\bwhoever\b/i, needs: 'check' },
|
|
59
65
|
{ re: /\bowned\s+(?:by|follow[- ]?up)\b/i, needs: 'check' },
|
|
60
66
|
{ re: /\bleft\s+for\b/i, needs: 'check' },
|
|
@@ -94,21 +100,27 @@ function clauses(sentence) {
|
|
|
94
100
|
export function defersBreakage(answer) {
|
|
95
101
|
for (const sentence of prose(answer).split(SENTENCE_BOUNDARY)) {
|
|
96
102
|
const sentenceBreaks = aboutACheck(sentence) && FAILURE.test(sentence);
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
103
|
+
// A condition governs its own half of a semicolon. It joins two independent
|
|
104
|
+
// clauses, so "the test fails only if X; I would flag it as known" states a
|
|
105
|
+
// condition and then decides — the decision is not one of X's options.
|
|
106
|
+
for (const half of sentence.split(';')) {
|
|
107
|
+
const weighsOptions = HYPOTHETICAL.test(half);
|
|
108
|
+
for (const clause of clauses(half)) {
|
|
109
|
+
for (const { re, needs } of PHRASES) {
|
|
110
|
+
const hit = re.exec(clause);
|
|
111
|
+
if (!hit)
|
|
112
|
+
continue;
|
|
113
|
+
const before = clause.slice(0, hit.index);
|
|
114
|
+
if (NOT_A_DECISION.test(before))
|
|
115
|
+
continue;
|
|
116
|
+
if (MODAL.test(before) && weighsOptions)
|
|
117
|
+
continue;
|
|
118
|
+
if (needs === 'check' && !aboutACheck(clause))
|
|
119
|
+
continue;
|
|
120
|
+
if (needs === 'breakage' && !sentenceBreaks)
|
|
121
|
+
continue;
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
112
124
|
}
|
|
113
125
|
}
|
|
114
126
|
}
|
|
@@ -39,13 +39,16 @@ export type HealthDelta = 'clean' | 'regressed' | 'pre-existing';
|
|
|
39
39
|
*/
|
|
40
40
|
export declare function classifyHealthDelta(baseline: HealthSignal | null, after: HealthSignal): HealthDelta;
|
|
41
41
|
/**
|
|
42
|
-
* Test commands the baseline
|
|
42
|
+
* Test commands the baseline RAN that now find no tests to run.
|
|
43
43
|
*
|
|
44
44
|
* A runner that found nothing observed nothing, which is a gap — in isolation. A
|
|
45
45
|
* task that deleted the test directory, renamed it, or broke the config's glob
|
|
46
46
|
* leaves the same gap, and the check reports the repo healthy because a gap never
|
|
47
47
|
* fails. Against a baseline that ran the suite, the suite is gone: this task's
|
|
48
48
|
* regression, and the largest one it can hide behind a green.
|
|
49
|
+
*
|
|
50
|
+
* Ran, not passed. A baseline that ran the suite RED ran it, and deleting a red
|
|
51
|
+
* suite is the same move with a larger payoff: the whole check turns green.
|
|
49
52
|
*/
|
|
50
53
|
export declare function vanishedSuites(baseline: HealthSignal | null, after: HealthSignal): HealthCommandResult[];
|
|
51
54
|
/** The failing commands the baseline did not have failing the same way — what a
|
|
@@ -58,19 +58,22 @@ export function classifyHealthDelta(baseline, after) {
|
|
|
58
58
|
}
|
|
59
59
|
const failureKey = (c) => JSON.stringify([c.cmd, c.exitCode]);
|
|
60
60
|
/**
|
|
61
|
-
* Test commands the baseline
|
|
61
|
+
* Test commands the baseline RAN that now find no tests to run.
|
|
62
62
|
*
|
|
63
63
|
* A runner that found nothing observed nothing, which is a gap — in isolation. A
|
|
64
64
|
* task that deleted the test directory, renamed it, or broke the config's glob
|
|
65
65
|
* leaves the same gap, and the check reports the repo healthy because a gap never
|
|
66
66
|
* fails. Against a baseline that ran the suite, the suite is gone: this task's
|
|
67
67
|
* regression, and the largest one it can hide behind a green.
|
|
68
|
+
*
|
|
69
|
+
* Ran, not passed. A baseline that ran the suite RED ran it, and deleting a red
|
|
70
|
+
* suite is the same move with a larger payoff: the whole check turns green.
|
|
68
71
|
*/
|
|
69
72
|
export function vanishedSuites(baseline, after) {
|
|
70
73
|
if (!baseline)
|
|
71
74
|
return [];
|
|
72
|
-
const
|
|
73
|
-
return (after.commands ?? []).filter(c => c.outcome === 'skip' && c.gap === 'empty-suite' &&
|
|
75
|
+
const ran = new Set((baseline.commands ?? []).filter(c => c.outcome !== 'skip').map(c => c.cmd));
|
|
76
|
+
return (after.commands ?? []).filter(c => c.outcome === 'skip' && c.gap === 'empty-suite' && ran.has(c.cmd));
|
|
74
77
|
}
|
|
75
78
|
/** The failing commands the baseline did not have failing the same way — what a
|
|
76
79
|
* `regressed` verdict is about. Every failing command when there is no baseline. */
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* because its script is a placeholder `exit 1`, and no repair task can fix either.
|
|
23
23
|
*/
|
|
24
24
|
import type { HealthSignal } from './health-baseline.js';
|
|
25
|
-
import type
|
|
25
|
+
import { type HealthCommandResult } from './repo-health-check.js';
|
|
26
26
|
/** The failing check, and what its output named. */
|
|
27
27
|
export interface HealthRed {
|
|
28
28
|
command: string;
|
|
@@ -36,9 +36,13 @@ export interface HealthRedOwners {
|
|
|
36
36
|
owners: string[];
|
|
37
37
|
}
|
|
38
38
|
/**
|
|
39
|
-
* What a red health result is about: its first
|
|
39
|
+
* What a red health result is about: its first red command that `mayRepair`
|
|
40
40
|
* admits. Null when there is none — a legacy baseline, a signal with no
|
|
41
41
|
* per-command detail, or only reds no repair can fix — so nothing to pin to.
|
|
42
|
+
*
|
|
43
|
+
* A vanished suite is red here though it failed nothing and left `ok` true. It
|
|
44
|
+
* is the regression the differential exists to catch, and without a subject
|
|
45
|
+
* ACCEPT queued no repair for it and the checkpoint spliced none.
|
|
42
46
|
*/
|
|
43
47
|
export declare function healthRedSubject(health: HealthSignal & {
|
|
44
48
|
output?: string;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isHealthRed } from './repo-health-check.js';
|
|
1
2
|
import { parseRepairTitleFile } from './root-cause-repair.js';
|
|
2
3
|
import { failClassOfReason } from './verify-work.js';
|
|
3
4
|
/** A path-like token: at least one directory separator, ending in a file name. */
|
|
@@ -23,12 +24,16 @@ function resolveTracked(token, cwd, tracked) {
|
|
|
23
24
|
return bySuffix.length === 1 ? bySuffix[0] : null;
|
|
24
25
|
}
|
|
25
26
|
/**
|
|
26
|
-
* What a red health result is about: its first
|
|
27
|
+
* What a red health result is about: its first red command that `mayRepair`
|
|
27
28
|
* admits. Null when there is none — a legacy baseline, a signal with no
|
|
28
29
|
* per-command detail, or only reds no repair can fix — so nothing to pin to.
|
|
30
|
+
*
|
|
31
|
+
* A vanished suite is red here though it failed nothing and left `ok` true. It
|
|
32
|
+
* is the regression the differential exists to catch, and without a subject
|
|
33
|
+
* ACCEPT queued no repair for it and the checkpoint spliced none.
|
|
29
34
|
*/
|
|
30
35
|
export function healthRedSubject(health, cwd, tracked, mayRepair = () => true) {
|
|
31
|
-
const failing = (health.commands ?? []).find(c => c
|
|
36
|
+
const failing = (health.commands ?? []).find(c => isHealthRed(c) && mayRepair(c));
|
|
32
37
|
if (!failing)
|
|
33
38
|
return null;
|
|
34
39
|
const files = [];
|
|
@@ -115,5 +115,7 @@ export declare function runRepoHealthCheck(cwd: string, opts?: {
|
|
|
115
115
|
* that will judge the result DIFFERENTIALLY may set this — see the header. */
|
|
116
116
|
withTests?: boolean;
|
|
117
117
|
}): Promise<HealthOutcome>;
|
|
118
|
+
/** A command the repo owes an answer for: it failed, or its suite went missing. */
|
|
119
|
+
export declare function isHealthRed(c: HealthCommandResult): boolean;
|
|
118
120
|
/** "`bun run lint` exited 1; `bun run test` exited 1" — every failing command. */
|
|
119
121
|
export declare function describeHealthFailures(commands: readonly HealthCommandResult[]): string;
|
|
@@ -280,10 +280,14 @@ export async function runRepoHealthCheck(cwd, opts = {}) {
|
|
|
280
280
|
output: ''
|
|
281
281
|
};
|
|
282
282
|
}
|
|
283
|
+
/** A command the repo owes an answer for: it failed, or its suite went missing. */
|
|
284
|
+
export function isHealthRed(c) {
|
|
285
|
+
return c.outcome === 'fail' || c.gap === 'empty-suite';
|
|
286
|
+
}
|
|
283
287
|
/** "`bun run lint` exited 1; `bun run test` exited 1" — every failing command. */
|
|
284
288
|
export function describeHealthFailures(commands) {
|
|
285
289
|
return commands
|
|
286
|
-
.filter(
|
|
290
|
+
.filter(isHealthRed)
|
|
287
291
|
.map(c => c.outcome === 'fail' ?
|
|
288
292
|
`\`${c.cmd}\` exited ${c.exitCode}`
|
|
289
293
|
: `\`${c.cmd}\` found no tests to run`)
|
package/dist/task/task-gates.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { verifyFailClass } from './verify-work.js';
|
|
2
2
|
import { resolutionOptions, classifyResolutionAnswer } from './verify-resolution.js';
|
|
3
3
|
import { resolveDisposition } from './gate-resolution.js';
|
|
4
|
-
import { classifyHealthDelta } from './health-baseline.js';
|
|
4
|
+
import { classifyHealthDelta, regressedCommands } from './health-baseline.js';
|
|
5
5
|
import { SessionUI, notifyBoth, notifyRun } from '../remote/bridge.js';
|
|
6
6
|
import { isYoloMode, YOLO_STAMP } from './yolo.js';
|
|
7
7
|
import { extractFailingCommand, findRepairCandidate, summariseDefect } from './root-cause-repair.js';
|
|
8
8
|
import { attributeEnforceFailure } from './enforce-attribution.js';
|
|
9
|
+
import { describeHealthFailures } from './repo-health-check.js';
|
|
9
10
|
import { healthRedSubject, parseHealthRepairTitle } from './health-repair.js';
|
|
10
11
|
// The debt ledger is reached through the injected `recordDebt` dep (so it stays
|
|
11
12
|
// absent-in-tests); only the origin TYPE and the cross-task-deletion reason SHAPE
|
|
@@ -417,15 +418,19 @@ export async function runEnforcePass(active, deps, p, rec, routeRootCause, args)
|
|
|
417
418
|
const delta = classifyHealthDelta(healthBefore ?? null, after);
|
|
418
419
|
if (delta === 'regressed') {
|
|
419
420
|
enforceEditsBlocked = true;
|
|
421
|
+
// What REGRESSED, not the check's own verdict: a vanished suite fails
|
|
422
|
+
// nothing, so `after.reason` reads "tests passed" under this discard.
|
|
423
|
+
const regressed = regressedCommands(healthBefore ?? null, after);
|
|
424
|
+
const why = regressed.length > 0 ? describeHealthFailures(regressed) : after.reason;
|
|
420
425
|
const outputTail = after.output ? ` — output:\n${clampOutput(after.output)}` : '';
|
|
421
426
|
if (deps.discardEdits) {
|
|
422
427
|
await deps.discardEdits(p.cwd);
|
|
423
|
-
await rec(`enforce: edits discarded pre-commit — REGRESSED repo health (${
|
|
428
|
+
await rec(`enforce: edits discarded pre-commit — REGRESSED repo health (${why})${outputTail}`);
|
|
424
429
|
}
|
|
425
430
|
else {
|
|
426
|
-
await rec(`enforce: edits REGRESSED repo health pre-commit (${
|
|
431
|
+
await rec(`enforce: edits REGRESSED repo health pre-commit (${why}) — no discard available, left uncommitted${outputTail}`);
|
|
427
432
|
}
|
|
428
|
-
notifyRun(active, `${p.tag}: guideline edits on "${p.title}" regressed repo health (${
|
|
433
|
+
notifyRun(active, `${p.tag}: guideline edits on "${p.title}" regressed repo health (${why.slice(0, 120)}) — discarded before commit.`, 'warning');
|
|
429
434
|
}
|
|
430
435
|
else if (delta === 'pre-existing') {
|
|
431
436
|
// Failing both before and after → not enforce's fault. Keep the edits;
|
|
@@ -65,9 +65,12 @@ export interface VerifyFail {
|
|
|
65
65
|
crossTaskDeletions?: CrossTaskDeletion[];
|
|
66
66
|
inheritedHealth?: string;
|
|
67
67
|
/** The health result behind a `repo-health` FAIL: which command, and what its
|
|
68
|
-
* output named. What an ACCEPT of this FAIL hands to the repair channel.
|
|
68
|
+
* output named. What an ACCEPT of this FAIL hands to the repair channel.
|
|
69
|
+
* `reason` describes what REGRESSED, which is not what the check itself said:
|
|
70
|
+
* a vanished suite fails nothing, so the check's verdict reads "tests passed". */
|
|
69
71
|
health?: HealthSignal & {
|
|
70
72
|
output?: string;
|
|
73
|
+
reason?: string;
|
|
71
74
|
};
|
|
72
75
|
/** What the deterministic probes found for THIS verdict, carried out so an
|
|
73
76
|
* AUTOFIX re-run is told what the gate already knows (see fix-context.ts)
|
package/dist/task/verify-work.js
CHANGED
|
@@ -939,13 +939,17 @@ export async function runWorkVerification(deps) {
|
|
|
939
939
|
};
|
|
940
940
|
}
|
|
941
941
|
const failClass = healthFailClass(regressed);
|
|
942
|
+
// Re-minted, never `h.reason`: a vanished suite fails nothing, so the
|
|
943
|
+
// check's own reason reads "tests passed" under a REGRESSED verdict.
|
|
944
|
+
const why = describeHealthFailures(regressed);
|
|
942
945
|
return {
|
|
943
946
|
ok: false,
|
|
944
947
|
failClass,
|
|
945
|
-
reason: `${VERIFY_FAIL_PREFIX[failClass]} ${
|
|
948
|
+
reason: `${VERIFY_FAIL_PREFIX[failClass]} ${why}`,
|
|
946
949
|
health: {
|
|
947
950
|
...h,
|
|
948
951
|
ok: false,
|
|
952
|
+
reason: why,
|
|
949
953
|
commands: regressed,
|
|
950
954
|
output: regressed[0].output ?? h.output
|
|
951
955
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mjasnikovs/pi-task",
|
|
3
|
-
"version": "0.42.
|
|
3
|
+
"version": "0.42.6",
|
|
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",
|