pi-goal-list-loop-audit 0.28.9 → 0.28.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@ Install:
|
|
|
27
27
|
pi install npm:pi-goal-list-loop-audit
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
Five top-level commands — `/goal`, `/list`, `/loop`, `/glla`, `/review`:
|
|
31
31
|
|
|
32
32
|
```
|
|
33
33
|
/goal # drafting: agent grills, you Confirm
|
|
@@ -40,12 +40,13 @@ Four top-level commands, that's all:
|
|
|
40
40
|
/goal cancel # abort
|
|
41
41
|
/goal tweak "<new objective>" # edit in place (Confirm dialog)
|
|
42
42
|
/goal archive # archived goals, newest first
|
|
43
|
-
/glla #
|
|
43
|
+
/glla # settings UI table · /glla key=value · /glla stats · /glla audits [N|full] · /glla postaudit · /glla autoaccept=on
|
|
44
44
|
/list fix the login bug, add dark mode, write docs # dump it — the agent shapes it into items, one Confirm
|
|
45
45
|
/list plan.md # file detected → bulk import, one Confirm (sisyphus/Ralph style)
|
|
46
46
|
/list <paste a checklist> # multi-line paste → same batch flow
|
|
47
47
|
/list "fix the flaky test. Done when: npm test green" # explicit contract → added directly, no interview
|
|
48
48
|
/list # show the list (add/import are optional no-op aliases — detection routes everything)
|
|
49
|
+
```
|
|
49
50
|
|
|
50
51
|
(Or just say it: "queue these 10 things…" — the agent manages the list too.)
|
|
51
52
|
|
|
@@ -53,6 +54,8 @@ Four top-level commands, that's all:
|
|
|
53
54
|
`/list next <n>` or the agent's `list_activate` tool picks any item — with
|
|
54
55
|
subagents, what gets worked next is a choice, not a position. Numbering always
|
|
55
56
|
matches `/list show`.
|
|
57
|
+
|
|
58
|
+
```
|
|
56
59
|
/list # show active + waiting items
|
|
57
60
|
/list next # skip current, activate next
|
|
58
61
|
/list remove <n> # drop item n from the list
|
|
@@ -67,6 +70,7 @@ matches `/list show`.
|
|
|
67
70
|
/loop start "keep improving SPEC.md" measure=none max=20 # metricless with an explicit cap (v0.23.0)
|
|
68
71
|
/loop status # iteration, best, stall, recent values
|
|
69
72
|
/loop stop # halt with summary
|
|
73
|
+
/review <goal-id> [off|on|auto|aggressive] # re-review an archived goal (bypasses the trigger gates)
|
|
70
74
|
```
|
|
71
75
|
|
|
72
76
|
**Metricless loops** (`measure=none`): for genuinely endless work — an
|
|
@@ -311,7 +315,7 @@ prompts/
|
|
|
311
315
|
goal-loop-forever-draft.md # /loop drafting prompt
|
|
312
316
|
scripts/
|
|
313
317
|
smoke.sh # live integration harness (tmux + real models)
|
|
314
|
-
tests/ #
|
|
318
|
+
tests/ # 545 tests across 58 files, no live pi required (mock-ctx harness drives the orchestrator)
|
|
315
319
|
docs/DESIGN.md # architectural decisions
|
|
316
320
|
PLAN.md # milestones, decisions, gates
|
|
317
321
|
```
|
|
@@ -144,11 +144,11 @@ export function buildStatusText(state: State, audit?: AuditDisplayProgress | nul
|
|
|
144
144
|
return `glla: ${g.policy} ${paint(theme, "error", "⚠ interrupted — stale handle · auto-resumes on pi restart")}`;
|
|
145
145
|
}
|
|
146
146
|
// v0.24.7: list policy gets its own wording — a queue item is not a goal.
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
//
|
|
147
|
+
// v0.28.11 (U10): goal policy joins it — "list 29" read as a command
|
|
148
|
+
// fragment; "29 queued" says what the number IS. Both policies now
|
|
149
|
+
// render "… · N queued".
|
|
150
150
|
const n = state.list?.length ?? 0;
|
|
151
|
-
const queue = n === 0 ? "" :
|
|
151
|
+
const queue = n === 0 ? "" : ` · ${n} queued`;
|
|
152
152
|
const tasks = g.taskList ? ` ${countDone(g)}/${countTotal(g)} tasks ·` : "";
|
|
153
153
|
return `glla: ${g.policy} ${paint(theme, "success", "●")}${tasks} ${fmtElapsed(now - Date.parse(g.createdAt))}${queue}`;
|
|
154
154
|
}
|
|
@@ -275,7 +275,7 @@ function goalLines(g: Goal, state: State, audit: AuditDisplayProgress | null | u
|
|
|
275
275
|
const queue = state.list?.length ?? 0;
|
|
276
276
|
const footer = isList
|
|
277
277
|
? `${queue > 0 ? `${queue} queued · ` : ""}/list · /glla`
|
|
278
|
-
: `${queue > 0 ?
|
|
278
|
+
: `${queue > 0 ? `${queue} queued · ` : ""}/goal status · /glla`;
|
|
279
279
|
lines.push(`└─ ${paint(theme, "dim", footer)}`);
|
|
280
280
|
return lines;
|
|
281
281
|
}
|
package/extensions/loops/goal.ts
CHANGED
|
@@ -758,6 +758,9 @@ function persistState(ctx: ExtensionContext): void {
|
|
|
758
758
|
// once per recovery. The TUI flag (buildWidgetLines) carries the standing
|
|
759
759
|
// state; these notifies are the LOUD part.
|
|
760
760
|
let persistenceDegradedNotified = false;
|
|
761
|
+
|
|
762
|
+
/** v0.28.11 (U9): objective-first notifies — truncate long objectives. */
|
|
763
|
+
const shortObj = (s: string): string => (s.length > 90 ? `${s.slice(0, 87)}…` : s);
|
|
761
764
|
function notifyPersistenceState(ctx: ExtensionContext): void {
|
|
762
765
|
if (isPersistenceDegraded() && !persistenceDegradedNotified) {
|
|
763
766
|
persistenceDegradedNotified = true;
|
|
@@ -885,7 +888,7 @@ function fireReviewer(
|
|
|
885
888
|
// to still count as "proposed" in the report + notify. Now the
|
|
886
889
|
// failure is LOUD and the proposal goes uncounted.
|
|
887
890
|
ctx.ui.notify(
|
|
888
|
-
`
|
|
891
|
+
`Postaudit /goal proposal NOT delivered: ${err instanceof Error ? err.message : String(err)} — the follow-up never reached the session. Restart pi if the session was just replaced.`,
|
|
889
892
|
"warning",
|
|
890
893
|
);
|
|
891
894
|
return false;
|
|
@@ -895,7 +898,7 @@ function fireReviewer(
|
|
|
895
898
|
ledger: (type, value) => appendLedger(ctx.cwd, type, value),
|
|
896
899
|
});
|
|
897
900
|
if (!outcome.fired && outcome.suppressedReason && opts.manual) {
|
|
898
|
-
ctx.ui.notify(`
|
|
901
|
+
ctx.ui.notify(`Postaudit suppressed: ${outcome.suppressedReason}`, "info");
|
|
899
902
|
}
|
|
900
903
|
// v0.27.5: surface the silent review to interactive users. The internal
|
|
901
904
|
// runReviewer notify fires DURING the goal-completion handler, easy to
|
|
@@ -911,7 +914,7 @@ function fireReviewer(
|
|
|
911
914
|
);
|
|
912
915
|
}
|
|
913
916
|
} catch (err) {
|
|
914
|
-
ctx.ui.notify(`
|
|
917
|
+
ctx.ui.notify(`Postaudit failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`, "warning");
|
|
915
918
|
}
|
|
916
919
|
}
|
|
917
920
|
|
|
@@ -1088,10 +1091,10 @@ async function cmdSet(args: string, ctx: ExtensionContext, skipDraft = false): P
|
|
|
1088
1091
|
// v0.28.1 (S3): the goal is persisted — mark the interrupt so the next
|
|
1089
1092
|
// fresh session auto-resumes, and tell the truth instead of "starting now".
|
|
1090
1093
|
updateGoal({ interruptedAt: nowIso(), interruptedReason: "created in a stale session" }, ctx);
|
|
1091
|
-
ctx.ui.notify(`Goal ${goal.
|
|
1094
|
+
ctx.ui.notify(`Goal saved: ${shortObj(goal.objective)} — safe in .pi-glla/, but this stale process can't send continuations. Restart pi and it auto-resumes. (id: ${goal.id})`, "warning");
|
|
1092
1095
|
return;
|
|
1093
1096
|
}
|
|
1094
|
-
ctx.ui.notify(`Goal ${goal.
|
|
1097
|
+
ctx.ui.notify(`Goal started: ${shortObj(goal.objective)} — the auditor will verify on completion. (id: ${goal.id})`, "info");
|
|
1095
1098
|
scheduleContinuation(ctx, true);
|
|
1096
1099
|
}
|
|
1097
1100
|
|
|
@@ -3453,17 +3456,17 @@ async function cmdToolOverride(args: string, ctx: ExtensionContext): Promise<voi
|
|
|
3453
3456
|
if (action === "allow") {
|
|
3454
3457
|
const allow = current.allow ?? [];
|
|
3455
3458
|
if (!allow.includes(tool)) apply({ allow: [...allow, tool] });
|
|
3456
|
-
ctx.ui.notify(`
|
|
3459
|
+
ctx.ui.notify(`"${tool}" is now always visible to the agent (project override saved).`, "info");
|
|
3457
3460
|
} else if (action === "hide") {
|
|
3458
3461
|
const hide = current.hide ?? [];
|
|
3459
3462
|
if (!hide.includes(tool)) apply({ hide: [...hide, tool] });
|
|
3460
|
-
ctx.ui.notify(`
|
|
3463
|
+
ctx.ui.notify(`"${tool}" is now always hidden from the agent (project override saved).`, "info");
|
|
3461
3464
|
} else if (action === "unallow") {
|
|
3462
3465
|
apply({ allow: (current.allow ?? []).filter((t) => t !== tool) });
|
|
3463
|
-
ctx.ui.notify(`
|
|
3466
|
+
ctx.ui.notify(`"${tool}" visibility override removed — the session decides again.`, "info");
|
|
3464
3467
|
} else {
|
|
3465
3468
|
apply({ hide: (current.hide ?? []).filter((t) => t !== tool) });
|
|
3466
|
-
ctx.ui.notify(`
|
|
3469
|
+
ctx.ui.notify(`"${tool}" hide override removed — the session decides again.`, "info");
|
|
3467
3470
|
}
|
|
3468
3471
|
return;
|
|
3469
3472
|
}
|
|
@@ -3490,7 +3493,12 @@ async function cmdToolOverride(args: string, ctx: ExtensionContext): Promise<voi
|
|
|
3490
3493
|
}
|
|
3491
3494
|
cfg[tool] = toolCfg;
|
|
3492
3495
|
apply({ perToolConfig: cfg });
|
|
3493
|
-
ctx.ui.notify(
|
|
3496
|
+
ctx.ui.notify(
|
|
3497
|
+
action === "set"
|
|
3498
|
+
? `"${tool}" setting saved: ${kv.slice(0, kv.indexOf("="))} = ${JSON.stringify(toolCfg[kv.slice(0, kv.indexOf("="))])} (project override).`
|
|
3499
|
+
: `"${tool}" setting "${kv}" removed — back to the built-in default.`,
|
|
3500
|
+
"info",
|
|
3501
|
+
);
|
|
3494
3502
|
return;
|
|
3495
3503
|
}
|
|
3496
3504
|
ctx.ui.notify(`Unknown tooloverride action: ${action}. Use: list | allow | hide | unallow | unhide | set | unset.`, "warning");
|
|
@@ -3530,7 +3538,7 @@ async function cmdReviewerSettings(ctx: ExtensionContext): Promise<void> {
|
|
|
3530
3538
|
const cfg = load();
|
|
3531
3539
|
let choice: string | undefined;
|
|
3532
3540
|
try {
|
|
3533
|
-
choice = await ctx.ui.select("
|
|
3541
|
+
choice = await ctx.ui.select("Postaudit — post-completion follow-up enqueuer (project settings)", reviewerMenuOptions(cfg));
|
|
3534
3542
|
} catch {
|
|
3535
3543
|
return;
|
|
3536
3544
|
}
|
|
@@ -3557,8 +3565,10 @@ async function cmdReviewerSettings(ctx: ExtensionContext): Promise<void> {
|
|
|
3557
3565
|
const n = Number(v?.trim());
|
|
3558
3566
|
if (Number.isSafeInteger(n) && n >= 1 && n <= 100) save({ maxReviewsPerDay: n });
|
|
3559
3567
|
}
|
|
3560
|
-
} catch {
|
|
3561
|
-
|
|
3568
|
+
} catch (err) {
|
|
3569
|
+
// v0.28.11 (E7): a swallowed save failure made the user believe the
|
|
3570
|
+
// toggle landed. Loud now.
|
|
3571
|
+
ctx.ui.notify(`Postaudit setting NOT saved: ${err instanceof Error ? err.message : String(err)} — check .pi-glla/settings.json permissions.`, "warning");
|
|
3562
3572
|
}
|
|
3563
3573
|
}
|
|
3564
3574
|
}
|
|
@@ -4041,7 +4051,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
4041
4051
|
handler: settingsHandler,
|
|
4042
4052
|
});
|
|
4043
4053
|
pi.registerCommand("review", {
|
|
4044
|
-
description: "Manually run the
|
|
4054
|
+
description: "Manually run the postaudit on an archived goal: /review <goal-id> [off|on|auto|aggressive] — extracts findings, writes a report to .pi-glla/reviews/, cascades per the mode (auto/aggressive = no Confirms). Bypasses the trigger gates (explicit user request).",
|
|
4045
4055
|
handler: (args: string, ctx: ExtensionContext) => { rememberCtx(ctx); return cmdReview(args, ctx); },
|
|
4046
4056
|
});
|
|
4047
4057
|
pi.registerCommand("list", {
|
package/extensions/reviewer.ts
CHANGED
|
@@ -226,27 +226,27 @@ export function runReviewer(
|
|
|
226
226
|
deps: ReviewerDeps,
|
|
227
227
|
): ReviewerOutcome {
|
|
228
228
|
const none = (suppressedReason: string): ReviewerOutcome => ({ fired: false, suppressedReason, enqueued: 0, proposed: 0 });
|
|
229
|
-
if (!config.enabled && !deps.manual) return none("
|
|
229
|
+
if (!config.enabled && !deps.manual) return none("the postaudit is disabled (/glla postaudit → Enabled)");
|
|
230
230
|
// v0.27.5: "off" mode is the user-friendly way to silence the postaudit
|
|
231
231
|
// — equivalent to enabled=false but exposed via the postaudit menu.
|
|
232
|
-
if (config.mode === "off" && !deps.manual) return none("postaudit mode
|
|
232
|
+
if (config.mode === "off" && !deps.manual) return none("postaudit mode is off (/glla postaudit → Mode)");
|
|
233
233
|
const event = source.kind === "goal" ? `${source.terminal}` : "list-complete";
|
|
234
234
|
if (!deps.manual) {
|
|
235
|
-
if (config.doNotFireOn.includes(event)) return none(`
|
|
236
|
-
if (source.kind === "goal" && source.terminal !== "goal-complete") return none(`
|
|
237
|
-
if (!config.fireOn.includes(source.kind === "goal" ? "goal-complete" : "list-complete")) return none("
|
|
235
|
+
if (config.doNotFireOn.includes(event)) return none(`this event type (${event}) is excluded in /glla postaudit → fire-on`);
|
|
236
|
+
if (source.kind === "goal" && source.terminal !== "goal-complete") return none(`the goal ended as ${source.terminal}, not a completion — no follow-up fires`);
|
|
237
|
+
if (!config.fireOn.includes(source.kind === "goal" ? "goal-complete" : "list-complete")) return none("this event type is excluded in /glla postaudit → fire-on");
|
|
238
238
|
// v0.26.2: in auto mode the queue emptying is the cascade's natural
|
|
239
239
|
// rhythm, not a runaway — the refire window must not strangle it.
|
|
240
240
|
// (The per-day cap below still bounds everything.)
|
|
241
241
|
const refireWindowApplies = !(config.mode === "auto" && source.kind === "list");
|
|
242
242
|
if (refireWindowApplies && reviewerFiredRecently(deps.ledgerEntries, REVIEWER_REFIRE_WINDOW_MS, deps.nowMs)) {
|
|
243
243
|
deps.ledger("reviewer_suppressed", { reason: "refire-window", goalId: source.goalId });
|
|
244
|
-
return none("
|
|
244
|
+
return none("a postaudit ran within the last 5 minutes (runaway prevention)");
|
|
245
245
|
}
|
|
246
246
|
const today = reviewsToday(deps.ledgerEntries, deps.nowMs);
|
|
247
247
|
if (today >= config.maxReviewsPerDay) {
|
|
248
248
|
deps.ledger("reviewer_suppressed", { reason: "day-cap", count: today, cap: config.maxReviewsPerDay, goalId: source.goalId });
|
|
249
|
-
return none(`
|
|
249
|
+
return none(`daily postaudit cap reached (${today}/${config.maxReviewsPerDay}) — /glla postaudit → Max reviews`);
|
|
250
250
|
}
|
|
251
251
|
}
|
|
252
252
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-goal-list-loop-audit",
|
|
3
|
-
"version": "0.28.
|
|
3
|
+
"version": "0.28.11",
|
|
4
4
|
"description": "Goal. Loop. Audit. Done. \u2014 a pi-coding-agent extension that supervises long-running work, with isolated auditor on each completion. Beat bamboozling by design: the auditor runs in a fresh session with no extensions, no skills, no editor \u2014 only the read tools needed to verify your goal.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "dracon",
|
|
@@ -93,21 +93,28 @@ Do NOT spawn more subagents of the failed type until quota resets — you will j
|
|
|
93
93
|
|
|
94
94
|
## DETACHED COMMIT DETECTION
|
|
95
95
|
|
|
96
|
-
If your commits keep getting rewritten away (same content, new SHA — or worse, content reverted), check
|
|
96
|
+
If your commits keep getting rewritten away (same content, new SHA — or worse, content reverted), check for an **auto-commit daemon** BEFORE diagnosing yourself as stuck or broken. **Skip this section entirely if your rig has no auto-committer** — most rigs don't. (The maintainer's rigs run one called `dracon-sync`; yours may run a different one, or none.)
|
|
97
|
+
|
|
98
|
+
Generic forensics (safe everywhere):
|
|
97
99
|
|
|
98
100
|
```bash
|
|
99
101
|
git reflog --date=iso | grep -E "filter-branch|filter-repo"
|
|
100
102
|
git reflog --date=iso | grep -E ": reset:"
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
If those show rewrites you didn't make, find the daemon (example for the maintainer's rig — adapt the pattern to yours):
|
|
106
|
+
|
|
107
|
+
```bash
|
|
101
108
|
ps -fea | grep -E "dracon-sync|filter-repo" | grep -v grep
|
|
102
109
|
```
|
|
103
110
|
|
|
104
|
-
|
|
111
|
+
An auto-committer may be rewriting your commits (e.g. `dracon-sync daemon` running `auto_rewrite_large_blobs`). The fix is NOT to keep re-committing — it is to:
|
|
105
112
|
|
|
106
|
-
1. Pause the daemon (`dracon-sync pause`, or write the `.pi-glla/.pause-auto-commit` sentinel via the goal tools).
|
|
107
|
-
2. Investigate the rewrite trigger (daemon config `max_push_blob_bytes`, `auto_rewrite_large_blobs`).
|
|
113
|
+
1. Pause the daemon if one exists (on the maintainer's rigs: `dracon-sync pause`, or write the `.pi-glla/.pause-auto-commit` sentinel via the goal tools).
|
|
114
|
+
2. Investigate the rewrite trigger (for `dracon-sync`: daemon config `max_push_blob_bytes`, `auto_rewrite_large_blobs`).
|
|
108
115
|
3. Add `.pi-glla/` to the daemon's exclude list.
|
|
109
116
|
|
|
110
|
-
Do NOT conclude "the loop is too eager" or "I am broken" before checking what
|
|
117
|
+
Do NOT conclude "the loop is too eager" or "I am broken" before checking what a daemon is doing — a daemon rewriting your commits makes YOUR loop look broken when it is not.
|
|
111
118
|
|
|
112
119
|
## TASK WORKFLOW
|
|
113
120
|
|