context-mcp-server 1.0.7 → 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 CHANGED
@@ -25,8 +25,8 @@ This gets worse as projects grow — reading 20 files to answer "what calls this
25
25
 
26
26
  ## What It Solves
27
27
 
28
- - **Persistent memory** — decisions, bugs, notes, and architecture saved across sessions, loaded automatically at conversation start
29
- - **Shared store** — `~/.context-mcp/` is one place on your machine; all AI tools read and write it
28
+ - **Persistent memory** — decisions, bugs, notes, and config saved across sessions, loaded automatically at conversation start
29
+ - **Shared store** — `~/.context-mcp/projects/<name>/` per-project on your machine; all AI tools read and write it
30
30
  - **ContextGraph** — build a knowledge graph of your codebase once, answer structural questions in ~500 tokens instead of ~50,000
31
31
 
32
32
  Real measured reduction on this project: **162× fewer tokens**, **99.38% reduction** per conversation.
@@ -95,10 +95,10 @@ ctx online --port 3200 # different port
95
95
  Both `ctx` and `context` are aliases for the same CLI.
96
96
 
97
97
  ```bash
98
- ctx # interactive mode (UI — no "ctx" prefix inside)
98
+ ctx # interactive mode (UI)
99
99
 
100
100
  # Context
101
- ctx list [project] # list entries, discussions, graphs
101
+ ctx list [project] # list entries by tree: graph / context / summary / plans
102
102
  ctx projects # all projects with graph status + recent entries
103
103
  ctx search "query" # keyword → semantic fallback search
104
104
  ctx add # add entry interactively
@@ -115,7 +115,6 @@ ctx settings # view and edit config interactively
115
115
 
116
116
  # Tools
117
117
  ctx benchmark # token savings report (memory + graph)
118
- ctx discuss [project] # view discussions
119
118
  ```
120
119
 
121
120
  ---
@@ -136,12 +135,12 @@ Any file or git operation outside that directory is rejected. Applies to all HTT
136
135
 
137
136
  ### Memory
138
137
 
139
- - `context.resume` — loads recent entries, discussions, and graph status; registers `rootPath` for sandboxing
140
- - `context.save` — store decisions, bugs, notes, architecture with type tags
141
- - `context.get` / `context.update` / `context.delete` — full CRUD
138
+ - `context.resume` — loads recent entries, active plans, and graph status; registers `rootPath` for sandboxing
139
+ - `context.save` — store context with 4 types: `decision`, `bug`, `note`, `config`
140
+ - `context.get` / `context.update` / `context.delete` — full CRUD, single or batch
142
141
  - `search` — keyword-first, semantic fallback
143
- - `discussion` — threaded plans with steps and cross-session continuity
144
- - Auto-deduplication on save; auto-compact at 20 entries
142
+ - `plan` — auto-triggered when AI makes any plan; saves a markdown summary to a `planDir` you specify
143
+ - Auto-deduplication on save; auto-compact at 20 entries → stored in `summary.json`
145
144
 
146
145
  ### ContextGraph
147
146
 
@@ -153,7 +152,7 @@ Any file or git operation outside that directory is rejected. Applies to all HTT
153
152
  codegraph_build(path)
154
153
  ```
155
154
 
156
- Parses codebase via tree-sitter AST (16 languages, regex fallback). Extracts functions, classes, imports, call edges. Saved to `~/.context-mcp/`.
155
+ Parses codebase via tree-sitter AST (16 languages, regex fallback). Extracts functions, classes, imports, call edges. Build metadata saved to `~/.context-mcp/projects/<name>/graph.json`.
157
156
 
158
157
  **Step 2 — Query** (instant, forever):
159
158
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "context-mcp-server",
3
- "version": "1.0.7",
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": {
@@ -14,7 +14,7 @@
14
14
  "mcp": "node src/index.js",
15
15
  "mcp-server": "node src/http.js",
16
16
  "cli": "node src/cli.js",
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/discussion.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
+ "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",
18
18
  "check-mcp": "npm run check",
19
19
  "test": "node --test",
20
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"
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"
package/src/cli.js CHANGED
@@ -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
- if (!projects[p]) projects[p] = { contexts: [], discussions: [] };
171
- projects[p].contexts.push(entry);
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
- const p = disc.project || 'global';
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 = projects[projectName];
189
- const graph = _graphForProject(allGraphs, projectName);
190
- const activeD = pData.discussions.filter(d => d.status === 'active').length;
191
- const totalSecs = (pData.contexts.length > 0 ? 1 : 0) + (pData.discussions.length > 0 ? 1 : 0) + (graph ? 1 : 0);
192
- let secIdx = 0;
193
-
194
- const projReg = projectRegistry.get(projectName);
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(`${pData.contexts.length} entries · ${pData.discussions.length} discussions`)}${activeD ? ` ${warn('● ' + activeD + ' active')}` : ''}`);
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
- // ── Graph ────────────────────────────────────────────────────────────────
200
- if (graph) {
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 === totalSecs;
203
- const builtAt = (graph.builtAt || '').slice(0, 10);
204
- console.log(` ${color(C.darkgray, isLast ? '└─' : '├─')} ${accent('⬡')} ${muted('graph')} ${faint(`${graph.nodes}n · ${graph.edges}e · ${graph.communities} clusters · ${builtAt}`)}`);
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 entries ───────────────────────────────────────────────────────
209
- if (pData.contexts.length) {
231
+ // ── Context ───────────────────────────────────────────────────────────────
232
+ if (pData.context.length) {
210
233
  secIdx++;
211
- const isLast = secIdx === totalSecs;
212
- console.log(` ${color(C.darkgray, isLast ? '└─' : '├─')} ${muted('memory')} ${faint(pData.contexts.length + ' entries')}`);
213
- pData.contexts.forEach((item, i) => {
214
- const br = i === pData.contexts.length - 1 ? '└─' : '├─';
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)} ${pill(type)} ${bold(item.title || '(no title)')} ${faint('id:' + id)} ${faint(date)}`);
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
- // ── Discussions ───────────────────────────────────────────────────────────
227
- if (pData.discussions.length) {
251
+ // ── Plans ─────────────────────────────────────────────────────────────────
252
+ if (pData.plans.length) {
228
253
  secIdx++;
229
- const isLast = secIdx === totalSecs;
230
- console.log(` ${color(C.darkgray, isLast ? '└─' : '├─')} ${muted('discussions')} ${faint(pData.discussions.length + ' total')}`);
231
- pData.discussions.forEach((disc, i) => {
232
- const br = i === pData.discussions.length - 1 ? '└─' : '├─';
233
- const sc = disc.status === 'done' ? 'green' : 'tcyan';
234
- const steps = disc.stepsSummary?.total ? faint(` ${disc.stepsSummary.done}/${disc.stepsSummary.total}`) : '';
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} discussions · ${allGraphs.length} graphs · ${projectNames.length} projects`));
266
+ console.log(faint(` ${entries.length} entries · ${allDiscussions.length} plans · ${allGraphs.length} graphs · ${projectNames.length} projects`));
255
267
  }
256
268
 
257
269
  // ── Search ────────────────────────────────────────────────────────────────────