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.
@@ -4,7 +4,9 @@
4
4
  * GitMem Init Wizard
5
5
  *
6
6
  * Interactive setup that detects existing config, prompts, and merges.
7
- * Usage: npx gitmem-mcp init [--yes] [--dry-run] [--project <name>]
7
+ * Supports Claude Code and Cursor IDE.
8
+ *
9
+ * Usage: npx gitmem-mcp init [--yes] [--dry-run] [--project <name>] [--client <claude|cursor>]
8
10
  */
9
11
 
10
12
  import {
@@ -28,20 +30,93 @@ const autoYes = args.includes("--yes") || args.includes("-y");
28
30
  const dryRun = args.includes("--dry-run");
29
31
  const projectIdx = args.indexOf("--project");
30
32
  const projectName = projectIdx !== -1 ? args[projectIdx + 1] : null;
31
-
32
- // Paths
33
+ const clientIdx = args.indexOf("--client");
34
+ const clientFlag = clientIdx !== -1 ? args[clientIdx + 1]?.toLowerCase() : null;
35
+
36
+ // ── Client Configuration ──
37
+
38
+ const CLIENT_CONFIGS = {
39
+ claude: {
40
+ name: "Claude Code",
41
+ mcpConfigPath: join(cwd, ".mcp.json"),
42
+ mcpConfigName: ".mcp.json",
43
+ instructionsFile: join(cwd, "CLAUDE.md"),
44
+ instructionsName: "CLAUDE.md",
45
+ templateFile: join(__dirname, "..", "CLAUDE.md.template"),
46
+ startMarker: "<!-- gitmem:start -->",
47
+ endMarker: "<!-- gitmem:end -->",
48
+ configDir: join(cwd, ".claude"),
49
+ settingsFile: join(cwd, ".claude", "settings.json"),
50
+ settingsLocalFile: join(cwd, ".claude", "settings.local.json"),
51
+ hasPermissions: true,
52
+ hooksInSettings: true,
53
+ completionMsg: "Setup complete! Start Claude Code \u2014 memory is active.",
54
+ },
55
+ cursor: {
56
+ name: "Cursor",
57
+ mcpConfigPath: join(cwd, ".cursor", "mcp.json"),
58
+ mcpConfigName: ".cursor/mcp.json",
59
+ instructionsFile: join(cwd, ".cursorrules"),
60
+ instructionsName: ".cursorrules",
61
+ templateFile: join(__dirname, "..", "cursorrules.template"),
62
+ startMarker: "# --- gitmem:start ---",
63
+ endMarker: "# --- gitmem:end ---",
64
+ configDir: join(cwd, ".cursor"),
65
+ settingsFile: null,
66
+ settingsLocalFile: null,
67
+ hasPermissions: false,
68
+ hooksInSettings: false,
69
+ hooksFile: join(cwd, ".cursor", "hooks.json"),
70
+ hooksFileName: ".cursor/hooks.json",
71
+ completionMsg: "Setup complete! Open Cursor (Agent mode) \u2014 memory is active.",
72
+ },
73
+ };
74
+
75
+ // Shared paths (client-agnostic)
33
76
  const gitmemDir = join(cwd, ".gitmem");
34
- const mcpJsonPath = join(cwd, ".mcp.json");
35
- const claudeMdPath = join(cwd, "CLAUDE.md");
36
- const claudeDir = join(cwd, ".claude");
37
- const settingsPath = join(claudeDir, "settings.json");
38
- const settingsLocalPath = join(claudeDir, "settings.local.json");
39
77
  const gitignorePath = join(cwd, ".gitignore");
40
- const templatePath = join(__dirname, "..", "CLAUDE.md.template");
41
78
  const starterScarsPath = join(__dirname, "..", "schema", "starter-scars.json");
42
79
  const hooksScriptsDir = join(__dirname, "..", "hooks", "scripts");
43
80
 
44
81
  let rl;
82
+ let client; // "claude" | "cursor" — set by detectClient()
83
+ let cc; // shorthand for CLIENT_CONFIGS[client]
84
+
85
+ // ── Client Detection ──
86
+
87
+ function detectClient() {
88
+ // Explicit flag takes priority
89
+ if (clientFlag) {
90
+ if (clientFlag !== "claude" && clientFlag !== "cursor") {
91
+ console.error(` Error: Unknown client "${clientFlag}". Use --client claude or --client cursor.`);
92
+ process.exit(1);
93
+ }
94
+ return clientFlag;
95
+ }
96
+
97
+ // Auto-detect based on directory presence
98
+ const hasCursorDir = existsSync(join(cwd, ".cursor"));
99
+ const hasClaudeDir = existsSync(join(cwd, ".claude"));
100
+ const hasMcpJson = existsSync(join(cwd, ".mcp.json"));
101
+ const hasClaudeMd = existsSync(join(cwd, "CLAUDE.md"));
102
+ const hasCursorRules = existsSync(join(cwd, ".cursorrules"));
103
+ const hasCursorMcp = existsSync(join(cwd, ".cursor", "mcp.json"));
104
+
105
+ // Strong Cursor signals
106
+ if (hasCursorDir && !hasClaudeDir && !hasMcpJson && !hasClaudeMd) return "cursor";
107
+ if (hasCursorRules && !hasClaudeMd) return "cursor";
108
+ if (hasCursorMcp && !hasMcpJson) return "cursor";
109
+
110
+ // Strong Claude signals
111
+ if (hasClaudeDir && !hasCursorDir) return "claude";
112
+ if (hasMcpJson && !hasCursorMcp) return "claude";
113
+ if (hasClaudeMd && !hasCursorRules) return "claude";
114
+
115
+ // Default to Claude Code (most common)
116
+ return "claude";
117
+ }
118
+
119
+ // ── Helpers ──
45
120
 
46
121
  async function confirm(message, defaultYes = true) {
47
122
  if (autoYes) return true;
@@ -85,7 +160,7 @@ function buildMcpConfig() {
85
160
  };
86
161
  }
87
162
 
88
- function buildHooks() {
163
+ function buildClaudeHooks() {
89
164
  const relScripts = ".gitmem/hooks";
90
165
  return {
91
166
  SessionStart: [
@@ -213,16 +288,60 @@ function buildHooks() {
213
288
  };
214
289
  }
215
290
 
291
+ function buildCursorHooks() {
292
+ const relScripts = ".gitmem/hooks";
293
+ // Cursor hooks format: .cursor/hooks.json
294
+ // Events: sessionStart, beforeMCPExecution, afterMCPExecution, stop
295
+ // No per-tool matchers — all MCP calls go through beforeMCPExecution
296
+ return {
297
+ sessionStart: [
298
+ {
299
+ command: `bash ${relScripts}/session-start.sh`,
300
+ timeout: 5000,
301
+ },
302
+ ],
303
+ beforeMCPExecution: [
304
+ {
305
+ command: `bash ${relScripts}/credential-guard.sh`,
306
+ timeout: 3000,
307
+ },
308
+ {
309
+ command: `bash ${relScripts}/recall-check.sh`,
310
+ timeout: 5000,
311
+ },
312
+ ],
313
+ afterMCPExecution: [
314
+ {
315
+ command: `bash ${relScripts}/post-tool-use.sh`,
316
+ timeout: 3000,
317
+ },
318
+ ],
319
+ stop: [
320
+ {
321
+ command: `bash ${relScripts}/session-close-check.sh`,
322
+ timeout: 5000,
323
+ },
324
+ ],
325
+ };
326
+ }
327
+
216
328
  function isGitmemHook(entry) {
217
- if (!entry.hooks || !Array.isArray(entry.hooks)) return false;
218
- return entry.hooks.some(
219
- (h) => typeof h.command === "string" && h.command.includes("gitmem")
220
- );
329
+ // Claude Code format: entry.hooks is an array of {command: "..."}
330
+ if (entry.hooks && Array.isArray(entry.hooks)) {
331
+ return entry.hooks.some(
332
+ (h) => typeof h.command === "string" && h.command.includes("gitmem")
333
+ );
334
+ }
335
+ // Cursor format: entry itself has {command: "..."}
336
+ if (typeof entry.command === "string") {
337
+ return entry.command.includes("gitmem");
338
+ }
339
+ return false;
221
340
  }
222
341
 
223
- function getClaudeMdTemplate() {
342
+ function getInstructionsTemplate() {
224
343
  try {
225
- return readFileSync(templatePath, "utf-8");
344
+ return readFileSync(cc.templateFile, "utf-8");
226
345
  } catch {
227
346
  return null;
228
347
  }
@@ -318,12 +437,15 @@ async function stepMemoryStore() {
318
437
  }
319
438
 
320
439
  async function stepMcpServer() {
321
- const existing = readJson(mcpJsonPath);
440
+ const mcpPath = cc.mcpConfigPath;
441
+ const mcpName = cc.mcpConfigName;
442
+
443
+ const existing = readJson(mcpPath);
322
444
  const hasGitmem =
323
445
  existing?.mcpServers?.gitmem || existing?.mcpServers?.["gitmem-mcp"];
324
446
 
325
447
  if (hasGitmem) {
326
- console.log(" Already configured in .mcp.json. Skipping.");
448
+ console.log(` Already configured in ${mcpName}. Skipping.`);
327
449
  return;
328
450
  }
329
451
 
@@ -332,8 +454,8 @@ async function stepMcpServer() {
332
454
  : 0;
333
455
  const tierLabel = process.env.SUPABASE_URL ? "pro" : "free";
334
456
  const prompt = existing
335
- ? `Add gitmem to .mcp.json? (${serverCount} existing server${serverCount !== 1 ? "s" : ""} preserved)`
336
- : "Create .mcp.json with gitmem server?";
457
+ ? `Add gitmem to ${mcpName}? (${serverCount} existing server${serverCount !== 1 ? "s" : ""} preserved)`
458
+ : `Create ${mcpName} with gitmem server?`;
337
459
 
338
460
  if (!(await confirm(prompt))) {
339
461
  console.log(" Skipped.");
@@ -341,40 +463,49 @@ async function stepMcpServer() {
341
463
  }
342
464
 
343
465
  if (dryRun) {
344
- console.log(` [dry-run] Would add gitmem entry to .mcp.json (${tierLabel} tier)`);
466
+ console.log(` [dry-run] Would add gitmem entry to ${mcpName} (${tierLabel} tier)`);
345
467
  return;
346
468
  }
347
469
 
470
+ // Ensure parent directory exists (for .cursor/mcp.json)
471
+ const parentDir = dirname(mcpPath);
472
+ if (!existsSync(parentDir)) {
473
+ mkdirSync(parentDir, { recursive: true });
474
+ }
475
+
348
476
  const config = existing || { mcpServers: {} };
349
477
  if (!config.mcpServers) config.mcpServers = {};
350
478
  config.mcpServers.gitmem = buildMcpConfig();
351
- writeJson(mcpJsonPath, config);
479
+ writeJson(mcpPath, config);
352
480
 
353
481
  console.log(
354
- ` Added gitmem entry to .mcp.json (${tierLabel} tier` +
355
- (process.env.SUPABASE_URL ? " Supabase detected" : " local storage") +
482
+ ` Added gitmem entry to ${mcpName} (${tierLabel} tier` +
483
+ (process.env.SUPABASE_URL ? " \u2014 Supabase detected" : " \u2014 local storage") +
356
484
  ")"
357
485
  );
358
486
  }
359
487
 
360
- async function stepClaudeMd() {
361
- const template = getClaudeMdTemplate();
488
+ async function stepInstructions() {
489
+ const template = getInstructionsTemplate();
490
+ const instrName = cc.instructionsName;
491
+
362
492
  if (!template) {
363
- console.log(" ! CLAUDE.md.template not found. Skipping.");
493
+ console.log(` ! ${instrName} template not found. Skipping.`);
364
494
  return;
365
495
  }
366
496
 
367
- const exists = existsSync(claudeMdPath);
368
- let content = exists ? readFileSync(claudeMdPath, "utf-8") : "";
497
+ const instrPath = cc.instructionsFile;
498
+ const exists = existsSync(instrPath);
499
+ let content = exists ? readFileSync(instrPath, "utf-8") : "";
369
500
 
370
- if (content.includes("<!-- gitmem:start -->")) {
371
- console.log(" Already configured in CLAUDE.md. Skipping.");
501
+ if (content.includes(cc.startMarker)) {
502
+ console.log(` Already configured in ${instrName}. Skipping.`);
372
503
  return;
373
504
  }
374
505
 
375
506
  const prompt = exists
376
- ? "Append gitmem section to CLAUDE.md?"
377
- : "Create CLAUDE.md with gitmem instructions?";
507
+ ? `Append gitmem section to ${instrName}?`
508
+ : `Create ${instrName} with gitmem instructions?`;
378
509
 
379
510
  if (!(await confirm(prompt))) {
380
511
  console.log(" Skipped.");
@@ -383,15 +514,15 @@ async function stepClaudeMd() {
383
514
 
384
515
  if (dryRun) {
385
516
  console.log(
386
- ` [dry-run] Would ${exists ? "append gitmem section to" : "create"} CLAUDE.md`
517
+ ` [dry-run] Would ${exists ? "append gitmem section to" : "create"} ${instrName}`
387
518
  );
388
519
  return;
389
520
  }
390
521
 
391
522
  // Template should already have delimiters, but ensure they're there
392
523
  let block = template;
393
- if (!block.includes("<!-- gitmem:start -->")) {
394
- block = `<!-- gitmem:start -->\n${block}\n<!-- gitmem:end -->`;
524
+ if (!block.includes(cc.startMarker)) {
525
+ block = `${cc.startMarker}\n${block}\n${cc.endMarker}`;
395
526
  }
396
527
 
397
528
  if (exists) {
@@ -400,23 +531,29 @@ async function stepClaudeMd() {
400
531
  content = block + "\n";
401
532
  }
402
533
 
403
- writeFileSync(claudeMdPath, content);
534
+ writeFileSync(instrPath, content);
404
535
  console.log(
405
- ` ${exists ? "Added gitmem section to" : "Created"} CLAUDE.md`
536
+ ` ${exists ? "Added gitmem section to" : "Created"} ${instrName}`
406
537
  );
407
538
  }
408
539
 
409
540
  async function stepPermissions() {
410
- const existing = readJson(settingsPath);
541
+ // Cursor doesn't have an equivalent permissions system
542
+ if (!cc.hasPermissions) {
543
+ console.log(` Not needed for ${cc.name}. Skipping.`);
544
+ return;
545
+ }
546
+
547
+ const existing = readJson(cc.settingsFile);
411
548
  const allow = existing?.permissions?.allow || [];
412
549
  const pattern = "mcp__gitmem__*";
413
550
 
414
551
  if (allow.includes(pattern)) {
415
- console.log(" Already configured in .claude/settings.json. Skipping.");
552
+ console.log(` Already configured in ${cc.configDir}/settings.json. Skipping.`);
416
553
  return;
417
554
  }
418
555
 
419
- if (!(await confirm("Add mcp__gitmem__* to .claude/settings.json?"))) {
556
+ if (!(await confirm(`Add mcp__gitmem__* to ${cc.configDir}/settings.json?`))) {
420
557
  console.log(" Skipped.");
421
558
  return;
422
559
  }
@@ -427,20 +564,48 @@ async function stepPermissions() {
427
564
  }
428
565
 
429
566
  const settings = existing || {};
430
- if (!existsSync(claudeDir)) {
431
- mkdirSync(claudeDir, { recursive: true });
567
+ if (!existsSync(cc.configDir)) {
568
+ mkdirSync(cc.configDir, { recursive: true });
432
569
  }
433
570
  const permissions = settings.permissions || {};
434
571
  const newAllow = permissions.allow || [];
435
572
  newAllow.push(pattern);
436
573
  settings.permissions = { ...permissions, allow: newAllow };
437
- writeJson(settingsPath, settings);
574
+ writeJson(cc.settingsFile, settings);
438
575
 
439
576
  console.log(" Added gitmem tool permissions");
440
577
  }
441
578
 
579
+ function copyHookScripts() {
580
+ const destHooksDir = join(gitmemDir, "hooks");
581
+ if (!existsSync(destHooksDir)) {
582
+ mkdirSync(destHooksDir, { recursive: true });
583
+ }
584
+ if (existsSync(hooksScriptsDir)) {
585
+ try {
586
+ for (const file of readdirSync(hooksScriptsDir)) {
587
+ if (file.endsWith(".sh")) {
588
+ const src = join(hooksScriptsDir, file);
589
+ const dest = join(destHooksDir, file);
590
+ writeFileSync(dest, readFileSync(src));
591
+ chmodSync(dest, 0o755);
592
+ }
593
+ }
594
+ } catch {
595
+ // Non-critical
596
+ }
597
+ }
598
+ }
599
+
442
600
  async function stepHooks() {
443
- const existing = readJson(settingsPath);
601
+ if (cc.hooksInSettings) {
602
+ return stepHooksClaude();
603
+ }
604
+ return stepHooksCursor();
605
+ }
606
+
607
+ async function stepHooksClaude() {
608
+ const existing = readJson(cc.settingsFile);
444
609
  const hooks = existing?.hooks || {};
445
610
  const hasGitmem = JSON.stringify(hooks).includes("gitmem");
446
611
 
@@ -472,32 +637,14 @@ async function stepHooks() {
472
637
  return;
473
638
  }
474
639
 
475
- // Copy hook scripts to .gitmem/hooks/ (works regardless of npx vs local install)
476
- const destHooksDir = join(gitmemDir, "hooks");
477
- if (!existsSync(destHooksDir)) {
478
- mkdirSync(destHooksDir, { recursive: true });
479
- }
480
- if (existsSync(hooksScriptsDir)) {
481
- try {
482
- for (const file of readdirSync(hooksScriptsDir)) {
483
- if (file.endsWith(".sh")) {
484
- const src = join(hooksScriptsDir, file);
485
- const dest = join(destHooksDir, file);
486
- writeFileSync(dest, readFileSync(src));
487
- chmodSync(dest, 0o755);
488
- }
489
- }
490
- } catch {
491
- // Non-critical
492
- }
493
- }
640
+ copyHookScripts();
494
641
 
495
642
  const settings = existing || {};
496
- if (!existsSync(claudeDir)) {
497
- mkdirSync(claudeDir, { recursive: true });
643
+ if (!existsSync(cc.configDir)) {
644
+ mkdirSync(cc.configDir, { recursive: true });
498
645
  }
499
646
 
500
- const gitmemHooks = buildHooks();
647
+ const gitmemHooks = buildClaudeHooks();
501
648
  const merged = { ...(settings.hooks || {}) };
502
649
 
503
650
  for (const [eventType, gitmemEntries] of Object.entries(gitmemHooks)) {
@@ -507,7 +654,7 @@ async function stepHooks() {
507
654
  }
508
655
 
509
656
  settings.hooks = merged;
510
- writeJson(settingsPath, settings);
657
+ writeJson(cc.settingsFile, settings);
511
658
 
512
659
  const preservedMsg =
513
660
  existingHookCount > 0
@@ -516,8 +663,8 @@ async function stepHooks() {
516
663
  console.log(` Merged 4 gitmem hook types${preservedMsg}`);
517
664
 
518
665
  // Warn about settings.local.json
519
- if (existsSync(settingsLocalPath)) {
520
- const local = readJson(settingsLocalPath);
666
+ if (cc.settingsLocalFile && existsSync(cc.settingsLocalFile)) {
667
+ const local = readJson(cc.settingsLocalFile);
521
668
  if (local?.hooks) {
522
669
  console.log("");
523
670
  console.log(
@@ -530,6 +677,69 @@ async function stepHooks() {
530
677
  }
531
678
  }
532
679
 
680
+ async function stepHooksCursor() {
681
+ const hooksPath = cc.hooksFile;
682
+ const hooksName = cc.hooksFileName;
683
+
684
+ const existing = readJson(hooksPath);
685
+ const hasGitmem = existing ? JSON.stringify(existing).includes("gitmem") : false;
686
+
687
+ if (hasGitmem) {
688
+ console.log(` Already configured in ${hooksName}. Skipping.`);
689
+ return;
690
+ }
691
+
692
+ // Count existing non-gitmem hooks
693
+ let existingHookCount = 0;
694
+ if (existing?.hooks) {
695
+ for (const entries of Object.values(existing.hooks)) {
696
+ if (Array.isArray(entries)) {
697
+ existingHookCount += entries.filter((e) => !isGitmemHook(e)).length;
698
+ }
699
+ }
700
+ }
701
+
702
+ const prompt =
703
+ existingHookCount > 0
704
+ ? `Merge gitmem hooks into ${hooksName}? (${existingHookCount} existing hook${existingHookCount !== 1 ? "s" : ""} preserved)`
705
+ : `Add gitmem lifecycle hooks to ${hooksName}?`;
706
+
707
+ if (!(await confirm(prompt))) {
708
+ console.log(" Skipped.");
709
+ return;
710
+ }
711
+
712
+ if (dryRun) {
713
+ console.log(" [dry-run] Would merge 4 gitmem hook types");
714
+ return;
715
+ }
716
+
717
+ copyHookScripts();
718
+
719
+ if (!existsSync(cc.configDir)) {
720
+ mkdirSync(cc.configDir, { recursive: true });
721
+ }
722
+
723
+ const gitmemHooks = buildCursorHooks();
724
+ const config = existing || {};
725
+ const merged = { ...(config.hooks || {}) };
726
+
727
+ for (const [eventType, gitmemEntries] of Object.entries(gitmemHooks)) {
728
+ const existingEntries = merged[eventType] || [];
729
+ const nonGitmem = existingEntries.filter((e) => !isGitmemHook(e));
730
+ merged[eventType] = [...nonGitmem, ...gitmemEntries];
731
+ }
732
+
733
+ config.hooks = merged;
734
+ writeJson(hooksPath, config);
735
+
736
+ const preservedMsg =
737
+ existingHookCount > 0
738
+ ? ` (${existingHookCount} existing hook${existingHookCount !== 1 ? "s" : ""} preserved)`
739
+ : "";
740
+ console.log(` Merged 4 gitmem hook types${preservedMsg}`);
741
+ }
742
+
533
743
  async function stepGitignore() {
534
744
  const exists = existsSync(gitignorePath);
535
745
  let content = exists ? readFileSync(gitignorePath, "utf-8") : "";
@@ -565,10 +775,19 @@ async function main() {
565
775
  const pkg = readJson(join(__dirname, "..", "package.json"));
566
776
  const version = pkg?.version || "1.0.0";
567
777
 
778
+ // Detect client before anything else
779
+ client = detectClient();
780
+ cc = CLIENT_CONFIGS[client];
781
+
568
782
  console.log("");
569
- console.log(` gitmem v${version} Setup`);
783
+ console.log(` gitmem v${version} \u2014 Setup for ${cc.name}`);
570
784
  if (dryRun) {
571
- console.log(" (dry-run mode no files will be written)");
785
+ console.log(" (dry-run mode \u2014 no files will be written)");
786
+ }
787
+ if (clientFlag) {
788
+ console.log(` (client: ${client} \u2014 via --client flag)`);
789
+ } else {
790
+ console.log(` (client: ${client} \u2014 auto-detected)`);
572
791
  }
573
792
  console.log("");
574
793
 
@@ -576,24 +795,24 @@ async function main() {
576
795
  console.log(" Detecting environment...");
577
796
  const detections = [];
578
797
 
579
- if (existsSync(mcpJsonPath)) {
580
- const mcp = readJson(mcpJsonPath);
798
+ if (existsSync(cc.mcpConfigPath)) {
799
+ const mcp = readJson(cc.mcpConfigPath);
581
800
  const count = mcp?.mcpServers ? Object.keys(mcp.mcpServers).length : 0;
582
801
  detections.push(
583
- ` .mcp.json found (${count} server${count !== 1 ? "s" : ""})`
802
+ ` ${cc.mcpConfigName} found (${count} server${count !== 1 ? "s" : ""})`
584
803
  );
585
804
  }
586
805
 
587
- if (existsSync(claudeMdPath)) {
588
- const content = readFileSync(claudeMdPath, "utf-8");
589
- const hasGitmem = content.includes("<!-- gitmem:start -->");
806
+ if (existsSync(cc.instructionsFile)) {
807
+ const content = readFileSync(cc.instructionsFile, "utf-8");
808
+ const hasGitmem = content.includes(cc.startMarker);
590
809
  detections.push(
591
- ` CLAUDE.md found (${hasGitmem ? "has gitmem section" : "no gitmem section"})`
810
+ ` ${cc.instructionsName} found (${hasGitmem ? "has gitmem section" : "no gitmem section"})`
592
811
  );
593
812
  }
594
813
 
595
- if (existsSync(settingsPath)) {
596
- const settings = readJson(settingsPath);
814
+ if (cc.settingsFile && existsSync(cc.settingsFile)) {
815
+ const settings = readJson(cc.settingsFile);
597
816
  const hookCount = settings?.hooks
598
817
  ? Object.values(settings.hooks).flat().length
599
818
  : 0;
@@ -602,6 +821,16 @@ async function main() {
602
821
  );
603
822
  }
604
823
 
824
+ if (!cc.hooksInSettings && cc.hooksFile && existsSync(cc.hooksFile)) {
825
+ const hooks = readJson(cc.hooksFile);
826
+ const hookCount = hooks?.hooks
827
+ ? Object.values(hooks.hooks).flat().length
828
+ : 0;
829
+ detections.push(
830
+ ` ${cc.hooksFileName} found (${hookCount} hook${hookCount !== 1 ? "s" : ""})`
831
+ );
832
+ }
833
+
605
834
  if (existsSync(gitignorePath)) {
606
835
  detections.push(" .gitignore found");
607
836
  }
@@ -621,35 +850,45 @@ async function main() {
621
850
  );
622
851
  console.log("");
623
852
 
624
- // Run steps
625
- console.log(" Step 1/6 Memory Store");
853
+ // Run steps — step count depends on client
854
+ const stepCount = cc.hasPermissions ? 6 : 5;
855
+ let step = 1;
856
+
857
+ console.log(` Step ${step}/${stepCount} \u2014 Memory Store`);
626
858
  await stepMemoryStore();
627
859
  console.log("");
860
+ step++;
628
861
 
629
- console.log(" Step 2/6 MCP Server");
862
+ console.log(` Step ${step}/${stepCount} \u2014 MCP Server`);
630
863
  await stepMcpServer();
631
864
  console.log("");
865
+ step++;
632
866
 
633
- console.log(" Step 3/6 Project Instructions");
634
- await stepClaudeMd();
867
+ console.log(` Step ${step}/${stepCount} \u2014 Project Instructions`);
868
+ await stepInstructions();
635
869
  console.log("");
870
+ step++;
636
871
 
637
- console.log(" Step 4/6 — Tool Permissions");
638
- await stepPermissions();
639
- console.log("");
872
+ if (cc.hasPermissions) {
873
+ console.log(` Step ${step}/${stepCount} \u2014 Tool Permissions`);
874
+ await stepPermissions();
875
+ console.log("");
876
+ step++;
877
+ }
640
878
 
641
- console.log(" Step 5/6 Lifecycle Hooks");
879
+ console.log(` Step ${step}/${stepCount} \u2014 Lifecycle Hooks`);
642
880
  await stepHooks();
643
881
  console.log("");
882
+ step++;
644
883
 
645
- console.log(" Step 6/6 Gitignore");
884
+ console.log(` Step ${step}/${stepCount} \u2014 Gitignore`);
646
885
  await stepGitignore();
647
886
  console.log("");
648
887
 
649
888
  if (dryRun) {
650
- console.log(" Dry run complete no files were modified.");
889
+ console.log(" Dry run complete \u2014 no files were modified.");
651
890
  } else {
652
- console.log(" Setup complete! Start Claude Code — memory is active.");
891
+ console.log(` ${cc.completionMsg}`);
653
892
  console.log(" To remove: npx gitmem-mcp uninstall");
654
893
  }
655
894
  console.log("");