pi-goal-list-loop-audit 0.38.67 → 0.38.69
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 +61 -0
- package/README.md +7 -7
- package/docs/INDEX.md +1 -1
- package/docs/SETTINGS.md +1 -0
- package/extensions/completion-summary.ts +91 -44
- package/extensions/goal-commands.ts +7 -3
- package/extensions/goal-continuation.ts +44 -3
- package/extensions/goal-loop-core.ts +76 -1
- package/extensions/goal-loop-display.ts +15 -1
- package/extensions/goal-recovery.ts +87 -16
- package/extensions/goal-settings.ts +10 -0
- package/extensions/length-continue.ts +37 -0
- package/extensions/loops/goal-activation.ts +125 -20
- package/extensions/loops/goal-auditor-hooks.ts +25 -26
- package/extensions/loops/goal-tools.ts +77 -13
- package/extensions/loops/goal.ts +2 -1
- package/extensions/main-model-recovery.ts +44 -5
- package/package.json +2 -2
- package/schemas/goal.schema.json +14 -0
- package/scripts/release-pack-smoke.mjs +56 -19
- package/skills/glla-delegate/SKILL.md +8 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,66 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.38.69 — About and README refresh (2026-09-20)
|
|
4
|
+
|
|
5
|
+
- Refined npm about line and README intro: tighter mission control wording, no em dashes.
|
|
6
|
+
- README range wording uses plain "to" (0 to 10, 1 to 10).
|
|
7
|
+
|
|
8
|
+
### Antigravity auto-continue port (survey 2026-09-19)
|
|
9
|
+
|
|
10
|
+
- Mid-run interruption budget (`decisionPauseBudget`): decision pauses past
|
|
11
|
+
budget auto-default to the recommended option and ledger
|
|
12
|
+
`decision_budget_auto_default` instead of stalling mid-run.
|
|
13
|
+
- Quota sleep-until-reset: quota-class failures with an explicit upstream
|
|
14
|
+
hint sleep until reset (5h cap; 5s eager first retry preserved); quota
|
|
15
|
+
waits never park at the 24h horizon; background primary probe re-arms at
|
|
16
|
+
reset while fallback serves.
|
|
17
|
+
- Completion-claim evidence-density lint: zero path:line tokens AND zero
|
|
18
|
+
gate rows rides a ledgered NOTE annotation (claim still audits); every
|
|
19
|
+
terminal path already ends with the archive record pointer.
|
|
20
|
+
- Per-repo pitfall registry: `.pi-glla/pitfalls.md` (absent stays absent)
|
|
21
|
+
rides the continuation prompt under REPO PITFALLS, ledgered once per goal.
|
|
22
|
+
- Objection-attached retries: every disapproval extracts durable TODOs via
|
|
23
|
+
`durableObjectionsForDisapproval` (aggressive keeps only cap/no-progress
|
|
24
|
+
behaviors); two pre-existing source-shape pins revised to the new shape.
|
|
25
|
+
|
|
26
|
+
## 0.38.68 — Relentless auto-continue: heat-routed exhaustion, quota retries, truthful recovering (2026-09-19)
|
|
27
|
+
|
|
28
|
+
### Length-exhaustion wedge fixed at the root: heat, not budget (field 162348)
|
|
29
|
+
|
|
30
|
+
Repeated output-token truncation at near-full context is a heat problem —
|
|
31
|
+
the prompt no longer fits the model — so growing the truncation budget
|
|
32
|
+
only ping-pongs into the same wall. `decideLengthExhaustion` now routes on
|
|
33
|
+
`contextPercent`: roomy context restarts the truncation budget relentlessly
|
|
34
|
+
(episode 1 of a bounded 2-episode budget, episode 2 parks durably),
|
|
35
|
+
near-full context with a larger-context fallback rotates models, and
|
|
36
|
+
near-full context without one yields to pi auto-compaction and lets the
|
|
37
|
+
settle path own the resume. The compact-defer path no longer kicks a fresh
|
|
38
|
+
turn into the known-hot window, and a manual `/goal resume` between
|
|
39
|
+
episodes restarts the cycle instead of parking one round early.
|
|
40
|
+
|
|
41
|
+
### Quota-identical auditor failures hammer bounded retries (field 150821)
|
|
42
|
+
|
|
43
|
+
Rate-limit and plan-quota walls are transient, so they are exempt from the
|
|
44
|
+
identical-failure park: the auditor keeps hammering its bounded retry plan
|
|
45
|
+
instead of parking with "check the auditor/model setup". Billing walls
|
|
46
|
+
and non-quota infra failures still park truthfully. A resumed or recovered
|
|
47
|
+
cycle reseeds the dead candidate chain via `freshAuditorCycleClaim`
|
|
48
|
+
instead of re-walking it, and no longer renders the previous cycle's dead
|
|
49
|
+
chain or diagnostic one round late.
|
|
50
|
+
|
|
51
|
+
### Armed retry renders recovering, not dead-paused (field 151113)
|
|
52
|
+
|
|
53
|
+
A supervised auditor wait with an armed retry now renders `recovering`
|
|
54
|
+
with an auto-retrying status line. Bare user waits with no pending claim
|
|
55
|
+
still render `paused` — no false recovering labels.
|
|
56
|
+
|
|
57
|
+
### glla-delegate skill states its drafting precondition (field 125302)
|
|
58
|
+
|
|
59
|
+
The skill doc now says `propose_goal_draft` only runs inside an already-open
|
|
60
|
+
drafting session (bare `/goal`, `/list`, or `/list add`), refuses from
|
|
61
|
+
normal chat, and falls back to `list_add` — the refusal is correct
|
|
62
|
+
behavior, so this is a doc fix with a regression pin, no prod change.
|
|
63
|
+
|
|
3
64
|
## 0.38.67 — Stop clobbering other extensions' compactions (2026-09-19)
|
|
4
65
|
|
|
5
66
|
### session_before_compact returns nothing (issue #56, ezoushen)
|
package/README.md
CHANGED
|
@@ -4,20 +4,20 @@
|
|
|
4
4
|
<img src="media/glla2.png" alt="GLLA mission control" width="960">
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
|
-
> **Long
|
|
7
|
+
> **Long running, high leverage autonomy for pi.**
|
|
8
8
|
>
|
|
9
9
|
> Give pi a meaningful outcome. GLLA helps it research, plan, execute,
|
|
10
10
|
> recover, and prove the result over hours or days instead of treating one
|
|
11
11
|
> chat turn as the whole job.
|
|
12
12
|
|
|
13
13
|
`pi-goal-list-loop-audit` (GLLA) is mission control for autonomous work in
|
|
14
|
-
[pi](https://github.com/badlogic/pi-mono). It
|
|
14
|
+
[pi](https://github.com/badlogic/pi-mono). It fits work that is too broad,
|
|
15
15
|
too long, or too important to leave to a single uninterrupted prompt:
|
|
16
16
|
repo-wide changes, migrations, audits, research, documentation overhauls,
|
|
17
17
|
large refactors, and continuous improvement.
|
|
18
18
|
|
|
19
|
-
GLLA does not promise that an agent
|
|
20
|
-
agent's work **more effective, durable, recoverable, and
|
|
19
|
+
GLLA does not promise that an agent never makes a mistake. It makes the
|
|
20
|
+
agent's work **more effective, durable, recoverable, and hard to declare
|
|
21
21
|
finished without evidence**:
|
|
22
22
|
|
|
23
23
|
- You state the outcome and what “done” means.
|
|
@@ -30,7 +30,7 @@ finished without evidence**:
|
|
|
30
30
|
- A separate detached auditor checks the saved completion claim before GLLA
|
|
31
31
|
accepts it.
|
|
32
32
|
|
|
33
|
-
The aim is not
|
|
33
|
+
The aim is not "run forever." The aim is **more useful work per unit of
|
|
34
34
|
attention, with event-driven progress instead of guessed-duration waiting, and
|
|
35
35
|
better evidence at the end**. See `docs/DESIGN-long-running-supervision.md` for
|
|
36
36
|
the long-running policy.
|
|
@@ -398,7 +398,7 @@ proof of a quota or billing state.
|
|
|
398
398
|
|
|
399
399
|
- automatic retries are bounded and visible; a BUSY/no-stream turn is parked
|
|
400
400
|
and re-dispatched within the configurable **Zero-stream retries** budget
|
|
401
|
-
(default 3, range 0
|
|
401
|
+
(default 3, range 0 to 10), then requires explicit resume;
|
|
402
402
|
- `/goal resume`, `/list resume`, and `/loop resume` are explicit recovery
|
|
403
403
|
paths;
|
|
404
404
|
- a user abort means stop, not “try again behind my back”;
|
|
@@ -431,7 +431,7 @@ Open `/glla` for the settings table. The most important choices are:
|
|
|
431
431
|
- **Subagent hang escalation:** warning-only at `0`, or one child-specific
|
|
432
432
|
action after a confirmed frozen interval;
|
|
433
433
|
- **Zero-stream retries:** automatic GLLA recovery attempts after a busy,
|
|
434
|
-
stream-silent Pi turn; `0` keeps recovery manual and `1
|
|
434
|
+
stream-silent Pi turn; `0` keeps recovery manual and `1 to 10` bounds repeats;
|
|
435
435
|
- **Audit cap and retry cadence:** bounds for repeated objections and
|
|
436
436
|
infrastructure recovery.
|
|
437
437
|
|
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.
|
|
19
|
+
v0.35.14–v0.38.69 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
|
package/docs/SETTINGS.md
CHANGED
|
@@ -64,6 +64,7 @@ copies are ignored (the recovery runtime reads the global file):
|
|
|
64
64
|
| `wedgeAlertMinutes` | unset (30, or off while aggressive mode is on — the default) | Busy-but-silent minutes before the wedge alert; `0` = off. The menu shows the effective value. |
|
|
65
65
|
| `autoResume` | `false` | Restored goals/loops/lists auto-resume in fresh sessions. Global-only. |
|
|
66
66
|
| `decisionPopup` | `true` | Decision pauses pop the picker (`false` = widget card only). |
|
|
67
|
+
| `decisionPauseBudget` | unset (unlimited) | Max agent-authored decision pauses per goal before auto-default: the (N+1)-th adopts the recommended option as a logged assumption without pausing. `0` = relentless from the first decision. |
|
|
67
68
|
| `carryover` | `"pause"` | Stale carryover on new activation: `"pause"` / `"clear"` / `"resume"`. |
|
|
68
69
|
| `autoAcceptDrafts` | `false` | Drafts activate without the Confirm dialog (unattended rigs). |
|
|
69
70
|
| `auditCap` | `5` (10 aggressive) | Pause after N consecutive auditor disapprovals (`0` = unlimited). |
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
|
-
import type
|
|
2
|
+
import { sanitizeDisplayText, type FindingGroup, type GateRow, type Goal, type Status } from "./goal-loop-core.js";
|
|
3
3
|
import { fmtElapsed, truncateCells } from "./goal-loop-display.js";
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -180,9 +180,10 @@ export interface HumanCompletionBrief {
|
|
|
180
180
|
* the durable archive keeps the full text. Falls back to the original
|
|
181
181
|
* when stripping would empty the value. */
|
|
182
182
|
export function chatSafeDetailValue(value: string): string {
|
|
183
|
+
const safeValue = sanitizeDisplayText(value);
|
|
183
184
|
// v0.38.45 audit: loop the innermost-group strip to a fixpoint
|
|
184
185
|
// (bounded) — a single pass left nested husks like "(log )" behind.
|
|
185
|
-
let withoutGroups = stripMachineGroups(
|
|
186
|
+
let withoutGroups = stripMachineGroups(safeValue);
|
|
186
187
|
const stripped = withoutGroups
|
|
187
188
|
.replace(/(?:\/var)?\/tmp\/\S+/g, "")
|
|
188
189
|
.replace(/\btarballs?\s+\S+\.tgz\b/gi, "")
|
|
@@ -190,7 +191,7 @@ export function chatSafeDetailValue(value: string): string {
|
|
|
190
191
|
.replace(/\(\s*\)/g, "")
|
|
191
192
|
.replace(/\s{2,}/g, " ")
|
|
192
193
|
.trim();
|
|
193
|
-
return stripped ||
|
|
194
|
+
return stripped || safeValue;
|
|
194
195
|
}
|
|
195
196
|
|
|
196
197
|
/** The human briefing: outcome first in its own words, then only the
|
|
@@ -384,7 +385,7 @@ function stripMachineTokens(line: string): string {
|
|
|
384
385
|
export function structuredSummaryLines(summary: string | undefined, label = "Outcome:"): string[] | null {
|
|
385
386
|
const raw = rawLabelValue(summary ?? "", label);
|
|
386
387
|
if (!raw || !isSectionStructured(raw)) return null;
|
|
387
|
-
const lines = raw.split("\n").map(stripMachineTokens);
|
|
388
|
+
const lines = raw.split("\n").map((line) => stripMachineTokens(sanitizeDisplayText(line)));
|
|
388
389
|
while (lines.length > 0 && !(lines[0] ?? "").trim()) lines.shift();
|
|
389
390
|
while (lines.length > 0 && !(lines[lines.length - 1] ?? "").trim()) lines.pop();
|
|
390
391
|
let text = lines.join("\n");
|
|
@@ -416,7 +417,7 @@ export interface RichTerminalParts {
|
|
|
416
417
|
}
|
|
417
418
|
|
|
418
419
|
function escapeTableCell(value: string): string {
|
|
419
|
-
return value.replace(/\|/g, "\\|").replace(/\s+/g, " ").trim();
|
|
420
|
+
return sanitizeDisplayText(value).replace(/\|/g, "\\|").replace(/\s+/g, " ").trim();
|
|
420
421
|
}
|
|
421
422
|
|
|
422
423
|
function testsRowStatus(value: string): string {
|
|
@@ -470,9 +471,10 @@ function auditRowStatus(history: Goal["auditHistory"]): string {
|
|
|
470
471
|
|
|
471
472
|
/** Split a stale-filtered `Label: value` detail into a bold lead + body. */
|
|
472
473
|
function leadBody(detail: string): { lead: string; body: string } {
|
|
473
|
-
const
|
|
474
|
-
|
|
475
|
-
return { lead:
|
|
474
|
+
const safeDetail = sanitizeDisplayText(detail);
|
|
475
|
+
const separator = safeDetail.indexOf(":");
|
|
476
|
+
if (separator < 0) return { lead: "Note", body: safeDetail };
|
|
477
|
+
return { lead: safeDetail.slice(0, separator).trim() || "Note", body: safeDetail.slice(separator + 1).trim() };
|
|
476
478
|
}
|
|
477
479
|
|
|
478
480
|
/**
|
|
@@ -487,7 +489,7 @@ const EVIDENCE_TOKEN_PATTERN = /(?<![/~+\w])[\w.+][\w.+/-]*\.[A-Za-z0-9]{1,8}:\d
|
|
|
487
489
|
|
|
488
490
|
export function extractEvidenceTokens(text: string): { text: string; evidence: string[] } {
|
|
489
491
|
const evidence: string[] = [];
|
|
490
|
-
const stripped = text
|
|
492
|
+
const stripped = sanitizeDisplayText(text)
|
|
491
493
|
.replace(EVIDENCE_TOKEN_PATTERN, (match) => {
|
|
492
494
|
if (evidence.length < RICH_EVIDENCE_TOKENS_PER_FINDING && !evidence.includes(match)) evidence.push(match);
|
|
493
495
|
// The token often rides in parentheses — move the wrapper too, so
|
|
@@ -501,6 +503,29 @@ export function extractEvidenceTokens(text: string): { text: string; evidence: s
|
|
|
501
503
|
return { text: stripped, evidence };
|
|
502
504
|
}
|
|
503
505
|
|
|
506
|
+
/**
|
|
507
|
+
* v0.38.69 (Antigravity port): completion-claim evidence-density lint.
|
|
508
|
+
* A walkthrough always ships areas + counts; GLLA richness rode optional
|
|
509
|
+
* agent params, so a flat six-label claim with zero path:line tokens and
|
|
510
|
+
* zero gate rows still passed. Returns a NOTE annotation when the claim
|
|
511
|
+
* carries no verifiable pointer at all (no evidence tokens across the
|
|
512
|
+
* summary and every group finding, and no gate rows) — the claim still
|
|
513
|
+
* audits, but the terminal render falls back to recorded facts. Tokens in
|
|
514
|
+
* finding groups count; gate rows count as density without tokens.
|
|
515
|
+
*/
|
|
516
|
+
export function completionSummaryDensityNote(
|
|
517
|
+
summary: string | undefined,
|
|
518
|
+
groups?: FindingGroup[],
|
|
519
|
+
gates?: GateRow[],
|
|
520
|
+
): string | undefined {
|
|
521
|
+
if (!summary?.trim()) return undefined;
|
|
522
|
+
const pool = [summary, ...(groups ?? []).flatMap((group) => group.findings ?? [])].join("\n");
|
|
523
|
+
const { evidence } = extractEvidenceTokens(pool);
|
|
524
|
+
if (evidence.length > 0) return undefined;
|
|
525
|
+
if ((gates ?? []).length > 0) return undefined;
|
|
526
|
+
return "low evidence density: no path:line evidence tokens and no verification gate rows — add file:line pointers (e.g. extensions/goal-recovery.ts:847) or a gateRows inventory so the terminal render is verifiable";
|
|
527
|
+
}
|
|
528
|
+
|
|
504
529
|
/**
|
|
505
530
|
* v0.38.50: one compact duration line from durable goal state — turns,
|
|
506
531
|
* wall-clock elapsed since creation, and audit count. Only known facts
|
|
@@ -509,7 +534,9 @@ export function extractEvidenceTokens(text: string): { text: string; evidence: s
|
|
|
509
534
|
export function buildDurationLine(goal: Goal, now = Date.now()): string | null {
|
|
510
535
|
const segs: string[] = [];
|
|
511
536
|
const turns = goal.telemetry?.turns;
|
|
512
|
-
|
|
537
|
+
// Field 20260918_172705: a zero turns count means untracked telemetry,
|
|
538
|
+
// not a known fact — omit it while elapsed/audits still render.
|
|
539
|
+
if (typeof turns === "number" && Number.isFinite(turns) && turns > 0) {
|
|
513
540
|
segs.push(`${turns} turn${turns === 1 ? "" : "s"}`);
|
|
514
541
|
}
|
|
515
542
|
const started = Date.parse(goal.createdAt ?? "");
|
|
@@ -519,15 +546,25 @@ export function buildDurationLine(goal: Goal, now = Date.now()): string | null {
|
|
|
519
546
|
return segs.length > 0 ? `\u2014 ${segs.join(" \u00b7 ")}` : null;
|
|
520
547
|
}
|
|
521
548
|
|
|
522
|
-
/** Partition informing details into findings / Tests / next buckets.
|
|
549
|
+
/** Partition informing details into findings / Tests / next buckets.
|
|
550
|
+
* Field 20260918_172705: exact-duplicate lines collapse to one per bucket
|
|
551
|
+
* (repeated claim details rendered as doubled rows). Near-duplicates with
|
|
552
|
+
* different wording still render — that prose belongs to the claim. */
|
|
523
553
|
export function partitionRichDetails(details: string[]): { findings: string[]; tests: string[]; next: string[] } {
|
|
554
|
+
const seen = new Set<string>();
|
|
555
|
+
const push = (bucket: string[], detail: string) => {
|
|
556
|
+
const key = detail.trim();
|
|
557
|
+
if (seen.has(key)) return;
|
|
558
|
+
seen.add(key);
|
|
559
|
+
bucket.push(detail);
|
|
560
|
+
};
|
|
524
561
|
const findings: string[] = [];
|
|
525
562
|
const tests: string[] = [];
|
|
526
563
|
const next: string[] = [];
|
|
527
564
|
for (const detail of details) {
|
|
528
|
-
if (/^\s*Tests\s*:/i.test(detail))
|
|
529
|
-
else if (/^\s*(Next|Unresolved|Left out)\s*:/i.test(detail))
|
|
530
|
-
else
|
|
565
|
+
if (/^\s*Tests\s*:/i.test(detail)) push(tests, detail);
|
|
566
|
+
else if (/^\s*(Next|Unresolved|Left out)\s*:/i.test(detail)) push(next, detail);
|
|
567
|
+
else push(findings, detail);
|
|
531
568
|
}
|
|
532
569
|
return { findings, tests, next };
|
|
533
570
|
}
|
|
@@ -538,15 +575,17 @@ export function partitionRichDetails(details: string[]): { findings: string[]; t
|
|
|
538
575
|
* `## Done — outcome` shape stays, so direct unit callers are unaffected.
|
|
539
576
|
*/
|
|
540
577
|
export function requestEchoHeadline(kind: "Done" | "Aborted", objective: string | undefined, outcome: string): string {
|
|
541
|
-
const
|
|
542
|
-
|
|
578
|
+
const safeObjective = objective ? sanitizeDisplayText(objective) : "";
|
|
579
|
+
const safeOutcome = sanitizeDisplayText(outcome);
|
|
580
|
+
const echo = safeObjective.trim() ? clipSummaryValue(safeObjective.trim(), RICH_OBJECTIVE_ECHO_CHARS) : null;
|
|
581
|
+
return echo ? `## ${kind}: ${echo} \u2014 ${safeOutcome}` : `## ${kind} \u2014 ${safeOutcome}`;
|
|
543
582
|
}
|
|
544
583
|
|
|
545
584
|
/** Chat-only projection: keep explanations and counts, not command/hash receipts.
|
|
546
585
|
* Repository-only findings are filtered separately; useful implementation
|
|
547
586
|
* references in substantive explanations are not themselves bookkeeping. */
|
|
548
587
|
function chatNarrative(value: string): string {
|
|
549
|
-
return stripMachineGroups(chatSafeDetailValue(value)
|
|
588
|
+
return stripMachineGroups(chatSafeDetailValue(sanitizeDisplayText(value))
|
|
550
589
|
.replace(/\b(?:fixed in|commit|HEAD(?: at)?|built from)\s+`?[a-f0-9]{7,64}`?/gi, "")
|
|
551
590
|
.replace(/\b(?=[a-f0-9]*[a-f])(?=[a-f0-9]*\d)[a-f0-9]{7,64}\b/gi, "")
|
|
552
591
|
.replace(/`(?:bun|npm|npx|node|git|tsc)\s+[^`]+`/g, "")
|
|
@@ -620,9 +659,12 @@ export function buildFinalRepoStateLines(cwd: string): string[] | undefined {
|
|
|
620
659
|
return undefined;
|
|
621
660
|
}
|
|
622
661
|
};
|
|
623
|
-
const
|
|
624
|
-
const
|
|
625
|
-
const
|
|
662
|
+
const branchRaw = run(["branch", "--show-current"]);
|
|
663
|
+
const headRaw = run(["log", "-1", "--format=%h %s"]);
|
|
664
|
+
const shortRaw = run(["status", "--short"]);
|
|
665
|
+
const branch = branchRaw === undefined ? undefined : sanitizeDisplayText(branchRaw);
|
|
666
|
+
const head = headRaw === undefined ? undefined : sanitizeDisplayText(headRaw);
|
|
667
|
+
const short = shortRaw === undefined ? undefined : shortRaw.split("\n").map((line) => sanitizeDisplayText(line)).join("\n");
|
|
626
668
|
if (!branch && !head && short === undefined) return undefined;
|
|
627
669
|
const lines = [`Branch ${branch ?? "detached"}${head ? ` @ ${head}` : ""}`];
|
|
628
670
|
if (short === undefined) lines.push("Tree state unreadable");
|
|
@@ -660,7 +702,7 @@ export function buildRichTerminalParts(args: {
|
|
|
660
702
|
chat?: boolean;
|
|
661
703
|
}): RichTerminalParts {
|
|
662
704
|
const { findings, tests, next } = partitionRichDetails(args.details);
|
|
663
|
-
const outcome = args.outcome;
|
|
705
|
+
const outcome = sanitizeDisplayText(args.outcome);
|
|
664
706
|
const kind = args.kind ?? "Done";
|
|
665
707
|
const headline = args.chat ? `## ${kind} — ${outcome}` : requestEchoHeadline(kind, args.objective, outcome);
|
|
666
708
|
const auditStatus = auditRowStatus(args.auditHistory);
|
|
@@ -676,11 +718,11 @@ export function buildRichTerminalParts(args: {
|
|
|
676
718
|
findingLines.push("| Area | Finding | Evidence |", "| --- | --- | --- |");
|
|
677
719
|
for (const group of groups) {
|
|
678
720
|
group.findings.forEach((finding, fi) => {
|
|
679
|
-
const { text, evidence } = extractEvidenceTokens(finding);
|
|
721
|
+
const { text, evidence } = extractEvidenceTokens(sanitizeDisplayText(finding));
|
|
680
722
|
const { lead, body } = leadBody(text);
|
|
681
723
|
// v0.38.52: test proof rides the Evidence cell (tables have no
|
|
682
724
|
// sub-bullets); cells stay pipe-escaped. v0.38.55: unclipped.
|
|
683
|
-
const proof = group.tests?.[fi]
|
|
725
|
+
const proof = group.tests?.[fi] ? sanitizeDisplayText(group.tests[fi]).trim() : "";
|
|
684
726
|
const evidenceCell = [evidence.join(", ") || "\u2014", ...(proof ? [`Tests: ${proof}`] : [])].join(" \u00b7 ");
|
|
685
727
|
findingLines.push(
|
|
686
728
|
`| ${escapeTableCell(group.title)} | ${escapeTableCell(`**${lead}** \u2014 ${body}`)} | ${escapeTableCell(evidenceCell)} |`,
|
|
@@ -689,12 +731,12 @@ export function buildRichTerminalParts(args: {
|
|
|
689
731
|
}
|
|
690
732
|
} else if (groups.length > 0) {
|
|
691
733
|
groups.forEach((group, i) => {
|
|
692
|
-
findingLines.push(`#### ${i + 1}. ${group.title}`);
|
|
734
|
+
findingLines.push(`#### ${i + 1}. ${sanitizeDisplayText(group.title)}`);
|
|
693
735
|
group.findings.forEach((finding, fi) => {
|
|
694
|
-
const { lead, body } = leadBody(args.chat ? chatNarrative(extractEvidenceTokens(finding).text) : finding);
|
|
736
|
+
const { lead, body } = leadBody(args.chat ? chatNarrative(extractEvidenceTokens(finding).text) : sanitizeDisplayText(finding));
|
|
695
737
|
findingLines.push(`- **${lead}** \u2014 ${body}`);
|
|
696
738
|
// v0.38.52: per-finding test proof (shot C) — absent stays absent.
|
|
697
|
-
const proof = group.tests?.[fi]
|
|
739
|
+
const proof = group.tests?.[fi] ? sanitizeDisplayText(group.tests[fi]).trim() : "";
|
|
698
740
|
if (proof) findingLines.push(` - Test Results: ${args.chat ? chatNarrative(proof) : proof}`);
|
|
699
741
|
});
|
|
700
742
|
});
|
|
@@ -716,22 +758,25 @@ export function buildRichTerminalParts(args: {
|
|
|
716
758
|
const showCommand = !args.chat && gates.some((row) => row.command?.trim());
|
|
717
759
|
if (gates.length > 0) {
|
|
718
760
|
for (const row of gates) {
|
|
719
|
-
const
|
|
720
|
-
const
|
|
721
|
-
const
|
|
761
|
+
const safeNotes = row.notes ? sanitizeDisplayText(row.notes).trim() : "";
|
|
762
|
+
const derived = testsRowStatus(safeNotes);
|
|
763
|
+
const notes = args.chat ? chatNarrative(safeNotes) : safeNotes;
|
|
764
|
+
const command = row.command ? sanitizeDisplayText(row.command).trim() : "\u2014";
|
|
765
|
+
const gate = sanitizeDisplayText(row.gate);
|
|
766
|
+
const scope = row.scope ? sanitizeDisplayText(row.scope).trim() : "\u2014";
|
|
722
767
|
tableRows.push(showCommand
|
|
723
|
-
? `| ${escapeTableCell(
|
|
724
|
-
: `| ${escapeTableCell(
|
|
768
|
+
? `| ${escapeTableCell(gate)} | ${escapeTableCell(command)} | ${escapeTableCell(scope)} | ${derived} | ${escapeTableCell(notes || "\u2014")} |`
|
|
769
|
+
: `| ${escapeTableCell(gate)} | ${escapeTableCell(scope)} | ${derived} | ${escapeTableCell(notes || "\u2014")} |`);
|
|
725
770
|
}
|
|
726
771
|
} else {
|
|
727
772
|
for (const detail of tests) {
|
|
728
773
|
const { body } = leadBody(detail);
|
|
729
774
|
const status = testsRowStatus(body);
|
|
730
|
-
tableRows.push(`| Tests | ${status} | ${escapeTableCell(args.chat ? chatNarrative(body) : body)} |`);
|
|
775
|
+
tableRows.push(`| Tests | ${status} | ${escapeTableCell(args.chat ? chatNarrative(body) : sanitizeDisplayText(body))} |`);
|
|
731
776
|
}
|
|
732
777
|
}
|
|
733
778
|
if (!args.chat && auditStatus !== "NO VERDICT") {
|
|
734
|
-
const auditBody = args.countsLine.replace(/^\u2014\s*/, "").replace(/\.\s*$/, "");
|
|
779
|
+
const auditBody = sanitizeDisplayText(args.countsLine).replace(/^\u2014\s*/, "").replace(/\.\s*$/, "");
|
|
735
780
|
// v0.38.55 audit: the Audit row's Scope names the row kind — the old
|
|
736
781
|
// shape duplicated the counts text in Scope and Notes.
|
|
737
782
|
if (gates.length > 0) {
|
|
@@ -763,8 +808,8 @@ export function buildRichTerminalParts(args: {
|
|
|
763
808
|
findingLines,
|
|
764
809
|
tableLines,
|
|
765
810
|
nextLines,
|
|
766
|
-
repoLines: args.chat ? [] : args.repoState ?? [],
|
|
767
|
-
summaryLines: args.summaryLines ?? [],
|
|
811
|
+
repoLines: args.chat ? [] : (args.repoState ?? []).map((line) => sanitizeDisplayText(line)),
|
|
812
|
+
summaryLines: (args.summaryLines ?? []).map((line) => sanitizeDisplayText(line)),
|
|
768
813
|
};
|
|
769
814
|
}
|
|
770
815
|
|
|
@@ -776,28 +821,30 @@ export function buildRichTerminalParts(args: {
|
|
|
776
821
|
* final repository state closes it — findings, verification, Next,
|
|
777
822
|
* repo state, in that order. */
|
|
778
823
|
export function composeRichTerminalLines(parts: RichTerminalParts): string[] {
|
|
779
|
-
const
|
|
780
|
-
|
|
781
|
-
|
|
824
|
+
const banner = sanitizeDisplayText(parts.banner ?? parts.headline);
|
|
825
|
+
const headline = sanitizeDisplayText(parts.headline);
|
|
826
|
+
const lines = [banner, ""];
|
|
827
|
+
if (headline !== lines[0]) lines.push(headline, "");
|
|
828
|
+
if (parts.durationLine) lines.push(sanitizeDisplayText(parts.durationLine), "");
|
|
782
829
|
// Structured-long (field 2026-09-16): the full Outcome body rides its
|
|
783
830
|
// own section between the headline and the findings — findings-first
|
|
784
831
|
// order is preserved (findings, verification, Next keep their relative
|
|
785
832
|
// order), the headline echo stays short, and the one-action Next rule
|
|
786
833
|
// is untouched.
|
|
787
834
|
if (parts.summaryLines.length > 0) {
|
|
788
|
-
lines.push("### Summary", ...parts.summaryLines, "");
|
|
835
|
+
lines.push("### Summary", ...parts.summaryLines.map((line) => sanitizeDisplayText(line)), "");
|
|
789
836
|
}
|
|
790
837
|
if (parts.findingLines.length > 0) {
|
|
791
|
-
lines.push("### Key Findings & Remediation", ...parts.findingLines, "");
|
|
838
|
+
lines.push("### Key Findings & Remediation", ...parts.findingLines.map((line) => sanitizeDisplayText(line)), "");
|
|
792
839
|
}
|
|
793
840
|
if (parts.tableLines.length > 0) {
|
|
794
|
-
lines.push("### Verification Summary", ...parts.tableLines, "");
|
|
841
|
+
lines.push("### Verification Summary", ...parts.tableLines.map((line) => sanitizeDisplayText(line)), "");
|
|
795
842
|
}
|
|
796
843
|
if (parts.nextLines.length > 0) {
|
|
797
|
-
lines.push("### Next", ...parts.nextLines, "");
|
|
844
|
+
lines.push("### Next", ...parts.nextLines.map((line) => sanitizeDisplayText(line)), "");
|
|
798
845
|
}
|
|
799
846
|
if (parts.repoLines.length > 0) {
|
|
800
|
-
lines.push("### Final Repository State", ...parts.repoLines.map((line) => `- ${line}`), "");
|
|
847
|
+
lines.push("### Final Repository State", ...parts.repoLines.map((line) => `- ${sanitizeDisplayText(line)}`), "");
|
|
801
848
|
}
|
|
802
849
|
return lines;
|
|
803
850
|
}
|
|
@@ -889,7 +936,7 @@ function stripApprovalModel(line: string): string {
|
|
|
889
936
|
return line.replace(/auditor\s+\S+\s+approved/, "auditor approved");
|
|
890
937
|
}
|
|
891
938
|
function trailerBullet(line: string): string {
|
|
892
|
-
return `• ${line.replace(/^—\s*/, "")}`;
|
|
939
|
+
return `• ${sanitizeDisplayText(line).replace(/^—\s*/, "")}`;
|
|
893
940
|
}
|
|
894
941
|
|
|
895
942
|
/** v0.38.25: the audit-goal counts line — verdict proof ONLY, built from
|
|
@@ -113,7 +113,7 @@ export interface CommandDeps {
|
|
|
113
113
|
releaseContinuationDispatchStandDown: () => void;
|
|
114
114
|
releaseInitialSessionLoadBarrier: () => void;
|
|
115
115
|
resolveCarryover: (ctx: ExtensionContext, trigger: "goal" | "loop" | "list") => boolean;
|
|
116
|
-
safeSteerUser: (ctx: ExtensionContext, text: string) => boolean;
|
|
116
|
+
resetLengthExhaustionEpisodes: () => void; safeSteerUser: (ctx: ExtensionContext, text: string) => boolean;
|
|
117
117
|
scheduleContinuation: (ctx: ExtensionContext, force?: boolean, delayMs?: number) => void;
|
|
118
118
|
scheduleSessionTimeout: (callback: () => void, delayMs: number) => NodeJS.Timeout;
|
|
119
119
|
createGoal: (objective: string, ctx: ExtensionContext, policy?: "goal" | "list") => Goal;
|
|
@@ -138,7 +138,7 @@ let listQueue: CommandDeps["listQueue"], notifyExternal: CommandDeps["notifyExte
|
|
|
138
138
|
archiveCurrentGoal: CommandDeps["archiveCurrentGoal"], healGoalPolicy: CommandDeps["healGoalPolicy"], startDrafting: CommandDeps["startDrafting"], warnIfStaleAtEntry: CommandDeps["warnIfStaleAtEntry"], queuePendingListOperation: CommandDeps["queuePendingListOperation"], freshCtx: CommandDeps["freshCtx"],
|
|
139
139
|
freshCtxForGeneration: CommandDeps["freshCtxForGeneration"], goStaleTerminal: CommandDeps["goStaleTerminal"], groupOpenChildren: CommandDeps["groupOpenChildren"], activateNextListItem: CommandDeps["activateNextListItem"], clearMainModelRecoveryTimer: CommandDeps["clearMainModelRecoveryTimer"], mainModelRecoveryTimerActive: CommandDeps["mainModelRecoveryTimerActive"], continuationDispatchPending: CommandDeps["continuationDispatchPending"], resetContinuationDispatchState: CommandDeps["resetContinuationDispatchState"],
|
|
140
140
|
isCompletionAuditRecoveryPending: CommandDeps["isCompletionAuditRecoveryPending"], markCompletionAuditRecoveryPending: CommandDeps["markCompletionAuditRecoveryPending"], retryStoredCompletionAudit: CommandDeps["retryStoredCompletionAudit"], probeMainModelRecovery: CommandDeps["probeMainModelRecovery"], releaseContinuationDispatchStandDown: CommandDeps["releaseContinuationDispatchStandDown"],
|
|
141
|
-
releaseInitialSessionLoadBarrier: CommandDeps["releaseInitialSessionLoadBarrier"], resolveCarryover: CommandDeps["resolveCarryover"], safeSteerUser: CommandDeps["safeSteerUser"], scheduleContinuation: CommandDeps["scheduleContinuation"], scheduleSessionTimeout: CommandDeps["scheduleSessionTimeout"],
|
|
141
|
+
releaseInitialSessionLoadBarrier: CommandDeps["releaseInitialSessionLoadBarrier"], resolveCarryover: CommandDeps["resolveCarryover"], resetLengthExhaustionEpisodes: CommandDeps["resetLengthExhaustionEpisodes"], safeSteerUser: CommandDeps["safeSteerUser"], scheduleContinuation: CommandDeps["scheduleContinuation"], scheduleSessionTimeout: CommandDeps["scheduleSessionTimeout"],
|
|
142
142
|
createGoal: CommandDeps["createGoal"], fireReviewer: CommandDeps["fireReviewer"], openSettingsUI: CommandDeps["openSettingsUI"], manuallyResumeMainModelRecovery: CommandDeps["manuallyResumeMainModelRecovery"], activeGoalCommand: CommandDeps["activeGoalCommand"],
|
|
143
143
|
activeGoalStatusCommand: CommandDeps["activeGoalStatusCommand"], activeGoalSurfaceCommand: CommandDeps["activeGoalSurfaceCommand"], goalNoun: CommandDeps["goalNoun"], displaySlice: CommandDeps["displaySlice"], shortObj: CommandDeps["shortObj"];
|
|
144
144
|
|
|
@@ -148,7 +148,7 @@ export function createGoalCommands(d: CommandDeps): void {
|
|
|
148
148
|
archiveCurrentGoal = d.archiveCurrentGoal; healGoalPolicy = d.healGoalPolicy; startDrafting = d.startDrafting; warnIfStaleAtEntry = d.warnIfStaleAtEntry; queuePendingListOperation = d.queuePendingListOperation; freshCtx = d.freshCtx;
|
|
149
149
|
freshCtxForGeneration = d.freshCtxForGeneration; goStaleTerminal = d.goStaleTerminal; groupOpenChildren = d.groupOpenChildren; activateNextListItem = d.activateNextListItem; clearMainModelRecoveryTimer = d.clearMainModelRecoveryTimer; mainModelRecoveryTimerActive = d.mainModelRecoveryTimerActive; continuationDispatchPending = d.continuationDispatchPending; resetContinuationDispatchState = d.resetContinuationDispatchState;
|
|
150
150
|
isCompletionAuditRecoveryPending = d.isCompletionAuditRecoveryPending; markCompletionAuditRecoveryPending = d.markCompletionAuditRecoveryPending; retryStoredCompletionAudit = d.retryStoredCompletionAudit; probeMainModelRecovery = d.probeMainModelRecovery; releaseContinuationDispatchStandDown = d.releaseContinuationDispatchStandDown;
|
|
151
|
-
releaseInitialSessionLoadBarrier = d.releaseInitialSessionLoadBarrier; resolveCarryover = d.resolveCarryover; safeSteerUser = d.safeSteerUser; scheduleContinuation = d.scheduleContinuation; scheduleSessionTimeout = d.scheduleSessionTimeout;
|
|
151
|
+
releaseInitialSessionLoadBarrier = d.releaseInitialSessionLoadBarrier; resolveCarryover = d.resolveCarryover; resetLengthExhaustionEpisodes = d.resetLengthExhaustionEpisodes; safeSteerUser = d.safeSteerUser; scheduleContinuation = d.scheduleContinuation; scheduleSessionTimeout = d.scheduleSessionTimeout;
|
|
152
152
|
createGoal = d.createGoal; fireReviewer = d.fireReviewer; openSettingsUI = d.openSettingsUI; manuallyResumeMainModelRecovery = d.manuallyResumeMainModelRecovery; activeGoalCommand = d.activeGoalCommand;
|
|
153
153
|
activeGoalStatusCommand = d.activeGoalStatusCommand; activeGoalSurfaceCommand = d.activeGoalSurfaceCommand; goalNoun = d.goalNoun; displaySlice = d.displaySlice; shortObj = d.shortObj;
|
|
154
154
|
agentsSnapshot = d.agentsSnapshot;
|
|
@@ -589,6 +589,10 @@ async function cmdResume(ctx: ExtensionContext): Promise<void> {
|
|
|
589
589
|
updateGoal({ status: "active", pauseReason: undefined, pauseSuggestedAction: undefined, pauseKind: undefined, pauseOptions: undefined, pauseRecommended: undefined, pauseResumeAt: undefined, interruptedAt: undefined, interruptedReason: undefined, autoResumedAt: undefined, autoResumedEvent: undefined, ...(staleEntry ? { interruptedAt: nowIso(), interruptedReason: "resumed in a stale session" } : {}), ...(usage ? { usage } : {}) }, ctx);
|
|
590
590
|
if (staleEntry) return;
|
|
591
591
|
releaseAuditorSurface();
|
|
592
|
+
// A manual resume starts a fresh relentless cycle: a user pause between
|
|
593
|
+
// an episode-1 exhaustion wedge and the next one must not make that
|
|
594
|
+
// wedge park one cycle early.
|
|
595
|
+
resetLengthExhaustionEpisodes();
|
|
592
596
|
// A stored completion claim is a direct-audit resume, not an agent turn.
|
|
593
597
|
// Keeping the claim while merely scheduling a continuation left manual
|
|
594
598
|
// pause/resume with an ACTIVE goal that no timer would ever consume.
|
|
@@ -1420,7 +1420,7 @@ export function sendStallEscalation(ctx: ExtensionContext, nudges: number): void
|
|
|
1420
1420
|
if (supervisorPaused(state)) return;
|
|
1421
1421
|
// Audit 2026-09-07 (HIGH): a stall nudge must not resurrect a stood-down
|
|
1422
1422
|
// chain — same abort-latch reasoning as sendContinuation.
|
|
1423
|
-
if (flags.sessionHandoffPending || flags.initialSessionLoadPending || !flags.extensionApi || flags.extensionApiStale || continuationDispatchStoodDown || pendingContinuationDispatch || flags.abortedStandDown) return;
|
|
1423
|
+
if (flags.sessionHandoffPending || flags.initialSessionLoadPending || !flags.extensionApi || flags.extensionApiStale || flags.staleTerminalDone || flags.zombieStoodDown || continuationDispatchStoodDown || pendingContinuationDispatch || flags.abortedStandDown) return;
|
|
1424
1424
|
if (!state.goal || !guardGoalBeforeContinuation(ctx, "stall-escalation")) return;
|
|
1425
1425
|
const remaining = HEARTBEAT_MAX_NUDGES - nudges;
|
|
1426
1426
|
const text = [
|
|
@@ -1464,7 +1464,7 @@ export function sendLengthContinue(ctx: ExtensionContext, consecutive: number):
|
|
|
1464
1464
|
if (supervisorPaused(state)) return;
|
|
1465
1465
|
// Audit 2026-09-07 (HIGH): a length nudge must not resurrect a stood-down
|
|
1466
1466
|
// chain — same abort-latch reasoning as sendContinuation.
|
|
1467
|
-
if (flags.sessionHandoffPending || flags.initialSessionLoadPending || !flags.extensionApi || flags.extensionApiStale || continuationDispatchStoodDown || pendingContinuationDispatch || flags.abortedStandDown) return;
|
|
1467
|
+
if (flags.sessionHandoffPending || flags.initialSessionLoadPending || !flags.extensionApi || flags.extensionApiStale || flags.staleTerminalDone || flags.zombieStoodDown || continuationDispatchStoodDown || pendingContinuationDispatch || flags.abortedStandDown) return;
|
|
1468
1468
|
if (state.goal && !guardGoalBeforeContinuation(ctx, "length-continuation")) return;
|
|
1469
1469
|
// v0.38.66 (PR #55, FOF11): active goals get completion-aware recovery
|
|
1470
1470
|
// text — a truncated turn must offer closure, not another blind work
|
|
@@ -1525,7 +1525,34 @@ export function buildPostCompactResync(briefExcerpt?: string): string {
|
|
|
1525
1525
|
return lines.join("\n") + "\n\n";
|
|
1526
1526
|
}
|
|
1527
1527
|
|
|
1528
|
-
|
|
1528
|
+
/**
|
|
1529
|
+
* v0.38.69 (Antigravity port, 09-04 borrow candidate #1): per-repo pitfall
|
|
1530
|
+
* registry. `.pi-glla/pitfalls.md` holds distilled, answer-agnostic rakes
|
|
1531
|
+
* (never ledger prose — the ledger is forensics, never distilled). Read at
|
|
1532
|
+
* goal start (and re-read when edited mid-goal); absent/blank/unreadable
|
|
1533
|
+
* resolves absent so repos without one render byte-identical prompts.
|
|
1534
|
+
*/
|
|
1535
|
+
export const PITFALLS_BRIEF_MAX_CHARS = 1500;
|
|
1536
|
+
|
|
1537
|
+
export function readPitfallsBrief(cwd: string): string | undefined {
|
|
1538
|
+
let body: string;
|
|
1539
|
+
try {
|
|
1540
|
+
body = fs.readFileSync(path.join(cwd, ".pi-glla", "pitfalls.md"), "utf8");
|
|
1541
|
+
} catch {
|
|
1542
|
+
return undefined;
|
|
1543
|
+
}
|
|
1544
|
+
const trimmed = body.trim();
|
|
1545
|
+
if (!trimmed) return undefined;
|
|
1546
|
+
return trimmed.length > PITFALLS_BRIEF_MAX_CHARS
|
|
1547
|
+
? trimmed.slice(0, PITFALLS_BRIEF_MAX_CHARS)
|
|
1548
|
+
: trimmed;
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
/** Goals already ledgered for their pitfalls consult this process — the
|
|
1552
|
+
* ledger records the consult once per goal, never once per turn. */
|
|
1553
|
+
const pitfallsLedgeredGoals = new Set<string>();
|
|
1554
|
+
|
|
1555
|
+
export function continuationPrompt(goal: Goal, opts: { includeRestartDetail?: boolean; pitfallsBrief?: string } = {}): string {
|
|
1529
1556
|
// Read the .md file as the template, then substitute {{tokens}}.
|
|
1530
1557
|
// For v0.1.0 we inline-substitute so we don't need fs at runtime.
|
|
1531
1558
|
const next = findNextPendingTask(goal.taskList?.tasks ?? []);
|
|
@@ -1583,6 +1610,20 @@ export function continuationPrompt(goal: Goal, opts: { includeRestartDetail?: bo
|
|
|
1583
1610
|
if (loadSettings(settingsCwd).visionAssist !== false) {
|
|
1584
1611
|
directives.push(VISION_ASSIST_GUIDANCE);
|
|
1585
1612
|
}
|
|
1613
|
+
// v0.38.69 (Antigravity port): the repo pitfall registry rides the
|
|
1614
|
+
// continuation prompt — consulted at goal start, re-read when edited.
|
|
1615
|
+
// Explicit opts win (tests); otherwise the repo file resolves, absent
|
|
1616
|
+
// keeping the prompt byte-identical for repos without one.
|
|
1617
|
+
const pitfallsBrief = opts.pitfallsBrief ?? readPitfallsBrief(settingsCwd);
|
|
1618
|
+
if (pitfallsBrief?.trim()) {
|
|
1619
|
+
directives.push(
|
|
1620
|
+
`## REPO PITFALLS (distilled — consult before acting)\n\nThese rakes already caught this repo. Check your plan against them before the first tool call and before every risky step:\n\n${pitfallsBrief.trim()}`,
|
|
1621
|
+
);
|
|
1622
|
+
if (!pitfallsLedgeredGoals.has(goal.id)) {
|
|
1623
|
+
pitfallsLedgeredGoals.add(goal.id);
|
|
1624
|
+
appendLedger(settingsCwd, "pitfalls_consulted", { goalId: goal.id });
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1586
1627
|
if (effSettings.aggressiveMode && isFullAuditObjective(goal.objective)) {
|
|
1587
1628
|
directives.push(
|
|
1588
1629
|
"## FULL-AUDIT MODE (aggressiveMode + survey objective)\n\nThis objective is a survey, not a single fix. Spawn 3+ `scout` subagents NOW — one per subsystem, in a single message so they run in parallel — synthesize their findings, and call `propose_task_list` with the result. Do not start fixing before the task list exists.",
|