pi-goal-list-loop-audit 0.26.1 → 0.26.2
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/extensions/loops/goal.ts +14 -5
- package/extensions/reviewer.ts +49 -19
- package/package.json +1 -1
package/extensions/loops/goal.ts
CHANGED
|
@@ -583,11 +583,12 @@ function archiveCurrentGoal(ctx: ExtensionContext, status: Status, stopReason?:
|
|
|
583
583
|
function fireReviewer(
|
|
584
584
|
ctx: ExtensionContext,
|
|
585
585
|
source: { kind: "goal" | "list"; goalId: string; objective: string; terminal: string },
|
|
586
|
-
opts: { manual?: boolean } = {},
|
|
586
|
+
opts: { manual?: boolean; mode?: "default" | "auto" | "report" } = {},
|
|
587
587
|
): void {
|
|
588
588
|
try {
|
|
589
589
|
const settings = loadSettings(ctx.cwd);
|
|
590
590
|
const config = resolveReviewerConfig(settings.reviewer as Partial<ReviewerConfig> | undefined);
|
|
591
|
+
if (opts.mode) config.mode = opts.mode;
|
|
591
592
|
const sources: Array<{ name: string; text: string }> = [];
|
|
592
593
|
try {
|
|
593
594
|
sources.push({ name: "archive", text: fs.readFileSync(archivedGoalPath(ctx.cwd, source.goalId), "utf-8") });
|
|
@@ -2884,9 +2885,16 @@ async function openSettingsUI(ctx: ExtensionContext): Promise<void> {
|
|
|
2884
2885
|
|
|
2885
2886
|
/** v0.26.0: /review <archived-goal-id> — manual reviewer invocation. */
|
|
2886
2887
|
async function cmdReview(args: string, ctx: ExtensionContext): Promise<void> {
|
|
2887
|
-
const
|
|
2888
|
+
const parts = args.trim().split(/\s+/).filter(Boolean);
|
|
2889
|
+
const id = parts[0] ?? "";
|
|
2890
|
+
const modeArg = parts[1];
|
|
2891
|
+
const mode = modeArg === "auto" || modeArg === "report" || modeArg === "default" ? modeArg : undefined;
|
|
2892
|
+
if (modeArg && !mode) {
|
|
2893
|
+
ctx.ui.notify(`Unknown mode "${modeArg}" — use auto | report | default.`, "warning");
|
|
2894
|
+
return;
|
|
2895
|
+
}
|
|
2888
2896
|
if (!id) {
|
|
2889
|
-
ctx.ui.notify("Usage: /review <goal-id> — see /goal archive for ids.", "info");
|
|
2897
|
+
ctx.ui.notify("Usage: /review <goal-id> [auto|report|default] — see /goal archive for ids.", "info");
|
|
2890
2898
|
return;
|
|
2891
2899
|
}
|
|
2892
2900
|
// Resolve the id against the archive (suffix match allowed).
|
|
@@ -2907,7 +2915,7 @@ async function cmdReview(args: string, ctx: ExtensionContext): Promise<void> {
|
|
|
2907
2915
|
ctx.ui.notify(`No archive found for ${id}.`, "warning");
|
|
2908
2916
|
return;
|
|
2909
2917
|
}
|
|
2910
|
-
fireReviewer(ctx, { kind: "goal", goalId, objective, terminal: "goal-complete" }, { manual: true });
|
|
2918
|
+
fireReviewer(ctx, { kind: "goal", goalId, objective, terminal: "goal-complete" }, { manual: true, mode });
|
|
2911
2919
|
}
|
|
2912
2920
|
|
|
2913
2921
|
/** v0.26.0: /glla reviewer — the reviewer config menu (project-scoped). */
|
|
@@ -2930,6 +2938,7 @@ async function cmdReviewerSettings(ctx: ExtensionContext): Promise<void> {
|
|
|
2930
2938
|
if (!choice || choice === "Done") return;
|
|
2931
2939
|
try {
|
|
2932
2940
|
if (choice.startsWith("Enabled")) save({ enabled: !cfg.enabled });
|
|
2941
|
+
else if (choice.startsWith("Mode")) save({ mode: cfg.mode === "default" ? "auto" : cfg.mode === "auto" ? "report" : "default" });
|
|
2933
2942
|
else if (choice.startsWith("Leverage mode")) save({ leverageMode: cfg.leverageMode === "fix-without-confirm" ? "confirm-all" : "fix-without-confirm" });
|
|
2934
2943
|
else if (choice.startsWith("Fire on goal-complete")) save({ fireOn: cfg.fireOn.includes("goal-complete") ? cfg.fireOn.filter((e) => e !== "goal-complete") : [...cfg.fireOn, "goal-complete"] });
|
|
2935
2944
|
else if (choice.startsWith("Fire on list-complete")) save({ fireOn: cfg.fireOn.includes("list-complete") ? cfg.fireOn.filter((e) => e !== "list-complete") : [...cfg.fireOn, "list-complete"] });
|
|
@@ -3368,7 +3377,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
3368
3377
|
handler: settingsHandler,
|
|
3369
3378
|
});
|
|
3370
3379
|
pi.registerCommand("review", {
|
|
3371
|
-
description: "Manually run the reviewer on an archived goal: /review <goal-id> — extracts findings, writes a report to .pi-glla/reviews/,
|
|
3380
|
+
description: "Manually run the reviewer on an archived goal: /review <goal-id> [auto|report|default] — extracts findings, writes a report to .pi-glla/reviews/, cascades per the mode (auto = auto-loop, no Confirms). Bypasses the trigger gates (explicit user request).",
|
|
3372
3381
|
handler: (args: string, ctx: ExtensionContext) => { rememberCtx(ctx); return cmdReview(args, ctx); },
|
|
3373
3382
|
});
|
|
3374
3383
|
pi.registerCommand("list", {
|
package/extensions/reviewer.ts
CHANGED
|
@@ -15,8 +15,15 @@
|
|
|
15
15
|
import * as fs from "node:fs";
|
|
16
16
|
import * as path from "node:path";
|
|
17
17
|
|
|
18
|
+
export type ReviewerMode = "default" | "auto" | "report";
|
|
19
|
+
|
|
18
20
|
export interface ReviewerConfig {
|
|
19
21
|
enabled: boolean;
|
|
22
|
+
/** v0.26.2: default = Confirm-gated cascade; auto = auto-loop — every
|
|
23
|
+
* finding class (incl. architectural) and the clean-completion audit
|
|
24
|
+
* become /list items with zero Confirms (strategic stays notify-only —
|
|
25
|
+
* decisions never auto-fire); report = write the report + notify only. */
|
|
26
|
+
mode: ReviewerMode;
|
|
20
27
|
fireOn: Array<"goal-complete" | "list-complete">;
|
|
21
28
|
doNotFireOn: string[];
|
|
22
29
|
cascade: Array<"convert-findings-to-list" | "queue-leftovers" | "fire-audit-on-clean" | "notify-and-idle">;
|
|
@@ -30,6 +37,7 @@ export interface ReviewerConfig {
|
|
|
30
37
|
|
|
31
38
|
export const DEFAULT_REVIEWER_CONFIG: ReviewerConfig = {
|
|
32
39
|
enabled: true,
|
|
40
|
+
mode: "default",
|
|
33
41
|
fireOn: ["goal-complete", "list-complete"],
|
|
34
42
|
doNotFireOn: ["goal-aborted", "goal-paused"],
|
|
35
43
|
cascade: ["convert-findings-to-list", "queue-leftovers", "fire-audit-on-clean", "notify-and-idle"],
|
|
@@ -61,7 +69,7 @@ const CLASS_PATTERNS: Array<{ class: FindingClass; re: RegExp }> = [
|
|
|
61
69
|
{ class: "strategic", re: /\bshould we\b|\bdeprecat|ship this\??|strategic/i },
|
|
62
70
|
{ class: "architectural", re: /\brewrite\b|new dependency|schema change|architectural|redesign/i },
|
|
63
71
|
{ class: "bug", re: /\bTODO\b|\bFIXME\b|\bbug\b|\bissue\b|regression|broken|\bfixme\b/i },
|
|
64
|
-
{ class: "refactor", re: /could be cleaner|consider refactoring|duplicat|refactor|left ?out|follow[\s-]?up|deferred/i },
|
|
72
|
+
{ class: "refactor", re: /could be cleaner|consider refactoring|duplicat|refactor|left ?out|follow[\s-]?up|deferred|could be improved|improvement|enhancement|consider adding|would be nice|nice to have/i },
|
|
65
73
|
];
|
|
66
74
|
|
|
67
75
|
export function classifyFindingText(line: string): FindingClass | undefined {
|
|
@@ -110,6 +118,7 @@ export interface ReviewReport {
|
|
|
110
118
|
objective: string;
|
|
111
119
|
findings: Finding[];
|
|
112
120
|
cascadeStep: string;
|
|
121
|
+
mode: ReviewerMode;
|
|
113
122
|
at: string;
|
|
114
123
|
}
|
|
115
124
|
|
|
@@ -120,7 +129,7 @@ export function formatReviewReport(r: ReviewReport): string {
|
|
|
120
129
|
return [
|
|
121
130
|
`# Review — ${r.goalId}`,
|
|
122
131
|
"",
|
|
123
|
-
`**Kind**: ${r.kind} · **At**: ${r.at}`,
|
|
132
|
+
`**Kind**: ${r.kind} · **At**: ${r.at} · **Mode**: ${r.mode}`,
|
|
124
133
|
"",
|
|
125
134
|
"## Summary",
|
|
126
135
|
"",
|
|
@@ -186,7 +195,11 @@ export function runReviewer(
|
|
|
186
195
|
if (config.doNotFireOn.includes(event)) return none(`doNotFireOn: ${event}`);
|
|
187
196
|
if (source.kind === "goal" && source.terminal !== "goal-complete") return none(`not a completion: ${source.terminal}`);
|
|
188
197
|
if (!config.fireOn.includes(source.kind === "goal" ? "goal-complete" : "list-complete")) return none("fireOn excludes this event");
|
|
189
|
-
|
|
198
|
+
// v0.26.2: in auto mode the queue emptying is the cascade's natural
|
|
199
|
+
// rhythm, not a runaway — the refire window must not strangle it.
|
|
200
|
+
// (The per-day cap below still bounds everything.)
|
|
201
|
+
const refireWindowApplies = !(config.mode === "auto" && source.kind === "list");
|
|
202
|
+
if (refireWindowApplies && reviewerFiredRecently(deps.ledgerEntries, REVIEWER_REFIRE_WINDOW_MS, deps.nowMs)) {
|
|
190
203
|
deps.ledger("reviewer_suppressed", { reason: "refire-window", goalId: source.goalId });
|
|
191
204
|
return none("reviewer fired within the last 5 minutes (runaway prevention)");
|
|
192
205
|
}
|
|
@@ -205,32 +218,47 @@ export function runReviewer(
|
|
|
205
218
|
let enqueued = 0;
|
|
206
219
|
let proposed = 0;
|
|
207
220
|
let cascadeStep = "notify-and-idle";
|
|
221
|
+
const auto = config.mode === "auto";
|
|
222
|
+
const reportOnly = config.mode === "report";
|
|
208
223
|
|
|
209
224
|
// Cascade: findings → list items (leverage: fix-without-confirm).
|
|
210
225
|
const convertStep = source.kind === "goal" ? "convert-findings-to-list" : "queue-leftovers";
|
|
211
|
-
if (bugs.length > 0 && config.cascade.includes(convertStep)) {
|
|
226
|
+
if (bugs.length > 0 && config.cascade.includes(convertStep) && !reportOnly) {
|
|
212
227
|
deps.enqueueListItems(bugs.map((f) => f.text));
|
|
213
228
|
enqueued = bugs.length;
|
|
214
229
|
cascadeStep = convertStep;
|
|
215
230
|
}
|
|
216
|
-
// Architectural findings → /goal proposal WITH Confirm
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
231
|
+
// Architectural findings: default mode → /goal proposal WITH Confirm;
|
|
232
|
+
// auto mode → /list items (the auto-loop rolls straight into them).
|
|
233
|
+
if (architectural.length > 0 && !reportOnly) {
|
|
234
|
+
if (auto) {
|
|
235
|
+
deps.enqueueListItems(architectural.map((f) => f.text));
|
|
236
|
+
enqueued += architectural.length;
|
|
237
|
+
cascadeStep = convertStep;
|
|
238
|
+
} else {
|
|
239
|
+
deps.proposeGoal(
|
|
240
|
+
architectural.map((f) => f.text).join("; "),
|
|
241
|
+
`reviewer found ${architectural.length} architectural-class finding(s) — needs your Confirm`,
|
|
242
|
+
);
|
|
243
|
+
proposed += architectural.length;
|
|
244
|
+
cascadeStep = "propose-goal";
|
|
245
|
+
}
|
|
224
246
|
}
|
|
225
|
-
// Clean completion → audit /goal (
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
)
|
|
231
|
-
|
|
247
|
+
// Clean completion → audit: default mode proposes a /goal (Confirm);
|
|
248
|
+
// auto mode enqueues the audit as a /list item (no Confirm — the
|
|
249
|
+
// cascade keeps rolling until the findings run dry).
|
|
250
|
+
if (findings.length === 0 && config.cascade.includes("fire-audit-on-clean") && !reportOnly) {
|
|
251
|
+
const auditObjective = `Post-completion regression scan after ${source.goalId} (${config.auditScope})`;
|
|
252
|
+
if (auto) {
|
|
253
|
+
deps.enqueueListItems([auditObjective]);
|
|
254
|
+
enqueued++;
|
|
255
|
+
} else {
|
|
256
|
+
deps.proposeGoal(auditObjective, "reviewer: completion looks clean — firing the audit step");
|
|
257
|
+
proposed++;
|
|
258
|
+
}
|
|
232
259
|
cascadeStep = "fire-audit-on-clean";
|
|
233
260
|
}
|
|
261
|
+
if (reportOnly) cascadeStep = "report-only";
|
|
234
262
|
|
|
235
263
|
const report: ReviewReport = {
|
|
236
264
|
goalId: source.goalId,
|
|
@@ -238,6 +266,7 @@ export function runReviewer(
|
|
|
238
266
|
objective: source.objective,
|
|
239
267
|
findings,
|
|
240
268
|
cascadeStep,
|
|
269
|
+
mode: config.mode,
|
|
241
270
|
at: new Date(deps.nowMs).toISOString(),
|
|
242
271
|
};
|
|
243
272
|
const reportPath = writeReviewReport(deps.cwd, report);
|
|
@@ -265,6 +294,7 @@ export function runReviewer(
|
|
|
265
294
|
export function reviewerMenuOptions(cfg: ReviewerConfig): string[] {
|
|
266
295
|
return [
|
|
267
296
|
`Enabled — ${cfg.enabled ? "ON" : "OFF"}`,
|
|
297
|
+
`Mode — ${cfg.mode} (default = Confirm-gated · auto = auto-loop, no Confirms · report = report only)`,
|
|
268
298
|
`Leverage mode — ${cfg.leverageMode} (bug/refactor findings)`,
|
|
269
299
|
`Fire on goal-complete — ${cfg.fireOn.includes("goal-complete") ? "ON" : "OFF"}`,
|
|
270
300
|
`Fire on list-complete — ${cfg.fireOn.includes("list-complete") ? "ON" : "OFF"}`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-goal-list-loop-audit",
|
|
3
|
-
"version": "0.26.
|
|
3
|
+
"version": "0.26.2",
|
|
4
4
|
"description": "Goal. Loop. Audit. Done. — 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 — only the read tools needed to verify your goal.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "dracon",
|