pi-context-map 0.7.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
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.7.1] - 2026-06-15
4
+ ### Bug Fixes
5
+ - **Fixed insights dropdown**: Changed from direct `addEventListener` to event delegation (`document.addEventListener('click', ...)` with `e.target.closest('.insight-header')`). The dropdown now works after SSE body replacement.
6
+ - **Fixed auto-open browser**: Added `cmd /c start` with `explorer` fallback for Windows. Browser now opens reliably on first `/context-map` invocation.
7
+ - **Removed unused variable**: Cleaned up `reportPath` destructuring warning in command handler.
8
+
3
9
  ## [0.7.0] - 2026-06-15
4
10
  ### Bug Fixes
5
11
  - **Fixed token accuracy**: Now uses Pi's actual token count from `ctx.getContextUsage()` instead of heuristic estimation. The usage percentage now matches Pi's terminal display.
package/README.md CHANGED
@@ -8,11 +8,15 @@ A visual context window mapping extension for [Pi](https://pi.dev/) that transfo
8
8
 
9
9
  ## Features
10
10
 
11
- - **Visual Context Budget**: Real-time breakdown of tokens used by System, History, Files, and Tool Results.
12
- - **Working Set Analysis**: Categorizes files as `Active`, `Stale`, or `Legacy` based on access recency.
11
+ - **Visual Context Budget**: Real-time breakdown of tokens used by System, Tools, History, Files, and Summaries.
12
+ - **Accurate Token Count**: Uses Pi's actual token count from `ctx.getContextUsage()`, not heuristic estimation matches the terminal display.
13
+ - **Working Set Analysis**: Categorizes files as `Active`, `Stale`, or `Legacy` based on position in the conversation.
13
14
  - **Token Weighting**: Identifies "token hogs" by calculating the approximate size of each file in the window.
14
- - **Operation Tracking**: Marks files with their last operation (Read 👁️, Write 📝, Edit ✍️).
15
- - **Temporal Mapping**: Visually maps when files entered the context to identify compaction candidates.
15
+ - **Operation Tracking**: Marks files with their last operation (Read, Write, Edit).
16
+ - **Compaction Detection**: Tracks compaction summaries and branch summaries as a separate slice.
17
+ - **Auto-Open Browser**: Report automatically opens in your default browser on first invocation.
18
+ - **Dark Mode**: Toggle between light and dark themes. Preference persists across sessions via localStorage.
19
+ - **Live Server**: SSE-powered localhost server with auto-refresh after each assistant message.
16
20
 
17
21
  ## Installation
18
22
 
@@ -33,20 +37,21 @@ The extension will analyze the session and create an interactive HTML report at:
33
37
 
34
38
  ## Context Statuses
35
39
 
36
- The extension categorizes files to help you manage context bloat:
40
+ Files are categorized by their position in the conversation (more reliable than turn-based calculation):
37
41
 
38
- | Status | Criteria | Action |
39
- |--------|----------|--------|
40
- | **Active** | Accessed in last 3 turns | Keep in context |
41
- | **Stale** | Accessed 4-10 turns ago | Monitor for removal |
42
- | **Legacy** | Accessed > 10 turns ago | Prime candidate for compaction |
42
+ | Status | Position in Messages | Action |
43
+ |--------|---------------------|--------|
44
+ | **Active** | Last 30% of messages | Keep in context |
45
+ | **Stale** | Middle 40% of messages | Monitor for removal |
46
+ | **Legacy** | First 30% of messages | Prime candidate for compaction |
43
47
 
44
48
  ## How It Works
45
49
 
46
- 1. **Scanning**: The analyzer iterates through the session history, identifying all `tool_use` calls involving file operations.
47
- 2. **Weighting**: It extracts the content length of tool results and applies a token heuristic (approx. 4 chars/token).
48
- 3. **Categorization**: It calculates the temporal distance between the current turn and the last file access.
49
- 4. **Visualization**: It generates a standalone HTML dashboard featuring a stacked composition bar, a file-weight grid with search/filter, and an interactive insights section.
50
+ 1. **Scanning**: The analyzer iterates through session messages, detecting `toolCall` blocks, `toolResult` messages, `compactionSummary` entries, and image attachments.
51
+ 2. **Weighting**: It calculates token counts for each message type using a code-aware heuristic (multipliers for code blocks, strings, etc.).
52
+ 3. **Accuracy**: When available, Pi's actual token count from `ctx.getContextUsage()` overrides the heuristic for the usage percentage.
53
+ 4. **Categorization**: Files are classified by their position in the message array (last 30% = active, middle 40% = stale, first 30% = legacy).
54
+ 5. **Visualization**: Generates a self-contained HTML dashboard with stacked composition bar, file cards with search/filter, dark mode toggle, and interactive insights.
50
55
 
51
56
  ## Live Localhost Server
52
57
 
@@ -148,10 +148,7 @@ export class ContextAnalyzer {
148
148
  turn,
149
149
  timestamp: msg.timestamp || Date.now(),
150
150
  },
151
- status: this.calculateStatus(
152
- index,
153
- totalMessages,
154
- ),
151
+ status: this.calculateStatus(index, totalMessages),
155
152
  });
156
153
  }
157
154
  }
@@ -178,10 +175,7 @@ export class ContextAnalyzer {
178
175
  turn,
179
176
  timestamp: msg.timestamp || Date.now(),
180
177
  },
181
- status: this.calculateStatus(
182
- index,
183
- totalMessages,
184
- ),
178
+ status: this.calculateStatus(index, totalMessages),
185
179
  });
186
180
  }
187
181
  }
@@ -200,10 +194,7 @@ export class ContextAnalyzer {
200
194
  turn,
201
195
  timestamp: msg.timestamp || Date.now(),
202
196
  },
203
- status: this.calculateStatus(
204
- index,
205
- totalMessages,
206
- ),
197
+ status: this.calculateStatus(index, totalMessages),
207
198
  });
208
199
  }
209
200
  }
@@ -224,15 +215,9 @@ export class ContextAnalyzer {
224
215
  const p = this.extractPath(block.name, block.arguments);
225
216
  if (p) {
226
217
  const opType = this.getOpType(block.name);
227
- const result = this.findToolResult(
228
- messages,
229
- index,
230
- block.id,
231
- );
218
+ const result = this.findToolResult(messages, index, block.id);
232
219
  const content = result?.content || "";
233
- const w = TokenCounter.count(
234
- String(JSON.stringify(content)),
235
- );
220
+ const w = TokenCounter.count(String(JSON.stringify(content)));
236
221
  fileTokens += w;
237
222
  fileRegistry.set(p, {
238
223
  path: p,
@@ -242,10 +227,7 @@ export class ContextAnalyzer {
242
227
  turn,
243
228
  timestamp: msg.timestamp || Date.now(),
244
229
  },
245
- status: this.calculateStatus(
246
- index,
247
- totalMessages,
248
- ),
230
+ status: this.calculateStatus(index, totalMessages),
249
231
  });
250
232
  }
251
233
  }
@@ -263,8 +245,7 @@ export class ContextAnalyzer {
263
245
 
264
246
  const mk = (tokens: number): ContextSlice => ({
265
247
  tokens: Math.ceil(tokens),
266
- percent:
267
- totalTokens > 0 ? Math.round((tokens / totalTokens) * 100) : 0,
248
+ percent: totalTokens > 0 ? Math.round((tokens / totalTokens) * 100) : 0,
268
249
  });
269
250
 
270
251
  const files_detail = Array.from(fileRegistry.values())
@@ -306,9 +287,7 @@ export class ContextAnalyzer {
306
287
  if (Array.isArray(content)) {
307
288
  for (const block of content) {
308
289
  if (block.type === "text" && typeof block.text === "string") {
309
- const match = block.text.match(
310
- /(?:\/|[A-Z]:\\)[\w./\\-]+\.\w+/,
311
- );
290
+ const match = block.text.match(/(?:\/|[A-Z]:\\)[\w./\\-]+\.\w+/);
312
291
  if (match) return match[0];
313
292
  }
314
293
  }
@@ -18,9 +18,10 @@ export class ReportGenerator {
18
18
  actualTokens?: number | null,
19
19
  ): string {
20
20
  // Use Pi's actual token count when available
21
- const total = actualTokens != null && actualTokens > 0
22
- ? actualTokens
23
- : composition.total.tokens;
21
+ const total =
22
+ actualTokens != null && actualTokens > 0
23
+ ? actualTokens
24
+ : composition.total.tokens;
24
25
  const usagePercent =
25
26
  total > 0 ? Math.round((total / contextWindow) * 100) : 0;
26
27
 
@@ -647,15 +648,15 @@ h2:first-of-type { margin-top: 48px; }
647
648
  applyTheme(cur === 'dark' ? 'light' : 'dark');
648
649
  });
649
650
 
650
- // Insight toggles
651
- var btns = document.querySelectorAll('.insight-header');
652
- for (var j = 0; j < btns.length; j++) {
653
- btns[j].addEventListener('click', function() {
654
- var card = this.closest('.insight-card');
655
- var was = card.classList.toggle('collapsed');
656
- this.setAttribute('aria-expanded', was ? 'false' : 'true');
657
- });
658
- }
651
+ // Insight toggles (event delegation — survives SSE body replacement)
652
+ document.addEventListener('click', function(e) {
653
+ var btn = e.target.closest('.insight-header');
654
+ if (!btn) return;
655
+ var card = btn.closest('.insight-card');
656
+ if (!card) return;
657
+ var was = card.classList.toggle('collapsed');
658
+ btn.setAttribute('aria-expanded', was ? 'false' : 'true');
659
+ });
659
660
 
660
661
  // Live SSE
661
662
  try {
@@ -27,21 +27,29 @@ function makeReportPath(sessionName?: string): string {
27
27
  const now = new Date();
28
28
  const date = now.toISOString().split("T")[0];
29
29
  const time = now.toTimeString().split(" ")[0].replace(/:/g, "-");
30
- const safe = (sessionName || "session")
31
- .replace(/[^\w.-]/g, "_")
32
- .slice(0, 40);
30
+ const safe = (sessionName || "session").replace(/[^\w.-]/g, "_").slice(0, 40);
33
31
  const filename = `${date}_${time}_${safe}.html`;
34
32
  return path.join(dir, filename);
35
33
  }
36
34
 
37
35
  function openBrowser(url: string): void {
38
36
  const platform = process.platform;
39
- if (platform === "win32") {
40
- exec(`start "" "${url}"`);
41
- } else if (platform === "darwin") {
42
- exec(`open "${url}"`);
43
- } else {
44
- exec(`xdg-open "${url}"`);
37
+ try {
38
+ if (platform === "win32") {
39
+ // Use cmd /c start with separate title arg to handle URLs with special chars
40
+ exec(`cmd /c start "" "${url}"`, (err) => {
41
+ if (err) {
42
+ // Fallback: try explorer directly
43
+ exec(`explorer "${url}"`);
44
+ }
45
+ });
46
+ } else if (platform === "darwin") {
47
+ exec(`open "${url}"`);
48
+ } else {
49
+ exec(`xdg-open "${url}"`);
50
+ }
51
+ } catch {
52
+ // Silent — browser open is best-effort
45
53
  }
46
54
  }
47
55
 
@@ -194,7 +202,7 @@ export default async function piContextMap(pi: ExtensionAPI): Promise<void> {
194
202
 
195
203
  ctx.ui.notify("Analyzing session context...", "info");
196
204
  try {
197
- const { composition, insights, reportPath } = await runAnalysis();
205
+ const { composition, insights } = await runAnalysis();
198
206
  const criticalCount = insights.filter(
199
207
  (i) => i.severity === "critical",
200
208
  ).length;
@@ -253,9 +261,7 @@ export default async function piContextMap(pi: ExtensionAPI): Promise<void> {
253
261
  actualPercent != null
254
262
  ? actualPercent
255
263
  : composition.total.tokens > 0
256
- ? Math.round(
257
- (composition.total.tokens / contextWindow) * 100,
258
- )
264
+ ? Math.round((composition.total.tokens / contextWindow) * 100)
259
265
  : 0;
260
266
  const summary =
261
267
  `Context: ${composition.total.tokens.toLocaleString()} tokens (${usagePercent.toFixed(1)}% of ${(contextWindow / 1000).toFixed(0)}k). ` +
@@ -270,8 +276,7 @@ export default async function piContextMap(pi: ExtensionAPI): Promise<void> {
270
276
  summary,
271
277
  "",
272
278
  ...insights.map(
273
- (i) =>
274
- `[${i.severity.toUpperCase()}] ${i.title}: ${i.message}`,
279
+ (i) => `[${i.severity.toUpperCase()}] ${i.title}: ${i.message}`,
275
280
  ),
276
281
  `Report: ${reportPath}`,
277
282
  serverUrl ? `Live: ${serverUrl}` : "",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-context-map",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "description": "Professional context profiler for Pi that visualizes the session context window, token distribution, and integrates with Nexus packages for actionable insights.",
5
5
  "keywords": [
6
6
  "pi-package",