@mjasnikovs/pi-task 0.18.19 → 0.18.21
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/README.md +23 -1
- package/assets/hero.png +0 -0
- package/assets/hero.svg +92 -0
- package/dist/task/auto-orchestrator.js +81 -13
- package/dist/task/coverage-loop.js +98 -9
- package/dist/task/gate-deps.js +26 -10
- package/dist/task/lint-fix.d.ts +11 -33
- package/dist/task/lint-fix.js +79 -4
- package/dist/task/requirements.d.ts +13 -4
- package/dist/task/requirements.js +19 -6
- package/dist/task/task-gates.d.ts +1 -1
- package/dist/task/task-gates.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
|
|
3
|
-

|
|
4
4
|
|
|
5
5
|
# pi-task
|
|
6
6
|
|
|
@@ -38,6 +38,22 @@ pi install npm:@mjasnikovs/pi-task
|
|
|
38
38
|
|
|
39
39
|
> Requires [`pi`](https://www.npmjs.com/package/@earendil-works/pi-coding-agent) (the Earendil coding agent) ≥ 0.80.
|
|
40
40
|
|
|
41
|
+
## Quickstart
|
|
42
|
+
|
|
43
|
+
One change — `/task` runs it through the full pipeline and hands the finished spec back in the same chat:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
/task add rate limiting to the /api/upload endpoint
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
A whole plan — `/task-auto` splits it into an ordered task list and runs each one through `/task`:
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
/task-auto Implement @MY_DETAILED_AND_LARGE_PLAN.md
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`@`-mentioning a file inlines its **contents**, so point it at the design doc you already wrote — no copy-paste.
|
|
56
|
+
|
|
41
57
|
## Slash commands
|
|
42
58
|
|
|
43
59
|
| Command | What it does |
|
|
@@ -54,6 +70,12 @@ pi install npm:@mjasnikovs/pi-task
|
|
|
54
70
|
|
|
55
71
|
## The pipeline
|
|
56
72
|
|
|
73
|
+
<div align="center">
|
|
74
|
+
|
|
75
|
+

|
|
76
|
+
|
|
77
|
+
</div>
|
|
78
|
+
|
|
57
79
|
| Phase | Output section | What happens |
|
|
58
80
|
| --- | --- | --- |
|
|
59
81
|
| **refine** | `refined prompt` | Sharpens your raw ask into an unambiguous, self-contained statement. |
|
package/assets/hero.png
ADDED
|
Binary file
|
package/assets/hero.svg
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 600" width="960" height="600" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif" role="img" aria-label="pi-task. Local models drift. pi-task doesn't trust one prompt. Your ask runs through five fixed phases — refine, research, grill, compose, critique — and what comes out is a spec.">
|
|
2
|
+
<defs>
|
|
3
|
+
<clipPath id="frame"><rect x="6" y="6" width="948" height="588" rx="18"/></clipPath>
|
|
4
|
+
<style>
|
|
5
|
+
.code { font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; }
|
|
6
|
+
.ic { fill:none; stroke-width:2.25; stroke-linecap:round; stroke-linejoin:round; }
|
|
7
|
+
.icf { stroke:none; }
|
|
8
|
+
.ph { font-size:18px; fill:#c9d1d9; }
|
|
9
|
+
.arw { fill:none; stroke:#6e7681; stroke-width:2.07; stroke-linecap:round; stroke-linejoin:round; }
|
|
10
|
+
</style>
|
|
11
|
+
</defs>
|
|
12
|
+
|
|
13
|
+
<rect x="6" y="6" width="948" height="588" rx="18" fill="#0d1117" stroke="#30363d" stroke-width="1.5"/>
|
|
14
|
+
|
|
15
|
+
<!-- ── watermark ── -->
|
|
16
|
+
<defs>
|
|
17
|
+
<linearGradient id="mtFade" gradientUnits="userSpaceOnUse" x1="6" y1="0" x2="688" y2="0">
|
|
18
|
+
<stop offset="0" stop-color="#fff" stop-opacity="1"/>
|
|
19
|
+
<stop offset="0.55" stop-color="#fff" stop-opacity="0.9"/>
|
|
20
|
+
<stop offset="1" stop-color="#fff" stop-opacity="0"/>
|
|
21
|
+
</linearGradient>
|
|
22
|
+
<mask id="mtMask"><rect x="0" y="0" width="960" height="600" fill="url(#mtFade)"/></mask>
|
|
23
|
+
</defs>
|
|
24
|
+
<g clip-path="url(#frame)" mask="url(#mtMask)" fill="none" stroke="#a371f7" stroke-linecap="round" stroke-linejoin="round">
|
|
25
|
+
<path d="M6.0 594.0L75.6 530.2L139.4 550.5L203.2 486.7L275.7 518.6L342.4 449.0L412.0 501.2L484.5 460.6L559.9 521.5L629.5 486.7L687.5 594.0" stroke-width="1.5" opacity="0.12"/>
|
|
26
|
+
<path d="M264.1 330.1L6.0 594.0M264.1 330.1L130.7 594.0M264.1 330.1L255.4 594.0M264.1 330.1L380.1 594.0M264.1 330.1L504.8 594.0M264.1 330.1L629.5 594.0" stroke-width="1" opacity="0.08"/>
|
|
27
|
+
<path d="M56.8 538.9L56.8 594.0M95.9 556.3L95.9 594.0M148.1 475.1L148.1 594.0M191.6 495.4L191.6 594.0M226.4 405.5L226.4 594.0M264.1 330.1L264.1 594.0M290.2 382.3L290.2 594.0M316.3 364.9L316.3 594.0M351.1 443.2L351.1 594.0M385.9 422.9L385.9 594.0M423.6 489.6L423.6 594.0M458.4 469.3L458.4 594.0M499.0 530.2L499.0 594.0M539.6 515.7L539.6 594.0M586.0 567.9L586.0 594.0" stroke-width="1" opacity="0.07"/>
|
|
28
|
+
<path d="M6.0 594.0L56.8 538.9L95.9 556.3L148.1 475.1L191.6 495.4L226.4 405.5L264.1 330.1L290.2 382.3L316.3 364.9L351.1 443.2L385.9 422.9L423.6 489.6L458.4 469.3L499.0 530.2L539.6 515.7L586.0 567.9L629.5 594.0" stroke-width="2" opacity="0.30"/>
|
|
29
|
+
<circle cx="6.0" cy="594.0" r="2.6" fill="#a371f7" stroke="none" opacity="0.34"/>
|
|
30
|
+
<circle cx="148.1" cy="475.1" r="2.6" fill="#a371f7" stroke="none" opacity="0.34"/>
|
|
31
|
+
<circle cx="264.1" cy="330.1" r="2.6" fill="#a371f7" stroke="none" opacity="0.34"/>
|
|
32
|
+
<circle cx="351.1" cy="443.2" r="2.6" fill="#a371f7" stroke="none" opacity="0.34"/>
|
|
33
|
+
<circle cx="458.4" cy="469.3" r="2.6" fill="#a371f7" stroke="none" opacity="0.34"/>
|
|
34
|
+
<circle cx="586.0" cy="567.9" r="2.6" fill="#a371f7" stroke="none" opacity="0.34"/>
|
|
35
|
+
</g>
|
|
36
|
+
|
|
37
|
+
<!-- ── logo lockup · π in a broken circle, drawn as paths (no font dependency) ── -->
|
|
38
|
+
<g transform="translate(371,59) scale(1.2500)" fill="none" stroke="#a371f7" stroke-width="2.40" stroke-linecap="round" stroke-linejoin="round">
|
|
39
|
+
<path d="M30.18 4.98A20 20 0 1 0 41.66 14.61"/>
|
|
40
|
+
<path d="M13.5 17H34.5M19.5 17V32M28.5 17V32"/>
|
|
41
|
+
</g>
|
|
42
|
+
<text x="449" y="99" font-size="44" font-weight="700" fill="#e6edf3">pi-task</text>
|
|
43
|
+
|
|
44
|
+
<!-- ── headline ── -->
|
|
45
|
+
<text x="480" y="224" text-anchor="middle" font-size="82" font-weight="700" fill="#6e7681">Local models drift.</text>
|
|
46
|
+
<text x="480" y="319" text-anchor="middle" font-size="82" font-weight="700" fill="#e6edf3">pi-task doesn't trust</text>
|
|
47
|
+
<text x="480" y="414" text-anchor="middle" font-size="82" font-weight="700" fill="#e6edf3">one prompt.</text>
|
|
48
|
+
|
|
49
|
+
<!-- ── phase strip ── -->
|
|
50
|
+
<rect x="65" y="473" width="831" height="61" rx="30.5" fill="#161b22" fill-opacity="0.92" stroke="#30363d" stroke-width="1.5"/>
|
|
51
|
+
<g transform="translate(108,492.5) scale(0.9167)" stroke="#58a6ff">
|
|
52
|
+
<circle class="ic" cx="12" cy="12" r="10"/>
|
|
53
|
+
<path class="ic" d="M22 12h-4M6 12H2M12 6V2M12 22v-4"/>
|
|
54
|
+
<circle class="icf" cx="12" cy="12" r="2.4" fill="#58a6ff"/>
|
|
55
|
+
</g>
|
|
56
|
+
<text x="139" y="509.8" class="ph">refine</text>
|
|
57
|
+
<path class="arw" d="M195 503.5h17.2M206.6 496.8l6.7 6.7-6.7 6.7"/>
|
|
58
|
+
<g transform="translate(229,492.5) scale(0.9167)" stroke="#39c5cf">
|
|
59
|
+
<path class="ic" d="M9.56 9.83 15.44 6.67M10.1 12h4.79M9.56 14.17 15.44 17.33"/>
|
|
60
|
+
<circle class="icf" cx="5.5" cy="12" r="2.4" fill="#39c5cf"/>
|
|
61
|
+
<circle class="icf" cx="19.5" cy="4.5" r="2.1" fill="#39c5cf"/>
|
|
62
|
+
<circle class="icf" cx="19.5" cy="12" r="2.1" fill="#39c5cf"/>
|
|
63
|
+
<circle class="icf" cx="19.5" cy="19.5" r="2.1" fill="#39c5cf"/>
|
|
64
|
+
</g>
|
|
65
|
+
<text x="260" y="509.8" class="ph">research</text>
|
|
66
|
+
<path class="arw" d="M341 503.5h17.2M352.6 496.8l6.7 6.7-6.7 6.7"/>
|
|
67
|
+
<g transform="translate(375,492.5) scale(0.9167)" stroke="#d29922">
|
|
68
|
+
<path class="ic" d="M7.6 8.4a4.5 4.5 0 1 1 5.9 4.3c-.9.35-1.5 1.2-1.5 2.2v.6"/>
|
|
69
|
+
<circle class="icf" cx="12" cy="19.8" r="1.5" fill="#d29922"/>
|
|
70
|
+
</g>
|
|
71
|
+
<text x="406" y="509.8" class="ph">grill</text>
|
|
72
|
+
<path class="arw" d="M446 503.5h17.2M457.6 496.8l6.7 6.7-6.7 6.7"/>
|
|
73
|
+
<g transform="translate(480,492.5) scale(0.9167)" stroke="#3fb950">
|
|
74
|
+
<path class="ic" d="M12 2.5 22 7.5 12 12.5 2 7.5 12 2.5z"/>
|
|
75
|
+
<path class="ic" d="M2 16.5 12 21.5 22 16.5"/>
|
|
76
|
+
</g>
|
|
77
|
+
<text x="511" y="509.8" class="ph">compose</text>
|
|
78
|
+
<path class="arw" d="M597 503.5h17.2M608.5 496.8l6.7 6.7-6.7 6.7"/>
|
|
79
|
+
<g transform="translate(631,492.5) scale(0.9167)" stroke="#a371f7">
|
|
80
|
+
<path class="ic" d="M20.5 15a9 9 0 1 1-2.1-9.4L23 10"/>
|
|
81
|
+
<path class="ic" d="M23 4v6h-6"/>
|
|
82
|
+
</g>
|
|
83
|
+
<text x="662" y="509.8" class="ph">critique</text>
|
|
84
|
+
<path class="arw" d="M732 503.5h17.2M743.5 496.8l6.7 6.7-6.7 6.7"/>
|
|
85
|
+
<g transform="translate(766,492.5) scale(0.9167)" stroke="#3fb950">
|
|
86
|
+
<path class="ic" d="M14 2.5H6.5a2 2 0 0 0-2 2v15a2 2 0 0 0 2 2h11a2 2 0 0 0 2-2V8z"/>
|
|
87
|
+
<path class="ic" d="M14 2.5V8h5.5"/>
|
|
88
|
+
<path class="ic" d="M8.5 13.5h7M8.5 17.5h7"/>
|
|
89
|
+
</g>
|
|
90
|
+
<text x="797" y="509.8" class="ph" font-weight="600" fill="#3fb950">a spec</text>
|
|
91
|
+
|
|
92
|
+
</svg>
|
|
@@ -42,7 +42,19 @@ import { LAUNCH_EXTRACT_PROMPT, enumerateScriptCandidates, parseScriptLines, kee
|
|
|
42
42
|
const MAX_CLARIFY_QUESTIONS = 8;
|
|
43
43
|
// Bounded coverage-triage rounds after decompose: judge → reprompt-with-missing
|
|
44
44
|
// → judge again, at most. Two rounds so one flaky retry doesn't end the gate,
|
|
45
|
-
// while a judge that keeps flagging can't loop the plan phase forever.
|
|
45
|
+
// while a judge that keeps flagging can't loop the plan phase forever. Each round
|
|
46
|
+
// spawns three model children (decompose + coverage-map + coverage-verdict), so
|
|
47
|
+
// the ceiling is also a latency/spawn budget, not just a correctness bound.
|
|
48
|
+
//
|
|
49
|
+
// Why 2 is safe to sit this low: when this number was picked (2026-07-03) adoption
|
|
50
|
+
// was LAST-WINS, so more rounds meant more chances to overwrite a good plan with a
|
|
51
|
+
// worse regeneration — the cap was protective. Adoption is now MONOTONE
|
|
52
|
+
// (coverage-loop.ts, 2026-07-15): a retry that drops owned coverage is rejected,
|
|
53
|
+
// never adopted, so extra rounds can only hold or grow coverage. The one gap that
|
|
54
|
+
// remained is that an adoption landing ON the last round can expose a NEW area with
|
|
55
|
+
// no round left to chase it — handled surgically by a single bonus round granted
|
|
56
|
+
// only in that exact case (see the loop), rather than by raising this ceiling for
|
|
57
|
+
// every run.
|
|
46
58
|
const MAX_COVERAGE_ROUNDS = 2;
|
|
47
59
|
/** Reprompt prefix when the coverage triage found feature areas no task covers. */
|
|
48
60
|
function coverageRepromptHint(missing) {
|
|
@@ -106,6 +118,17 @@ function logPlanDebug(cwd, msg) {
|
|
|
106
118
|
.then(() => fsp.appendFile(path.join(dir, 'plan-debug.log'), line))
|
|
107
119
|
.catch(() => { });
|
|
108
120
|
}
|
|
121
|
+
/** Normalise a missing-area string for cross-round identity — lowercased alnum
|
|
122
|
+
* words, punctuation and quote-wrapping collapsed. Used only to tell whether an
|
|
123
|
+
* adopted plan introduced a NEW gap versus re-surfacing the same one (#2 bonus
|
|
124
|
+
* round); intentionally coarse, so trivial rewording of the same area does not
|
|
125
|
+
* read as new and buy an extra round. */
|
|
126
|
+
function normMissingArea(s) {
|
|
127
|
+
return s
|
|
128
|
+
.toLowerCase()
|
|
129
|
+
.replace(/[^a-z0-9]+/g, ' ')
|
|
130
|
+
.trim();
|
|
131
|
+
}
|
|
109
132
|
/**
|
|
110
133
|
* Clarify's answer-side TRIAGE — the second stage /task-auto's clarify gate was
|
|
111
134
|
* missing that /task's grill already had. /task-auto's clarify was single-stage:
|
|
@@ -569,7 +592,8 @@ export async function planAuto(ctx, cwd, feature, deps) {
|
|
|
569
592
|
return {
|
|
570
593
|
plan: { titles, covered, missing },
|
|
571
594
|
accounting: acc,
|
|
572
|
-
suspect: isSuspectPlan(titles, featureForModel)
|
|
595
|
+
suspect: isSuspectPlan(titles, featureForModel),
|
|
596
|
+
judgeMissing: verdictMissing
|
|
573
597
|
};
|
|
574
598
|
};
|
|
575
599
|
const hasRequirements = reqEntries.length > 0;
|
|
@@ -579,6 +603,17 @@ export async function planAuto(ctx, cwd, feature, deps) {
|
|
|
579
603
|
// The carried accounting (cross-cutting + unowned) for the plan that ships.
|
|
580
604
|
let accounting = best.accounting;
|
|
581
605
|
let round = 0;
|
|
606
|
+
// #2: the round cap can be lifted ONCE. An adoption is a fresh whole-plan roll,
|
|
607
|
+
// so the plan that gets adopted can expose an uncovered area the pre-adoption
|
|
608
|
+
// plan never had — and if that adoption lands on the last allowed round, the
|
|
609
|
+
// loop breaks before the new gap ever gets a reprompt (mx5 2026-07-16: the
|
|
610
|
+
// 55-title plan was adopted on the final round AND was the first to reveal §10's
|
|
611
|
+
// test-infra gap; the cap fired the same instant, so it was never chased). Grant
|
|
612
|
+
// exactly one bonus round when — and only when — an adoption introduces a NEW
|
|
613
|
+
// missing area at the cap. Bounded to one so a judge that flags forever still
|
|
614
|
+
// cannot loop the plan phase; a persistent (non-new) gap never re-triggers it.
|
|
615
|
+
let roundCap = MAX_COVERAGE_ROUNDS;
|
|
616
|
+
let bonusRoundUsed = false;
|
|
582
617
|
for (;;) {
|
|
583
618
|
if (best.plan.titles.length === 0)
|
|
584
619
|
break;
|
|
@@ -596,7 +631,7 @@ export async function planAuto(ctx, cwd, feature, deps) {
|
|
|
596
631
|
}
|
|
597
632
|
break;
|
|
598
633
|
}
|
|
599
|
-
if (round >=
|
|
634
|
+
if (round >= roundCap)
|
|
600
635
|
break;
|
|
601
636
|
round++;
|
|
602
637
|
logPlanDebug(cwd, `decompose-coverage round ${round}: INCOMPLETE — missing: `
|
|
@@ -606,9 +641,29 @@ export async function planAuto(ctx, cwd, feature, deps) {
|
|
|
606
641
|
const cand = await scorePlan(retryTitles);
|
|
607
642
|
const decision = decideAdoption(best.plan, cand.plan, hasRequirements);
|
|
608
643
|
if (decision.adopt) {
|
|
644
|
+
// Snapshot the pre-adoption plan to decide whether this adoption earns a
|
|
645
|
+
// bonus round. Two guards keep the bonus off generic judge churn: it must
|
|
646
|
+
// be a real coverage GAIN (grounded covered-set strictly grew — a flaky
|
|
647
|
+
// judge that just relabels the same-shaped plan's gap does not qualify),
|
|
648
|
+
// and it must expose a NEW area (a gap already present is one we have or
|
|
649
|
+
// will reprompt against anyway). Requirements-path only: without grounded
|
|
650
|
+
// requirements "missing" is pure holistic-judge free-text that can change
|
|
651
|
+
// every round, so there is no trustworthy "grew"/"new" signal to gate on.
|
|
652
|
+
const priorCovered = best.plan.covered.size;
|
|
653
|
+
const priorMissing = new Set(best.plan.missing.map(normMissingArea));
|
|
609
654
|
best = cand;
|
|
610
655
|
accounting = cand.accounting ?? accounting;
|
|
611
656
|
logPlanDebug(cwd, `decompose retry ADOPTED — ${decision.reason}`);
|
|
657
|
+
if (!bonusRoundUsed
|
|
658
|
+
&& round >= roundCap
|
|
659
|
+
&& hasRequirements
|
|
660
|
+
&& cand.plan.covered.size > priorCovered
|
|
661
|
+
&& cand.plan.missing.some(m => !priorMissing.has(normMissingArea(m)))) {
|
|
662
|
+
bonusRoundUsed = true;
|
|
663
|
+
roundCap++;
|
|
664
|
+
logPlanDebug(cwd, 'decompose-coverage: bonus round granted — adoption grew coverage and '
|
|
665
|
+
+ 'exposed a new uncovered area at the cap');
|
|
666
|
+
}
|
|
612
667
|
}
|
|
613
668
|
else {
|
|
614
669
|
// Rejected: keep the better current plan. The loop re-checks it at the
|
|
@@ -631,7 +686,10 @@ export async function planAuto(ctx, cwd, feature, deps) {
|
|
|
631
686
|
if (unresolvedMissing !== null) {
|
|
632
687
|
logPlanDebug(cwd, `decompose-coverage exhausted ${round} round(s) still INCOMPLETE — missing: `
|
|
633
688
|
+ unresolvedMissing.join('; ').slice(0, 300));
|
|
634
|
-
ctx.ui.notify(`/task-auto:
|
|
689
|
+
ctx.ui.notify(`/task-auto: no task fully owns — ${unresolvedMissing.join('; ').slice(0, 200)}. `
|
|
690
|
+
+ 'Carried into every task via .pi-tasks/requirements.md, but not as a dedicated '
|
|
691
|
+
+ 'task. To give it one, stop now and add it to the plan in .pi-tasks/; otherwise '
|
|
692
|
+
+ 'it proceeds.', 'warning');
|
|
635
693
|
}
|
|
636
694
|
// Carry what no single task owns (goal A(b)/(c)): cross-cutting requirements
|
|
637
695
|
// become `.pi-tasks/requirements.md`, injected VERBATIM into every task's
|
|
@@ -639,15 +697,25 @@ export async function planAuto(ctx, cwd, feature, deps) {
|
|
|
639
697
|
// is authoritative" pointer recovered it in 1 of ~6 tasks; content travels,
|
|
640
698
|
// pointers don't). Requirements still unmapped after the rounds are carried
|
|
641
699
|
// too — marked — and recorded user-visibly in the plan file, never dropped.
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
700
|
+
//
|
|
701
|
+
// #1: the holistic-judge missing areas are carried as a THIRD channel. They are
|
|
702
|
+
// areas requirement-extraction never captured as a tracked entry (so the
|
|
703
|
+
// grounded accounting is structurally blind to them), seen only by the judge —
|
|
704
|
+
// exactly the class that, having no carrier, was warned-about then dropped (mx5
|
|
705
|
+
// 2026-07-16, §10 test-infra). Carried independent of `accounting` so a mapping
|
|
706
|
+
// fault (accounting === null) can't strand them either.
|
|
707
|
+
const carriedCrossCutting = accounting?.crossCutting ?? [];
|
|
708
|
+
const carriedUnmapped = accounting?.unmapped ?? [];
|
|
709
|
+
const carriedJudge = best.judgeMissing;
|
|
710
|
+
if (carriedCrossCutting.length > 0 || carriedUnmapped.length > 0 || carriedJudge.length > 0) {
|
|
711
|
+
await appendCarriedRequirements(cwd, carriedCrossCutting, carriedUnmapped, carriedJudge);
|
|
712
|
+
const parts = [
|
|
713
|
+
carriedCrossCutting.length > 0 ? `${carriedCrossCutting.length} cross-cutting` : '',
|
|
714
|
+
carriedUnmapped.length > 0 ? `${carriedUnmapped.length} unowned` : '',
|
|
715
|
+
carriedJudge.length > 0 ? `${carriedJudge.length} judge-flagged` : ''
|
|
716
|
+
].filter(p => p.length > 0);
|
|
717
|
+
ctx.ui.notify(`/task-auto: carrying ${parts.join(', ')} requirement(s) into every task`
|
|
718
|
+
+ ' — see .pi-tasks/requirements.md.', 'info');
|
|
651
719
|
}
|
|
652
720
|
// Cross-slice contract registry (mx5 run 8, F3): now that the plan is settled,
|
|
653
721
|
// extract the interface facts MORE THAN ONE slice must agree on — endpoint paths,
|
|
@@ -32,17 +32,106 @@
|
|
|
32
32
|
// domain-agnostic (no mx5/web vocabulary).
|
|
33
33
|
const COVERAGE_STOPWORDS = new Set([
|
|
34
34
|
// function words
|
|
35
|
-
'the',
|
|
36
|
-
'
|
|
37
|
-
'
|
|
38
|
-
'
|
|
35
|
+
'the',
|
|
36
|
+
'a',
|
|
37
|
+
'an',
|
|
38
|
+
'and',
|
|
39
|
+
'or',
|
|
40
|
+
'of',
|
|
41
|
+
'to',
|
|
42
|
+
'in',
|
|
43
|
+
'on',
|
|
44
|
+
'for',
|
|
45
|
+
'with',
|
|
46
|
+
'by',
|
|
47
|
+
'at',
|
|
48
|
+
'as',
|
|
49
|
+
'is',
|
|
50
|
+
'are',
|
|
51
|
+
'be',
|
|
52
|
+
'it',
|
|
53
|
+
'its',
|
|
54
|
+
'that',
|
|
55
|
+
'this',
|
|
56
|
+
'from',
|
|
57
|
+
'into',
|
|
58
|
+
'out',
|
|
59
|
+
'up',
|
|
60
|
+
'per',
|
|
61
|
+
'via',
|
|
62
|
+
'not',
|
|
63
|
+
'no',
|
|
64
|
+
'but',
|
|
65
|
+
'if',
|
|
66
|
+
'then',
|
|
67
|
+
'than',
|
|
68
|
+
'so',
|
|
69
|
+
'such',
|
|
70
|
+
'each',
|
|
71
|
+
'any',
|
|
72
|
+
'all',
|
|
73
|
+
'every',
|
|
74
|
+
'when',
|
|
75
|
+
'where',
|
|
76
|
+
'must',
|
|
77
|
+
'should',
|
|
78
|
+
'shall',
|
|
79
|
+
'may',
|
|
80
|
+
'can',
|
|
81
|
+
'will',
|
|
82
|
+
'end',
|
|
83
|
+
'new',
|
|
39
84
|
// generic task verbs
|
|
40
|
-
'add',
|
|
41
|
-
'
|
|
42
|
-
'
|
|
85
|
+
'add',
|
|
86
|
+
'implement',
|
|
87
|
+
'create',
|
|
88
|
+
'build',
|
|
89
|
+
'scaffold',
|
|
90
|
+
'setup',
|
|
91
|
+
'set',
|
|
92
|
+
'support',
|
|
93
|
+
'handle',
|
|
94
|
+
'apply',
|
|
95
|
+
'use',
|
|
96
|
+
'used',
|
|
97
|
+
'using',
|
|
98
|
+
'make',
|
|
99
|
+
'makes',
|
|
100
|
+
'made',
|
|
101
|
+
'enable',
|
|
102
|
+
'provide',
|
|
103
|
+
'ensure',
|
|
104
|
+
'allow',
|
|
105
|
+
'run',
|
|
106
|
+
'runs',
|
|
107
|
+
'get',
|
|
108
|
+
'gets',
|
|
109
|
+
'define',
|
|
110
|
+
'configure',
|
|
111
|
+
'init',
|
|
112
|
+
'update',
|
|
113
|
+
'manage',
|
|
43
114
|
// generic project nouns
|
|
44
|
-
'cli',
|
|
45
|
-
'
|
|
115
|
+
'cli',
|
|
116
|
+
'tool',
|
|
117
|
+
'app',
|
|
118
|
+
'application',
|
|
119
|
+
'project',
|
|
120
|
+
'feature',
|
|
121
|
+
'task',
|
|
122
|
+
'tasks',
|
|
123
|
+
'user',
|
|
124
|
+
'users',
|
|
125
|
+
'mode',
|
|
126
|
+
'flag',
|
|
127
|
+
'flags',
|
|
128
|
+
'option',
|
|
129
|
+
'options',
|
|
130
|
+
'system',
|
|
131
|
+
'code',
|
|
132
|
+
'thing',
|
|
133
|
+
'things',
|
|
134
|
+
'work'
|
|
46
135
|
]);
|
|
47
136
|
/** Distinctive content tokens of a phrase: lowercased alphanumeric words ≥3 chars,
|
|
48
137
|
* minus the ubiquitous stopwords. `--json` → `json`, `dead-letter` → `dead`,`letter`.
|
package/dist/task/gate-deps.js
CHANGED
|
@@ -492,17 +492,33 @@ export function buildGateDeps(params) {
|
|
|
492
492
|
contracts: () => readContracts(cwd2)
|
|
493
493
|
});
|
|
494
494
|
},
|
|
495
|
-
lintFix: (fixCtx, cwd2, taskTitle, failReason) =>
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
495
|
+
lintFix: async (fixCtx, cwd2, taskTitle, taskId, failReason) => {
|
|
496
|
+
// Same frozen extraction the enforce guard and the verify rule-4b
|
|
497
|
+
// probe consume (mx5 run 12: the lint-fix child edited spec-frozen
|
|
498
|
+
// tsconfig.json because ESLint's own error text instructed it, then
|
|
499
|
+
// verify failed the TASK for that edit — the child must know the
|
|
500
|
+
// spec's do-not-touch list AND be mechanically denied it).
|
|
501
|
+
let frozenPaths = [];
|
|
502
|
+
try {
|
|
503
|
+
const { body } = await readTaskFile(cwd2, taskId);
|
|
504
|
+
frozenPaths = frozenPathsFromSpec(extractSpecForVerification(body));
|
|
504
505
|
}
|
|
505
|
-
|
|
506
|
+
catch {
|
|
507
|
+
// spec unreadable → no frozen paths; the guard degrades to a no-op
|
|
508
|
+
}
|
|
509
|
+
return runBoundedLintFix({
|
|
510
|
+
cwd: cwd2,
|
|
511
|
+
signal,
|
|
512
|
+
failReason,
|
|
513
|
+
runChild: makeGateChild(fixCtx, cwd2, taskTitle, 'lint-fix', 'verify-debug.log'),
|
|
514
|
+
repoHealth: () => Promise.resolve(runRepoHealthCheck(cwd2)),
|
|
515
|
+
git: async (args) => {
|
|
516
|
+
const r = await git(cwd2, args, signal);
|
|
517
|
+
return { exitCode: r.exitCode, stdout: r.stdout };
|
|
518
|
+
},
|
|
519
|
+
frozenPaths
|
|
520
|
+
});
|
|
521
|
+
},
|
|
506
522
|
// Deterministic static check + tree helpers for the enforce pre-commit gate.
|
|
507
523
|
repoHealth: cwd2 => Promise.resolve(runRepoHealthCheck(cwd2)),
|
|
508
524
|
dirty: async (cwd2) => {
|
package/dist/task/lint-fix.d.ts
CHANGED
|
@@ -1,33 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* lint-fix — the bounded, graduated resolution for a repo-health verify FAIL.
|
|
3
|
-
*
|
|
4
|
-
* The failure this closes (mx5 run 3, TASK_0017): AUTOFIX's only hammer is a FULL
|
|
5
|
-
* implementation re-run. For a repo-health FAIL of 10 trivial lint findings the live
|
|
6
|
-
* run burned two 36–56-minute impl turns, each REGENERATING a fresh 900-line rewrite
|
|
7
|
-
* that failed lint differently — the loop cannot converge because the tool is bigger
|
|
8
|
-
* than the defect. Validated live on the real TASK_0017 tree: a bounded fix child
|
|
9
|
-
* (read,edit,bash) reached lint-clean in 64s and 106s, 2/2.
|
|
10
|
-
*
|
|
11
|
-
* The same validation caught the design's failure mode: BOTH runs cheated, running
|
|
12
|
-
* `git checkout -- src/test/request.ts` — REVERTING the task's uncommitted work to
|
|
13
|
-
* make the findings vanish. So this pass ships with a deterministic REVERT-GUARD,
|
|
14
|
-
* outcome-based (command filtering can't catch every path to the same effect):
|
|
15
|
-
*
|
|
16
|
-
* - Before the child runs: snapshot the full working state (`git add -A` +
|
|
17
|
-
* `git write-tree`, then unstage) and record which files differ from HEAD.
|
|
18
|
-
* - After: any pre-existing work file now byte-identical to HEAD means the child
|
|
19
|
-
* discarded work instead of fixing it → restore the snapshot, report not-applied.
|
|
20
|
-
* - Converge check: the injected repoHealth must pass; otherwise not-applied.
|
|
21
|
-
*
|
|
22
|
-
* A file whose ENTIRE pre-fix diff was the lint finding (fixing it legitimately
|
|
23
|
-
* restores HEAD) trips the guard conservatively — the fix is discarded and the
|
|
24
|
-
* ordinary AUTOFIX picker takes over. Safe direction: the guard may only cost time,
|
|
25
|
-
* never work.
|
|
26
|
-
*
|
|
27
|
-
* Not-applied is never terminal: the caller falls through to the existing
|
|
28
|
-
* recommend → AUTOFIX/ACCEPT/dismiss picker, so this pass can only make the loop
|
|
29
|
-
* faster, never change what it can decide.
|
|
30
|
-
*/
|
|
31
1
|
export interface LintFixResult {
|
|
32
2
|
/** true → findings fixed, repo health passes, work preserved. */
|
|
33
3
|
ok: boolean;
|
|
@@ -51,6 +21,13 @@ export interface LintFixDeps {
|
|
|
51
21
|
exitCode: number;
|
|
52
22
|
stdout: string;
|
|
53
23
|
}>;
|
|
24
|
+
/**
|
|
25
|
+
* Paths the task's spec forbids modifying (frozenPathsFromSpec over the same
|
|
26
|
+
* composed spec verify judges). Injected into the child's prompt AND enforced
|
|
27
|
+
* deterministically post-child: a frozen path the child changed is reverted
|
|
28
|
+
* and the fix reported not-applied. Absent/empty → no-op, prior behavior.
|
|
29
|
+
*/
|
|
30
|
+
frozenPaths?: string[];
|
|
54
31
|
}
|
|
55
32
|
/** The fix child edits and runs the checker; bash exists to RUN the check, not git. */
|
|
56
33
|
export declare const LINT_FIX_TOOLS = "read,edit,bash";
|
|
@@ -60,14 +37,15 @@ export declare const LINT_FIX_TOOLS = "read,edit,bash";
|
|
|
60
37
|
* an explicit ban on discarding work (both validation runs reached green via
|
|
61
38
|
* `git checkout` of the work file until the guard existed).
|
|
62
39
|
*/
|
|
63
|
-
export declare function buildLintFixPrompt(failReason: string): string;
|
|
40
|
+
export declare function buildLintFixPrompt(failReason: string, frozenPaths?: string[]): string;
|
|
64
41
|
/**
|
|
65
42
|
* Pure guard core: which pre-existing work files did the fix pass revert to HEAD?
|
|
66
43
|
* `preDirty` = files differing from HEAD before the fix; `stillDirty` = after.
|
|
67
44
|
*/
|
|
68
45
|
export declare function revertGuardViolations(preDirty: string[], stillDirty: Set<string>): string[];
|
|
69
46
|
/**
|
|
70
|
-
* Run the bounded fix: snapshot → child → revert-guard →
|
|
71
|
-
* throws for an outcome; only a child-level user cancel
|
|
47
|
+
* Run the bounded fix: snapshot → child → revert-guard → frozen-path guard →
|
|
48
|
+
* converge check. Never throws for an outcome; only a child-level user cancel
|
|
49
|
+
* propagates from runChild.
|
|
72
50
|
*/
|
|
73
51
|
export declare function runBoundedLintFix(deps: LintFixDeps): Promise<LintFixResult>;
|
package/dist/task/lint-fix.js
CHANGED
|
@@ -27,7 +27,24 @@
|
|
|
27
27
|
* Not-applied is never terminal: the caller falls through to the existing
|
|
28
28
|
* recommend → AUTOFIX/ACCEPT/dismiss picker, so this pass can only make the loop
|
|
29
29
|
* faster, never change what it can decide.
|
|
30
|
+
*
|
|
31
|
+
* FROZEN-PATH GUARD (mx5 run 12, TASK_0021/0022): the checker's own error text
|
|
32
|
+
* can INSTRUCT an edit to a spec-frozen file (typed ESLint: "playwright/index.ts
|
|
33
|
+
* was not found by the project … Consider either including it in the
|
|
34
|
+
* tsconfig.json") and this child complies — while the task's spec froze
|
|
35
|
+
* `tsconfig.json` and verify's rule-4b prohibition probe then fails the TASK for
|
|
36
|
+
* the gate child's edit. Two gates, contradictory rules, same file; the loop
|
|
37
|
+
* never converges (live: TASK_0021 burned all three unattended AUTOFIX rounds).
|
|
38
|
+
* Prompt framing alone is A/B-proven ~0–1/5 on the weak model (see
|
|
39
|
+
* frozen-path-guard.ts), so the deny is mechanical: the spec's frozen paths are
|
|
40
|
+
* threaded in via `frozenPaths`, injected into the prompt as a do-not-touch list
|
|
41
|
+
* (belt), and any frozen path the child still changed is deterministically
|
|
42
|
+
* reverted post-child and the fix reported not-applied (suspenders). Only paths
|
|
43
|
+
* that were CLEAN before the child ran are reverted — a frozen path already
|
|
44
|
+
* dirty with (possibly task) work is left alone, in the guard's safe direction:
|
|
45
|
+
* cost time, never work.
|
|
30
46
|
*/
|
|
47
|
+
import { parseChangedFrozenFiles, revertFrozenPaths } from './frozen-path-guard.js';
|
|
31
48
|
/** The fix child edits and runs the checker; bash exists to RUN the check, not git. */
|
|
32
49
|
export const LINT_FIX_TOOLS = 'read,edit,bash';
|
|
33
50
|
/**
|
|
@@ -36,7 +53,20 @@ export const LINT_FIX_TOOLS = 'read,edit,bash';
|
|
|
36
53
|
* an explicit ban on discarding work (both validation runs reached green via
|
|
37
54
|
* `git checkout` of the work file until the guard existed).
|
|
38
55
|
*/
|
|
39
|
-
export function buildLintFixPrompt(failReason) {
|
|
56
|
+
export function buildLintFixPrompt(failReason, frozenPaths = []) {
|
|
57
|
+
const frozenBlock = frozenPaths.length === 0 ?
|
|
58
|
+
[]
|
|
59
|
+
: [
|
|
60
|
+
'3b. HARD CONSTRAINT — SPEC-FROZEN PATHS. The task spec forbids modifying:',
|
|
61
|
+
...frozenPaths.map(p => ` - ${p}`),
|
|
62
|
+
' You must NOT edit, create, or delete anything at or under these paths,',
|
|
63
|
+
" EVEN IF the checker's own error message suggests exactly that fix",
|
|
64
|
+
' (e.g. "consider including it in the tsconfig.json"). Any change you make',
|
|
65
|
+
' to a frozen path is detected and reverted, and the whole fix is rejected.',
|
|
66
|
+
' If the check cannot pass without touching a frozen path, STOP and report',
|
|
67
|
+
' LINT-FIX: BLOCKED with the reason.',
|
|
68
|
+
''
|
|
69
|
+
];
|
|
40
70
|
return [
|
|
41
71
|
'You are a bounded static-analysis fix pass. A verification gate just failed',
|
|
42
72
|
`with: ${failReason}`,
|
|
@@ -59,6 +89,7 @@ export function buildLintFixPrompt(failReason) {
|
|
|
59
89
|
' Reverting the work would make the findings vanish — that is destroying the',
|
|
60
90
|
' task, not fixing it, and it is detected and rejected.',
|
|
61
91
|
'',
|
|
92
|
+
...frozenBlock,
|
|
62
93
|
'4. Re-run the check after editing and confirm it exits 0.',
|
|
63
94
|
'',
|
|
64
95
|
'End with exactly one line:',
|
|
@@ -91,8 +122,21 @@ async function dirtyFiles(deps) {
|
|
|
91
122
|
.filter(l => l.length > 0);
|
|
92
123
|
}
|
|
93
124
|
/**
|
|
94
|
-
*
|
|
95
|
-
*
|
|
125
|
+
* Which spec-frozen paths currently show a change in `git status`? Null when git
|
|
126
|
+
* itself failed — inconclusive, not evidence (same discipline as dirtyFiles).
|
|
127
|
+
*/
|
|
128
|
+
async function frozenDirtySet(deps, frozen) {
|
|
129
|
+
if (frozen.length === 0)
|
|
130
|
+
return new Set();
|
|
131
|
+
const r = await deps.git(['status', '--porcelain', '--', ...frozen]);
|
|
132
|
+
if (r.exitCode !== 0)
|
|
133
|
+
return null;
|
|
134
|
+
return new Set(parseChangedFrozenFiles(r.stdout));
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Run the bounded fix: snapshot → child → revert-guard → frozen-path guard →
|
|
138
|
+
* converge check. Never throws for an outcome; only a child-level user cancel
|
|
139
|
+
* propagates from runChild.
|
|
96
140
|
*/
|
|
97
141
|
export async function runBoundedLintFix(deps) {
|
|
98
142
|
// Snapshot the full working state as a tree object (includes untracked files),
|
|
@@ -122,8 +166,14 @@ export async function runBoundedLintFix(deps) {
|
|
|
122
166
|
snapshot = wt.stdout.trim();
|
|
123
167
|
await deps.git(['reset']);
|
|
124
168
|
}
|
|
169
|
+
// FROZEN-PATH baseline: frozen paths already dirty BEFORE the child ran carry
|
|
170
|
+
// (possibly the task's own) work — the guard must never revert those, only
|
|
171
|
+
// changes the CHILD introduces on top of a clean frozen path. A git failure
|
|
172
|
+
// here disables the guard for this run (inconclusive ≠ license to revert).
|
|
173
|
+
const frozen = deps.frozenPaths ?? [];
|
|
174
|
+
const preFrozenDirty = await frozenDirtySet(deps, frozen);
|
|
125
175
|
try {
|
|
126
|
-
await deps.runChild(LINT_FIX_TOOLS, buildLintFixPrompt(deps.failReason), deps.signal);
|
|
176
|
+
await deps.runChild(LINT_FIX_TOOLS, buildLintFixPrompt(deps.failReason, frozen), deps.signal);
|
|
127
177
|
}
|
|
128
178
|
catch (err) {
|
|
129
179
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -174,6 +224,31 @@ export async function runBoundedLintFix(deps) {
|
|
|
174
224
|
+ `${violations.length > 3 ? ', …' : ''}) — fix ${snapshot ? 'rolled back' : 'REJECTED but no snapshot to restore'}`
|
|
175
225
|
};
|
|
176
226
|
}
|
|
227
|
+
// FROZEN-PATH GUARD: a frozen path that was clean pre-child and is changed
|
|
228
|
+
// now is the child's edit — the exact write verify's rule-4b prohibition
|
|
229
|
+
// probe is guaranteed to fail the TASK for (mx5 run 12: ESLint's own error
|
|
230
|
+
// text instructed the tsconfig.json edit and the child complied, 5/5 in the
|
|
231
|
+
// live A/B). Revert JUST those paths (they were clean, so HEAD == pre-state:
|
|
232
|
+
// no work can be lost) and report not-applied so the caller falls through to
|
|
233
|
+
// the ordinary recommend → AUTOFIX/ACCEPT picker. Inconclusive git (either
|
|
234
|
+
// side) → guard steps aside; the verify probe still catches what survives.
|
|
235
|
+
if (preFrozenDirty !== null) {
|
|
236
|
+
const postFrozenDirty = await frozenDirtySet(deps, frozen);
|
|
237
|
+
if (postFrozenDirty !== null) {
|
|
238
|
+
const frozenViolations = [...postFrozenDirty].filter(f => !preFrozenDirty.has(f));
|
|
239
|
+
if (frozenViolations.length > 0) {
|
|
240
|
+
const reverted = await revertFrozenPaths(frozenViolations, deps.git);
|
|
241
|
+
return {
|
|
242
|
+
ok: false,
|
|
243
|
+
reason: `frozen-path: fix child modified spec-frozen path(s) `
|
|
244
|
+
+ `(${frozenViolations.slice(0, 3).join(', ')}`
|
|
245
|
+
+ `${frozenViolations.length > 3 ? ', …' : ''}) — `
|
|
246
|
+
+ `${reverted.length > 0 ? 'reverted' : 'REJECTED (revert found nothing to undo)'}; `
|
|
247
|
+
+ `the static findings need a fix that respects the spec's constraints`
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
177
252
|
const health = await deps.repoHealth();
|
|
178
253
|
if (!health.ok) {
|
|
179
254
|
return { ok: false, reason: `did not converge: ${health.reason}` };
|
|
@@ -76,11 +76,20 @@ export declare function accountCoverage(requirements: RequirementEntry[], mappin
|
|
|
76
76
|
/** The stored carried-requirements text ('' when none recorded). */
|
|
77
77
|
export declare function readRequirements(cwd: string): Promise<string>;
|
|
78
78
|
/**
|
|
79
|
-
* Append carried requirements
|
|
80
|
-
*
|
|
81
|
-
*
|
|
79
|
+
* Append carried requirements, deduped against what is stored. Three channels,
|
|
80
|
+
* each better carried into every task than silently lost — host-side only,
|
|
81
|
+
* children never write it, best-effort:
|
|
82
|
+
* • `crossCutting` — obligations no single task owns (policy/global rules).
|
|
83
|
+
* • `unresolved` — grounded requirements still unmapped after the retry rounds.
|
|
84
|
+
* • `judgeFlagged` — free-text areas the holistic coverage judge flagged as
|
|
85
|
+
* uncovered that requirement-extraction never captured as a tracked entry, so
|
|
86
|
+
* the grounded channels above are structurally blind to them (mx5 2026-07-16:
|
|
87
|
+
* §10's test-infra setup was seen ONLY by the judge and, having no carrier,
|
|
88
|
+
* was warned-about then dropped). These are plain strings, not quotes of the
|
|
89
|
+
* source; marked distinctly so a task can tell an inferred area from a verbatim
|
|
90
|
+
* obligation.
|
|
82
91
|
*/
|
|
83
|
-
export declare function appendCarriedRequirements(cwd: string, crossCutting: RequirementEntry[], unresolved?: RequirementEntry[]): Promise<void>;
|
|
92
|
+
export declare function appendCarriedRequirements(cwd: string, crossCutting: RequirementEntry[], unresolved?: RequirementEntry[], judgeFlagged?: string[]): Promise<void>;
|
|
84
93
|
/**
|
|
85
94
|
* The read-only block refine/compose receive when carried requirements exist.
|
|
86
95
|
* Verbatim content travels with every task (the directive pattern that works),
|
|
@@ -308,12 +308,21 @@ function formatEntry(e, marker) {
|
|
|
308
308
|
return `"${e.quote}"${anchor}${marker ? ` [${marker}]` : ''}`;
|
|
309
309
|
}
|
|
310
310
|
/**
|
|
311
|
-
* Append carried requirements
|
|
312
|
-
*
|
|
313
|
-
*
|
|
311
|
+
* Append carried requirements, deduped against what is stored. Three channels,
|
|
312
|
+
* each better carried into every task than silently lost — host-side only,
|
|
313
|
+
* children never write it, best-effort:
|
|
314
|
+
* • `crossCutting` — obligations no single task owns (policy/global rules).
|
|
315
|
+
* • `unresolved` — grounded requirements still unmapped after the retry rounds.
|
|
316
|
+
* • `judgeFlagged` — free-text areas the holistic coverage judge flagged as
|
|
317
|
+
* uncovered that requirement-extraction never captured as a tracked entry, so
|
|
318
|
+
* the grounded channels above are structurally blind to them (mx5 2026-07-16:
|
|
319
|
+
* §10's test-infra setup was seen ONLY by the judge and, having no carrier,
|
|
320
|
+
* was warned-about then dropped). These are plain strings, not quotes of the
|
|
321
|
+
* source; marked distinctly so a task can tell an inferred area from a verbatim
|
|
322
|
+
* obligation.
|
|
314
323
|
*/
|
|
315
|
-
export async function appendCarriedRequirements(cwd, crossCutting, unresolved = []) {
|
|
316
|
-
if (crossCutting.length === 0 && unresolved.length === 0)
|
|
324
|
+
export async function appendCarriedRequirements(cwd, crossCutting, unresolved = [], judgeFlagged = []) {
|
|
325
|
+
if (crossCutting.length === 0 && unresolved.length === 0 && judgeFlagged.length === 0)
|
|
317
326
|
return;
|
|
318
327
|
try {
|
|
319
328
|
const existing = (await readRequirements(cwd)).split('\n').filter(l => l.trim().length > 0);
|
|
@@ -324,7 +333,11 @@ export async function appendCarriedRequirements(cwd, crossCutting, unresolved =
|
|
|
324
333
|
const merged = [...existing];
|
|
325
334
|
for (const [entries, marker] of [
|
|
326
335
|
[crossCutting, undefined],
|
|
327
|
-
[unresolved, 'no task owns this — surfaced at plan time']
|
|
336
|
+
[unresolved, 'no task owns this — surfaced at plan time'],
|
|
337
|
+
[
|
|
338
|
+
judgeFlagged.map(q => ({ quote: q, anchor: '' })),
|
|
339
|
+
'judge-flagged uncovered area, no task owns this — surfaced at plan time'
|
|
340
|
+
]
|
|
328
341
|
]) {
|
|
329
342
|
for (const e of entries) {
|
|
330
343
|
const key = normalise(e.quote);
|
|
@@ -87,7 +87,7 @@ export interface GateDeps {
|
|
|
87
87
|
* at most once per gate sequence; not-applied falls through to the picker.
|
|
88
88
|
* Absent → the loop goes straight to recommend/picker as before.
|
|
89
89
|
*/
|
|
90
|
-
lintFix?: (ctx: ExtensionCommandContext, cwd: string, taskTitle: string, failReason: string) => Promise<{
|
|
90
|
+
lintFix?: (ctx: ExtensionCommandContext, cwd: string, taskTitle: string, taskId: string, failReason: string) => Promise<{
|
|
91
91
|
ok: boolean;
|
|
92
92
|
reason?: string;
|
|
93
93
|
}>;
|
package/dist/task/task-gates.js
CHANGED
|
@@ -98,7 +98,7 @@ export async function runGatesForTask(ctxIn, deps, p) {
|
|
|
98
98
|
if (!lintFixAttempted && deps.lintFix && failReason.startsWith('repo health:')) {
|
|
99
99
|
lintFixAttempted = true;
|
|
100
100
|
active.ui.notify(`${p.tag}: static findings on "${p.title}" — attempting bounded lint fix…`, 'info');
|
|
101
|
-
const fix = await deps.lintFix(active, p.cwd, p.title, failReason);
|
|
101
|
+
const fix = await deps.lintFix(active, p.cwd, p.title, p.taskId, failReason);
|
|
102
102
|
await rec(`lint-fix: ${fix.ok ?
|
|
103
103
|
`applied${fix.reason ? ` (${fix.reason})` : ''} — re-verifying`
|
|
104
104
|
: `not applied (${fix.reason ?? 'failed'})`}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mjasnikovs/pi-task",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.21",
|
|
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",
|
|
@@ -85,6 +85,6 @@
|
|
|
85
85
|
"extensions": [
|
|
86
86
|
"dist/index.js"
|
|
87
87
|
],
|
|
88
|
-
"image": "https://raw.githubusercontent.com/mjasnikovs/pi-task/main/assets/
|
|
88
|
+
"image": "https://raw.githubusercontent.com/mjasnikovs/pi-task/main/assets/hero.png"
|
|
89
89
|
}
|
|
90
90
|
}
|