pi-rtk-optimizer 0.5.5 → 0.6.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.
- package/CHANGELOG.md +8 -0
- package/README.md +9 -34
- package/config/config.example.json +0 -9
- package/package.json +3 -3
- package/src/additional-coverage-test.ts +1 -1
- package/src/command-rewriter-test.ts +118 -160
- package/src/command-rewriter.ts +43 -594
- package/src/config-modal-test.ts +2 -0
- package/src/config-modal.ts +1 -105
- package/src/config-store.ts +0 -24
- package/src/index.ts +6 -6
- package/src/rtk-rewrite-provider.ts +90 -0
- package/src/runtime-guard-test.ts +4 -3
- package/src/runtime-guard.ts +1 -1
- package/src/types.ts +0 -18
- package/src/rewrite-bypass.ts +0 -332
- package/src/rewrite-rules.ts +0 -255
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.6.0] - 2026-04-27
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- **Breaking:** Command rewriting now delegates rewrite decisions to the installed `rtk rewrite` command, making RTK the source of truth for command support, shell parsing, bypasses, and compound-command behavior instead of the extension's local rewrite rule tables.
|
|
14
|
+
|
|
15
|
+
### Removed
|
|
16
|
+
- **Breaking:** Removed the rewrite category configuration surface (`rewriteGitGithub`, `rewriteFilesystem`, `rewriteRust`, `rewriteJavaScript`, `rewritePython`, `rewriteGo`, `rewriteContainers`, `rewriteNetwork`, and `rewritePackageManagers`) from configuration normalization, examples, settings UI, and documentation. Configure rewrite policy in RTK itself instead of this extension.
|
|
17
|
+
|
|
10
18
|
## [0.5.5] - 2026-04-24
|
|
11
19
|
|
|
12
20
|
### Changed
|
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# pi-rtk-optimizer
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/pi-rtk-optimizer) [](LICENSE)
|
|
4
|
+
|
|
3
5
|
> RTK command rewriting and tool output compaction extension for the Pi coding agent.
|
|
4
6
|
|
|
5
7
|
<img width="1360" height="752" alt="image" src="https://github.com/user-attachments/assets/f4536889-62ec-429a-984e-dc0de9f1f709" />
|
|
@@ -12,19 +14,9 @@
|
|
|
12
14
|
### Command Rewriting
|
|
13
15
|
|
|
14
16
|
- **Automatic rewriting** or **suggestion-only** mode for common development workflows
|
|
15
|
-
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
- **Rust** — `cargo` commands
|
|
19
|
-
- **JavaScript** — `vitest`, `npm`, `yarn`, `pnpm`, `bun` commands
|
|
20
|
-
- **Python** — `pytest`, `python`, `pip`, `uv` commands
|
|
21
|
-
- **Go** — `go` commands
|
|
22
|
-
- **Containers** — `docker`, `docker-compose`, `podman` commands
|
|
23
|
-
- **Network** — `curl`, `wget` commands
|
|
24
|
-
- **Package Managers** — `apt`, `brew`, `dnf`, `pacman`, `yum` commands
|
|
25
|
-
- Runtime guard when `rtk` binary is unavailable (falls back to original commands only in rewrite mode)
|
|
26
|
-
- Safe rewrite bypasses for structured `gh` output commands and non-interactive container shell sessions
|
|
27
|
-
- Improved command parsing for `sed`, shell separators, and `pnpm dlx` proxy rewrites
|
|
17
|
+
- Delegates bash command rewrite decisions to the installed `rtk rewrite` command, keeping RTK as the source of truth for supported commands, shell parsing, bypasses, and compound-command behavior
|
|
18
|
+
- Runtime guard when `rtk` binary is unavailable (raw commands run unchanged and repeated missing-binary rewrite probes are avoided)
|
|
19
|
+
- Pi-specific shell safety fixups for rewritten commands on Windows
|
|
28
20
|
|
|
29
21
|
### Output Compaction Pipeline
|
|
30
22
|
|
|
@@ -126,19 +118,11 @@ A starter template is included at `config/config.example.json`.
|
|
|
126
118
|
| `guardWhenRtkMissing` | boolean | `true` | Run original commands when rtk binary unavailable |
|
|
127
119
|
| `showRewriteNotifications` | boolean | `true` | Show rewrite notices in TUI |
|
|
128
120
|
|
|
129
|
-
#### Rewrite
|
|
121
|
+
#### Rewrite Source
|
|
122
|
+
|
|
123
|
+
Bash command support is intentionally resolved by the installed `rtk` binary through `rtk rewrite`. The extension does not maintain duplicate rewrite rules or category classifiers; update/configure RTK itself for command support policy.
|
|
130
124
|
|
|
131
|
-
|
|
132
|
-
|--------|---------|-------------------|
|
|
133
|
-
| `rewriteGitGithub` | `true` | `git`, `gh` |
|
|
134
|
-
| `rewriteFilesystem` | `true` | `cat`, `head`, `tail`, `grep`, `rg`, `ls`, `tree`, `find`, `diff`, `wc` |
|
|
135
|
-
| `rewriteRust` | `true` | `cargo` |
|
|
136
|
-
| `rewriteJavaScript` | `true` | `vitest`, `npm`, `yarn`, `pnpm`, `bun` |
|
|
137
|
-
| `rewritePython` | `true` | `pytest`, `python`, `pip`, `uv` |
|
|
138
|
-
| `rewriteGo` | `true` | `go` |
|
|
139
|
-
| `rewriteContainers` | `true` | `docker`, `docker-compose`, `podman` |
|
|
140
|
-
| `rewriteNetwork` | `true` | `curl`, `wget` |
|
|
141
|
-
| `rewritePackageManagers` | `true` | `apt`, `brew`, `dnf`, `pacman`, `yum` |
|
|
125
|
+
> **Breaking in 0.6.0:** Rewrite category toggles (`rewriteGitGithub`, `rewriteFilesystem`, `rewriteRust`, `rewriteJavaScript`, `rewritePython`, `rewriteGo`, `rewriteContainers`, `rewriteNetwork`, and `rewritePackageManagers`) were removed from the extension config surface. Existing rewrite policy should be configured in RTK because the extension now delegates rewrite ownership to `rtk rewrite`.
|
|
142
126
|
|
|
143
127
|
#### Output Compaction Settings
|
|
144
128
|
|
|
@@ -185,15 +169,6 @@ Skill-read preservation covers the global Pi skills directory (`~/.pi/agent/skil
|
|
|
185
169
|
"mode": "rewrite",
|
|
186
170
|
"guardWhenRtkMissing": true,
|
|
187
171
|
"showRewriteNotifications": true,
|
|
188
|
-
"rewriteGitGithub": true,
|
|
189
|
-
"rewriteFilesystem": true,
|
|
190
|
-
"rewriteRust": true,
|
|
191
|
-
"rewriteJavaScript": true,
|
|
192
|
-
"rewritePython": true,
|
|
193
|
-
"rewriteGo": true,
|
|
194
|
-
"rewriteContainers": true,
|
|
195
|
-
"rewriteNetwork": true,
|
|
196
|
-
"rewritePackageManagers": true,
|
|
197
172
|
"outputCompaction": {
|
|
198
173
|
"enabled": true,
|
|
199
174
|
"stripAnsi": true,
|
|
@@ -3,15 +3,6 @@
|
|
|
3
3
|
"mode": "rewrite",
|
|
4
4
|
"guardWhenRtkMissing": true,
|
|
5
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
6
|
"outputCompaction": {
|
|
16
7
|
"enabled": true,
|
|
17
8
|
"stripAnsi": true,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-rtk-optimizer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Pi extension that optimizes RTK command rewriting and tool output compaction for the coding agent.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.ts",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
]
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
|
-
"@mariozechner/pi-coding-agent": "^0.70.
|
|
62
|
-
"@mariozechner/pi-tui": "^0.70.
|
|
61
|
+
"@mariozechner/pi-coding-agent": "^0.70.2",
|
|
62
|
+
"@mariozechner/pi-tui": "^0.70.2"
|
|
63
63
|
}
|
|
64
64
|
}
|
|
@@ -64,7 +64,7 @@ runTest("config-store normalizes invalid values and clamps numeric ranges", () =
|
|
|
64
64
|
|
|
65
65
|
assert.equal(normalized.enabled, true);
|
|
66
66
|
assert.equal(normalized.mode, "rewrite");
|
|
67
|
-
assert.equal(normalized
|
|
67
|
+
assert.equal(Object.hasOwn(normalized, "rewriteGitGithub"), false);
|
|
68
68
|
assert.equal(normalized.outputCompaction.stripAnsi, false);
|
|
69
69
|
assert.equal(normalized.outputCompaction.sourceCodeFilteringEnabled, true);
|
|
70
70
|
assert.equal(normalized.outputCompaction.sourceCodeFiltering, "minimal");
|
|
@@ -1,160 +1,118 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
|
|
3
|
-
import { computeRewriteDecision } from "./command-rewriter.ts";
|
|
4
|
-
import { cloneDefaultConfig, runTest } from "./test-helpers.ts";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
return
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
runTest("
|
|
12
|
-
const config = cloneDefaultConfig();
|
|
13
|
-
const decision = computeRewriteDecision("
|
|
14
|
-
|
|
15
|
-
assert.equal(decision.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
);
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
assert.equal(decision.
|
|
29
|
-
assert.equal(decision.reason, "no_match");
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
runTest("
|
|
33
|
-
const config = cloneDefaultConfig();
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
assert.equal(
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
assert.equal(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
assert.equal(
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
assert.equal(decision.changed,
|
|
80
|
-
assert.equal(decision.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
const
|
|
87
|
-
assert.equal(
|
|
88
|
-
assert.equal(
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
];
|
|
120
|
-
|
|
121
|
-
for (const command of auditedCommands) {
|
|
122
|
-
const decision = computeRewriteDecision(command, config);
|
|
123
|
-
assert.equal(decision.changed, false, command);
|
|
124
|
-
assert.equal(decision.rewrittenCommand, command, command);
|
|
125
|
-
assert.equal(decision.reason, "no_match", command);
|
|
126
|
-
}
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
runTest("formatting-sensitive ls flows bypass rewrites", () => {
|
|
130
|
-
const config = cloneDefaultConfig();
|
|
131
|
-
const auditedCommands = ["ls -la", "ls -l src | grep command-rewriter"];
|
|
132
|
-
|
|
133
|
-
for (const command of auditedCommands) {
|
|
134
|
-
const decision = computeRewriteDecision(command, config);
|
|
135
|
-
assert.equal(decision.changed, false, command);
|
|
136
|
-
assert.equal(decision.rewrittenCommand, command, command);
|
|
137
|
-
assert.equal(decision.reason, "no_match", command);
|
|
138
|
-
}
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
runTest("native bash shell proxy flows bypass rewrites when RTK parity is unsafe", () => {
|
|
142
|
-
const config = cloneDefaultConfig();
|
|
143
|
-
const command = 'bash -lc "find src -type f | xargs grep todo"';
|
|
144
|
-
const decision = computeRewriteDecision(command, config);
|
|
145
|
-
|
|
146
|
-
assert.equal(decision.changed, false);
|
|
147
|
-
assert.equal(decision.rewrittenCommand, command);
|
|
148
|
-
assert.equal(decision.reason, "no_match");
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
runTest("python proxy rewrites preserve the original executable token", () => {
|
|
152
|
-
const config = cloneDefaultConfig();
|
|
153
|
-
const decision = computeRewriteDecision('python3.11 -c "print(1)"', config);
|
|
154
|
-
|
|
155
|
-
assert.equal(decision.changed, true);
|
|
156
|
-
assert.equal(decision.rule?.id, "python-proxy");
|
|
157
|
-
assert.equal(decision.rewrittenCommand, `rtk proxy ${expectedProxyExecutable("python3.11")} -c "print(1)"`);
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
console.log("All command-rewriter tests passed.");
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
|
|
3
|
+
import { computeRewriteDecision } from "./command-rewriter.ts";
|
|
4
|
+
import { cloneDefaultConfig, runTest } from "./test-helpers.ts";
|
|
5
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
6
|
+
|
|
7
|
+
function createMockPi(execResult: { code: number; stdout?: string; stderr?: string }): ExtensionAPI {
|
|
8
|
+
return { exec: async () => execResult } as unknown as ExtensionAPI;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
runTest("empty command unchanged", async () => {
|
|
12
|
+
const config = cloneDefaultConfig();
|
|
13
|
+
const decision = await computeRewriteDecision("", config, createMockPi({ code: 1 }));
|
|
14
|
+
assert.equal(decision.changed, false);
|
|
15
|
+
assert.equal(decision.reason, "empty");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
runTest("already rtk unchanged", async () => {
|
|
19
|
+
const config = cloneDefaultConfig();
|
|
20
|
+
const decision = await computeRewriteDecision("rtk status", config, createMockPi({ code: 1 }));
|
|
21
|
+
assert.equal(decision.changed, false);
|
|
22
|
+
assert.equal(decision.reason, "already_rtk");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
runTest("rtk unsupported heredoc result leaves command unchanged", async () => {
|
|
26
|
+
const config = cloneDefaultConfig();
|
|
27
|
+
const decision = await computeRewriteDecision("cat <<EOF", config, createMockPi({ code: 1 }));
|
|
28
|
+
assert.equal(decision.changed, false);
|
|
29
|
+
assert.equal(decision.reason, "no_match");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
runTest("quoted heredoc marker is delegated to RTK rewrite", async () => {
|
|
33
|
+
const config = cloneDefaultConfig();
|
|
34
|
+
const command = 'echo "<<not heredoc" && git status';
|
|
35
|
+
const decision = await computeRewriteDecision(
|
|
36
|
+
command,
|
|
37
|
+
config,
|
|
38
|
+
createMockPi({ code: 3, stdout: 'echo "<<not heredoc" && rtk git status' }),
|
|
39
|
+
);
|
|
40
|
+
assert.equal(decision.changed, true);
|
|
41
|
+
assert.equal(decision.rewrittenCommand, 'echo "<<not heredoc" && rtk git status');
|
|
42
|
+
assert.equal(decision.reason, "ok");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
runTest("legacy category toggles do not pre-filter RTK rewrite source of truth", async () => {
|
|
46
|
+
const config = { ...cloneDefaultConfig(), rewriteGitGithub: false };
|
|
47
|
+
const decision = await computeRewriteDecision("git status", config, createMockPi({ code: 3, stdout: "rtk git status" }));
|
|
48
|
+
assert.equal(decision.changed, true);
|
|
49
|
+
assert.equal(decision.rewrittenCommand, "rtk git status");
|
|
50
|
+
assert.equal(decision.reason, "ok");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
runTest("rtk exit 0 rewrites", async () => {
|
|
54
|
+
const config = cloneDefaultConfig();
|
|
55
|
+
const decision = await computeRewriteDecision("git status", config, createMockPi({ code: 0, stdout: "rtk git status" }));
|
|
56
|
+
assert.equal(decision.changed, true);
|
|
57
|
+
assert.equal(decision.rewrittenCommand, "rtk git status");
|
|
58
|
+
assert.equal(decision.reason, "ok");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
runTest("rtk exit 3 rewrites", async () => {
|
|
62
|
+
const config = cloneDefaultConfig();
|
|
63
|
+
const decision = await computeRewriteDecision("git status", config, createMockPi({ code: 3, stdout: "rtk git status" }));
|
|
64
|
+
assert.equal(decision.changed, true);
|
|
65
|
+
assert.equal(decision.rewrittenCommand, "rtk git status");
|
|
66
|
+
assert.equal(decision.reason, "ok");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
runTest("exit 1 leaves unchanged", async () => {
|
|
70
|
+
const config = cloneDefaultConfig();
|
|
71
|
+
const decision = await computeRewriteDecision("git status", config, createMockPi({ code: 1 }));
|
|
72
|
+
assert.equal(decision.changed, false);
|
|
73
|
+
assert.equal(decision.reason, "no_match");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
runTest("exit 2 leaves unchanged", async () => {
|
|
77
|
+
const config = cloneDefaultConfig();
|
|
78
|
+
const decision = await computeRewriteDecision("git status", config, createMockPi({ code: 2, stderr: "denied" }));
|
|
79
|
+
assert.equal(decision.changed, false);
|
|
80
|
+
assert.equal(decision.reason, "no_match");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
runTest("unknown category passes through to RTK", async () => {
|
|
84
|
+
const config = cloneDefaultConfig();
|
|
85
|
+
const pi = createMockPi({ code: 0, stdout: "rtk custom" });
|
|
86
|
+
const decision = await computeRewriteDecision("custom-cmd", config, pi);
|
|
87
|
+
assert.equal(decision.changed, true);
|
|
88
|
+
assert.equal(decision.rewrittenCommand, "rtk custom");
|
|
89
|
+
assert.equal(decision.reason, "ok");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
runTest("exec error/timeout leaves unchanged", async () => {
|
|
93
|
+
const config = cloneDefaultConfig();
|
|
94
|
+
const pi = {
|
|
95
|
+
exec: async () => {
|
|
96
|
+
throw new Error("timeout");
|
|
97
|
+
},
|
|
98
|
+
} as unknown as ExtensionAPI;
|
|
99
|
+
const decision = await computeRewriteDecision("git status", config, pi);
|
|
100
|
+
assert.equal(decision.changed, false);
|
|
101
|
+
assert.equal(decision.reason, "no_match");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
runTest("compound commands forwarded to RTK", async () => {
|
|
105
|
+
const config = cloneDefaultConfig();
|
|
106
|
+
let capturedArgs: string[] = [];
|
|
107
|
+
const pi = {
|
|
108
|
+
exec: async (_cmd: string, args: string[]) => {
|
|
109
|
+
capturedArgs = args;
|
|
110
|
+
return { code: 0, stdout: "rtk result" };
|
|
111
|
+
},
|
|
112
|
+
} as unknown as ExtensionAPI;
|
|
113
|
+
const decision = await computeRewriteDecision("git status && cargo test", config, pi);
|
|
114
|
+
assert.equal(decision.changed, true);
|
|
115
|
+
assert.deepEqual(capturedArgs, ["rewrite", "git status && cargo test"]);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
console.log("All command-rewriter tests passed.");
|