context-mcp-server 1.0.6 → 1.0.8
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 +98 -359
- package/codegraph/server.py +37 -46
- package/package.json +4 -3
- package/pyproject.toml +1 -1
- package/src/assests/main.png +0 -0
- package/src/cli.js +69 -57
- package/src/db.js +946 -798
- package/src/guard.js +9 -3
- package/src/migrator.js +124 -0
- package/src/server.js +7 -6
- package/src/templates/AGENTS.md +6 -13
- package/src/templates/CLAUDE.md +6 -12
- package/src/templates/GEMINI.md +6 -13
- package/src/templates/commands/context-resume.md +1 -1
- package/src/templates/commands/save-context.md +1 -1
- package/src/templates/cursor-rules.mdc +3 -3
- package/src/templates/skills/SKILL.md +9 -16
- package/src/templates/windsurf-rules.md +3 -3
- package/src/tools/codegraph.js +8 -77
- package/src/tools/context.js +23 -11
- package/src/tools/gitTools.js +1 -3
- package/src/tools/plan.js +130 -0
- package/uv.lock +1 -1
- package/codegraph/extractors/audio_extractor.py +0 -8
- package/codegraph/extractors/doc_extractor.py +0 -34
- package/codegraph/extractors/image_extractor.py +0 -26
- package/src/tools/discussion.js +0 -123
package/codegraph/server.py
CHANGED
|
@@ -4,8 +4,7 @@ codegraph/server.py — MCP server exposing codebase knowledge graph tools.
|
|
|
4
4
|
|
|
5
5
|
Tools:
|
|
6
6
|
codegraph_build — scan project, extract AST nodes, build graph (local only, no API)
|
|
7
|
-
codegraph_query —
|
|
8
|
-
codegraph_explain — look up a specific node: type, file, connections
|
|
7
|
+
codegraph_query — structural question OR single-node lookup (or both); replaces codegraph_explain
|
|
9
8
|
codegraph_report — return full CODEGRAPH_REPORT.md
|
|
10
9
|
codegraph_nodes — list nodes of a given type
|
|
11
10
|
codegraph_path — shortest path between two concepts
|
|
@@ -56,36 +55,21 @@ TOOLS = [
|
|
|
56
55
|
Tool(
|
|
57
56
|
name="codegraph_query",
|
|
58
57
|
description=(
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
58
|
+
"Ask a structural question about the codebase OR look up a specific node by name — or both in one call. "
|
|
59
|
+
"Pass `question` for natural-language traversal: what calls X, what does module Y depend on. "
|
|
60
|
+
"Pass `node` for fast single-node lookup: returns type, file, depends_on, used_by. "
|
|
61
|
+
"Pass both to get node detail + surrounding graph context together. "
|
|
62
|
+
"Returns structured text within token_budget. Use before reading any files."
|
|
64
63
|
),
|
|
65
64
|
inputSchema={
|
|
66
65
|
"type": "object",
|
|
67
66
|
"properties": {
|
|
68
67
|
"path": {"type": "string", "description": "Project root"},
|
|
69
|
-
"question": {"type": "string", "description": "Natural language question"},
|
|
68
|
+
"question": {"type": "string", "description": "Natural language question about the codebase"},
|
|
69
|
+
"node": {"type": "string", "description": "Node name or partial name to look up (type, file, deps, callers)"},
|
|
70
70
|
"token_budget": {"type": "integer", "description": "Max tokens in response (default 2000)"},
|
|
71
71
|
},
|
|
72
|
-
"required": ["path"
|
|
73
|
-
},
|
|
74
|
-
),
|
|
75
|
-
Tool(
|
|
76
|
-
name="codegraph_explain",
|
|
77
|
-
description=(
|
|
78
|
-
"Look up a specific function, class, or module by name — returns its type, file location, "
|
|
79
|
-
"and all direct connections (what it depends on, what uses it). "
|
|
80
|
-
"Use when you already know the name and want its full context in the graph."
|
|
81
|
-
),
|
|
82
|
-
inputSchema={
|
|
83
|
-
"type": "object",
|
|
84
|
-
"properties": {
|
|
85
|
-
"path": {"type": "string", "description": "Project root"},
|
|
86
|
-
"node": {"type": "string", "description": "Node name or partial name"},
|
|
87
|
-
},
|
|
88
|
-
"required": ["path", "node"],
|
|
72
|
+
"required": ["path"],
|
|
89
73
|
},
|
|
90
74
|
),
|
|
91
75
|
Tool(
|
|
@@ -143,7 +127,7 @@ async def call_tool(name: str, arguments: dict):
|
|
|
143
127
|
async def _dispatch(name: str, args: dict):
|
|
144
128
|
if name == "codegraph_build": return await _build(args)
|
|
145
129
|
if name == "codegraph_query": return await _query(args)
|
|
146
|
-
if name == "codegraph_explain": return await
|
|
130
|
+
if name == "codegraph_explain": return await _query(args)
|
|
147
131
|
if name == "codegraph_report": return await _report(args)
|
|
148
132
|
if name == "codegraph_nodes": return await _nodes(args)
|
|
149
133
|
if name == "codegraph_path": return await _path(args)
|
|
@@ -223,19 +207,8 @@ async def _build(args: dict) -> dict:
|
|
|
223
207
|
|
|
224
208
|
# ── Query / Report / Nodes / Path ─────────────────────────────────────────────
|
|
225
209
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
if not graph_dict:
|
|
229
|
-
raise ValueError("No graph found. Run codegraph_build first.")
|
|
230
|
-
return graph_answer(args["question"], graph_dict, token_budget=args.get("token_budget", 2000))
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
async def _explain(args: dict) -> dict:
|
|
234
|
-
graph_dict = load_graph(args["path"])
|
|
235
|
-
if not graph_dict:
|
|
236
|
-
raise ValueError("No graph found. Run codegraph_build first.")
|
|
237
|
-
|
|
238
|
-
query = args["node"].lower()
|
|
210
|
+
def _explain_node(node_name: str, graph_dict: dict) -> dict:
|
|
211
|
+
query = node_name.lower()
|
|
239
212
|
nodes = graph_dict.get("nodes", [])
|
|
240
213
|
edges = graph_dict.get("edges", [])
|
|
241
214
|
|
|
@@ -244,8 +217,8 @@ async def _explain(args: dict) -> dict:
|
|
|
244
217
|
match = next((n for n in nodes if query in n.get("name", "").lower()), None)
|
|
245
218
|
if not match:
|
|
246
219
|
candidates = [n["name"] for n in nodes if query in n.get("id", "").lower()]
|
|
247
|
-
return {"found": False, "query":
|
|
248
|
-
"message": f"No node matching '{
|
|
220
|
+
return {"found": False, "query": node_name,
|
|
221
|
+
"message": f"No node matching '{node_name}'.",
|
|
249
222
|
"suggestions": candidates[:10]}
|
|
250
223
|
|
|
251
224
|
nid = match["id"]
|
|
@@ -254,13 +227,13 @@ async def _explain(args: dict) -> dict:
|
|
|
254
227
|
if e.get("from") == nid:
|
|
255
228
|
t = next((n for n in nodes if n.get("id") == e.get("to")), None)
|
|
256
229
|
depends_on.append({"name": t["name"] if t else e["to"],
|
|
257
|
-
"file": t.get("file","") if t else "",
|
|
258
|
-
"relation": e.get("relation","→")})
|
|
230
|
+
"file": t.get("file", "") if t else "",
|
|
231
|
+
"relation": e.get("relation", "→")})
|
|
259
232
|
elif e.get("to") == nid:
|
|
260
233
|
s = next((n for n in nodes if n.get("id") == e.get("from")), None)
|
|
261
234
|
used_by.append({"name": s["name"] if s else e["from"],
|
|
262
|
-
"file": s.get("file","") if s else "",
|
|
263
|
-
"relation": e.get("relation","→")})
|
|
235
|
+
"file": s.get("file", "") if s else "",
|
|
236
|
+
"relation": e.get("relation", "→")})
|
|
264
237
|
|
|
265
238
|
return {
|
|
266
239
|
"found": True,
|
|
@@ -270,10 +243,28 @@ async def _explain(args: dict) -> dict:
|
|
|
270
243
|
"description": match.get("description") or None,
|
|
271
244
|
"depends_on": depends_on[:20],
|
|
272
245
|
"used_by": used_by[:20],
|
|
273
|
-
"hint": None,
|
|
274
246
|
}
|
|
275
247
|
|
|
276
248
|
|
|
249
|
+
async def _query(args: dict) -> dict:
|
|
250
|
+
graph_dict = load_graph(args["path"])
|
|
251
|
+
if not graph_dict:
|
|
252
|
+
raise ValueError("No graph found. Run codegraph_build first.")
|
|
253
|
+
|
|
254
|
+
question = args.get("question")
|
|
255
|
+
node_name = args.get("node")
|
|
256
|
+
|
|
257
|
+
if not question and not node_name:
|
|
258
|
+
raise ValueError("Provide at least one of: question, node")
|
|
259
|
+
|
|
260
|
+
result = {}
|
|
261
|
+
if node_name:
|
|
262
|
+
result["node"] = _explain_node(node_name, graph_dict)
|
|
263
|
+
if question:
|
|
264
|
+
result["query"] = graph_answer(question, graph_dict, token_budget=args.get("token_budget", 2000))
|
|
265
|
+
return result
|
|
266
|
+
|
|
267
|
+
|
|
277
268
|
async def _report(args: dict) -> dict:
|
|
278
269
|
report_path = Path(args["path"]) / "codegraph-cache" / "CODEGRAPH_REPORT.md"
|
|
279
270
|
if report_path.exists():
|
package/package.json
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "context-mcp-server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"description": "Persistent AI memory + codebase knowledge graph MCP server. Works across Claude Code, Cursor, Gemini CLI, Codex, Windsurf, VS Code Copilot, Claude.ai, and ChatGPT.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"context-mcp": "./src/index.js",
|
|
8
8
|
"context-mcp-server": "./src/index.js",
|
|
9
9
|
"context-mcp-http": "./src/http.js",
|
|
10
|
-
"ctx": "./src/cli.js"
|
|
10
|
+
"ctx": "./src/cli.js",
|
|
11
|
+
"context": "./src/cli.js"
|
|
11
12
|
},
|
|
12
13
|
"scripts": {
|
|
13
14
|
"mcp": "node src/index.js",
|
|
14
15
|
"mcp-server": "node src/http.js",
|
|
15
16
|
"cli": "node src/cli.js",
|
|
16
|
-
"check": "node --check src/index.js && node --check src/server.js && node --check src/db.js && node --check src/vector.js && node --check src/summarizer.js && node --check src/search.js && node --check src/config.js && node --check src/cli.js && node --check src/http.js && node --check src/tools/context.js && node --check src/tools/search.js && node --check src/tools/
|
|
17
|
+
"check": "node --check src/index.js && node --check src/server.js && node --check src/db.js && node --check src/vector.js && node --check src/summarizer.js && node --check src/search.js && node --check src/config.js && node --check src/cli.js && node --check src/http.js && node --check src/tools/context.js && node --check src/tools/search.js && node --check src/tools/plan.js &&node --check src/tools/errorCheck.js && node --check src/tools/fileTools.js && node --check src/tools/gitTools.js && node --check src/tools/codegraph.js && node --check src/hooks/autoLink.js && node --check src/hooks/autoContext.js",
|
|
17
18
|
"check-mcp": "npm run check",
|
|
18
19
|
"test": "node --test",
|
|
19
20
|
"prepublishOnly": "npm run check"
|
package/pyproject.toml
CHANGED
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "codegraph-mcp"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.8"
|
|
8
8
|
description = "Codebase knowledge graph MCP server — AST extraction, graph queries, community detection"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
Binary file
|
package/src/cli.js
CHANGED
|
@@ -107,8 +107,8 @@ function printSection(title, meta = '') {
|
|
|
107
107
|
function printUsage() {
|
|
108
108
|
printBanner();
|
|
109
109
|
|
|
110
|
-
// Terminal commands (ctx ...)
|
|
111
|
-
printSection('Terminal commands', 'run from your shell');
|
|
110
|
+
// Terminal commands (ctx / context ...)
|
|
111
|
+
printSection('Terminal commands', 'run from your shell (ctx … or context …)');
|
|
112
112
|
const cmd = (c, desc) => console.log(` ${accent(c.padEnd(40))} ${faint(desc)}`);
|
|
113
113
|
cmd('ctx', 'open interactive mode');
|
|
114
114
|
cmd('ctx list [project]', 'list entries + discussions + graphs');
|
|
@@ -131,8 +131,8 @@ function printUsage() {
|
|
|
131
131
|
cmd('ctx help', 'show this screen');
|
|
132
132
|
console.log('');
|
|
133
133
|
|
|
134
|
-
// Interactive mode commands (no
|
|
135
|
-
printSection('Interactive mode', 'type these inside
|
|
134
|
+
// Interactive mode commands (no prefix needed)
|
|
135
|
+
printSection('Interactive mode', 'type these inside the UI — no "ctx" prefix needed');
|
|
136
136
|
const icmd = (c, desc) => console.log(` ${accent(c.padEnd(40))} ${faint(desc)}`);
|
|
137
137
|
icmd('list [project]', 'list entries');
|
|
138
138
|
icmd('search <query>', 'search context');
|
|
@@ -163,17 +163,20 @@ function cmdList(args) {
|
|
|
163
163
|
|
|
164
164
|
printSection('Context', filterProject ? `project: ${filterProject}` : 'all projects');
|
|
165
165
|
|
|
166
|
-
// Build per-project map
|
|
166
|
+
// Build per-project map, split entries into their three trees
|
|
167
167
|
const projects = {};
|
|
168
|
+
const ensureProj = p => {
|
|
169
|
+
if (!projects[p]) projects[p] = { context: [], summary: [], plans: [] };
|
|
170
|
+
return projects[p];
|
|
171
|
+
};
|
|
168
172
|
for (const entry of entries) {
|
|
169
173
|
const p = entry.project || 'global';
|
|
170
|
-
|
|
171
|
-
|
|
174
|
+
const d = ensureProj(p);
|
|
175
|
+
if (entry.type === 'compaction') d.summary.push(entry);
|
|
176
|
+
else d.context.push(entry);
|
|
172
177
|
}
|
|
173
178
|
for (const disc of allDiscussions) {
|
|
174
|
-
|
|
175
|
-
if (!projects[p]) projects[p] = { contexts: [], discussions: [] };
|
|
176
|
-
projects[p].discussions.push(disc);
|
|
179
|
+
ensureProj(disc.project || 'global').plans.push(disc);
|
|
177
180
|
}
|
|
178
181
|
|
|
179
182
|
const projectNames = Object.keys(projects).sort();
|
|
@@ -185,73 +188,82 @@ function cmdList(args) {
|
|
|
185
188
|
}
|
|
186
189
|
|
|
187
190
|
for (const projectName of projectNames) {
|
|
188
|
-
const pData
|
|
189
|
-
const
|
|
190
|
-
const
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
191
|
+
const pData = projects[projectName];
|
|
192
|
+
const graphBuild = _graphForProject(allGraphs, projectName);
|
|
193
|
+
const totalEntries = pData.context.length + pData.summary.length;
|
|
194
|
+
const activePlans = pData.plans.filter(p => p.status === 'active').length;
|
|
195
|
+
const sections = [
|
|
196
|
+
graphBuild && 'graph',
|
|
197
|
+
pData.context.length && 'context',
|
|
198
|
+
pData.summary.length && 'summary',
|
|
199
|
+
pData.plans.length && 'plans',
|
|
200
|
+
].filter(Boolean);
|
|
201
|
+
let secIdx = 0;
|
|
202
|
+
|
|
203
|
+
const projReg = projectRegistry.get(projectName);
|
|
195
204
|
const projIdStr = projReg?.id ? faint(' id:' + projReg.id.slice(0, 8)) : '';
|
|
196
|
-
console.log(`\n ${color(C.dblue, '◆')} ${bold(lblue(projectName))}${projIdStr} ${faint(`${
|
|
205
|
+
console.log(`\n ${color(C.dblue, '◆')} ${bold(lblue(projectName))}${projIdStr} ${faint(`${totalEntries} entries · ${pData.plans.length} plans`)}${activePlans ? ` ${warn('● ' + activePlans + ' active')}` : ''}`);
|
|
197
206
|
console.log(` ${color(C.darkgray, '│')}`);
|
|
198
207
|
|
|
199
|
-
|
|
200
|
-
|
|
208
|
+
const renderEntries = (items, label, secIsLast) => {
|
|
209
|
+
console.log(` ${color(C.darkgray, secIsLast ? '└─' : '├─')} ${muted(label)} ${faint(items.length + ' entries')}`);
|
|
210
|
+
items.forEach((item, i) => {
|
|
211
|
+
const br = i === items.length - 1 ? '└─' : '├─';
|
|
212
|
+
const date = (item.createdAt || '').slice(0, 10);
|
|
213
|
+
const id = item.id.slice(0, 8);
|
|
214
|
+
const tags = safeTags(item.tags);
|
|
215
|
+
const pipe = secIsLast ? ' ' : '│';
|
|
216
|
+
console.log(` ${color(C.darkgray, pipe)} ${color(C.darkgray, br)} ${pill(item.type || 'note')} ${bold(item.title || '(no title)')} ${faint('id:' + id)} ${faint(date)}`);
|
|
217
|
+
if (tags.length) console.log(` ${color(C.darkgray, pipe)} ${faint(tags.map(t => '#' + t).join(' '))}`);
|
|
218
|
+
});
|
|
219
|
+
if (!secIsLast) console.log(` ${color(C.darkgray, '│')}`);
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
// ── Graph (build stats only) ──────────────────────────────────────────────
|
|
223
|
+
if (graphBuild) {
|
|
201
224
|
secIdx++;
|
|
202
|
-
const isLast = secIdx ===
|
|
203
|
-
const builtAt = (
|
|
204
|
-
console.log(` ${color(C.darkgray, isLast ? '└─' : '├─')} ${accent('⬡')} ${muted('graph')} ${faint(`${
|
|
225
|
+
const isLast = secIdx === sections.length;
|
|
226
|
+
const builtAt = (graphBuild.builtAt || '').slice(0, 10);
|
|
227
|
+
console.log(` ${color(C.darkgray, isLast ? '└─' : '├─')} ${accent('⬡')} ${muted('graph')} ${faint(`${graphBuild.nodes}n · ${graphBuild.edges}e · ${graphBuild.communities} clusters · ${builtAt}`)}`);
|
|
205
228
|
if (!isLast) console.log(` ${color(C.darkgray, '│')}`);
|
|
206
229
|
}
|
|
207
230
|
|
|
208
|
-
// ── Context
|
|
209
|
-
if (pData.
|
|
231
|
+
// ── Context ───────────────────────────────────────────────────────────────
|
|
232
|
+
if (pData.context.length) {
|
|
210
233
|
secIdx++;
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
234
|
+
renderEntries(pData.context, 'context', secIdx === sections.length);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ── Summary (compaction digests only — no type labels) ───────────────────
|
|
238
|
+
if (pData.summary.length) {
|
|
239
|
+
secIdx++;
|
|
240
|
+
const isLast = secIdx === sections.length;
|
|
241
|
+
console.log(` ${color(C.darkgray, isLast ? '└─' : '├─')} ${muted('summary')} ${faint(pData.summary.length + ' compactions')}`);
|
|
242
|
+
pData.summary.forEach((item, i) => {
|
|
243
|
+
const br = i === pData.summary.length - 1 ? '└─' : '├─';
|
|
215
244
|
const date = (item.createdAt || '').slice(0, 10);
|
|
216
|
-
const type = item.type || 'note';
|
|
217
|
-
const id = item.id.slice(0, 8);
|
|
218
|
-
const tags = safeTags(item.tags);
|
|
219
245
|
const pipe = isLast ? ' ' : '│';
|
|
220
|
-
console.log(` ${color(C.darkgray, pipe)} ${color(C.darkgray, br)} ${
|
|
221
|
-
if (tags.length) console.log(` ${color(C.darkgray, pipe)} ${faint(tags.map(t => '#' + t).join(' '))}`);
|
|
246
|
+
console.log(` ${color(C.darkgray, pipe)} ${color(C.darkgray, br)} ${faint('◎')} ${bold(item.title || '(compaction)')} ${faint(date)}`);
|
|
222
247
|
});
|
|
223
248
|
if (!isLast) console.log(` ${color(C.darkgray, '│')}`);
|
|
224
249
|
}
|
|
225
250
|
|
|
226
|
-
// ──
|
|
227
|
-
if (pData.
|
|
251
|
+
// ── Plans ─────────────────────────────────────────────────────────────────
|
|
252
|
+
if (pData.plans.length) {
|
|
228
253
|
secIdx++;
|
|
229
|
-
const isLast = secIdx ===
|
|
230
|
-
console.log(` ${color(C.darkgray, isLast ? '└─' : '├─')} ${muted('
|
|
231
|
-
pData.
|
|
232
|
-
const br
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
const pipe = isLast ? ' ' : '│';
|
|
236
|
-
console.log(` ${color(C.darkgray, pipe)} ${color(C.darkgray, br)} ${warn(disc.status === 'active' ? '●' : '○')} ${bold(disc.name)} ${pill(disc.status, sc)} ${faint(disc.type || 'plan')}${steps}`);
|
|
237
|
-
if (disc.description) console.log(` ${color(C.darkgray, pipe)} ${faint(disc.description)}`);
|
|
254
|
+
const isLast = secIdx === sections.length;
|
|
255
|
+
console.log(` ${color(C.darkgray, isLast ? '└─' : '├─')} ${muted('plans')} ${faint(pData.plans.length + ' total')}`);
|
|
256
|
+
pData.plans.forEach((plan, i) => {
|
|
257
|
+
const br = i === pData.plans.length - 1 ? '└─' : '├─';
|
|
258
|
+
const pipe = isLast ? ' ' : '│';
|
|
259
|
+
console.log(` ${color(C.darkgray, pipe)} ${color(C.darkgray, br)} ${warn(plan.status === 'active' ? '●' : '○')} ${bold(plan.name)} ${pill(plan.status, plan.status === 'done' ? 'green' : 'tcyan')} ${faint((plan.description || '').slice(0, 60))}`);
|
|
238
260
|
});
|
|
239
261
|
}
|
|
240
262
|
}
|
|
241
263
|
|
|
242
|
-
// Orphan graphs (no matching project)
|
|
243
|
-
const orphanGraphs = allGraphs.filter(g => !projectNames.some(p => _graphForProject([g], p)));
|
|
244
|
-
if (orphanGraphs.length) {
|
|
245
|
-
console.log(`\n ${color(C.dblue, '◇')} ${muted('other graphs')}`);
|
|
246
|
-
for (const g of orphanGraphs) {
|
|
247
|
-
const pathShort = g.path.replace(/\\/g, '/').split('/').slice(-2).join('/');
|
|
248
|
-
console.log(` ${accent('⬡')} ${bold(pathShort)} ${faint(`${g.nodes}n · ${g.edges}e · ${g.communities} clusters`)}`);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
264
|
console.log('');
|
|
253
265
|
console.log(line());
|
|
254
|
-
console.log(faint(` ${entries.length} entries · ${allDiscussions.length}
|
|
266
|
+
console.log(faint(` ${entries.length} entries · ${allDiscussions.length} plans · ${allGraphs.length} graphs · ${projectNames.length} projects`));
|
|
255
267
|
}
|
|
256
268
|
|
|
257
269
|
// ── Search ────────────────────────────────────────────────────────────────────
|
|
@@ -429,7 +441,7 @@ function cmdBenchmark() {
|
|
|
429
441
|
printSection('Benchmark', 'real token savings');
|
|
430
442
|
|
|
431
443
|
const RESUME_LIMIT = 15;
|
|
432
|
-
const COMPACT_AT =
|
|
444
|
+
const COMPACT_AT = 20;
|
|
433
445
|
|
|
434
446
|
// ── Measure entry sizes from actual stored data ──────────────────────────────
|
|
435
447
|
const allEntries = getContext({ limit: 500, compact: false });
|