pi-goal-list-loop-audit 0.26.1 → 0.26.2

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.
@@ -583,11 +583,12 @@ function archiveCurrentGoal(ctx: ExtensionContext, status: Status, stopReason?:
583
583
  function fireReviewer(
584
584
  ctx: ExtensionContext,
585
585
  source: { kind: "goal" | "list"; goalId: string; objective: string; terminal: string },
586
- opts: { manual?: boolean } = {},
586
+ opts: { manual?: boolean; mode?: "default" | "auto" | "report" } = {},
587
587
  ): void {
588
588
  try {
589
589
  const settings = loadSettings(ctx.cwd);
590
590
  const config = resolveReviewerConfig(settings.reviewer as Partial<ReviewerConfig> | undefined);
591
+ if (opts.mode) config.mode = opts.mode;
591
592
  const sources: Array<{ name: string; text: string }> = [];
592
593
  try {
593
594
  sources.push({ name: "archive", text: fs.readFileSync(archivedGoalPath(ctx.cwd, source.goalId), "utf-8") });
@@ -2884,9 +2885,16 @@ async function openSettingsUI(ctx: ExtensionContext): Promise<void> {
2884
2885
 
2885
2886
  /** v0.26.0: /review <archived-goal-id> — manual reviewer invocation. */
2886
2887
  async function cmdReview(args: string, ctx: ExtensionContext): Promise<void> {
2887
- const id = args.trim();
2888
+ const parts = args.trim().split(/\s+/).filter(Boolean);
2889
+ const id = parts[0] ?? "";
2890
+ const modeArg = parts[1];
2891
+ const mode = modeArg === "auto" || modeArg === "report" || modeArg === "default" ? modeArg : undefined;
2892
+ if (modeArg && !mode) {
2893
+ ctx.ui.notify(`Unknown mode "${modeArg}" — use auto | report | default.`, "warning");
2894
+ return;
2895
+ }
2888
2896
  if (!id) {
2889
- ctx.ui.notify("Usage: /review <goal-id> — see /goal archive for ids.", "info");
2897
+ ctx.ui.notify("Usage: /review <goal-id> [auto|report|default] — see /goal archive for ids.", "info");
2890
2898
  return;
2891
2899
  }
2892
2900
  // Resolve the id against the archive (suffix match allowed).
@@ -2907,7 +2915,7 @@ async function cmdReview(args: string, ctx: ExtensionContext): Promise<void> {
2907
2915
  ctx.ui.notify(`No archive found for ${id}.`, "warning");
2908
2916
  return;
2909
2917
  }
2910
- fireReviewer(ctx, { kind: "goal", goalId, objective, terminal: "goal-complete" }, { manual: true });
2918
+ fireReviewer(ctx, { kind: "goal", goalId, objective, terminal: "goal-complete" }, { manual: true, mode });
2911
2919
  }
2912
2920
 
2913
2921
  /** v0.26.0: /glla reviewer — the reviewer config menu (project-scoped). */
@@ -2930,6 +2938,7 @@ async function cmdReviewerSettings(ctx: ExtensionContext): Promise<void> {
2930
2938
  if (!choice || choice === "Done") return;
2931
2939
  try {
2932
2940
  if (choice.startsWith("Enabled")) save({ enabled: !cfg.enabled });
2941
+ else if (choice.startsWith("Mode")) save({ mode: cfg.mode === "default" ? "auto" : cfg.mode === "auto" ? "report" : "default" });
2933
2942
  else if (choice.startsWith("Leverage mode")) save({ leverageMode: cfg.leverageMode === "fix-without-confirm" ? "confirm-all" : "fix-without-confirm" });
2934
2943
  else if (choice.startsWith("Fire on goal-complete")) save({ fireOn: cfg.fireOn.includes("goal-complete") ? cfg.fireOn.filter((e) => e !== "goal-complete") : [...cfg.fireOn, "goal-complete"] });
2935
2944
  else if (choice.startsWith("Fire on list-complete")) save({ fireOn: cfg.fireOn.includes("list-complete") ? cfg.fireOn.filter((e) => e !== "list-complete") : [...cfg.fireOn, "list-complete"] });
@@ -3368,7 +3377,7 @@ export default function (pi: ExtensionAPI): void {
3368
3377
  handler: settingsHandler,
3369
3378
  });
3370
3379
  pi.registerCommand("review", {
3371
- description: "Manually run the reviewer on an archived goal: /review <goal-id> — extracts findings, writes a report to .pi-glla/reviews/, enqueues bug-class fixes to /list, proposes architectural items as /goal. Bypasses the trigger gates (explicit user request).",
3380
+ description: "Manually run the reviewer on an archived goal: /review <goal-id> [auto|report|default] — extracts findings, writes a report to .pi-glla/reviews/, cascades per the mode (auto = auto-loop, no Confirms). Bypasses the trigger gates (explicit user request).",
3372
3381
  handler: (args: string, ctx: ExtensionContext) => { rememberCtx(ctx); return cmdReview(args, ctx); },
3373
3382
  });
3374
3383
  pi.registerCommand("list", {
@@ -15,8 +15,15 @@
15
15
  import * as fs from "node:fs";
16
16
  import * as path from "node:path";
17
17
 
18
+ export type ReviewerMode = "default" | "auto" | "report";
19
+
18
20
  export interface ReviewerConfig {
19
21
  enabled: boolean;
22
+ /** v0.26.2: default = Confirm-gated cascade; auto = auto-loop — every
23
+ * finding class (incl. architectural) and the clean-completion audit
24
+ * become /list items with zero Confirms (strategic stays notify-only —
25
+ * decisions never auto-fire); report = write the report + notify only. */
26
+ mode: ReviewerMode;
20
27
  fireOn: Array<"goal-complete" | "list-complete">;
21
28
  doNotFireOn: string[];
22
29
  cascade: Array<"convert-findings-to-list" | "queue-leftovers" | "fire-audit-on-clean" | "notify-and-idle">;
@@ -30,6 +37,7 @@ export interface ReviewerConfig {
30
37
 
31
38
  export const DEFAULT_REVIEWER_CONFIG: ReviewerConfig = {
32
39
  enabled: true,
40
+ mode: "default",
33
41
  fireOn: ["goal-complete", "list-complete"],
34
42
  doNotFireOn: ["goal-aborted", "goal-paused"],
35
43
  cascade: ["convert-findings-to-list", "queue-leftovers", "fire-audit-on-clean", "notify-and-idle"],
@@ -61,7 +69,7 @@ const CLASS_PATTERNS: Array<{ class: FindingClass; re: RegExp }> = [
61
69
  { class: "strategic", re: /\bshould we\b|\bdeprecat|ship this\??|strategic/i },
62
70
  { class: "architectural", re: /\brewrite\b|new dependency|schema change|architectural|redesign/i },
63
71
  { class: "bug", re: /\bTODO\b|\bFIXME\b|\bbug\b|\bissue\b|regression|broken|\bfixme\b/i },
64
- { class: "refactor", re: /could be cleaner|consider refactoring|duplicat|refactor|left ?out|follow[\s-]?up|deferred/i },
72
+ { class: "refactor", re: /could be cleaner|consider refactoring|duplicat|refactor|left ?out|follow[\s-]?up|deferred|could be improved|improvement|enhancement|consider adding|would be nice|nice to have/i },
65
73
  ];
66
74
 
67
75
  export function classifyFindingText(line: string): FindingClass | undefined {
@@ -110,6 +118,7 @@ export interface ReviewReport {
110
118
  objective: string;
111
119
  findings: Finding[];
112
120
  cascadeStep: string;
121
+ mode: ReviewerMode;
113
122
  at: string;
114
123
  }
115
124
 
@@ -120,7 +129,7 @@ export function formatReviewReport(r: ReviewReport): string {
120
129
  return [
121
130
  `# Review — ${r.goalId}`,
122
131
  "",
123
- `**Kind**: ${r.kind} · **At**: ${r.at}`,
132
+ `**Kind**: ${r.kind} · **At**: ${r.at} · **Mode**: ${r.mode}`,
124
133
  "",
125
134
  "## Summary",
126
135
  "",
@@ -186,7 +195,11 @@ export function runReviewer(
186
195
  if (config.doNotFireOn.includes(event)) return none(`doNotFireOn: ${event}`);
187
196
  if (source.kind === "goal" && source.terminal !== "goal-complete") return none(`not a completion: ${source.terminal}`);
188
197
  if (!config.fireOn.includes(source.kind === "goal" ? "goal-complete" : "list-complete")) return none("fireOn excludes this event");
189
- if (reviewerFiredRecently(deps.ledgerEntries, REVIEWER_REFIRE_WINDOW_MS, deps.nowMs)) {
198
+ // v0.26.2: in auto mode the queue emptying is the cascade's natural
199
+ // rhythm, not a runaway — the refire window must not strangle it.
200
+ // (The per-day cap below still bounds everything.)
201
+ const refireWindowApplies = !(config.mode === "auto" && source.kind === "list");
202
+ if (refireWindowApplies && reviewerFiredRecently(deps.ledgerEntries, REVIEWER_REFIRE_WINDOW_MS, deps.nowMs)) {
190
203
  deps.ledger("reviewer_suppressed", { reason: "refire-window", goalId: source.goalId });
191
204
  return none("reviewer fired within the last 5 minutes (runaway prevention)");
192
205
  }
@@ -205,32 +218,47 @@ export function runReviewer(
205
218
  let enqueued = 0;
206
219
  let proposed = 0;
207
220
  let cascadeStep = "notify-and-idle";
221
+ const auto = config.mode === "auto";
222
+ const reportOnly = config.mode === "report";
208
223
 
209
224
  // Cascade: findings → list items (leverage: fix-without-confirm).
210
225
  const convertStep = source.kind === "goal" ? "convert-findings-to-list" : "queue-leftovers";
211
- if (bugs.length > 0 && config.cascade.includes(convertStep)) {
226
+ if (bugs.length > 0 && config.cascade.includes(convertStep) && !reportOnly) {
212
227
  deps.enqueueListItems(bugs.map((f) => f.text));
213
228
  enqueued = bugs.length;
214
229
  cascadeStep = convertStep;
215
230
  }
216
- // Architectural findings → /goal proposal WITH Confirm.
217
- if (architectural.length > 0) {
218
- deps.proposeGoal(
219
- architectural.map((f) => f.text).join("; "),
220
- `reviewer found ${architectural.length} architectural-class finding(s) needs your Confirm`,
221
- );
222
- proposed += architectural.length;
223
- cascadeStep = "propose-goal";
231
+ // Architectural findings: default mode → /goal proposal WITH Confirm;
232
+ // auto mode → /list items (the auto-loop rolls straight into them).
233
+ if (architectural.length > 0 && !reportOnly) {
234
+ if (auto) {
235
+ deps.enqueueListItems(architectural.map((f) => f.text));
236
+ enqueued += architectural.length;
237
+ cascadeStep = convertStep;
238
+ } else {
239
+ deps.proposeGoal(
240
+ architectural.map((f) => f.text).join("; "),
241
+ `reviewer found ${architectural.length} architectural-class finding(s) — needs your Confirm`,
242
+ );
243
+ proposed += architectural.length;
244
+ cascadeStep = "propose-goal";
245
+ }
224
246
  }
225
- // Clean completion → audit /goal (opt-in cascade step).
226
- if (findings.length === 0 && config.cascade.includes("fire-audit-on-clean")) {
227
- deps.proposeGoal(
228
- `Post-completion regression scan after ${source.goalId} (${config.auditScope})`,
229
- "reviewer: completion looks clean firing the audit step",
230
- );
231
- proposed++;
247
+ // Clean completion → audit: default mode proposes a /goal (Confirm);
248
+ // auto mode enqueues the audit as a /list item (no Confirm — the
249
+ // cascade keeps rolling until the findings run dry).
250
+ if (findings.length === 0 && config.cascade.includes("fire-audit-on-clean") && !reportOnly) {
251
+ const auditObjective = `Post-completion regression scan after ${source.goalId} (${config.auditScope})`;
252
+ if (auto) {
253
+ deps.enqueueListItems([auditObjective]);
254
+ enqueued++;
255
+ } else {
256
+ deps.proposeGoal(auditObjective, "reviewer: completion looks clean — firing the audit step");
257
+ proposed++;
258
+ }
232
259
  cascadeStep = "fire-audit-on-clean";
233
260
  }
261
+ if (reportOnly) cascadeStep = "report-only";
234
262
 
235
263
  const report: ReviewReport = {
236
264
  goalId: source.goalId,
@@ -238,6 +266,7 @@ export function runReviewer(
238
266
  objective: source.objective,
239
267
  findings,
240
268
  cascadeStep,
269
+ mode: config.mode,
241
270
  at: new Date(deps.nowMs).toISOString(),
242
271
  };
243
272
  const reportPath = writeReviewReport(deps.cwd, report);
@@ -265,6 +294,7 @@ export function runReviewer(
265
294
  export function reviewerMenuOptions(cfg: ReviewerConfig): string[] {
266
295
  return [
267
296
  `Enabled — ${cfg.enabled ? "ON" : "OFF"}`,
297
+ `Mode — ${cfg.mode} (default = Confirm-gated · auto = auto-loop, no Confirms · report = report only)`,
268
298
  `Leverage mode — ${cfg.leverageMode} (bug/refactor findings)`,
269
299
  `Fire on goal-complete — ${cfg.fireOn.includes("goal-complete") ? "ON" : "OFF"}`,
270
300
  `Fire on list-complete — ${cfg.fireOn.includes("list-complete") ? "ON" : "OFF"}`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-goal-list-loop-audit",
3
- "version": "0.26.1",
3
+ "version": "0.26.2",
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",