pi-rtk-optimizer 0.3.3 → 0.5.0

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.
Files changed (38) hide show
  1. package/CHANGELOG.md +102 -67
  2. package/README.md +292 -290
  3. package/config/config.example.json +36 -35
  4. package/package.json +4 -4
  5. package/src/additional-coverage-test.ts +278 -0
  6. package/src/boolean-format.ts +3 -0
  7. package/src/command-rewriter-test.ts +160 -120
  8. package/src/command-rewriter.ts +594 -585
  9. package/src/config-modal-test.ts +168 -0
  10. package/src/config-modal.ts +613 -600
  11. package/src/config-store.ts +224 -217
  12. package/src/index-test.ts +54 -0
  13. package/src/index.ts +410 -289
  14. package/src/output-compactor-test.ts +500 -158
  15. package/src/output-compactor.ts +432 -349
  16. package/src/record-utils.ts +6 -0
  17. package/src/rewrite-bypass.ts +332 -173
  18. package/src/rewrite-pipeline-safety.ts +154 -0
  19. package/src/rewrite-rules.ts +255 -255
  20. package/src/rtk-command-environment.ts +64 -0
  21. package/src/runtime-guard-test.ts +42 -50
  22. package/src/runtime-guard.ts +14 -14
  23. package/src/techniques/build.ts +155 -155
  24. package/src/techniques/emoji.ts +91 -0
  25. package/src/techniques/git.ts +231 -229
  26. package/src/techniques/index.ts +10 -16
  27. package/src/techniques/linter.ts +151 -161
  28. package/src/techniques/path-utils.ts +67 -0
  29. package/src/techniques/rtk.ts +136 -0
  30. package/src/techniques/search.ts +67 -76
  31. package/src/techniques/source.ts +253 -253
  32. package/src/techniques/test-output.ts +172 -172
  33. package/src/test-helpers.ts +10 -0
  34. package/src/tool-execution-sanitizer.ts +69 -0
  35. package/src/types-shims.d.ts +192 -183
  36. package/src/types.ts +103 -114
  37. package/src/zellij-modal.ts +1001 -1001
  38. package/src/compat-commands.ts +0 -207
@@ -1,158 +1,500 @@
1
- import assert from "node:assert/strict";
2
-
3
- import { compactToolResult } from "./output-compactor.ts";
4
- import { DEFAULT_RTK_INTEGRATION_CONFIG, type RtkIntegrationConfig } from "./types.ts";
5
-
6
- function runTest(name: string, testFn: () => void): void {
7
- testFn();
8
- console.log(`[PASS] ${name}`);
9
- }
10
-
11
- function cloneConfig(): RtkIntegrationConfig {
12
- return structuredClone(DEFAULT_RTK_INTEGRATION_CONFIG);
13
- }
14
-
15
- function buildReadContent(lineCount: number): string {
16
- const lines: string[] = [];
17
- for (let index = 0; index < lineCount; index += 1) {
18
- if (index % 2 === 0) {
19
- lines.push(`// comment ${index}`);
20
- } else {
21
- lines.push(`const value${index} = ${index};`);
22
- }
23
- }
24
- return `${lines.join("\n")}\n`;
25
- }
26
-
27
- function firstTextBlock(content: unknown[] | undefined): string {
28
- if (!Array.isArray(content) || content.length === 0) {
29
- return "";
30
- }
31
- const first = content[0] as { type?: string; text?: string };
32
- if (first?.type !== "text" || typeof first.text !== "string") {
33
- return "";
34
- }
35
- return first.text;
36
- }
37
-
38
- runTest("precision read with offset keeps exact output (no source/smart/hard truncation)", () => {
39
- const config = cloneConfig();
40
- config.outputCompaction.truncate.enabled = true;
41
- config.outputCompaction.truncate.maxChars = 500;
42
- config.outputCompaction.smartTruncate.enabled = true;
43
- config.outputCompaction.smartTruncate.maxLines = 40;
44
-
45
- const content = buildReadContent(220);
46
- const result = compactToolResult(
47
- {
48
- toolName: "read",
49
- input: { path: "sample.ts", offset: 1 },
50
- content: [{ type: "text", text: content }],
51
- },
52
- config,
53
- );
54
-
55
- assert.equal(result.changed, false);
56
- assert.deepEqual(result.techniques, []);
57
- });
58
-
59
- runTest("precision read with limit keeps exact output", () => {
60
- const config = cloneConfig();
61
- config.outputCompaction.truncate.enabled = true;
62
- config.outputCompaction.truncate.maxChars = 500;
63
- config.outputCompaction.smartTruncate.enabled = true;
64
- config.outputCompaction.smartTruncate.maxLines = 40;
65
-
66
- const content = buildReadContent(220);
67
- const result = compactToolResult(
68
- {
69
- toolName: "read",
70
- input: { path: "sample.ts", limit: 200 },
71
- content: [{ type: "text", text: content }],
72
- },
73
- config,
74
- );
75
-
76
- assert.equal(result.changed, false);
77
- assert.deepEqual(result.techniques, []);
78
- });
79
-
80
- runTest("normal read compacts and adds banner", () => {
81
- const config = cloneConfig();
82
- config.outputCompaction.smartTruncate.enabled = true;
83
- config.outputCompaction.smartTruncate.maxLines = 40;
84
-
85
- const content = buildReadContent(220);
86
- const result = compactToolResult(
87
- {
88
- toolName: "read",
89
- input: { path: "sample.ts" },
90
- content: [{ type: "text", text: content }],
91
- },
92
- config,
93
- );
94
-
95
- assert.equal(result.changed, true);
96
- assert.ok(result.techniques.includes("source:minimal"));
97
-
98
- const compacted = firstTextBlock(result.content);
99
- assert.ok(compacted.startsWith("[RTK compacted output:"));
100
- assert.ok(compacted.includes("source:minimal"));
101
- });
102
-
103
- runTest("short read output stays exact below threshold", () => {
104
- const config = cloneConfig();
105
- const content = buildReadContent(40);
106
-
107
- const result = compactToolResult(
108
- {
109
- toolName: "read",
110
- input: { path: "sample.ts" },
111
- content: [{ type: "text", text: content }],
112
- },
113
- config,
114
- );
115
-
116
- assert.equal(result.changed, false);
117
- assert.deepEqual(result.techniques, []);
118
- });
119
-
120
- runTest("read output stays exact at the 80-line boundary with trailing newline", () => {
121
- const config = cloneConfig();
122
- config.outputCompaction.smartTruncate.enabled = true;
123
- config.outputCompaction.smartTruncate.maxLines = 40;
124
-
125
- const content = buildReadContent(80);
126
- const result = compactToolResult(
127
- {
128
- toolName: "read",
129
- input: { path: "sample.ts" },
130
- content: [{ type: "text", text: content }],
131
- },
132
- config,
133
- );
134
-
135
- assert.equal(result.changed, false);
136
- assert.deepEqual(result.techniques, []);
137
- });
138
-
139
- runTest("read output compacts once the content exceeds the 80-line exactness threshold", () => {
140
- const config = cloneConfig();
141
- config.outputCompaction.smartTruncate.enabled = true;
142
- config.outputCompaction.smartTruncate.maxLines = 40;
143
-
144
- const content = buildReadContent(81);
145
- const result = compactToolResult(
146
- {
147
- toolName: "read",
148
- input: { path: "sample.ts" },
149
- content: [{ type: "text", text: content }],
150
- },
151
- config,
152
- );
153
-
154
- assert.equal(result.changed, true);
155
- assert.ok(result.techniques.includes("source:minimal") || result.techniques.includes("smart-truncate"));
156
- });
157
-
158
- console.log("All output-compactor tests passed.");
1
+ import assert from "node:assert/strict";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+
5
+ import { compactToolResult } from "./output-compactor.ts";
6
+ import { cloneDefaultConfig, runTest } from "./test-helpers.ts";
7
+
8
+ function buildReadContent(lineCount: number): string {
9
+ const lines: string[] = [];
10
+ for (let index = 0; index < lineCount; index += 1) {
11
+ if (index % 2 === 0) {
12
+ lines.push(`// comment ${index}`);
13
+ } else {
14
+ lines.push(`const value${index} = ${index};`);
15
+ }
16
+ }
17
+ return `${lines.join("\n")}\n`;
18
+ }
19
+
20
+ function firstTextBlock(content: unknown[] | undefined): string {
21
+ if (!Array.isArray(content) || content.length === 0) {
22
+ return "";
23
+ }
24
+ const first = content[0] as { type?: string; text?: string };
25
+ if (first?.type !== "text" || typeof first.text !== "string") {
26
+ return "";
27
+ }
28
+ return first.text;
29
+ }
30
+
31
+ const OUTPUT_EMOJI_MARKERS = ["āœ“", "āœ”", "āŒ", "āš ļø", "⚠", "šŸ“‹", "šŸ“„", "šŸ”", "āœ…", "ā­ļø", "šŸ“Œ", "šŸ“", "ā“", "•"];
32
+
33
+ function compactBashOutput(command: string, text: string): string {
34
+ const result = compactToolResult(
35
+ {
36
+ toolName: "bash",
37
+ input: { command },
38
+ content: [{ type: "text", text }],
39
+ },
40
+ cloneDefaultConfig(),
41
+ );
42
+
43
+ assert.equal(result.changed, true);
44
+ return firstTextBlock(result.content);
45
+ }
46
+
47
+ function compactGrepOutput(text: string): string {
48
+ const result = compactToolResult(
49
+ {
50
+ toolName: "grep",
51
+ input: { pattern: "match" },
52
+ content: [{ type: "text", text }],
53
+ },
54
+ cloneDefaultConfig(),
55
+ );
56
+
57
+ assert.equal(result.changed, true);
58
+ return firstTextBlock(result.content);
59
+ }
60
+
61
+ function assertNoOutputEmoji(text: string): void {
62
+ for (const marker of OUTPUT_EMOJI_MARKERS) {
63
+ assert.equal(text.includes(marker), false, `Unexpected output emoji marker: ${marker}`);
64
+ }
65
+ }
66
+
67
+ runTest("precision read with offset keeps exact output (no source/smart/hard truncation)", () => {
68
+ const config = cloneDefaultConfig();
69
+ config.outputCompaction.truncate.enabled = true;
70
+ config.outputCompaction.truncate.maxChars = 500;
71
+ config.outputCompaction.smartTruncate.enabled = true;
72
+ config.outputCompaction.smartTruncate.maxLines = 40;
73
+
74
+ const content = buildReadContent(220);
75
+ const result = compactToolResult(
76
+ {
77
+ toolName: "read",
78
+ input: { path: "sample.ts", offset: 1 },
79
+ content: [{ type: "text", text: content }],
80
+ },
81
+ config,
82
+ );
83
+
84
+ assert.equal(result.changed, false);
85
+ assert.deepEqual(result.techniques, []);
86
+ });
87
+
88
+ runTest("precision read with limit keeps exact output", () => {
89
+ const config = cloneDefaultConfig();
90
+ config.outputCompaction.truncate.enabled = true;
91
+ config.outputCompaction.truncate.maxChars = 500;
92
+ config.outputCompaction.smartTruncate.enabled = true;
93
+ config.outputCompaction.smartTruncate.maxLines = 40;
94
+
95
+ const content = buildReadContent(220);
96
+ const result = compactToolResult(
97
+ {
98
+ toolName: "read",
99
+ input: { path: "sample.ts", limit: 200 },
100
+ content: [{ type: "text", text: content }],
101
+ },
102
+ config,
103
+ );
104
+
105
+ assert.equal(result.changed, false);
106
+ assert.deepEqual(result.techniques, []);
107
+ });
108
+
109
+ runTest("normal read compacts and adds banner", () => {
110
+ const config = cloneDefaultConfig();
111
+ config.outputCompaction.smartTruncate.enabled = true;
112
+ config.outputCompaction.smartTruncate.maxLines = 40;
113
+
114
+ const content = buildReadContent(220);
115
+ const result = compactToolResult(
116
+ {
117
+ toolName: "read",
118
+ input: { path: "sample.ts" },
119
+ content: [{ type: "text", text: content }],
120
+ },
121
+ config,
122
+ );
123
+
124
+ assert.equal(result.changed, true);
125
+ assert.ok(result.techniques.includes("source:minimal"));
126
+
127
+ const compacted = firstTextBlock(result.content);
128
+ assert.ok(compacted.startsWith("[RTK compacted output:"));
129
+ assert.ok(compacted.includes("source:minimal"));
130
+ });
131
+
132
+ runTest("short read output stays exact below threshold", () => {
133
+ const config = cloneDefaultConfig();
134
+ const content = buildReadContent(40);
135
+
136
+ const result = compactToolResult(
137
+ {
138
+ toolName: "read",
139
+ input: { path: "sample.ts" },
140
+ content: [{ type: "text", text: content }],
141
+ },
142
+ config,
143
+ );
144
+
145
+ assert.equal(result.changed, false);
146
+ assert.deepEqual(result.techniques, []);
147
+ });
148
+
149
+ runTest("read output stays exact at the 80-line boundary with trailing newline", () => {
150
+ const config = cloneDefaultConfig();
151
+ config.outputCompaction.smartTruncate.enabled = true;
152
+ config.outputCompaction.smartTruncate.maxLines = 40;
153
+
154
+ const content = buildReadContent(80);
155
+ const result = compactToolResult(
156
+ {
157
+ toolName: "read",
158
+ input: { path: "sample.ts" },
159
+ content: [{ type: "text", text: content }],
160
+ },
161
+ config,
162
+ );
163
+
164
+ assert.equal(result.changed, false);
165
+ assert.deepEqual(result.techniques, []);
166
+ });
167
+
168
+ runTest("read output compacts once the content exceeds the 80-line exactness threshold", () => {
169
+ const config = cloneDefaultConfig();
170
+ config.outputCompaction.smartTruncate.enabled = true;
171
+ config.outputCompaction.smartTruncate.maxLines = 40;
172
+
173
+ const content = buildReadContent(81);
174
+ const result = compactToolResult(
175
+ {
176
+ toolName: "read",
177
+ input: { path: "sample.ts" },
178
+ content: [{ type: "text", text: content }],
179
+ },
180
+ config,
181
+ );
182
+
183
+ assert.equal(result.changed, true);
184
+ assert.ok(result.techniques.includes("source:minimal") || result.techniques.includes("smart-truncate"));
185
+ });
186
+
187
+ runTest("source file reads skip lossy source filtering when truncation safeguards are not needed", () => {
188
+ const config = cloneDefaultConfig();
189
+ config.outputCompaction.smartTruncate.enabled = false;
190
+ config.outputCompaction.truncate.enabled = false;
191
+
192
+ const content = buildReadContent(120);
193
+ const result = compactToolResult(
194
+ {
195
+ toolName: "read",
196
+ input: { path: "sample.ts" },
197
+ content: [{ type: "text", text: content }],
198
+ },
199
+ config,
200
+ );
201
+
202
+ assert.equal(result.changed, false);
203
+ assert.deepEqual(result.techniques, []);
204
+ assert.equal(firstTextBlock(result.content), "");
205
+ });
206
+
207
+ runTest("skill reads stay exact when preserveExactSkillReads is enabled for user skills", () => {
208
+ const config = cloneDefaultConfig();
209
+ config.outputCompaction.preserveExactSkillReads = true;
210
+ config.outputCompaction.truncate.enabled = true;
211
+ config.outputCompaction.truncate.maxChars = 500;
212
+ config.outputCompaction.smartTruncate.enabled = true;
213
+ config.outputCompaction.smartTruncate.maxLines = 40;
214
+
215
+ const content = buildReadContent(220);
216
+ const result = compactToolResult(
217
+ {
218
+ toolName: "read",
219
+ input: { path: join(homedir(), ".pi", "agent", "skills", "example", "SKILL.md") },
220
+ content: [{ type: "text", text: content }],
221
+ },
222
+ config,
223
+ );
224
+
225
+ assert.equal(result.changed, false);
226
+ assert.deepEqual(result.techniques, []);
227
+ });
228
+
229
+ runTest("project .pi skill reads stay exact when preserveExactSkillReads is enabled", () => {
230
+ const config = cloneDefaultConfig();
231
+ config.outputCompaction.preserveExactSkillReads = true;
232
+ config.outputCompaction.truncate.enabled = true;
233
+ config.outputCompaction.truncate.maxChars = 500;
234
+ config.outputCompaction.smartTruncate.enabled = true;
235
+ config.outputCompaction.smartTruncate.maxLines = 40;
236
+
237
+ const content = buildReadContent(220);
238
+ const result = compactToolResult(
239
+ {
240
+ toolName: "read",
241
+ input: { path: ".pi/skills/example/SKILL.md" },
242
+ content: [{ type: "text", text: content }],
243
+ },
244
+ config,
245
+ );
246
+
247
+ assert.equal(result.changed, false);
248
+ assert.deepEqual(result.techniques, []);
249
+ });
250
+
251
+ runTest("ancestor .agents skill reads stay exact when preserveExactSkillReads is enabled", () => {
252
+ const config = cloneDefaultConfig();
253
+ config.outputCompaction.preserveExactSkillReads = true;
254
+ config.outputCompaction.truncate.enabled = true;
255
+ config.outputCompaction.truncate.maxChars = 500;
256
+ config.outputCompaction.smartTruncate.enabled = true;
257
+ config.outputCompaction.smartTruncate.maxLines = 40;
258
+
259
+ const content = buildReadContent(220);
260
+ const result = compactToolResult(
261
+ {
262
+ toolName: "read",
263
+ input: { path: "../.agents/skills/example/SKILL.md" },
264
+ content: [{ type: "text", text: content }],
265
+ },
266
+ config,
267
+ );
268
+
269
+ assert.equal(result.changed, false);
270
+ assert.deepEqual(result.techniques, []);
271
+ });
272
+
273
+ runTest("build output uses plain-text status markers", () => {
274
+ const compacted = compactBashOutput("npm run build", "Compiling app v0.1.0\n");
275
+
276
+ assert.equal(compacted, "[OK] Build successful (1 units compiled)");
277
+ assertNoOutputEmoji(compacted);
278
+ });
279
+
280
+ runTest("git status output uses plain-text labels", () => {
281
+ const compacted = compactBashOutput(
282
+ "git status --short --branch",
283
+ "## main...origin/main\nM staged.ts\n M modified.ts\n?? new.ts\nUU conflict.ts\n",
284
+ );
285
+
286
+ assert.ok(compacted.startsWith("Branch: main\n"));
287
+ assert.ok(compacted.includes("Staged: 1 files\n staged.ts\n"));
288
+ assert.ok(compacted.includes("Modified: 1 files\n modified.ts\n"));
289
+ assert.ok(compacted.includes("Untracked: 1 files\n new.ts\n"));
290
+ assert.ok(compacted.includes("Conflicts: 1 files"));
291
+ assertNoOutputEmoji(compacted);
292
+ });
293
+
294
+ runTest("git diff output uses plain-text file markers", () => {
295
+ const compacted = compactBashOutput(
296
+ "git diff",
297
+ "diff --git a/src/example.ts b/src/example.ts\n@@ -1 +1 @@\n-oldValue\n+newValue\n",
298
+ );
299
+
300
+ assert.ok(compacted.includes("\n> src/example.ts\n"));
301
+ assertNoOutputEmoji(compacted);
302
+ });
303
+
304
+ runTest("linter success output uses plain-text status markers", () => {
305
+ const compacted = compactBashOutput("npx eslint .", "");
306
+
307
+ assert.equal(compacted, "[OK] ESLint: No issues found");
308
+ assertNoOutputEmoji(compacted);
309
+ });
310
+
311
+ runTest("test output uses plain-text labels and bullets", () => {
312
+ const compacted = compactBashOutput(
313
+ "bun test",
314
+ "3 passed, 1 failed, 2 skipped\nFAIL src/example.test.ts\n Expected: true\n Received: false\n\n\n",
315
+ );
316
+
317
+ assert.ok(compacted.includes("Test Results:"));
318
+ assert.ok(compacted.includes("PASS: 3 passed"));
319
+ assert.ok(compacted.includes("FAIL: 1 failed"));
320
+ assert.ok(compacted.includes("SKIP: 2 skipped"));
321
+ assert.ok(compacted.includes(" - FAIL src/example.test.ts"));
322
+ assertNoOutputEmoji(compacted);
323
+ });
324
+
325
+ runTest("search output uses plain-text summary and file markers", () => {
326
+ const compacted = compactGrepOutput("src/a.ts:1:const match = true;\nsrc/b.ts:2:return match;\n");
327
+
328
+ assert.ok(compacted.startsWith("2 matches in 2 files:\n\n"));
329
+ assert.ok(compacted.includes("> src/a.ts (1 matches):\n"));
330
+ assert.ok(compacted.includes("> src/b.ts (1 matches):\n"));
331
+ assertNoOutputEmoji(compacted);
332
+ });
333
+
334
+ runTest("rtk env output sanitizes emoji section headers", () => {
335
+ const compacted = compactBashOutput(
336
+ "rtk env",
337
+ "šŸ“‚ PATH Variables:\nšŸ”§ Language/Runtime:\nā˜ļø Cloud/Services:\nšŸ› ļø Tools:\nšŸ“‹ Other:\nšŸ“Š Total: 91 vars\n",
338
+ );
339
+
340
+ assert.ok(compacted.includes("PATH Variables:"));
341
+ assert.ok(compacted.includes("Language/Runtime:"));
342
+ assert.ok(compacted.includes("Cloud/Services:"));
343
+ assert.ok(compacted.includes("Tools:"));
344
+ assert.ok(compacted.includes("Other:"));
345
+ assert.ok(compacted.includes("Total: 91 vars"));
346
+ assertNoOutputEmoji(compacted);
347
+ });
348
+
349
+ runTest("rtk-shaped env output sanitizes even when command name is not rtk", () => {
350
+ const compacted = compactBashOutput(
351
+ "echo probe",
352
+ "šŸ“‚ PATH Variables:\nšŸ”§ Language/Runtime:\nšŸ“Š Total: 91 vars\n",
353
+ );
354
+
355
+ assert.ok(compacted.includes("PATH Variables:"));
356
+ assert.ok(compacted.includes("Language/Runtime:"));
357
+ assert.ok(compacted.includes("Total: 91 vars"));
358
+ assertNoOutputEmoji(compacted);
359
+ });
360
+
361
+ runTest("rtk git-style output sanitizes emoji status markers", () => {
362
+ const compacted = compactBashOutput(
363
+ "rtk git status",
364
+ "šŸ“Œ main\nāœ… Staged: 1 files\nšŸ“ Modified: 2 files\nā“ Untracked: 1 files\nāš ļø Conflicts: 1 files\n",
365
+ );
366
+
367
+ assert.ok(compacted.includes("Branch: main"));
368
+ assert.ok(compacted.includes("[OK] Staged: 1 files"));
369
+ assert.ok(compacted.includes("Modified: 2 files"));
370
+ assert.ok(compacted.includes("[INFO] Untracked: 1 files"));
371
+ assert.ok(compacted.includes("[WARN] Conflicts: 1 files"));
372
+ assertNoOutputEmoji(compacted);
373
+ });
374
+
375
+ runTest("rtk grep-style output sanitizes emoji file markers", () => {
376
+ const compacted = compactBashOutput(
377
+ "rtk grep EXTENSION_NAME agent/extensions/pi-rtk-optimizer/src/constants.ts",
378
+ "šŸ” 2 in 1F:\n\nšŸ“„ agent/extensions/pi-rtk-optimizer/src/constants.ts (2):\n 4: export const EXTENSION_NAME = \"pi-rtk-optimizer\";\n",
379
+ );
380
+
381
+ assert.ok(compacted.startsWith("2 in 1F:\n\n> agent/extensions/pi-rtk-optimizer/src/constants.ts (2):"));
382
+ assertNoOutputEmoji(compacted);
383
+ });
384
+
385
+ runTest("rtk git diff verbose summary sanitizes file markers", () => {
386
+ const compacted = compactBashOutput(
387
+ "rtk git diff -- agent/extensions/pi-mcp-adapter/package.json",
388
+ "agent/extensions/pi-mcp-adapter/package.json | 2 +-\n\n--- Changes ---\n\nšŸ“„ agent/extensions/pi-mcp-adapter/package.json\n @@ -38,7 +38,7 @@\n - \"@mariozechner/pi-coding-agent\": \"^0.58.1\",\n",
389
+ );
390
+
391
+ assert.ok(compacted.includes("--- Changes ---"));
392
+ assert.ok(compacted.includes("> agent/extensions/pi-mcp-adapter/package.json"));
393
+ assertNoOutputEmoji(compacted);
394
+ });
395
+
396
+ runTest("git diff compaction skips already-compacted RTK-shaped output", () => {
397
+ const compacted = compactBashOutput(
398
+ "git diff -- agent/extensions/pi-mcp-adapter/package.json",
399
+ "agent/extensions/pi-mcp-adapter/package.json | 2 +-\n\n--- Changes ---\n\nšŸ“„ agent/extensions/pi-mcp-adapter/package.json\n @@ -38,7 +38,7 @@\n - \"@mariozechner/pi-coding-agent\": \"^0.58.1\",\n",
400
+ );
401
+
402
+ assert.ok(compacted.includes("--- Changes ---"));
403
+ assert.ok(compacted.includes("> agent/extensions/pi-mcp-adapter/package.json"));
404
+ assertNoOutputEmoji(compacted);
405
+ });
406
+
407
+ runTest("rtk-shaped diff output sanitizes even when command name is not rtk", () => {
408
+ const compacted = compactBashOutput(
409
+ "echo probe",
410
+ "šŸ“Š file-a.txt → file-b.txt\n +1 added, -1 removed, ~0 modified\n\n- 2 beta\n+ 2 gamma\n",
411
+ );
412
+
413
+ assert.ok(compacted.startsWith("file-a.txt -> file-b.txt"));
414
+ assertNoOutputEmoji(compacted);
415
+ });
416
+
417
+ runTest("rtk-shaped identical diff output sanitizes even when command name is not rtk", () => {
418
+ const compacted = compactBashOutput("echo probe", "āœ… Files are identical");
419
+
420
+ assert.equal(compacted, "[OK] Files are identical");
421
+ assertNoOutputEmoji(compacted);
422
+ });
423
+
424
+ runTest("hook warning is stripped even when the command label is not rtk", () => {
425
+ const compacted = compactBashOutput(
426
+ "echo probe",
427
+ "[rtk] /!\\ No hook installed — run `rtk init -g` for automatic token savings\n\n4 files changed\n",
428
+ );
429
+
430
+ assert.equal(compacted, "4 files changed\n");
431
+ });
432
+
433
+ runTest("hook-only output compacts to an empty text result", () => {
434
+ const result = compactToolResult(
435
+ {
436
+ toolName: "bash",
437
+ input: { command: "rtk git status" },
438
+ content: [{ type: "text", text: "[rtk] /!\\ No hook installed — run `rtk init -g` for automatic token savings\n" }],
439
+ },
440
+ cloneDefaultConfig(),
441
+ );
442
+
443
+ assert.equal(result.changed, true);
444
+ assert.ok(result.techniques.includes("rtk-hook-warning"));
445
+ assert.equal(firstTextBlock(result.content), "");
446
+ });
447
+
448
+ runTest("non-hook RTK warnings are preserved verbatim", () => {
449
+ const result = compactToolResult(
450
+ {
451
+ toolName: "bash",
452
+ input: { command: "FOO=1 rtk git status" },
453
+ content: [{ type: "text", text: "[rtk] warning: builtin filters: parse failure\n\nworking tree clean\n" }],
454
+ },
455
+ cloneDefaultConfig(),
456
+ );
457
+
458
+ assert.equal(result.changed, false);
459
+ assert.equal(result.content, undefined);
460
+ assert.deepEqual(result.techniques, []);
461
+ });
462
+
463
+ runTest("emoji RTK warnings stay visible and are sanitized to plain text", () => {
464
+ const compacted = compactBashOutput(
465
+ "rtk init --hook-only",
466
+ "āš ļø Warning: --hook-only only makes sense with --global\n For local projects, use default mode or --claude-md\n\nready\n",
467
+ );
468
+
469
+ assert.ok(compacted.includes("[WARN] Warning: --hook-only only makes sense with --global"));
470
+ assert.ok(compacted.includes("For local projects, use default mode or --claude-md"));
471
+ assert.ok(compacted.includes("ready\n"));
472
+ assertNoOutputEmoji(compacted);
473
+ });
474
+
475
+ runTest("outdated hook warning is stripped while preserving the RTK payload", () => {
476
+ const compacted = compactBashOutput(
477
+ "rtk gain",
478
+ "āš ļø Hook outdated — run `rtk init -g` to update\n\nSaved 42 tokens\n",
479
+ );
480
+
481
+ assert.equal(compacted, "Saved 42 tokens\n");
482
+ });
483
+
484
+ runTest("quoted hook warning text is preserved as payload", () => {
485
+ const quotedHookText = 'const warning = "No hook installed — run `rtk init -g` for automatic token savings";\n';
486
+ const result = compactToolResult(
487
+ {
488
+ toolName: "bash",
489
+ input: { command: "echo probe" },
490
+ content: [{ type: "text", text: quotedHookText }],
491
+ },
492
+ cloneDefaultConfig(),
493
+ );
494
+
495
+ assert.equal(result.changed, false);
496
+ assert.equal(result.content, undefined);
497
+ assert.deepEqual(result.techniques, []);
498
+ });
499
+
500
+ console.log("All output-compactor tests passed.");