context-mcp-server 1.1.0 → 1.1.2

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.
Files changed (72) hide show
  1. package/README.md +29 -7
  2. package/codegraph/__pycache__/affected.cpython-313.pyc +0 -0
  3. package/codegraph/__pycache__/cache.cpython-313.pyc +0 -0
  4. package/codegraph/__pycache__/callflow_html.cpython-313.pyc +0 -0
  5. package/codegraph/__pycache__/export.cpython-313.pyc +0 -0
  6. package/codegraph/__pycache__/report.cpython-313.pyc +0 -0
  7. package/codegraph/__pycache__/server.cpython-313.pyc +0 -0
  8. package/codegraph/__pycache__/tree_html.cpython-313.pyc +0 -0
  9. package/codegraph/affected.py +233 -0
  10. package/codegraph/cache.py +51 -2
  11. package/codegraph/callflow_html.py +273 -0
  12. package/codegraph/export.py +544 -0
  13. package/codegraph/extractors/__pycache__/ast_extractor.cpython-313.pyc +0 -0
  14. package/codegraph/extractors/ast_extractor.py +143 -16
  15. package/codegraph/graph/__pycache__/builder.cpython-313.pyc +0 -0
  16. package/codegraph/graph/__pycache__/clustering.cpython-313.pyc +0 -0
  17. package/codegraph/graph/__pycache__/query.cpython-313.pyc +0 -0
  18. package/codegraph/graph/__pycache__/symbol_resolution.cpython-313.pyc +0 -0
  19. package/codegraph/graph/builder.py +10 -0
  20. package/codegraph/graph/clustering.py +247 -10
  21. package/codegraph/graph/query.py +56 -0
  22. package/codegraph/graph/symbol_resolution.py +112 -0
  23. package/codegraph/report.py +53 -0
  24. package/codegraph/server.py +99 -10
  25. package/codegraph/tree_html.py +241 -0
  26. package/package.json +2 -2
  27. package/pyproject.toml +4 -1
  28. package/src/cli.js +303 -90
  29. package/src/server.js +7 -1
  30. package/src/templates/antigravity/GEMINI.md +96 -0
  31. package/src/templates/antigravity/hooks/context-mcp-post-tool-use.js +62 -0
  32. package/src/templates/antigravity/workflows/context-resume.md +20 -0
  33. package/src/templates/antigravity/workflows/graph-build.md +23 -0
  34. package/src/templates/antigravity/workflows/save-context.md +29 -0
  35. package/src/templates/{CLAUDE.md → claude/CLAUDE.md} +3 -0
  36. package/src/templates/claude/commands/graph-build.md +9 -0
  37. package/src/templates/claude/commands/save-context.md +19 -0
  38. package/src/templates/claude/hooks/context-mcp-post-tool-use.js +59 -0
  39. package/src/templates/claude/hooks/context-mcp-pre-tool-use.js +26 -0
  40. package/src/templates/{skills → claude/skills}/SKILL.md +3 -0
  41. package/src/templates/codex/AGENTS.md +107 -0
  42. package/src/templates/codex/hooks/context-mcp-post-tool-use.js +46 -0
  43. package/src/templates/codex/hooks/context-mcp-pre-tool-use.js +23 -0
  44. package/src/templates/codex/prompts/context-resume.md +15 -0
  45. package/src/templates/codex/prompts/graph-build.md +14 -0
  46. package/src/templates/codex/prompts/save-context.md +24 -0
  47. package/src/templates/cursor/commands/context-resume.md +7 -0
  48. package/src/templates/cursor/commands/graph-build.md +7 -0
  49. package/src/templates/cursor/commands/save-context.md +12 -0
  50. package/src/templates/{cursor-rules.mdc → cursor/cursor-rules.mdc} +13 -3
  51. package/src/templates/cursor/hooks/context-mcp-post-tool-use.js +55 -0
  52. package/src/templates/{GEMINI.md → gemini/GEMINI.md} +3 -1
  53. package/src/templates/gemini/commands/context-resume.toml +15 -0
  54. package/src/templates/gemini/commands/graph-build.toml +14 -0
  55. package/src/templates/gemini/commands/save-context.toml +24 -0
  56. package/src/templates/gemini/hooks/context-mcp-after-tool.js +59 -0
  57. package/src/templates/gemini/hooks/context-mcp-before-tool.js +26 -0
  58. package/src/templates/vscode/commands/context-resume.prompt.md +15 -0
  59. package/src/templates/vscode/commands/graph-build.prompt.md +10 -0
  60. package/src/templates/vscode/commands/save-context.prompt.md +16 -0
  61. package/src/templates/vscode/hooks/context-mcp-post-tool-use.js +58 -0
  62. package/src/templates/windsurf/hooks/context-mcp-post-run-command.js +57 -0
  63. package/src/templates/{windsurf-rules.md → windsurf/windsurf-rules.md} +6 -4
  64. package/src/templates/windsurf/workflows/context-resume.md +11 -0
  65. package/src/templates/windsurf/workflows/graph-build.md +11 -0
  66. package/src/templates/windsurf/workflows/save-context.md +18 -0
  67. package/src/tools/codegraph.js +37 -0
  68. package/uv.lock +1100 -3
  69. package/src/templates/AGENTS.md +0 -90
  70. package/src/templates/commands/graph-build.md +0 -5
  71. package/src/templates/commands/save-context.md +0 -12
  72. /package/src/templates/{commands → claude/commands}/context-resume.md +0 -0
package/src/cli.js CHANGED
@@ -119,7 +119,6 @@ function printUsage() {
119
119
  cmd('ctx summary [project]', 'summarize recent entries');
120
120
  cmd('ctx projects', 'show all projects + graphs');
121
121
  cmd('ctx discuss [project]', 'show discussions');
122
- cmd('ctx stats', 'storage report: entries, types, graph status');
123
122
  console.log('');
124
123
  cmd('ctx install --initial', 'install / update Node.js + Python (codegraph) deps only');
125
124
  cmd('ctx install --<platform>', 'write MCP config + skill/rules file only (no uv/npm)');
@@ -386,60 +385,6 @@ function cmdSummary(args) {
386
385
  console.log('');
387
386
  }
388
387
 
389
- // ── Benchmark ────────────────────────────────────────────────────────────────
390
-
391
-
392
- function cmdStats() {
393
- const projects = listProjects();
394
- const graphs = listGraphs();
395
- const allEntries = getContext({ limit: 2000, compact: false });
396
-
397
- printSection('Stats', 'context-mcp storage report');
398
-
399
- // ── Projects ──────────────────────────────────────────────────────────────────
400
- console.log(`\n ${bold(lblue('Projects'))}`);
401
- if (!projects.length) {
402
- console.log(` ${faint('no projects yet')}`);
403
- } else {
404
- for (const p of projects) {
405
- const g = graphs.find(gr => {
406
- const gp = (gr.path || '').replace(/\\/g, '/').replace(/\/$/, '').split('/').pop();
407
- return gp === p.name;
408
- });
409
- const graphStatus = g ? ok(`graph: ${g.nodes} nodes, ${g.edges} edges`) : faint('no graph');
410
- const builtAt = g?.builtAt ? faint(' built ' + g.builtAt.slice(0, 10)) : '';
411
- console.log(` ${bold(p.name.padEnd(24))} ${muted(String(p.count).padStart(3) + ' entries')} ${graphStatus}${builtAt}`);
412
- }
413
- }
414
-
415
- // ── Entry type breakdown ──────────────────────────────────────────────────────
416
- console.log(`\n ${bold(lblue('Entries by type'))}`);
417
- if (!allEntries.length) {
418
- console.log(` ${faint('no entries yet')}`);
419
- } else {
420
- const byType = {};
421
- for (const e of allEntries) { byType[e.type] = (byType[e.type] || 0) + 1; }
422
- for (const [type, count] of Object.entries(byType).sort((a, b) => b[1] - a[1])) {
423
- const bar = color(C.dblue, '█'.repeat(Math.min(count, 20))) + color(C.darkgray, '░'.repeat(Math.max(0, 20 - count)));
424
- console.log(` ${type.padEnd(14)} ${bar} ${muted(count)}`);
425
- }
426
- }
427
-
428
- // ── Storage summary ───────────────────────────────────────────────────────────
429
- const compactions = allEntries.filter(e => e.type === 'compaction').length;
430
- const avgSize = allEntries.length
431
- ? Math.round(allEntries.reduce((s, e) => s + (e.content || '').length, 0) / allEntries.length)
432
- : 0;
433
-
434
- console.log(`\n ${bold(lblue('Storage'))}`);
435
- console.log(` ${faint('total entries: ')} ${muted(allEntries.length)}`);
436
- console.log(` ${faint('compactions: ')} ${muted(compactions)}`);
437
- console.log(` ${faint('avg entry size: ')} ${muted(avgSize + ' chars')}`);
438
- console.log(` ${faint('graphs built: ')} ${muted(graphs.length)}`);
439
- console.log('');
440
- }
441
-
442
-
443
388
  // ── Install ───────────────────────────────────────────────────────────────────
444
389
 
445
390
  const TPLS = join(__dirname, 'templates');
@@ -469,6 +414,7 @@ const _GLOBAL_GITIGNORE_ENTRIES = [
469
414
  '.gemini/',
470
415
  '.codex/',
471
416
  '.windsurf/',
417
+ '.agents/',
472
418
  // Build outputs and session artifacts
473
419
  'codegraph-cache/',
474
420
  '.mcp.json',
@@ -483,6 +429,7 @@ function _graphForProject(graphs, projectName) {
483
429
 
484
430
  const _PROJECT_GITIGNORE_ENTRIES = [
485
431
  '.claude/', '.cursor/', '.vscode/', '.gemini/', '.codex/',
432
+ '.agents/',
486
433
  'codegraph-cache/', '.mcp.json', 'CLAUDE.md', 'GEMINI.md', 'AGENTS.md',
487
434
  ];
488
435
 
@@ -525,7 +472,7 @@ function _updateGlobalGitignore() {
525
472
  }
526
473
 
527
474
  function _writeCommands(baseDir) {
528
- const cmdsDir = join(TPLS, 'commands');
475
+ const cmdsDir = join(TPLS, 'claude', 'commands');
529
476
  const destDir = join(baseDir, '.claude', 'commands');
530
477
  for (const [name, label] of [
531
478
  ['context-resume.md', '/context-resume'],
@@ -541,20 +488,144 @@ function _writeCommands(baseDir) {
541
488
 
542
489
  const MCP_SERVER_CMD = { command: 'npx', args: ['-y', 'context-mcp-server@latest'] };
543
490
 
491
+ function _tomlString(value) {
492
+ return JSON.stringify(value);
493
+ }
494
+
495
+ function _copyHooks(platform, dotDir, dir, hookFiles) {
496
+ const hooksSrc = join(TPLS, platform, 'hooks');
497
+ const hooksDest = join(dir, dotDir, 'hooks');
498
+ for (const file of hookFiles) {
499
+ const src = join(hooksSrc, file);
500
+ if (existsSync(src)) {
501
+ _writeFile(join(hooksDest, file), readFileSync(src, 'utf8'), `${dotDir}/hooks/${file}`);
502
+ }
503
+ }
504
+ }
505
+
506
+ function _copyCodexHooks(dir) {
507
+ _copyHooks('codex', '.codex', dir, [
508
+ 'context-mcp-pre-tool-use.js',
509
+ 'context-mcp-post-tool-use.js',
510
+ ]);
511
+ }
512
+
513
+ // Read JSON from path (returns {} on missing/invalid), run mutateFn, write back.
514
+ function _mergeJsonFile(filePath, label, mutateFn) {
515
+ let obj = {};
516
+ try { obj = JSON.parse(readFileSync(filePath, 'utf8')); } catch {}
517
+ mutateFn(obj);
518
+ _writeFile(filePath, JSON.stringify(obj, null, 2), label);
519
+ return obj;
520
+ }
521
+
522
+ // Merge `entries` ({ EventName: [hookGroup] }) into an existing settings.json
523
+ // hooks object, replacing any previously installed context-mcp groups.
524
+ function _mergeHooksIntoSettings(settingsPath, entries, label) {
525
+ let settings = {};
526
+ try { settings = JSON.parse(readFileSync(settingsPath, 'utf8')); } catch {}
527
+ settings.hooks = settings.hooks || {};
528
+ for (const [event, groups] of Object.entries(entries)) {
529
+ const existing = Array.isArray(settings.hooks[event]) ? settings.hooks[event] : [];
530
+ settings.hooks[event] = existing
531
+ .filter(g => !(g.hooks || []).some(h => String(h.command || '').includes('context-mcp-')))
532
+ .concat(groups);
533
+ }
534
+ _writeFile(settingsPath, JSON.stringify(settings, null, 2), label);
535
+ return settings;
536
+ }
537
+
538
+ function _codexConfigToml(dir, includeHooks = false) {
539
+ const lines = [
540
+ '[mcp_servers.context-mcp]',
541
+ 'command = "npx"',
542
+ 'args = ["-y", "context-mcp-server@latest"]',
543
+ 'default_tools_approval_mode = "prompt"',
544
+ '',
545
+ '[mcp_servers.context-mcp.tools.context]',
546
+ 'approval_mode = "approve"',
547
+ '',
548
+ '[mcp_servers.context-mcp.tools.search]',
549
+ 'approval_mode = "approve"',
550
+ '',
551
+ '[mcp_servers.context-mcp.tools.codegraph_query]',
552
+ 'approval_mode = "approve"',
553
+ ];
554
+
555
+ if (includeHooks) {
556
+ const preHook = join(dir, '.codex', 'hooks', 'context-mcp-pre-tool-use.js');
557
+ const postHook = join(dir, '.codex', 'hooks', 'context-mcp-post-tool-use.js');
558
+ lines.push(
559
+ '',
560
+ '[[hooks.PreToolUse]]',
561
+ 'matcher = "^Bash$"',
562
+ '',
563
+ '[[hooks.PreToolUse.hooks]]',
564
+ 'type = "command"',
565
+ `command = ${_tomlString(`node ${_tomlString(preHook)}`)}`,
566
+ `command_windows = ${_tomlString(`node ${_tomlString(preHook)}`)}`,
567
+ 'timeout = 30',
568
+ 'statusMessage = "Checking shell command"',
569
+ '',
570
+ '[[hooks.PostToolUse]]',
571
+ 'matcher = "^Bash$"',
572
+ '',
573
+ '[[hooks.PostToolUse.hooks]]',
574
+ 'type = "command"',
575
+ `command = ${_tomlString(`node ${_tomlString(postHook)}`)}`,
576
+ `command_windows = ${_tomlString(`node ${_tomlString(postHook)}`)}`,
577
+ 'timeout = 30',
578
+ 'statusMessage = "Saving failed shell context"',
579
+ );
580
+ }
581
+
582
+ return `${lines.join('\n')}\n`;
583
+ }
584
+
544
585
  const PLATFORMS = {
545
586
  claude: {
546
587
  label: 'Claude Code',
547
588
  restartNote: 'Type /context-resume in Claude Code to start using context-mcp.',
548
589
  install(dir, scope) {
549
- // Install as a skill (global, works across all projects) instead of a flat CLAUDE.md
550
- const skillSrc = join(TPLS, 'skills', 'SKILL.md');
590
+ // Skill user-global, works across all projects
591
+ const skillSrc = join(TPLS, 'claude', 'skills', 'SKILL.md');
551
592
  const skillDest = join(homedir(), '.claude', 'skills', 'context-mcp', 'SKILL.md');
552
593
  if (existsSync(skillSrc)) {
553
594
  _writeFile(skillDest, readFileSync(skillSrc, 'utf8'), '~/.claude/skills/context-mcp/');
554
595
  }
555
- // Slash commands are always user-global (not project-scoped)
596
+ // Slash commands user-global
556
597
  _writeCommands(homedir());
557
- // Register via claude CLI so the server is trusted immediately (no manual trust prompt)
598
+ // Rules file project root for project scope, ~/.claude/CLAUDE.md for global
599
+ const claudeMd = _tpl('claude/CLAUDE.md');
600
+ if (claudeMd) {
601
+ const claudeMdPath = scope === 'project'
602
+ ? join(dir, 'CLAUDE.md')
603
+ : join(homedir(), '.claude', 'CLAUDE.md');
604
+ _writeFile(claudeMdPath, claudeMd, scope === 'project' ? 'CLAUDE.md' : '~/.claude/CLAUDE.md');
605
+ }
606
+ // Hooks — write into the appropriate settings.json scope
607
+ // Project hooks live in .claude/hooks/ and are committed; user hooks in ~/.claude/hooks/
608
+ const hooksBase = scope === 'project' ? dir : homedir();
609
+ _copyHooks('claude', '.claude', hooksBase, [
610
+ 'context-mcp-pre-tool-use.js',
611
+ 'context-mcp-post-tool-use.js',
612
+ ]);
613
+ const preHook = join(hooksBase, '.claude', 'hooks', 'context-mcp-pre-tool-use.js');
614
+ const postHook = join(hooksBase, '.claude', 'hooks', 'context-mcp-post-tool-use.js');
615
+ const settingsPath = scope === 'project'
616
+ ? join(dir, '.claude', 'settings.json')
617
+ : join(homedir(), '.claude', 'settings.json');
618
+ _mergeHooksIntoSettings(settingsPath, {
619
+ PreToolUse: [{
620
+ matcher: 'Bash',
621
+ hooks: [{ type: 'command', command: preHook, timeout: 30, statusMessage: 'Checking shell command' }],
622
+ }],
623
+ PostToolUse: [{
624
+ matcher: 'Bash',
625
+ hooks: [{ type: 'command', command: postHook, timeout: 30, statusMessage: 'Saving failed shell context' }],
626
+ }],
627
+ }, scope === 'project' ? '.claude/settings.json' : '~/.claude/settings.json');
628
+ // Register MCP server via claude CLI
558
629
  const scopeFlag = scope === 'global' ? 'user' : 'project';
559
630
  const reg = spawnSync(
560
631
  'claude', ['mcp', 'add', '--scope', scopeFlag, 'context-mcp', '--', 'npx', '-y', 'context-mcp-server@latest'],
@@ -571,17 +642,34 @@ const PLATFORMS = {
571
642
  label: 'Cursor',
572
643
  restartNote: 'Restart Cursor to load the new MCP server.',
573
644
  install(dir, scope) {
645
+ // MCP config
574
646
  const mcpJson = JSON.stringify({ mcpServers: { 'context-mcp': MCP_SERVER_CMD } }, null, 2);
575
647
  _writeFile(join(dir, '.cursor', 'mcp.json'), mcpJson, '.cursor/mcp.json');
576
648
  if (scope === 'project') {
577
- const mdc = _tpl('cursor-rules.mdc');
649
+ // Rules
650
+ const mdc = _tpl('cursor/cursor-rules.mdc');
578
651
  if (mdc) _writeFile(join(dir, '.cursor', 'rules', 'context-mcp.mdc'), mdc, '.cursor/rules/context-mcp.mdc');
652
+ // Slash commands
653
+ const cmdsSrc = join(TPLS, 'cursor', 'commands');
654
+ for (const file of ['context-resume.md', 'graph-build.md', 'save-context.md']) {
655
+ const src = join(cmdsSrc, file);
656
+ if (existsSync(src)) _writeFile(join(dir, '.cursor', 'commands', file), readFileSync(src, 'utf8'), `.cursor/commands/${file}`);
657
+ }
658
+ // Hook script
659
+ _copyHooks('cursor', '.cursor', dir, ['context-mcp-post-tool-use.js']);
660
+ const hookPath = join(dir, '.cursor', 'hooks', 'context-mcp-post-tool-use.js');
661
+ // Merge into .cursor/hooks.json (must keep "version": 1)
662
+ _mergeJsonFile(join(dir, '.cursor', 'hooks.json'), '.cursor/hooks.json', obj => {
663
+ obj.version = 1;
664
+ obj.hooks = obj.hooks || {};
665
+ const strip = arr => (arr || []).filter(h => !String(h.command || '').includes('context-mcp-'));
666
+ obj.hooks.postToolUse = strip(obj.hooks.postToolUse).concat([
667
+ { command: `node "${hookPath}"`, timeout: 30 },
668
+ ]);
669
+ });
579
670
  }
580
- // Try to enable via cursor CLI (enable/disable only — no add command)
581
- const reg = spawnSync(
582
- 'cursor', ['agent', 'mcp', 'enable', 'context-mcp'],
583
- { encoding: 'utf8', shell: true },
584
- );
671
+ // Try to enable via cursor CLI
672
+ const reg = spawnSync('cursor', ['agent', 'mcp', 'enable', 'context-mcp'], { encoding: 'utf8', shell: true });
585
673
  if (reg.status === 0) {
586
674
  console.log(` ${ok('✓')} ${'enabled via cursor agent mcp'.padEnd(28)}`);
587
675
  }
@@ -590,22 +678,85 @@ const PLATFORMS = {
590
678
  vscode: {
591
679
  label: 'VS Code Copilot',
592
680
  restartNote: 'Reload VS Code window (Ctrl+Shift+P → "Reload Window").',
593
- install(dir) {
681
+ install(dir, scope) {
682
+ // MCP config
594
683
  const mcpJson = JSON.stringify({
595
684
  servers: { 'context-mcp': { type: 'stdio', ...MCP_SERVER_CMD } },
596
685
  }, null, 2);
597
686
  _writeFile(join(dir, '.vscode', 'mcp.json'), mcpJson, '.vscode/mcp.json');
687
+ if (scope === 'project') {
688
+ // Prompt files (.github/prompts/)
689
+ const cmdsSrc = join(TPLS, 'vscode', 'commands');
690
+ for (const file of ['context-resume.prompt.md', 'graph-build.prompt.md', 'save-context.prompt.md']) {
691
+ const src = join(cmdsSrc, file);
692
+ if (existsSync(src)) _writeFile(join(dir, '.github', 'prompts', file), readFileSync(src, 'utf8'), `.github/prompts/${file}`);
693
+ }
694
+ // Hook script
695
+ _copyHooks('vscode', '.vscode', dir, ['context-mcp-post-tool-use.js']);
696
+ const hookPath = join(dir, '.vscode', 'hooks', 'context-mcp-post-tool-use.js');
697
+ // Merge hooks into .vscode/settings.json under github.copilot.chat.agent.hooks
698
+ _mergeJsonFile(join(dir, '.vscode', 'settings.json'), '.vscode/settings.json', obj => {
699
+ const hooks = obj['github.copilot.chat.agent.hooks'] || {};
700
+ const strip = arr => (arr || []).filter(h => !String(h.command || '').includes('context-mcp-'));
701
+ hooks.PostToolUse = strip(hooks.PostToolUse).concat([
702
+ { type: 'command', command: `node "${hookPath}"`, timeout: 30, windows: `node "${hookPath}"` },
703
+ ]);
704
+ obj['github.copilot.chat.agent.hooks'] = hooks;
705
+ });
706
+ }
598
707
  },
599
708
  },
600
709
  gemini: {
601
710
  label: 'Gemini CLI',
602
711
  restartNote: 'Restart your Gemini CLI session.',
603
712
  install(dir, scope) {
604
- const cfg = JSON.stringify({ mcpServers: { 'context-mcp': MCP_SERVER_CMD } }, null, 2);
605
- _writeFile(join(dir, '.gemini', 'settings.json'), cfg, '.gemini/settings.json');
606
- if (scope === 'project') {
607
- const md = _tpl('GEMINI.md');
608
- if (md) _writeFile(join(dir, 'GEMINI.md'), md, 'GEMINI.md');
713
+ // MCP server config merged into existing settings.json
714
+ const settingsPath = scope === 'project'
715
+ ? join(dir, '.gemini', 'settings.json')
716
+ : join(homedir(), '.gemini', 'settings.json');
717
+ let settings = {};
718
+ try { settings = JSON.parse(readFileSync(settingsPath, 'utf8')); } catch {}
719
+ settings.mcpServers = settings.mcpServers || {};
720
+ settings.mcpServers['context-mcp'] = MCP_SERVER_CMD;
721
+ // Hooks — write BeforeTool/AfterTool into settings.json
722
+ const hooksBase = scope === 'project' ? dir : homedir();
723
+ _copyHooks('gemini', '.gemini', hooksBase, [
724
+ 'context-mcp-before-tool.js',
725
+ 'context-mcp-after-tool.js',
726
+ ]);
727
+ const beforeHook = join(hooksBase, '.gemini', 'hooks', 'context-mcp-before-tool.js');
728
+ const afterHook = join(hooksBase, '.gemini', 'hooks', 'context-mcp-after-tool.js');
729
+ settings.hooks = settings.hooks || {};
730
+ const stripOld = (arr) => (arr || []).filter(
731
+ g => !(g.hooks || []).some(h => String(h.command || '').includes('context-mcp-')),
732
+ );
733
+ settings.hooks.BeforeTool = stripOld(settings.hooks.BeforeTool).concat([{
734
+ matcher: 'run_shell_command',
735
+ hooks: [{ type: 'command', command: `node ${beforeHook}`, timeout: 30 }],
736
+ }]);
737
+ settings.hooks.AfterTool = stripOld(settings.hooks.AfterTool).concat([{
738
+ matcher: 'run_shell_command',
739
+ hooks: [{ type: 'command', command: `node ${afterHook}`, timeout: 30 }],
740
+ }]);
741
+ _writeFile(settingsPath,
742
+ JSON.stringify(settings, null, 2),
743
+ scope === 'project' ? '.gemini/settings.json' : '~/.gemini/settings.json',
744
+ );
745
+ // Slash commands (.toml) — project: .gemini/commands/, global: ~/.gemini/commands/
746
+ const geminiCmdsDest = join(hooksBase, '.gemini', 'commands');
747
+ const cmdsSrc = join(TPLS, 'gemini', 'commands');
748
+ for (const file of ['context-resume.toml', 'graph-build.toml', 'save-context.toml']) {
749
+ const src = join(cmdsSrc, file);
750
+ const label = scope === 'project' ? `.gemini/commands/${file}` : `~/.gemini/commands/${file}`;
751
+ if (existsSync(src)) _writeFile(join(geminiCmdsDest, file), readFileSync(src, 'utf8'), label);
752
+ }
753
+ // Rules file — project root for project scope, ~/.gemini/GEMINI.md for global
754
+ const geminiMd = _tpl('gemini/GEMINI.md');
755
+ if (geminiMd) {
756
+ const geminiMdPath = scope === 'project'
757
+ ? join(dir, 'GEMINI.md')
758
+ : join(homedir(), '.gemini', 'GEMINI.md');
759
+ _writeFile(geminiMdPath, geminiMd, scope === 'project' ? 'GEMINI.md' : '~/.gemini/GEMINI.md');
609
760
  }
610
761
  },
611
762
  },
@@ -613,11 +764,24 @@ const PLATFORMS = {
613
764
  label: 'Codex CLI',
614
765
  restartNote: 'Restart your Codex CLI session.',
615
766
  install(dir, scope) {
616
- const toml = `[[mcp_servers]]\nname = "context-mcp"\ncommand = "npx"\nargs = ["-y", "context-mcp-server@latest"]\n`;
617
- _writeFile(join(dir, '.codex', 'config.toml'), toml, '.codex/config.toml');
618
- if (scope === 'project') {
619
- const md = _tpl('AGENTS.md');
620
- if (md) _writeFile(join(dir, 'AGENTS.md'), md, 'AGENTS.md');
767
+ const includeHooks = scope === 'project';
768
+ if (includeHooks) _copyCodexHooks(dir);
769
+ _writeFile(join(dir, '.codex', 'config.toml'), _codexConfigToml(dir, includeHooks), '.codex/config.toml');
770
+ // Rules file — project root for project scope, ~/.codex/AGENTS.md for global
771
+ const codexMd = _tpl('codex/AGENTS.md');
772
+ if (codexMd) {
773
+ const codexMdPath = scope === 'project'
774
+ ? join(dir, 'AGENTS.md')
775
+ : join(homedir(), '.codex', 'AGENTS.md');
776
+ _writeFile(codexMdPath, codexMd, scope === 'project' ? 'AGENTS.md' : '~/.codex/AGENTS.md');
777
+ }
778
+ // Prompts (slash commands) — always user-global; Codex only loads ~/.codex/prompts/
779
+ const promptsSrc = join(TPLS, 'codex', 'prompts');
780
+ for (const file of ['context-resume.md', 'graph-build.md', 'save-context.md']) {
781
+ const src = join(promptsSrc, file);
782
+ if (existsSync(src)) {
783
+ _writeFile(join(homedir(), '.codex', 'prompts', file), readFileSync(src, 'utf8'), `~/.codex/prompts/${file}`);
784
+ }
621
785
  }
622
786
  // Register via codex CLI so server is active immediately
623
787
  const reg = spawnSync(
@@ -634,15 +798,68 @@ const PLATFORMS = {
634
798
  windsurf: {
635
799
  label: 'Windsurf',
636
800
  restartNote: 'Restart Windsurf to load the updated MCP config.',
637
- install(dir) {
638
- const rules = _tpl('windsurf-rules.md');
801
+ install(dir, scope) {
802
+ // Rules file
803
+ const rules = _tpl('windsurf/windsurf-rules.md');
639
804
  if (rules) _writeFile(join(dir, '.windsurf', 'rules', 'context-mcp.md'), rules, '.windsurf/rules/context-mcp.md');
640
- const globalCfgPath = join(homedir(), '.codeium', 'windsurf', 'mcp_config.json');
641
- let existing = {};
642
- try { existing = JSON.parse(readFileSync(globalCfgPath, 'utf8')); } catch {}
643
- existing.mcpServers = existing.mcpServers || {};
644
- existing.mcpServers['context-mcp'] = { command: 'npx', args: ['-y', 'context-mcp-server@latest'] };
645
- _writeFile(globalCfgPath, JSON.stringify(existing, null, 2), '~/.codeium/windsurf/mcp_config.json');
805
+ // Global MCP config (~/.codeium/windsurf/mcp_config.json)
806
+ _mergeJsonFile(join(homedir(), '.codeium', 'windsurf', 'mcp_config.json'), '~/.codeium/windsurf/mcp_config.json', obj => {
807
+ obj.mcpServers = obj.mcpServers || {};
808
+ obj.mcpServers['context-mcp'] = { command: 'npx', args: ['-y', 'context-mcp-server@latest'] };
809
+ });
810
+ if (scope === 'project') {
811
+ // Workflows (slash commands)
812
+ const wfSrc = join(TPLS, 'windsurf', 'workflows');
813
+ for (const file of ['context-resume.md', 'graph-build.md', 'save-context.md']) {
814
+ const src = join(wfSrc, file);
815
+ if (existsSync(src)) _writeFile(join(dir, '.windsurf', 'workflows', file), readFileSync(src, 'utf8'), `.windsurf/workflows/${file}`);
816
+ }
817
+ // Hook script + .windsurf/hooks.json
818
+ _copyHooks('windsurf', '.windsurf', dir, ['context-mcp-post-run-command.js']);
819
+ const hookPath = join(dir, '.windsurf', 'hooks', 'context-mcp-post-run-command.js');
820
+ _mergeJsonFile(join(dir, '.windsurf', 'hooks.json'), '.windsurf/hooks.json', obj => {
821
+ obj.hooks = obj.hooks || {};
822
+ const strip = arr => (arr || []).filter(h => !String(h.command || '').includes('context-mcp-'));
823
+ obj.hooks.post_run_command = strip(obj.hooks.post_run_command).concat([{
824
+ command: `node "${hookPath}"`,
825
+ powershell: `node "${hookPath}"`,
826
+ show_output: false,
827
+ }]);
828
+ });
829
+ }
830
+ },
831
+ },
832
+ antigravity: {
833
+ label: 'Antigravity IDE',
834
+ restartNote: 'Restart your Antigravity session to pick up hooks and rules.',
835
+ install(dir, scope) {
836
+ // Antigravity uses stdio-incompatible MCP transport — integrate via ctx CLI + GEMINI.md instead.
837
+ if (scope === 'project') {
838
+ // Post-tool hook — saves failed tool calls to context-mcp via ctx CLI
839
+ _copyHooks('antigravity', '.agents', dir, ['context-mcp-post-tool-use.js']);
840
+ const hookPath = join(dir, '.agents', 'hooks', 'context-mcp-post-tool-use.js');
841
+ _mergeJsonFile(join(dir, '.agents', 'hooks.json'), '.agents/hooks.json', obj => {
842
+ const strip = arr => (arr || []).filter(h => !String(h.command || '').includes('context-mcp-'));
843
+ obj.hooks = strip(obj.hooks).concat([{
844
+ event: 'PostToolUse',
845
+ command: `node "${hookPath}"`,
846
+ }]);
847
+ });
848
+ // Workflows (slash commands) — .agents/workflows/
849
+ const wfSrc = join(TPLS, 'antigravity', 'workflows');
850
+ for (const file of ['context-resume.md', 'graph-build.md', 'save-context.md']) {
851
+ const src = join(wfSrc, file);
852
+ if (existsSync(src)) _writeFile(join(dir, '.agents', 'workflows', file), readFileSync(src, 'utf8'), `.agents/workflows/${file}`);
853
+ }
854
+ }
855
+ // Rules file — project root for project scope, ~/.config/antigravity/GEMINI.md for global
856
+ const agMd = _tpl('antigravity/GEMINI.md');
857
+ if (agMd) {
858
+ const agMdPath = scope === 'project'
859
+ ? join(dir, 'GEMINI.md')
860
+ : join(homedir(), '.config', 'antigravity', 'GEMINI.md');
861
+ _writeFile(agMdPath, agMd, scope === 'project' ? 'GEMINI.md' : '~/.config/antigravity/GEMINI.md');
862
+ }
646
863
  },
647
864
  },
648
865
  };
@@ -706,7 +923,7 @@ async function cmdInstall(args) {
706
923
 
707
924
  if (!keys.length) {
708
925
  printSection('Install');
709
- console.log(` ${muted('Usage:')} ctx install ${faint('[--initial] [--claude] [--cursor] [--vscode] [--gemini] [--codex] [--windsurf] [--all]')}`);
926
+ console.log(` ${muted('Usage:')} ctx install ${faint('[--initial] [--claude] [--cursor] [--vscode] [--gemini] [--codex] [--windsurf] [--antigravity] [--all]')}`);
710
927
  console.log('');
711
928
  console.log(` ${accent('--initial ')} ${faint('Install / update Node.js + Python (codegraph) deps')}`);
712
929
  console.log('');
@@ -1100,8 +1317,6 @@ async function interactive() {
1100
1317
  clearScreen(); printCompactHeader('discussions'); cmdDiscussions(rest); break;
1101
1318
  case 'summary':
1102
1319
  clearScreen(); printCompactHeader('summary'); cmdSummary(rest); break;
1103
- case 'stats':
1104
- clearScreen(); printCompactHeader('stats'); cmdStats(); break;
1105
1320
  case 'install':
1106
1321
  clearScreen(); printCompactHeader('install'); await cmdInstall(rest); break;
1107
1322
  case 'online':
@@ -1166,8 +1381,6 @@ async function checkForUpdate() {
1166
1381
  cmdDiscussions(rest); break;
1167
1382
  case 'summary':
1168
1383
  cmdSummary(rest); break;
1169
- case 'stats':
1170
- cmdStats(); break;
1171
1384
  case 'install':
1172
1385
  await cmdInstall(rest);
1173
1386
  process.exit(0);
package/src/server.js CHANGED
@@ -1,8 +1,14 @@
1
1
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
2
  import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
3
+ import { readFileSync } from 'node:fs';
4
+ import { join, dirname } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
3
6
 
4
7
  import { getConfig } from './config.js';
5
8
 
9
+ const __dirname = dirname(fileURLToPath(import.meta.url));
10
+ const { version } = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
11
+
6
12
  import * as contextTool from './tools/context.js';
7
13
  import * as searchTool from './tools/search.js';
8
14
  import * as planTool from './tools/plan.js';
@@ -23,7 +29,7 @@ export function createServer({ enableFileTools = false, enableGitTools = getConf
23
29
  };
24
30
 
25
31
  const server = new Server(
26
- { name: 'context-mcp', version: '1.0.0' },
32
+ { name: 'context-mcp', version },
27
33
  { capabilities: { tools: {} } }
28
34
  );
29
35
 
@@ -0,0 +1,96 @@
1
+ # Context-MCP — Antigravity IDE Usage Guide
2
+
3
+ Persistent memory + codebase knowledge graph via `ctx` CLI commands.
4
+ Run `ctx resume` to start every session. Use `ctx search` to recall past work. Use `ctx save` to persist decisions. Use `ctx add` to record discoveries.
5
+
6
+ ---
7
+
8
+ ## 1. Start of Every Conversation (MANDATORY)
9
+
10
+ Run `ctx resume --project <project>` (shell command) before anything else.
11
+
12
+ Returns recent entries, active plans, graph status.
13
+
14
+ - Graph built → use `ctx graph query` / `ctx graph arch` before reading files
15
+ - Graph not built → run `ctx graph build <path>` first
16
+ - `totalEntries ≥ 20` → save compaction summary FIRST (see Rule 4)
17
+ - Active plans present → read them before starting new work
18
+
19
+ ---
20
+
21
+ ## 2. Save Triggers (MANDATORY)
22
+
23
+ Run `ctx save` after finishing anything worth keeping:
24
+
25
+ ```
26
+ ctx save --project <project> --title "..." --why "..." --outcome "..." --type note
27
+ ctx save --project <project> --title "..." --content "..." --type bug
28
+ ctx save --project <project> --title "Session summary — YYYY-MM-DD" --content "..." --type compaction
29
+ ```
30
+
31
+ | Trigger | Command |
32
+ |---|---|
33
+ | Task / fix / feature complete | `ctx save` with `--why` + `--outcome` + `--files` |
34
+ | Decision made | `ctx save` with `--why` + `--outcome` |
35
+ | Discovery / constraint | `ctx save` with `--content` |
36
+ | User says "save this" | `ctx save` with `--title` + `--content` |
37
+ | Compact memory | `ctx save --type compaction` |
38
+
39
+ **Do NOT save:** routine reads, search results, explanations of existing code.
40
+
41
+ ---
42
+
43
+ ## 3. Plans (MANDATORY for multi-file work)
44
+
45
+ **Create a plan when:** editing 2+ files, multi-step implementation, refactor, multi-file bug fix.
46
+
47
+ ```
48
+ ctx plan save --project <project> --name "..." --content "..."
49
+ ctx plan done --project <project> --name "..." # deletes the plan
50
+ ```
51
+
52
+ Check active plans on resume — don't create duplicates.
53
+
54
+ ---
55
+
56
+ ## 4. Auto-Summary at ≥ 20 Entries (MANDATORY)
57
+
58
+ When `totalEntries ≥ 20`, run this BEFORE the user's task:
59
+
60
+ ```
61
+ ctx save --project <project> --type compaction \
62
+ --title "Session summary — YYYY-MM-DD" \
63
+ --content "<what was built, decided, broke, current state>"
64
+ ```
65
+
66
+ ---
67
+
68
+ ## 5. Search Before Asking
69
+
70
+ Run `ctx search "<query>" --project <project>` before asking the user to re-explain past work.
71
+
72
+ ---
73
+
74
+ ## 6. ContextGraph CLI
75
+
76
+ ```
77
+ ctx graph build <path> → build AST graph (run once, incremental)
78
+ ctx graph arch <path> → module map: files, exports, imports
79
+ ctx graph query <path> "<question>" → structural question about the codebase
80
+ ctx graph nodes <path> <type> → list all nodes of a type
81
+ ctx graph report <path> → god nodes, clusters, surprising connections
82
+ ```
83
+
84
+ Use `ctx graph arch` first. Never read files for structure questions.
85
+
86
+ ---
87
+
88
+ ## 7. Rules
89
+
90
+ 1. `ctx resume` first — before any tool or response
91
+ 2. Always pass `--project`
92
+ 3. Save on task complete — `--why` + `--outcome` + `--files`
93
+ 4. Compaction at ≥ 20 entries — before starting task
94
+ 5. Plan before multi-file work — `ctx plan done` deletes it
95
+ 6. `ctx search` before asking about past work
96
+ 7. `ctx graph` before reading files