@contextmirror/claude-memory 0.2.1 → 0.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/dist/cli.js CHANGED
@@ -16,6 +16,8 @@ import { scanProjects } from './scanner/projectScanner.js';
16
16
  import { generateGlobalContext, writeGlobalContext, getMemoryDir } from './scanner/contextGenerator.js';
17
17
  import { generateBriefing, briefingToClaudeMd } from './briefing/briefingGenerator.js';
18
18
  import { runSetupWizard } from './setup/setupWizard.js';
19
+ import { activateLicense, deactivateLicense, getLicenseStatus } from './license/index.js';
20
+ import { detectStaleProjects } from './scanner/stalenessDetector.js';
19
21
  const program = new Command();
20
22
  program
21
23
  .name('claude-memory')
@@ -28,14 +30,83 @@ program
28
30
  .argument('[directory]', 'Root directory to scan', `${homedir()}/Project`)
29
31
  .option('-d, --depth <number>', 'Max depth to search', '2')
30
32
  .option('--generate-claude-md', 'Generate CLAUDE.md for projects without one')
33
+ .option('-q, --quick', 'Only rescan stale projects (faster)')
34
+ .option('--check', 'Only check for stale projects, don\'t scan')
31
35
  .action(async (directory, options) => {
36
+ // Check-only mode: just report staleness
37
+ if (options.check) {
38
+ const context = loadContext();
39
+ if (!context) {
40
+ console.log('No projects scanned yet. Run: claude-memory scan');
41
+ return;
42
+ }
43
+ const report = detectStaleProjects(context);
44
+ console.log('🧠 Claude Memory - Staleness Check\n');
45
+ console.log(`Last full scan: ${new Date(context.lastUpdated).toLocaleString()}`);
46
+ console.log(`Data age: ${report.dataAgeHours} hours\n`);
47
+ if (report.staleProjects.length === 0) {
48
+ console.log('āœ… All projects are up to date!');
49
+ }
50
+ else {
51
+ console.log(`āš ļø ${report.staleProjects.length} project(s) need updating:\n`);
52
+ for (const p of report.staleProjects) {
53
+ const icon = p.reason === 'git_activity' ? 'šŸ“' :
54
+ p.reason === 'file_changed' ? 'šŸ“„' : 'ā°';
55
+ console.log(` ${icon} ${p.name}`);
56
+ console.log(` ${p.details}`);
57
+ }
58
+ console.log(`\n${report.freshCount} project(s) are fresh.`);
59
+ console.log('\nRun `claude-memory scan --quick` to refresh only stale projects.');
60
+ }
61
+ return;
62
+ }
32
63
  console.log('🧠 Claude Memory Scanner\n');
64
+ // Quick mode: only scan stale projects
65
+ let projectsToScan;
66
+ if (options.quick) {
67
+ const existingContext = loadContext();
68
+ if (existingContext) {
69
+ const report = detectStaleProjects(existingContext);
70
+ if (report.staleProjects.length === 0) {
71
+ console.log('āœ… All projects are up to date! Nothing to scan.');
72
+ return;
73
+ }
74
+ projectsToScan = report.staleProjects.map(p => p.path);
75
+ console.log(`šŸ”„ Quick mode: Rescanning ${projectsToScan.length} stale project(s)...\n`);
76
+ }
77
+ }
33
78
  const projects = await scanProjects({
34
79
  rootDir: directory,
35
80
  maxDepth: parseInt(options.depth, 10),
36
81
  generateClaudeMd: options.generateClaudeMd,
82
+ onlyPaths: projectsToScan,
37
83
  });
38
- const context = generateGlobalContext(projects);
84
+ if (projects.length === 0) {
85
+ console.log('\nāš ļø No projects found. Context not updated.');
86
+ console.log(' Check the directory path and try again.');
87
+ return;
88
+ }
89
+ // For quick mode, merge with existing context
90
+ let context;
91
+ if (options.quick && projectsToScan) {
92
+ const existingContext = loadContext();
93
+ if (existingContext) {
94
+ // Replace only the rescanned projects
95
+ const rescannedPaths = new Set(projects.map(p => p.path));
96
+ const unchangedProjects = existingContext.projects.filter(p => !rescannedPaths.has(p.path));
97
+ context = {
98
+ ...existingContext,
99
+ lastUpdated: new Date().toISOString(),
100
+ projects: [...unchangedProjects, ...projects],
101
+ };
102
+ }
103
+ else {
104
+ context = generateGlobalContext(projects);
105
+ }
106
+ }
107
+ else {
108
+ context = generateGlobalContext(projects);
109
+ }
39
110
  writeGlobalContext(context);
40
111
  console.log('\nāœ… Done! Global context updated.');
41
112
  console.log('\nTo use with Claude Code, add the MCP server to your config:');
@@ -89,6 +160,106 @@ program
89
160
  console.log(` CLAUDE.md: ${project.hasFiles.claudeMd ? 'āœ“' : 'āœ—'}`);
90
161
  console.log(` README.md: ${project.hasFiles.readme ? 'āœ“' : 'āœ—'}`);
91
162
  });
163
+ // Exclude command - exclude a project from scanning
164
+ program
165
+ .command('exclude')
166
+ .description('Exclude a project from future scans')
167
+ .argument('<project>', 'Project path or name')
168
+ .action((projectQuery) => {
169
+ const context = loadContext();
170
+ if (!context) {
171
+ console.log('No projects scanned yet. Run: claude-memory scan');
172
+ return;
173
+ }
174
+ // Find the project
175
+ const project = context.projects.find((p) => p.name.toLowerCase() === projectQuery.toLowerCase() || p.path.includes(projectQuery));
176
+ if (!project) {
177
+ // Maybe it's a direct path
178
+ if (existsSync(projectQuery)) {
179
+ addExclusion(projectQuery);
180
+ console.log(`āœ… Excluded: ${projectQuery}`);
181
+ console.log(' This project will be skipped in future scans.');
182
+ return;
183
+ }
184
+ console.log(`Project not found: ${projectQuery}`);
185
+ return;
186
+ }
187
+ addExclusion(project.path);
188
+ console.log(`āœ… Excluded: ${project.name}`);
189
+ console.log(` Path: ${project.path}`);
190
+ console.log(' This project will be skipped in future scans.');
191
+ console.log(' Run `claude-memory include` to re-add it.');
192
+ });
193
+ // Include command - remove a project from exclusion list
194
+ program
195
+ .command('include')
196
+ .description('Remove a project from the exclusion list')
197
+ .argument('<project>', 'Project path or name')
198
+ .action((projectQuery) => {
199
+ const context = loadContext();
200
+ if (!context) {
201
+ console.log('No projects scanned yet. Run: claude-memory scan');
202
+ return;
203
+ }
204
+ const excluded = context.excludedProjects || [];
205
+ const match = excluded.find(p => p.toLowerCase().includes(projectQuery.toLowerCase()));
206
+ if (!match) {
207
+ console.log(`Project not in exclusion list: ${projectQuery}`);
208
+ if (excluded.length > 0) {
209
+ console.log('\nCurrently excluded:');
210
+ excluded.forEach(p => console.log(` - ${p}`));
211
+ }
212
+ return;
213
+ }
214
+ removeExclusion(match);
215
+ console.log(`āœ… Removed from exclusion list: ${match}`);
216
+ console.log(' Run `claude-memory scan` to add it back to your projects.');
217
+ });
218
+ // Excluded command - list excluded projects
219
+ program
220
+ .command('excluded')
221
+ .description('List excluded projects')
222
+ .action(() => {
223
+ const context = loadContext();
224
+ const excluded = context?.excludedProjects || [];
225
+ if (excluded.length === 0) {
226
+ console.log('No excluded projects.');
227
+ console.log('Use `claude-memory exclude <project>` to exclude a project.');
228
+ return;
229
+ }
230
+ console.log('🚫 Excluded Projects\n');
231
+ excluded.forEach(p => console.log(` ${p}`));
232
+ console.log('\nUse `claude-memory include <project>` to re-add.');
233
+ });
234
+ function addExclusion(projectPath) {
235
+ const contextPath = join(getMemoryDir(), 'context.json');
236
+ if (!existsSync(contextPath))
237
+ return;
238
+ try {
239
+ const context = JSON.parse(readFileSync(contextPath, 'utf-8'));
240
+ context.excludedProjects = context.excludedProjects || [];
241
+ if (!context.excludedProjects.includes(projectPath)) {
242
+ context.excludedProjects.push(projectPath);
243
+ writeFileSync(contextPath, JSON.stringify(context, null, 2), 'utf-8');
244
+ }
245
+ }
246
+ catch {
247
+ console.error('Failed to update exclusion list');
248
+ }
249
+ }
250
+ function removeExclusion(projectPath) {
251
+ const contextPath = join(getMemoryDir(), 'context.json');
252
+ if (!existsSync(contextPath))
253
+ return;
254
+ try {
255
+ const context = JSON.parse(readFileSync(contextPath, 'utf-8'));
256
+ context.excludedProjects = (context.excludedProjects || []).filter((p) => p !== projectPath);
257
+ writeFileSync(contextPath, JSON.stringify(context, null, 2), 'utf-8');
258
+ }
259
+ catch {
260
+ console.error('Failed to update exclusion list');
261
+ }
262
+ }
92
263
  // Briefing command - deep project analysis and CLAUDE.md generation
93
264
  program
94
265
  .command('briefing')
@@ -193,6 +364,68 @@ program
193
364
  interactive: !options.nonInteractive,
194
365
  });
195
366
  });
367
+ // Activate command - activate a Pro license
368
+ program
369
+ .command('activate')
370
+ .description('Activate a Pro license key')
371
+ .argument('<key>', 'License key (format: CM-XXXX-XXXX-XXXX)')
372
+ .action(async (key) => {
373
+ console.log('Activating license...\n');
374
+ const result = await activateLicense(key);
375
+ if (!result.valid) {
376
+ console.log(`āŒ ${result.error}`);
377
+ return;
378
+ }
379
+ console.log('āœ… License activated successfully!');
380
+ console.log(` Plan: Pro`);
381
+ if (result.license?.email) {
382
+ console.log(` Email: ${result.license.email}`);
383
+ }
384
+ console.log('\nPro features are now unlocked.');
385
+ });
386
+ // Status command - show license status
387
+ program
388
+ .command('status')
389
+ .description('Show license status')
390
+ .action(() => {
391
+ const status = getLicenseStatus();
392
+ console.log('🧠 Claude Memory License Status\n');
393
+ console.log(` Plan: ${status.plan === 'pro' ? 'Pro' : 'Free'}`);
394
+ if (status.isPro) {
395
+ if (status.email) {
396
+ console.log(` Email: ${status.email}`);
397
+ }
398
+ if (status.activatedAt) {
399
+ console.log(` Activated: ${new Date(status.activatedAt).toLocaleDateString()}`);
400
+ }
401
+ if (status.expiresAt) {
402
+ console.log(` Expires: ${new Date(status.expiresAt).toLocaleDateString()}`);
403
+ if (status.daysRemaining) {
404
+ console.log(` Days remaining: ${status.daysRemaining}`);
405
+ }
406
+ }
407
+ console.log(` Status: Active`);
408
+ }
409
+ else {
410
+ console.log(` Status: ${status.expiresAt ? 'Expired' : 'Not activated'}`);
411
+ console.log('\n Upgrade at https://claude-memory.dev');
412
+ console.log(' Or run: claude-memory activate <your-key>');
413
+ }
414
+ });
415
+ // Deactivate command - remove license
416
+ program
417
+ .command('deactivate')
418
+ .description('Remove Pro license')
419
+ .action(() => {
420
+ const removed = deactivateLicense();
421
+ if (removed) {
422
+ console.log('āœ… License removed.');
423
+ console.log(' You are now on the Free plan.');
424
+ }
425
+ else {
426
+ console.log('No license to remove.');
427
+ }
428
+ });
196
429
  function loadContext() {
197
430
  const contextPath = join(getMemoryDir(), 'context.json');
198
431
  if (!existsSync(contextPath)) {
@@ -0,0 +1,39 @@
1
+ /**
2
+ * License management for Claude Memory Pro
3
+ */
4
+ import { License, LicenseStatus, LicenseValidationResult } from './types.js';
5
+ /**
6
+ * Load license from disk
7
+ */
8
+ export declare function loadLicense(): License | null;
9
+ /**
10
+ * Save license to disk
11
+ */
12
+ export declare function saveLicense(license: License): void;
13
+ /**
14
+ * Check if user has Pro license
15
+ */
16
+ export declare function isPro(): boolean;
17
+ /**
18
+ * Validate a license key format
19
+ */
20
+ export declare function validateKeyFormat(key: string): boolean;
21
+ /**
22
+ * Activate a license key
23
+ *
24
+ * Phase 1: Offline validation (format check only)
25
+ * Phase 2: Will call LemonSqueezy API for server validation
26
+ */
27
+ export declare function activateLicense(key: string): Promise<LicenseValidationResult>;
28
+ /**
29
+ * Deactivate (remove) the current license
30
+ */
31
+ export declare function deactivateLicense(): boolean;
32
+ /**
33
+ * Get current license status
34
+ */
35
+ export declare function getLicenseStatus(): LicenseStatus;
36
+ /**
37
+ * Get a user-friendly error message for Pro features
38
+ */
39
+ export declare function getProFeatureMessage(featureName: string): string;
@@ -0,0 +1,153 @@
1
+ /**
2
+ * License management for Claude Memory Pro
3
+ */
4
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from 'fs';
5
+ import { join } from 'path';
6
+ import { homedir } from 'os';
7
+ const MEMORY_DIR = join(homedir(), '.claude-memory');
8
+ const LICENSE_PATH = join(MEMORY_DIR, 'license.json');
9
+ // License key format: CM-XXXX-XXXX-XXXX (alphanumeric)
10
+ const LICENSE_KEY_PATTERN = /^CM-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$/i;
11
+ /**
12
+ * Ensure the memory directory exists
13
+ */
14
+ function ensureMemoryDir() {
15
+ if (!existsSync(MEMORY_DIR)) {
16
+ mkdirSync(MEMORY_DIR, { recursive: true });
17
+ }
18
+ }
19
+ /**
20
+ * Load license from disk
21
+ */
22
+ export function loadLicense() {
23
+ if (!existsSync(LICENSE_PATH)) {
24
+ return null;
25
+ }
26
+ try {
27
+ const data = readFileSync(LICENSE_PATH, 'utf-8');
28
+ return JSON.parse(data);
29
+ }
30
+ catch {
31
+ return null;
32
+ }
33
+ }
34
+ /**
35
+ * Save license to disk
36
+ */
37
+ export function saveLicense(license) {
38
+ ensureMemoryDir();
39
+ writeFileSync(LICENSE_PATH, JSON.stringify(license, null, 2), 'utf-8');
40
+ }
41
+ /**
42
+ * Check if user has Pro license
43
+ */
44
+ export function isPro() {
45
+ const license = loadLicense();
46
+ if (!license) {
47
+ return false;
48
+ }
49
+ if (license.plan !== 'pro') {
50
+ return false;
51
+ }
52
+ // Check expiration
53
+ if (license.expiresAt) {
54
+ const expiresAt = new Date(license.expiresAt);
55
+ if (expiresAt < new Date()) {
56
+ return false;
57
+ }
58
+ }
59
+ return true;
60
+ }
61
+ /**
62
+ * Validate a license key format
63
+ */
64
+ export function validateKeyFormat(key) {
65
+ return LICENSE_KEY_PATTERN.test(key);
66
+ }
67
+ /**
68
+ * Activate a license key
69
+ *
70
+ * Phase 1: Offline validation (format check only)
71
+ * Phase 2: Will call LemonSqueezy API for server validation
72
+ */
73
+ export async function activateLicense(key) {
74
+ // Normalize key to uppercase
75
+ const normalizedKey = key.toUpperCase();
76
+ // Validate format
77
+ if (!validateKeyFormat(normalizedKey)) {
78
+ return {
79
+ valid: false,
80
+ error: 'Invalid license key format. Expected: CM-XXXX-XXXX-XXXX',
81
+ };
82
+ }
83
+ // TODO: Phase 2 - Call LemonSqueezy API to validate
84
+ // const response = await fetch('https://api.claude-memory.dev/validate', {
85
+ // method: 'POST',
86
+ // body: JSON.stringify({ key: normalizedKey }),
87
+ // });
88
+ // For now, accept any valid format (Phase 1 - offline)
89
+ const license = {
90
+ key: normalizedKey,
91
+ plan: 'pro',
92
+ activatedAt: new Date().toISOString(),
93
+ // No expiration for now
94
+ };
95
+ saveLicense(license);
96
+ return {
97
+ valid: true,
98
+ license,
99
+ };
100
+ }
101
+ /**
102
+ * Deactivate (remove) the current license
103
+ */
104
+ export function deactivateLicense() {
105
+ if (!existsSync(LICENSE_PATH)) {
106
+ return false;
107
+ }
108
+ try {
109
+ unlinkSync(LICENSE_PATH);
110
+ return true;
111
+ }
112
+ catch {
113
+ return false;
114
+ }
115
+ }
116
+ /**
117
+ * Get current license status
118
+ */
119
+ export function getLicenseStatus() {
120
+ const license = loadLicense();
121
+ if (!license) {
122
+ return {
123
+ isPro: false,
124
+ plan: 'free',
125
+ };
126
+ }
127
+ let daysRemaining;
128
+ let isExpired = false;
129
+ if (license.expiresAt) {
130
+ const expiresAt = new Date(license.expiresAt);
131
+ const now = new Date();
132
+ const diffMs = expiresAt.getTime() - now.getTime();
133
+ daysRemaining = Math.ceil(diffMs / (1000 * 60 * 60 * 24));
134
+ isExpired = daysRemaining < 0;
135
+ }
136
+ return {
137
+ isPro: license.plan === 'pro' && !isExpired,
138
+ plan: isExpired ? 'free' : license.plan,
139
+ email: license.email,
140
+ expiresAt: license.expiresAt,
141
+ daysRemaining: daysRemaining && daysRemaining > 0 ? daysRemaining : undefined,
142
+ activatedAt: license.activatedAt,
143
+ };
144
+ }
145
+ /**
146
+ * Get a user-friendly error message for Pro features
147
+ */
148
+ export function getProFeatureMessage(featureName) {
149
+ return `${featureName} is a Pro feature.
150
+
151
+ Upgrade at https://claude-memory.dev or activate your license:
152
+ claude-memory activate <your-key>`;
153
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * License types for Claude Memory Pro
3
+ */
4
+ export interface License {
5
+ /** License key (format: CM-XXXX-XXXX-XXXX) */
6
+ key: string;
7
+ /** Email associated with the license */
8
+ email?: string;
9
+ /** License plan */
10
+ plan: 'free' | 'pro';
11
+ /** When the license was activated */
12
+ activatedAt: string;
13
+ /** When the license expires (if applicable) */
14
+ expiresAt?: string;
15
+ }
16
+ export interface LicenseStatus {
17
+ /** Whether the user has Pro features */
18
+ isPro: boolean;
19
+ /** Current plan */
20
+ plan: 'free' | 'pro';
21
+ /** Email associated with license */
22
+ email?: string;
23
+ /** Expiration date */
24
+ expiresAt?: string;
25
+ /** Days remaining until expiration */
26
+ daysRemaining?: number;
27
+ /** When activated */
28
+ activatedAt?: string;
29
+ }
30
+ export interface LicenseValidationResult {
31
+ /** Whether the key is valid */
32
+ valid: boolean;
33
+ /** Error message if invalid */
34
+ error?: string;
35
+ /** License details if valid */
36
+ license?: License;
37
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * License types for Claude Memory Pro
3
+ */
4
+ export {};
@@ -14,7 +14,10 @@ import { readFileSync, existsSync, readdirSync, statSync, writeFileSync } from '
14
14
  import { join } from 'path';
15
15
  import { homedir } from 'os';
16
16
  import { DEFAULT_MEMORY_CONFIG } from '../types/index.js';
17
+ import { isPro, getProFeatureMessage } from '../license/index.js';
18
+ import { detectStaleProjects, checkCurrentProjectStaleness, formatStalenessForMcp } from '../scanner/stalenessDetector.js';
17
19
  const MEMORY_DIR = join(homedir(), '.claude-memory');
20
+ const CURRENT_VERSION = '0.2.3'; // Update this with each release
18
21
  // Tool definitions
19
22
  const tools = [
20
23
  {
@@ -92,6 +95,25 @@ const tools = [
92
95
  required: ['project'],
93
96
  },
94
97
  },
98
+ // Pro tools
99
+ {
100
+ name: 'search_code',
101
+ description: '[PRO] Search for code patterns across all your projects. Find implementations, discover how you solved similar problems before. Requires Pro license.',
102
+ inputSchema: {
103
+ type: 'object',
104
+ properties: {
105
+ query: {
106
+ type: 'string',
107
+ description: 'Code pattern or text to search for',
108
+ },
109
+ filePattern: {
110
+ type: 'string',
111
+ description: 'File pattern to search (e.g., "*.ts", "*.py"). Optional.',
112
+ },
113
+ },
114
+ required: ['query'],
115
+ },
116
+ },
95
117
  ];
96
118
  function loadContext() {
97
119
  const contextPath = join(MEMORY_DIR, 'context.json');
@@ -218,6 +240,13 @@ function handleGetGlobalContext(cwd) {
218
240
  lines.push(`**Status:** ${cwdStatus.status}`);
219
241
  if (cwdStatus.projectName) {
220
242
  lines.push(`**Project:** ${cwdStatus.projectName}`);
243
+ // Check if current project is stale
244
+ if (context) {
245
+ const currentStaleness = checkCurrentProjectStaleness(context, cwd);
246
+ if (currentStaleness.isStale && currentStaleness.reason) {
247
+ lines.push(`**āš ļø Stale:** ${currentStaleness.reason}`);
248
+ }
249
+ }
221
250
  }
222
251
  if (cwdStatus.needsSetup) {
223
252
  lines.push(`**Needs Setup:** Yes`);
@@ -243,9 +272,22 @@ function handleGetGlobalContext(cwd) {
243
272
  lines.push('No projects scanned yet. Run `claude-memory scan` first.');
244
273
  return lines.join('\n');
245
274
  }
275
+ // Check for stale data
276
+ const hoursSinceUpdate = (Date.now() - new Date(context.lastUpdated).getTime()) / (1000 * 60 * 60);
246
277
  lines.push('# Your Development Environment');
247
278
  lines.push('');
248
- lines.push(`Last scanned: ${new Date(context.lastUpdated).toLocaleString()}`);
279
+ lines.push(`Last scanned: ${new Date(context.lastUpdated).toLocaleString()} | claude-memory v${CURRENT_VERSION}`);
280
+ // Run detailed staleness check
281
+ const stalenessReport = detectStaleProjects(context);
282
+ // Add staleness warnings if needed
283
+ if (stalenessReport.staleProjects.length > 0) {
284
+ lines.push('');
285
+ lines.push(formatStalenessForMcp(stalenessReport));
286
+ }
287
+ else if (hoursSinceUpdate > 24) {
288
+ lines.push('');
289
+ lines.push(`> āš ļø **Data may be stale** (${Math.floor(hoursSinceUpdate)} hours old). Run \`claude-memory scan\` to refresh.`);
290
+ }
249
291
  lines.push('');
250
292
  lines.push('## Active Projects');
251
293
  lines.push('');
@@ -344,6 +386,35 @@ function handleRecordInsight(content, relatedProjects) {
344
386
  return `Failed to record insight: ${err instanceof Error ? err.message : 'Unknown error'}`;
345
387
  }
346
388
  }
389
+ /**
390
+ * Pro feature: Search code across all projects
391
+ */
392
+ function handleSearchCode(query, filePattern) {
393
+ // Check Pro license
394
+ if (!isPro()) {
395
+ return getProFeatureMessage('Cross-project code search');
396
+ }
397
+ const context = loadContext();
398
+ if (!context) {
399
+ return 'No projects scanned yet. Run `claude-memory scan` first.';
400
+ }
401
+ const lines = [
402
+ `# Code Search Results for "${query}"`,
403
+ '',
404
+ ];
405
+ // TODO: Implement actual code search
406
+ // For now, return a placeholder that shows the feature is gated
407
+ lines.push('Searching across projects:');
408
+ for (const p of context.projects) {
409
+ lines.push(`- ${p.name} (${p.path})`);
410
+ }
411
+ lines.push('');
412
+ lines.push(`Pattern: ${filePattern || '*'}`);
413
+ lines.push('');
414
+ lines.push('> Note: Full code search implementation coming soon.');
415
+ lines.push('> This will index and search actual file contents across all your projects.');
416
+ return lines.join('\n');
417
+ }
347
418
  function handleGetProjectAnalysis(projectQuery) {
348
419
  const context = loadContext();
349
420
  if (!context) {
@@ -512,6 +583,9 @@ async function main() {
512
583
  case 'get_project_analysis':
513
584
  result = handleGetProjectAnalysis(args.project);
514
585
  break;
586
+ case 'search_code':
587
+ result = handleSearchCode(args.query, args.filePattern);
588
+ break;
515
589
  default:
516
590
  throw new Error(`Unknown tool: ${name}`);
517
591
  }
@@ -2,12 +2,19 @@
2
2
  * Context Generator - Produces markdown context files
3
3
  */
4
4
  import { ProjectInfo, GlobalContext } from '../types/index.js';
5
+ /**
6
+ * Load existing context from disk (if any)
7
+ */
8
+ export declare function loadExistingContext(): GlobalContext | null;
5
9
  /**
6
10
  * Generate global context from scanned projects
11
+ * Preserves existing insights and user patterns
7
12
  */
8
13
  export declare function generateGlobalContext(projects: ProjectInfo[]): GlobalContext;
9
14
  /**
10
15
  * Write global context to ~/.claude-memory/
16
+ * Uses atomic write (write to temp, then rename) to prevent corruption
17
+ * Creates backup before overwriting
11
18
  */
12
19
  export declare function writeGlobalContext(context: GlobalContext): void;
13
20
  /**