pi-goal-list-loop-audit 0.15.1 → 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
@@ -33,13 +33,14 @@ Four top-level commands, that's all:
33
33
  /goal # drafting: agent grills, you Confirm
34
34
  /goal "audit the repo" # no contract clause → agent grills you first (propose is gated on it)
35
35
  /goal "Step 1. Step 2. Done when: tests pass." # has contract → starts now
36
+ /goal start "fix the flaky login test" # explicit skip-draft: starts now, no interview (auditor infers the contract)
36
37
  /goal status # show state
37
38
  /goal pause # pause
38
39
  /goal resume # resume
39
40
  /goal cancel # abort
40
41
  /goal tweak "<new objective>" # edit in place (Confirm dialog)
41
42
  /goal archive # archived goals, newest first
42
- /gla # open the settings UI (or /gla key=value)
43
+ /glla # open the settings UI (or /glla key=value)
43
44
  /list add # draft a contract (or a whole batch via items[])
44
45
  /list add "<objective>" # queue one directly
45
46
  /list add plan.md # file detected → bulk import, one Confirm
@@ -69,7 +70,9 @@ your objectives can start with any verb.)
69
70
 
70
71
  Drafting rules: **no-args drafts, args-without-a-`Done when:`-clause get
71
72
  grilled by the agent (proposing is mechanically blocked until you have
72
- replied at least once), args-with-a-clause start instantly, a file path is
73
+ replied at least once typed chat or an answered `ask_user_question` dialog
74
+ both count), args-with-a-clause start instantly, `/goal start` skips the
75
+ interview by explicit command, a file path is
73
76
  bulk direct.** A
74
77
  sisyphus-style plan file (checklists, bullets, numbered, plain lines) imports
75
78
  as-is — headings become nothing, items become goals. And the drafter itself
@@ -148,7 +151,7 @@ Each loop is a different policy class on the same status machine.
148
151
 
149
152
  ## Live TUI (always know it's on)
150
153
 
151
- 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
152
155
  goal/loop at all times: objective, status, elapsed, tokens, next task or loop
153
156
  metric, pause reason, and live auditor progress during audits. If something is
154
157
  running, you can see it — no command needed.
@@ -163,19 +166,19 @@ No external watchdog plugin needed.
163
166
  ## Config (one global place, rarely opened)
164
167
 
165
168
  ```
166
- /gla # open the settings UI
167
- /gla model=provider/id # auditor model override → GLOBAL
168
- /gla thinking=high # auditor thinking → GLOBAL
169
- /gla notify='cmd "$1"' # push on complete/pause/stop → GLOBAL
170
- /gla tokenlimit=10000000 # per-goal token budget (default: off) → GLOBAL
171
- /gla tokenlimit=0 # explicitly no cap (the default)
172
- /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
173
176
  ```
174
177
 
175
178
  Resolution per key: **project > global > defaults**. The auditor defaults to
176
179
  your pi session model. When the session provider is extension-registered the
177
180
  auditor can't auth it — you're told once (info level) with the fix:
178
- `/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
179
182
  picks a model itself. Thinking follows the session too (floor `high`).
180
183
 
181
184
  ## Token guard
@@ -183,7 +186,7 @@ picks a model itself. Thinking follows the session too (floor `high`).
183
186
  Every goal tracks real token usage; crossing the budget pauses the goal.
184
187
  Default 10,000,000 per goal — a runaway threshold, not a big-goal threshold
185
188
  (real research/feature goals legitimately burn 2-4M). Tune with
186
- `/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
187
190
  (max iterations + plateau).
188
191
 
189
192
  ## Compatibility (what goes well, what conflicts)
@@ -209,8 +212,8 @@ not requirements).
209
212
 
210
213
  **Two footnotes**: (1) extension-registered providers work in the main session
211
214
  but not the auditor's extension-less session — if audits fail auth, set the
212
- override once with `/gla model=`. (2) `pi-notify-agent` notifies on every turn;
213
- `/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.
214
217
 
215
218
  ## Files
216
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
 
@@ -158,10 +158,10 @@ export interface Goal {
158
158
  export type GoalRoute =
159
159
  | { kind: "draft" }
160
160
  | { kind: "set"; text: string }
161
- | { kind: "sub"; name: "status" | "pause" | "resume" | "cancel" | "tweak" | "archive"; rest: string };
161
+ | { kind: "sub"; name: "status" | "pause" | "resume" | "cancel" | "tweak" | "archive" | "start"; rest: string };
162
162
 
163
163
  const GOAL_EXACT_SUBS = new Set(["status", "pause", "resume", "cancel"]);
164
- const GOAL_ARG_SUBS = new Set(["tweak", "archive"]);
164
+ const GOAL_ARG_SUBS = new Set(["tweak", "archive", "start"]);
165
165
 
166
166
  export function routeGoalArgs(raw: string): GoalRoute {
167
167
  const trimmed = raw.trim();
@@ -173,7 +173,7 @@ export function routeGoalArgs(raw: string): GoalRoute {
173
173
  return { kind: "sub", name: first as "status" | "pause" | "resume" | "cancel", rest: "" };
174
174
  }
175
175
  if (GOAL_ARG_SUBS.has(first)) {
176
- return { kind: "sub", name: first as "tweak" | "archive", rest };
176
+ return { kind: "sub", name: first as "tweak" | "archive" | "start", rest };
177
177
  }
178
178
  return { kind: "set", text: trimmed };
179
179
  }
@@ -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
  }
@@ -434,7 +434,7 @@ async function startDrafting(ctx: ExtensionContext, target: "goal" | "list" | "l
434
434
  const [file, label, tool] = prompts[target]!;
435
435
  ctx.ui.notify(
436
436
  seed
437
- ? `${label}: the objective has no "Done when:" clause — the agent will grill you about it first (nothing activates until you confirm).`
437
+ ? `${label}: the objective has no "Done when:" clause — the agent will grill you about it first (nothing activates until you confirm). Skip the interview entirely: /goal start <objective>.`
438
438
  : `${label} started. The agent will grill until the contract is concrete, then ${tool} opens a Confirm dialog. No work begins before confirmation.`,
439
439
  "info",
440
440
  );
@@ -481,6 +481,16 @@ async function cmdGoal(args: string, ctx: ExtensionContext): Promise<void> {
481
481
  if (route.name === "cancel") return cmdCancel(ctx);
482
482
  if (route.name === "tweak") return cmdTweak(route.rest, ctx);
483
483
  if (route.name === "archive") return cmdGoals(ctx);
484
+ // v0.16.0: /goal start <objective> — explicit skip-draft. Activates
485
+ // immediately, no interview, no "Done when:" heuristic. Symmetric
486
+ // with /loop start. The auditor infers the contract from the objective.
487
+ if (route.name === "start") {
488
+ if (!route.rest) {
489
+ ctx.ui.notify("Usage: /goal start <objective> — activates immediately, skipping the drafting interview. (Without start, an objective needs a 'Done when:' clause or it gets drafted first.)", "warning");
490
+ return;
491
+ }
492
+ return cmdSet(route.rest, ctx, true);
493
+ }
484
494
  }
485
495
  return cmdSet(route.kind === "set" ? route.text : "", ctx);
486
496
  }
@@ -489,7 +499,7 @@ async function cmdGoal(args: string, ctx: ExtensionContext): Promise<void> {
489
499
  // /goal: bypass drafting, start now (the only entry in v0.1.0)
490
500
  // =================================================================
491
501
 
492
- async function cmdSet(args: string, ctx: ExtensionContext): Promise<void> {
502
+ async function cmdSet(args: string, ctx: ExtensionContext, skipDraft = false): Promise<void> {
493
503
  let raw = args.trim();
494
504
  // Users naturally quote the objective ("/goal \"do X\""); strip one layer of
495
505
  // surrounding matching quotes so they don't leak into the goal text.
@@ -507,7 +517,8 @@ async function cmdSet(args: string, ctx: ExtensionContext): Promise<void> {
507
517
  // v0.11.0: a contract-less objective gets drafted, not activated raw —
508
518
  // the pi-goal-x lesson: arg + Enter is worse than a 5-minute draft.
509
519
  // Include an explicit "Done when: …" clause to activate instantly.
510
- if (goalArgsNeedDrafting(raw)) {
520
+ // v0.16.0: /goal start bypasses this by explicit user command.
521
+ if (!skipDraft && goalArgsNeedDrafting(raw)) {
511
522
  await startDrafting(ctx, "goal", raw);
512
523
  return;
513
524
  }
@@ -534,7 +545,7 @@ async function cmdStatus(ctx: ExtensionContext): Promise<void> {
534
545
  `Objective: ${g.objective}`,
535
546
  `Auto-continue: ${g.autoContinue ? "on" : "off"}`,
536
547
  `Iteration: ${iterationCounter}`,
537
- `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)"}`,
538
549
  ];
539
550
  if (g.auditHistory && g.auditHistory.length > 0) {
540
551
  lines.push(`Audits: ${g.auditHistory.length} (${g.auditHistory.filter((v) => v.approved).length} approved)`);
@@ -837,7 +848,7 @@ function notifyExternal(ctx: ExtensionContext, message: string): void {
837
848
  }
838
849
 
839
850
  // =================================================================
840
- // Loop 3: /loop — metric-driven forever loop
851
+ // Loop 3: /loop — metric-driven process loop (never completes)
841
852
  //
842
853
  // The anti-doorknob law: the loop only believes a number. The orchestrator
843
854
  // runs the user's measure command (via pi.exec) after every agent turn;
@@ -1412,7 +1423,7 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
1412
1423
  parameters: Type.Object({
1413
1424
  objective: Type.String({ description: "The clarified, concrete objective (single item) or a summary when items[] is used" }),
1414
1425
  verificationContract: Type.Optional(Type.String({ description: "Checkable done-criteria (commands, file states, test outcomes)" })),
1415
- 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." })),
1416
1427
  }),
1417
1428
  async execute(_id, params, _signal, _onUpdate, execCtx) {
1418
1429
  const p = params as { objective: string; verificationContract?: string; items?: string[] };
@@ -2079,7 +2090,7 @@ async function cmdSettings(args: string, ctx: ExtensionContext): Promise<void> {
2079
2090
  // We detect duplicates at session start and warn loudly once.
2080
2091
  // =================================================================
2081
2092
 
2082
- const OUR_COMMANDS = ["goal", "gla", "list", "queue", "loop"];
2093
+ const OUR_COMMANDS = ["goal", "glla", "list", "loop"];
2083
2094
  let collisionWarned = false;
2084
2095
 
2085
2096
  // Providers verified to exist in a bare (extension-less) session. The auditor
@@ -2149,7 +2160,7 @@ export default function (pi: ExtensionAPI): void {
2149
2160
  // /loop — the metric loop (draft|start|status|stop)
2150
2161
  // /gla — the settings UI (+ scriptable key=value)
2151
2162
  pi.registerCommand("goal", {
2152
- description: "Set/draft a goal, or /goal status|pause|resume|cancel|tweak <text>|archive. Objectives without a 'Done when:' clause are grilled into a contract first (nothing activates until you confirm); include the clause to start instantly.",
2163
+ description: "Set/draft a goal, or /goal status|pause|resume|cancel|tweak <text>|archive|start <objective>. Objectives without a 'Done when:' clause are grilled into a contract first; include the clause or use /goal start to skip the interview and activate instantly.",
2153
2164
  handler: (args: string, ctx: ExtensionContext) => { rememberCtx(ctx); return cmdGoal(args, ctx); },
2154
2165
  });
2155
2166
  const settingsHandler = (args: string, ctx: ExtensionContext) => { rememberCtx(ctx); return cmdSettings(args, ctx); };
@@ -2157,23 +2168,12 @@ export default function (pi: ExtensionAPI): void {
2157
2168
  description: "Open the settings UI for goals, loops, lists, and the auditor. Scriptable form: /glla key=value · /glla project key=value",
2158
2169
  handler: settingsHandler,
2159
2170
  });
2160
- // /gla kept as an alias (renamed package, v0.15.0) — muscle memory is real.
2161
- pi.registerCommand("gla", {
2162
- description: "Alias for /glla (package renamed to pi-goal-list-loop-audit in v0.15.0).",
2163
- handler: settingsHandler,
2164
- });
2165
2171
  pi.registerCommand("list", {
2166
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",
2167
2173
  handler: (args: string, ctx: ExtensionContext) => { rememberCtx(ctx); return cmdList(args, ctx); },
2168
2174
  });
2169
- // Alias for one release (renamed 0.10.0 back to /list — order isn't
2170
- // mandatory with subagents; it's a shopping list, not a FIFO).
2171
- pi.registerCommand("queue", {
2172
- description: "Alias for /list (renamed in v0.10.0). Same subcommands.",
2173
- handler: (args: string, ctx: ExtensionContext) => { rememberCtx(ctx); return cmdList(args, ctx); },
2174
- });
2175
2175
  pi.registerCommand("loop", {
2176
- 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.",
2177
2177
  handler: (args: string, ctx: ExtensionContext) => { rememberCtx(ctx); return cmdLoop(args, ctx); },
2178
2178
  });
2179
2179
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-goal-list-loop-audit",
3
- "version": "0.15.1",
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)"