pi-goal-list-loop-audit 0.28.10 → 0.28.12

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
@@ -209,7 +209,7 @@ No external watchdog plugin needed.
209
209
  /glla quotaretryminutes=60 # minutes before auto-retrying a quota-exhausted auditor
210
210
  /glla stuckmax=10 # consecutive stuck interventions before a loop stops (default 5)
211
211
  /glla auditfeedbackchars=800 # cap the executor-visible auditor report (default 0 = full report)
212
- /glla autoaccept=on # drafts ACTIVATE without the Confirm dialog (unattended rigs)
212
+ /glla autoaccept=on # drafts ACTIVATE without the Confirm dialo... (every draft dialog also offers "always auto-accept" inline)unattended rigs)
213
213
  /glla project tokenlimit=500 # rare per-project override
214
214
  ```
215
215
 
@@ -144,11 +144,11 @@ export function buildStatusText(state: State, audit?: AuditDisplayProgress | nul
144
144
  return `glla: ${g.policy} ${paint(theme, "error", "⚠ interrupted — stale handle · auto-resumes on pi restart")}`;
145
145
  }
146
146
  // v0.24.7: list policy gets its own wording — a queue item is not a goal.
147
- // Before: "glla: list ● 3m 19s · list 29" (policy label AND queue counter
148
- // both said "list"). After: "glla: list ● 3m 19s · 29 queued". Goal
149
- // policy keeps the bare "list N" suffix (no duplication there).
147
+ // v0.28.11 (U10): goal policy joins it — "list 29" read as a command
148
+ // fragment; "29 queued" says what the number IS. Both policies now
149
+ // render "… · N queued".
150
150
  const n = state.list?.length ?? 0;
151
- const queue = n === 0 ? "" : g.policy === "list" ? ` · ${n} queued` : ` · list ${n}`;
151
+ const queue = n === 0 ? "" : ` · ${n} queued`;
152
152
  const tasks = g.taskList ? ` ${countDone(g)}/${countTotal(g)} tasks ·` : "";
153
153
  return `glla: ${g.policy} ${paint(theme, "success", "●")}${tasks} ${fmtElapsed(now - Date.parse(g.createdAt))}${queue}`;
154
154
  }
@@ -275,7 +275,7 @@ function goalLines(g: Goal, state: State, audit: AuditDisplayProgress | null | u
275
275
  const queue = state.list?.length ?? 0;
276
276
  const footer = isList
277
277
  ? `${queue > 0 ? `${queue} queued · ` : ""}/list · /glla`
278
- : `${queue > 0 ? `list ${queue} · ` : ""}/goal status · /glla`;
278
+ : `${queue > 0 ? `${queue} queued · ` : ""}/goal status · /glla`;
279
279
  lines.push(`└─ ${paint(theme, "dim", footer)}`);
280
280
  return lines;
281
281
  }
@@ -259,6 +259,36 @@ function warnIfStaleAtEntry(ctx: ExtensionContext, what: string): boolean {
259
259
  return true;
260
260
  }
261
261
 
262
+ /** v0.28.12: draft-class confirm with the auto-accept escape hatch SURFACED.
263
+ * The polis incident: a user sat through a 14-item batch Confirm having
264
+ * already reviewed every item during drafting, never knowing /glla
265
+ * autoaccept=on existed — the Yes/No dialog never mentioned it. Now every
266
+ * draft dialog is a 3-choice select; the ALWAYS choice persists project
267
+ * autoAcceptDrafts=true and accepts. Returns "stale" when the dialog can't
268
+ * render (session replacement) so call sites keep their NOT-a-rejection
269
+ * handling; falls back to the plain confirm if select is unavailable. */
270
+ type DraftChoice = "yes" | "no" | "stale";
271
+ async function confirmDraft(ctx: ExtensionContext, title: string, body: string): Promise<DraftChoice> {
272
+ const ALWAYS = "Yes — and always auto-accept drafts (sets autoAcceptDrafts for this project)";
273
+ try {
274
+ const choice = await ctx.ui.select(`${title}\n\n${body}`, ["Yes", ALWAYS, "No"]);
275
+ if (choice === ALWAYS) {
276
+ saveSettings("project", ctx.cwd, { autoAcceptDrafts: true });
277
+ appendLedger(ctx.cwd, "draft_autoaccept_enabled", { via: title });
278
+ ctx.ui.notify("Draft auto-accept ON for this project — future draft confirms are skipped. Undo: /glla autoaccept=off.", "info");
279
+ return "yes";
280
+ }
281
+ return choice === "Yes" ? "yes" : "no";
282
+ } catch (err) {
283
+ if (isStaleApiError(err)) return "stale";
284
+ try {
285
+ return (await ctx.ui.confirm(title, body)) ? "yes" : "no";
286
+ } catch (err2) {
287
+ return isStaleApiError(err2) ? "stale" : "no";
288
+ }
289
+ }
290
+ }
291
+
262
292
  // The most recent ExtensionContext seen from any event or command handler.
263
293
  // pi replaces sessions (newSession/fork/reload) and stale ctx throws on use,
264
294
  // so timers must never capture a ctx — they read lastCtx at fire time.
@@ -758,6 +788,9 @@ function persistState(ctx: ExtensionContext): void {
758
788
  // once per recovery. The TUI flag (buildWidgetLines) carries the standing
759
789
  // state; these notifies are the LOUD part.
760
790
  let persistenceDegradedNotified = false;
791
+
792
+ /** v0.28.11 (U9): objective-first notifies — truncate long objectives. */
793
+ const shortObj = (s: string): string => (s.length > 90 ? `${s.slice(0, 87)}…` : s);
761
794
  function notifyPersistenceState(ctx: ExtensionContext): void {
762
795
  if (isPersistenceDegraded() && !persistenceDegradedNotified) {
763
796
  persistenceDegradedNotified = true;
@@ -885,7 +918,7 @@ function fireReviewer(
885
918
  // to still count as "proposed" in the report + notify. Now the
886
919
  // failure is LOUD and the proposal goes uncounted.
887
920
  ctx.ui.notify(
888
- `Reviewer /goal proposal NOT delivered: ${err instanceof Error ? err.message : String(err)} — the follow-up never reached the session. Restart pi if the session was just replaced.`,
921
+ `Postaudit /goal proposal NOT delivered: ${err instanceof Error ? err.message : String(err)} — the follow-up never reached the session. Restart pi if the session was just replaced.`,
889
922
  "warning",
890
923
  );
891
924
  return false;
@@ -895,7 +928,7 @@ function fireReviewer(
895
928
  ledger: (type, value) => appendLedger(ctx.cwd, type, value),
896
929
  });
897
930
  if (!outcome.fired && outcome.suppressedReason && opts.manual) {
898
- ctx.ui.notify(`Reviewer suppressed: ${outcome.suppressedReason}`, "info");
931
+ ctx.ui.notify(`Postaudit suppressed: ${outcome.suppressedReason}`, "info");
899
932
  }
900
933
  // v0.27.5: surface the silent review to interactive users. The internal
901
934
  // runReviewer notify fires DURING the goal-completion handler, easy to
@@ -911,7 +944,7 @@ function fireReviewer(
911
944
  );
912
945
  }
913
946
  } catch (err) {
914
- ctx.ui.notify(`Reviewer failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`, "warning");
947
+ ctx.ui.notify(`Postaudit failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`, "warning");
915
948
  }
916
949
  }
917
950
 
@@ -1088,10 +1121,10 @@ async function cmdSet(args: string, ctx: ExtensionContext, skipDraft = false): P
1088
1121
  // v0.28.1 (S3): the goal is persisted — mark the interrupt so the next
1089
1122
  // fresh session auto-resumes, and tell the truth instead of "starting now".
1090
1123
  updateGoal({ interruptedAt: nowIso(), interruptedReason: "created in a stale session" }, ctx);
1091
- ctx.ui.notify(`Goal ${goal.id} created and safe in .pi-glla/ — this stale process can't send continuations. Restart pi and it auto-resumes.`, "warning");
1124
+ ctx.ui.notify(`Goal saved: ${shortObj(goal.objective)} — safe in .pi-glla/, but this stale process can't send continuations. Restart pi and it auto-resumes. (id: ${goal.id})`, "warning");
1092
1125
  return;
1093
1126
  }
1094
- ctx.ui.notify(`Goal ${goal.id} created — starting now. Auditor will verify on completion.`, "info");
1127
+ ctx.ui.notify(`Goal started: ${shortObj(goal.objective)} — the auditor will verify on completion. (id: ${goal.id})`, "info");
1095
1128
  scheduleContinuation(ctx, true);
1096
1129
  }
1097
1130
 
@@ -2612,10 +2645,11 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
2612
2645
  details: {},
2613
2646
  };
2614
2647
  }
2648
+ const liveCtx = (execCtx as ExtensionContext | undefined) ?? ctx;
2615
2649
  // v0.14.0: the interview floor — no Confirm until the user replied.
2616
2650
  // v0.23.8: /glla autoaccept=on skips the floor AND the Confirm —
2617
2651
  // the seed carries the intent (unattended rigs). Default off.
2618
- const autoAccept = loadSettings(ctx.cwd).autoAcceptDrafts === true;
2652
+ const autoAccept = loadSettings(liveCtx.cwd).autoAcceptDrafts === true;
2619
2653
  if (!autoAccept) {
2620
2654
  if (draftingUserReplies === 0) draftingBlockedProposals++;
2621
2655
  const block = draftProposalBlock(draftingUserReplies, draftingBlockedProposals);
@@ -2630,7 +2664,6 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
2630
2664
  details: {},
2631
2665
  };
2632
2666
  }
2633
- const liveCtx = (execCtx as ExtensionContext | undefined) ?? ctx;
2634
2667
  // v0.28.1 (S3): honest staleness warning before any Confirm attempt.
2635
2668
  warnIfStaleAtEntry(liveCtx, "goal drafting");
2636
2669
  // Multi-item list draft: one Confirm for the whole batch.
@@ -2645,21 +2678,19 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
2645
2678
  liveCtx.ui.notify(`List batch auto-accepted (/glla autoaccept=on): ${p.items.length} items${batchActivates ? " — item 1 ACTIVATES now" : ""}.`, "info");
2646
2679
  appendLedger(liveCtx.cwd, "draft_autoaccepted", { kind: "batch", count: p.items.length });
2647
2680
  } else {
2648
- try {
2649
- batchConfirmed = await liveCtx.ui.confirm(
2650
- "Confirm list batch",
2651
- `${p.items.length} items:\n${preview}${batchActivates ? "\n\n(List is empty — confirming ACTIVATES item 1 immediately as the active goal.)" : ""}`,
2652
- );
2653
- } catch (err) {
2654
- // v0.28.1 (T1): a stale confirm is NOT a rejection — nothing was
2681
+ const c = await confirmDraft(
2682
+ liveCtx,
2683
+ "Confirm list batch",
2684
+ `${p.items.length} items:\n${preview}${batchActivates ? "\n\n(List is empty — confirming ACTIVATES item 1 immediately as the active goal.)" : ""}`,
2685
+ );
2686
+ if (c === "stale") {
2687
+ // v0.28.1 (T1): a stale dialog is NOT a rejection — nothing was
2655
2688
  // refused; the dialog simply can't render in a doomed process.
2656
- if (isStaleApiError(err)) {
2657
- extensionApiStale = true;
2658
- appendLedger(liveCtx.cwd, "extension_api_stale", { where: "batch confirm" });
2659
- return { content: [{ type: "text", text: "The Confirm dialog could not render: pi invalidated this session's extension handle (session replacement). This is NOT a rejection — do NOT refine or re-propose. Tell the user to restart pi, then re-run the drafting flow." }], details: {} };
2660
- }
2661
- batchConfirmed = false;
2689
+ extensionApiStale = true;
2690
+ appendLedger(liveCtx.cwd, "extension_api_stale", { where: "batch confirm" });
2691
+ return { content: [{ type: "text", text: "The Confirm dialog could not render: pi invalidated this session's extension handle (session replacement). This is NOT a rejection — do NOT refine or re-propose. Tell the user to restart pi, then re-run the drafting flow." }], details: {} };
2662
2692
  }
2693
+ batchConfirmed = c === "yes";
2663
2694
  }
2664
2695
  if (!batchConfirmed) {
2665
2696
  return {
@@ -2696,17 +2727,14 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
2696
2727
  liveCtx.ui.notify(`Draft auto-accepted (/glla autoaccept=on)${willActivate ? " — ACTIVATING now" : ""}: ${p.objective.trim().slice(0, 90)}`, "info");
2697
2728
  appendLedger(liveCtx.cwd, "draft_autoaccepted", { kind: isListDraft ? "list" : "goal", objective: p.objective.trim().slice(0, 200) });
2698
2729
  } else {
2699
- try {
2700
- confirmed = await liveCtx.ui.confirm(isListDraft ? "Confirm list item" : "Confirm goal", `${p.objective.trim()}${contractBlock}${activationNote}`);
2701
- } catch (err) {
2702
- // v0.28.1 (T1): a stale confirm is NOT "Draft rejected by the user".
2703
- if (isStaleApiError(err)) {
2704
- extensionApiStale = true;
2705
- appendLedger(liveCtx.cwd, "extension_api_stale", { where: "draft confirm" });
2706
- return { content: [{ type: "text", text: "The Confirm dialog could not render: pi invalidated this session's extension handle (session replacement). This is NOT a rejection — do NOT refine or re-propose. Tell the user to restart pi, then re-run the drafting flow." }], details: {} };
2707
- }
2708
- confirmed = false;
2730
+ const c = await confirmDraft(liveCtx, isListDraft ? "Confirm list item" : "Confirm goal", `${p.objective.trim()}${contractBlock}${activationNote}`);
2731
+ if (c === "stale") {
2732
+ // v0.28.1 (T1): a stale dialog is NOT "Draft rejected by the user".
2733
+ extensionApiStale = true;
2734
+ appendLedger(liveCtx.cwd, "extension_api_stale", { where: "draft confirm" });
2735
+ return { content: [{ type: "text", text: "The Confirm dialog could not render: pi invalidated this session's extension handle (session replacement). This is NOT a rejection — do NOT refine or re-propose. Tell the user to restart pi, then re-run the drafting flow." }], details: {} };
2709
2736
  }
2737
+ confirmed = c === "yes";
2710
2738
  }
2711
2739
  if (!confirmed) {
2712
2740
  return {
@@ -2811,7 +2839,7 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
2811
2839
  // drafter path was still defaulting to 50 after v0.23.6 flipped the
2812
2840
  // CLI default.
2813
2841
  const max = p.max !== undefined && Number.isFinite(p.max) && p.max >= 0 ? Math.floor(p.max) : metricless ? 0 : 50;
2814
- const autoAccept = loadSettings(ctx.cwd).autoAcceptDrafts === true;
2842
+ const autoAccept = loadSettings(liveCtx.cwd).autoAcceptDrafts === true;
2815
2843
  let confirmed = false;
2816
2844
  if (autoAccept) {
2817
2845
  confirmed = true;
@@ -2819,12 +2847,14 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
2819
2847
  appendLedger(liveCtx.cwd, "draft_autoaccepted", { kind: "loop", target: p.target.trim().slice(0, 200), metricless });
2820
2848
  } else {
2821
2849
  try {
2822
- confirmed = await liveCtx.ui.confirm(
2850
+ const c = await confirmDraft(
2851
+ liveCtx,
2823
2852
  "Confirm loop",
2824
2853
  metricless
2825
2854
  ? `Target: ${p.target.trim()}\n\nMeasure: NONE — metricless spec loop. There is NO plateau stop: the loop ends only at ${max > 0 ? `${max} iterations` : "NO iteration cap"}${typeof p.time === "number" && p.time > 0 ? ` · Time bound: ${p.time}h` : ""}${typeof p.tokens === "number" && p.tokens > 0 ? ` · Token bound: ${p.tokens.toLocaleString()}` : ""} · /loop stop.${p.branch ? "\nbranch mode: scratch branch, every iteration committed (clean tree required)" : ""}\n\nEvery iteration must make ONE real, inspectable change — cosmetic churn is the known failure mode (doorknob-polishing). Start it?`
2826
2855
  : `Target: ${p.target.trim()}\n\nMeasure: ${p.measureCmd}\nTest-run output: ${rawOutput.slice(0, 200)}\nParsed number: ${parsed} (${p.direction === "min" ? "lower is better" : "higher is better"})\n\nPlateau stop: ${window} non-improving iterations · Cap: ${max > 0 ? `${max} iterations` : "none (unbounded)"}${typeof p.time === "number" && p.time > 0 ? ` · Time bound: ${p.time}h` : ""}${typeof p.tokens === "number" && p.tokens > 0 ? ` · Token bound: ${p.tokens.toLocaleString()}` : ""}${p.branch ? "\nbranch mode: scratch branch (clean tree required)" : ""}\n\nThe loop never completes — it runs until one of these bounds, plateau, or /loop stop. Start it?`,
2827
2856
  );
2857
+ confirmed = c === "yes";
2828
2858
  } catch {
2829
2859
  confirmed = false;
2830
2860
  }
@@ -2904,13 +2934,20 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
2904
2934
  }
2905
2935
  }
2906
2936
  let confirmed = false;
2907
- try {
2908
- confirmed = await liveCtx.ui.confirm(
2909
- "Confirm loop spec refinement",
2937
+ if (loadSettings(liveCtx.cwd).autoAcceptDrafts === true) {
2938
+ confirmed = true;
2939
+ liveCtx.ui.notify("Loop spec refinement auto-accepted (/glla autoaccept=on).", "info");
2940
+ appendLedger(liveCtx.cwd, "draft_autoaccepted", { kind: "loop-refine" });
2941
+ } else {
2942
+ try {
2943
+ confirmed = (await confirmDraft(
2944
+ liveCtx,
2945
+ "Confirm loop spec refinement",
2910
2946
  `Rationale: ${p.rationale}\n\nTarget:\n old: ${loop.target.slice(0, 120)}\n new: ${newTarget.slice(0, 120)}\n\nMeasure:\n old: ${loop.measureCmd}\n new: ${newMeasure}${newMeasure !== loop.measureCmd ? `\n test-run: ${testOutput.slice(0, 120)} → ${newBaseline}` : ""}\n\nThe loop keeps running against the refined spec (iteration ${loop.iteration} so far). Apply?`,
2911
- );
2912
- } catch {
2913
- confirmed = false;
2947
+ )) === "yes";
2948
+ } catch {
2949
+ confirmed = false;
2950
+ }
2914
2951
  }
2915
2952
  if (!confirmed) {
2916
2953
  return { content: [{ type: "text", text: "Refinement rejected by the user. The loop continues against the current spec — keep improving the metric as defined." }], details: {} };
@@ -3048,7 +3085,7 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
3048
3085
  const subs = (t.subtasks ?? []).map((s, j) => ` ${i + 1}.${j + 1} ${s}`).join("\n");
3049
3086
  return `${i + 1}. ${t.title}` + (subs ? `\n${subs}` : "");
3050
3087
  }).join("\n");
3051
- const autoAcceptTasks = loadSettings(ctx.cwd).autoAcceptDrafts === true;
3088
+ const autoAcceptTasks = loadSettings(liveCtx.cwd).autoAcceptDrafts === true;
3052
3089
  let confirmed = false;
3053
3090
  if (autoAcceptTasks) {
3054
3091
  confirmed = true;
@@ -3056,7 +3093,7 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
3056
3093
  appendLedger(liveCtx.cwd, "draft_autoaccepted", { kind: "tasks", count: p.tasks.length });
3057
3094
  } else {
3058
3095
  try {
3059
- confirmed = await liveCtx.ui.confirm("Confirm task list", preview);
3096
+ confirmed = (await confirmDraft(liveCtx, "Confirm task list", preview)) === "yes";
3060
3097
  } catch {
3061
3098
  confirmed = false;
3062
3099
  }
@@ -3453,17 +3490,17 @@ async function cmdToolOverride(args: string, ctx: ExtensionContext): Promise<voi
3453
3490
  if (action === "allow") {
3454
3491
  const allow = current.allow ?? [];
3455
3492
  if (!allow.includes(tool)) apply({ allow: [...allow, tool] });
3456
- ctx.ui.notify(`toolOverrides.allow += ${tool}`, "info");
3493
+ ctx.ui.notify(`"${tool}" is now always visible to the agent (project override saved).`, "info");
3457
3494
  } else if (action === "hide") {
3458
3495
  const hide = current.hide ?? [];
3459
3496
  if (!hide.includes(tool)) apply({ hide: [...hide, tool] });
3460
- ctx.ui.notify(`toolOverrides.hide += ${tool}`, "info");
3497
+ ctx.ui.notify(`"${tool}" is now always hidden from the agent (project override saved).`, "info");
3461
3498
  } else if (action === "unallow") {
3462
3499
  apply({ allow: (current.allow ?? []).filter((t) => t !== tool) });
3463
- ctx.ui.notify(`toolOverrides.allow -= ${tool}`, "info");
3500
+ ctx.ui.notify(`"${tool}" visibility override removed — the session decides again.`, "info");
3464
3501
  } else {
3465
3502
  apply({ hide: (current.hide ?? []).filter((t) => t !== tool) });
3466
- ctx.ui.notify(`toolOverrides.hide -= ${tool}`, "info");
3503
+ ctx.ui.notify(`"${tool}" hide override removed — the session decides again.`, "info");
3467
3504
  }
3468
3505
  return;
3469
3506
  }
@@ -3490,7 +3527,12 @@ async function cmdToolOverride(args: string, ctx: ExtensionContext): Promise<voi
3490
3527
  }
3491
3528
  cfg[tool] = toolCfg;
3492
3529
  apply({ perToolConfig: cfg });
3493
- ctx.ui.notify(`toolOverrides.perToolConfig.${tool} ${action === "set" ? "set" : "unset"}`, "info");
3530
+ ctx.ui.notify(
3531
+ action === "set"
3532
+ ? `"${tool}" setting saved: ${kv.slice(0, kv.indexOf("="))} = ${JSON.stringify(toolCfg[kv.slice(0, kv.indexOf("="))])} (project override).`
3533
+ : `"${tool}" setting "${kv}" removed — back to the built-in default.`,
3534
+ "info",
3535
+ );
3494
3536
  return;
3495
3537
  }
3496
3538
  ctx.ui.notify(`Unknown tooloverride action: ${action}. Use: list | allow | hide | unallow | unhide | set | unset.`, "warning");
@@ -3530,7 +3572,7 @@ async function cmdReviewerSettings(ctx: ExtensionContext): Promise<void> {
3530
3572
  const cfg = load();
3531
3573
  let choice: string | undefined;
3532
3574
  try {
3533
- choice = await ctx.ui.select("Reviewer — post-completion follow-up enqueuer (project settings)", reviewerMenuOptions(cfg));
3575
+ choice = await ctx.ui.select("Postaudit — post-completion follow-up enqueuer (project settings)", reviewerMenuOptions(cfg));
3534
3576
  } catch {
3535
3577
  return;
3536
3578
  }
@@ -3557,8 +3599,10 @@ async function cmdReviewerSettings(ctx: ExtensionContext): Promise<void> {
3557
3599
  const n = Number(v?.trim());
3558
3600
  if (Number.isSafeInteger(n) && n >= 1 && n <= 100) save({ maxReviewsPerDay: n });
3559
3601
  }
3560
- } catch {
3561
- /* individual save failures are non-fatal */
3602
+ } catch (err) {
3603
+ // v0.28.11 (E7): a swallowed save failure made the user believe the
3604
+ // toggle landed. Loud now.
3605
+ ctx.ui.notify(`Postaudit setting NOT saved: ${err instanceof Error ? err.message : String(err)} — check .pi-glla/settings.json permissions.`, "warning");
3562
3606
  }
3563
3607
  }
3564
3608
  }
@@ -4041,7 +4085,7 @@ export default function (pi: ExtensionAPI): void {
4041
4085
  handler: settingsHandler,
4042
4086
  });
4043
4087
  pi.registerCommand("review", {
4044
- description: "Manually run the reviewer on an archived goal: /review <goal-id> [off|on|auto|aggressive] — extracts findings, writes a report to .pi-glla/reviews/, cascades per the mode (auto/aggressive = no Confirms). Bypasses the trigger gates (explicit user request).",
4088
+ description: "Manually run the postaudit on an archived goal: /review <goal-id> [off|on|auto|aggressive] — extracts findings, writes a report to .pi-glla/reviews/, cascades per the mode (auto/aggressive = no Confirms). Bypasses the trigger gates (explicit user request).",
4045
4089
  handler: (args: string, ctx: ExtensionContext) => { rememberCtx(ctx); return cmdReview(args, ctx); },
4046
4090
  });
4047
4091
  pi.registerCommand("list", {
@@ -226,27 +226,27 @@ export function runReviewer(
226
226
  deps: ReviewerDeps,
227
227
  ): ReviewerOutcome {
228
228
  const none = (suppressedReason: string): ReviewerOutcome => ({ fired: false, suppressedReason, enqueued: 0, proposed: 0 });
229
- if (!config.enabled && !deps.manual) return none("reviewer disabled");
229
+ if (!config.enabled && !deps.manual) return none("the postaudit is disabled (/glla postaudit → Enabled)");
230
230
  // v0.27.5: "off" mode is the user-friendly way to silence the postaudit
231
231
  // — equivalent to enabled=false but exposed via the postaudit menu.
232
- if (config.mode === "off" && !deps.manual) return none("postaudit mode = off");
232
+ if (config.mode === "off" && !deps.manual) return none("postaudit mode is off (/glla postaudit → Mode)");
233
233
  const event = source.kind === "goal" ? `${source.terminal}` : "list-complete";
234
234
  if (!deps.manual) {
235
- if (config.doNotFireOn.includes(event)) return none(`doNotFireOn: ${event}`);
236
- if (source.kind === "goal" && source.terminal !== "goal-complete") return none(`not a completion: ${source.terminal}`);
237
- if (!config.fireOn.includes(source.kind === "goal" ? "goal-complete" : "list-complete")) return none("fireOn excludes this event");
235
+ if (config.doNotFireOn.includes(event)) return none(`this event type (${event}) is excluded in /glla postaudit → fire-on`);
236
+ if (source.kind === "goal" && source.terminal !== "goal-complete") return none(`the goal ended as ${source.terminal}, not a completion — no follow-up fires`);
237
+ if (!config.fireOn.includes(source.kind === "goal" ? "goal-complete" : "list-complete")) return none("this event type is excluded in /glla postaudit → fire-on");
238
238
  // v0.26.2: in auto mode the queue emptying is the cascade's natural
239
239
  // rhythm, not a runaway — the refire window must not strangle it.
240
240
  // (The per-day cap below still bounds everything.)
241
241
  const refireWindowApplies = !(config.mode === "auto" && source.kind === "list");
242
242
  if (refireWindowApplies && reviewerFiredRecently(deps.ledgerEntries, REVIEWER_REFIRE_WINDOW_MS, deps.nowMs)) {
243
243
  deps.ledger("reviewer_suppressed", { reason: "refire-window", goalId: source.goalId });
244
- return none("reviewer fired within the last 5 minutes (runaway prevention)");
244
+ return none("a postaudit ran within the last 5 minutes (runaway prevention)");
245
245
  }
246
246
  const today = reviewsToday(deps.ledgerEntries, deps.nowMs);
247
247
  if (today >= config.maxReviewsPerDay) {
248
248
  deps.ledger("reviewer_suppressed", { reason: "day-cap", count: today, cap: config.maxReviewsPerDay, goalId: source.goalId });
249
- return none(`day cap reached (${today}/${config.maxReviewsPerDay})`);
249
+ return none(`daily postaudit cap reached (${today}/${config.maxReviewsPerDay}) — /glla postaudit → Max reviews`);
250
250
  }
251
251
  }
252
252
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-goal-list-loop-audit",
3
- "version": "0.28.10",
3
+ "version": "0.28.12",
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",
@@ -93,21 +93,28 @@ Do NOT spawn more subagents of the failed type until quota resets — you will j
93
93
 
94
94
  ## DETACHED COMMIT DETECTION
95
95
 
96
- If your commits keep getting rewritten away (same content, new SHA — or worse, content reverted), check the auto-committer daemon BEFORE diagnosing yourself as stuck or broken:
96
+ If your commits keep getting rewritten away (same content, new SHA — or worse, content reverted), check for an **auto-commit daemon** BEFORE diagnosing yourself as stuck or broken. **Skip this section entirely if your rig has no auto-committer** — most rigs don't. (The maintainer's rigs run one called `dracon-sync`; yours may run a different one, or none.)
97
+
98
+ Generic forensics (safe everywhere):
97
99
 
98
100
  ```bash
99
101
  git reflog --date=iso | grep -E "filter-branch|filter-repo"
100
102
  git reflog --date=iso | grep -E ": reset:"
103
+ ```
104
+
105
+ If those show rewrites you didn't make, find the daemon (example for the maintainer's rig — adapt the pattern to yours):
106
+
107
+ ```bash
101
108
  ps -fea | grep -E "dracon-sync|filter-repo" | grep -v grep
102
109
  ```
103
110
 
104
- The auto-committer (`dracon-sync daemon`) may be running `auto_rewrite_large_blobs` on your commits. The fix is NOT to keep re-committing — it is to:
111
+ An auto-committer may be rewriting your commits (e.g. `dracon-sync daemon` running `auto_rewrite_large_blobs`). The fix is NOT to keep re-committing — it is to:
105
112
 
106
- 1. Pause the daemon (`dracon-sync pause`, or write the `.pi-glla/.pause-auto-commit` sentinel via the goal tools).
107
- 2. Investigate the rewrite trigger (daemon config `max_push_blob_bytes`, `auto_rewrite_large_blobs`).
113
+ 1. Pause the daemon if one exists (on the maintainer's rigs: `dracon-sync pause`, or write the `.pi-glla/.pause-auto-commit` sentinel via the goal tools).
114
+ 2. Investigate the rewrite trigger (for `dracon-sync`: daemon config `max_push_blob_bytes`, `auto_rewrite_large_blobs`).
108
115
  3. Add `.pi-glla/` to the daemon's exclude list.
109
116
 
110
- Do NOT conclude "the loop is too eager" or "I am broken" before checking what the daemon is doing — the daemon rewriting your commits makes YOUR loop look broken when it is not.
117
+ Do NOT conclude "the loop is too eager" or "I am broken" before checking what a daemon is doing — a daemon rewriting your commits makes YOUR loop look broken when it is not.
111
118
 
112
119
  ## TASK WORKFLOW
113
120