pi-goal-list-loop-audit 0.27.6 → 0.27.7

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.
@@ -679,7 +679,7 @@ function archiveCurrentGoal(ctx: ExtensionContext, status: Status, stopReason?:
679
679
  function fireReviewer(
680
680
  ctx: ExtensionContext,
681
681
  source: { kind: "goal" | "list"; goalId: string; objective: string; terminal: string },
682
- opts: { manual?: boolean; mode?: "default" | "auto" | "report" } = {},
682
+ opts: { manual?: boolean; mode?: "off" | "default" | "auto" | "aggressive" | "report" } = {},
683
683
  ): void {
684
684
  try {
685
685
  const settings = loadSettings(ctx.cwd);
@@ -3068,13 +3068,16 @@ async function cmdReview(args: string, ctx: ExtensionContext): Promise<void> {
3068
3068
  const parts = args.trim().split(/\s+/).filter(Boolean);
3069
3069
  const id = parts[0] ?? "";
3070
3070
  const modeArg = parts[1];
3071
- const mode = modeArg === "auto" || modeArg === "report" || modeArg === "default" ? modeArg : undefined;
3071
+ const validModes = ["off", "default", "auto", "aggressive", "report"] as const;
3072
+ const mode = (validModes as readonly string[]).includes(modeArg ?? "")
3073
+ ? (modeArg as typeof validModes[number])
3074
+ : undefined;
3072
3075
  if (modeArg && !mode) {
3073
- ctx.ui.notify(`Unknown mode "${modeArg}" — use auto | report | default.`, "warning");
3076
+ ctx.ui.notify(`Unknown mode "${modeArg}" — use off | default | auto | aggressive | report.`, "warning");
3074
3077
  return;
3075
3078
  }
3076
3079
  if (!id) {
3077
- ctx.ui.notify("Usage: /review <goal-id> [auto|report|default] — see /goal archive for ids.", "info");
3080
+ ctx.ui.notify(`Usage: /review <goal-id> [${validModes.join("|")}] — see /goal archive for ids.`, "info");
3078
3081
  return;
3079
3082
  }
3080
3083
  // Resolve the id against the archive (suffix match allowed).
@@ -3098,15 +3101,22 @@ async function cmdReview(args: string, ctx: ExtensionContext): Promise<void> {
3098
3101
  fireReviewer(ctx, { kind: "goal", goalId, objective, terminal: "goal-complete" }, { manual: true, mode });
3099
3102
  }
3100
3103
 
3101
- /** v0.26.0: /glla reviewer — the reviewer config menu (project-scoped). */
3104
+ /** v0.27.5: /glla reviewer | postaudit — the post-completion audit config menu
3105
+ * (project-scoped). Reads the dual-write settings (postaudit wins over the
3106
+ * legacy reviewer key), and writes back to whichever key was read first —
3107
+ * so we don't drift two parallel config blocks. */
3102
3108
  async function cmdReviewerSettings(ctx: ExtensionContext): Promise<void> {
3109
+ const settings = loadSettings(ctx.cwd);
3110
+ const block = (settings.postaudit ?? settings.reviewer) as Partial<ReviewerConfig> | undefined;
3111
+ const settingsKey: "postaudit" | "reviewer" = settings.postaudit !== undefined ? "postaudit" : "reviewer";
3103
3112
  if (!ctx.hasUI) {
3104
- const cfg = resolveReviewerConfig(loadSettings(ctx.cwd).reviewer as Partial<ReviewerConfig> | undefined);
3105
- ctx.ui.notify(`reviewer (project): ${JSON.stringify(cfg, null, 2)}`, "info");
3113
+ const cfg = resolveReviewerConfig(block);
3114
+ ctx.ui.notify(`${settingsKey} (project): ${JSON.stringify(cfg, null, 2)}`, "info");
3106
3115
  return;
3107
3116
  }
3108
- const load = () => resolveReviewerConfig(loadSettings(ctx.cwd).reviewer as Partial<ReviewerConfig> | undefined);
3109
- const save = (patch: Partial<ReviewerConfig>) => saveSettings("project", ctx.cwd, { reviewer: { ...load(), ...patch } as Record<string, unknown> });
3117
+ const load = () => resolveReviewerConfig(loadSettings(ctx.cwd)[settingsKey] as Partial<ReviewerConfig> | undefined);
3118
+ const save = (patch: Partial<ReviewerConfig>) =>
3119
+ saveSettings("project", ctx.cwd, { [settingsKey]: { ...load(), ...patch } as Record<string, unknown> });
3110
3120
  for (;;) {
3111
3121
  const cfg = load();
3112
3122
  let choice: string | undefined;
@@ -3118,7 +3128,13 @@ async function cmdReviewerSettings(ctx: ExtensionContext): Promise<void> {
3118
3128
  if (!choice || choice === "Done") return;
3119
3129
  try {
3120
3130
  if (choice.startsWith("Enabled")) save({ enabled: !cfg.enabled });
3121
- else if (choice.startsWith("Mode")) save({ mode: cfg.mode === "default" ? "auto" : cfg.mode === "auto" ? "report" : "default" });
3131
+ else if (choice.startsWith("Mode")) {
3132
+ // v0.27.5: 5-state cycle off → default → auto → aggressive → report → off
3133
+ const order: Array<"off" | "default" | "auto" | "aggressive" | "report"> = ["off", "default", "auto", "aggressive", "report"];
3134
+ const i = order.indexOf(cfg.mode as typeof order[number]);
3135
+ const next = order[(i + 1) % order.length]!;
3136
+ save({ mode: next });
3137
+ }
3122
3138
  else if (choice.startsWith("Leverage mode")) save({ leverageMode: cfg.leverageMode === "fix-without-confirm" ? "confirm-all" : "fix-without-confirm" });
3123
3139
  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"] });
3124
3140
  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"] });
@@ -15,7 +15,7 @@
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";
18
+ export type ReviewerMode = "off" | "default" | "auto" | "aggressive" | "report";
19
19
 
20
20
  export interface ReviewerConfig {
21
21
  enabled: boolean;
@@ -203,6 +203,8 @@ export interface ReviewerOutcome {
203
203
  reportPath?: string;
204
204
  enqueued: number;
205
205
  proposed: number;
206
+ /** v0.27.5: the cascade step that actually fired (notify-and-idle | convert-findings-to-list | queue-leftovers | fire-audit-on-clean | propose-goal | aggressive-relaunch | report-only) — surfaces in /goal status and tests. */
207
+ cascadeStep?: string;
206
208
  }
207
209
 
208
210
  export const REVIEWER_REFIRE_WINDOW_MS = 5 * 60_000;
@@ -215,6 +217,9 @@ export function runReviewer(
215
217
  ): ReviewerOutcome {
216
218
  const none = (suppressedReason: string): ReviewerOutcome => ({ fired: false, suppressedReason, enqueued: 0, proposed: 0 });
217
219
  if (!config.enabled && !deps.manual) return none("reviewer disabled");
220
+ // v0.27.5: "off" mode is the user-friendly way to silence the postaudit
221
+ // — equivalent to enabled=false but exposed via the postaudit menu.
222
+ if (config.mode === "off" && !deps.manual) return none("postaudit mode = off");
218
223
  const event = source.kind === "goal" ? `${source.terminal}` : "list-complete";
219
224
  if (!deps.manual) {
220
225
  if (config.doNotFireOn.includes(event)) return none(`doNotFireOn: ${event}`);
@@ -243,7 +248,8 @@ export function runReviewer(
243
248
  let enqueued = 0;
244
249
  let proposed = 0;
245
250
  let cascadeStep = "notify-and-idle";
246
- const auto = config.mode === "auto";
251
+ const auto = config.mode === "auto" || config.mode === "aggressive";
252
+ const aggressive = config.mode === "aggressive";
247
253
  const reportOnly = config.mode === "report";
248
254
 
249
255
  // Cascade: findings → list items (leverage: fix-without-confirm).
@@ -255,8 +261,22 @@ export function runReviewer(
255
261
  }
256
262
  // Architectural findings: default mode → /goal proposal WITH Confirm;
257
263
  // auto mode → /list items (the auto-loop rolls straight into them).
264
+ // aggressive mode → enqueue AND relaunch as the next active goal
265
+ // (skips both Confirm and the queue — the unattended rig never stops).
258
266
  if (architectural.length > 0 && !reportOnly) {
259
- if (auto) {
267
+ if (aggressive) {
268
+ deps.enqueueListItems(architectural.map((f) => f.text));
269
+ // v0.27.5 aggressive: also propose the FIRST architectural finding
270
+ // as a relaunch so the queue gets burned through even when the
271
+ // unattended rig can't Confirm.
272
+ deps.proposeGoal(
273
+ architectural[0]!.text,
274
+ `aggressive postaudit: relaunching as /goal without Confirm (${architectural.length} architectural findings total)`,
275
+ );
276
+ enqueued += architectural.length;
277
+ proposed += 1;
278
+ cascadeStep = "aggressive-relaunch";
279
+ } else if (auto) {
260
280
  deps.enqueueListItems(architectural.map((f) => f.text));
261
281
  enqueued += architectural.length;
262
282
  cascadeStep = convertStep;
@@ -272,16 +292,22 @@ export function runReviewer(
272
292
  // Clean completion → audit: default mode proposes a /goal (Confirm);
273
293
  // auto mode enqueues the audit as a /list item (no Confirm — the
274
294
  // cascade keeps rolling until the findings run dry).
295
+ // aggressive mode → relaunch the audit goal directly (no Confirm).
275
296
  if (findings.length === 0 && config.cascade.includes("fire-audit-on-clean") && !reportOnly) {
276
297
  const auditObjective = `Post-completion regression scan after ${source.goalId} (${config.auditScope})`;
277
- if (auto) {
298
+ if (aggressive) {
299
+ deps.proposeGoal(auditObjective, "aggressive postaudit: clean completion — relaunching the regression scan as /goal");
300
+ proposed++;
301
+ cascadeStep = "aggressive-relaunch";
302
+ } else if (auto) {
278
303
  deps.enqueueListItems([auditObjective]);
279
304
  enqueued++;
305
+ cascadeStep = "fire-audit-on-clean";
280
306
  } else {
281
307
  deps.proposeGoal(auditObjective, "reviewer: completion looks clean — firing the audit step");
282
308
  proposed++;
309
+ cascadeStep = "fire-audit-on-clean";
283
310
  }
284
- cascadeStep = "fire-audit-on-clean";
285
311
  }
286
312
  if (reportOnly) cascadeStep = "report-only";
287
313
 
@@ -311,7 +337,7 @@ export function runReviewer(
311
337
  if (strategic.length > 0) {
312
338
  deps.notify(`Reviewer: ${strategic.length} strategic finding(s) need YOUR call — see the report's Strategic section.`, "warning");
313
339
  }
314
- return { fired: true, report, reportPath, enqueued, proposed };
340
+ return { fired: true, report, reportPath, enqueued, proposed, cascadeStep };
315
341
  }
316
342
 
317
343
  /** /glla reviewer menu options, derived from the config — extracted so
@@ -319,7 +345,7 @@ export function runReviewer(
319
345
  export function reviewerMenuOptions(cfg: ReviewerConfig): string[] {
320
346
  return [
321
347
  `Enabled — ${cfg.enabled ? "ON" : "OFF"}`,
322
- `Mode — ${cfg.mode} (default = Confirm-gated · auto = auto-loop, no Confirms · report = report only)`,
348
+ `Mode — ${cfg.mode} (off = silenced · default = Confirm-gated · auto = auto-loop, no Confirms · aggressive = auto + relaunch · report = report only)`,
323
349
  `Leverage mode — ${cfg.leverageMode} (bug/refactor findings)`,
324
350
  `Fire on goal-complete — ${cfg.fireOn.includes("goal-complete") ? "ON" : "OFF"}`,
325
351
  `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.27.6",
3
+ "version": "0.27.7",
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",