gitmem-mcp 1.0.14 → 1.1.0

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/CHANGELOG.md CHANGED
@@ -7,6 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.1.0] - 2026-02-17
11
+
12
+ ### Added
13
+ - **Cursor IDE support**: `npx gitmem-mcp init` auto-detects Cursor projects (`.cursor/` directory) and generates Cursor-specific config: `.cursor/mcp.json`, `.cursorrules`, `.cursor/hooks.json` with camelCase event names. Also supports `--client cursor` flag for explicit selection.
14
+ - **Cursor uninstall**: `npx gitmem-mcp uninstall` cleanly removes gitmem from Cursor config while preserving user hooks, other MCP servers, and existing `.cursorrules` content.
15
+ - **Cursor clean room testing**: Docker container (`Dockerfile.cursor`) with Cursor CLI v2026.02.13 + gitmem for end-to-end validation. Includes comprehensive test plan (16 tests across 3 phases).
16
+ - **34 new E2E tests**: Cross-tool Cursor integration tests covering init/uninstall for both clients, idempotency, content isolation, and edge cases.
17
+ - **454 new unit tests**: Confirm-scars rejection rate tests, recall threshold tests.
18
+
19
+ ### Fixed
20
+ - **Confirm-scars rejection rate**: Reduced false rejections by improving scar matching tolerance.
21
+ - **Recall relevance threshold**: Added minimum relevance floor to reduce noise in recall results.
22
+ - **Recall nudge**: Improved guidance when recall returns low-relevance results.
23
+
24
+ ### Validated
25
+ - Independent Cursor AI agent scored gitmem **88% (18.5/21)** across 7 test scenarios run 3 times each. Verdict: "GitMem is a must-have." ([OD-695](https://linear.app/nteg-labs/issue/OD-695), [OD-696](https://linear.app/nteg-labs/issue/OD-696), [OD-697](https://linear.app/nteg-labs/issue/OD-697), [OD-698](https://linear.app/nteg-labs/issue/OD-698) filed from findings.)
26
+
27
+ ## [1.0.15] - 2026-02-16
28
+
29
+ ### Fixed
30
+ - **Thread dedup without API key**: Dedup silently fell back to exact text match when no embedding API key (OpenAI/OpenRouter/Ollama) was set — which is the default for free tier users. Near-duplicate threads with the same topic but different wording slipped through. Added zero-dependency token overlap coefficient as a middle tier (threshold 0.6, lowered to 0.4 when threads share an issue prefix like `OD-692:`). Also upgraded `deduplicateThreadList` with the same logic. +18 unit tests.
31
+
10
32
  ## [1.0.12] - 2026-02-16
11
33
 
12
34
  ### Fixed
package/README.md CHANGED
@@ -5,8 +5,8 @@
5
5
  <p align="center">
6
6
  <a href="https://www.npmjs.com/package/gitmem-mcp"><img src="https://img.shields.io/npm/v/gitmem-mcp?style=flat-square&color=ed1e25&label=npm" alt="npm version" /></a>
7
7
  <a href="https://www.npmjs.com/package/gitmem-mcp"><img src="https://img.shields.io/npm/dm/gitmem-mcp?style=flat-square&color=333333&label=downloads" alt="npm downloads" /></a>
8
- <img src="https://img.shields.io/badge/license-MIT-ed1e25?style=flat-square" alt="MIT License" />
9
- <img src="https://img.shields.io/badge/build-passing-333333?style=flat-square" alt="Build" />
8
+ <a href="https://github.com/nTEG-dev/gitmem/blob/main/LICENSE"><img src="https://img.shields.io/github/license/nTEG-dev/gitmem?style=flat-square&color=ed1e25" alt="MIT License" /></a>
9
+ <a href="https://github.com/nTEG-dev/gitmem/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/nTEG-dev/gitmem/ci.yml?style=flat-square&color=333333&label=build" alt="Build" /></a>
10
10
  <img src="https://img.shields.io/badge/node-%3E%3D22-ed1e25?style=flat-square" alt="Node.js >= 22" />
11
11
  </p>
12
12
 
package/bin/gitmem.js CHANGED
@@ -40,19 +40,26 @@ Usage:
40
40
  npx gitmem-mcp init Interactive setup wizard (recommended)
41
41
  npx gitmem-mcp init --yes Non-interactive setup (accept all defaults)
42
42
  npx gitmem-mcp init --dry-run Show what would be configured
43
+ npx gitmem-mcp init --client cursor Set up for Cursor IDE
43
44
  npx gitmem-mcp uninstall Clean removal of gitmem from project
44
45
  npx gitmem-mcp uninstall --all Also delete .gitmem/ data directory
45
46
 
46
47
  Other commands:
47
48
  npx gitmem-mcp setup Output SQL for Supabase schema setup (pro/dev tier)
48
- npx gitmem-mcp configure Generate .mcp.json config for Claude Code
49
+ npx gitmem-mcp configure Generate .mcp.json config for Claude Code / Cursor
49
50
  npx gitmem-mcp check Run diagnostic health check
50
51
  npx gitmem-mcp check --full Full diagnostic with benchmarks
51
- npx gitmem-mcp install-hooks Install Claude Code hooks (standalone)
52
- npx gitmem-mcp uninstall-hooks Remove Claude Code hooks (standalone)
52
+ npx gitmem-mcp install-hooks Install hooks (standalone)
53
+ npx gitmem-mcp uninstall-hooks Remove hooks (standalone)
53
54
  npx gitmem-mcp server Start MCP server (default)
54
55
  npx gitmem-mcp help Show this help message
55
56
 
57
+ Options:
58
+ --client <claude|cursor> Target IDE (auto-detected if not specified)
59
+ --project <name> Set project namespace
60
+ --yes / -y Accept all defaults (non-interactive)
61
+ --dry-run Show what would change without writing files
62
+
56
63
  Quick Start:
57
64
  npx gitmem-mcp init One command sets up everything
58
65
  npx gitmem-mcp uninstall One command removes everything
@@ -459,19 +466,37 @@ async function cmdSessionRefresh() {
459
466
  }
460
467
 
461
468
  /**
462
- * Install gitmem hooks as project-level hooks in .claude/settings.json
469
+ * Install gitmem hooks as project-level hooks.
470
+ *
471
+ * Claude Code: writes to .claude/settings.json
472
+ * Cursor: writes to .cursor/hooks.json
463
473
  *
464
- * Writes hook entries pointing to scripts in node_modules/gitmem-mcp/hooks/scripts/.
465
- * No plugin registration needed — Claude Code reads hooks directly from settings.
466
474
  * Use --force to overwrite existing hook entries.
475
+ * Use --client <claude|cursor> to target a specific IDE.
467
476
  */
468
477
  function cmdInstallHooks() {
469
478
  const force = process.argv.includes("--force");
479
+ const clientIdx = process.argv.indexOf("--client");
480
+ const clientArg = clientIdx !== -1 ? process.argv[clientIdx + 1]?.toLowerCase() : null;
470
481
  const scriptsDir = join(__dirname, "..", "hooks", "scripts");
471
- const claudeDir = join(process.cwd(), ".claude");
472
- const settingsPath = join(claudeDir, "settings.json");
473
482
 
474
- console.log("GitMem Hooks — Install");
483
+ // Detect client
484
+ let clientName;
485
+ if (clientArg === "cursor") {
486
+ clientName = "cursor";
487
+ } else if (clientArg === "claude") {
488
+ clientName = "claude";
489
+ } else if (clientArg) {
490
+ console.error(`Error: Unknown client "${clientArg}". Use --client claude or --client cursor.`);
491
+ process.exit(1);
492
+ } else {
493
+ // Auto-detect
494
+ const hasCursorDir = existsSync(join(process.cwd(), ".cursor"));
495
+ const hasClaudeDir = existsSync(join(process.cwd(), ".claude"));
496
+ clientName = (hasCursorDir && !hasClaudeDir) ? "cursor" : "claude";
497
+ }
498
+
499
+ console.log(`GitMem Hooks — Install (${clientName === "cursor" ? "Cursor" : "Claude Code"})`);
475
500
  console.log("======================");
476
501
  console.log("");
477
502
 
@@ -489,7 +514,7 @@ function cmdInstallHooks() {
489
514
  }
490
515
  }
491
516
 
492
- // Copy hook scripts to .gitmem/hooks/ (works regardless of npx vs local install)
517
+ // Copy hook scripts to .gitmem/hooks/
493
518
  const gitmemDir = join(process.cwd(), ".gitmem");
494
519
  const destHooksDir = join(gitmemDir, "hooks");
495
520
  if (!existsSync(destHooksDir)) {
@@ -505,146 +530,261 @@ function cmdInstallHooks() {
505
530
  }
506
531
 
507
532
  const relScripts = ".gitmem/hooks";
508
- const gitmemHooks = {
509
- SessionStart: [
510
- {
511
- hooks: [
512
- {
513
- type: "command",
514
- command: `bash ${relScripts}/session-start.sh`,
515
- statusMessage: "Initializing GitMem session...",
516
- timeout: 5000,
517
- },
518
- ],
519
- },
520
- ],
521
- PreToolUse: [
522
- { matcher: "Bash", hooks: [{ type: "command", command: `bash ${relScripts}/recall-check.sh`, timeout: 5000 }] },
523
- { matcher: "Write", hooks: [{ type: "command", command: `bash ${relScripts}/recall-check.sh`, timeout: 5000 }] },
524
- { matcher: "Edit", hooks: [{ type: "command", command: `bash ${relScripts}/recall-check.sh`, timeout: 5000 }] },
525
- ],
526
- PostToolUse: [
527
- { matcher: "mcp__gitmem__recall", hooks: [{ type: "command", command: `bash ${relScripts}/post-tool-use.sh`, timeout: 3000 }] },
528
- { matcher: "mcp__gitmem__search", hooks: [{ type: "command", command: `bash ${relScripts}/post-tool-use.sh`, timeout: 3000 }] },
529
- { matcher: "Bash", hooks: [{ type: "command", command: `bash ${relScripts}/post-tool-use.sh`, timeout: 3000 }] },
530
- { matcher: "Write", hooks: [{ type: "command", command: `bash ${relScripts}/post-tool-use.sh`, timeout: 3000 }] },
531
- { matcher: "Edit", hooks: [{ type: "command", command: `bash ${relScripts}/post-tool-use.sh`, timeout: 3000 }] },
532
- ],
533
- Stop: [
534
- {
535
- hooks: [
536
- {
537
- type: "command",
538
- command: `bash ${relScripts}/session-close-check.sh`,
539
- timeout: 5000,
540
- },
541
- ],
542
- },
543
- ],
544
- };
545
533
 
546
- // Read existing settings or create new
547
- let settings = {};
548
- if (existsSync(settingsPath)) {
549
- try {
550
- settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
551
- } catch {
552
- console.warn(" Warning: Could not parse existing .claude/settings.json, creating fresh");
534
+ if (clientName === "cursor") {
535
+ // Cursor: write to .cursor/hooks.json
536
+ const cursorDir = join(process.cwd(), ".cursor");
537
+ const hooksPath = join(cursorDir, "hooks.json");
538
+
539
+ const gitmemHooks = {
540
+ sessionStart: [{ command: `bash ${relScripts}/session-start.sh`, timeout: 5000 }],
541
+ beforeMCPExecution: [
542
+ { command: `bash ${relScripts}/credential-guard.sh`, timeout: 3000 },
543
+ { command: `bash ${relScripts}/recall-check.sh`, timeout: 5000 },
544
+ ],
545
+ afterMCPExecution: [{ command: `bash ${relScripts}/post-tool-use.sh`, timeout: 3000 }],
546
+ stop: [{ command: `bash ${relScripts}/session-close-check.sh`, timeout: 5000 }],
547
+ };
548
+
549
+ let config = {};
550
+ if (existsSync(hooksPath)) {
551
+ try {
552
+ config = JSON.parse(readFileSync(hooksPath, "utf-8"));
553
+ } catch {
554
+ console.warn(" Warning: Could not parse existing .cursor/hooks.json, creating fresh");
555
+ }
556
+ } else {
557
+ mkdirSync(cursorDir, { recursive: true });
553
558
  }
554
- } else {
555
- mkdirSync(claudeDir, { recursive: true });
556
- }
557
559
 
558
- // Check if hooks already exist
559
- if (settings.hooks && !force) {
560
- const hasGitmem = JSON.stringify(settings.hooks).includes("gitmem");
561
- if (hasGitmem) {
562
- console.log("GitMem hooks already installed in .claude/settings.json");
563
- console.log("");
564
- console.log("To reinstall (overwrite), run:");
565
- console.log(" npx gitmem-mcp install-hooks --force");
566
- return;
560
+ if (config.hooks && !force) {
561
+ const hasGitmem = JSON.stringify(config.hooks).includes("gitmem");
562
+ if (hasGitmem) {
563
+ console.log("GitMem hooks already installed in .cursor/hooks.json");
564
+ console.log("");
565
+ console.log("To reinstall (overwrite), run:");
566
+ console.log(" npx gitmem-mcp install-hooks --client cursor --force");
567
+ return;
568
+ }
567
569
  }
568
- }
569
570
 
570
- // Merge hooks into settings (replace hooks section entirely)
571
- settings.hooks = gitmemHooks;
572
- writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
571
+ config.hooks = gitmemHooks;
572
+ writeFileSync(hooksPath, JSON.stringify(config, null, 2));
573
573
 
574
- console.log("Hooks written to .claude/settings.json");
575
- console.log(`Scripts at: ${relScripts}/`);
576
- console.log("");
574
+ console.log("Hooks written to .cursor/hooks.json");
575
+ console.log(`Scripts at: ${relScripts}/`);
576
+ console.log("");
577
+ console.log("Installed! Hooks will activate on next Cursor Agent session.");
578
+
579
+ } else {
580
+ // Claude Code: write to .claude/settings.json
581
+ const claudeDir = join(process.cwd(), ".claude");
582
+ const settingsPath = join(claudeDir, "settings.json");
583
+
584
+ const gitmemHooks = {
585
+ SessionStart: [
586
+ {
587
+ hooks: [
588
+ {
589
+ type: "command",
590
+ command: `bash ${relScripts}/session-start.sh`,
591
+ statusMessage: "Initializing GitMem session...",
592
+ timeout: 5000,
593
+ },
594
+ ],
595
+ },
596
+ ],
597
+ PreToolUse: [
598
+ { matcher: "Bash", hooks: [{ type: "command", command: `bash ${relScripts}/credential-guard.sh`, timeout: 3000 }, { type: "command", command: `bash ${relScripts}/recall-check.sh`, timeout: 5000 }] },
599
+ { matcher: "Read", hooks: [{ type: "command", command: `bash ${relScripts}/credential-guard.sh`, timeout: 3000 }] },
600
+ { matcher: "Write", hooks: [{ type: "command", command: `bash ${relScripts}/recall-check.sh`, timeout: 5000 }] },
601
+ { matcher: "Edit", hooks: [{ type: "command", command: `bash ${relScripts}/recall-check.sh`, timeout: 5000 }] },
602
+ ],
603
+ PostToolUse: [
604
+ { matcher: "mcp__gitmem__recall", hooks: [{ type: "command", command: `bash ${relScripts}/post-tool-use.sh`, timeout: 3000 }] },
605
+ { matcher: "mcp__gitmem__search", hooks: [{ type: "command", command: `bash ${relScripts}/post-tool-use.sh`, timeout: 3000 }] },
606
+ { matcher: "Bash", hooks: [{ type: "command", command: `bash ${relScripts}/post-tool-use.sh`, timeout: 3000 }] },
607
+ { matcher: "Write", hooks: [{ type: "command", command: `bash ${relScripts}/post-tool-use.sh`, timeout: 3000 }] },
608
+ { matcher: "Edit", hooks: [{ type: "command", command: `bash ${relScripts}/post-tool-use.sh`, timeout: 3000 }] },
609
+ ],
610
+ Stop: [
611
+ {
612
+ hooks: [
613
+ {
614
+ type: "command",
615
+ command: `bash ${relScripts}/session-close-check.sh`,
616
+ timeout: 5000,
617
+ },
618
+ ],
619
+ },
620
+ ],
621
+ };
577
622
 
578
- // Verify gitmem MCP is configured
579
- let mcpFound = false;
580
- const mcpPaths = [
581
- join(process.cwd(), ".mcp.json"),
582
- join(process.cwd(), ".claude", "mcp.json"),
583
- join(homedir(), ".claude.json"),
584
- ];
585
- for (const p of mcpPaths) {
586
- if (existsSync(p)) {
623
+ let settings = {};
624
+ if (existsSync(settingsPath)) {
587
625
  try {
588
- const cfg = JSON.parse(readFileSync(p, "utf-8"));
589
- const servers = cfg.mcpServers || {};
590
- if (servers.gitmem || servers["gitmem-mcp"]) {
591
- mcpFound = true;
592
- break;
593
- }
626
+ settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
594
627
  } catch {
595
- // ignore parse errors
628
+ console.warn(" Warning: Could not parse existing .claude/settings.json, creating fresh");
596
629
  }
630
+ } else {
631
+ mkdirSync(claudeDir, { recursive: true });
597
632
  }
598
- }
599
633
 
600
- if (!mcpFound) {
601
- console.log("WARNING: gitmem MCP server not detected in .mcp.json.");
602
- console.log(" Hooks will be silent until gitmem MCP is configured.");
603
- console.log(" Run: npx gitmem-mcp configure");
634
+ if (settings.hooks && !force) {
635
+ const hasGitmem = JSON.stringify(settings.hooks).includes("gitmem");
636
+ if (hasGitmem) {
637
+ console.log("GitMem hooks already installed in .claude/settings.json");
638
+ console.log("");
639
+ console.log("To reinstall (overwrite), run:");
640
+ console.log(" npx gitmem-mcp install-hooks --force");
641
+ return;
642
+ }
643
+ }
644
+
645
+ settings.hooks = gitmemHooks;
646
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
647
+
648
+ console.log("Hooks written to .claude/settings.json");
649
+ console.log(`Scripts at: ${relScripts}/`);
604
650
  console.log("");
651
+
652
+ // Verify gitmem MCP is configured
653
+ let mcpFound = false;
654
+ const mcpPaths = [
655
+ join(process.cwd(), ".mcp.json"),
656
+ join(process.cwd(), ".claude", "mcp.json"),
657
+ join(homedir(), ".claude.json"),
658
+ ];
659
+ for (const p of mcpPaths) {
660
+ if (existsSync(p)) {
661
+ try {
662
+ const cfg = JSON.parse(readFileSync(p, "utf-8"));
663
+ const servers = cfg.mcpServers || {};
664
+ if (servers.gitmem || servers["gitmem-mcp"]) {
665
+ mcpFound = true;
666
+ break;
667
+ }
668
+ } catch {
669
+ // ignore parse errors
670
+ }
671
+ }
672
+ }
673
+
674
+ if (!mcpFound) {
675
+ console.log("WARNING: gitmem MCP server not detected in .mcp.json.");
676
+ console.log(" Hooks will be silent until gitmem MCP is configured.");
677
+ console.log(" Run: npx gitmem-mcp configure");
678
+ console.log("");
679
+ }
680
+
681
+ console.log("Installed! Hooks will activate on next Claude Code session.");
605
682
  }
606
683
 
607
- console.log("Installed! Hooks will activate on next Claude Code session.");
608
684
  console.log("");
609
685
  console.log("To update after a gitmem version bump:");
610
686
  console.log(" npx gitmem-mcp install-hooks --force");
611
687
  }
612
688
 
613
689
  /**
614
- * Uninstall gitmem hooks from .claude/settings.json
690
+ * Uninstall gitmem hooks.
691
+ *
692
+ * Claude Code: removes from .claude/settings.json
693
+ * Cursor: removes from .cursor/hooks.json
615
694
  *
616
- * Removes the hooks section, cleans up legacy plugin directories,
617
- * and removes temp state directories.
695
+ * Also cleans up legacy plugin directories and temp state.
696
+ * Use --client <claude|cursor> to target a specific IDE.
618
697
  */
619
698
  function cmdUninstallHooks() {
620
- console.log("GitMem Hooks Uninstall");
699
+ const clientIdx = process.argv.indexOf("--client");
700
+ const clientArg = clientIdx !== -1 ? process.argv[clientIdx + 1]?.toLowerCase() : null;
701
+
702
+ // Detect client
703
+ let clientName;
704
+ if (clientArg === "cursor") {
705
+ clientName = "cursor";
706
+ } else if (clientArg === "claude") {
707
+ clientName = "claude";
708
+ } else if (clientArg) {
709
+ console.error(`Error: Unknown client "${clientArg}". Use --client claude or --client cursor.`);
710
+ process.exit(1);
711
+ } else {
712
+ const hasCursorDir = existsSync(join(process.cwd(), ".cursor"));
713
+ const hasClaudeDir = existsSync(join(process.cwd(), ".claude"));
714
+ clientName = (hasCursorDir && !hasClaudeDir) ? "cursor" : "claude";
715
+ }
716
+
717
+ console.log(`GitMem Hooks — Uninstall (${clientName === "cursor" ? "Cursor" : "Claude Code"})`);
621
718
  console.log("========================");
622
719
  console.log("");
623
720
 
624
- // Remove hooks from .claude/settings.json
625
- const settingsPath = join(process.cwd(), ".claude", "settings.json");
626
- if (existsSync(settingsPath)) {
627
- try {
628
- const cfg = JSON.parse(readFileSync(settingsPath, "utf-8"));
629
- if (cfg.hooks) {
630
- delete cfg.hooks;
631
- writeFileSync(settingsPath, JSON.stringify(cfg, null, 2));
632
- console.log("[uninstall] Removed hooks from .claude/settings.json");
633
- } else {
634
- console.log("[uninstall] No hooks found in .claude/settings.json");
721
+ if (clientName === "cursor") {
722
+ // Remove hooks from .cursor/hooks.json
723
+ const hooksPath = join(process.cwd(), ".cursor", "hooks.json");
724
+ if (existsSync(hooksPath)) {
725
+ try {
726
+ const cfg = JSON.parse(readFileSync(hooksPath, "utf-8"));
727
+ if (cfg.hooks) {
728
+ // Filter out gitmem hooks, preserve others
729
+ const cleaned = {};
730
+ let removed = 0;
731
+ for (const [eventType, entries] of Object.entries(cfg.hooks)) {
732
+ if (!Array.isArray(entries)) continue;
733
+ const nonGitmem = entries.filter((e) => {
734
+ if (typeof e.command === "string" && e.command.includes("gitmem")) {
735
+ removed++;
736
+ return false;
737
+ }
738
+ return true;
739
+ });
740
+ if (nonGitmem.length > 0) cleaned[eventType] = nonGitmem;
741
+ }
742
+ if (removed > 0) {
743
+ if (Object.keys(cleaned).length > 0) {
744
+ cfg.hooks = cleaned;
745
+ } else {
746
+ delete cfg.hooks;
747
+ }
748
+ writeFileSync(hooksPath, JSON.stringify(cfg, null, 2));
749
+ console.log(`[uninstall] Removed ${removed} gitmem hooks from .cursor/hooks.json`);
750
+ } else {
751
+ console.log("[uninstall] No gitmem hooks found in .cursor/hooks.json");
752
+ }
753
+ } else {
754
+ console.log("[uninstall] No hooks found in .cursor/hooks.json");
755
+ }
756
+ } catch {
757
+ // ignore parse errors
635
758
  }
636
- // Also clean legacy enabledPlugins
637
- if (cfg.enabledPlugins) {
638
- for (const key of Object.keys(cfg.enabledPlugins)) {
639
- if (key.startsWith("gitmem-hooks")) {
640
- delete cfg.enabledPlugins[key];
641
- writeFileSync(settingsPath, JSON.stringify(cfg, null, 2));
642
- console.log("[cleanup] Removed legacy enabledPlugins entry");
759
+ } else {
760
+ console.log("[uninstall] No .cursor/hooks.json found");
761
+ }
762
+ } else {
763
+ // Remove hooks from .claude/settings.json
764
+ const settingsPath = join(process.cwd(), ".claude", "settings.json");
765
+ if (existsSync(settingsPath)) {
766
+ try {
767
+ const cfg = JSON.parse(readFileSync(settingsPath, "utf-8"));
768
+ if (cfg.hooks) {
769
+ delete cfg.hooks;
770
+ writeFileSync(settingsPath, JSON.stringify(cfg, null, 2));
771
+ console.log("[uninstall] Removed hooks from .claude/settings.json");
772
+ } else {
773
+ console.log("[uninstall] No hooks found in .claude/settings.json");
774
+ }
775
+ // Also clean legacy enabledPlugins
776
+ if (cfg.enabledPlugins) {
777
+ for (const key of Object.keys(cfg.enabledPlugins)) {
778
+ if (key.startsWith("gitmem-hooks")) {
779
+ delete cfg.enabledPlugins[key];
780
+ writeFileSync(settingsPath, JSON.stringify(cfg, null, 2));
781
+ console.log("[cleanup] Removed legacy enabledPlugins entry");
782
+ }
643
783
  }
644
784
  }
785
+ } catch {
786
+ // ignore parse errors
645
787
  }
646
- } catch {
647
- // ignore parse errors
648
788
  }
649
789
  }
650
790
 
@@ -690,10 +830,11 @@ function cmdUninstallHooks() {
690
830
  console.log("");
691
831
  console.log("Uninstall complete.");
692
832
  console.log("");
693
- console.log("Notes:");
694
- console.log(" - gitmem MCP server config (.mcp.json) was NOT modified");
695
- console.log(" - Restart Claude Code for changes to take effect");
696
- console.log(" - To reinstall: npx gitmem-mcp install-hooks");
833
+ const mcpConfig = clientName === "cursor" ? ".cursor/mcp.json" : ".mcp.json";
834
+ console.log(`Notes:`);
835
+ console.log(` - gitmem MCP server config (${mcpConfig}) was NOT modified`);
836
+ console.log(` - Restart ${clientName === "cursor" ? "Cursor" : "Claude Code"} for changes to take effect`);
837
+ console.log(` - To reinstall: npx gitmem-mcp install-hooks${clientName === "cursor" ? " --client cursor" : ""}`);
697
838
  }
698
839
 
699
840
  switch (command) {