pi-goal-list-loop-audit 0.38.51 → 0.38.53

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,68 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.38.53 — Consent-safe GLLA delegation skill and list-draft staging (2026-09-13)
4
+
5
+ Added the packaged `glla-delegate` skill for normal-chat goal/list delegation.
6
+ It documents the consent ladder, main-session ownership, auto-activation warning,
7
+ and tool-first usage without adding new runtime powers. Its quoted frontmatter
8
+ is validated through Pi's public loader and the packed-file release gate.
9
+
10
+ ### Added
11
+
12
+ - **Consent-safe delegation guidance:** explicit queue requests may use
13
+ `list_add`; discovered follow-ups are offered first; durable goals use
14
+ `propose_goal_draft`; speculative queueing, unowned activation, and agent
15
+ `/loop` control remain prohibited.
16
+ - **Pasted-list policy:** supplied multi-line, bullet, numbered, and checklist
17
+ structure is preserved without an exact-versus-refined meta-question. Only a
18
+ genuinely ambiguous individual item remains eligible for clarification, with
19
+ one-record seeds retaining the normal draft path.
20
+ - **Release/package coverage:** `skills/glla-delegate/SKILL.md` is allowlisted,
21
+ declared in `pi.skills`, required by the packed-artifact smoke, and loaded
22
+ with zero diagnostics in the release contract.
23
+
24
+ ### Fixed
25
+
26
+ - **Draft staging:** interview and stale-entry gates remain ahead of Confirm;
27
+ confirmation remains ahead of conflict handling and enqueue.
28
+ - **Paused list carryover:** a confirmed multi-item draft now routes its first
29
+ promotion through the carryover-aware activation choke point, archiving the
30
+ paused predecessor before starting the list head instead of leaving the batch
31
+ stranded behind it.
32
+
33
+ ## 0.38.52 — Second Gemini-gap: gate-inventory table + per-finding Test Results (2026-09-12)
34
+
35
+ Surveyed five more Antigravity report screenshots
36
+ (audit/SECOND-GEMINI-SUMMARY-GAP-2026-09-12.md) and closed the second gap
37
+ in the terminal render only (chat + transcript + archive human layer;
38
+ progress cards, status, widget, external notifies unchanged).
39
+
40
+ ### Added
41
+
42
+ - **Agent-supplied gate inventory:** optional `complete_goal gateRows`
43
+ (one row per quality gate with scope and outcome notes, sanitized at
44
+ the claim boundary, max 10). The Verification Summary widens to
45
+ Quality Gate | Scope | Status | Notes and supersedes the mechanical
46
+ Tests rows. Statuses are mechanically derived, never claimed: PASS
47
+ only when the notes say pass with zero failures, FAIL on nonzero
48
+ failures, REPORTED otherwise — so bare-exit notes like `Clean exit 0`
49
+ honestly stay REPORTED. Absent inventory keeps the 3-col mechanical
50
+ table byte-identical.
51
+ - **Per-finding test proof:** optional parallel `tests` array on each
52
+ `findingGroups` entry (`tests[i]` proves `findings[i]`, clipped to
53
+ findings, sanitized at the claim boundary). Nested area sections render
54
+ each as a `Test Results:` sub-bullet; the 4+-group table rides the
55
+ proof in the Evidence cell (`Evidence · Tests:`). Groups without tests
56
+ render exactly as v0.38.50.
57
+
58
+ ### Preserved
59
+
60
+ - Chat close stays archive-only: no commit hash, emoji markers, or
61
+ artifact links in chat (survey rejects). The verbatim six-label
62
+ machine record, auditor-verified-before-posting, and the honesty
63
+ rules (PASS only on pass, NO VERDICT never renders, pipe escaping)
64
+ are unchanged.
65
+
3
66
  ## 0.38.51 — Bare /list drafts from context like bare /goal (2026-09-12)
4
67
 
5
68
  User note: empty `/list` could not start anything while empty `/goal`
package/README.md CHANGED
@@ -157,7 +157,8 @@ quietly inventing an unbounded backlog.
157
157
  ```text
158
158
  /list "fix the cache. Done when: tests pass"
159
159
  /list plan.md # import a checklist or plan file
160
- /list # show active and waiting items
160
+ /list # interview + Confirm for a context draft
161
+ /list show # show active and waiting items
161
162
  /list start # activate the queued head, or draft one clear recent request
162
163
  /list next # explicitly skip/activate the next item
163
164
  /list next <n> # explicitly choose a specific item
@@ -167,6 +168,13 @@ quietly inventing an unbounded backlog.
167
168
  /list cancel # stop the active item and drop waiting items
168
169
  ```
169
170
 
171
+ A pasted multi-line, bulleted, numbered, or checklist-style list keeps its
172
+ item wording and boundaries; GLLA does not ask for an “exact or refined” choice.
173
+ Only a genuinely ambiguous individual item needs clarification. The packaged
174
+ `skills/glla-delegate/SKILL.md` records the safe normal-chat delegation path:
175
+ explicit queue requests may use `list_add`, while discovered follow-ups are
176
+ offered first and durable goals remain Confirm-gated.
177
+
170
178
  Order is the default, not the law. Automatic advance normally uses the head of
171
179
  the queue, while `/list next <n>` or the agent's `list_activate` tool can choose
172
180
  another item. Numbering always matches `/list` output. After a list item is
@@ -114,7 +114,10 @@ The full six-label recap lives verbatim in the archive (`## Completion summary`)
114
114
  and the status/history surfaces. The human layer in chat, transcript, and the
115
115
  archive's `## Terminal summary` renders the same facts as rich sections
116
116
  (`## Done: <objective> — <outcome>`, duration line, Key Findings grouped by area
117
- or tabulated at 4+ groups, Verification Summary table, Next, capped and clipped
117
+ or tabulated at 4+ groups with per-finding `Test Results:` proof lines,
118
+ Verification Summary table — widened to Quality Gate | Scope | Status | Notes
119
+ with derived statuses when the agent supplies a gate inventory — Next, capped
120
+ and clipped
118
121
  to fixed budgets). Width-bound and external surfaces (status line, widget card,
119
122
  external notifies) use the compact single-line projection of all six labels.
120
123
  Every terminal goal notification, including version-bearing already-shipped claims,
package/docs/INDEX.md CHANGED
@@ -16,7 +16,7 @@ Policy contracts and recent changes live in the `audit/` directory of the
16
16
  failback; v0.35.9 hardened cross-version npm tarball checks; v0.35.10
17
17
  handles multi-entry npm dry-run reports; v0.35.11 accepts both npm report
18
18
  shapes; v0.35.12 supports npm 12's keyed pack reports; v0.35.13 fixes stale-API recovery loops.
19
- v0.35.14–v0.38.51 continue through the supervisor freeze (`/glla pause`),
19
+ v0.35.14–v0.38.53 continue through the supervisor freeze (`/glla pause`),
20
20
  load hold, auditor picker parity, Windows launch fix, zombie-watchdog
21
21
  subagent carve-out, due-wait backstop, the `/glla agents` visibility panel,
22
22
  durable state-root selection, blank-until-resume auditor context, frozen
@@ -25,7 +25,8 @@ Policy contracts and recent changes live in the `audit/` directory of the
25
25
  extensions, bounded zero-stream retry containment, crash-safe persistence,
26
26
  packed-artifact release verification, and the 2026-09-07 display/lifecycle/
27
27
  settings audit pass (abort-latch send guards, ownership compare-and-swap,
28
- auditor inherit/clear parity) and the v0.38.25 post-objective summary
28
+ auditor inherit/clear parity), the v0.38.53 consent-safe `glla-delegate`
29
+ skill/list-drafting release, and the v0.38.25 post-objective summary
29
30
  (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
31
  - `../README.md`: what the plugin is, install, quickstart, and the
31
32
  architectural guarantee (drafting + confirm + detached auditor).
@@ -37,6 +38,7 @@ Policy contracts and recent changes live in the `audit/` directory of the
37
38
  - `../README.md`: what the plugin is, install, quickstart
38
39
  - `SETTINGS.md`: canonical settings reference (files, precedence, every key)
39
40
  - `../INSTALL.md`: source install / local development setup
41
+ - `../skills/glla-delegate/SKILL.md`: normal-chat goal/list delegation and consent ladder
40
42
  - `../CHANGELOG.md`: user-facing changelog; the first versioned heading is
41
43
  the current released package version (use `/glla version` to compare with
42
44
  the registry); post-release work may appear in `Unreleased` above it.
@@ -1,4 +1,4 @@
1
- import type { FindingGroup, Goal, Status } from "./goal-loop-core.js";
1
+ import type { FindingGroup, GateRow, Goal, Status } from "./goal-loop-core.js";
2
2
  import { fmtElapsed, truncateCells } from "./goal-loop-display.js";
3
3
 
4
4
  /**
@@ -347,7 +347,8 @@ export function extractEvidenceTokens(text: string): { text: string; evidence: s
347
347
  /**
348
348
  * v0.38.50: enforce the total-findings budget across groups in order —
349
349
  * groups that go empty are dropped so the table/nesting never shows a
350
- * bare area header.
350
+ * bare area header. v0.38.52: parallel `tests` ride along clipped to the
351
+ * surviving findings so tests[i] still proves findings[i].
351
352
  */
352
353
  export function takeBudgetedGroups(groups: FindingGroup[]): FindingGroup[] {
353
354
  const out: FindingGroup[] = [];
@@ -356,7 +357,9 @@ export function takeBudgetedGroups(groups: FindingGroup[]): FindingGroup[] {
356
357
  if (remaining <= 0) break;
357
358
  const findings = group.findings.slice(0, remaining);
358
359
  if (findings.length === 0) continue;
359
- out.push({ title: group.title, findings });
360
+ // v0.38.52: keep the parallel test lines aligned with the survivors.
361
+ const aligned = group.tests?.slice(0, findings.length);
362
+ out.push(aligned && aligned.length > 0 ? { title: group.title, findings, tests: aligned } : { title: group.title, findings });
360
363
  remaining -= findings.length;
361
364
  }
362
365
  return out;
@@ -407,7 +410,12 @@ export function requestEchoHeadline(kind: "Done" | "Aborted", objective: string
407
410
  * v0.38.50: agent-structured groups render as `#### n. Area` subsections
408
411
  * with nested evidence bullets; at RICH_TABLE_GROUP_THRESHOLD groups the
409
412
  * same facts render as an Area | Finding | Evidence table instead.
410
- * Without groups the flat six-label projection stays the fallback. */
413
+ * Without groups the flat six-label projection stays the fallback.
414
+ * v0.38.52: optional per-finding `tests` render as a `Test Results:`
415
+ * sub-bullet (nested) or ride the Evidence cell (table); optional agent
416
+ * `gates` widen the Verification table to Gate | Scope | Status | Notes
417
+ * with mechanically derived statuses, superseding the mechanical Tests
418
+ * rows. Without gates the 3-col mechanical table is byte-identical. */
411
419
  export function buildRichTerminalParts(args: {
412
420
  outcome: string;
413
421
  details: string[];
@@ -416,6 +424,7 @@ export function buildRichTerminalParts(args: {
416
424
  objective?: string;
417
425
  durationLine?: string | null;
418
426
  groups?: FindingGroup[];
427
+ gates?: GateRow[];
419
428
  }): RichTerminalParts {
420
429
  const { findings, tests, next } = partitionRichDetails(args.details);
421
430
  const headline = requestEchoHeadline("Done", args.objective, args.outcome);
@@ -425,21 +434,28 @@ export function buildRichTerminalParts(args: {
425
434
  if (useTable) {
426
435
  findingLines.push("| Area | Finding | Evidence |", "| --- | --- | --- |");
427
436
  for (const group of budgeted) {
428
- for (const finding of group.findings) {
437
+ group.findings.forEach((finding, fi) => {
429
438
  const { text, evidence } = extractEvidenceTokens(finding);
430
439
  const { lead, body } = leadBody(text);
440
+ // v0.38.52: test proof rides the Evidence cell (tables have no
441
+ // sub-bullets); cells stay pipe-escaped and budgeted.
442
+ const proof = group.tests?.[fi]?.trim();
443
+ const evidenceCell = [evidence.join(", ") || "\u2014", ...(proof ? [`Tests: ${proof}`] : [])].join(" \u00b7 ");
431
444
  findingLines.push(
432
- `| ${escapeTableCell(clipSummaryValue(group.title, RICH_OBJECTIVE_ECHO_CHARS))} | ${escapeTableCell(`**${lead}** \u2014 ${clipSummaryValue(body, RICH_VALUE_BUDGET)}`)} | ${escapeTableCell(evidence.join(", ") || "\u2014")} |`,
445
+ `| ${escapeTableCell(clipSummaryValue(group.title, RICH_OBJECTIVE_ECHO_CHARS))} | ${escapeTableCell(`**${lead}** \u2014 ${clipSummaryValue(body, RICH_VALUE_BUDGET)}`)} | ${escapeTableCell(clipSummaryValue(evidenceCell, RICH_VALUE_BUDGET))} |`,
433
446
  );
434
- }
447
+ });
435
448
  }
436
449
  } else if (budgeted.length > 0) {
437
450
  budgeted.forEach((group, i) => {
438
451
  findingLines.push(`#### ${i + 1}. ${clipSummaryValue(group.title, RICH_OBJECTIVE_ECHO_CHARS)}`);
439
- for (const finding of group.findings) {
452
+ group.findings.forEach((finding, fi) => {
440
453
  const { lead, body } = leadBody(finding);
441
454
  findingLines.push(`- **${lead}** \u2014 ${clipSummaryValue(body, RICH_VALUE_BUDGET)}`);
442
- }
455
+ // v0.38.52: per-finding test proof (shot C) — absent stays absent.
456
+ const proof = group.tests?.[fi]?.trim();
457
+ if (proof) findingLines.push(` - Test Results: ${clipSummaryValue(proof, RICH_VALUE_BUDGET)}`);
458
+ });
443
459
  });
444
460
  } else {
445
461
  findings.slice(0, RICH_FINDINGS_CAP).forEach((detail, i) => {
@@ -448,17 +464,33 @@ export function buildRichTerminalParts(args: {
448
464
  });
449
465
  }
450
466
  const tableRows: string[] = [];
451
- for (const detail of tests.slice(0, 2)) {
452
- const { body } = leadBody(detail);
453
- tableRows.push(`| Tests | ${testsRowStatus(body)} | ${escapeTableCell(clipSummaryValue(body, RICH_VALUE_BUDGET))} |`);
467
+ const gates = (args.gates ?? []).slice(0, 10);
468
+ if (gates.length > 0) {
469
+ // v0.38.52 (shots B/D): the agent inventory supersedes the mechanical
470
+ // Tests rows — one curated gate list, never a duplicated one. Status
471
+ // is derived, never claimed: PASS only when the notes say pass with
472
+ // zero failures ("Clean exit 0"-style notes honestly stay REPORTED).
473
+ for (const row of gates) {
474
+ const derived = testsRowStatus(row.notes ?? "");
475
+ tableRows.push(`| ${escapeTableCell(clipSummaryValue(row.gate, RICH_OBJECTIVE_ECHO_CHARS))} | ${escapeTableCell(clipSummaryValue(row.scope?.trim() || "\u2014", RICH_VALUE_BUDGET))} | ${derived} | ${escapeTableCell(clipSummaryValue(row.notes?.trim() || "\u2014", RICH_VALUE_BUDGET))} |`);
476
+ }
477
+ } else {
478
+ for (const detail of tests.slice(0, 2)) {
479
+ const { body } = leadBody(detail);
480
+ tableRows.push(`| Tests | ${testsRowStatus(body)} | ${escapeTableCell(clipSummaryValue(body, RICH_VALUE_BUDGET))} |`);
481
+ }
454
482
  }
455
483
  const auditStatus = auditRowStatus(args.auditHistory);
456
484
  if (auditStatus !== "NO VERDICT") {
457
485
  const auditBody = args.countsLine.replace(/^\u2014\s*/, "").replace(/\.\s*$/, "");
458
- tableRows.push(`| Audit | ${auditStatus} | ${escapeTableCell(clipSummaryValue(auditBody, RICH_VALUE_BUDGET))} |`);
486
+ if (gates.length > 0) {
487
+ tableRows.push(`| Audit | ${escapeTableCell(clipSummaryValue(auditBody, RICH_VALUE_BUDGET))} | ${auditStatus} | ${escapeTableCell(clipSummaryValue(auditBody, RICH_VALUE_BUDGET))} |`);
488
+ } else {
489
+ tableRows.push(`| Audit | ${auditStatus} | ${escapeTableCell(clipSummaryValue(auditBody, RICH_VALUE_BUDGET))} |`);
490
+ }
459
491
  }
460
492
  const tableLines = tableRows.length > 0
461
- ? ["| Check | Status | Details |", "| --- | --- | --- |", ...tableRows]
493
+ ? [(gates.length > 0 ? "| Quality Gate | Scope | Status | Notes |" : "| Check | Status | Details |"), (gates.length > 0 ? "| --- | --- | --- | --- |" : "| --- | --- | --- |"), ...tableRows]
462
494
  : [];
463
495
  const nextLines = next.slice(0, RICH_NEXT_CAP).map((detail) => {
464
496
  const { lead, body } = leadBody(detail);
@@ -495,7 +527,7 @@ export function composeRichTerminalLines(parts: RichTerminalParts, opts?: { head
495
527
  * raw six-label recap stays verbatim in `## Completion summary` as the
496
528
  * machine layer. Headline follows terminal status; aborted records never
497
529
  * wear a `Done` headline. */
498
- export function buildRichArchiveSection(goal: Goal, status: Status, archivePath: string, findingGroups?: FindingGroup[]): string[] {
530
+ export function buildRichArchiveSection(goal: Goal, status: Status, archivePath: string, findingGroups?: FindingGroup[], gateRows?: GateRow[]): string[] {
499
531
  const facts: CompletionSummaryFacts = { goal, status, archivePath };
500
532
  const summary = resolveCompletionSummary(facts, goal.completionSummary).summary;
501
533
  const brief = humanCompletionBrief(summary, 140, RICH_VALUE_BUDGET);
@@ -513,6 +545,7 @@ export function buildRichArchiveSection(goal: Goal, status: Status, archivePath:
513
545
  objective: goal.objective,
514
546
  durationLine: buildDurationLine(goal),
515
547
  groups: findingGroups,
548
+ gates: gateRows,
516
549
  });
517
550
  return [
518
551
  ...composeRichTerminalLines(parts, {
@@ -611,6 +644,12 @@ export interface TerminalApprovalRenderInput {
611
644
  * sanitized at claim time). Absent keeps the flat six-label fallback.
612
645
  */
613
646
  findingGroups?: FindingGroup[];
647
+ /**
648
+ * v0.38.52: agent-supplied verification gate rows (complete_goal
649
+ * gateRows, sanitized at claim time). Absent keeps the mechanical
650
+ * 3-col verification table.
651
+ */
652
+ gateRows?: GateRow[];
614
653
  }
615
654
 
616
655
  export interface TerminalApprovalRender {
@@ -689,6 +728,7 @@ export function buildTerminalApprovalRender(input: TerminalApprovalRenderInput):
689
728
  objective: input.goal.objective,
690
729
  durationLine: buildDurationLine(input.goal),
691
730
  groups: input.findingGroups,
731
+ gates: input.gateRows,
692
732
  });
693
733
  const chatBody = composeRichTerminalLines(richParts);
694
734
  const transcriptBody = composeRichTerminalLines(richParts);
@@ -282,12 +282,21 @@ export type AuditorRecoveryFailureClass = "transport" | "timeout" | "no-verdict"
282
282
  export interface FindingGroup {
283
283
  title: string;
284
284
  findings: string[];
285
+ /**
286
+ * v0.38.52 (second Gemini-gap survey, shot C): optional per-finding
287
+ * test-result lines, aligned by index with `findings` (tests[i] proves
288
+ * findings[i]). Absent/short entries simply omit the sub-line — old
289
+ * claims without `tests` render byte-identical to v0.38.50.
290
+ */
291
+ tests?: string[];
285
292
  }
286
293
 
287
294
  export const MAX_FINDING_GROUPS = 6;
288
295
  export const MAX_GROUP_FINDINGS = 6;
289
296
  export const MAX_GROUP_TITLE_CHARS = 120;
290
297
  export const MAX_GROUP_FINDING_CHARS = 500;
298
+ /** v0.38.52: per-finding test-result lines share the finding budget. */
299
+ export const MAX_GROUP_TESTS_CHARS = 500;
291
300
 
292
301
  /**
293
302
  * Bound agent-supplied finding groups at the trust boundary. Drops
@@ -313,11 +322,64 @@ export function sanitizeFindingGroups(value: unknown): FindingGroup[] | undefine
313
322
  if (text) findings.push(text);
314
323
  }
315
324
  if (findings.length === 0) continue;
316
- groups.push({ title, findings });
325
+ // v0.38.52: optional test-result lines ride parallel to findings.
326
+ // Non-string entries drop; the array is clipped to the finding count
327
+ // so tests[i] always proves findings[i] after budgeting.
328
+ let tests: string[] | undefined;
329
+ if (Array.isArray(raw.tests)) {
330
+ const parsed: string[] = [];
331
+ for (const entry of raw.tests) {
332
+ if (typeof entry !== "string") continue;
333
+ const text = entry.trim().slice(0, MAX_GROUP_TESTS_CHARS);
334
+ if (text) parsed.push(text);
335
+ }
336
+ if (parsed.length > 0) tests = parsed.slice(0, findings.length);
337
+ }
338
+ groups.push(tests ? { title, findings, tests } : { title, findings });
317
339
  }
318
340
  return groups.length > 0 ? groups : undefined;
319
341
  }
320
342
 
343
+ /**
344
+ * v0.38.52 (second Gemini-gap survey, shots B/D): one agent-supplied
345
+ * verification gate row for the widened terminal table. Presentation
346
+ * only — the six-label completionSummary stays the audited substance.
347
+ * Status is NEVER agent-controlled: the renderer derives PASS/FAIL/
348
+ * REPORTED mechanically from `notes` via testsRowStatus, so a gate can
349
+ * only claim PASS when its notes say so with zero failures.
350
+ */
351
+ export interface GateRow {
352
+ gate: string;
353
+ scope?: string;
354
+ notes?: string;
355
+ }
356
+
357
+ export const MAX_GATE_ROWS = 10;
358
+ export const MAX_GATE_GATE_CHARS = 120;
359
+ export const MAX_GATE_SCOPE_CHARS = 200;
360
+ export const MAX_GATE_NOTES_CHARS = 400;
361
+
362
+ /**
363
+ * Bound agent-supplied gate rows at the trust boundary. Drops blank
364
+ * gates; clips cells. Returns undefined when nothing valid survives —
365
+ * the mechanical 3-col table stays the fallback.
366
+ */
367
+ export function sanitizeGateRows(value: unknown): GateRow[] | undefined {
368
+ if (!Array.isArray(value)) return undefined;
369
+ const rows: GateRow[] = [];
370
+ for (const entry of value) {
371
+ if (rows.length >= MAX_GATE_ROWS) break;
372
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
373
+ const raw = entry as Record<string, unknown>;
374
+ const gate = typeof raw.gate === "string" ? raw.gate.trim().slice(0, MAX_GATE_GATE_CHARS) : "";
375
+ if (!gate) continue;
376
+ const scope = typeof raw.scope === "string" ? raw.scope.trim().slice(0, MAX_GATE_SCOPE_CHARS) : "";
377
+ const notes = typeof raw.notes === "string" ? raw.notes.trim().slice(0, MAX_GATE_NOTES_CHARS) : "";
378
+ rows.push({ gate, ...(scope ? { scope } : {}), ...(notes ? { notes } : {}) });
379
+ }
380
+ return rows.length > 0 ? rows : undefined;
381
+ }
382
+
321
383
  export interface PendingCompletion {
322
384
  completionSummary?: string;
323
385
  verificationSummary?: string;
@@ -352,6 +414,12 @@ export interface PendingCompletion {
352
414
  * Absent means the flat six-label render stays the fallback.
353
415
  */
354
416
  findingGroups?: FindingGroup[];
417
+ /**
418
+ * v0.38.52: agent-supplied verification gate rows for the widened
419
+ * terminal table (complete_goal gateRows, sanitized at claim time).
420
+ * Absent means the mechanical 3-col table stays the fallback.
421
+ */
422
+ gateRows?: GateRow[];
355
423
  /**
356
424
  * Durable one-shot recovery fence. A parked claim may receive one
357
425
  * automatic retry after a validated healthy lifecycle/recovery event;
@@ -2213,6 +2281,10 @@ function normalizePendingCompletion(value: unknown): PendingCompletion {
2213
2281
  // garbage at the boundary degrades to absent (flat fallback), never
2214
2282
  // to a half-structured projection.
2215
2283
  const sanitizedGroups = sanitizeFindingGroups(raw.findingGroups);
2284
+ // v0.38.52: agent-supplied gate rows survive reloads sanitized; garbage
2285
+ // at the boundary degrades to absent (mechanical fallback), never to a
2286
+ // half-structured projection.
2287
+ const sanitizedGates = sanitizeGateRows(raw.gateRows);
2216
2288
  return {
2217
2289
  ...canonicalOrUnknown,
2218
2290
  ...(auditorCandidateRefs !== undefined ? { auditorCandidateRefs } : {}),
@@ -2226,6 +2298,7 @@ function normalizePendingCompletion(value: unknown): PendingCompletion {
2226
2298
  ...(auditorFailureAt ? { auditorFailureAt } : {}),
2227
2299
  ...(timeoutEscalation !== undefined ? { timeoutEscalation } : {}),
2228
2300
  ...(sanitizedGroups ? { findingGroups: sanitizedGroups } : {}),
2301
+ ...(sanitizedGates ? { gateRows: sanitizedGates } : {}),
2229
2302
  ...(phase === "running" || phase === "recovery-pending" || phase === "retry-waiting" ? { phase } : {}),
2230
2303
  ...(typeof raw.retryAttempts === "number"
2231
2304
  ? { retryAttempts: raw.retryAttempts }
@@ -1442,13 +1442,15 @@ async function retryStoredCompletionAudit(origin: CompletionAuditOrigin = "provi
1442
1442
  // v0.38.37: the deliberate non-do the agent claimed, if any.
1443
1443
  ...(claim.leftOut ? { leftOut: claim.leftOut } : {}),
1444
1444
  // v0.38.50: agent-structured finding groups ride the audited claim.
1445
+ // v0.38.52: same for the gate inventory.
1445
1446
  ...(claim.findingGroups ? { findingGroups: claim.findingGroups } : {}),
1447
+ ...(claim.gateRows ? { gateRows: claim.gateRows } : {}),
1446
1448
  extras: inspectionSessionPath
1447
1449
  ? [`Auditor session kept for review: pi --session ${inspectionSessionPath} (or pi --fork ${inspectionSessionPath}).`]
1448
1450
  : [],
1449
1451
  });
1450
1452
  const approvalObjective = state.goal.objective;
1451
- const archived = archiveCurrentGoal(liveCtx, "complete", `auditor ${result.model} approved (${origin})`, {}, { findingGroups: claim.findingGroups });
1453
+ const archived = archiveCurrentGoal(liveCtx, "complete", `auditor ${result.model} approved (${origin})`, {}, { findingGroups: claim.findingGroups, gateRows: claim.gateRows });
1452
1454
  if (!archived) {
1453
1455
  // archiveCurrentGoal already preserved the live record and warned the
1454
1456
  // user. Keep the approved claim recoverable, but never emit a terminal
@@ -860,7 +860,7 @@ async function startDrafting(ctx: ExtensionContext, target: "goal" | "list" | "l
860
860
  // concatenation produced "…regular draft.Goal drafting — deep planning: …".
861
861
  const seededHint = (planNote ? `${planNote} ` : "") + (
862
862
  target === "list"
863
- ? `${label}: free-text is valid without a "Done when:" clause — the agent will turn it into short list items and grill for concrete per-item contracts (nothing activates until you confirm). To skip drafting, include a per-item "Done when:" clause.`
863
+ ? `${label}: free-text is valid without a "Done when:" clause — the agent will turn it into short list items and grill for concrete per-item contracts (nothing activates until you confirm). If the seed is already a pasted multi-line, bulleted, numbered, or checklist-style list, use its wording and structure as supplied; do not ask whether it should be exact or refined. To skip drafting, include a per-item "Done when:" clause.`
864
864
  : target === "loop"
865
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].`
866
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>.`);
@@ -877,7 +877,10 @@ async function startDrafting(ctx: ExtensionContext, target: "goal" | "list" | "l
877
877
  "for the big version. When the user has many items to enqueue at once ('queue these 50 audits'), propose them ALL AT " +
878
878
  "ONCE with the items[] parameter — one Confirm for the whole batch, never 50 separate proposals. Each items[] entry " +
879
879
  "is still a SHORT task — never an aggregate wrapper ('land all N findings' with a '≥N commits' contract is the " +
880
- "canonical anti-pattern: the auto-committer squashes, the count fails, the auditor disapproves finished work).]",
880
+ "canonical anti-pattern: the auto-committer squashes, the count fails, the auditor disapproves finished work). " +
881
+ "For a pasted multi-line, bulleted, numbered, or checklist-style seed, preserve the supplied wording and structure, " +
882
+ "strip only import syntax and empty headings/lines, and propose items[] directly — do not ask whether it should be exact " +
883
+ "or refined; clarify only a genuinely ambiguous individual item.]",
881
884
  );
882
885
  }
883
886
  // v0.14.0: the LLM grills (its strength — v0.13.0's canned questionnaire
@@ -30,6 +30,7 @@ import { state, replaceState, persistStateLine } from "../goal-state.js";
30
30
 
31
31
  import {
32
32
  type FindingGroup,
33
+ type GateRow,
33
34
  type Goal,
34
35
  type Policy,
35
36
  type State,
@@ -1102,7 +1103,8 @@ function archiveCurrentGoal(
1102
1103
  patch: Partial<Pick<Goal, "auditHistory" | "completionSummary" | "pendingTasks">> = {},
1103
1104
  // v0.38.50: agent-structured finding groups ride the audited claim into
1104
1105
  // the archive rich section only — abort/wip paths omit it (flat fallback).
1105
- opts?: { findingGroups?: FindingGroup[] },
1106
+ // v0.38.52: same for the gate inventory.
1107
+ opts?: { findingGroups?: FindingGroup[]; gateRows?: GateRow[] },
1106
1108
  ): boolean {
1107
1109
  if (!state.goal) return false;
1108
1110
  if (status !== "complete" && status !== "aborted") return false;
@@ -1151,7 +1153,7 @@ function archiveCurrentGoal(
1151
1153
  // it carries the same rich markdown as chat over the verbatim
1152
1154
  // six-label machine record. Built from the terminal goal AFTER the
1153
1155
  // summary fence resolves, so chat and archive cannot disagree.
1154
- const richSection = buildRichArchiveSection(terminalGoal, status, archivePath, opts?.findingGroups);
1156
+ const richSection = buildRichArchiveSection(terminalGoal, status, archivePath, opts?.findingGroups, opts?.gateRows);
1155
1157
  const richMd = `${md}\n## Terminal summary\n\n${richSection.join("\n")}\n`;
1156
1158
  // An existing same-id archive is an immutable fence. Check it before
1157
1159
  // publishing an intent so an unrelated/sentinel winner can never make the
@@ -134,6 +134,7 @@ isGoalRevisionCurrent,
134
134
  buildDurableChoiceRecord,
135
135
  normalizeDurableDeferRecommendationInput,
136
136
  sanitizeFindingGroups,
137
+ sanitizeGateRows,
137
138
  } from "../goal-loop-core.js";
138
139
  import {
139
140
  applyValidatedBatch,
@@ -540,6 +541,7 @@ function registerAgentTools(pi: any): void {
540
541
  findingGroups: Type.Optional(Type.Array(Type.Object({
541
542
  title: Type.String({ maxLength: 120, description: "Work-area name (e.g. a subsystem, screen, or phase)" }),
542
543
  findings: Type.Array(Type.String({ maxLength: 500 }), { maxItems: 6, description: "Findings in this area as `Lead: body with path:line evidence` (max 6 per area)" }),
544
+ tests: Type.Optional(Type.Array(Type.String({ maxLength: 500 }), { maxItems: 6, description: "v0.38.52: optional per-finding test-result lines, aligned by index with findings (tests[i] proves findings[i]); each renders as a `Test Results:` sub-line" })),
543
545
  }), {
544
546
  maxItems: 6,
545
547
  description:
@@ -549,6 +551,18 @@ function registerAgentTools(pi: any): void {
549
551
  "Presentation only — the six-label completionSummary stays the audited substance, and file:line tokens are never invented. " +
550
552
  "Omit for single-area work — the flat six-label render stays the fallback.",
551
553
  })),
554
+ gateRows: Type.Optional(Type.Array(Type.Object({
555
+ gate: Type.String({ maxLength: 120, description: "Gate name (e.g. Unit Tests, Typecheck, E2E, Production Build)" }),
556
+ scope: Type.Optional(Type.String({ maxLength: 200, description: "What the gate covered (e.g. suites, tiers, invariants)" })),
557
+ notes: Type.Optional(Type.String({ maxLength: 400, description: "Outcome notes with counts (e.g. 743 passed, 0 failed). Status is DERIVED from these notes — PASS only when they say pass with zero failures, never claimed." })),
558
+ }), {
559
+ maxItems: 10,
560
+ description:
561
+ "v0.38.52: optional verification gate inventory for the terminal summary. " +
562
+ "Widens the Verification table to Quality Gate | Scope | Status | Notes and supersedes the mechanical Tests rows. " +
563
+ "Presentation only — the six-label completionSummary stays the audited substance, and counts are never invented. " +
564
+ "Omit when there is no gate inventory — the mechanical 3-col table stays the fallback.",
565
+ })),
552
566
  }),
553
567
  async execute(_id, params, signal, _onUpdate, execCtx) {
554
568
  const foreign0 = foreignToolGuard(execCtx);
@@ -572,7 +586,7 @@ function registerAgentTools(pi: any): void {
572
586
  }
573
587
  return { content: [{ type: "text", text: `No active goal — it is ${state.goal.status}.` }], details: {} };
574
588
  }
575
- const p = params as { completionSummary?: string; verificationSummary?: string; newObjective?: string; leftOut?: string; findingGroups?: unknown };
589
+ const p = params as { completionSummary?: string; verificationSummary?: string; newObjective?: string; leftOut?: string; findingGroups?: unknown; gateRows?: unknown };
576
590
  if (state.goal.repairTarget) {
577
591
  return {
578
592
  content: [{ type: "text", text: `This repair card cannot be completed yet. Redraft the original target as a confirmed task list with propose_task_list (include objective: ${state.goal.repairTarget.objective.slice(0, 180)}), then continue the real work.` }],
@@ -787,7 +801,9 @@ function registerAgentTools(pi: any): void {
787
801
  }
788
802
  // v0.38.50: agent-structured finding groups ride the same claim —
789
803
  // sanitized once at the boundary, rendered only after approval.
804
+ // v0.38.52: same for the agent-supplied gate inventory.
790
805
  const sanitizedGroups = sanitizeFindingGroups(p.findingGroups);
806
+ const sanitizedGates = sanitizeGateRows(p.gateRows);
791
807
  const completionClaim = beginCompletionAudit(ctx, {
792
808
  completionSummary: finalSummary,
793
809
  verificationSummary: p.verificationSummary,
@@ -795,6 +811,7 @@ function registerAgentTools(pi: any): void {
795
811
  // the terminal render — the only source the summary may cite.
796
812
  ...(p.leftOut?.trim() ? { leftOut: p.leftOut.trim().slice(0, 500) } : {}),
797
813
  ...(sanitizedGroups ? { findingGroups: sanitizedGroups } : {}),
814
+ ...(sanitizedGates ? { gateRows: sanitizedGates } : {}),
798
815
  at: nowIso(),
799
816
  }, "complete-goal");
800
817
  if (!completionClaim) {
@@ -1252,6 +1269,7 @@ function registerAgentTools(pi: any): void {
1252
1269
  // v0.38.50: same for agent-structured finding groups.
1253
1270
  const escLeftOut = state.goal.pendingCompletion?.leftOut;
1254
1271
  const escFindingGroups = state.goal.pendingCompletion?.findingGroups;
1272
+ const escGateRows = state.goal.pendingCompletion?.gateRows;
1255
1273
  updateGoal({ status: "active", auditHistory: history, pendingCompletion: undefined, pauseReason: "audit aborted by user (Esc)" }, ctx);
1256
1274
  const abortConfirmCtx = freshCtxForGeneration(auditGeneration);
1257
1275
  if (!abortConfirmCtx) return staleToolResult();
@@ -1294,8 +1312,9 @@ function registerAgentTools(pi: any): void {
1294
1312
  // captured from it above.
1295
1313
  ...(escLeftOut ? { leftOut: escLeftOut } : {}),
1296
1314
  ...(escFindingGroups ? { findingGroups: escFindingGroups } : {}),
1315
+ ...(escGateRows ? { gateRows: escGateRows } : {}),
1297
1316
  });
1298
- if (!archiveCurrentGoal(ctx, "complete", terminalReason, {}, { findingGroups: escFindingGroups })) {
1317
+ if (!archiveCurrentGoal(ctx, "complete", terminalReason, {}, { findingGroups: escFindingGroups, gateRows: escGateRows })) {
1299
1318
  return {
1300
1319
  content: [{ type: "text", text: "The audit was aborted, but the terminal archive could not be persisted. The goal remains active; fix persistence and retry." }],
1301
1320
  details: {},
@@ -1351,14 +1370,16 @@ function registerAgentTools(pi: any): void {
1351
1370
  // v0.38.37: the deliberate non-do rides the durable claim.
1352
1371
  ...(durableCompletionClaim.leftOut ? { leftOut: durableCompletionClaim.leftOut } : {}),
1353
1372
  // v0.38.50: agent-structured finding groups ride the same claim.
1373
+ // v0.38.52: same for the gate inventory.
1354
1374
  ...(durableCompletionClaim.findingGroups ? { findingGroups: durableCompletionClaim.findingGroups } : {}),
1375
+ ...(durableCompletionClaim.gateRows ? { gateRows: durableCompletionClaim.gateRows } : {}),
1355
1376
  extras: inspectionSessionPath
1356
1377
  ? [`Auditor session kept for review: pi --session ${inspectionSessionPath} (or pi --fork ${inspectionSessionPath}).`]
1357
1378
  : [],
1358
1379
  });
1359
1380
  const manualObjective = state.goal.objective;
1360
1381
  const manualGoalId = state.goal.id;
1361
- const archived = archiveCurrentGoal(ctx, "complete", terminalReason, {}, { findingGroups: durableCompletionClaim.findingGroups });
1382
+ const archived = archiveCurrentGoal(ctx, "complete", terminalReason, {}, { findingGroups: durableCompletionClaim.findingGroups, gateRows: durableCompletionClaim.gateRows });
1362
1383
  if (!archived) {
1363
1384
  // The archive helper preserves the live objective and emits the
1364
1385
  // persistence warning. Stop here: an approved verdict is not a
@@ -2419,17 +2440,26 @@ function registerAgentTools(pi: any): void {
2419
2440
  // v0.23.7: show ALL items in full — the user approves the whole
2420
2441
  // batch; hidden items would be approved blind.
2421
2442
  const preview = p.items.map((t, i) => ` ${i + 1}. ${t}`).join("\n");
2422
- const batchActivates = !state.goal || state.goal.status === "complete" || state.goal.status === "aborted";
2443
+ const startsFromEmpty = !state.goal || state.goal.status === "complete" || state.goal.status === "aborted";
2444
+ const replacesPaused = state.goal?.status === "paused";
2445
+ const batchActivates = startsFromEmpty || replacesPaused;
2446
+ const activationNote = startsFromEmpty
2447
+ ? listQueue().length > 0
2448
+ ? "\n\n(Confirming starts the current list head immediately.)"
2449
+ : "\n\n(List is empty — confirming ACTIVATES the first proposed item immediately as the active goal.)"
2450
+ : replacesPaused
2451
+ ? "\n\n(A paused objective is held — confirming resolves that carryover and ACTIVATES the current list head. Reject if you only wanted to queue these items.)"
2452
+ : "";
2423
2453
  let batchConfirmed = false;
2424
2454
  if (autoAccept) {
2425
2455
  batchConfirmed = true;
2426
- liveCtx.ui.notify(`List batch auto-accepted (Auto-accept drafts = on in /glla settings): ${p.items.length} items${batchActivates ? " — item 1 ACTIVATES now" : ""}.`, "info");
2456
+ liveCtx.ui.notify(`List batch auto-accepted (Auto-accept drafts = on in /glla settings): ${p.items.length} items${batchActivates ? " — the list head ACTIVATES now" : ""}.`, "info");
2427
2457
  appendLedger(liveCtx.cwd, "draft_autoaccepted", { kind: "batch", count: p.items.length });
2428
2458
  } else {
2429
2459
  const c = await confirmDraft(
2430
2460
  liveCtx,
2431
2461
  "Confirm list batch",
2432
- `${p.items.length} items:\n${preview}${batchActivates ? "\n\n(List is empty — confirming ACTIVATES item 1 immediately as the active goal.)" : ""}`,
2462
+ `${p.items.length} items:\n${preview}${activationNote}`,
2433
2463
  );
2434
2464
  const afterConfirm = freshCtxForGeneration(draftGeneration);
2435
2465
  if (!afterConfirm) {
@@ -2468,10 +2498,25 @@ function registerAgentTools(pi: any): void {
2468
2498
  }
2469
2499
  draftingTarget = null;
2470
2500
  await ((globalThis as any).restoreDrafterModel?.() ?? Promise.resolve());
2471
- const wasIdle = !state.goal || state.goal.status === "complete" || state.goal.status === "aborted";
2501
+ // enqueueItems owns the empty/terminal-slot auto-start. A paused
2502
+ // objective is different: preserve the one-confirm batch, then route
2503
+ // promotion through the same carryover-aware choke point so the
2504
+ // paused record is archived before the new list head starts.
2505
+ const slotWasEmpty = !state.goal || state.goal.status === "complete" || state.goal.status === "aborted";
2506
+ const pausedBeforeEnqueue = state.goal?.status === "paused";
2472
2507
  const n = enqueueItems(liveCtx, p.items, "drafted batch");
2473
- if (wasIdle) {
2474
- return { content: [{ type: "text", text: `${n} items confirmed; first activated (list was empty). Begin work now.` }], details: {} };
2508
+ if (n === 0) {
2509
+ return { content: [{ type: "text", text: "No new list items were queued — the proposed batch was empty after duplicate/persistence checks." }], details: {} };
2510
+ }
2511
+ if (pausedBeforeEnqueue && state.goal?.status === "paused") {
2512
+ const activated = activateNextListItem(liveCtx);
2513
+ if (activated) {
2514
+ return { content: [{ type: "text", text: `${n} items confirmed; the list head activated after paused carryover resolution. Begin work now.` }], details: {} };
2515
+ }
2516
+ return { content: [{ type: "text", text: `${n} items confirmed and queued (${listQueue().length} waiting), but activation was held. The paused objective remains recoverable; retry list activation after fixing the reported issue.` }], details: {} };
2517
+ }
2518
+ if (slotWasEmpty && state.goal?.status === "active" && state.goal.policy === "list") {
2519
+ return { content: [{ type: "text", text: `${n} items confirmed; the first list item activated. Begin work now.` }], details: {} };
2475
2520
  }
2476
2521
  return { content: [{ type: "text", text: `${n} items confirmed and added to the list (${listQueue().length} waiting).` }], details: {} };
2477
2522
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-goal-list-loop-audit",
3
- "version": "0.38.51",
3
+ "version": "0.38.53",
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",
@@ -29,6 +29,7 @@
29
29
  },
30
30
  "files": [
31
31
  "extensions",
32
+ "skills",
32
33
  "prompts",
33
34
  "schemas",
34
35
  "scripts",
@@ -45,6 +46,9 @@
45
46
  "pi": {
46
47
  "extensions": [
47
48
  "extensions/loops/goal.ts"
49
+ ],
50
+ "skills": [
51
+ "skills/glla-delegate"
48
52
  ]
49
53
  },
50
54
  "scripts": {
@@ -205,4 +205,4 @@ The orchestrator's backstop is the stall watchdog: three consecutive turns with
205
205
 
206
206
  `complete_goal` submits a nonterminal claim, not a completion verdict. While the detached audit is pending, do not say the goal is done/complete/approved or present a final success summary. Do not wait or poll. If a response is needed, give one brief pending-status sentence. GLLA posts the concrete outcome-first summary into chat after verified approval and durable archive, without needing another user prompt. Do not duplicate that summary. A rejected claim remains unfinished and repair work continues.
207
207
 
208
- The agent-written completionSummary is user-facing evidence, not a step log: open with the outcome in plain words, then one verifiable result per label with its proof inline (test counts, file paths, commit refs) — never raw run stats. Keep each label value to one or two short clauses so it survives the renderer's clause-boundary clipping (rich sections budget ~200 chars per value); cite human-readable proof (counts, versions, repo-relative paths, PR numbers) and never machine paths (/var/tmp logs, tarballs — those live in the archive only). Cut values at clause boundaries, never mid-word; write Next: as one concrete follow-up action or none (audit-self-referential lines like "verdict decides" are stripped at render); pass what was deliberately left out as the complete_goal leftOut parameter — it renders as the closing bullet, and is omitted when absent. When the work spans distinct areas (subsystems, screens, phases), group the findings with the complete_goal findingGroups parameter — one group per area, each finding as `Lead: body` with repo-relative `path:line` evidence tokens (never invented paths); 4+ groups render as an Area | Finding | Evidence table, fewer as nested area sections. Single-area work omits findingGroups — the flat six-label render stays the fallback.
208
+ The agent-written completionSummary is user-facing evidence, not a step log: open with the outcome in plain words, then one verifiable result per label with its proof inline (test counts, file paths, commit refs) — never raw run stats. Keep each label value to one or two short clauses so it survives the renderer's clause-boundary clipping (rich sections budget ~200 chars per value); cite human-readable proof (counts, versions, repo-relative paths, PR numbers) and never machine paths (/var/tmp logs, tarballs — those live in the archive only). Cut values at clause boundaries, never mid-word; write Next: as one concrete follow-up action or none (audit-self-referential lines like "verdict decides" are stripped at render); pass what was deliberately left out as the complete_goal leftOut parameter — it renders as the closing bullet, and is omitted when absent. When the work spans distinct areas (subsystems, screens, phases), group the findings with the complete_goal findingGroups parameter — one group per area, each finding as `Lead: body` with repo-relative `path:line` evidence tokens (never invented paths); 4+ groups render as an Area | Finding | Evidence table, fewer as nested area sections. Add per-finding test proof with the parallel `tests` array (tests[i] proves findings[i]) — each renders as a `Test Results:` sub-line. When the work ran named quality gates (unit, typecheck, E2E, build, invariants), pass the complete_goal gateRows inventory — one row per gate with scope and outcome notes; the Verification table widens to Quality Gate | Scope | Status | Notes with mechanically derived statuses (PASS only when the notes say pass with zero failures) and supersedes the mechanical Tests rows. Single-area work omits findingGroups — the flat six-label render stays the fallback; work with no gate inventory omits gateRows — the mechanical 3-col table stays the fallback.
@@ -71,6 +71,17 @@ While drafting, watch the seed's shape and recommend the right mode:
71
71
  The user invoked `/goal` with no objective. Your job is to turn their vague
72
72
  request into a **confirmed goal contract**. Do NOT start substantive work yet.
73
73
 
74
+ ## Pasted list seeds
75
+
76
+ When this prompt is being used for `/list` and the user supplies a multi-line,
77
+ bulleted, numbered, or checklist-style seed, use its structure and wording as
78
+ provided. Strip only import syntax and empty headings/lines, then propose the
79
+ resulting items together through `items[]`. Do not ask whether the user wants
80
+ the list exact or refined: there is no such choice. Clarify only a genuinely
81
+ ambiguous individual item; never turn a complete pasted list into a
82
+ meta-choice. A direct user request to add such a list may use the normal
83
+ bulk-enqueue path instead of drafting.
84
+
74
85
  ## Protocol
75
86
 
76
87
  1. If the request is vague, ask ONE focused question at a time. Offer a
@@ -49,6 +49,16 @@ confirms.
49
49
  earn its place — decisions, constraints, and checkable outcomes only.
50
50
  Do not pad with boilerplate the user did not ask for.
51
51
 
52
+ ## Pasted list seeds
53
+
54
+ When plan mode is used for `/list` and the user supplies a multi-line,
55
+ bulleted, numbered, or checklist-style seed, preserve its item boundaries and
56
+ wording as supplied. Normalize only import syntax and empty headings/lines;
57
+ do not ask whether the list should be exact or refined. Clarify only a
58
+ genuinely ambiguous individual item, never turn a complete pasted list into a
59
+ meta-choice. Research may deepen contracts and verification, but it must not
60
+ rewrite a clear pasted list merely to make it sound more polished.
61
+
52
62
  ## Hard rules (unchanged from regular drafting)
53
63
 
54
64
  - Do NOT start implementing during planning. Research is read-only.
@@ -49,6 +49,7 @@ try {
49
49
  "scripts/goal-auditor-launch.mjs",
50
50
  "scripts/goal-auditor-worker.mjs",
51
51
  "scripts/release-pack-smoke.mjs",
52
+ "skills/glla-delegate/SKILL.md",
52
53
  ];
53
54
  const listing = run("tar", ["-tzf", tarball]);
54
55
  for (const file of required) {
@@ -0,0 +1,44 @@
1
+ ---
2
+ name: glla-delegate
3
+ description: "Use GLLA's audited goals and list queue from normal chat: queue explicit work, offer follow-ups before adding them, or draft a confirmed goal. Includes safe handling for pasted list-like input."
4
+ ---
5
+
6
+ # GLLA delegation
7
+
8
+ GLLA is not a scratch task list. A queued item becomes an audited goal, and
9
+ when the queue is idle its first item may activate immediately. Use these
10
+ tools in the main session only:
11
+
12
+ - `list_status` is read-only and safe for inspection.
13
+ - If the user explicitly asks to queue, add, backlog, or list work, call
14
+ `list_add` with the requested items. Do not turn a direct queue request
15
+ into an unnecessary interview.
16
+ - If you discover a useful follow-up while doing other work, finish the
17
+ current request and offer to queue it first. Queue it without another
18
+ question only when the user has given standing permission such as
19
+ “queue follow-ups as you find them.”
20
+ - If one durable, multi-hour objective is warranted, interview only what is
21
+ genuinely unknown, then call `propose_goal_draft`; the user's Confirm
22
+ dialog is the activation gate. Never activate a speculative raw seed.
23
+ - Never call `list_activate` for an item the user has not selected or clearly
24
+ authorized. Never control `/loop` bounds, metrics, or stopping on the
25
+ user's behalf.
26
+
27
+ ## Pasted list-like input
28
+
29
+ When the user supplies a multi-line, bulleted, numbered, or checklist-style
30
+ list and asks to use or add it, use the structure and wording as supplied.
31
+ Strip only import syntax and empty headings/lines, then pass the resulting
32
+ items in one `list_add` call. Do **not** ask whether the user wants the list
33
+ “exact” or “refined”; there is no such choice. Clarify only a genuinely
34
+ ambiguous individual item, never turn a complete pasted list into a
35
+ meta-choice.
36
+
37
+ If a `/list` drafting session is already active, do not bypass its Confirm
38
+ gate with `list_add`: pass the complete pasted set once as `items[]` to
39
+ `propose_goal_draft` instead. This keeps one confirmation for the whole
40
+ batch and prevents partial activation.
41
+
42
+ Use the tools, not slash-command text, when acting. Worker and subagent
43
+ sessions do not own GLLA state; respect a foreign-session refusal instead of
44
+ routing around it.