@contextmirror/claude-memory 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +10 -0
- package/README.md +108 -0
- package/dist/cli.d.ts +11 -0
- package/dist/cli.js +139 -0
- package/dist/generator/claudeMdGenerator.d.ts +20 -0
- package/dist/generator/claudeMdGenerator.js +202 -0
- package/dist/mcp/server.d.ts +10 -0
- package/dist/mcp/server.js +391 -0
- package/dist/scanner/contextGenerator.d.ts +16 -0
- package/dist/scanner/contextGenerator.js +98 -0
- package/dist/scanner/projectScanner.d.ts +8 -0
- package/dist/scanner/projectScanner.js +319 -0
- package/dist/types/index.d.ts +87 -0
- package/dist/types/index.js +10 -0
- package/package.json +37 -0
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Memory MCP Server
|
|
3
|
+
*
|
|
4
|
+
* Exposes tools for Claude Code to query cross-project context:
|
|
5
|
+
* - get_global_context: Overview of all projects
|
|
6
|
+
* - get_project_summary: Details about a specific project
|
|
7
|
+
* - search_projects: Search across all projects
|
|
8
|
+
* - record_insight: Save a cross-project insight
|
|
9
|
+
*/
|
|
10
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
11
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
12
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
13
|
+
import { readFileSync, existsSync, readdirSync, statSync, writeFileSync } from 'fs';
|
|
14
|
+
import { join } from 'path';
|
|
15
|
+
import { homedir } from 'os';
|
|
16
|
+
const MEMORY_DIR = join(homedir(), '.claude-memory');
|
|
17
|
+
// Tool definitions
|
|
18
|
+
const tools = [
|
|
19
|
+
{
|
|
20
|
+
name: 'get_global_context',
|
|
21
|
+
description: 'Get an overview of all projects the user is working on. Returns project names, descriptions, tech stacks, and recent activity. Use this at the start of conversations to understand the user\'s development environment.',
|
|
22
|
+
inputSchema: {
|
|
23
|
+
type: 'object',
|
|
24
|
+
properties: {},
|
|
25
|
+
required: [],
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: 'get_project_summary',
|
|
30
|
+
description: 'Get detailed information about a specific project. Includes description, tech stack, current branch, and any recorded insights.',
|
|
31
|
+
inputSchema: {
|
|
32
|
+
type: 'object',
|
|
33
|
+
properties: {
|
|
34
|
+
project: {
|
|
35
|
+
type: 'string',
|
|
36
|
+
description: 'Project name or partial path',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
required: ['project'],
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'search_projects',
|
|
44
|
+
description: 'Search across all projects for mentions of a term. Useful for finding where patterns are implemented or features exist.',
|
|
45
|
+
inputSchema: {
|
|
46
|
+
type: 'object',
|
|
47
|
+
properties: {
|
|
48
|
+
query: {
|
|
49
|
+
type: 'string',
|
|
50
|
+
description: 'Search term (searches names, descriptions, tech stacks)',
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
required: ['query'],
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: 'record_insight',
|
|
58
|
+
description: 'Record a cross-project insight or pattern for future reference. Use this when you discover something that could be useful in other projects.',
|
|
59
|
+
inputSchema: {
|
|
60
|
+
type: 'object',
|
|
61
|
+
properties: {
|
|
62
|
+
content: {
|
|
63
|
+
type: 'string',
|
|
64
|
+
description: 'The insight to record',
|
|
65
|
+
},
|
|
66
|
+
relatedProjects: {
|
|
67
|
+
type: 'array',
|
|
68
|
+
items: { type: 'string' },
|
|
69
|
+
description: 'Names of related projects',
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
required: ['content'],
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: 'get_project_analysis',
|
|
77
|
+
description: 'Get deep analysis of a project for CLAUDE.md generation. Returns directory structure, README content, package.json details, and key file locations. Use this when the user asks you to generate or write a CLAUDE.md for a project.',
|
|
78
|
+
inputSchema: {
|
|
79
|
+
type: 'object',
|
|
80
|
+
properties: {
|
|
81
|
+
project: {
|
|
82
|
+
type: 'string',
|
|
83
|
+
description: 'Project name or path',
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
required: ['project'],
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
];
|
|
90
|
+
function loadContext() {
|
|
91
|
+
const contextPath = join(MEMORY_DIR, 'context.json');
|
|
92
|
+
if (!existsSync(contextPath)) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
return JSON.parse(readFileSync(contextPath, 'utf-8'));
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function handleGetGlobalContext() {
|
|
103
|
+
const context = loadContext();
|
|
104
|
+
if (!context) {
|
|
105
|
+
return 'No projects scanned yet. Run `claude-memory scan` first.';
|
|
106
|
+
}
|
|
107
|
+
const lines = [
|
|
108
|
+
`# Your Development Environment`,
|
|
109
|
+
'',
|
|
110
|
+
`Last scanned: ${new Date(context.lastUpdated).toLocaleString()}`,
|
|
111
|
+
'',
|
|
112
|
+
'## Active Projects',
|
|
113
|
+
'',
|
|
114
|
+
];
|
|
115
|
+
// Sort by activity
|
|
116
|
+
const sorted = [...context.projects].sort((a, b) => new Date(b.lastActivity).getTime() - new Date(a.lastActivity).getTime());
|
|
117
|
+
for (const p of sorted) {
|
|
118
|
+
const dirty = p.isDirty ? ' ⚠️' : '';
|
|
119
|
+
lines.push(`### ${p.name}${dirty}`);
|
|
120
|
+
lines.push(`- **Path:** ${p.path}`);
|
|
121
|
+
lines.push(`- **Stack:** ${p.techStack.join(', ')}`);
|
|
122
|
+
lines.push(`- **Branch:** ${p.currentBranch}`);
|
|
123
|
+
lines.push(`- **Description:** ${p.description}`);
|
|
124
|
+
lines.push('');
|
|
125
|
+
}
|
|
126
|
+
return lines.join('\n');
|
|
127
|
+
}
|
|
128
|
+
function handleGetProjectSummary(projectQuery) {
|
|
129
|
+
const context = loadContext();
|
|
130
|
+
if (!context) {
|
|
131
|
+
return 'No projects scanned yet. Run `claude-memory scan` first.';
|
|
132
|
+
}
|
|
133
|
+
const project = context.projects.find((p) => p.name.toLowerCase() === projectQuery.toLowerCase() ||
|
|
134
|
+
p.path.toLowerCase().includes(projectQuery.toLowerCase()));
|
|
135
|
+
if (!project) {
|
|
136
|
+
const names = context.projects.map((p) => p.name).join(', ');
|
|
137
|
+
return `Project "${projectQuery}" not found. Known projects: ${names}`;
|
|
138
|
+
}
|
|
139
|
+
return [
|
|
140
|
+
`# ${project.name}`,
|
|
141
|
+
'',
|
|
142
|
+
`**Path:** ${project.path}`,
|
|
143
|
+
`**Language:** ${project.language}`,
|
|
144
|
+
`**Tech Stack:** ${project.techStack.join(', ')}`,
|
|
145
|
+
`**Branch:** ${project.currentBranch}${project.isDirty ? ' (uncommitted changes)' : ''}`,
|
|
146
|
+
`**Last Activity:** ${new Date(project.lastActivity).toLocaleString()}`,
|
|
147
|
+
'',
|
|
148
|
+
'## Description',
|
|
149
|
+
project.description,
|
|
150
|
+
'',
|
|
151
|
+
'## Key Files',
|
|
152
|
+
`- CLAUDE.md: ${project.hasFiles.claudeMd ? '✓ exists' : '✗ missing'}`,
|
|
153
|
+
`- README.md: ${project.hasFiles.readme ? '✓ exists' : '✗ missing'}`,
|
|
154
|
+
'',
|
|
155
|
+
project.insights.length > 0
|
|
156
|
+
? ['## Insights', ...project.insights.map((i) => `- **${i.title}:** ${i.description}`)].join('\n')
|
|
157
|
+
: '',
|
|
158
|
+
].join('\n');
|
|
159
|
+
}
|
|
160
|
+
function handleSearchProjects(query) {
|
|
161
|
+
const context = loadContext();
|
|
162
|
+
if (!context) {
|
|
163
|
+
return 'No projects scanned yet. Run `claude-memory scan` first.';
|
|
164
|
+
}
|
|
165
|
+
const q = query.toLowerCase();
|
|
166
|
+
const matches = context.projects.filter((p) => p.name.toLowerCase().includes(q) ||
|
|
167
|
+
p.description.toLowerCase().includes(q) ||
|
|
168
|
+
p.techStack.some((t) => t.toLowerCase().includes(q)));
|
|
169
|
+
if (matches.length === 0) {
|
|
170
|
+
return `No projects match "${query}".`;
|
|
171
|
+
}
|
|
172
|
+
const lines = [`# Search Results for "${query}"`, ''];
|
|
173
|
+
for (const p of matches) {
|
|
174
|
+
lines.push(`- **${p.name}** - ${p.techStack.join(', ')}`);
|
|
175
|
+
lines.push(` ${p.description.slice(0, 100)}...`);
|
|
176
|
+
}
|
|
177
|
+
return lines.join('\n');
|
|
178
|
+
}
|
|
179
|
+
function handleRecordInsight(content, relatedProjects) {
|
|
180
|
+
const contextPath = join(MEMORY_DIR, 'context.json');
|
|
181
|
+
if (!existsSync(contextPath)) {
|
|
182
|
+
return 'No context file found. Run `claude-memory scan` first.';
|
|
183
|
+
}
|
|
184
|
+
try {
|
|
185
|
+
const context = JSON.parse(readFileSync(contextPath, 'utf-8'));
|
|
186
|
+
// Add the new insight
|
|
187
|
+
context.insights.push({
|
|
188
|
+
content,
|
|
189
|
+
relatedProjects: relatedProjects || [],
|
|
190
|
+
discoveredAt: new Date().toISOString(),
|
|
191
|
+
});
|
|
192
|
+
// Write back
|
|
193
|
+
writeFileSync(contextPath, JSON.stringify(context, null, 2), 'utf-8');
|
|
194
|
+
return `✅ Insight recorded: "${content.slice(0, 100)}..."${relatedProjects?.length ? ` (related to: ${relatedProjects.join(', ')})` : ''}`;
|
|
195
|
+
}
|
|
196
|
+
catch (err) {
|
|
197
|
+
return `Failed to record insight: ${err instanceof Error ? err.message : 'Unknown error'}`;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
function handleGetProjectAnalysis(projectQuery) {
|
|
201
|
+
const context = loadContext();
|
|
202
|
+
if (!context) {
|
|
203
|
+
return 'No projects scanned yet. Run `claude-memory scan` first.';
|
|
204
|
+
}
|
|
205
|
+
const project = context.projects.find((p) => p.name.toLowerCase() === projectQuery.toLowerCase() ||
|
|
206
|
+
p.path.toLowerCase().includes(projectQuery.toLowerCase()));
|
|
207
|
+
if (!project) {
|
|
208
|
+
const names = context.projects.map((p) => p.name).join(', ');
|
|
209
|
+
return `Project "${projectQuery}" not found. Known projects: ${names}`;
|
|
210
|
+
}
|
|
211
|
+
const lines = [
|
|
212
|
+
'# Project Analysis for CLAUDE.md Generation',
|
|
213
|
+
'',
|
|
214
|
+
`## Basic Info`,
|
|
215
|
+
`- **Name:** ${project.name}`,
|
|
216
|
+
`- **Path:** ${project.path}`,
|
|
217
|
+
`- **Language:** ${project.language}`,
|
|
218
|
+
`- **Tech Stack:** ${project.techStack.join(', ')}`,
|
|
219
|
+
`- **Branch:** ${project.currentBranch}${project.isDirty ? ' (uncommitted changes)' : ''}`,
|
|
220
|
+
`- **Last Activity:** ${new Date(project.lastActivity).toLocaleString()}`,
|
|
221
|
+
'',
|
|
222
|
+
];
|
|
223
|
+
// Directory structure
|
|
224
|
+
lines.push('## Directory Structure');
|
|
225
|
+
lines.push('```');
|
|
226
|
+
lines.push(getDirectoryStructure(project.path, 3));
|
|
227
|
+
lines.push('```');
|
|
228
|
+
lines.push('');
|
|
229
|
+
// README content
|
|
230
|
+
const readmePath = join(project.path, 'README.md');
|
|
231
|
+
if (existsSync(readmePath)) {
|
|
232
|
+
try {
|
|
233
|
+
const readme = readFileSync(readmePath, 'utf-8').slice(0, 3000);
|
|
234
|
+
lines.push('## README.md Content');
|
|
235
|
+
lines.push('```markdown');
|
|
236
|
+
lines.push(readme);
|
|
237
|
+
lines.push('```');
|
|
238
|
+
lines.push('');
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
// Skip if can't read
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
// Package.json info
|
|
245
|
+
const pkgPath = join(project.path, 'package.json');
|
|
246
|
+
if (existsSync(pkgPath)) {
|
|
247
|
+
try {
|
|
248
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
249
|
+
lines.push('## package.json');
|
|
250
|
+
lines.push(`- **Name:** ${pkg.name || 'unnamed'}`);
|
|
251
|
+
lines.push(`- **Description:** ${pkg.description || 'none'}`);
|
|
252
|
+
if (pkg.scripts) {
|
|
253
|
+
lines.push('- **Scripts:**');
|
|
254
|
+
for (const [name, cmd] of Object.entries(pkg.scripts)) {
|
|
255
|
+
lines.push(` - \`${name}\`: ${cmd}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (pkg.dependencies) {
|
|
259
|
+
lines.push(`- **Dependencies:** ${Object.keys(pkg.dependencies).join(', ')}`);
|
|
260
|
+
}
|
|
261
|
+
lines.push('');
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
// Skip if can't read
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
// Key files
|
|
268
|
+
lines.push('## Key Entry Files');
|
|
269
|
+
const keyPatterns = [
|
|
270
|
+
'src/index.ts', 'src/index.js', 'src/main.ts', 'src/main.js',
|
|
271
|
+
'src/app.ts', 'src/app.js', 'src/cli.ts', 'src/cli.js',
|
|
272
|
+
'index.ts', 'index.js', 'main.ts', 'main.js',
|
|
273
|
+
'main.py', 'app.py', '__main__.py',
|
|
274
|
+
'src/lib.rs', 'src/main.rs', 'main.go',
|
|
275
|
+
];
|
|
276
|
+
for (const pattern of keyPatterns) {
|
|
277
|
+
if (existsSync(join(project.path, pattern))) {
|
|
278
|
+
lines.push(`- ${pattern}`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
lines.push('');
|
|
282
|
+
lines.push('## Instructions');
|
|
283
|
+
lines.push('Based on the above analysis, please generate a CLAUDE.md file that includes:');
|
|
284
|
+
lines.push('1. Project title and one-line description');
|
|
285
|
+
lines.push('2. What the project does (2-3 sentences)');
|
|
286
|
+
lines.push('3. Tech stack overview');
|
|
287
|
+
lines.push('4. Project structure explanation');
|
|
288
|
+
lines.push('5. How to run/build');
|
|
289
|
+
lines.push('6. Any important context for future Claude sessions');
|
|
290
|
+
return lines.join('\n');
|
|
291
|
+
}
|
|
292
|
+
function getDirectoryStructure(dir, maxDepth, prefix = '', depth = 0) {
|
|
293
|
+
if (depth >= maxDepth)
|
|
294
|
+
return '';
|
|
295
|
+
const ignore = ['node_modules', '.git', 'dist', 'build', '__pycache__', 'target', '.next', '.nuxt', 'coverage', '.cache', '.venv', 'venv'];
|
|
296
|
+
let result = '';
|
|
297
|
+
try {
|
|
298
|
+
const entries = readdirSync(dir).filter(e => !ignore.includes(e) && !e.startsWith('.'));
|
|
299
|
+
entries.sort((a, b) => {
|
|
300
|
+
const aIsDir = statSync(join(dir, a)).isDirectory();
|
|
301
|
+
const bIsDir = statSync(join(dir, b)).isDirectory();
|
|
302
|
+
if (aIsDir && !bIsDir)
|
|
303
|
+
return -1;
|
|
304
|
+
if (!aIsDir && bIsDir)
|
|
305
|
+
return 1;
|
|
306
|
+
return a.localeCompare(b);
|
|
307
|
+
});
|
|
308
|
+
for (let i = 0; i < entries.length; i++) {
|
|
309
|
+
const entry = entries[i];
|
|
310
|
+
const fullPath = join(dir, entry);
|
|
311
|
+
const isLast = i === entries.length - 1;
|
|
312
|
+
const connector = isLast ? '└── ' : '├── ';
|
|
313
|
+
const newPrefix = prefix + (isLast ? ' ' : '│ ');
|
|
314
|
+
try {
|
|
315
|
+
const stat = statSync(fullPath);
|
|
316
|
+
if (stat.isDirectory()) {
|
|
317
|
+
result += `${prefix}${connector}${entry}/\n`;
|
|
318
|
+
result += getDirectoryStructure(fullPath, maxDepth, newPrefix, depth + 1);
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
result += `${prefix}${connector}${entry}\n`;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
catch {
|
|
325
|
+
// Skip inaccessible
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
catch {
|
|
330
|
+
// Skip inaccessible
|
|
331
|
+
}
|
|
332
|
+
return result;
|
|
333
|
+
}
|
|
334
|
+
async function main() {
|
|
335
|
+
const server = new Server({
|
|
336
|
+
name: 'claude-memory',
|
|
337
|
+
version: '0.1.0',
|
|
338
|
+
}, {
|
|
339
|
+
capabilities: {
|
|
340
|
+
tools: {},
|
|
341
|
+
},
|
|
342
|
+
});
|
|
343
|
+
// List tools
|
|
344
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
345
|
+
tools,
|
|
346
|
+
}));
|
|
347
|
+
// Handle tool calls
|
|
348
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
349
|
+
const { name, arguments: args } = request.params;
|
|
350
|
+
try {
|
|
351
|
+
let result;
|
|
352
|
+
switch (name) {
|
|
353
|
+
case 'get_global_context':
|
|
354
|
+
result = handleGetGlobalContext();
|
|
355
|
+
break;
|
|
356
|
+
case 'get_project_summary':
|
|
357
|
+
result = handleGetProjectSummary(args.project);
|
|
358
|
+
break;
|
|
359
|
+
case 'search_projects':
|
|
360
|
+
result = handleSearchProjects(args.query);
|
|
361
|
+
break;
|
|
362
|
+
case 'record_insight':
|
|
363
|
+
result = handleRecordInsight(args.content, args.relatedProjects);
|
|
364
|
+
break;
|
|
365
|
+
case 'get_project_analysis':
|
|
366
|
+
result = handleGetProjectAnalysis(args.project);
|
|
367
|
+
break;
|
|
368
|
+
default:
|
|
369
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
370
|
+
}
|
|
371
|
+
return {
|
|
372
|
+
content: [{ type: 'text', text: result }],
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
catch (error) {
|
|
376
|
+
return {
|
|
377
|
+
content: [
|
|
378
|
+
{
|
|
379
|
+
type: 'text',
|
|
380
|
+
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
381
|
+
},
|
|
382
|
+
],
|
|
383
|
+
isError: true,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
const transport = new StdioServerTransport();
|
|
388
|
+
await server.connect(transport);
|
|
389
|
+
console.error('Claude Memory MCP server running');
|
|
390
|
+
}
|
|
391
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Generator - Produces markdown context files
|
|
3
|
+
*/
|
|
4
|
+
import { ProjectInfo, GlobalContext } from '../types/index.js';
|
|
5
|
+
/**
|
|
6
|
+
* Generate global context from scanned projects
|
|
7
|
+
*/
|
|
8
|
+
export declare function generateGlobalContext(projects: ProjectInfo[]): GlobalContext;
|
|
9
|
+
/**
|
|
10
|
+
* Write global context to ~/.claude-memory/
|
|
11
|
+
*/
|
|
12
|
+
export declare function writeGlobalContext(context: GlobalContext): void;
|
|
13
|
+
/**
|
|
14
|
+
* Get the memory directory path
|
|
15
|
+
*/
|
|
16
|
+
export declare function getMemoryDir(): string;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Generator - Produces markdown context files
|
|
3
|
+
*/
|
|
4
|
+
import { writeFileSync, mkdirSync, existsSync } from 'fs';
|
|
5
|
+
import { join } from 'path';
|
|
6
|
+
import { homedir } from 'os';
|
|
7
|
+
const MEMORY_DIR = join(homedir(), '.claude-memory');
|
|
8
|
+
/**
|
|
9
|
+
* Generate global context from scanned projects
|
|
10
|
+
*/
|
|
11
|
+
export function generateGlobalContext(projects) {
|
|
12
|
+
return {
|
|
13
|
+
lastUpdated: new Date().toISOString(),
|
|
14
|
+
projects,
|
|
15
|
+
insights: [], // TODO: Cross-project analysis
|
|
16
|
+
userPatterns: [], // TODO: Pattern detection
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Write global context to ~/.claude-memory/
|
|
21
|
+
*/
|
|
22
|
+
export function writeGlobalContext(context) {
|
|
23
|
+
// Ensure directory exists
|
|
24
|
+
if (!existsSync(MEMORY_DIR)) {
|
|
25
|
+
mkdirSync(MEMORY_DIR, { recursive: true });
|
|
26
|
+
}
|
|
27
|
+
// Write JSON for MCP server
|
|
28
|
+
const jsonPath = join(MEMORY_DIR, 'context.json');
|
|
29
|
+
writeFileSync(jsonPath, JSON.stringify(context, null, 2));
|
|
30
|
+
// Write markdown for human readability
|
|
31
|
+
const mdPath = join(MEMORY_DIR, 'global-context.md');
|
|
32
|
+
writeFileSync(mdPath, formatGlobalContextMarkdown(context));
|
|
33
|
+
console.log(`\n📝 Written to:`);
|
|
34
|
+
console.log(` ${jsonPath}`);
|
|
35
|
+
console.log(` ${mdPath}`);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Format global context as markdown
|
|
39
|
+
*/
|
|
40
|
+
function formatGlobalContextMarkdown(context) {
|
|
41
|
+
const lines = [
|
|
42
|
+
'# Claude Global Memory',
|
|
43
|
+
'',
|
|
44
|
+
`> Last updated: ${new Date(context.lastUpdated).toLocaleString()}`,
|
|
45
|
+
'',
|
|
46
|
+
'## Your Projects',
|
|
47
|
+
'',
|
|
48
|
+
];
|
|
49
|
+
// Sort by last activity (most recent first)
|
|
50
|
+
const sorted = [...context.projects].sort((a, b) => new Date(b.lastActivity).getTime() - new Date(a.lastActivity).getTime());
|
|
51
|
+
for (const project of sorted) {
|
|
52
|
+
const activityDate = new Date(project.lastActivity).toLocaleDateString();
|
|
53
|
+
const dirty = project.isDirty ? ' ⚠️ uncommitted changes' : '';
|
|
54
|
+
lines.push(`### ${project.name}`);
|
|
55
|
+
lines.push('');
|
|
56
|
+
lines.push(`**Path:** \`${project.path}\``);
|
|
57
|
+
lines.push(`**Stack:** ${project.techStack.join(', ') || 'Unknown'}`);
|
|
58
|
+
lines.push(`**Branch:** ${project.currentBranch}${dirty}`);
|
|
59
|
+
lines.push(`**Last Activity:** ${activityDate}`);
|
|
60
|
+
lines.push('');
|
|
61
|
+
lines.push(`> ${project.description}`);
|
|
62
|
+
lines.push('');
|
|
63
|
+
// Key files
|
|
64
|
+
const keyFiles = [];
|
|
65
|
+
if (project.hasFiles.claudeMd)
|
|
66
|
+
keyFiles.push('CLAUDE.md');
|
|
67
|
+
if (project.hasFiles.readme)
|
|
68
|
+
keyFiles.push('README.md');
|
|
69
|
+
if (keyFiles.length > 0) {
|
|
70
|
+
lines.push(`📄 Has: ${keyFiles.join(', ')}`);
|
|
71
|
+
lines.push('');
|
|
72
|
+
}
|
|
73
|
+
lines.push('---');
|
|
74
|
+
lines.push('');
|
|
75
|
+
}
|
|
76
|
+
// Summary stats
|
|
77
|
+
lines.push('## Summary');
|
|
78
|
+
lines.push('');
|
|
79
|
+
lines.push(`- **Total Projects:** ${context.projects.length}`);
|
|
80
|
+
const byLanguage = context.projects.reduce((acc, p) => {
|
|
81
|
+
acc[p.language] = (acc[p.language] || 0) + 1;
|
|
82
|
+
return acc;
|
|
83
|
+
}, {});
|
|
84
|
+
lines.push(`- **Languages:** ${Object.entries(byLanguage).map(([l, c]) => `${l} (${c})`).join(', ')}`);
|
|
85
|
+
const withClaudeMd = context.projects.filter((p) => p.hasFiles.claudeMd).length;
|
|
86
|
+
lines.push(`- **With CLAUDE.md:** ${withClaudeMd}/${context.projects.length}`);
|
|
87
|
+
const dirty = context.projects.filter((p) => p.isDirty).length;
|
|
88
|
+
if (dirty > 0) {
|
|
89
|
+
lines.push(`- **⚠️ Uncommitted Changes:** ${dirty} projects`);
|
|
90
|
+
}
|
|
91
|
+
return lines.join('\n');
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Get the memory directory path
|
|
95
|
+
*/
|
|
96
|
+
export function getMemoryDir() {
|
|
97
|
+
return MEMORY_DIR;
|
|
98
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Scanner - Discovers and analyzes projects
|
|
3
|
+
*/
|
|
4
|
+
import { ProjectInfo, ScanOptions } from '../types/index.js';
|
|
5
|
+
/**
|
|
6
|
+
* Scan a directory for projects and extract information
|
|
7
|
+
*/
|
|
8
|
+
export declare function scanProjects(options?: Partial<ScanOptions>): Promise<ProjectInfo[]>;
|