@claudetools/tools 0.7.0 → 0.7.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.
@@ -0,0 +1,634 @@
1
+ /**
2
+ * Codebase Mapper
3
+ *
4
+ * Client-side AST analysis to generate codebase maps.
5
+ * Runs in the MCP server and uploads results to the API.
6
+ */
7
+ import { readFileSync, readdirSync, statSync, existsSync } from 'fs';
8
+ import { join, relative, extname, basename, dirname } from 'path';
9
+ import * as ts from 'typescript';
10
+ import { API_BASE_URL } from './config.js';
11
+ // =============================================================================
12
+ // File Discovery
13
+ // =============================================================================
14
+ const IGNORED_DIRS = new Set([
15
+ 'node_modules', '.git', 'dist', 'build', 'coverage', '.next', '.turbo',
16
+ '__pycache__', '.venv', 'venv', '.tox', 'target', '.idea', '.vscode',
17
+ ]);
18
+ const SUPPORTED_EXTENSIONS = new Set([
19
+ '.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs',
20
+ ]);
21
+ function discoverFiles(root) {
22
+ const files = [];
23
+ function walk(dir) {
24
+ const entries = readdirSync(dir);
25
+ for (const entry of entries) {
26
+ if (entry.startsWith('.') || IGNORED_DIRS.has(entry))
27
+ continue;
28
+ const fullPath = join(dir, entry);
29
+ const stat = statSync(fullPath);
30
+ if (stat.isDirectory()) {
31
+ walk(fullPath);
32
+ }
33
+ else if (stat.isFile() && SUPPORTED_EXTENSIONS.has(extname(entry))) {
34
+ files.push(fullPath);
35
+ }
36
+ }
37
+ }
38
+ walk(root);
39
+ return files;
40
+ }
41
+ // =============================================================================
42
+ // AST Analysis
43
+ // =============================================================================
44
+ function analyzeFile(filePath, root) {
45
+ const content = readFileSync(filePath, 'utf-8');
46
+ const relativePath = relative(root, filePath);
47
+ const lines = content.split('\n').length;
48
+ const symbols = [];
49
+ const imports = [];
50
+ const exports = [];
51
+ // Parse with TypeScript
52
+ const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true, filePath.endsWith('.tsx') || filePath.endsWith('.jsx')
53
+ ? ts.ScriptKind.TSX
54
+ : ts.ScriptKind.TS);
55
+ function visit(node) {
56
+ const line = sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1;
57
+ // Track imports
58
+ if (ts.isImportDeclaration(node)) {
59
+ const moduleSpecifier = node.moduleSpecifier;
60
+ if (ts.isStringLiteral(moduleSpecifier)) {
61
+ imports.push(moduleSpecifier.text);
62
+ }
63
+ }
64
+ // Track exports
65
+ if (ts.isExportDeclaration(node)) {
66
+ const moduleSpecifier = node.moduleSpecifier;
67
+ if (moduleSpecifier && ts.isStringLiteral(moduleSpecifier)) {
68
+ exports.push(moduleSpecifier.text);
69
+ }
70
+ }
71
+ // Check for export modifier using canHaveModifiers
72
+ const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined;
73
+ const isExported = modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ?? false;
74
+ // Functions
75
+ if (ts.isFunctionDeclaration(node) && node.name) {
76
+ symbols.push({
77
+ name: node.name.text,
78
+ type: 'function',
79
+ exported: isExported,
80
+ line,
81
+ signature: getSignature(node),
82
+ });
83
+ }
84
+ // Arrow functions and const declarations
85
+ if (ts.isVariableStatement(node)) {
86
+ const varModifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined;
87
+ const isConstExported = varModifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ?? false;
88
+ for (const decl of node.declarationList.declarations) {
89
+ if (ts.isIdentifier(decl.name)) {
90
+ const name = decl.name.text;
91
+ let symbolType = 'const';
92
+ // Detect React components (PascalCase + returns JSX)
93
+ if (/^[A-Z]/.test(name) && decl.initializer) {
94
+ if (ts.isArrowFunction(decl.initializer) || ts.isFunctionExpression(decl.initializer)) {
95
+ symbolType = 'component';
96
+ }
97
+ }
98
+ // Detect hooks
99
+ if (name.startsWith('use') && name.length > 3) {
100
+ symbolType = 'function';
101
+ }
102
+ symbols.push({
103
+ name,
104
+ type: symbolType,
105
+ exported: isConstExported,
106
+ line: sourceFile.getLineAndCharacterOfPosition(decl.getStart()).line + 1,
107
+ });
108
+ }
109
+ }
110
+ }
111
+ // Classes
112
+ if (ts.isClassDeclaration(node) && node.name) {
113
+ symbols.push({
114
+ name: node.name.text,
115
+ type: 'class',
116
+ exported: isExported,
117
+ line,
118
+ });
119
+ }
120
+ // Interfaces
121
+ if (ts.isInterfaceDeclaration(node)) {
122
+ symbols.push({
123
+ name: node.name.text,
124
+ type: 'interface',
125
+ exported: isExported,
126
+ line,
127
+ });
128
+ }
129
+ // Type aliases
130
+ if (ts.isTypeAliasDeclaration(node)) {
131
+ symbols.push({
132
+ name: node.name.text,
133
+ type: 'type',
134
+ exported: isExported,
135
+ line,
136
+ });
137
+ }
138
+ // Enums
139
+ if (ts.isEnumDeclaration(node)) {
140
+ symbols.push({
141
+ name: node.name.text,
142
+ type: 'enum',
143
+ exported: isExported,
144
+ line,
145
+ });
146
+ }
147
+ ts.forEachChild(node, visit);
148
+ }
149
+ visit(sourceFile);
150
+ return {
151
+ path: filePath,
152
+ relativePath,
153
+ role: detectFileRole(relativePath, symbols),
154
+ symbols,
155
+ imports,
156
+ exports,
157
+ dependencies: [], // Populated later
158
+ dependents: [], // Populated later
159
+ linesOfCode: lines,
160
+ };
161
+ }
162
+ function getSignature(node) {
163
+ if (!node.parameters.length && !node.type)
164
+ return undefined;
165
+ const params = node.parameters
166
+ .map((p) => {
167
+ const name = p.name.getText();
168
+ const type = p.type?.getText() || 'any';
169
+ return `${name}: ${type}`;
170
+ })
171
+ .join(', ');
172
+ const returnType = node.type?.getText() || 'void';
173
+ return `(${params}) => ${returnType}`;
174
+ }
175
+ function detectFileRole(relativePath, symbols) {
176
+ const path = relativePath.toLowerCase();
177
+ const fileName = basename(path, extname(path));
178
+ // Test files
179
+ if (path.includes('.test.') || path.includes('.spec.') || path.includes('__tests__')) {
180
+ return 'test';
181
+ }
182
+ // Config files
183
+ if (fileName.includes('config') || fileName.includes('settings')) {
184
+ return 'config';
185
+ }
186
+ // Entry points
187
+ if (['index', 'main', 'app'].includes(fileName) && !path.includes('/')) {
188
+ return 'entry-point';
189
+ }
190
+ // Type definitions
191
+ if (path.includes('/types') || fileName.endsWith('.d') || path.includes('types.ts')) {
192
+ return 'type-definitions';
193
+ }
194
+ // Route handlers (Next.js, Remix, etc.)
195
+ if (path.includes('/routes/') || path.includes('/pages/') || path.includes('/app/')) {
196
+ return 'route-handler';
197
+ }
198
+ // API endpoints
199
+ if (path.includes('/api/') || path.includes('/handlers/')) {
200
+ return 'api-endpoint';
201
+ }
202
+ // MCP tools
203
+ if (path.includes('/tools') || path.includes('tool-handlers')) {
204
+ return 'mcp-tool';
205
+ }
206
+ // Components
207
+ if (path.includes('/components/') || symbols.some((s) => s.type === 'component')) {
208
+ return 'component';
209
+ }
210
+ // Hooks
211
+ if (path.includes('/hooks/') || symbols.some((s) => s.name.startsWith('use'))) {
212
+ return 'hook';
213
+ }
214
+ // Models
215
+ if (path.includes('/models/') || path.includes('/entities/') || path.includes('/schema')) {
216
+ return 'model';
217
+ }
218
+ // Services
219
+ if (path.includes('/services/') || path.includes('/providers/')) {
220
+ return 'service';
221
+ }
222
+ // Extractors
223
+ if (path.includes('/extraction/') || path.includes('/extractors/')) {
224
+ return 'extractor';
225
+ }
226
+ // Utilities
227
+ if (path.includes('/utils/') || path.includes('/helpers/') || path.includes('/lib/')) {
228
+ return 'utility';
229
+ }
230
+ return 'unknown';
231
+ }
232
+ // =============================================================================
233
+ // Dependency Analysis
234
+ // =============================================================================
235
+ function buildDependencyGraph(files, root) {
236
+ // Build a map from relative import paths to file entries
237
+ const filesByPath = {};
238
+ for (const file of Object.values(files)) {
239
+ // Add with and without extension
240
+ const relPath = file.relativePath;
241
+ filesByPath[relPath] = relPath;
242
+ filesByPath[relPath.replace(/\.[^.]+$/, '')] = relPath;
243
+ // Add index files
244
+ if (basename(relPath).startsWith('index.')) {
245
+ filesByPath[dirname(relPath)] = relPath;
246
+ }
247
+ }
248
+ // Resolve dependencies
249
+ for (const file of Object.values(files)) {
250
+ const fileDir = dirname(file.relativePath);
251
+ for (const imp of file.imports) {
252
+ // Skip external packages
253
+ if (!imp.startsWith('.') && !imp.startsWith('/'))
254
+ continue;
255
+ // Resolve relative import
256
+ let resolved = imp.startsWith('.')
257
+ ? join(fileDir, imp).replace(/\\/g, '/')
258
+ : imp;
259
+ // Normalize
260
+ resolved = resolved.replace(/^\.\//, '');
261
+ // Find matching file
262
+ const target = filesByPath[resolved];
263
+ if (target && target !== file.relativePath) {
264
+ file.dependencies.push(target);
265
+ // Add reverse dependency
266
+ const targetFile = files[target];
267
+ if (targetFile) {
268
+ targetFile.dependents.push(file.relativePath);
269
+ }
270
+ }
271
+ }
272
+ }
273
+ }
274
+ // =============================================================================
275
+ // Call Graph Building
276
+ // =============================================================================
277
+ function buildCallGraph(files) {
278
+ const callGraph = {};
279
+ const reverseCallGraph = {};
280
+ // Build symbol lookup
281
+ const symbolFiles = {};
282
+ for (const file of Object.values(files)) {
283
+ for (const symbol of file.symbols) {
284
+ if (symbol.exported) {
285
+ symbolFiles[symbol.name] = file.relativePath;
286
+ }
287
+ }
288
+ }
289
+ // For now, use a simple heuristic based on dependencies
290
+ // Full call graph would require more sophisticated AST analysis
291
+ for (const file of Object.values(files)) {
292
+ for (const symbol of file.symbols) {
293
+ if (symbol.type === 'function' || symbol.type === 'component') {
294
+ callGraph[symbol.name] = [];
295
+ reverseCallGraph[symbol.name] = [];
296
+ // Infer calls from dependencies
297
+ for (const dep of file.dependencies) {
298
+ const depFile = files[dep];
299
+ if (depFile) {
300
+ for (const depSymbol of depFile.symbols) {
301
+ if (depSymbol.exported && (depSymbol.type === 'function' || depSymbol.type === 'component')) {
302
+ callGraph[symbol.name].push(depSymbol.name);
303
+ if (!reverseCallGraph[depSymbol.name]) {
304
+ reverseCallGraph[depSymbol.name] = [];
305
+ }
306
+ reverseCallGraph[depSymbol.name].push(symbol.name);
307
+ }
308
+ }
309
+ }
310
+ }
311
+ }
312
+ }
313
+ }
314
+ return { callGraph, reverseCallGraph };
315
+ }
316
+ // =============================================================================
317
+ // Directory Analysis
318
+ // =============================================================================
319
+ function analyzeDirectories(files, root) {
320
+ const directories = {};
321
+ const dirFiles = {};
322
+ // Group files by directory
323
+ for (const file of Object.values(files)) {
324
+ const dir = dirname(file.relativePath) || '.';
325
+ if (!dirFiles[dir]) {
326
+ dirFiles[dir] = [];
327
+ }
328
+ dirFiles[dir].push(file);
329
+ }
330
+ // Analyze each directory
331
+ for (const [dir, filesInDir] of Object.entries(dirFiles)) {
332
+ const languages = {};
333
+ const keyFiles = [];
334
+ for (const file of filesInDir) {
335
+ const ext = extname(file.relativePath);
336
+ languages[ext] = (languages[ext] || 0) + 1;
337
+ // Key files are entry points or have many exports
338
+ if (file.role === 'entry-point' || file.symbols.filter((s) => s.exported).length > 5) {
339
+ keyFiles.push(file.relativePath);
340
+ }
341
+ }
342
+ const primaryLang = Object.entries(languages)
343
+ .sort((a, b) => b[1] - a[1])[0]?.[0] || 'unknown';
344
+ directories[dir] = {
345
+ path: dir,
346
+ role: detectDirectoryRole(dir, filesInDir),
347
+ fileCount: filesInDir.length,
348
+ primaryLanguage: primaryLang,
349
+ keyFiles: keyFiles.slice(0, 3),
350
+ };
351
+ }
352
+ return directories;
353
+ }
354
+ function detectDirectoryRole(dir, files) {
355
+ const dirLower = dir.toLowerCase();
356
+ if (dirLower.includes('component'))
357
+ return 'components';
358
+ if (dirLower.includes('hook'))
359
+ return 'hooks';
360
+ if (dirLower.includes('route') || dirLower.includes('page'))
361
+ return 'routes';
362
+ if (dirLower.includes('api') || dirLower.includes('handler'))
363
+ return 'api';
364
+ if (dirLower.includes('util') || dirLower.includes('helper') || dirLower.includes('lib'))
365
+ return 'utilities';
366
+ if (dirLower.includes('type'))
367
+ return 'types';
368
+ if (dirLower.includes('test') || dirLower.includes('spec'))
369
+ return 'tests';
370
+ if (dirLower.includes('config'))
371
+ return 'configuration';
372
+ if (dirLower.includes('model') || dirLower.includes('schema'))
373
+ return 'models';
374
+ if (dirLower.includes('service') || dirLower.includes('provider'))
375
+ return 'services';
376
+ // Check file roles
377
+ const roleCounts = {};
378
+ for (const file of files) {
379
+ roleCounts[file.role] = (roleCounts[file.role] || 0) + 1;
380
+ }
381
+ const topRole = Object.entries(roleCounts).sort((a, b) => b[1] - a[1])[0];
382
+ return topRole ? topRole[0] : 'misc';
383
+ }
384
+ // =============================================================================
385
+ // Framework Detection
386
+ // =============================================================================
387
+ function detectFrameworks(root) {
388
+ const frameworks = [];
389
+ try {
390
+ const pkgPath = join(root, 'package.json');
391
+ if (existsSync(pkgPath)) {
392
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
393
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
394
+ if (deps.next)
395
+ frameworks.push('Next.js');
396
+ if (deps.react)
397
+ frameworks.push('React');
398
+ if (deps.vue)
399
+ frameworks.push('Vue');
400
+ if (deps.svelte)
401
+ frameworks.push('Svelte');
402
+ if (deps['@tanstack/react-query'])
403
+ frameworks.push('TanStack Query');
404
+ if (deps.express)
405
+ frameworks.push('Express');
406
+ if (deps.hono)
407
+ frameworks.push('Hono');
408
+ if (deps.fastify)
409
+ frameworks.push('Fastify');
410
+ if (deps.drizzle)
411
+ frameworks.push('Drizzle');
412
+ if (deps.prisma)
413
+ frameworks.push('Prisma');
414
+ if (deps.zod)
415
+ frameworks.push('Zod');
416
+ if (deps.typescript)
417
+ frameworks.push('TypeScript');
418
+ }
419
+ }
420
+ catch {
421
+ // Ignore package.json errors
422
+ }
423
+ return frameworks;
424
+ }
425
+ // =============================================================================
426
+ // Markdown Generation
427
+ // =============================================================================
428
+ function generateMarkdown(index) {
429
+ let md = `# Codebase Map: ${basename(index.projectRoot)}\n\n`;
430
+ md += `> Generated ${index.generatedAt}\n\n`;
431
+ // Summary
432
+ md += `## Summary\n\n`;
433
+ md += `- **Files:** ${index.summary.totalFiles}\n`;
434
+ md += `- **Symbols:** ${index.summary.totalSymbols}\n`;
435
+ md += `- **Frameworks:** ${index.summary.frameworks.join(', ') || 'None detected'}\n\n`;
436
+ // Languages
437
+ md += `### Languages\n\n`;
438
+ for (const [lang, count] of Object.entries(index.summary.languages)) {
439
+ md += `- ${lang}: ${count} files\n`;
440
+ }
441
+ md += '\n';
442
+ // Directory structure
443
+ md += `## Structure\n\n`;
444
+ const sortedDirs = Object.values(index.directories).sort((a, b) => a.path.localeCompare(b.path));
445
+ for (const dir of sortedDirs) {
446
+ const indent = ' '.repeat(dir.path.split('/').length - 1);
447
+ md += `${indent}- **${dir.path}/** (${dir.role}, ${dir.fileCount} files)\n`;
448
+ if (dir.keyFiles.length > 0) {
449
+ for (const keyFile of dir.keyFiles) {
450
+ md += `${indent} - 📄 ${basename(keyFile)}\n`;
451
+ }
452
+ }
453
+ }
454
+ md += '\n';
455
+ // Key entry points
456
+ const entryPoints = Object.values(index.files).filter((f) => f.role === 'entry-point' || f.symbols.filter((s) => s.exported).length > 10);
457
+ if (entryPoints.length > 0) {
458
+ md += `## Entry Points\n\n`;
459
+ for (const file of entryPoints.slice(0, 10)) {
460
+ const exportedSymbols = file.symbols.filter((s) => s.exported);
461
+ md += `### ${file.relativePath}\n`;
462
+ md += `- **Role:** ${file.role}\n`;
463
+ md += `- **Lines:** ${file.linesOfCode}\n`;
464
+ md += `- **Exports:** ${exportedSymbols.length}\n\n`;
465
+ if (exportedSymbols.length > 0 && exportedSymbols.length <= 20) {
466
+ for (const sym of exportedSymbols) {
467
+ md += ` - \`${sym.name}\` (${sym.type})\n`;
468
+ }
469
+ md += '\n';
470
+ }
471
+ }
472
+ }
473
+ // Key symbols
474
+ const exportedSymbols = Object.entries(index.symbols)
475
+ .filter(([_, locs]) => locs.some((l) => l.exported))
476
+ .sort((a, b) => b[1].length - a[1].length)
477
+ .slice(0, 30);
478
+ if (exportedSymbols.length > 0) {
479
+ md += `## Key Symbols\n\n`;
480
+ md += `| Symbol | Type | Location |\n`;
481
+ md += `|--------|------|----------|\n`;
482
+ for (const [name, locs] of exportedSymbols) {
483
+ const loc = locs[0];
484
+ md += `| \`${name}\` | ${loc.type} | ${loc.file}:${loc.line} |\n`;
485
+ }
486
+ md += '\n';
487
+ }
488
+ return md;
489
+ }
490
+ // =============================================================================
491
+ // Main Export
492
+ // =============================================================================
493
+ export async function generateCodebaseMap(projectRoot, projectId, apiKey) {
494
+ try {
495
+ // Discover and analyze files
496
+ const filePaths = discoverFiles(projectRoot);
497
+ if (filePaths.length === 0) {
498
+ return { success: false, error: 'No supported files found in project' };
499
+ }
500
+ const files = {};
501
+ const languages = {};
502
+ let totalSymbols = 0;
503
+ for (const filePath of filePaths) {
504
+ try {
505
+ const entry = analyzeFile(filePath, projectRoot);
506
+ files[entry.relativePath] = entry;
507
+ const ext = extname(entry.relativePath);
508
+ languages[ext] = (languages[ext] || 0) + 1;
509
+ totalSymbols += entry.symbols.length;
510
+ }
511
+ catch (err) {
512
+ // Skip files that can't be parsed
513
+ console.error(`Failed to analyze ${filePath}:`, err);
514
+ }
515
+ }
516
+ // Build dependency graph
517
+ buildDependencyGraph(files, projectRoot);
518
+ // Build call graph
519
+ const { callGraph, reverseCallGraph } = buildCallGraph(files);
520
+ // Analyze directories
521
+ const directories = analyzeDirectories(files, projectRoot);
522
+ // Detect frameworks
523
+ const frameworks = detectFrameworks(projectRoot);
524
+ // Build symbols index
525
+ const symbols = {};
526
+ for (const file of Object.values(files)) {
527
+ for (const sym of file.symbols) {
528
+ if (!symbols[sym.name]) {
529
+ symbols[sym.name] = [];
530
+ }
531
+ symbols[sym.name].push({
532
+ file: file.relativePath,
533
+ line: sym.line,
534
+ type: sym.type,
535
+ exported: sym.exported,
536
+ });
537
+ }
538
+ }
539
+ // Build index
540
+ const index = {
541
+ version: '1.0.0',
542
+ generatedAt: new Date().toISOString(),
543
+ projectRoot,
544
+ summary: {
545
+ totalFiles: Object.keys(files).length,
546
+ totalSymbols,
547
+ languages,
548
+ frameworks,
549
+ },
550
+ directories,
551
+ files,
552
+ symbols,
553
+ callGraph,
554
+ reverseCallGraph,
555
+ };
556
+ // Generate markdown
557
+ const markdown = generateMarkdown(index);
558
+ // Upload to API
559
+ const response = await fetch(`${API_BASE_URL}/api/v1/codebase/${projectId}/generate`, {
560
+ method: 'POST',
561
+ headers: {
562
+ 'Content-Type': 'application/json',
563
+ Authorization: `Bearer ${apiKey}`,
564
+ },
565
+ body: JSON.stringify({ index, markdown }),
566
+ });
567
+ if (!response.ok) {
568
+ const text = await response.text();
569
+ return { success: false, error: `API error: ${response.status} - ${text}` };
570
+ }
571
+ return { success: true };
572
+ }
573
+ catch (err) {
574
+ return { success: false, error: String(err) };
575
+ }
576
+ }
577
+ /**
578
+ * Generate a codebase map locally without uploading
579
+ * Useful for previewing or debugging
580
+ */
581
+ export function generateCodebaseMapLocal(projectRoot) {
582
+ const filePaths = discoverFiles(projectRoot);
583
+ const files = {};
584
+ const languages = {};
585
+ let totalSymbols = 0;
586
+ for (const filePath of filePaths) {
587
+ try {
588
+ const entry = analyzeFile(filePath, projectRoot);
589
+ files[entry.relativePath] = entry;
590
+ const ext = extname(entry.relativePath);
591
+ languages[ext] = (languages[ext] || 0) + 1;
592
+ totalSymbols += entry.symbols.length;
593
+ }
594
+ catch {
595
+ // Skip files that can't be parsed
596
+ }
597
+ }
598
+ buildDependencyGraph(files, projectRoot);
599
+ const { callGraph, reverseCallGraph } = buildCallGraph(files);
600
+ const directories = analyzeDirectories(files, projectRoot);
601
+ const frameworks = detectFrameworks(projectRoot);
602
+ const symbols = {};
603
+ for (const file of Object.values(files)) {
604
+ for (const sym of file.symbols) {
605
+ if (!symbols[sym.name]) {
606
+ symbols[sym.name] = [];
607
+ }
608
+ symbols[sym.name].push({
609
+ file: file.relativePath,
610
+ line: sym.line,
611
+ type: sym.type,
612
+ exported: sym.exported,
613
+ });
614
+ }
615
+ }
616
+ const index = {
617
+ version: '1.0.0',
618
+ generatedAt: new Date().toISOString(),
619
+ projectRoot,
620
+ summary: {
621
+ totalFiles: Object.keys(files).length,
622
+ totalSymbols,
623
+ languages,
624
+ frameworks,
625
+ },
626
+ directories,
627
+ files,
628
+ symbols,
629
+ callGraph,
630
+ reverseCallGraph,
631
+ };
632
+ const markdown = generateMarkdown(index);
633
+ return { index, markdown };
634
+ }
@@ -34,7 +34,7 @@ export declare function resolveProjectId(): string;
34
34
  export declare function resolveProjectIdAsync(): Promise<string>;
35
35
  /**
36
36
  * Gets the default project ID (synchronous version)
37
- * Throws if not already resolved - call resolveProjectIdAsync() first
37
+ * Checks session-context.md first, then config, then falls back to cwd resolution
38
38
  */
39
39
  export declare function getDefaultProjectId(): string;
40
40
  /**