pi-goal-list-loop-audit 0.29.5 → 0.29.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.
package/README.md CHANGED
@@ -42,7 +42,7 @@ Five top-level commands — `/goal`, `/list`, `/loop`, `/glla`, `/review`:
42
42
  /goal audit # run the isolated auditor on the current goal now — no agent turn (v0.28.27)
43
43
  /goal tweak "<new objective>" # edit in place (Confirm dialog)
44
44
  /goal archive # archived goals, newest first
45
- /glla # settings UI table · /glla key=value · /glla stats · /glla audits [N|full] · /glla postaudit · /glla autoaccept=on
45
+ /glla # settings UI table · /glla key=value · /glla stats · /glla audits [N|full] · /glla postaudit · /glla wipe (nuclear reset, Confirm-gated) · /glla autoaccept=on
46
46
  /list fix the login bug, add dark mode, write docs # dump it — the agent shapes it into items, one Confirm
47
47
  /list plan.md # file detected → bulk import, one Confirm (sisyphus/Ralph style)
48
48
  /list <paste a checklist> # multi-line paste → same batch flow
@@ -194,7 +194,29 @@ running, you can see it — no command needed.
194
194
  A 15s heartbeat detects the precise stall condition — active goal/loop + idle
195
195
  session + nothing scheduled + quiet for 60s — and re-fires the continuation
196
196
  itself. Three consecutive zero-tool turns pause the goal / stop the loop.
197
- No external watchdog plugin needed.
197
+ No external watchdog plugin needed. It also recovers **stranded audits**
198
+ (v0.29.1): a goal stuck in `auditing` with no auditor session alive re-runs
199
+ the stored claim after 90s instead of black-holing. Storm protection: the
200
+ send→pause→notify path rearms once per cycle and loud-stops after a 6-error
201
+ brake streak, so a broken provider can't spin forever.
202
+
203
+ **User aborts mean STOP** (v0.29.4): Esc-aborting a turn stands the chain
204
+ down with a named notify (`/goal resume` to continue) — it does NOT count
205
+ toward stall warnings, does NOT auto re-fire, and the stand-down survives
206
+ the heartbeat (v0.29.5). Five consecutive aborts still pause loudly as a
207
+ backstop. A queued steer interrupt (`length`/`toolResult` races) is not an
208
+ abort and resumes normally.
209
+
210
+ ## One active thing (auto-arbitrated)
211
+
212
+ At most one goal/list-item/loop owns the active slot — activation guards
213
+ refuse a second live thing in-session, and **session loads auto-arbitrate
214
+ dirty stacked state** (v0.29.6): if a pre-guard project persisted a live
215
+ loop AND a live goal, the one with the most recent activity keeps the slot
216
+ and the loser is ARCHIVED (recoverable under `.pi-glla/archive/` / `/loop
217
+ status`), never silently wiped. No picker, no per-start arbitration chores.
218
+ The queued list is a backlog, not a second live thing — untouched.
219
+ `/glla wipe` remains the manual, Confirm-gated clean slate.
198
220
 
199
221
  ## Config (one global place, rarely opened)
200
222
 
@@ -206,17 +228,21 @@ No external watchdog plugin needed.
206
228
  /glla tokenlimit=10000000 # per-goal token budget (default: off) → GLOBAL
207
229
  /glla tokenlimit=0 # explicitly no cap (the default)
208
230
  /glla wedgealert=30 # hung-command alert minutes (default: 30, 0 = off)
209
- /glla autoresume=on # auto-resume goals/loops on ANY session start (default: load HELD, never auto-start — explicit /goal resume, /list resume, or /loop; off: never)
231
+ /glla autoresume=on # auto-resume goals/loops on ANY session start (default: load HELD, never auto-start — explicit /goal resume, /list resume, or /loop; off: never) — GLOBAL-only (v0.29.5): project-level keys are inert
210
232
  /glla auditcap=5 # pause the goal after N consecutive auditor disapprovals (default 5, 0 = unlimited)
211
233
  /glla aggressivemode=on # keep-going defaults: autoResume, cap 10, stuck 10, wedge off, quota auto-retry, cap→TODOs
212
234
  /glla quotaretryminutes=60 # minutes before auto-retrying a quota-exhausted auditor
213
235
  /glla stuckmax=10 # consecutive stuck interventions before a loop stops (default 5)
214
236
  /glla auditfeedbackchars=800 # cap the executor-visible auditor report (default 0 = full report)
215
- /glla autoaccept=on # drafts ACTIVATE without the Confirm dialo... (every draft dialog also offers "always auto-accept" inline)unattended rigs)
237
+ /glla autoaccept=on # drafts ACTIVATE without the Confirm dialog (v0.29.4: they start immediately — autoResume no longer gates drafts; every draft dialog also offers "always auto-accept" inline)
216
238
  /glla project tokenlimit=500 # rare per-project override
217
239
  ```
218
240
 
219
- Resolution per key: **project > global > defaults**. The auditor defaults to
241
+ Resolution per key: **project > global > defaults** — EXCEPT `autoResume`,
242
+ which is **global-only** (v0.29.5): per-project opt-ins from old versions
243
+ silently overrode the global hold at launch (the junk-runner incident), so
244
+ the launch-restore gate and the reviewer-enqueue gate read only the global
245
+ file now. The auditor defaults to
220
246
  your pi session model. When the session provider is extension-registered the
221
247
  auditor can't auth it — you're told once (info level) with the fix:
222
248
  `/glla model=provider/id`, set once, rarely touched again. The plugin never
@@ -232,7 +258,12 @@ history and is available through `/goal status` regardless.
232
258
  floor — every `propose_*` draft (goal, list batch, loop, task list)
233
259
  activates the moment the agent proposes it, with a notification and a
234
260
  `draft_autoaccepted` ledger entry (auto-accept is never silent). The seed
235
- carries the intent. Pair with `autoresume=on` for fully unattended rigs.
261
+ carries the intent. Since v0.29.4 auto-accepted drafts **start immediately**
262
+ — the draft path is decoupled from `autoResume`, which gates ONLY
263
+ launch-time restore of persisted state ("load it but don't auto-start it").
264
+ For fully unattended rigs you typically want both on; for attended rigs,
265
+ `autoaccept=on` + `autoresume=off` is the sweet spot (new drafts go, old
266
+ state holds).
236
267
 
237
268
  ## Subagents
238
269
 
@@ -330,7 +361,7 @@ prompts/
330
361
  goal-loop-forever-draft.md # /loop drafting prompt
331
362
  scripts/
332
363
  smoke.sh # live integration harness (tmux + real models)
333
- tests/ # 545 tests across 58 files, no live pi required (mock-ctx harness drives the orchestrator)
364
+ tests/ # 613 tests across 58 files, no live pi required (mock-ctx harness drives the orchestrator)
334
365
  docs/DESIGN.md # architectural decisions
335
366
  PLAN.md # milestones, decisions, gates
336
367
  ```
package/docs/DESIGN.md CHANGED
@@ -44,6 +44,52 @@ below; later releases append addenda rather than rewrite history.
44
44
  - **`notify=<cmd>`**: fire-and-forget shell-out on goal complete / goal pause /
45
45
  loop stop, message as `$1`. Settings parser is quote-aware.
46
46
 
47
+ ## Addendum v0.5.0–v0.29.6 (current state — the long-session era)
48
+
49
+ The v0.4.0 addendum closed the scaffold era. v0.5.0 through v0.29.6 are 25+
50
+ releases of unattended-rig hardening; the full record is CHANGELOG.md. The
51
+ architectural decisions that changed the SHAPE of the system:
52
+
53
+ - **Self-watchdog is baked in** (v0.5.0): a 15s heartbeat owns liveness —
54
+ supervising + idle + nothing scheduled + 60s quiet → re-fire. External
55
+ liveness plugins are retired.
56
+ - **The restore gate** (v0.26.9 tri-state; hardened v0.28.30, v0.29.4): a
57
+ bare `pi` start HOLDS everything and paints it — nothing auto-starts from
58
+ persisted state. In-session reload/fork/compaction continues automatically.
59
+ `autoResume` (on → any session start resumes; off → never) is **global-only**
60
+ (v0.29.5) after a stale per-project opt-in silently overrode the global hold.
61
+ - **Drafts and restores are decoupled** (v0.29.4): `autoAcceptDrafts` is the
62
+ pre-consent for in-session drafts — they START immediately. `autoResume`
63
+ gates only launch-time restore. "The session auto-starts in some cases ok;
64
+ launching pi must not."
65
+ - **User aborts mean STOP** (v0.29.4/0.29.5): an aborted turn is exempt from
66
+ stall accounting, stands the chain down with no auto re-fire, and the
67
+ stand-down gates the heartbeat + post-compaction refires. The 5-abort loud
68
+ pause is the backstop.
69
+ - **One active thing, auto-arbitrated** (v0.28.21 guards; v0.29.6 load-time
70
+ arbitration): at most one live artifact. Dirty stacked states at load are
71
+ resolved deterministically — most recent activity keeps the slot, the loser
72
+ is archived (recoverable), no picker. `/glla wipe` (v0.28.31) is the manual,
73
+ Confirm-gated clean slate.
74
+ - **The completion lifecycle owns its pauses** (v0.29.1): storm/stall
75
+ escalation never pauses `auditing` goals; a stranded `auditing` state (no
76
+ live auditor, 90s stale) re-runs the stored claim; send/pause/notify storms
77
+ rearm once per cycle; the provider-error brake (v0.28.13) keeps cross-cycle
78
+ memory and parks after 6 consecutive errors.
79
+ - **The audit loop is the project reviewer** (v0.29.0): `/loop audit` runs
80
+ fresh audit passes every iteration, appends findings to
81
+ `.pi-glla/audit-loop/findings.md`, fixes the top open ones; the orchestrator
82
+ counts open boxes as the measure and the plateau stop ends it. The
83
+ reviewer's reflexive fire-audit-on-clean cascade is opt-in (it paid for
84
+ verification twice and was hydra fuel).
85
+ - **Git discipline is prompt-law** (v0.29.2): continuation/draft prompts
86
+ forbid inventing git identities or branches (field incidents: agents
87
+ committing as `phase-e-agent <phase-e@local>`).
88
+ - **Reviewer = strategist, not verifier** (v0.24.6–v0.29.0): the reviewer
89
+ scans sources and proposes list items through the standard drafting +
90
+ Confirm path; it never audits work — the isolated auditor is the only
91
+ verifier.
92
+
47
93
  ## Addendum v0.4.0 (completion)
48
94
 
49
95
  - **Auditor compaction enabled** (flaw #3 — the last open one). Safety:
@@ -1003,6 +1003,43 @@ function updateGoal(patch: Partial<Goal>, ctx: ExtensionContext): void {
1003
1003
  persistState(ctx);
1004
1004
  }
1005
1005
 
1006
+ // v0.29.6: stacked-state auto-arbitration (user directive: "auto archive /
1007
+ // wipe extra goals/loops/lists … make sure that we only have one"). Dirty
1008
+ // pre-guard states can persist a live loop AND a live goal; the 0.28.21
1009
+ // decision picker asked the user to arbitrate artifacts they didn't
1010
+ // remember at every pi start. Now deterministic: MOST RECENT ACTIVITY
1011
+ // keeps the slot; the loser is ARCHIVED (recoverable), never wiped. The
1012
+ // queued list is a backlog, not a live artifact — untouched.
1013
+ function autoArbitrateStackedState(ctx: ExtensionContext): void {
1014
+ const loop = state.loop?.active ? state.loop : undefined;
1015
+ const goal = state.goal && state.goal.status !== "complete" && state.goal.status !== "aborted" ? state.goal : undefined;
1016
+ if (!loop || !goal) return; // at most one live artifact — the invariant holds
1017
+ const lastMeasure = loop.history.length > 0 ? loop.history[loop.history.length - 1] : undefined;
1018
+ const loopMs = Date.parse(lastMeasure?.at ?? loop.startedAt ?? "") || 0;
1019
+ const goalMs = Date.parse(goal.updatedAt ?? goal.createdAt ?? "") || 0;
1020
+ const keepGoal = goalMs > loopMs; // tie → the loop keeps the slot (0.28.21 default)
1021
+ appendLedger(ctx.cwd, "stacked_state_auto_arbitrated", {
1022
+ kept: keepGoal ? "goal" : "loop",
1023
+ goalId: goal.id,
1024
+ goalMs,
1025
+ loopMs,
1026
+ loopIteration: loop.iteration,
1027
+ loopTarget: loop.target.slice(0, 120),
1028
+ });
1029
+ if (keepGoal) {
1030
+ // Same shape as /loop stop: the loop record stays in state (inactive)
1031
+ // with an honest reason — /loop status still shows it.
1032
+ state = { ...state, loop: { ...loop, active: false, stopReason: "auto-arbitrated on session load: the goal was more recent (one active thing)" } };
1033
+ persistState(ctx);
1034
+ } else {
1035
+ archiveCurrentGoal(ctx, "aborted", "auto-arbitrated on session load: the loop was more recent (one active thing)");
1036
+ }
1037
+ ctx.ui.notify(
1038
+ `Stacked state auto-arbitrated (one active thing): kept the ${keepGoal ? "goal" : "loop"} — more recent activity — and archived the ${keepGoal ? `loop (iter ${loop.iteration}, best ${loop.bestValue ?? "n/a"})` : `goal (${goal.id})`}. Recoverable: /loop status · .pi-glla/archive/ · /glla wipe for a clean slate.`,
1039
+ "info",
1040
+ );
1041
+ }
1042
+
1006
1043
  function archiveCurrentGoal(ctx: ExtensionContext, status: Status, stopReason?: string): void {
1007
1044
  if (!state.goal) return;
1008
1045
  const goal = state.goal;
@@ -5192,6 +5229,9 @@ export default function (pi: ExtensionAPI): void {
5192
5229
  } catch (err) {
5193
5230
  ctx.ui.notify(`glla subagent override sync failed: ${err instanceof Error ? err.message : String(err)}`, "warning");
5194
5231
  }
5232
+ // v0.29.6: stacked-state auto-arbitration FIRST — one live artifact
5233
+ // survives before the restore gate decides hold-vs-resume for it.
5234
+ autoArbitrateStackedState(ctx);
5195
5235
  // Restore gate (v0.26.9 tri-state): a human LOADING a session
5196
5236
  // ("startup"/"new"/"resume", or no reason) HOLDS — the popup shows what
5197
5237
  // is waiting and nothing starts until they resume explicitly. In-session
@@ -5276,31 +5316,10 @@ export default function (pi: ExtensionAPI): void {
5276
5316
  ctx.ui.notify(`List has ${listQueue().length} item(s) waiting — /list next to activate the head.`, "info");
5277
5317
  }
5278
5318
  }
5279
- // v0.28.21: enforce one-active-thing at the restore boundary for DIRTY
5280
- // legacy states — pre-guard versions could persist an active goal AND
5281
- // an active/held loop; the chain above handles the loop first, and the
5282
- // goal would otherwise stay active and fire on agent_end. Pause it:
5283
- // at most one thing owns the active slot, and nothing auto-starts.
5284
- if (state.loop && state.goal && state.goal.status === "active") {
5285
- updateGoal({
5286
- status: "paused",
5287
- pauseKind: "decision",
5288
- // v0.29.3: third option — the wipe escape. Old projects stack a
5289
- // goal AND a loop AND a list from pre-guard versions; arbitrating
5290
- // between two artifacts the user doesn't even remember is the odd
5291
- // part — "i feel like wipe does [make sense]". Wipe keeps its own
5292
- // Confirm (destructive), so picking it is safe to offer.
5293
- pauseOptions: ["Stop the loop, then resume the goal (/loop stop)", "Cancel the goal (/goal cancel) — the loop keeps running", "Wipe everything — clean slate for stale leftovers (/glla wipe)"],
5294
- pauseRecommended: 1,
5295
- pauseReason: "held on session load — the loop owns the active slot (one active thing at a time)",
5296
- pauseSuggestedAction: "/loop to work the loop, or /loop stop then /goal resume to work the goal",
5297
- }, ctx);
5298
- ctx.ui.notify(
5299
- `Goal held — a loop also exists; one active thing at a time. /loop to resume the loop, or /loop stop then /goal resume.`,
5300
- "info",
5301
- );
5302
- maybeDecisionPopup(ctx);
5303
- }
5319
+ // v0.29.6: the 0.28.21 loop-vs-goal decision picker is SUPERSEDED by
5320
+ // auto-arbitration above — stacked states resolve deterministically
5321
+ // (most recent activity keeps the slot; the loser is archived) before
5322
+ // the restore gate, so a live loop and a live goal cannot coexist here.
5304
5323
  // Always paint on session load (v0.22.1): the branches above only reach
5305
5324
  // refreshUI via persistState, so a goal that was ALREADY paused (or any
5306
5325
  // state that doesn't mutate on load) rendered nothing — "can't tell if
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-goal-list-loop-audit",
3
- "version": "0.29.5",
3
+ "version": "0.29.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",