pi-goal-list-loop-audit 0.24.3 → 0.24.5
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 +1 -1
- package/extensions/goal-loop-core.ts +42 -1
- package/extensions/goal-loop-forever.ts +10 -4
- package/extensions/loops/goal.ts +55 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -60,7 +60,7 @@ matches `/list show`.
|
|
|
60
60
|
/list cancel # stop the whole list: abort the active item + drop all waiting
|
|
61
61
|
/loop # draft the loop (agent grills; measure is test-run before you confirm)
|
|
62
62
|
/loop start "keep polishing the UI" # infinite metricless loop (v0.23.6): no plateau, no cap — ends at time=/tokens= or /loop stop
|
|
63
|
-
/loop respec # infinite metricless loop reconciling the codebase against the root SPEC.md / spec.md (v0.24.3)
|
|
63
|
+
/loop respec # infinite metricless loop reconciling the codebase against the root SPEC.md / spec.md (v0.24.3) — 2 specs = you pick, 0 specs = drafting, 1 spec = auto-start (v0.24.4)
|
|
64
64
|
/loop start "reduce TODOs" measure="grep -c TODO src.txt | head -1" direction=min
|
|
65
65
|
/loop start "shrink the bundle" measure="..." direction=min time=4 tokens=500000 # arbitrary bounds
|
|
66
66
|
/loop start "reduce TODOs" measure="..." direction=min branch=1 # scratch-branch mode
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* pi-goal-list-loop-audit — v0.
|
|
2
|
+
* pi-goal-list-loop-audit — v0.24.5
|
|
3
3
|
* extensions/goal-loop-core.ts
|
|
4
4
|
*
|
|
5
5
|
* Shared types, state machine, JSONL persistence, helpers.
|
|
@@ -703,3 +703,44 @@ export function classifySessionCtx(ownerSession: unknown, ownerLive: boolean, se
|
|
|
703
703
|
if (!ownerSession || !ownerLive) return "claim";
|
|
704
704
|
return sessionManager === ownerSession ? "refresh" : "foreign";
|
|
705
705
|
}
|
|
706
|
+
|
|
707
|
+
// =================================================================
|
|
708
|
+
// v0.24.5: tool-visibility self-heal
|
|
709
|
+
// =================================================================
|
|
710
|
+
//
|
|
711
|
+
// Root cause (INCIDENT-COMPLETION-BLACKHOLE-2026-07-23): external
|
|
712
|
+
// extensions like pi-plugin-list-selector-modlist call pi.setActiveTools
|
|
713
|
+
// with a frozen tool snapshot at session_start. When glla's session_start
|
|
714
|
+
// handler runs BEFORE theirs (load order), our lazily-registered agent
|
|
715
|
+
// tools get registered, briefly auto-activated, then wiped from the
|
|
716
|
+
// model-facing active set on the very next pi.setActiveTools call from
|
|
717
|
+
// modlist. Commands, widget, watchdog keep working (they don't go
|
|
718
|
+
// through the tool registry), but every agent tool — complete_goal,
|
|
719
|
+
// propose_loop_draft, etc. — answers "Tool not found" to the model.
|
|
720
|
+
//
|
|
721
|
+
// Self-heal: any handler that triggers registerAgentTools must also
|
|
722
|
+
// ensure the registered tool names are present in pi.getActiveTools(),
|
|
723
|
+
// re-adding any missing ones via pi.setActiveTools. Once per session,
|
|
724
|
+
// notify the user naming the external allowlist as the likely culprit
|
|
725
|
+
// so they can fix their profile once and silence it.
|
|
726
|
+
|
|
727
|
+
export const GLLA_TOOL_NAMES = [
|
|
728
|
+
"complete_goal",
|
|
729
|
+
"pause_goal",
|
|
730
|
+
"complete_task",
|
|
731
|
+
"update_task_status",
|
|
732
|
+
"propose_goal_draft",
|
|
733
|
+
"propose_loop_draft",
|
|
734
|
+
"propose_loop_refine",
|
|
735
|
+
"list_add",
|
|
736
|
+
"list_activate",
|
|
737
|
+
"list_status",
|
|
738
|
+
"propose_task_list",
|
|
739
|
+
] as const;
|
|
740
|
+
|
|
741
|
+
export type GllaToolName = (typeof GLLA_TOOL_NAMES)[number];
|
|
742
|
+
|
|
743
|
+
export function missingGllaTools(activeNames: readonly string[]): readonly GllaToolName[] {
|
|
744
|
+
const active = new Set(activeNames);
|
|
745
|
+
return GLLA_TOOL_NAMES.filter((n) => !active.has(n));
|
|
746
|
+
}
|
|
@@ -302,15 +302,21 @@ export function parseLoopStartArgs(raw: string): {
|
|
|
302
302
|
/** Root-only spec candidates, in priority order. No fuzzy search. */
|
|
303
303
|
export const RESPEC_SPEC_CANDIDATES = ["SPEC.md", "spec.md"] as const;
|
|
304
304
|
|
|
305
|
-
/** Resolve
|
|
306
|
-
export function
|
|
305
|
+
/** Resolve every root spec candidate that exists (priority order). */
|
|
306
|
+
export function resolveSpecFiles(cwd: string): string[] {
|
|
307
|
+
const found: string[] = [];
|
|
307
308
|
for (const name of RESPEC_SPEC_CANDIDATES) {
|
|
308
309
|
const p = join(cwd, name);
|
|
309
310
|
try {
|
|
310
|
-
if (existsSync(p) && statSync(p).isFile())
|
|
311
|
+
if (existsSync(p) && statSync(p).isFile()) found.push(p);
|
|
311
312
|
} catch { /* unreadable — keep looking */ }
|
|
312
313
|
}
|
|
313
|
-
return
|
|
314
|
+
return found;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/** Resolve the project spec in the root only; null when absent. */
|
|
318
|
+
export function resolveSpecFile(cwd: string): string | null {
|
|
319
|
+
return resolveSpecFiles(cwd)[0] ?? null;
|
|
314
320
|
}
|
|
315
321
|
|
|
316
322
|
/**
|
package/extensions/loops/goal.ts
CHANGED
|
@@ -63,6 +63,7 @@ import {
|
|
|
63
63
|
shouldAutoResumeOnSessionStart,
|
|
64
64
|
statusLabel,
|
|
65
65
|
writeGoalMd,
|
|
66
|
+
missingGllaTools,
|
|
66
67
|
} from "../goal-loop-core.js";
|
|
67
68
|
import { runGoalCompletionAuditor } from "../goal-loop-auditor.js";
|
|
68
69
|
import {
|
|
@@ -82,8 +83,7 @@ import {
|
|
|
82
83
|
parseLoopStartArgs,
|
|
83
84
|
parseMetric,
|
|
84
85
|
LOOP_DEFAULTS,
|
|
85
|
-
|
|
86
|
-
resolveSpecFile,
|
|
86
|
+
resolveSpecFiles,
|
|
87
87
|
respecTarget,
|
|
88
88
|
type LoopState,
|
|
89
89
|
} from "../goal-loop-forever.js";
|
|
@@ -1425,14 +1425,37 @@ async function cmdLoop(args: string, ctx: ExtensionContext): Promise<void> {
|
|
|
1425
1425
|
ctx.ui.notify("A loop is already active. /loop stop first.", "warning");
|
|
1426
1426
|
return;
|
|
1427
1427
|
}
|
|
1428
|
-
const
|
|
1429
|
-
if (
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1428
|
+
const specs = resolveSpecFiles(ctx.cwd);
|
|
1429
|
+
if (specs.length === 0) {
|
|
1430
|
+
// No spec → the target is undetermined; grill instead of dead-ending
|
|
1431
|
+
// on an error (v0.24.4).
|
|
1432
|
+
ctx.ui.notify("No SPEC.md / spec.md in the project root — drafting the loop target with you (or bootstrap a spec first).", "info");
|
|
1433
|
+
await startDrafting(
|
|
1434
|
+
ctx,
|
|
1435
|
+
"loop",
|
|
1436
|
+
"reconcile the codebase against the project spec — but NO SPEC.md / spec.md exists in the root. Grill the user: should the first work be bootstrapping a SPEC.md from the current code (then reconcile against it), or is the reconciliation target better stated in prose? Challenge vague answers.",
|
|
1433
1437
|
);
|
|
1434
1438
|
return;
|
|
1435
1439
|
}
|
|
1440
|
+
let specPath = specs[0]!;
|
|
1441
|
+
if (specs.length > 1) {
|
|
1442
|
+
// Two specs = ambiguous — never silently pick (v0.24.4). One
|
|
1443
|
+
// slash-bar select, plus a nudge to consolidate.
|
|
1444
|
+
const names = specs.map((p) => path.basename(p));
|
|
1445
|
+
const choice = await ctx.ui.select(
|
|
1446
|
+
"Both SPEC.md and spec.md exist in the root — which one is the spec?",
|
|
1447
|
+
names,
|
|
1448
|
+
);
|
|
1449
|
+
if (choice === undefined) {
|
|
1450
|
+
ctx.ui.notify("respec cancelled.", "info");
|
|
1451
|
+
return;
|
|
1452
|
+
}
|
|
1453
|
+
specPath = specs[names.indexOf(choice)]!;
|
|
1454
|
+
ctx.ui.notify(
|
|
1455
|
+
`Using ${path.basename(specPath)} as the spec. Both files exist — worth consolidating; the loop treats only ${path.basename(specPath)} as the spec.`,
|
|
1456
|
+
"info",
|
|
1457
|
+
);
|
|
1458
|
+
}
|
|
1436
1459
|
const target = respecTarget(path.basename(specPath));
|
|
1437
1460
|
await startLoopFromConfig(ctx, {
|
|
1438
1461
|
target,
|
|
@@ -2707,6 +2730,29 @@ export default function (pi: ExtensionAPI): void {
|
|
|
2707
2730
|
// "no active goal" if called).
|
|
2708
2731
|
let registeredCtx: ExtensionContext | null = null;
|
|
2709
2732
|
|
|
2733
|
+
// v0.24.5 tool-visibility self-heal: surface the notify exactly once
|
|
2734
|
+
// per session so the user learns about an external allowlist once and
|
|
2735
|
+
// can fix their profile to silence it.
|
|
2736
|
+
let toolHealNotified = false;
|
|
2737
|
+
function ensureAgentToolsActive(pi: ExtensionAPI, ctx: ExtensionContext): void {
|
|
2738
|
+
try {
|
|
2739
|
+
const active = pi.getActiveTools();
|
|
2740
|
+
const missing = missingGllaTools(active);
|
|
2741
|
+
if (missing.length === 0) return;
|
|
2742
|
+
pi.setActiveTools([...active, ...missing]);
|
|
2743
|
+
if (!toolHealNotified) {
|
|
2744
|
+
toolHealNotified = true;
|
|
2745
|
+
const list = missing.join(", ");
|
|
2746
|
+
ctx.ui.notify(
|
|
2747
|
+
`glla: ${missing.length} agent tool(s) were hidden by an external tool allowlist (e.g. a modlist profile) and have been re-activated (${list}). Add them to your allowlist profile to silence this.`,
|
|
2748
|
+
"warning",
|
|
2749
|
+
);
|
|
2750
|
+
}
|
|
2751
|
+
} catch {
|
|
2752
|
+
// Older pi without getActiveTools/setActiveTools — nothing we can do.
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
|
|
2710
2756
|
pi.on("message_start", async (event: any, _ctx: ExtensionContext) => {
|
|
2711
2757
|
// v0.14.0 drafting floor: count real user replies while drafting. Our
|
|
2712
2758
|
// own injected draft prompt arrives as a user message — skip that one.
|
|
@@ -2751,6 +2797,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
2751
2797
|
registerAgentTools(pi, ctx);
|
|
2752
2798
|
registeredCtx = ctx;
|
|
2753
2799
|
}
|
|
2800
|
+
ensureAgentToolsActive(pi, ctx);
|
|
2754
2801
|
warnOnCommandCollision(ctx);
|
|
2755
2802
|
warnIfAuditorProviderRisky(ctx);
|
|
2756
2803
|
// Restore gate (v0.21.0): a fresh session ("startup"/"new", or a pi too
|
|
@@ -2831,6 +2878,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
2831
2878
|
registerAgentTools(pi, ctx);
|
|
2832
2879
|
registeredCtx = ctx;
|
|
2833
2880
|
}
|
|
2881
|
+
ensureAgentToolsActive(pi, ctx);
|
|
2834
2882
|
// Nudge accounting: a supervising turn with zero tool calls is a nudge
|
|
2835
2883
|
// (no real progress); 3 consecutive → pause. Tool-use turns reset it.
|
|
2836
2884
|
if (isSupervising()) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-goal-list-loop-audit",
|
|
3
|
-
"version": "0.24.
|
|
3
|
+
"version": "0.24.5",
|
|
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",
|