gitmem-mcp 1.2.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/bin/init-wizard.js +307 -404
- package/bin/uninstall.js +79 -80
- package/dist/schemas/analyze.d.ts +2 -2
- package/dist/server.js +7 -0
- package/dist/services/enforcement.js +12 -14
- package/dist/services/metrics.d.ts +1 -1
- package/dist/services/metrics.js +1 -0
- package/dist/services/session-state.d.ts +23 -2
- package/dist/services/session-state.js +46 -0
- package/dist/tools/definitions.d.ts +460 -0
- package/dist/tools/definitions.js +81 -2
- package/dist/tools/recall.d.ts +1 -0
- package/dist/tools/recall.js +12 -1
- package/dist/tools/reflect-scars.d.ts +20 -0
- package/dist/tools/reflect-scars.js +219 -0
- package/dist/tools/session-close.js +28 -3
- package/dist/types/index.d.ts +24 -0
- package/hooks/scripts/auto-retrieve-hook.sh +31 -0
- package/package.json +1 -1
- 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) {
|
|
@@ -253,113 +283,55 @@ function buildClaudeHooks() {
|
|
|
253
283
|
],
|
|
254
284
|
},
|
|
255
285
|
],
|
|
256
|
-
|
|
257
|
-
{
|
|
258
|
-
matcher: "Bash",
|
|
259
|
-
hooks: [
|
|
260
|
-
{
|
|
261
|
-
type: "command",
|
|
262
|
-
command: `bash ${relScripts}/credential-guard.sh`,
|
|
263
|
-
timeout: 3000,
|
|
264
|
-
},
|
|
265
|
-
{
|
|
266
|
-
type: "command",
|
|
267
|
-
command: `bash ${relScripts}/recall-check.sh`,
|
|
268
|
-
timeout: 5000,
|
|
269
|
-
},
|
|
270
|
-
],
|
|
271
|
-
},
|
|
286
|
+
UserPromptSubmit: [
|
|
272
287
|
{
|
|
273
|
-
matcher: "Read",
|
|
274
288
|
hooks: [
|
|
275
289
|
{
|
|
276
290
|
type: "command",
|
|
277
|
-
command: `bash ${relScripts}/
|
|
291
|
+
command: `bash ${relScripts}/auto-retrieve-hook.sh`,
|
|
278
292
|
timeout: 3000,
|
|
279
293
|
},
|
|
280
294
|
],
|
|
281
295
|
},
|
|
282
|
-
{
|
|
283
|
-
matcher: "Write",
|
|
284
|
-
hooks: [
|
|
285
|
-
{
|
|
286
|
-
type: "command",
|
|
287
|
-
command: `bash ${relScripts}/recall-check.sh`,
|
|
288
|
-
timeout: 5000,
|
|
289
|
-
},
|
|
290
|
-
],
|
|
291
|
-
},
|
|
292
|
-
{
|
|
293
|
-
matcher: "Edit",
|
|
294
|
-
hooks: [
|
|
295
|
-
{
|
|
296
|
-
type: "command",
|
|
297
|
-
command: `bash ${relScripts}/recall-check.sh`,
|
|
298
|
-
timeout: 5000,
|
|
299
|
-
},
|
|
300
|
-
],
|
|
301
|
-
},
|
|
302
296
|
],
|
|
303
|
-
|
|
304
|
-
{
|
|
305
|
-
matcher: "mcp__gitmem__recall",
|
|
306
|
-
hooks: [
|
|
307
|
-
{
|
|
308
|
-
type: "command",
|
|
309
|
-
command: `bash ${relScripts}/post-tool-use.sh`,
|
|
310
|
-
timeout: 3000,
|
|
311
|
-
},
|
|
312
|
-
],
|
|
313
|
-
},
|
|
297
|
+
PreToolUse: [
|
|
314
298
|
{
|
|
315
|
-
matcher: "
|
|
299
|
+
matcher: "Bash",
|
|
316
300
|
hooks: [
|
|
317
|
-
{
|
|
318
|
-
|
|
319
|
-
command: `bash ${relScripts}/post-tool-use.sh`,
|
|
320
|
-
timeout: 3000,
|
|
321
|
-
},
|
|
301
|
+
{ type: "command", command: `bash ${relScripts}/credential-guard.sh`, timeout: 3000 },
|
|
302
|
+
{ type: "command", command: `bash ${relScripts}/recall-check.sh`, timeout: 5000 },
|
|
322
303
|
],
|
|
323
304
|
},
|
|
324
305
|
{
|
|
325
|
-
matcher: "
|
|
306
|
+
matcher: "Read",
|
|
326
307
|
hooks: [
|
|
327
|
-
{
|
|
328
|
-
type: "command",
|
|
329
|
-
command: `bash ${relScripts}/post-tool-use.sh`,
|
|
330
|
-
timeout: 3000,
|
|
331
|
-
},
|
|
308
|
+
{ type: "command", command: `bash ${relScripts}/credential-guard.sh`, timeout: 3000 },
|
|
332
309
|
],
|
|
333
310
|
},
|
|
334
311
|
{
|
|
335
312
|
matcher: "Write",
|
|
336
313
|
hooks: [
|
|
337
|
-
{
|
|
338
|
-
type: "command",
|
|
339
|
-
command: `bash ${relScripts}/post-tool-use.sh`,
|
|
340
|
-
timeout: 3000,
|
|
341
|
-
},
|
|
314
|
+
{ type: "command", command: `bash ${relScripts}/recall-check.sh`, timeout: 5000 },
|
|
342
315
|
],
|
|
343
316
|
},
|
|
344
317
|
{
|
|
345
318
|
matcher: "Edit",
|
|
346
319
|
hooks: [
|
|
347
|
-
{
|
|
348
|
-
type: "command",
|
|
349
|
-
command: `bash ${relScripts}/post-tool-use.sh`,
|
|
350
|
-
timeout: 3000,
|
|
351
|
-
},
|
|
320
|
+
{ type: "command", command: `bash ${relScripts}/recall-check.sh`, timeout: 5000 },
|
|
352
321
|
],
|
|
353
322
|
},
|
|
354
323
|
],
|
|
324
|
+
PostToolUse: [
|
|
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 }] },
|
|
330
|
+
],
|
|
355
331
|
Stop: [
|
|
356
332
|
{
|
|
357
333
|
hooks: [
|
|
358
|
-
{
|
|
359
|
-
type: "command",
|
|
360
|
-
command: `bash ${relScripts}/session-close-check.sh`,
|
|
361
|
-
timeout: 5000,
|
|
362
|
-
},
|
|
334
|
+
{ type: "command", command: `bash ${relScripts}/session-close-check.sh`, timeout: 5000 },
|
|
363
335
|
],
|
|
364
336
|
},
|
|
365
337
|
],
|
|
@@ -368,49 +340,21 @@ function buildClaudeHooks() {
|
|
|
368
340
|
|
|
369
341
|
function buildCursorHooks() {
|
|
370
342
|
const relScripts = ".gitmem/hooks";
|
|
371
|
-
// Cursor hooks format: .cursor/hooks.json
|
|
372
|
-
// Events: sessionStart, beforeMCPExecution, afterMCPExecution, stop
|
|
373
|
-
// No per-tool matchers — all MCP calls go through beforeMCPExecution
|
|
374
343
|
return {
|
|
375
|
-
sessionStart: [
|
|
376
|
-
{
|
|
377
|
-
command: `bash ${relScripts}/session-start.sh`,
|
|
378
|
-
timeout: 5000,
|
|
379
|
-
},
|
|
380
|
-
],
|
|
344
|
+
sessionStart: [{ command: `bash ${relScripts}/session-start.sh`, timeout: 5000 }],
|
|
381
345
|
beforeMCPExecution: [
|
|
382
|
-
{
|
|
383
|
-
|
|
384
|
-
timeout: 3000,
|
|
385
|
-
},
|
|
386
|
-
{
|
|
387
|
-
command: `bash ${relScripts}/recall-check.sh`,
|
|
388
|
-
timeout: 5000,
|
|
389
|
-
},
|
|
390
|
-
],
|
|
391
|
-
afterMCPExecution: [
|
|
392
|
-
{
|
|
393
|
-
command: `bash ${relScripts}/post-tool-use.sh`,
|
|
394
|
-
timeout: 3000,
|
|
395
|
-
},
|
|
396
|
-
],
|
|
397
|
-
stop: [
|
|
398
|
-
{
|
|
399
|
-
command: `bash ${relScripts}/session-close-check.sh`,
|
|
400
|
-
timeout: 5000,
|
|
401
|
-
},
|
|
346
|
+
{ command: `bash ${relScripts}/credential-guard.sh`, timeout: 3000 },
|
|
347
|
+
{ command: `bash ${relScripts}/recall-check.sh`, timeout: 5000 },
|
|
402
348
|
],
|
|
349
|
+
afterMCPExecution: [{ command: `bash ${relScripts}/post-tool-use.sh`, timeout: 3000 }],
|
|
350
|
+
stop: [{ command: `bash ${relScripts}/session-close-check.sh`, timeout: 5000 }],
|
|
403
351
|
};
|
|
404
352
|
}
|
|
405
353
|
|
|
406
354
|
function isGitmemHook(entry) {
|
|
407
|
-
// Claude Code format: entry.hooks is an array of {command: "..."}
|
|
408
355
|
if (entry.hooks && Array.isArray(entry.hooks)) {
|
|
409
|
-
return entry.hooks.some(
|
|
410
|
-
(h) => typeof h.command === "string" && h.command.includes("gitmem")
|
|
411
|
-
);
|
|
356
|
+
return entry.hooks.some((h) => typeof h.command === "string" && h.command.includes("gitmem"));
|
|
412
357
|
}
|
|
413
|
-
// Cursor format: entry itself has {command: "..."}
|
|
414
358
|
if (typeof entry.command === "string") {
|
|
415
359
|
return entry.command.includes("gitmem");
|
|
416
360
|
}
|
|
@@ -425,7 +369,29 @@ function getInstructionsTemplate() {
|
|
|
425
369
|
}
|
|
426
370
|
}
|
|
427
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
|
+
|
|
428
393
|
// ── Steps ──
|
|
394
|
+
// Each returns { done: bool } so main can track progress
|
|
429
395
|
|
|
430
396
|
async function stepMemoryStore() {
|
|
431
397
|
const learningsPath = join(gitmemDir, "learnings.json");
|
|
@@ -441,29 +407,29 @@ async function stepMemoryStore() {
|
|
|
441
407
|
try {
|
|
442
408
|
starterScars = JSON.parse(readFileSync(starterScarsPath, "utf-8"));
|
|
443
409
|
} catch {
|
|
444
|
-
|
|
445
|
-
return;
|
|
410
|
+
log(WARN, "Could not read starter lessons. Skipping.");
|
|
411
|
+
return { done: false };
|
|
446
412
|
}
|
|
447
413
|
|
|
448
414
|
if (exists && existingCount >= starterScars.length) {
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
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
|
+
}
|
|
462
428
|
}
|
|
463
429
|
|
|
464
430
|
if (dryRun) {
|
|
465
|
-
|
|
466
|
-
return;
|
|
431
|
+
log(CHECK, `Would create .gitmem/ with ${starterScars.length} starter lessons`, "[dry-run]");
|
|
432
|
+
return { done: true };
|
|
467
433
|
}
|
|
468
434
|
|
|
469
435
|
if (!existsSync(gitmemDir)) {
|
|
@@ -498,6 +464,19 @@ async function stepMemoryStore() {
|
|
|
498
464
|
}
|
|
499
465
|
writeJson(learningsPath, existing);
|
|
500
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
|
+
|
|
501
480
|
// Empty collection files
|
|
502
481
|
for (const file of ["sessions.json", "decisions.json", "scar-usage.json"]) {
|
|
503
482
|
const filePath = join(gitmemDir, file);
|
|
@@ -506,20 +485,14 @@ async function stepMemoryStore() {
|
|
|
506
485
|
}
|
|
507
486
|
}
|
|
508
487
|
|
|
509
|
-
// Closing payload template
|
|
488
|
+
// Closing payload template
|
|
510
489
|
const templatePath = join(gitmemDir, "closing-payload-template.json");
|
|
511
490
|
if (!existsSync(templatePath)) {
|
|
512
491
|
writeJson(templatePath, {
|
|
513
492
|
closing_reflection: {
|
|
514
|
-
what_broke: "",
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
what_worked: "",
|
|
518
|
-
wrong_assumption: "",
|
|
519
|
-
scars_applied: [],
|
|
520
|
-
institutional_memory_items: "",
|
|
521
|
-
collaborative_dynamic: "",
|
|
522
|
-
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: ""
|
|
523
496
|
},
|
|
524
497
|
task_completion: {
|
|
525
498
|
questions_displayed_at: "ISO-8601 timestamp",
|
|
@@ -528,56 +501,52 @@ async function stepMemoryStore() {
|
|
|
528
501
|
human_response_at: "ISO-8601 timestamp",
|
|
529
502
|
human_response: "no corrections | actual corrections text"
|
|
530
503
|
},
|
|
531
|
-
human_corrections: "",
|
|
532
|
-
|
|
533
|
-
learnings_created: [],
|
|
534
|
-
open_threads: [],
|
|
535
|
-
decisions: []
|
|
504
|
+
human_corrections: "", scars_to_record: [],
|
|
505
|
+
learnings_created: [], open_threads: [], decisions: []
|
|
536
506
|
});
|
|
537
507
|
}
|
|
538
508
|
|
|
539
|
-
|
|
540
|
-
`
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
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}`
|
|
544
516
|
);
|
|
517
|
+
return { done: true };
|
|
545
518
|
}
|
|
546
519
|
|
|
547
520
|
async function stepMcpServer() {
|
|
548
521
|
const mcpPath = cc.mcpConfigPath;
|
|
549
522
|
const mcpName = cc.mcpConfigName;
|
|
550
|
-
const isUserLevel = cc.mcpConfigScope === "user";
|
|
551
523
|
|
|
552
524
|
const existing = readJson(mcpPath);
|
|
553
|
-
const hasGitmem =
|
|
554
|
-
existing?.mcpServers?.gitmem || existing?.mcpServers?.["gitmem-mcp"];
|
|
525
|
+
const hasGitmem = existing?.mcpServers?.gitmem || existing?.mcpServers?.["gitmem-mcp"];
|
|
555
526
|
|
|
556
527
|
if (hasGitmem) {
|
|
557
|
-
|
|
558
|
-
return;
|
|
528
|
+
log(CHECK, `MCP server already configured ${C.dim}(${mcpName})${C.reset}`);
|
|
529
|
+
return { done: false };
|
|
559
530
|
}
|
|
560
531
|
|
|
561
|
-
const serverCount = existing?.mcpServers
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
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
|
+
}
|
|
573
543
|
}
|
|
574
544
|
|
|
575
545
|
if (dryRun) {
|
|
576
|
-
|
|
577
|
-
return;
|
|
546
|
+
log(CHECK, `Would configure MCP server in ${mcpName}`, "[dry-run]");
|
|
547
|
+
return { done: true };
|
|
578
548
|
}
|
|
579
549
|
|
|
580
|
-
// Ensure parent directory exists (for .cursor/mcp.json, .vscode/mcp.json, ~/.codeium/windsurf/)
|
|
581
550
|
const parentDir = dirname(mcpPath);
|
|
582
551
|
if (!existsSync(parentDir)) {
|
|
583
552
|
mkdirSync(parentDir, { recursive: true });
|
|
@@ -588,12 +557,12 @@ async function stepMcpServer() {
|
|
|
588
557
|
config.mcpServers.gitmem = buildMcpConfig();
|
|
589
558
|
writeJson(mcpPath, config);
|
|
590
559
|
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
(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`
|
|
596
564
|
);
|
|
565
|
+
return { done: true };
|
|
597
566
|
}
|
|
598
567
|
|
|
599
568
|
async function stepInstructions() {
|
|
@@ -601,8 +570,8 @@ async function stepInstructions() {
|
|
|
601
570
|
const instrName = cc.instructionsName;
|
|
602
571
|
|
|
603
572
|
if (!template) {
|
|
604
|
-
|
|
605
|
-
return;
|
|
573
|
+
log(WARN, `${instrName} template not found. Skipping.`);
|
|
574
|
+
return { done: false };
|
|
606
575
|
}
|
|
607
576
|
|
|
608
577
|
const instrPath = cc.instructionsFile;
|
|
@@ -610,33 +579,31 @@ async function stepInstructions() {
|
|
|
610
579
|
let content = exists ? readFileSync(instrPath, "utf-8") : "";
|
|
611
580
|
|
|
612
581
|
if (content.includes(cc.startMarker)) {
|
|
613
|
-
|
|
614
|
-
return;
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
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
|
+
}
|
|
624
595
|
}
|
|
625
596
|
|
|
626
597
|
if (dryRun) {
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
);
|
|
630
|
-
return;
|
|
598
|
+
log(CHECK, `Would ${exists ? "update" : "create"} ${instrName}`, "[dry-run]");
|
|
599
|
+
return { done: true };
|
|
631
600
|
}
|
|
632
601
|
|
|
633
|
-
// Template should already have delimiters, but ensure they're there
|
|
634
602
|
let block = template;
|
|
635
603
|
if (!block.includes(cc.startMarker)) {
|
|
636
604
|
block = `${cc.startMarker}\n${block}\n${cc.endMarker}`;
|
|
637
605
|
}
|
|
638
606
|
|
|
639
|
-
// Ensure parent directory exists (for .github/copilot-instructions.md)
|
|
640
607
|
const instrParentDir = dirname(instrPath);
|
|
641
608
|
if (!existsSync(instrParentDir)) {
|
|
642
609
|
mkdirSync(instrParentDir, { recursive: true });
|
|
@@ -649,35 +616,38 @@ async function stepInstructions() {
|
|
|
649
616
|
}
|
|
650
617
|
|
|
651
618
|
writeFileSync(instrPath, content);
|
|
652
|
-
|
|
653
|
-
|
|
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"
|
|
654
625
|
);
|
|
626
|
+
return { done: true };
|
|
655
627
|
}
|
|
656
628
|
|
|
657
629
|
async function stepPermissions() {
|
|
658
|
-
|
|
659
|
-
if (!cc.hasPermissions) {
|
|
660
|
-
console.log(` Not needed for ${cc.name}. Skipping.`);
|
|
661
|
-
return;
|
|
662
|
-
}
|
|
630
|
+
if (!cc.hasPermissions) return { done: false };
|
|
663
631
|
|
|
664
632
|
const existing = readJson(cc.settingsFile);
|
|
665
633
|
const allow = existing?.permissions?.allow || [];
|
|
666
634
|
const pattern = "mcp__gitmem__*";
|
|
667
635
|
|
|
668
636
|
if (allow.includes(pattern)) {
|
|
669
|
-
|
|
670
|
-
return;
|
|
637
|
+
log(CHECK, `Tool permissions already configured`);
|
|
638
|
+
return { done: false };
|
|
671
639
|
}
|
|
672
640
|
|
|
673
|
-
if (
|
|
674
|
-
|
|
675
|
-
|
|
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
|
+
}
|
|
676
646
|
}
|
|
677
647
|
|
|
678
648
|
if (dryRun) {
|
|
679
|
-
|
|
680
|
-
return;
|
|
649
|
+
log(CHECK, "Would auto-approve gitmem tools", "[dry-run]");
|
|
650
|
+
return { done: true };
|
|
681
651
|
}
|
|
682
652
|
|
|
683
653
|
const settings = existing || {};
|
|
@@ -690,35 +660,16 @@ async function stepPermissions() {
|
|
|
690
660
|
settings.permissions = { ...permissions, allow: newAllow };
|
|
691
661
|
writeJson(cc.settingsFile, settings);
|
|
692
662
|
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
if (!existsSync(destHooksDir)) {
|
|
699
|
-
mkdirSync(destHooksDir, { recursive: true });
|
|
700
|
-
}
|
|
701
|
-
if (existsSync(hooksScriptsDir)) {
|
|
702
|
-
try {
|
|
703
|
-
for (const file of readdirSync(hooksScriptsDir)) {
|
|
704
|
-
if (file.endsWith(".sh")) {
|
|
705
|
-
const src = join(hooksScriptsDir, file);
|
|
706
|
-
const dest = join(destHooksDir, file);
|
|
707
|
-
writeFileSync(dest, readFileSync(src));
|
|
708
|
-
chmodSync(dest, 0o755);
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
} catch {
|
|
712
|
-
// Non-critical
|
|
713
|
-
}
|
|
714
|
-
}
|
|
663
|
+
log(CHECK,
|
|
664
|
+
"Auto-approved gitmem tools",
|
|
665
|
+
"Memory tools run without interrupting you"
|
|
666
|
+
);
|
|
667
|
+
return { done: true };
|
|
715
668
|
}
|
|
716
669
|
|
|
717
670
|
async function stepHooks() {
|
|
718
671
|
if (!cc.hasHooks) {
|
|
719
|
-
|
|
720
|
-
console.log(" Enforcement relies on system prompt instructions instead.");
|
|
721
|
-
return;
|
|
672
|
+
return { done: false };
|
|
722
673
|
}
|
|
723
674
|
if (cc.hooksInSettings) {
|
|
724
675
|
return stepHooksClaude();
|
|
@@ -732,11 +683,10 @@ async function stepHooksClaude() {
|
|
|
732
683
|
const hasGitmem = JSON.stringify(hooks).includes("gitmem");
|
|
733
684
|
|
|
734
685
|
if (hasGitmem) {
|
|
735
|
-
|
|
736
|
-
return;
|
|
686
|
+
log(CHECK, `Automatic memory hooks already configured`);
|
|
687
|
+
return { done: false };
|
|
737
688
|
}
|
|
738
689
|
|
|
739
|
-
// Count existing non-gitmem hooks
|
|
740
690
|
let existingHookCount = 0;
|
|
741
691
|
for (const entries of Object.values(hooks)) {
|
|
742
692
|
if (Array.isArray(entries)) {
|
|
@@ -744,19 +694,20 @@ async function stepHooksClaude() {
|
|
|
744
694
|
}
|
|
745
695
|
}
|
|
746
696
|
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
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
|
+
}
|
|
755
706
|
}
|
|
756
707
|
|
|
757
708
|
if (dryRun) {
|
|
758
|
-
|
|
759
|
-
return;
|
|
709
|
+
log(CHECK, "Would add automatic memory hooks", "[dry-run]");
|
|
710
|
+
return { done: true };
|
|
760
711
|
}
|
|
761
712
|
|
|
762
713
|
copyHookScripts();
|
|
@@ -778,25 +729,23 @@ async function stepHooksClaude() {
|
|
|
778
729
|
settings.hooks = merged;
|
|
779
730
|
writeJson(cc.settingsFile, settings);
|
|
780
731
|
|
|
781
|
-
const
|
|
782
|
-
existingHookCount
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
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
|
+
);
|
|
786
740
|
|
|
787
|
-
// Warn about settings.local.json
|
|
788
741
|
if (cc.settingsLocalFile && existsSync(cc.settingsLocalFile)) {
|
|
789
742
|
const local = readJson(cc.settingsLocalFile);
|
|
790
743
|
if (local?.hooks) {
|
|
791
|
-
console.log(
|
|
792
|
-
console.log(
|
|
793
|
-
" Note: .claude/settings.local.json also has hooks."
|
|
794
|
-
);
|
|
795
|
-
console.log(
|
|
796
|
-
" Local hooks take precedence. You may need to manually merge."
|
|
797
|
-
);
|
|
744
|
+
console.log(` ${C.yellow}Note:${C.reset} ${C.dim}.claude/settings.local.json also has hooks \u2014 may need manual merge${C.reset}`);
|
|
798
745
|
}
|
|
799
746
|
}
|
|
747
|
+
|
|
748
|
+
return { done: true };
|
|
800
749
|
}
|
|
801
750
|
|
|
802
751
|
async function stepHooksCursor() {
|
|
@@ -807,11 +756,10 @@ async function stepHooksCursor() {
|
|
|
807
756
|
const hasGitmem = existing ? JSON.stringify(existing).includes("gitmem") : false;
|
|
808
757
|
|
|
809
758
|
if (hasGitmem) {
|
|
810
|
-
|
|
811
|
-
return;
|
|
759
|
+
log(CHECK, `Automatic memory hooks already configured ${C.dim}(${hooksName})${C.reset}`);
|
|
760
|
+
return { done: false };
|
|
812
761
|
}
|
|
813
762
|
|
|
814
|
-
// Count existing non-gitmem hooks
|
|
815
763
|
let existingHookCount = 0;
|
|
816
764
|
if (existing?.hooks) {
|
|
817
765
|
for (const entries of Object.values(existing.hooks)) {
|
|
@@ -821,19 +769,19 @@ async function stepHooksCursor() {
|
|
|
821
769
|
}
|
|
822
770
|
}
|
|
823
771
|
|
|
824
|
-
|
|
825
|
-
existingHookCount > 0
|
|
826
|
-
? `
|
|
827
|
-
: `Add
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
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
|
+
}
|
|
832
780
|
}
|
|
833
781
|
|
|
834
782
|
if (dryRun) {
|
|
835
|
-
|
|
836
|
-
return;
|
|
783
|
+
log(CHECK, "Would add automatic memory hooks", "[dry-run]");
|
|
784
|
+
return { done: true };
|
|
837
785
|
}
|
|
838
786
|
|
|
839
787
|
copyHookScripts();
|
|
@@ -855,11 +803,15 @@ async function stepHooksCursor() {
|
|
|
855
803
|
config.hooks = merged;
|
|
856
804
|
writeJson(hooksPath, config);
|
|
857
805
|
|
|
858
|
-
const
|
|
859
|
-
existingHookCount
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
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 };
|
|
863
815
|
}
|
|
864
816
|
|
|
865
817
|
async function stepGitignore() {
|
|
@@ -867,18 +819,20 @@ async function stepGitignore() {
|
|
|
867
819
|
let content = exists ? readFileSync(gitignorePath, "utf-8") : "";
|
|
868
820
|
|
|
869
821
|
if (content.includes(".gitmem/")) {
|
|
870
|
-
|
|
871
|
-
return;
|
|
822
|
+
log(CHECK, `.gitignore already configured`);
|
|
823
|
+
return { done: false };
|
|
872
824
|
}
|
|
873
825
|
|
|
874
|
-
if (
|
|
875
|
-
|
|
876
|
-
|
|
826
|
+
if (interactive) {
|
|
827
|
+
if (!(await confirm("Add .gitmem/ to .gitignore?"))) {
|
|
828
|
+
log(SKIP, "Gitignore skipped");
|
|
829
|
+
return { done: false };
|
|
830
|
+
}
|
|
877
831
|
}
|
|
878
832
|
|
|
879
833
|
if (dryRun) {
|
|
880
|
-
|
|
881
|
-
return;
|
|
834
|
+
log(CHECK, "Would update .gitignore", "[dry-run]");
|
|
835
|
+
return { done: true };
|
|
882
836
|
}
|
|
883
837
|
|
|
884
838
|
if (exists) {
|
|
@@ -888,7 +842,11 @@ async function stepGitignore() {
|
|
|
888
842
|
}
|
|
889
843
|
writeFileSync(gitignorePath, content);
|
|
890
844
|
|
|
891
|
-
|
|
845
|
+
log(CHECK,
|
|
846
|
+
`${exists ? "Updated" : "Created"} .gitignore`,
|
|
847
|
+
"Memory stays local \u2014 not committed to your repo"
|
|
848
|
+
);
|
|
849
|
+
return { done: true };
|
|
892
850
|
}
|
|
893
851
|
|
|
894
852
|
// ── Main ──
|
|
@@ -897,126 +855,71 @@ async function main() {
|
|
|
897
855
|
const pkg = readJson(join(__dirname, "..", "package.json"));
|
|
898
856
|
const version = pkg?.version || "1.0.0";
|
|
899
857
|
|
|
900
|
-
// Detect client before anything else
|
|
901
858
|
client = detectClient();
|
|
902
859
|
cc = CLIENT_CONFIGS[client];
|
|
903
860
|
|
|
861
|
+
// ── Header — matches gitmem MCP product line format ──
|
|
904
862
|
console.log("");
|
|
905
|
-
console.log(
|
|
906
|
-
|
|
907
|
-
console.log(" (dry-run mode \u2014 no files will be written)");
|
|
908
|
-
}
|
|
909
|
-
if (clientFlag) {
|
|
910
|
-
console.log(` (client: ${client} \u2014 via --client flag)`);
|
|
911
|
-
} else {
|
|
912
|
-
console.log(` (client: ${client} \u2014 auto-detected)`);
|
|
913
|
-
}
|
|
914
|
-
console.log("");
|
|
915
|
-
|
|
916
|
-
// Detect environment
|
|
917
|
-
console.log(" Detecting environment...");
|
|
918
|
-
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}`);
|
|
919
865
|
|
|
920
|
-
if (
|
|
921
|
-
|
|
922
|
-
const count = mcp?.mcpServers ? Object.keys(mcp.mcpServers).length : 0;
|
|
923
|
-
detections.push(
|
|
924
|
-
` ${cc.mcpConfigName} found (${count} server${count !== 1 ? "s" : ""})`
|
|
925
|
-
);
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
if (existsSync(cc.instructionsFile)) {
|
|
929
|
-
const content = readFileSync(cc.instructionsFile, "utf-8");
|
|
930
|
-
const hasGitmem = content.includes(cc.startMarker);
|
|
931
|
-
detections.push(
|
|
932
|
-
` ${cc.instructionsName} found (${hasGitmem ? "has gitmem section" : "no gitmem section"})`
|
|
933
|
-
);
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
if (cc.settingsFile && existsSync(cc.settingsFile)) {
|
|
937
|
-
const settings = readJson(cc.settingsFile);
|
|
938
|
-
const hookCount = settings?.hooks
|
|
939
|
-
? Object.values(settings.hooks).flat().length
|
|
940
|
-
: 0;
|
|
941
|
-
detections.push(
|
|
942
|
-
` .claude/settings.json found (${hookCount} hook${hookCount !== 1 ? "s" : ""})`
|
|
943
|
-
);
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
if (!cc.hooksInSettings && cc.hasHooks && cc.hooksFile && existsSync(cc.hooksFile)) {
|
|
947
|
-
const hooks = readJson(cc.hooksFile);
|
|
948
|
-
const hookCount = hooks?.hooks
|
|
949
|
-
? Object.values(hooks.hooks).flat().length
|
|
950
|
-
: 0;
|
|
951
|
-
detections.push(
|
|
952
|
-
` ${cc.hooksFileName} found (${hookCount} hook${hookCount !== 1 ? "s" : ""})`
|
|
953
|
-
);
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
if (existsSync(gitignorePath)) {
|
|
957
|
-
detections.push(" .gitignore found");
|
|
866
|
+
if (dryRun) {
|
|
867
|
+
console.log(`${C.dim}dry-run mode \u2014 no files will be written${C.reset}`);
|
|
958
868
|
}
|
|
959
869
|
|
|
960
|
-
|
|
961
|
-
detections.push(" .gitmem/ found");
|
|
962
|
-
}
|
|
870
|
+
console.log("");
|
|
963
871
|
|
|
964
|
-
|
|
965
|
-
console.log(d);
|
|
966
|
-
}
|
|
872
|
+
// ── Run steps ──
|
|
967
873
|
|
|
968
|
-
|
|
969
|
-
console.log(
|
|
970
|
-
` Tier: ${tier}` +
|
|
971
|
-
(tier === "free" ? " (no SUPABASE_URL detected)" : " (SUPABASE_URL detected)")
|
|
972
|
-
);
|
|
973
|
-
console.log("");
|
|
874
|
+
let configured = 0;
|
|
974
875
|
|
|
975
|
-
|
|
976
|
-
let stepCount = 4; // memory store + mcp server + instructions + gitignore
|
|
977
|
-
if (cc.hasPermissions) stepCount++;
|
|
978
|
-
if (cc.hasHooks) stepCount++;
|
|
979
|
-
let step = 1;
|
|
876
|
+
const d = _color && !dryRun ? 500 : 0;
|
|
980
877
|
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
step++;
|
|
878
|
+
const r1 = await stepMemoryStore();
|
|
879
|
+
if (r1.done) configured++;
|
|
880
|
+
if (d) await sleep(d);
|
|
985
881
|
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
step++;
|
|
882
|
+
const r2 = await stepMcpServer();
|
|
883
|
+
if (r2.done) configured++;
|
|
884
|
+
if (d) await sleep(d);
|
|
990
885
|
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
step++;
|
|
886
|
+
const r3 = await stepInstructions();
|
|
887
|
+
if (r3.done) configured++;
|
|
888
|
+
if (d) await sleep(d);
|
|
995
889
|
|
|
996
890
|
if (cc.hasPermissions) {
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
step++;
|
|
891
|
+
const r4 = await stepPermissions();
|
|
892
|
+
if (r4.done) configured++;
|
|
893
|
+
if (d) await sleep(d);
|
|
1001
894
|
}
|
|
1002
895
|
|
|
1003
896
|
if (cc.hasHooks) {
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
step++;
|
|
897
|
+
const r5 = await stepHooks();
|
|
898
|
+
if (r5.done) configured++;
|
|
899
|
+
if (d) await sleep(d);
|
|
1008
900
|
}
|
|
1009
901
|
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
console.log("");
|
|
902
|
+
const r6 = await stepGitignore();
|
|
903
|
+
if (r6.done) configured++;
|
|
1013
904
|
|
|
905
|
+
// ── Footer ──
|
|
1014
906
|
if (dryRun) {
|
|
1015
|
-
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}`);
|
|
1016
911
|
} else {
|
|
1017
|
-
console.log(
|
|
1018
|
-
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(`${C.dim}Remove:${C.reset} npx gitmem-mcp uninstall`);
|
|
918
|
+
console.log("");
|
|
919
|
+
console.log(`${C.dim}Try asking your agent:${C.reset}`);
|
|
920
|
+
console.log(` ${C.italic}"Review the gitmem tools, test them, convince yourself"${C.reset}`);
|
|
1019
921
|
}
|
|
922
|
+
|
|
1020
923
|
console.log("");
|
|
1021
924
|
|
|
1022
925
|
if (rl) rl.close();
|