@xynogen/pix-optimizer 1.1.6 → 1.1.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xynogen/pix-optimizer",
3
- "version": "1.1.6",
3
+ "version": "1.1.7",
4
4
  "description": "Performance optimization suite for Pi Coding Agent - caveman mode + RTK tool rewriting + jq/TOON JSON compression + ponytail lazy-dev mode",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
package/src/caveman.ts CHANGED
@@ -10,6 +10,7 @@ import type {
10
10
  ExtensionCommandContext,
11
11
  ExtensionContext,
12
12
  } from "@earendil-works/pi-coding-agent";
13
+ import { loadOptValue, saveOptValue } from "./persist.ts";
13
14
  import type { OptimizerHandle, OptimizerStatus } from "./status.ts";
14
15
 
15
16
  // ── Levels ────────────────────────────────────────────────────────────────────
@@ -155,11 +156,16 @@ export function caveman(
155
156
  // -- Restore live level from the session log on load --
156
157
 
157
158
  pi.on("session_start", async (_event, ctx) => {
159
+ // Session log first (survives in-session branch nav), then disk (survives
160
+ // a full quit/restart). Disk wins when present so a new session restores
161
+ // the last chosen level instead of defaulting to off.
158
162
  for (const entry of ctx.sessionManager.getEntries()) {
159
163
  if (entry.type === "custom" && entry.customType === "caveman-level") {
160
164
  level = (entry.data as { level: Level })?.level ?? level;
161
165
  }
162
166
  }
167
+ const saved = loadOptValue("caveman");
168
+ if (saved && LEVELS.includes(saved as Level)) level = saved as Level;
163
169
  syncStatus(ctx);
164
170
  });
165
171
 
@@ -182,6 +188,7 @@ export function caveman(
182
188
  level = resolved;
183
189
 
184
190
  pi.appendEntry("caveman-level", { level });
191
+ saveOptValue("caveman", level);
185
192
  syncStatus(ctx);
186
193
 
187
194
  ctx.ui.notify(
package/src/json.ts CHANGED
@@ -22,6 +22,7 @@ import type {
22
22
  ExtensionCommandContext,
23
23
  ExtensionContext,
24
24
  } from "@earendil-works/pi-coding-agent";
25
+ import { loadOptValue, saveOptValue } from "./persist.ts";
25
26
  import type { OptimizerHandle, OptimizerStatus } from "./status.ts";
26
27
 
27
28
  // ── System prompt ───────────────────────────────────────────────────────────
@@ -225,6 +226,9 @@ export function json(
225
226
  }
226
227
 
227
228
  pi.on("session_start", async (_event, ctx) => {
229
+ // Restore the user's on/off choice from disk (survives quit/restart).
230
+ const saved = loadOptValue("toon");
231
+ if (saved === "on" || saved === "off") enabled = saved === "on";
228
232
  syncStatus(ctx);
229
233
  });
230
234
  pi.on("agent_start", async (_event, ctx) => {
@@ -277,6 +281,7 @@ export function json(
277
281
  ctx: ExtensionCommandContext,
278
282
  ): Promise<void> {
279
283
  enabled = value === "on";
284
+ saveOptValue("toon", enabled ? "on" : "off");
280
285
 
281
286
  syncStatus(ctx);
282
287
  ctx.ui.notify(`JSON/TOON guidance ${enabled ? "on" : "off"}.`, "info");
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Regression test for the cross-session reset bug: optimizer tool states
3
+ * (caveman/ponytail/rtk/toon) were lost on a full quit/restart because they
4
+ * only persisted to the session log. persist.ts adds disk persistence so a
5
+ * value written in one session is readable in the next.
6
+ */
7
+
8
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
9
+ import { mkdtempSync, rmSync } from "node:fs";
10
+ import { tmpdir } from "node:os";
11
+ import { join } from "node:path";
12
+ import { loadOptValue, saveOptValue } from "./persist.ts";
13
+
14
+ let tmpAgentDir: string;
15
+
16
+ beforeAll(() => {
17
+ tmpAgentDir = mkdtempSync(join(tmpdir(), "optimizer-persist-test-"));
18
+ process.env.PI_CODING_AGENT_DIR = tmpAgentDir;
19
+ });
20
+
21
+ afterAll(() => {
22
+ delete process.env.PI_CODING_AGENT_DIR;
23
+ try {
24
+ rmSync(tmpAgentDir, { recursive: true });
25
+ } catch {
26
+ // temp dir may already be gone — safe to ignore
27
+ }
28
+ });
29
+
30
+ describe("optimizer persistence", () => {
31
+ test("returns undefined before anything is saved", () => {
32
+ expect(loadOptValue("caveman")).toBeUndefined();
33
+ });
34
+
35
+ test("round-trips a single tool value across save/load (new-session sim)", () => {
36
+ saveOptValue("caveman", "lite");
37
+ // A fresh load (as a new session would do) sees the persisted value.
38
+ expect(loadOptValue("caveman")).toBe("lite");
39
+ });
40
+
41
+ test("persists each tool independently in one shared file", () => {
42
+ saveOptValue("ponytail", "full");
43
+ saveOptValue("rtk", "off");
44
+ saveOptValue("toon", "on");
45
+ expect(loadOptValue("caveman")).toBe("lite");
46
+ expect(loadOptValue("ponytail")).toBe("full");
47
+ expect(loadOptValue("rtk")).toBe("off");
48
+ expect(loadOptValue("toon")).toBe("on");
49
+ });
50
+
51
+ test("overwriting one tool leaves the others intact", () => {
52
+ saveOptValue("caveman", "ultra");
53
+ expect(loadOptValue("caveman")).toBe("ultra");
54
+ expect(loadOptValue("ponytail")).toBe("full");
55
+ });
56
+ });
package/src/persist.ts ADDED
@@ -0,0 +1,50 @@
1
+ /**
2
+ * persist.ts — disk-backed persistence for the /optimizer tool states.
3
+ *
4
+ * caveman/ponytail previously saved only to the session log (lost on a fresh
5
+ * session); rtk/toon never persisted at all. This stores every tool's current
6
+ * value in one file under the agent dir so the picker survives a full quit and
7
+ * restart. Each tool reads its value on session_start and writes on run().
8
+ *
9
+ * ~/.pi/agent/optimizer.json → { "caveman": "lite", "rtk": "on", ... }
10
+ */
11
+
12
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
13
+ import { dirname, join } from "node:path";
14
+ import { getAgentDir } from "@earendil-works/pi-coding-agent";
15
+ import type { OptimizerTool } from "./status.ts";
16
+
17
+ type OptimizerConfig = Partial<Record<OptimizerTool, string>>;
18
+
19
+ function getStatePath(): string {
20
+ return join(getAgentDir(), "optimizer.json");
21
+ }
22
+
23
+ function readAll(): OptimizerConfig {
24
+ try {
25
+ const sp = getStatePath();
26
+ if (!existsSync(sp)) return {};
27
+ const raw = JSON.parse(readFileSync(sp, "utf-8")) as OptimizerConfig;
28
+ return raw && typeof raw === "object" ? raw : {};
29
+ } catch {
30
+ // Missing, corrupt, or no getAgentDir (tests/headless) — start clean.
31
+ return {};
32
+ }
33
+ }
34
+
35
+ /** Read a single tool's persisted value, or undefined if none. */
36
+ export function loadOptValue(tool: OptimizerTool): string | undefined {
37
+ return readAll()[tool];
38
+ }
39
+
40
+ /** Persist a single tool's value, merging into the shared config file. */
41
+ export function saveOptValue(tool: OptimizerTool, value: string): void {
42
+ try {
43
+ const sp = getStatePath();
44
+ mkdirSync(dirname(sp), { recursive: true });
45
+ const next = { ...readAll(), [tool]: value };
46
+ writeFileSync(sp, JSON.stringify(next, null, 2), "utf-8");
47
+ } catch (err) {
48
+ console.warn(`optimizer: persist ${tool} failed:`, err);
49
+ }
50
+ }
package/src/ponytail.ts CHANGED
@@ -14,6 +14,7 @@ import type {
14
14
  ExtensionCommandContext,
15
15
  ExtensionContext,
16
16
  } from "@earendil-works/pi-coding-agent";
17
+ import { loadOptValue, saveOptValue } from "./persist.ts";
17
18
  import type { OptimizerHandle, OptimizerStatus } from "./status.ts";
18
19
 
19
20
  // ── Levels ────────────────────────────────────────────────────────────────────
@@ -159,11 +160,15 @@ export function ponytail(
159
160
  // -- Restore live level from the session log on load --
160
161
 
161
162
  pi.on("session_start", async (_event, ctx) => {
163
+ // Session log first (in-session branch nav), then disk (survives a full
164
+ // quit/restart). Disk wins when present.
162
165
  for (const entry of ctx.sessionManager.getEntries()) {
163
166
  if (entry.type === "custom" && entry.customType === "ponytail-level") {
164
167
  level = (entry.data as { level: Level })?.level ?? level;
165
168
  }
166
169
  }
170
+ const saved = loadOptValue("ponytail");
171
+ if (saved && LEVELS.includes(saved as Level)) level = saved as Level;
167
172
  syncStatus(ctx);
168
173
  });
169
174
 
@@ -186,6 +191,7 @@ export function ponytail(
186
191
  level = resolved;
187
192
 
188
193
  pi.appendEntry("ponytail-level", { level });
194
+ saveOptValue("ponytail", level);
189
195
  syncStatus(ctx);
190
196
 
191
197
  ctx.ui.notify(
package/src/rtk.ts CHANGED
@@ -11,6 +11,7 @@ import type {
11
11
  ExtensionCommandContext,
12
12
  ExtensionContext,
13
13
  } from "@earendil-works/pi-coding-agent";
14
+ import { loadOptValue, saveOptValue } from "./persist.ts";
14
15
  import type { OptimizerHandle, OptimizerStatus } from "./status.ts";
15
16
 
16
17
  /**
@@ -283,6 +284,9 @@ export function rtk(
283
284
  // Keep the status indicator in sync across the agent lifecycle. Probe
284
285
  // availability on session start so the icon reflects reality immediately.
285
286
  pi.on("session_start", async (_event, ctx) => {
287
+ // Restore the user's on/off choice from disk (survives quit/restart).
288
+ const saved = loadOptValue("rtk");
289
+ if (saved === "on" || saved === "off") enabled = saved === "on";
286
290
  const probe = await checkRtkAvailability();
287
291
  if (!probe.available && !warnedMissing) {
288
292
  ctx.ui.notify(
@@ -307,6 +311,7 @@ export function rtk(
307
311
  ctx: ExtensionCommandContext,
308
312
  ): Promise<void> {
309
313
  enabled = value === "on";
314
+ saveOptValue("rtk", enabled ? "on" : "off");
310
315
 
311
316
  await checkRtkAvailability();
312
317
  syncStatus(ctx);