pi-rtk-optimizer 0.3.3 → 0.4.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.
@@ -1,35 +1,36 @@
1
- {
2
- "enabled": true,
3
- "mode": "rewrite",
4
- "guardWhenRtkMissing": true,
5
- "showRewriteNotifications": true,
6
- "rewriteGitGithub": true,
7
- "rewriteFilesystem": true,
8
- "rewriteRust": true,
9
- "rewriteJavaScript": true,
10
- "rewritePython": true,
11
- "rewriteGo": true,
12
- "rewriteContainers": true,
13
- "rewriteNetwork": true,
14
- "rewritePackageManagers": true,
15
- "outputCompaction": {
16
- "enabled": true,
17
- "stripAnsi": true,
18
- "sourceCodeFilteringEnabled": true,
19
- "truncate": {
20
- "enabled": true,
21
- "maxChars": 12000
22
- },
23
- "sourceCodeFiltering": "minimal",
24
- "smartTruncate": {
25
- "enabled": true,
26
- "maxLines": 220
27
- },
28
- "aggregateTestOutput": true,
29
- "filterBuildOutput": true,
30
- "compactGitOutput": true,
31
- "aggregateLinterOutput": true,
32
- "groupSearchOutput": true,
33
- "trackSavings": true
34
- }
35
- }
1
+ {
2
+ "enabled": true,
3
+ "mode": "rewrite",
4
+ "guardWhenRtkMissing": true,
5
+ "showRewriteNotifications": true,
6
+ "rewriteGitGithub": true,
7
+ "rewriteFilesystem": true,
8
+ "rewriteRust": true,
9
+ "rewriteJavaScript": true,
10
+ "rewritePython": true,
11
+ "rewriteGo": true,
12
+ "rewriteContainers": true,
13
+ "rewriteNetwork": true,
14
+ "rewritePackageManagers": true,
15
+ "outputCompaction": {
16
+ "enabled": true,
17
+ "stripAnsi": true,
18
+ "sourceCodeFilteringEnabled": true,
19
+ "preserveExactSkillReads": false,
20
+ "truncate": {
21
+ "enabled": true,
22
+ "maxChars": 12000
23
+ },
24
+ "sourceCodeFiltering": "minimal",
25
+ "smartTruncate": {
26
+ "enabled": true,
27
+ "maxLines": 220
28
+ },
29
+ "aggregateTestOutput": true,
30
+ "filterBuildOutput": true,
31
+ "compactGitOutput": true,
32
+ "aggregateLinterOutput": true,
33
+ "groupSearchOutput": true,
34
+ "trackSavings": true
35
+ }
36
+ }
package/package.json CHANGED
@@ -1,60 +1,60 @@
1
- {
2
- "name": "pi-rtk-optimizer",
3
- "version": "0.3.3",
4
- "description": "Pi extension that optimizes RTK command rewriting and tool output compaction for the coding agent.",
5
- "type": "module",
6
- "main": "./index.ts",
7
- "exports": {
8
- ".": "./index.ts"
9
- },
10
- "files": [
11
- "index.ts",
12
- "src",
13
- "config/config.example.json",
14
- "README.md",
15
- "CHANGELOG.md",
16
- "LICENSE"
17
- ],
18
- "scripts": {
19
- "build": "npx --yes -p typescript@5.7.3 tsc -p tsconfig.json --noCheck",
20
- "lint": "npm run build",
21
- "typecheck": "npx --yes -p typescript@5.7.3 tsc -p tsconfig.json",
22
- "test": "bun ./src/output-compactor-test.ts && bun ./src/command-rewriter-test.ts && bun ./src/runtime-guard-test.ts",
23
- "check": "npm run lint && npm run typecheck && npm run test",
24
- "build:check": "bunx esbuild ./index.ts --bundle --platform=node --format=esm --outfile=./.pi-rtk-optimizer-check.mjs --external:@mariozechner/pi-coding-agent --external:@mariozechner/pi-tui && bun -e \"import { unlinkSync } from 'node:fs'; unlinkSync('./.pi-rtk-optimizer-check.mjs');\""
25
- },
26
- "keywords": [
27
- "pi-package",
28
- "pi",
29
- "pi-extension",
30
- "rtk",
31
- "token-optimization",
32
- "tool-compaction",
33
- "coding-agent"
34
- ],
35
- "author": "MasuRii",
36
- "license": "MIT",
37
- "repository": {
38
- "type": "git",
39
- "url": "git+https://github.com/MasuRii/pi-rtk-optimizer.git"
40
- },
41
- "bugs": {
42
- "url": "https://github.com/MasuRii/pi-rtk-optimizer/issues"
43
- },
44
- "homepage": "https://github.com/MasuRii/pi-rtk-optimizer#readme",
45
- "engines": {
46
- "node": ">=20"
47
- },
48
- "publishConfig": {
49
- "access": "public"
50
- },
51
- "pi": {
52
- "extensions": [
53
- "./index.ts"
54
- ]
55
- },
56
- "peerDependencies": {
57
- "@mariozechner/pi-coding-agent": "*",
58
- "@mariozechner/pi-tui": "*"
59
- }
60
- }
1
+ {
2
+ "name": "pi-rtk-optimizer",
3
+ "version": "0.4.0",
4
+ "description": "Pi extension that optimizes RTK command rewriting and tool output compaction for the coding agent.",
5
+ "type": "module",
6
+ "main": "./index.ts",
7
+ "exports": {
8
+ ".": "./index.ts"
9
+ },
10
+ "files": [
11
+ "index.ts",
12
+ "src",
13
+ "config/config.example.json",
14
+ "README.md",
15
+ "CHANGELOG.md",
16
+ "LICENSE"
17
+ ],
18
+ "scripts": {
19
+ "build": "npx --yes -p typescript@5.7.3 tsc -p tsconfig.json --noCheck",
20
+ "lint": "npm run build",
21
+ "typecheck": "npx --yes -p typescript@5.7.3 tsc -p tsconfig.json",
22
+ "test": "bun ./src/output-compactor-test.ts && bun ./src/command-rewriter-test.ts && bun ./src/runtime-guard-test.ts && bun ./src/additional-coverage-test.ts && bun ./src/config-modal-test.ts && bun ./src/index-test.ts",
23
+ "check": "npm run lint && npm run typecheck && npm run test",
24
+ "build:check": "bunx esbuild ./index.ts --bundle --platform=node --format=esm --outfile=./.pi-rtk-optimizer-check.mjs --external:@mariozechner/pi-coding-agent --external:@mariozechner/pi-tui && bun -e \"import { unlinkSync } from 'node:fs'; unlinkSync('./.pi-rtk-optimizer-check.mjs');\""
25
+ },
26
+ "keywords": [
27
+ "pi-package",
28
+ "pi",
29
+ "pi-extension",
30
+ "rtk",
31
+ "token-optimization",
32
+ "tool-compaction",
33
+ "coding-agent"
34
+ ],
35
+ "author": "MasuRii",
36
+ "license": "MIT",
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "git+https://github.com/MasuRii/pi-rtk-optimizer.git"
40
+ },
41
+ "bugs": {
42
+ "url": "https://github.com/MasuRii/pi-rtk-optimizer/issues"
43
+ },
44
+ "homepage": "https://github.com/MasuRii/pi-rtk-optimizer#readme",
45
+ "engines": {
46
+ "node": ">=20"
47
+ },
48
+ "publishConfig": {
49
+ "access": "public"
50
+ },
51
+ "pi": {
52
+ "extensions": [
53
+ "./index.ts"
54
+ ]
55
+ },
56
+ "peerDependencies": {
57
+ "@mariozechner/pi-coding-agent": "*",
58
+ "@mariozechner/pi-tui": "*"
59
+ }
60
+ }
@@ -0,0 +1,197 @@
1
+ import assert from "node:assert/strict";
2
+ import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
3
+
4
+ import {
5
+ ensureConfigExists,
6
+ getRtkIntegrationConfigPath,
7
+ loadRtkIntegrationConfig,
8
+ normalizeRtkIntegrationConfig,
9
+ saveRtkIntegrationConfig,
10
+ } from "./config-store.ts";
11
+ import { clearOutputMetrics, getOutputMetricsSummary, trackOutputSavings } from "./output-metrics.ts";
12
+ import { runTest } from "./test-helpers.ts";
13
+ import { matchesCommandPatterns, normalizeCommandForDetection } from "./techniques/command-detection.ts";
14
+ import { compactPath } from "./techniques/path-utils.ts";
15
+ import { applyWindowsBashCompatibilityFixes } from "./windows-command-helpers.ts";
16
+ import { applyRewrittenCommandShellSafetyFixups } from "./rewrite-pipeline-safety.ts";
17
+
18
+ function makeTempConfigPath(): string {
19
+ return `${getRtkIntegrationConfigPath()}.test-${Date.now()}-${Math.random().toString(16).slice(2)}.json`;
20
+ }
21
+
22
+ function cleanupFile(path: string): void {
23
+ for (const candidate of [path, `${path}.tmp`]) {
24
+ try {
25
+ if (existsSync(candidate)) {
26
+ unlinkSync(candidate);
27
+ }
28
+ } catch {
29
+ // Ignore cleanup failures in tests.
30
+ }
31
+ }
32
+ }
33
+
34
+ runTest("config-store normalizes invalid values and clamps numeric ranges", () => {
35
+ const normalized = normalizeRtkIntegrationConfig({
36
+ enabled: "yes",
37
+ mode: "invalid",
38
+ rewriteGitGithub: false,
39
+ outputCompaction: {
40
+ stripAnsi: false,
41
+ sourceCodeFilteringEnabled: "sometimes",
42
+ sourceCodeFiltering: "extreme",
43
+ truncate: {
44
+ enabled: true,
45
+ maxChars: 12,
46
+ },
47
+ smartTruncate: {
48
+ enabled: true,
49
+ maxLines: 999_999,
50
+ },
51
+ trackSavings: false,
52
+ },
53
+ });
54
+
55
+ assert.equal(normalized.enabled, true);
56
+ assert.equal(normalized.mode, "rewrite");
57
+ assert.equal(normalized.rewriteGitGithub, false);
58
+ assert.equal(normalized.outputCompaction.stripAnsi, false);
59
+ assert.equal(normalized.outputCompaction.sourceCodeFilteringEnabled, true);
60
+ assert.equal(normalized.outputCompaction.sourceCodeFiltering, "minimal");
61
+ assert.equal(normalized.outputCompaction.truncate.maxChars, 1_000);
62
+ assert.equal(normalized.outputCompaction.smartTruncate.maxLines, 4_000);
63
+ assert.equal(normalized.outputCompaction.trackSavings, false);
64
+ });
65
+
66
+ runTest("config-store can ensure, save, and reload isolated config files", () => {
67
+ const tempPath = makeTempConfigPath();
68
+ cleanupFile(tempPath);
69
+
70
+ try {
71
+ const ensured = ensureConfigExists(tempPath);
72
+ assert.equal(ensured.created, true);
73
+ assert.equal(existsSync(tempPath), true);
74
+
75
+ const defaultLoad = loadRtkIntegrationConfig(tempPath);
76
+ assert.equal(defaultLoad.warning, undefined);
77
+ assert.equal(defaultLoad.config.mode, "rewrite");
78
+
79
+ const saved = saveRtkIntegrationConfig(
80
+ {
81
+ ...defaultLoad.config,
82
+ mode: "suggest",
83
+ outputCompaction: {
84
+ ...defaultLoad.config.outputCompaction,
85
+ truncate: {
86
+ ...defaultLoad.config.outputCompaction.truncate,
87
+ maxChars: 250_000,
88
+ },
89
+ },
90
+ },
91
+ tempPath,
92
+ );
93
+ assert.equal(saved.success, true);
94
+
95
+ const reloaded = loadRtkIntegrationConfig(tempPath);
96
+ assert.equal(reloaded.config.mode, "suggest");
97
+ assert.equal(reloaded.config.outputCompaction.truncate.maxChars, 200_000);
98
+ assert.ok(readFileSync(tempPath, "utf-8").endsWith("\n"));
99
+ } finally {
100
+ cleanupFile(tempPath);
101
+ }
102
+ });
103
+
104
+ runTest("config-store falls back to defaults when JSON is invalid", () => {
105
+ const tempPath = makeTempConfigPath();
106
+ cleanupFile(tempPath);
107
+
108
+ try {
109
+ writeFileSync(tempPath, "{not valid json", "utf-8");
110
+ const loaded = loadRtkIntegrationConfig(tempPath);
111
+ assert.equal(loaded.config.mode, "rewrite");
112
+ assert.ok((loaded.warning ?? "").includes(tempPath));
113
+ assert.ok((loaded.warning ?? "").includes("Failed to parse"));
114
+ } finally {
115
+ cleanupFile(tempPath);
116
+ }
117
+ });
118
+
119
+ runTest("output metrics summarize tracked savings and clear state", () => {
120
+ clearOutputMetrics();
121
+ assert.equal(getOutputMetricsSummary(), "RTK output compaction metrics: no data yet.");
122
+
123
+ const first = trackOutputSavings("1234567890", "12345", "bash", ["ansi", "truncate"]);
124
+ assert.equal(first.tool, "bash");
125
+ assert.equal(first.techniques, "ansi,truncate");
126
+ assert.equal(first.savingsPercent, 50);
127
+
128
+ trackOutputSavings("123456", "1234", "read", []);
129
+ const summary = getOutputMetricsSummary();
130
+ assert.ok(summary.includes("calls=2, saved=7 chars (43.8%)"));
131
+ assert.ok(summary.includes("- bash: 1 calls, saved 5 chars (50.0%)"));
132
+ assert.ok(summary.includes("- read: 1 calls, saved 2 chars (33.3%)"));
133
+
134
+ clearOutputMetrics();
135
+ assert.equal(getOutputMetricsSummary(), "RTK output compaction metrics: no data yet.");
136
+ });
137
+
138
+ runTest("command detection ignores env prefixes, blank lines, and chained suffixes", () => {
139
+ assert.equal(normalizeCommandForDetection("NODE_ENV=test FOO=bar npm test && echo done"), "npm test");
140
+ assert.equal(normalizeCommandForDetection("\n\n PYTHONPATH=src git status\n echo later"), "git status");
141
+ assert.equal(normalizeCommandForDetection(" "), null);
142
+ assert.equal(matchesCommandPatterns("CI=1 bun test | head -5", [/^bun test/]), true);
143
+ assert.equal(matchesCommandPatterns("echo hello", [/^bun test/]), false);
144
+ });
145
+
146
+ runTest("path compaction preserves the tail and handles Windows separators", () => {
147
+ const unixPath = "/Users/example/projects/pi-rtk-optimizer/src/techniques/path-utils.ts";
148
+ const compactUnixPath = compactPath(unixPath, 28);
149
+ assert.ok(compactUnixPath.length <= 28);
150
+ assert.ok(compactUnixPath.endsWith("path-utils.ts"));
151
+ assert.ok(compactUnixPath.includes("/"));
152
+
153
+ const windowsPath = "C:\\Users\\Administrator\\Documents\\pi-rtk-optimizer\\src\\windows-command-helpers.ts";
154
+ const compactWindowsPath = compactPath(windowsPath, 30);
155
+ assert.ok(compactWindowsPath.length <= 30);
156
+ assert.equal(compactWindowsPath.includes("\\"), true);
157
+ assert.ok(compactWindowsPath.endsWith("windows-command-helpers.ts"));
158
+
159
+ assert.equal(compactPath("src/file.ts", 40), "src/file.ts");
160
+ });
161
+
162
+ runTest("windows bash compatibility rewrites only when the runtime is Windows", () => {
163
+ const command = "cd /d C:\\Users\\Administrator\\project && python script.py";
164
+ const fixed = applyWindowsBashCompatibilityFixes(command);
165
+
166
+ if (process.platform === "win32") {
167
+ assert.deepEqual(fixed.applied, ["cd-/d", "python-utf8"]);
168
+ assert.equal(
169
+ fixed.command,
170
+ 'PYTHONIOENCODING=utf-8 cd "C:/Users/Administrator/project" && python script.py',
171
+ );
172
+
173
+ const alreadyUtf8 = applyWindowsBashCompatibilityFixes("PYTHONIOENCODING=utf-8 python script.py");
174
+ assert.deepEqual(alreadyUtf8.applied, []);
175
+ assert.equal(alreadyUtf8.command, "PYTHONIOENCODING=utf-8 python script.py");
176
+ } else {
177
+ assert.deepEqual(fixed.applied, []);
178
+ assert.equal(fixed.command, command);
179
+ }
180
+ });
181
+
182
+ runTest("rewrite pipeline safety buffers rewritten Windows producer commands", () => {
183
+ const rewritten = applyRewrittenCommandShellSafetyFixups("rtk git diff | grep TODO");
184
+
185
+ if (process.platform === "win32") {
186
+ assert.ok(rewritten.includes('mktemp'));
187
+ assert.ok(rewritten.includes('trap'));
188
+ assert.ok(rewritten.includes('rtk git diff > "$__pi_rtk_pipe_tmp"'));
189
+ assert.ok(rewritten.includes('(grep TODO) < "$__pi_rtk_pipe_tmp"'));
190
+ } else {
191
+ assert.equal(rewritten, "rtk git diff | grep TODO");
192
+ }
193
+
194
+ assert.equal(applyRewrittenCommandShellSafetyFixups("git diff | grep TODO"), "git diff | grep TODO");
195
+ });
196
+
197
+ console.log("All additional coverage tests passed.");
@@ -0,0 +1,3 @@
1
+ export function toOnOff(value: boolean, truthyLabel = "on", falsyLabel = "off"): string {
2
+ return value ? truthyLabel : falsyLabel;
3
+ }