pi-goal-list-loop-audit 0.24.1 → 0.24.3
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 +2 -0
- package/extensions/goal-loop-auditor.ts +17 -9
- package/extensions/goal-loop-core.ts +16 -1
- package/extensions/goal-loop-forever.ts +30 -0
- package/extensions/goal-loop-shield.ts +18 -0
- package/extensions/loops/goal.ts +110 -3
- package/package.json +1 -1
- package/prompts/goal-loop-forever-metricless.md +2 -0
- package/prompts/goal-loop-forever.md +2 -0
package/README.md
CHANGED
|
@@ -60,6 +60,7 @@ matches `/list show`.
|
|
|
60
60
|
/list cancel # stop the whole list: abort the active item + drop all waiting
|
|
61
61
|
/loop # draft the loop (agent grills; measure is test-run before you confirm)
|
|
62
62
|
/loop start "keep polishing the UI" # infinite metricless loop (v0.23.6): no plateau, no cap — ends at time=/tokens= or /loop stop
|
|
63
|
+
/loop respec # infinite metricless loop reconciling the codebase against the root SPEC.md / spec.md (v0.24.3)
|
|
63
64
|
/loop start "reduce TODOs" measure="grep -c TODO src.txt | head -1" direction=min
|
|
64
65
|
/loop start "shrink the bundle" measure="..." direction=min time=4 tokens=500000 # arbitrary bounds
|
|
65
66
|
/loop start "reduce TODOs" measure="..." direction=min branch=1 # scratch-branch mode
|
|
@@ -199,6 +200,7 @@ No external watchdog plugin needed.
|
|
|
199
200
|
/glla tokenlimit=0 # explicitly no cap (the default)
|
|
200
201
|
/glla wedgealert=30 # hung-command alert minutes (default: 30, 0 = off)
|
|
201
202
|
/glla autoresume=on # held goals/loops auto-resume in fresh sessions (unattended rigs)
|
|
203
|
+
/glla auditcap=5 # pause the goal after N consecutive auditor disapprovals (default 3, 0 = unlimited)
|
|
202
204
|
/glla autoaccept=on # drafts ACTIVATE without the Confirm dialog (unattended rigs)
|
|
203
205
|
/glla project tokenlimit=500 # rare per-project override
|
|
204
206
|
```
|
|
@@ -34,6 +34,9 @@ import { AUDITOR_STALL_MS } from "./goal-loop-backoff.js";
|
|
|
34
34
|
export interface GoalAuditorResult {
|
|
35
35
|
approved: boolean;
|
|
36
36
|
disapproved: boolean;
|
|
37
|
+
/** v0.24.2: third verdict — the goal can NEVER be satisfied as stated. */
|
|
38
|
+
impossible?: boolean;
|
|
39
|
+
impossibleReason?: string;
|
|
37
40
|
output: string;
|
|
38
41
|
model: string;
|
|
39
42
|
thinkingLevel?: ThinkingLevel;
|
|
@@ -107,6 +110,8 @@ function buildGoalAuditorPrompt(goal: Goal, completionSummary: string | null | u
|
|
|
107
110
|
"Return a concise audit report. The final line MUST be exactly one of:",
|
|
108
111
|
"<approved/>",
|
|
109
112
|
"<disapproved/>",
|
|
113
|
+
"<impossible>one-line reason</impossible>",
|
|
114
|
+
"Use <impossible> ONLY when the objective can NEVER be satisfied as stated — contradictory requirements, a premise that is factually wrong, or resources the agent can never obtain. Incomplete or shoddy work is <disapproved/>, not impossible.",
|
|
110
115
|
"",
|
|
111
116
|
"Goal markdown (full state):",
|
|
112
117
|
"<goal>",
|
|
@@ -149,7 +154,7 @@ function buildGoalAuditorPrompt(goal: Goal, completionSummary: string | null | u
|
|
|
149
154
|
? ["4. Verify that the executor has satisfied every item in the <verification_contract>. If any item is missing or weakly addressed, disapprove."]
|
|
150
155
|
: []),
|
|
151
156
|
"5. Explain missing or weak evidence, especially scaffold-vs-final quality gaps.",
|
|
152
|
-
"6. End with exactly <approved/> only if the objective is truly complete; otherwise end with exactly <disapproved/>.",
|
|
157
|
+
"6. End with exactly <approved/> only if the objective is truly complete; <impossible>reason</impossible> if it can never be satisfied as stated; otherwise end with exactly <disapproved/>.",
|
|
153
158
|
...(goal.verificationContract?.trim()
|
|
154
159
|
? [
|
|
155
160
|
"",
|
|
@@ -175,8 +180,8 @@ function buildGoalAuditorPrompt(goal: Goal, completionSummary: string | null | u
|
|
|
175
180
|
|
|
176
181
|
// regression_shield lives in goal-loop-shield.ts (dependency-free, so unit
|
|
177
182
|
// tests can import it without pulling in pi). Re-exported for callers.
|
|
178
|
-
export { checkRegressionShield, contractItems, type RegressionShieldResult } from "./goal-loop-shield.js";
|
|
179
|
-
import { checkRegressionShield } from "./goal-loop-shield.js";
|
|
183
|
+
export { checkRegressionShield, contractItems, parseAuditorVerdict, type RegressionShieldResult } from "./goal-loop-shield.js";
|
|
184
|
+
import { checkRegressionShield, parseAuditorVerdict } from "./goal-loop-shield.js";
|
|
180
185
|
|
|
181
186
|
// =================================================================
|
|
182
187
|
// Auditor entry point
|
|
@@ -354,18 +359,19 @@ export async function runGoalCompletionAuditor(args: {
|
|
|
354
359
|
};
|
|
355
360
|
}
|
|
356
361
|
|
|
357
|
-
const
|
|
358
|
-
const approved =
|
|
359
|
-
const disapproved =
|
|
362
|
+
const parsed = parseAuditorVerdict(outputParts.join("\n\n"));
|
|
363
|
+
const approved = parsed.approved;
|
|
364
|
+
const disapproved = parsed.disapproved;
|
|
365
|
+
const impossible = parsed.impossible;
|
|
360
366
|
|
|
361
|
-
if (!approved && !disapproved) {
|
|
367
|
+
if (!approved && !disapproved && !impossible) {
|
|
362
368
|
return {
|
|
363
369
|
approved: false,
|
|
364
370
|
disapproved: false,
|
|
365
371
|
output,
|
|
366
372
|
model: modelLabel(model),
|
|
367
373
|
thinkingLevel,
|
|
368
|
-
error: `Auditor produced no verdict marker (<approved/>/<disapproved
|
|
374
|
+
error: `Auditor produced no verdict marker (<approved/>/<disapproved/>/<impossible>)${streamError ? ` — stream error: ${streamError}` : ""}. Treating as an error, not a verdict.`,
|
|
369
375
|
};
|
|
370
376
|
}
|
|
371
377
|
|
|
@@ -407,6 +413,8 @@ export async function runGoalCompletionAuditor(args: {
|
|
|
407
413
|
return {
|
|
408
414
|
approved,
|
|
409
415
|
disapproved,
|
|
416
|
+
impossible,
|
|
417
|
+
impossibleReason: parsed.impossibleReason,
|
|
410
418
|
output,
|
|
411
419
|
model: modelLabel(model),
|
|
412
420
|
thinkingLevel,
|
|
@@ -416,7 +424,7 @@ export async function runGoalCompletionAuditor(args: {
|
|
|
416
424
|
|
|
417
425
|
progress.phase = "complete";
|
|
418
426
|
emitProgress();
|
|
419
|
-
return { approved, disapproved, output, model: modelLabel(model), thinkingLevel };
|
|
427
|
+
return { approved, disapproved, impossible, impossibleReason: parsed.impossibleReason, output, model: modelLabel(model), thinkingLevel };
|
|
420
428
|
} catch (err) {
|
|
421
429
|
// v0.11.1 (audit critical): a runtime exception is INFRASTRUCTURE, never
|
|
422
430
|
// a verdict. The three-way split identifies infra by `error &&
|
|
@@ -88,6 +88,9 @@ export interface AuditVerdict {
|
|
|
88
88
|
at: string;
|
|
89
89
|
approved: boolean;
|
|
90
90
|
disapproved: boolean;
|
|
91
|
+
/** v0.24.2: the auditor's third verdict — the goal can NEVER be satisfied as stated. */
|
|
92
|
+
impossible?: boolean;
|
|
93
|
+
impossibleReason?: string;
|
|
91
94
|
model: string;
|
|
92
95
|
thinkingLevel?: string;
|
|
93
96
|
report?: string;
|
|
@@ -368,6 +371,18 @@ export interface State {
|
|
|
368
371
|
loop?: import("./goal-loop-forever.js").LoopState;
|
|
369
372
|
}
|
|
370
373
|
|
|
374
|
+
/** v0.24.2: count TRAILING consecutive disapprovals (the disapproval-cap
|
|
375
|
+
* input). Shield-blocks (approved:true) and infra errors (neither flag)
|
|
376
|
+
* break the streak — they are not verdicts on the work. */
|
|
377
|
+
export function countTrailingDisapprovals(history: AuditVerdict[]): number {
|
|
378
|
+
let n = 0;
|
|
379
|
+
for (let i = history.length - 1; i >= 0; i--) {
|
|
380
|
+
if (history[i]!.disapproved) n++;
|
|
381
|
+
else break;
|
|
382
|
+
}
|
|
383
|
+
return n;
|
|
384
|
+
}
|
|
385
|
+
|
|
371
386
|
/** Default per-goal token budget (v0.9.7): a runaway threshold, not a
|
|
372
387
|
* "big goal" threshold — real research/feature goals legitimately burn 2-4M.
|
|
373
388
|
* Loop 3 doesn't rely on this cap (it has max-iterations + plateau brakes). */
|
|
@@ -499,7 +514,7 @@ export function renderGoalMarkdown(goal: Goal): string {
|
|
|
499
514
|
lines.push("## Audit history");
|
|
500
515
|
lines.push("");
|
|
501
516
|
for (const v of goal.auditHistory) {
|
|
502
|
-
lines.push(`- ${v.at} — ${v.approved ? "approved" : "disapproved"} — \`${v.model}\``);
|
|
517
|
+
lines.push(`- ${v.at} — ${v.approved ? "approved" : v.impossible ? "impossible" : "disapproved"} — \`${v.model}\``);
|
|
503
518
|
}
|
|
504
519
|
lines.push("");
|
|
505
520
|
}
|
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
* self-reports progress.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
import { existsSync, statSync } from "node:fs";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
|
|
13
16
|
export type LoopDirection = "min" | "max";
|
|
14
17
|
|
|
15
18
|
export interface LoopMeasure {
|
|
@@ -293,3 +296,30 @@ export function parseLoopStartArgs(raw: string): {
|
|
|
293
296
|
tokenBudget: Number.isFinite(tokensRaw) && tokensRaw > 0 ? tokensRaw : undefined,
|
|
294
297
|
};
|
|
295
298
|
}
|
|
299
|
+
|
|
300
|
+
// ---- /loop respec (v0.24.3) ----
|
|
301
|
+
|
|
302
|
+
/** Root-only spec candidates, in priority order. No fuzzy search. */
|
|
303
|
+
export const RESPEC_SPEC_CANDIDATES = ["SPEC.md", "spec.md"] as const;
|
|
304
|
+
|
|
305
|
+
/** Resolve the project spec in the root only; null when absent. */
|
|
306
|
+
export function resolveSpecFile(cwd: string): string | null {
|
|
307
|
+
for (const name of RESPEC_SPEC_CANDIDATES) {
|
|
308
|
+
const p = join(cwd, name);
|
|
309
|
+
try {
|
|
310
|
+
if (existsSync(p) && statSync(p).isFile()) return p;
|
|
311
|
+
} catch { /* unreadable — keep looking */ }
|
|
312
|
+
}
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* The respec target. The spec is DATA, not gospel: the loop reconciles code
|
|
318
|
+
* against it but reports stale/contradictory requirements instead of forcing
|
|
319
|
+
* the code to match a bad spec. Rotation keeps it honest: implement one
|
|
320
|
+
* iteration, audit the next (the doorknob failure is implementing nothing
|
|
321
|
+
* while claiming polish).
|
|
322
|
+
*/
|
|
323
|
+
export function respecTarget(specName: string): string {
|
|
324
|
+
return `Reconcile the codebase against ${specName} (the project spec in the root). Read the spec critically first: if a requirement is stale, contradictory, or wrong for the current codebase, report the discrepancy and move on — never force the code to match a bad spec. Otherwise pick the next gap between spec and code and close it. Rotate: one iteration implements a missing or outdated spec item, the next audits something already "implemented" against the spec and fixes what drifted.`;
|
|
325
|
+
}
|
|
@@ -94,3 +94,21 @@ export function checkRegressionShield(report: string, contract: string): Regress
|
|
|
94
94
|
hasEvidenceBlock,
|
|
95
95
|
};
|
|
96
96
|
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* v0.24.2: pure auditor-verdict parser (approved / disapproved / impossible).
|
|
100
|
+
* Lives here (not goal-loop-auditor.ts) so tests can import it without
|
|
101
|
+
* dragging in the auditor's relative .js imports. The verdict is read from
|
|
102
|
+
* the last output block that mentions any verdict tag.
|
|
103
|
+
*/
|
|
104
|
+
export function parseAuditorVerdict(output: string): { approved: boolean; disapproved: boolean; impossible: boolean; impossibleReason?: string } {
|
|
105
|
+
const parts = output.split("\n\n");
|
|
106
|
+
const lastAssistant = [...parts].reverse().find((t) => /<\/?(approved|disapproved|impossible)[ />]/i.test(t)) ?? output;
|
|
107
|
+
const impossibleMatch = /<impossible>([\s\S]*?)<\/impossible>/i.exec(lastAssistant);
|
|
108
|
+
return {
|
|
109
|
+
approved: /<approved\/>/i.test(lastAssistant),
|
|
110
|
+
disapproved: /<disapproved\/>/i.test(lastAssistant),
|
|
111
|
+
impossible: impossibleMatch !== null,
|
|
112
|
+
impossibleReason: impossibleMatch?.[1]?.trim().slice(0, 300) || undefined,
|
|
113
|
+
};
|
|
114
|
+
}
|
package/extensions/loops/goal.ts
CHANGED
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
LIST_DRAFTING_BLOCK_MESSAGE,
|
|
41
41
|
sumNewAssistantTokens,
|
|
42
42
|
takeAt,
|
|
43
|
+
countTrailingDisapprovals,
|
|
43
44
|
goalArgsNeedDrafting,
|
|
44
45
|
buildSeedGrillMessage,
|
|
45
46
|
askUserQuestionAnswered,
|
|
@@ -80,6 +81,10 @@ import {
|
|
|
80
81
|
loopBranchName,
|
|
81
82
|
parseLoopStartArgs,
|
|
82
83
|
parseMetric,
|
|
84
|
+
LOOP_DEFAULTS,
|
|
85
|
+
RESPEC_SPEC_CANDIDATES,
|
|
86
|
+
resolveSpecFile,
|
|
87
|
+
respecTarget,
|
|
83
88
|
type LoopState,
|
|
84
89
|
} from "../goal-loop-forever.js";
|
|
85
90
|
import {
|
|
@@ -1407,6 +1412,40 @@ async function cmdLoop(args: string, ctx: ExtensionContext): Promise<void> {
|
|
|
1407
1412
|
return;
|
|
1408
1413
|
}
|
|
1409
1414
|
|
|
1415
|
+
if (sub === "respec") {
|
|
1416
|
+
// v0.24.3: reconcile the codebase against the root spec, forever.
|
|
1417
|
+
// Same auto-start path as /loop start (the user typed the command —
|
|
1418
|
+
// that IS the act); metricless + unbounded by design. No limit-nagging:
|
|
1419
|
+
// bounds exist on /loop start for whoever wants them.
|
|
1420
|
+
if (state.goal && state.goal.status === "active") {
|
|
1421
|
+
ctx.ui.notify("A goal is active — /goal cancel or /goal pause it before starting a loop.", "warning");
|
|
1422
|
+
return;
|
|
1423
|
+
}
|
|
1424
|
+
if (isLoopActive()) {
|
|
1425
|
+
ctx.ui.notify("A loop is already active. /loop stop first.", "warning");
|
|
1426
|
+
return;
|
|
1427
|
+
}
|
|
1428
|
+
const specPath = resolveSpecFile(ctx.cwd);
|
|
1429
|
+
if (!specPath) {
|
|
1430
|
+
ctx.ui.notify(
|
|
1431
|
+
`/loop respec: no spec in the project root (looked for ${RESPEC_SPEC_CANDIDATES.join(", ")}). Create one, or use /loop start "<target>" directly.`,
|
|
1432
|
+
"warning",
|
|
1433
|
+
);
|
|
1434
|
+
return;
|
|
1435
|
+
}
|
|
1436
|
+
const target = respecTarget(path.basename(specPath));
|
|
1437
|
+
await startLoopFromConfig(ctx, {
|
|
1438
|
+
target,
|
|
1439
|
+
measureCmd: "",
|
|
1440
|
+
direction: undefined,
|
|
1441
|
+
plateauWindow: LOOP_DEFAULTS.plateauWindow,
|
|
1442
|
+
maxIterations: 0,
|
|
1443
|
+
branch: false,
|
|
1444
|
+
force: false,
|
|
1445
|
+
});
|
|
1446
|
+
return;
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1410
1449
|
// Anything else is a natural-language target (v0.22.4): draft it — the
|
|
1411
1450
|
// metric is the whole game for a loop, and /loop start with full params
|
|
1412
1451
|
// is the skip-drafting path. Previously this fell through to a usage
|
|
@@ -1477,6 +1516,8 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
1477
1516
|
at: nowIso(),
|
|
1478
1517
|
approved: result.approved,
|
|
1479
1518
|
disapproved: result.disapproved,
|
|
1519
|
+
impossible: result.impossible,
|
|
1520
|
+
impossibleReason: result.impossibleReason,
|
|
1480
1521
|
model: result.model,
|
|
1481
1522
|
thinkingLevel: result.thinkingLevel,
|
|
1482
1523
|
report: result.output,
|
|
@@ -1521,6 +1562,30 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
1521
1562
|
return { content: [{ type: "text", text: `Goal approved by auditor ${result.model}.` }], details: {} };
|
|
1522
1563
|
}
|
|
1523
1564
|
|
|
1565
|
+
// IMPOSSIBLE (v0.24.2, Claude-Code lesson): the auditor's escape hatch
|
|
1566
|
+
// for goals that can NEVER be satisfied as stated. Not a disapproval —
|
|
1567
|
+
// continuing would burn tokens on a provably unwinnable objective.
|
|
1568
|
+
// Bounded and surfaced: the goal pauses and the user decides.
|
|
1569
|
+
if (result.impossible) {
|
|
1570
|
+
const reason = result.impossibleReason || "(no reason given)";
|
|
1571
|
+
updateGoal({
|
|
1572
|
+
status: "paused",
|
|
1573
|
+
auditHistory: history,
|
|
1574
|
+
pauseReason: `auditor verdict: IMPOSSIBLE — ${reason}`,
|
|
1575
|
+
pauseSuggestedAction: "The auditor says this goal can never be satisfied as stated. /goal tweak the objective (or /goal cancel), then /goal resume.",
|
|
1576
|
+
}, ctx);
|
|
1577
|
+
ctx.ui.notify(`Auditor: goal IMPOSSIBLE — ${reason}. Goal paused; /goal tweak or /goal cancel, then /goal resume.`, "warning");
|
|
1578
|
+
appendLedger(ctx.cwd, "goal_paused", { reason: `auditor impossible: ${reason}` });
|
|
1579
|
+
notifyExternal(ctx, `Goal paused (auditor: impossible): ${reason.slice(0, 120)}`);
|
|
1580
|
+
return {
|
|
1581
|
+
content: [{
|
|
1582
|
+
type: "text",
|
|
1583
|
+
text: `The auditor's verdict is IMPOSSIBLE: ${reason}\n\nThis is not a disapproval — the auditor says the objective can never be satisfied as stated. The goal is now PAUSED. Do not call complete_goal again. Report the verdict to the user and suggest /goal tweak (narrow or correct the objective) or /goal cancel.`,
|
|
1584
|
+
}],
|
|
1585
|
+
details: {},
|
|
1586
|
+
};
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1524
1589
|
// THREE-WAY SPLIT (v0.9.9): infrastructure failure is NOT a verdict.
|
|
1525
1590
|
// The wild-caught case: 6 silent "disapprovals" that were really a dead
|
|
1526
1591
|
// auditor model. The agent must be able to tell the difference.
|
|
@@ -1567,6 +1632,30 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
1567
1632
|
const noContractHint = state.goal.verificationContract?.trim()
|
|
1568
1633
|
? ""
|
|
1569
1634
|
: "\n\nNote: this goal has no verification contract, so the auditor inferred done-criteria from the objective text. For sharper verdicts, /goal tweak the objective to add a 'Done when: ...' clause.";
|
|
1635
|
+
// v0.24.2 (Claude-Code lesson — their stop-hook blocks cap at 8): a
|
|
1636
|
+
// goal the auditor can NEVER approve used to re-continue forever.
|
|
1637
|
+
// auditCap consecutive disapprovals → pause + notify, bounded and
|
|
1638
|
+
// surfaced like every other stop in this stack.
|
|
1639
|
+
const auditCap = loadSettings(ctx.cwd).auditCap ?? 3;
|
|
1640
|
+
const trailingDisapprovals = countTrailingDisapprovals(history);
|
|
1641
|
+
if (auditCap > 0 && trailingDisapprovals >= auditCap) {
|
|
1642
|
+
updateGoal({
|
|
1643
|
+
status: "paused",
|
|
1644
|
+
auditHistory: history,
|
|
1645
|
+
pauseReason: `auditor disapproved ${trailingDisapprovals}× consecutively (cap ${auditCap})`,
|
|
1646
|
+
pauseSuggestedAction: "Read the audit history (/goal status), fix the actual gap or /goal tweak the objective, then /goal resume. Raise the cap with /glla auditcap=N.",
|
|
1647
|
+
}, ctx);
|
|
1648
|
+
ctx.ui.notify(`Goal paused: auditor disapproved ${trailingDisapprovals}× consecutively (cap ${auditCap}). /goal status for the reports; /goal resume to continue.`, "warning");
|
|
1649
|
+
appendLedger(ctx.cwd, "goal_paused", { reason: `disapproval cap: ${trailingDisapprovals} consecutive (cap ${auditCap})` });
|
|
1650
|
+
notifyExternal(ctx, `Goal paused: ${trailingDisapprovals} consecutive auditor disapprovals`);
|
|
1651
|
+
return {
|
|
1652
|
+
content: [{
|
|
1653
|
+
type: "text",
|
|
1654
|
+
text: `The auditor has now disapproved ${trailingDisapprovals} times in a row (cap ${auditCap}). The goal is PAUSED — continuing to re-attempt without addressing the pattern wastes tokens. Latest report (first 800 chars):\n${result.output.slice(0, 800)}\n\nDo not call complete_goal again. Summarize the repeated objections for the user and ask how to proceed (/goal status shows all reports; /goal resume resumes).`,
|
|
1655
|
+
}],
|
|
1656
|
+
details: {},
|
|
1657
|
+
};
|
|
1658
|
+
}
|
|
1570
1659
|
updateGoal({
|
|
1571
1660
|
status: "active",
|
|
1572
1661
|
auditHistory: history,
|
|
@@ -1817,7 +1906,7 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
1817
1906
|
const p = params as { target: string; measureCmd?: string; direction?: "min" | "max"; window?: number; max?: number; time?: number; tokens?: number; branch?: boolean };
|
|
1818
1907
|
if (draftingTarget !== "loop") {
|
|
1819
1908
|
return {
|
|
1820
|
-
content: [{ type: "text", text: "
|
|
1909
|
+
content: [{ type: "text", text: "You cannot start or draft a loop — only the user can, from the slash bar (the Confirm is the product). Do NOT write draft files or wait for the user to say 'start' in chat; that dead-ends. Instead hand the user the exact command: /loop start \"<target>\" (bare = infinite metricless; add measure=\"<cmd>\" direction=min|max for a metric loop), or /loop respec to reconcile against the root spec, or /loop with no args to draft interactively." }],
|
|
1821
1910
|
details: {},
|
|
1822
1911
|
};
|
|
1823
1912
|
}
|
|
@@ -2146,6 +2235,8 @@ interface Settings {
|
|
|
2146
2235
|
/** on → restored goals/loops/lists auto-resume even in fresh sessions
|
|
2147
2236
|
* (unattended rigs). Default off: restore holds until /goal resume. */
|
|
2148
2237
|
autoResume?: boolean;
|
|
2238
|
+
/** v0.24.2: pause the goal after N consecutive auditor disapprovals (0 = unlimited). Default 3. */
|
|
2239
|
+
auditCap?: number;
|
|
2149
2240
|
/** on → propose_* drafts activate WITHOUT the Confirm dialog and the
|
|
2150
2241
|
* interview floor is skipped — the seed carries the intent (unattended
|
|
2151
2242
|
* rigs). Default off: nothing activates before the user confirms. */
|
|
@@ -2194,7 +2285,7 @@ function settingsProvenance(cwd: string): Record<keyof Settings, { value: unknow
|
|
|
2194
2285
|
const glob = readSettingsFile(globalSettingsPath());
|
|
2195
2286
|
const effective = loadSettings(cwd);
|
|
2196
2287
|
const out: Record<string, { value: unknown; source: "project" | "global" | "default" }> = {};
|
|
2197
|
-
const keys: Array<keyof Settings> = ["auditorModel", "auditorThinkingLevel", "notifyCmd", "tokenLimit", "wedgeAlertMinutes", "autoResume", "autoAcceptDrafts"];
|
|
2288
|
+
const keys: Array<keyof Settings> = ["auditorModel", "auditorThinkingLevel", "notifyCmd", "tokenLimit", "wedgeAlertMinutes", "autoResume", "autoAcceptDrafts", "auditCap"];
|
|
2198
2289
|
for (const k of keys) {
|
|
2199
2290
|
if ((proj as Record<string, unknown>)[k] !== undefined) out[k] = { value: (proj as any)[k], source: "project" };
|
|
2200
2291
|
else if ((glob as Record<string, unknown>)[k] !== undefined) out[k] = { value: (glob as any)[k], source: "global" };
|
|
@@ -2365,6 +2456,7 @@ async function cmdSettings(args: string, ctx: ExtensionContext): Promise<void> {
|
|
|
2365
2456
|
fmt("tokenLimit", "tokenLimit"),
|
|
2366
2457
|
fmt("autoResume", "autoResume"),
|
|
2367
2458
|
fmt("autoAcceptDrafts", "autoAccept"),
|
|
2459
|
+
fmt("auditCap", "auditCap"),
|
|
2368
2460
|
`\nglobal: ${globalSettingsPath()}`,
|
|
2369
2461
|
`project: ${projectSettingsPath(ctx.cwd)}`,
|
|
2370
2462
|
`Set with: /glla key=value (global) · /glla project key=value (project override)`,
|
|
@@ -2437,6 +2529,19 @@ async function cmdSettings(args: string, ctx: ExtensionContext): Promise<void> {
|
|
|
2437
2529
|
} else {
|
|
2438
2530
|
ctx.ui.notify(`autoaccept must be on or off, got: ${value}`, "warning");
|
|
2439
2531
|
}
|
|
2532
|
+
} else if (key === "auditcap") {
|
|
2533
|
+
if (["off", "unset", "default"].includes(value)) {
|
|
2534
|
+
patch.auditCap = undefined;
|
|
2535
|
+
changed = true;
|
|
2536
|
+
} else {
|
|
2537
|
+
const n = Number.parseInt(value, 10);
|
|
2538
|
+
if (Number.isInteger(n) && n >= 0) {
|
|
2539
|
+
patch.auditCap = n;
|
|
2540
|
+
changed = true;
|
|
2541
|
+
} else {
|
|
2542
|
+
ctx.ui.notify(`auditcap must be a non-negative integer (0 = unlimited), got: ${value}`, "warning");
|
|
2543
|
+
}
|
|
2544
|
+
}
|
|
2440
2545
|
} else if (key === "thinking" || key === "auditorthinkinglevel") {
|
|
2441
2546
|
if (["off", "minimal", "low", "medium", "high", "xhigh"].includes(value)) {
|
|
2442
2547
|
patch.auditorThinkingLevel = value as Settings["auditorThinkingLevel"];
|
|
@@ -2568,6 +2673,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
2568
2673
|
["notify=", "desktop push command: /glla notify='notify-send pi \"$1\"'"],
|
|
2569
2674
|
["tokenlimit=", "per-goal token budget (0 = off): /glla tokenlimit=2000000"],
|
|
2570
2675
|
["autoresume=", "on: auto-resume held goals/loops in fresh sessions"],
|
|
2676
|
+
["auditcap=", "N: pause goal after N consecutive auditor disapprovals (default 3, 0 = unlimited)"],
|
|
2571
2677
|
["autoaccept=", "on: drafts activate without the Confirm dialog (unattended rigs)"],
|
|
2572
2678
|
["project", "write a project override: /glla project key=value"],
|
|
2573
2679
|
]),
|
|
@@ -2586,9 +2692,10 @@ export default function (pi: ExtensionAPI): void {
|
|
|
2586
2692
|
handler: (args: string, ctx: ExtensionContext) => { rememberCtx(ctx); return cmdList(args, ctx); },
|
|
2587
2693
|
});
|
|
2588
2694
|
pi.registerCommand("loop", {
|
|
2589
|
-
description: "Loop 3: metric-driven process — it never completes. /loop <target> drafts the metric with you · /loop start \"<target>\" = infinite metricless loop (no plateau, no cap; ends at time=/tokens= or /loop stop) · add measure=\"<cmd>\" direction=min|max [window=5] [max=50] [branch=1] for a metric loop · /loop status · /loop stop. 'Improve until X' is a /goal, not a loop.",
|
|
2695
|
+
description: "Loop 3: metric-driven process — it never completes. /loop <target> drafts the metric with you · /loop start \"<target>\" = infinite metricless loop (no plateau, no cap; ends at time=/tokens= or /loop stop) · /loop respec = infinite metricless reconcile against the root SPEC.md · add measure=\"<cmd>\" direction=min|max [window=5] [max=50] [branch=1] for a metric loop · /loop status · /loop stop. 'Improve until X' is a /goal, not a loop.",
|
|
2590
2696
|
getArgumentCompletions: completions([
|
|
2591
2697
|
["start", "skip drafting: /loop start \"<target>\" measure=\"<cmd>\" direction=min|max [window=5] [max=50]"],
|
|
2698
|
+
["respec", "infinite metricless loop reconciling the codebase against the root SPEC.md"],
|
|
2592
2699
|
["status", "show metric, iteration, best/last values, stall count"],
|
|
2593
2700
|
["stop", "end the loop (keeps the best state)"],
|
|
2594
2701
|
]),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-goal-list-loop-audit",
|
|
3
|
-
"version": "0.24.
|
|
3
|
+
"version": "0.24.3",
|
|
4
4
|
"description": "Goal. Loop. Audit. Done. — a pi-coding-agent extension that supervises long-running work, with isolated auditor on each completion. Beat bamboozling by design: the auditor runs in a fresh session with no extensions, no skills, no editor — only the read tools needed to verify your goal.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "dracon",
|