pi-goal-list-loop-audit 0.28.10 → 0.28.12
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
|
@@ -209,7 +209,7 @@ No external watchdog plugin needed.
|
|
|
209
209
|
/glla quotaretryminutes=60 # minutes before auto-retrying a quota-exhausted auditor
|
|
210
210
|
/glla stuckmax=10 # consecutive stuck interventions before a loop stops (default 5)
|
|
211
211
|
/glla auditfeedbackchars=800 # cap the executor-visible auditor report (default 0 = full report)
|
|
212
|
-
/glla autoaccept=on # drafts ACTIVATE without the Confirm dialog
|
|
212
|
+
/glla autoaccept=on # drafts ACTIVATE without the Confirm dialo... (every draft dialog also offers "always auto-accept" inline)unattended rigs)
|
|
213
213
|
/glla project tokenlimit=500 # rare per-project override
|
|
214
214
|
```
|
|
215
215
|
|
|
@@ -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
|
@@ -259,6 +259,36 @@ function warnIfStaleAtEntry(ctx: ExtensionContext, what: string): boolean {
|
|
|
259
259
|
return true;
|
|
260
260
|
}
|
|
261
261
|
|
|
262
|
+
/** v0.28.12: draft-class confirm with the auto-accept escape hatch SURFACED.
|
|
263
|
+
* The polis incident: a user sat through a 14-item batch Confirm having
|
|
264
|
+
* already reviewed every item during drafting, never knowing /glla
|
|
265
|
+
* autoaccept=on existed — the Yes/No dialog never mentioned it. Now every
|
|
266
|
+
* draft dialog is a 3-choice select; the ALWAYS choice persists project
|
|
267
|
+
* autoAcceptDrafts=true and accepts. Returns "stale" when the dialog can't
|
|
268
|
+
* render (session replacement) so call sites keep their NOT-a-rejection
|
|
269
|
+
* handling; falls back to the plain confirm if select is unavailable. */
|
|
270
|
+
type DraftChoice = "yes" | "no" | "stale";
|
|
271
|
+
async function confirmDraft(ctx: ExtensionContext, title: string, body: string): Promise<DraftChoice> {
|
|
272
|
+
const ALWAYS = "Yes — and always auto-accept drafts (sets autoAcceptDrafts for this project)";
|
|
273
|
+
try {
|
|
274
|
+
const choice = await ctx.ui.select(`${title}\n\n${body}`, ["Yes", ALWAYS, "No"]);
|
|
275
|
+
if (choice === ALWAYS) {
|
|
276
|
+
saveSettings("project", ctx.cwd, { autoAcceptDrafts: true });
|
|
277
|
+
appendLedger(ctx.cwd, "draft_autoaccept_enabled", { via: title });
|
|
278
|
+
ctx.ui.notify("Draft auto-accept ON for this project — future draft confirms are skipped. Undo: /glla autoaccept=off.", "info");
|
|
279
|
+
return "yes";
|
|
280
|
+
}
|
|
281
|
+
return choice === "Yes" ? "yes" : "no";
|
|
282
|
+
} catch (err) {
|
|
283
|
+
if (isStaleApiError(err)) return "stale";
|
|
284
|
+
try {
|
|
285
|
+
return (await ctx.ui.confirm(title, body)) ? "yes" : "no";
|
|
286
|
+
} catch (err2) {
|
|
287
|
+
return isStaleApiError(err2) ? "stale" : "no";
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
262
292
|
// The most recent ExtensionContext seen from any event or command handler.
|
|
263
293
|
// pi replaces sessions (newSession/fork/reload) and stale ctx throws on use,
|
|
264
294
|
// so timers must never capture a ctx — they read lastCtx at fire time.
|
|
@@ -758,6 +788,9 @@ function persistState(ctx: ExtensionContext): void {
|
|
|
758
788
|
// once per recovery. The TUI flag (buildWidgetLines) carries the standing
|
|
759
789
|
// state; these notifies are the LOUD part.
|
|
760
790
|
let persistenceDegradedNotified = false;
|
|
791
|
+
|
|
792
|
+
/** v0.28.11 (U9): objective-first notifies — truncate long objectives. */
|
|
793
|
+
const shortObj = (s: string): string => (s.length > 90 ? `${s.slice(0, 87)}…` : s);
|
|
761
794
|
function notifyPersistenceState(ctx: ExtensionContext): void {
|
|
762
795
|
if (isPersistenceDegraded() && !persistenceDegradedNotified) {
|
|
763
796
|
persistenceDegradedNotified = true;
|
|
@@ -885,7 +918,7 @@ function fireReviewer(
|
|
|
885
918
|
// to still count as "proposed" in the report + notify. Now the
|
|
886
919
|
// failure is LOUD and the proposal goes uncounted.
|
|
887
920
|
ctx.ui.notify(
|
|
888
|
-
`
|
|
921
|
+
`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
922
|
"warning",
|
|
890
923
|
);
|
|
891
924
|
return false;
|
|
@@ -895,7 +928,7 @@ function fireReviewer(
|
|
|
895
928
|
ledger: (type, value) => appendLedger(ctx.cwd, type, value),
|
|
896
929
|
});
|
|
897
930
|
if (!outcome.fired && outcome.suppressedReason && opts.manual) {
|
|
898
|
-
ctx.ui.notify(`
|
|
931
|
+
ctx.ui.notify(`Postaudit suppressed: ${outcome.suppressedReason}`, "info");
|
|
899
932
|
}
|
|
900
933
|
// v0.27.5: surface the silent review to interactive users. The internal
|
|
901
934
|
// runReviewer notify fires DURING the goal-completion handler, easy to
|
|
@@ -911,7 +944,7 @@ function fireReviewer(
|
|
|
911
944
|
);
|
|
912
945
|
}
|
|
913
946
|
} catch (err) {
|
|
914
|
-
ctx.ui.notify(`
|
|
947
|
+
ctx.ui.notify(`Postaudit failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`, "warning");
|
|
915
948
|
}
|
|
916
949
|
}
|
|
917
950
|
|
|
@@ -1088,10 +1121,10 @@ async function cmdSet(args: string, ctx: ExtensionContext, skipDraft = false): P
|
|
|
1088
1121
|
// v0.28.1 (S3): the goal is persisted — mark the interrupt so the next
|
|
1089
1122
|
// fresh session auto-resumes, and tell the truth instead of "starting now".
|
|
1090
1123
|
updateGoal({ interruptedAt: nowIso(), interruptedReason: "created in a stale session" }, ctx);
|
|
1091
|
-
ctx.ui.notify(`Goal ${goal.
|
|
1124
|
+
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
1125
|
return;
|
|
1093
1126
|
}
|
|
1094
|
-
ctx.ui.notify(`Goal ${goal.
|
|
1127
|
+
ctx.ui.notify(`Goal started: ${shortObj(goal.objective)} — the auditor will verify on completion. (id: ${goal.id})`, "info");
|
|
1095
1128
|
scheduleContinuation(ctx, true);
|
|
1096
1129
|
}
|
|
1097
1130
|
|
|
@@ -2612,10 +2645,11 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
2612
2645
|
details: {},
|
|
2613
2646
|
};
|
|
2614
2647
|
}
|
|
2648
|
+
const liveCtx = (execCtx as ExtensionContext | undefined) ?? ctx;
|
|
2615
2649
|
// v0.14.0: the interview floor — no Confirm until the user replied.
|
|
2616
2650
|
// v0.23.8: /glla autoaccept=on skips the floor AND the Confirm —
|
|
2617
2651
|
// the seed carries the intent (unattended rigs). Default off.
|
|
2618
|
-
const autoAccept = loadSettings(
|
|
2652
|
+
const autoAccept = loadSettings(liveCtx.cwd).autoAcceptDrafts === true;
|
|
2619
2653
|
if (!autoAccept) {
|
|
2620
2654
|
if (draftingUserReplies === 0) draftingBlockedProposals++;
|
|
2621
2655
|
const block = draftProposalBlock(draftingUserReplies, draftingBlockedProposals);
|
|
@@ -2630,7 +2664,6 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
2630
2664
|
details: {},
|
|
2631
2665
|
};
|
|
2632
2666
|
}
|
|
2633
|
-
const liveCtx = (execCtx as ExtensionContext | undefined) ?? ctx;
|
|
2634
2667
|
// v0.28.1 (S3): honest staleness warning before any Confirm attempt.
|
|
2635
2668
|
warnIfStaleAtEntry(liveCtx, "goal drafting");
|
|
2636
2669
|
// Multi-item list draft: one Confirm for the whole batch.
|
|
@@ -2645,21 +2678,19 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
2645
2678
|
liveCtx.ui.notify(`List batch auto-accepted (/glla autoaccept=on): ${p.items.length} items${batchActivates ? " — item 1 ACTIVATES now" : ""}.`, "info");
|
|
2646
2679
|
appendLedger(liveCtx.cwd, "draft_autoaccepted", { kind: "batch", count: p.items.length });
|
|
2647
2680
|
} else {
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
// v0.28.1 (T1): a stale
|
|
2681
|
+
const c = await confirmDraft(
|
|
2682
|
+
liveCtx,
|
|
2683
|
+
"Confirm list batch",
|
|
2684
|
+
`${p.items.length} items:\n${preview}${batchActivates ? "\n\n(List is empty — confirming ACTIVATES item 1 immediately as the active goal.)" : ""}`,
|
|
2685
|
+
);
|
|
2686
|
+
if (c === "stale") {
|
|
2687
|
+
// v0.28.1 (T1): a stale dialog is NOT a rejection — nothing was
|
|
2655
2688
|
// refused; the dialog simply can't render in a doomed process.
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
return { content: [{ type: "text", text: "The Confirm dialog could not render: pi invalidated this session's extension handle (session replacement). This is NOT a rejection — do NOT refine or re-propose. Tell the user to restart pi, then re-run the drafting flow." }], details: {} };
|
|
2660
|
-
}
|
|
2661
|
-
batchConfirmed = false;
|
|
2689
|
+
extensionApiStale = true;
|
|
2690
|
+
appendLedger(liveCtx.cwd, "extension_api_stale", { where: "batch confirm" });
|
|
2691
|
+
return { content: [{ type: "text", text: "The Confirm dialog could not render: pi invalidated this session's extension handle (session replacement). This is NOT a rejection — do NOT refine or re-propose. Tell the user to restart pi, then re-run the drafting flow." }], details: {} };
|
|
2662
2692
|
}
|
|
2693
|
+
batchConfirmed = c === "yes";
|
|
2663
2694
|
}
|
|
2664
2695
|
if (!batchConfirmed) {
|
|
2665
2696
|
return {
|
|
@@ -2696,17 +2727,14 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
2696
2727
|
liveCtx.ui.notify(`Draft auto-accepted (/glla autoaccept=on)${willActivate ? " — ACTIVATING now" : ""}: ${p.objective.trim().slice(0, 90)}`, "info");
|
|
2697
2728
|
appendLedger(liveCtx.cwd, "draft_autoaccepted", { kind: isListDraft ? "list" : "goal", objective: p.objective.trim().slice(0, 200) });
|
|
2698
2729
|
} else {
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
appendLedger(liveCtx.cwd, "extension_api_stale", { where: "draft confirm" });
|
|
2706
|
-
return { content: [{ type: "text", text: "The Confirm dialog could not render: pi invalidated this session's extension handle (session replacement). This is NOT a rejection — do NOT refine or re-propose. Tell the user to restart pi, then re-run the drafting flow." }], details: {} };
|
|
2707
|
-
}
|
|
2708
|
-
confirmed = false;
|
|
2730
|
+
const c = await confirmDraft(liveCtx, isListDraft ? "Confirm list item" : "Confirm goal", `${p.objective.trim()}${contractBlock}${activationNote}`);
|
|
2731
|
+
if (c === "stale") {
|
|
2732
|
+
// v0.28.1 (T1): a stale dialog is NOT "Draft rejected by the user".
|
|
2733
|
+
extensionApiStale = true;
|
|
2734
|
+
appendLedger(liveCtx.cwd, "extension_api_stale", { where: "draft confirm" });
|
|
2735
|
+
return { content: [{ type: "text", text: "The Confirm dialog could not render: pi invalidated this session's extension handle (session replacement). This is NOT a rejection — do NOT refine or re-propose. Tell the user to restart pi, then re-run the drafting flow." }], details: {} };
|
|
2709
2736
|
}
|
|
2737
|
+
confirmed = c === "yes";
|
|
2710
2738
|
}
|
|
2711
2739
|
if (!confirmed) {
|
|
2712
2740
|
return {
|
|
@@ -2811,7 +2839,7 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
2811
2839
|
// drafter path was still defaulting to 50 after v0.23.6 flipped the
|
|
2812
2840
|
// CLI default.
|
|
2813
2841
|
const max = p.max !== undefined && Number.isFinite(p.max) && p.max >= 0 ? Math.floor(p.max) : metricless ? 0 : 50;
|
|
2814
|
-
const autoAccept = loadSettings(
|
|
2842
|
+
const autoAccept = loadSettings(liveCtx.cwd).autoAcceptDrafts === true;
|
|
2815
2843
|
let confirmed = false;
|
|
2816
2844
|
if (autoAccept) {
|
|
2817
2845
|
confirmed = true;
|
|
@@ -2819,12 +2847,14 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
2819
2847
|
appendLedger(liveCtx.cwd, "draft_autoaccepted", { kind: "loop", target: p.target.trim().slice(0, 200), metricless });
|
|
2820
2848
|
} else {
|
|
2821
2849
|
try {
|
|
2822
|
-
|
|
2850
|
+
const c = await confirmDraft(
|
|
2851
|
+
liveCtx,
|
|
2823
2852
|
"Confirm loop",
|
|
2824
2853
|
metricless
|
|
2825
2854
|
? `Target: ${p.target.trim()}\n\nMeasure: NONE — metricless spec loop. There is NO plateau stop: the loop ends only at ${max > 0 ? `${max} iterations` : "NO iteration cap"}${typeof p.time === "number" && p.time > 0 ? ` · Time bound: ${p.time}h` : ""}${typeof p.tokens === "number" && p.tokens > 0 ? ` · Token bound: ${p.tokens.toLocaleString()}` : ""} · /loop stop.${p.branch ? "\nbranch mode: scratch branch, every iteration committed (clean tree required)" : ""}\n\nEvery iteration must make ONE real, inspectable change — cosmetic churn is the known failure mode (doorknob-polishing). Start it?`
|
|
2826
2855
|
: `Target: ${p.target.trim()}\n\nMeasure: ${p.measureCmd}\nTest-run output: ${rawOutput.slice(0, 200)}\nParsed number: ${parsed} (${p.direction === "min" ? "lower is better" : "higher is better"})\n\nPlateau stop: ${window} non-improving iterations · Cap: ${max > 0 ? `${max} iterations` : "none (unbounded)"}${typeof p.time === "number" && p.time > 0 ? ` · Time bound: ${p.time}h` : ""}${typeof p.tokens === "number" && p.tokens > 0 ? ` · Token bound: ${p.tokens.toLocaleString()}` : ""}${p.branch ? "\nbranch mode: scratch branch (clean tree required)" : ""}\n\nThe loop never completes — it runs until one of these bounds, plateau, or /loop stop. Start it?`,
|
|
2827
2856
|
);
|
|
2857
|
+
confirmed = c === "yes";
|
|
2828
2858
|
} catch {
|
|
2829
2859
|
confirmed = false;
|
|
2830
2860
|
}
|
|
@@ -2904,13 +2934,20 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
2904
2934
|
}
|
|
2905
2935
|
}
|
|
2906
2936
|
let confirmed = false;
|
|
2907
|
-
|
|
2908
|
-
confirmed =
|
|
2909
|
-
|
|
2937
|
+
if (loadSettings(liveCtx.cwd).autoAcceptDrafts === true) {
|
|
2938
|
+
confirmed = true;
|
|
2939
|
+
liveCtx.ui.notify("Loop spec refinement auto-accepted (/glla autoaccept=on).", "info");
|
|
2940
|
+
appendLedger(liveCtx.cwd, "draft_autoaccepted", { kind: "loop-refine" });
|
|
2941
|
+
} else {
|
|
2942
|
+
try {
|
|
2943
|
+
confirmed = (await confirmDraft(
|
|
2944
|
+
liveCtx,
|
|
2945
|
+
"Confirm loop spec refinement",
|
|
2910
2946
|
`Rationale: ${p.rationale}\n\nTarget:\n old: ${loop.target.slice(0, 120)}\n new: ${newTarget.slice(0, 120)}\n\nMeasure:\n old: ${loop.measureCmd}\n new: ${newMeasure}${newMeasure !== loop.measureCmd ? `\n test-run: ${testOutput.slice(0, 120)} → ${newBaseline}` : ""}\n\nThe loop keeps running against the refined spec (iteration ${loop.iteration} so far). Apply?`,
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2947
|
+
)) === "yes";
|
|
2948
|
+
} catch {
|
|
2949
|
+
confirmed = false;
|
|
2950
|
+
}
|
|
2914
2951
|
}
|
|
2915
2952
|
if (!confirmed) {
|
|
2916
2953
|
return { content: [{ type: "text", text: "Refinement rejected by the user. The loop continues against the current spec — keep improving the metric as defined." }], details: {} };
|
|
@@ -3048,7 +3085,7 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
3048
3085
|
const subs = (t.subtasks ?? []).map((s, j) => ` ${i + 1}.${j + 1} ${s}`).join("\n");
|
|
3049
3086
|
return `${i + 1}. ${t.title}` + (subs ? `\n${subs}` : "");
|
|
3050
3087
|
}).join("\n");
|
|
3051
|
-
const autoAcceptTasks = loadSettings(
|
|
3088
|
+
const autoAcceptTasks = loadSettings(liveCtx.cwd).autoAcceptDrafts === true;
|
|
3052
3089
|
let confirmed = false;
|
|
3053
3090
|
if (autoAcceptTasks) {
|
|
3054
3091
|
confirmed = true;
|
|
@@ -3056,7 +3093,7 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
3056
3093
|
appendLedger(liveCtx.cwd, "draft_autoaccepted", { kind: "tasks", count: p.tasks.length });
|
|
3057
3094
|
} else {
|
|
3058
3095
|
try {
|
|
3059
|
-
confirmed = await liveCtx
|
|
3096
|
+
confirmed = (await confirmDraft(liveCtx, "Confirm task list", preview)) === "yes";
|
|
3060
3097
|
} catch {
|
|
3061
3098
|
confirmed = false;
|
|
3062
3099
|
}
|
|
@@ -3453,17 +3490,17 @@ async function cmdToolOverride(args: string, ctx: ExtensionContext): Promise<voi
|
|
|
3453
3490
|
if (action === "allow") {
|
|
3454
3491
|
const allow = current.allow ?? [];
|
|
3455
3492
|
if (!allow.includes(tool)) apply({ allow: [...allow, tool] });
|
|
3456
|
-
ctx.ui.notify(`
|
|
3493
|
+
ctx.ui.notify(`"${tool}" is now always visible to the agent (project override saved).`, "info");
|
|
3457
3494
|
} else if (action === "hide") {
|
|
3458
3495
|
const hide = current.hide ?? [];
|
|
3459
3496
|
if (!hide.includes(tool)) apply({ hide: [...hide, tool] });
|
|
3460
|
-
ctx.ui.notify(`
|
|
3497
|
+
ctx.ui.notify(`"${tool}" is now always hidden from the agent (project override saved).`, "info");
|
|
3461
3498
|
} else if (action === "unallow") {
|
|
3462
3499
|
apply({ allow: (current.allow ?? []).filter((t) => t !== tool) });
|
|
3463
|
-
ctx.ui.notify(`
|
|
3500
|
+
ctx.ui.notify(`"${tool}" visibility override removed — the session decides again.`, "info");
|
|
3464
3501
|
} else {
|
|
3465
3502
|
apply({ hide: (current.hide ?? []).filter((t) => t !== tool) });
|
|
3466
|
-
ctx.ui.notify(`
|
|
3503
|
+
ctx.ui.notify(`"${tool}" hide override removed — the session decides again.`, "info");
|
|
3467
3504
|
}
|
|
3468
3505
|
return;
|
|
3469
3506
|
}
|
|
@@ -3490,7 +3527,12 @@ async function cmdToolOverride(args: string, ctx: ExtensionContext): Promise<voi
|
|
|
3490
3527
|
}
|
|
3491
3528
|
cfg[tool] = toolCfg;
|
|
3492
3529
|
apply({ perToolConfig: cfg });
|
|
3493
|
-
ctx.ui.notify(
|
|
3530
|
+
ctx.ui.notify(
|
|
3531
|
+
action === "set"
|
|
3532
|
+
? `"${tool}" setting saved: ${kv.slice(0, kv.indexOf("="))} = ${JSON.stringify(toolCfg[kv.slice(0, kv.indexOf("="))])} (project override).`
|
|
3533
|
+
: `"${tool}" setting "${kv}" removed — back to the built-in default.`,
|
|
3534
|
+
"info",
|
|
3535
|
+
);
|
|
3494
3536
|
return;
|
|
3495
3537
|
}
|
|
3496
3538
|
ctx.ui.notify(`Unknown tooloverride action: ${action}. Use: list | allow | hide | unallow | unhide | set | unset.`, "warning");
|
|
@@ -3530,7 +3572,7 @@ async function cmdReviewerSettings(ctx: ExtensionContext): Promise<void> {
|
|
|
3530
3572
|
const cfg = load();
|
|
3531
3573
|
let choice: string | undefined;
|
|
3532
3574
|
try {
|
|
3533
|
-
choice = await ctx.ui.select("
|
|
3575
|
+
choice = await ctx.ui.select("Postaudit — post-completion follow-up enqueuer (project settings)", reviewerMenuOptions(cfg));
|
|
3534
3576
|
} catch {
|
|
3535
3577
|
return;
|
|
3536
3578
|
}
|
|
@@ -3557,8 +3599,10 @@ async function cmdReviewerSettings(ctx: ExtensionContext): Promise<void> {
|
|
|
3557
3599
|
const n = Number(v?.trim());
|
|
3558
3600
|
if (Number.isSafeInteger(n) && n >= 1 && n <= 100) save({ maxReviewsPerDay: n });
|
|
3559
3601
|
}
|
|
3560
|
-
} catch {
|
|
3561
|
-
|
|
3602
|
+
} catch (err) {
|
|
3603
|
+
// v0.28.11 (E7): a swallowed save failure made the user believe the
|
|
3604
|
+
// toggle landed. Loud now.
|
|
3605
|
+
ctx.ui.notify(`Postaudit setting NOT saved: ${err instanceof Error ? err.message : String(err)} — check .pi-glla/settings.json permissions.`, "warning");
|
|
3562
3606
|
}
|
|
3563
3607
|
}
|
|
3564
3608
|
}
|
|
@@ -4041,7 +4085,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
4041
4085
|
handler: settingsHandler,
|
|
4042
4086
|
});
|
|
4043
4087
|
pi.registerCommand("review", {
|
|
4044
|
-
description: "Manually run the
|
|
4088
|
+
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
4089
|
handler: (args: string, ctx: ExtensionContext) => { rememberCtx(ctx); return cmdReview(args, ctx); },
|
|
4046
4090
|
});
|
|
4047
4091
|
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.12",
|
|
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
|
|