pi-goal-list-loop-audit 0.25.3 → 0.25.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -159,7 +159,9 @@ function buildGoalAuditorPrompt(goal: Goal, completionSummary: string | null | u
|
|
|
159
159
|
? ["4. Verify that the executor has satisfied every item in the <verification_contract>. If any item is missing or weakly addressed, disapprove."]
|
|
160
160
|
: []),
|
|
161
161
|
"5. Explain missing or weak evidence, especially scaffold-vs-final quality gaps.",
|
|
162
|
-
"6.
|
|
162
|
+
"6. When you disapprove, end the report body with a '## Required fixes' section: one line per blocking gap, each an actionable instruction the executor can complete (most critical first). This tail is what the executor sees first — make it self-sufficient.",
|
|
163
|
+
"7. Write the report in English, and never emit <think> blocks or fragments — your reasoning stays private; the report is the verdict plus evidence.",
|
|
164
|
+
"8. 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/>.",
|
|
163
165
|
...(goal.verificationContract?.trim()
|
|
164
166
|
? [
|
|
165
167
|
"",
|
|
@@ -309,8 +309,14 @@ export const DEFAULT_AUDIT_FEEDBACK_CHARS = 0;
|
|
|
309
309
|
* Bound the auditor report returned to the executor after disapproval.
|
|
310
310
|
* A limit of 0 explicitly means "show the full report".
|
|
311
311
|
*/
|
|
312
|
+
/** Executor-visible excerpt of a disapproval report. Full by default
|
|
313
|
+
* (maxChars 0). When capped, keep the TAIL: since v0.25.4 the auditor
|
|
314
|
+
* ends disapprovals with the actionable `## Required fixes` section —
|
|
315
|
+
* head-slicing would cut exactly what the executor needs. */
|
|
312
316
|
export function auditFeedbackExcerpt(output: string, maxChars: number): string {
|
|
313
|
-
|
|
317
|
+
if (maxChars === 0 || output.length <= maxChars) return output;
|
|
318
|
+
return `[head truncated — full report via /goal status]
|
|
319
|
+
…${output.slice(-maxChars)}`;
|
|
314
320
|
}
|
|
315
321
|
|
|
316
322
|
export interface ListItem {
|
|
@@ -400,10 +406,19 @@ export interface State {
|
|
|
400
406
|
/** v0.24.2: count TRAILING consecutive disapprovals (the disapproval-cap
|
|
401
407
|
* input). Shield-blocks (approved:true) and infra errors (neither flag)
|
|
402
408
|
* break the streak — they are not verdicts on the work. */
|
|
409
|
+
/** v0.24.2: count TRAILING consecutive disapprovals (the disapproval-cap
|
|
410
|
+
* input). Shield-blocks (approved:true) break the streak — the work was
|
|
411
|
+
* judged good. v0.25.4: pure infra errors (error set, neither verdict
|
|
412
|
+
* flag) are TRANSPARENT, not streak-breakers — the auditor never judged
|
|
413
|
+
* the work, so D,D,infra,D is still 3 trailing disapprovals (before, 39
|
|
414
|
+
* hegemon-style infra errors would reset the cap and re-open infinite
|
|
415
|
+
* re-continuation). */
|
|
403
416
|
export function countTrailingDisapprovals(history: AuditVerdict[]): number {
|
|
404
417
|
let n = 0;
|
|
405
418
|
for (let i = history.length - 1; i >= 0; i--) {
|
|
406
|
-
|
|
419
|
+
const v = history[i]!;
|
|
420
|
+
if (v.disapproved) n++;
|
|
421
|
+
else if (v.error && !v.approved) continue; // infra: not a verdict
|
|
407
422
|
else break;
|
|
408
423
|
}
|
|
409
424
|
return n;
|
|
@@ -1044,3 +1059,89 @@ export function formatListDepth(stats: ListDepthStats): string {
|
|
|
1044
1059
|
if (stats.durationSamples > 0) lines.push(`avg item duration: ${fmtAge(stats.avgDurationMs!)} (from last ${stats.durationSamples} archived)`);
|
|
1045
1060
|
return lines.join("\n");
|
|
1046
1061
|
}
|
|
1062
|
+
|
|
1063
|
+
// =================================================================
|
|
1064
|
+
// v0.25.4: auditor polish — durable audit log, think-block hygiene,
|
|
1065
|
+
// actionable-tail slicing, infra-transparent streaks
|
|
1066
|
+
// =================================================================
|
|
1067
|
+
|
|
1068
|
+
/** Strip think-block leakage from auditor reports before storage/display.
|
|
1069
|
+
* Motivation (wild, 2026-07-25): MiniMax-M3 reports arrive with
|
|
1070
|
+
* `<think>...</think>` bodies, stray `</think>` fragments, and non-English
|
|
1071
|
+
* reasoning spillover — the executor's feedback should be the verdict,
|
|
1072
|
+
* not the auditor's private monologue. */
|
|
1073
|
+
export function stripThinkBlocks(text: string): string {
|
|
1074
|
+
return text
|
|
1075
|
+
.replace(/<think>[\s\S]*?<\/think>/gi, "")
|
|
1076
|
+
.replace(/<\/?think>/gi, "")
|
|
1077
|
+
.replace(/<200b>/g, "") // stray partial-tag artifact seen in the wild
|
|
1078
|
+
.replace(/^\s+/, "");
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
/** One durable audit-log entry — survives state-snapshot rotation, so
|
|
1082
|
+
* /glla audits can answer "where are we weak" across the whole project. */
|
|
1083
|
+
export interface AuditLogEntry {
|
|
1084
|
+
at: string;
|
|
1085
|
+
goalId: string;
|
|
1086
|
+
objective: string;
|
|
1087
|
+
verdict: "approved" | "disapproved" | "impossible" | "shield_blocked" | "error";
|
|
1088
|
+
model: string;
|
|
1089
|
+
thinkingLevel: string;
|
|
1090
|
+
report: string;
|
|
1091
|
+
impossibleReason?: string;
|
|
1092
|
+
error?: string;
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
export function auditLogPath(cwd: string): string {
|
|
1096
|
+
return path.join(cwd, ".pi-glla", "audits.jsonl");
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
export function appendAuditLog(cwd: string, entry: AuditLogEntry): void {
|
|
1100
|
+
try {
|
|
1101
|
+
ensureDirs(cwd);
|
|
1102
|
+
fs.appendFileSync(auditLogPath(cwd), JSON.stringify(entry) + "\n");
|
|
1103
|
+
} catch {
|
|
1104
|
+
/* log best-effort — never block the verdict path */
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
export function readAuditLog(cwd: string, limit?: number): AuditLogEntry[] {
|
|
1109
|
+
let raw: string;
|
|
1110
|
+
try {
|
|
1111
|
+
raw = fs.readFileSync(auditLogPath(cwd), "utf-8");
|
|
1112
|
+
} catch {
|
|
1113
|
+
return [];
|
|
1114
|
+
}
|
|
1115
|
+
const out: AuditLogEntry[] = [];
|
|
1116
|
+
for (const line of raw.split("\n")) {
|
|
1117
|
+
const t = line.trim();
|
|
1118
|
+
if (!t) continue;
|
|
1119
|
+
try {
|
|
1120
|
+
const e = JSON.parse(t);
|
|
1121
|
+
if (e && typeof e.goalId === "string" && typeof e.verdict === "string") out.push(e as AuditLogEntry);
|
|
1122
|
+
} catch {
|
|
1123
|
+
/* skip malformed */
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
return limit !== undefined ? out.slice(-limit) : out;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
const VERDICT_GLYPH: Record<AuditLogEntry["verdict"], string> = {
|
|
1130
|
+
approved: "✔",
|
|
1131
|
+
disapproved: "✖",
|
|
1132
|
+
impossible: "⛔",
|
|
1133
|
+
shield_blocked: "🛡",
|
|
1134
|
+
error: "⚠",
|
|
1135
|
+
};
|
|
1136
|
+
|
|
1137
|
+
/** /glla audits list view: one line per verdict, newest last. */
|
|
1138
|
+
export function formatAuditLog(entries: AuditLogEntry[]): string {
|
|
1139
|
+
if (entries.length === 0) return "(no audits logged yet — the log starts with the next verdict)";
|
|
1140
|
+
return entries
|
|
1141
|
+
.map((e) => {
|
|
1142
|
+
const day = e.at.slice(5, 16).replace("T", " ");
|
|
1143
|
+
const firstLine = (e.report.split("\n").find((l) => l.trim()) ?? "").trim().slice(0, 90);
|
|
1144
|
+
return `${VERDICT_GLYPH[e.verdict]} ${day} [${e.goalId.slice(-6)}] ${e.model} — ${firstLine}`;
|
|
1145
|
+
})
|
|
1146
|
+
.join("\n");
|
|
1147
|
+
}
|
|
@@ -72,6 +72,9 @@ export interface AuditDisplayProgress {
|
|
|
72
72
|
currentTool?: string;
|
|
73
73
|
label?: string;
|
|
74
74
|
elapsedMs?: number;
|
|
75
|
+
/** v0.25.4: last progress-event time — the widget flags auditor-quiet
|
|
76
|
+
* stalls when this goes stale while the audit is in flight. */
|
|
77
|
+
lastEventAt?: number;
|
|
75
78
|
}
|
|
76
79
|
|
|
77
80
|
/**
|
|
@@ -175,7 +178,12 @@ function goalLines(g: Goal, state: State, audit: AuditDisplayProgress | null | u
|
|
|
175
178
|
const lines = [head, `├─ ${isList ? "list item · " : ""}${statusWord} · ${fmtElapsed(now - Date.parse(g.createdAt))}${tokens}`];
|
|
176
179
|
if (g.status === "auditing") {
|
|
177
180
|
lines.push(`├─ auditor: ${audit?.label ?? "running"}${audit?.currentTool ? ` · ${truncate(audit.currentTool, 30)}` : ""}`);
|
|
178
|
-
|
|
181
|
+
// v0.25.4: auditor-quiet stall — progress events stopped arriving
|
|
182
|
+
// while the audit is in flight (hung model call, stuck tool).
|
|
183
|
+
const quietMs = audit?.lastEventAt !== undefined ? now - audit.lastEventAt : 0;
|
|
184
|
+
if (quietMs > 3 * 60_000) {
|
|
185
|
+
lines.push(`└─ ${paint(theme, "warning", `auditor quiet ${fmtElapsed(quietMs)} — may be stuck; Esc aborts, verdict is not counted`)}`);
|
|
186
|
+
} else if (audit?.elapsedMs) lines.push(`└─ ${paint(theme, "dim", `${fmtElapsed(audit.elapsedMs)} in isolated session`)}`);
|
|
179
187
|
else lines.push(`└─ ${paint(theme, "dim", "isolated session, read-only tools")}`);
|
|
180
188
|
return lines;
|
|
181
189
|
}
|
package/extensions/loops/goal.ts
CHANGED
|
@@ -39,7 +39,12 @@ import {
|
|
|
39
39
|
isFullAuditObjective,
|
|
40
40
|
lastShippedAtMs,
|
|
41
41
|
resolveEffectiveAggressiveSettings,
|
|
42
|
+
appendAuditLog,
|
|
42
43
|
computeListDepth,
|
|
44
|
+
formatAuditLog,
|
|
45
|
+
readAuditLog,
|
|
46
|
+
stripThinkBlocks,
|
|
47
|
+
type AuditLogEntry,
|
|
43
48
|
ledgerPath,
|
|
44
49
|
crossRecommendMode,
|
|
45
50
|
formatListDepth,
|
|
@@ -1697,7 +1702,7 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
1697
1702
|
ctx.ui.notify(`Auditor running (isolated session, model: ${via ?? "setting"})…`, "info");
|
|
1698
1703
|
// Esc during the audit aborts this tool's signal → threaded into the
|
|
1699
1704
|
// auditor session, which aborts cleanly and returns "Auditor aborted."
|
|
1700
|
-
latestAuditProgress = { label: "starting" };
|
|
1705
|
+
latestAuditProgress = { label: "starting", lastEventAt: Date.now() };
|
|
1701
1706
|
const result = await runGoalCompletionAuditor({
|
|
1702
1707
|
ctx,
|
|
1703
1708
|
goal: state.goal,
|
|
@@ -1711,6 +1716,7 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
1711
1716
|
currentTool: progress.currentTool,
|
|
1712
1717
|
label: progress.label,
|
|
1713
1718
|
elapsedMs: progress.elapsedMs,
|
|
1719
|
+
lastEventAt: Date.now(),
|
|
1714
1720
|
};
|
|
1715
1721
|
refreshUI(ctx);
|
|
1716
1722
|
},
|
|
@@ -1723,6 +1729,11 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
1723
1729
|
const auditorRan = result.output.trim().length > 0;
|
|
1724
1730
|
const history = state.goal.auditHistory ?? [];
|
|
1725
1731
|
if (auditorRan) {
|
|
1732
|
+
// v0.25.4: strip think-block leakage (MiniMax-M3 `</think>`
|
|
1733
|
+
// fragments + reasoning spillover) before anything stores or
|
|
1734
|
+
// displays the report.
|
|
1735
|
+
const cleanOutput = stripThinkBlocks(result.output);
|
|
1736
|
+
result.output = cleanOutput;
|
|
1726
1737
|
history.push({
|
|
1727
1738
|
at: nowIso(),
|
|
1728
1739
|
approved: result.approved,
|
|
@@ -1731,13 +1742,36 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
1731
1742
|
impossibleReason: result.impossibleReason,
|
|
1732
1743
|
model: result.model,
|
|
1733
1744
|
thinkingLevel: result.thinkingLevel,
|
|
1734
|
-
report:
|
|
1745
|
+
report: cleanOutput,
|
|
1735
1746
|
error: result.error,
|
|
1736
1747
|
regressionShieldPassed: result.regressionShieldPassed,
|
|
1737
1748
|
regressionShieldMissing: result.regressionShieldMissing,
|
|
1738
1749
|
});
|
|
1739
1750
|
// Cap history — 39 infra errors taught us unbounded growth is real.
|
|
1740
1751
|
if (history.length > 20) history.splice(0, history.length - 20);
|
|
1752
|
+
// v0.25.4: durable append-only audit log — survives state-snapshot
|
|
1753
|
+
// rotation; the review surface for "where are we weak".
|
|
1754
|
+
const verdict: AuditLogEntry["verdict"] =
|
|
1755
|
+
result.error && !result.approved && !result.disapproved
|
|
1756
|
+
? "error"
|
|
1757
|
+
: result.approved && result.regressionShieldPassed === false
|
|
1758
|
+
? "shield_blocked"
|
|
1759
|
+
: result.approved
|
|
1760
|
+
? "approved"
|
|
1761
|
+
: result.impossible
|
|
1762
|
+
? "impossible"
|
|
1763
|
+
: "disapproved";
|
|
1764
|
+
appendAuditLog(ctx.cwd, {
|
|
1765
|
+
at: nowIso(),
|
|
1766
|
+
goalId: state.goal.id,
|
|
1767
|
+
objective: state.goal.objective.slice(0, 200),
|
|
1768
|
+
verdict,
|
|
1769
|
+
model: result.model,
|
|
1770
|
+
thinkingLevel: result.thinkingLevel ?? "(default)",
|
|
1771
|
+
report: cleanOutput,
|
|
1772
|
+
impossibleReason: result.impossibleReason,
|
|
1773
|
+
error: result.error,
|
|
1774
|
+
});
|
|
1741
1775
|
}
|
|
1742
1776
|
|
|
1743
1777
|
// Escape hatch: the user aborted the audit (Esc). Offer the explicit
|
|
@@ -1916,7 +1950,7 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
1916
1950
|
const auditFeedbackIsFull = auditFeedbackChars === 0 || result.output.length <= auditFeedbackChars;
|
|
1917
1951
|
const auditFeedbackLabel = auditFeedbackIsFull
|
|
1918
1952
|
? "full report"
|
|
1919
|
-
: `
|
|
1953
|
+
: `last ${auditFeedbackChars} chars (Required-fixes tail)`;
|
|
1920
1954
|
const auditFeedbackTruncationHint = auditFeedbackIsFull
|
|
1921
1955
|
? ""
|
|
1922
1956
|
: `\n\nReport truncated at the configured limit. /goal status shows the full report; change future feedback with /glla auditfeedbackchars=N (0 = full report).`;
|
|
@@ -2744,6 +2778,24 @@ function cmdStats(args: string, ctx: ExtensionContext): void {
|
|
|
2744
2778
|
ctx.ui.notify(`glla stats — ${rollups.length} project(s)${prematureOnly ? " (premature filter)" : ""}\n${out}`, "info");
|
|
2745
2779
|
}
|
|
2746
2780
|
|
|
2781
|
+
/**
|
|
2782
|
+
* v0.25.4: /glla audits [N|full] — browse the durable per-project audit
|
|
2783
|
+
* log (.pi-glla/audits.jsonl). Default: last 10 verdicts, one line each.
|
|
2784
|
+
* "full" prints the latest report in full.
|
|
2785
|
+
*/
|
|
2786
|
+
function cmdAudits(args: string, ctx: ExtensionContext): void {
|
|
2787
|
+
const full = /\bfull\b/.test(args);
|
|
2788
|
+
const nMatch = args.match(/\b(\d+)\b/);
|
|
2789
|
+
if (full) {
|
|
2790
|
+
const latest = readAuditLog(ctx.cwd).at(-1);
|
|
2791
|
+
ctx.ui.notify(latest ? `Latest audit — ${latest.verdict} (${latest.model}, ${latest.at})\n${latest.report}` : "No audits logged yet.", "info");
|
|
2792
|
+
return;
|
|
2793
|
+
}
|
|
2794
|
+
const n = nMatch ? Number(nMatch[1]) : 10;
|
|
2795
|
+
const entries = readAuditLog(ctx.cwd, n);
|
|
2796
|
+
ctx.ui.notify(`glla audits — last ${entries.length} verdict(s) in ${ctx.cwd}\n${formatAuditLog(entries)}`, "info");
|
|
2797
|
+
}
|
|
2798
|
+
|
|
2747
2799
|
async function cmdSettings(args: string, ctx: ExtensionContext): Promise<void> {
|
|
2748
2800
|
// The plugin's ONE config surface — global by default, rarely opened.
|
|
2749
2801
|
// /glla show effective values + where each comes from
|
|
@@ -2762,6 +2814,10 @@ async function cmdSettings(args: string, ctx: ExtensionContext): Promise<void> {
|
|
|
2762
2814
|
cmdStats(trimmed.slice("stats".length).trim(), ctx);
|
|
2763
2815
|
return;
|
|
2764
2816
|
}
|
|
2817
|
+
if (/^audits\b/.test(trimmed)) {
|
|
2818
|
+
cmdAudits(trimmed.slice("audits".length).trim(), ctx);
|
|
2819
|
+
return;
|
|
2820
|
+
}
|
|
2765
2821
|
if (!trimmed) {
|
|
2766
2822
|
if (ctx.hasUI) {
|
|
2767
2823
|
await openSettingsUI(ctx);
|
|
@@ -3059,6 +3115,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
3059
3115
|
["quotaretryminutes=", "N: minutes before auto-retrying a quota-exhausted auditor (default 60)"],
|
|
3060
3116
|
["stuckmax=", "N: consecutive stuck interventions before a loop stops (default 5)"],
|
|
3061
3117
|
["stats", "per-project ledger rollups: /glla stats [json|premature|project=<path>]"],
|
|
3118
|
+
["audits", "audit-log browser: /glla audits [N|full] — recent verdicts from .pi-glla/audits.jsonl"],
|
|
3062
3119
|
["autoaccept=", "on: drafts activate without the Confirm dialog (unattended rigs)"],
|
|
3063
3120
|
["project", "write a project override: /glla project key=value"],
|
|
3064
3121
|
]),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-goal-list-loop-audit",
|
|
3
|
-
"version": "0.25.
|
|
3
|
+
"version": "0.25.4",
|
|
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",
|