@xynogen/pix-optimizer 1.1.18 → 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/src/rtk.test.ts DELETED
@@ -1,277 +0,0 @@
1
- import { describe, expect, it, mock } from "bun:test";
2
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
3
- import {
4
- applyRtkRewrite,
5
- type BashCallEvent,
6
- buildSudoBlockReason,
7
- detectSudoSegments,
8
- probeRtkAvailability,
9
- rewriteChain,
10
- splitChain,
11
- } from "./rtk.ts";
12
-
13
- /** Build a fresh bash tool_call event for hook tests. */
14
- function bashEvent(command: string): BashCallEvent {
15
- return { toolName: "bash", input: { command } };
16
- }
17
-
18
- describe("probeRtkAvailability", () => {
19
- it("probes rtk directly and accepts a successful version check", async () => {
20
- const exec = mock(async () => ({
21
- stdout: "rtk 0.1.0",
22
- stderr: "",
23
- code: 0,
24
- killed: false,
25
- }));
26
-
27
- expect(await probeRtkAvailability({ exec } as Pick<ExtensionAPI, "exec">)).toBe(true);
28
- expect(exec).toHaveBeenCalledWith("rtk", ["--version"], { timeout: 3000 });
29
- });
30
-
31
- it("rejects an unsuccessful version check", async () => {
32
- const exec = mock(async () => ({ stdout: "", stderr: "missing", code: 1, killed: false }));
33
-
34
- expect(await probeRtkAvailability({ exec } as Pick<ExtensionAPI, "exec">)).toBe(false);
35
- });
36
-
37
- it("treats a spawn failure as unavailable", async () => {
38
- const exec = mock(async () => {
39
- throw new Error("ENOENT");
40
- });
41
-
42
- expect(await probeRtkAvailability({ exec } as Pick<ExtensionAPI, "exec">)).toBe(false);
43
- });
44
- });
45
-
46
- describe("splitChain", () => {
47
- it("returns single segment for plain command", () => {
48
- expect(splitChain("git status")).toEqual(["git status"]);
49
- });
50
-
51
- it("splits on && keeping operator", () => {
52
- expect(splitChain("git add . && git push")).toEqual(["git add . ", "&&", " git push"]);
53
- });
54
-
55
- it("splits on ||, ;, |", () => {
56
- expect(splitChain("a || b")).toEqual(["a ", "||", " b"]);
57
- expect(splitChain("a ; b")).toEqual(["a ", ";", " b"]);
58
- expect(splitChain("a | b")).toEqual(["a ", "|", " b"]);
59
- });
60
-
61
- it("ignores operators inside double quotes", () => {
62
- expect(splitChain('git commit -m "a && b"')).toEqual(['git commit -m "a && b"']);
63
- });
64
-
65
- it("ignores operators inside single quotes", () => {
66
- expect(splitChain("echo 'x | y'")).toEqual(["echo 'x | y'"]);
67
- });
68
-
69
- it("returns null on unbalanced quotes", () => {
70
- expect(splitChain('git commit -m "oops')).toBeNull();
71
- });
72
- });
73
-
74
- describe("rewriteChain", () => {
75
- it("prefixes a single known command", () => {
76
- expect(rewriteChain("git status")).toBe("rtk git status");
77
- });
78
-
79
- it("prefixes every segment in a chain", () => {
80
- expect(rewriteChain("git add . && git commit -m x && git push")).toBe(
81
- "rtk git add . && rtk git commit -m x && rtk git push",
82
- );
83
- });
84
-
85
- it("prefixes mixed known commands", () => {
86
- expect(rewriteChain("cargo build && npm test")).toBe("rtk cargo build && rtk npm test");
87
- });
88
-
89
- it("leaves unknown commands alone", () => {
90
- expect(rewriteChain("echo hi && mkdir x")).toBe("echo hi && mkdir x");
91
- });
92
-
93
- it("does not prefix find (rtk find rejects -not/-exec)", () => {
94
- const cmd = "find . -type f -not -path '*/node_modules/*'";
95
- expect(rewriteChain(cmd)).toBe(cmd);
96
- });
97
-
98
- it("only prefixes known segments in a mixed chain", () => {
99
- expect(rewriteChain("cd /tmp && git status")).toBe("cd /tmp && rtk git status");
100
- });
101
-
102
- it("does not double-prefix already-rtk commands", () => {
103
- expect(rewriteChain("rtk git status")).toBe("rtk git status");
104
- expect(rewriteChain("rtk git add . && git push")).toBe("rtk git add . && rtk git push");
105
- });
106
-
107
- it("does not touch operators inside quotes", () => {
108
- expect(rewriteChain('git commit -m "a && b"')).toBe('rtk git commit -m "a && b"');
109
- });
110
-
111
- it("returns original on unbalanced quotes", () => {
112
- const cmd = 'git commit -m "oops';
113
- expect(rewriteChain(cmd)).toBe(cmd);
114
- });
115
-
116
- it("prefixes known commands across a pipe (ls, wc)", () => {
117
- expect(rewriteChain("ls -la | wc -l")).toBe("rtk ls -la | rtk wc -l");
118
- });
119
-
120
- it("truly leaves a chain of only-unknown commands untouched", () => {
121
- expect(rewriteChain("cd /tmp | sort | uniq")).toBe("cd /tmp | sort | uniq");
122
- });
123
-
124
- it("handles pipes between known commands", () => {
125
- expect(rewriteChain("git log | grep fix")).toBe("rtk git log | rtk grep fix");
126
- });
127
- });
128
-
129
- // ── detectSudoSegments ───────────────────────────────────────────────────────
130
-
131
- describe("detectSudoSegments", () => {
132
- it("returns empty for command with no sudo", () => {
133
- expect(detectSudoSegments(["git status"])).toEqual([]);
134
- });
135
-
136
- it("detects a plain sudo segment", () => {
137
- expect(detectSudoSegments(["sudo apt-get install foo"])).toEqual(["sudo apt-get install foo"]);
138
- });
139
-
140
- it("detects sudo in a chain (operators excluded)", () => {
141
- const parts = ["git status ", "&&", " sudo make install"];
142
- expect(detectSudoSegments(parts)).toEqual(["sudo make install"]);
143
- });
144
-
145
- it("detects multiple sudo segments", () => {
146
- const parts = ["sudo rm -rf /tmp ", ";", " sudo reboot"];
147
- expect(detectSudoSegments(parts)).toEqual(["sudo rm -rf /tmp", "sudo reboot"]);
148
- });
149
-
150
- it("does not match 'sudoer' or 'pseudo'", () => {
151
- expect(detectSudoSegments(["sudoers-check", "pseudo sudo"])).toEqual([]);
152
- });
153
-
154
- it("returns empty for operators-only parts", () => {
155
- expect(detectSudoSegments(["&&", "||", ";"])).toEqual([]);
156
- });
157
- });
158
-
159
- // ── buildSudoBlockReason ──────────────────────────────────────────────────────
160
-
161
- describe("buildSudoBlockReason", () => {
162
- it("with sudo_run available: mentions sudo_run tool", () => {
163
- const reason = buildSudoBlockReason(["sudo apt install curl"], true);
164
- expect(reason).toContain("sudo_run");
165
- expect(reason).toContain("sudo apt install curl");
166
- expect(reason).not.toContain("not available");
167
- });
168
-
169
- it("with sudo_run available: instructs to strip sudo prefix", () => {
170
- const reason = buildSudoBlockReason(["sudo make install"], true);
171
- expect(reason).toContain("Strip the leading");
172
- });
173
-
174
- it("without sudo_run: explains restriction and asks user to run manually", () => {
175
- const reason = buildSudoBlockReason(["sudo reboot"], false);
176
- expect(reason).toContain("no sudo_run tool is available");
177
- expect(reason).toContain("manually");
178
- expect(reason).toContain("sudo reboot");
179
- });
180
-
181
- it("lists all blocked commands", () => {
182
- const reason = buildSudoBlockReason(["sudo rm -rf /tmp", "sudo reboot"], true);
183
- expect(reason).toContain("sudo rm -rf /tmp");
184
- expect(reason).toContain("sudo reboot");
185
- });
186
- });
187
-
188
- // Integration tests for the `tool_call` hook step. These guard the bug that
189
- // silently disabled rewriting: wrong event name + wrong field + wrong patch
190
- // mechanism. They assert on the IN-PLACE mutation contract the SDK requires.
191
- describe("applyRtkRewrite (tool_call hook step)", () => {
192
- it("mutates event.input.command in place for a known bash command", () => {
193
- const event = bashEvent("git status");
194
- const changed = applyRtkRewrite(event, {
195
- enabled: true,
196
- rtkAvailable: true,
197
- });
198
- expect(changed).toBe(true);
199
- expect(event.input.command).toBe("rtk git status");
200
- });
201
-
202
- it("rewrites every segment of a chain in place", () => {
203
- const event = bashEvent("git add . && git push");
204
- applyRtkRewrite(event, { enabled: true, rtkAvailable: true });
205
- expect(event.input.command).toBe("rtk git add . && rtk git push");
206
- });
207
-
208
- it("does not mutate when disabled", () => {
209
- const event = bashEvent("git status");
210
- const changed = applyRtkRewrite(event, {
211
- enabled: false,
212
- rtkAvailable: true,
213
- });
214
- expect(changed).toBe(false);
215
- expect(event.input.command).toBe("git status");
216
- });
217
-
218
- it("does not mutate when rtk binary is unavailable", () => {
219
- const event = bashEvent("git status");
220
- const changed = applyRtkRewrite(event, {
221
- enabled: true,
222
- rtkAvailable: false,
223
- });
224
- expect(changed).toBe(false);
225
- expect(event.input.command).toBe("git status");
226
- });
227
-
228
- it("ignores non-bash tools", () => {
229
- const event: BashCallEvent = {
230
- toolName: "grep",
231
- input: { command: "git status" },
232
- };
233
- const changed = applyRtkRewrite(event, {
234
- enabled: true,
235
- rtkAvailable: true,
236
- });
237
- expect(changed).toBe(false);
238
- expect(event.input.command).toBe("git status");
239
- });
240
-
241
- it("leaves unknown commands untouched", () => {
242
- const event = bashEvent("mkdir build && cd build");
243
- const changed = applyRtkRewrite(event, {
244
- enabled: true,
245
- rtkAvailable: true,
246
- });
247
- expect(changed).toBe(false);
248
- expect(event.input.command).toBe("mkdir build && cd build");
249
- });
250
-
251
- it("does not double-prefix an already-rtk command", () => {
252
- const event = bashEvent("rtk git status");
253
- const changed = applyRtkRewrite(event, {
254
- enabled: true,
255
- rtkAvailable: true,
256
- });
257
- expect(changed).toBe(false);
258
- expect(event.input.command).toBe("rtk git status");
259
- });
260
-
261
- it("handles missing / non-string command safely", () => {
262
- const event: BashCallEvent = { toolName: "bash", input: {} };
263
- expect(applyRtkRewrite(event, { enabled: true, rtkAvailable: true })).toBe(false);
264
- const event2: BashCallEvent = { toolName: "bash", input: { command: 123 } };
265
- expect(applyRtkRewrite(event2, { enabled: true, rtkAvailable: true })).toBe(false);
266
- });
267
-
268
- it("leaves command unchanged on unbalanced quotes", () => {
269
- const event = bashEvent('git commit -m "oops');
270
- const changed = applyRtkRewrite(event, {
271
- enabled: true,
272
- rtkAvailable: true,
273
- });
274
- expect(changed).toBe(false);
275
- expect(event.input.command).toBe('git commit -m "oops');
276
- });
277
- });
@@ -1,93 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
- import { OptimizerStatus, renderStatus, STATUS_KEY, toolIcon } from "./status.ts";
3
-
4
- /** Tagging colorizer: <accent>X</accent> / <dim>X</dim> for assertions. */
5
- const tag = (c: string, t: string) => `<${c}>${t}</${c}>`;
6
-
7
- // Icons resolve against the global pix-pretty mode (default "nerd" in tests).
8
- const CV = toolIcon("caveman");
9
- const RK = toolIcon("rtk");
10
- const TN = toolIcon("toon");
11
- const PT = toolIcon("ponytail");
12
-
13
- describe("renderStatus", () => {
14
- it("shows ALL icons in order, accent when enabled", () => {
15
- expect(renderStatus({ caveman: true, rtk: true, toon: true, ponytail: true }, tag)).toBe(
16
- `<accent>${CV}</accent> <accent>${RK}</accent> <accent>${TN}</accent> <accent>${PT}</accent> `,
17
- );
18
- });
19
-
20
- it("dims disabled tools but still shows them", () => {
21
- expect(renderStatus({ caveman: false, rtk: true, toon: true, ponytail: true }, tag)).toBe(
22
- `<dim>${CV}</dim> <accent>${RK}</accent> <accent>${TN}</accent> <accent>${PT}</accent> `,
23
- );
24
- });
25
-
26
- it("all dim when nothing enabled (cell never empty)", () => {
27
- expect(renderStatus({}, tag)).toBe(
28
- `<dim>${CV}</dim> <dim>${RK}</dim> <dim>${TN}</dim> <dim>${PT}</dim> `,
29
- );
30
- });
31
-
32
- it("preserves fixed order regardless of insertion order", () => {
33
- expect(renderStatus({ toon: true, caveman: true }, tag)).toBe(
34
- `<accent>${CV}</accent> <dim>${RK}</dim> <accent>${TN}</accent> <dim>${PT}</dim> `,
35
- );
36
- });
37
-
38
- it("resolves icons via the shared catalog (non-empty glyphs)", () => {
39
- // toolIcon delegates to pix-pretty's icon(); every tool has a glyph.
40
- expect([CV, RK, TN, PT].every((g) => g.length > 0)).toBe(true);
41
- });
42
- });
43
-
44
- describe("OptimizerStatus", () => {
45
- /** Minimal ui stub capturing setStatus calls. */
46
- function fakeCtx() {
47
- const calls: { key: string; text: string }[] = [];
48
- return {
49
- calls,
50
- ui: {
51
- setStatus: (key: string, text: string | undefined) => calls.push({ key, text: text ?? "" }),
52
- theme: { fg: (c: string, t: string) => `<${c}>${t}</${c}>` },
53
- },
54
- } as const;
55
- }
56
-
57
- it("paints the shared key with per-icon accent/dim", () => {
58
- const status = new OptimizerStatus();
59
- const ctx = fakeCtx();
60
- status.set("rtk", true, ctx as never);
61
- const last = ctx.calls.at(-1);
62
- if (!last) throw new Error("no calls");
63
- expect(last.key).toBe(STATUS_KEY);
64
- // caveman + toon + ponytail still unset (dim), rtk accent.
65
- expect(last.text).toBe(
66
- `<dim>${CV}</dim> <accent>${RK}</accent> <dim>${TN}</dim> <dim>${PT}</dim> `,
67
- );
68
- });
69
-
70
- it("accumulates state across tools", () => {
71
- const status = new OptimizerStatus();
72
- const ctx = fakeCtx();
73
- status.set("caveman", true, ctx as never);
74
- status.set("toon", true, ctx as never);
75
- const last = ctx.calls.at(-1);
76
- if (!last) throw new Error("no calls");
77
- expect(last.text).toBe(
78
- `<accent>${CV}</accent> <dim>${RK}</dim> <accent>${TN}</accent> <dim>${PT}</dim> `,
79
- );
80
- });
81
-
82
- it("dims an icon when its tool toggles off (cell stays populated)", () => {
83
- const status = new OptimizerStatus();
84
- const ctx = fakeCtx();
85
- status.set("rtk", true, ctx as never);
86
- status.set("rtk", false, ctx as never);
87
- const last = ctx.calls.at(-1);
88
- if (!last) throw new Error("no calls");
89
- expect(last.text).toBe(
90
- `<dim>${CV}</dim> <dim>${RK}</dim> <dim>${TN}</dim> <dim>${PT}</dim> `,
91
- );
92
- });
93
- });
@@ -1,96 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
- import { filterModelWarnings, stripModelWarningParagraphs } from "./tool-result-filter.ts";
3
-
4
- // ── stripModelWarningParagraphs ───────────────────────────────────────────────
5
-
6
- describe("stripModelWarningParagraphs", () => {
7
- it("passes through text with no warnings", () => {
8
- const text = "Exit code: 0\n\nsome output here";
9
- expect(stripModelWarningParagraphs(text)).toBe(text);
10
- });
11
-
12
- it("strips a BLIND WRITE paragraph", () => {
13
- const text =
14
- "Exit code: 0\n\nsome output\n\n⚠ BLIND WRITE — editing `foo.ts` without reading in the last 5 tool calls. Read the file first to avoid assumptions.";
15
- const result = stripModelWarningParagraphs(text);
16
- expect(result).not.toContain("BLIND WRITE");
17
- expect(result).toContain("some output");
18
- });
19
-
20
- it("strips a THRASHING paragraph", () => {
21
- const text =
22
- "ok\n\n🔴 THRASHING — 3 consecutive `edit` calls on foo.ts. Consider reading first.";
23
- const result = stripModelWarningParagraphs(text);
24
- expect(result).not.toContain("THRASHING");
25
- expect(result).toContain("ok");
26
- });
27
-
28
- it("strips multiple warning paragraphs", () => {
29
- const text = "output\n\n⚠ BLIND WRITE — foo\n\n🔴 THRASHING — bar\n\nmore output";
30
- const result = stripModelWarningParagraphs(text);
31
- expect(result).not.toContain("BLIND WRITE");
32
- expect(result).not.toContain("THRASHING");
33
- expect(result).toContain("output");
34
- expect(result).toContain("more output");
35
- });
36
-
37
- it("returns original reference when nothing stripped", () => {
38
- const text = "no warnings here";
39
- expect(stripModelWarningParagraphs(text)).toBe(text);
40
- });
41
-
42
- it("trims trailing whitespace after stripping", () => {
43
- const text = "output\n\n⚠ BLIND WRITE — foo";
44
- const result = stripModelWarningParagraphs(text);
45
- expect(result).toBe("output");
46
- });
47
-
48
- it("does not strip mid-paragraph BLIND WRITE mentions", () => {
49
- // Only strips when the paragraph *starts* with the prefix
50
- const text = "The BLIND WRITE check is a feature.\n\nsome output";
51
- expect(stripModelWarningParagraphs(text)).toBe(text);
52
- });
53
- });
54
-
55
- // ── filterModelWarnings ───────────────────────────────────────────────────────
56
-
57
- describe("filterModelWarnings", () => {
58
- it("returns original array when no warnings present", () => {
59
- const content = [{ type: "text" as const, text: "clean output" }];
60
- expect(filterModelWarnings(content)).toBe(content);
61
- });
62
-
63
- it("filters BLIND WRITE from text block", () => {
64
- const content = [
65
- {
66
- type: "text" as const,
67
- text: "Exit code: 0\n\nok\n\n⚠ BLIND WRITE — editing `x.ts` without reading.",
68
- },
69
- ];
70
- const result = filterModelWarnings(content);
71
- expect(result).not.toBe(content);
72
- expect(result[0]?.type).toBe("text");
73
- if (result[0]?.type === "text") {
74
- expect((result[0] as { type: string; text: string }).text).not.toContain("BLIND WRITE");
75
- }
76
- });
77
-
78
- it("leaves image blocks untouched", () => {
79
- const img = { type: "image", source: { type: "base64", data: "abc" } };
80
- const content = [img] as never[];
81
- expect(filterModelWarnings(content)).toBe(content);
82
- });
83
-
84
- it("only modifies affected blocks, keeps others by reference", () => {
85
- const clean = { type: "text" as const, text: "clean" };
86
- const dirty = {
87
- type: "text" as const,
88
- text: "output\n\n⚠ BLIND WRITE — foo",
89
- };
90
- const content = [clean, dirty];
91
- const result = filterModelWarnings(content);
92
- // The array is new but clean block is the same reference
93
- expect(result[0]).toBe(clean);
94
- expect(result[1]).not.toBe(dirty);
95
- });
96
- });