context-mcp-server 1.0.4 → 1.0.6

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
@@ -371,7 +371,10 @@ Available to web clients (Claude.ai, ChatGPT) only — local AI clients use thei
371
371
 
372
372
  All file and git operations are sandboxed to the registered project root. Enable git tools with `--access-git` or `access_git: true` in config.
373
373
 
374
- ### CodeGraph
374
+ ### ContextGraph (CodeGraph)
375
+
376
+ > Also referred to as **ContextGraph** — the MCP tools use the `codegraph_*` prefix but both names mean the same thing.
377
+
375
378
  - `codegraph_build` — AST scan using tree-sitter: functions, classes, imports, edges. Runs locally, no API cost.
376
379
  - `codegraph_query` — fetch any details about the codebase using natural language: find functions, classes, files, dependencies, callers
377
380
  - `codegraph_explain` — single node: type, file location, all direct connections (depends_on, used_by)
@@ -381,15 +384,25 @@ All file and git operations are sandboxed to the registered project root. Enable
381
384
 
382
385
  ### Multi-AI Support
383
386
 
384
- | AI | Config File | Instruction File |
385
- |----|------------|-----------------|
386
- | Claude Code | `.claude/mcp.json` | `CLAUDE.md` |
387
- | VS Code Copilot | `.vscode/mcp.json` | `CLAUDE.md` |
388
- | Cursor | `.cursor/mcp.json` | `.cursor/rules/context-mcp.mdc` |
389
- | Gemini CLI | `.gemini/settings.json` | `GEMINI.md` |
390
- | Codex CLI | `.codex/config.toml` | `AGENTS.md` |
391
- | Windsurf | `~/.codeium/windsurf/mcp_config.json` | `.windsurf/rules/context-mcp.md` |
392
- | Claude.ai / ChatGPT | HTTP (`ctx online`) | — |
387
+ | AI | Config File | Instructions | Slash Commands |
388
+ |----|------------|--------------|----------------|
389
+ | Claude Code | `.claude/mcp.json` | Skill → `~/.claude/skills/context-mcp/` (global) | ✓ (3 commands) |
390
+ | Cursor | `.cursor/mcp.json` | Rule → `.cursor/rules/context-mcp.mdc` | — |
391
+ | Windsurf | `~/.codeium/windsurf/mcp_config.json` | Rule → `.windsurf/rules/context-mcp.md` | — |
392
+ | Gemini CLI | `.gemini/settings.json` | `GEMINI.md` | — |
393
+ | Codex CLI | `.codex/config.toml` | `AGENTS.md` | — |
394
+ | VS Code Copilot | `.vscode/mcp.json` | — | — |
395
+ | Claude.ai / ChatGPT | HTTP (`ctx online`) | — | — |
396
+
397
+ > Claude Code installs context-mcp as a **skill** (`~/.claude/skills/context-mcp/SKILL.md`) — available globally across all projects, not just the current one. Cursor and Windsurf use their native rules system. Gemini and Codex use plain instruction files since they have no skill/rules system.
398
+
399
+ `ctx install --claude` also writes slash commands into `.claude/commands/`:
400
+
401
+ | Command | What it does |
402
+ |---------|-------------|
403
+ | `/context-resume` | Resume context for the current project |
404
+ | `/graph-build` | Build or rebuild the ContextGraph |
405
+ | `/save-context` | Save a note, decision, or bug to context |
393
406
 
394
407
  > The context store lives at `~/.context-mcp/` — not inside any tool, IDE, or session. A decision saved in Claude Code is visible in Cursor. A bug logged from Gemini CLI shows up when you resume in Codex.
395
408
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "context-mcp-server",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
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": {
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.4"
7
+ version = "1.0.6"
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
@@ -106,28 +106,46 @@ function printSection(title, meta = '') {
106
106
 
107
107
  function printUsage() {
108
108
  printBanner();
109
- printSection('Commands');
109
+
110
+ // Terminal commands (ctx ...)
111
+ printSection('Terminal commands', 'run from your shell');
110
112
  const cmd = (c, desc) => console.log(` ${accent(c.padEnd(40))} ${faint(desc)}`);
111
- cmd('ctx', 'open interactive mode');
112
- cmd('ctx list [project]', 'list entries + discussions + graphs');
113
- cmd('ctx search <query>', 'keyword → semantic fallback search');
114
- cmd('ctx add', 'add entry interactively');
115
- cmd('ctx delete <id-prefix>', 'delete one entry');
116
- cmd('ctx delete project <name|id>', 'delete all entries for a project (by name or id)');
117
- cmd('ctx summary [project]', 'summarize recent entries');
118
- cmd('ctx projects', 'show all projects + graphs');
119
- cmd('ctx discuss [project]', 'show discussions');
120
- cmd('ctx benchmark', 'token savings report (memory + graph)');
113
+ cmd('ctx', 'open interactive mode');
114
+ cmd('ctx list [project]', 'list entries + discussions + graphs');
115
+ cmd('ctx search <query>', 'keyword → semantic fallback search');
116
+ cmd('ctx add', 'add entry interactively');
117
+ cmd('ctx delete <id-prefix>', 'delete one entry');
118
+ cmd('ctx delete project <name|id>', 'delete all entries for a project');
119
+ cmd('ctx summary [project]', 'summarize recent entries');
120
+ cmd('ctx projects', 'show all projects + graphs');
121
+ cmd('ctx discuss [project]', 'show discussions');
122
+ cmd('ctx benchmark', 'token savings report (memory + graph)');
121
123
  console.log('');
122
- cmd('ctx install --initial', 'install / update Node.js + Python (codegraph) deps');
123
- cmd('ctx install --<platform>', 'write MCP config + instruction file for an AI platform');
124
+ cmd('ctx install --initial', 'install / update Node.js + Python (codegraph) deps');
125
+ cmd('ctx install --<platform>', 'write MCP config + skill/rules for an AI platform');
124
126
  cmd('ctx install --all', 'install for all platforms at once');
125
- cmd('ctx online [--port N]', 'start HTTP server + show credentials for Claude.ai / ChatGPT');
127
+ cmd('ctx online [--port N]', 'start HTTP server for Claude.ai / ChatGPT');
126
128
  cmd('ctx online --close', 'stop the running HTTP server');
127
129
  cmd('ctx settings', 'view and edit config (port, host, client id/secret)');
128
- console.log('');
130
+ cmd('ctx update', 'check for and apply latest version');
129
131
  cmd('ctx help', 'show this screen');
130
132
  console.log('');
133
+
134
+ // Interactive mode commands (no ctx prefix)
135
+ printSection('Interactive mode', 'type these inside ctx (no "ctx" prefix)');
136
+ const icmd = (c, desc) => console.log(` ${accent(c.padEnd(40))} ${faint(desc)}`);
137
+ icmd('list [project]', 'list entries');
138
+ icmd('search <query>', 'search context');
139
+ icmd('add', 'add entry');
140
+ icmd('projects', 'show all projects');
141
+ icmd('discuss [project]', 'show discussions');
142
+ icmd('summary [project]', 'summarize recent entries');
143
+ icmd('benchmark', 'token savings report');
144
+ icmd('install --<platform>', 'install for a platform');
145
+ icmd('settings', 'edit config');
146
+ icmd('clear', 'clear screen');
147
+ icmd('exit / quit / q', 'exit interactive mode');
148
+ console.log('');
131
149
  }
132
150
  function clearScreen() {
133
151
  // \x1b[2J = clear screen, \x1b[3J = clear scrollback, \x1b[H = cursor home
@@ -168,7 +186,7 @@ function cmdList(args) {
168
186
 
169
187
  for (const projectName of projectNames) {
170
188
  const pData = projects[projectName];
171
- const graph = allGraphs.find(g => g.path?.toLowerCase().includes(projectName.toLowerCase()));
189
+ const graph = _graphForProject(allGraphs, projectName);
172
190
  const activeD = pData.discussions.filter(d => d.status === 'active').length;
173
191
  const totalSecs = (pData.contexts.length > 0 ? 1 : 0) + (pData.discussions.length > 0 ? 1 : 0) + (graph ? 1 : 0);
174
192
  let secIdx = 0;
@@ -222,7 +240,7 @@ function cmdList(args) {
222
240
  }
223
241
 
224
242
  // Orphan graphs (no matching project)
225
- const orphanGraphs = allGraphs.filter(g => !projectNames.some(p => g.path?.toLowerCase().includes(p.toLowerCase())));
243
+ const orphanGraphs = allGraphs.filter(g => !projectNames.some(p => _graphForProject([g], p)));
226
244
  if (orphanGraphs.length) {
227
245
  console.log(`\n ${color(C.dblue, '◇')} ${muted('other graphs')}`);
228
246
  for (const g of orphanGraphs) {
@@ -274,7 +292,7 @@ function cmdProjects() {
274
292
  const entries = getContext({ project: project.name, limit: 3, compact: true }).filter(e => e.status !== 'archived');
275
293
  const discs = allDiscs.filter(d => (d.project || 'global') === project.name);
276
294
  const activeD = discs.filter(d => d.status === 'active');
277
- const graph = graphs.find(g => g.path?.toLowerCase().includes(project.name.toLowerCase()));
295
+ const graph = _graphForProject(graphs, project.name);
278
296
 
279
297
  const barLen = Math.min(Math.ceil(project.count / 2), 24);
280
298
  const bar = color(C.dblue, '█'.repeat(barLen)) + color(C.darkgray, '░'.repeat(24 - barLen));
@@ -531,65 +549,187 @@ function _writeFile(filePath, content, label) {
531
549
  console.log(` ${ok('✓')} ${label.padEnd(28)} ${faint(filePath.replace(/\\/g, '/'))}`);
532
550
  }
533
551
 
552
+ // Entries ctx install writes into project roots — add to user's global gitignore if one exists
553
+ const _GLOBAL_GITIGNORE_ENTRIES = [
554
+ // Installed instruction files
555
+ 'CLAUDE.md',
556
+ 'GEMINI.md',
557
+ 'AGENTS.md',
558
+ // AI/IDE platform config folders (context-mcp specific — safe to ignore globally)
559
+ '.claude/',
560
+ '.cursor/',
561
+ '.vscode/',
562
+ '.gemini/',
563
+ '.codex/',
564
+ '.windsurf/',
565
+ // Build outputs and session artifacts
566
+ 'codegraph-cache/',
567
+ '.mcp.json',
568
+ ];
569
+
570
+ // Match a graph to a project by exact last-path-component comparison (not substring)
571
+ function _graphForProject(graphs, projectName) {
572
+ const norm = p => (p || '').toLowerCase().replace(/\\/g, '/').replace(/\/$/, '');
573
+ const name = projectName.toLowerCase();
574
+ return graphs.find(g => norm(g.path).split('/').pop() === name) || null;
575
+ }
576
+
577
+ const _PROJECT_GITIGNORE_ENTRIES = [
578
+ '.claude/', '.cursor/', '.vscode/', '.gemini/', '.codex/',
579
+ 'codegraph-cache/', '.mcp.json', 'CLAUDE.md', 'GEMINI.md', 'AGENTS.md',
580
+ ];
581
+
582
+ function _updateProjectGitignore(projectDir) {
583
+ const giPath = join(projectDir, '.gitignore');
584
+ const existing = existsSync(giPath) ? readFileSync(giPath, 'utf8') : '';
585
+ const lines = existing.split(/\r?\n/);
586
+ const missing = _PROJECT_GITIGNORE_ENTRIES.filter(e => !lines.includes(e));
587
+ if (!missing.length) return;
588
+ const block = '\n# context-mcp — written by ctx install\n' + missing.join('\n') + '\n';
589
+ writeFileSync(giPath, (existing ? existing.trimEnd() : '') + block, 'utf8');
590
+ console.log(` ${ok('✓')} ${'project .gitignore'.padEnd(28)} ${faint(giPath.replace(/\\/g, '/'))}`);
591
+ for (const e of missing) console.log(` ${faint('+ ' + e)}`);
592
+ }
593
+
594
+ function _updateGlobalGitignore() {
595
+ // Resolve global gitignore path: git config > ~/.gitignore_global > ~/.gitignore
596
+ let giPath = null;
597
+ const gitCfg = spawnSync('git', ['config', '--global', 'core.excludesFile'], { encoding: 'utf8' });
598
+ if (gitCfg.status === 0 && gitCfg.stdout.trim()) {
599
+ const resolved = gitCfg.stdout.trim().replace(/^~/, homedir());
600
+ if (existsSync(resolved)) giPath = resolved;
601
+ }
602
+ if (!giPath) {
603
+ for (const candidate of [join(homedir(), '.gitignore_global'), join(homedir(), '.gitignore')]) {
604
+ if (existsSync(candidate)) { giPath = candidate; break; }
605
+ }
606
+ }
607
+ if (!giPath) return; // no global gitignore — skip silently
608
+
609
+ const existing = readFileSync(giPath, 'utf8');
610
+ const lines = existing.split(/\r?\n/);
611
+ const missing = _GLOBAL_GITIGNORE_ENTRIES.filter(e => !lines.includes(e));
612
+ if (!missing.length) return;
613
+
614
+ const block = '\n# context-mcp — written by ctx install\n' + missing.join('\n') + '\n';
615
+ writeFileSync(giPath, existing.trimEnd() + block, 'utf8');
616
+ console.log(` ${ok('✓')} ${'global gitignore'.padEnd(28)} ${faint(giPath.replace(/\\/g, '/'))}`);
617
+ for (const e of missing) console.log(` ${faint('+ ' + e)}`);
618
+ }
619
+
620
+ function _writeCommands(baseDir) {
621
+ const cmdsDir = join(TPLS, 'commands');
622
+ const destDir = join(baseDir, '.claude', 'commands');
623
+ for (const [name, label] of [
624
+ ['context-resume.md', '/context-resume'],
625
+ ['graph-build.md', '/graph-build'],
626
+ ['save-context.md', '/save-context'],
627
+ ]) {
628
+ const src = join(cmdsDir, name);
629
+ if (existsSync(src)) {
630
+ _writeFile(join(destDir, name), readFileSync(src, 'utf8'), label);
631
+ }
632
+ }
633
+ }
634
+
635
+ const MCP_SERVER_CMD = { command: 'npx', args: ['-y', 'context-mcp-server@latest'] };
636
+
534
637
  const PLATFORMS = {
535
638
  claude: {
536
639
  label: 'Claude Code',
537
- install(cwd) {
538
- const mcpJson = JSON.stringify({
539
- mcpServers: { 'context-mcp': { command: 'npx', args: ['-y', 'context-mcp-server@latest'] } },
540
- }, null, 2);
541
- _writeFile(join(cwd, '.claude', 'mcp.json'), mcpJson, '.claude/mcp.json');
542
- const md = _tpl('CLAUDE.md');
543
- if (md) _writeFile(join(cwd, 'CLAUDE.md'), md, 'CLAUDE.md');
640
+ restartNote: 'Type /context-resume in Claude Code to start using context-mcp.',
641
+ install(dir, scope) {
642
+ // Install as a skill (global, works across all projects) instead of a flat CLAUDE.md
643
+ const skillSrc = join(TPLS, 'skills', 'SKILL.md');
644
+ const skillDest = join(homedir(), '.claude', 'skills', 'context-mcp', 'SKILL.md');
645
+ if (existsSync(skillSrc)) {
646
+ _writeFile(skillDest, readFileSync(skillSrc, 'utf8'), '~/.claude/skills/context-mcp/');
647
+ }
648
+ // Slash commands are always user-global (not project-scoped)
649
+ _writeCommands(homedir());
650
+ // Register via claude CLI so the server is trusted immediately (no manual trust prompt)
651
+ const scopeFlag = scope === 'global' ? 'user' : 'project';
652
+ const reg = spawnSync(
653
+ 'claude', ['mcp', 'add', '--scope', scopeFlag, 'context-mcp', '--', 'npx', '-y', 'context-mcp-server@latest'],
654
+ { encoding: 'utf8', shell: true },
655
+ );
656
+ if (reg.status === 0) {
657
+ console.log(` ${ok('✓')} ${'registered via claude mcp add'.padEnd(28)} ${faint('scope: ' + scopeFlag)}`);
658
+ } else {
659
+ console.log(` ${faint('ℹ')} claude CLI not found — open Claude Code and trust context-mcp when prompted`);
660
+ }
544
661
  },
545
662
  },
546
663
  cursor: {
547
664
  label: 'Cursor',
548
- install(cwd) {
549
- const mcpJson = JSON.stringify({
550
- mcpServers: { 'context-mcp': { command: 'npx', args: ['-y', 'context-mcp-server@latest'] } },
551
- }, null, 2);
552
- _writeFile(join(cwd, '.cursor', 'mcp.json'), mcpJson, '.cursor/mcp.json');
553
- const mdc = _tpl('cursor-rules.mdc');
554
- if (mdc) _writeFile(join(cwd, '.cursor', 'rules', 'context-mcp.mdc'), mdc, '.cursor/rules/context-mcp.mdc');
665
+ restartNote: 'Restart Cursor to load the new MCP server.',
666
+ install(dir, scope) {
667
+ const mcpJson = JSON.stringify({ mcpServers: { 'context-mcp': MCP_SERVER_CMD } }, null, 2);
668
+ _writeFile(join(dir, '.cursor', 'mcp.json'), mcpJson, '.cursor/mcp.json');
669
+ if (scope === 'project') {
670
+ const mdc = _tpl('cursor-rules.mdc');
671
+ if (mdc) _writeFile(join(dir, '.cursor', 'rules', 'context-mcp.mdc'), mdc, '.cursor/rules/context-mcp.mdc');
672
+ }
673
+ // Try to enable via cursor CLI (enable/disable only — no add command)
674
+ const reg = spawnSync(
675
+ 'cursor', ['agent', 'mcp', 'enable', 'context-mcp'],
676
+ { encoding: 'utf8', shell: true },
677
+ );
678
+ if (reg.status === 0) {
679
+ console.log(` ${ok('✓')} ${'enabled via cursor agent mcp'.padEnd(28)}`);
680
+ }
555
681
  },
556
682
  },
557
683
  vscode: {
558
684
  label: 'VS Code Copilot',
559
- install(cwd) {
685
+ restartNote: 'Reload VS Code window (Ctrl+Shift+P → "Reload Window").',
686
+ install(dir) {
560
687
  const mcpJson = JSON.stringify({
561
- servers: { 'context-mcp': { type: 'stdio', command: 'npx', args: ['-y', 'context-mcp-server@latest'] } },
688
+ servers: { 'context-mcp': { type: 'stdio', ...MCP_SERVER_CMD } },
562
689
  }, null, 2);
563
- _writeFile(join(cwd, '.vscode', 'mcp.json'), mcpJson, '.vscode/mcp.json');
564
- const md = _tpl('CLAUDE.md');
565
- if (md) _writeFile(join(cwd, 'CLAUDE.md'), md, 'CLAUDE.md');
690
+ _writeFile(join(dir, '.vscode', 'mcp.json'), mcpJson, '.vscode/mcp.json');
566
691
  },
567
692
  },
568
693
  gemini: {
569
694
  label: 'Gemini CLI',
570
- install(cwd) {
571
- const cfg = JSON.stringify({
572
- mcpServers: { 'context-mcp': { command: 'npx', args: ['-y', 'context-mcp-server@latest'] } },
573
- }, null, 2);
574
- _writeFile(join(cwd, '.gemini', 'settings.json'), cfg, '.gemini/settings.json');
575
- const md = _tpl('GEMINI.md');
576
- if (md) _writeFile(join(cwd, 'GEMINI.md'), md, 'GEMINI.md');
695
+ restartNote: 'Restart your Gemini CLI session.',
696
+ install(dir, scope) {
697
+ const cfg = JSON.stringify({ mcpServers: { 'context-mcp': MCP_SERVER_CMD } }, null, 2);
698
+ _writeFile(join(dir, '.gemini', 'settings.json'), cfg, '.gemini/settings.json');
699
+ if (scope === 'project') {
700
+ const md = _tpl('GEMINI.md');
701
+ if (md) _writeFile(join(dir, 'GEMINI.md'), md, 'GEMINI.md');
702
+ }
577
703
  },
578
704
  },
579
705
  codex: {
580
706
  label: 'Codex CLI',
581
- install(cwd) {
707
+ restartNote: 'Restart your Codex CLI session.',
708
+ install(dir, scope) {
582
709
  const toml = `[[mcp_servers]]\nname = "context-mcp"\ncommand = "npx"\nargs = ["-y", "context-mcp-server@latest"]\n`;
583
- _writeFile(join(cwd, '.codex', 'config.toml'), toml, '.codex/config.toml');
584
- const md = _tpl('AGENTS.md');
585
- if (md) _writeFile(join(cwd, 'AGENTS.md'), md, 'AGENTS.md');
710
+ _writeFile(join(dir, '.codex', 'config.toml'), toml, '.codex/config.toml');
711
+ if (scope === 'project') {
712
+ const md = _tpl('AGENTS.md');
713
+ if (md) _writeFile(join(dir, 'AGENTS.md'), md, 'AGENTS.md');
714
+ }
715
+ // Register via codex CLI so server is active immediately
716
+ const reg = spawnSync(
717
+ 'codex', ['mcp', 'add', 'context-mcp', '--', 'npx', '-y', 'context-mcp-server@latest'],
718
+ { encoding: 'utf8', shell: true },
719
+ );
720
+ if (reg.status === 0) {
721
+ console.log(` ${ok('✓')} ${'registered via codex mcp add'.padEnd(28)}`);
722
+ } else {
723
+ console.log(` ${faint('ℹ')} codex CLI not found — server will load on next Codex session`);
724
+ }
586
725
  },
587
726
  },
588
727
  windsurf: {
589
728
  label: 'Windsurf',
590
- install(cwd) {
729
+ restartNote: 'Restart Windsurf to load the updated MCP config.',
730
+ install(dir) {
591
731
  const rules = _tpl('windsurf-rules.md');
592
- if (rules) _writeFile(join(cwd, '.windsurf', 'rules', 'context-mcp.md'), rules, '.windsurf/rules/context-mcp.md');
732
+ if (rules) _writeFile(join(dir, '.windsurf', 'rules', 'context-mcp.md'), rules, '.windsurf/rules/context-mcp.md');
593
733
  const globalCfgPath = join(homedir(), '.codeium', 'windsurf', 'mcp_config.json');
594
734
  let existing = {};
595
735
  try { existing = JSON.parse(readFileSync(globalCfgPath, 'utf8')); } catch {}
@@ -600,7 +740,7 @@ const PLATFORMS = {
600
740
  },
601
741
  };
602
742
 
603
- function cmdInstall(args) {
743
+ async function cmdInstall(args) {
604
744
  const flags = new Set(args.map(a => a.replace(/^--/, '')));
605
745
  const all = flags.has('all');
606
746
  const initial = flags.has('initial');
@@ -665,22 +805,59 @@ function cmdInstall(args) {
665
805
  return;
666
806
  }
667
807
 
668
- const cwd = process.cwd();
669
- printSection('Install', keys.map(k => PLATFORMS[k].label).join(', '));
670
- console.log('');
808
+ // ── Scope prompt (skip for windsurf-only installs — it's always global) ──────
809
+ const nonWindsurf = keys.filter(k => k !== 'windsurf');
810
+ let scope = 'project';
811
+ let baseDir = process.cwd();
812
+
813
+ if (nonWindsurf.length > 0) {
814
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
815
+ const ask = q => new Promise(resolve => rl.question(` ${accent('›')} ${muted(q)} `, resolve));
816
+
817
+ printSection('Install', keys.map(k => PLATFORMS[k].label).join(', '));
818
+ console.log('');
819
+ console.log(` ${muted('Install scope:')}`);
820
+ console.log(` ${accent('1.')} For this project ${faint('(writes config into current directory)')}`);
821
+ console.log(` ${accent('2.')} Globally ${faint('(writes config to your home directory)')}`);
822
+ console.log('');
823
+ const answer = (await ask('Choose (1/2) [1]:')).trim();
824
+ rl.close();
825
+ console.log('');
826
+
827
+ if (answer === '2') {
828
+ scope = 'global';
829
+ baseDir = homedir();
830
+ }
831
+ } else {
832
+ printSection('Install', keys.map(k => PLATFORMS[k].label).join(', '));
833
+ console.log('');
834
+ }
671
835
 
672
836
  for (const key of keys) {
673
- console.log(` ${bold(lblue(PLATFORMS[key].label))}`);
837
+ const platform = PLATFORMS[key];
838
+ console.log(` ${bold(lblue(platform.label))}`);
839
+ const dir = key === 'windsurf' ? process.cwd() : baseDir;
674
840
  try {
675
- PLATFORMS[key].install(cwd);
841
+ platform.install(dir, scope);
676
842
  } catch (err) {
677
843
  console.log(` ${bad('✗')} failed: ${err.message}`);
678
844
  }
845
+ if (platform.restartNote) {
846
+ console.log(` ${faint('→ ' + platform.restartNote)}`);
847
+ }
679
848
  console.log('');
680
849
  }
681
850
 
851
+ const destLabel = scope === 'global' ? homedir().replace(/\\/g, '/') : process.cwd().replace(/\\/g, '/');
682
852
  console.log(line());
683
- console.log(faint(` ${keys.length} platform(s) installed into ${cwd.replace(/\\/g, '/')}`));
853
+ console.log(faint(` ${keys.length} platform(s) installed · scope: ${scope} · ${destLabel}`));
854
+ console.log('');
855
+
856
+ // ── Project .gitignore — add context-mcp entries for this project ──────────
857
+ _updateProjectGitignore(process.cwd());
858
+
859
+ // ── Global gitignore — add context-mcp runtime files if global gitignore exists ──
860
+ _updateGlobalGitignore();
684
861
  console.log('');
685
862
 
686
863
  // ── Python / uv setup (codegraph) ─────────────────────────────────────────
@@ -853,10 +1030,10 @@ async function cmdSettings(existingRl) {
853
1030
  console.log('');
854
1031
 
855
1032
  const choice = (await ask('Edit field (1-' + FIELDS.length + '):')).trim();
856
- if (!existingRl) rl.close();
857
1033
 
858
1034
  const idx = parseInt(choice) - 1;
859
1035
  if (isNaN(idx) || idx < 0 || idx >= FIELDS.length) {
1036
+ if (!existingRl) rl.close();
860
1037
  console.log(` ${faint('no changes made')}`);
861
1038
  return;
862
1039
  }
@@ -1007,7 +1184,7 @@ async function interactive() {
1007
1184
  case 'benchmark': case 'bench':
1008
1185
  clearScreen(); printCompactHeader('benchmark'); cmdBenchmark(); break;
1009
1186
  case 'install':
1010
- clearScreen(); printCompactHeader('install'); cmdInstall(rest); break;
1187
+ clearScreen(); printCompactHeader('install'); await cmdInstall(rest); break;
1011
1188
  case 'online':
1012
1189
  clearScreen(); printCompactHeader('online'); cmdOnline(rest); break;
1013
1190
  case 'settings': case 'config':
@@ -1033,6 +1210,25 @@ function printBye() {
1033
1210
  console.log(`\n ${ok('✓')} ${bold(lblue('goodbye'))} ${faint('keep building')}\n`);
1034
1211
  }
1035
1212
 
1213
+ // ── Update check ──────────────────────────────────────────────────────────────
1214
+
1215
+ async function checkForUpdate() {
1216
+ try {
1217
+ const result = spawnSync(
1218
+ 'npm', ['view', 'context-mcp-server', 'version', '--json'],
1219
+ { encoding: 'utf8', timeout: 3000, shell: true },
1220
+ );
1221
+ if (result.status !== 0 || !result.stdout) return;
1222
+ const parsed = JSON.parse(result.stdout.trim());
1223
+ const latest = typeof parsed === 'string' ? parsed : String(parsed);
1224
+ const current = pkg.version;
1225
+ if (latest && latest !== current) {
1226
+ console.log(` ${warn('↑')} ${bold('Update available')} ${faint(current)} ${accent('→')} ${ok(latest)} ${faint('run:')} ${accent('npm i -g context-mcp-server@latest')}`);
1227
+ console.log('');
1228
+ }
1229
+ } catch {}
1230
+ }
1231
+
1036
1232
  // ── CLI entry point ───────────────────────────────────────────────────────────
1037
1233
 
1038
1234
  (async () => {
@@ -1052,7 +1248,24 @@ function printBye() {
1052
1248
  case 'benchmark': case 'bench':
1053
1249
  cmdBenchmark(); break;
1054
1250
  case 'install':
1055
- cmdInstall(rest); break;
1251
+ await cmdInstall(rest);
1252
+ process.exit(0);
1253
+ break;
1254
+ case 'update': {
1255
+ printSection('Update');
1256
+ console.log('');
1257
+ const upd = spawnSync(
1258
+ 'npm', ['install', '-g', 'context-mcp-server@latest'],
1259
+ { encoding: 'utf8', shell: true, stdio: 'inherit' },
1260
+ );
1261
+ if (upd.status === 0) {
1262
+ console.log(`\n ${ok('✓')} ${bold('context-mcp updated to latest')}`);
1263
+ } else {
1264
+ console.log(`\n ${bad('✗')} update failed — try: ${accent('npm i -g context-mcp-server@latest')}`);
1265
+ }
1266
+ console.log('');
1267
+ break;
1268
+ }
1056
1269
  case 'online':
1057
1270
  cmdOnline(rest); break;
1058
1271
  case 'settings': case 'config':
@@ -1062,10 +1275,13 @@ function printBye() {
1062
1275
  case 'delete': case 'del': case 'rm':
1063
1276
  cmdDelete(rest); break;
1064
1277
  case 'help': case '--help': case '-h':
1065
- printUsage(); break;
1278
+ await checkForUpdate();
1279
+ printUsage();
1280
+ break;
1066
1281
  case '--version': case '-v':
1067
1282
  console.log(pkg.version); break;
1068
1283
  default:
1284
+ await checkForUpdate();
1069
1285
  await interactive();
1070
1286
  }
1071
1287
  })();
package/src/db.js CHANGED
@@ -23,6 +23,11 @@ const PROJECTS_PATH = join(DATA_DIR, 'projects.json');
23
23
 
24
24
  const MAX_CONTENT_LENGTH = 5000;
25
25
  const PREVIEW_LENGTH = 200;
26
+
27
+ // Normalize file paths for cross-platform comparison (Windows case + slash variants)
28
+ function normPath(p) {
29
+ return p ? p.toLowerCase().replace(/\\/g, '/').replace(/\/$/, '') : '';
30
+ }
26
31
  const WRITE_DEBOUNCE_MS = 500;
27
32
  const LOCK_WAIT_TIMEOUT_MS = 2000;
28
33
 
@@ -126,9 +131,9 @@ function mergeStore(latest, local) {
126
131
  if (_changedDiscussionNames.has(disc.name)) discussionsByName.set(disc.name, disc);
127
132
  }
128
133
 
129
- const graphsByPath = new Map((latest.graphs || []).map(g => [g.path, g]));
134
+ const graphsByPath = new Map((latest.graphs || []).map(g => [normPath(g.path), g]));
130
135
  for (const graph of (local.graphs || [])) {
131
- if (_changedGraphPaths.has(graph.path)) graphsByPath.set(graph.path, graph);
136
+ if (_changedGraphPaths.has(graph.path)) graphsByPath.set(normPath(graph.path), graph);
132
137
  }
133
138
 
134
139
  const projectsById = new Map((latest.projects || []).map(p => [p.id, p]));
@@ -713,7 +718,7 @@ export function flushStore() { flushToDisk(); }
713
718
 
714
719
  // ── Auto-compaction ───────────────────────────────────────────────────────────
715
720
 
716
- const COMPACTION_THRESHOLD = 50;
721
+ const COMPACTION_THRESHOLD = 20;
717
722
  const COMPACTION_TARGET = 30;
718
723
 
719
724
  export function shouldCompact(project) {
@@ -753,7 +758,14 @@ export function compactProject(project, summaryContent) {
753
758
  export function saveGraph({ path, nodes, edges, communities, cached, changed, time_ms, summary }) {
754
759
  refreshFromDisk();
755
760
  const store = load();
756
- const existing = store.graphs.find(g => g.path === path);
761
+ // Deduplicate: collapse any case/slash variants of same path, keep newest
762
+ const dupes = store.graphs.filter(g => normPath(g.path) === normPath(path));
763
+ if (dupes.length > 1) {
764
+ const keep = dupes.reduce((a, b) => (a.builtAt >= b.builtAt ? a : b));
765
+ store.graphs = store.graphs.filter(g => normPath(g.path) !== normPath(path));
766
+ store.graphs.push(keep);
767
+ }
768
+ const existing = store.graphs.find(g => normPath(g.path) === normPath(path));
757
769
  const record = {
758
770
  path,
759
771
  nodes: nodes ?? existing?.nodes ?? 0,
@@ -777,7 +789,7 @@ export function saveGraph({ path, nodes, edges, communities, cached, changed, ti
777
789
 
778
790
  export function getGraph(path) {
779
791
  const store = load();
780
- if (path) return store.graphs.find(g => g.path === path) || null;
792
+ if (path) return store.graphs.find(g => normPath(g.path) === normPath(path)) || null;
781
793
  return store.graphs;
782
794
  }
783
795
 
@@ -3,7 +3,7 @@ import { fireAutoLink } from './autoLink.js';
3
3
 
4
4
  export function saveAutoContext({ title, content, type, files, state, tags = [] }) {
5
5
  const entry = saveContext({
6
- project: state.sessionProject || 'global',
6
+ project: state.sessionProject || null,
7
7
  sessionId: state.sessionId || null,
8
8
  title,
9
9
  content,
@@ -20,22 +20,40 @@ Then:
20
20
 
21
21
  ---
22
22
 
23
- ## 2. During the Conversation
23
+ ## 2. When to Auto-Save Context
24
24
 
25
- | Situation | Action |
26
- |-----------|--------|
27
- | Decision made | `context.save` type: `"decision"` |
28
- | Bug found/fixed | `context.save` type: `"bug"` |
29
- | Architecture understood | `context.save` type: `"architecture"` |
30
- | User says "save/remember this" | `context.save` immediately |
31
- | Feature spans sessions | `discussion.save` status: `"active"` |
32
- | Need past info | `search` before asking user |
25
+ **After graph build or rebuild** — every time `codegraph_build` completes:
26
+ ```
27
+ context.save type: "architecture" title: "ContextGraph built — <project>"
28
+ content: "nodes: X | edges: Y | communities: Z"
29
+ ```
30
+
31
+ **User explicitly asks** "save this", "remember this", "note that" → save immediately.
33
32
 
34
- Always pass `project`. Auto-compact fires at >50 entries.
33
+ **During plan / implementation / discussion / research** — save only when genuinely valuable:
34
+
35
+ | What happened | Type |
36
+ |--------------|------|
37
+ | Approach / library / pattern decided | `decision` |
38
+ | Bug found (root cause known) or fixed | `bug` |
39
+ | System structure understood | `architecture` |
40
+ | Gotcha, constraint, non-obvious behavior | `note` |
41
+ | Config / env var / secret key discovered | `config` |
42
+ | External API or service integration learned | `note` |
43
+ | Performance insight (why something is slow/fast) | `note` |
44
+ | How to run tests / test pattern discovered | `note` |
45
+ | Deploy / release step discovered | `note` |
46
+ | Milestone / feature / task completed | `note` |
47
+
48
+ Do NOT save: routine reads, search results, temporary debugging dead-ends.
49
+ Feature spans sessions → `discussion.save` status: `"active"`.
50
+ Need past info → `search` before asking. Always pass `project`.
35
51
 
36
52
  ---
37
53
 
38
- ## 3. CodeGraph Pipeline
54
+ ## 3. ContextGraph Pipeline
55
+
56
+ > The knowledge graph is also called **ContextGraph**. The MCP tools use the `codegraph_*` prefix — both names refer to the same thing.
39
57
 
40
58
  ### Step 1 — Build (once, fast, local)
41
59
  ```
@@ -15,7 +15,12 @@ Every conversation starts with `context.resume`. Every codebase question uses `c
15
15
 
16
16
  ## 1. Start of Every Conversation (MANDATORY)
17
17
 
18
- Call `context` tool, `action: "resume"`, `project: "<project-name>"` **before anything else**.
18
+ Call `context` tool **before anything else** with:
19
+ - `action: "resume"`
20
+ - `project: "<basename of git repo root dir>"` — infer from cwd if not stated
21
+ - `rootPath: "<absolute path to git repo root>"` — required for sandbox + graph lookup
22
+
23
+ Both fields are required: `project` names the memory bucket, `rootPath` enables exact graph matching and file sandboxing.
19
24
 
20
25
  Returns:
21
26
  - `recentEntries` — decisions, bugs, notes from previous conversations
@@ -28,22 +33,44 @@ Then:
28
33
 
29
34
  ---
30
35
 
31
- ## 2. During the Conversation
36
+ ## 2. When to Auto-Save Context
37
+
38
+ ### Always save — no user prompt needed
39
+
40
+ **After graph build or rebuild** — every time `codegraph_build` completes:
41
+ ```
42
+ context.save project: "<project>" type: "architecture" title: "ContextGraph built — <project>"
43
+ content: "nodes: X | edges: Y | communities: Z"
44
+ ```
32
45
 
33
- | Situation | Action |
34
- |-----------|--------|
35
- | Decision made | `context.save` type: `"decision"` |
36
- | Bug found/fixed | `context.save` type: `"bug"` |
37
- | Architecture understood | `context.save` type: `"architecture"` |
38
- | User says "save/remember this" | `context.save` immediately |
39
- | Feature spans multiple conversations | `discussion.create` or `discussion.update` |
40
- | Need past info | `search` before asking user |
46
+ **User explicitly asks** any phrase like "save this", "remember this", "note that" → save immediately.
41
47
 
42
- Always pass `project`. Auto-compact fires at >50 entriesoldest summarized automatically.
48
+ **During plan / implementation / discussion / research**save only when genuinely valuable:
49
+
50
+ | What happened | Type |
51
+ |--------------|------|
52
+ | Approach / library / pattern decided | `decision` |
53
+ | Bug found (root cause known) or fixed | `bug` |
54
+ | System structure understood | `architecture` |
55
+ | Gotcha, constraint, non-obvious behavior | `note` |
56
+ | Config / env var / secret key discovered | `config` |
57
+ | External API or service integration learned | `note` |
58
+ | Performance insight (why something is slow/fast) | `note` |
59
+ | How to run tests / test pattern discovered | `note` |
60
+ | Deploy / release step discovered | `note` |
61
+ | Milestone / feature / task completed | `note` |
62
+
63
+ Do NOT save: routine reads, search results, temporary debugging dead-ends.
64
+
65
+ Feature spans multiple sessions → `discussion.create` or `discussion.update`.
66
+ Need past info → `search` before asking user.
67
+ Always pass `project`. Auto-compact fires at >20 entries.
43
68
 
44
69
  ---
45
70
 
46
- ## 3. CodeGraph Pipeline
71
+ ## 3. ContextGraph Pipeline
72
+
73
+ > The knowledge graph is also called **ContextGraph**. The MCP tools use the `codegraph_*` prefix — both names refer to the same thing.
47
74
 
48
75
  ### Step 1 — Build (once per project, fast, local)
49
76
  ```
@@ -20,22 +20,40 @@ Then:
20
20
 
21
21
  ---
22
22
 
23
- ## 2. During the Conversation
23
+ ## 2. When to Auto-Save Context
24
24
 
25
- | Situation | Action |
26
- |-----------|--------|
27
- | Decision made | `context.save` type: `"decision"` |
28
- | Bug found/fixed | `context.save` type: `"bug"` |
29
- | Architecture understood | `context.save` type: `"architecture"` |
30
- | User says "save/remember this" | `context.save` immediately |
31
- | Feature spans sessions | `discussion.save` then `discussion.update` |
32
- | Need past info | `search` before asking user |
25
+ **After graph build or rebuild** — every time `codegraph_build` completes:
26
+ ```
27
+ context.save type: "architecture" title: "ContextGraph built — <project>"
28
+ content: "nodes: X | edges: Y | communities: Z"
29
+ ```
30
+
31
+ **User explicitly asks** "save this", "remember this", "note that" → save immediately.
33
32
 
34
- Always pass `project`. Auto-compact fires at >50 entries.
33
+ **During plan / implementation / discussion / research** — save only when genuinely valuable:
34
+
35
+ | What happened | Type |
36
+ |--------------|------|
37
+ | Approach / library / pattern decided | `decision` |
38
+ | Bug found (root cause known) or fixed | `bug` |
39
+ | System structure understood | `architecture` |
40
+ | Gotcha, constraint, non-obvious behavior | `note` |
41
+ | Config / env var / secret key discovered | `config` |
42
+ | External API or service integration learned | `note` |
43
+ | Performance insight (why something is slow/fast) | `note` |
44
+ | How to run tests / test pattern discovered | `note` |
45
+ | Deploy / release step discovered | `note` |
46
+ | Milestone / feature / task completed | `note` |
47
+
48
+ Do NOT save: routine reads, search results, temporary debugging dead-ends.
49
+ Feature spans sessions → `discussion.save` then `discussion.update`.
50
+ Need past info → `search` before asking. Always pass `project`.
35
51
 
36
52
  ---
37
53
 
38
- ## 3. CodeGraph Pipeline
54
+ ## 3. ContextGraph Pipeline
55
+
56
+ > The knowledge graph is also called **ContextGraph**. The MCP tools use the `codegraph_*` prefix — both names refer to the same thing.
39
57
 
40
58
  ### Step 1 — Build (once, fast, local)
41
59
  ```
@@ -0,0 +1,10 @@
1
+ Call the `context` MCP tool with `action: "resume"`, `project: "$ARGUMENTS"` (if no argument given, infer the project name from the current working directory name), and `rootPath: "<absolute path to the project root / git repo root>"`.
2
+
3
+ Both `project` and `rootPath` are required: `project` names the memory bucket, `rootPath` enables exact graph lookup and file sandboxing.
4
+
5
+ This loads:
6
+ - Recent decisions, bugs, and notes from past sessions
7
+ - Active discussions
8
+ - ContextGraph status (built or not)
9
+
10
+ If `codegraph.built` is false in the response, immediately call `codegraph_build` on the project path before proceeding.
@@ -0,0 +1,5 @@
1
+ Call `codegraph_build` with the path `$ARGUMENTS` (if no argument given, use the current working directory).
2
+
3
+ This builds the ContextGraph for the project — parses all source files into an AST knowledge graph using tree-sitter. Takes a few seconds. Once built, use `codegraph_query` to answer any structural question about the codebase instead of reading files directly.
4
+
5
+ After build completes, report: total nodes, edges, and communities found.
@@ -0,0 +1,9 @@
1
+ Call the `context` MCP tool with `action: "save"` to store a note for the current project.
2
+
3
+ Parse `$ARGUMENTS` to determine:
4
+ - `project`: infer from current working directory name if not specified
5
+ - `title`: first sentence or phrase from the argument
6
+ - `content`: full argument text
7
+ - `type`: auto-detect — if mentions a bug/fix → `"bug"`, decision/chose/decided → `"decision"`, structure/architecture → `"architecture"`, otherwise `"note"`
8
+
9
+ Confirm to the user what was saved (title, type, project).
@@ -0,0 +1,115 @@
1
+ ---
2
+ name: context-mcp
3
+ description: >
4
+ Persistent memory + ContextGraph (codebase knowledge graph) for Claude.
5
+ Use at the START of every conversation to resume project context. Use
6
+ whenever the user mentions a project, asks to remember/save something,
7
+ references past work, or says "pick up where we left off". Also use
8
+ when the user asks about code structure, dependencies, or what exists
9
+ in a codebase — query the ContextGraph before reading any files.
10
+ ---
11
+
12
+ # Context-MCP
13
+
14
+ Persistent memory + codebase knowledge graph across every conversation.
15
+ `context.resume` starts every session. `codegraph_query` answers every structure question. Files only for bugs/logic.
16
+
17
+ ---
18
+
19
+ ## MANDATORY: Start of Every Conversation
20
+
21
+ Call `context` tool **before any tool or response** with:
22
+ - `action: "resume"`
23
+ - `project: "<basename of git repo root dir>"` — infer from `cwd` if not stated
24
+ - `rootPath: "<absolute path to git repo root>"` — required for sandbox + graph lookup
25
+
26
+ Both fields are required: `project` names the memory bucket, `rootPath` enables exact graph matching and file sandboxing.
27
+
28
+ Returns:
29
+ - `recentEntries` — decisions, bugs, notes from past sessions
30
+ - `activeDiscussions` — ongoing topics (auto-linked if exactly one active)
31
+ - `codegraph` — `{ built: true/false, nodes, edges, communities }`
32
+
33
+ Then:
34
+ - `codegraph.built: true` → use `codegraph_query` before reading any files
35
+ - `codegraph.built: false` → call `codegraph_build(path)` first, then proceed
36
+
37
+ ---
38
+
39
+ ## When to Auto-Save Context
40
+
41
+ ### Always save — no user prompt needed
42
+
43
+ **1. After graph build or rebuild**
44
+ Every time `codegraph_build` completes successfully, immediately call:
45
+ ```
46
+ context.save project: "<project>" type: "architecture" title: "ContextGraph built — <project>"
47
+ content: "nodes: X | edges: Y | communities: Z | built: <timestamp>"
48
+ ```
49
+
50
+ **2. When user explicitly asks**
51
+ Any phrase like "save this", "remember this", "note that", "log this" → `context.save` immediately with whatever was just discussed.
52
+
53
+ **3. During plan / implementation / discussion / research — save when something valuable happens**
54
+ Only save if the moment is genuinely worth keeping across sessions:
55
+ - A decision was made and agreed on (approach, library, pattern, architecture)
56
+ - A bug was found with its root cause identified, or fixed
57
+ - An important discovery (gotcha, constraint, non-obvious behavior, env requirement)
58
+ - A significant milestone reached (feature complete, refactor done, plan finalized)
59
+ - Something that would save future-you from re-learning it
60
+
61
+ Do NOT save for: routine file reads, search results, explanations of existing code, temporary debugging steps that led nowhere.
62
+
63
+ | What happened | Type |
64
+ |--------------|------|
65
+ | Approach / library / pattern decided | `decision` |
66
+ | Bug found (root cause known) or fixed | `bug` |
67
+ | System structure understood | `architecture` |
68
+ | Gotcha, constraint, non-obvious behavior | `note` |
69
+ | Config / env var / secret key discovered | `config` |
70
+ | External API or service integration learned | `note` |
71
+ | Performance insight (why something is slow/fast) | `note` |
72
+ | How to run tests / test pattern discovered | `note` |
73
+ | Deploy / release step discovered | `note` |
74
+ | Milestone / feature / task completed | `note` |
75
+
76
+ Always pass `project`. Feature spans multiple sessions → `discussion.create` or `discussion.update`. Need past info → `search` before asking user. Auto-compact fires at >20 entries.
77
+
78
+ ---
79
+
80
+ ## ContextGraph Pipeline
81
+
82
+ > Also called **CodeGraph**. MCP tools use the `codegraph_*` prefix — both names mean the same thing.
83
+
84
+ ### Build (once per project, ~seconds, local)
85
+ ```
86
+ codegraph_build(path)
87
+ ```
88
+ Parses codebase into AST graph via tree-sitter. Extracts functions, classes, imports, call edges for 16+ languages. Build files get a single metadata node. Saved to `~/.context-mcp/graphs.json`.
89
+
90
+ ### Query (free, instant, forever)
91
+ ```
92
+ codegraph_query(path, question) → find functions, classes, files, dependencies, callers
93
+ codegraph_explain(path, node) → one node: type, file, depends_on, used_by
94
+ codegraph_path(path, from, to) → shortest path between two concepts
95
+ codegraph_nodes(path, type) → list all nodes of a type
96
+ codegraph_report(path) → god nodes, clusters, surprises
97
+ ```
98
+
99
+ ---
100
+
101
+ ## Graph vs File
102
+
103
+ **Graph** — what exists: finding functions, classes, files, dependencies, callers, imports, paths between concepts.
104
+
105
+ **File** — bugs, logic inside a specific function, tracing unexpected behavior.
106
+
107
+ ---
108
+
109
+ ## Rules
110
+
111
+ 1. **`context.resume` first** — before any tool or response
112
+ 2. **Always pass `project`** — never save to global unless truly cross-project
113
+ 3. **`search` before asking** — if user references past work, find it first
114
+ 4. **`codegraph_query` before reading files** — graph is faster and cheaper
115
+ 5. **Read files for bugs/logic only** — graph is structure, not behavior
@@ -184,7 +184,10 @@ export function handle(name, args, state) {
184
184
  summary: result.summary || '',
185
185
  });
186
186
 
187
- const project = state?.sessionProject || 'global';
187
+ const inferredProject = args.path
188
+ ? args.path.replace(/\\/g, '/').replace(/\/$/, '').split('/').pop()
189
+ : null;
190
+ const project = state?.sessionProject || inferredProject || null;
188
191
  const title = `CodeGraph — ${args.path}`;
189
192
  const content = [
190
193
  `nodes: ${result.nodes} | edges: ${result.edges} | communities: ${result.communities}`,
@@ -92,9 +92,15 @@ export async function handle(args, state) {
92
92
  .filter(e => e.status !== 'archived');
93
93
  const discussions = listDiscussions({ project: proj, status: 'active' });
94
94
  const allGraphs = listGraphs();
95
- const graph = proj
96
- ? allGraphs.find(g => g.path?.toLowerCase().includes(proj.toLowerCase())) || allGraphs[0] || null
97
- : allGraphs[0] || null;
95
+ const np = p => (p || '').toLowerCase().replace(/\\/g, '/');
96
+ const graph = resolvedRoot
97
+ ? allGraphs.find(g => np(g.path) === np(resolvedRoot)) || null
98
+ : proj
99
+ ? allGraphs.find(g => {
100
+ const parts = np(g.path).split('/');
101
+ return parts[parts.length - 1] === proj.toLowerCase();
102
+ }) || null
103
+ : null;
98
104
  const totalEntries = countContext(proj);
99
105
 
100
106
  // Auto-restore single active discussion
@@ -29,11 +29,10 @@ function resolveCwd(args, state) {
29
29
  // Auto-detect project root on first use if not already configured.
30
30
  if (!state.projectRootPath) {
31
31
  const detected = autoDetectRoot(args.cwd ? pathResolve(args.cwd) : process.cwd());
32
- if (detected) state.projectRootPath = detected;
32
+ state.projectRootPath = detected || process.cwd();
33
33
  }
34
- const raw = args.cwd ? pathResolve(args.cwd) : (state.projectRootPath || process.cwd());
35
- if (state.projectRootPath) return guardPath(raw, state.projectRootPath);
36
- return raw;
34
+ const raw = args.cwd ? pathResolve(args.cwd) : state.projectRootPath;
35
+ return guardPath(raw, state.projectRootPath);
37
36
  }
38
37
 
39
38
  const ROOT_NOTE = ' All paths must be within the project root (sandboxed — access outside root is denied).';
package/uv.lock CHANGED
@@ -168,7 +168,7 @@ wheels = [
168
168
 
169
169
  [[package]]
170
170
  name = "codegraph-mcp"
171
- version = "1.0.3"
171
+ version = "1.0.5"
172
172
  source = { editable = "." }
173
173
  dependencies = [
174
174
  { name = "mcp" },