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 +22 -0
- package/README.md +2 -2
- package/bin/gitmem.js +264 -123
- package/bin/init-wizard.js +332 -93
- package/bin/uninstall.js +187 -45
- package/cursorrules.template +92 -0
- package/dist/commands/check.js +8 -1
- package/dist/schemas/absorb-observations.js +3 -3
- package/dist/schemas/create-decision.js +8 -8
- package/dist/schemas/create-learning.js +13 -13
- package/dist/schemas/prepare-context.js +2 -2
- package/dist/schemas/thread.js +10 -10
- package/dist/server.js +1 -1
- package/dist/services/behavioral-decay.js +3 -1
- package/dist/services/embedding.js +2 -0
- package/dist/services/local-file-storage.js +3 -1
- package/dist/services/thread-dedup.d.ts +27 -7
- package/dist/services/thread-dedup.js +112 -10
- package/dist/services/thread-manager.js +5 -2
- package/dist/tools/confirm-scars.js +6 -3
- package/dist/tools/create-thread.d.ts +1 -1
- package/dist/tools/recall.js +3 -1
- package/hooks/scripts/recall-check.sh +51 -52
- package/package.json +2 -1
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/
|
|
9
|
-
<img src="https://img.shields.io/
|
|
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
|
|
52
|
-
npx gitmem-mcp uninstall-hooks Remove
|
|
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
|
|
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
|
-
|
|
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/
|
|
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
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
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
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
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
|
-
|
|
571
|
-
|
|
572
|
-
writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
571
|
+
config.hooks = gitmemHooks;
|
|
572
|
+
writeFileSync(hooksPath, JSON.stringify(config, null, 2));
|
|
573
573
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
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
|
-
|
|
579
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
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
|
|
690
|
+
* Uninstall gitmem hooks.
|
|
691
|
+
*
|
|
692
|
+
* Claude Code: removes from .claude/settings.json
|
|
693
|
+
* Cursor: removes from .cursor/hooks.json
|
|
615
694
|
*
|
|
616
|
-
*
|
|
617
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
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
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
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
|
-
|
|
694
|
-
console.log(
|
|
695
|
-
console.log(
|
|
696
|
-
console.log(
|
|
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) {
|