gitmem-mcp 1.0.0 → 1.0.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 (246) hide show
  1. package/CLAUDE.md.template +63 -55
  2. package/README.md +60 -29
  3. package/bin/gitmem.js +233 -109
  4. package/bin/init-wizard.js +642 -0
  5. package/bin/uninstall.js +288 -0
  6. package/dist/commands/check.js +20 -20
  7. package/dist/commands/check.js.map +1 -1
  8. package/dist/constants/closing-questions.d.ts +6 -0
  9. package/dist/constants/closing-questions.d.ts.map +1 -1
  10. package/dist/constants/closing-questions.js +65 -0
  11. package/dist/constants/closing-questions.js.map +1 -1
  12. package/dist/hooks/format-utils.d.ts +52 -0
  13. package/dist/hooks/format-utils.d.ts.map +1 -0
  14. package/dist/hooks/format-utils.js +89 -0
  15. package/dist/hooks/format-utils.js.map +1 -0
  16. package/dist/hooks/quick-retrieve.d.ts +30 -0
  17. package/dist/hooks/quick-retrieve.d.ts.map +1 -0
  18. package/dist/hooks/quick-retrieve.js +149 -0
  19. package/dist/hooks/quick-retrieve.js.map +1 -0
  20. package/dist/index.js +0 -0
  21. package/dist/schemas/active-sessions.d.ts +8 -8
  22. package/dist/schemas/analyze.d.ts +3 -3
  23. package/dist/schemas/common.d.ts +2 -2
  24. package/dist/schemas/common.d.ts.map +1 -1
  25. package/dist/schemas/common.js +1 -1
  26. package/dist/schemas/common.js.map +1 -1
  27. package/dist/schemas/create-decision.d.ts +3 -3
  28. package/dist/schemas/create-learning.d.ts +13 -13
  29. package/dist/schemas/log.d.ts +3 -3
  30. package/dist/schemas/prepare-context.d.ts +3 -3
  31. package/dist/schemas/recall.d.ts +3 -3
  32. package/dist/schemas/record-scar-usage-batch.d.ts +8 -3
  33. package/dist/schemas/record-scar-usage-batch.d.ts.map +1 -1
  34. package/dist/schemas/record-scar-usage.d.ts +3 -0
  35. package/dist/schemas/record-scar-usage.d.ts.map +1 -1
  36. package/dist/schemas/record-scar-usage.js +1 -0
  37. package/dist/schemas/record-scar-usage.js.map +1 -1
  38. package/dist/schemas/registry.d.ts +18 -0
  39. package/dist/schemas/registry.d.ts.map +1 -0
  40. package/dist/schemas/registry.js +158 -0
  41. package/dist/schemas/registry.js.map +1 -0
  42. package/dist/schemas/save-transcript.d.ts +3 -3
  43. package/dist/schemas/search-transcripts.d.ts +33 -0
  44. package/dist/schemas/search-transcripts.d.ts.map +1 -0
  45. package/dist/schemas/search-transcripts.js +26 -0
  46. package/dist/schemas/search-transcripts.js.map +1 -0
  47. package/dist/schemas/search.d.ts +3 -3
  48. package/dist/schemas/session-close.d.ts +43 -15
  49. package/dist/schemas/session-close.d.ts.map +1 -1
  50. package/dist/schemas/session-close.js +7 -2
  51. package/dist/schemas/session-close.js.map +1 -1
  52. package/dist/schemas/session-start.d.ts +3 -3
  53. package/dist/schemas/thread.d.ts +3 -3
  54. package/dist/server.d.ts.map +1 -1
  55. package/dist/server.js +82 -28
  56. package/dist/server.js.map +1 -1
  57. package/dist/services/active-sessions.d.ts +2 -1
  58. package/dist/services/active-sessions.d.ts.map +1 -1
  59. package/dist/services/active-sessions.js +130 -84
  60. package/dist/services/active-sessions.js.map +1 -1
  61. package/dist/services/analytics.d.ts.map +1 -1
  62. package/dist/services/analytics.js +1 -0
  63. package/dist/services/analytics.js.map +1 -1
  64. package/dist/services/behavioral-decay.d.ts +40 -0
  65. package/dist/services/behavioral-decay.d.ts.map +1 -0
  66. package/dist/services/behavioral-decay.js +110 -0
  67. package/dist/services/behavioral-decay.js.map +1 -0
  68. package/dist/services/bm25.d.ts +39 -0
  69. package/dist/services/bm25.d.ts.map +1 -0
  70. package/dist/services/bm25.js +132 -0
  71. package/dist/services/bm25.js.map +1 -0
  72. package/dist/services/cache.d.ts.map +1 -1
  73. package/dist/services/cache.js +9 -8
  74. package/dist/services/cache.js.map +1 -1
  75. package/dist/services/cache.test.js +17 -17
  76. package/dist/services/cache.test.js.map +1 -1
  77. package/dist/services/compliance-validator.d.ts.map +1 -1
  78. package/dist/services/compliance-validator.js +12 -1
  79. package/dist/services/compliance-validator.js.map +1 -1
  80. package/dist/services/display-protocol.d.ts +31 -0
  81. package/dist/services/display-protocol.d.ts.map +1 -0
  82. package/dist/services/display-protocol.js +73 -0
  83. package/dist/services/display-protocol.js.map +1 -0
  84. package/dist/services/effect-tracker.d.ts +81 -0
  85. package/dist/services/effect-tracker.d.ts.map +1 -0
  86. package/dist/services/effect-tracker.js +181 -0
  87. package/dist/services/effect-tracker.js.map +1 -0
  88. package/dist/services/file-lock.d.ts +31 -0
  89. package/dist/services/file-lock.d.ts.map +1 -0
  90. package/dist/services/file-lock.js +124 -0
  91. package/dist/services/file-lock.js.map +1 -0
  92. package/dist/services/gitmem-dir.d.ts +7 -0
  93. package/dist/services/gitmem-dir.d.ts.map +1 -1
  94. package/dist/services/gitmem-dir.js +21 -0
  95. package/dist/services/gitmem-dir.js.map +1 -1
  96. package/dist/services/local-file-storage.d.ts +3 -2
  97. package/dist/services/local-file-storage.d.ts.map +1 -1
  98. package/dist/services/local-file-storage.js +30 -43
  99. package/dist/services/local-file-storage.js.map +1 -1
  100. package/dist/services/local-vector-search.d.ts +10 -9
  101. package/dist/services/local-vector-search.d.ts.map +1 -1
  102. package/dist/services/local-vector-search.js +28 -23
  103. package/dist/services/local-vector-search.js.map +1 -1
  104. package/dist/services/metrics.d.ts +7 -2
  105. package/dist/services/metrics.d.ts.map +1 -1
  106. package/dist/services/metrics.js +41 -33
  107. package/dist/services/metrics.js.map +1 -1
  108. package/dist/services/session-state.d.ts +8 -0
  109. package/dist/services/session-state.d.ts.map +1 -1
  110. package/dist/services/session-state.js +9 -2
  111. package/dist/services/session-state.js.map +1 -1
  112. package/dist/services/startup.d.ts +12 -13
  113. package/dist/services/startup.d.ts.map +1 -1
  114. package/dist/services/startup.js +104 -57
  115. package/dist/services/startup.js.map +1 -1
  116. package/dist/services/supabase-client.d.ts +2 -1
  117. package/dist/services/supabase-client.d.ts.map +1 -1
  118. package/dist/services/supabase-client.js +22 -16
  119. package/dist/services/supabase-client.js.map +1 -1
  120. package/dist/services/thread-dedup.d.ts +9 -0
  121. package/dist/services/thread-dedup.d.ts.map +1 -1
  122. package/dist/services/thread-dedup.js +27 -0
  123. package/dist/services/thread-dedup.js.map +1 -1
  124. package/dist/services/thread-manager.d.ts.map +1 -1
  125. package/dist/services/thread-manager.js +38 -16
  126. package/dist/services/thread-manager.js.map +1 -1
  127. package/dist/services/thread-suggestions.d.ts.map +1 -1
  128. package/dist/services/thread-suggestions.js +1 -1
  129. package/dist/services/thread-suggestions.js.map +1 -1
  130. package/dist/services/thread-supabase.d.ts +0 -1
  131. package/dist/services/thread-supabase.d.ts.map +1 -1
  132. package/dist/services/thread-supabase.js +83 -54
  133. package/dist/services/thread-supabase.js.map +1 -1
  134. package/dist/services/timezone.d.ts.map +1 -1
  135. package/dist/services/timezone.js +1 -0
  136. package/dist/services/timezone.js.map +1 -1
  137. package/dist/services/transcript-chunker.d.ts.map +1 -1
  138. package/dist/services/transcript-chunker.js +18 -4
  139. package/dist/services/transcript-chunker.js.map +1 -1
  140. package/dist/services/variant-generation.d.ts +41 -0
  141. package/dist/services/variant-generation.d.ts.map +1 -0
  142. package/dist/services/variant-generation.js +263 -0
  143. package/dist/services/variant-generation.js.map +1 -0
  144. package/dist/tools/absorb-observations.d.ts.map +1 -1
  145. package/dist/tools/absorb-observations.js +9 -0
  146. package/dist/tools/absorb-observations.js.map +1 -1
  147. package/dist/tools/analyze.d.ts.map +1 -1
  148. package/dist/tools/analyze.js +13 -2
  149. package/dist/tools/analyze.js.map +1 -1
  150. package/dist/tools/archive-learning.d.ts +28 -0
  151. package/dist/tools/archive-learning.d.ts.map +1 -0
  152. package/dist/tools/archive-learning.js +81 -0
  153. package/dist/tools/archive-learning.js.map +1 -0
  154. package/dist/tools/cleanup-threads.d.ts +1 -0
  155. package/dist/tools/cleanup-threads.d.ts.map +1 -1
  156. package/dist/tools/cleanup-threads.js +111 -18
  157. package/dist/tools/cleanup-threads.js.map +1 -1
  158. package/dist/tools/confirm-scars.d.ts.map +1 -1
  159. package/dist/tools/confirm-scars.js +8 -2
  160. package/dist/tools/confirm-scars.js.map +1 -1
  161. package/dist/tools/create-decision.d.ts.map +1 -1
  162. package/dist/tools/create-decision.js +11 -8
  163. package/dist/tools/create-decision.js.map +1 -1
  164. package/dist/tools/create-learning.d.ts.map +1 -1
  165. package/dist/tools/create-learning.js +35 -11
  166. package/dist/tools/create-learning.js.map +1 -1
  167. package/dist/tools/create-linear-issue.d.ts +18 -0
  168. package/dist/tools/create-linear-issue.d.ts.map +1 -0
  169. package/dist/tools/create-linear-issue.js +197 -0
  170. package/dist/tools/create-linear-issue.js.map +1 -0
  171. package/dist/tools/create-thread.d.ts +2 -1
  172. package/dist/tools/create-thread.d.ts.map +1 -1
  173. package/dist/tools/create-thread.js +9 -4
  174. package/dist/tools/create-thread.js.map +1 -1
  175. package/dist/tools/definitions.d.ts +785 -34
  176. package/dist/tools/definitions.d.ts.map +1 -1
  177. package/dist/tools/definitions.js +239 -95
  178. package/dist/tools/definitions.js.map +1 -1
  179. package/dist/tools/dismiss-suggestion.d.ts +1 -0
  180. package/dist/tools/dismiss-suggestion.d.ts.map +1 -1
  181. package/dist/tools/dismiss-suggestion.js +4 -0
  182. package/dist/tools/dismiss-suggestion.js.map +1 -1
  183. package/dist/tools/graph-traverse.d.ts +1 -0
  184. package/dist/tools/graph-traverse.d.ts.map +1 -1
  185. package/dist/tools/graph-traverse.js +24 -9
  186. package/dist/tools/graph-traverse.js.map +1 -1
  187. package/dist/tools/list-threads.d.ts.map +1 -1
  188. package/dist/tools/list-threads.js +49 -5
  189. package/dist/tools/list-threads.js.map +1 -1
  190. package/dist/tools/log.d.ts +1 -0
  191. package/dist/tools/log.d.ts.map +1 -1
  192. package/dist/tools/log.js +84 -17
  193. package/dist/tools/log.js.map +1 -1
  194. package/dist/tools/prepare-context.d.ts +1 -0
  195. package/dist/tools/prepare-context.d.ts.map +1 -1
  196. package/dist/tools/prepare-context.js +15 -85
  197. package/dist/tools/prepare-context.js.map +1 -1
  198. package/dist/tools/promote-suggestion.d.ts +1 -0
  199. package/dist/tools/promote-suggestion.d.ts.map +1 -1
  200. package/dist/tools/promote-suggestion.js +5 -0
  201. package/dist/tools/promote-suggestion.js.map +1 -1
  202. package/dist/tools/recall.d.ts +2 -0
  203. package/dist/tools/recall.d.ts.map +1 -1
  204. package/dist/tools/recall.js +43 -10
  205. package/dist/tools/recall.js.map +1 -1
  206. package/dist/tools/recall.test.js +6 -6
  207. package/dist/tools/recall.test.js.map +1 -1
  208. package/dist/tools/record-scar-usage-batch.d.ts.map +1 -1
  209. package/dist/tools/record-scar-usage-batch.js +13 -0
  210. package/dist/tools/record-scar-usage-batch.js.map +1 -1
  211. package/dist/tools/record-scar-usage.d.ts.map +1 -1
  212. package/dist/tools/record-scar-usage.js +6 -0
  213. package/dist/tools/record-scar-usage.js.map +1 -1
  214. package/dist/tools/resolve-thread.d.ts.map +1 -1
  215. package/dist/tools/resolve-thread.js +57 -6
  216. package/dist/tools/resolve-thread.js.map +1 -1
  217. package/dist/tools/save-transcript.d.ts +1 -0
  218. package/dist/tools/save-transcript.d.ts.map +1 -1
  219. package/dist/tools/save-transcript.js +3 -1
  220. package/dist/tools/save-transcript.js.map +1 -1
  221. package/dist/tools/search-transcripts.d.ts +44 -0
  222. package/dist/tools/search-transcripts.d.ts.map +1 -0
  223. package/dist/tools/search-transcripts.js +158 -0
  224. package/dist/tools/search-transcripts.js.map +1 -0
  225. package/dist/tools/search.d.ts +1 -0
  226. package/dist/tools/search.d.ts.map +1 -1
  227. package/dist/tools/search.js +74 -3
  228. package/dist/tools/search.js.map +1 -1
  229. package/dist/tools/session-close.d.ts.map +1 -1
  230. package/dist/tools/session-close.js +563 -326
  231. package/dist/tools/session-close.js.map +1 -1
  232. package/dist/tools/session-start.d.ts +10 -6
  233. package/dist/tools/session-start.d.ts.map +1 -1
  234. package/dist/tools/session-start.js +317 -426
  235. package/dist/tools/session-start.js.map +1 -1
  236. package/dist/types/index.d.ts +37 -4
  237. package/dist/types/index.d.ts.map +1 -1
  238. package/hooks/hooks/hooks.json +8 -37
  239. package/hooks/scripts/auto-retrieve-hook.sh +163 -0
  240. package/hooks/scripts/post-tool-use.sh +0 -16
  241. package/hooks/scripts/recall-check.sh +0 -11
  242. package/hooks/scripts/session-close-check.sh +1 -1
  243. package/hooks/scripts/session-start.sh +89 -13
  244. package/hooks/tests/test-hooks.sh +3 -49
  245. package/package.json +3 -2
  246. package/schema/setup.sql +1 -1
@@ -0,0 +1,642 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * GitMem Init Wizard
5
+ *
6
+ * Interactive setup that detects existing config, prompts, and merges.
7
+ * Usage: npx gitmem init [--yes] [--dry-run] [--project <name>]
8
+ */
9
+
10
+ import {
11
+ readFileSync,
12
+ writeFileSync,
13
+ mkdirSync,
14
+ existsSync,
15
+ readdirSync,
16
+ chmodSync,
17
+ } from "fs";
18
+ import { dirname, join } from "path";
19
+ import { fileURLToPath } from "url";
20
+ import { createInterface } from "readline/promises";
21
+
22
+ const __dirname = dirname(fileURLToPath(import.meta.url));
23
+ const cwd = process.cwd();
24
+
25
+ // Parse flags
26
+ const args = process.argv.slice(2);
27
+ const autoYes = args.includes("--yes") || args.includes("-y");
28
+ const dryRun = args.includes("--dry-run");
29
+ const projectIdx = args.indexOf("--project");
30
+ const projectName = projectIdx !== -1 ? args[projectIdx + 1] : null;
31
+
32
+ // Paths
33
+ 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
+ const gitignorePath = join(cwd, ".gitignore");
40
+ const templatePath = join(__dirname, "..", "CLAUDE.md.template");
41
+ const starterScarsPath = join(__dirname, "..", "schema", "starter-scars.json");
42
+ const hooksScriptsDir = join(__dirname, "..", "hooks", "scripts");
43
+
44
+ let rl;
45
+
46
+ async function confirm(message, defaultYes = true) {
47
+ if (autoYes) return true;
48
+ if (!rl) {
49
+ rl = createInterface({ input: process.stdin, output: process.stdout });
50
+ }
51
+ const suffix = defaultYes ? "[Y/n]" : "[y/N]";
52
+ const answer = await rl.question(` ${message} ${suffix} `);
53
+ const trimmed = answer.trim().toLowerCase();
54
+ if (trimmed === "") return defaultYes;
55
+ return trimmed === "y" || trimmed === "yes";
56
+ }
57
+
58
+ function readJson(path) {
59
+ try {
60
+ return JSON.parse(readFileSync(path, "utf-8"));
61
+ } catch {
62
+ return null;
63
+ }
64
+ }
65
+
66
+ function writeJson(path, data) {
67
+ writeFileSync(path, JSON.stringify(data, null, 2) + "\n");
68
+ }
69
+
70
+ function buildMcpConfig() {
71
+ const supabaseUrl = process.env.SUPABASE_URL;
72
+ if (!supabaseUrl) {
73
+ return { command: "npx", args: ["-y", "gitmem-mcp"] };
74
+ }
75
+ return {
76
+ command: "npx",
77
+ args: ["-y", "gitmem-mcp"],
78
+ env: {
79
+ SUPABASE_URL: supabaseUrl,
80
+ SUPABASE_SERVICE_ROLE_KEY:
81
+ process.env.SUPABASE_SERVICE_ROLE_KEY || "<your-service-role-key>",
82
+ OPENAI_API_KEY:
83
+ process.env.OPENAI_API_KEY || "<your-openai-key-or-remove>",
84
+ },
85
+ };
86
+ }
87
+
88
+ function buildHooks() {
89
+ const relScripts = "node_modules/gitmem-mcp/hooks/scripts";
90
+ return {
91
+ SessionStart: [
92
+ {
93
+ hooks: [
94
+ {
95
+ type: "command",
96
+ command: `bash ${relScripts}/session-start.sh`,
97
+ statusMessage: "Initializing GitMem session...",
98
+ timeout: 5000,
99
+ },
100
+ ],
101
+ },
102
+ ],
103
+ PreToolUse: [
104
+ {
105
+ matcher: "Bash",
106
+ hooks: [
107
+ {
108
+ type: "command",
109
+ command: `bash ${relScripts}/recall-check.sh`,
110
+ timeout: 5000,
111
+ },
112
+ ],
113
+ },
114
+ {
115
+ matcher: "Write",
116
+ hooks: [
117
+ {
118
+ type: "command",
119
+ command: `bash ${relScripts}/recall-check.sh`,
120
+ timeout: 5000,
121
+ },
122
+ ],
123
+ },
124
+ {
125
+ matcher: "Edit",
126
+ hooks: [
127
+ {
128
+ type: "command",
129
+ command: `bash ${relScripts}/recall-check.sh`,
130
+ timeout: 5000,
131
+ },
132
+ ],
133
+ },
134
+ ],
135
+ PostToolUse: [
136
+ {
137
+ matcher: "mcp__gitmem__recall",
138
+ hooks: [
139
+ {
140
+ type: "command",
141
+ command: `bash ${relScripts}/post-tool-use.sh`,
142
+ timeout: 3000,
143
+ },
144
+ ],
145
+ },
146
+ {
147
+ matcher: "mcp__gitmem__search",
148
+ hooks: [
149
+ {
150
+ type: "command",
151
+ command: `bash ${relScripts}/post-tool-use.sh`,
152
+ timeout: 3000,
153
+ },
154
+ ],
155
+ },
156
+ {
157
+ matcher: "Bash",
158
+ hooks: [
159
+ {
160
+ type: "command",
161
+ command: `bash ${relScripts}/post-tool-use.sh`,
162
+ timeout: 3000,
163
+ },
164
+ ],
165
+ },
166
+ {
167
+ matcher: "Write",
168
+ hooks: [
169
+ {
170
+ type: "command",
171
+ command: `bash ${relScripts}/post-tool-use.sh`,
172
+ timeout: 3000,
173
+ },
174
+ ],
175
+ },
176
+ {
177
+ matcher: "Edit",
178
+ hooks: [
179
+ {
180
+ type: "command",
181
+ command: `bash ${relScripts}/post-tool-use.sh`,
182
+ timeout: 3000,
183
+ },
184
+ ],
185
+ },
186
+ ],
187
+ Stop: [
188
+ {
189
+ hooks: [
190
+ {
191
+ type: "command",
192
+ command: `bash ${relScripts}/session-close-check.sh`,
193
+ timeout: 5000,
194
+ },
195
+ ],
196
+ },
197
+ ],
198
+ };
199
+ }
200
+
201
+ function isGitmemHook(entry) {
202
+ if (!entry.hooks || !Array.isArray(entry.hooks)) return false;
203
+ return entry.hooks.some(
204
+ (h) => typeof h.command === "string" && h.command.includes("gitmem")
205
+ );
206
+ }
207
+
208
+ function getClaudeMdTemplate() {
209
+ try {
210
+ return readFileSync(templatePath, "utf-8");
211
+ } catch {
212
+ return null;
213
+ }
214
+ }
215
+
216
+ // ── Steps ──
217
+
218
+ async function stepMemoryStore() {
219
+ const learningsPath = join(gitmemDir, "learnings.json");
220
+ const exists = existsSync(learningsPath);
221
+ let existingCount = 0;
222
+ if (exists) {
223
+ try {
224
+ existingCount = JSON.parse(readFileSync(learningsPath, "utf-8")).length;
225
+ } catch {}
226
+ }
227
+
228
+ let starterScars;
229
+ try {
230
+ starterScars = JSON.parse(readFileSync(starterScarsPath, "utf-8"));
231
+ } catch {
232
+ console.log(" ! Could not read starter-scars.json. Skipping.");
233
+ return;
234
+ }
235
+
236
+ if (exists && existingCount >= starterScars.length) {
237
+ console.log(
238
+ ` Already configured (${existingCount} scars in .gitmem/). Skipping.`
239
+ );
240
+ return;
241
+ }
242
+
243
+ const prompt = exists
244
+ ? `Merge ${starterScars.length} starter scars into .gitmem/? (${existingCount} existing)`
245
+ : `Create .gitmem/ with ${starterScars.length} starter scars?`;
246
+
247
+ if (!(await confirm(prompt))) {
248
+ console.log(" Skipped.");
249
+ return;
250
+ }
251
+
252
+ if (dryRun) {
253
+ console.log(` [dry-run] Would create .gitmem/ with ${starterScars.length} starter scars`);
254
+ return;
255
+ }
256
+
257
+ if (!existsSync(gitmemDir)) {
258
+ mkdirSync(gitmemDir, { recursive: true });
259
+ }
260
+
261
+ // Config
262
+ const configPath = join(gitmemDir, "config.json");
263
+ if (!existsSync(configPath)) {
264
+ const config = {};
265
+ if (projectName) config.project = projectName;
266
+ writeJson(configPath, config);
267
+ } else if (projectName) {
268
+ const config = readJson(configPath) || {};
269
+ config.project = projectName;
270
+ writeJson(configPath, config);
271
+ }
272
+
273
+ // Merge scars
274
+ let existing = [];
275
+ if (existsSync(learningsPath)) {
276
+ existing = readJson(learningsPath) || [];
277
+ }
278
+ const existingIds = new Set(existing.map((s) => s.id));
279
+ let added = 0;
280
+ const now = new Date().toISOString();
281
+ for (const scar of starterScars) {
282
+ if (!existingIds.has(scar.id)) {
283
+ existing.push({ ...scar, created_at: now, source_date: now.slice(0, 10) });
284
+ added++;
285
+ }
286
+ }
287
+ writeJson(learningsPath, existing);
288
+
289
+ // Empty collection files
290
+ for (const file of ["sessions.json", "decisions.json", "scar-usage.json"]) {
291
+ const filePath = join(gitmemDir, file);
292
+ if (!existsSync(filePath)) {
293
+ writeFileSync(filePath, "[]");
294
+ }
295
+ }
296
+
297
+ console.log(
298
+ ` Created .gitmem/ with ${starterScars.length} starter scars` +
299
+ (added < starterScars.length
300
+ ? ` (${added} new, ${starterScars.length - added} already existed)`
301
+ : "")
302
+ );
303
+ }
304
+
305
+ async function stepMcpServer() {
306
+ const existing = readJson(mcpJsonPath);
307
+ const hasGitmem =
308
+ existing?.mcpServers?.gitmem || existing?.mcpServers?.["gitmem-mcp"];
309
+
310
+ if (hasGitmem) {
311
+ console.log(" Already configured in .mcp.json. Skipping.");
312
+ return;
313
+ }
314
+
315
+ const serverCount = existing?.mcpServers
316
+ ? Object.keys(existing.mcpServers).length
317
+ : 0;
318
+ const tierLabel = process.env.SUPABASE_URL ? "pro" : "free";
319
+ const prompt = existing
320
+ ? `Add gitmem to .mcp.json? (${serverCount} existing server${serverCount !== 1 ? "s" : ""} preserved)`
321
+ : "Create .mcp.json with gitmem server?";
322
+
323
+ if (!(await confirm(prompt))) {
324
+ console.log(" Skipped.");
325
+ return;
326
+ }
327
+
328
+ if (dryRun) {
329
+ console.log(` [dry-run] Would add gitmem entry to .mcp.json (${tierLabel} tier)`);
330
+ return;
331
+ }
332
+
333
+ const config = existing || { mcpServers: {} };
334
+ if (!config.mcpServers) config.mcpServers = {};
335
+ config.mcpServers.gitmem = buildMcpConfig();
336
+ writeJson(mcpJsonPath, config);
337
+
338
+ console.log(
339
+ ` Added gitmem entry to .mcp.json (${tierLabel} tier` +
340
+ (process.env.SUPABASE_URL ? " — Supabase detected" : " — local storage") +
341
+ ")"
342
+ );
343
+ }
344
+
345
+ async function stepClaudeMd() {
346
+ const template = getClaudeMdTemplate();
347
+ if (!template) {
348
+ console.log(" ! CLAUDE.md.template not found. Skipping.");
349
+ return;
350
+ }
351
+
352
+ const exists = existsSync(claudeMdPath);
353
+ let content = exists ? readFileSync(claudeMdPath, "utf-8") : "";
354
+
355
+ if (content.includes("<!-- gitmem:start -->")) {
356
+ console.log(" Already configured in CLAUDE.md. Skipping.");
357
+ return;
358
+ }
359
+
360
+ const prompt = exists
361
+ ? "Append gitmem section to CLAUDE.md?"
362
+ : "Create CLAUDE.md with gitmem instructions?";
363
+
364
+ if (!(await confirm(prompt))) {
365
+ console.log(" Skipped.");
366
+ return;
367
+ }
368
+
369
+ if (dryRun) {
370
+ console.log(
371
+ ` [dry-run] Would ${exists ? "append gitmem section to" : "create"} CLAUDE.md`
372
+ );
373
+ return;
374
+ }
375
+
376
+ // Template should already have delimiters, but ensure they're there
377
+ let block = template;
378
+ if (!block.includes("<!-- gitmem:start -->")) {
379
+ block = `<!-- gitmem:start -->\n${block}\n<!-- gitmem:end -->`;
380
+ }
381
+
382
+ if (exists) {
383
+ content = content.trimEnd() + "\n\n" + block + "\n";
384
+ } else {
385
+ content = block + "\n";
386
+ }
387
+
388
+ writeFileSync(claudeMdPath, content);
389
+ console.log(
390
+ ` ${exists ? "Added gitmem section to" : "Created"} CLAUDE.md`
391
+ );
392
+ }
393
+
394
+ async function stepPermissions() {
395
+ const existing = readJson(settingsPath);
396
+ const allow = existing?.permissions?.allow || [];
397
+ const pattern = "mcp__gitmem__*";
398
+
399
+ if (allow.includes(pattern)) {
400
+ console.log(" Already configured in .claude/settings.json. Skipping.");
401
+ return;
402
+ }
403
+
404
+ if (!(await confirm("Add mcp__gitmem__* to .claude/settings.json?"))) {
405
+ console.log(" Skipped.");
406
+ return;
407
+ }
408
+
409
+ if (dryRun) {
410
+ console.log(" [dry-run] Would add gitmem tool permissions");
411
+ return;
412
+ }
413
+
414
+ const settings = existing || {};
415
+ if (!existsSync(claudeDir)) {
416
+ mkdirSync(claudeDir, { recursive: true });
417
+ }
418
+ const permissions = settings.permissions || {};
419
+ const newAllow = permissions.allow || [];
420
+ newAllow.push(pattern);
421
+ settings.permissions = { ...permissions, allow: newAllow };
422
+ writeJson(settingsPath, settings);
423
+
424
+ console.log(" Added gitmem tool permissions");
425
+ }
426
+
427
+ async function stepHooks() {
428
+ const existing = readJson(settingsPath);
429
+ const hooks = existing?.hooks || {};
430
+ const hasGitmem = JSON.stringify(hooks).includes("gitmem");
431
+
432
+ if (hasGitmem) {
433
+ console.log(" Already configured in .claude/settings.json. Skipping.");
434
+ return;
435
+ }
436
+
437
+ // Count existing non-gitmem hooks
438
+ let existingHookCount = 0;
439
+ for (const entries of Object.values(hooks)) {
440
+ if (Array.isArray(entries)) {
441
+ existingHookCount += entries.filter((e) => !isGitmemHook(e)).length;
442
+ }
443
+ }
444
+
445
+ const prompt =
446
+ existingHookCount > 0
447
+ ? `Merge gitmem hooks into .claude/settings.json? (${existingHookCount} existing hook${existingHookCount !== 1 ? "s" : ""} preserved)`
448
+ : "Add gitmem lifecycle hooks to .claude/settings.json?";
449
+
450
+ if (!(await confirm(prompt))) {
451
+ console.log(" Skipped.");
452
+ return;
453
+ }
454
+
455
+ if (dryRun) {
456
+ console.log(" [dry-run] Would merge 4 gitmem hook types");
457
+ return;
458
+ }
459
+
460
+ // Make hook scripts executable
461
+ if (existsSync(hooksScriptsDir)) {
462
+ try {
463
+ for (const file of readdirSync(hooksScriptsDir)) {
464
+ if (file.endsWith(".sh")) {
465
+ chmodSync(join(hooksScriptsDir, file), 0o755);
466
+ }
467
+ }
468
+ } catch {
469
+ // Non-critical
470
+ }
471
+ }
472
+
473
+ const settings = existing || {};
474
+ if (!existsSync(claudeDir)) {
475
+ mkdirSync(claudeDir, { recursive: true });
476
+ }
477
+
478
+ const gitmemHooks = buildHooks();
479
+ const merged = { ...(settings.hooks || {}) };
480
+
481
+ for (const [eventType, gitmemEntries] of Object.entries(gitmemHooks)) {
482
+ const existingEntries = merged[eventType] || [];
483
+ const nonGitmem = existingEntries.filter((e) => !isGitmemHook(e));
484
+ merged[eventType] = [...nonGitmem, ...gitmemEntries];
485
+ }
486
+
487
+ settings.hooks = merged;
488
+ writeJson(settingsPath, settings);
489
+
490
+ const preservedMsg =
491
+ existingHookCount > 0
492
+ ? ` (${existingHookCount} existing hook${existingHookCount !== 1 ? "s" : ""} preserved)`
493
+ : "";
494
+ console.log(` Merged 4 gitmem hook types${preservedMsg}`);
495
+
496
+ // Warn about settings.local.json
497
+ if (existsSync(settingsLocalPath)) {
498
+ const local = readJson(settingsLocalPath);
499
+ if (local?.hooks) {
500
+ console.log("");
501
+ console.log(
502
+ " Note: .claude/settings.local.json also has hooks."
503
+ );
504
+ console.log(
505
+ " Local hooks take precedence. You may need to manually merge."
506
+ );
507
+ }
508
+ }
509
+ }
510
+
511
+ async function stepGitignore() {
512
+ const exists = existsSync(gitignorePath);
513
+ let content = exists ? readFileSync(gitignorePath, "utf-8") : "";
514
+
515
+ if (content.includes(".gitmem/")) {
516
+ console.log(" Already configured in .gitignore. Skipping.");
517
+ return;
518
+ }
519
+
520
+ if (!(await confirm("Add .gitmem/ to .gitignore?"))) {
521
+ console.log(" Skipped.");
522
+ return;
523
+ }
524
+
525
+ if (dryRun) {
526
+ console.log(" [dry-run] Would add .gitmem/ to .gitignore");
527
+ return;
528
+ }
529
+
530
+ if (exists) {
531
+ content = content.trimEnd() + "\n.gitmem/\n";
532
+ } else {
533
+ content = ".gitmem/\n";
534
+ }
535
+ writeFileSync(gitignorePath, content);
536
+
537
+ console.log(` ${exists ? "Updated" : "Created"} .gitignore`);
538
+ }
539
+
540
+ // ── Main ──
541
+
542
+ async function main() {
543
+ const pkg = readJson(join(__dirname, "..", "package.json"));
544
+ const version = pkg?.version || "1.0.0";
545
+
546
+ console.log("");
547
+ console.log(` gitmem v${version} — Setup`);
548
+ if (dryRun) {
549
+ console.log(" (dry-run mode — no files will be written)");
550
+ }
551
+ console.log("");
552
+
553
+ // Detect environment
554
+ console.log(" Detecting environment...");
555
+ const detections = [];
556
+
557
+ if (existsSync(mcpJsonPath)) {
558
+ const mcp = readJson(mcpJsonPath);
559
+ const count = mcp?.mcpServers ? Object.keys(mcp.mcpServers).length : 0;
560
+ detections.push(
561
+ ` .mcp.json found (${count} server${count !== 1 ? "s" : ""})`
562
+ );
563
+ }
564
+
565
+ if (existsSync(claudeMdPath)) {
566
+ const content = readFileSync(claudeMdPath, "utf-8");
567
+ const hasGitmem = content.includes("<!-- gitmem:start -->");
568
+ detections.push(
569
+ ` CLAUDE.md found (${hasGitmem ? "has gitmem section" : "no gitmem section"})`
570
+ );
571
+ }
572
+
573
+ if (existsSync(settingsPath)) {
574
+ const settings = readJson(settingsPath);
575
+ const hookCount = settings?.hooks
576
+ ? Object.values(settings.hooks).flat().length
577
+ : 0;
578
+ detections.push(
579
+ ` .claude/settings.json found (${hookCount} hook${hookCount !== 1 ? "s" : ""})`
580
+ );
581
+ }
582
+
583
+ if (existsSync(gitignorePath)) {
584
+ detections.push(" .gitignore found");
585
+ }
586
+
587
+ if (existsSync(gitmemDir)) {
588
+ detections.push(" .gitmem/ found");
589
+ }
590
+
591
+ for (const d of detections) {
592
+ console.log(d);
593
+ }
594
+
595
+ const tier = process.env.SUPABASE_URL ? "pro" : "free";
596
+ console.log(
597
+ ` Tier: ${tier}` +
598
+ (tier === "free" ? " (no SUPABASE_URL detected)" : " (SUPABASE_URL detected)")
599
+ );
600
+ console.log("");
601
+
602
+ // Run steps
603
+ console.log(" Step 1/6 — Memory Store");
604
+ await stepMemoryStore();
605
+ console.log("");
606
+
607
+ console.log(" Step 2/6 — MCP Server");
608
+ await stepMcpServer();
609
+ console.log("");
610
+
611
+ console.log(" Step 3/6 — Project Instructions");
612
+ await stepClaudeMd();
613
+ console.log("");
614
+
615
+ console.log(" Step 4/6 — Tool Permissions");
616
+ await stepPermissions();
617
+ console.log("");
618
+
619
+ console.log(" Step 5/6 — Lifecycle Hooks");
620
+ await stepHooks();
621
+ console.log("");
622
+
623
+ console.log(" Step 6/6 — Gitignore");
624
+ await stepGitignore();
625
+ console.log("");
626
+
627
+ if (dryRun) {
628
+ console.log(" Dry run complete — no files were modified.");
629
+ } else {
630
+ console.log(" Setup complete! Start Claude Code — memory is active.");
631
+ console.log(" To remove: npx gitmem uninstall");
632
+ }
633
+ console.log("");
634
+
635
+ if (rl) rl.close();
636
+ }
637
+
638
+ main().catch((err) => {
639
+ console.error("Error:", err.message || err);
640
+ if (rl) rl.close();
641
+ process.exit(1);
642
+ });