pi-rtk-optimizer 0.5.5 → 0.7.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.
@@ -54,25 +54,11 @@ function parseIntegerInRange(value: string, min: number, max: number): number |
54
54
  }
55
55
 
56
56
  function summarizeConfig(config: RtkIntegrationConfig, runtimeStatus: RuntimeStatus): string {
57
- const categories = [
58
- config.rewriteGitGithub ? "git" : "",
59
- config.rewriteFilesystem ? "files" : "",
60
- config.rewriteRust ? "rust" : "",
61
- config.rewriteJavaScript ? "js" : "",
62
- config.rewritePython ? "python" : "",
63
- config.rewriteGo ? "go" : "",
64
- config.rewriteContainers ? "containers" : "",
65
- config.rewriteNetwork ? "network" : "",
66
- config.rewritePackageManagers ? "packages" : "",
67
- ]
68
- .filter((value) => value.length > 0)
69
- .join(",");
70
-
71
57
  const runtime = runtimeStatus.rtkAvailable
72
58
  ? "rtk=available"
73
59
  : `rtk=missing${runtimeStatus.lastError ? ` (${runtimeStatus.lastError})` : ""}`;
74
60
 
75
- return `enabled=${config.enabled}, mode=${config.mode}, rewriteNotice=${config.showRewriteNotifications}, compaction=${config.outputCompaction.enabled}, sourceFilterEnabled=${config.outputCompaction.sourceCodeFilteringEnabled}, preserveSkillReads=${config.outputCompaction.preserveExactSkillReads}, sourceFilter=${config.outputCompaction.sourceCodeFiltering}, categories=[${categories || "none"}], ${runtime}`;
61
+ return `enabled=${config.enabled}, mode=${config.mode}, rewriteSource=rtk, rewriteNotice=${config.showRewriteNotifications}, compaction=${config.outputCompaction.enabled}, readCompaction=${config.outputCompaction.readCompaction.enabled}, sourceFilterEnabled=${config.outputCompaction.sourceCodeFilteringEnabled}, preserveSkillReads=${config.outputCompaction.preserveExactSkillReads}, sourceFilter=${config.outputCompaction.sourceCodeFiltering}, ${runtime}`;
76
62
  }
77
63
 
78
64
  function buildSettingItems(config: RtkIntegrationConfig): SettingItem[] {
@@ -119,6 +105,13 @@ function buildSettingItems(config: RtkIntegrationConfig): SettingItem[] {
119
105
  currentValue: toOnOff(config.outputCompaction.stripAnsi),
120
106
  values: ON_OFF,
121
107
  },
108
+ {
109
+ id: "outputReadCompactionEnabled",
110
+ label: "Read compaction enabled",
111
+ description: "If off, read tool output stays exact; build/test/git/grep compaction can still run",
112
+ currentValue: toOnOff(config.outputCompaction.readCompaction.enabled),
113
+ values: ON_OFF,
114
+ },
122
115
  {
123
116
  id: "outputTruncateEnabled",
124
117
  label: "Hard truncation enabled",
@@ -210,69 +203,6 @@ function buildSettingItems(config: RtkIntegrationConfig): SettingItem[] {
210
203
  currentValue: toOnOff(config.outputCompaction.trackSavings),
211
204
  values: ON_OFF,
212
205
  },
213
- {
214
- id: "rewriteGitGithub",
215
- label: "Rewrite git / gh",
216
- description: "git status/log/diff and gh pr/issue/run/api/release",
217
- currentValue: toOnOff(config.rewriteGitGithub),
218
- values: ON_OFF,
219
- },
220
- {
221
- id: "rewriteFilesystem",
222
- label: "Rewrite filesystem commands",
223
- description: "cat, head, rg/grep, ls, tree, find, diff",
224
- currentValue: toOnOff(config.rewriteFilesystem),
225
- values: ON_OFF,
226
- },
227
- {
228
- id: "rewriteRust",
229
- label: "Rewrite Rust commands",
230
- description: "cargo test/build/clippy/check/install/nextest/fmt",
231
- currentValue: toOnOff(config.rewriteRust),
232
- values: ON_OFF,
233
- },
234
- {
235
- id: "rewriteJavaScript",
236
- label: "Rewrite JavaScript/TypeScript",
237
- description: "vitest, npm/npx/next, tsc, lint, prettier, playwright, prisma",
238
- currentValue: toOnOff(config.rewriteJavaScript),
239
- values: ON_OFF,
240
- },
241
- {
242
- id: "rewritePython",
243
- label: "Rewrite Python",
244
- description: "pytest, ruff, pip, uv pip",
245
- currentValue: toOnOff(config.rewritePython),
246
- values: ON_OFF,
247
- },
248
- {
249
- id: "rewriteGo",
250
- label: "Rewrite Go",
251
- description: "go test/build/vet and golangci-lint",
252
- currentValue: toOnOff(config.rewriteGo),
253
- values: ON_OFF,
254
- },
255
- {
256
- id: "rewriteContainers",
257
- label: "Rewrite containers",
258
- description: "docker compose/ps/images/logs/run/build/exec and kubectl core ops",
259
- currentValue: toOnOff(config.rewriteContainers),
260
- values: ON_OFF,
261
- },
262
- {
263
- id: "rewriteNetwork",
264
- label: "Rewrite network",
265
- description: "curl and wget",
266
- currentValue: toOnOff(config.rewriteNetwork),
267
- values: ON_OFF,
268
- },
269
- {
270
- id: "rewritePackageManagers",
271
- label: "Rewrite package managers",
272
- description: "pnpm list/ls/outdated/build/typecheck and npm list/outdated",
273
- currentValue: toOnOff(config.rewritePackageManagers),
274
- values: ON_OFF,
275
- },
276
206
  ];
277
207
  }
278
208
 
@@ -296,6 +226,14 @@ function applySetting(config: RtkIntegrationConfig, id: string, value: string):
296
226
  ...config,
297
227
  outputCompaction: { ...config.outputCompaction, stripAnsi: value === "on" },
298
228
  };
229
+ case "outputReadCompactionEnabled":
230
+ return {
231
+ ...config,
232
+ outputCompaction: {
233
+ ...config.outputCompaction,
234
+ readCompaction: { enabled: value === "on" },
235
+ },
236
+ };
299
237
  case "outputTruncateEnabled":
300
238
  return {
301
239
  ...config,
@@ -418,24 +356,6 @@ function applySetting(config: RtkIntegrationConfig, id: string, value: string):
418
356
  trackSavings: value === "on",
419
357
  },
420
358
  };
421
- case "rewriteGitGithub":
422
- return { ...config, rewriteGitGithub: value === "on" };
423
- case "rewriteFilesystem":
424
- return { ...config, rewriteFilesystem: value === "on" };
425
- case "rewriteRust":
426
- return { ...config, rewriteRust: value === "on" };
427
- case "rewriteJavaScript":
428
- return { ...config, rewriteJavaScript: value === "on" };
429
- case "rewritePython":
430
- return { ...config, rewritePython: value === "on" };
431
- case "rewriteGo":
432
- return { ...config, rewriteGo: value === "on" };
433
- case "rewriteContainers":
434
- return { ...config, rewriteContainers: value === "on" };
435
- case "rewriteNetwork":
436
- return { ...config, rewriteNetwork: value === "on" };
437
- case "rewritePackageManagers":
438
- return { ...config, rewritePackageManagers: value === "on" };
439
359
  default:
440
360
  return config;
441
361
  }
@@ -448,6 +368,7 @@ function syncSettingValues(settingsList: SettingValueSyncTarget, config: RtkInte
448
368
  settingsList.updateValue("guardWhenRtkMissing", toOnOff(config.guardWhenRtkMissing));
449
369
  settingsList.updateValue("outputCompactionEnabled", toOnOff(config.outputCompaction.enabled));
450
370
  settingsList.updateValue("outputStripAnsi", toOnOff(config.outputCompaction.stripAnsi));
371
+ settingsList.updateValue("outputReadCompactionEnabled", toOnOff(config.outputCompaction.readCompaction.enabled));
451
372
  settingsList.updateValue("outputTruncateEnabled", toOnOff(config.outputCompaction.truncate.enabled));
452
373
  settingsList.updateValue("outputTruncateMaxChars", String(config.outputCompaction.truncate.maxChars));
453
374
  settingsList.updateValue("outputSourceFilteringEnabled", toOnOff(config.outputCompaction.sourceCodeFilteringEnabled));
@@ -461,15 +382,6 @@ function syncSettingValues(settingsList: SettingValueSyncTarget, config: RtkInte
461
382
  settingsList.updateValue("outputAggregateLinterOutput", toOnOff(config.outputCompaction.aggregateLinterOutput));
462
383
  settingsList.updateValue("outputGroupSearchOutput", toOnOff(config.outputCompaction.groupSearchOutput));
463
384
  settingsList.updateValue("outputTrackSavings", toOnOff(config.outputCompaction.trackSavings));
464
- settingsList.updateValue("rewriteGitGithub", toOnOff(config.rewriteGitGithub));
465
- settingsList.updateValue("rewriteFilesystem", toOnOff(config.rewriteFilesystem));
466
- settingsList.updateValue("rewriteRust", toOnOff(config.rewriteRust));
467
- settingsList.updateValue("rewriteJavaScript", toOnOff(config.rewriteJavaScript));
468
- settingsList.updateValue("rewritePython", toOnOff(config.rewritePython));
469
- settingsList.updateValue("rewriteGo", toOnOff(config.rewriteGo));
470
- settingsList.updateValue("rewriteContainers", toOnOff(config.rewriteContainers));
471
- settingsList.updateValue("rewriteNetwork", toOnOff(config.rewriteNetwork));
472
- settingsList.updateValue("rewritePackageManagers", toOnOff(config.rewritePackageManagers));
473
385
  }
474
386
 
475
387
  async function openSettingsModal(ctx: ExtensionCommandContext, controller: RtkIntegrationController): Promise<void> {
@@ -30,10 +30,14 @@ function toMode(value: unknown): RtkIntegrationConfig["mode"] {
30
30
  : DEFAULT_RTK_INTEGRATION_CONFIG.mode;
31
31
  }
32
32
 
33
- function toSourceFilterLevel(value: unknown): RtkSourceFilterLevel {
33
+ function toSourceFilterLevel(value: unknown, fallback: RtkSourceFilterLevel): RtkSourceFilterLevel {
34
34
  return RTK_SOURCE_FILTER_LEVELS.includes(value as RtkSourceFilterLevel)
35
35
  ? (value as RtkSourceFilterLevel)
36
- : DEFAULT_RTK_INTEGRATION_CONFIG.outputCompaction.sourceCodeFiltering;
36
+ : fallback;
37
+ }
38
+
39
+ function hasOwnProperty(source: Record<string, unknown>, key: string): boolean {
40
+ return Object.prototype.hasOwnProperty.call(source, key);
37
41
  }
38
42
 
39
43
  function toObject(value: unknown): Record<string, unknown> {
@@ -46,8 +50,20 @@ function toObject(value: unknown): Record<string, unknown> {
46
50
  export function normalizeRtkIntegrationConfig(raw: unknown): RtkIntegrationConfig {
47
51
  const source = toObject(raw);
48
52
  const outputCompactionSource = toObject(source.outputCompaction);
53
+ const readCompactionSource = toObject(outputCompactionSource.readCompaction);
49
54
  const truncateSource = toObject(outputCompactionSource.truncate);
50
55
  const smartTruncateSource = toObject(outputCompactionSource.smartTruncate);
56
+ const hasReadCompaction = hasOwnProperty(outputCompactionSource, "readCompaction");
57
+ const legacyReadCompactionFallback = !hasReadCompaction;
58
+ const sourceFilteringFallback = legacyReadCompactionFallback
59
+ ? true
60
+ : DEFAULT_RTK_INTEGRATION_CONFIG.outputCompaction.sourceCodeFilteringEnabled;
61
+ const sourceFilterLevelFallback = legacyReadCompactionFallback
62
+ ? "minimal"
63
+ : DEFAULT_RTK_INTEGRATION_CONFIG.outputCompaction.sourceCodeFiltering;
64
+ const smartTruncateEnabledFallback = legacyReadCompactionFallback
65
+ ? true
66
+ : DEFAULT_RTK_INTEGRATION_CONFIG.outputCompaction.smartTruncate.enabled;
51
67
 
52
68
  return {
53
69
  enabled: toBoolean(source.enabled, DEFAULT_RTK_INTEGRATION_CONFIG.enabled),
@@ -60,30 +76,6 @@ export function normalizeRtkIntegrationConfig(raw: unknown): RtkIntegrationConfi
60
76
  source.showRewriteNotifications,
61
77
  DEFAULT_RTK_INTEGRATION_CONFIG.showRewriteNotifications,
62
78
  ),
63
- rewriteGitGithub: toBoolean(
64
- source.rewriteGitGithub,
65
- DEFAULT_RTK_INTEGRATION_CONFIG.rewriteGitGithub,
66
- ),
67
- rewriteFilesystem: toBoolean(
68
- source.rewriteFilesystem,
69
- DEFAULT_RTK_INTEGRATION_CONFIG.rewriteFilesystem,
70
- ),
71
- rewriteRust: toBoolean(source.rewriteRust, DEFAULT_RTK_INTEGRATION_CONFIG.rewriteRust),
72
- rewriteJavaScript: toBoolean(
73
- source.rewriteJavaScript,
74
- DEFAULT_RTK_INTEGRATION_CONFIG.rewriteJavaScript,
75
- ),
76
- rewritePython: toBoolean(source.rewritePython, DEFAULT_RTK_INTEGRATION_CONFIG.rewritePython),
77
- rewriteGo: toBoolean(source.rewriteGo, DEFAULT_RTK_INTEGRATION_CONFIG.rewriteGo),
78
- rewriteContainers: toBoolean(
79
- source.rewriteContainers,
80
- DEFAULT_RTK_INTEGRATION_CONFIG.rewriteContainers,
81
- ),
82
- rewriteNetwork: toBoolean(source.rewriteNetwork, DEFAULT_RTK_INTEGRATION_CONFIG.rewriteNetwork),
83
- rewritePackageManagers: toBoolean(
84
- source.rewritePackageManagers,
85
- DEFAULT_RTK_INTEGRATION_CONFIG.rewritePackageManagers,
86
- ),
87
79
  outputCompaction: {
88
80
  enabled: toBoolean(
89
81
  outputCompactionSource.enabled,
@@ -93,9 +85,17 @@ export function normalizeRtkIntegrationConfig(raw: unknown): RtkIntegrationConfi
93
85
  outputCompactionSource.stripAnsi,
94
86
  DEFAULT_RTK_INTEGRATION_CONFIG.outputCompaction.stripAnsi,
95
87
  ),
88
+ readCompaction: {
89
+ enabled: hasReadCompaction
90
+ ? toBoolean(
91
+ readCompactionSource.enabled,
92
+ DEFAULT_RTK_INTEGRATION_CONFIG.outputCompaction.readCompaction.enabled,
93
+ )
94
+ : true,
95
+ },
96
96
  sourceCodeFilteringEnabled: toBoolean(
97
97
  outputCompactionSource.sourceCodeFilteringEnabled,
98
- DEFAULT_RTK_INTEGRATION_CONFIG.outputCompaction.sourceCodeFilteringEnabled,
98
+ sourceFilteringFallback,
99
99
  ),
100
100
  preserveExactSkillReads: toBoolean(
101
101
  outputCompactionSource.preserveExactSkillReads,
@@ -113,11 +113,14 @@ export function normalizeRtkIntegrationConfig(raw: unknown): RtkIntegrationConfi
113
113
  200_000,
114
114
  ),
115
115
  },
116
- sourceCodeFiltering: toSourceFilterLevel(outputCompactionSource.sourceCodeFiltering),
116
+ sourceCodeFiltering: toSourceFilterLevel(
117
+ outputCompactionSource.sourceCodeFiltering,
118
+ sourceFilterLevelFallback,
119
+ ),
117
120
  smartTruncate: {
118
121
  enabled: toBoolean(
119
122
  smartTruncateSource.enabled,
120
- DEFAULT_RTK_INTEGRATION_CONFIG.outputCompaction.smartTruncate.enabled,
123
+ smartTruncateEnabledFallback,
121
124
  ),
122
125
  maxLines: toInteger(
123
126
  smartTruncateSource.maxLines,
package/src/index-test.ts CHANGED
@@ -34,6 +34,7 @@ const { DEFAULT_RTK_INTEGRATION_CONFIG } = await import("./types.ts");
34
34
  function configWith(overrides: {
35
35
  enabled?: boolean;
36
36
  compactionEnabled?: boolean;
37
+ readCompactionEnabled?: boolean;
37
38
  sourceFilteringEnabled?: boolean;
38
39
  sourceFilteringLevel?: "none" | "minimal" | "aggressive";
39
40
  smartTruncateEnabled?: boolean;
@@ -46,6 +47,10 @@ function configWith(overrides: {
46
47
  outputCompaction: {
47
48
  ...base.outputCompaction,
48
49
  enabled: overrides.compactionEnabled ?? base.outputCompaction.enabled,
50
+ readCompaction: {
51
+ ...base.outputCompaction.readCompaction,
52
+ enabled: overrides.readCompactionEnabled ?? base.outputCompaction.readCompaction.enabled,
53
+ },
49
54
  sourceCodeFilteringEnabled:
50
55
  overrides.sourceFilteringEnabled ?? base.outputCompaction.sourceCodeFilteringEnabled,
51
56
  sourceCodeFiltering: overrides.sourceFilteringLevel ?? base.outputCompaction.sourceCodeFiltering,
@@ -86,13 +91,23 @@ runTest("bounded notice tracker coerces invalid limits to a safe minimum", () =>
86
91
  runTest("source-filter note injected when source filtering is active", () => {
87
92
  assert.equal(
88
93
  shouldInjectSourceFilterTroubleshootingNote(
89
- configWith({ sourceFilteringEnabled: true, sourceFilteringLevel: "minimal" }),
94
+ configWith({
95
+ readCompactionEnabled: true,
96
+ sourceFilteringEnabled: true,
97
+ sourceFilteringLevel: "minimal",
98
+ smartTruncateEnabled: true,
99
+ }),
90
100
  ),
91
101
  true,
92
102
  );
93
103
  assert.equal(
94
104
  shouldInjectSourceFilterTroubleshootingNote(
95
- configWith({ sourceFilteringEnabled: true, sourceFilteringLevel: "aggressive" }),
105
+ configWith({
106
+ readCompactionEnabled: true,
107
+ sourceFilteringEnabled: true,
108
+ sourceFilteringLevel: "aggressive",
109
+ smartTruncateEnabled: true,
110
+ }),
96
111
  ),
97
112
  true,
98
113
  );
@@ -106,6 +121,20 @@ runTest("source-filter note skipped when compaction is disabled", () => {
106
121
  assert.equal(shouldInjectSourceFilterTroubleshootingNote(configWith({ compactionEnabled: false })), false);
107
122
  });
108
123
 
124
+ runTest("source-filter note skipped when read compaction is disabled", () => {
125
+ assert.equal(
126
+ shouldInjectSourceFilterTroubleshootingNote(
127
+ configWith({
128
+ readCompactionEnabled: false,
129
+ sourceFilteringEnabled: true,
130
+ sourceFilteringLevel: "minimal",
131
+ smartTruncateEnabled: true,
132
+ }),
133
+ ),
134
+ false,
135
+ );
136
+ });
137
+
109
138
  runTest("source-filter note skipped when source filtering flag is off", () => {
110
139
  assert.equal(
111
140
  shouldInjectSourceFilterTroubleshootingNote(configWith({ sourceFilteringEnabled: false })),
package/src/index.ts CHANGED
@@ -28,13 +28,14 @@ function trimMessage(raw: string, maxLength = 220): string {
28
28
  }
29
29
 
30
30
  const SOURCE_FILTER_TROUBLESHOOTING_NOTE =
31
- "RTK note: If file edits repeatedly fail because old text does not match, ask the user to manually run '/rtk' in the Pi TUI, disable 'Read source filtering enabled', re-read the file, apply the edit, then ask the user to manually re-enable it in the Pi TUI.";
31
+ "RTK note: If file edits repeatedly fail because old text does not match, ask the user to manually run '/rtk' in the Pi TUI, disable 'Read compaction enabled', re-read the file, apply the edit, then ask the user to manually re-enable it in the Pi TUI.";
32
32
 
33
33
  export function shouldInjectSourceFilterTroubleshootingNote(config: RtkIntegrationConfig): boolean {
34
34
  const compaction = config.outputCompaction;
35
35
  return (
36
36
  config.enabled &&
37
37
  compaction.enabled &&
38
+ compaction.readCompaction.enabled &&
38
39
  compaction.sourceCodeFilteringEnabled &&
39
40
  compaction.sourceCodeFiltering !== "none" &&
40
41
  (compaction.smartTruncate.enabled || compaction.truncate.enabled)
@@ -123,7 +124,6 @@ export default function rtkIntegrationExtension(pi: ExtensionAPI): void {
123
124
  return;
124
125
  }
125
126
 
126
- console.warn(`[${EXTENSION_NAME}] ${message}`);
127
127
  if (ctx.hasUI) {
128
128
  ctx.ui.notify(message, level);
129
129
  }
@@ -222,7 +222,7 @@ export default function rtkIntegrationExtension(pi: ExtensionAPI): void {
222
222
  };
223
223
 
224
224
  const maybeWarnRtkMissing = (ctx: ExtensionContext): void => {
225
- if (!config.enabled || config.mode !== "rewrite" || !config.guardWhenRtkMissing) {
225
+ if (!config.enabled || !config.guardWhenRtkMissing) {
226
226
  return;
227
227
  }
228
228
 
@@ -237,7 +237,8 @@ export default function rtkIntegrationExtension(pi: ExtensionAPI): void {
237
237
 
238
238
  missingRtkWarningShown = true;
239
239
  const reason = runtimeStatus.lastError ? ` (${runtimeStatus.lastError})` : "";
240
- warnOnce(ctx, `${EXTENSION_NAME}: rtk binary unavailable, command rewrite bypassed${reason}.`);
240
+ const handling = config.mode === "suggest" ? "rewrite suggestions" : "command rewrite";
241
+ warnOnce(ctx, `${EXTENSION_NAME}: rtk binary unavailable, ${handling} bypassed${reason}.`);
241
242
  };
242
243
 
243
244
  const ensureRuntimeStatusFresh = async (): Promise<void> => {
@@ -361,8 +362,8 @@ export default function rtkIntegrationExtension(pi: ExtensionAPI): void {
361
362
  return {};
362
363
  }
363
364
 
364
- const decision = computeRewriteDecision(event.input.command, config);
365
- if (!decision.changed || !decision.rule) {
365
+ const decision = await computeRewriteDecision(event.input.command, config, pi);
366
+ if (!decision.changed) {
366
367
  return {};
367
368
  }
368
369
 
@@ -376,7 +377,7 @@ export default function rtkIntegrationExtension(pi: ExtensionAPI): void {
376
377
  }
377
378
 
378
379
  if (config.mode === "suggest") {
379
- const suggestionKey = `${decision.rule.id}:${decision.rewrittenCommand}`;
380
+ const suggestionKey = `${decision.originalCommand}:${decision.rewrittenCommand}`;
380
381
  if (suggestionNotices.remember(suggestionKey) && ctx.hasUI) {
381
382
  ctx.ui.notify(`RTK suggestion: ${decision.rewrittenCommand}`, "info");
382
383
  }
@@ -24,6 +24,10 @@ function buildReadContent(lineCount: number): string {
24
24
  return `${lines.join("\n")}\n`;
25
25
  }
26
26
 
27
+ function setReadCompaction(config: ReturnType<typeof cloneDefaultConfig>, enabled: boolean): void {
28
+ config.outputCompaction.readCompaction = { enabled };
29
+ }
30
+
27
31
  function firstTextBlock(content: unknown[] | undefined): string {
28
32
  if (!Array.isArray(content) || content.length === 0) {
29
33
  return "";
@@ -73,6 +77,7 @@ function assertNoOutputEmoji(text: string): void {
73
77
 
74
78
  runTest("precision read with offset keeps exact output (no source/smart/hard truncation)", () => {
75
79
  const config = cloneDefaultConfig();
80
+ setReadCompaction(config, true);
76
81
  config.outputCompaction.truncate.enabled = true;
77
82
  config.outputCompaction.truncate.maxChars = 500;
78
83
  config.outputCompaction.smartTruncate.enabled = true;
@@ -94,6 +99,7 @@ runTest("precision read with offset keeps exact output (no source/smart/hard tru
94
99
 
95
100
  runTest("precision read with limit keeps exact output", () => {
96
101
  const config = cloneDefaultConfig();
102
+ setReadCompaction(config, true);
97
103
  config.outputCompaction.truncate.enabled = true;
98
104
  config.outputCompaction.truncate.maxChars = 500;
99
105
  config.outputCompaction.smartTruncate.enabled = true;
@@ -113,8 +119,34 @@ runTest("precision read with limit keeps exact output", () => {
113
119
  assert.deepEqual(result.techniques, []);
114
120
  });
115
121
 
116
- runTest("normal read compacts and adds banner", () => {
122
+ runTest("default read output stays exact when read compaction is disabled by default", () => {
123
+ const config = cloneDefaultConfig();
124
+ config.outputCompaction.sourceCodeFilteringEnabled = true;
125
+ config.outputCompaction.sourceCodeFiltering = "aggressive";
126
+ config.outputCompaction.smartTruncate.enabled = true;
127
+ config.outputCompaction.smartTruncate.maxLines = 40;
128
+ config.outputCompaction.truncate.enabled = true;
129
+ config.outputCompaction.truncate.maxChars = 500;
130
+
131
+ const content = buildReadContent(220);
132
+ const result = compactToolResult(
133
+ {
134
+ toolName: "read",
135
+ input: { path: "sample.ts" },
136
+ content: [{ type: "text", text: content }],
137
+ },
138
+ config,
139
+ );
140
+
141
+ assert.equal(result.changed, false);
142
+ assert.deepEqual(result.techniques, []);
143
+ });
144
+
145
+ runTest("normal read compacts and adds banner when read compaction is enabled", () => {
117
146
  const config = cloneDefaultConfig();
147
+ setReadCompaction(config, true);
148
+ config.outputCompaction.sourceCodeFilteringEnabled = true;
149
+ config.outputCompaction.sourceCodeFiltering = "minimal";
118
150
  config.outputCompaction.smartTruncate.enabled = true;
119
151
  config.outputCompaction.smartTruncate.maxLines = 40;
120
152
 
@@ -138,6 +170,7 @@ runTest("normal read compacts and adds banner", () => {
138
170
 
139
171
  runTest("short read output stays exact below threshold", () => {
140
172
  const config = cloneDefaultConfig();
173
+ setReadCompaction(config, true);
141
174
  const content = buildReadContent(40);
142
175
 
143
176
  const result = compactToolResult(
@@ -155,6 +188,7 @@ runTest("short read output stays exact below threshold", () => {
155
188
 
156
189
  runTest("read output stays exact at the 80-line boundary with trailing newline", () => {
157
190
  const config = cloneDefaultConfig();
191
+ setReadCompaction(config, true);
158
192
  config.outputCompaction.smartTruncate.enabled = true;
159
193
  config.outputCompaction.smartTruncate.maxLines = 40;
160
194
 
@@ -172,8 +206,11 @@ runTest("read output stays exact at the 80-line boundary with trailing newline",
172
206
  assert.deepEqual(result.techniques, []);
173
207
  });
174
208
 
175
- runTest("read output compacts once the content exceeds the 80-line exactness threshold", () => {
209
+ runTest("read output compacts once the content exceeds the 80-line exactness threshold when read compaction is enabled", () => {
176
210
  const config = cloneDefaultConfig();
211
+ setReadCompaction(config, true);
212
+ config.outputCompaction.sourceCodeFilteringEnabled = true;
213
+ config.outputCompaction.sourceCodeFiltering = "minimal";
177
214
  config.outputCompaction.smartTruncate.enabled = true;
178
215
  config.outputCompaction.smartTruncate.maxLines = 40;
179
216
 
@@ -193,6 +230,9 @@ runTest("read output compacts once the content exceeds the 80-line exactness thr
193
230
 
194
231
  runTest("source file reads skip lossy source filtering when truncation safeguards are not needed", () => {
195
232
  const config = cloneDefaultConfig();
233
+ setReadCompaction(config, true);
234
+ config.outputCompaction.sourceCodeFilteringEnabled = true;
235
+ config.outputCompaction.sourceCodeFiltering = "minimal";
196
236
  config.outputCompaction.smartTruncate.enabled = false;
197
237
  config.outputCompaction.truncate.enabled = false;
198
238
 
@@ -213,6 +253,7 @@ runTest("source file reads skip lossy source filtering when truncation safeguard
213
253
 
214
254
  runTest("skill reads stay exact when preserveExactSkillReads is enabled for user skills", () => {
215
255
  const config = cloneDefaultConfig();
256
+ setReadCompaction(config, true);
216
257
  config.outputCompaction.preserveExactSkillReads = true;
217
258
  config.outputCompaction.truncate.enabled = true;
218
259
  config.outputCompaction.truncate.maxChars = 500;
@@ -235,6 +276,7 @@ runTest("skill reads stay exact when preserveExactSkillReads is enabled for user
235
276
 
236
277
  runTest("project .pi skill reads stay exact when preserveExactSkillReads is enabled", () => {
237
278
  const config = cloneDefaultConfig();
279
+ setReadCompaction(config, true);
238
280
  config.outputCompaction.preserveExactSkillReads = true;
239
281
  config.outputCompaction.truncate.enabled = true;
240
282
  config.outputCompaction.truncate.maxChars = 500;
@@ -257,6 +299,7 @@ runTest("project .pi skill reads stay exact when preserveExactSkillReads is enab
257
299
 
258
300
  runTest("ancestor .agents skill reads stay exact when preserveExactSkillReads is enabled", () => {
259
301
  const config = cloneDefaultConfig();
302
+ setReadCompaction(config, true);
260
303
  config.outputCompaction.preserveExactSkillReads = true;
261
304
  config.outputCompaction.truncate.enabled = true;
262
305
  config.outputCompaction.truncate.maxChars = 500;
@@ -140,6 +140,10 @@ function shouldPreserveExactReadOutput(
140
140
  input: Record<string, unknown>,
141
141
  config: RtkIntegrationConfig,
142
142
  ): boolean {
143
+ if (!config.outputCompaction.readCompaction.enabled) {
144
+ return true;
145
+ }
146
+
143
147
  if (hasExplicitReadRange(input)) {
144
148
  return true;
145
149
  }
@@ -10,6 +10,26 @@ interface ProducerRewritePlan {
10
10
  captureStderr: boolean;
11
11
  }
12
12
 
13
+ interface ShellSafetyTarget {
14
+ environmentPrelude: string;
15
+ command: string;
16
+ }
17
+
18
+ const LEADING_RTK_DB_PATH_EXPORT_PRELUDE_PATTERN =
19
+ /^(\s*export\s+RTK_DB_PATH=(?:"(?:\\.|[^"])*"|'[^']*'|[^\s;]+)\s*;\s*)([\s\S]*)$/u;
20
+
21
+ function splitLeadingRtkDbPathExportPrelude(command: string): ShellSafetyTarget {
22
+ const match = command.match(LEADING_RTK_DB_PATH_EXPORT_PRELUDE_PATTERN);
23
+ if (!match) {
24
+ return { environmentPrelude: "", command };
25
+ }
26
+
27
+ return {
28
+ environmentPrelude: match[1] ?? "",
29
+ command: match[2] ?? "",
30
+ };
31
+ }
32
+
13
33
  function isTopLevelQuoteCharacter(character: string): character is '"' | "'" | "`" {
14
34
  return character === '"' || character === "'" || character === "`";
15
35
  }
@@ -134,7 +154,8 @@ export function applyRewrittenCommandShellSafetyFixups(command: string): string
134
154
  return command;
135
155
  }
136
156
 
137
- const parsedPipeline = parseSimpleTopLevelPipeline(command);
157
+ const target = splitLeadingRtkDbPathExportPrelude(command);
158
+ const parsedPipeline = parseSimpleTopLevelPipeline(target.command);
138
159
  if (!parsedPipeline) {
139
160
  return command;
140
161
  }
@@ -153,5 +174,5 @@ export function applyRewrittenCommandShellSafetyFixups(command: string): string
153
174
  return command;
154
175
  }
155
176
 
156
- return buildBufferedPipelineCommand(producer, remainder);
177
+ return `${target.environmentPrelude}${buildBufferedPipelineCommand(producer, remainder)}`;
157
178
  }