context-mcp-server 1.1.0 → 1.1.1

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 +277 -86
  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,136 @@ 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
+ // Hooks write into the appropriate settings.json scope
599
+ // Project hooks live in .claude/hooks/ and are committed; user hooks in ~/.claude/hooks/
600
+ const hooksBase = scope === 'project' ? dir : homedir();
601
+ _copyHooks('claude', '.claude', hooksBase, [
602
+ 'context-mcp-pre-tool-use.js',
603
+ 'context-mcp-post-tool-use.js',
604
+ ]);
605
+ const preHook = join(hooksBase, '.claude', 'hooks', 'context-mcp-pre-tool-use.js');
606
+ const postHook = join(hooksBase, '.claude', 'hooks', 'context-mcp-post-tool-use.js');
607
+ const settingsPath = scope === 'project'
608
+ ? join(dir, '.claude', 'settings.json')
609
+ : join(homedir(), '.claude', 'settings.json');
610
+ _mergeHooksIntoSettings(settingsPath, {
611
+ PreToolUse: [{
612
+ matcher: 'Bash',
613
+ hooks: [{ type: 'command', command: preHook, timeout: 30, statusMessage: 'Checking shell command' }],
614
+ }],
615
+ PostToolUse: [{
616
+ matcher: 'Bash',
617
+ hooks: [{ type: 'command', command: postHook, timeout: 30, statusMessage: 'Saving failed shell context' }],
618
+ }],
619
+ }, scope === 'project' ? '.claude/settings.json' : '~/.claude/settings.json');
620
+ // Register MCP server via claude CLI
558
621
  const scopeFlag = scope === 'global' ? 'user' : 'project';
559
622
  const reg = spawnSync(
560
623
  'claude', ['mcp', 'add', '--scope', scopeFlag, 'context-mcp', '--', 'npx', '-y', 'context-mcp-server@latest'],
@@ -571,17 +634,34 @@ const PLATFORMS = {
571
634
  label: 'Cursor',
572
635
  restartNote: 'Restart Cursor to load the new MCP server.',
573
636
  install(dir, scope) {
637
+ // MCP config
574
638
  const mcpJson = JSON.stringify({ mcpServers: { 'context-mcp': MCP_SERVER_CMD } }, null, 2);
575
639
  _writeFile(join(dir, '.cursor', 'mcp.json'), mcpJson, '.cursor/mcp.json');
576
640
  if (scope === 'project') {
577
- const mdc = _tpl('cursor-rules.mdc');
641
+ // Rules
642
+ const mdc = _tpl('cursor/cursor-rules.mdc');
578
643
  if (mdc) _writeFile(join(dir, '.cursor', 'rules', 'context-mcp.mdc'), mdc, '.cursor/rules/context-mcp.mdc');
644
+ // Slash commands
645
+ const cmdsSrc = join(TPLS, 'cursor', 'commands');
646
+ for (const file of ['context-resume.md', 'graph-build.md', 'save-context.md']) {
647
+ const src = join(cmdsSrc, file);
648
+ if (existsSync(src)) _writeFile(join(dir, '.cursor', 'commands', file), readFileSync(src, 'utf8'), `.cursor/commands/${file}`);
649
+ }
650
+ // Hook script
651
+ _copyHooks('cursor', '.cursor', dir, ['context-mcp-post-tool-use.js']);
652
+ const hookPath = join(dir, '.cursor', 'hooks', 'context-mcp-post-tool-use.js');
653
+ // Merge into .cursor/hooks.json (must keep "version": 1)
654
+ _mergeJsonFile(join(dir, '.cursor', 'hooks.json'), '.cursor/hooks.json', obj => {
655
+ obj.version = 1;
656
+ obj.hooks = obj.hooks || {};
657
+ const strip = arr => (arr || []).filter(h => !String(h.command || '').includes('context-mcp-'));
658
+ obj.hooks.postToolUse = strip(obj.hooks.postToolUse).concat([
659
+ { command: `node "${hookPath}"`, timeout: 30 },
660
+ ]);
661
+ });
579
662
  }
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
- );
663
+ // Try to enable via cursor CLI
664
+ const reg = spawnSync('cursor', ['agent', 'mcp', 'enable', 'context-mcp'], { encoding: 'utf8', shell: true });
585
665
  if (reg.status === 0) {
586
666
  console.log(` ${ok('✓')} ${'enabled via cursor agent mcp'.padEnd(28)}`);
587
667
  }
@@ -590,21 +670,79 @@ const PLATFORMS = {
590
670
  vscode: {
591
671
  label: 'VS Code Copilot',
592
672
  restartNote: 'Reload VS Code window (Ctrl+Shift+P → "Reload Window").',
593
- install(dir) {
673
+ install(dir, scope) {
674
+ // MCP config
594
675
  const mcpJson = JSON.stringify({
595
676
  servers: { 'context-mcp': { type: 'stdio', ...MCP_SERVER_CMD } },
596
677
  }, null, 2);
597
678
  _writeFile(join(dir, '.vscode', 'mcp.json'), mcpJson, '.vscode/mcp.json');
679
+ if (scope === 'project') {
680
+ // Prompt files (.github/prompts/)
681
+ const cmdsSrc = join(TPLS, 'vscode', 'commands');
682
+ for (const file of ['context-resume.prompt.md', 'graph-build.prompt.md', 'save-context.prompt.md']) {
683
+ const src = join(cmdsSrc, file);
684
+ if (existsSync(src)) _writeFile(join(dir, '.github', 'prompts', file), readFileSync(src, 'utf8'), `.github/prompts/${file}`);
685
+ }
686
+ // Hook script
687
+ _copyHooks('vscode', '.vscode', dir, ['context-mcp-post-tool-use.js']);
688
+ const hookPath = join(dir, '.vscode', 'hooks', 'context-mcp-post-tool-use.js');
689
+ // Merge hooks into .vscode/settings.json under github.copilot.chat.agent.hooks
690
+ _mergeJsonFile(join(dir, '.vscode', 'settings.json'), '.vscode/settings.json', obj => {
691
+ const hooks = obj['github.copilot.chat.agent.hooks'] || {};
692
+ const strip = arr => (arr || []).filter(h => !String(h.command || '').includes('context-mcp-'));
693
+ hooks.PostToolUse = strip(hooks.PostToolUse).concat([
694
+ { type: 'command', command: `node "${hookPath}"`, timeout: 30, windows: `node "${hookPath}"` },
695
+ ]);
696
+ obj['github.copilot.chat.agent.hooks'] = hooks;
697
+ });
698
+ }
598
699
  },
599
700
  },
600
701
  gemini: {
601
702
  label: 'Gemini CLI',
602
703
  restartNote: 'Restart your Gemini CLI session.',
603
704
  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');
705
+ // MCP server config merged into existing settings.json
706
+ const settingsPath = scope === 'project'
707
+ ? join(dir, '.gemini', 'settings.json')
708
+ : join(homedir(), '.gemini', 'settings.json');
709
+ let settings = {};
710
+ try { settings = JSON.parse(readFileSync(settingsPath, 'utf8')); } catch {}
711
+ settings.mcpServers = settings.mcpServers || {};
712
+ settings.mcpServers['context-mcp'] = MCP_SERVER_CMD;
713
+ // Hooks — write BeforeTool/AfterTool into settings.json
714
+ const hooksBase = scope === 'project' ? dir : homedir();
715
+ _copyHooks('gemini', '.gemini', hooksBase, [
716
+ 'context-mcp-before-tool.js',
717
+ 'context-mcp-after-tool.js',
718
+ ]);
719
+ const beforeHook = join(hooksBase, '.gemini', 'hooks', 'context-mcp-before-tool.js');
720
+ const afterHook = join(hooksBase, '.gemini', 'hooks', 'context-mcp-after-tool.js');
721
+ settings.hooks = settings.hooks || {};
722
+ const stripOld = (arr) => (arr || []).filter(
723
+ g => !(g.hooks || []).some(h => String(h.command || '').includes('context-mcp-')),
724
+ );
725
+ settings.hooks.BeforeTool = stripOld(settings.hooks.BeforeTool).concat([{
726
+ matcher: 'run_shell_command',
727
+ hooks: [{ type: 'command', command: `node ${beforeHook}`, timeout: 30 }],
728
+ }]);
729
+ settings.hooks.AfterTool = stripOld(settings.hooks.AfterTool).concat([{
730
+ matcher: 'run_shell_command',
731
+ hooks: [{ type: 'command', command: `node ${afterHook}`, timeout: 30 }],
732
+ }]);
733
+ _writeFile(settingsPath,
734
+ JSON.stringify(settings, null, 2),
735
+ scope === 'project' ? '.gemini/settings.json' : '~/.gemini/settings.json',
736
+ );
737
+ // Slash commands (.toml) — project-scoped only
606
738
  if (scope === 'project') {
607
- const md = _tpl('GEMINI.md');
739
+ const cmdsSrc = join(TPLS, 'gemini', 'commands');
740
+ const cmdsDest = join(dir, '.gemini', 'commands');
741
+ for (const file of ['context-resume.toml', 'graph-build.toml', 'save-context.toml']) {
742
+ const src = join(cmdsSrc, file);
743
+ if (existsSync(src)) _writeFile(join(cmdsDest, file), readFileSync(src, 'utf8'), `.gemini/commands/${file}`);
744
+ }
745
+ const md = _tpl('gemini/GEMINI.md');
608
746
  if (md) _writeFile(join(dir, 'GEMINI.md'), md, 'GEMINI.md');
609
747
  }
610
748
  },
@@ -613,12 +751,21 @@ const PLATFORMS = {
613
751
  label: 'Codex CLI',
614
752
  restartNote: 'Restart your Codex CLI session.',
615
753
  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');
754
+ const includeHooks = scope === 'project';
755
+ if (includeHooks) _copyCodexHooks(dir);
756
+ _writeFile(join(dir, '.codex', 'config.toml'), _codexConfigToml(dir, includeHooks), '.codex/config.toml');
618
757
  if (scope === 'project') {
619
- const md = _tpl('AGENTS.md');
758
+ const md = _tpl('codex/AGENTS.md');
620
759
  if (md) _writeFile(join(dir, 'AGENTS.md'), md, 'AGENTS.md');
621
760
  }
761
+ // Prompts (slash commands) — always user-global; Codex only loads ~/.codex/prompts/
762
+ const promptsSrc = join(TPLS, 'codex', 'prompts');
763
+ for (const file of ['context-resume.md', 'graph-build.md', 'save-context.md']) {
764
+ const src = join(promptsSrc, file);
765
+ if (existsSync(src)) {
766
+ _writeFile(join(homedir(), '.codex', 'prompts', file), readFileSync(src, 'utf8'), `~/.codex/prompts/${file}`);
767
+ }
768
+ }
622
769
  // Register via codex CLI so server is active immediately
623
770
  const reg = spawnSync(
624
771
  'codex', ['mcp', 'add', 'context-mcp', '--', 'npx', '-y', 'context-mcp-server@latest'],
@@ -634,15 +781,63 @@ const PLATFORMS = {
634
781
  windsurf: {
635
782
  label: 'Windsurf',
636
783
  restartNote: 'Restart Windsurf to load the updated MCP config.',
637
- install(dir) {
638
- const rules = _tpl('windsurf-rules.md');
784
+ install(dir, scope) {
785
+ // Rules file
786
+ const rules = _tpl('windsurf/windsurf-rules.md');
639
787
  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');
788
+ // Global MCP config (~/.codeium/windsurf/mcp_config.json)
789
+ _mergeJsonFile(join(homedir(), '.codeium', 'windsurf', 'mcp_config.json'), '~/.codeium/windsurf/mcp_config.json', obj => {
790
+ obj.mcpServers = obj.mcpServers || {};
791
+ obj.mcpServers['context-mcp'] = { command: 'npx', args: ['-y', 'context-mcp-server@latest'] };
792
+ });
793
+ if (scope === 'project') {
794
+ // Workflows (slash commands)
795
+ const wfSrc = join(TPLS, 'windsurf', 'workflows');
796
+ for (const file of ['context-resume.md', 'graph-build.md', 'save-context.md']) {
797
+ const src = join(wfSrc, file);
798
+ if (existsSync(src)) _writeFile(join(dir, '.windsurf', 'workflows', file), readFileSync(src, 'utf8'), `.windsurf/workflows/${file}`);
799
+ }
800
+ // Hook script + .windsurf/hooks.json
801
+ _copyHooks('windsurf', '.windsurf', dir, ['context-mcp-post-run-command.js']);
802
+ const hookPath = join(dir, '.windsurf', 'hooks', 'context-mcp-post-run-command.js');
803
+ _mergeJsonFile(join(dir, '.windsurf', 'hooks.json'), '.windsurf/hooks.json', obj => {
804
+ obj.hooks = obj.hooks || {};
805
+ const strip = arr => (arr || []).filter(h => !String(h.command || '').includes('context-mcp-'));
806
+ obj.hooks.post_run_command = strip(obj.hooks.post_run_command).concat([{
807
+ command: `node "${hookPath}"`,
808
+ powershell: `node "${hookPath}"`,
809
+ show_output: false,
810
+ }]);
811
+ });
812
+ }
813
+ },
814
+ },
815
+ antigravity: {
816
+ label: 'Antigravity IDE',
817
+ restartNote: 'Restart your Antigravity session to pick up hooks and rules.',
818
+ install(dir, scope) {
819
+ // Antigravity uses stdio-incompatible MCP transport — integrate via ctx CLI + GEMINI.md instead.
820
+ if (scope === 'project') {
821
+ // Post-tool hook — saves failed tool calls to context-mcp via ctx CLI
822
+ _copyHooks('antigravity', '.agents', dir, ['context-mcp-post-tool-use.js']);
823
+ const hookPath = join(dir, '.agents', 'hooks', 'context-mcp-post-tool-use.js');
824
+ _mergeJsonFile(join(dir, '.agents', 'hooks.json'), '.agents/hooks.json', obj => {
825
+ const strip = arr => (arr || []).filter(h => !String(h.command || '').includes('context-mcp-'));
826
+ obj.hooks = strip(obj.hooks).concat([{
827
+ event: 'PostToolUse',
828
+ command: `node "${hookPath}"`,
829
+ }]);
830
+ });
831
+ // Workflows (slash commands) — .agents/workflows/
832
+ const wfSrc = join(TPLS, 'antigravity', 'workflows');
833
+ for (const file of ['context-resume.md', 'graph-build.md', 'save-context.md']) {
834
+ const src = join(wfSrc, file);
835
+ if (existsSync(src)) _writeFile(join(dir, '.agents', 'workflows', file), readFileSync(src, 'utf8'), `.agents/workflows/${file}`);
836
+ }
837
+ // Rules file — Antigravity reads GEMINI.md at project root
838
+ const md = _tpl('antigravity/GEMINI.md');
839
+ if (md) _writeFile(join(dir, 'GEMINI.md'), md, 'GEMINI.md');
840
+ }
646
841
  },
647
842
  },
648
843
  };
@@ -706,7 +901,7 @@ async function cmdInstall(args) {
706
901
 
707
902
  if (!keys.length) {
708
903
  printSection('Install');
709
- console.log(` ${muted('Usage:')} ctx install ${faint('[--initial] [--claude] [--cursor] [--vscode] [--gemini] [--codex] [--windsurf] [--all]')}`);
904
+ console.log(` ${muted('Usage:')} ctx install ${faint('[--initial] [--claude] [--cursor] [--vscode] [--gemini] [--codex] [--windsurf] [--antigravity] [--all]')}`);
710
905
  console.log('');
711
906
  console.log(` ${accent('--initial ')} ${faint('Install / update Node.js + Python (codegraph) deps')}`);
712
907
  console.log('');
@@ -1100,8 +1295,6 @@ async function interactive() {
1100
1295
  clearScreen(); printCompactHeader('discussions'); cmdDiscussions(rest); break;
1101
1296
  case 'summary':
1102
1297
  clearScreen(); printCompactHeader('summary'); cmdSummary(rest); break;
1103
- case 'stats':
1104
- clearScreen(); printCompactHeader('stats'); cmdStats(); break;
1105
1298
  case 'install':
1106
1299
  clearScreen(); printCompactHeader('install'); await cmdInstall(rest); break;
1107
1300
  case 'online':
@@ -1166,8 +1359,6 @@ async function checkForUpdate() {
1166
1359
  cmdDiscussions(rest); break;
1167
1360
  case 'summary':
1168
1361
  cmdSummary(rest); break;
1169
- case 'stats':
1170
- cmdStats(); break;
1171
1362
  case 'install':
1172
1363
  await cmdInstall(rest);
1173
1364
  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
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Antigravity IDE PostToolUse hook for context-mcp.
5
+ *
6
+ * Input (stdin JSON): {
7
+ * toolCall: { name, args },
8
+ * stepIdx, conversationId, workspacePaths,
9
+ * error ← present when the tool invocation failed
10
+ * }
11
+ *
12
+ * Saves failed tool invocations to context-mcp so the next session can see
13
+ * what broke. Exits silently when the tool succeeded or no error is present.
14
+ */
15
+
16
+ import { spawnSync } from 'node:child_process';
17
+
18
+ process.stdin.resume();
19
+ process.stdin.setEncoding('utf8');
20
+
21
+ let input = '';
22
+ process.stdin.on('data', chunk => { input += chunk; });
23
+ process.stdin.on('end', () => {
24
+ let event = {};
25
+ try {
26
+ event = input.trim() ? JSON.parse(input) : {};
27
+ } catch {
28
+ return;
29
+ }
30
+
31
+ const toolCall = event.toolCall || {};
32
+ const toolName = toolCall.name || '';
33
+ const error = event.error;
34
+
35
+ if (!error || !toolName) return;
36
+
37
+ const args = toolCall.args || {};
38
+ const argsSnippet = Object.keys(args).length
39
+ ? JSON.stringify(args).slice(0, 200)
40
+ : '';
41
+
42
+ const cwd = (event.workspacePaths || [])[0] || process.cwd();
43
+ const project = cwd.replace(/\\/g, '/').replace(/\/$/, '').split('/').pop() || 'default';
44
+
45
+ const content = [
46
+ `Tool: ${toolName}`,
47
+ argsSnippet ? `Args: ${argsSnippet}` : null,
48
+ `Error: ${String(error).slice(0, 4000)}`,
49
+ ].filter(Boolean).join('\n\n');
50
+
51
+ spawnSync('ctx', [
52
+ 'save',
53
+ '--project', project,
54
+ '--type', 'bug',
55
+ '--title', `Failed tool: ${toolName.slice(0, 80)}`,
56
+ '--content', content,
57
+ ], {
58
+ encoding: 'utf8',
59
+ shell: true,
60
+ stdio: 'ignore',
61
+ });
62
+ });