pi-goal-list-loop-audit 0.24.2 → 0.24.4
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 -0
- package/extensions/goal-loop-forever.ts +36 -0
- package/extensions/loops/goal.ts +63 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -60,6 +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) — 2 specs = you pick, 0 specs = drafting, 1 spec = auto-start (v0.24.4)
|
|
63
64
|
/loop start "reduce TODOs" measure="grep -c TODO src.txt | head -1" direction=min
|
|
64
65
|
/loop start "shrink the bundle" measure="..." direction=min time=4 tokens=500000 # arbitrary bounds
|
|
65
66
|
/loop start "reduce TODOs" measure="..." direction=min branch=1 # scratch-branch mode
|
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
* self-reports progress.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
import { existsSync, statSync } from "node:fs";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
|
|
13
16
|
export type LoopDirection = "min" | "max";
|
|
14
17
|
|
|
15
18
|
export interface LoopMeasure {
|
|
@@ -293,3 +296,36 @@ export function parseLoopStartArgs(raw: string): {
|
|
|
293
296
|
tokenBudget: Number.isFinite(tokensRaw) && tokensRaw > 0 ? tokensRaw : undefined,
|
|
294
297
|
};
|
|
295
298
|
}
|
|
299
|
+
|
|
300
|
+
// ---- /loop respec (v0.24.3) ----
|
|
301
|
+
|
|
302
|
+
/** Root-only spec candidates, in priority order. No fuzzy search. */
|
|
303
|
+
export const RESPEC_SPEC_CANDIDATES = ["SPEC.md", "spec.md"] as const;
|
|
304
|
+
|
|
305
|
+
/** Resolve every root spec candidate that exists (priority order). */
|
|
306
|
+
export function resolveSpecFiles(cwd: string): string[] {
|
|
307
|
+
const found: string[] = [];
|
|
308
|
+
for (const name of RESPEC_SPEC_CANDIDATES) {
|
|
309
|
+
const p = join(cwd, name);
|
|
310
|
+
try {
|
|
311
|
+
if (existsSync(p) && statSync(p).isFile()) found.push(p);
|
|
312
|
+
} catch { /* unreadable — keep looking */ }
|
|
313
|
+
}
|
|
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;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* The respec target. The spec is DATA, not gospel: the loop reconciles code
|
|
324
|
+
* against it but reports stale/contradictory requirements instead of forcing
|
|
325
|
+
* the code to match a bad spec. Rotation keeps it honest: implement one
|
|
326
|
+
* iteration, audit the next (the doorknob failure is implementing nothing
|
|
327
|
+
* while claiming polish).
|
|
328
|
+
*/
|
|
329
|
+
export function respecTarget(specName: string): string {
|
|
330
|
+
return `Reconcile the codebase against ${specName} (the project spec in the root). Read the spec critically first: if a requirement is stale, contradictory, or wrong for the current codebase, report the discrepancy and move on — never force the code to match a bad spec. Otherwise pick the next gap between spec and code and close it. Rotate: one iteration implements a missing or outdated spec item, the next audits something already "implemented" against the spec and fixes what drifted.`;
|
|
331
|
+
}
|
package/extensions/loops/goal.ts
CHANGED
|
@@ -81,6 +81,9 @@ import {
|
|
|
81
81
|
loopBranchName,
|
|
82
82
|
parseLoopStartArgs,
|
|
83
83
|
parseMetric,
|
|
84
|
+
LOOP_DEFAULTS,
|
|
85
|
+
resolveSpecFiles,
|
|
86
|
+
respecTarget,
|
|
84
87
|
type LoopState,
|
|
85
88
|
} from "../goal-loop-forever.js";
|
|
86
89
|
import {
|
|
@@ -1408,6 +1411,63 @@ async function cmdLoop(args: string, ctx: ExtensionContext): Promise<void> {
|
|
|
1408
1411
|
return;
|
|
1409
1412
|
}
|
|
1410
1413
|
|
|
1414
|
+
if (sub === "respec") {
|
|
1415
|
+
// v0.24.3: reconcile the codebase against the root spec, forever.
|
|
1416
|
+
// Same auto-start path as /loop start (the user typed the command —
|
|
1417
|
+
// that IS the act); metricless + unbounded by design. No limit-nagging:
|
|
1418
|
+
// bounds exist on /loop start for whoever wants them.
|
|
1419
|
+
if (state.goal && state.goal.status === "active") {
|
|
1420
|
+
ctx.ui.notify("A goal is active — /goal cancel or /goal pause it before starting a loop.", "warning");
|
|
1421
|
+
return;
|
|
1422
|
+
}
|
|
1423
|
+
if (isLoopActive()) {
|
|
1424
|
+
ctx.ui.notify("A loop is already active. /loop stop first.", "warning");
|
|
1425
|
+
return;
|
|
1426
|
+
}
|
|
1427
|
+
const specs = resolveSpecFiles(ctx.cwd);
|
|
1428
|
+
if (specs.length === 0) {
|
|
1429
|
+
// No spec → the target is undetermined; grill instead of dead-ending
|
|
1430
|
+
// on an error (v0.24.4).
|
|
1431
|
+
ctx.ui.notify("No SPEC.md / spec.md in the project root — drafting the loop target with you (or bootstrap a spec first).", "info");
|
|
1432
|
+
await startDrafting(
|
|
1433
|
+
ctx,
|
|
1434
|
+
"loop",
|
|
1435
|
+
"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.",
|
|
1436
|
+
);
|
|
1437
|
+
return;
|
|
1438
|
+
}
|
|
1439
|
+
let specPath = specs[0]!;
|
|
1440
|
+
if (specs.length > 1) {
|
|
1441
|
+
// Two specs = ambiguous — never silently pick (v0.24.4). One
|
|
1442
|
+
// slash-bar select, plus a nudge to consolidate.
|
|
1443
|
+
const names = specs.map((p) => path.basename(p));
|
|
1444
|
+
const choice = await ctx.ui.select(
|
|
1445
|
+
"Both SPEC.md and spec.md exist in the root — which one is the spec?",
|
|
1446
|
+
names,
|
|
1447
|
+
);
|
|
1448
|
+
if (choice === undefined) {
|
|
1449
|
+
ctx.ui.notify("respec cancelled.", "info");
|
|
1450
|
+
return;
|
|
1451
|
+
}
|
|
1452
|
+
specPath = specs[names.indexOf(choice)]!;
|
|
1453
|
+
ctx.ui.notify(
|
|
1454
|
+
`Using ${path.basename(specPath)} as the spec. Both files exist — worth consolidating; the loop treats only ${path.basename(specPath)} as the spec.`,
|
|
1455
|
+
"info",
|
|
1456
|
+
);
|
|
1457
|
+
}
|
|
1458
|
+
const target = respecTarget(path.basename(specPath));
|
|
1459
|
+
await startLoopFromConfig(ctx, {
|
|
1460
|
+
target,
|
|
1461
|
+
measureCmd: "",
|
|
1462
|
+
direction: undefined,
|
|
1463
|
+
plateauWindow: LOOP_DEFAULTS.plateauWindow,
|
|
1464
|
+
maxIterations: 0,
|
|
1465
|
+
branch: false,
|
|
1466
|
+
force: false,
|
|
1467
|
+
});
|
|
1468
|
+
return;
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1411
1471
|
// Anything else is a natural-language target (v0.22.4): draft it — the
|
|
1412
1472
|
// metric is the whole game for a loop, and /loop start with full params
|
|
1413
1473
|
// is the skip-drafting path. Previously this fell through to a usage
|
|
@@ -1868,7 +1928,7 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
1868
1928
|
const p = params as { target: string; measureCmd?: string; direction?: "min" | "max"; window?: number; max?: number; time?: number; tokens?: number; branch?: boolean };
|
|
1869
1929
|
if (draftingTarget !== "loop") {
|
|
1870
1930
|
return {
|
|
1871
|
-
content: [{ type: "text", text: "
|
|
1931
|
+
content: [{ type: "text", text: "You cannot start or draft a loop — only the user can, from the slash bar (the Confirm is the product). Do NOT write draft files or wait for the user to say 'start' in chat; that dead-ends. Instead hand the user the exact command: /loop start \"<target>\" (bare = infinite metricless; add measure=\"<cmd>\" direction=min|max for a metric loop), or /loop respec to reconcile against the root spec, or /loop with no args to draft interactively." }],
|
|
1872
1932
|
details: {},
|
|
1873
1933
|
};
|
|
1874
1934
|
}
|
|
@@ -2654,9 +2714,10 @@ export default function (pi: ExtensionAPI): void {
|
|
|
2654
2714
|
handler: (args: string, ctx: ExtensionContext) => { rememberCtx(ctx); return cmdList(args, ctx); },
|
|
2655
2715
|
});
|
|
2656
2716
|
pi.registerCommand("loop", {
|
|
2657
|
-
description: "Loop 3: metric-driven process — it never completes. /loop <target> drafts the metric with you · /loop start \"<target>\" = infinite metricless loop (no plateau, no cap; ends at time=/tokens= or /loop stop) · add measure=\"<cmd>\" direction=min|max [window=5] [max=50] [branch=1] for a metric loop · /loop status · /loop stop. 'Improve until X' is a /goal, not a loop.",
|
|
2717
|
+
description: "Loop 3: metric-driven process — it never completes. /loop <target> drafts the metric with you · /loop start \"<target>\" = infinite metricless loop (no plateau, no cap; ends at time=/tokens= or /loop stop) · /loop respec = infinite metricless reconcile against the root SPEC.md · add measure=\"<cmd>\" direction=min|max [window=5] [max=50] [branch=1] for a metric loop · /loop status · /loop stop. 'Improve until X' is a /goal, not a loop.",
|
|
2658
2718
|
getArgumentCompletions: completions([
|
|
2659
2719
|
["start", "skip drafting: /loop start \"<target>\" measure=\"<cmd>\" direction=min|max [window=5] [max=50]"],
|
|
2720
|
+
["respec", "infinite metricless loop reconciling the codebase against the root SPEC.md"],
|
|
2660
2721
|
["status", "show metric, iteration, best/last values, stall count"],
|
|
2661
2722
|
["stop", "end the loop (keeps the best state)"],
|
|
2662
2723
|
]),
|
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.4",
|
|
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",
|