pi-context-map 0.6.2 → 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.
- package/CHANGELOG.md +11 -0
- package/extensions/analyzer.ts +100 -24
- package/extensions/generator.ts +5 -1
- package/extensions/index.ts +89 -18
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.7.0] - 2026-06-15
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
- **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.
|
|
6
|
+
- **Fixed compaction summary detection**: Now detects `role: "compactionSummary"` (Pi's actual format) with the `summary` field. Summaries no longer show 0%.
|
|
7
|
+
- **Fixed file status calculation**: Changed from turn-based to position-based. Files in the last 30% of messages are "active", middle 40% are "stale", first 30% are "legacy".
|
|
8
|
+
- **Removed error spam**: Silent error handling instead of console.error for non-critical failures.
|
|
9
|
+
|
|
10
|
+
### Features
|
|
11
|
+
- **Auto-open browser**: Report automatically opens in default browser on first `/context-map` invocation.
|
|
12
|
+
- **Pi actual tokens in HTML**: Generator accepts and displays Pi's real token count.
|
|
13
|
+
|
|
3
14
|
## [0.6.2] - 2026-06-15
|
|
4
15
|
### Bug Fixes
|
|
5
16
|
- **Fixed Pi message format**: Now uses `type: "toolCall"` (not `"tool_use"`) and `toolCallId` (not `tool_call_id`) to match Pi's actual `@mariozechner/pi-ai` types.
|
package/extensions/analyzer.ts
CHANGED
|
@@ -3,13 +3,16 @@
|
|
|
3
3
|
* Parses Pi session messages to identify the active working set of files,
|
|
4
4
|
* their token weights, and their temporal status.
|
|
5
5
|
*
|
|
6
|
-
* Pi message format (from @mariozechner/pi-ai):
|
|
7
|
-
* UserMessage:
|
|
8
|
-
* AssistantMessage:
|
|
9
|
-
* ToolResultMessage:
|
|
6
|
+
* Pi message format (from @mariozechner/pi-ai + pi-coding-agent):
|
|
7
|
+
* UserMessage: { role: "user", content: string | (TextContent | ImageContent)[] }
|
|
8
|
+
* AssistantMessage: { role: "assistant", content: (TextContent | ThinkingContent | ToolCall)[] }
|
|
9
|
+
* ToolResultMessage: { role: "toolResult", toolCallId, toolName, content }
|
|
10
|
+
* CompactionSummaryMessage:{ role: "compactionSummary", summary: string, tokensBefore: number }
|
|
11
|
+
* BranchSummaryMessage: { role: "branchSummary", summary: string }
|
|
12
|
+
* BashExecutionMessage: { role: "bashExecution", command, output }
|
|
13
|
+
* CustomMessage: { role: "custom", customType, content }
|
|
10
14
|
*
|
|
11
15
|
* ToolCall: { type: "toolCall", id, name, arguments }
|
|
12
|
-
* ToolCall.id maps to ToolResultMessage.toolCallId
|
|
13
16
|
*/
|
|
14
17
|
import { TokenCounter } from "./token-counter";
|
|
15
18
|
|
|
@@ -39,6 +42,9 @@ export interface ContextComposition {
|
|
|
39
42
|
summaries: ContextSlice;
|
|
40
43
|
total: ContextSlice;
|
|
41
44
|
files_detail: FileContext[];
|
|
45
|
+
/** Pi's actual token count from ctx.getContextUsage() — may differ from heuristic total */
|
|
46
|
+
actualTokens?: number | null;
|
|
47
|
+
actualPercent?: number | null;
|
|
42
48
|
}
|
|
43
49
|
|
|
44
50
|
export class ContextAnalyzer {
|
|
@@ -60,23 +66,65 @@ export class ContextAnalyzer {
|
|
|
60
66
|
systemTokens += TokenCounter.count(systemPrompt);
|
|
61
67
|
}
|
|
62
68
|
|
|
69
|
+
// Track message indices for status calculation
|
|
70
|
+
const totalMessages = messages.length;
|
|
71
|
+
|
|
63
72
|
for (let index = 0; index < messages.length; index++) {
|
|
64
73
|
const msg = messages[index];
|
|
65
74
|
const turn = index + 1;
|
|
75
|
+
|
|
76
|
+
// Normalize role — Pi may use different role strings
|
|
66
77
|
const role = msg.role || "";
|
|
67
78
|
|
|
68
|
-
// 1. Compaction summaries (Pi
|
|
79
|
+
// 1. Compaction summaries (Pi uses role="compactionSummary" with summary field)
|
|
69
80
|
if (
|
|
81
|
+
role === "compactionSummary" ||
|
|
70
82
|
role === "compaction" ||
|
|
71
83
|
msg.type === "compaction" ||
|
|
72
84
|
msg.customType === "compaction" ||
|
|
73
85
|
msg.compactionEntry
|
|
74
86
|
) {
|
|
75
|
-
|
|
87
|
+
// Use the summary field if available, otherwise fall back to content
|
|
88
|
+
const summaryText =
|
|
89
|
+
typeof msg.summary === "string"
|
|
90
|
+
? msg.summary
|
|
91
|
+
: typeof msg.content === "string"
|
|
92
|
+
? msg.content
|
|
93
|
+
: JSON.stringify(msg.content || msg);
|
|
94
|
+
summaryTokens += TokenCounter.count(summaryText);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 2. Branch summaries
|
|
99
|
+
if (role === "branchSummary") {
|
|
100
|
+
const summaryText =
|
|
101
|
+
typeof msg.summary === "string" ? msg.summary : JSON.stringify(msg);
|
|
102
|
+
summaryTokens += TokenCounter.count(summaryText);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 3. Bash executions
|
|
107
|
+
if (role === "bashExecution") {
|
|
108
|
+
toolTokens += TokenCounter.countMessage(msg);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 4. Custom messages (extensions)
|
|
113
|
+
if (role === "custom") {
|
|
114
|
+
// Categorize based on customType
|
|
115
|
+
const customType = msg.customType || "";
|
|
116
|
+
if (
|
|
117
|
+
customType.includes("compaction") ||
|
|
118
|
+
customType.includes("summary")
|
|
119
|
+
) {
|
|
120
|
+
summaryTokens += TokenCounter.countMessage(msg);
|
|
121
|
+
} else {
|
|
122
|
+
historyTokens += TokenCounter.countMessage(msg);
|
|
123
|
+
}
|
|
76
124
|
continue;
|
|
77
125
|
}
|
|
78
126
|
|
|
79
|
-
//
|
|
127
|
+
// 5. Tool results (Pi uses role="toolResult")
|
|
80
128
|
if (role === "toolResult") {
|
|
81
129
|
toolTokens += TokenCounter.countMessage(msg);
|
|
82
130
|
// Track file content from tool results
|
|
@@ -100,7 +148,10 @@ export class ContextAnalyzer {
|
|
|
100
148
|
turn,
|
|
101
149
|
timestamp: msg.timestamp || Date.now(),
|
|
102
150
|
},
|
|
103
|
-
status: this.calculateStatus(
|
|
151
|
+
status: this.calculateStatus(
|
|
152
|
+
index,
|
|
153
|
+
totalMessages,
|
|
154
|
+
),
|
|
104
155
|
});
|
|
105
156
|
}
|
|
106
157
|
}
|
|
@@ -108,7 +159,7 @@ export class ContextAnalyzer {
|
|
|
108
159
|
continue;
|
|
109
160
|
}
|
|
110
161
|
|
|
111
|
-
//
|
|
162
|
+
// 6. User messages
|
|
112
163
|
if (role === "user") {
|
|
113
164
|
historyTokens += TokenCounter.countMessage(msg);
|
|
114
165
|
// Track file attachments (images, file paths in text)
|
|
@@ -127,7 +178,10 @@ export class ContextAnalyzer {
|
|
|
127
178
|
turn,
|
|
128
179
|
timestamp: msg.timestamp || Date.now(),
|
|
129
180
|
},
|
|
130
|
-
status: this.calculateStatus(
|
|
181
|
+
status: this.calculateStatus(
|
|
182
|
+
index,
|
|
183
|
+
totalMessages,
|
|
184
|
+
),
|
|
131
185
|
});
|
|
132
186
|
}
|
|
133
187
|
}
|
|
@@ -146,7 +200,10 @@ export class ContextAnalyzer {
|
|
|
146
200
|
turn,
|
|
147
201
|
timestamp: msg.timestamp || Date.now(),
|
|
148
202
|
},
|
|
149
|
-
status: this.calculateStatus(
|
|
203
|
+
status: this.calculateStatus(
|
|
204
|
+
index,
|
|
205
|
+
totalMessages,
|
|
206
|
+
),
|
|
150
207
|
});
|
|
151
208
|
}
|
|
152
209
|
}
|
|
@@ -157,7 +214,7 @@ export class ContextAnalyzer {
|
|
|
157
214
|
continue;
|
|
158
215
|
}
|
|
159
216
|
|
|
160
|
-
//
|
|
217
|
+
// 7. Assistant messages — track toolCall blocks
|
|
161
218
|
if (role === "assistant") {
|
|
162
219
|
historyTokens += TokenCounter.countMessage(msg);
|
|
163
220
|
if (Array.isArray(msg.content)) {
|
|
@@ -167,9 +224,15 @@ export class ContextAnalyzer {
|
|
|
167
224
|
const p = this.extractPath(block.name, block.arguments);
|
|
168
225
|
if (p) {
|
|
169
226
|
const opType = this.getOpType(block.name);
|
|
170
|
-
const result = this.findToolResult(
|
|
227
|
+
const result = this.findToolResult(
|
|
228
|
+
messages,
|
|
229
|
+
index,
|
|
230
|
+
block.id,
|
|
231
|
+
);
|
|
171
232
|
const content = result?.content || "";
|
|
172
|
-
const w = TokenCounter.count(
|
|
233
|
+
const w = TokenCounter.count(
|
|
234
|
+
String(JSON.stringify(content)),
|
|
235
|
+
);
|
|
173
236
|
fileTokens += w;
|
|
174
237
|
fileRegistry.set(p, {
|
|
175
238
|
path: p,
|
|
@@ -179,7 +242,10 @@ export class ContextAnalyzer {
|
|
|
179
242
|
turn,
|
|
180
243
|
timestamp: msg.timestamp || Date.now(),
|
|
181
244
|
},
|
|
182
|
-
status: this.calculateStatus(
|
|
245
|
+
status: this.calculateStatus(
|
|
246
|
+
index,
|
|
247
|
+
totalMessages,
|
|
248
|
+
),
|
|
183
249
|
});
|
|
184
250
|
}
|
|
185
251
|
}
|
|
@@ -188,7 +254,7 @@ export class ContextAnalyzer {
|
|
|
188
254
|
continue;
|
|
189
255
|
}
|
|
190
256
|
|
|
191
|
-
//
|
|
257
|
+
// 8. Everything else
|
|
192
258
|
historyTokens += TokenCounter.countMessage(msg);
|
|
193
259
|
}
|
|
194
260
|
|
|
@@ -197,7 +263,8 @@ export class ContextAnalyzer {
|
|
|
197
263
|
|
|
198
264
|
const mk = (tokens: number): ContextSlice => ({
|
|
199
265
|
tokens: Math.ceil(tokens),
|
|
200
|
-
percent:
|
|
266
|
+
percent:
|
|
267
|
+
totalTokens > 0 ? Math.round((tokens / totalTokens) * 100) : 0,
|
|
201
268
|
});
|
|
202
269
|
|
|
203
270
|
const files_detail = Array.from(fileRegistry.values())
|
|
@@ -239,7 +306,9 @@ export class ContextAnalyzer {
|
|
|
239
306
|
if (Array.isArray(content)) {
|
|
240
307
|
for (const block of content) {
|
|
241
308
|
if (block.type === "text" && typeof block.text === "string") {
|
|
242
|
-
const match = block.text.match(
|
|
309
|
+
const match = block.text.match(
|
|
310
|
+
/(?:\/|[A-Z]:\\)[\w./\\-]+\.\w+/,
|
|
311
|
+
);
|
|
243
312
|
if (match) return match[0];
|
|
244
313
|
}
|
|
245
314
|
}
|
|
@@ -260,13 +329,20 @@ export class ContextAnalyzer {
|
|
|
260
329
|
}
|
|
261
330
|
}
|
|
262
331
|
|
|
332
|
+
/**
|
|
333
|
+
* Calculate file status based on position in message array.
|
|
334
|
+
* Files near the end are "active", middle are "stale", beginning are "legacy".
|
|
335
|
+
* This is more reliable than turn-based calculation since the context event
|
|
336
|
+
* replaces all messages at once.
|
|
337
|
+
*/
|
|
263
338
|
private calculateStatus(
|
|
264
|
-
|
|
265
|
-
|
|
339
|
+
messageIndex: number,
|
|
340
|
+
totalMessages: number,
|
|
266
341
|
): FileContext["status"] {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
if (
|
|
342
|
+
if (totalMessages === 0) return "legacy";
|
|
343
|
+
const ratio = messageIndex / totalMessages;
|
|
344
|
+
if (ratio >= 0.7) return "active";
|
|
345
|
+
if (ratio >= 0.3) return "stale";
|
|
270
346
|
return "legacy";
|
|
271
347
|
}
|
|
272
348
|
|
package/extensions/generator.ts
CHANGED
|
@@ -15,8 +15,12 @@ export class ReportGenerator {
|
|
|
15
15
|
composition: ContextComposition,
|
|
16
16
|
insights: Insight[],
|
|
17
17
|
contextWindow: number = 128_000,
|
|
18
|
+
actualTokens?: number | null,
|
|
18
19
|
): string {
|
|
19
|
-
|
|
20
|
+
// Use Pi's actual token count when available
|
|
21
|
+
const total = actualTokens != null && actualTokens > 0
|
|
22
|
+
? actualTokens
|
|
23
|
+
: composition.total.tokens;
|
|
20
24
|
const usagePercent =
|
|
21
25
|
total > 0 ? Math.round((total / contextWindow) * 100) : 0;
|
|
22
26
|
|
package/extensions/index.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* pi-context-map
|
|
3
3
|
* Professional Context Profiler for Pi.
|
|
4
|
-
* v0.
|
|
4
|
+
* v0.7.0 — Fixed token accuracy (uses Pi's actual count), compactionSummary detection,
|
|
5
|
+
* auto-open browser, position-based file status, error cleanup.
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
import type {
|
|
@@ -16,6 +17,7 @@ import { LiveReportServer } from "./live-server";
|
|
|
16
17
|
import * as path from "node:path";
|
|
17
18
|
import * as fs from "node:fs";
|
|
18
19
|
import * as os from "node:os";
|
|
20
|
+
import { exec } from "node:child_process";
|
|
19
21
|
|
|
20
22
|
function makeReportPath(sessionName?: string): string {
|
|
21
23
|
const dir = path.join(os.homedir(), ".pi", "context-map");
|
|
@@ -25,11 +27,24 @@ function makeReportPath(sessionName?: string): string {
|
|
|
25
27
|
const now = new Date();
|
|
26
28
|
const date = now.toISOString().split("T")[0];
|
|
27
29
|
const time = now.toTimeString().split(" ")[0].replace(/:/g, "-");
|
|
28
|
-
const safe = (sessionName || "session")
|
|
30
|
+
const safe = (sessionName || "session")
|
|
31
|
+
.replace(/[^\w.-]/g, "_")
|
|
32
|
+
.slice(0, 40);
|
|
29
33
|
const filename = `${date}_${time}_${safe}.html`;
|
|
30
34
|
return path.join(dir, filename);
|
|
31
35
|
}
|
|
32
36
|
|
|
37
|
+
function openBrowser(url: string): void {
|
|
38
|
+
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}"`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
33
48
|
export default async function piContextMap(pi: ExtensionAPI): Promise<void> {
|
|
34
49
|
const analyzer = new ContextAnalyzer();
|
|
35
50
|
const liveServer = new LiveReportServer();
|
|
@@ -37,18 +52,30 @@ export default async function piContextMap(pi: ExtensionAPI): Promise<void> {
|
|
|
37
52
|
let sessionMessages: AgentMessage[] = [];
|
|
38
53
|
let currentTurn = 0;
|
|
39
54
|
let contextWindow = 128_000;
|
|
55
|
+
let actualTokens: number | null = null;
|
|
56
|
+
let actualPercent: number | null = null;
|
|
40
57
|
let systemPrompt = "";
|
|
41
58
|
let currentReportPath = makeReportPath();
|
|
59
|
+
let isFirstRun = true;
|
|
42
60
|
|
|
43
|
-
// Capture messages, context window,
|
|
61
|
+
// Capture messages, context window, system prompt, and actual token count from Pi
|
|
44
62
|
pi.on("context", (event: any, ctx: any) => {
|
|
45
63
|
if (event?.messages && Array.isArray(event.messages)) {
|
|
46
64
|
sessionMessages = event.messages;
|
|
47
65
|
}
|
|
48
66
|
try {
|
|
49
67
|
const usage = ctx?.getContextUsage?.();
|
|
50
|
-
if (usage
|
|
51
|
-
contextWindow
|
|
68
|
+
if (usage) {
|
|
69
|
+
if (usage.contextWindow && usage.contextWindow > 0) {
|
|
70
|
+
contextWindow = usage.contextWindow;
|
|
71
|
+
}
|
|
72
|
+
// Use Pi's actual token count — this is the real value
|
|
73
|
+
if (usage.tokens != null && usage.tokens > 0) {
|
|
74
|
+
actualTokens = usage.tokens;
|
|
75
|
+
}
|
|
76
|
+
if (usage.percent != null && usage.percent > 0) {
|
|
77
|
+
actualPercent = usage.percent;
|
|
78
|
+
}
|
|
52
79
|
}
|
|
53
80
|
} catch {
|
|
54
81
|
// Keep fallback
|
|
@@ -71,6 +98,7 @@ export default async function piContextMap(pi: ExtensionAPI): Promise<void> {
|
|
|
71
98
|
// Update report path when session changes
|
|
72
99
|
pi.on("session_start", () => {
|
|
73
100
|
currentReportPath = makeReportPath();
|
|
101
|
+
isFirstRun = true;
|
|
74
102
|
});
|
|
75
103
|
|
|
76
104
|
// Persist messages on compaction so they survive reload
|
|
@@ -78,7 +106,7 @@ export default async function piContextMap(pi: ExtensionAPI): Promise<void> {
|
|
|
78
106
|
if (event?.compactionEntry) {
|
|
79
107
|
try {
|
|
80
108
|
pi.appendEntry("context-map-snapshot", {
|
|
81
|
-
messages: sessionMessages.slice(-50),
|
|
109
|
+
messages: sessionMessages.slice(-50),
|
|
82
110
|
turn: currentTurn,
|
|
83
111
|
timestamp: Date.now(),
|
|
84
112
|
});
|
|
@@ -99,11 +127,40 @@ export default async function piContextMap(pi: ExtensionAPI): Promise<void> {
|
|
|
99
127
|
currentTurn,
|
|
100
128
|
systemPrompt,
|
|
101
129
|
);
|
|
130
|
+
|
|
131
|
+
// Override with Pi's actual token count when available
|
|
132
|
+
if (actualTokens != null && actualTokens > 0) {
|
|
133
|
+
composition.actualTokens = actualTokens;
|
|
134
|
+
composition.actualPercent = actualPercent;
|
|
135
|
+
// Recalculate percentages relative to actual total
|
|
136
|
+
const total = actualTokens;
|
|
137
|
+
if (total > 0) {
|
|
138
|
+
composition.system.percent = Math.round(
|
|
139
|
+
(composition.system.tokens / total) * 100,
|
|
140
|
+
);
|
|
141
|
+
composition.tools.percent = Math.round(
|
|
142
|
+
(composition.tools.tokens / total) * 100,
|
|
143
|
+
);
|
|
144
|
+
composition.history.percent = Math.round(
|
|
145
|
+
(composition.history.tokens / total) * 100,
|
|
146
|
+
);
|
|
147
|
+
composition.files.percent = Math.round(
|
|
148
|
+
(composition.files.tokens / total) * 100,
|
|
149
|
+
);
|
|
150
|
+
composition.summaries.percent = Math.round(
|
|
151
|
+
(composition.summaries.tokens / total) * 100,
|
|
152
|
+
);
|
|
153
|
+
// Use Pi's actual total for the usage calculation
|
|
154
|
+
composition.total.tokens = total;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
102
158
|
const insights = InsightEngine.generate(composition);
|
|
103
159
|
const html = ReportGenerator.generateHTML(
|
|
104
160
|
composition,
|
|
105
161
|
insights,
|
|
106
162
|
contextWindow,
|
|
163
|
+
actualTokens,
|
|
107
164
|
);
|
|
108
165
|
|
|
109
166
|
try {
|
|
@@ -113,7 +170,7 @@ export default async function piContextMap(pi: ExtensionAPI): Promise<void> {
|
|
|
113
170
|
}
|
|
114
171
|
fs.writeFileSync(currentReportPath, html, "utf8");
|
|
115
172
|
} catch (err: any) {
|
|
116
|
-
|
|
173
|
+
// Silent — don't spam console
|
|
117
174
|
}
|
|
118
175
|
|
|
119
176
|
if (liveServer.isRunning) {
|
|
@@ -145,17 +202,27 @@ export default async function piContextMap(pi: ExtensionAPI): Promise<void> {
|
|
|
145
202
|
criticalCount > 0
|
|
146
203
|
? `Context map generated. ${criticalCount} critical insight(s) found.`
|
|
147
204
|
: "Context map generated successfully.";
|
|
148
|
-
|
|
205
|
+
|
|
206
|
+
// Use Pi's actual percentage when available
|
|
207
|
+
const usageDisplay =
|
|
208
|
+
actualPercent != null
|
|
209
|
+
? `${actualPercent.toFixed(1)}%`
|
|
210
|
+
: `${composition.total.percent}%`;
|
|
211
|
+
|
|
212
|
+
let details = `Usage: ${usageDisplay} of ${(contextWindow / 1000).toFixed(0)}k`;
|
|
149
213
|
if (serverUrl) {
|
|
150
|
-
details += ` |
|
|
214
|
+
details += ` | ${serverUrl}`;
|
|
151
215
|
}
|
|
152
|
-
details += ` | Messages: ${sessionMessages.length}`;
|
|
153
|
-
details += ` | System: ${composition.system.tokens}t (${composition.system.percent}%)`;
|
|
154
|
-
details += ` | Tools: ${composition.tools.tokens}t (${composition.tools.percent}%)`;
|
|
155
216
|
ctx.ui.notify(
|
|
156
217
|
`${summary} ${details}`,
|
|
157
218
|
criticalCount > 0 ? "warning" : "success",
|
|
158
219
|
);
|
|
220
|
+
|
|
221
|
+
// Auto-open browser on first run
|
|
222
|
+
if (isFirstRun && serverUrl) {
|
|
223
|
+
openBrowser(serverUrl);
|
|
224
|
+
isFirstRun = false;
|
|
225
|
+
}
|
|
159
226
|
} catch (error: any) {
|
|
160
227
|
ctx.ui.notify(
|
|
161
228
|
`Failed to generate context map: ${error.message}`,
|
|
@@ -183,15 +250,18 @@ export default async function piContextMap(pi: ExtensionAPI): Promise<void> {
|
|
|
183
250
|
try {
|
|
184
251
|
const { composition, insights, reportPath } = await runAnalysis();
|
|
185
252
|
const usagePercent =
|
|
186
|
-
|
|
187
|
-
?
|
|
188
|
-
: 0
|
|
253
|
+
actualPercent != null
|
|
254
|
+
? actualPercent
|
|
255
|
+
: composition.total.tokens > 0
|
|
256
|
+
? Math.round(
|
|
257
|
+
(composition.total.tokens / contextWindow) * 100,
|
|
258
|
+
)
|
|
259
|
+
: 0;
|
|
189
260
|
const summary =
|
|
190
|
-
`Context: ${composition.total.tokens.toLocaleString()} tokens
|
|
261
|
+
`Context: ${composition.total.tokens.toLocaleString()} tokens (${usagePercent.toFixed(1)}% of ${(contextWindow / 1000).toFixed(0)}k). ` +
|
|
191
262
|
`System ${composition.system.percent}%, Tools ${composition.tools.percent}%, ` +
|
|
192
263
|
`History ${composition.history.percent}%, Files ${composition.files.percent}%, ` +
|
|
193
264
|
`Summaries ${composition.summaries.percent}%. ` +
|
|
194
|
-
`Usage: ${usagePercent}% of ${(contextWindow / 1000).toFixed(0)}k window. ` +
|
|
195
265
|
`Messages: ${sessionMessages.length}. ` +
|
|
196
266
|
`${insights.length} insight(s).`;
|
|
197
267
|
return {
|
|
@@ -200,7 +270,8 @@ export default async function piContextMap(pi: ExtensionAPI): Promise<void> {
|
|
|
200
270
|
summary,
|
|
201
271
|
"",
|
|
202
272
|
...insights.map(
|
|
203
|
-
(i) =>
|
|
273
|
+
(i) =>
|
|
274
|
+
`[${i.severity.toUpperCase()}] ${i.title}: ${i.message}`,
|
|
204
275
|
),
|
|
205
276
|
`Report: ${reportPath}`,
|
|
206
277
|
serverUrl ? `Live: ${serverUrl}` : "",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-context-map",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
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",
|