pi-rtk-optimizer 0.5.3 → 0.5.5

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,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.5.5] - 2026-04-24
11
+
12
+ ### Changed
13
+ - Config path resolution now uses Pi's `getAgentDir()` API so `PI_CODING_AGENT_DIR` is respected for extension config paths (thanks to @tynanbe for PR #3).
14
+ - Global skill-read preservation paths now resolve through Pi's agent directory so `PI_CODING_AGENT_DIR` is respected (thanks to @tynanbe for PR #3).
15
+ - Source-filter troubleshooting note injection now only runs when output compaction, source filtering, and read truncation safeguards are active (thanks to @philipbjorge for PR #4).
16
+ - Updated `@mariozechner/pi-coding-agent` and `@mariozechner/pi-tui` peer dependencies to ^0.70.0.
17
+ - Clarified README and settings modal copy for global extension/config paths, skill directory paths, source-filter note behavior, architecture, and event hooks.
18
+
19
+ ### Removed
20
+ - Removed the unused local asset directory.
21
+ - Removed the `session_switch` event refresh handler.
22
+
10
23
  ## [0.5.3] - 2026-04-01
11
24
 
12
25
  ### Changed
package/README.md CHANGED
@@ -59,9 +59,10 @@ Multi-stage pipeline to reduce token consumption:
59
59
 
60
60
  Place this folder in one of the following locations:
61
61
 
62
- ```
63
- ~/.pi/agent/extensions/pi-rtk-optimizer # Global (all projects)
64
- .pi/extensions/pi-rtk-optimizer # Project-specific
62
+ ```text
63
+ ~/.pi/agent/extensions/pi-rtk-optimizer # Global default (when PI_CODING_AGENT_DIR is unset)
64
+ $PI_CODING_AGENT_DIR/extensions/pi-rtk-optimizer # Global when PI_CODING_AGENT_DIR is set
65
+ .pi/extensions/pi-rtk-optimizer # Project-specific
65
66
  ```
66
67
 
67
68
  Pi auto-discovers extensions in these paths on startup.
@@ -107,8 +108,9 @@ Use arrow keys to navigate settings, Enter to cycle values, and Escape to close.
107
108
 
108
109
  Configuration is stored at:
109
110
 
110
- ```
111
- ~/.pi/agent/extensions/pi-rtk-optimizer/config.json
111
+ ```text
112
+ Default global path: ~/.pi/agent/extensions/pi-rtk-optimizer/config.json
113
+ Actual global path: $PI_CODING_AGENT_DIR/extensions/pi-rtk-optimizer/config.json when PI_CODING_AGENT_DIR is set
112
114
  ```
113
115
 
114
116
  A starter template is included at `config/config.example.json`.
@@ -145,7 +147,7 @@ A starter template is included at `config/config.example.json`.
145
147
  | `outputCompaction.enabled` | boolean | `true` | Enable output compaction pipeline |
146
148
  | `outputCompaction.stripAnsi` | boolean | `true` | Remove ANSI escape codes |
147
149
  | `outputCompaction.sourceCodeFilteringEnabled` | boolean | `true` | Enable source code filtering for `read` output |
148
- | `outputCompaction.preserveExactSkillReads` | boolean | `false` | Keep reads under Pi skill directories exact, bypassing read compaction |
150
+ | `outputCompaction.preserveExactSkillReads` | boolean | `false` | Keep reads under configured Pi/global/project skill directories exact, bypassing read compaction |
149
151
  | `outputCompaction.sourceCodeFiltering` | string | `"minimal"` | Filter level: `"none"`, `"minimal"`, `"aggressive"` |
150
152
  | `outputCompaction.aggregateTestOutput` | boolean | `true` | Summarize test runner output |
151
153
  | `outputCompaction.filterBuildOutput` | boolean | `true` | Filter build/compile output |
@@ -154,6 +156,8 @@ A starter template is included at `config/config.example.json`.
154
156
  | `outputCompaction.groupSearchOutput` | boolean | `true` | Group search results by file |
155
157
  | `outputCompaction.trackSavings` | boolean | `true` | Track compaction metrics |
156
158
 
159
+ Skill-read preservation covers the global Pi skills directory (`~/.pi/agent/skills` by default, or `$PI_CODING_AGENT_DIR/skills` when set), `~/.agents/skills`, project `.pi/skills`, and ancestor `.agents/skills` directories.
160
+
157
161
  #### Truncation Settings
158
162
 
159
163
  | Option | Type | Default | Range | Description |
@@ -171,7 +175,7 @@ A starter template is included at `config/config.example.json`.
171
175
  | `minimal` | Removes non-doc comments, collapses blank lines |
172
176
  | `aggressive` | Also removes imports, keeps only signatures and key logic |
173
177
 
174
- > **Note:** If file edits fail because "old text does not match," disable source filtering via `/rtk`, re-read the file, apply the edit, then re-enable filtering.
178
+ > **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.
175
179
 
176
180
  ### Example Configuration
177
181
 
@@ -224,15 +228,19 @@ src/
224
228
  ├── index.ts # Extension bootstrap and event wiring
225
229
  ├── config-store.ts # Config load/save with normalization
226
230
  ├── config-modal.ts # TUI settings modal and /rtk handler
227
- ├── command-rewriter.ts # Command tokenization and rewrite logic
228
- ├── rewrite-bypass.ts # Rewrite safety bypass rules for interactive/structured commands
229
- ├── rewrite-rules.ts # Rewrite rule catalog
230
- ├── runtime-guard.ts # Runtime availability guard helpers for rewrite mode
231
- ├── output-compactor.ts # Tool result compaction pipeline
232
- ├── output-metrics.ts # Savings tracking and reporting
233
- ├── command-completions.ts # /rtk subcommand completions
231
+ ├── command-rewriter.ts # Command tokenization and rewrite logic
232
+ ├── rewrite-bypass.ts # Rewrite safety bypass rules for interactive/structured commands
233
+ ├── rewrite-rules.ts # Rewrite rule catalog
234
+ ├── rewrite-pipeline-safety.ts # Shell-safety fixups for rewritten commands
235
+ ├── rtk-command-environment.ts # RTK_DB_PATH scoping for rewritten commands
236
+ ├── shell-env-prefix.ts # Environment assignment parsing helpers
237
+ ├── runtime-guard.ts # Runtime availability guard helpers for rewrite mode
238
+ ├── output-compactor.ts # Tool result compaction pipeline
239
+ ├── output-metrics.ts # Savings tracking and reporting
240
+ ├── tool-execution-sanitizer.ts # Streaming bash execution output sanitizer
241
+ ├── command-completions.ts # /rtk subcommand completions
234
242
  ├── windows-command-helpers.ts # Windows bash compatibility
235
- └── techniques/ # Compaction technique implementations
243
+ └── techniques/ # Compaction technique implementations
236
244
  ├── ansi.ts # ANSI code stripping
237
245
  ├── build.ts # Build output filtering
238
246
  ├── test-output.ts # Test output aggregation
@@ -247,9 +255,12 @@ src/
247
255
 
248
256
  The extension hooks into Pi's event system:
249
257
 
250
- - **`beforeToolCall`** — Rewrites bash commands to rtk equivalents
251
- - **`afterToolResult`** — Compacts tool output before context consumption
252
- - **`command`** Handles `/rtk` command and subcommands
258
+ - **`tool_call`** — Rewrites bash commands to rtk equivalents or emits suggestions
259
+ - **`tool_result`** — Compacts completed tool output before context consumption
260
+ - **`tool_execution_start` / `tool_execution_update` / `tool_execution_end`** Tracks and sanitizes streamed bash output
261
+ - **`before_agent_start`** — Conditionally injects source-filter troubleshooting guidance
262
+ - **`session_start` / `agent_end`** — Refreshes config and clears in-session tracking state
263
+ - **Registered `/rtk` command** — Handles settings, status, verification, stats, and reset subcommands
253
264
 
254
265
  ### Windows Compatibility
255
266
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-rtk-optimizer",
3
- "version": "0.5.3",
3
+ "version": "0.5.5",
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.64.0",
62
- "@mariozechner/pi-tui": "^0.64.0"
61
+ "@mariozechner/pi-coding-agent": "^0.70.0",
62
+ "@mariozechner/pi-tui": "^0.70.0"
63
63
  }
64
64
  }
@@ -1,13 +1,7 @@
1
1
  import assert from "node:assert/strict";
2
2
  import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
3
+ import { mock } from "bun:test";
3
4
 
4
- import {
5
- ensureConfigExists,
6
- getRtkIntegrationConfigPath,
7
- loadRtkIntegrationConfig,
8
- normalizeRtkIntegrationConfig,
9
- saveRtkIntegrationConfig,
10
- } from "./config-store.ts";
11
5
  import { clearOutputMetrics, getOutputMetricsSummary, trackOutputSavings } from "./output-metrics.ts";
12
6
  import { runTest } from "./test-helpers.ts";
13
7
  import { matchesCommandPatterns, normalizeCommandForDetection } from "./techniques/command-detection.ts";
@@ -19,6 +13,18 @@ import { sanitizeStreamingBashExecutionResult } from "./tool-execution-sanitizer
19
13
  import { sanitizeRtkEmojiOutput } from "./techniques/emoji.ts";
20
14
  import { stripRtkHookWarnings } from "./techniques/rtk.ts";
21
15
 
16
+ mock.module("@mariozechner/pi-coding-agent", () => ({
17
+ getAgentDir: () => "/tmp/.pi/agent",
18
+ }));
19
+
20
+ const {
21
+ ensureConfigExists,
22
+ getRtkIntegrationConfigPath,
23
+ loadRtkIntegrationConfig,
24
+ normalizeRtkIntegrationConfig,
25
+ saveRtkIntegrationConfig,
26
+ } = await import("./config-store.ts");
27
+
22
28
  function makeTempConfigPath(): string {
23
29
  return `${getRtkIntegrationConfigPath()}.test-${Date.now()}-${Math.random().toString(16).slice(2)}.json`;
24
30
  }
@@ -4,6 +4,7 @@ import { mock } from "bun:test";
4
4
  import { cloneDefaultConfig, runTest } from "./test-helpers.ts";
5
5
 
6
6
  mock.module("@mariozechner/pi-coding-agent", () => ({
7
+ getAgentDir: () => "/tmp/.pi/agent",
7
8
  getSettingsListTheme: () => ({}),
8
9
  }));
9
10
 
@@ -143,7 +143,7 @@ function buildSettingItems(config: RtkIntegrationConfig): SettingItem[] {
143
143
  {
144
144
  id: "outputPreserveExactSkillReads",
145
145
  label: "Preserve exact skill reads",
146
- description: "If on, read results under ~/.pi/agent/skills, ~/.agents/skills, .pi/skills, and ancestor .agents/skills skip read compaction",
146
+ description: "If on, read results under the global Pi skills directory (default: ~/.pi/agent/skills, respects PI_CODING_AGENT_DIR), ~/.agents/skills, .pi/skills, and ancestor .agents/skills skip read compaction",
147
147
  currentValue: toOnOff(config.outputCompaction.preserveExactSkillReads),
148
148
  values: ON_OFF,
149
149
  },
package/src/constants.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { homedir } from "node:os";
2
- import { join } from "node:path";
3
-
4
- export const EXTENSION_NAME = "pi-rtk-optimizer";
5
- export const CONFIG_DIR = join(homedir(), ".pi", "agent", "extensions", EXTENSION_NAME);
6
- export const CONFIG_PATH = join(CONFIG_DIR, "config.json");
1
+ import { getAgentDir } from "@mariozechner/pi-coding-agent";
2
+ import { join } from "node:path";
3
+
4
+ export const EXTENSION_NAME = "pi-rtk-optimizer";
5
+ export const CONFIG_DIR = join(getAgentDir(), "extensions", EXTENSION_NAME);
6
+ export const CONFIG_PATH = join(CONFIG_DIR, "config.json");
package/src/index-test.ts CHANGED
@@ -4,6 +4,7 @@ import { mock } from "bun:test";
4
4
  import { runTest } from "./test-helpers.ts";
5
5
 
6
6
  mock.module("@mariozechner/pi-coding-agent", () => ({
7
+ getAgentDir: () => "/tmp/.pi/agent",
7
8
  getSettingsListTheme: () => ({}),
8
9
  isToolCallEventType: (toolName: string, event: Record<string, unknown>) => event.toolName === toolName,
9
10
  }));
@@ -27,7 +28,38 @@ mock.module("@mariozechner/pi-tui", () => ({
27
28
  visibleWidth: (text: string) => text.length,
28
29
  }));
29
30
 
30
- const { createBoundedNoticeTracker } = await import("./index.ts");
31
+ const { createBoundedNoticeTracker, shouldInjectSourceFilterTroubleshootingNote } = await import("./index.ts");
32
+ const { DEFAULT_RTK_INTEGRATION_CONFIG } = await import("./types.ts");
33
+
34
+ function configWith(overrides: {
35
+ enabled?: boolean;
36
+ compactionEnabled?: boolean;
37
+ sourceFilteringEnabled?: boolean;
38
+ sourceFilteringLevel?: "none" | "minimal" | "aggressive";
39
+ smartTruncateEnabled?: boolean;
40
+ truncateEnabled?: boolean;
41
+ }): typeof DEFAULT_RTK_INTEGRATION_CONFIG {
42
+ const base = DEFAULT_RTK_INTEGRATION_CONFIG;
43
+ return {
44
+ ...base,
45
+ enabled: overrides.enabled ?? base.enabled,
46
+ outputCompaction: {
47
+ ...base.outputCompaction,
48
+ enabled: overrides.compactionEnabled ?? base.outputCompaction.enabled,
49
+ sourceCodeFilteringEnabled:
50
+ overrides.sourceFilteringEnabled ?? base.outputCompaction.sourceCodeFilteringEnabled,
51
+ sourceCodeFiltering: overrides.sourceFilteringLevel ?? base.outputCompaction.sourceCodeFiltering,
52
+ smartTruncate: {
53
+ ...base.outputCompaction.smartTruncate,
54
+ enabled: overrides.smartTruncateEnabled ?? base.outputCompaction.smartTruncate.enabled,
55
+ },
56
+ truncate: {
57
+ ...base.outputCompaction.truncate,
58
+ enabled: overrides.truncateEnabled ?? base.outputCompaction.truncate.enabled,
59
+ },
60
+ },
61
+ };
62
+ }
31
63
 
32
64
  runTest("bounded notice tracker evicts old entries and supports reset", () => {
33
65
  const tracker = createBoundedNoticeTracker(2);
@@ -51,4 +83,52 @@ runTest("bounded notice tracker coerces invalid limits to a safe minimum", () =>
51
83
  assert.equal(tracker.remember("alpha"), true);
52
84
  });
53
85
 
86
+ runTest("source-filter note injected when source filtering is active", () => {
87
+ assert.equal(
88
+ shouldInjectSourceFilterTroubleshootingNote(
89
+ configWith({ sourceFilteringEnabled: true, sourceFilteringLevel: "minimal" }),
90
+ ),
91
+ true,
92
+ );
93
+ assert.equal(
94
+ shouldInjectSourceFilterTroubleshootingNote(
95
+ configWith({ sourceFilteringEnabled: true, sourceFilteringLevel: "aggressive" }),
96
+ ),
97
+ true,
98
+ );
99
+ });
100
+
101
+ runTest("source-filter note skipped when extension is disabled", () => {
102
+ assert.equal(shouldInjectSourceFilterTroubleshootingNote(configWith({ enabled: false })), false);
103
+ });
104
+
105
+ runTest("source-filter note skipped when compaction is disabled", () => {
106
+ assert.equal(shouldInjectSourceFilterTroubleshootingNote(configWith({ compactionEnabled: false })), false);
107
+ });
108
+
109
+ runTest("source-filter note skipped when source filtering flag is off", () => {
110
+ assert.equal(
111
+ shouldInjectSourceFilterTroubleshootingNote(configWith({ sourceFilteringEnabled: false })),
112
+ false,
113
+ );
114
+ });
115
+
116
+ runTest("source-filter note skipped when filtering level is 'none'", () => {
117
+ assert.equal(
118
+ shouldInjectSourceFilterTroubleshootingNote(
119
+ configWith({ sourceFilteringEnabled: true, sourceFilteringLevel: "none" }),
120
+ ),
121
+ false,
122
+ );
123
+ });
124
+
125
+ runTest("source-filter note skipped when all read filtering safeguards are disabled", () => {
126
+ assert.equal(
127
+ shouldInjectSourceFilterTroubleshootingNote(
128
+ configWith({ smartTruncateEnabled: false, truncateEnabled: false }),
129
+ ),
130
+ false,
131
+ );
132
+ });
133
+
54
134
  console.log("All index tests passed.");
package/src/index.ts CHANGED
@@ -30,6 +30,17 @@ function trimMessage(raw: string, maxLength = 220): string {
30
30
  const SOURCE_FILTER_TROUBLESHOOTING_NOTE =
31
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.";
32
32
 
33
+ export function shouldInjectSourceFilterTroubleshootingNote(config: RtkIntegrationConfig): boolean {
34
+ const compaction = config.outputCompaction;
35
+ return (
36
+ config.enabled &&
37
+ compaction.enabled &&
38
+ compaction.sourceCodeFilteringEnabled &&
39
+ compaction.sourceCodeFiltering !== "none" &&
40
+ (compaction.smartTruncate.enabled || compaction.truncate.enabled)
41
+ );
42
+ }
43
+
33
44
  function mergeCompactionDetails(
34
45
  existingDetails: unknown,
35
46
  compaction: ToolResultCompactionMetadata,
@@ -262,14 +273,6 @@ export default function rtkIntegrationExtension(pi: ExtensionAPI): void {
262
273
  maybeWarnRtkMissing(ctx);
263
274
  });
264
275
 
265
- pi.on("session_switch", async (_event, ctx) => {
266
- warnedMessages.reset();
267
- suggestionNotices.reset();
268
- clearTrackedBashCommands();
269
- missingRtkWarningShown = false;
270
- await refreshConfig(ctx);
271
- maybeWarnRtkMissing(ctx);
272
- });
273
276
 
274
277
  pi.on("agent_end", async () => {
275
278
  clearTrackedBashCommands();
@@ -324,6 +327,10 @@ export default function rtkIntegrationExtension(pi: ExtensionAPI): void {
324
327
  await ensureRuntimeStatusFresh();
325
328
  maybeWarnRtkMissing(ctx);
326
329
 
330
+ if (!shouldInjectSourceFilterTroubleshootingNote(config)) {
331
+ return {};
332
+ }
333
+
327
334
  if (event.systemPrompt.includes(SOURCE_FILTER_TROUBLESHOOTING_NOTE)) {
328
335
  return {};
329
336
  }
@@ -1,10 +1,17 @@
1
1
  import assert from "node:assert/strict";
2
- import { homedir } from "node:os";
3
2
  import { join } from "node:path";
3
+ import { mock } from "bun:test";
4
4
 
5
- import { compactToolResult } from "./output-compactor.ts";
6
5
  import { cloneDefaultConfig, runTest } from "./test-helpers.ts";
7
6
 
7
+ const TEST_AGENT_DIR = "/tmp/.pi/agent";
8
+
9
+ mock.module("@mariozechner/pi-coding-agent", () => ({
10
+ getAgentDir: () => TEST_AGENT_DIR,
11
+ }));
12
+
13
+ const { compactToolResult } = await import("./output-compactor.ts");
14
+
8
15
  function buildReadContent(lineCount: number): string {
9
16
  const lines: string[] = [];
10
17
  for (let index = 0; index < lineCount; index += 1) {
@@ -216,7 +223,7 @@ runTest("skill reads stay exact when preserveExactSkillReads is enabled for user
216
223
  const result = compactToolResult(
217
224
  {
218
225
  toolName: "read",
219
- input: { path: join(homedir(), ".pi", "agent", "skills", "example", "SKILL.md") },
226
+ input: { path: join(TEST_AGENT_DIR, "skills", "example", "SKILL.md") },
220
227
  content: [{ type: "text", text: content }],
221
228
  },
222
229
  config,
@@ -1,3 +1,4 @@
1
+ import { getAgentDir } from "@mariozechner/pi-coding-agent";
1
2
  import { homedir } from "node:os";
2
3
  import { dirname, join, resolve, sep } from "node:path";
3
4
  import {
@@ -60,7 +61,7 @@ const LOSSY_TECHNIQUE_PREFIXES = [
60
61
 
61
62
  const READ_EXACT_OUTPUT_LINE_THRESHOLD = 80;
62
63
  const READ_COMPACTION_BANNER_PREFIX = "[RTK compacted output:";
63
- const USER_SKILL_ROOTS = [join(homedir(), ".pi", "agent", "skills"), join(homedir(), ".agents", "skills")];
64
+ const USER_SKILL_ROOTS = [join(getAgentDir(), "skills"), join(homedir(), ".agents", "skills")];
64
65
 
65
66
  function normalizePathForComparison(path: string): string {
66
67
  return process.platform === "win32" ? path.toLowerCase() : path;
@@ -89,6 +89,7 @@ declare module "@mariozechner/pi-coding-agent" {
89
89
  getFgAnsi?(name: string): string;
90
90
  }
91
91
 
92
+ export function getAgentDir(): string;
92
93
  export function getSettingsListTheme(): unknown;
93
94
 
94
95
  export interface ExtensionAPI {