pi-goal-list-loop-audit 0.29.16 → 0.29.18
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.
|
@@ -427,7 +427,7 @@ export function auditMeasureCmd(): string {
|
|
|
427
427
|
* identity, on the current branch — no invented identities or branches).
|
|
428
428
|
*/
|
|
429
429
|
export function auditTarget(): string {
|
|
430
|
-
return `Audit the project for real problems and fix them, iteration by iteration. Every iteration: (1)
|
|
430
|
+
return `Audit the project for real problems and fix them, iteration by iteration — FIX-FIRST: the open backlog comes down before new hunting (user design 2026-07-30: "audit to fix then audit then fix again" — not find-and-present). Every iteration: (1) FIX the highest-severity OPEN finding(s) in ${AUDIT_FINDINGS_REL} — real fixes, committed — then check the box: "- [x] … — fixed in <commit>". An iteration that closes nothing while OPEN findings remain is a wasted iteration: if the top findings are genuinely blocked, say what blocks them in one line and work the first unblocked one — "no new action this turn" is never an acceptable iteration while open boxes exist. (2) RE-AUDIT on cadence, not every iteration — run a fresh audit pass (spawn Explore subagents for breadth; hunting real issues: bugs, broken flows, regressions, drift between docs and code, dead code, security holes; not style nits, not speculative refactors) ONLY when no OPEN findings remain, when roughly ten iterations have passed since the last pass, or when your own fixes plausibly broke something. (3) Append every NEW finding as one checkbox line "- [ ] SEVERITY: short description (file:line)" to ${AUDIT_FINDINGS_REL} (create the file on the first finding; append-only — never delete, rewrite, or reorder existing lines; never re-report a finding already listed). (4) Honesty law: never fabricate findings to look busy; never mark a finding fixed without the fix commit existing. The orchestrator counts CLOSED findings every iteration (direction=max): discovery alone does not move the metric — landing fixes does. When a full audit pass surfaces nothing new AND no open findings remain, say so plainly — the plateau stop ends the loop when the well is dry.`;
|
|
431
431
|
}
|
|
432
432
|
|
|
433
433
|
// ---- /goal audit-project (v0.29.8) ----
|
package/extensions/loops/goal.ts
CHANGED
|
@@ -152,6 +152,11 @@ import {
|
|
|
152
152
|
SettingsMenuComponent,
|
|
153
153
|
type SettingsRow,
|
|
154
154
|
} from "../settings-menu.js";
|
|
155
|
+
import {
|
|
156
|
+
buildModelPickItems,
|
|
157
|
+
ModelPickerComponent,
|
|
158
|
+
type ModelPickItem,
|
|
159
|
+
} from "../model-picker.js";
|
|
155
160
|
import {
|
|
156
161
|
applyMeasurement,
|
|
157
162
|
applyMetriclessTick,
|
|
@@ -3943,19 +3948,37 @@ function getSessionThinkingLevel(): "off" | "minimal" | "low" | "medium" | "high
|
|
|
3943
3948
|
function resolveAuditorModel(ctx: ExtensionContext, ref?: string): { model: any; error?: string; via?: string } {
|
|
3944
3949
|
if (ref && ref.trim()) {
|
|
3945
3950
|
const trimmed = ref.trim();
|
|
3951
|
+
// v0.29.17: an unavailable configured model (unknown id, or a provider
|
|
3952
|
+
// with no configured auth) falls back LOUDLY to the session model —
|
|
3953
|
+
// user request: "fall back to the session if unavailable". The v0.9.12
|
|
3954
|
+
// no-SILENT-substitution law stands: the fallback notifies + ledgers.
|
|
3955
|
+
// (Quota-exhausted keys stay on the quota-retry path — the model IS
|
|
3956
|
+
// available there; the key's window is the failure, not the model.)
|
|
3957
|
+
const fail = (reason: string) => {
|
|
3958
|
+
const sessionModel = ctx.model as any;
|
|
3959
|
+
if (sessionModel) {
|
|
3960
|
+
appendLedger(ctx.cwd, "auditor_model_fallback", { configured: trimmed, reason });
|
|
3961
|
+
ctx.ui.notify(`Auditor model "${trimmed}" is unavailable (${reason}) — falling back to the session model. Fix via /glla → Auditor model.`, "warning");
|
|
3962
|
+
return { model: sessionModel, via: "session-fallback" };
|
|
3963
|
+
}
|
|
3964
|
+
return { model: undefined, error: `${reason}: ${trimmed}` };
|
|
3965
|
+
};
|
|
3946
3966
|
const slash = trimmed.indexOf("/");
|
|
3947
3967
|
if (slash > 0) {
|
|
3948
3968
|
const provider = trimmed.slice(0, slash);
|
|
3949
3969
|
const id = trimmed.slice(slash + 1);
|
|
3950
3970
|
const model = ctx.modelRegistry.find(provider, id);
|
|
3951
|
-
|
|
3971
|
+
if (!model) return fail("model not found");
|
|
3972
|
+
if (!ctx.modelRegistry.hasConfiguredAuth(model)) return fail(`no configured auth for ${provider}`);
|
|
3973
|
+
return { model, via: "setting" };
|
|
3952
3974
|
}
|
|
3953
3975
|
const matches = ctx.modelRegistry.getAvailable().filter((m: any) => m.id === trimmed || m.name === trimmed);
|
|
3954
|
-
|
|
3976
|
+
if (matches[0]) return { model: matches[0], via: "setting" };
|
|
3977
|
+
return fail("no available model matching");
|
|
3955
3978
|
}
|
|
3956
3979
|
const sessionModel = ctx.model as any;
|
|
3957
3980
|
if (sessionModel) return { model: sessionModel, via: "session" };
|
|
3958
|
-
return { model: undefined, error: "no session model and no auditorModel configured — set one with /glla model
|
|
3981
|
+
return { model: undefined, error: "no session model and no auditorModel configured — set one with /glla → Auditor model" };
|
|
3959
3982
|
}
|
|
3960
3983
|
|
|
3961
3984
|
// (v0.9.12) The auto-fallback apparatus was REMOVED: no tier ranking, no
|
|
@@ -4032,6 +4055,43 @@ async function promptSettingsMenu(
|
|
|
4032
4055
|
*/
|
|
4033
4056
|
// v0.28.7 (T4): exported for the behavioral settings-editor tests
|
|
4034
4057
|
// (tests/settings-editors.test.ts drives each editor class end-to-end).
|
|
4058
|
+
/**
|
|
4059
|
+
* v0.29.17: /model-style fuzzy picker for model-valued settings. Builds the
|
|
4060
|
+
* item list from the registry (configured-auth providers only — a pick
|
|
4061
|
+
* from this list can never be a dead provider) and hosts the picker via
|
|
4062
|
+
* ctx.ui.custom. Falls back to the typed input when the runtime has no
|
|
4063
|
+
* custom shard (headless) — typing stays the emergency hatch there.
|
|
4064
|
+
* Returns { kind: "session" } to clear the override, { kind: "ref" } with
|
|
4065
|
+
* provider/id, or undefined for cancel.
|
|
4066
|
+
*/
|
|
4067
|
+
async function promptModelRef(
|
|
4068
|
+
ctx: ExtensionContext,
|
|
4069
|
+
title: string,
|
|
4070
|
+
emptyLabel: string,
|
|
4071
|
+
): Promise<{ kind: "session" } | { kind: "ref"; ref: string } | undefined> {
|
|
4072
|
+
if (typeof (ctx.ui as { custom?: unknown }).custom !== "function" || !ctx.modelRegistry) {
|
|
4073
|
+
const v = await ctx.ui.input(title, "provider/model-id — empty keeps the default");
|
|
4074
|
+
if (v === undefined) return undefined;
|
|
4075
|
+
return v.trim() ? { kind: "ref", ref: v.trim() } : { kind: "session" };
|
|
4076
|
+
}
|
|
4077
|
+
const sessionModel = ctx.model as any;
|
|
4078
|
+
const sessionLabel = sessionModel ? `${sessionModel.provider}/${sessionModel.id}` : "pi session model";
|
|
4079
|
+
const models = ctx.modelRegistry
|
|
4080
|
+
.getAvailable()
|
|
4081
|
+
.filter((m: any) => ctx.modelRegistry.hasConfiguredAuth(m));
|
|
4082
|
+
const items = buildModelPickItems(models, sessionLabel);
|
|
4083
|
+
const pick = await ctx.ui.custom<ModelPickItem | undefined>((tui, theme, keybindings, done) => {
|
|
4084
|
+
return new ModelPickerComponent({ title, items }, () => tui.requestRender(), theme, keybindings, done);
|
|
4085
|
+
});
|
|
4086
|
+
if (!pick) return undefined;
|
|
4087
|
+
if (pick.kind === "session") return { kind: "session" };
|
|
4088
|
+
if (pick.kind === "model" && pick.ref) return { kind: "ref", ref: pick.ref };
|
|
4089
|
+
// manual escape hatch — typed provider/model, validated like before
|
|
4090
|
+
const v = await ctx.ui.input(title, emptyLabel);
|
|
4091
|
+
if (v === undefined) return undefined;
|
|
4092
|
+
return v.trim() ? { kind: "ref", ref: v.trim() } : { kind: "session" };
|
|
4093
|
+
}
|
|
4094
|
+
|
|
4035
4095
|
export async function handleSettingChoice(id: string, ctx: ExtensionContext): Promise<void> {
|
|
4036
4096
|
switch (id) {
|
|
4037
4097
|
case "autoResume": {
|
|
@@ -4071,8 +4131,10 @@ export async function handleSettingChoice(id: string, ctx: ExtensionContext): Pr
|
|
|
4071
4131
|
return;
|
|
4072
4132
|
}
|
|
4073
4133
|
case "auditorModel": {
|
|
4074
|
-
const
|
|
4075
|
-
if (
|
|
4134
|
+
const pick = await promptModelRef(ctx, "Auditor model override", "provider/model-id — empty keeps the pi session model");
|
|
4135
|
+
if (pick === undefined) return;
|
|
4136
|
+
saveSettings("global", ctx.cwd, { auditorModel: pick.kind === "session" ? undefined : pick.ref });
|
|
4137
|
+
if (pick.kind === "session") ctx.ui.notify("Auditor model override cleared — the auditor follows the pi session model.", "info");
|
|
4076
4138
|
return;
|
|
4077
4139
|
}
|
|
4078
4140
|
case "auditorThinkingLevel": {
|
|
@@ -4177,15 +4239,14 @@ export async function handleSettingChoice(id: string, ctx: ExtensionContext): Pr
|
|
|
4177
4239
|
case "subagentModelOverrides.Plan":
|
|
4178
4240
|
case "subagentModelOverrides.general-purpose": {
|
|
4179
4241
|
const agentType = id.slice("subagentModelOverrides.".length);
|
|
4180
|
-
const
|
|
4181
|
-
if (
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
}
|
|
4242
|
+
const pick = await promptModelRef(ctx, `Model pin for ${agentType} subagents`, "provider/model-id e.g. minimax/MiniMax-M3 — always wins over strategy; empty = follow strategy");
|
|
4243
|
+
if (pick === undefined) return;
|
|
4244
|
+
const current = loadSettings(ctx.cwd).subagentModelOverrides ?? {};
|
|
4245
|
+
const next = { ...current };
|
|
4246
|
+
if (pick.kind === "ref") next[agentType] = pick.ref;
|
|
4247
|
+
else delete next[agentType];
|
|
4248
|
+
saveSettings("global", ctx.cwd, { subagentModelOverrides: Object.keys(next).length > 0 ? next : undefined });
|
|
4249
|
+
ctx.ui.notify(`${agentType} model pin saved — applies to NEW pi sessions.`, "info");
|
|
4189
4250
|
return;
|
|
4190
4251
|
}
|
|
4191
4252
|
case "subagentResolved":
|
|
@@ -5410,6 +5471,17 @@ export default function (pi: ExtensionAPI): void {
|
|
|
5410
5471
|
persistState(ctx);
|
|
5411
5472
|
appendLedger(ctx.cwd, "audit_loop_metric_migrated", { from: "open-count/min", to: "closed-count/max" });
|
|
5412
5473
|
}
|
|
5474
|
+
// v0.29.18: migrate live/held audit loops to the FIX-FIRST target —
|
|
5475
|
+
// the audit-every-iteration template made discovery (8-12/iter)
|
|
5476
|
+
// outpace fixes (1/iter) and allowed "no new action" iterations with
|
|
5477
|
+
// open boxes (field: hegemon iter 26 — the user watched it find and
|
|
5478
|
+
// present instead of fix). Target-only swap: the metric (closed
|
|
5479
|
+
// count/max) is unchanged, so best/stall stay.
|
|
5480
|
+
if (state.loop?.kind === "audit" && state.loop.target?.includes("Every iteration: (1) run a FRESH audit pass")) {
|
|
5481
|
+
state.loop = { ...state.loop, target: auditTarget() };
|
|
5482
|
+
persistState(ctx);
|
|
5483
|
+
appendLedger(ctx.cwd, "audit_loop_target_migrated", { from: "audit-every-iteration", to: "fix-first" });
|
|
5484
|
+
}
|
|
5413
5485
|
if (isLoopActive()) {
|
|
5414
5486
|
const l = state.loop!;
|
|
5415
5487
|
if (autoResume) {
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
// pi-goal-list-loop-audit — v0.29.17
|
|
2
|
+
// extensions/model-picker.ts
|
|
3
|
+
//
|
|
4
|
+
// A /model-style fuzzy picker for model-valued settings (Auditor model,
|
|
5
|
+
// subagent model pins). Why this exists:
|
|
6
|
+
// • ctx.ui.select renders EVERY option unsorted with no search — a full
|
|
7
|
+
// model registry is hundreds of rows; unusable (field: the auditor
|
|
8
|
+
// model was left on a quota-dead openrouter key partly because fixing
|
|
9
|
+
// it meant hand-typing provider/model into a bare input).
|
|
10
|
+
// • pi's own /model dialog (ModelSelectorComponent) needs ModelRuntime
|
|
11
|
+
// and SettingsManager internals that extensions never receive.
|
|
12
|
+
// So we rebuild the same interaction shape — a search line with a
|
|
13
|
+
// fuzzy-filtered list — from pi-tui primitives (fuzzyFilter) over
|
|
14
|
+
// ctx.modelRegistry, hosted via ctx.ui.custom, exactly like the v0.28.0
|
|
15
|
+
// settings table. Unit-testable via synthetic handleInput calls.
|
|
16
|
+
//
|
|
17
|
+
// Item order: "session model" (clear override) first, then every
|
|
18
|
+
// configured-auth model sorted by provider/id, then "type manually…" last.
|
|
19
|
+
|
|
20
|
+
import { fuzzyFilter, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
21
|
+
import type { SettingsMenuTheme, KeybindingsManagerLike } from "./settings-menu.ts";
|
|
22
|
+
|
|
23
|
+
export type ModelPickKind = "session" | "model" | "manual";
|
|
24
|
+
|
|
25
|
+
export interface ModelPickItem {
|
|
26
|
+
kind: ModelPickKind;
|
|
27
|
+
/** provider/model-id for kind === "model". */
|
|
28
|
+
ref?: string;
|
|
29
|
+
/** Row label shown in the list. */
|
|
30
|
+
label: string;
|
|
31
|
+
/** Text the fuzzy filter matches against. */
|
|
32
|
+
searchText: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface RegistryModelLike {
|
|
36
|
+
provider: string;
|
|
37
|
+
id: string;
|
|
38
|
+
name?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Build the picker's static item list from registry models (already
|
|
42
|
+
* filtered to configured-auth providers by the caller). Session row first,
|
|
43
|
+
* manual-entry row last; models sorted by provider then id. */
|
|
44
|
+
export function buildModelPickItems(models: RegistryModelLike[], sessionLabel: string): ModelPickItem[] {
|
|
45
|
+
const sorted = [...models].sort((a, b) =>
|
|
46
|
+
a.provider === b.provider ? a.id.localeCompare(b.id) : a.provider.localeCompare(b.provider),
|
|
47
|
+
);
|
|
48
|
+
return [
|
|
49
|
+
{
|
|
50
|
+
kind: "session",
|
|
51
|
+
label: `session model (${sessionLabel}) — clear the override`,
|
|
52
|
+
searchText: "session model default clear override follow",
|
|
53
|
+
},
|
|
54
|
+
...sorted.map((m) => {
|
|
55
|
+
const ref = `${m.provider}/${m.id}`;
|
|
56
|
+
return {
|
|
57
|
+
kind: "model" as const,
|
|
58
|
+
ref,
|
|
59
|
+
label: m.name && m.name !== m.id ? `${ref} — ${m.name}` : ref,
|
|
60
|
+
searchText: `${ref} ${m.name ?? ""}`,
|
|
61
|
+
};
|
|
62
|
+
}),
|
|
63
|
+
{
|
|
64
|
+
kind: "manual",
|
|
65
|
+
label: "type provider/model manually…",
|
|
66
|
+
searchText: "manual type custom provider model id",
|
|
67
|
+
},
|
|
68
|
+
];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface ModelPickerFactoryDeps {
|
|
72
|
+
title: string;
|
|
73
|
+
items: ModelPickItem[];
|
|
74
|
+
/** Cap on visible list rows (window scrolls with the selection). */
|
|
75
|
+
maxVisibleRows?: number;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export class ModelPickerComponent {
|
|
79
|
+
private readonly title: string;
|
|
80
|
+
private readonly items: ModelPickItem[];
|
|
81
|
+
private readonly maxRows: number;
|
|
82
|
+
private readonly requestRender: () => void;
|
|
83
|
+
private readonly theme: SettingsMenuTheme;
|
|
84
|
+
private readonly keybindings: KeybindingsManagerLike;
|
|
85
|
+
private readonly done: (item: ModelPickItem | undefined) => void;
|
|
86
|
+
|
|
87
|
+
private query = "";
|
|
88
|
+
private selectedIdx = 0;
|
|
89
|
+
|
|
90
|
+
constructor(
|
|
91
|
+
deps: ModelPickerFactoryDeps,
|
|
92
|
+
requestRender: () => void,
|
|
93
|
+
theme: SettingsMenuTheme,
|
|
94
|
+
keybindings: KeybindingsManagerLike,
|
|
95
|
+
done: (item: ModelPickItem | undefined) => void,
|
|
96
|
+
) {
|
|
97
|
+
this.title = deps.title;
|
|
98
|
+
this.items = deps.items;
|
|
99
|
+
this.maxRows = deps.maxVisibleRows ?? 12;
|
|
100
|
+
this.requestRender = requestRender;
|
|
101
|
+
this.theme = theme;
|
|
102
|
+
this.keybindings = keybindings;
|
|
103
|
+
this.done = done;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Current search query. Exposed for tests. */
|
|
107
|
+
getQuery(): string {
|
|
108
|
+
return this.query;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Index into the filtered list. Exposed for tests. */
|
|
112
|
+
getSelectedIdx(): number {
|
|
113
|
+
return this.selectedIdx;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Filtered items for the current query. Exposed for tests. */
|
|
117
|
+
filteredItems(): ModelPickItem[] {
|
|
118
|
+
if (!this.query.trim()) return this.items;
|
|
119
|
+
return fuzzyFilter(this.items, this.query.trim(), (it) => it.searchText);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private refresh(): void {
|
|
123
|
+
this.requestRender();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private move(delta: number): void {
|
|
127
|
+
const n = this.filteredItems().length;
|
|
128
|
+
if (n === 0) return;
|
|
129
|
+
this.selectedIdx = ((this.selectedIdx + delta) % n + n) % n;
|
|
130
|
+
this.refresh();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
render(width: number): string[] {
|
|
134
|
+
const w = Math.max(20, width - 2);
|
|
135
|
+
const lines: string[] = [];
|
|
136
|
+
lines.push(this.theme.fg("accent", this.theme.bold(this.title)));
|
|
137
|
+
lines.push("");
|
|
138
|
+
const searchLine = `search: ${this.query}`;
|
|
139
|
+
lines.push(this.theme.fg("muted", truncateToWidth(searchLine, w, "…") + "▏"));
|
|
140
|
+
lines.push("");
|
|
141
|
+
const filtered = this.filteredItems();
|
|
142
|
+
if (filtered.length === 0) {
|
|
143
|
+
lines.push(this.theme.fg("warning", " no matches — keep typing, or Esc to cancel"));
|
|
144
|
+
} else {
|
|
145
|
+
const sel = Math.min(this.selectedIdx, filtered.length - 1);
|
|
146
|
+
const half = Math.floor(this.maxRows / 2);
|
|
147
|
+
const start = Math.max(0, Math.min(sel - half, filtered.length - this.maxRows));
|
|
148
|
+
const window = filtered.slice(start, start + this.maxRows);
|
|
149
|
+
if (start > 0) lines.push(this.theme.fg("dim", ` ↑ ${start} more`));
|
|
150
|
+
for (let i = 0; i < window.length; i++) {
|
|
151
|
+
const idx = start + i;
|
|
152
|
+
const it = window[i]!;
|
|
153
|
+
const row = truncateToWidth(it.label, w - 2, "…");
|
|
154
|
+
lines.push(idx === sel ? this.theme.fg("accent", `→ ${row}`) : ` ${row}`);
|
|
155
|
+
}
|
|
156
|
+
const remaining = filtered.length - (start + window.length);
|
|
157
|
+
if (remaining > 0) lines.push(this.theme.fg("dim", ` ↓ ${remaining} more`));
|
|
158
|
+
}
|
|
159
|
+
lines.push("");
|
|
160
|
+
lines.push(this.theme.fg("dim", "type to filter · ↑/↓ move · enter select · esc cancel"));
|
|
161
|
+
return lines;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
handleInput(data: string): void {
|
|
165
|
+
if (this.keybindings.matches(data, "tui.select.confirm")) {
|
|
166
|
+
const it = this.filteredItems()[this.selectedIdx];
|
|
167
|
+
this.done(it);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (this.keybindings.matches(data, "tui.select.cancel") || data === "\x1b") {
|
|
171
|
+
this.done(undefined);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (this.keybindings.matches(data, "tui.select.up")) {
|
|
175
|
+
this.move(-1);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (this.keybindings.matches(data, "tui.select.down")) {
|
|
179
|
+
this.move(+1);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
if (data === "\x7f" || data === "\b") {
|
|
183
|
+
if (this.query.length > 0) {
|
|
184
|
+
this.query = this.query.slice(0, -1);
|
|
185
|
+
this.selectedIdx = 0;
|
|
186
|
+
this.refresh();
|
|
187
|
+
}
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
// Printable input (single keystrokes and pasted runs alike). Ignore
|
|
191
|
+
// escape/CSI sequences — they start with \x1b and were handled above.
|
|
192
|
+
if (!data.startsWith("\x1b")) {
|
|
193
|
+
const printable = [...data].filter((ch) => ch >= " ").join("");
|
|
194
|
+
if (printable.length > 0) {
|
|
195
|
+
this.query += printable;
|
|
196
|
+
this.selectedIdx = 0;
|
|
197
|
+
this.refresh();
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
invalidate(): void {
|
|
203
|
+
// Stateless beyond the query/selection — nothing to clear.
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Re-export for callers that only need the width helper's type signature.
|
|
208
|
+
export { visibleWidth };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-goal-list-loop-audit",
|
|
3
|
-
"version": "0.29.
|
|
3
|
+
"version": "0.29.18",
|
|
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",
|