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.
- package/dist/cli/index.js +488 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/db/migrations.d.ts +1 -1
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/migrations.js +102 -2
- package/dist/db/migrations.js.map +1 -1
- package/dist/eslint/ast-types.d.ts +235 -0
- package/dist/eslint/ast-types.d.ts.map +1 -0
- package/dist/eslint/ast-types.js +9 -0
- package/dist/eslint/ast-types.js.map +1 -0
- package/dist/eslint/rules/consistent-error-handling.d.ts.map +1 -1
- package/dist/eslint/rules/consistent-error-handling.js +2 -1
- package/dist/eslint/rules/consistent-error-handling.js.map +1 -1
- package/dist/eslint/rules/no-async-useeffect.js.map +1 -1
- package/dist/events/EventBus.js.map +1 -1
- package/dist/events/types.d.ts +174 -4
- package/dist/events/types.d.ts.map +1 -1
- package/dist/events/types.js +15 -0
- package/dist/events/types.js.map +1 -1
- package/dist/hooks/session-start.js +3 -3
- package/dist/hooks/session-start.js.map +1 -1
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +8 -4
- package/dist/mcp/server.js.map +1 -1
- package/dist/patterns/feedback-loop.d.ts +2 -2
- package/dist/patterns/lifecycle.d.ts.map +1 -1
- package/dist/patterns/lifecycle.js +11 -13
- package/dist/patterns/lifecycle.js.map +1 -1
- package/dist/patterns/registry.d.ts +2 -2
- package/dist/plugins/PluginManager.d.ts.map +1 -1
- package/dist/plugins/PluginManager.js +4 -1
- package/dist/plugins/PluginManager.js.map +1 -1
- package/dist/schema/codewiki-types.d.ts +1899 -0
- package/dist/schema/codewiki-types.d.ts.map +1 -0
- package/dist/schema/codewiki-types.js +585 -0
- package/dist/schema/codewiki-types.js.map +1 -0
- package/dist/schema/expert-types.d.ts +2 -2
- package/dist/schema/opportunity-types.d.ts +505 -0
- package/dist/schema/opportunity-types.d.ts.map +1 -0
- package/dist/schema/opportunity-types.js +255 -0
- package/dist/schema/opportunity-types.js.map +1 -0
- package/dist/services/AuditLog.d.ts.map +1 -1
- package/dist/services/AuditLog.js +4 -1
- package/dist/services/AuditLog.js.map +1 -1
- package/dist/services/CodeWikiIndexer.d.ts +145 -0
- package/dist/services/CodeWikiIndexer.d.ts.map +1 -0
- package/dist/services/CodeWikiIndexer.js +664 -0
- package/dist/services/CodeWikiIndexer.js.map +1 -0
- package/dist/services/OpportunityDiscovery.d.ts +84 -0
- package/dist/services/OpportunityDiscovery.d.ts.map +1 -0
- package/dist/services/OpportunityDiscovery.js +754 -0
- package/dist/services/OpportunityDiscovery.js.map +1 -0
- package/dist/services/ProjectScanner.d.ts.map +1 -1
- package/dist/services/ProjectScanner.js +1 -1
- package/dist/services/ProjectScanner.js.map +1 -1
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +2 -0
- package/dist/services/index.js.map +1 -1
- package/dist/utils/env.d.ts +149 -0
- package/dist/utils/env.d.ts.map +1 -0
- package/dist/utils/env.js +223 -0
- package/dist/utils/env.js.map +1 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +6 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/logger.d.ts +126 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +231 -0
- package/dist/utils/logger.js.map +1 -0
- 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
|