gitmem-mcp 1.2.2 → 1.3.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.
- package/CHANGELOG.md +24 -0
- package/README.md +1 -1
- package/bin/init-wizard.js +299 -408
- package/bin/uninstall.js +79 -80
- package/dist/services/enforcement.js +12 -14
- package/dist/services/local-file-storage.js +2 -1
- package/dist/services/session-state.d.ts +12 -1
- package/dist/services/session-state.js +18 -0
- package/dist/tools/log.js +1 -1
- package/dist/tools/recall.d.ts +1 -0
- package/dist/tools/recall.js +12 -1
- package/package.json +2 -2
- package/schema/starter-scars.json +75 -3
package/bin/init-wizard.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* GitMem Init Wizard
|
|
4
|
+
* GitMem Init Wizard — v2
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* Non-interactive by default on fresh install. Prompts only when
|
|
7
|
+
* existing config needs a merge decision.
|
|
8
8
|
*
|
|
9
|
-
* Usage: npx gitmem-mcp init [--yes] [--dry-run] [--project <name>] [--client <claude|cursor|vscode|windsurf|generic>]
|
|
9
|
+
* Usage: npx gitmem-mcp init [--yes] [--interactive] [--dry-run] [--project <name>] [--client <claude|cursor|vscode|windsurf|generic>]
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import {
|
|
@@ -24,9 +24,42 @@ import { createInterface } from "readline/promises";
|
|
|
24
24
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
25
25
|
const cwd = process.cwd();
|
|
26
26
|
|
|
27
|
-
//
|
|
27
|
+
// ── ANSI Colors — matches gitmem MCP display-protocol.ts ──
|
|
28
|
+
|
|
29
|
+
function useColor() {
|
|
30
|
+
if (process.env.NO_COLOR !== undefined) return false;
|
|
31
|
+
if (process.env.GITMEM_NO_COLOR !== undefined) return false;
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const _color = useColor();
|
|
36
|
+
|
|
37
|
+
const C = {
|
|
38
|
+
reset: _color ? "\x1b[0m" : "",
|
|
39
|
+
bold: _color ? "\x1b[1m" : "",
|
|
40
|
+
dim: _color ? "\x1b[2m" : "",
|
|
41
|
+
red: _color ? "\x1b[31m" : "", // brand accent (Racing Red)
|
|
42
|
+
green: _color ? "\x1b[32m" : "", // success
|
|
43
|
+
yellow: _color ? "\x1b[33m" : "", // warning / prompts
|
|
44
|
+
underline: _color ? "\x1b[4m" : "",
|
|
45
|
+
italic: _color ? "\x1b[3m" : "",
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// Brand mark: ripple icon — dim outer ring, red inner ring, bold center dot
|
|
49
|
+
const RIPPLE = `${C.dim}(${C.reset}${C.red}(${C.reset}${C.bold}\u25cf${C.reset}${C.red})${C.reset}${C.dim})${C.reset}`;
|
|
50
|
+
const PRODUCT = `${RIPPLE} ${C.red}gitmem${C.reset}`;
|
|
51
|
+
|
|
52
|
+
const CHECK = `${C.bold}\u2714${C.reset}`;
|
|
53
|
+
const SKIP = `${C.dim}\u00b7${C.reset}`;
|
|
54
|
+
const WARN = `${C.yellow}!${C.reset}`;
|
|
55
|
+
|
|
56
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
57
|
+
|
|
58
|
+
// ── Parse flags ──
|
|
59
|
+
|
|
28
60
|
const args = process.argv.slice(2);
|
|
29
61
|
const autoYes = args.includes("--yes") || args.includes("-y");
|
|
62
|
+
const interactive = args.includes("--interactive") || args.includes("-i");
|
|
30
63
|
const dryRun = args.includes("--dry-run");
|
|
31
64
|
const projectIdx = args.indexOf("--project");
|
|
32
65
|
const projectName = projectIdx !== -1 ? args[projectIdx + 1] : null;
|
|
@@ -35,7 +68,6 @@ const clientFlag = clientIdx !== -1 ? args[clientIdx + 1]?.toLowerCase() : null;
|
|
|
35
68
|
|
|
36
69
|
// ── Client Configuration ──
|
|
37
70
|
|
|
38
|
-
// Resolve user home directory for clients that use user-level config
|
|
39
71
|
const homeDir = process.env.HOME || process.env.USERPROFILE || "~";
|
|
40
72
|
|
|
41
73
|
const CLIENT_CONFIGS = {
|
|
@@ -55,7 +87,7 @@ const CLIENT_CONFIGS = {
|
|
|
55
87
|
hasPermissions: true,
|
|
56
88
|
hooksInSettings: true,
|
|
57
89
|
hasHooks: true,
|
|
58
|
-
|
|
90
|
+
completionVerb: "Start Claude Code",
|
|
59
91
|
},
|
|
60
92
|
cursor: {
|
|
61
93
|
name: "Cursor",
|
|
@@ -75,7 +107,7 @@ const CLIENT_CONFIGS = {
|
|
|
75
107
|
hasHooks: true,
|
|
76
108
|
hooksFile: join(cwd, ".cursor", "hooks.json"),
|
|
77
109
|
hooksFileName: ".cursor/hooks.json",
|
|
78
|
-
|
|
110
|
+
completionVerb: "Open Cursor (Agent mode)",
|
|
79
111
|
},
|
|
80
112
|
vscode: {
|
|
81
113
|
name: "VS Code (Copilot)",
|
|
@@ -93,7 +125,7 @@ const CLIENT_CONFIGS = {
|
|
|
93
125
|
hasPermissions: false,
|
|
94
126
|
hooksInSettings: false,
|
|
95
127
|
hasHooks: false,
|
|
96
|
-
|
|
128
|
+
completionVerb: "Open VS Code",
|
|
97
129
|
},
|
|
98
130
|
windsurf: {
|
|
99
131
|
name: "Windsurf",
|
|
@@ -111,7 +143,7 @@ const CLIENT_CONFIGS = {
|
|
|
111
143
|
hasPermissions: false,
|
|
112
144
|
hooksInSettings: false,
|
|
113
145
|
hasHooks: false,
|
|
114
|
-
|
|
146
|
+
completionVerb: "Open Windsurf",
|
|
115
147
|
},
|
|
116
148
|
generic: {
|
|
117
149
|
name: "Generic MCP Client",
|
|
@@ -129,27 +161,25 @@ const CLIENT_CONFIGS = {
|
|
|
129
161
|
hasPermissions: false,
|
|
130
162
|
hooksInSettings: false,
|
|
131
163
|
hasHooks: false,
|
|
132
|
-
|
|
133
|
-
"Setup complete! Configure your MCP client to use the gitmem server from .mcp.json.",
|
|
164
|
+
completionVerb: "Configure your MCP client with .mcp.json",
|
|
134
165
|
},
|
|
135
166
|
};
|
|
136
167
|
|
|
137
|
-
// Shared paths
|
|
168
|
+
// Shared paths
|
|
138
169
|
const gitmemDir = join(cwd, ".gitmem");
|
|
139
170
|
const gitignorePath = join(cwd, ".gitignore");
|
|
140
171
|
const starterScarsPath = join(__dirname, "..", "schema", "starter-scars.json");
|
|
141
172
|
const hooksScriptsDir = join(__dirname, "..", "hooks", "scripts");
|
|
142
173
|
|
|
143
174
|
let rl;
|
|
144
|
-
let client;
|
|
145
|
-
let cc;
|
|
175
|
+
let client;
|
|
176
|
+
let cc;
|
|
146
177
|
|
|
147
178
|
// ── Client Detection ──
|
|
148
179
|
|
|
149
180
|
const VALID_CLIENTS = Object.keys(CLIENT_CONFIGS);
|
|
150
181
|
|
|
151
182
|
function detectClient() {
|
|
152
|
-
// Explicit flag takes priority
|
|
153
183
|
if (clientFlag) {
|
|
154
184
|
if (!VALID_CLIENTS.includes(clientFlag)) {
|
|
155
185
|
console.error(` Error: Unknown client "${clientFlag}". Use --client ${VALID_CLIENTS.join("|")}.`);
|
|
@@ -158,7 +188,6 @@ function detectClient() {
|
|
|
158
188
|
return clientFlag;
|
|
159
189
|
}
|
|
160
190
|
|
|
161
|
-
// Auto-detect based on directory/file presence
|
|
162
191
|
const hasCursorDir = existsSync(join(cwd, ".cursor"));
|
|
163
192
|
const hasClaudeDir = existsSync(join(cwd, ".claude"));
|
|
164
193
|
const hasMcpJson = existsSync(join(cwd, ".mcp.json"));
|
|
@@ -169,28 +198,20 @@ function detectClient() {
|
|
|
169
198
|
const hasVscodeMcp = existsSync(join(cwd, ".vscode", "mcp.json"));
|
|
170
199
|
const hasCopilotInstructions = existsSync(join(cwd, ".github", "copilot-instructions.md"));
|
|
171
200
|
const hasWindsurfRules = existsSync(join(cwd, ".windsurfrules"));
|
|
172
|
-
const hasWindsurfMcp = existsSync(
|
|
173
|
-
join(homeDir, ".codeium", "windsurf", "mcp_config.json")
|
|
174
|
-
);
|
|
175
201
|
|
|
176
|
-
// Strong Cursor signals
|
|
177
202
|
if (hasCursorDir && !hasClaudeDir && !hasMcpJson && !hasClaudeMd) return "cursor";
|
|
178
203
|
if (hasCursorRules && !hasClaudeMd && !hasCopilotInstructions) return "cursor";
|
|
179
204
|
if (hasCursorMcp && !hasMcpJson && !hasVscodeMcp) return "cursor";
|
|
180
205
|
|
|
181
|
-
// Strong Claude signals
|
|
182
206
|
if (hasClaudeDir && !hasCursorDir && !hasVscodeDir) return "claude";
|
|
183
207
|
if (hasMcpJson && !hasCursorMcp && !hasVscodeMcp) return "claude";
|
|
184
208
|
if (hasClaudeMd && !hasCursorRules && !hasCopilotInstructions) return "claude";
|
|
185
209
|
|
|
186
|
-
// VS Code signals
|
|
187
210
|
if (hasVscodeMcp && !hasMcpJson && !hasCursorMcp) return "vscode";
|
|
188
211
|
if (hasCopilotInstructions && !hasClaudeMd && !hasCursorRules) return "vscode";
|
|
189
212
|
|
|
190
|
-
// Windsurf signals
|
|
191
213
|
if (hasWindsurfRules && !hasClaudeMd && !hasCursorRules && !hasCopilotInstructions) return "windsurf";
|
|
192
214
|
|
|
193
|
-
// Default to Claude Code (most common)
|
|
194
215
|
return "claude";
|
|
195
216
|
}
|
|
196
217
|
|
|
@@ -202,7 +223,7 @@ async function confirm(message, defaultYes = true) {
|
|
|
202
223
|
rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
203
224
|
}
|
|
204
225
|
const suffix = defaultYes ? "[Y/n]" : "[y/N]";
|
|
205
|
-
const answer = await rl.question(
|
|
226
|
+
const answer = await rl.question(`${C.yellow}?${C.reset} ${message} ${C.dim}${suffix}${C.reset} `);
|
|
206
227
|
const trimmed = answer.trim().toLowerCase();
|
|
207
228
|
if (trimmed === "") return defaultYes;
|
|
208
229
|
return trimmed === "y" || trimmed === "yes";
|
|
@@ -220,6 +241,15 @@ function writeJson(path, data) {
|
|
|
220
241
|
writeFileSync(path, JSON.stringify(data, null, 2) + "\n");
|
|
221
242
|
}
|
|
222
243
|
|
|
244
|
+
function log(icon, main, detail) {
|
|
245
|
+
if (detail) {
|
|
246
|
+
console.log(`${icon} ${C.bold}${main}${C.reset}`);
|
|
247
|
+
console.log(` ${C.dim}${detail}${C.reset}`);
|
|
248
|
+
} else {
|
|
249
|
+
console.log(`${icon} ${main}`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
223
253
|
function buildMcpConfig() {
|
|
224
254
|
const supabaseUrl = process.env.SUPABASE_URL;
|
|
225
255
|
if (!supabaseUrl) {
|
|
@@ -268,109 +298,40 @@ function buildClaudeHooks() {
|
|
|
268
298
|
{
|
|
269
299
|
matcher: "Bash",
|
|
270
300
|
hooks: [
|
|
271
|
-
{
|
|
272
|
-
|
|
273
|
-
command: `bash ${relScripts}/credential-guard.sh`,
|
|
274
|
-
timeout: 3000,
|
|
275
|
-
},
|
|
276
|
-
{
|
|
277
|
-
type: "command",
|
|
278
|
-
command: `bash ${relScripts}/recall-check.sh`,
|
|
279
|
-
timeout: 5000,
|
|
280
|
-
},
|
|
301
|
+
{ type: "command", command: `bash ${relScripts}/credential-guard.sh`, timeout: 3000 },
|
|
302
|
+
{ type: "command", command: `bash ${relScripts}/recall-check.sh`, timeout: 5000 },
|
|
281
303
|
],
|
|
282
304
|
},
|
|
283
305
|
{
|
|
284
306
|
matcher: "Read",
|
|
285
307
|
hooks: [
|
|
286
|
-
{
|
|
287
|
-
type: "command",
|
|
288
|
-
command: `bash ${relScripts}/credential-guard.sh`,
|
|
289
|
-
timeout: 3000,
|
|
290
|
-
},
|
|
308
|
+
{ type: "command", command: `bash ${relScripts}/credential-guard.sh`, timeout: 3000 },
|
|
291
309
|
],
|
|
292
310
|
},
|
|
293
311
|
{
|
|
294
312
|
matcher: "Write",
|
|
295
313
|
hooks: [
|
|
296
|
-
{
|
|
297
|
-
type: "command",
|
|
298
|
-
command: `bash ${relScripts}/recall-check.sh`,
|
|
299
|
-
timeout: 5000,
|
|
300
|
-
},
|
|
314
|
+
{ type: "command", command: `bash ${relScripts}/recall-check.sh`, timeout: 5000 },
|
|
301
315
|
],
|
|
302
316
|
},
|
|
303
317
|
{
|
|
304
318
|
matcher: "Edit",
|
|
305
319
|
hooks: [
|
|
306
|
-
{
|
|
307
|
-
type: "command",
|
|
308
|
-
command: `bash ${relScripts}/recall-check.sh`,
|
|
309
|
-
timeout: 5000,
|
|
310
|
-
},
|
|
320
|
+
{ type: "command", command: `bash ${relScripts}/recall-check.sh`, timeout: 5000 },
|
|
311
321
|
],
|
|
312
322
|
},
|
|
313
323
|
],
|
|
314
324
|
PostToolUse: [
|
|
315
|
-
{
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
command: `bash ${relScripts}/post-tool-use.sh`,
|
|
321
|
-
timeout: 3000,
|
|
322
|
-
},
|
|
323
|
-
],
|
|
324
|
-
},
|
|
325
|
-
{
|
|
326
|
-
matcher: "mcp__gitmem__search",
|
|
327
|
-
hooks: [
|
|
328
|
-
{
|
|
329
|
-
type: "command",
|
|
330
|
-
command: `bash ${relScripts}/post-tool-use.sh`,
|
|
331
|
-
timeout: 3000,
|
|
332
|
-
},
|
|
333
|
-
],
|
|
334
|
-
},
|
|
335
|
-
{
|
|
336
|
-
matcher: "Bash",
|
|
337
|
-
hooks: [
|
|
338
|
-
{
|
|
339
|
-
type: "command",
|
|
340
|
-
command: `bash ${relScripts}/post-tool-use.sh`,
|
|
341
|
-
timeout: 3000,
|
|
342
|
-
},
|
|
343
|
-
],
|
|
344
|
-
},
|
|
345
|
-
{
|
|
346
|
-
matcher: "Write",
|
|
347
|
-
hooks: [
|
|
348
|
-
{
|
|
349
|
-
type: "command",
|
|
350
|
-
command: `bash ${relScripts}/post-tool-use.sh`,
|
|
351
|
-
timeout: 3000,
|
|
352
|
-
},
|
|
353
|
-
],
|
|
354
|
-
},
|
|
355
|
-
{
|
|
356
|
-
matcher: "Edit",
|
|
357
|
-
hooks: [
|
|
358
|
-
{
|
|
359
|
-
type: "command",
|
|
360
|
-
command: `bash ${relScripts}/post-tool-use.sh`,
|
|
361
|
-
timeout: 3000,
|
|
362
|
-
},
|
|
363
|
-
],
|
|
364
|
-
},
|
|
325
|
+
{ matcher: "mcp__gitmem__recall", hooks: [{ type: "command", command: `bash ${relScripts}/post-tool-use.sh`, timeout: 3000 }] },
|
|
326
|
+
{ matcher: "mcp__gitmem__search", hooks: [{ type: "command", command: `bash ${relScripts}/post-tool-use.sh`, timeout: 3000 }] },
|
|
327
|
+
{ matcher: "Bash", hooks: [{ type: "command", command: `bash ${relScripts}/post-tool-use.sh`, timeout: 3000 }] },
|
|
328
|
+
{ matcher: "Write", hooks: [{ type: "command", command: `bash ${relScripts}/post-tool-use.sh`, timeout: 3000 }] },
|
|
329
|
+
{ matcher: "Edit", hooks: [{ type: "command", command: `bash ${relScripts}/post-tool-use.sh`, timeout: 3000 }] },
|
|
365
330
|
],
|
|
366
331
|
Stop: [
|
|
367
332
|
{
|
|
368
333
|
hooks: [
|
|
369
|
-
{
|
|
370
|
-
type: "command",
|
|
371
|
-
command: `bash ${relScripts}/session-close-check.sh`,
|
|
372
|
-
timeout: 5000,
|
|
373
|
-
},
|
|
334
|
+
{ type: "command", command: `bash ${relScripts}/session-close-check.sh`, timeout: 5000 },
|
|
374
335
|
],
|
|
375
336
|
},
|
|
376
337
|
],
|
|
@@ -379,49 +340,21 @@ function buildClaudeHooks() {
|
|
|
379
340
|
|
|
380
341
|
function buildCursorHooks() {
|
|
381
342
|
const relScripts = ".gitmem/hooks";
|
|
382
|
-
// Cursor hooks format: .cursor/hooks.json
|
|
383
|
-
// Events: sessionStart, beforeMCPExecution, afterMCPExecution, stop
|
|
384
|
-
// No per-tool matchers — all MCP calls go through beforeMCPExecution
|
|
385
343
|
return {
|
|
386
|
-
sessionStart: [
|
|
387
|
-
{
|
|
388
|
-
command: `bash ${relScripts}/session-start.sh`,
|
|
389
|
-
timeout: 5000,
|
|
390
|
-
},
|
|
391
|
-
],
|
|
344
|
+
sessionStart: [{ command: `bash ${relScripts}/session-start.sh`, timeout: 5000 }],
|
|
392
345
|
beforeMCPExecution: [
|
|
393
|
-
{
|
|
394
|
-
|
|
395
|
-
timeout: 3000,
|
|
396
|
-
},
|
|
397
|
-
{
|
|
398
|
-
command: `bash ${relScripts}/recall-check.sh`,
|
|
399
|
-
timeout: 5000,
|
|
400
|
-
},
|
|
401
|
-
],
|
|
402
|
-
afterMCPExecution: [
|
|
403
|
-
{
|
|
404
|
-
command: `bash ${relScripts}/post-tool-use.sh`,
|
|
405
|
-
timeout: 3000,
|
|
406
|
-
},
|
|
407
|
-
],
|
|
408
|
-
stop: [
|
|
409
|
-
{
|
|
410
|
-
command: `bash ${relScripts}/session-close-check.sh`,
|
|
411
|
-
timeout: 5000,
|
|
412
|
-
},
|
|
346
|
+
{ command: `bash ${relScripts}/credential-guard.sh`, timeout: 3000 },
|
|
347
|
+
{ command: `bash ${relScripts}/recall-check.sh`, timeout: 5000 },
|
|
413
348
|
],
|
|
349
|
+
afterMCPExecution: [{ command: `bash ${relScripts}/post-tool-use.sh`, timeout: 3000 }],
|
|
350
|
+
stop: [{ command: `bash ${relScripts}/session-close-check.sh`, timeout: 5000 }],
|
|
414
351
|
};
|
|
415
352
|
}
|
|
416
353
|
|
|
417
354
|
function isGitmemHook(entry) {
|
|
418
|
-
// Claude Code format: entry.hooks is an array of {command: "..."}
|
|
419
355
|
if (entry.hooks && Array.isArray(entry.hooks)) {
|
|
420
|
-
return entry.hooks.some(
|
|
421
|
-
(h) => typeof h.command === "string" && h.command.includes("gitmem")
|
|
422
|
-
);
|
|
356
|
+
return entry.hooks.some((h) => typeof h.command === "string" && h.command.includes("gitmem"));
|
|
423
357
|
}
|
|
424
|
-
// Cursor format: entry itself has {command: "..."}
|
|
425
358
|
if (typeof entry.command === "string") {
|
|
426
359
|
return entry.command.includes("gitmem");
|
|
427
360
|
}
|
|
@@ -436,7 +369,29 @@ function getInstructionsTemplate() {
|
|
|
436
369
|
}
|
|
437
370
|
}
|
|
438
371
|
|
|
372
|
+
function copyHookScripts() {
|
|
373
|
+
const destHooksDir = join(gitmemDir, "hooks");
|
|
374
|
+
if (!existsSync(destHooksDir)) {
|
|
375
|
+
mkdirSync(destHooksDir, { recursive: true });
|
|
376
|
+
}
|
|
377
|
+
if (existsSync(hooksScriptsDir)) {
|
|
378
|
+
try {
|
|
379
|
+
for (const file of readdirSync(hooksScriptsDir)) {
|
|
380
|
+
if (file.endsWith(".sh")) {
|
|
381
|
+
const src = join(hooksScriptsDir, file);
|
|
382
|
+
const dest = join(destHooksDir, file);
|
|
383
|
+
writeFileSync(dest, readFileSync(src));
|
|
384
|
+
chmodSync(dest, 0o755);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
} catch {
|
|
388
|
+
// Non-critical
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
439
393
|
// ── Steps ──
|
|
394
|
+
// Each returns { done: bool } so main can track progress
|
|
440
395
|
|
|
441
396
|
async function stepMemoryStore() {
|
|
442
397
|
const learningsPath = join(gitmemDir, "learnings.json");
|
|
@@ -452,29 +407,29 @@ async function stepMemoryStore() {
|
|
|
452
407
|
try {
|
|
453
408
|
starterScars = JSON.parse(readFileSync(starterScarsPath, "utf-8"));
|
|
454
409
|
} catch {
|
|
455
|
-
|
|
456
|
-
return;
|
|
410
|
+
log(WARN, "Could not read starter lessons. Skipping.");
|
|
411
|
+
return { done: false };
|
|
457
412
|
}
|
|
458
413
|
|
|
459
414
|
if (exists && existingCount >= starterScars.length) {
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
415
|
+
log(CHECK, `Memory store already set up ${C.dim}(${existingCount} lessons in .gitmem/)${C.reset}`);
|
|
416
|
+
return { done: false };
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Needs merge — prompt if existing data OR interactive mode
|
|
420
|
+
if (exists || interactive) {
|
|
421
|
+
const prompt = exists
|
|
422
|
+
? `Merge ${starterScars.length} lessons into .gitmem/? (${existingCount} existing)`
|
|
423
|
+
: `Create .gitmem/ with ${starterScars.length} starter lessons?`;
|
|
424
|
+
if (!(await confirm(prompt))) {
|
|
425
|
+
log(SKIP, "Memory store skipped");
|
|
426
|
+
return { done: false };
|
|
427
|
+
}
|
|
473
428
|
}
|
|
474
429
|
|
|
475
430
|
if (dryRun) {
|
|
476
|
-
|
|
477
|
-
return;
|
|
431
|
+
log(CHECK, `Would create .gitmem/ with ${starterScars.length} starter lessons`, "[dry-run]");
|
|
432
|
+
return { done: true };
|
|
478
433
|
}
|
|
479
434
|
|
|
480
435
|
if (!existsSync(gitmemDir)) {
|
|
@@ -509,6 +464,19 @@ async function stepMemoryStore() {
|
|
|
509
464
|
}
|
|
510
465
|
writeJson(learningsPath, existing);
|
|
511
466
|
|
|
467
|
+
// Starter thread — nudges user to add their own project-specific scar
|
|
468
|
+
const threadsPath = join(gitmemDir, "threads.json");
|
|
469
|
+
if (!existsSync(threadsPath)) {
|
|
470
|
+
writeJson(threadsPath, [
|
|
471
|
+
{
|
|
472
|
+
id: "t-welcome01",
|
|
473
|
+
text: "Add your first project-specific scar from a real mistake — starter lessons are generic, yours will be relevant",
|
|
474
|
+
status: "open",
|
|
475
|
+
created_at: now,
|
|
476
|
+
},
|
|
477
|
+
]);
|
|
478
|
+
}
|
|
479
|
+
|
|
512
480
|
// Empty collection files
|
|
513
481
|
for (const file of ["sessions.json", "decisions.json", "scar-usage.json"]) {
|
|
514
482
|
const filePath = join(gitmemDir, file);
|
|
@@ -517,20 +485,14 @@ async function stepMemoryStore() {
|
|
|
517
485
|
}
|
|
518
486
|
}
|
|
519
487
|
|
|
520
|
-
// Closing payload template
|
|
488
|
+
// Closing payload template
|
|
521
489
|
const templatePath = join(gitmemDir, "closing-payload-template.json");
|
|
522
490
|
if (!existsSync(templatePath)) {
|
|
523
491
|
writeJson(templatePath, {
|
|
524
492
|
closing_reflection: {
|
|
525
|
-
what_broke: "",
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
what_worked: "",
|
|
529
|
-
wrong_assumption: "",
|
|
530
|
-
scars_applied: [],
|
|
531
|
-
institutional_memory_items: "",
|
|
532
|
-
collaborative_dynamic: "",
|
|
533
|
-
rapport_notes: ""
|
|
493
|
+
what_broke: "", what_took_longer: "", do_differently: "",
|
|
494
|
+
what_worked: "", wrong_assumption: "", scars_applied: [],
|
|
495
|
+
institutional_memory_items: "", collaborative_dynamic: "", rapport_notes: ""
|
|
534
496
|
},
|
|
535
497
|
task_completion: {
|
|
536
498
|
questions_displayed_at: "ISO-8601 timestamp",
|
|
@@ -539,56 +501,52 @@ async function stepMemoryStore() {
|
|
|
539
501
|
human_response_at: "ISO-8601 timestamp",
|
|
540
502
|
human_response: "no corrections | actual corrections text"
|
|
541
503
|
},
|
|
542
|
-
human_corrections: "",
|
|
543
|
-
|
|
544
|
-
learnings_created: [],
|
|
545
|
-
open_threads: [],
|
|
546
|
-
decisions: []
|
|
504
|
+
human_corrections: "", scars_to_record: [],
|
|
505
|
+
learnings_created: [], open_threads: [], decisions: []
|
|
547
506
|
});
|
|
548
507
|
}
|
|
549
508
|
|
|
550
|
-
|
|
551
|
-
`
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
509
|
+
const mergeNote = added < starterScars.length
|
|
510
|
+
? ` (${added} new, ${starterScars.length - added} already existed)`
|
|
511
|
+
: "";
|
|
512
|
+
|
|
513
|
+
log(CHECK,
|
|
514
|
+
"Created .gitmem/ \u2014 your local memory store",
|
|
515
|
+
`${starterScars.length} lessons from common mistakes included${mergeNote}`
|
|
555
516
|
);
|
|
517
|
+
return { done: true };
|
|
556
518
|
}
|
|
557
519
|
|
|
558
520
|
async function stepMcpServer() {
|
|
559
521
|
const mcpPath = cc.mcpConfigPath;
|
|
560
522
|
const mcpName = cc.mcpConfigName;
|
|
561
|
-
const isUserLevel = cc.mcpConfigScope === "user";
|
|
562
523
|
|
|
563
524
|
const existing = readJson(mcpPath);
|
|
564
|
-
const hasGitmem =
|
|
565
|
-
existing?.mcpServers?.gitmem || existing?.mcpServers?.["gitmem-mcp"];
|
|
525
|
+
const hasGitmem = existing?.mcpServers?.gitmem || existing?.mcpServers?.["gitmem-mcp"];
|
|
566
526
|
|
|
567
527
|
if (hasGitmem) {
|
|
568
|
-
|
|
569
|
-
return;
|
|
528
|
+
log(CHECK, `MCP server already configured ${C.dim}(${mcpName})${C.reset}`);
|
|
529
|
+
return { done: false };
|
|
570
530
|
}
|
|
571
531
|
|
|
572
|
-
const serverCount = existing?.mcpServers
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
return;
|
|
532
|
+
const serverCount = existing?.mcpServers ? Object.keys(existing.mcpServers).length : 0;
|
|
533
|
+
|
|
534
|
+
// Existing servers — prompt for merge
|
|
535
|
+
if ((existing && serverCount > 0) || interactive) {
|
|
536
|
+
const prompt = existing
|
|
537
|
+
? `Add gitmem to ${mcpName}? (${serverCount} existing server${serverCount !== 1 ? "s" : ""} preserved)`
|
|
538
|
+
: `Create ${mcpName} with gitmem server?`;
|
|
539
|
+
if (!(await confirm(prompt))) {
|
|
540
|
+
log(SKIP, "MCP server skipped");
|
|
541
|
+
return { done: false };
|
|
542
|
+
}
|
|
584
543
|
}
|
|
585
544
|
|
|
586
545
|
if (dryRun) {
|
|
587
|
-
|
|
588
|
-
return;
|
|
546
|
+
log(CHECK, `Would configure MCP server in ${mcpName}`, "[dry-run]");
|
|
547
|
+
return { done: true };
|
|
589
548
|
}
|
|
590
549
|
|
|
591
|
-
// Ensure parent directory exists (for .cursor/mcp.json, .vscode/mcp.json, ~/.codeium/windsurf/)
|
|
592
550
|
const parentDir = dirname(mcpPath);
|
|
593
551
|
if (!existsSync(parentDir)) {
|
|
594
552
|
mkdirSync(parentDir, { recursive: true });
|
|
@@ -599,12 +557,12 @@ async function stepMcpServer() {
|
|
|
599
557
|
config.mcpServers.gitmem = buildMcpConfig();
|
|
600
558
|
writeJson(mcpPath, config);
|
|
601
559
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
(isUserLevel ? " [user-level]" : "")
|
|
560
|
+
const preserveNote = serverCount > 0 ? ` (${serverCount} existing server${serverCount !== 1 ? "s" : ""} preserved)` : "";
|
|
561
|
+
log(CHECK,
|
|
562
|
+
`Configured MCP server${preserveNote}`,
|
|
563
|
+
`${cc.name} connects to gitmem automatically`
|
|
607
564
|
);
|
|
565
|
+
return { done: true };
|
|
608
566
|
}
|
|
609
567
|
|
|
610
568
|
async function stepInstructions() {
|
|
@@ -612,8 +570,8 @@ async function stepInstructions() {
|
|
|
612
570
|
const instrName = cc.instructionsName;
|
|
613
571
|
|
|
614
572
|
if (!template) {
|
|
615
|
-
|
|
616
|
-
return;
|
|
573
|
+
log(WARN, `${instrName} template not found. Skipping.`);
|
|
574
|
+
return { done: false };
|
|
617
575
|
}
|
|
618
576
|
|
|
619
577
|
const instrPath = cc.instructionsFile;
|
|
@@ -621,33 +579,31 @@ async function stepInstructions() {
|
|
|
621
579
|
let content = exists ? readFileSync(instrPath, "utf-8") : "";
|
|
622
580
|
|
|
623
581
|
if (content.includes(cc.startMarker)) {
|
|
624
|
-
|
|
625
|
-
return;
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
582
|
+
log(CHECK, `Instructions already configured ${C.dim}(${instrName})${C.reset}`);
|
|
583
|
+
return { done: false };
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// Existing file without gitmem section — prompt for append
|
|
587
|
+
if (exists || interactive) {
|
|
588
|
+
const prompt = exists
|
|
589
|
+
? `Add gitmem section to existing ${instrName}?`
|
|
590
|
+
: `Create ${instrName} with gitmem instructions?`;
|
|
591
|
+
if (!(await confirm(prompt))) {
|
|
592
|
+
log(SKIP, "Instructions skipped");
|
|
593
|
+
return { done: false };
|
|
594
|
+
}
|
|
635
595
|
}
|
|
636
596
|
|
|
637
597
|
if (dryRun) {
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
);
|
|
641
|
-
return;
|
|
598
|
+
log(CHECK, `Would ${exists ? "update" : "create"} ${instrName}`, "[dry-run]");
|
|
599
|
+
return { done: true };
|
|
642
600
|
}
|
|
643
601
|
|
|
644
|
-
// Template should already have delimiters, but ensure they're there
|
|
645
602
|
let block = template;
|
|
646
603
|
if (!block.includes(cc.startMarker)) {
|
|
647
604
|
block = `${cc.startMarker}\n${block}\n${cc.endMarker}`;
|
|
648
605
|
}
|
|
649
606
|
|
|
650
|
-
// Ensure parent directory exists (for .github/copilot-instructions.md)
|
|
651
607
|
const instrParentDir = dirname(instrPath);
|
|
652
608
|
if (!existsSync(instrParentDir)) {
|
|
653
609
|
mkdirSync(instrParentDir, { recursive: true });
|
|
@@ -660,35 +616,38 @@ async function stepInstructions() {
|
|
|
660
616
|
}
|
|
661
617
|
|
|
662
618
|
writeFileSync(instrPath, content);
|
|
663
|
-
|
|
664
|
-
|
|
619
|
+
|
|
620
|
+
log(CHECK,
|
|
621
|
+
`${exists ? "Updated" : "Created"} ${instrName}`,
|
|
622
|
+
exists
|
|
623
|
+
? "Added gitmem section (your existing content is preserved)"
|
|
624
|
+
: "Teaches your agent how to use memory"
|
|
665
625
|
);
|
|
626
|
+
return { done: true };
|
|
666
627
|
}
|
|
667
628
|
|
|
668
629
|
async function stepPermissions() {
|
|
669
|
-
|
|
670
|
-
if (!cc.hasPermissions) {
|
|
671
|
-
console.log(` Not needed for ${cc.name}. Skipping.`);
|
|
672
|
-
return;
|
|
673
|
-
}
|
|
630
|
+
if (!cc.hasPermissions) return { done: false };
|
|
674
631
|
|
|
675
632
|
const existing = readJson(cc.settingsFile);
|
|
676
633
|
const allow = existing?.permissions?.allow || [];
|
|
677
634
|
const pattern = "mcp__gitmem__*";
|
|
678
635
|
|
|
679
636
|
if (allow.includes(pattern)) {
|
|
680
|
-
|
|
681
|
-
return;
|
|
637
|
+
log(CHECK, `Tool permissions already configured`);
|
|
638
|
+
return { done: false };
|
|
682
639
|
}
|
|
683
640
|
|
|
684
|
-
if (
|
|
685
|
-
|
|
686
|
-
|
|
641
|
+
if (interactive) {
|
|
642
|
+
if (!(await confirm(`Auto-approve gitmem tools in ${cc.configDir}/settings.json?`))) {
|
|
643
|
+
log(SKIP, "Tool permissions skipped");
|
|
644
|
+
return { done: false };
|
|
645
|
+
}
|
|
687
646
|
}
|
|
688
647
|
|
|
689
648
|
if (dryRun) {
|
|
690
|
-
|
|
691
|
-
return;
|
|
649
|
+
log(CHECK, "Would auto-approve gitmem tools", "[dry-run]");
|
|
650
|
+
return { done: true };
|
|
692
651
|
}
|
|
693
652
|
|
|
694
653
|
const settings = existing || {};
|
|
@@ -701,35 +660,16 @@ async function stepPermissions() {
|
|
|
701
660
|
settings.permissions = { ...permissions, allow: newAllow };
|
|
702
661
|
writeJson(cc.settingsFile, settings);
|
|
703
662
|
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
if (!existsSync(destHooksDir)) {
|
|
710
|
-
mkdirSync(destHooksDir, { recursive: true });
|
|
711
|
-
}
|
|
712
|
-
if (existsSync(hooksScriptsDir)) {
|
|
713
|
-
try {
|
|
714
|
-
for (const file of readdirSync(hooksScriptsDir)) {
|
|
715
|
-
if (file.endsWith(".sh")) {
|
|
716
|
-
const src = join(hooksScriptsDir, file);
|
|
717
|
-
const dest = join(destHooksDir, file);
|
|
718
|
-
writeFileSync(dest, readFileSync(src));
|
|
719
|
-
chmodSync(dest, 0o755);
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
} catch {
|
|
723
|
-
// Non-critical
|
|
724
|
-
}
|
|
725
|
-
}
|
|
663
|
+
log(CHECK,
|
|
664
|
+
"Auto-approved gitmem tools",
|
|
665
|
+
"Memory tools run without interrupting you"
|
|
666
|
+
);
|
|
667
|
+
return { done: true };
|
|
726
668
|
}
|
|
727
669
|
|
|
728
670
|
async function stepHooks() {
|
|
729
671
|
if (!cc.hasHooks) {
|
|
730
|
-
|
|
731
|
-
console.log(" Enforcement relies on system prompt instructions instead.");
|
|
732
|
-
return;
|
|
672
|
+
return { done: false };
|
|
733
673
|
}
|
|
734
674
|
if (cc.hooksInSettings) {
|
|
735
675
|
return stepHooksClaude();
|
|
@@ -743,11 +683,10 @@ async function stepHooksClaude() {
|
|
|
743
683
|
const hasGitmem = JSON.stringify(hooks).includes("gitmem");
|
|
744
684
|
|
|
745
685
|
if (hasGitmem) {
|
|
746
|
-
|
|
747
|
-
return;
|
|
686
|
+
log(CHECK, `Automatic memory hooks already configured`);
|
|
687
|
+
return { done: false };
|
|
748
688
|
}
|
|
749
689
|
|
|
750
|
-
// Count existing non-gitmem hooks
|
|
751
690
|
let existingHookCount = 0;
|
|
752
691
|
for (const entries of Object.values(hooks)) {
|
|
753
692
|
if (Array.isArray(entries)) {
|
|
@@ -755,19 +694,20 @@ async function stepHooksClaude() {
|
|
|
755
694
|
}
|
|
756
695
|
}
|
|
757
696
|
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
697
|
+
// Existing hooks — prompt for merge
|
|
698
|
+
if (existingHookCount > 0 || interactive) {
|
|
699
|
+
const prompt = existingHookCount > 0
|
|
700
|
+
? `Add memory hooks? (${existingHookCount} existing hook${existingHookCount !== 1 ? "s" : ""} preserved)`
|
|
701
|
+
: "Add automatic memory hooks for session tracking?";
|
|
702
|
+
if (!(await confirm(prompt))) {
|
|
703
|
+
log(SKIP, "Hooks skipped");
|
|
704
|
+
return { done: false };
|
|
705
|
+
}
|
|
766
706
|
}
|
|
767
707
|
|
|
768
708
|
if (dryRun) {
|
|
769
|
-
|
|
770
|
-
return;
|
|
709
|
+
log(CHECK, "Would add automatic memory hooks", "[dry-run]");
|
|
710
|
+
return { done: true };
|
|
771
711
|
}
|
|
772
712
|
|
|
773
713
|
copyHookScripts();
|
|
@@ -789,25 +729,23 @@ async function stepHooksClaude() {
|
|
|
789
729
|
settings.hooks = merged;
|
|
790
730
|
writeJson(cc.settingsFile, settings);
|
|
791
731
|
|
|
792
|
-
const
|
|
793
|
-
existingHookCount
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
732
|
+
const preserveNote = existingHookCount > 0
|
|
733
|
+
? ` (${existingHookCount} existing hook${existingHookCount !== 1 ? "s" : ""} preserved)`
|
|
734
|
+
: "";
|
|
735
|
+
|
|
736
|
+
log(CHECK,
|
|
737
|
+
`Added automatic memory hooks${preserveNote}`,
|
|
738
|
+
"Sessions auto-start, memory retrieval on key actions"
|
|
739
|
+
);
|
|
797
740
|
|
|
798
|
-
// Warn about settings.local.json
|
|
799
741
|
if (cc.settingsLocalFile && existsSync(cc.settingsLocalFile)) {
|
|
800
742
|
const local = readJson(cc.settingsLocalFile);
|
|
801
743
|
if (local?.hooks) {
|
|
802
|
-
console.log(
|
|
803
|
-
console.log(
|
|
804
|
-
" Note: .claude/settings.local.json also has hooks."
|
|
805
|
-
);
|
|
806
|
-
console.log(
|
|
807
|
-
" Local hooks take precedence. You may need to manually merge."
|
|
808
|
-
);
|
|
744
|
+
console.log(` ${C.yellow}Note:${C.reset} ${C.dim}.claude/settings.local.json also has hooks \u2014 may need manual merge${C.reset}`);
|
|
809
745
|
}
|
|
810
746
|
}
|
|
747
|
+
|
|
748
|
+
return { done: true };
|
|
811
749
|
}
|
|
812
750
|
|
|
813
751
|
async function stepHooksCursor() {
|
|
@@ -818,11 +756,10 @@ async function stepHooksCursor() {
|
|
|
818
756
|
const hasGitmem = existing ? JSON.stringify(existing).includes("gitmem") : false;
|
|
819
757
|
|
|
820
758
|
if (hasGitmem) {
|
|
821
|
-
|
|
822
|
-
return;
|
|
759
|
+
log(CHECK, `Automatic memory hooks already configured ${C.dim}(${hooksName})${C.reset}`);
|
|
760
|
+
return { done: false };
|
|
823
761
|
}
|
|
824
762
|
|
|
825
|
-
// Count existing non-gitmem hooks
|
|
826
763
|
let existingHookCount = 0;
|
|
827
764
|
if (existing?.hooks) {
|
|
828
765
|
for (const entries of Object.values(existing.hooks)) {
|
|
@@ -832,19 +769,19 @@ async function stepHooksCursor() {
|
|
|
832
769
|
}
|
|
833
770
|
}
|
|
834
771
|
|
|
835
|
-
|
|
836
|
-
existingHookCount > 0
|
|
837
|
-
? `
|
|
838
|
-
: `Add
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
772
|
+
if (existingHookCount > 0 || interactive) {
|
|
773
|
+
const prompt = existingHookCount > 0
|
|
774
|
+
? `Add memory hooks to ${hooksName}? (${existingHookCount} existing hook${existingHookCount !== 1 ? "s" : ""} preserved)`
|
|
775
|
+
: `Add automatic memory hooks to ${hooksName}?`;
|
|
776
|
+
if (!(await confirm(prompt))) {
|
|
777
|
+
log(SKIP, "Hooks skipped");
|
|
778
|
+
return { done: false };
|
|
779
|
+
}
|
|
843
780
|
}
|
|
844
781
|
|
|
845
782
|
if (dryRun) {
|
|
846
|
-
|
|
847
|
-
return;
|
|
783
|
+
log(CHECK, "Would add automatic memory hooks", "[dry-run]");
|
|
784
|
+
return { done: true };
|
|
848
785
|
}
|
|
849
786
|
|
|
850
787
|
copyHookScripts();
|
|
@@ -866,11 +803,15 @@ async function stepHooksCursor() {
|
|
|
866
803
|
config.hooks = merged;
|
|
867
804
|
writeJson(hooksPath, config);
|
|
868
805
|
|
|
869
|
-
const
|
|
870
|
-
existingHookCount
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
806
|
+
const preserveNote = existingHookCount > 0
|
|
807
|
+
? ` (${existingHookCount} existing hook${existingHookCount !== 1 ? "s" : ""} preserved)`
|
|
808
|
+
: "";
|
|
809
|
+
|
|
810
|
+
log(CHECK,
|
|
811
|
+
`Added automatic memory hooks${preserveNote}`,
|
|
812
|
+
"Sessions auto-start, memory retrieval on key actions"
|
|
813
|
+
);
|
|
814
|
+
return { done: true };
|
|
874
815
|
}
|
|
875
816
|
|
|
876
817
|
async function stepGitignore() {
|
|
@@ -878,18 +819,20 @@ async function stepGitignore() {
|
|
|
878
819
|
let content = exists ? readFileSync(gitignorePath, "utf-8") : "";
|
|
879
820
|
|
|
880
821
|
if (content.includes(".gitmem/")) {
|
|
881
|
-
|
|
882
|
-
return;
|
|
822
|
+
log(CHECK, `.gitignore already configured`);
|
|
823
|
+
return { done: false };
|
|
883
824
|
}
|
|
884
825
|
|
|
885
|
-
if (
|
|
886
|
-
|
|
887
|
-
|
|
826
|
+
if (interactive) {
|
|
827
|
+
if (!(await confirm("Add .gitmem/ to .gitignore?"))) {
|
|
828
|
+
log(SKIP, "Gitignore skipped");
|
|
829
|
+
return { done: false };
|
|
830
|
+
}
|
|
888
831
|
}
|
|
889
832
|
|
|
890
833
|
if (dryRun) {
|
|
891
|
-
|
|
892
|
-
return;
|
|
834
|
+
log(CHECK, "Would update .gitignore", "[dry-run]");
|
|
835
|
+
return { done: true };
|
|
893
836
|
}
|
|
894
837
|
|
|
895
838
|
if (exists) {
|
|
@@ -899,7 +842,11 @@ async function stepGitignore() {
|
|
|
899
842
|
}
|
|
900
843
|
writeFileSync(gitignorePath, content);
|
|
901
844
|
|
|
902
|
-
|
|
845
|
+
log(CHECK,
|
|
846
|
+
`${exists ? "Updated" : "Created"} .gitignore`,
|
|
847
|
+
"Memory stays local \u2014 not committed to your repo"
|
|
848
|
+
);
|
|
849
|
+
return { done: true };
|
|
903
850
|
}
|
|
904
851
|
|
|
905
852
|
// ── Main ──
|
|
@@ -908,126 +855,70 @@ async function main() {
|
|
|
908
855
|
const pkg = readJson(join(__dirname, "..", "package.json"));
|
|
909
856
|
const version = pkg?.version || "1.0.0";
|
|
910
857
|
|
|
911
|
-
// Detect client before anything else
|
|
912
858
|
client = detectClient();
|
|
913
859
|
cc = CLIENT_CONFIGS[client];
|
|
914
860
|
|
|
861
|
+
// ── Header — matches gitmem MCP product line format ──
|
|
915
862
|
console.log("");
|
|
916
|
-
console.log(
|
|
917
|
-
|
|
918
|
-
console.log(" (dry-run mode \u2014 no files will be written)");
|
|
919
|
-
}
|
|
920
|
-
if (clientFlag) {
|
|
921
|
-
console.log(` (client: ${client} \u2014 via --client flag)`);
|
|
922
|
-
} else {
|
|
923
|
-
console.log(` (client: ${client} \u2014 auto-detected)`);
|
|
924
|
-
}
|
|
925
|
-
console.log("");
|
|
926
|
-
|
|
927
|
-
// Detect environment
|
|
928
|
-
console.log(" Detecting environment...");
|
|
929
|
-
const detections = [];
|
|
863
|
+
console.log(`${PRODUCT} \u2500\u2500 init v${version}`);
|
|
864
|
+
console.log(`${C.dim}Setting up for ${cc.name}${clientFlag ? "" : " (auto-detected)"}${C.reset}`);
|
|
930
865
|
|
|
931
|
-
if (
|
|
932
|
-
|
|
933
|
-
const count = mcp?.mcpServers ? Object.keys(mcp.mcpServers).length : 0;
|
|
934
|
-
detections.push(
|
|
935
|
-
` ${cc.mcpConfigName} found (${count} server${count !== 1 ? "s" : ""})`
|
|
936
|
-
);
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
if (existsSync(cc.instructionsFile)) {
|
|
940
|
-
const content = readFileSync(cc.instructionsFile, "utf-8");
|
|
941
|
-
const hasGitmem = content.includes(cc.startMarker);
|
|
942
|
-
detections.push(
|
|
943
|
-
` ${cc.instructionsName} found (${hasGitmem ? "has gitmem section" : "no gitmem section"})`
|
|
944
|
-
);
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
if (cc.settingsFile && existsSync(cc.settingsFile)) {
|
|
948
|
-
const settings = readJson(cc.settingsFile);
|
|
949
|
-
const hookCount = settings?.hooks
|
|
950
|
-
? Object.values(settings.hooks).flat().length
|
|
951
|
-
: 0;
|
|
952
|
-
detections.push(
|
|
953
|
-
` .claude/settings.json found (${hookCount} hook${hookCount !== 1 ? "s" : ""})`
|
|
954
|
-
);
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
if (!cc.hooksInSettings && cc.hasHooks && cc.hooksFile && existsSync(cc.hooksFile)) {
|
|
958
|
-
const hooks = readJson(cc.hooksFile);
|
|
959
|
-
const hookCount = hooks?.hooks
|
|
960
|
-
? Object.values(hooks.hooks).flat().length
|
|
961
|
-
: 0;
|
|
962
|
-
detections.push(
|
|
963
|
-
` ${cc.hooksFileName} found (${hookCount} hook${hookCount !== 1 ? "s" : ""})`
|
|
964
|
-
);
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
if (existsSync(gitignorePath)) {
|
|
968
|
-
detections.push(" .gitignore found");
|
|
866
|
+
if (dryRun) {
|
|
867
|
+
console.log(`${C.dim}dry-run mode \u2014 no files will be written${C.reset}`);
|
|
969
868
|
}
|
|
970
869
|
|
|
971
|
-
|
|
972
|
-
detections.push(" .gitmem/ found");
|
|
973
|
-
}
|
|
870
|
+
console.log("");
|
|
974
871
|
|
|
975
|
-
|
|
976
|
-
console.log(d);
|
|
977
|
-
}
|
|
872
|
+
// ── Run steps ──
|
|
978
873
|
|
|
979
|
-
|
|
980
|
-
console.log(
|
|
981
|
-
` Tier: ${tier}` +
|
|
982
|
-
(tier === "free" ? " (no SUPABASE_URL detected)" : " (SUPABASE_URL detected)")
|
|
983
|
-
);
|
|
984
|
-
console.log("");
|
|
874
|
+
let configured = 0;
|
|
985
875
|
|
|
986
|
-
|
|
987
|
-
let stepCount = 4; // memory store + mcp server + instructions + gitignore
|
|
988
|
-
if (cc.hasPermissions) stepCount++;
|
|
989
|
-
if (cc.hasHooks) stepCount++;
|
|
990
|
-
let step = 1;
|
|
876
|
+
const d = _color && !dryRun ? 500 : 0;
|
|
991
877
|
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
step++;
|
|
878
|
+
const r1 = await stepMemoryStore();
|
|
879
|
+
if (r1.done) configured++;
|
|
880
|
+
if (d) await sleep(d);
|
|
996
881
|
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
step++;
|
|
882
|
+
const r2 = await stepMcpServer();
|
|
883
|
+
if (r2.done) configured++;
|
|
884
|
+
if (d) await sleep(d);
|
|
1001
885
|
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
step++;
|
|
886
|
+
const r3 = await stepInstructions();
|
|
887
|
+
if (r3.done) configured++;
|
|
888
|
+
if (d) await sleep(d);
|
|
1006
889
|
|
|
1007
890
|
if (cc.hasPermissions) {
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
step++;
|
|
891
|
+
const r4 = await stepPermissions();
|
|
892
|
+
if (r4.done) configured++;
|
|
893
|
+
if (d) await sleep(d);
|
|
1012
894
|
}
|
|
1013
895
|
|
|
1014
896
|
if (cc.hasHooks) {
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
step++;
|
|
897
|
+
const r5 = await stepHooks();
|
|
898
|
+
if (r5.done) configured++;
|
|
899
|
+
if (d) await sleep(d);
|
|
1019
900
|
}
|
|
1020
901
|
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
console.log("");
|
|
902
|
+
const r6 = await stepGitignore();
|
|
903
|
+
if (r6.done) configured++;
|
|
1024
904
|
|
|
905
|
+
// ── Footer ──
|
|
1025
906
|
if (dryRun) {
|
|
1026
|
-
console.log(
|
|
907
|
+
console.log(`${C.dim}Dry run complete \u2014 no files were modified.${C.reset}`);
|
|
908
|
+
} else if (configured === 0) {
|
|
909
|
+
console.log(`${C.dim}gitmem-mcp is already installed and configured.${C.reset}`);
|
|
910
|
+
console.log(`${C.dim}Need help? ${C.reset}${C.red}https://gitmem.ai/docs${C.reset}`);
|
|
1027
911
|
} else {
|
|
1028
|
-
console.log(
|
|
1029
|
-
console.log("
|
|
912
|
+
console.log("");
|
|
913
|
+
console.log("───────────────────────────────────────────────────");
|
|
914
|
+
console.log("");
|
|
915
|
+
console.log(`${PRODUCT} ${C.red}${C.bold}installed successfully!${C.reset}`);
|
|
916
|
+
console.log(`${C.dim}Docs:${C.reset} ${C.red}https://gitmem.ai/docs${C.reset}`);
|
|
917
|
+
console.log("");
|
|
918
|
+
console.log(`${C.dim}Try asking your agent:${C.reset}`);
|
|
919
|
+
console.log(` ${C.italic}"Review the gitmem tools, test them, convince yourself"${C.reset}`);
|
|
1030
920
|
}
|
|
921
|
+
|
|
1031
922
|
console.log("");
|
|
1032
923
|
|
|
1033
924
|
if (rl) rl.close();
|