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 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
+ [![npm version](https://img.shields.io/npm/v/pi-rtk-optimizer?style=flat-square)](https://www.npmjs.com/package/pi-rtk-optimizer) [![License](https://img.shields.io/github/license/MasuRii/pi-rtk-optimizer?style=flat-square)](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
- - Supported command categories:
16
- - **Git/GitHub** `git`, `gh` commands
17
- - **Filesystem** `cat`, `head`, `tail`, `grep`, `rg`, `ls`, `tree`, `find`, `diff`, `wc`, `bash`, `cmd`, `powershell`
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 Category Toggles
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
- | Option | Default | Commands Affected |
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.5.5",
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.0",
62
- "@mariozechner/pi-tui": "^0.70.0"
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.rewriteGitGithub, false);
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
- function expectedProxyExecutable(base: string): string {
7
- const windowsExecutables = new Set(["npm", "npx", "pnpm", "yarn"]);
8
- return process.platform === "win32" && windowsExecutables.has(base) ? `${base}.cmd` : base;
9
- }
10
-
11
- runTest("pnpm dlx rewrites through RTK proxy instead of generic pnpm wrapper", () => {
12
- const config = cloneDefaultConfig();
13
- const decision = computeRewriteDecision("pnpm dlx create-vite@latest demo --template react-ts", config);
14
-
15
- assert.equal(decision.changed, true);
16
- assert.equal(decision.rule?.id, "pnpm-dlx-proxy");
17
- assert.equal(
18
- decision.rewrittenCommand,
19
- `rtk proxy ${expectedProxyExecutable("pnpm")} dlx create-vite@latest demo --template react-ts`,
20
- );
21
- });
22
-
23
- runTest("docker run with shell command bypasses rewrite when interactive flags are missing", () => {
24
- const config = cloneDefaultConfig();
25
- const decision = computeRewriteDecision("docker run ubuntu bash", config);
26
-
27
- assert.equal(decision.changed, false);
28
- assert.equal(decision.rewrittenCommand, "docker run ubuntu bash");
29
- assert.equal(decision.reason, "no_match");
30
- });
31
-
32
- runTest("docker run with -it keeps container rewrite enabled", () => {
33
- const config = cloneDefaultConfig();
34
- const decision = computeRewriteDecision("docker run -it ubuntu bash", config);
35
-
36
- assert.equal(decision.changed, true);
37
- assert.equal(decision.rule?.id, "docker");
38
- assert.equal(decision.rewrittenCommand, "rtk docker run -it ubuntu bash");
39
- });
40
-
41
- runTest("docker compose exec without -it bypasses interactive shell rewrite", () => {
42
- const config = cloneDefaultConfig();
43
- const decision = computeRewriteDecision("docker compose exec web bash", config);
44
-
45
- assert.equal(decision.changed, false);
46
- assert.equal(decision.rewrittenCommand, "docker compose exec web bash");
47
- });
48
-
49
- runTest("container rewrites stay enabled for scripted shells and non-shell commands", () => {
50
- const config = cloneDefaultConfig();
51
-
52
- const scriptedShell = computeRewriteDecision('docker run ubuntu bash -lc "echo hi"', config);
53
- assert.equal(scriptedShell.changed, true);
54
- assert.equal(scriptedShell.rule?.id, "docker");
55
- assert.equal(scriptedShell.rewrittenCommand, 'rtk docker run ubuntu bash -lc "echo hi"');
56
-
57
- const nonShell = computeRewriteDecision("docker run ubuntu python app.py", config);
58
- assert.equal(nonShell.changed, true);
59
- assert.equal(nonShell.rule?.id, "docker");
60
- assert.equal(nonShell.rewrittenCommand, "rtk docker run ubuntu python app.py");
61
- });
62
-
63
- runTest("kubectl exec requires interactive flags before rewriting shell sessions", () => {
64
- const config = cloneDefaultConfig();
65
- const missingFlagsDecision = computeRewriteDecision("kubectl exec pod-123 -- bash", config);
66
- assert.equal(missingFlagsDecision.changed, false);
67
- assert.equal(missingFlagsDecision.rewrittenCommand, "kubectl exec pod-123 -- bash");
68
-
69
- const interactiveDecision = computeRewriteDecision("kubectl exec -it pod-123 -- bash", config);
70
- assert.equal(interactiveDecision.changed, true);
71
- assert.equal(interactiveDecision.rule?.id, "kubectl");
72
- assert.equal(interactiveDecision.rewrittenCommand, "rtk kubectl exec -it pod-123 -- bash");
73
- });
74
-
75
- runTest("sed scripts keep internal separators intact while later pipe segments still rewrite", () => {
76
- const config = cloneDefaultConfig();
77
- const decision = computeRewriteDecision("sed -e s/a/b/;d file.txt | git status", config);
78
-
79
- assert.equal(decision.changed, true);
80
- assert.equal(decision.rewrittenCommand, "sed -e s/a/b/;d file.txt | rtk git status");
81
- assert.equal(decision.rule?.id, "git-any");
82
- });
83
-
84
- runTest("background operators rewrite both command segments without misreading redirect ampersands", () => {
85
- const config = cloneDefaultConfig();
86
- const backgroundDecision = computeRewriteDecision("git status & cargo test", config);
87
- assert.equal(backgroundDecision.changed, true);
88
- assert.equal(backgroundDecision.rewrittenCommand, "rtk git status & rtk cargo test");
89
-
90
- const redirectDecision = computeRewriteDecision("cargo test 2>&1 | head -5", config);
91
- assert.equal(redirectDecision.changed, true);
92
- assert.equal(redirectDecision.rewrittenCommand, "rtk cargo test 2>&1 | head -5");
93
- assert.equal(redirectDecision.rule?.id, "cargo-any");
94
- });
95
-
96
- runTest("gh structured output commands bypass RTK rewrites", () => {
97
- const config = cloneDefaultConfig();
98
- const structuredCommands = [
99
- "gh pr list --json number,title",
100
- "gh issue list --jq '.[].title'",
101
- "gh pr view 123 --template '{{.title}}'",
102
- ];
103
-
104
- for (const command of structuredCommands) {
105
- const decision = computeRewriteDecision(command, config);
106
- assert.equal(decision.changed, false);
107
- assert.equal(decision.rewrittenCommand, command);
108
- assert.equal(decision.reason, "no_match");
109
- }
110
- });
111
-
112
- runTest("compound find and search shell flows bypass rewrites when RTK parity is unsafe", () => {
113
- const config = cloneDefaultConfig();
114
- const auditedCommands = [
115
- "find src -type f | xargs grep todo",
116
- "find . -name '*.ts' -exec grep -n FIXME {} +",
117
- "grep -R TODO src | head -n 5",
118
- "rg TODO src && wc -l",
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.");