pi-context-map 0.1.4 → 0.2.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 +12 -24
- package/package.json +21 -17
- package/dist/analyzer.d.ts +0 -40
- package/dist/analyzer.js +0 -117
- package/dist/generator.d.ts +0 -11
- package/dist/generator.js +0 -258
- package/dist/index.d.ts +0 -6
- package/dist/index.js +0 -50
- package/docs/proposal.md +0 -41
- package/docs/spec.md +0 -57
- package/tasks.md +0 -32
- package/tsconfig.json +0 -20
- /package/{src → extensions}/analyzer.ts +0 -0
- /package/{src → extensions}/generator.ts +0 -0
- /package/{src → extensions}/index.ts +0 -0
- /package/{src → extensions}/types/pi-ai.d.ts +0 -0
- /package/{src → extensions}/types/pi-coding-agent.d.ts +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,27 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
- Aligned package manifest with official Pi specifications.
|
|
12
|
-
- Moved core libraries to `peerDependencies` to prevent bundling conflicts.
|
|
13
|
-
|
|
14
|
-
## [0.1.2] - 2026-06-14
|
|
15
|
-
### Added
|
|
16
|
-
- Professional README.md with badges and usage guides.
|
|
17
|
-
- MIT License.
|
|
18
|
-
|
|
19
|
-
## [0.1.1] - 2026-06-14
|
|
20
|
-
### Added
|
|
21
|
-
- Initial release of the context mapping logic.
|
|
22
|
-
- Integration with the Pi extension API.
|
|
23
|
-
|
|
24
|
-
## [0.1.0] - 2026-06-14
|
|
3
|
+
## [0.2.0] - 2026-06-14
|
|
4
|
+
### Professional Context Profiler
|
|
5
|
+
- **Architectural Modernization**: Migrated to source-shipping (`./extensions`) and async factory function pattern.
|
|
6
|
+
- **Nexus Synergy**: Optimized for compatibility with the Nexus monorepo (e.g., `pi-ultra-compact`).
|
|
7
|
+
- **TUI Integration**: Refined command registration for seamless discovery in the Pi command palette.
|
|
8
|
+
- **LSP Clean**: Resolved type mismatches with the latest `pi-coding-agent` API.
|
|
9
|
+
|
|
10
|
+
## [0.1.4] - 2026-06-13
|
|
25
11
|
### Initial Release
|
|
26
|
-
-
|
|
27
|
-
-
|
|
12
|
+
- Visual context window mapping and token distribution dashboard.
|
|
13
|
+
- Categorization of files as `Active`, `Stale`, or `Legacy`.
|
|
14
|
+
- Operation tracking and temporal mapping for compaction candidates.
|
|
15
|
+
- Standalone HTML report generation at `~/.pi/context-map/report.html`.
|
package/package.json
CHANGED
|
@@ -1,33 +1,37 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-context-map",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
5
|
-
"main": "dist/index.js",
|
|
6
|
-
"types": "dist/index.d.ts",
|
|
7
|
-
"scripts": {
|
|
8
|
-
"build": "tsc",
|
|
9
|
-
"dev": "tsc -w"
|
|
10
|
-
},
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Professional context profiler for Pi that visualizes the session context window, token distribution, and integrates with Nexus packages for actionable insights.",
|
|
11
5
|
"keywords": [
|
|
12
|
-
"pi",
|
|
13
6
|
"pi-package",
|
|
14
|
-
"extension",
|
|
15
7
|
"context",
|
|
16
8
|
"visualization",
|
|
17
|
-
"tokens"
|
|
9
|
+
"tokens",
|
|
10
|
+
"profiler",
|
|
11
|
+
"nexus"
|
|
18
12
|
],
|
|
19
|
-
"author": "ZachDreamZ",
|
|
20
|
-
"license": "MIT",
|
|
21
13
|
"pi": {
|
|
22
|
-
"extensions": ["
|
|
14
|
+
"extensions": ["./extensions"]
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"extensions",
|
|
18
|
+
"README.md",
|
|
19
|
+
"CHANGELOG.md",
|
|
20
|
+
"LICENSE"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsc",
|
|
24
|
+
"test": "jest",
|
|
25
|
+
"prepublishOnly": "npm run build"
|
|
23
26
|
},
|
|
24
|
-
"dependencies": {},
|
|
25
27
|
"peerDependencies": {
|
|
26
|
-
"
|
|
27
|
-
"@earendil-works/pi-coding-agent": "*"
|
|
28
|
+
"pi-coding-agent": "*"
|
|
28
29
|
},
|
|
29
30
|
"devDependencies": {
|
|
30
31
|
"typescript": "^5.0.0",
|
|
32
|
+
"jest": "^29.0.0",
|
|
33
|
+
"ts-jest": "^29.0.0",
|
|
34
|
+
"@types/jest": "^29.0.0",
|
|
31
35
|
"@types/node": "^20.0.0"
|
|
32
36
|
}
|
|
33
37
|
}
|
package/dist/analyzer.d.ts
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ContextAnalyzer
|
|
3
|
-
* Responsible for parsing Pi session messages to identify the active working set of files,
|
|
4
|
-
* their token weights, and their temporal status.
|
|
5
|
-
*/
|
|
6
|
-
export interface FileOp {
|
|
7
|
-
type: "read" | "write" | "edit" | "delete";
|
|
8
|
-
turn: number;
|
|
9
|
-
timestamp: number;
|
|
10
|
-
}
|
|
11
|
-
export interface FileContext {
|
|
12
|
-
path: string;
|
|
13
|
-
weight: number;
|
|
14
|
-
lastOp: FileOp;
|
|
15
|
-
status: "active" | "stale" | "legacy";
|
|
16
|
-
}
|
|
17
|
-
export interface ContextMap {
|
|
18
|
-
files: FileContext[];
|
|
19
|
-
totalTokens: number;
|
|
20
|
-
systemTokens: number;
|
|
21
|
-
historyTokens: number;
|
|
22
|
-
fileTokens: number;
|
|
23
|
-
toolTokens: number;
|
|
24
|
-
}
|
|
25
|
-
export declare class ContextAnalyzer {
|
|
26
|
-
/**
|
|
27
|
-
* Heuristic for token estimation: approx 4 chars per token.
|
|
28
|
-
*/
|
|
29
|
-
private static TOKEN_HEURISTIC;
|
|
30
|
-
/**
|
|
31
|
-
* Analyze session messages to produce a context map.
|
|
32
|
-
* @param messages The full session conversation history.
|
|
33
|
-
* @param currentTurn The current turn number.
|
|
34
|
-
*/
|
|
35
|
-
analyze(messages: any[], currentTurn: number): ContextMap;
|
|
36
|
-
private extractPath;
|
|
37
|
-
private getOpType;
|
|
38
|
-
private calculateStatus;
|
|
39
|
-
private findToolResult;
|
|
40
|
-
}
|
package/dist/analyzer.js
DELETED
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* ContextAnalyzer
|
|
4
|
-
* Responsible for parsing Pi session messages to identify the active working set of files,
|
|
5
|
-
* their token weights, and their temporal status.
|
|
6
|
-
*/
|
|
7
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
-
exports.ContextAnalyzer = void 0;
|
|
9
|
-
class ContextAnalyzer {
|
|
10
|
-
/**
|
|
11
|
-
* Heuristic for token estimation: approx 4 chars per token.
|
|
12
|
-
*/
|
|
13
|
-
static TOKEN_HEURISTIC = 4;
|
|
14
|
-
/**
|
|
15
|
-
* Analyze session messages to produce a context map.
|
|
16
|
-
* @param messages The full session conversation history.
|
|
17
|
-
* @param currentTurn The current turn number.
|
|
18
|
-
*/
|
|
19
|
-
analyze(messages, currentTurn) {
|
|
20
|
-
const fileRegistry = new Map();
|
|
21
|
-
let totalTokens = 0;
|
|
22
|
-
let fileTokens = 0;
|
|
23
|
-
let toolTokens = 0;
|
|
24
|
-
messages.forEach((msg, index) => {
|
|
25
|
-
const turn = index + 1;
|
|
26
|
-
// Basic token estimation for the message
|
|
27
|
-
const msgText = typeof msg.content === "string"
|
|
28
|
-
? msg.content
|
|
29
|
-
: JSON.stringify(msg.content);
|
|
30
|
-
const msgTokens = Math.ceil(msgText.length / ContextAnalyzer.TOKEN_HEURISTIC);
|
|
31
|
-
totalTokens += msgTokens;
|
|
32
|
-
if (msg.role === "assistant" && Array.isArray(msg.content)) {
|
|
33
|
-
for (const block of msg.content) {
|
|
34
|
-
if (block.type === "tool_use") {
|
|
35
|
-
const input = block.input;
|
|
36
|
-
const path = this.extractPath(block.name, input);
|
|
37
|
-
if (path) {
|
|
38
|
-
const opType = this.getOpType(block.name);
|
|
39
|
-
// If the file is already tracked, update it
|
|
40
|
-
// Find the tool result for this tool use to get actual content length
|
|
41
|
-
const result = this.findToolResult(messages, index, block.id);
|
|
42
|
-
const content = result?.content || "";
|
|
43
|
-
const weight = Math.ceil(String(content).length / ContextAnalyzer.TOKEN_HEURISTIC);
|
|
44
|
-
fileRegistry.set(path, {
|
|
45
|
-
path,
|
|
46
|
-
weight,
|
|
47
|
-
lastOp: {
|
|
48
|
-
type: opType,
|
|
49
|
-
turn,
|
|
50
|
-
timestamp: msg.timestamp || Date.now(),
|
|
51
|
-
},
|
|
52
|
-
status: this.calculateStatus(turn, currentTurn),
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
if (msg.role === "tool") {
|
|
59
|
-
toolTokens += Math.ceil(String(msg.content).length / ContextAnalyzer.TOKEN_HEURISTIC);
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
const files = Array.from(fileRegistry.values());
|
|
63
|
-
fileTokens = files.reduce((acc, f) => acc + f.weight, 0);
|
|
64
|
-
return {
|
|
65
|
-
files: files.sort((a, b) => b.weight - a.weight).slice(0, 100),
|
|
66
|
-
totalTokens,
|
|
67
|
-
systemTokens: 0, // Pi provides this via ctx, not messages
|
|
68
|
-
historyTokens: totalTokens - fileTokens - toolTokens,
|
|
69
|
-
fileTokens,
|
|
70
|
-
toolTokens,
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
extractPath(toolName, input) {
|
|
74
|
-
if (toolName === "read" || toolName === "write" || toolName === "edit") {
|
|
75
|
-
return typeof input.path === "string" ? input.path : null;
|
|
76
|
-
}
|
|
77
|
-
if (toolName === "bash") {
|
|
78
|
-
// Simple regex for paths in bash commands (e.g., cat path/to/file)
|
|
79
|
-
const match = input.command?.match(/(?:cat|ls|rm|mv|cp|vi|nano)\s+([^\s;]+)/);
|
|
80
|
-
return match ? match[1] : null;
|
|
81
|
-
}
|
|
82
|
-
return null;
|
|
83
|
-
}
|
|
84
|
-
getOpType(toolName) {
|
|
85
|
-
switch (toolName) {
|
|
86
|
-
case "write":
|
|
87
|
-
return "write";
|
|
88
|
-
case "edit":
|
|
89
|
-
return "edit";
|
|
90
|
-
case "bash":
|
|
91
|
-
return "delete"; // Simplified; usually bash implies modification or deletion
|
|
92
|
-
default:
|
|
93
|
-
return "read";
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
calculateStatus(turn, currentTurn) {
|
|
97
|
-
const diff = currentTurn - turn;
|
|
98
|
-
if (diff <= 3)
|
|
99
|
-
return "active";
|
|
100
|
-
if (diff <= 10)
|
|
101
|
-
return "stale";
|
|
102
|
-
return "legacy";
|
|
103
|
-
}
|
|
104
|
-
findToolResult(messages, toolTurnIndex, toolId) {
|
|
105
|
-
// Look for the tool result immediately following the tool use
|
|
106
|
-
for (let i = toolTurnIndex + 1; i < messages.length; i++) {
|
|
107
|
-
if (messages[i].role === "tool" && messages[i].tool_call_id === toolId) {
|
|
108
|
-
return messages[i];
|
|
109
|
-
}
|
|
110
|
-
// If we hit another assistant turn, the result for this specific call is likely gone/compacted
|
|
111
|
-
if (messages[i].role === "assistant")
|
|
112
|
-
break;
|
|
113
|
-
}
|
|
114
|
-
return null;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
exports.ContextAnalyzer = ContextAnalyzer;
|
package/dist/generator.d.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ReportGenerator
|
|
3
|
-
* Generates a visual HTML dashboard based on the ContextMap.
|
|
4
|
-
*/
|
|
5
|
-
import type { ContextMap } from "./analyzer";
|
|
6
|
-
export declare class ReportGenerator {
|
|
7
|
-
static generateHTML(map: ContextMap): string;
|
|
8
|
-
static writeReport(html: string): string;
|
|
9
|
-
private static getOpIcon;
|
|
10
|
-
private static escapeHtml;
|
|
11
|
-
}
|
package/dist/generator.js
DELETED
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* ReportGenerator
|
|
4
|
-
* Generates a visual HTML dashboard based on the ContextMap.
|
|
5
|
-
*/
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.ReportGenerator = void 0;
|
|
8
|
-
const node_fs_1 = require("node:fs");
|
|
9
|
-
const node_path_1 = require("node:path");
|
|
10
|
-
const node_os_1 = require("node:os");
|
|
11
|
-
class ReportGenerator {
|
|
12
|
-
static generateHTML(map) {
|
|
13
|
-
const fileCards = map.files
|
|
14
|
-
.map((file) => `
|
|
15
|
-
<div class="file-card ${file.status}">
|
|
16
|
-
<div class="file-header">
|
|
17
|
-
<span class="file-path">${ReportGenerator.escapeHtml(file.path)}</span>
|
|
18
|
-
<span class="file-weight">${file.weight.toLocaleString()} tokens</span>
|
|
19
|
-
</div>
|
|
20
|
-
<div class="file-footer">
|
|
21
|
-
<span class="op-badge">${ReportGenerator.getOpIcon(file.lastOp.type)} ${file.lastOp.type}</span>
|
|
22
|
-
<span class="turn-badge">Turn ${file.lastOp.turn}</span>
|
|
23
|
-
<span class="status-text">${file.status.toUpperCase()}</span>
|
|
24
|
-
</div>
|
|
25
|
-
<div class="weight-bar">
|
|
26
|
-
<div class="weight-fill" style="width: ${Math.min(100, (file.weight / 1000) * 100)}%"></div>
|
|
27
|
-
</div>
|
|
28
|
-
</div>
|
|
29
|
-
`)
|
|
30
|
-
.join("");
|
|
31
|
-
const budgetPercent = (map.fileTokens / map.totalTokens) * 100;
|
|
32
|
-
return `
|
|
33
|
-
<!DOCTYPE html>
|
|
34
|
-
<html lang="en">
|
|
35
|
-
<head>
|
|
36
|
-
<meta charset="UTF-8">
|
|
37
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
38
|
-
<title>Pi Context Map</title>
|
|
39
|
-
<style>
|
|
40
|
-
:root {
|
|
41
|
-
--bg: #0f172a;
|
|
42
|
-
--card-bg: #1e293b;
|
|
43
|
-
--text: #f1f5f9;
|
|
44
|
-
--text-dim: #94a3b8;
|
|
45
|
-
--primary: #38bdf8;
|
|
46
|
-
--active: #22c55e;
|
|
47
|
-
--stale: #eab308;
|
|
48
|
-
--legacy: #ef4444;
|
|
49
|
-
--border: #334155;
|
|
50
|
-
}
|
|
51
|
-
body {
|
|
52
|
-
background: var(--bg);
|
|
53
|
-
color: var(--text);
|
|
54
|
-
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
55
|
-
margin: 0;
|
|
56
|
-
padding: 2rem;
|
|
57
|
-
line-height: 1.5;
|
|
58
|
-
}
|
|
59
|
-
.container { max-width: 1200px; margin: 0 auto; }
|
|
60
|
-
header { margin-bottom: 3rem; border-bottom: 1px solid var(--border); padding-bottom: 2rem; }
|
|
61
|
-
h1 { font-size: 2rem; margin: 0; color: var(--primary); }
|
|
62
|
-
.stats-grid {
|
|
63
|
-
display: grid;
|
|
64
|
-
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
65
|
-
gap: 1.5rem;
|
|
66
|
-
margin-top: 2rem;
|
|
67
|
-
}
|
|
68
|
-
.stat-card {
|
|
69
|
-
background: var(--card-bg);
|
|
70
|
-
padding: 1.5rem;
|
|
71
|
-
border-radius: 12px;
|
|
72
|
-
border: 1px solid var(--border);
|
|
73
|
-
text-align: center;
|
|
74
|
-
}
|
|
75
|
-
.stat-value { font-size: 1.5rem; font-weight: bold; display: block; }
|
|
76
|
-
.stat-label { color: var(--text-dim); font-size: 0.875rem; text-transform: uppercase; }
|
|
77
|
-
|
|
78
|
-
.budget-container {
|
|
79
|
-
margin: 2rem 0;
|
|
80
|
-
background: var(--card-bg);
|
|
81
|
-
padding: 1rem;
|
|
82
|
-
border-radius: 12px;
|
|
83
|
-
border: 1px solid var(--border);
|
|
84
|
-
}
|
|
85
|
-
.budget-bar {
|
|
86
|
-
height: 24px;
|
|
87
|
-
background: #020617;
|
|
88
|
-
border-radius: 12px;
|
|
89
|
-
display: flex;
|
|
90
|
-
overflow: hidden;
|
|
91
|
-
margin-bottom: 0.5rem;
|
|
92
|
-
}
|
|
93
|
-
.budget-segment { height: 100%; transition: width 0.3s ease; }
|
|
94
|
-
.seg-system { background: #6366f1; }
|
|
95
|
-
.seg-history { background: #a855f7; }
|
|
96
|
-
.seg-files { background: var(--primary); }
|
|
97
|
-
.seg-tools { background: #ec4899; }
|
|
98
|
-
|
|
99
|
-
.budget-legend {
|
|
100
|
-
display: flex;
|
|
101
|
-
gap: 1rem;
|
|
102
|
-
justify-content: center;
|
|
103
|
-
font-size: 0.75rem;
|
|
104
|
-
color: var(--text-dim);
|
|
105
|
-
}
|
|
106
|
-
.legend-item { display: flex; align-items: center; gap: 0.5rem; }
|
|
107
|
-
.dot { width: 8px; height: 8px; border-radius: 50%; }
|
|
108
|
-
|
|
109
|
-
.file-grid {
|
|
110
|
-
display: grid;
|
|
111
|
-
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
112
|
-
gap: 1rem;
|
|
113
|
-
}
|
|
114
|
-
.file-card {
|
|
115
|
-
background: var(--card-bg);
|
|
116
|
-
border: 1px solid var(--border);
|
|
117
|
-
border-radius: 12px;
|
|
118
|
-
padding: 1rem;
|
|
119
|
-
transition: transform 0.2s ease, border-color 0.2s ease;
|
|
120
|
-
display: flex;
|
|
121
|
-
flex-direction: column;
|
|
122
|
-
justify-content: space-between;
|
|
123
|
-
}
|
|
124
|
-
.file-card:hover { transform: translateY(-4px); border-color: var(--primary); }
|
|
125
|
-
.file-header {
|
|
126
|
-
display: flex;
|
|
127
|
-
justify-content: space-between;
|
|
128
|
-
align-items: flex-start;
|
|
129
|
-
margin-bottom: 1rem;
|
|
130
|
-
}
|
|
131
|
-
.file-path {
|
|
132
|
-
font-family: 'Fira Code', monospace;
|
|
133
|
-
font-size: 0.875rem;
|
|
134
|
-
word-break: break-all;
|
|
135
|
-
margin-right: 1rem;
|
|
136
|
-
color: var(--text);
|
|
137
|
-
}
|
|
138
|
-
.file-weight { font-size: 0.75rem; color: var(--text-dim); white-space: nowrap; }
|
|
139
|
-
.file-footer {
|
|
140
|
-
display: flex;
|
|
141
|
-
justify-content: space-between;
|
|
142
|
-
align-items: center;
|
|
143
|
-
margin-top: 1rem;
|
|
144
|
-
font-size: 0.75rem;
|
|
145
|
-
}
|
|
146
|
-
.op-badge {
|
|
147
|
-
background: #0f172a;
|
|
148
|
-
padding: 2px 6px;
|
|
149
|
-
border-radius: 4px;
|
|
150
|
-
color: var(--text-dim);
|
|
151
|
-
}
|
|
152
|
-
.turn-badge { color: var(--text-dim); }
|
|
153
|
-
.status-text { font-weight: bold; text-transform: uppercase; }
|
|
154
|
-
|
|
155
|
-
/* Status Colors */
|
|
156
|
-
.active { border-left: 4px solid var(--active); }
|
|
157
|
-
.active .status-text { color: var(--active); }
|
|
158
|
-
.stale { border-left: 4px solid var(--stale); }
|
|
159
|
-
.stale .status-text { color: var(--stale); }
|
|
160
|
-
.legacy { border-left: 4px solid var(--legacy); }
|
|
161
|
-
.legacy .status-text { color: var(--legacy); }
|
|
162
|
-
|
|
163
|
-
.weight-bar {
|
|
164
|
-
height: 4px;
|
|
165
|
-
background: #020617;
|
|
166
|
-
border-radius: 2px;
|
|
167
|
-
margin-top: 1rem;
|
|
168
|
-
overflow: hidden;
|
|
169
|
-
}
|
|
170
|
-
.weight-fill {
|
|
171
|
-
height: 100%;
|
|
172
|
-
background: var(--primary);
|
|
173
|
-
transition: width 0.3s ease;
|
|
174
|
-
}
|
|
175
|
-
</style>
|
|
176
|
-
</head>
|
|
177
|
-
<body>
|
|
178
|
-
<div class="container">
|
|
179
|
-
<header>
|
|
180
|
-
<h1>Pi Context Map</h1>
|
|
181
|
-
<p style="color: var(--text-dim)">Session context window visualization and token distribution.</p>
|
|
182
|
-
|
|
183
|
-
<div class="stats-grid">
|
|
184
|
-
<div class="stat-card">
|
|
185
|
-
<span class="stat-value">${map.totalTokens.toLocaleString()}</span>
|
|
186
|
-
<span class="stat-label">Total Tokens</span>
|
|
187
|
-
</div>
|
|
188
|
-
<div class="stat-card">
|
|
189
|
-
<span class="stat-value">${map.files.length}</span>
|
|
190
|
-
<span class="stat-label">Files in Context</span>
|
|
191
|
-
</div>
|
|
192
|
-
<div class="stat-card">
|
|
193
|
-
<span class="stat-value">${map.fileTokens.toLocaleString()}</span>
|
|
194
|
-
<span class="stat-label">File Tokens</span>
|
|
195
|
-
</div>
|
|
196
|
-
<div class="stat-card">
|
|
197
|
-
<span class="stat-value">${Math.round(budgetPercent)}%</span>
|
|
198
|
-
<span class="stat-label">File Load</span>
|
|
199
|
-
</div>
|
|
200
|
-
</div>
|
|
201
|
-
|
|
202
|
-
<div class="budget-container">
|
|
203
|
-
<div class="budget-bar">
|
|
204
|
-
<div class="budget-segment seg-system" style="width: ${(map.systemTokens / map.totalTokens) * 100 || 0}%"></div>
|
|
205
|
-
<div class="budget-segment seg-history" style="width: ${(map.historyTokens / map.totalTokens) * 100 || 0}%"></div>
|
|
206
|
-
<div class="budget-segment seg-files" style="width: ${(map.fileTokens / map.totalTokens) * 100 || 0}%"></div>
|
|
207
|
-
<div class="budget-segment seg-tools" style="width: ${(map.toolTokens / map.totalTokens) * 100 || 0}%"></div>
|
|
208
|
-
</div>
|
|
209
|
-
<div class="budget-legend">
|
|
210
|
-
<div class="legend-item"><span class="dot seg-system"></span> System</div>
|
|
211
|
-
<div class="legend-item"><span class="dot seg-history"></span> History</div>
|
|
212
|
-
<div class="legend-item"><span class="dot seg-files"></span> Files</div>
|
|
213
|
-
<div class="legend-item"><span class="dot seg-tools"></span> Tools</div>
|
|
214
|
-
</div>
|
|
215
|
-
</div>
|
|
216
|
-
</header>
|
|
217
|
-
|
|
218
|
-
<div class="file-grid">
|
|
219
|
-
${fileCards}
|
|
220
|
-
</div>
|
|
221
|
-
</div>
|
|
222
|
-
</body>
|
|
223
|
-
</html>
|
|
224
|
-
`;
|
|
225
|
-
}
|
|
226
|
-
static writeReport(html) {
|
|
227
|
-
const reportDir = (0, node_path_1.join)((0, node_os_1.homedir)(), ".pi", "context-map");
|
|
228
|
-
(0, node_fs_1.mkdirSync)(reportDir, { recursive: true });
|
|
229
|
-
const reportPath = (0, node_path_1.join)(reportDir, "report.html");
|
|
230
|
-
(0, node_fs_1.writeFileSync)(reportPath, html, "utf8");
|
|
231
|
-
return reportPath;
|
|
232
|
-
}
|
|
233
|
-
static getOpIcon(type) {
|
|
234
|
-
switch (type) {
|
|
235
|
-
case "read":
|
|
236
|
-
return "👁️";
|
|
237
|
-
case "write":
|
|
238
|
-
return "📝";
|
|
239
|
-
case "edit":
|
|
240
|
-
return "✍️";
|
|
241
|
-
case "delete":
|
|
242
|
-
return "🗑️";
|
|
243
|
-
default:
|
|
244
|
-
return "📄";
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
static escapeHtml(text) {
|
|
248
|
-
const map = {
|
|
249
|
-
"&": "&",
|
|
250
|
-
"<": "<",
|
|
251
|
-
">": ">",
|
|
252
|
-
'"': """,
|
|
253
|
-
"'": "'",
|
|
254
|
-
};
|
|
255
|
-
return text.replace(/[&<>"']/g, (m) => map[m]);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
exports.ReportGenerator = ReportGenerator;
|
package/dist/index.d.ts
DELETED
package/dist/index.js
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* pi-context-map
|
|
4
|
-
* Pi extension to visualize session context window and token distribution.
|
|
5
|
-
*/
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.default = default_1;
|
|
8
|
-
const analyzer_1 = require("./analyzer");
|
|
9
|
-
const generator_1 = require("./generator");
|
|
10
|
-
function default_1(pi) {
|
|
11
|
-
const analyzer = new analyzer_1.ContextAnalyzer();
|
|
12
|
-
// Register the /context-map command
|
|
13
|
-
pi.registerCommand("context-map", {
|
|
14
|
-
description: "Generate a visual map of the current session context window.",
|
|
15
|
-
handler: (_args, ctx) => {
|
|
16
|
-
ctx.ui.notify("Analyzing session context...", "info");
|
|
17
|
-
try {
|
|
18
|
-
// 1. Extract messages and current turn
|
|
19
|
-
// Note: We assume ctx.session.messages is available.
|
|
20
|
-
// If not, we may need to fetch them via another API or use provided event data.
|
|
21
|
-
const messages = ctx.session.messages || [];
|
|
22
|
-
const currentTurn = messages.length;
|
|
23
|
-
if (messages.length === 0) {
|
|
24
|
-
ctx.ui.notify("No session history found to map.", "warning");
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
// 2. Analyze context
|
|
28
|
-
const map = analyzer.analyze(messages, currentTurn);
|
|
29
|
-
// 3. Generate HTML Report
|
|
30
|
-
const html = generator_1.ReportGenerator.generateHTML(map);
|
|
31
|
-
const reportPath = generator_1.ReportGenerator.writeReport(html);
|
|
32
|
-
ctx.ui.notify(`Context map generated successfully! \nPath: ${reportPath}`, "success");
|
|
33
|
-
// Providing a link or instruction to open the report
|
|
34
|
-
ctx.ui.notify("You can open the report.html in your browser to see the visualization.", "info");
|
|
35
|
-
}
|
|
36
|
-
catch (error) {
|
|
37
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
38
|
-
ctx.ui.notify(`Failed to generate context map: ${message}`, "error");
|
|
39
|
-
}
|
|
40
|
-
},
|
|
41
|
-
});
|
|
42
|
-
// Optional: Notify the user when a significant amount of context is loaded
|
|
43
|
-
pi.on("session_before_compact", (event, ctx) => {
|
|
44
|
-
const { preparation } = event;
|
|
45
|
-
const tokens = preparation.tokensBefore;
|
|
46
|
-
if (tokens > 100_000) {
|
|
47
|
-
ctx.ui.notify(`High context load detected (${(tokens / 1000).toFixed(1)}k tokens). Try /context-map to see what's consuming space.`, "info");
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
}
|
package/docs/proposal.md
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
# Proposal: pi-context-map
|
|
2
|
-
|
|
3
|
-
## 1. Problem Statement
|
|
4
|
-
As Pi sessions grow in complexity, the "Context Window" becomes a black box. Users and agents often lose track of:
|
|
5
|
-
- Which files are currently consuming the most tokens.
|
|
6
|
-
- When a file was last "refreshed" (read) by the agent.
|
|
7
|
-
- The distribution of tokens between history, system prompts, and tool outputs.
|
|
8
|
-
|
|
9
|
-
This leads to "context bloat," where the agent becomes sluggish or forgets critical instructions because the window is filled with stale file content.
|
|
10
|
-
|
|
11
|
-
## 2. Goal
|
|
12
|
-
Create a Pi extension that provides a **real-time, visual map of the current session's context**. It should transform the abstract concept of a "token window" into a concrete, actionable dashboard.
|
|
13
|
-
|
|
14
|
-
## 3. Core Features
|
|
15
|
-
### A. `/context-map` Command
|
|
16
|
-
A command that generates a visual report of the current context.
|
|
17
|
-
|
|
18
|
-
### B. Context Analysis
|
|
19
|
-
- **File Inventory**: List all files currently in context.
|
|
20
|
-
- **Weight Tracking**: Approximate token count per file.
|
|
21
|
-
- **Status Mapping**:
|
|
22
|
-
- `Active`: Read/Modified in the last 3 turns.
|
|
23
|
-
- `Stale`: Read 4-10 turns ago.
|
|
24
|
-
- `Legacy`: Read >10 turns ago (candidate for compaction).
|
|
25
|
-
- **Operation History**: Mark if a file was Read 🟢, Written 🟠, or Edited 🟡.
|
|
26
|
-
|
|
27
|
-
### C. Visual Output
|
|
28
|
-
The extension will generate an `index.html` report (stored in `.pi/context-map/report.html`) featuring:
|
|
29
|
-
- **Token Budget Bar**: Visual breakdown of context usage.
|
|
30
|
-
- **File Treemap/List**: Files sized by their token weight.
|
|
31
|
-
- **Temporal Timeline**: A simple timeline showing when files entered the context.
|
|
32
|
-
|
|
33
|
-
## 4. Success Criteria
|
|
34
|
-
- The user can run `/context-map` and immediately see which file is the "token hog."
|
|
35
|
-
- The user can identify "stale" files that can be removed to free up space.
|
|
36
|
-
- Zero performance degradation during normal session operation.
|
|
37
|
-
- Full compatibility with `pi-ultra-compact` (since both manage context).
|
|
38
|
-
|
|
39
|
-
## 5. Non-Goals
|
|
40
|
-
- This is a *visualization* tool, not an *automatic* context cleaner (though it provides the data needed for a user to trigger compaction).
|
|
41
|
-
- It will not modify the actual LLM context window, only report on it.
|
package/docs/spec.md
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
# Technical Specification: pi-context-map
|
|
2
|
-
|
|
3
|
-
## 1. Architecture Overview
|
|
4
|
-
`pi-context-map` is a Pi extension that analyzes the session message history to derive a "map" of the active context. It operates as a read-only analysis tool triggered by a user command.
|
|
5
|
-
|
|
6
|
-
## 2. Data Extraction Logic
|
|
7
|
-
When `/context-map` is called, the extension will:
|
|
8
|
-
1. **Scan Messages**: Iterate through all messages in the current session history.
|
|
9
|
-
2. **Identify File Ops**:
|
|
10
|
-
- Scan `tool_use` blocks for `read`, `write`, `edit`, and `bash` (regex for file paths).
|
|
11
|
-
- Map each file to its most recent occurrence (turn number).
|
|
12
|
-
3. **Calculate Weights**:
|
|
13
|
-
- For each file found, retrieve its content length from the corresponding `tool_result` if available.
|
|
14
|
-
- Estimate tokens using a heuristic: $\text{tokens} \approx \text{chars} / 4$.
|
|
15
|
-
4. **Assign Status**:
|
|
16
|
-
- **Active**: Turn difference $\le 3$.
|
|
17
|
-
- **Stale**: $3 <$ Turn difference $\le 10$.
|
|
18
|
-
- **Legacy**: Turn difference $> 10$.
|
|
19
|
-
|
|
20
|
-
## 3. Component Design
|
|
21
|
-
|
|
22
|
-
### A. `ContextAnalyzer` (Class)
|
|
23
|
-
- `analyze(messages: Message[])`: Returns a `ContextMap` object.
|
|
24
|
-
- `calculateTokens(text: string)`: Returns estimated token count.
|
|
25
|
-
- `getFileMetadata(path: string)`: Tracks the operation type and timestamp.
|
|
26
|
-
|
|
27
|
-
### B. `ReportGenerator` (Class)
|
|
28
|
-
- `generateHTML(map: ContextMap)`: Produces a standalone HTML string.
|
|
29
|
-
- `writeReport(html: string)`: Saves the report to `.pi/context-map/report.html` and opens it.
|
|
30
|
-
|
|
31
|
-
### C. `ExtensionEntry` (Main)
|
|
32
|
-
- `pi.registerCommand("context-map", ...)`: The entry point.
|
|
33
|
-
- `pi.on("session_before_compact", ...)`: (Optional) Could trigger a map update before compaction.
|
|
34
|
-
|
|
35
|
-
## 4. Visual Specification (HTML Dashboard)
|
|
36
|
-
The report will be a single-file HTML dashboard with:
|
|
37
|
-
- **Header**: Session ID, Total Estimated Tokens, and Timestamp.
|
|
38
|
-
- **Context Budget**: A CSS-based stacked bar showing:
|
|
39
|
-
- `[ System ] [ History ] [ Files ] [ Tool Outputs ]`
|
|
40
|
-
- **File Grid**:
|
|
41
|
-
- Cards for each file.
|
|
42
|
-
- Size proportional to token weight.
|
|
43
|
-
- Color-coded by status (Green $\to$ Yellow $\to$ Red).
|
|
44
|
-
- Icons for operation type (👁️ for read, ✍️ for edit).
|
|
45
|
-
- **Stats Table**:
|
|
46
|
-
- File Path | Tokens | Last Turn | Status.
|
|
47
|
-
|
|
48
|
-
## 5. Implementation Details
|
|
49
|
-
- **Language**: TypeScript.
|
|
50
|
-
- **Dependencies**: `@earendil-works/pi-coding-agent`, `node:fs`, `node:path`.
|
|
51
|
-
- **Complexity**:
|
|
52
|
-
- Time: $O(N)$ where $N$ is number of messages.
|
|
53
|
-
- Space: $O(F)$ where $F$ is number of unique files in context.
|
|
54
|
-
|
|
55
|
-
## 6. Error Handling
|
|
56
|
-
- **Missing Tool Results**: If a file was read but the result is missing (e.g., due to previous compaction), mark weight as "Unknown" and status as "Stale".
|
|
57
|
-
- **Large Repos**: Limit the map to the top 100 largest files to prevent the HTML report from crashing the browser.
|
package/tasks.md
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
# Implementation Tasks: pi-context-map
|
|
2
|
-
|
|
3
|
-
## Phase 1: Foundation
|
|
4
|
-
- [ ] Initialize `package.json` and TypeScript config.
|
|
5
|
-
- [ ] Create project folder structure (`src/`, `docs/`).
|
|
6
|
-
|
|
7
|
-
## Phase 2: Core Logic (`ContextAnalyzer`)
|
|
8
|
-
- [ ] Implement message scanning logic to find file operations.
|
|
9
|
-
- [ ] Implement token estimation heuristic.
|
|
10
|
-
- [ ] Implement status assignment (Active/Stale/Legacy).
|
|
11
|
-
- [ ] Create unit tests for analyzer logic.
|
|
12
|
-
|
|
13
|
-
## Phase 3: Visualization (`ReportGenerator`)
|
|
14
|
-
- [ ] Design the HTML dashboard template.
|
|
15
|
-
- [ ] Implement data-to-HTML mapping.
|
|
16
|
-
- [ ] Implement file writing to `.pi/context-map/report.html`.
|
|
17
|
-
|
|
18
|
-
## Phase 4: Pi Integration
|
|
19
|
-
- [ ] Register `/context-map` command.
|
|
20
|
-
- [ ] Implement command handler that triggers analysis $\to$ report $\to$ notification.
|
|
21
|
-
- [ ] Add "Open Report" link in the Pi notification.
|
|
22
|
-
|
|
23
|
-
## Phase 5: QA & Polishing
|
|
24
|
-
- [ ] Test with high-token sessions.
|
|
25
|
-
- [ ] Verify accuracy of token weights.
|
|
26
|
-
- [ ] Run Pi Lens diagnostics to ensure zero blockers.
|
|
27
|
-
- [ ] Polish HTML CSS for "Architect" look.
|
|
28
|
-
|
|
29
|
-
## Phase 6: Release
|
|
30
|
-
- [ ] Update README.md.
|
|
31
|
-
- [ ] Publish to npm.
|
|
32
|
-
- [ ] Final GitHub release.
|
package/tsconfig.json
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "NodeNext",
|
|
5
|
-
"moduleResolution": "NodeNext",
|
|
6
|
-
"outDir": "./dist",
|
|
7
|
-
"rootDir": "./src",
|
|
8
|
-
"strict": true,
|
|
9
|
-
"esModuleInterop": true,
|
|
10
|
-
"skipLibCheck": true,
|
|
11
|
-
"forceConsistentCasingInFileNames": true,
|
|
12
|
-
"declaration": true,
|
|
13
|
-
"baseUrl": ".",
|
|
14
|
-
"paths": {
|
|
15
|
-
"*": ["src/types/*"]
|
|
16
|
-
}
|
|
17
|
-
},
|
|
18
|
-
"include": ["src/**/*"],
|
|
19
|
-
"typeRoots": ["./node_modules/@types", "src/types"]
|
|
20
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|