@contextmirror/claude-memory 0.2.1 → 0.2.2

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.
@@ -5,27 +5,61 @@ import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
5
5
  import { join, basename } from 'path';
6
6
  import { simpleGit } from 'simple-git';
7
7
  import { DEFAULT_SCAN_OPTIONS } from '../types/index.js';
8
+ import { loadExistingContext } from './contextGenerator.js';
8
9
  /**
9
10
  * Scan a directory for projects and extract information
10
11
  */
11
12
  export async function scanProjects(options = {}) {
12
13
  const opts = { ...DEFAULT_SCAN_OPTIONS, ...options };
13
14
  const projects = [];
15
+ const errors = [];
14
16
  console.log(`šŸ” Scanning ${opts.rootDir} for projects...`);
17
+ // Verify root directory exists
18
+ if (!existsSync(opts.rootDir)) {
19
+ console.error(`āŒ Directory does not exist: ${opts.rootDir}`);
20
+ console.error(` Check the path and try again.`);
21
+ return [];
22
+ }
15
23
  const candidates = findProjectRoots(opts.rootDir, opts.maxDepth, opts.ignore);
16
- for (const projectPath of candidates) {
24
+ if (candidates.length === 0) {
25
+ console.log(` No project directories found in ${opts.rootDir}`);
26
+ console.log(` Looking for: package.json, Cargo.toml, pyproject.toml, go.mod, or .git`);
27
+ return [];
28
+ }
29
+ // Load excluded projects from existing context
30
+ const existingContext = loadExistingContext();
31
+ const excludedProjects = new Set(existingContext?.excludedProjects || []);
32
+ // Filter out excluded projects
33
+ const filteredCandidates = candidates.filter(path => {
34
+ if (excludedProjects.has(path)) {
35
+ console.log(` ⊘ Skipping excluded: ${basename(path)}`);
36
+ return false;
37
+ }
38
+ return true;
39
+ });
40
+ console.log(` Found ${filteredCandidates.length} candidate directories\n`);
41
+ if (excludedProjects.size > 0) {
42
+ console.log(` (${excludedProjects.size} excluded)\n`);
43
+ }
44
+ for (const projectPath of filteredCandidates) {
17
45
  try {
18
46
  const info = await analyzeProject(projectPath);
19
47
  if (info) {
20
48
  projects.push(info);
21
- console.log(` āœ“ Found: ${info.name} (${info.language})`);
49
+ console.log(` āœ“ ${info.name} (${info.language}) - ${info.techStack.join(', ') || 'no stack detected'}`);
22
50
  }
23
51
  }
24
52
  catch (err) {
25
- console.warn(` ⚠ Failed to analyze: ${projectPath}`);
53
+ const errorMsg = err instanceof Error ? err.message : 'Unknown error';
54
+ errors.push({ path: projectPath, error: errorMsg });
55
+ console.warn(` ⚠ Failed: ${basename(projectPath)} - ${errorMsg}`);
26
56
  }
27
57
  }
28
- console.log(`\nšŸ“Š Found ${projects.length} projects`);
58
+ console.log(`\nšŸ“Š Results:`);
59
+ console.log(` āœ… Successfully scanned: ${projects.length}`);
60
+ if (errors.length > 0) {
61
+ console.log(` āš ļø Failed to scan: ${errors.length}`);
62
+ }
29
63
  return projects;
30
64
  }
31
65
  /**
@@ -77,23 +111,33 @@ async function analyzeProject(projectPath) {
77
111
  const name = detectProjectName(projectPath);
78
112
  const description = detectDescription(projectPath);
79
113
  const { language, techStack } = detectTechStack(projectPath);
80
- // Git info
114
+ // Git info with timeout to prevent hanging on large/slow repos
81
115
  let lastActivity = new Date().toISOString();
82
116
  let currentBranch = 'unknown';
83
117
  let isDirty = false;
84
118
  if (existsSync(join(projectPath, '.git'))) {
85
119
  try {
86
- const git = simpleGit(projectPath);
87
- const log = await git.log({ maxCount: 1 });
88
- if (log.latest) {
89
- lastActivity = log.latest.date;
90
- }
91
- const status = await git.status();
92
- currentBranch = status.current || 'unknown';
93
- isDirty = !status.isClean();
120
+ // 10 second timeout for git operations
121
+ const git = simpleGit(projectPath, { timeout: { block: 10000 } });
122
+ // Wrap in Promise.race for extra safety
123
+ const gitOps = async () => {
124
+ const log = await git.log({ maxCount: 1 });
125
+ if (log.latest) {
126
+ lastActivity = log.latest.date;
127
+ }
128
+ const status = await git.status();
129
+ currentBranch = status.current || 'unknown';
130
+ isDirty = !status.isClean();
131
+ };
132
+ const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error('Git timeout')), 10000));
133
+ await Promise.race([gitOps(), timeout]);
94
134
  }
95
- catch {
96
- // Git operations failed, use defaults
135
+ catch (err) {
136
+ // Git operations failed or timed out, use defaults
137
+ const errorMsg = err instanceof Error ? err.message : 'Unknown error';
138
+ if (errorMsg.includes('timeout') || errorMsg.includes('Timeout')) {
139
+ console.warn(` āš ļø Git timeout for ${basename(projectPath)} - using defaults`);
140
+ }
97
141
  }
98
142
  }
99
143
  // Extract insights from CLAUDE.md if it exists
@@ -25,12 +25,33 @@ const MCP_SERVER_CONFIG = {
25
25
  * Run the setup wizard
26
26
  */
27
27
  export async function runSetupWizard(options = {}) {
28
- const { projectsDir = guessProjectsDir(), skipMcp = false, skipBriefing = false, interactive = true, } = options;
28
+ const { projectsDir: providedDir, skipMcp = false, skipBriefing = false, interactive = true, } = options;
29
29
  console.log('');
30
30
  console.log('╔══════════════════════════════════════════════════════════╗');
31
31
  console.log('ā•‘ 🧠 Claude Memory - Setup Wizard ā•‘');
32
32
  console.log('ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•');
33
33
  console.log('');
34
+ // Determine projects directory
35
+ let projectsDir = providedDir || guessProjectsDir();
36
+ if (!projectsDir) {
37
+ console.log('šŸ“ Step 0: Locate your projects\n');
38
+ console.log(' Could not auto-detect your projects directory.');
39
+ console.log(' Common locations like ~/Projects, ~/Code, ~/dev were not found.\n');
40
+ if (interactive) {
41
+ const answer = await ask(' Enter your projects directory path: ');
42
+ if (!answer || !existsSync(answer)) {
43
+ console.log('\n āŒ Invalid directory. Please run:');
44
+ console.log(' claude-memory setup -d /path/to/your/projects');
45
+ return;
46
+ }
47
+ projectsDir = answer;
48
+ }
49
+ else {
50
+ console.log(' āŒ Cannot continue without a projects directory.');
51
+ console.log(' Run: claude-memory setup -d /path/to/your/projects');
52
+ return;
53
+ }
54
+ }
34
55
  // Step 1: Find projects
35
56
  console.log('šŸ“ Step 1: Scanning for projects\n');
36
57
  console.log(` Looking in: ${projectsDir}`);
@@ -164,6 +185,7 @@ export async function runSetupWizard(options = {}) {
164
185
  }
165
186
  /**
166
187
  * Guess the user's projects directory
188
+ * Returns null if we can't find a reasonable guess (better than scanning home!)
167
189
  */
168
190
  function guessProjectsDir() {
169
191
  const home = homedir();
@@ -178,14 +200,17 @@ function guessProjectsDir() {
178
200
  join(home, 'src'),
179
201
  join(home, 'repos'),
180
202
  join(home, 'git'),
203
+ join(home, 'workspace'),
204
+ join(home, 'Workspace'),
181
205
  ];
182
206
  for (const dir of candidates) {
183
207
  if (existsSync(dir)) {
184
208
  return dir;
185
209
  }
186
210
  }
187
- // Fall back to home directory
188
- return home;
211
+ // Don't fall back to home - that scans way too much and causes issues
212
+ // Return null and let the caller handle it
213
+ return null;
189
214
  }
190
215
  /**
191
216
  * Save memory config to ~/.claude-memory/config.json
@@ -1,6 +1,8 @@
1
1
  /**
2
2
  * Core types for Claude Memory
3
3
  */
4
+ /** Current schema version - increment when making breaking changes */
5
+ export declare const SCHEMA_VERSION = 1;
4
6
  export interface ProjectInfo {
5
7
  /** Absolute path to project root */
6
8
  path: string;
@@ -43,14 +45,18 @@ export interface ProjectInsight {
43
45
  discoveredAt: string;
44
46
  }
45
47
  export interface GlobalContext {
48
+ /** Schema version for migration support */
49
+ schemaVersion: number;
46
50
  /** When the global context was last updated */
47
51
  lastUpdated: string;
48
52
  /** All scanned projects */
49
53
  projects: ProjectInfo[];
50
- /** Cross-project insights */
54
+ /** Cross-project insights (preserved across rescans) */
51
55
  insights: GlobalInsight[];
52
56
  /** User preferences/patterns observed */
53
57
  userPatterns: UserPattern[];
58
+ /** Projects to exclude from scanning (by path) */
59
+ excludedProjects?: string[];
54
60
  }
55
61
  export interface GlobalInsight {
56
62
  /** Insight content */
@@ -1,10 +1,43 @@
1
1
  /**
2
2
  * Core types for Claude Memory
3
3
  */
4
+ /** Current schema version - increment when making breaking changes */
5
+ export const SCHEMA_VERSION = 1;
6
+ /**
7
+ * Get a sensible default projects directory
8
+ * Checks common locations, doesn't fall back to home
9
+ */
10
+ function getDefaultProjectsDir() {
11
+ const home = process.env.HOME || '';
12
+ if (!home)
13
+ return './projects';
14
+ const candidates = [
15
+ `${home}/Projects`,
16
+ `${home}/Project`,
17
+ `${home}/projects`,
18
+ `${home}/Code`,
19
+ `${home}/code`,
20
+ `${home}/dev`,
21
+ ];
22
+ // Import fs dynamically to avoid issues
23
+ try {
24
+ const fs = require('fs');
25
+ for (const dir of candidates) {
26
+ if (fs.existsSync(dir)) {
27
+ return dir;
28
+ }
29
+ }
30
+ }
31
+ catch {
32
+ // fs not available, use first candidate
33
+ }
34
+ // Default to ~/Projects (most common convention)
35
+ return `${home}/Projects`;
36
+ }
4
37
  export const DEFAULT_SCAN_OPTIONS = {
5
- rootDir: process.env.HOME ? `${process.env.HOME}/Project` : './projects',
38
+ rootDir: getDefaultProjectsDir(),
6
39
  maxDepth: 2,
7
- ignore: ['node_modules', '.git', 'dist', 'build', '__pycache__', 'target'],
40
+ ignore: ['node_modules', '.git', 'dist', 'build', '__pycache__', 'target', '.venv', 'venv', 'coverage', '.next'],
8
41
  generateClaudeMd: false,
9
42
  overwriteClaudeMd: false,
10
43
  };