pi-repoprompt-mcp 0.2.5 → 0.2.7

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/README.md CHANGED
@@ -4,7 +4,7 @@ A token-efficient RepoPrompt MCP integration for Pi.
4
4
 
5
5
  Exposes a single `rp` tool (RepoPrompt MCP proxy) plus `/rp …` commands, with:
6
6
  - window/tab binding (auto-detect by `cwd`, optional persistence)
7
- - output rendering (syntax + diff highlighting)
7
+ - output rendering (syntax + diff highlighting; uses `delta` when installed, honoring the user's global git/delta color config, with graceful fallback)
8
8
  - safety guardrails (deletes blocked unless explicitly allowed; optional edit confirmation)
9
9
  - optional [Gurpartap/pi-readcache](https://github.com/Gurpartap/pi-readcache)-like caching for RepoPrompt `read_file` results (unchanged markers + diffs) to save on tokens
10
10
 
@@ -3,7 +3,7 @@
3
3
  // First-class RepoPrompt integration with:
4
4
  // - Auto-detection of matching windows based on cwd
5
5
  // - Syntax highlighting for code blocks
6
- // - Word-level diff highlighting
6
+ // - Delta-powered diff highlighting (with graceful fallback)
7
7
  // - Safety guards for destructive operations
8
8
  // - Persistent window binding across sessions
9
9
 
@@ -1,5 +1,7 @@
1
1
  // render.ts - Syntax highlighting and diff rendering for RepoPrompt output
2
2
 
3
+ import { spawnSync } from "node:child_process";
4
+
3
5
  import { highlightCode, type Theme } from "@mariozechner/pi-coding-agent";
4
6
  import * as Diff from "diff";
5
7
 
@@ -73,6 +75,92 @@ export function parseFencedBlocks(text: string): FencedBlock[] {
73
75
  return blocks;
74
76
  }
75
77
 
78
+ // ─────────────────────────────────────────────────────────────────────────────
79
+ // Delta Diff Rendering
80
+ // ─────────────────────────────────────────────────────────────────────────────
81
+
82
+ const ANSI_ESCAPE_RE = /\x1b\[[0-9;]*m/g;
83
+ const DELTA_TIMEOUT_MS = 5000;
84
+ const DELTA_MAX_BUFFER = 8 * 1024 * 1024;
85
+ const DELTA_CACHE_MAX_ENTRIES = 200;
86
+
87
+ let deltaAvailable: boolean | null = null;
88
+ const deltaDiffCache = new Map<string, string | null>();
89
+
90
+ function isDeltaInstalled(): boolean {
91
+ if (deltaAvailable !== null) {
92
+ return deltaAvailable;
93
+ }
94
+
95
+ const check = spawnSync("delta", ["--version"], {
96
+ stdio: "ignore",
97
+ timeout: 1000,
98
+ });
99
+
100
+ deltaAvailable = !check.error && check.status === 0;
101
+ return deltaAvailable;
102
+ }
103
+
104
+ function runDelta(diffText: string): string | null {
105
+ const result = spawnSync("delta", ["--color-only", "--paging=never"], {
106
+ encoding: "utf-8",
107
+ input: diffText,
108
+ timeout: DELTA_TIMEOUT_MS,
109
+ maxBuffer: DELTA_MAX_BUFFER,
110
+ });
111
+
112
+ if (result.error || result.status !== 0) {
113
+ return null;
114
+ }
115
+
116
+ return typeof result.stdout === "string" ? result.stdout : null;
117
+ }
118
+
119
+ function stripSyntheticHeader(deltaOutput: string): string {
120
+ const outputLines = deltaOutput.split("\n");
121
+ const bodyStart = outputLines.findIndex((line) => line.replace(ANSI_ESCAPE_RE, "").startsWith("@@"));
122
+
123
+ if (bodyStart >= 0) {
124
+ return outputLines.slice(bodyStart + 1).join("\n");
125
+ }
126
+
127
+ return deltaOutput;
128
+ }
129
+
130
+ function renderDiffBlockWithDelta(code: string): string | null {
131
+ if (!isDeltaInstalled()) {
132
+ return null;
133
+ }
134
+
135
+ const cached = deltaDiffCache.get(code);
136
+ if (cached !== undefined) {
137
+ return cached;
138
+ }
139
+
140
+ let rendered = runDelta(code);
141
+
142
+ if (!rendered) {
143
+ const syntheticDiff = [
144
+ "--- a/file",
145
+ "+++ b/file",
146
+ "@@ -1,1 +1,1 @@",
147
+ code,
148
+ ].join("\n");
149
+
150
+ const syntheticRendered = runDelta(syntheticDiff);
151
+ if (syntheticRendered) {
152
+ rendered = stripSyntheticHeader(syntheticRendered);
153
+ }
154
+ }
155
+
156
+ if (deltaDiffCache.size >= DELTA_CACHE_MAX_ENTRIES) {
157
+ deltaDiffCache.clear();
158
+ }
159
+
160
+ deltaDiffCache.set(code, rendered);
161
+ return rendered;
162
+ }
163
+
76
164
  // ─────────────────────────────────────────────────────────────────────────────
77
165
  // Word-Level Diff Highlighting
78
166
  // ─────────────────────────────────────────────────────────────────────────────
@@ -128,6 +216,11 @@ function renderIntraLineDiff(
128
216
  * Render diff lines with syntax highlighting (red/green, word-level inverse)
129
217
  */
130
218
  export function renderDiffBlock(code: string, theme: Theme): string {
219
+ const deltaRendered = renderDiffBlockWithDelta(code);
220
+ if (deltaRendered !== null) {
221
+ return deltaRendered;
222
+ }
223
+
131
224
  const lines = code.split("\n");
132
225
  const result: string[] = [];
133
226
 
@@ -378,7 +471,7 @@ export interface RenderOptions {
378
471
 
379
472
  /**
380
473
  * Render RepoPrompt output with syntax highlighting for fenced code blocks.
381
- * - ```diff blocks get word-level diff highlighting
474
+ * - ```diff blocks use delta when available, with word-level fallback
382
475
  * - Other fenced blocks get syntax highlighting via Pi's highlightCode
383
476
  * - Non-fenced content is rendered with markdown-aware styling
384
477
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-repoprompt-mcp",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
4
4
  "description": "A token-efficient RepoPrompt MCP integration for Pi",
5
5
  "keywords": ["pi-package", "pi", "pi-coding-agent", "repoprompt", "mcp"],
6
6
  "license": "MIT",