pi-goal-list-loop-audit 0.38.46 → 0.38.47

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/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.38.47 — layered prompts: skeleton plus on-demand detail (2026-09-11)
4
+
5
+ ### Changed
6
+
7
+ - **Prompt layers:** every `prompts/*.md` file is now a static skeleton plus on-demand detail blocks (`<!-- glla-layer: detail <id> -->`). The continuation template carries three conditional details — `auditor-disapproval` (live disapproval), `survey-pivot` (survey objective), `session-restart` (resync/recovery) — loaded only when their trigger is already known at render time; loop, draft, and plan prompts are skeleton-only (per-iteration essential or already file-granular on-demand). Static layers resolve before dynamic substitution, in file order every turn.
8
+ - **Measured saving:** clean-scenario full continuation render drops 24,347 to 21,801 chars (~10%, est. tokens 6,087 to 5,451); firing predicates re-add exactly their own bodies.
9
+ - **Loud failure:** a missing prompt file, unclosed/duplicate block, or unknown detail id throws naming the file — the `[template-not-found]`, inline mini-prompt, and `[DRAFTING] Clarify` silent fallbacks are gone.
10
+ - **Mid-turn contingencies stay put:** subagent death, provider errors, detached commits, stalls, and task workflow remain in the skeleton (their trigger cannot be known before the turn; no mid-turn fetch exists).
11
+ - **Pins:** `tests/prompt-layers.test.ts` (19) — full-assembly render-diff for all 7 files, exact-delta proofs, loud-missing, order determinism; `tests/context-growth-measurement.test.ts` and `tests/context-checkpoint.test.ts` re-baselined deliberately (deltas verified byte-exact against a pre-change worktree).
12
+
3
13
  ## 0.38.46 — rich Antigravity-style terminal summaries (2026-09-11)
4
14
 
5
15
  ### Changed
package/INSTALL.md CHANGED
@@ -31,11 +31,11 @@ GLLA loads into new pi sessions. If pi is already open, reload that session:
31
31
 
32
32
  ## Updating
33
33
 
34
- The status line always shows the running version (`glla: … · v0.38.44`).
34
+ The status line always shows the running version (`glla: … · v0.38.46`).
35
35
  When the npm registry is ahead, it also nudges:
36
36
 
37
37
  ```text
38
- glla: … · v0.38.43 · update v0.38.44 available
38
+ glla: … · v0.38.45 · update v0.38.46 available
39
39
  ```
40
40
 
41
41
  The nudge comes from a daily sidecar check (`.pi-glla/update-check.json`),
@@ -45,7 +45,7 @@ refreshed on command contact and never blocking a turn. To update:
45
45
  pi install npm:pi-goal-list-loop-audit@latest
46
46
  ```
47
47
 
48
- Then `/reload` every open pi session — a session keeps running the
48
+ Then `/reload` every open pi session: a session keeps running the
49
49
  version it loaded with until reloaded. `/glla version` says whether the
50
50
  current session is stale and repeats the update command.
51
51
 
@@ -58,7 +58,7 @@ and confirmation UX:
58
58
  pi install npm:@juicesharp/rpiv-ask-user-question
59
59
  ```
60
60
 
61
- For best automation and quality, add the **pinned parallel-orchestration companion** (`pi-subagents` 0.62.0) — GLLA's power-max choice for `runs.all` fan-out, `runs.lanes` worker→review→fix chains, structured verification, worktree isolation, and durable recovery:
61
+ For best automation and quality, add the **pinned parallel-orchestration companion** (`pi-subagents` 0.62.0): GLLA's power-max choice for `runs.all` fan-out, `runs.lanes` worker→review→fix chains, structured verification, worktree isolation, and durable recovery:
62
62
 
63
63
  ```bash
64
64
  pi install npm:pi-subagents@0.62.0
@@ -260,9 +260,9 @@ supervisor, or let it own the session instead of GLLA.
260
260
 
261
261
  ## Further reading
262
262
 
263
- - [`README.md`](README.md) — product overview, first-use guide, commands,
263
+ - [`README.md`](README.md): product overview, first-use guide, commands,
264
264
  companion policy, autonomy model, recovery, and maintainer map;
265
- - [`docs/DESIGN.md`](docs/DESIGN.md) — architecture and design decisions;
266
- - [`docs/INDEX.md`](docs/INDEX.md) — shipped and repository-only documentation;
267
- - [`docs/RELEASING.md`](docs/RELEASING.md) — release process;
268
- - [`CHANGELOG.md`](CHANGELOG.md) — version history.
265
+ - [`docs/DESIGN.md`](docs/DESIGN.md): architecture and design decisions;
266
+ - [`docs/INDEX.md`](docs/INDEX.md): shipped and repository-only documentation;
267
+ - [`docs/RELEASING.md`](docs/RELEASING.md): release process;
268
+ - [`CHANGELOG.md`](CHANGELOG.md): version history.
package/README.md CHANGED
@@ -94,7 +94,7 @@ Done when:
94
94
  ```
95
95
 
96
96
  The contract is the important part. Replace the example with the result you
97
- actually want and checks that another person—or another agent—could inspect.
97
+ actually want and checks that another person (or another agent) could inspect.
98
98
 
99
99
  For an objective that needs shaping, start with bare `/goal` and answer the
100
100
  interview. GLLA will research the ambiguity, ask focused questions, and show a
@@ -132,7 +132,7 @@ forcing every problem into a loop.
132
132
  | `/list` | Several outcomes or a backlog of independently verifiable items | Each item is worked and audited separately; the queue advances safely |
133
133
  | `/loop` | Ongoing improvement with no single final item | A metric, specification, audit cadence, bound, or `/loop stop` ends the process |
134
134
 
135
- ### `/goal` — one outcome
135
+ ### `/goal`: one outcome
136
136
 
137
137
  ```text
138
138
  /goal # interview + Confirm
@@ -152,7 +152,7 @@ A goal is the best default for work with a finish line. If the agent discovers
152
152
  that the objective is too large, it can propose a bounded task plan instead of
153
153
  quietly inventing an unbounded backlog.
154
154
 
155
- ### `/list` — a durable work pool
155
+ ### `/list`: a durable work pool
156
156
 
157
157
  ```text
158
158
  /list "fix the cache. Done when: tests pass"
@@ -179,11 +179,11 @@ bounded bootstrap turn containing `propose_task_list`. Confirm the redraft;
179
179
  automatic repeats are fenced. Use `/list resume` or `/glla resume` for an
180
180
  intentional retry of a waiting/restored queue, and `/list next` when you
181
181
  intentionally want to skip or choose another queued item. `/list start` is
182
- also explicit: it activates the queued head, or—when the queue is empty—uses
182
+ also explicit: it activates the queued head, or, when the queue is empty, uses
183
183
  one clear recent user request as a seed for the normal Confirm-gated list
184
184
  drafting flow. Ambiguous context is never queued automatically.
185
185
 
186
- ### `/loop` — an improvement process
186
+ ### `/loop`: an improvement process
187
187
 
188
188
  ```text
189
189
  /loop # interview + Confirm
@@ -274,13 +274,13 @@ GLLA is the supervisor. These companions add capabilities around it:
274
274
 
275
275
  ### Recommended for almost everyone
276
276
 
277
- - **`@juicesharp/rpiv-ask-user-question`** — structured questions, multi-select,
277
+ - **`@juicesharp/rpiv-ask-user-question`**: structured questions, multi-select,
278
278
  previews, and Confirm dialogs for drafting and decisions. GLLA has a prose
279
279
  fallback, but this is the intended UX.
280
280
 
281
- ### Recommended for power — parallel orchestration (`pi-subagents`)
281
+ ### Recommended for power: parallel orchestration (`pi-subagents`)
282
282
 
283
- - **`pi-subagents` 0.62.0 (pinned) — the power-max choice for GLLA.** Use it
283
+ - **`pi-subagents` 0.62.0 (pinned): the power-max choice for GLLA.** Use it
284
284
  when you want the best automation and quality: `runs.all` parallel fan-out,
285
285
  `runs.lanes` worker→review→fix chains, `outputSchema` + `acceptance` structured
286
286
  verification, `runs.host` gated shell, worktree isolation, model routing
@@ -295,7 +295,7 @@ GLLA is the supervisor. These companions add capabilities around it:
295
295
 
296
296
  The main pi session remains the owner of the goal/list/loop; subagents are
297
297
  workers and cannot silently replace the parent's objective. A short or mostly
298
- sequential goal can still run cleanly without workers — install when
298
+ sequential goal can still run cleanly without workers; install when
299
299
  parallelism will pay for its coordination and model usage.
300
300
 
301
301
  Display coexistence: pi-subagents renders its own inline run panels +
@@ -304,7 +304,7 @@ GLLA is the supervisor. These companions add capabilities around it:
304
304
  swapping order, set pi-subagents `inlineToolDisplay: "summary"` (one
305
305
  stable row per run) and pin `fleetViewPlacement`; GLLA's own richness is
306
306
  the `subagentDisplayRichness` setting (`/glla` → Subagents): `rich`
307
- (default — worker rows + task linkage), `compact` (count line), `quiet`
307
+ (default: worker rows + task linkage), `compact` (count line), `quiet`
308
308
  (hung/aborting workers only). Upstream triple-render report:
309
309
  nicobailon/pi-subagents#1931.
310
310
 
@@ -319,17 +319,17 @@ recommendation in the same session. Existing Tintin-era agent files are
319
319
  cleaned only when GLLA's management marker proves that GLLA owns them; old
320
320
  settings are not silently remapped to a different role. Do not stack
321
321
  `@quintinshaw/pi-dynamic-workflows` as a competing orchestrator alongside
322
- GLLA + `pi-subagents` in the same session — duplicate tools and competing
322
+ GLLA + `pi-subagents` in the same session: duplicate tools and competing
323
323
  orchestration events create ambiguous ownership. Use its quality helpers
324
324
  (`verify`/`judgePanel`/`loopUntilDry`) only as isolated complements if needed.
325
325
 
326
326
  ### Useful, but optional
327
327
 
328
- - **`@pi-unipi/notify`** — Telegram, Gotify, or ntfy delivery when you need
328
+ - **`@pi-unipi/notify`**: Telegram, Gotify, or ntfy delivery when you need
329
329
  alerts away from the desktop. GLLA's local notifications work without it;
330
330
  when no command is configured, it auto-detects `notify-send`/`osascript`;
331
331
  `notify=off` silences notifications.
332
- - **`pi-chrome`** — logged-in browser research and interaction when a goal needs
332
+ - **`pi-chrome`**: logged-in browser research and interaction when a goal needs
333
333
  a real web session. It is not required for repository-only work.
334
334
 
335
335
  ### What not to combine with GLLA
@@ -501,4 +501,4 @@ repository material, not first-use package content.
501
501
 
502
502
  ## License
503
503
 
504
- GNU Affero General Public License v3.0-only — see [LICENSE](LICENSE).
504
+ GNU Affero General Public License v3.0-only; see [LICENSE](LICENSE).
package/docs/INDEX.md CHANGED
@@ -1,16 +1,14 @@
1
- # docs/ — index
1
+ # docs/ index
2
2
 
3
3
  Ordered by reading path, not alphabetically.
4
4
 
5
5
  ## Active focus (recent work, durable artifacts)
6
6
 
7
- This package's policy contracts and recent changes are recorded in
8
- the audit/ directory of the **repository checkout** — it is not
9
- shipped in the npm tarball (see "Repository-only material" below).
7
+ Policy contracts and recent changes live in the `audit/` directory of the
8
+ **repository checkout**; that directory is not shipped in the npm tarball
9
+ (see "Repository-only material" below). The shipped entry points are:
10
10
 
11
- For shipped docs, the relevant entry points are:
12
-
13
- - `../CHANGELOG.md` — user-facing changelog; an `Unreleased` section may lead
11
+ - `../CHANGELOG.md`: user-facing changelog; an `Unreleased` section may lead
14
12
  the file, followed by the current released version. v0.35.5 adopted the six-label completion
15
13
  recap; v0.35.6 added typed-boundary regression pins; v0.35.7 added
16
14
  deterministic fast-fail pre-audits, zero-pause autonomous execution, and
@@ -18,7 +16,7 @@ For shipped docs, the relevant entry points are:
18
16
  failback; v0.35.9 hardened cross-version npm tarball checks; v0.35.10
19
17
  handles multi-entry npm dry-run reports; v0.35.11 accepts both npm report
20
18
  shapes; v0.35.12 supports npm 12's keyed pack reports; v0.35.13 fixes stale-API recovery loops.
21
- v0.35.14–v0.38.46 continue through the supervisor freeze (`/glla pause`),
19
+ v0.35.14–v0.38.47 continue through the supervisor freeze (`/glla pause`),
22
20
  load hold, auditor picker parity, Windows launch fix, zombie-watchdog
23
21
  subagent carve-out, due-wait backstop, the `/glla agents` visibility panel,
24
22
  durable state-root selection, blank-until-resume auditor context, frozen
@@ -28,43 +26,43 @@ For shipped docs, the relevant entry points are:
28
26
  packed-artifact release verification, and the 2026-09-07 display/lifecycle/
29
27
  settings audit pass (abort-latch send guards, ownership compare-and-swap,
30
28
  auditor inherit/clear parity) and the v0.38.25 post-objective summary
31
- (canonical approval render, persist + replay, audit-goal counts line), the v0.38.27 selective port of PRs #45/#46 (`/loop pause` soft-hold, widget subtask count), the v0.38.28 provenance-row closure fix, and the v0.38.29 compact active-card recovery/judgment projection — see CHANGELOG.md for the full trail.
32
- - `../README.md` — what the plugin is, install, quickstart, and the
29
+ (canonical approval render, persist + replay, audit-goal counts line), the v0.38.27 selective port of PRs #45/#46 (`/loop pause` soft-hold, widget subtask count), the v0.38.28 provenance-row closure fix, and the v0.38.29 compact active-card recovery/judgment projection; see CHANGELOG.md for the full trail.
30
+ - `../README.md`: what the plugin is, install, quickstart, and the
33
31
  architectural guarantee (drafting + confirm + detached auditor).
34
- - `../INSTALL.md` — source install / local development setup; the recommended
32
+ - `../INSTALL.md`: source install / local development setup; the recommended
35
33
  companion plugins and the `auditor reads / writes are path-checked`
36
34
  note.
37
35
 
38
36
  ## Entry points
39
- - `../README.md` — what the plugin is, install, quickstart
40
- - `SETTINGS.md` — canonical settings reference (files, precedence, every key)
41
- - `../INSTALL.md` — source install / local development setup
42
- - `../CHANGELOG.md` — user-facing changelog; the first versioned heading is
37
+ - `../README.md`: what the plugin is, install, quickstart
38
+ - `SETTINGS.md`: canonical settings reference (files, precedence, every key)
39
+ - `../INSTALL.md`: source install / local development setup
40
+ - `../CHANGELOG.md`: user-facing changelog; the first versioned heading is
43
41
  the current released package version (use `/glla version` to compare with
44
42
  the registry); post-release work may appear in `Unreleased` above it.
45
43
 
46
44
  ## Architecture
47
- - `DESIGN.md` — plugin design (types, state, extension lifecycle)
48
- - `DESIGN-long-running-supervision.md` — v0.36.0 event/progress-driven supervision, aggressive recovery, terminal recaps, and future decision checklist
49
- - `GLLA-POSITIONING-AND-DECOMPOSITION-2026-08-08.md` — ecosystem
45
+ - `DESIGN.md`: plugin design (types, state, extension lifecycle)
46
+ - `DESIGN-long-running-supervision.md`: v0.36.0 event/progress-driven supervision, aggressive recovery, terminal recaps, and future decision checklist
47
+ - `GLLA-POSITIONING-AND-DECOMPOSITION-2026-08-08.md`: ecosystem
50
48
  positioning, competitor review, and the goal.ts decomposition plan
51
- (the current strategic doc — read this before touching
49
+ (the current strategic doc; read this before touching
52
50
  `extensions/loops/goal.ts`)
53
- - `VISION-ASSIST.md` — vision-assist plugin notes
54
- - `RELEASING.md` — how to publish to npm
51
+ - `VISION-ASSIST.md`: vision-assist plugin notes
52
+ - `RELEASING.md`: how to publish to npm
55
53
 
56
54
  ## Supporting material
57
- - `../prompts/` — goal/loop drafting prompt templates
58
- - `../schemas/` — goal state JSON schema
59
- - `../examples/` — example objective files
60
- - `../CHANGELOG.md` — user-facing changelog (unreleased at top)
61
- - `/glla bug` — `extensions/goal-commands.ts:cmdGllaBug` captures failure context to `<stateDir>/bugs/<ts>-<id>.md` without touching `active.jsonl`/`goals/*.md` (see `tests/glla-bug-capture.test.ts`)
55
+ - `../prompts/`: goal/loop drafting prompt templates
56
+ - `../schemas/`: goal state JSON schema
57
+ - `../examples/`: example objective files
58
+ - `../CHANGELOG.md`: user-facing changelog (unreleased at top)
59
+ - `/glla bug`: `extensions/goal-commands.ts:cmdGllaBug` captures failure context to `<stateDir>/bugs/<ts>-<id>.md` without touching `active.jsonl`/`goals/*.md` (see `tests/glla-bug-capture.test.ts`)
62
60
 
63
61
  ## Repository-only material
64
62
  The audit history and competitor research live in `audit/` and `.research/`
65
63
  for contributors, but are intentionally not included in the npm tarball.
66
64
 
67
65
  ## Research material
68
- `.research/` — competitor plugin sources pulled from npm tarballs for study
66
+ `.research/`: competitor plugin sources pulled from npm tarballs for study
69
67
  (gitignored, local only). Re-pull with `cd .research && npm pack <pkg> &&
70
68
  tar xzf <tgz>`; see the positioning doc's appendix for the package list.
@@ -56,6 +56,7 @@ import {
56
56
  type ObjectiveRepairTarget,
57
57
  } from "./goal-loop-core.js";
58
58
  import { auditorSurfaceSuppressed } from "./loops/goal-auditor-surface.js";
59
+ import { assemblePrompt } from "./prompt-layers.js";
59
60
  import {
60
61
  createContinuationDispatch,
61
62
  transitionDispatch,
@@ -1235,7 +1236,7 @@ export function buildContinuationContent(goal: Goal, opts: { resync?: string; fi
1235
1236
  const resync = opts.resync ?? "";
1236
1237
  const marker = buildMarkerContent(goal.id);
1237
1238
  const needsFull = (opts.firstSend ?? false) || needsFullContinuation(goal);
1238
- if (needsFull) return { content: resync + continuationPrompt(goal), kind: resync ? "full+resync" : "full" };
1239
+ if (needsFull) return { content: resync + continuationPrompt(goal, { includeRestartDetail: Boolean(resync) || Boolean(goal.autoResumedAt) }), kind: resync ? "full+resync" : "full" };
1239
1240
  if (resync) return { content: resync + marker, kind: "resync" };
1240
1241
  return { content: marker, kind: "marker" };
1241
1242
  }
@@ -1493,7 +1494,7 @@ export function buildPostCompactResync(briefExcerpt?: string): string {
1493
1494
  return lines.join("\n") + "\n\n";
1494
1495
  }
1495
1496
 
1496
- export function continuationPrompt(goal: Goal): string {
1497
+ export function continuationPrompt(goal: Goal, opts: { includeRestartDetail?: boolean } = {}): string {
1497
1498
  // Read the .md file as the template, then substitute {{tokens}}.
1498
1499
  // For v0.1.0 we inline-substitute so we don't need fs at runtime.
1499
1500
  const next = findNextPendingTask(goal.taskList?.tasks ?? []);
@@ -1503,13 +1504,17 @@ export function continuationPrompt(goal: Goal): string {
1503
1504
  const taskSummary = goal.taskList?.tasks.length
1504
1505
  ? buildTaskSummary(goal.taskList.tasks)
1505
1506
  : "(no task list)";
1506
- const tmplPath = path.resolve(__dirname, "..", "prompts", "goal-loop-continuation.md");
1507
- let tmpl: string;
1508
- try {
1509
- tmpl = fs.readFileSync(tmplPath, "utf-8");
1510
- } catch {
1511
- tmpl = "[template-not-found]";
1512
- }
1507
+ // Layered assembly: skeleton always; details only when their trigger is
1508
+ // already known at render time. Static layers resolve before dynamic
1509
+ // ${...} substitution, in file order every turn. Mid-turn contingencies
1510
+ // (subagent death, provider errors, detached commits, stalls, task
1511
+ // workflow) stay in the skeleton: their trigger cannot be known before
1512
+ // the turn, and there is no mid-turn detail fetch.
1513
+ const detailIds = new Set<string>();
1514
+ if (liveDisapproval(goal.auditHistory ?? [])) detailIds.add("auditor-disapproval");
1515
+ if (isFullAuditObjective(goal.objective)) detailIds.add("survey-pivot");
1516
+ if (opts.includeRestartDetail || goal.autoResumedAt) detailIds.add("session-restart");
1517
+ const tmpl = assemblePrompt("goal-loop-continuation.md", detailIds);
1513
1518
  // v0.25.0 (contract items 22/28): conditional directives — aggressiveMode
1514
1519
  // TODOs from the audit cap, and the full-audit fan-out directive when the
1515
1520
  // objective reads as a survey pivot. The canonical judgment policy and
@@ -26,6 +26,7 @@ import {
26
26
  supervisorPaused,
27
27
  } from "./goal-loop-core.js";
28
28
  import { BACKOFF_IDLE_RETRY_MS, MEASURE_TIMEOUT_MS } from "./goal-loop-backoff.js";
29
+ import { loadPromptWhole } from "./prompt-layers.js";
29
30
  import {
30
31
  REPETITION,
31
32
  continueVariant,
@@ -285,15 +286,7 @@ function loopPrompt(loop: LoopState, regressionNote: string, strategyNote: strin
285
286
  // v0.23.0: metricless loops get their own prompt — no metric section,
286
287
  // anti-doorknob rules instead of anti-gaming rules.
287
288
  const metricless = !loop.measureCmd;
288
- const tmplPath = path.resolve(__dirname, "..", "..", "prompts", metricless ? "goal-loop-forever-metricless.md" : "goal-loop-forever.md");
289
- let tmpl: string;
290
- try {
291
- tmpl = fs.readFileSync(tmplPath, "utf-8");
292
- } catch {
293
- tmpl = metricless
294
- ? `[LOOP ITERATION ${loop.iteration + 1}] Target: ${loop.target}. Metricless spec loop — make ONE real, inspectable change advancing the target. No cosmetic churn. ${variantNote} ${interventionNote}`
295
- : `[LOOP ITERATION ${loop.iteration + 1}] Target: ${loop.target}. Measure: ${loop.measureCmd} (${loop.direction}). Make ONE small change to improve the metric. ${interventionNote}`;
296
- }
289
+ const tmpl = loadPromptWhole(metricless ? "goal-loop-forever-metricless.md" : "goal-loop-forever.md");
297
290
  return tmpl
298
291
  .replace(/\$\{ITERATION\}/g, String(loop.iteration + 1))
299
292
  .replace(/\$\{TARGET\}/g, loop.target)
@@ -26,6 +26,7 @@ import { Type } from "typebox";
26
26
  // (positioning doc invariant #2). Property reads on the imported binding are
27
27
  // fine; wholesale replacement goes through replaceState().
28
28
  import { state, replaceState, persistStateLine } from "../goal-state.js";
29
+ import { loadPromptWhole } from "../prompt-layers.js";
29
30
 
30
31
  import {
31
32
  type Goal,
@@ -863,11 +864,9 @@ async function startDrafting(ctx: ExtensionContext, target: "goal" | "list" | "l
863
864
  : target === "loop"
864
865
  ? `${label}: a loop target needs a metric and a direction — the agent will help you design them first (nothing activates until you confirm). Skip the interview entirely: /loop start "<target>" (bare = infinite metricless) or /loop start "<target>" measure="<cmd>" direction=min|max [window=5] [max=50] [time=h] [tokens=n] [branch=1].`
865
866
  : `${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>.`);
866
- const tmplPath = path.resolve(__dirname, "..", "..", "prompts", file);
867
- let tmpl: string;
868
- try {
869
- tmpl = fs.readFileSync(tmplPath, "utf-8");
870
- if (target === "list") {
867
+ // Layered-prompt contract: a missing draft file fails loudly — never render lean.
868
+ let tmpl = loadPromptWhole(file);
869
+ if (target === "list") {
871
870
  tmpl = tmpl.replace(
872
871
  "[GOAL DRAFTING]",
873
872
  "[LIST DRAFTING — the confirmed item goes into the /list LIST, it does not activate immediately. " +
@@ -880,9 +879,6 @@ async function startDrafting(ctx: ExtensionContext, target: "goal" | "list" | "l
880
879
  "is still a SHORT task — never an aggregate wrapper ('land all N findings' with a '≥N commits' contract is the " +
881
880
  "canonical anti-pattern: the auto-committer squashes, the count fails, the auditor disapproves finished work).]",
882
881
  );
883
- }
884
- } catch {
885
- tmpl = `[DRAFTING] Clarify the user's ${target}, then call ${tool}.`;
886
882
  }
887
883
  // v0.14.0: the LLM grills (its strength — v0.13.0's canned questionnaire
888
884
  // accepted non-answers), the plugin enforces the floor: propose_goal_draft
@@ -0,0 +1,145 @@
1
+ // ============================================================================
2
+ // prompt-layers.ts — static skeleton + on-demand detail assembly for prompts/
3
+ // ============================================================================
4
+ // Every prompts/*.md file is one canonical, editor-renderable document.
5
+ // Detail sections are wrapped in zero-width structural markers:
6
+ //
7
+ // <!-- glla-layer: detail <id> -->
8
+ // ...section body, untouched...
9
+ // <!-- glla-layer: end -->
10
+ //
11
+ // Everything outside a detail block is the skeleton: always loaded.
12
+ // A detail block loads only when its phase predicate holds. Assembly order
13
+ // is file order, every turn — static layers resolve before dynamic
14
+ // ${...} substitution slots, so renders are deterministic.
15
+ //
16
+ // Loudness contract: any structural problem (missing file, unclosed block,
17
+ // duplicate id, end-without-start, unknown requested id) THROWS naming the
18
+ // file. A render never proceeds lean on a "[template-not-found]" fallback.
19
+ // Marker lines are structural only and are stripped from assembled output,
20
+ // so full assembly (every detail active) is byte-identical to the source
21
+ // file minus its marker lines.
22
+ // ============================================================================
23
+
24
+ import * as fs from "node:fs";
25
+ import * as path from "node:path";
26
+
27
+ const LAYER_START_RE = /^\s*<!--\s*glla-layer:\s*detail\s+([A-Za-z0-9_-]+)\s*-->\s*$/;
28
+ const LAYER_END_RE = /^\s*<!--\s*glla-layer:\s*end\s*-->\s*$/;
29
+
30
+ export interface PromptSegment {
31
+ kind: "skeleton" | "detail";
32
+ /** Present only for detail segments. */
33
+ id?: string;
34
+ /** Exact source lines for this segment (marker lines excluded). */
35
+ lines: string[];
36
+ }
37
+
38
+ export function promptFilePath(name: string): string {
39
+ return path.resolve(__dirname, "..", "prompts", name);
40
+ }
41
+
42
+ /** Parse one prompt source into ordered segments. Throws naming the file
43
+ * on any structural defect. Pure (no fs) so tests can feed fixtures. */
44
+ export function parsePromptLayers(source: string, name: string): PromptSegment[] {
45
+ const segments: PromptSegment[] = [];
46
+ let current: PromptSegment = { kind: "skeleton", lines: [] };
47
+ let openId: string | null = null;
48
+ const seen = new Set<string>();
49
+ const fail = (why: string): never => {
50
+ throw new Error(`[glla] prompts/${name}: ${why}`);
51
+ };
52
+ for (const line of source.split("\n")) {
53
+ const start = line.match(LAYER_START_RE);
54
+ const end = LAYER_END_RE.test(line);
55
+ if (start) {
56
+ if (openId !== null) fail(`detail "${openId}" unclosed before detail "${start[1]}" starts`);
57
+ const id = start[1] as string;
58
+ if (seen.has(id)) fail(`duplicate detail id "${id}"`);
59
+ seen.add(id);
60
+ segments.push(current);
61
+ current = { kind: "detail", id, lines: [] };
62
+ openId = id;
63
+ continue;
64
+ }
65
+ if (end) {
66
+ if (openId === null) fail("glla-layer end marker without an open detail block");
67
+ segments.push(current);
68
+ current = { kind: "skeleton", lines: [] };
69
+ openId = null;
70
+ continue;
71
+ }
72
+ current.lines.push(line);
73
+ }
74
+ if (openId !== null) fail(`detail "${openId}" unclosed at end of file`);
75
+ segments.push(current);
76
+ return segments;
77
+ }
78
+
79
+ /** Read + parse a prompt file. Throws naming the path when unreadable. */
80
+ export function loadPromptSegments(name: string): PromptSegment[] {
81
+ const file = promptFilePath(name);
82
+ let source: string;
83
+ try {
84
+ source = fs.readFileSync(file, "utf-8");
85
+ } catch (err) {
86
+ throw new Error(
87
+ `[glla] prompt file missing: prompts/${name} (${file}): ${(err as Error)?.message ?? err}. ` +
88
+ "Refusing to render lean — restore the file, do not proceed without it.",
89
+ );
90
+ }
91
+ return parsePromptLayers(source, name);
92
+ }
93
+
94
+ /** Raw whole-file read. Throws (loud) when unreadable. Use for
95
+ * skeleton-only phases and for render-diff baselines. */
96
+ export function loadPromptWhole(name: string): string {
97
+ const file = promptFilePath(name);
98
+ try {
99
+ return fs.readFileSync(file, "utf-8");
100
+ } catch (err) {
101
+ throw new Error(
102
+ `[glla] prompt file missing: prompts/${name} (${file}): ${(err as Error)?.message ?? err}. ` +
103
+ "Refusing to render lean — restore the file, do not proceed without it.",
104
+ );
105
+ }
106
+ }
107
+
108
+ /** Detail ids declared by a prompt file, in file order. */
109
+ export function promptDetailIds(name: string): string[] {
110
+ const ids: string[] = [];
111
+ for (const seg of loadPromptSegments(name)) {
112
+ if (seg.kind === "detail" && seg.id) ids.push(seg.id);
113
+ }
114
+ return ids;
115
+ }
116
+
117
+ /** Assemble skeleton + the requested details in file order. Unknown ids
118
+ * throw. Empty `active` renders the bare skeleton. */
119
+ export function assemblePrompt(name: string, active: Iterable<string>): string {
120
+ const segments = loadPromptSegments(name);
121
+ const want = new Set(active);
122
+ const known = new Set<string>();
123
+ for (const seg of segments) {
124
+ if (seg.kind === "detail" && seg.id) known.add(seg.id);
125
+ }
126
+ for (const id of want) {
127
+ if (!known.has(id)) {
128
+ throw new Error(
129
+ `[glla] prompts/${name}: unknown detail id "${id}" (known: ${[...known].join(", ") || "(none)"}). ` +
130
+ "Refusing to render lean — fix the request, do not proceed without it.",
131
+ );
132
+ }
133
+ }
134
+ const out: string[] = [];
135
+ for (const seg of segments) {
136
+ if (seg.kind === "skeleton" || (seg.id && want.has(seg.id))) out.push(...seg.lines);
137
+ }
138
+ return out.join("\n");
139
+ }
140
+
141
+ /** Full assembly: every declared detail active. Render-diff gate: this must
142
+ * equal the source file minus its marker lines. */
143
+ export function assemblePromptFull(name: string): string {
144
+ return assemblePrompt(name, promptDetailIds(name));
145
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-goal-list-loop-audit",
3
- "version": "0.38.46",
3
+ "version": "0.38.47",
4
4
  "description": "Mission control for autonomous pi: interview-drafted goals, an audited task queue, and forever-loops (metric, spec, project-audit) that run for hours. A detached extension-less auditor process re-verifies every completion with raw evidence without holding the main pi turn; confirmed drafts, decision pauses and consent gates keep you in charge.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "author": "dracon",
@@ -77,6 +77,7 @@ When the agent calls any of these, the orchestrator tracks the call and persists
77
77
  - **Bound every long command.** Wrap test suites, builds, and dev servers in `timeout <seconds>` (e.g. `timeout 120 bun test src/lib`). An unbounded command that hangs burns an hour; a bounded one burns two minutes and tells you it hung. If a command produces no output for many minutes, treat it as hung: kill it, diagnose why, rerun bounded.
78
78
  - **Chunk output near context-full & microcompaction.** When the conversation is heavy (long-running audit, deep debug, big rollout), prefer smaller commits, smaller tool outputs, and focused reasoning — one or two punchy paragraphs, one well-scoped tool call at a time. Don't try to fit a thousand lines of work into one reply. Spool massive stdout/diffs to disk logs if needed. glla's auto-continue fires on `stop_reason="length"` and will reschedule you; chunking is cheaper than recovering from the cap. Save large file writes for their own turns.
79
79
 
80
+ <!-- glla-layer: detail auditor-disapproval -->
80
81
  ## WHEN THE AUDITOR DISAPPROVES
81
82
 
82
83
  If the orchestrator tells you the auditor disapproved, **investigate before asking the user**:
@@ -90,7 +91,9 @@ If the orchestrator tells you the auditor disapproved, **investigate before aski
90
91
  "The auditor's last 3 reports all complain about saves-3 not shipping. The current objective IS saves-3, but the work shipped is menu-3 + kingdom-2 (different items). I shipped those because [reason]. The auditor is disapproving because the original objective isn't literally shipped. Three options: A. /goal tweak the objective to menu-3+kingdom-2, then /goal resume — B. Re-scope saves-3 and ship it — C. Pivot to a different item entirely."
91
92
 
92
93
  Do NOT ask the user to choose between generic options like "/goal resume / Move on silently / Different item". Those options tell the user nothing. Always include YOUR ASSESSMENT with quoted objections and shipped evidence.
94
+ <!-- glla-layer: end -->
93
95
 
96
+ <!-- glla-layer: detail survey-pivot -->
94
97
  ## PIVOT DETECTION
95
98
 
96
99
  When the user says "do a full audit", "survey the project", "find all problems", "mark a tasklist", or similar — the goal is a SURVEY, not a single fix. You must:
@@ -100,6 +103,7 @@ When the user says "do a full audit", "survey the project", "find all problems",
100
103
  3. Use subagents to PARALLEL-survey different subsystems (game logic, UI, audio, tests, docs) — one `scout` agent per subsystem, spawned in a single message.
101
104
  4. Don't ship a single bug fix and then ask if the user wants to continue — the user already said "do a full audit".
102
105
  5. After the task list is confirmed, work through tasks systematically with `complete_task` / `update_task_status`.
106
+ <!-- glla-layer: end -->
103
107
 
104
108
  ## WHEN SUBAGENTS DIE: RESUME, DON'T RESPAWN
105
109
 
@@ -108,9 +112,11 @@ A failed subagent's SESSION survives its death (verified in pi-subagents source:
108
112
  1. **Resume it immediately**: `Agent(resume: "<id>", prompt: "Wrap up now: report what you found within ~150 lines.")`. A resumed agent already HAS its research; it only needs to report. Do not infer a reset time from the error text.
109
113
  2. **Resume failed with "not found"?** The manager forgot it (a /reload wipes the in-process registry — old IDs are unresumable). Only NOW respawn, and never the same wide brief: SPLIT into 2 narrower agents or ABSORB the remainder inline.
110
114
 
115
+ <!-- glla-layer: detail session-restart -->
111
116
  ## AFTER A SESSION RESTART: YOUR SUBAGENTS ARE DEAD
112
117
 
113
118
  Subagents run IN-PROCESS — a /reload or restart kills every running one instantly and silently (no failure event, no ✗), and their IDs become unresumable. On a restored session, check your transcript for in-flight agents and relaunch them (tight briefs) or absorb their scope — do NOT sit waiting for results that can never arrive. This is why long fan-out passes belong under a glla goal/list item: the goal plane survives restarts and re-drives the fan-out through the continuation; an ad-hoc fan-out dies with the tab.
119
+ <!-- glla-layer: end -->
114
120
 
115
121
  ## WHEN SUBAGENTS HIT PROVIDER/RUNTIME ERRORS
116
122