pi-goal-list-loop-audit 0.28.9 → 0.28.11

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
@@ -27,7 +27,7 @@ Install:
27
27
  pi install npm:pi-goal-list-loop-audit
28
28
  ```
29
29
 
30
- Four top-level commands, that's all:
30
+ Five top-level commands `/goal`, `/list`, `/loop`, `/glla`, `/review`:
31
31
 
32
32
  ```
33
33
  /goal # drafting: agent grills, you Confirm
@@ -40,12 +40,13 @@ Four top-level commands, that's all:
40
40
  /goal cancel # abort
41
41
  /goal tweak "<new objective>" # edit in place (Confirm dialog)
42
42
  /goal archive # archived goals, newest first
43
- /glla # open the settings UI (or /glla key=value)
43
+ /glla # settings UI table · /glla key=value · /glla stats · /glla audits [N|full] · /glla postaudit · /glla autoaccept=on
44
44
  /list fix the login bug, add dark mode, write docs # dump it — the agent shapes it into items, one Confirm
45
45
  /list plan.md # file detected → bulk import, one Confirm (sisyphus/Ralph style)
46
46
  /list <paste a checklist> # multi-line paste → same batch flow
47
47
  /list "fix the flaky test. Done when: npm test green" # explicit contract → added directly, no interview
48
48
  /list # show the list (add/import are optional no-op aliases — detection routes everything)
49
+ ```
49
50
 
50
51
  (Or just say it: "queue these 10 things…" — the agent manages the list too.)
51
52
 
@@ -53,6 +54,8 @@ Four top-level commands, that's all:
53
54
  `/list next <n>` or the agent's `list_activate` tool picks any item — with
54
55
  subagents, what gets worked next is a choice, not a position. Numbering always
55
56
  matches `/list show`.
57
+
58
+ ```
56
59
  /list # show active + waiting items
57
60
  /list next # skip current, activate next
58
61
  /list remove <n> # drop item n from the list
@@ -67,6 +70,7 @@ matches `/list show`.
67
70
  /loop start "keep improving SPEC.md" measure=none max=20 # metricless with an explicit cap (v0.23.0)
68
71
  /loop status # iteration, best, stall, recent values
69
72
  /loop stop # halt with summary
73
+ /review <goal-id> [off|on|auto|aggressive] # re-review an archived goal (bypasses the trigger gates)
70
74
  ```
71
75
 
72
76
  **Metricless loops** (`measure=none`): for genuinely endless work — an
@@ -311,7 +315,7 @@ prompts/
311
315
  goal-loop-forever-draft.md # /loop drafting prompt
312
316
  scripts/
313
317
  smoke.sh # live integration harness (tmux + real models)
314
- tests/ # 168 unit tests, no live pi required
318
+ tests/ # 545 tests across 58 files, no live pi required (mock-ctx harness drives the orchestrator)
315
319
  docs/DESIGN.md # architectural decisions
316
320
  PLAN.md # milestones, decisions, gates
317
321
  ```
@@ -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
  }
@@ -758,6 +758,9 @@ function persistState(ctx: ExtensionContext): void {
758
758
  // once per recovery. The TUI flag (buildWidgetLines) carries the standing
759
759
  // state; these notifies are the LOUD part.
760
760
  let persistenceDegradedNotified = false;
761
+
762
+ /** v0.28.11 (U9): objective-first notifies — truncate long objectives. */
763
+ const shortObj = (s: string): string => (s.length > 90 ? `${s.slice(0, 87)}…` : s);
761
764
  function notifyPersistenceState(ctx: ExtensionContext): void {
762
765
  if (isPersistenceDegraded() && !persistenceDegradedNotified) {
763
766
  persistenceDegradedNotified = true;
@@ -885,7 +888,7 @@ function fireReviewer(
885
888
  // to still count as "proposed" in the report + notify. Now the
886
889
  // failure is LOUD and the proposal goes uncounted.
887
890
  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.`,
891
+ `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
892
  "warning",
890
893
  );
891
894
  return false;
@@ -895,7 +898,7 @@ function fireReviewer(
895
898
  ledger: (type, value) => appendLedger(ctx.cwd, type, value),
896
899
  });
897
900
  if (!outcome.fired && outcome.suppressedReason && opts.manual) {
898
- ctx.ui.notify(`Reviewer suppressed: ${outcome.suppressedReason}`, "info");
901
+ ctx.ui.notify(`Postaudit suppressed: ${outcome.suppressedReason}`, "info");
899
902
  }
900
903
  // v0.27.5: surface the silent review to interactive users. The internal
901
904
  // runReviewer notify fires DURING the goal-completion handler, easy to
@@ -911,7 +914,7 @@ function fireReviewer(
911
914
  );
912
915
  }
913
916
  } catch (err) {
914
- ctx.ui.notify(`Reviewer failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`, "warning");
917
+ ctx.ui.notify(`Postaudit failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`, "warning");
915
918
  }
916
919
  }
917
920
 
@@ -1088,10 +1091,10 @@ async function cmdSet(args: string, ctx: ExtensionContext, skipDraft = false): P
1088
1091
  // v0.28.1 (S3): the goal is persisted — mark the interrupt so the next
1089
1092
  // fresh session auto-resumes, and tell the truth instead of "starting now".
1090
1093
  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");
1094
+ 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
1095
  return;
1093
1096
  }
1094
- ctx.ui.notify(`Goal ${goal.id} created starting now. Auditor will verify on completion.`, "info");
1097
+ ctx.ui.notify(`Goal started: ${shortObj(goal.objective)} — the auditor will verify on completion. (id: ${goal.id})`, "info");
1095
1098
  scheduleContinuation(ctx, true);
1096
1099
  }
1097
1100
 
@@ -3453,17 +3456,17 @@ async function cmdToolOverride(args: string, ctx: ExtensionContext): Promise<voi
3453
3456
  if (action === "allow") {
3454
3457
  const allow = current.allow ?? [];
3455
3458
  if (!allow.includes(tool)) apply({ allow: [...allow, tool] });
3456
- ctx.ui.notify(`toolOverrides.allow += ${tool}`, "info");
3459
+ ctx.ui.notify(`"${tool}" is now always visible to the agent (project override saved).`, "info");
3457
3460
  } else if (action === "hide") {
3458
3461
  const hide = current.hide ?? [];
3459
3462
  if (!hide.includes(tool)) apply({ hide: [...hide, tool] });
3460
- ctx.ui.notify(`toolOverrides.hide += ${tool}`, "info");
3463
+ ctx.ui.notify(`"${tool}" is now always hidden from the agent (project override saved).`, "info");
3461
3464
  } else if (action === "unallow") {
3462
3465
  apply({ allow: (current.allow ?? []).filter((t) => t !== tool) });
3463
- ctx.ui.notify(`toolOverrides.allow -= ${tool}`, "info");
3466
+ ctx.ui.notify(`"${tool}" visibility override removed — the session decides again.`, "info");
3464
3467
  } else {
3465
3468
  apply({ hide: (current.hide ?? []).filter((t) => t !== tool) });
3466
- ctx.ui.notify(`toolOverrides.hide -= ${tool}`, "info");
3469
+ ctx.ui.notify(`"${tool}" hide override removed — the session decides again.`, "info");
3467
3470
  }
3468
3471
  return;
3469
3472
  }
@@ -3490,7 +3493,12 @@ async function cmdToolOverride(args: string, ctx: ExtensionContext): Promise<voi
3490
3493
  }
3491
3494
  cfg[tool] = toolCfg;
3492
3495
  apply({ perToolConfig: cfg });
3493
- ctx.ui.notify(`toolOverrides.perToolConfig.${tool} ${action === "set" ? "set" : "unset"}`, "info");
3496
+ ctx.ui.notify(
3497
+ action === "set"
3498
+ ? `"${tool}" setting saved: ${kv.slice(0, kv.indexOf("="))} = ${JSON.stringify(toolCfg[kv.slice(0, kv.indexOf("="))])} (project override).`
3499
+ : `"${tool}" setting "${kv}" removed — back to the built-in default.`,
3500
+ "info",
3501
+ );
3494
3502
  return;
3495
3503
  }
3496
3504
  ctx.ui.notify(`Unknown tooloverride action: ${action}. Use: list | allow | hide | unallow | unhide | set | unset.`, "warning");
@@ -3530,7 +3538,7 @@ async function cmdReviewerSettings(ctx: ExtensionContext): Promise<void> {
3530
3538
  const cfg = load();
3531
3539
  let choice: string | undefined;
3532
3540
  try {
3533
- choice = await ctx.ui.select("Reviewer — post-completion follow-up enqueuer (project settings)", reviewerMenuOptions(cfg));
3541
+ choice = await ctx.ui.select("Postaudit — post-completion follow-up enqueuer (project settings)", reviewerMenuOptions(cfg));
3534
3542
  } catch {
3535
3543
  return;
3536
3544
  }
@@ -3557,8 +3565,10 @@ async function cmdReviewerSettings(ctx: ExtensionContext): Promise<void> {
3557
3565
  const n = Number(v?.trim());
3558
3566
  if (Number.isSafeInteger(n) && n >= 1 && n <= 100) save({ maxReviewsPerDay: n });
3559
3567
  }
3560
- } catch {
3561
- /* individual save failures are non-fatal */
3568
+ } catch (err) {
3569
+ // v0.28.11 (E7): a swallowed save failure made the user believe the
3570
+ // toggle landed. Loud now.
3571
+ ctx.ui.notify(`Postaudit setting NOT saved: ${err instanceof Error ? err.message : String(err)} — check .pi-glla/settings.json permissions.`, "warning");
3562
3572
  }
3563
3573
  }
3564
3574
  }
@@ -4041,7 +4051,7 @@ export default function (pi: ExtensionAPI): void {
4041
4051
  handler: settingsHandler,
4042
4052
  });
4043
4053
  pi.registerCommand("review", {
4044
- 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).",
4054
+ 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
4055
  handler: (args: string, ctx: ExtensionContext) => { rememberCtx(ctx); return cmdReview(args, ctx); },
4046
4056
  });
4047
4057
  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.9",
3
+ "version": "0.28.11",
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