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 +24 -0
- package/README.md +19 -12
- package/config/config.example.json +6 -3
- package/package.json +67 -64
- package/src/additional-coverage-test.ts +151 -50
- package/src/command-rewriter-test.ts +187 -118
- package/src/command-rewriter.ts +5 -2
- package/src/config-modal-test.ts +95 -29
- package/src/config-modal.ts +29 -3
- package/src/config-store.ts +32 -5
- package/src/index-test.ts +227 -3
- package/src/index.ts +50 -5
- package/src/output-compactor-test.ts +45 -2
- package/src/output-compactor.ts +26 -15
- package/src/rewrite-pipeline-safety.ts +203 -157
- package/src/rtk-command-environment.ts +13 -4
- package/src/rtk-executable-resolver.ts +97 -0
- package/src/rtk-rewrite-provider.ts +39 -3
- package/src/shell-env-prefix.ts +5 -1
- package/src/test-helpers.ts +23 -10
- package/src/tool-execution-sanitizer.ts +80 -69
- package/src/types.ts +13 -3
- package/src/windows-command-helpers.ts +92 -16
- package/src/zellij-modal.ts +1 -1
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.
|
|
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 | `"
|
|
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 | `
|
|
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
|
|
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
|
-
"
|
|
179
|
+
"readCompaction": {
|
|
180
|
+
"enabled": false
|
|
181
|
+
},
|
|
182
|
+
"sourceCodeFilteringEnabled": false,
|
|
176
183
|
"preserveExactSkillReads": false,
|
|
177
|
-
"sourceCodeFiltering": "
|
|
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":
|
|
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
|
|
207
|
-
├── rewrite-
|
|
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
|
-
#
|
|
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
|
-
"
|
|
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": "
|
|
18
|
+
"sourceCodeFiltering": "none",
|
|
16
19
|
"smartTruncate": {
|
|
17
|
-
"enabled":
|
|
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.
|
|
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": "
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"check": "
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"pi
|
|
28
|
-
"pi",
|
|
29
|
-
"pi-
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
|
|
42
|
-
"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
"
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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.
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
-
|
|
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
|
|
208
|
-
const
|
|
209
|
-
|
|
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
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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
|
-
|
|
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
|
-
|
|
289
|
-
assert.equal(
|
|
290
|
-
assert.
|
|
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
|
-
|
|
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",
|