pi-goal-list-loop-audit 0.38.45 → 0.38.46
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 +10 -0
- package/docs/INDEX.md +1 -1
- package/extensions/completion-summary.ts +171 -9
- package/extensions/loops/goal-orchestrator.ts +8 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.38.46 — rich Antigravity-style terminal summaries (2026-09-11)
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- **Rich terminal voice (field 20260911_003839/003903/003907):** the terminal approval render is now `## Done — outcome` + `### Key Findings & Remediation` (numbered, bold lead + code refs) + `### Verification Summary` (`| Check | Status | Details |` table with Tests PASS/FAIL and the audit verdict row) + `### Next`, closed by the pinned approval/record trailer. The flat six-bullet card is gone from chat and transcript.
|
|
8
|
+
- **Rich archive too:** `archiveCurrentGoal` appends a `## Terminal summary` section built from the same parts over the verbatim six-label machine record; aborted records wear a `## Aborted —` headline, never `Done`.
|
|
9
|
+
- **Scope held:** progress cards, status line, and external notifies keep their compact projections; the legacy `buildApprovalChatLines` helper is untouched.
|
|
10
|
+
- **Budgets (verbose by owner choice):** findings cap 8, values/table cells 200 chars, Next cap 4; table pipes escaped; Tests status honest (PASS only on `pass` without nonzero `fail`, else FAIL/REPORTED); empty sections omitted, never invented.
|
|
11
|
+
- **Pins:** `tests/rich-terminal-summary.test.ts` (8) — headline, numbering, table, FAIL detection, pipe escaping, stale-Next behavior, trailer contract, no-audit path, archive headlines; terminal-render + communication suites migrated to the rich shape.
|
|
12
|
+
|
|
3
13
|
## 0.38.45 — full-project audit pass: 16 fixes across version, summary, and approval paths (2026-09-10)
|
|
4
14
|
|
|
5
15
|
### Fixed
|
package/docs/INDEX.md
CHANGED
|
@@ -18,7 +18,7 @@ For shipped docs, the relevant entry points are:
|
|
|
18
18
|
failback; v0.35.9 hardened cross-version npm tarball checks; v0.35.10
|
|
19
19
|
handles multi-entry npm dry-run reports; v0.35.11 accepts both npm report
|
|
20
20
|
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.
|
|
21
|
+
v0.35.14–v0.38.46 continue through the supervisor freeze (`/glla pause`),
|
|
22
22
|
load hold, auditor picker parity, Windows launch fix, zombie-watchdog
|
|
23
23
|
subagent carve-out, due-wait backstop, the `/glla agents` visibility panel,
|
|
24
24
|
durable state-root selection, blank-until-resume auditor context, frozen
|
|
@@ -269,6 +269,148 @@ export function withoutStaleNext(details: string[] | undefined): string[] {
|
|
|
269
269
|
return kept;
|
|
270
270
|
}
|
|
271
271
|
|
|
272
|
+
/** Rich terminal voice (field 20260911_003839/003903/003907 — the
|
|
273
|
+
* Antigravity close): section headers, numbered findings with bold
|
|
274
|
+
* leads + code refs, and a verification table, instead of the flat
|
|
275
|
+
* six-bullet card. Scope is the terminal render only (chat +
|
|
276
|
+
* transcript + archive human layer); progress cards, status line, and
|
|
277
|
+
* external notifies keep their compact projections. Verbose by owner
|
|
278
|
+
* choice: findings cap 8, values 200, table cells 200. */
|
|
279
|
+
export const RICH_FINDINGS_CAP = 8;
|
|
280
|
+
export const RICH_VALUE_BUDGET = 200;
|
|
281
|
+
export const RICH_NEXT_CAP = 4;
|
|
282
|
+
|
|
283
|
+
export interface RichTerminalParts {
|
|
284
|
+
headline: string;
|
|
285
|
+
findingLines: string[];
|
|
286
|
+
tableLines: string[];
|
|
287
|
+
nextLines: string[];
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function escapeTableCell(value: string): string {
|
|
291
|
+
return value.replace(/\|/g, "\\|").replace(/\s+/g, " ").trim();
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function testsRowStatus(value: string): string {
|
|
295
|
+
const fail = /(\d+)\s*fail/i.exec(value);
|
|
296
|
+
if (fail && Number.parseInt(fail[1]!, 10) > 0) return "FAIL";
|
|
297
|
+
if (/pass/i.test(value)) return "PASS";
|
|
298
|
+
return "REPORTED";
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function auditRowStatus(history: Goal["auditHistory"]): string {
|
|
302
|
+
const entries = Array.isArray(history) ? history : [];
|
|
303
|
+
const last = entries[entries.length - 1];
|
|
304
|
+
if (!last) return "NO VERDICT";
|
|
305
|
+
if (last.approved) return `APPROVED \u00d7${entries.length}`;
|
|
306
|
+
if (last.disapproved) return `DISAPPROVED \u00d7${entries.length}`;
|
|
307
|
+
if (last.impossible) return "IMPOSSIBLE";
|
|
308
|
+
return "NO VERDICT";
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/** Split a stale-filtered `Label: value` detail into a bold lead + body. */
|
|
312
|
+
function leadBody(detail: string): { lead: string; body: string } {
|
|
313
|
+
const separator = detail.indexOf(":");
|
|
314
|
+
if (separator < 0) return { lead: "Note", body: detail };
|
|
315
|
+
return { lead: detail.slice(0, separator).trim() || "Note", body: detail.slice(separator + 1).trim() };
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/** Partition informing details into findings / Tests / next buckets. */
|
|
319
|
+
export function partitionRichDetails(details: string[]): { findings: string[]; tests: string[]; next: string[] } {
|
|
320
|
+
const findings: string[] = [];
|
|
321
|
+
const tests: string[] = [];
|
|
322
|
+
const next: string[] = [];
|
|
323
|
+
for (const detail of details) {
|
|
324
|
+
if (/^\s*Tests\s*:/i.test(detail)) tests.push(detail);
|
|
325
|
+
else if (/^\s*(Next|Unresolved|Left out)\s*:/i.test(detail)) next.push(detail);
|
|
326
|
+
else findings.push(detail);
|
|
327
|
+
}
|
|
328
|
+
return { findings, tests, next };
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/** Build the section parts shared by chat, transcript, and archive. */
|
|
332
|
+
export function buildRichTerminalParts(args: {
|
|
333
|
+
outcome: string;
|
|
334
|
+
details: string[];
|
|
335
|
+
countsLine: string;
|
|
336
|
+
auditHistory?: Goal["auditHistory"];
|
|
337
|
+
}): RichTerminalParts {
|
|
338
|
+
const { findings, tests, next } = partitionRichDetails(args.details);
|
|
339
|
+
const findingLines = findings.slice(0, RICH_FINDINGS_CAP).map((detail, i) => {
|
|
340
|
+
const { lead, body } = leadBody(detail);
|
|
341
|
+
return `${i + 1}. **${lead}** \u2014 ${clipSummaryValue(body, RICH_VALUE_BUDGET)}`;
|
|
342
|
+
});
|
|
343
|
+
const tableRows: string[] = [];
|
|
344
|
+
for (const detail of tests.slice(0, 2)) {
|
|
345
|
+
const { body } = leadBody(detail);
|
|
346
|
+
tableRows.push(`| Tests | ${testsRowStatus(body)} | ${escapeTableCell(clipSummaryValue(body, RICH_VALUE_BUDGET))} |`);
|
|
347
|
+
}
|
|
348
|
+
const auditStatus = auditRowStatus(args.auditHistory);
|
|
349
|
+
if (auditStatus !== "NO VERDICT") {
|
|
350
|
+
const auditBody = args.countsLine.replace(/^\u2014\s*/, "").replace(/\.\s*$/, "");
|
|
351
|
+
tableRows.push(`| Audit | ${auditStatus} | ${escapeTableCell(clipSummaryValue(auditBody, RICH_VALUE_BUDGET))} |`);
|
|
352
|
+
}
|
|
353
|
+
const tableLines = tableRows.length > 0
|
|
354
|
+
? ["| Check | Status | Details |", "| --- | --- | --- |", ...tableRows]
|
|
355
|
+
: [];
|
|
356
|
+
const nextLines = next.slice(0, RICH_NEXT_CAP).map((detail) => {
|
|
357
|
+
const { lead, body } = leadBody(detail);
|
|
358
|
+
return `- **${lead}** \u2014 ${clipSummaryValue(body, RICH_VALUE_BUDGET)}`;
|
|
359
|
+
});
|
|
360
|
+
return {
|
|
361
|
+
headline: `## Done \u2014 ${args.outcome}`,
|
|
362
|
+
findingLines,
|
|
363
|
+
tableLines,
|
|
364
|
+
nextLines,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/** Compose parts + headline + section headers into markdown lines. */
|
|
369
|
+
export function composeRichTerminalLines(parts: RichTerminalParts, opts?: { headline?: string }): string[] {
|
|
370
|
+
const lines = [opts?.headline ?? parts.headline, ""];
|
|
371
|
+
if (parts.findingLines.length > 0) {
|
|
372
|
+
lines.push("### Key Findings & Remediation", ...parts.findingLines, "");
|
|
373
|
+
}
|
|
374
|
+
if (parts.tableLines.length > 0) {
|
|
375
|
+
lines.push("### Verification Summary", ...parts.tableLines, "");
|
|
376
|
+
}
|
|
377
|
+
if (parts.nextLines.length > 0) {
|
|
378
|
+
lines.push("### Next", ...parts.nextLines, "");
|
|
379
|
+
}
|
|
380
|
+
return lines;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/** Rich human layer for the durable archive (owner: the archive IS for
|
|
384
|
+
* humans). Same sections as chat, composed from durable goal state — the
|
|
385
|
+
* raw six-label recap stays verbatim in `## Completion summary` as the
|
|
386
|
+
* machine layer. Headline follows terminal status; aborted records never
|
|
387
|
+
* wear a `Done` headline. */
|
|
388
|
+
export function buildRichArchiveSection(goal: Goal, status: Status, archivePath: string): string[] {
|
|
389
|
+
const facts: CompletionSummaryFacts = { goal, status, archivePath };
|
|
390
|
+
const summary = resolveCompletionSummary(facts, goal.completionSummary).summary;
|
|
391
|
+
const brief = humanCompletionBrief(summary, 140, RICH_VALUE_BUDGET);
|
|
392
|
+
const history = goal.auditHistory ?? [];
|
|
393
|
+
const latest = history[history.length - 1];
|
|
394
|
+
const approval = latest
|
|
395
|
+
? `\u2014 auditor ${latest.approved ? "approved" : latest.disapproved ? "disapproved" : latest.impossible ? "impossible" : "left no verdict"}.`
|
|
396
|
+
: "\u2014 completed without a recorded auditor verdict.";
|
|
397
|
+
const countsLine = buildAuditCountsLine(goal);
|
|
398
|
+
const parts = buildRichTerminalParts({
|
|
399
|
+
outcome: brief.outcome,
|
|
400
|
+
details: withoutStaleNext(brief.details),
|
|
401
|
+
countsLine,
|
|
402
|
+
auditHistory: history,
|
|
403
|
+
});
|
|
404
|
+
return [
|
|
405
|
+
...composeRichTerminalLines(parts, {
|
|
406
|
+
headline: status === "complete" ? parts.headline : `## Aborted \u2014 ${brief.outcome}`,
|
|
407
|
+
}),
|
|
408
|
+
trailerBullet(stripApprovalModel(approval)),
|
|
409
|
+
trailerBullet(countsLine),
|
|
410
|
+
trailerBullet(`\u2014 record: ${archivePath}`),
|
|
411
|
+
];
|
|
412
|
+
}
|
|
413
|
+
|
|
272
414
|
/** v0.38.20: the approval chat notify — outcome first, all bounded
|
|
273
415
|
* informing details (the full record lives in the archive and the
|
|
274
416
|
* transcript notice), then the approval trailer and the record pointer.
|
|
@@ -382,15 +524,23 @@ export function buildTerminalApprovalRender(input: TerminalApprovalRenderInput):
|
|
|
382
524
|
};
|
|
383
525
|
const candidate = input.completionSummary ?? input.goal.completionSummary;
|
|
384
526
|
const recap = compactTerminalCompletionSummary(facts, candidate);
|
|
385
|
-
|
|
527
|
+
// Rich voice (field 20260911_*): the chat/transcript render uses a
|
|
528
|
+
// verbose brief (200-char values) while the outcome headline keeps
|
|
529
|
+
// the 140-char budget. Filler drops via the same briefValueContent
|
|
530
|
+
// filter; absent stays absent.
|
|
531
|
+
const richBrief = humanCompletionBrief(
|
|
532
|
+
resolveCompletionSummary(facts, candidate).summary,
|
|
533
|
+
140,
|
|
534
|
+
RICH_VALUE_BUDGET,
|
|
535
|
+
);
|
|
386
536
|
// v0.38.37 (audit 2026-09-08): the deliberate non-do comes from the
|
|
387
537
|
// agent's complete_goal leftOut claim — never invented. Filler ("none")
|
|
388
538
|
// drops via the same briefValueContent filter; absent stays absent.
|
|
389
539
|
const leftOut = input.leftOut?.trim();
|
|
390
540
|
const leftOutContent = leftOut ? briefValueContent(leftOut) : null;
|
|
391
|
-
const
|
|
392
|
-
?
|
|
393
|
-
:
|
|
541
|
+
const richDetails = leftOutContent
|
|
542
|
+
? [...richBrief.details, `Left out: ${clipSummaryValue(leftOutContent, RICH_VALUE_BUDGET)}`]
|
|
543
|
+
: richBrief.details;
|
|
394
544
|
const countsLine = input.countsLine ?? buildAuditCountsLine(input.goal, input.auditNote);
|
|
395
545
|
// v0.38.42 (field 20260909_140404): one canonical approval bullet in
|
|
396
546
|
// chat and transcript — no model ID in either, via-retry news kept. A
|
|
@@ -408,19 +558,31 @@ export function buildTerminalApprovalRender(input: TerminalApprovalRenderInput):
|
|
|
408
558
|
const approvalBullet = foldCounts
|
|
409
559
|
? `• ${chatApproval.replace(/^—\s*/, "").replace(/\.\s*$/, "")} (${history.length} verdict).`
|
|
410
560
|
: trailerBullet(chatApproval);
|
|
561
|
+
const recordBullet = trailerBullet(input.record);
|
|
562
|
+
// Rich voice: headline + Key Findings + Verification table + Next,
|
|
563
|
+
// closed by the pinned trailer (approval/counts/record, record last).
|
|
564
|
+
// The transcript mirrors chat without the record pointer and extras —
|
|
565
|
+
// the record lives in chat and the archive, matching the old contract.
|
|
566
|
+
const richParts = buildRichTerminalParts({
|
|
567
|
+
outcome: richBrief.outcome,
|
|
568
|
+
details: withoutStaleNext(richDetails),
|
|
569
|
+
countsLine,
|
|
570
|
+
auditHistory: input.goal.auditHistory,
|
|
571
|
+
});
|
|
572
|
+
const chatBody = composeRichTerminalLines(richParts);
|
|
573
|
+
const transcriptBody = composeRichTerminalLines(richParts);
|
|
411
574
|
return {
|
|
412
575
|
chatLines: [
|
|
413
|
-
|
|
414
|
-
...withoutStaleNext(brief.details).map((detail) => `• ${detail}`),
|
|
576
|
+
...chatBody,
|
|
415
577
|
approvalBullet,
|
|
416
578
|
...(foldCounts ? [] : [trailerBullet(countsLine)]),
|
|
417
|
-
|
|
579
|
+
recordBullet,
|
|
418
580
|
...(input.extras ?? []),
|
|
419
581
|
],
|
|
420
582
|
recap,
|
|
421
|
-
transcriptLines: [...
|
|
583
|
+
transcriptLines: [...transcriptBody, approvalBullet],
|
|
422
584
|
countsLine,
|
|
423
|
-
outcome:
|
|
585
|
+
outcome: richBrief.outcome,
|
|
424
586
|
approval: input.approval,
|
|
425
587
|
};
|
|
426
588
|
}
|
|
@@ -241,6 +241,7 @@ import {
|
|
|
241
241
|
import { buildStatusText, buildWidgetLines, type AuditDisplayProgress } from "../goal-loop-display.js";
|
|
242
242
|
import {
|
|
243
243
|
buildLoopCompletionSummary,
|
|
244
|
+
buildRichArchiveSection,
|
|
244
245
|
compactLoopCompletionSummary,
|
|
245
246
|
compactTerminalCompletionSummary,
|
|
246
247
|
isTerminalLoopStopReason,
|
|
@@ -1142,6 +1143,12 @@ function archiveCurrentGoal(
|
|
|
1142
1143
|
completionSummary: summaryResolution.summary,
|
|
1143
1144
|
};
|
|
1144
1145
|
const md = renderGoalMarkdown(terminalGoal);
|
|
1146
|
+
// Field 20260911_* (rich terminal voice): the archive is for humans —
|
|
1147
|
+
// it carries the same rich markdown as chat over the verbatim
|
|
1148
|
+
// six-label machine record. Built from the terminal goal AFTER the
|
|
1149
|
+
// summary fence resolves, so chat and archive cannot disagree.
|
|
1150
|
+
const richSection = buildRichArchiveSection(terminalGoal, status, archivePath);
|
|
1151
|
+
const richMd = `${md}\n## Terminal summary\n\n${richSection.join("\n")}\n`;
|
|
1145
1152
|
// An existing same-id archive is an immutable fence. Check it before
|
|
1146
1153
|
// publishing an intent so an unrelated/sentinel winner can never make the
|
|
1147
1154
|
// startup reconciler terminalize this live goal.
|
|
@@ -1179,7 +1186,7 @@ function archiveCurrentGoal(
|
|
|
1179
1186
|
// mitigates, this removes the predictability too).
|
|
1180
1187
|
const temp = `${target}.${process.pid}.${Date.now()}.${randomUUID()}.tmp`;
|
|
1181
1188
|
try {
|
|
1182
|
-
fs.writeFileSync(temp,
|
|
1189
|
+
fs.writeFileSync(temp, richMd, { encoding: "utf-8", flag: "wx" });
|
|
1183
1190
|
fs.linkSync(temp, target);
|
|
1184
1191
|
return true;
|
|
1185
1192
|
} catch (err) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-goal-list-loop-audit",
|
|
3
|
-
"version": "0.38.
|
|
3
|
+
"version": "0.38.46",
|
|
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",
|