pi-goal-list-loop-audit 0.28.13 → 0.28.15
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/goal-settings.ts +6 -0
- package/extensions/loops/goal.ts +147 -8
- package/package.json +1 -1
|
@@ -33,6 +33,11 @@ export interface Settings {
|
|
|
33
33
|
/** on → restored goals/loops/lists auto-resume even in fresh sessions
|
|
34
34
|
* (unattended rigs). Default off: restore holds until /goal resume. */
|
|
35
35
|
autoResume?: boolean;
|
|
36
|
+
/** v0.28.14: what happens to stale carryover (paused goal, waiting list,
|
|
37
|
+
* held loop from before this session) when NEW work activates.
|
|
38
|
+
* pause (default) = leave it + ONE summary; clear = drop it all honestly;
|
|
39
|
+
* resume = legacy silent stacking. */
|
|
40
|
+
carryover?: "resume" | "pause" | "clear";
|
|
36
41
|
/** v0.24.2: pause the goal after N consecutive auditor disapprovals (0 = unlimited).
|
|
37
42
|
* Default 5 (raised from 3 in v0.25.0, contract item 7). */
|
|
38
43
|
auditCap?: number;
|
|
@@ -146,6 +151,7 @@ export const SETTINGS_KEYS: Array<keyof Settings> = [
|
|
|
146
151
|
"tokenLimit",
|
|
147
152
|
"wedgeAlertMinutes",
|
|
148
153
|
"autoResume",
|
|
154
|
+
"carryover",
|
|
149
155
|
"autoAcceptDrafts",
|
|
150
156
|
"auditCap",
|
|
151
157
|
"auditFeedbackChars",
|
package/extensions/loops/goal.ts
CHANGED
|
@@ -289,6 +289,57 @@ async function confirmDraft(ctx: ExtensionContext, title: string, body: string):
|
|
|
289
289
|
}
|
|
290
290
|
}
|
|
291
291
|
|
|
292
|
+
// v0.28.14: ONE summary + policy application for stale carryover when NEW
|
|
293
|
+
// work activates. pause (default): surface what's waiting, stack nothing
|
|
294
|
+
// silently. clear: drop the queue, dismiss the held loop, archive the
|
|
295
|
+
// paused goal — honestly, with a ledger trail. resume: legacy silent
|
|
296
|
+
// behavior. A new GOAL replacing a paused one archives it in every policy
|
|
297
|
+
// (one-active-thing: state.goal holds exactly one goal).
|
|
298
|
+
function resolveCarryover(ctx: ExtensionContext, trigger: "goal" | "loop" | "list"): void {
|
|
299
|
+
if (carryoverResolved || !carryoverSnapshot) return;
|
|
300
|
+
carryoverResolved = true;
|
|
301
|
+
const snap = carryoverSnapshot;
|
|
302
|
+
carryoverSnapshot = null;
|
|
303
|
+
const policy = loadSettings(ctx.cwd).carryover ?? "pause";
|
|
304
|
+
if (policy === "resume") return; // legacy silent stacking
|
|
305
|
+
const done: string[] = [];
|
|
306
|
+
const waiting: string[] = [];
|
|
307
|
+
const pausedGoal = state.goal && state.goal.status === "paused" ? state.goal : null;
|
|
308
|
+
// A new goal OR list item replaces the goal slot; a loop leaves it paused.
|
|
309
|
+
if (pausedGoal && (trigger === "goal" || trigger === "list" || policy === "clear")) {
|
|
310
|
+
archiveCurrentGoal(ctx, "aborted", trigger === "loop" ? "carryover cleared" : `replaced by new ${trigger} (carryover)`);
|
|
311
|
+
done.push(`archived paused goal "${(snap.pausedGoal ?? pausedGoal.objective).slice(0, 60)}"`);
|
|
312
|
+
} else if (snap.pausedGoal) {
|
|
313
|
+
waiting.push(`paused goal "${snap.pausedGoal}" (/goal resume)`);
|
|
314
|
+
}
|
|
315
|
+
if (snap.listCount > 0) {
|
|
316
|
+
if (policy === "clear") {
|
|
317
|
+
state = { ...state, list: [] };
|
|
318
|
+
done.push(`dropped ${snap.listCount} waiting list item(s)`);
|
|
319
|
+
} else {
|
|
320
|
+
waiting.push(`${snap.listCount} waiting list item(s) (/list next)`);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
if (snap.heldLoop) {
|
|
324
|
+
if (policy === "clear" && state.loop && !state.loop.active && state.loop.stopReason === HELD_ON_RESTORE) {
|
|
325
|
+
state.loop = { ...state.loop, stopReason: "cleared: carryover" };
|
|
326
|
+
done.push(`dismissed held loop "${snap.heldLoop}"`);
|
|
327
|
+
} else {
|
|
328
|
+
waiting.push(`held loop "${snap.heldLoop}" (/loop to resume)`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
persistState(ctx);
|
|
332
|
+
appendLedger(ctx.cwd, "carryover_resolved", { policy, trigger, cleared: done.length, waiting: waiting.length });
|
|
333
|
+
const summary = [...done.map((d) => `✂ ${d}`), ...waiting.map((w) => `⏸ ${w}`)].join(" · ");
|
|
334
|
+
if (!summary) return;
|
|
335
|
+
ctx.ui.notify(
|
|
336
|
+
policy === "clear"
|
|
337
|
+
? `Carryover cleared (${trigger}): ${summary}`
|
|
338
|
+
: `Carryover from before this session: ${summary}${waiting.length > 0 ? " — /glla carryover=clear drops these automatically." : ""}`,
|
|
339
|
+
"info",
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
|
|
292
343
|
// The most recent ExtensionContext seen from any event or command handler.
|
|
293
344
|
// pi replaces sessions (newSession/fork/reload) and stale ctx throws on use,
|
|
294
345
|
// so timers must never capture a ctx — they read lastCtx at fire time.
|
|
@@ -352,6 +403,11 @@ let postRestoreGraceTurns = 0;
|
|
|
352
403
|
// refire's own noteActivity, which is what made the hegemon zombie spin
|
|
353
404
|
// self-sustaining (619 refires / 23.5h / zero turns).
|
|
354
405
|
let consecutiveStalls = 0;
|
|
406
|
+
// v0.28.14: carryover snapshot — unfinished work loaded from disk at
|
|
407
|
+
// session_start (predates this session). Resolved ONCE per session at the
|
|
408
|
+
// first NEW activation (new goal / new loop) per the carryover setting.
|
|
409
|
+
let carryoverSnapshot: { pausedGoal?: string; listCount: number; heldLoop?: string } | null = null;
|
|
410
|
+
let carryoverResolved = true;
|
|
355
411
|
// v0.26.6: precise replacement for the removed ship-recency suppression —
|
|
356
412
|
// set while complete_goal's isolated audit runs, so the heartbeat never
|
|
357
413
|
// refires into an in-flight completion.
|
|
@@ -807,7 +863,13 @@ function notifyPersistenceState(ctx: ExtensionContext): void {
|
|
|
807
863
|
}
|
|
808
864
|
|
|
809
865
|
function setGoal(goal: Goal, ctx: ExtensionContext): void {
|
|
810
|
-
|
|
866
|
+
// v0.28.14: never silently orphan a live goal — a paused/active goal
|
|
867
|
+
// being replaced is archived honestly first (the old behavior left it in
|
|
868
|
+
// goals/ but untracked: "older goals lying around leading to confusion").
|
|
869
|
+
if (state.goal && state.goal.id !== goal.id && (state.goal.status === "active" || state.goal.status === "paused")) {
|
|
870
|
+
archiveCurrentGoal(ctx, "aborted", `replaced by goal ${goal.id}`);
|
|
871
|
+
}
|
|
872
|
+
state = { ...state, goal }; // preserve list AND loop (v0.28.14: the bare reconstruction used to nuke a held/active loop whenever a goal was set)
|
|
811
873
|
const file = writeGoalMd(ctx.cwd, goal);
|
|
812
874
|
state.goal!.activePath = path.relative(ctx.cwd, file) || file;
|
|
813
875
|
persistState(ctx);
|
|
@@ -838,7 +900,7 @@ function archiveCurrentGoal(ctx: ExtensionContext, status: Status, stopReason?:
|
|
|
838
900
|
if (archived) {
|
|
839
901
|
try { fs.unlinkSync(goalMdPath(ctx.cwd, goal.id)); } catch {}
|
|
840
902
|
}
|
|
841
|
-
state = { goal: { ...goal, status, archivedPath: path.relative(ctx.cwd, target) || target, stopReason }
|
|
903
|
+
state = { ...state, goal: { ...goal, status, archivedPath: path.relative(ctx.cwd, target) || target, stopReason } };
|
|
842
904
|
appendLedger(ctx.cwd, "goal_archived", { goalId: goal.id, status, stopReason });
|
|
843
905
|
persistState(ctx);
|
|
844
906
|
// Loop 2: a list-sourced goal COMPLETED → auto-activate the next item.
|
|
@@ -957,6 +1019,17 @@ function listQueue(): NonNullable<State["list"]> {
|
|
|
957
1019
|
}
|
|
958
1020
|
|
|
959
1021
|
function activateNextListItem(ctx: ExtensionContext, n = 1): boolean {
|
|
1022
|
+
// v0.28.14: one-active-thing choke point — NO call site (session_start,
|
|
1023
|
+
// completion cascade, /list next, list_activate, list-draft auto-activate)
|
|
1024
|
+
// may activate a list item over a live loop, present or future.
|
|
1025
|
+
if (isLoopActive()) {
|
|
1026
|
+
appendLedger(ctx.cwd, "list_activation_blocked_loop", {});
|
|
1027
|
+
return false;
|
|
1028
|
+
}
|
|
1029
|
+
// v0.28.14: carryover resolution runs BEFORE the item is taken — under
|
|
1030
|
+
// carryover=clear the stale queue is dropped first and there is nothing
|
|
1031
|
+
// to activate; under pause the ONE summary precedes the activation.
|
|
1032
|
+
resolveCarryover(ctx, "list");
|
|
960
1033
|
const queue = listQueue();
|
|
961
1034
|
const taken = takeAt(queue, n);
|
|
962
1035
|
if (!taken) return false;
|
|
@@ -1110,6 +1183,7 @@ async function cmdSet(args: string, ctx: ExtensionContext, skipDraft = false): P
|
|
|
1110
1183
|
return;
|
|
1111
1184
|
}
|
|
1112
1185
|
draftingTarget = null; // explicit objective cancels any drafting session
|
|
1186
|
+
resolveCarryover(ctx, "goal"); // v0.28.14: surface/clear stale leftovers
|
|
1113
1187
|
const goal = createGoal(raw, ctx);
|
|
1114
1188
|
setGoal(goal, ctx);
|
|
1115
1189
|
// Reset counters
|
|
@@ -1194,10 +1268,17 @@ async function cmdResume(ctx: ExtensionContext): Promise<void> {
|
|
|
1194
1268
|
}
|
|
1195
1269
|
|
|
1196
1270
|
async function cmdCancel(ctx: ExtensionContext): Promise<void> {
|
|
1197
|
-
if (!state.goal)
|
|
1271
|
+
if (!state.goal) {
|
|
1272
|
+
// v0.28.14: users reach for /goal cancel to kill a LOOP (no goal
|
|
1273
|
+
// active) — point at the right verb instead of doing nothing silently.
|
|
1274
|
+
if (isLoopActive()) {
|
|
1275
|
+
ctx.ui.notify("No goal to cancel — a LOOP is active: /loop stop (or /loop cancel) ends it.", "info");
|
|
1276
|
+
}
|
|
1277
|
+
return;
|
|
1278
|
+
}
|
|
1198
1279
|
archiveCurrentGoal(ctx, "aborted", "user cancelled");
|
|
1199
1280
|
ctx.abort();
|
|
1200
|
-
ctx.ui.notify(
|
|
1281
|
+
ctx.ui.notify(`Goal aborted.${isLoopActive() ? " A loop is still active — /loop stop ends it." : ""}`, "info");
|
|
1201
1282
|
}
|
|
1202
1283
|
|
|
1203
1284
|
async function cmdGoals(ctx: ExtensionContext): Promise<void> {
|
|
@@ -1464,6 +1545,11 @@ async function cmdList(args: string, ctx: ExtensionContext): Promise<void> {
|
|
|
1464
1545
|
ctx.ui.notify(`Usage: /list next [1-${listQueue().length || 1}]`, "info");
|
|
1465
1546
|
return;
|
|
1466
1547
|
}
|
|
1548
|
+
// v0.28.14: one-active-thing — /list next must not jump a live loop.
|
|
1549
|
+
if (isLoopActive()) {
|
|
1550
|
+
ctx.ui.notify("A loop is active — /loop stop it before activating a list item.", "warning");
|
|
1551
|
+
return;
|
|
1552
|
+
}
|
|
1467
1553
|
if (state.goal && state.goal.status === "active") {
|
|
1468
1554
|
archiveCurrentGoal(ctx, "aborted", `skipped via /list next ${n > 1 ? n : ""}`.trim());
|
|
1469
1555
|
}
|
|
@@ -1912,6 +1998,7 @@ async function startLoopFromConfig(ctx: ExtensionContext, cfg: LoopConfig): Prom
|
|
|
1912
1998
|
);
|
|
1913
1999
|
return false;
|
|
1914
2000
|
}
|
|
2001
|
+
resolveCarryover(ctx, "loop"); // v0.28.14: surface/clear stale leftovers
|
|
1915
2002
|
state = {
|
|
1916
2003
|
...state,
|
|
1917
2004
|
loop: {
|
|
@@ -1965,6 +2052,12 @@ async function cmdLoop(args: string, ctx: ExtensionContext): Promise<void> {
|
|
|
1965
2052
|
}
|
|
1966
2053
|
const stored = state.loop;
|
|
1967
2054
|
if (stored && !stored.active && stored.stopReason === HELD_ON_RESTORE) {
|
|
2055
|
+
// v0.28.14: one-active-thing — a held loop must not resume over an
|
|
2056
|
+
// active goal/list-item (this was the last unguarded stacking path).
|
|
2057
|
+
if (state.goal && state.goal.status === "active") {
|
|
2058
|
+
ctx.ui.notify("A goal is active — the held loop stays held. /goal pause or /goal cancel it first, then /loop to resume.", "warning");
|
|
2059
|
+
return;
|
|
2060
|
+
}
|
|
1968
2061
|
state.loop = { ...stored, active: true, stopReason: undefined };
|
|
1969
2062
|
persistState(ctx);
|
|
1970
2063
|
scheduleLoopTick(ctx);
|
|
@@ -2026,13 +2119,15 @@ async function cmdLoop(args: string, ctx: ExtensionContext): Promise<void> {
|
|
|
2026
2119
|
return;
|
|
2027
2120
|
}
|
|
2028
2121
|
|
|
2029
|
-
|
|
2122
|
+
// v0.28.14: /loop cancel is a first-class alias — users reached for
|
|
2123
|
+
// /goal cancel to kill loops because "cancel" is the verb they know.
|
|
2124
|
+
if (sub === "stop" || sub === "cancel") {
|
|
2030
2125
|
if (!state.loop) {
|
|
2031
2126
|
ctx.ui.notify("No loop to stop.", "info");
|
|
2032
2127
|
return;
|
|
2033
2128
|
}
|
|
2034
2129
|
clearLoopTimer();
|
|
2035
|
-
state.loop = { ...state.loop, active: false, stopReason: state.loop.stopReason ??
|
|
2130
|
+
state.loop = { ...state.loop, active: false, stopReason: state.loop.stopReason ?? `stopped by user (/loop ${sub})` };
|
|
2036
2131
|
persistState(ctx);
|
|
2037
2132
|
await finishLoopGit(ctx, state.loop);
|
|
2038
2133
|
appendLedger(ctx.cwd, "loop_stopped", { reason: "user", iterations: state.loop.iteration, best: state.loop.bestValue });
|
|
@@ -2646,6 +2741,12 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
2646
2741
|
};
|
|
2647
2742
|
}
|
|
2648
2743
|
const liveCtx = (execCtx as ExtensionContext | undefined) ?? ctx;
|
|
2744
|
+
// v0.28.14: one-active-thing EARLY guard — refuse the whole interview
|
|
2745
|
+
// when a loop is live (the post-confirm backstop below stays: state
|
|
2746
|
+
// can change mid-interview).
|
|
2747
|
+
if (isLoopActive()) {
|
|
2748
|
+
return { content: [{ type: "text", text: "A loop is active — one active thing at a time. The user must /loop stop it before a goal or list item can activate; do not re-propose until then." }], details: {} };
|
|
2749
|
+
}
|
|
2649
2750
|
// v0.14.0: the interview floor — no Confirm until the user replied.
|
|
2650
2751
|
// v0.23.8: /glla autoaccept=on skips the floor AND the Confirm —
|
|
2651
2752
|
// the seed carries the intent (unattended rigs). Default off.
|
|
@@ -2745,6 +2846,11 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
2745
2846
|
const confirmedTarget = draftingTarget;
|
|
2746
2847
|
draftingTarget = null;
|
|
2747
2848
|
const full = p.objective.trim() + (normContract ? `\nDone when:\n${normContract}` : "");
|
|
2849
|
+
// v0.28.14: one-active-thing — no goal/list activation over a live loop.
|
|
2850
|
+
if (isLoopActive()) {
|
|
2851
|
+
return { content: [{ type: "text", text: "A loop is active — one active thing at a time. The user must /loop stop it before a goal or list item can activate; do not re-propose until then." }], details: {} };
|
|
2852
|
+
}
|
|
2853
|
+
resolveCarryover(liveCtx, "goal"); // v0.28.14: surface/clear stale leftovers
|
|
2748
2854
|
// List drafting: the confirmed contract goes into the QUEUE, not active.
|
|
2749
2855
|
if (confirmedTarget === "list") {
|
|
2750
2856
|
const extracted = extractVerificationContract(full);
|
|
@@ -2795,6 +2901,11 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
2795
2901
|
details: {},
|
|
2796
2902
|
};
|
|
2797
2903
|
}
|
|
2904
|
+
// v0.28.14: one-active-thing EARLY guard — refuse before the
|
|
2905
|
+
// interview floor (a live goal blocks any loop proposal).
|
|
2906
|
+
if (state.goal && state.goal.status === "active") {
|
|
2907
|
+
return { content: [{ type: "text", text: "A goal is active — one active thing at a time. The user must /goal pause or /goal cancel it before a loop can start; do not re-propose until then." }], details: {} };
|
|
2908
|
+
}
|
|
2798
2909
|
// v0.14.0: the interview floor — no Confirm until the user replied.
|
|
2799
2910
|
if (draftingUserReplies === 0) draftingBlockedProposals++;
|
|
2800
2911
|
const loopBlock = draftProposalBlock(draftingUserReplies, draftingBlockedProposals);
|
|
@@ -2810,6 +2921,12 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
2810
2921
|
return { content: [{ type: "text", text: 'direction=min|max is required for a measured loop (omit measureCmd or pass "none" for a metricless spec loop).' }], details: {} };
|
|
2811
2922
|
}
|
|
2812
2923
|
const liveCtx = (execCtx as ExtensionContext | undefined) ?? ctx;
|
|
2924
|
+
// v0.28.14: one-active-thing — refuse to even test-run a loop measure
|
|
2925
|
+
// while a goal/list-item is active (the /loop start COMMAND guards
|
|
2926
|
+
// this; the tool path used to skip it and stack a loop over a goal).
|
|
2927
|
+
if (state.goal && state.goal.status === "active") {
|
|
2928
|
+
return { content: [{ type: "text", text: "A goal is active — one active thing at a time. The user must /goal pause or /goal cancel it before a loop can start; do not re-propose until then." }], details: {} };
|
|
2929
|
+
}
|
|
2813
2930
|
// THE TEST-RUN: orchestrator runs the proposed measure once. The user
|
|
2814
2931
|
// sees the real number before a single iteration burns tokens.
|
|
2815
2932
|
// (Metricless loops skip this — there is no measure to test-run.)
|
|
@@ -3019,6 +3136,10 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
3019
3136
|
return { content: [{ type: "text", text: "n must be a positive integer (1-based position)." }], details: {} };
|
|
3020
3137
|
}
|
|
3021
3138
|
const liveCtx = (execCtx as ExtensionContext | undefined) ?? ctx;
|
|
3139
|
+
// v0.28.14: one-active-thing — a list item must not jump a live loop.
|
|
3140
|
+
if (isLoopActive()) {
|
|
3141
|
+
return { content: [{ type: "text", text: "A loop is active — one active thing at a time. The user must /loop stop it before a list item can activate." }], details: {} };
|
|
3142
|
+
}
|
|
3022
3143
|
if (state.goal && state.goal.status === "active") {
|
|
3023
3144
|
archiveCurrentGoal(liveCtx, "aborted", "skipped via list_activate");
|
|
3024
3145
|
}
|
|
@@ -3812,6 +3933,14 @@ async function cmdSettings(args: string, ctx: ExtensionContext): Promise<void> {
|
|
|
3812
3933
|
} else {
|
|
3813
3934
|
ctx.ui.notify(`autoresume must be on or off, got: ${value}`, "warning");
|
|
3814
3935
|
}
|
|
3936
|
+
} else if (key === "carryover") {
|
|
3937
|
+
if (["resume", "pause", "clear"].includes(value)) {
|
|
3938
|
+
patch.carryover = value as "resume" | "pause" | "clear";
|
|
3939
|
+
changed = true;
|
|
3940
|
+
ctx.ui.notify(`carryover=${value}: ${value === "clear" ? "stale goals/lists/held-loops are dropped when new work activates" : value === "pause" ? "stale carryover is surfaced in one summary when new work activates (default)" : "legacy behavior — carryover stacks silently"}.`, "info");
|
|
3941
|
+
} else {
|
|
3942
|
+
ctx.ui.notify(`carryover must be resume, pause, or clear, got: ${value}`, "warning");
|
|
3943
|
+
}
|
|
3815
3944
|
} else if (key === "autoaccept") {
|
|
3816
3945
|
if (["on", "true", "1", "yes"].includes(value)) {
|
|
3817
3946
|
patch.autoAcceptDrafts = true;
|
|
@@ -3935,7 +4064,7 @@ async function cmdSettings(args: string, ctx: ExtensionContext): Promise<void> {
|
|
|
3935
4064
|
}
|
|
3936
4065
|
}
|
|
3937
4066
|
if (!changed) {
|
|
3938
|
-
ctx.ui.notify("Nothing changed. Use key=value (model, thinking, notify, tokenlimit, autoresume, auditcap, auditfeedbackchars, aggressivemode, quotaretryminutes, stuckmax), optionally prefixed with 'project'.", "info");
|
|
4067
|
+
ctx.ui.notify("Nothing changed. Use key=value (model, thinking, notify, tokenlimit, autoresume, carryover, auditcap, auditfeedbackchars, aggressivemode, quotaretryminutes, stuckmax), optionally prefixed with 'project'.", "info");
|
|
3939
4068
|
return;
|
|
3940
4069
|
}
|
|
3941
4070
|
saveSettings(scope, ctx.cwd, patch);
|
|
@@ -4102,12 +4231,13 @@ export default function (pi: ExtensionAPI): void {
|
|
|
4102
4231
|
handler: (args: string, ctx: ExtensionContext) => { rememberCtx(ctx); return cmdList(args, ctx); },
|
|
4103
4232
|
});
|
|
4104
4233
|
pi.registerCommand("loop", {
|
|
4105
|
-
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.",
|
|
4234
|
+
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 (alias /loop cancel). 'Improve until X' is a /goal, not a loop.",
|
|
4106
4235
|
getArgumentCompletions: completions([
|
|
4107
4236
|
["start", "skip drafting: /loop start \"<target>\" measure=\"<cmd>\" direction=min|max [window=5] [max=50]"],
|
|
4108
4237
|
["respec", "infinite metricless loop reconciling the codebase against the root SPEC.md"],
|
|
4109
4238
|
["status", "show metric, iteration, best/last values, stall count"],
|
|
4110
4239
|
["stop", "end the loop (keeps the best state)"],
|
|
4240
|
+
["cancel", "alias of /loop stop — end the loop"],
|
|
4111
4241
|
["finish", "end the loop cleanly: /loop finish [reason] → stopReason 'completed: <reason>'"],
|
|
4112
4242
|
]),
|
|
4113
4243
|
handler: (args: string, ctx: ExtensionContext) => { rememberCtx(ctx); return cmdLoop(args, ctx); },
|
|
@@ -4253,6 +4383,15 @@ export default function (pi: ExtensionAPI): void {
|
|
|
4253
4383
|
// a foreign session.
|
|
4254
4384
|
if (isForeignCtx(ctx)) return;
|
|
4255
4385
|
state = readState(ctx.cwd);
|
|
4386
|
+
// v0.28.14: snapshot carryover BEFORE any restore logic mutates state —
|
|
4387
|
+
// a paused goal, waiting list items, or a loop that was live/held when
|
|
4388
|
+
// the last session ended. Resolved once at the first NEW activation.
|
|
4389
|
+
carryoverSnapshot = {
|
|
4390
|
+
pausedGoal: state.goal && state.goal.status === "paused" ? state.goal.objective.slice(0, 60) : undefined,
|
|
4391
|
+
listCount: listQueue().length,
|
|
4392
|
+
heldLoop: state.loop && (state.loop.active || state.loop.stopReason === HELD_ON_RESTORE) ? state.loop.target.slice(0, 60) : undefined,
|
|
4393
|
+
};
|
|
4394
|
+
carryoverResolved = !(carryoverSnapshot.pausedGoal || carryoverSnapshot.listCount > 0 || carryoverSnapshot.heldLoop);
|
|
4256
4395
|
if (!registeredCtx) {
|
|
4257
4396
|
registerAgentTools(pi, ctx);
|
|
4258
4397
|
registeredCtx = ctx;
|
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.15",
|
|
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",
|