pi-goal-list-loop-audit 0.28.25 → 0.28.27

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 CHANGED
@@ -39,6 +39,7 @@ Five top-level commands — `/goal`, `/list`, `/loop`, `/glla`, `/review`:
39
39
  /goal resume # resume
40
40
  /goal cancel # abort
41
41
  /goal decide # re-open the decision picker (v0.28.23)
42
+ /goal audit # run the isolated auditor on the current goal now — no agent turn (v0.28.27)
42
43
  /goal tweak "<new objective>" # edit in place (Confirm dialog)
43
44
  /goal archive # archived goals, newest first
44
45
  /glla # settings UI table · /glla key=value · /glla stats · /glla audits [N|full] · /glla postaudit · /glla autoaccept=on
@@ -176,6 +176,15 @@ export interface Goal {
176
176
  * At 3 the goal pauses loudly — a broken auditor model must not spin a
177
177
  * silent retry-forever loop. Cleared on any real auditor run. */
178
178
  auditInfraStreak?: number;
179
+ /** v0.28.26: the completion claim captured when an audit attempt is
180
+ * quota-blocked. The quota retry re-runs the AUDITOR directly with this
181
+ * stored claim instead of re-engaging the agent — re-engaging produced a
182
+ * hallucinated-closure repetition loop in the field (π-games 2026-07-29:
183
+ * the agent concluded the goal was closed, stopped calling complete_goal,
184
+ * and repeated the same essay until the stall brake fired). Cleared when
185
+ * the retry resolves. Only consumed while paused with an "auditor quota:"
186
+ * reason, so a stale value is unreachable by construction. */
187
+ pendingCompletion?: { completionSummary?: string; verificationSummary?: string; at: string };
179
188
  /** v0.25.0 (contract item 22): auditor objections extracted as TODOs when
180
189
  * aggressiveMode keeps the goal active past the disapproval cap. Rendered
181
190
  * into every continuation prompt until the next audit clears them. */
@@ -203,9 +212,9 @@ export interface Goal {
203
212
  export type GoalRoute =
204
213
  | { kind: "draft" }
205
214
  | { kind: "set"; text: string }
206
- | { kind: "sub"; name: "status" | "pause" | "resume" | "cancel" | "decide" | "tweak" | "archive" | "start"; rest: string };
215
+ | { kind: "sub"; name: "status" | "pause" | "resume" | "cancel" | "decide" | "audit" | "tweak" | "archive" | "start"; rest: string };
207
216
 
208
- const GOAL_EXACT_SUBS = new Set(["status", "pause", "resume", "cancel", "decide"]);
217
+ const GOAL_EXACT_SUBS = new Set(["status", "pause", "resume", "cancel", "decide", "audit"]);
209
218
  const GOAL_ARG_SUBS = new Set(["tweak", "archive", "start"]);
210
219
 
211
220
  export function routeGoalArgs(raw: string): GoalRoute {
@@ -568,6 +568,12 @@ function heartbeatTick(): void {
568
568
  // machinery below stays quiet for 3 minutes while the replaced session
569
569
  // settles (latch watchdog, wedge alert, refire counting all resume after).
570
570
  if (Date.now() < compactionGraceUntil) return;
571
+ // v0.28.27: a stale (session-replaced) handle can never land a send —
572
+ // the terminal warning already fired once. ALL stall machinery stays
573
+ // quiet from here on: refiring into a dead process is misleading, and
574
+ // worse, the stall escalation would PAUSE the goal — silently cancelling
575
+ // the interruptedAt → auto-resume-on-restart promise the footer shows.
576
+ if (extensionApiStale) return;
571
577
  // v0.26.5: pending-latch watchdog — a queued continuation whose turn
572
578
  // trigger was dropped (field-observed post-compaction: continuation
573
579
  // ACCEPTED at compact+0s, then 22 minutes of silence). The stuck latch
@@ -947,6 +953,148 @@ function archiveCurrentGoal(ctx: ExtensionContext, status: Status, stopReason?:
947
953
  }
948
954
  }
949
955
 
956
+ /**
957
+ * v0.28.26: quota-window retry for a STORED completion claim. The auditor
958
+ * was quota-blocked at complete_goal time; the claim (completionSummary +
959
+ * verificationSummary) was persisted on the goal, and when the quota window
960
+ * elapses we re-run the AUDITOR directly — no agent turn. Re-engaging the
961
+ * agent to re-submit an unchanged claim produced a hallucinated-closure
962
+ * repetition loop in the field (π-games: the model concluded the goal was
963
+ * closed, repeated the same essay 4×+, stormed continuations, compacted 14×
964
+ * in 35 minutes, and burned the stall brake).
965
+ *
966
+ * Outcomes: approved → close + cascade (archiveCurrentGoal handles list
967
+ * advance + reviewer); quota again → re-pause with a fresh scheduled retry
968
+ * (claim preserved); anything else (disapproved, impossible, non-quota
969
+ * infra) → hand back to the agent: resume active + continuation, verdict
970
+ * durable in auditHistory.
971
+ */
972
+ async function retryStoredCompletionAudit(ctx: ExtensionContext, origin: "quota-retry" | "manual" = "quota-retry"): Promise<void> {
973
+ const goal = state.goal;
974
+ if (!goal?.pendingCompletion) return;
975
+ if (completionAuditInFlight) return;
976
+ const liveCtx = freshCtx() ?? ctx;
977
+ const claim = goal.pendingCompletion;
978
+ updateGoal({ status: "auditing" }, liveCtx);
979
+ appendLedger(liveCtx.cwd, "goal_resumed", { via: origin === "manual" ? "manual-audit" : "quota-retry-direct-audit" });
980
+ liveCtx.ui.notify(origin === "manual"
981
+ ? "Manual /goal audit — running the isolated auditor now (no agent turn needed)."
982
+ : "Auditor quota window elapsed — retrying the audit with your stored completion claim (no agent turn needed).", "info");
983
+ const settings = loadSettings(liveCtx.cwd);
984
+ const { model: auditorModel, error: modelError, via } = resolveAuditorModel(liveCtx, settings.auditorModel);
985
+ if (modelError) liveCtx.ui.notify(`Auditor model issue: ${modelError}`, "warning");
986
+ latestAuditProgress = { label: "quota-retry", lastEventAt: Date.now() };
987
+ completionAuditInFlight = true;
988
+ const auditStartMs = Date.now();
989
+ let result: Awaited<ReturnType<typeof runGoalCompletionAuditor>>;
990
+ try {
991
+ ({ result } = await runWithInfraRetry(
992
+ () =>
993
+ runGoalCompletionAuditor({
994
+ ctx: liveCtx,
995
+ goal: state.goal!,
996
+ completionSummary: claim.completionSummary,
997
+ verificationSummary: claim.verificationSummary,
998
+ model: auditorModel,
999
+ thinkingLevel: settings.auditorThinkingLevel ?? getSessionThinkingLevel(),
1000
+ onProgress: (progress) => {
1001
+ latestAuditProgress = { currentTool: progress.currentTool, label: progress.label, elapsedMs: progress.elapsedMs, lastEventAt: Date.now() };
1002
+ refreshUI(liveCtx);
1003
+ },
1004
+ }),
1005
+ { onRetry: (err) => appendLedger(liveCtx.cwd, "audit_infra_retry", { goalId: state.goal?.id, error: err.slice(0, 200) }) },
1006
+ ));
1007
+ } finally {
1008
+ completionAuditInFlight = false;
1009
+ latestAuditProgress = null;
1010
+ }
1011
+ if (!state.goal) return; // aborted mid-audit
1012
+
1013
+ // Record the run in history (same compact shape as the tool path).
1014
+ const auditorRan = result.output.trim().length > 0;
1015
+ const history = state.goal.auditHistory ?? [];
1016
+ if (auditorRan) {
1017
+ result.output = stripThinkBlocks(result.output);
1018
+ history.push({
1019
+ at: nowIso(),
1020
+ approved: result.approved,
1021
+ disapproved: result.disapproved,
1022
+ impossible: result.impossible,
1023
+ impossibleReason: result.impossibleReason,
1024
+ model: result.model,
1025
+ thinkingLevel: result.thinkingLevel,
1026
+ report: result.output,
1027
+ error: result.error,
1028
+ regressionShieldPassed: result.regressionShieldPassed,
1029
+ regressionShieldMissing: result.regressionShieldMissing,
1030
+ durationMs: Date.now() - auditStartMs,
1031
+ } as any);
1032
+ if (history.length > 20) history.splice(0, history.length - 20);
1033
+ }
1034
+
1035
+ if (result.approved) {
1036
+ updateGoal({ auditHistory: history, pendingCompletion: undefined }, liveCtx);
1037
+ const objective = state.goal.objective;
1038
+ archiveCurrentGoal(liveCtx, "complete", `auditor ${result.model} approved (${origin})`);
1039
+ liveCtx.ui.notify(`Goal complete — auditor ${result.model} approved${origin === "manual" ? " on /goal audit" : " on the quota retry"}.`, "info");
1040
+ notifyExternal(liveCtx, `Goal complete (auditor approved, ${origin}): ${objective.slice(0, 120)}`);
1041
+ return;
1042
+ }
1043
+
1044
+ if (result.error && !result.disapproved && isQuotaError(result.error)) {
1045
+ // Still quota'd — re-pause with a fresh window, claim preserved.
1046
+ const settingsNow = loadSettings(liveCtx.cwd);
1047
+ const defaultSec = (settingsNow.quotaRetryMinutes ?? DEFAULT_QUOTA_RETRY_MINUTES) * 60;
1048
+ const quota = parseQuotaError(result.error, defaultSec);
1049
+ const retryMin = Math.max(1, Math.round(quota.retryAfterSec / 60));
1050
+ updateGoal({
1051
+ status: "paused",
1052
+ auditHistory: history,
1053
+ pauseKind: "wait",
1054
+ pauseResumeAt: new Date(Date.now() + quota.retryAfterSec * 1000).toISOString(),
1055
+ pauseReason: `auditor quota: ${result.error}`,
1056
+ pauseSuggestedAction: `Quota auto-retry in ${retryMin}m — or /goal resume to retry now`,
1057
+ }, liveCtx);
1058
+ appendLedger(liveCtx.cwd, "goal_paused", { reason: `auditor quota: retry in ${quota.retryAfterSec}s (stored-claim retry)` });
1059
+ liveCtx.ui.notify(`Auditor still quota-limited — next auto-retry in ${retryMin}m (your completion claim is stored; no action needed).`, "warning");
1060
+ scheduleQuotaRetry(liveCtx, quota.retryAfterSec, result.error, () => {
1061
+ if (state.goal && state.goal.status === "paused" && (state.goal.pauseReason ?? "").startsWith("auditor quota:") && state.goal.pendingCompletion) {
1062
+ void retryStoredCompletionAudit(liveCtx, origin);
1063
+ }
1064
+ });
1065
+ return;
1066
+ }
1067
+
1068
+ // Any other outcome — disapproved, impossible, non-quota infra — belongs
1069
+ // to the agent: resume and let the continuation drive the next step. The
1070
+ // verdict is durable in auditHistory + /goal status.
1071
+ updateGoal({
1072
+ status: "active",
1073
+ auditHistory: history,
1074
+ pendingCompletion: undefined,
1075
+ pauseReason: result.disapproved
1076
+ ? `auditor disapproved on quota-retry — see /goal status`
1077
+ : result.impossible
1078
+ ? `auditor verdict: IMPOSSIBLE on quota-retry — ${(result.impossibleReason ?? "").slice(0, 120)}`
1079
+ : `auditor infrastructure error on quota-retry: ${(result.error ?? "").slice(0, 120)}`,
1080
+ }, liveCtx);
1081
+ liveCtx.ui.notify(
1082
+ result.disapproved
1083
+ ? `Auditor (${origin}) DISAPPROVED — resuming; the report is in /goal status.`
1084
+ : result.impossible
1085
+ ? `Auditor (${origin}): goal IMPOSSIBLE — ${(result.impossibleReason ?? "").slice(0, 100)}. Resuming; consider /goal tweak.`
1086
+ : `Auditor (${origin}) hit an infrastructure error — resuming; re-call complete_goal when ready.`,
1087
+ "warning",
1088
+ );
1089
+ appendLedger(liveCtx.cwd, "quota_retry_audit_verdict", {
1090
+ approved: false,
1091
+ disapproved: result.disapproved,
1092
+ impossible: result.impossible,
1093
+ error: result.error?.slice(0, 160),
1094
+ });
1095
+ scheduleContinuation(liveCtx, true);
1096
+ }
1097
+
950
1098
  /**
951
1099
  * v0.26.0: bind the reviewer to the live session. Sources for finding
952
1100
  * extraction: the archived goal markdown + its audit reports + the
@@ -1169,6 +1317,30 @@ async function cmdGoal(args: string, ctx: ExtensionContext): Promise<void> {
1169
1317
  if (!shown) ctx.ui.notify("No pending decision — the goal isn't paused on a choice (or no UI).", "info");
1170
1318
  return;
1171
1319
  }
1320
+ // v0.28.27: /goal audit — run the isolated auditor on the current goal
1321
+ // RIGHT NOW, without engaging the agent. The user's "the work looks
1322
+ // done — just verify it" handle (and the manual counterpart of the
1323
+ // v0.28.26 stored-claim quota retry). Seeds a synthesized claim so a
1324
+ // quota block falls into the same pendingCompletion retry machinery.
1325
+ if (route.name === "audit") {
1326
+ if (!state.goal) {
1327
+ ctx.ui.notify("No active goal — /goal audit needs a goal to verify.", "warning");
1328
+ return;
1329
+ }
1330
+ if (completionAuditInFlight) {
1331
+ ctx.ui.notify("An audit is already running…", "info");
1332
+ return;
1333
+ }
1334
+ updateGoal({
1335
+ pendingCompletion: {
1336
+ completionSummary: "Manual audit requested by the user via /goal audit (no agent completion claim). Verify the objective against the repo directly.",
1337
+ at: nowIso(),
1338
+ },
1339
+ }, ctx);
1340
+ appendLedger(ctx.cwd, "manual_audit_requested", { goalId: state.goal.id });
1341
+ void retryStoredCompletionAudit(ctx, "manual");
1342
+ return;
1343
+ }
1172
1344
  if (route.name === "tweak") return cmdTweak(route.rest, ctx);
1173
1345
  if (route.name === "archive") return cmdGoals(ctx);
1174
1346
  // v0.16.0: /goal start <objective> — explicit skip-draft. Activates
@@ -2574,6 +2746,9 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
2574
2746
  status: "paused",
2575
2747
  auditHistory: history,
2576
2748
  auditInfraStreak: undefined, // quota reached the auditor — infra streak broken
2749
+ // v0.28.26: store the claim — the quota retry re-runs the
2750
+ // auditor DIRECTLY with it (no agent turn to confuse).
2751
+ pendingCompletion: { completionSummary: p.completionSummary, verificationSummary: p.verificationSummary, at: nowIso() },
2577
2752
  pauseKind: "wait",
2578
2753
  pauseResumeAt: new Date(Date.now() + quota.retryAfterSec * 1000).toISOString(),
2579
2754
  pauseReason: `auditor quota: ${result.error}`,
@@ -2584,6 +2759,13 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
2584
2759
  // Re-check: only auto-resume if STILL paused for the quota
2585
2760
  // reason (a user /goal pause during the window is not stomped).
2586
2761
  if (state.goal && state.goal.status === "paused" && (state.goal.pauseReason ?? "").startsWith("auditor quota:")) {
2762
+ // v0.28.26: a stored claim retries the AUDITOR directly — the
2763
+ // agent is not needed to re-submit an unchanged claim, and
2764
+ // re-engaging it produced hallucinated-closure loops.
2765
+ if (state.goal.pendingCompletion) {
2766
+ void retryStoredCompletionAudit(ctx);
2767
+ return;
2768
+ }
2587
2769
  updateGoal({ status: "active" }, ctx);
2588
2770
  appendLedger(ctx.cwd, "goal_resumed", { via: "quota-retry" });
2589
2771
  if (resolveEffectiveAggressiveSettings(loadSettings(ctx.cwd)).aggressiveMode) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-goal-list-loop-audit",
3
- "version": "0.28.25",
3
+ "version": "0.28.27",
4
4
  "description": "Goal. Loop. Audit. Done. \u2014 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 \u2014 only the read tools needed to verify your goal.",
5
5
  "license": "MIT",
6
6
  "author": "dracon",
@@ -53,6 +53,7 @@
53
53
  "interruptedAt": { "type": "string" },
54
54
  "interruptedReason": { "type": "string" },
55
55
  "auditInfraStreak": { "type": "number" },
56
+ "pendingCompletion": { "type": "object" },
56
57
  "activePath": { "type": "string" },
57
58
  "archivedPath": { "type": "string" },
58
59
  "usage": {