pi-rtk-optimizer 0.6.0 → 0.7.1

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,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.7.1] - 2026-05-04
11
+
12
+ ### Changed
13
+ - Clarified the README architecture inventory for delegated `rtk rewrite` ownership and documented Bun as a development verification prerequisite.
14
+ - Pinned TypeScript and esbuild as dev dependencies so build and bundle checks use locked local tooling.
15
+ - Added RTK executable path visibility to runtime verification output and documented audit/debug config expectations.
16
+
17
+ ### Fixed
18
+ - Hardened `RTK_DB_PATH` shell quoting against inherited temp paths containing command-substitution syntax.
19
+ - Made the custom test helper await async tests before reporting pass.
20
+ - Preserved RTK rewrite error details through the extension's existing UI warning path.
21
+ - Expanded Windows command and rewritten-pipeline fixups for leading compound-command cases.
22
+ - Normalized compaction technique return handling while preserving existing output behavior.
23
+ - Added lifecycle and vendored modal regression coverage for high-risk extension event paths.
24
+
25
+ ## [0.7.0] - 2026-04-30
26
+
27
+ ### Added
28
+ - Added opt-in `readCompaction` controls for `read` output so lossy source filtering and smart truncation stay disabled unless explicitly enabled.
29
+
30
+ ### Changed
31
+ - Updated README and example configuration defaults for safer read-compaction behavior and troubleshooting guidance.
32
+ - Updated `@mariozechner/pi-coding-agent` and `@mariozechner/pi-tui` peer dependencies to ^0.72.0.
33
+
10
34
  ## [0.6.0] - 2026-04-27
11
35
 
12
36
  ### Changed
package/README.md CHANGED
@@ -16,6 +16,7 @@
16
16
  - **Automatic rewriting** or **suggestion-only** mode for common development workflows
17
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
18
  - Runtime guard when `rtk` binary is unavailable (raw commands run unchanged and repeated missing-binary rewrite probes are avoided)
19
+ - `/rtk show` and `/rtk verify` surface the resolved `rtk` executable path when the host can resolve it
19
20
  - Pi-specific shell safety fixups for rewritten commands on Windows
20
21
 
21
22
  ### Output Compaction Pipeline
@@ -107,6 +108,8 @@ Actual global path: $PI_CODING_AGENT_DIR/extensions/pi-rtk-optimizer/config.json
107
108
 
108
109
  A starter template is included at `config/config.example.json`.
109
110
 
111
+ For audit or debugging sessions, keep `showRewriteNotifications` enabled and disable lossy `read` compaction/source filtering before gathering evidence. Existing `config.json` files are user-owned runtime state; do not overwrite local choices unless you intentionally want to change live extension behavior.
112
+
110
113
  ### Configuration Options
111
114
 
112
115
  #### Top-Level Settings
@@ -130,9 +133,10 @@ Bash command support is intentionally resolved by the installed `rtk` binary thr
130
133
  |--------|------|---------|-------------|
131
134
  | `outputCompaction.enabled` | boolean | `true` | Enable output compaction pipeline |
132
135
  | `outputCompaction.stripAnsi` | boolean | `true` | Remove ANSI escape codes |
133
- | `outputCompaction.sourceCodeFilteringEnabled` | boolean | `true` | Enable source code filtering for `read` output |
136
+ | `outputCompaction.readCompaction.enabled` | boolean | `false` | Enable lossy compaction for `read` output; defaults off so code reads stay exact |
137
+ | `outputCompaction.sourceCodeFilteringEnabled` | boolean | `false` | Enable source code filtering for `read` output when read compaction is enabled |
134
138
  | `outputCompaction.preserveExactSkillReads` | boolean | `false` | Keep reads under configured Pi/global/project skill directories exact, bypassing read compaction |
135
- | `outputCompaction.sourceCodeFiltering` | string | `"minimal"` | Filter level: `"none"`, `"minimal"`, `"aggressive"` |
139
+ | `outputCompaction.sourceCodeFiltering` | string | `"none"` | Filter level: `"none"`, `"minimal"`, `"aggressive"` |
136
140
  | `outputCompaction.aggregateTestOutput` | boolean | `true` | Summarize test runner output |
137
141
  | `outputCompaction.filterBuildOutput` | boolean | `true` | Filter build/compile output |
138
142
  | `outputCompaction.compactGitOutput` | boolean | `true` | Compact git command output |
@@ -146,7 +150,7 @@ Skill-read preservation covers the global Pi skills directory (`~/.pi/agent/skil
146
150
 
147
151
  | Option | Type | Default | Range | Description |
148
152
  |--------|------|---------|-------|-------------|
149
- | `outputCompaction.smartTruncate.enabled` | boolean | `true` | — | Enable smart line-based truncation |
153
+ | `outputCompaction.smartTruncate.enabled` | boolean | `false` | — | Enable smart line-based truncation for read output when read compaction is enabled |
150
154
  | `outputCompaction.smartTruncate.maxLines` | number | `220` | 40–4000 | Maximum lines after smart truncation |
151
155
  | `outputCompaction.truncate.enabled` | boolean | `true` | — | Enable hard character truncation |
152
156
  | `outputCompaction.truncate.maxChars` | number | `12000` | 1000–200000 | Maximum characters in final output |
@@ -159,7 +163,7 @@ Skill-read preservation covers the global Pi skills directory (`~/.pi/agent/skil
159
163
  | `minimal` | Removes non-doc comments, collapses blank lines |
160
164
  | `aggressive` | Also removes imports, keeps only signatures and key logic |
161
165
 
162
- > **Note:** When source filtering and read truncation safeguards are active, Pi injects a troubleshooting note for repeated file-edit mismatches. If edits fail because "old text does not match," disable source filtering via `/rtk`, re-read the file, apply the edit, then re-enable filtering.
166
+ > **Note:** When read compaction, source filtering, and read truncation safeguards are active, Pi injects a troubleshooting note for repeated file-edit mismatches. If edits fail because "old text does not match," disable read compaction via `/rtk`, re-read the file, apply the edit, then re-enable compaction.
163
167
 
164
168
  ### Example Configuration
165
169
 
@@ -172,9 +176,12 @@ Skill-read preservation covers the global Pi skills directory (`~/.pi/agent/skil
172
176
  "outputCompaction": {
173
177
  "enabled": true,
174
178
  "stripAnsi": true,
175
- "sourceCodeFilteringEnabled": true,
179
+ "readCompaction": {
180
+ "enabled": false
181
+ },
182
+ "sourceCodeFilteringEnabled": false,
176
183
  "preserveExactSkillReads": false,
177
- "sourceCodeFiltering": "minimal",
184
+ "sourceCodeFiltering": "none",
178
185
  "aggregateTestOutput": true,
179
186
  "filterBuildOutput": true,
180
187
  "compactGitOutput": true,
@@ -182,7 +189,7 @@ Skill-read preservation covers the global Pi skills directory (`~/.pi/agent/skil
182
189
  "groupSearchOutput": true,
183
190
  "trackSavings": true,
184
191
  "smartTruncate": {
185
- "enabled": true,
192
+ "enabled": false,
186
193
  "maxLines": 220
187
194
  },
188
195
  "truncate": {
@@ -203,9 +210,8 @@ src/
203
210
  ├── index.ts # Extension bootstrap and event wiring
204
211
  ├── config-store.ts # Config load/save with normalization
205
212
  ├── config-modal.ts # TUI settings modal and /rtk handler
206
- ├── command-rewriter.ts # Command tokenization and rewrite logic
207
- ├── rewrite-bypass.ts # Rewrite safety bypass rules for interactive/structured commands
208
- ├── rewrite-rules.ts # Rewrite rule catalog
213
+ ├── command-rewriter.ts # Command rewrite decision adapter for RTK delegation
214
+ ├── rtk-rewrite-provider.ts # Calls `rtk rewrite` as the rewrite source of truth
209
215
  ├── rewrite-pipeline-safety.ts # Shell-safety fixups for rewritten commands
210
216
  ├── rtk-command-environment.ts # RTK_DB_PATH scoping for rewritten commands
211
217
  ├── shell-env-prefix.ts # Environment assignment parsing helpers
@@ -248,17 +254,18 @@ Automatic fixes applied on Windows:
248
254
 
249
255
  - **Peer dependencies:** `@mariozechner/pi-coding-agent`, `@mariozechner/pi-tui`
250
256
  - **Runtime:** Node.js ≥20, optional `rtk` binary for command rewriting
257
+ - **Development verification:** Node.js ≥20, npm, and Bun for the test scripts
251
258
 
252
259
  ## Development
253
260
 
254
261
  ```bash
255
- # Build
262
+ # Transpile-only TypeScript build check
256
263
  npm run build
257
264
 
258
265
  # Full typecheck
259
266
  npm run typecheck
260
267
 
261
- # Run tests
268
+ # Run Bun-based tests
262
269
  npm run test
263
270
 
264
271
  # Full verification
@@ -6,15 +6,18 @@
6
6
  "outputCompaction": {
7
7
  "enabled": true,
8
8
  "stripAnsi": true,
9
- "sourceCodeFilteringEnabled": true,
9
+ "readCompaction": {
10
+ "enabled": false
11
+ },
12
+ "sourceCodeFilteringEnabled": false,
10
13
  "preserveExactSkillReads": false,
11
14
  "truncate": {
12
15
  "enabled": true,
13
16
  "maxChars": 12000
14
17
  },
15
- "sourceCodeFiltering": "minimal",
18
+ "sourceCodeFiltering": "none",
16
19
  "smartTruncate": {
17
- "enabled": true,
20
+ "enabled": false,
18
21
  "maxLines": 220
19
22
  },
20
23
  "aggregateTestOutput": true,
package/package.json CHANGED
@@ -1,64 +1,67 @@
1
- {
2
- "name": "pi-rtk-optimizer",
3
- "version": "0.6.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
- "pi-coding-agent",
31
- "coding-agent",
32
- "rtk",
33
- "token-optimization",
34
- "tool-compaction",
35
- "output-compaction",
36
- "command-rewrite",
37
- "optimization"
38
- ],
39
- "author": "MasuRii",
40
- "license": "MIT",
41
- "repository": {
42
- "type": "git",
43
- "url": "git+https://github.com/MasuRii/pi-rtk-optimizer.git"
44
- },
45
- "bugs": {
46
- "url": "https://github.com/MasuRii/pi-rtk-optimizer/issues"
47
- },
48
- "homepage": "https://github.com/MasuRii/pi-rtk-optimizer#readme",
49
- "engines": {
50
- "node": ">=20"
51
- },
52
- "publishConfig": {
53
- "access": "public"
54
- },
55
- "pi": {
56
- "extensions": [
57
- "./index.ts"
58
- ]
59
- },
60
- "peerDependencies": {
61
- "@mariozechner/pi-coding-agent": "^0.70.2",
62
- "@mariozechner/pi-tui": "^0.70.2"
63
- }
64
- }
1
+ {
2
+ "name": "pi-rtk-optimizer",
3
+ "version": "0.7.1",
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": "tsc -p tsconfig.json --noCheck",
20
+ "typecheck": "tsc -p tsconfig.json",
21
+ "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",
22
+ "check": "npm run typecheck && npm run test && npm run build:check",
23
+ "build:check": "esbuild ./index.ts --bundle --platform=node --format=esm --outfile=./.pi-rtk-optimizer-check.mjs --external:@mariozechner/pi-coding-agent --external:@mariozechner/pi-tui && node -e \"import { unlinkSync } from 'node:fs'; unlinkSync('./.pi-rtk-optimizer-check.mjs');\""
24
+ },
25
+ "keywords": [
26
+ "pi-package",
27
+ "pi",
28
+ "pi-extension",
29
+ "pi-coding-agent",
30
+ "coding-agent",
31
+ "rtk",
32
+ "token-optimization",
33
+ "tool-compaction",
34
+ "output-compaction",
35
+ "command-rewrite",
36
+ "optimization"
37
+ ],
38
+ "author": "MasuRii",
39
+ "license": "MIT",
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "git+https://github.com/MasuRii/pi-rtk-optimizer.git"
43
+ },
44
+ "bugs": {
45
+ "url": "https://github.com/MasuRii/pi-rtk-optimizer/issues"
46
+ },
47
+ "homepage": "https://github.com/MasuRii/pi-rtk-optimizer#readme",
48
+ "engines": {
49
+ "node": ">=20"
50
+ },
51
+ "publishConfig": {
52
+ "access": "public"
53
+ },
54
+ "devDependencies": {
55
+ "esbuild": "0.28.0",
56
+ "typescript": "5.7.3"
57
+ },
58
+ "pi": {
59
+ "extensions": [
60
+ "./index.ts"
61
+ ]
62
+ },
63
+ "peerDependencies": {
64
+ "@mariozechner/pi-coding-agent": "^0.72.0",
65
+ "@mariozechner/pi-tui": "^0.72.0"
66
+ }
67
+ }
@@ -66,6 +66,7 @@ runTest("config-store normalizes invalid values and clamps numeric ranges", () =
66
66
  assert.equal(normalized.mode, "rewrite");
67
67
  assert.equal(Object.hasOwn(normalized, "rewriteGitGithub"), false);
68
68
  assert.equal(normalized.outputCompaction.stripAnsi, false);
69
+ assert.equal(normalized.outputCompaction.readCompaction.enabled, true);
69
70
  assert.equal(normalized.outputCompaction.sourceCodeFilteringEnabled, true);
70
71
  assert.equal(normalized.outputCompaction.sourceCodeFiltering, "minimal");
71
72
  assert.equal(normalized.outputCompaction.truncate.maxChars, 1_000);
@@ -73,18 +74,32 @@ runTest("config-store normalizes invalid values and clamps numeric ranges", () =
73
74
  assert.equal(normalized.outputCompaction.trackSavings, false);
74
75
  });
75
76
 
77
+ runTest("config-store uses safer read defaults when readCompaction is explicit", () => {
78
+ const normalized = normalizeRtkIntegrationConfig({
79
+ outputCompaction: {
80
+ readCompaction: { enabled: false },
81
+ },
82
+ });
83
+
84
+ assert.equal(normalized.outputCompaction.readCompaction.enabled, false);
85
+ assert.equal(normalized.outputCompaction.sourceCodeFilteringEnabled, false);
86
+ assert.equal(normalized.outputCompaction.sourceCodeFiltering, "none");
87
+ assert.equal(normalized.outputCompaction.smartTruncate.enabled, false);
88
+ });
89
+
76
90
  runTest("config-store can ensure, save, and reload isolated config files", () => {
77
91
  const tempPath = makeTempConfigPath();
78
92
  cleanupFile(tempPath);
79
93
 
80
94
  try {
81
95
  const ensured = ensureConfigExists(tempPath);
82
- assert.equal(ensured.created, true);
96
+ assert.equal(ensured.error, undefined);
83
97
  assert.equal(existsSync(tempPath), true);
84
98
 
85
99
  const defaultLoad = loadRtkIntegrationConfig(tempPath);
86
100
  assert.equal(defaultLoad.warning, undefined);
87
101
  assert.equal(defaultLoad.config.mode, "rewrite");
102
+ assert.equal(defaultLoad.config.outputCompaction.readCompaction.enabled, false);
88
103
 
89
104
  const saved = saveRtkIntegrationConfig(
90
105
  {
@@ -153,6 +168,40 @@ runTest("command detection ignores env prefixes, blank lines, and chained suffix
153
168
  assert.equal(matchesCommandPatterns("echo hello", [/^bun test/]), false);
154
169
  });
155
170
 
171
+ runTest("RTK command environment preserves explicit leading RTK_DB_PATH overrides", () => {
172
+ const command = 'RTK_DB_PATH="/custom/history.db" rtk git diff';
173
+ assert.equal(applyRtkCommandEnvironment(command), command);
174
+
175
+ const singleQuotedCommand = "RTK_DB_PATH='/custom/it'\\''s/history.db' rtk git diff";
176
+ assert.equal(applyRtkCommandEnvironment(singleQuotedCommand), singleQuotedCommand);
177
+
178
+ const exportedCommand = 'export RTK_DB_PATH="/custom/history.db"; rtk git diff';
179
+ assert.equal(applyRtkCommandEnvironment(exportedCommand), exportedCommand);
180
+ });
181
+
182
+ runTest("RTK command environment single-quotes hostile temp paths", () => {
183
+ const previousTmpDir = process.env.TMPDIR;
184
+ const previousTmp = process.env.TMP;
185
+ const previousTemp = process.env.TEMP;
186
+ const hostilePath = process.platform === "win32" ? "C:\\Temp\\$(touch owned)`bad`'dir" : "/tmp/$(touch owned)`bad`'dir";
187
+
188
+ try {
189
+ process.env.TMPDIR = hostilePath;
190
+ process.env.TMP = hostilePath;
191
+ process.env.TEMP = hostilePath;
192
+
193
+ const rewritten = applyRtkCommandEnvironment("rtk git status");
194
+ assert.ok(rewritten.startsWith("export RTK_DB_PATH='"));
195
+ assert.ok(rewritten.includes("$(touch owned)`bad`'\\''dir"));
196
+ assert.ok(rewritten.endsWith("; rtk git status"));
197
+ assert.equal(/^export RTK_DB_PATH=\"/.test(rewritten), false);
198
+ } finally {
199
+ process.env.TMPDIR = previousTmpDir;
200
+ process.env.TMP = previousTmp;
201
+ process.env.TEMP = previousTemp;
202
+ }
203
+ });
204
+
156
205
  runTest("path compaction preserves the tail and handles Windows separators", () => {
157
206
  const unixPath = "/Users/example/projects/pi-rtk-optimizer/src/techniques/path-utils.ts";
158
207
  const compactUnixPath = compactPath(unixPath, 28);
@@ -171,59 +220,99 @@ runTest("path compaction preserves the tail and handles Windows separators", ()
171
220
 
172
221
  runTest("windows bash compatibility rewrites only when the runtime is Windows", () => {
173
222
  const command = "cd /d C:\\Users\\Administrator\\project && python script.py";
174
- const fixed = applyWindowsBashCompatibilityFixes(command);
223
+ const fixed = applyWindowsBashCompatibilityFixes(command, "win32");
224
+ assert.deepEqual(fixed.applied, ["cd-/d", "python-utf8"]);
225
+ assert.equal(
226
+ fixed.command,
227
+ 'PYTHONIOENCODING=utf-8 cd "C:/Users/Administrator/project" && python script.py',
228
+ );
175
229
 
176
- if (process.platform === "win32") {
177
- assert.deepEqual(fixed.applied, ["cd-/d", "python-utf8"]);
178
- assert.equal(
179
- fixed.command,
180
- 'PYTHONIOENCODING=utf-8 cd "C:/Users/Administrator/project" && python script.py',
181
- );
230
+ const unchanged = applyWindowsBashCompatibilityFixes(command, "linux");
231
+ assert.deepEqual(unchanged.applied, []);
232
+ assert.equal(unchanged.command, command);
182
233
 
183
- const alreadyUtf8 = applyWindowsBashCompatibilityFixes("PYTHONIOENCODING=utf-8 python script.py");
184
- assert.deepEqual(alreadyUtf8.applied, []);
185
- assert.equal(alreadyUtf8.command, "PYTHONIOENCODING=utf-8 python script.py");
186
- } else {
187
- assert.deepEqual(fixed.applied, []);
188
- assert.equal(fixed.command, command);
189
- }
234
+ const alreadyUtf8 = applyWindowsBashCompatibilityFixes("PYTHONIOENCODING=utf-8 python script.py", "win32");
235
+ assert.deepEqual(alreadyUtf8.applied, []);
236
+ assert.equal(alreadyUtf8.command, "PYTHONIOENCODING=utf-8 python script.py");
237
+ });
238
+
239
+ runTest("windows bash compatibility rewrites compound cd slash-d operators", () => {
240
+ assert.equal(
241
+ applyWindowsBashCompatibilityFixes("cd /d C:\\work || echo failed", "win32").command,
242
+ 'cd "C:/work" || echo failed',
243
+ );
244
+ assert.equal(
245
+ applyWindowsBashCompatibilityFixes("cd /d C:\\work ; echo done", "win32").command,
246
+ 'cd "C:/work" ; echo done',
247
+ );
248
+ assert.equal(
249
+ applyWindowsBashCompatibilityFixes("cd /d C:\\work | cat", "win32").command,
250
+ 'cd "C:/work" | cat',
251
+ );
252
+ assert.equal(
253
+ applyWindowsBashCompatibilityFixes('cd /d "C:\\work space" || echo failed', "win32").command,
254
+ 'cd "C:/work space" || echo failed',
255
+ );
190
256
  });
191
257
 
192
258
  runTest("rewrite pipeline safety buffers rewritten Windows producer commands", () => {
193
- const rewritten = applyRewrittenCommandShellSafetyFixups("rtk git diff | grep TODO");
194
-
195
- if (process.platform === "win32") {
196
- assert.ok(rewritten.includes('mktemp'));
197
- assert.ok(rewritten.includes('trap'));
198
- assert.ok(rewritten.includes('rtk git diff > "$__pi_rtk_pipe_tmp"'));
199
- assert.ok(rewritten.includes('(grep TODO) < "$__pi_rtk_pipe_tmp"'));
200
- } else {
201
- assert.equal(rewritten, "rtk git diff | grep TODO");
202
- }
259
+ const rewritten = applyRewrittenCommandShellSafetyFixups("rtk git diff | grep TODO", "win32");
260
+ assert.ok(rewritten.includes('mktemp'));
261
+ assert.ok(rewritten.includes('trap'));
262
+ assert.ok(rewritten.includes('rtk git diff > "$__pi_rtk_pipe_tmp"'));
263
+ assert.ok(rewritten.includes('(grep TODO) < "$__pi_rtk_pipe_tmp"'));
264
+
265
+ assert.equal(
266
+ applyRewrittenCommandShellSafetyFixups("rtk git diff | grep TODO", "linux"),
267
+ "rtk git diff | grep TODO",
268
+ );
269
+ assert.equal(applyRewrittenCommandShellSafetyFixups("git diff | grep TODO", "win32"), "git diff | grep TODO");
270
+ });
271
+
272
+ runTest("rewrite pipeline safety buffers leading pipelines before compound suffixes", () => {
273
+ const andCommand = applyRewrittenCommandShellSafetyFixups("rtk git diff | grep TODO && echo done", "win32");
274
+ assert.ok(andCommand.includes('(grep TODO) < "$__pi_rtk_pipe_tmp"'));
275
+ assert.ok(andCommand.endsWith("&& echo done"));
276
+
277
+ const orCommand = applyRewrittenCommandShellSafetyFixups("rtk git diff | grep TODO || echo none", "win32");
278
+ assert.ok(orCommand.includes('(grep TODO) < "$__pi_rtk_pipe_tmp"'));
279
+ assert.ok(orCommand.endsWith("|| echo none"));
203
280
 
204
- assert.equal(applyRewrittenCommandShellSafetyFixups("git diff | grep TODO"), "git diff | grep TODO");
281
+ const semicolonCommand = applyRewrittenCommandShellSafetyFixups("rtk git diff | grep TODO; echo done", "win32");
282
+ assert.ok(semicolonCommand.includes('(grep TODO) < "$__pi_rtk_pipe_tmp"'));
283
+ assert.ok(semicolonCommand.endsWith("; echo done"));
205
284
  });
206
285
 
207
- runTest("rewrite pipeline safety keeps RTK_DB_PATH scoped to rewritten producer commands", () => {
208
- const rewritten = applyRewrittenCommandShellSafetyFixups(
209
- applyRtkCommandEnvironment("rtk git diff agent/extensions/pi-multi-auth/account-manager.ts | head -200"),
286
+ runTest("rewrite pipeline safety keeps exported RTK_DB_PATH on rewritten producer commands", () => {
287
+ const envScopedCommand = applyRtkCommandEnvironment("rtk git diff agent/extensions/pi-multi-auth/account-manager.ts | head -200");
288
+ const rewritten = applyRewrittenCommandShellSafetyFixups(envScopedCommand, "win32");
289
+
290
+ assert.ok(rewritten.startsWith("export RTK_DB_PATH="));
291
+ assert.equal(rewritten.startsWith("RTK_DB_PATH="), false);
292
+ assert.ok(rewritten.includes("; {"));
293
+ assert.ok(
294
+ rewritten.includes('rtk git diff agent/extensions/pi-multi-auth/account-manager.ts > "$__pi_rtk_pipe_tmp"'),
210
295
  );
296
+ assert.ok(rewritten.includes('(head -200) < "$__pi_rtk_pipe_tmp"'));
211
297
 
212
- if (process.platform === "win32") {
213
- assert.ok(rewritten.startsWith("{"));
214
- assert.equal(rewritten.startsWith("RTK_DB_PATH="), false);
215
- assert.ok(rewritten.includes("RTK_DB_PATH="));
216
- assert.ok(
217
- rewritten.includes('rtk git diff agent/extensions/pi-multi-auth/account-manager.ts > "$__pi_rtk_pipe_tmp"'),
218
- );
219
- assert.ok(rewritten.includes('(head -200) < "$__pi_rtk_pipe_tmp"'));
220
- } else {
221
- assert.ok(rewritten.startsWith("RTK_DB_PATH="));
222
- assert.equal(
223
- rewritten,
224
- applyRtkCommandEnvironment("rtk git diff agent/extensions/pi-multi-auth/account-manager.ts | head -200"),
225
- );
226
- }
298
+ assert.equal(applyRewrittenCommandShellSafetyFixups(envScopedCommand, "linux"), envScopedCommand);
299
+ });
300
+
301
+ runTest("rewrite pipeline safety buffers explicit RTK_DB_PATH export preludes", () => {
302
+ const command = 'export RTK_DB_PATH="/custom/history.db"; rtk git diff | head -200';
303
+ const rewritten = applyRewrittenCommandShellSafetyFixups(command, "win32");
304
+
305
+ assert.ok(rewritten.startsWith('export RTK_DB_PATH="/custom/history.db"; {'));
306
+ assert.ok(rewritten.includes('rtk git diff > "$__pi_rtk_pipe_tmp"'));
307
+ assert.ok(rewritten.includes('(head -200) < "$__pi_rtk_pipe_tmp"'));
308
+
309
+ assert.equal(applyRewrittenCommandShellSafetyFixups(command, "linux"), command);
310
+ });
311
+
312
+ runTest("RTK command environment uses export prelude for shell compound commands", () => {
313
+ const rewritten = applyRtkCommandEnvironment('for d in a b; do echo "$d"; done');
314
+ assert.ok(/^export RTK_DB_PATH=/.test(rewritten));
315
+ assert.ok(/; for d in a b; do echo "\$d"; done$/.test(rewritten));
227
316
  });
228
317
 
229
318
  runTest("stripRtkHookWarnings handles bare, prefixed, and already-sanitized hook notices", () => {
@@ -273,11 +362,16 @@ runTest("streaming sanitizer strips hook notices, sanitizes emoji output, and pr
273
362
  },
274
363
  ],
275
364
  };
276
- assert.equal(sanitizeStreamingBashExecutionResult(hookNoticeResult, "rtk git status"), true);
365
+ const hookNoticeSanitization = sanitizeStreamingBashExecutionResult(hookNoticeResult, "rtk git status");
366
+ assert.equal(hookNoticeSanitization.changed, true);
277
367
  assert.equal(
278
- (hookNoticeResult.content[0] as { text: string }).text,
368
+ ((hookNoticeSanitization.result as typeof hookNoticeResult).content[0] as { text: string }).text,
279
369
  "working tree clean\n",
280
370
  );
371
+ assert.equal(
372
+ (hookNoticeResult.content[0] as { text: string }).text,
373
+ "[rtk] /!\\ No hook installed — run `rtk init -g` for automatic token savings\n\nworking tree clean\n",
374
+ );
281
375
 
282
376
  const emojiResult = {
283
377
  content: [
@@ -285,9 +379,14 @@ runTest("streaming sanitizer strips hook notices, sanitizes emoji output, and pr
285
379
  { type: "image", url: "ignored" },
286
380
  ],
287
381
  };
288
- assert.equal(sanitizeStreamingBashExecutionResult(emojiResult, "rtk git diff -- src/file.ts"), true);
289
- assert.equal((emojiResult.content[0] as { text: string }).text, "> src/file.ts\n[OK] Files are identical\n");
290
- assert.deepEqual(emojiResult.content[1], { type: "image", url: "ignored" });
382
+ const emojiSanitization = sanitizeStreamingBashExecutionResult(emojiResult, "rtk git diff -- src/file.ts");
383
+ assert.equal(emojiSanitization.changed, true);
384
+ assert.equal(
385
+ ((emojiSanitization.result as typeof emojiResult).content[0] as { text: string }).text,
386
+ "> src/file.ts\n[OK] Files are identical\n",
387
+ );
388
+ assert.equal((emojiResult.content[0] as { text: string }).text, "📄 src/file.ts\n✅ Files are identical\n");
389
+ assert.deepEqual((emojiSanitization.result as typeof emojiResult).content[1], { type: "image", url: "ignored" });
291
390
 
292
391
  const parseWarningResult = {
293
392
  content: [
@@ -297,7 +396,9 @@ runTest("streaming sanitizer strips hook notices, sanitizes emoji output, and pr
297
396
  },
298
397
  ],
299
398
  };
300
- assert.equal(sanitizeStreamingBashExecutionResult(parseWarningResult, "rtk git status"), false);
399
+ const parseWarningSanitization = sanitizeStreamingBashExecutionResult(parseWarningResult, "rtk git status");
400
+ assert.equal(parseWarningSanitization.changed, false);
401
+ assert.equal(parseWarningSanitization.result, parseWarningResult);
301
402
  assert.equal(
302
403
  (parseWarningResult.content[0] as { text: string }).text,
303
404
  "[rtk] warning: builtin filters: parse failure\n\nworking tree clean\n",