pi-goal-list-loop-audit 0.21.1 → 0.22.0

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
@@ -88,7 +88,7 @@ grilling turn that ends in a Confirm dialog. For `/loop` specifically, the orche
88
88
  measure command once** and shows the real number in the dialog — you validate
89
89
  the metric before a single iteration burns tokens.
90
90
 
91
- With `branch=1`, all work lands on a scratch branch (`pi-gla-loop/<ts>-<slug>`):
91
+ With `branch=1`, all work lands on a scratch branch (`pi-glla-loop/<ts>-<slug>`):
92
92
  improvements are committed, regressions are hard-reset (scratch branch only),
93
93
  and on stop you return to your original branch with merge instructions.
94
94
  Requires a clean working tree.
@@ -185,9 +185,10 @@ picks a model itself. Thinking follows the session too (floor `high`).
185
185
  ## Token guard
186
186
 
187
187
  Every goal tracks real token usage; crossing the budget pauses the goal.
188
- Default 10,000,000 per goal — a runaway threshold, not a big-goal threshold
189
- (real research/feature goals legitimately burn 2-4M). Tune with
190
- `/glla tokenlimit=<n>`. Loop 3 doesn't need this cap — it has its own brakes
188
+ Off by default (opt-in)set a budget with `/glla tokenlimit=<n>`. A high
189
+ value like 10000000 is a runaway threshold, not a big-goal threshold
190
+ (real research/feature goals legitimately burn 2-4M). Loop 3 doesn't need
191
+ this cap — it has its own brakes
191
192
  (max iterations + plateau).
192
193
 
193
194
  ## Compatibility (what goes well, what conflicts)
@@ -234,7 +235,7 @@ prompts/
234
235
  goal-loop-forever-draft.md # /loop drafting prompt
235
236
  scripts/
236
237
  smoke.sh # live integration harness (tmux + real models)
237
- tests/ # 150 unit tests, no live pi required
238
+ tests/ # 168 unit tests, no live pi required
238
239
  docs/DESIGN.md # architectural decisions
239
240
  PLAN.md # milestones, decisions, gates
240
241
  ```
package/docs/DESIGN.md CHANGED
@@ -51,8 +51,8 @@ below; later releases append addenda rather than rewrite history.
51
51
  auditor's evidence → disapproval, never a false approval.
52
52
  - **Token guard**: real accumulation from assistant-message `usage.totalTokens`
53
53
  (deduped across replayed `agent_end` history). Crossing `tokenlimit`
54
- (default 1M) pauses the goal with a clear reason.
55
- - **Loop 3 `branch=1`**: scratch branch `pi-gla-loop/<ts>-<slug>`; commit per
54
+ (opt-in; off by default since v0.12.0) pauses the goal with a clear reason.
55
+ - **Loop 3 `branch=1`**: scratch branch `pi-glla-loop/<ts>-<slug>`; commit per
56
56
  improvement, `git reset --hard` per regression — scratch branch only.
57
57
  Refuses non-git dirs and dirty trees; returns to the original branch on
58
58
  stop with merge instructions.
@@ -107,7 +107,7 @@ This is a deliberate trade-off. If the user wants drafting in v0.1.0, say so and
107
107
 
108
108
  Some alternatives considered:
109
109
  - Three packages: `pi-goal-list-loop-audit`, `pi-goal-list-loop-audit-list`, `pi-goal-list-loop-audit-loop`.
110
- - One package with three subcommands: `/goal`, `/pi-gla-list`, `/pi-gla-loop`.
110
+ - One package with three subcommands: `/goal`, `/list`, `/loop`.
111
111
 
112
112
  We choose **one package with subcommands**. Reasoning:
113
113
  - Single install (`pi install npm:pi-goal-list-loop-audit`).
@@ -173,8 +173,8 @@ active → auditing (complete_goal called)
173
173
  auditing → complete (auditor <approved/>)
174
174
  auditing → active (auditor <disapproved/>; reset iteration counter)
175
175
  active → paused (pause_goal called, or stuck > 5 min, or empty turn)
176
- paused → active (user /pi-gla-resume)
177
- active → aborted (user /pi-gla-cancel)
176
+ paused → active (user /goal resume)
177
+ active → aborted (user /goal cancel)
178
178
  ```
179
179
 
180
180
  ### Decision 9: JSONL state (deterministic compaction)
@@ -189,9 +189,9 @@ This protects against model-generated summaries losing fidelity.
189
189
  |---|---|
190
190
  | `Esc` during auditor | Pause; user picks "complete without audit" or "continue" |
191
191
  | `Esc` during agent turn | Pause |
192
- | User `/pi-gla-pause` | Pause |
193
- | User `/pi-gla-cancel` | Abort (wipes active goal) |
194
- | Stuck > 5 min | Pause + notify |
192
+ | User `/goal pause` | Pause |
193
+ | User `/goal cancel` | Abort (wipes active goal) |
194
+ | Stall watchdog (3 consecutive no-tool turns) | Pause + notify |
195
195
  | Empty turn (no tool calls) | Pause (no momentum) |
196
196
 
197
197
  ## Open follow-ups (post-v0.1.0)
@@ -5,13 +5,11 @@
5
5
  * Isolated completion auditor. Runs in a fresh pi agent session with no
6
6
  * extensions, no skills, no prompts, no themes, no editor. Only read tools.
7
7
  *
8
- * In v0.1.0 the auditor behaves the same as pi-goal-x's auditor: it must
9
- * call at least one read tool before <approved/>.
10
- *
11
- * In v0.2.0 (NOT YET IMPLEMENTED) we will add regression_shield: the
12
- * auditor's report must include raw output (cat / grep / bash) for every
13
- * must-verify item in the verification contract. The orchestrator will
14
- * reject <approved/> without that evidence.
8
+ * Two enforced floors: the auditor must call at least one read tool before
9
+ * <approved/>, and regression_shield (goal-loop-shield.ts) requires the
10
+ * report to include raw output (cat / grep / bash) for every must-verify
11
+ * item in the verification contract the orchestrator rejects
12
+ * evidence-free approvals.
15
13
  */
16
14
 
17
15
  import type { Model } from "@earendil-works/pi-ai";
@@ -181,7 +181,7 @@ export function routeGoalArgs(raw: string): GoalRoute {
181
181
  /**
182
182
  * Parse a bulk list-import file (v0.8.1): markdown checklists (`- [ ]`,
183
183
  * `- [x]`), bullets (`-`, `*`, `•`), numbered items (`1.`, `2)`), and plain
184
- * lines all become queue items. Headings (`# …`), blank lines, and HTML
184
+ * lines all become list items. Headings (`# …`), blank lines, and HTML
185
185
  * comments are skipped. A sisyphus-style plan file should import clean.
186
186
  */
187
187
  export function parseListImport(content: string): string[] {
@@ -357,7 +357,7 @@ export function takeAt<T>(items: T[], n: number): [T, T[]] | null {
357
357
 
358
358
  export interface State {
359
359
  goal: Goal | null;
360
- /** Loop 2: queue of pending goal items. Activated one at a time. */
360
+ /** Loop 2: list of pending goal items. Activated one at a time. */
361
361
  list?: ListItem[];
362
362
  /** Loop 3: metric-driven forever loop. */
363
363
  loop?: import("./goal-loop-forever.js").LoopState;
@@ -4,6 +4,9 @@
4
4
  *
5
5
  * Pure display builders for the live TUI (status line + above-editor widget).
6
6
  * No pi imports — unit tests exercise these directly. The orchestrator calls
7
+ * No RUNTIME imports at all: tests run under `node --experimental-strip-types`,
8
+ * which does not rewrite `.js` → `.ts` specifiers — a value import from
9
+ * ./goal-loop-core.js breaks the suite (type-only imports are erased, safe).
7
10
  * ctx.ui.setStatus/setWidget with whatever these return.
8
11
  */
9
12
 
@@ -140,8 +143,11 @@ function goalLines(g: Goal, state: State, audit: AuditDisplayProgress | null | u
140
143
  : paint(theme, "success", "●");
141
144
  const head = `${icon} ${truncate(g.objective.replace(/\s+/g, " "), 64)}`;
142
145
  const statusWord = g.status === "active" ? paint(theme, "success", "active") : g.status;
143
- const tokens = paint(theme, "dim", `${fmtTokens(g.usage?.tokensUsed ?? 0)}/${fmtTokens(g.usage?.tokensLimit ?? 10_000_000)} tok`);
144
- const lines = [head, `├─ ${statusWord} · ${fmtElapsed(now - Date.parse(g.createdAt))} · ${tokens}`];
146
+ // Token segment only when a budget is set (v0.22.0): the guard is opt-in,
147
+ // and "0/0 tok" carried no information when off.
148
+ const tokenLimit = g.usage?.tokensLimit ?? 0;
149
+ const tokens = tokenLimit > 0 ? ` · ${paint(theme, "dim", `${fmtTokens(g.usage?.tokensUsed ?? 0)}/${fmtTokens(tokenLimit)} tok`)}` : "";
150
+ const lines = [head, `├─ ${statusWord} · ${fmtElapsed(now - Date.parse(g.createdAt))}${tokens}`];
145
151
  if (g.status === "auditing") {
146
152
  lines.push(`├─ auditor: ${audit?.label ?? "running"}${audit?.currentTool ? ` · ${truncate(audit.currentTool, 30)}` : ""}`);
147
153
  if (audit?.elapsedMs) lines.push(`└─ ${paint(theme, "dim", `${fmtElapsed(audit.elapsedMs)} in isolated session`)}`);
@@ -71,15 +71,11 @@ import {
71
71
  } from "../goal-loop-forever.js";
72
72
  import {
73
73
  accountTurnForNudges,
74
- BACKOFF_HARD_CAP_MS,
75
74
  BACKOFF_IDLE_RETRY_MS,
76
- backoffMs,
77
75
  HEARTBEAT_INTERVAL_MS,
78
76
  HEARTBEAT_MAX_NUDGES,
79
77
  HEARTBEAT_STALL_MS,
80
- humanMs,
81
78
  shouldHeartbeatRefire,
82
- shouldPauseAfterBackoff,
83
79
  } from "../goal-loop-backoff.js";
84
80
 
85
81
  // =================================================================
@@ -87,7 +83,6 @@ import {
87
83
  // =================================================================
88
84
 
89
85
  const GOAL_EVENT_ENTRY = "goal-event";
90
- const STATE_ENTRY = "goal-state";
91
86
  /** stopReason marker for a loop held (not stopped) by the fresh-session restore gate. */
92
87
  const HELD_ON_RESTORE = "held: restored in a fresh session";
93
88
 
@@ -201,7 +196,6 @@ let continuationTimer: NodeJS.Timeout | null = null;
201
196
  let continuationScheduledFor: string | null = null;
202
197
  let iterationCounter = 0;
203
198
  let toolCallsThisTurn = 0;
204
- let consecutiveStuckIterations = 0;
205
199
  let consecutiveErrorIterations = 0;
206
200
  let consecutiveNoToolIterations = 0;
207
201
 
@@ -420,7 +414,6 @@ function activateNextListItem(ctx: ExtensionContext, n = 1): boolean {
420
414
  if (next.verificationContract) goal.verificationContract = next.verificationContract;
421
415
  setGoal(goal, ctx);
422
416
  iterationCounter = 0;
423
- consecutiveStuckIterations = 0;
424
417
  consecutiveErrorIterations = 0;
425
418
  ctx.ui.notify(`List item #${n} activated (${rest.length} remaining): ${goal.objective.slice(0, 80)}`, "info");
426
419
  scheduleContinuation(ctx, true);
@@ -534,7 +527,6 @@ async function cmdSet(args: string, ctx: ExtensionContext, skipDraft = false): P
534
527
  setGoal(goal, ctx);
535
528
  // Reset counters
536
529
  iterationCounter = 0;
537
- consecutiveStuckIterations = 0;
538
530
  consecutiveErrorIterations = 0;
539
531
  consecutiveNoToolIterations = 0;
540
532
  ctx.ui.notify(`Goal ${goal.id} created — starting now. Auditor will verify on completion.`, "info");
@@ -1233,7 +1225,7 @@ async function cmdLoop(args: string, ctx: ExtensionContext): Promise<void> {
1233
1225
  return;
1234
1226
  }
1235
1227
 
1236
- ctx.ui.notify("Usage: /loop [status] | /loop start \"<target>\" measure=\"<cmd>\" direction=min|max [done=<value>] [window=5] [max=50] | /loop stop", "info");
1228
+ ctx.ui.notify("Usage: /loop [status] | /loop start \"<target>\" measure=\"<cmd>\" direction=min|max [window=5] [max=50] | /loop stop", "info");
1237
1229
  }
1238
1230
 
1239
1231
  // =================================================================
@@ -1545,7 +1537,6 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
1545
1537
  const goal = createGoal(full, liveCtx);
1546
1538
  setGoal(goal, liveCtx);
1547
1539
  iterationCounter = 0;
1548
- consecutiveStuckIterations = 0;
1549
1540
  consecutiveErrorIterations = 0;
1550
1541
  scheduleContinuation(liveCtx, true);
1551
1542
  return {
@@ -1774,7 +1765,7 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
1774
1765
 
1775
1766
  pi.registerTool(defineTool({
1776
1767
  name: "list_status",
1777
- label: "Queue status",
1768
+ label: "List status",
1778
1769
  description: "Show the active goal and the /list list (loop 2) as text: what's running, what's waiting.",
1779
1770
  parameters: Type.Object({}),
1780
1771
  async execute() {
@@ -1856,7 +1847,8 @@ interface Settings {
1856
1847
  auditorThinkingLevel?: "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
1857
1848
  /** Shell command run on goal complete / goal pause / loop stop; message passed as $1. */
1858
1849
  notifyCmd?: string;
1859
- /** Per-goal token budget; crossing it pauses the goal. Default 1,000,000. */
1850
+ /** Per-goal token budget; crossing it pauses the goal. Off by default
1851
+ * (opt-in guard, v0.12.0): unset/0 = no budget. */
1860
1852
  tokenLimit?: number;
1861
1853
  /** on → restored goals/loops/lists auto-resume even in fresh sessions
1862
1854
  * (unattended rigs). Default off: restore holds until /goal resume. */
@@ -2147,10 +2139,13 @@ async function cmdSettings(args: string, ctx: ExtensionContext): Promise<void> {
2147
2139
  const OUR_COMMANDS = ["goal", "glla", "list", "loop"];
2148
2140
  let collisionWarned = false;
2149
2141
 
2150
- // Providers verified to exist in a bare (extension-less) session. The auditor
2151
- // spawns exactly such a session, so extension-registered providers (kilocode,
2152
- // zenmux on this rig) fail inside it. Unknown providers get a soft one-time
2153
- // notice not an error, since the built-in set grows over time.
2142
+ // Providers known to pi core. The auditor inherits the already-resolved
2143
+ // Model object from this session (in-process createAgentSession), so a
2144
+ // provider defined in ~/.pi/agent/models.json with auth.json credentials
2145
+ // works even though it is not "built-in". Unknown providers get a soft
2146
+ // one-time conditional notice: if audits error with auth failures, an
2147
+ // explicit /glla model= override is the fix. (v0.22.0: reworded from the
2148
+ // stale "extension-registered → auditor fails auth" premise.)
2154
2149
  const KNOWN_BUILTIN_PROVIDERS = new Set([
2155
2150
  "anthropic", "google", "google-vertex", "google-gemini-cli", "openai", "openai-codex",
2156
2151
  "openrouter", "opencode", "azure-openai-responses", "groq", "cerebras", "xai", "zai",
@@ -2167,7 +2162,7 @@ function warnIfAuditorProviderRisky(ctx: ExtensionContext): void {
2167
2162
  const provider = (ctx.model as any)?.provider as string | undefined;
2168
2163
  if (!provider || KNOWN_BUILTIN_PROVIDERS.has(provider)) return;
2169
2164
  ctx.ui.notify(
2170
- `pi-goal-list-loop-audit: session provider "${provider}" is extension-registered the auditor (extension-less session) will fail auth with it. If audits error, set a built-in override once: /glla model=provider/id`,
2165
+ `pi-goal-list-loop-audit: session provider "${provider}" is not a known built-in. The auditor inherits the resolved model in-process, so this usually works but if audits error with auth/provider failures, set an explicit override once: /glla model=provider/id`,
2171
2166
  "info",
2172
2167
  );
2173
2168
  } catch {
@@ -2410,10 +2405,9 @@ export default function (pi: ExtensionAPI): void {
2410
2405
  consecutiveErrorIterations = 0;
2411
2406
  }
2412
2407
 
2413
- // Hard 5-min cap: if no successful continue for >5 min, pause.
2414
- // For v0.1.0 we only detect this on agent_end; a tighter watchdog lands in v0.2.0.
2415
- // We schedule the next continuation; if it produces nothing useful in 5 min,
2416
- // the next agent_end iteration of THIS branch will trigger the cap.
2408
+ // No wall-clock cap by design: a goal ends via completion, explicit
2409
+ // pause/cancel, the stall watchdog, the 5-consecutive-errors pause, or
2410
+ // the token guard never via an elapsed-time cutoff.
2417
2411
 
2418
2412
  scheduleContinuation(ctx, false);
2419
2413
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-goal-list-loop-audit",
3
- "version": "0.21.1",
3
+ "version": "0.22.0",
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",
@@ -73,11 +73,11 @@ pause_goal({reason: "...", suggestedAction: "..."})
73
73
 
74
74
  ## HARD RULES
75
75
 
76
- - **Do not modify the objective autonomously.** The objective is the user's; if it has drifted from what makes sense, call `pause_goal` and propose a `/pi-gla-tweak` instead.
76
+ - **Do not modify the objective autonomously.** The objective is the user's; if it has drifted from what makes sense, call `pause_goal` and propose a `/goal tweak` instead.
77
77
  - **Do not pretend completion.** If verification evidence is missing, call `pause_goal` instead of `complete_goal`.
78
78
  - **Do not polish doorknobs.** If you are out of work and the goal is satisfied, call `complete_goal` instead of inventing a side-improvement.
79
79
  - **Do not give up early.** If a task is hard, run it down properly. The auditor will catch doorknobs; the agent's job is to do the real work.
80
80
 
81
- ## BACKOFF
81
+ ## STALLS
82
82
 
83
- If the orchestrator schedules a 5-minute pause between iterations, that's the safety net: it means the agent has not made meaningful progress in a while. Use the pause to surface what is blocking, not to keep iterating in a way that wastes effort.
83
+ The orchestrator's backstop is the stall watchdog: three consecutive turns with no tool calls pause the goal. If you feel yourself spinning repeating the same approach, no new evidence stop early instead: call `pause_goal` with what is blocking and a concrete suggested action, rather than burning the remaining watchdog turns.
@@ -2,7 +2,7 @@
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
3
  "$id": "https://raw.githubusercontent.com/DraconDev/pi-goal-list-loop-audit/main/schemas/goal.schema.json",
4
4
  "title": "pi-goal-list-loop-audit goal state",
5
- "description": "Active goal state for pi-goal-list-loop-audit v0.4.0 (loops 1-3). Persisted as markdown at .pi-gla/goals/<id>.md and mirrored into .pi-gla/active.jsonl state events.",
5
+ "description": "Active goal state for pi-goal-list-loop-audit (loops 1-3). Persisted as markdown at .pi-glla/goals/<id>.md and mirrored into .pi-glla/active.jsonl state events.",
6
6
  "type": "object",
7
7
  "required": ["id", "objective", "status", "policy", "autoContinue", "usage", "createdAt", "updatedAt"],
8
8
  "properties": {