pi-goal-list-loop-audit 0.16.0 → 0.17.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
@@ -40,7 +40,7 @@ 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
- /gla # open the settings UI (or /gla key=value)
43
+ /glla # open the settings UI (or /glla key=value)
44
44
  /list add # draft a contract (or a whole batch via items[])
45
45
  /list add "<objective>" # queue one directly
46
46
  /list add plan.md # file detected → bulk import, one Confirm
@@ -151,7 +151,7 @@ Each loop is a different policy class on the same status machine.
151
151
 
152
152
  ## Live TUI (always know it's on)
153
153
 
154
- A persistent `gla:` status segment + an above-editor widget show the current
154
+ A persistent `glla:` status segment + an above-editor widget show the current
155
155
  goal/loop at all times: objective, status, elapsed, tokens, next task or loop
156
156
  metric, pause reason, and live auditor progress during audits. If something is
157
157
  running, you can see it — no command needed.
@@ -166,19 +166,19 @@ No external watchdog plugin needed.
166
166
  ## Config (one global place, rarely opened)
167
167
 
168
168
  ```
169
- /gla # open the settings UI
170
- /gla model=provider/id # auditor model override → GLOBAL
171
- /gla thinking=high # auditor thinking → GLOBAL
172
- /gla notify='cmd "$1"' # push on complete/pause/stop → GLOBAL
173
- /gla tokenlimit=10000000 # per-goal token budget (default: off) → GLOBAL
174
- /gla tokenlimit=0 # explicitly no cap (the default)
175
- /gla project tokenlimit=500 # rare per-project override
169
+ /glla # open the settings UI
170
+ /glla model=provider/id # auditor model override → GLOBAL
171
+ /glla thinking=high # auditor thinking → GLOBAL
172
+ /glla notify='cmd "$1"' # push on complete/pause/stop → GLOBAL
173
+ /glla tokenlimit=10000000 # per-goal token budget (default: off) → GLOBAL
174
+ /glla tokenlimit=0 # explicitly no cap (the default)
175
+ /glla project tokenlimit=500 # rare per-project override
176
176
  ```
177
177
 
178
178
  Resolution per key: **project > global > defaults**. The auditor defaults to
179
179
  your pi session model. When the session provider is extension-registered the
180
180
  auditor can't auth it — you're told once (info level) with the fix:
181
- `/gla model=provider/id`, set once, rarely touched again. The plugin never
181
+ `/glla model=provider/id`, set once, rarely touched again. The plugin never
182
182
  picks a model itself. Thinking follows the session too (floor `high`).
183
183
 
184
184
  ## Token guard
@@ -186,7 +186,7 @@ picks a model itself. Thinking follows the session too (floor `high`).
186
186
  Every goal tracks real token usage; crossing the budget pauses the goal.
187
187
  Default 10,000,000 per goal — a runaway threshold, not a big-goal threshold
188
188
  (real research/feature goals legitimately burn 2-4M). Tune with
189
- `/gla tokenlimit=<n>`. Loop 3 doesn't need this cap — it has its own brakes
189
+ `/glla tokenlimit=<n>`. Loop 3 doesn't need this cap — it has its own brakes
190
190
  (max iterations + plateau).
191
191
 
192
192
  ## Compatibility (what goes well, what conflicts)
@@ -212,8 +212,8 @@ not requirements).
212
212
 
213
213
  **Two footnotes**: (1) extension-registered providers work in the main session
214
214
  but not the auditor's extension-less session — if audits fail auth, set the
215
- override once with `/gla model=`. (2) `pi-notify-agent` notifies on every turn;
216
- `/gla notify=` fires only on goal complete/pause/loop stop.
215
+ override once with `/glla model=`. (2) `pi-notify-agent` notifies on every turn;
216
+ `/glla notify=` fires only on goal complete/pause/loop stop.
217
217
 
218
218
  ## Files
219
219
 
package/docs/DESIGN.md CHANGED
@@ -179,7 +179,7 @@ active → aborted (user /pi-gla-cancel)
179
179
 
180
180
  ### Decision 9: JSONL state (deterministic compaction)
181
181
 
182
- Goal state lives in `.pi-gla/active.jsonl`. Each line is a state transition. On compaction, the summary is rebuilt deterministically from the JSONL (autoresearch pattern).
182
+ Goal state lives in `.pi-glla/active.jsonl`. Each line is a state transition. On compaction, the summary is rebuilt deterministically from the JSONL (autoresearch pattern).
183
183
 
184
184
  This protects against model-generated summaries losing fidelity.
185
185
 
@@ -297,7 +297,7 @@ export async function runGoalCompletionAuditor(args: {
297
297
  output,
298
298
  model: modelLabel(model),
299
299
  thinkingLevel,
300
- error: `Auditor produced no output${streamError ? `: ${streamError}` : " — the auditor session likely failed (check the model's auth/quota, or set a working one with /gla model=provider/id)"}`,
300
+ error: `Auditor produced no output${streamError ? `: ${streamError}` : " — the auditor session likely failed (check the model's auth/quota, or set a working one with /glla model=provider/id)"}`,
301
301
  };
302
302
  }
303
303
 
@@ -334,7 +334,17 @@ export const DEFAULT_STATE: State = {
334
334
  // =================================================================
335
335
 
336
336
  export function piGlaDir(cwd: string): string {
337
- return path.join(cwd, ".pi-gla");
337
+ const dir = path.join(cwd, ".pi-glla");
338
+ // v0.17.0: one-time migration of the pre-rename state dir (.pi-gla →
339
+ // .pi-glla). Active goals, ledgers, and project settings move with the
340
+ // name — no relics, no lost state.
341
+ const legacy = path.join(cwd, ".pi-gla");
342
+ try {
343
+ if (!fs.existsSync(dir) && fs.existsSync(legacy)) fs.renameSync(legacy, dir);
344
+ } catch {
345
+ // read-only fs or partial state — fall through and use the new dir
346
+ }
347
+ return dir;
338
348
  }
339
349
 
340
350
  export function goalMdPath(cwd: string, id: string): string {
@@ -46,28 +46,28 @@ export interface AuditDisplayProgress {
46
46
  }
47
47
 
48
48
  /**
49
- * One-line status for ctx.ui.setStatus("pi-gla", …).
49
+ * One-line status for ctx.ui.setStatus("pi-glla", …).
50
50
  * Returns undefined when nothing is being supervised (clears the segment).
51
51
  */
52
52
  export function buildStatusText(state: State, audit?: AuditDisplayProgress | null, now = Date.now()): string | undefined {
53
53
  if (state.loop?.active) {
54
54
  const l = state.loop;
55
55
  const arrow = l.direction === "min" ? "↓" : "↑";
56
- return `gla: loop ${arrow} iter ${l.iteration}/${l.maxIterations} · best ${l.bestValue ?? "n/a"} · stall ${l.stallCount}/${l.plateauWindow}`;
56
+ return `glla: loop ${arrow} iter ${l.iteration}/${l.maxIterations} · best ${l.bestValue ?? "n/a"} · stall ${l.stallCount}/${l.plateauWindow}`;
57
57
  }
58
58
  const g = state.goal;
59
59
  if (!g) return undefined;
60
60
  if (g.status === "auditing") {
61
61
  const tool = audit?.currentTool ? ` · ${audit.currentTool}` : "";
62
- return `gla: auditing…${tool}`;
62
+ return `glla: auditing…${tool}`;
63
63
  }
64
64
  if (g.status === "paused") {
65
- return `gla: paused ⏸ ${truncate(g.pauseReason ?? "", 40)}`;
65
+ return `glla: paused ⏸ ${truncate(g.pauseReason ?? "", 40)}`;
66
66
  }
67
67
  if (g.status === "active") {
68
68
  const queue = state.list?.length ? ` · list ${state.list.length}` : "";
69
69
  const tasks = g.taskList ? ` ${countDone(g)}/${countTotal(g)} tasks ·` : "";
70
- return `gla: goal ●${tasks} ${fmtElapsed(now - Date.parse(g.createdAt))}${queue}`;
70
+ return `glla: goal ●${tasks} ${fmtElapsed(now - Date.parse(g.createdAt))}${queue}`;
71
71
  }
72
72
  return undefined; // complete/aborted → clear
73
73
  }
@@ -99,7 +99,7 @@ function countTotal(g: Goal): number {
99
99
  // ---- above-editor widget (multi-line panel) ----
100
100
 
101
101
  /**
102
- * Widget lines for ctx.ui.setWidget("pi-gla", lines).
102
+ * Widget lines for ctx.ui.setWidget("pi-glla", lines).
103
103
  * Returns undefined when nothing is worth showing.
104
104
  */
105
105
  export function buildWidgetLines(state: State, audit?: AuditDisplayProgress | null, now = Date.now()): string[] | undefined {
@@ -11,7 +11,7 @@
11
11
  * /goal "<objective>" | /goal (draft) | /goal status|pause|resume|cancel|tweak <text>|archive
12
12
  * /list add|show|next|remove|clear
13
13
  * /loop (draft) | /loop start|status|stop
14
- * /gla (settings UI) | /gla key=value | /gla project key=value
14
+ * /glla (settings UI) | /glla key=value | /glla project key=value
15
15
  */
16
16
 
17
17
  import * as fs from "node:fs";
@@ -142,8 +142,8 @@ let uiTicker: NodeJS.Timeout | null = null;
142
142
  function refreshUI(ctx: ExtensionContext): void {
143
143
  if (!ctx.hasUI) return;
144
144
  try {
145
- ctx.ui.setStatus("pi-gla", buildStatusText(state, latestAuditProgress));
146
- ctx.ui.setWidget("pi-gla", buildWidgetLines(state, latestAuditProgress));
145
+ ctx.ui.setStatus("pi-glla", buildStatusText(state, latestAuditProgress));
146
+ ctx.ui.setWidget("pi-glla", buildWidgetLines(state, latestAuditProgress));
147
147
  } catch {
148
148
  // stale ctx — next event refreshes
149
149
  }
@@ -545,7 +545,7 @@ async function cmdStatus(ctx: ExtensionContext): Promise<void> {
545
545
  `Objective: ${g.objective}`,
546
546
  `Auto-continue: ${g.autoContinue ? "on" : "off"}`,
547
547
  `Iteration: ${iterationCounter}`,
548
- `Tokens: ${(g.usage?.tokensUsed ?? 0).toLocaleString()}${(g.usage?.tokensLimit ?? 0) > 0 ? ` / ${(g.usage!.tokensLimit).toLocaleString()}` : " (no cap — /gla tokenlimit=<n> to set)"}`,
548
+ `Tokens: ${(g.usage?.tokensUsed ?? 0).toLocaleString()}${(g.usage?.tokensLimit ?? 0) > 0 ? ` / ${(g.usage!.tokensLimit).toLocaleString()}` : " (no cap — /glla tokenlimit=<n> to set)"}`,
549
549
  ];
550
550
  if (g.auditHistory && g.auditHistory.length > 0) {
551
551
  lines.push(`Audits: ${g.auditHistory.length} (${g.auditHistory.filter((v) => v.approved).length} approved)`);
@@ -848,7 +848,7 @@ function notifyExternal(ctx: ExtensionContext, message: string): void {
848
848
  }
849
849
 
850
850
  // =================================================================
851
- // Loop 3: /loop — metric-driven forever loop
851
+ // Loop 3: /loop — metric-driven process loop (never completes)
852
852
  //
853
853
  // The anti-doorknob law: the loop only believes a number. The orchestrator
854
854
  // runs the user's measure command (via pi.exec) after every agent turn;
@@ -1423,7 +1423,7 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
1423
1423
  parameters: Type.Object({
1424
1424
  objective: Type.String({ description: "The clarified, concrete objective (single item) or a summary when items[] is used" }),
1425
1425
  verificationContract: Type.Optional(Type.String({ description: "Checkable done-criteria (commands, file states, test outcomes)" })),
1426
- items: Type.Optional(Type.Array(Type.String(), { description: "LIST drafting only: many objectives at once (e.g. 'queue these 50 things'). Each becomes a queue item; per-item 'Done when:' clauses are honored." })),
1426
+ items: Type.Optional(Type.Array(Type.String(), { description: "LIST drafting only: many objectives at once (e.g. 'queue these 50 things'). Each becomes a list item; per-item 'Done when:' clauses are honored." })),
1427
1427
  }),
1428
1428
  async execute(_id, params, _signal, _onUpdate, execCtx) {
1429
1429
  const p = params as { objective: string; verificationContract?: string; items?: string[] };
@@ -2090,7 +2090,7 @@ async function cmdSettings(args: string, ctx: ExtensionContext): Promise<void> {
2090
2090
  // We detect duplicates at session start and warn loudly once.
2091
2091
  // =================================================================
2092
2092
 
2093
- const OUR_COMMANDS = ["goal", "gla", "list", "queue", "loop"];
2093
+ const OUR_COMMANDS = ["goal", "glla", "list", "loop"];
2094
2094
  let collisionWarned = false;
2095
2095
 
2096
2096
  // Providers verified to exist in a bare (extension-less) session. The auditor
@@ -2168,23 +2168,12 @@ export default function (pi: ExtensionAPI): void {
2168
2168
  description: "Open the settings UI for goals, loops, lists, and the auditor. Scriptable form: /glla key=value · /glla project key=value",
2169
2169
  handler: settingsHandler,
2170
2170
  });
2171
- // /gla kept as an alias (renamed package, v0.15.0) — muscle memory is real.
2172
- pi.registerCommand("gla", {
2173
- description: "Alias for /glla (package renamed to pi-goal-list-loop-audit in v0.15.0).",
2174
- handler: settingsHandler,
2175
- });
2176
2171
  pi.registerCommand("list", {
2177
2172
  description: "Loop 2: the list of audited goals — order is the default, not the law. /list add <obj or file or paste> | /list show | /list next [n] | /list remove <n> | /list clear",
2178
2173
  handler: (args: string, ctx: ExtensionContext) => { rememberCtx(ctx); return cmdList(args, ctx); },
2179
2174
  });
2180
- // Alias for one release (renamed 0.10.0 back to /list — order isn't
2181
- // mandatory with subagents; it's a shopping list, not a FIFO).
2182
- pi.registerCommand("queue", {
2183
- description: "Alias for /list (renamed in v0.10.0). Same subcommands.",
2184
- handler: (args: string, ctx: ExtensionContext) => { rememberCtx(ctx); return cmdList(args, ctx); },
2185
- });
2186
2175
  pi.registerCommand("loop", {
2187
- description: "Loop 3: metric-driven forever loop. /loop start \"<target>\" measure=\"<cmd>\" direction=min|max [done=<value>] [window=5] [max=50] | /loop status | /loop stop",
2176
+ description: "Loop 3: metric-driven process — it never completes. /loop start \"<target>\" measure=\"<cmd>\" direction=min|max [window=5] [max=50] [time=<hours>] [tokens=<budget>] [branch=1] | /loop status | /loop stop. 'Improve until X' is a /goal, not a loop.",
2188
2177
  handler: (args: string, ctx: ExtensionContext) => { rememberCtx(ctx); return cmdLoop(args, ctx); },
2189
2178
  });
2190
2179
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-goal-list-loop-audit",
3
- "version": "0.16.0",
3
+ "version": "0.17.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",
@@ -43,7 +43,7 @@ You have `read`, `write`, `edit`, `bash`, `grep`, `find`, `ls`, and the goal too
43
43
 
44
44
  If the objective decomposes into milestones and no task list exists yet, call `propose_task_list` early — the user confirms it, then you track progress with `complete_task` / `update_task_status` as you go (not in a batch at the end). Limits: 20 tasks, 5 subtasks per task.
45
45
 
46
- When the agent calls any of these, the orchestrator tracks the call and persists state to `.pi-gla/active.jsonl`.
46
+ When the agent calls any of these, the orchestrator tracks the call and persists state to `.pi-glla/active.jsonl`.
47
47
 
48
48
  ## TASK WORKFLOW
49
49
 
package/scripts/smoke.sh CHANGED
@@ -2,7 +2,7 @@
2
2
  # pi-goal-list-loop-audit — live integration smoke
3
3
  #
4
4
  # Drives a real pi session in tmux against a scratch dir and asserts on the
5
- # .pi-gla ledger. This is the M2 "integration harness": it exercises the full
5
+ # .pi-glla ledger. This is the M2 "integration harness": it exercises the full
6
6
  # loop (goal → agent work → complete_goal → isolated auditor → archive) with
7
7
  # real models, which unit tests cannot do.
8
8
  #
@@ -43,7 +43,7 @@ wait_for() { # wait_for <pattern> <timeout-s>
43
43
  return 1
44
44
  }
45
45
  ledger_has() { # ledger_has <jq-ish python expr substring>
46
- python3 - "$1" "$WORK/.pi-gla/active.jsonl" <<'EOF'
46
+ python3 - "$1" "$WORK/.pi-glla/active.jsonl" <<'EOF'
47
47
  import json, sys
48
48
  needle, path = sys.argv[1], sys.argv[2]
49
49
  try:
@@ -81,7 +81,7 @@ case "$SCENARIO" in
81
81
  sleep 2
82
82
  if [ -f "$WORK/smoke.txt" ]; then pass "smoke.txt created"; else fail "smoke.txt missing"; fi
83
83
  if ledger_has '"approved":true'; then pass "ledger records approval"; else fail "ledger missing approval"; fi
84
- if ls "$WORK/.pi-gla/archive/"*.md >/dev/null 2>&1; then pass "goal archived"; else fail "archive empty"; fi
84
+ if ls "$WORK/.pi-glla/archive/"*.md >/dev/null 2>&1; then pass "goal archived"; else fail "archive empty"; fi
85
85
  if ledger_has '"regressionShieldPassed":true'; then pass "regression_shield recorded"; else fail "shield outcome missing"; fi
86
86
  ;;
87
87
 
@@ -93,11 +93,11 @@ case "$SCENARIO" in
93
93
  if wait_for "approved by auditor" 120; then pass "item 1 approved"; else fail "item 1 not approved"; fi
94
94
  # wait for second archive file
95
95
  for i in $(seq 1 120); do
96
- n=$(ls "$WORK/.pi-gla/archive/"*.md 2>/dev/null | wc -l)
96
+ n=$(ls "$WORK/.pi-glla/archive/"*.md 2>/dev/null | wc -l)
97
97
  [ "$n" -ge 2 ] && break
98
98
  sleep 1
99
99
  done
100
- n=$(ls "$WORK/.pi-gla/archive/"*.md 2>/dev/null | wc -l)
100
+ n=$(ls "$WORK/.pi-glla/archive/"*.md 2>/dev/null | wc -l)
101
101
  if [ "$n" -ge 2 ]; then pass "both items archived ($n)"; else fail "only $n archived"; fi
102
102
  if [ -f "$WORK/a.txt" ] && [ -f "$WORK/b.txt" ]; then pass "both files created"; else fail "files missing"; fi
103
103
  if ledger_has '"list":[]'; then pass "list drained"; else fail "list not empty"; fi
@@ -119,7 +119,7 @@ case "$SCENARIO" in
119
119
  echo 5 > "$WORK/num.txt"
120
120
  NOTIFY_LOG="$WORK/notify.log"
121
121
  # project scope — never write test config into the user's GLOBAL settings
122
- send "/gla project notify='echo \$1 >> $NOTIFY_LOG'"
122
+ send "/glla project notify='echo \$1 >> $NOTIFY_LOG'"
123
123
  sleep 4
124
124
  send '/loop start "Reduce the number in num.txt toward zero, never below 0" measure="cat num.txt" direction=min window=3 max=12'
125
125
  say "waiting for plateau stop (up to 300s)"