cmp-standards 3.1.2 → 3.3.1

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.
Files changed (76) hide show
  1. package/dist/cli/index.js +488 -1
  2. package/dist/cli/index.js.map +1 -1
  3. package/dist/db/migrations.d.ts +1 -1
  4. package/dist/db/migrations.d.ts.map +1 -1
  5. package/dist/db/migrations.js +102 -2
  6. package/dist/db/migrations.js.map +1 -1
  7. package/dist/eslint/ast-types.d.ts +235 -0
  8. package/dist/eslint/ast-types.d.ts.map +1 -0
  9. package/dist/eslint/ast-types.js +9 -0
  10. package/dist/eslint/ast-types.js.map +1 -0
  11. package/dist/eslint/rules/consistent-error-handling.d.ts.map +1 -1
  12. package/dist/eslint/rules/consistent-error-handling.js +2 -1
  13. package/dist/eslint/rules/consistent-error-handling.js.map +1 -1
  14. package/dist/eslint/rules/no-async-useeffect.js.map +1 -1
  15. package/dist/events/EventBus.js.map +1 -1
  16. package/dist/events/types.d.ts +174 -4
  17. package/dist/events/types.d.ts.map +1 -1
  18. package/dist/events/types.js +15 -0
  19. package/dist/events/types.js.map +1 -1
  20. package/dist/hooks/session-start.js +3 -3
  21. package/dist/hooks/session-start.js.map +1 -1
  22. package/dist/index.d.ts +11 -0
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +21 -0
  25. package/dist/index.js.map +1 -1
  26. package/dist/mcp/server.d.ts.map +1 -1
  27. package/dist/mcp/server.js +8 -4
  28. package/dist/mcp/server.js.map +1 -1
  29. package/dist/patterns/feedback-loop.d.ts +2 -2
  30. package/dist/patterns/lifecycle.d.ts.map +1 -1
  31. package/dist/patterns/lifecycle.js +11 -13
  32. package/dist/patterns/lifecycle.js.map +1 -1
  33. package/dist/patterns/registry.d.ts +2 -2
  34. package/dist/plugins/PluginManager.d.ts.map +1 -1
  35. package/dist/plugins/PluginManager.js +4 -1
  36. package/dist/plugins/PluginManager.js.map +1 -1
  37. package/dist/schema/codewiki-types.d.ts +1899 -0
  38. package/dist/schema/codewiki-types.d.ts.map +1 -0
  39. package/dist/schema/codewiki-types.js +585 -0
  40. package/dist/schema/codewiki-types.js.map +1 -0
  41. package/dist/schema/expert-types.d.ts +2 -2
  42. package/dist/schema/opportunity-types.d.ts +505 -0
  43. package/dist/schema/opportunity-types.d.ts.map +1 -0
  44. package/dist/schema/opportunity-types.js +255 -0
  45. package/dist/schema/opportunity-types.js.map +1 -0
  46. package/dist/services/AuditLog.d.ts.map +1 -1
  47. package/dist/services/AuditLog.js +4 -1
  48. package/dist/services/AuditLog.js.map +1 -1
  49. package/dist/services/CodeWikiIndexer.d.ts +145 -0
  50. package/dist/services/CodeWikiIndexer.d.ts.map +1 -0
  51. package/dist/services/CodeWikiIndexer.js +664 -0
  52. package/dist/services/CodeWikiIndexer.js.map +1 -0
  53. package/dist/services/OpportunityDiscovery.d.ts +84 -0
  54. package/dist/services/OpportunityDiscovery.d.ts.map +1 -0
  55. package/dist/services/OpportunityDiscovery.js +754 -0
  56. package/dist/services/OpportunityDiscovery.js.map +1 -0
  57. package/dist/services/ProjectScanner.d.ts.map +1 -1
  58. package/dist/services/ProjectScanner.js +1 -1
  59. package/dist/services/ProjectScanner.js.map +1 -1
  60. package/dist/services/index.d.ts +1 -0
  61. package/dist/services/index.d.ts.map +1 -1
  62. package/dist/services/index.js +2 -0
  63. package/dist/services/index.js.map +1 -1
  64. package/dist/utils/env.d.ts +149 -0
  65. package/dist/utils/env.d.ts.map +1 -0
  66. package/dist/utils/env.js +223 -0
  67. package/dist/utils/env.js.map +1 -0
  68. package/dist/utils/index.d.ts +3 -0
  69. package/dist/utils/index.d.ts.map +1 -1
  70. package/dist/utils/index.js +6 -0
  71. package/dist/utils/index.js.map +1 -1
  72. package/dist/utils/logger.d.ts +126 -0
  73. package/dist/utils/logger.d.ts.map +1 -0
  74. package/dist/utils/logger.js +231 -0
  75. package/dist/utils/logger.js.map +1 -0
  76. package/package.json +1 -1
@@ -0,0 +1,664 @@
1
+ /**
2
+ * @file CodeWiki Indexer Service - cmp-standards v3.3
3
+ * @description Automated code indexing service for knowledge graph generation
4
+ *
5
+ * Features:
6
+ * - Scans project files and extracts metadata
7
+ * - Parses TypeScript/JavaScript for symbols, imports, exports
8
+ * - Detects dependencies and relationships
9
+ * - Stores indexed data in Turso database
10
+ * - Supports incremental indexing (only changed files)
11
+ */
12
+ import { createHash } from 'crypto';
13
+ import { existsSync, statSync, readFileSync } from 'fs';
14
+ import { relative, basename, extname, resolve, dirname } from 'path';
15
+ import { glob } from 'glob';
16
+ import { turso } from '../db/turso-client.js';
17
+ import { getLogger } from '../utils/logger.js';
18
+ import { createFileMetadata, createCodeStructure, createCodeDependency, createIndexRun, detectLanguage, isTestFile, isTypeDefinitionFile, } from '../schema/codewiki-types.js';
19
+ const logger = getLogger();
20
+ // =============================================================================
21
+ // Constants
22
+ // =============================================================================
23
+ const DEFAULT_INCLUDE = [
24
+ '**/*.ts',
25
+ '**/*.tsx',
26
+ '**/*.js',
27
+ '**/*.jsx',
28
+ '**/*.mjs',
29
+ '**/*.cjs',
30
+ ];
31
+ const DEFAULT_EXCLUDE = [
32
+ '**/node_modules/**',
33
+ '**/dist/**',
34
+ '**/build/**',
35
+ '**/.git/**',
36
+ '**/coverage/**',
37
+ '**/*.min.js',
38
+ '**/*.map',
39
+ '**/package-lock.json',
40
+ '**/yarn.lock',
41
+ '**/pnpm-lock.yaml',
42
+ ];
43
+ const FILE_LIMITS = {
44
+ quick: 50,
45
+ standard: 200,
46
+ thorough: 500,
47
+ };
48
+ // =============================================================================
49
+ // CodeWiki Indexer Service
50
+ // =============================================================================
51
+ export class CodeWikiIndexer {
52
+ system;
53
+ rootDir;
54
+ fileMetadataCache = new Map();
55
+ constructor(system, rootDir) {
56
+ this.system = system;
57
+ this.rootDir = rootDir ?? process.cwd();
58
+ }
59
+ /**
60
+ * Run full indexing process
61
+ */
62
+ async index(options) {
63
+ const startTime = Date.now();
64
+ const runId = `run_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
65
+ const depth = options.depth ?? 'standard';
66
+ const paths = options.paths ?? ['.'];
67
+ const rootDir = options.rootDir ?? this.rootDir;
68
+ // Create index run record
69
+ const indexRun = createIndexRun(options.projectId, paths, depth);
70
+ await this.saveIndexRun(runId, indexRun);
71
+ const errors = [];
72
+ try {
73
+ // Load existing file metadata cache
74
+ await this.loadMetadataCache();
75
+ // Collect files to index
76
+ const files = await this.collectFiles(rootDir, {
77
+ include: options.include ?? DEFAULT_INCLUDE,
78
+ exclude: options.exclude ?? DEFAULT_EXCLUDE,
79
+ limit: FILE_LIMITS[depth],
80
+ paths,
81
+ });
82
+ indexRun.stats.filesScanned = files.length;
83
+ logger.info('CodeWiki indexing started', {
84
+ projectId: options.projectId,
85
+ files: files.length,
86
+ depth,
87
+ });
88
+ // Process each file
89
+ for (const file of files) {
90
+ try {
91
+ const result = await this.processFile(file, runId, options.force ?? false);
92
+ if (result.indexed) {
93
+ indexRun.stats.filesIndexed++;
94
+ if (result.changed) {
95
+ indexRun.stats.filesChanged++;
96
+ }
97
+ indexRun.stats.symbolsExtracted += result.symbolCount;
98
+ indexRun.stats.dependenciesFound += result.dependencyCount;
99
+ }
100
+ else {
101
+ indexRun.stats.filesSkipped++;
102
+ }
103
+ }
104
+ catch (err) {
105
+ const error = err instanceof Error ? err.message : String(err);
106
+ errors.push({ file: file.relativePath, error });
107
+ indexRun.stats.errorsEncountered++;
108
+ logger.warn('Failed to index file', { file: file.relativePath, error });
109
+ }
110
+ }
111
+ // Update run status
112
+ indexRun.status = 'completed';
113
+ indexRun.completedAt = new Date().toISOString();
114
+ indexRun.durationMs = Date.now() - startTime;
115
+ indexRun.errors = errors;
116
+ await this.saveIndexRun(runId, indexRun);
117
+ logger.info('CodeWiki indexing completed', {
118
+ projectId: options.projectId,
119
+ filesIndexed: indexRun.stats.filesIndexed,
120
+ symbolsExtracted: indexRun.stats.symbolsExtracted,
121
+ durationMs: indexRun.durationMs,
122
+ });
123
+ return {
124
+ runId,
125
+ status: indexRun.status,
126
+ stats: indexRun.stats,
127
+ durationMs: indexRun.durationMs ?? 0,
128
+ errors,
129
+ };
130
+ }
131
+ catch (err) {
132
+ indexRun.status = 'blocked';
133
+ indexRun.completedAt = new Date().toISOString();
134
+ indexRun.durationMs = Date.now() - startTime;
135
+ indexRun.errors = errors;
136
+ await this.saveIndexRun(runId, indexRun);
137
+ throw err;
138
+ }
139
+ }
140
+ /**
141
+ * Collect files matching patterns
142
+ */
143
+ async collectFiles(rootDir, options) {
144
+ const files = [];
145
+ for (const basePath of options.paths) {
146
+ const searchDir = resolve(rootDir, basePath);
147
+ if (!existsSync(searchDir)) {
148
+ logger.warn('Path does not exist', { path: searchDir });
149
+ continue;
150
+ }
151
+ for (const pattern of options.include) {
152
+ const matches = await glob(pattern, {
153
+ cwd: searchDir,
154
+ ignore: options.exclude,
155
+ nodir: true,
156
+ absolute: true,
157
+ });
158
+ for (const match of matches) {
159
+ if (files.length >= options.limit)
160
+ break;
161
+ try {
162
+ const stat = statSync(match);
163
+ files.push({
164
+ absolutePath: match,
165
+ relativePath: relative(rootDir, match),
166
+ fileName: basename(match),
167
+ extension: extname(match).slice(1),
168
+ size: stat.size,
169
+ mtime: stat.mtime,
170
+ });
171
+ }
172
+ catch {
173
+ // Skip files that can't be stat'd
174
+ }
175
+ }
176
+ if (files.length >= options.limit)
177
+ break;
178
+ }
179
+ }
180
+ return files;
181
+ }
182
+ /**
183
+ * Process a single file
184
+ */
185
+ async processFile(file, runId, force) {
186
+ // Read file content
187
+ const content = readFileSync(file.absolutePath, 'utf-8');
188
+ const hash = this.hashContent(content);
189
+ // Check if file has changed
190
+ const cached = this.fileMetadataCache.get(file.relativePath);
191
+ if (!force && cached && cached.hash === hash) {
192
+ return { indexed: false, changed: false, symbolCount: 0, dependencyCount: 0 };
193
+ }
194
+ const changed = cached !== undefined;
195
+ const language = detectLanguage(file.extension);
196
+ // Create file metadata
197
+ const metadata = this.createMetadata(file, content, hash, language);
198
+ const metadataId = await this.saveFileMetadata(metadata, runId);
199
+ // Update cache
200
+ this.fileMetadataCache.set(file.relativePath, { id: metadataId, hash });
201
+ // Parse code structure for supported languages
202
+ let symbolCount = 0;
203
+ let dependencyCount = 0;
204
+ if (this.isParseableLanguage(language)) {
205
+ const structure = await this.parseCodeStructure(file, content, metadataId, language);
206
+ symbolCount = structure.symbols.length;
207
+ dependencyCount = structure.imports.length;
208
+ await this.saveCodeStructure(structure, metadataId, runId);
209
+ // Extract dependencies
210
+ const dependencies = await this.extractDependencies(file, structure, metadataId);
211
+ for (const dep of dependencies) {
212
+ await this.saveCodeDependency(dep, runId);
213
+ }
214
+ dependencyCount = dependencies.length;
215
+ }
216
+ return { indexed: true, changed, symbolCount, dependencyCount };
217
+ }
218
+ /**
219
+ * Create file metadata from file info
220
+ */
221
+ createMetadata(file, content, hash, language) {
222
+ const lines = content.split('\n');
223
+ const nonEmptyLines = lines.filter(l => l.trim().length > 0).length;
224
+ const commentLines = this.countCommentLines(content, language);
225
+ return createFileMetadata(file.absolutePath, {
226
+ relativePath: file.relativePath,
227
+ fileName: file.fileName,
228
+ extension: file.extension,
229
+ language,
230
+ size: file.size,
231
+ loc: lines.length,
232
+ locNonEmpty: nonEmptyLines,
233
+ locComments: commentLines,
234
+ hash,
235
+ lastModified: file.mtime.toISOString(),
236
+ projectId: this.system,
237
+ });
238
+ }
239
+ /**
240
+ * Parse code structure from content
241
+ */
242
+ async parseCodeStructure(file, content, metadataId, language) {
243
+ const symbols = [];
244
+ const imports = [];
245
+ const exports = [];
246
+ // Simple regex-based parsing (Phase 1)
247
+ // TODO: Phase 2 will use proper AST parsing with typescript compiler API
248
+ const lines = content.split('\n');
249
+ let lineNum = 0;
250
+ for (const line of lines) {
251
+ lineNum++;
252
+ const trimmed = line.trim();
253
+ // Parse imports
254
+ const importMatch = trimmed.match(/^import\s+(.+)\s+from\s+['"](.+)['"]/);
255
+ if (importMatch) {
256
+ const importSpec = importMatch[1];
257
+ const source = importMatch[2];
258
+ const isExternal = !source.startsWith('.') && !source.startsWith('/');
259
+ imports.push({
260
+ source: source ?? '',
261
+ isExternal,
262
+ isTypeOnly: trimmed.startsWith('import type'),
263
+ isNamespace: importSpec?.includes('* as') ?? false,
264
+ isDefault: !importSpec?.startsWith('{') && !importSpec?.includes('* as'),
265
+ names: this.parseImportNames(importSpec ?? ''),
266
+ line: lineNum,
267
+ });
268
+ continue;
269
+ }
270
+ // Parse exports
271
+ if (trimmed.startsWith('export ')) {
272
+ const isDefault = trimmed.includes('export default');
273
+ const isType = trimmed.startsWith('export type');
274
+ const isReExport = trimmed.includes(' from ');
275
+ let name = 'default';
276
+ if (!isDefault) {
277
+ const nameMatch = trimmed.match(/export\s+(?:const|let|var|function|class|type|interface|enum)\s+(\w+)/);
278
+ name = nameMatch?.[1] ?? 'unknown';
279
+ }
280
+ exports.push({
281
+ name,
282
+ isDefault,
283
+ isType,
284
+ isReExport,
285
+ source: isReExport ? trimmed.match(/from\s+['"](.+)['"]/)?.[1] : undefined,
286
+ line: lineNum,
287
+ });
288
+ }
289
+ // Parse symbols (basic patterns)
290
+ this.parseSymbolFromLine(trimmed, lineNum, symbols);
291
+ }
292
+ return createCodeStructure(file.absolutePath, metadataId, {
293
+ symbols,
294
+ imports,
295
+ exports,
296
+ isTest: isTestFile(file.relativePath),
297
+ isTypeDefinition: isTypeDefinitionFile(file.relativePath),
298
+ isComponent: this.detectReactComponent(content),
299
+ framework: this.detectFramework(content),
300
+ });
301
+ }
302
+ /**
303
+ * Parse import names from import specifier
304
+ */
305
+ parseImportNames(spec) {
306
+ const names = [];
307
+ // Default import
308
+ if (!spec.startsWith('{') && !spec.includes('* as')) {
309
+ const defaultName = spec.split(',')[0]?.trim();
310
+ if (defaultName) {
311
+ names.push({ name: defaultName });
312
+ }
313
+ }
314
+ // Namespace import
315
+ const nsMatch = spec.match(/\*\s+as\s+(\w+)/);
316
+ if (nsMatch) {
317
+ names.push({ name: '*', alias: nsMatch[1] });
318
+ }
319
+ // Named imports
320
+ const namedMatch = spec.match(/\{([^}]+)\}/);
321
+ if (namedMatch) {
322
+ const namedParts = namedMatch[1]?.split(',') ?? [];
323
+ for (const part of namedParts) {
324
+ const aliasMatch = part.trim().match(/(\w+)\s+as\s+(\w+)/);
325
+ if (aliasMatch) {
326
+ names.push({ name: aliasMatch[1] ?? '', alias: aliasMatch[2] });
327
+ }
328
+ else {
329
+ const name = part.trim();
330
+ if (name) {
331
+ names.push({ name });
332
+ }
333
+ }
334
+ }
335
+ }
336
+ return names;
337
+ }
338
+ /**
339
+ * Parse symbol from a single line
340
+ */
341
+ parseSymbolFromLine(line, lineNum, symbols) {
342
+ // Function declarations
343
+ const funcMatch = line.match(/^(?:export\s+)?(?:async\s+)?function\s+(\w+)/);
344
+ if (funcMatch) {
345
+ symbols.push({
346
+ name: funcMatch[1] ?? '',
347
+ kind: 'function',
348
+ lineStart: lineNum,
349
+ lineEnd: lineNum, // Basic - doesn't track end
350
+ isExported: line.startsWith('export'),
351
+ isDefault: false,
352
+ isAsync: line.includes('async '),
353
+ generics: [],
354
+ decorators: [],
355
+ modifiers: [],
356
+ });
357
+ return;
358
+ }
359
+ // Arrow functions (const x = () => or const x = async () =>)
360
+ const arrowMatch = line.match(/^(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*(?::\s*\w+)?\s*=>/);
361
+ if (arrowMatch) {
362
+ symbols.push({
363
+ name: arrowMatch[1] ?? '',
364
+ kind: 'arrow_function',
365
+ lineStart: lineNum,
366
+ lineEnd: lineNum,
367
+ isExported: line.startsWith('export'),
368
+ isDefault: false,
369
+ isAsync: line.includes('async'),
370
+ generics: [],
371
+ decorators: [],
372
+ modifiers: [],
373
+ });
374
+ return;
375
+ }
376
+ // Class declarations
377
+ const classMatch = line.match(/^(?:export\s+)?(?:abstract\s+)?class\s+(\w+)/);
378
+ if (classMatch) {
379
+ symbols.push({
380
+ name: classMatch[1] ?? '',
381
+ kind: 'class',
382
+ lineStart: lineNum,
383
+ lineEnd: lineNum,
384
+ isExported: line.startsWith('export'),
385
+ isDefault: false,
386
+ modifiers: line.includes('abstract') ? ['abstract'] : [],
387
+ generics: [],
388
+ decorators: [],
389
+ });
390
+ return;
391
+ }
392
+ // Interface declarations
393
+ const ifaceMatch = line.match(/^(?:export\s+)?interface\s+(\w+)/);
394
+ if (ifaceMatch) {
395
+ symbols.push({
396
+ name: ifaceMatch[1] ?? '',
397
+ kind: 'interface',
398
+ lineStart: lineNum,
399
+ lineEnd: lineNum,
400
+ isExported: line.startsWith('export'),
401
+ isDefault: false,
402
+ generics: [],
403
+ decorators: [],
404
+ modifiers: [],
405
+ });
406
+ return;
407
+ }
408
+ // Type aliases
409
+ const typeMatch = line.match(/^(?:export\s+)?type\s+(\w+)/);
410
+ if (typeMatch) {
411
+ symbols.push({
412
+ name: typeMatch[1] ?? '',
413
+ kind: 'type',
414
+ lineStart: lineNum,
415
+ lineEnd: lineNum,
416
+ isExported: line.startsWith('export'),
417
+ isDefault: false,
418
+ generics: [],
419
+ decorators: [],
420
+ modifiers: [],
421
+ });
422
+ return;
423
+ }
424
+ // Enum declarations
425
+ const enumMatch = line.match(/^(?:export\s+)?(?:const\s+)?enum\s+(\w+)/);
426
+ if (enumMatch) {
427
+ symbols.push({
428
+ name: enumMatch[1] ?? '',
429
+ kind: 'enum',
430
+ lineStart: lineNum,
431
+ lineEnd: lineNum,
432
+ isExported: line.startsWith('export'),
433
+ isDefault: false,
434
+ generics: [],
435
+ decorators: [],
436
+ modifiers: [],
437
+ });
438
+ return;
439
+ }
440
+ // Const declarations (not arrow functions)
441
+ const constMatch = line.match(/^(?:export\s+)?const\s+(\w+)\s*[=:]/);
442
+ if (constMatch && !arrowMatch) {
443
+ symbols.push({
444
+ name: constMatch[1] ?? '',
445
+ kind: 'const',
446
+ lineStart: lineNum,
447
+ lineEnd: lineNum,
448
+ isExported: line.startsWith('export'),
449
+ isDefault: false,
450
+ generics: [],
451
+ decorators: [],
452
+ modifiers: [],
453
+ });
454
+ }
455
+ }
456
+ /**
457
+ * Extract dependencies from code structure
458
+ */
459
+ async extractDependencies(file, structure, metadataId) {
460
+ const dependencies = [];
461
+ for (const imp of structure.imports) {
462
+ let targetFile = imp.source;
463
+ let targetFileId;
464
+ if (!imp.isExternal) {
465
+ // Resolve relative import
466
+ targetFile = this.resolveImport(file.absolutePath, imp.source);
467
+ // Try to find the target in our cache
468
+ const cached = this.fileMetadataCache.get(relative(this.rootDir, targetFile));
469
+ targetFileId = cached?.id;
470
+ }
471
+ dependencies.push(createCodeDependency(file.absolutePath, targetFile, {
472
+ sourceFileId: metadataId,
473
+ targetFileId,
474
+ isExternal: imp.isExternal,
475
+ packageName: imp.isExternal ? this.getPackageName(imp.source) : undefined,
476
+ dependencyType: imp.isTypeOnly ? 'type_only' : 'import',
477
+ symbols: imp.names.map(n => n.alias ?? n.name),
478
+ line: imp.line,
479
+ }));
480
+ }
481
+ return dependencies;
482
+ }
483
+ /**
484
+ * Resolve relative import to absolute path
485
+ */
486
+ resolveImport(fromFile, importPath) {
487
+ const dir = dirname(fromFile);
488
+ let resolved = resolve(dir, importPath);
489
+ // Try common extensions if not specified
490
+ const extensions = ['.ts', '.tsx', '.js', '.jsx', '/index.ts', '/index.tsx', '/index.js'];
491
+ for (const ext of extensions) {
492
+ const withExt = resolved + ext;
493
+ if (existsSync(withExt)) {
494
+ return withExt;
495
+ }
496
+ }
497
+ return resolved;
498
+ }
499
+ /**
500
+ * Get package name from import source
501
+ */
502
+ getPackageName(source) {
503
+ // Handle scoped packages (@scope/package)
504
+ if (source.startsWith('@')) {
505
+ const parts = source.split('/');
506
+ return `${parts[0]}/${parts[1]}`;
507
+ }
508
+ // Handle regular packages (package or package/subpath)
509
+ return source.split('/')[0] ?? source;
510
+ }
511
+ /**
512
+ * Detect if content contains a React component
513
+ */
514
+ detectReactComponent(content) {
515
+ return (content.includes('React.') ||
516
+ content.includes('from \'react\'') ||
517
+ content.includes('from "react"') ||
518
+ /return\s+\(?\s*</.test(content));
519
+ }
520
+ /**
521
+ * Detect framework from content
522
+ */
523
+ detectFramework(content) {
524
+ if (content.includes('from \'next') || content.includes('from "next')) {
525
+ return 'next';
526
+ }
527
+ if (content.includes('from \'react') || content.includes('from "react')) {
528
+ return 'react';
529
+ }
530
+ if (content.includes('from \'express') || content.includes('from "express')) {
531
+ return 'express';
532
+ }
533
+ if (content.includes('from \'fastify') || content.includes('from "fastify')) {
534
+ return 'fastify';
535
+ }
536
+ return undefined;
537
+ }
538
+ /**
539
+ * Count comment lines in content
540
+ */
541
+ countCommentLines(content, language) {
542
+ if (!['typescript', 'javascript', 'tsx', 'jsx'].includes(language)) {
543
+ return 0;
544
+ }
545
+ let count = 0;
546
+ let inBlockComment = false;
547
+ for (const line of content.split('\n')) {
548
+ const trimmed = line.trim();
549
+ if (inBlockComment) {
550
+ count++;
551
+ if (trimmed.includes('*/')) {
552
+ inBlockComment = false;
553
+ }
554
+ continue;
555
+ }
556
+ if (trimmed.startsWith('//')) {
557
+ count++;
558
+ continue;
559
+ }
560
+ if (trimmed.startsWith('/*')) {
561
+ count++;
562
+ inBlockComment = !trimmed.includes('*/');
563
+ continue;
564
+ }
565
+ }
566
+ return count;
567
+ }
568
+ /**
569
+ * Check if language is parseable
570
+ */
571
+ isParseableLanguage(language) {
572
+ return ['typescript', 'javascript', 'tsx', 'jsx'].includes(language);
573
+ }
574
+ /**
575
+ * Hash content for change detection
576
+ */
577
+ hashContent(content) {
578
+ return createHash('sha256').update(content).digest('hex').slice(0, 16);
579
+ }
580
+ // ===========================================================================
581
+ // Database Operations
582
+ // ===========================================================================
583
+ /**
584
+ * Load existing file metadata cache
585
+ */
586
+ async loadMetadataCache() {
587
+ try {
588
+ const items = await turso.query({
589
+ type: 'file_metadata',
590
+ system: this.system,
591
+ status: 'active',
592
+ limit: 500,
593
+ });
594
+ for (const item of items) {
595
+ try {
596
+ const content = item.content;
597
+ this.fileMetadataCache.set(content.relativePath, {
598
+ id: item.id,
599
+ hash: content.hash,
600
+ });
601
+ }
602
+ catch {
603
+ // Skip invalid entries
604
+ }
605
+ }
606
+ logger.debug('Loaded file metadata cache', { count: this.fileMetadataCache.size });
607
+ }
608
+ catch (err) {
609
+ logger.warn('Failed to load metadata cache', { error: String(err) });
610
+ }
611
+ }
612
+ /**
613
+ * Save file metadata to database
614
+ */
615
+ async saveFileMetadata(metadata, _runId) {
616
+ const id = await turso.create('file_metadata', this.system, metadata, 'active');
617
+ return id;
618
+ }
619
+ /**
620
+ * Save code structure to database
621
+ */
622
+ async saveCodeStructure(structure, _metadataId, _runId) {
623
+ await turso.create('code_structure', this.system, structure, 'active');
624
+ }
625
+ /**
626
+ * Save code dependency to database
627
+ */
628
+ async saveCodeDependency(dependency, _runId) {
629
+ await turso.create('code_dependency', this.system, dependency, 'active');
630
+ }
631
+ /**
632
+ * Save index run to database
633
+ */
634
+ async saveIndexRun(_runId, run) {
635
+ await turso.create('index_run', this.system, run, run.status);
636
+ }
637
+ }
638
+ // =============================================================================
639
+ // Singleton & Factory
640
+ // =============================================================================
641
+ let indexerInstance = null;
642
+ /**
643
+ * Get CodeWiki indexer instance
644
+ */
645
+ export function getCodeWikiIndexer(system, rootDir) {
646
+ if (!indexerInstance || indexerInstance['system'] !== system) {
647
+ indexerInstance = new CodeWikiIndexer(system, rootDir);
648
+ }
649
+ return indexerInstance;
650
+ }
651
+ /**
652
+ * Create new CodeWiki indexer
653
+ */
654
+ export function createCodeWikiIndexer(system, rootDir) {
655
+ return new CodeWikiIndexer(system, rootDir);
656
+ }
657
+ /**
658
+ * Reset indexer instance
659
+ */
660
+ export function resetCodeWikiIndexer() {
661
+ indexerInstance = null;
662
+ }
663
+ export default CodeWikiIndexer;
664
+ //# sourceMappingURL=CodeWikiIndexer.js.map