@xynogen/pix-optimizer 1.1.17 → 1.1.19

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.17",
3
+ "version": "1.1.19",
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",
@@ -9,6 +9,7 @@
9
9
  },
10
10
  "files": [
11
11
  "src",
12
+ "!src/**/*.test.ts",
12
13
  "README.md",
13
14
  "LICENSE"
14
15
  ],
@@ -48,8 +49,7 @@
48
49
  "access": "public"
49
50
  },
50
51
  "dependencies": {
51
- "@xynogen/pix-pretty": "^1.7.9",
52
- "@xynogen/pix-data": "^0.3.0"
52
+ "@xynogen/pix-pretty": "^1.7.22"
53
53
  },
54
54
  "peerDependencies": {
55
55
  "@earendil-works/pi-coding-agent": "*",
package/src/caveman.ts CHANGED
@@ -5,12 +5,12 @@
5
5
  * called by index.ts alongside rtk(pi).
6
6
  */
7
7
 
8
- import type {
9
- ExtensionAPI,
10
- ExtensionCommandContext,
11
- ExtensionContext,
12
- } from "@earendil-works/pi-coding-agent";
13
- import { loadOptValue, saveOptValue } from "./persist.ts";
8
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
9
+ import {
10
+ createMode,
11
+ resolveLevel as resolveLevelGeneric,
12
+ toggleLevel as toggleLevelGeneric,
13
+ } from "./mode.ts";
14
14
  import type { OptimizerHandle, OptimizerStatus } from "./status.ts";
15
15
 
16
16
  // ── Levels ────────────────────────────────────────────────────────────────────
@@ -97,11 +97,7 @@ export function buildPrompt(level: Level): string {
97
97
  * Handles stop aliases (stop/quit → "off") and valid level names.
98
98
  */
99
99
  export function resolveLevel(arg: string): Level | null {
100
- const a = arg.trim().toLowerCase();
101
- if (STOP_ALIASES.has(a)) return "off";
102
- if (LEVEL_NUMBERS[a]) return LEVEL_NUMBERS[a];
103
- if (LEVELS.includes(a as Level)) return a as Level;
104
- return null;
100
+ return resolveLevelGeneric(arg, LEVELS, LEVEL_NUMBERS, STOP_ALIASES);
105
101
  }
106
102
 
107
103
  /**
@@ -127,74 +123,18 @@ export function buildHelp(current: Level): string {
127
123
  * Toggle: off → full, anything else → off.
128
124
  */
129
125
  export function toggleLevel(current: Level): Level {
130
- return current === "off" ? "full" : "off";
126
+ return toggleLevelGeneric(current);
131
127
  }
132
128
 
133
129
  // ── Pi extension ────────────────────────────────────────────────────────────
134
130
 
135
131
  export function caveman(pi: ExtensionAPI, status: OptimizerStatus): OptimizerHandle {
136
- let level: Level = "off";
137
-
138
- // -- Status: report into the shared optimizer indicator. --
139
-
140
- function syncStatus(ctx: Pick<ExtensionContext, "ui">) {
141
- status.set("caveman", level !== "off", ctx);
142
- }
143
-
144
- // Inject caveman prompt via before_agent_start
145
- pi.on("before_agent_start", async (event, _ctx) => {
146
- const prompt = buildPrompt(level);
147
- if (!prompt) return undefined;
148
- const existing = event.systemPrompt ?? "";
149
- return { systemPrompt: `${prompt}\n\n${existing}` };
150
- });
151
-
152
- // -- Restore live level from the session log on load --
153
-
154
- pi.on("session_start", async (_event, ctx) => {
155
- // Session log first (survives in-session branch nav), then disk (survives
156
- // a full quit/restart). Disk wins when present so a new session restores
157
- // the last chosen level instead of defaulting to off.
158
- for (const entry of ctx.sessionManager.getEntries()) {
159
- if (entry.type === "custom" && entry.customType === "caveman-level") {
160
- level = (entry.data as { level: Level })?.level ?? level;
161
- }
162
- }
163
- const saved = loadOptValue("caveman");
164
- if (saved && LEVELS.includes(saved as Level)) level = saved as Level;
165
- syncStatus(ctx);
166
- });
167
-
168
- pi.on("agent_start", async (_event, ctx) => {
169
- syncStatus(ctx);
170
- });
171
- pi.on("agent_end", async (_event, ctx) => {
172
- syncStatus(ctx);
173
- });
174
- pi.on("session_shutdown", async () => {});
175
-
176
- // -- Overlay value handler (called by the /optimizer overlay) --
177
-
178
- async function run(value: string, ctx: ExtensionCommandContext): Promise<void> {
179
- const resolved = resolveLevel(value);
180
- if (resolved === null) return;
181
- level = resolved;
182
-
183
- pi.appendEntry("caveman-level", { level });
184
- saveOptValue("caveman", level);
185
- syncStatus(ctx);
186
-
187
- ctx.ui.notify(
188
- level === "off" ? "Caveman mode off." : `Caveman: ${STATUS_LABELS[level]}`,
189
- "info",
190
- );
191
- }
192
-
193
- return {
132
+ return createMode(pi, status, {
194
133
  name: "caveman",
195
134
  help: "caveman — terse output",
196
- values: LEVELS,
197
- current: () => level,
198
- run,
199
- };
135
+ levels: LEVELS,
136
+ buildPrompt,
137
+ resolve: resolveLevel,
138
+ notify: (level) => (level === "off" ? "Caveman mode off." : `Caveman: ${STATUS_LABELS[level]}`),
139
+ });
200
140
  }
package/src/mode.ts ADDED
@@ -0,0 +1,116 @@
1
+ /**
2
+ * mode.ts — shared plumbing for prompt-injection optimizer modes.
3
+ *
4
+ * caveman.ts and ponytail.ts are the same *kind* of tool: a level enum, a
5
+ * system-prompt fragment injected via before_agent_start, and identical
6
+ * persistence/status wiring. Only the prompt/help/label *content* differs —
7
+ * that stays in each module. This file owns the generic level resolution and
8
+ * the extension factory both call.
9
+ */
10
+
11
+ import type {
12
+ ExtensionAPI,
13
+ ExtensionCommandContext,
14
+ ExtensionContext,
15
+ } from "@earendil-works/pi-coding-agent";
16
+ import { loadOptValue, saveOptValue } from "./persist.ts";
17
+ import type { OptimizerHandle, OptimizerStatus, OptimizerTool } from "./status.ts";
18
+
19
+ /**
20
+ * Resolve a raw command arg to a level, or null if unrecognised.
21
+ * Handles stop aliases (stop/quit → "off"), numeric shortcuts, and level names.
22
+ */
23
+ export function resolveLevel<L extends string>(
24
+ arg: string,
25
+ levels: readonly L[],
26
+ levelNumbers: Record<string, L>,
27
+ stopAliases: ReadonlySet<string>,
28
+ ): L | null {
29
+ const a = arg.trim().toLowerCase();
30
+ if (stopAliases.has(a)) return "off" as L;
31
+ if (levelNumbers[a]) return levelNumbers[a];
32
+ if (levels.includes(a as L)) return a as L;
33
+ return null;
34
+ }
35
+
36
+ /** Toggle: off → full, anything else → off. */
37
+ export function toggleLevel<L extends string>(current: L): L {
38
+ return (current === "off" ? "full" : "off") as L;
39
+ }
40
+
41
+ export interface ModeConfig<L extends string> {
42
+ /** Status-registry key + persistence key + `${name}-level` custom entry. */
43
+ name: OptimizerTool;
44
+ /** One-line overlay summary. */
45
+ help: string;
46
+ /** Cyclable values in display order. */
47
+ levels: readonly L[];
48
+ /** Build the system-prompt fragment for a level ("" when off). */
49
+ buildPrompt(level: L): string;
50
+ /** Resolve an overlay/command value to a level (each module supplies its enum). */
51
+ resolve(value: string): L | null;
52
+ /** Notification shown after a successful change. */
53
+ notify(level: L): string;
54
+ }
55
+
56
+ /**
57
+ * Build the shared extension: prompt injection + session restore + status +
58
+ * overlay handle. Identical across every prompt-mode tool.
59
+ */
60
+ export function createMode<L extends string>(
61
+ pi: ExtensionAPI,
62
+ status: OptimizerStatus,
63
+ config: ModeConfig<L>,
64
+ ): OptimizerHandle {
65
+ const { name, levels, buildPrompt } = config;
66
+ const customType = `${name}-level`;
67
+ let level = "off" as L;
68
+
69
+ function syncStatus(ctx: Pick<ExtensionContext, "ui">) {
70
+ status.set(name, level !== "off", ctx);
71
+ }
72
+
73
+ pi.on("before_agent_start", async (event, _ctx) => {
74
+ const prompt = buildPrompt(level);
75
+ if (!prompt) return undefined;
76
+ const existing = event.systemPrompt ?? "";
77
+ return { systemPrompt: `${prompt}\n\n${existing}` };
78
+ });
79
+
80
+ pi.on("session_start", async (_event, ctx) => {
81
+ // Session log first (survives in-session branch nav), then disk (survives
82
+ // a full quit/restart). Disk wins when present.
83
+ for (const entry of ctx.sessionManager.getEntries()) {
84
+ if (entry.type === "custom" && entry.customType === customType) {
85
+ level = ((entry.data as { level: L })?.level ?? level) as L;
86
+ }
87
+ }
88
+ const saved = loadOptValue(name);
89
+ if (saved && levels.includes(saved as L)) level = saved as L;
90
+ syncStatus(ctx);
91
+ });
92
+
93
+ pi.on("agent_start", async (_event, ctx) => syncStatus(ctx));
94
+ pi.on("agent_end", async (_event, ctx) => syncStatus(ctx));
95
+ pi.on("session_shutdown", async () => {});
96
+
97
+ async function run(value: string, ctx: ExtensionCommandContext): Promise<void> {
98
+ const resolved = config.resolve(value);
99
+ if (resolved === null) return;
100
+ level = resolved;
101
+
102
+ pi.appendEntry(customType, { level });
103
+ saveOptValue(name, level);
104
+ syncStatus(ctx);
105
+
106
+ ctx.ui.notify(config.notify(level), "info");
107
+ }
108
+
109
+ return {
110
+ name,
111
+ help: config.help,
112
+ values: levels,
113
+ current: () => level,
114
+ run,
115
+ };
116
+ }
package/src/opt.ts CHANGED
@@ -15,6 +15,7 @@
15
15
  */
16
16
 
17
17
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
18
+ import { matchesKey } from "@earendil-works/pi-tui";
18
19
  import { frameLines } from "@xynogen/pix-pretty/modal-frame";
19
20
  import type { OptimizerHandle, OptimizerStatus, OptimizerTool } from "./status.ts";
20
21
  import { toolIcon } from "./status.ts";
@@ -166,12 +167,20 @@ export function registerOptCommand(
166
167
  },
167
168
  invalidate: () => {},
168
169
  handleInput: (data: string) => {
169
- if (data === "k" || data === "\u001b[A") move(-1);
170
- else if (data === "j" || data === "\u001b[B") move(1);
171
- else if (data === "h" || data === "\u001b[D") cycle(-1);
172
- else if (data === "l" || data === "\u001b[C" || data === " " || data === "\r")
170
+ // matchesKey handles both legacy bytes and Kitty CSI-u encodings
171
+ // for letters and special keys alike — raw string compares like
172
+ // `data === "k"` silently fail under the Kitty keyboard protocol.
173
+ if (matchesKey(data, "k") || matchesKey(data, "up")) move(-1);
174
+ else if (matchesKey(data, "j") || matchesKey(data, "down")) move(1);
175
+ else if (matchesKey(data, "h") || matchesKey(data, "left")) cycle(-1);
176
+ else if (
177
+ matchesKey(data, "l") ||
178
+ matchesKey(data, "right") ||
179
+ matchesKey(data, "space") ||
180
+ matchesKey(data, "enter")
181
+ )
173
182
  cycle(1);
174
- else if (data === "\u001b" || data === "q") {
183
+ else if (matchesKey(data, "escape") || matchesKey(data, "q")) {
175
184
  done(null);
176
185
  return;
177
186
  } else return;
package/src/persist.ts CHANGED
@@ -12,7 +12,6 @@
12
12
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
13
13
  import { dirname, join } from "node:path";
14
14
  import { getAgentDir } from "@earendil-works/pi-coding-agent";
15
- import { pixConfig } from "@xynogen/pix-data/pix-config";
16
15
  import type { OptimizerTool } from "./status.ts";
17
16
 
18
17
  type OptimizerFileConfig = Partial<Record<OptimizerTool, string>>;
@@ -32,17 +31,9 @@ function readFile(): OptimizerFileConfig {
32
31
  }
33
32
  }
34
33
 
35
- /**
36
- * Read a single tool's persisted value.
37
- * Precedence: optimizer.json (runtime toggle) → pix.json → undefined
38
- */
34
+ /** Read a single tool's persisted value from optimizer.json. */
39
35
  export function loadOptValue(tool: OptimizerTool): string | undefined {
40
- const fromFile = readFile()[tool];
41
- if (fromFile !== undefined) return fromFile;
42
- // Fall back to pix.json
43
- const pix = pixConfig().optimizer;
44
- const val = pix[tool as keyof typeof pix];
45
- return val && val !== "off" ? val : undefined;
36
+ return readFile()[tool];
46
37
  }
47
38
 
48
39
  /** Persist a single tool's value, merging into the shared config file. */
package/src/ponytail.ts CHANGED
@@ -9,12 +9,12 @@
9
9
  * skill. We inject it as a system-prompt fragment — no external hooks/files.
10
10
  */
11
11
 
12
- import type {
13
- ExtensionAPI,
14
- ExtensionCommandContext,
15
- ExtensionContext,
16
- } from "@earendil-works/pi-coding-agent";
17
- import { loadOptValue, saveOptValue } from "./persist.ts";
12
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
13
+ import {
14
+ createMode,
15
+ resolveLevel as resolveLevelGeneric,
16
+ toggleLevel as toggleLevelGeneric,
17
+ } from "./mode.ts";
18
18
  import type { OptimizerHandle, OptimizerStatus } from "./status.ts";
19
19
 
20
20
  // ── Levels ────────────────────────────────────────────────────────────────────
@@ -102,11 +102,7 @@ export function buildPrompt(level: Level): string {
102
102
  * Handles stop aliases (stop/quit → "off") and valid level names.
103
103
  */
104
104
  export function resolveLevel(arg: string): Level | null {
105
- const a = arg.trim().toLowerCase();
106
- if (STOP_ALIASES.has(a)) return "off";
107
- if (LEVEL_NUMBERS[a]) return LEVEL_NUMBERS[a];
108
- if (LEVELS.includes(a as Level)) return a as Level;
109
- return null;
105
+ return resolveLevelGeneric(arg, LEVELS, LEVEL_NUMBERS, STOP_ALIASES);
110
106
  }
111
107
 
112
108
  /**
@@ -131,73 +127,19 @@ export function buildHelp(current: Level): string {
131
127
  * Toggle: off → full, anything else → off.
132
128
  */
133
129
  export function toggleLevel(current: Level): Level {
134
- return current === "off" ? "full" : "off";
130
+ return toggleLevelGeneric(current);
135
131
  }
136
132
 
137
133
  // ── Pi extension ────────────────────────────────────────────────────────────
138
134
 
139
135
  export function ponytail(pi: ExtensionAPI, status: OptimizerStatus): OptimizerHandle {
140
- let level: Level = "off";
141
-
142
- // -- Status: report into the shared optimizer indicator. --
143
-
144
- function syncStatus(ctx: Pick<ExtensionContext, "ui">) {
145
- status.set("ponytail", level !== "off", ctx);
146
- }
147
-
148
- // Inject ponytail prompt via before_agent_start
149
- pi.on("before_agent_start", async (event, _ctx) => {
150
- const prompt = buildPrompt(level);
151
- if (!prompt) return undefined;
152
- const existing = event.systemPrompt ?? "";
153
- return { systemPrompt: `${prompt}\n\n${existing}` };
154
- });
155
-
156
- // -- Restore live level from the session log on load --
157
-
158
- pi.on("session_start", async (_event, ctx) => {
159
- // Session log first (in-session branch nav), then disk (survives a full
160
- // quit/restart). Disk wins when present.
161
- for (const entry of ctx.sessionManager.getEntries()) {
162
- if (entry.type === "custom" && entry.customType === "ponytail-level") {
163
- level = (entry.data as { level: Level })?.level ?? level;
164
- }
165
- }
166
- const saved = loadOptValue("ponytail");
167
- if (saved && LEVELS.includes(saved as Level)) level = saved as Level;
168
- syncStatus(ctx);
169
- });
170
-
171
- pi.on("agent_start", async (_event, ctx) => {
172
- syncStatus(ctx);
173
- });
174
- pi.on("agent_end", async (_event, ctx) => {
175
- syncStatus(ctx);
176
- });
177
- pi.on("session_shutdown", async () => {});
178
-
179
- // -- Overlay value handler (called by the /optimizer overlay) --
180
-
181
- async function run(value: string, ctx: ExtensionCommandContext): Promise<void> {
182
- const resolved = resolveLevel(value);
183
- if (resolved === null) return;
184
- level = resolved;
185
-
186
- pi.appendEntry("ponytail-level", { level });
187
- saveOptValue("ponytail", level);
188
- syncStatus(ctx);
189
-
190
- ctx.ui.notify(
191
- level === "off" ? "Ponytail mode off." : `Ponytail: ${STATUS_LABELS[level]}`,
192
- "info",
193
- );
194
- }
195
-
196
- return {
136
+ return createMode(pi, status, {
197
137
  name: "ponytail",
198
138
  help: "ponytail — lazy senior dev (minimal code)",
199
- values: LEVELS,
200
- current: () => level,
201
- run,
202
- };
139
+ levels: LEVELS,
140
+ buildPrompt,
141
+ resolve: resolveLevel,
142
+ notify: (level) =>
143
+ level === "off" ? "Ponytail mode off." : `Ponytail: ${STATUS_LABELS[level]}`,
144
+ });
203
145
  }
@@ -1,42 +0,0 @@
1
- import { describe, expect, it, mock } from "bun:test";
2
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
3
- import { canExecute } from "./capability.ts";
4
-
5
- function executor(impl: ExtensionAPI["exec"]): Pick<ExtensionAPI, "exec"> {
6
- return { exec: impl };
7
- }
8
-
9
- describe("canExecute", () => {
10
- it("runs the requested capability probe without a shell", async () => {
11
- const exec = mock(async () => ({
12
- stdout: "jq-1.7",
13
- stderr: "",
14
- code: 0,
15
- killed: false,
16
- }));
17
-
18
- expect(await canExecute(executor(exec), "jq", ["--version"])).toBe(true);
19
- expect(exec).toHaveBeenCalledWith("jq", ["--version"], { timeout: 3000 });
20
- });
21
-
22
- it("supports a custom timeout", async () => {
23
- const exec = mock(async () => ({ stdout: "2.3.0", stderr: "", code: 0, killed: false }));
24
-
25
- expect(await canExecute(executor(exec), "toon", ["--version"], 1000)).toBe(true);
26
- expect(exec).toHaveBeenCalledWith("toon", ["--version"], { timeout: 1000 });
27
- });
28
-
29
- it("returns false for a non-zero exit", async () => {
30
- const exec = mock(async () => ({ stdout: "", stderr: "failed", code: 1, killed: false }));
31
-
32
- expect(await canExecute(executor(exec), "jq", ["--version"])).toBe(false);
33
- });
34
-
35
- it("returns false when the executable cannot be spawned", async () => {
36
- const exec = mock(async () => {
37
- throw new Error("ENOENT");
38
- });
39
-
40
- expect(await canExecute(executor(exec), "toon", ["--version"])).toBe(false);
41
- });
42
- });
@@ -1,203 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
- import {
3
- buildHelp,
4
- buildPrompt,
5
- LEVEL_NUMBERS,
6
- LEVELS,
7
- type Level,
8
- resolveLevel,
9
- STATUS_LABELS,
10
- STOP_ALIASES,
11
- toggleLevel,
12
- } from "./caveman.ts";
13
-
14
- // ── LEVELS ────────────────────────────────────────────────────────────────────
15
-
16
- describe("LEVELS", () => {
17
- it("contains off as first entry", () => {
18
- expect(LEVELS[0]).toBe("off");
19
- });
20
-
21
- it("contains all expected levels", () => {
22
- const expected: Level[] = ["off", "lite", "full", "ultra", "micro"];
23
- for (const l of expected) expect(LEVELS).toContain(l);
24
- });
25
- });
26
-
27
- // ── STOP_ALIASES ──────────────────────────────────────────────────────────────
28
-
29
- describe("STOP_ALIASES", () => {
30
- it("includes off, stop, quit", () => {
31
- expect(STOP_ALIASES.has("off")).toBe(true);
32
- expect(STOP_ALIASES.has("stop")).toBe(true);
33
- expect(STOP_ALIASES.has("quit")).toBe(true);
34
- });
35
-
36
- it("does not include active levels", () => {
37
- expect(STOP_ALIASES.has("full")).toBe(false);
38
- expect(STOP_ALIASES.has("ultra")).toBe(false);
39
- });
40
- });
41
-
42
- // ── STATUS_LABELS ─────────────────────────────────────────────────────────────
43
-
44
- describe("STATUS_LABELS", () => {
45
- it("has a label for every non-off level", () => {
46
- const nonOff = LEVELS.filter((l) => l !== "off") as Exclude<Level, "off">[];
47
- for (const l of nonOff) {
48
- expect(STATUS_LABELS[l]).toBeTruthy();
49
- }
50
- });
51
-
52
- it("latin levels are uppercase", () => {
53
- expect(STATUS_LABELS.lite).toBe("LITE");
54
- expect(STATUS_LABELS.full).toBe("FULL");
55
- expect(STATUS_LABELS.ultra).toBe("ULTRA");
56
- expect(STATUS_LABELS.micro).toBe("MICRO");
57
- });
58
- });
59
-
60
- // ── resolveLevel ──────────────────────────────────────────────────────────────
61
-
62
- describe("resolveLevel", () => {
63
- it("resolves valid levels", () => {
64
- expect(resolveLevel("lite")).toBe("lite");
65
- expect(resolveLevel("full")).toBe("full");
66
- expect(resolveLevel("ultra")).toBe("ultra");
67
- expect(resolveLevel("micro")).toBe("micro");
68
- expect(resolveLevel("off")).toBe("off");
69
- });
70
-
71
- it("maps stop aliases to off", () => {
72
- expect(resolveLevel("stop")).toBe("off");
73
- expect(resolveLevel("quit")).toBe("off");
74
- });
75
-
76
- it("is case-insensitive", () => {
77
- expect(resolveLevel("FULL")).toBe("full");
78
- expect(resolveLevel("Ultra")).toBe("ultra");
79
- expect(resolveLevel("STOP")).toBe("off");
80
- });
81
-
82
- it("trims whitespace", () => {
83
- expect(resolveLevel(" full ")).toBe("full");
84
- });
85
-
86
- it("returns null for unknown input", () => {
87
- expect(resolveLevel("unknown")).toBeNull();
88
- expect(resolveLevel("")).toBeNull();
89
- expect(resolveLevel("config")).toBeNull(); // config handled separately
90
- });
91
- });
92
-
93
- // ── numeric levels ────────────────────────────────────────────────────────────
94
-
95
- describe("numeric levels", () => {
96
- it("maps 1/2/3 to lite/full/ultra", () => {
97
- expect(resolveLevel("1")).toBe("lite");
98
- expect(resolveLevel("2")).toBe("full");
99
- expect(resolveLevel("3")).toBe("ultra");
100
- });
101
-
102
- it("maps 0 to off", () => {
103
- expect(resolveLevel("0")).toBe("off");
104
- });
105
-
106
- it("LEVEL_NUMBERS only covers 1-3", () => {
107
- expect(Object.keys(LEVEL_NUMBERS).sort()).toEqual(["1", "2", "3"]);
108
- });
109
-
110
- it("rejects out-of-range numbers", () => {
111
- expect(resolveLevel("4")).toBeNull();
112
- expect(resolveLevel("9")).toBeNull();
113
- });
114
-
115
- it("trims whitespace around numbers", () => {
116
- expect(resolveLevel(" 2 ")).toBe("full");
117
- });
118
- });
119
-
120
- // ── buildHelp ─────────────────────────────────────────────────────────────────
121
-
122
- describe("buildHelp", () => {
123
- it("lists numeric shortcuts", () => {
124
- const help = buildHelp("off");
125
- expect(help).toContain("1");
126
- expect(help).toContain("lite");
127
- expect(help).toContain("2");
128
- expect(help).toContain("full");
129
- expect(help).toContain("3");
130
- expect(help).toContain("ultra");
131
- });
132
-
133
- it("shows current level when active", () => {
134
- expect(buildHelp("ultra")).toContain("ULTRA");
135
- });
136
-
137
- it("shows off when disabled", () => {
138
- expect(buildHelp("off")).toContain("off");
139
- });
140
-
141
- it("mentions config", () => {
142
- expect(buildHelp("off")).toContain("config");
143
- });
144
-
145
- it("does not mention wenyan", () => {
146
- expect(buildHelp("off").toLowerCase()).not.toContain("wenyan");
147
- });
148
- });
149
-
150
- // ── toggleLevel ───────────────────────────────────────────────────────────────
151
-
152
- describe("toggleLevel", () => {
153
- it("off → full", () => {
154
- expect(toggleLevel("off")).toBe("full");
155
- });
156
-
157
- it("full → off", () => {
158
- expect(toggleLevel("full")).toBe("off");
159
- });
160
-
161
- it("any non-off level → off", () => {
162
- const nonOff = LEVELS.filter((l) => l !== "off") as Level[];
163
- for (const l of nonOff) {
164
- expect(toggleLevel(l)).toBe("off");
165
- }
166
- });
167
- });
168
-
169
- // ── buildPrompt ───────────────────────────────────────────────────────────────
170
-
171
- describe("buildPrompt", () => {
172
- it("returns empty string for off", () => {
173
- expect(buildPrompt("off")).toBe("");
174
- });
175
-
176
- it("returns MICRO_PROMPT for micro (no BASE)", () => {
177
- const p = buildPrompt("micro");
178
- expect(p).toContain("Token efficiency");
179
- expect(p).not.toContain("CAVEMAN MODE");
180
- });
181
-
182
- it("includes BASE for all standard levels", () => {
183
- const standard = ["lite", "full", "ultra"] as Level[];
184
- for (const l of standard) {
185
- expect(buildPrompt(l)).toContain("CAVEMAN MODE");
186
- }
187
- });
188
-
189
- it("includes SAFETY clause for all standard levels", () => {
190
- const standard = ["lite", "full", "ultra"] as Level[];
191
- for (const l of standard) {
192
- expect(buildPrompt(l)).toContain("Auto-clarity");
193
- }
194
- });
195
-
196
- it("each level has distinct intensity instructions", () => {
197
- const lite = buildPrompt("lite");
198
- const ultra = buildPrompt("ultra");
199
- expect(lite).toContain("Keep articles");
200
- expect(ultra).toContain("Abbreviate");
201
- expect(lite).not.toContain("Abbreviate");
202
- });
203
- });