@mjasnikovs/pi-task 0.14.22 → 0.15.1
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/config/register.js +6 -6
- package/dist/task/auto-commit.d.ts +19 -0
- package/dist/task/auto-commit.js +21 -0
- package/dist/task/auto-orchestrator.d.ts +17 -1
- package/dist/task/auto-orchestrator.js +69 -21
- package/dist/task/enforce-guidelines.d.ts +32 -1
- package/dist/task/enforce-guidelines.js +50 -2
- package/package.json +1 -1
package/dist/config/register.js
CHANGED
|
@@ -18,15 +18,15 @@ const ITEMS = [
|
|
|
18
18
|
label: 'orientation',
|
|
19
19
|
description: 'Pre-supply the project core (manifest, types, schema…) to the read-heavy research workers'
|
|
20
20
|
},
|
|
21
|
-
{
|
|
22
|
-
id: 'enforceGuidelines',
|
|
23
|
-
label: 'enforce guidelines',
|
|
24
|
-
description: 'Before each /task-auto commit, re-check the work against AGENTS.md/CLAUDE.md and fix drift'
|
|
25
|
-
},
|
|
26
21
|
{
|
|
27
22
|
id: 'verifyWork',
|
|
28
23
|
label: 'verify work',
|
|
29
|
-
description: "After each /task-auto task, RUN its spec's VERIFY block in the workspace and report a PASS/FAIL verdict"
|
|
24
|
+
description: "After each /task-auto task, RUN its spec's VERIFY block in the workspace and report a PASS/FAIL verdict (also the signal that lets 'enforce guidelines' fix safely)"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: 'enforceGuidelines',
|
|
28
|
+
label: 'enforce guidelines',
|
|
29
|
+
description: "Check each /task-auto commit against AGENTS.md/CLAUDE.md. Needs 'verify work' to FIX drift (fixes are reverted if they regress verification); without it, only reports violations"
|
|
30
30
|
}
|
|
31
31
|
];
|
|
32
32
|
function makeTheme(theme) {
|
|
@@ -18,3 +18,22 @@ export interface CommitResult {
|
|
|
18
18
|
* `{committed: false, reason}` so the caller can warn and keep going.
|
|
19
19
|
*/
|
|
20
20
|
export declare function gitCommitAll(cwd: string, message: string, signal?: AbortSignal, spawnFn?: SpawnFn): Promise<CommitResult>;
|
|
21
|
+
/**
|
|
22
|
+
* Drop the last commit, restoring the tree to its parent — the differential
|
|
23
|
+
* guard's "revert" when an `'edit'` enforcement pass regressed the verified task
|
|
24
|
+
* commit. The enforcement fixes are committed first (as `ENFORCE GUIDELINES`);
|
|
25
|
+
* when re-running verification against that commit reports a regression, this
|
|
26
|
+
* `git reset --hard HEAD~1` throws the enforce commit away and brings back the
|
|
27
|
+
* verified task commit underneath it.
|
|
28
|
+
*
|
|
29
|
+
* `reset --hard` is safe here precisely because it runs right after the enforce
|
|
30
|
+
* commit: the working tree is clean (everything was just committed), so there is
|
|
31
|
+
* no unrelated uncommitted work for it to destroy. HEAD~1 is the verified task
|
|
32
|
+
* commit. The enforcement child runs `read,edit` with no `write`, so the dropped
|
|
33
|
+
* commit contains only its in-place source edits — nothing else to preserve.
|
|
34
|
+
*
|
|
35
|
+
* Best-effort and never throws: a git failure is swallowed (the caller has
|
|
36
|
+
* already decided to keep the verified work; a failed reset only leaves the
|
|
37
|
+
* enforce commit in place, which is surfaced as a warning).
|
|
38
|
+
*/
|
|
39
|
+
export declare function gitDropLastCommit(cwd: string, signal?: AbortSignal, spawnFn?: SpawnFn): Promise<void>;
|
package/dist/task/auto-commit.js
CHANGED
|
@@ -54,3 +54,24 @@ export async function gitCommitAll(cwd, message, signal, spawnFn) {
|
|
|
54
54
|
}
|
|
55
55
|
return { committed: true };
|
|
56
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Drop the last commit, restoring the tree to its parent — the differential
|
|
59
|
+
* guard's "revert" when an `'edit'` enforcement pass regressed the verified task
|
|
60
|
+
* commit. The enforcement fixes are committed first (as `ENFORCE GUIDELINES`);
|
|
61
|
+
* when re-running verification against that commit reports a regression, this
|
|
62
|
+
* `git reset --hard HEAD~1` throws the enforce commit away and brings back the
|
|
63
|
+
* verified task commit underneath it.
|
|
64
|
+
*
|
|
65
|
+
* `reset --hard` is safe here precisely because it runs right after the enforce
|
|
66
|
+
* commit: the working tree is clean (everything was just committed), so there is
|
|
67
|
+
* no unrelated uncommitted work for it to destroy. HEAD~1 is the verified task
|
|
68
|
+
* commit. The enforcement child runs `read,edit` with no `write`, so the dropped
|
|
69
|
+
* commit contains only its in-place source edits — nothing else to preserve.
|
|
70
|
+
*
|
|
71
|
+
* Best-effort and never throws: a git failure is swallowed (the caller has
|
|
72
|
+
* already decided to keep the verified work; a failed reset only leaves the
|
|
73
|
+
* enforce commit in place, which is surfaced as a warning).
|
|
74
|
+
*/
|
|
75
|
+
export async function gitDropLastCommit(cwd, signal, spawnFn) {
|
|
76
|
+
await git(cwd, ['reset', '--hard', 'HEAD~1'], signal, spawnFn);
|
|
77
|
+
}
|
|
@@ -33,7 +33,14 @@ export interface AutoDeps {
|
|
|
33
33
|
* each task runs in a fresh session, so the captured ctx is stale by the time
|
|
34
34
|
* enforcement runs and using it for the loader widget throws "stale ctx".
|
|
35
35
|
*/
|
|
36
|
-
enforce?: (ctx: ExtensionCommandContext, cwd: string, taskTitle: string
|
|
36
|
+
enforce?: (ctx: ExtensionCommandContext, cwd: string, taskTitle: string,
|
|
37
|
+
/**
|
|
38
|
+
* `'edit'` (read,edit — fix in place) only when there is a clean
|
|
39
|
+
* verification signal to guard the edits against; otherwise `'flag'`
|
|
40
|
+
* (read-only, report don't fix). The loop picks the mode from whether the
|
|
41
|
+
* verify gate produced a genuine clean pass.
|
|
42
|
+
*/
|
|
43
|
+
mode: 'edit' | 'flag') => Promise<EnforceOutcome>;
|
|
37
44
|
/**
|
|
38
45
|
* Verify the just-finished task's work by RUNNING its composed spec's VERIFY
|
|
39
46
|
* block in the real workspace (a fresh child of the same local model, with a
|
|
@@ -53,6 +60,15 @@ export interface AutoDeps {
|
|
|
53
60
|
* taskId (to read the spec) and the gate's failure reason.
|
|
54
61
|
*/
|
|
55
62
|
recommend?: (ctx: ExtensionCommandContext, cwd: string, taskTitle: string, taskId: string, failReason: string) => Promise<ResolutionOutcome>;
|
|
63
|
+
/**
|
|
64
|
+
* Discard the working-tree edits an `'edit'` enforcement pass made, restoring
|
|
65
|
+
* the verified task commit. The differential guard calls this when re-running
|
|
66
|
+
* verification AFTER enforcement reports a regression: the guideline fixes are
|
|
67
|
+
* thrown away and the verified implementation is kept. Optional: absent in
|
|
68
|
+
* tests; the loop then skips the revert (and warns). Scoped to exclude the
|
|
69
|
+
* .pi-tasks bookkeeping so task front matter is not rolled back.
|
|
70
|
+
*/
|
|
71
|
+
revert?: (cwd: string) => Promise<void>;
|
|
56
72
|
}
|
|
57
73
|
/**
|
|
58
74
|
* Expand any @file references in the feature text by appending each referenced
|
|
@@ -14,7 +14,7 @@ import { AUTO_CLARIFY_PROMPT, AUTO_DECOMPOSE_PROMPT } from './auto-prompts.js';
|
|
|
14
14
|
import { isDuplicateQuestion, MAX_DUP_STRIKES, DUP_REPROMPT_HINT } from './question-dedup.js';
|
|
15
15
|
import { allocateAutoId, buildAutoBody, parseDecomposeList, parseTaskList, checkOffTask, stampTaskInProgress, findResumableAuto } from './auto-io.js';
|
|
16
16
|
import { writeTaskFile, readTaskFile, updateTaskFrontMatter, taskFilePath, tasksDir } from './task-io.js';
|
|
17
|
-
import { gitCommitAll } from './auto-commit.js';
|
|
17
|
+
import { gitCommitAll, gitDropLastCommit } from './auto-commit.js';
|
|
18
18
|
import { runGuidelineEnforcement, classifyEnforceChildFailure } from './enforce-guidelines.js';
|
|
19
19
|
import { runWorkVerification, extractSpecForVerification } from './verify-work.js';
|
|
20
20
|
import { researchResolution, resolutionOptions, classifyResolutionAnswer } from './verify-resolution.js';
|
|
@@ -446,13 +446,15 @@ function defaultDeps(ctx, cwd, signal, title) {
|
|
|
446
446
|
commit: (cwd2, message) => getConfig().autoCommit ?
|
|
447
447
|
gitCommitAll(cwd2, message, signal)
|
|
448
448
|
: Promise.resolve({ committed: false, reason: 'auto-commit disabled' }),
|
|
449
|
-
|
|
449
|
+
revert: cwd2 => gitDropLastCommit(cwd2, signal),
|
|
450
|
+
enforce: (enforceCtx, cwd2, taskTitle, mode) => {
|
|
450
451
|
if (!getConfig().enforceGuidelines) {
|
|
451
452
|
return Promise.resolve({ ok: true, reason: 'disabled' });
|
|
452
453
|
}
|
|
453
454
|
return runGuidelineEnforcement({
|
|
454
455
|
cwd: cwd2,
|
|
455
456
|
signal,
|
|
457
|
+
mode,
|
|
456
458
|
// Run the worker child UNGUARDED: no loop detector, no wall-clock
|
|
457
459
|
// timeout. This pass's job is to rework files in place until every
|
|
458
460
|
// violation is fixed, and it legitimately reads and edits the same
|
|
@@ -721,6 +723,14 @@ export async function runAutoLoop(ctx, cwd, id, deps) {
|
|
|
721
723
|
// /task-auto-resume re-runs it (rather than blessing broken work). Off
|
|
722
724
|
// by default (deps.verify returns a disabled no-op) and a no-op when the
|
|
723
725
|
// task has no composed spec to verify against.
|
|
726
|
+
//
|
|
727
|
+
// Whether this gate produced a GENUINE clean pass (a real signal ran
|
|
728
|
+
// and the work met it) also decides how the enforce pass below is
|
|
729
|
+
// allowed to behave: only a genuine pass gives a signal to revert
|
|
730
|
+
// against, so only then may enforce edit in place (see the enforce
|
|
731
|
+
// block). A no-op pass (no spec), a disabled gate, or a user
|
|
732
|
+
// accept-override leaves this false → enforce runs flag-only.
|
|
733
|
+
let verifyCleanPass = false;
|
|
724
734
|
if (deps.verify) {
|
|
725
735
|
active.ui.notify(`${id}: verifying "${next.title}"…`, 'info');
|
|
726
736
|
let verified = await deps.verify(active, cwd, next.title, res.taskId);
|
|
@@ -781,6 +791,10 @@ export async function runAutoLoop(ctx, cwd, id, deps) {
|
|
|
781
791
|
}
|
|
782
792
|
// Loop exited because the work verified OR the user accepted the
|
|
783
793
|
// artifact — either way fall through to check-off/commit/enforce.
|
|
794
|
+
// A genuine clean pass is ok===true with NO reason; a no-op pass
|
|
795
|
+
// ('no spec to verify') or an accept-override (verified.ok still
|
|
796
|
+
// false at break) does NOT count as a guardable signal.
|
|
797
|
+
verifyCleanPass = verified.ok && !verified.reason;
|
|
784
798
|
}
|
|
785
799
|
// res.ok === true means runner.run() completed, so res.taskId is the
|
|
786
800
|
// allocated TASK_NNNN id (never empty here). checkOffTask tolerates an
|
|
@@ -799,29 +813,63 @@ export async function runAutoLoop(ctx, cwd, id, deps) {
|
|
|
799
813
|
active.ui.notify(`${id}: not committed (${commit.reason ?? 'unknown'}) — continuing.`, 'warning');
|
|
800
814
|
}
|
|
801
815
|
// With the task committed, hold its work to the project's AGENTS.md /
|
|
802
|
-
// CLAUDE.md rules
|
|
803
|
-
//
|
|
804
|
-
//
|
|
805
|
-
//
|
|
806
|
-
//
|
|
807
|
-
//
|
|
808
|
-
//
|
|
809
|
-
//
|
|
810
|
-
//
|
|
811
|
-
//
|
|
816
|
+
// CLAUDE.md rules — but as a step INSIDE the validation gate, never the
|
|
817
|
+
// blind post-commit write pass it used to be. A bare read,edit enforce
|
|
818
|
+
// pass trashes working code: A/B-proven, it degraded a clean build in
|
|
819
|
+
// 4/5 runs while declaring CLEAN. So enforcement is now gated by the
|
|
820
|
+
// verify signal:
|
|
821
|
+
//
|
|
822
|
+
// - 'edit' mode (read,edit — fix in place) runs ONLY when the verify
|
|
823
|
+
// gate just produced a genuine clean pass. That pass is the
|
|
824
|
+
// differential SIGNAL: enforce commits its fixes, then the SAME
|
|
825
|
+
// signal is re-run against the enforced tree. A regression means the
|
|
826
|
+
// pass made the artifact worse than it found it — its commit is
|
|
827
|
+
// dropped and the verified implementation is kept (A/B: 5/5 clean).
|
|
828
|
+
// - 'flag' mode (read-only — report don't fix) runs when there is no
|
|
829
|
+
// such signal (verify off, no spec, or an accept-override): with
|
|
830
|
+
// nothing to revert against, the pass may not rewrite logic, only
|
|
831
|
+
// surface violations as a warning ("no signal ⇒ no license"; A/B:
|
|
832
|
+
// 5/5 clean, 5/5 caught the real violation).
|
|
833
|
+
//
|
|
834
|
+
// Skipped when nothing was committed this round (autoCommit off or an
|
|
835
|
+
// empty commit), when the feature is off, when there are no guideline
|
|
836
|
+
// files, or in tests with no enforce dep.
|
|
812
837
|
if (deps.enforce && commit.committed) {
|
|
813
|
-
|
|
814
|
-
|
|
838
|
+
const mode = verifyCleanPass ? 'edit' : 'flag';
|
|
839
|
+
active.ui.notify(mode === 'edit' ?
|
|
840
|
+
`${id}: enforcing AGENTS.md/CLAUDE.md on "${next.title}"…`
|
|
841
|
+
: `${id}: reviewing "${next.title}" against AGENTS.md/CLAUDE.md (no verify signal — report only)…`, 'info');
|
|
842
|
+
const verdict = await deps.enforce(active, cwd, next.title, mode);
|
|
815
843
|
if (!verdict.ok) {
|
|
816
|
-
active.ui.notify(`${id}: guideline enforcement on "${next.title}" — ${verdict.reason ?? 'not clean'} — continuing.`, 'warning');
|
|
844
|
+
active.ui.notify(`${id}: guideline ${mode === 'edit' ? 'enforcement' : 'review'} on "${next.title}" — ${verdict.reason ?? 'not clean'} — continuing.`, 'warning');
|
|
817
845
|
}
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
846
|
+
if (mode === 'edit') {
|
|
847
|
+
// Commit whatever the pass fixed as its own snapshot. A no-op
|
|
848
|
+
// when it made no edits (nothing to commit) — and then there is
|
|
849
|
+
// nothing to re-verify or revert, so a clean task skips the
|
|
850
|
+
// second verify entirely.
|
|
851
|
+
const enforceCommit = await deps.commit(cwd, `ENFORCE GUIDELINES: ${next.title} (${res.taskId})`);
|
|
852
|
+
if (enforceCommit.committed) {
|
|
853
|
+
// Differential guard: re-run the verify signal against the
|
|
854
|
+
// enforced tree. verifyCleanPass implies deps.verify exists,
|
|
855
|
+
// so this is a real check. A regression ⇒ drop the enforce
|
|
856
|
+
// commit, keep the verified task commit underneath it.
|
|
857
|
+
const after = deps.verify ?
|
|
858
|
+
await deps.verify(active, cwd, next.title, res.taskId)
|
|
859
|
+
: { ok: true };
|
|
860
|
+
if (!after.ok) {
|
|
861
|
+
// Regression: drop the enforce commit if we can, and
|
|
862
|
+
// never report it as a kept fix.
|
|
863
|
+
if (deps.revert)
|
|
864
|
+
await deps.revert(cwd);
|
|
865
|
+
active.ui.notify(`${id}: guideline fixes regressed verification on "${next.title}" (${(after.reason ?? 'now fails').slice(0, 120)}) — ${deps.revert ? 'reverted them, kept the verified work' : 'left in place (no revert available)'}.`, 'warning');
|
|
866
|
+
}
|
|
867
|
+
else {
|
|
868
|
+
active.ui.notify(`${id}: committed guideline fixes for "${next.title}".`, 'info');
|
|
869
|
+
}
|
|
870
|
+
}
|
|
824
871
|
}
|
|
872
|
+
// 'flag' mode makes no edits — nothing to commit or revert.
|
|
825
873
|
}
|
|
826
874
|
}
|
|
827
875
|
}
|
|
@@ -21,6 +21,20 @@ export declare const GUIDELINE_FILENAMES: readonly ["AGENTS.md", "CLAUDE.md"];
|
|
|
21
21
|
* (the prompt scopes them) but is a soft instruction for READS.
|
|
22
22
|
*/
|
|
23
23
|
declare const ENFORCE_TOOLS = "read,edit";
|
|
24
|
+
/**
|
|
25
|
+
* Flag-only enforcement gets exactly ONE tool: `read`. It can inspect the changed
|
|
26
|
+
* files and report violations but CANNOT edit, create, or run anything.
|
|
27
|
+
*
|
|
28
|
+
* This is the "no signal ⇒ no license to rewrite logic" half of the gate. When a
|
|
29
|
+
* task ships no behavioral verification to guard a destructive edit (no runnable
|
|
30
|
+
* VERIFY, or the verify gate did not produce a genuine clean pass), letting the
|
|
31
|
+
* weak model rewrite working code is exactly what trashes the build — A/B-proven:
|
|
32
|
+
* with `read,edit` and no guard the model degraded a clean tree in 4/5 runs while
|
|
33
|
+
* declaring CLEAN. Demoted to `read`, it cannot trash anything (5/5 clean) and
|
|
34
|
+
* still names the real violation (5/5). So with no signal to revert against, the
|
|
35
|
+
* pass reports the violation as a warning instead of editing.
|
|
36
|
+
*/
|
|
37
|
+
declare const ENFORCE_FLAG_TOOLS = "read";
|
|
24
38
|
export interface GuidelineDoc {
|
|
25
39
|
/** Filenames found, in discovery order (e.g. ['AGENTS.md', 'CLAUDE.md']). */
|
|
26
40
|
files: string[];
|
|
@@ -48,6 +62,13 @@ export declare function discoverGuidelines(cwd: string, readFile?: (p: string) =
|
|
|
48
62
|
* is unit-tested without spawning pi.
|
|
49
63
|
*/
|
|
50
64
|
export declare function buildEnforcePrompt(rulesText: string, diff: string): string;
|
|
65
|
+
/**
|
|
66
|
+
* Build the FLAG-ONLY enforcement prompt: same rules + diff, but the child has a
|
|
67
|
+
* `read` tool only and is told to REPORT violations, not fix them. Kept pure so
|
|
68
|
+
* the wording is unit-tested without spawning pi. Used when there is no
|
|
69
|
+
* verification signal to guard a destructive edit (see ENFORCE_FLAG_TOOLS).
|
|
70
|
+
*/
|
|
71
|
+
export declare function buildEnforceFlagPrompt(rulesText: string, diff: string): string;
|
|
51
72
|
/**
|
|
52
73
|
* Parse the child's final verdict. Scans for the LAST `ENFORCE: CLEAN` /
|
|
53
74
|
* `ENFORCE: VIOLATION` marker (the model may discuss before concluding).
|
|
@@ -115,6 +136,16 @@ export declare function captureCommitDiff(cwd: string, signal?: AbortSignal, spa
|
|
|
115
136
|
export interface EnforcementDeps {
|
|
116
137
|
cwd: string;
|
|
117
138
|
signal?: AbortSignal;
|
|
139
|
+
/**
|
|
140
|
+
* Which capability the pass runs with.
|
|
141
|
+
* - `'edit'` (default): `read,edit` — the model fixes violations in place. The
|
|
142
|
+
* caller is responsible for guarding those edits with a differential
|
|
143
|
+
* verification check (revert on regression), because a bare edit pass
|
|
144
|
+
* trashes working code (A/B: 4/5).
|
|
145
|
+
* - `'flag'`: `read` only — the model reports violations but cannot touch the
|
|
146
|
+
* tree. Used when there is no verification signal to guard an edit against.
|
|
147
|
+
*/
|
|
148
|
+
mode?: 'edit' | 'flag';
|
|
118
149
|
/** Defaults to the real cwd-only file discovery. */
|
|
119
150
|
discover?: (cwd: string) => Promise<GuidelineDoc | null>;
|
|
120
151
|
/** Defaults to the real git diff capture. */
|
|
@@ -130,4 +161,4 @@ export interface EnforcementDeps {
|
|
|
130
161
|
* blocking outcome (ok: false) so an unverifiable task is not committed.
|
|
131
162
|
*/
|
|
132
163
|
export declare function runGuidelineEnforcement(deps: EnforcementDeps): Promise<EnforceOutcome>;
|
|
133
|
-
export { ENFORCE_TOOLS };
|
|
164
|
+
export { ENFORCE_TOOLS, ENFORCE_FLAG_TOOLS };
|
|
@@ -48,6 +48,20 @@ export const GUIDELINE_FILENAMES = ['AGENTS.md', 'CLAUDE.md'];
|
|
|
48
48
|
* (the prompt scopes them) but is a soft instruction for READS.
|
|
49
49
|
*/
|
|
50
50
|
const ENFORCE_TOOLS = 'read,edit';
|
|
51
|
+
/**
|
|
52
|
+
* Flag-only enforcement gets exactly ONE tool: `read`. It can inspect the changed
|
|
53
|
+
* files and report violations but CANNOT edit, create, or run anything.
|
|
54
|
+
*
|
|
55
|
+
* This is the "no signal ⇒ no license to rewrite logic" half of the gate. When a
|
|
56
|
+
* task ships no behavioral verification to guard a destructive edit (no runnable
|
|
57
|
+
* VERIFY, or the verify gate did not produce a genuine clean pass), letting the
|
|
58
|
+
* weak model rewrite working code is exactly what trashes the build — A/B-proven:
|
|
59
|
+
* with `read,edit` and no guard the model degraded a clean tree in 4/5 runs while
|
|
60
|
+
* declaring CLEAN. Demoted to `read`, it cannot trash anything (5/5 clean) and
|
|
61
|
+
* still names the real violation (5/5). So with no signal to revert against, the
|
|
62
|
+
* pass reports the violation as a warning instead of editing.
|
|
63
|
+
*/
|
|
64
|
+
const ENFORCE_FLAG_TOOLS = 'read';
|
|
51
65
|
/**
|
|
52
66
|
* Read the guideline files that live directly in `cwd` (AGENTS.md, CLAUDE.md).
|
|
53
67
|
* Returns null when none exist or all are empty — the caller treats that as a
|
|
@@ -110,6 +124,37 @@ export function buildEnforcePrompt(rulesText, diff) {
|
|
|
110
124
|
'Output the verdict line verbatim — it is parsed mechanically.'
|
|
111
125
|
].join('\n');
|
|
112
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* Build the FLAG-ONLY enforcement prompt: same rules + diff, but the child has a
|
|
129
|
+
* `read` tool only and is told to REPORT violations, not fix them. Kept pure so
|
|
130
|
+
* the wording is unit-tested without spawning pi. Used when there is no
|
|
131
|
+
* verification signal to guard a destructive edit (see ENFORCE_FLAG_TOOLS).
|
|
132
|
+
*/
|
|
133
|
+
export function buildEnforceFlagPrompt(rulesText, diff) {
|
|
134
|
+
return [
|
|
135
|
+
'You are a strict guideline-enforcement REVIEW pass running right after an AI',
|
|
136
|
+
'coding agent finished a task and committed it. The agent is known to skip',
|
|
137
|
+
'project rules, so do not trust that it followed them — verify against the',
|
|
138
|
+
'changes.',
|
|
139
|
+
'',
|
|
140
|
+
'You have a `read` tool — and NOTHING else. You CANNOT edit, create, or run',
|
|
141
|
+
'anything. Your job is to REVIEW and REPORT, not to fix.',
|
|
142
|
+
'',
|
|
143
|
+
'PROJECT GUIDELINES (from this repository):',
|
|
144
|
+
rulesText,
|
|
145
|
+
'',
|
|
146
|
+
'CHANGES IN THE LAST COMMIT (review these specifically against the rules):',
|
|
147
|
+
diff.trim().length > 0 ? diff : '(no textual diff captured — nothing to verify)',
|
|
148
|
+
'',
|
|
149
|
+
'Read each changed file and check it against EVERY rule above. Do NOT attempt',
|
|
150
|
+
'to fix anything — only report what you find.',
|
|
151
|
+
'',
|
|
152
|
+
'When you are done, output EXACTLY ONE of these as the final line:',
|
|
153
|
+
' ENFORCE: CLEAN (the changes comply with every rule)',
|
|
154
|
+
' ENFORCE: VIOLATION <text> (a rule is violated; say which — do not fix it)',
|
|
155
|
+
'Output the verdict line verbatim — it is parsed mechanically.'
|
|
156
|
+
].join('\n');
|
|
157
|
+
}
|
|
113
158
|
/**
|
|
114
159
|
* Parse the child's final verdict. Scans for the LAST `ENFORCE: CLEAN` /
|
|
115
160
|
* `ENFORCE: VIOLATION` marker (the model may discuss before concluding).
|
|
@@ -228,9 +273,12 @@ export async function runGuidelineEnforcement(deps) {
|
|
|
228
273
|
if (!doc)
|
|
229
274
|
return { ok: true, reason: 'no guideline files' };
|
|
230
275
|
const diff = await getDiff(deps.cwd, deps.signal);
|
|
276
|
+
const flagOnly = deps.mode === 'flag';
|
|
277
|
+
const tools = flagOnly ? ENFORCE_FLAG_TOOLS : ENFORCE_TOOLS;
|
|
278
|
+
const prompt = flagOnly ? buildEnforceFlagPrompt(doc.text, diff) : buildEnforcePrompt(doc.text, diff);
|
|
231
279
|
let text;
|
|
232
280
|
try {
|
|
233
|
-
text = await deps.runChild(
|
|
281
|
+
text = await deps.runChild(tools, prompt, deps.signal);
|
|
234
282
|
}
|
|
235
283
|
catch (err) {
|
|
236
284
|
// A user cancellation is not an enforcement failure — re-throw it so the
|
|
@@ -246,4 +294,4 @@ export async function runGuidelineEnforcement(deps) {
|
|
|
246
294
|
return { ok: true };
|
|
247
295
|
return { ok: false, reason: `guideline violation: ${verdict.detail}` };
|
|
248
296
|
}
|
|
249
|
-
export { ENFORCE_TOOLS };
|
|
297
|
+
export { ENFORCE_TOOLS, ENFORCE_FLAG_TOOLS };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mjasnikovs/pi-task",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.1",
|
|
4
4
|
"description": "Deterministic spec-orchestration for local models, with a bundled real-time remote web view and web/docs/fetch/worker subagent tools.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|