opencode-autognosis 1.0.1 → 2.0.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.
@@ -0,0 +1,982 @@
1
+ import { tool } from "@opencode-ai/plugin";
2
+ import { exec } from "node:child_process";
3
+ import * as fs from "node:fs/promises";
4
+ import * as fsSync from "node:fs";
5
+ import * as path from "node:path";
6
+ import { promisify } from "node:util";
7
+ import * as crypto from "node:crypto";
8
+ import ts from "typescript";
9
+ const execAsync = promisify(exec);
10
+ const PROJECT_ROOT = process.cwd();
11
+ const OPENCODE_DIR = path.join(PROJECT_ROOT, ".opencode");
12
+ export const CHUNK_DIR = path.join(OPENCODE_DIR, "chunks");
13
+ const CACHE_DIR = path.join(OPENCODE_DIR, "cache");
14
+ // Internal logging
15
+ function log(message, data) {
16
+ console.error(`[ChunkCards] ${message}`, data || '');
17
+ }
18
+ // =============================================================================
19
+ // HELPERS
20
+ // =============================================================================
21
+ async function runCmd(cmd, cwd = PROJECT_ROOT, timeoutMs = 30000) {
22
+ try {
23
+ const { stdout, stderr } = await execAsync(cmd, {
24
+ cwd,
25
+ maxBuffer: 10 * 1024 * 1024,
26
+ timeout: timeoutMs
27
+ });
28
+ return { stdout: stdout.trim(), stderr: stderr.trim() };
29
+ }
30
+ catch (error) {
31
+ if (error.signal === 'SIGTERM' && error.code === undefined) {
32
+ return { stdout: "", stderr: `Command timed out after ${timeoutMs}ms`, error, timedOut: true };
33
+ }
34
+ return { stdout: "", stderr: error.message, error };
35
+ }
36
+ }
37
+ export async function ensureChunkDir() {
38
+ await fs.mkdir(CHUNK_DIR, { recursive: true });
39
+ }
40
+ export function calculateHash(content) {
41
+ return crypto.createHash('sha256').update(content).digest('hex');
42
+ }
43
+ export function calculateComplexity(content) {
44
+ // Simple complexity calculation based on code metrics
45
+ const lines = content.split('\n').length;
46
+ const cyclomaticComplexity = (content.match(/\b(if|while|for|switch|case|catch)\b/g) || []).length;
47
+ const nestingDepth = Math.max(...content.split('\n').map(line => (line.match(/^\s*/)?.[0]?.length || 0)));
48
+ return Math.min(100, (lines * 0.1) + (cyclomaticComplexity * 5) + (nestingDepth * 2));
49
+ }
50
+ export function extractSymbols(content, filePath = '') {
51
+ // Extract function names, class names, and variable names
52
+ const symbols = [];
53
+ if (filePath) {
54
+ const ext = path.extname(filePath);
55
+ if (ext === '.cpp' || ext === '.c' || ext === '.h' || ext === '.hpp' || ext === '.cc') {
56
+ const funcs = extractFunctionsCpp(content);
57
+ const classes = extractClassesCpp(content);
58
+ symbols.push(...funcs.map(f => f.name));
59
+ symbols.push(...classes.map(c => c.name));
60
+ return symbols;
61
+ }
62
+ if (ext === '.swift') {
63
+ const funcs = extractFunctionsSwift(content);
64
+ const classes = extractClassesSwift(content);
65
+ symbols.push(...funcs.map(f => f.name));
66
+ symbols.push(...classes.map(c => c.name));
67
+ return symbols;
68
+ }
69
+ }
70
+ // Functions
71
+ const functionMatches = content.match(/(?:function|const|let|var)\s+(\w+)\s*=/g);
72
+ if (functionMatches) {
73
+ symbols.push(...functionMatches.map(m => m.split(/\s+/)[1]));
74
+ }
75
+ // Classes
76
+ const classMatches = content.match(/class\s+(\w+)/g);
77
+ if (classMatches) {
78
+ symbols.push(...classMatches.map(m => m.split(/\s+/)[1]));
79
+ }
80
+ // Interfaces/Types
81
+ const typeMatches = content.match(/(?:interface|type)\s+(\w+)/g);
82
+ if (typeMatches) {
83
+ symbols.push(...typeMatches.map(m => m.split(/\s+/)[1]));
84
+ }
85
+ return symbols.filter(s => s && s.length > 0);
86
+ }
87
+ // =============================================================================
88
+ // CHUNK CARDS IMPLEMENTATION
89
+ // =============================================================================
90
+ export function chunkCardsTools() {
91
+ return {
92
+ chunk_create_card: tool({
93
+ description: "Create a Chunk Card for code analysis. Supports summary, API, and invariant card types with automatic metadata extraction.",
94
+ args: {
95
+ file_path: tool.schema.string().describe("Absolute path to the source file"),
96
+ chunk_type: tool.schema.enum(["summary", "api", "invariant"]).describe("Type of chunk card to create"),
97
+ content: tool.schema.string().optional().describe("Custom content (auto-generated if not provided)"),
98
+ force_recreate: tool.schema.boolean().optional().default(false).describe("Force recreation even if card exists")
99
+ },
100
+ async execute({ file_path, chunk_type, content, force_recreate }) {
101
+ log("Tool call: chunk_create_card", { file_path, chunk_type, force_recreate });
102
+ try {
103
+ await ensureChunkDir();
104
+ // Validate file exists
105
+ if (!fsSync.existsSync(file_path)) {
106
+ return JSON.stringify({
107
+ status: "ERROR",
108
+ message: `File not found: ${file_path}`
109
+ }, null, 2);
110
+ }
111
+ // Generate card ID
112
+ const fileHash = calculateHash(file_path);
113
+ const cardId = `${path.basename(file_path)}-${chunk_type}-${fileHash.slice(0, 8)}`;
114
+ const cardPath = path.join(CHUNK_DIR, `${cardId}.json`);
115
+ // Check if card already exists
116
+ if (!force_recreate && fsSync.existsSync(cardPath)) {
117
+ const existingCard = JSON.parse(await fs.readFile(cardPath, 'utf-8'));
118
+ return JSON.stringify({
119
+ status: "EXISTS",
120
+ card: existingCard,
121
+ message: "Card already exists. Use force_recreate=true to override."
122
+ }, null, 2);
123
+ }
124
+ // Read source file if no custom content provided
125
+ let sourceContent = content;
126
+ if (!sourceContent) {
127
+ sourceContent = await fs.readFile(file_path, 'utf-8');
128
+ }
129
+ // Parse AST for JS/TS files
130
+ const ast = parseFileAST(file_path, sourceContent);
131
+ // Generate chunk content based on type
132
+ let chunkContent = "";
133
+ switch (chunk_type) {
134
+ case "summary":
135
+ chunkContent = await generateSummaryChunk(sourceContent, file_path, ast);
136
+ break;
137
+ case "api":
138
+ chunkContent = await generateApiChunk(sourceContent, file_path, ast);
139
+ break;
140
+ case "invariant":
141
+ chunkContent = await generateInvariantChunk(sourceContent, file_path, ast);
142
+ break;
143
+ }
144
+ // Create chunk card
145
+ const chunkCard = {
146
+ id: cardId,
147
+ file_path,
148
+ chunk_type,
149
+ content: chunkContent,
150
+ metadata: {
151
+ created_at: new Date().toISOString(),
152
+ updated_at: new Date().toISOString(),
153
+ hash: calculateHash(chunkContent),
154
+ dependencies: await extractDependencies(sourceContent, ast, file_path),
155
+ symbols: extractSymbolsFromAST(ast, sourceContent) || extractSymbols(sourceContent, file_path),
156
+ complexity_score: calculateComplexity(sourceContent)
157
+ }
158
+ };
159
+ // Save chunk card
160
+ await fs.writeFile(cardPath, JSON.stringify(chunkCard, null, 2));
161
+ return JSON.stringify({
162
+ status: "SUCCESS",
163
+ card: chunkCard,
164
+ saved_to: cardPath
165
+ }, null, 2);
166
+ }
167
+ catch (error) {
168
+ return JSON.stringify({
169
+ status: "ERROR",
170
+ message: error instanceof Error ? error.message : `${error}`
171
+ }, null, 2);
172
+ }
173
+ }
174
+ }),
175
+ chunk_get_card: tool({
176
+ description: "Retrieve a Chunk Card by ID or file path and type.",
177
+ args: {
178
+ card_id: tool.schema.string().optional().describe("Card ID to retrieve"),
179
+ file_path: tool.schema.string().optional().describe("File path to search for cards"),
180
+ chunk_type: tool.schema.enum(["summary", "api", "invariant"]).optional().describe("Filter by chunk type")
181
+ },
182
+ async execute({ card_id, file_path, chunk_type }) {
183
+ log("Tool call: chunk_get_card", { card_id, file_path, chunk_type });
184
+ try {
185
+ await ensureChunkDir();
186
+ if (card_id) {
187
+ // Direct card lookup
188
+ const cardPath = path.join(CHUNK_DIR, `${card_id}.json`);
189
+ if (!fsSync.existsSync(cardPath)) {
190
+ return JSON.stringify({
191
+ status: "NOT_FOUND",
192
+ message: `Card not found: ${card_id}`
193
+ }, null, 2);
194
+ }
195
+ const card = JSON.parse(await fs.readFile(cardPath, 'utf-8'));
196
+ return JSON.stringify({
197
+ status: "SUCCESS",
198
+ card
199
+ }, null, 2);
200
+ }
201
+ else if (file_path) {
202
+ // Search by file path
203
+ const files = await fs.readdir(CHUNK_DIR);
204
+ const matchingCards = [];
205
+ for (const file of files) {
206
+ if (file.endsWith('.json')) {
207
+ const cardPath = path.join(CHUNK_DIR, file);
208
+ const card = JSON.parse(await fs.readFile(cardPath, 'utf-8'));
209
+ if (card.file_path === file_path && (!chunk_type || card.chunk_type === chunk_type)) {
210
+ matchingCards.push(card);
211
+ }
212
+ }
213
+ }
214
+ return JSON.stringify({
215
+ status: "SUCCESS",
216
+ cards: matchingCards,
217
+ count: matchingCards.length
218
+ }, null, 2);
219
+ }
220
+ else {
221
+ return JSON.stringify({
222
+ status: "ERROR",
223
+ message: "Either card_id or file_path must be provided"
224
+ }, null, 2);
225
+ }
226
+ }
227
+ catch (error) {
228
+ return JSON.stringify({
229
+ status: "ERROR",
230
+ message: error instanceof Error ? error.message : `${error}`
231
+ }, null, 2);
232
+ }
233
+ }
234
+ }),
235
+ chunk_list_cards: tool({
236
+ description: "List all Chunk Cards with optional filtering and pagination.",
237
+ args: {
238
+ chunk_type: tool.schema.enum(["summary", "api", "invariant"]).optional().describe("Filter by chunk type"),
239
+ file_pattern: tool.schema.string().optional().describe("Filter by file path pattern (regex)"),
240
+ limit: tool.schema.number().optional().default(50).describe("Maximum number of cards to return"),
241
+ offset: tool.schema.number().optional().default(0).describe("Number of cards to skip")
242
+ },
243
+ async execute({ chunk_type, file_pattern, limit, offset }) {
244
+ log("Tool call: chunk_list_cards", { chunk_type, file_pattern, limit, offset });
245
+ try {
246
+ await ensureChunkDir();
247
+ const files = await fs.readdir(CHUNK_DIR);
248
+ const cards = [];
249
+ for (const file of files) {
250
+ if (file.endsWith('.json')) {
251
+ const cardPath = path.join(CHUNK_DIR, file);
252
+ const card = JSON.parse(await fs.readFile(cardPath, 'utf-8'));
253
+ // Apply filters
254
+ if (chunk_type && card.chunk_type !== chunk_type)
255
+ continue;
256
+ if (file_pattern && !new RegExp(file_pattern).test(card.file_path))
257
+ continue;
258
+ cards.push(card);
259
+ }
260
+ }
261
+ // Sort by creation date (newest first)
262
+ cards.sort((a, b) => new Date(b.metadata.created_at).getTime() - new Date(a.metadata.created_at).getTime());
263
+ // Apply pagination
264
+ const paginatedCards = cards.slice(offset, offset + limit);
265
+ return JSON.stringify({
266
+ status: "SUCCESS",
267
+ cards: paginatedCards,
268
+ pagination: {
269
+ total: cards.length,
270
+ limit,
271
+ offset,
272
+ has_more: offset + limit < cards.length
273
+ }
274
+ }, null, 2);
275
+ }
276
+ catch (error) {
277
+ return JSON.stringify({
278
+ status: "ERROR",
279
+ message: error instanceof Error ? error.message : `${error}`
280
+ }, null, 2);
281
+ }
282
+ }
283
+ }),
284
+ chunk_delete_card: tool({
285
+ description: "Delete a Chunk Card by ID.",
286
+ args: {
287
+ card_id: tool.schema.string().describe("Card ID to delete")
288
+ },
289
+ async execute({ card_id }) {
290
+ log("Tool call: chunk_delete_card", { card_id });
291
+ try {
292
+ const cardPath = path.join(CHUNK_DIR, `${card_id}.json`);
293
+ if (!fsSync.existsSync(cardPath)) {
294
+ return JSON.stringify({
295
+ status: "NOT_FOUND",
296
+ message: `Card not found: ${card_id}`
297
+ }, null, 2);
298
+ }
299
+ await fs.unlink(cardPath);
300
+ return JSON.stringify({
301
+ status: "SUCCESS",
302
+ message: `Card deleted: ${card_id}`
303
+ }, null, 2);
304
+ }
305
+ catch (error) {
306
+ return JSON.stringify({
307
+ status: "ERROR",
308
+ message: error instanceof Error ? error.message : `${error}`
309
+ }, null, 2);
310
+ }
311
+ }
312
+ })
313
+ };
314
+ }
315
+ // =============================================================================
316
+ // CHUNK GENERATION HELPERS
317
+ // =============================================================================
318
+ export async function generateSummaryChunk(content, filePath, ast) {
319
+ const lines = content.split('\n');
320
+ const fileName = path.basename(filePath);
321
+ const fileExtension = path.extname(filePath);
322
+ // Extract key information
323
+ let functions = [];
324
+ let classes = [];
325
+ let imports = [];
326
+ let exports = [];
327
+ if (ast) {
328
+ functions = extractFunctionsFromAST(ast);
329
+ classes = extractClassesFromAST(ast);
330
+ imports = extractImportsFromAST(ast);
331
+ exports = extractExportsFromAST(ast);
332
+ }
333
+ else if (fileExtension === '.cpp' || fileExtension === '.c' || fileExtension === '.h' || fileExtension === '.hpp' || fileExtension === '.cc') {
334
+ functions = extractFunctionsCpp(content);
335
+ classes = extractClassesCpp(content);
336
+ imports = extractImportsCpp(content);
337
+ }
338
+ else if (fileExtension === '.swift') {
339
+ functions = extractFunctionsSwift(content);
340
+ classes = extractClassesSwift(content);
341
+ imports = extractImportsSwift(content);
342
+ }
343
+ else {
344
+ functions = extractFunctions(content);
345
+ classes = extractClasses(content);
346
+ imports = extractImports(content);
347
+ exports = extractExports(content);
348
+ }
349
+ const summary = `# Summary: ${fileName}
350
+
351
+ ## File Type
352
+ ${fileExtension} - ${getFileTypeDescription(fileExtension)}
353
+
354
+ ## Purpose
355
+ ${extractPurpose(content)}
356
+
357
+ ## Key Components
358
+ ${functions.length > 0 ? `
359
+ ### Functions (${functions.length})
360
+ ${functions.map(fn => `- **${fn.name}**: ${fn.description}`).join('\n')}
361
+ ` : ''}
362
+
363
+ ${classes.length > 0 ? `
364
+ ### Classes (${classes.length})
365
+ ${classes.map(cls => `- **${cls.name}**: ${cls.description}`).join('\n')}
366
+ ` : ''}
367
+
368
+ ## Dependencies
369
+ ${imports.length > 0 ? imports.map(imp => `- ${imp}`).join('\n') : 'No external dependencies'}
370
+
371
+ ## Exports
372
+ ${exports.length > 0 ? exports.map(exp => `- ${exp}`).join('\n') : 'No exports'}
373
+
374
+ ## Complexity Metrics
375
+ - Lines of code: ${lines.length}
376
+ - Estimated complexity: ${calculateComplexity(content)}/100
377
+
378
+ ## Notes
379
+ ${extractNotes(content)}`;
380
+ return summary;
381
+ }
382
+ export async function generateApiChunk(content, filePath, ast) {
383
+ let functions = [];
384
+ let classes = [];
385
+ let interfaces = [];
386
+ let types = [];
387
+ const fileExtension = path.extname(filePath);
388
+ if (ast) {
389
+ functions = extractFunctionsFromAST(ast);
390
+ classes = extractClassesFromAST(ast);
391
+ interfaces = extractInterfacesFromAST(ast);
392
+ types = extractTypesFromAST(ast);
393
+ }
394
+ else if (fileExtension === '.cpp' || fileExtension === '.c' || fileExtension === '.h' || fileExtension === '.hpp' || fileExtension === '.cc') {
395
+ functions = extractFunctionsCpp(content);
396
+ classes = extractClassesCpp(content);
397
+ // C++ interfaces/types logic is complex, skipping for now
398
+ }
399
+ else if (fileExtension === '.swift') {
400
+ functions = extractFunctionsSwift(content);
401
+ classes = extractClassesSwift(content);
402
+ // Swift protocols could map to interfaces
403
+ }
404
+ else {
405
+ functions = extractFunctions(content);
406
+ classes = extractClasses(content);
407
+ interfaces = extractInterfaces(content);
408
+ types = extractTypes(content);
409
+ }
410
+ const api = `# API Surface: ${path.basename(filePath)}
411
+
412
+ ## Public Functions
413
+ ${functions.filter(fn => fn.isExported).map(fn => `
414
+ ### ${fn.name}
415
+ \`\`\`typescript
416
+ ${fn.signature}
417
+ \`\`\`
418
+ ${fn.description}
419
+ ${fn.params.length > 0 ? `
420
+ **Parameters:**
421
+ ${fn.params.map(p => `- \`${p.name}\`: ${p.type} - ${p.description}`).join('\n')}
422
+ ` : ''}
423
+ ${fn.returns ? `**Returns:** ${fn.returns}` : ''}
424
+ `).join('\n')}
425
+
426
+ ## Classes
427
+ ${classes.map(cls => `
428
+ ### ${cls.name}
429
+ \`\`\`typescript
430
+ ${cls.signature}
431
+ \`\`\`
432
+ ${cls.description}
433
+ ${cls.methods.length > 0 ? `
434
+ **Methods:**
435
+ ${cls.methods.map(method => `- \`${method.name}\`: ${method.signature}`).join('\n')}
436
+ ` : ''}
437
+ ${cls.properties.length > 0 ? `
438
+ **Properties:**
439
+ ${cls.properties.map(prop => `- \`${prop.name}\`: ${prop.type}`).join('\n')}
440
+ ` : ''}
441
+ `).join('\n')}
442
+
443
+ ## Interfaces
444
+ ${interfaces.map(iface => `
445
+ ### ${iface.name}
446
+ \`\`\`typescript
447
+ ${iface.signature}
448
+ \`\`\`
449
+ ${iface.description}
450
+ `).join('\n')}
451
+
452
+ ## Types
453
+ ${types.map(type => `
454
+ ### ${type.name}
455
+ \`\`\`typescript
456
+ ${type.signature}
457
+ \`\`\`
458
+ ${type.description}
459
+ `).join('\n')}`;
460
+ return api;
461
+ }
462
+ export async function generateInvariantChunk(content, filePath, ast) {
463
+ const invariants = extractInvariants(content);
464
+ const constraints = extractConstraints(content);
465
+ const assumptions = extractAssumptions(content);
466
+ const invariant = `# Invariants: ${path.basename(filePath)}
467
+
468
+ ## Core Invariants
469
+ ${invariants.map(inv => `- **${inv.name}**: ${inv.description}`).join('\n')}
470
+
471
+ ## Constraints
472
+ ${constraints.map(con => `- **${con.name}**: ${con.description}`).join('\n')}
473
+
474
+ ## Assumptions
475
+ ${assumptions.map(ass => `- **${ass.name}**: ${ass.description}`).join('\n')}
476
+
477
+ ## State Management
478
+ ${extractStateManagement(content)}
479
+
480
+ ## Error Handling
481
+ ${extractErrorHandling(content)}
482
+
483
+ ## Performance Considerations
484
+ ${extractPerformanceConsiderations(content)}
485
+
486
+ ## Security Considerations
487
+ ${extractSecurityConsiderations(content)}`;
488
+ return invariant;
489
+ }
490
+ // =============================================================================
491
+ // EXTRACTION HELPERS
492
+ // =============================================================================
493
+ function extractFunctions(content) {
494
+ const functions = [];
495
+ const functionRegex = /(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)\s*:\s*([^;{]+)/g;
496
+ let match;
497
+ while ((match = functionRegex.exec(content)) !== null) {
498
+ functions.push({
499
+ name: match[1],
500
+ signature: match[0],
501
+ isExported: match[0].includes('export'),
502
+ params: parseParameters(match[2]),
503
+ returns: match[3]?.trim() || 'void',
504
+ description: extractFunctionDescription(content, match[1])
505
+ });
506
+ }
507
+ return functions;
508
+ }
509
+ function extractClasses(content) {
510
+ const classes = [];
511
+ const classRegex = /(?:export\s+)?class\s+(\w+)(?:\s+extends\s+(\w+))?\s*{([^}]*)}/g;
512
+ let match;
513
+ while ((match = classRegex.exec(content)) !== null) {
514
+ classes.push({
515
+ name: match[1],
516
+ signature: match[0],
517
+ extends: match[2] || null,
518
+ description: extractClassDescription(content, match[1]),
519
+ methods: extractMethods(match[3]),
520
+ properties: extractProperties(match[3])
521
+ });
522
+ }
523
+ return classes;
524
+ }
525
+ function extractImports(content) {
526
+ const imports = [];
527
+ const importRegex = /import\s+.*?\s+from\s+['"]([^'"]+)['"]/g;
528
+ let match;
529
+ while ((match = importRegex.exec(content)) !== null) {
530
+ imports.push(match[1]);
531
+ }
532
+ return imports;
533
+ }
534
+ function extractExports(content) {
535
+ const exports = [];
536
+ const exportRegex = /export\s+(?:const|let|var|function|class|interface|type)\s+(\w+)/g;
537
+ let match;
538
+ while ((match = exportRegex.exec(content)) !== null) {
539
+ exports.push(match[1]);
540
+ }
541
+ return exports;
542
+ }
543
+ function extractInterfaces(content) {
544
+ const interfaces = [];
545
+ const interfaceRegex = /(?:export\s+)?interface\s+(\w+)\s*{([^}]*)}/g;
546
+ let match;
547
+ while ((match = interfaceRegex.exec(content)) !== null) {
548
+ interfaces.push({
549
+ name: match[1],
550
+ signature: match[0],
551
+ description: extractInterfaceDescription(content, match[1])
552
+ });
553
+ }
554
+ return interfaces;
555
+ }
556
+ function extractTypes(content) {
557
+ const types = [];
558
+ const typeRegex = /(?:export\s+)?type\s+(\w+)\s*=\s*([^;]+)/g;
559
+ let match;
560
+ while ((match = typeRegex.exec(content)) !== null) {
561
+ types.push({
562
+ name: match[1],
563
+ signature: match[0],
564
+ description: extractTypeDescription(content, match[1])
565
+ });
566
+ }
567
+ return types;
568
+ }
569
+ function extractPurpose(content) {
570
+ // Extract purpose from comments or make an educated guess
571
+ const purposeMatch = content.match(/\/\*\*[^*]*\*[^*]*\*\/\s*(?:function|class|const)/s);
572
+ return purposeMatch ? "Documented purpose found in JSDoc" : "Purpose not explicitly documented";
573
+ }
574
+ function extractNotes(content) {
575
+ // Extract important notes from comments
576
+ return "No specific notes found";
577
+ }
578
+ function parseParameters(params) {
579
+ if (!params.trim())
580
+ return [];
581
+ return params.split(',').map(param => {
582
+ const [name, type] = param.trim().split(':').map(s => s.trim());
583
+ return { name: name || '', type: type || 'any', description: '' };
584
+ });
585
+ }
586
+ function extractFunctionDescription(content, functionName) {
587
+ // Extract JSDoc description for function
588
+ const jsdocMatch = content.match(new RegExp(`\\/\\*\\*[^*]*\\*[^*]*\\*\\/\\s*(?:async\\s+)?function\\s+${functionName}`, 's'));
589
+ return jsdocMatch ? "Function has JSDoc documentation" : "No documentation found";
590
+ }
591
+ function extractClassDescription(content, className) {
592
+ // Extract JSDoc description for class
593
+ const jsdocMatch = content.match(new RegExp(`\\/\\*\\*[^*]*\\*[^*]*\\*\\/\\s*class\\s+${className}`, 's'));
594
+ return jsdocMatch ? "Class has JSDoc documentation" : "No documentation found";
595
+ }
596
+ function extractMethods(classBody) {
597
+ // Extract methods from class body
598
+ return [];
599
+ }
600
+ function extractProperties(classBody) {
601
+ // Extract properties from class body
602
+ return [];
603
+ }
604
+ function extractInterfaceDescription(content, interfaceName) {
605
+ return "Interface description not available";
606
+ }
607
+ function extractTypeDescription(content, typeName) {
608
+ return "Type description not available";
609
+ }
610
+ function extractInvariants(content) {
611
+ const invariants = [];
612
+ // Look for validation checks that throw errors
613
+ const throwMatches = content.match(/if\s*\(([^)]+)\)\s*throw\s*new\s*Error\(([^)]+)\)/g);
614
+ if (throwMatches) {
615
+ throwMatches.forEach(m => {
616
+ invariants.push({ name: "Validation Check", description: m });
617
+ });
618
+ }
619
+ // Look for assert calls
620
+ const assertMatches = content.match(/assert\(([^,]+)(?:,\s*["']([^"']+)["'])?\)/g);
621
+ if (assertMatches) {
622
+ assertMatches.forEach(m => {
623
+ invariants.push({ name: "Assertion", description: m });
624
+ });
625
+ }
626
+ return invariants;
627
+ }
628
+ function extractConstraints(content) {
629
+ const constraints = [];
630
+ // Look for UPPERCASE constants which usually denote limits/config
631
+ const constMatches = content.match(/const\s+([A-Z_][A-Z0-9_]*)\s*=\s*([^;]+)/g);
632
+ if (constMatches) {
633
+ constMatches.forEach(m => {
634
+ const parts = m.split('=');
635
+ constraints.push({ name: parts[0].replace('const', '').trim(), description: parts[1].trim() });
636
+ });
637
+ }
638
+ return constraints;
639
+ }
640
+ function extractAssumptions(content) {
641
+ const assumptions = [];
642
+ // Look for comments indicating assumptions
643
+ const commentMatches = content.match(/\/\/\s*(TODO|FIXME|ASSUME|NOTE):\s*(.+)/g);
644
+ if (commentMatches) {
645
+ commentMatches.forEach(m => {
646
+ assumptions.push({ name: "Code Annotation", description: m.replace(/\/\/\s*/, '').trim() });
647
+ });
648
+ }
649
+ return assumptions;
650
+ }
651
+ function extractStateManagement(content) {
652
+ const patterns = [];
653
+ if (content.includes('useState'))
654
+ patterns.push("React useState hook");
655
+ if (content.includes('useReducer'))
656
+ patterns.push("React useReducer hook");
657
+ if (content.includes('this.state'))
658
+ patterns.push("Class component state");
659
+ if (content.includes('redux') || content.includes('dispatch'))
660
+ patterns.push("Redux/Flux pattern");
661
+ if (content.includes('mobx') || content.includes('observable'))
662
+ patterns.push("MobX pattern");
663
+ return patterns.length > 0 ? `Detected patterns: ${patterns.join(', ')}` : "No explicit state management patterns detected";
664
+ }
665
+ function extractErrorHandling(content) {
666
+ const tryCount = (content.match(/try\s*\{/g) || []).length;
667
+ const catchCount = (content.match(/catch\s*(\(|{)/g) || []).length;
668
+ const throwCount = (content.match(/throw\s+new\s+Error/g) || []).length;
669
+ if (tryCount === 0 && throwCount === 0)
670
+ return "No explicit error handling patterns detected";
671
+ return `Error handling metrics: ${tryCount} try-catch blocks, ${throwCount} throw statements`;
672
+ }
673
+ function extractPerformanceConsiderations(content) {
674
+ const patterns = [];
675
+ if (content.includes('useMemo'))
676
+ patterns.push("Uses React.useMemo");
677
+ if (content.includes('useCallback'))
678
+ patterns.push("Uses React.useCallback");
679
+ if (content.match(/await\s+Promise\.all/))
680
+ patterns.push("Uses parallel execution (Promise.all)");
681
+ if (content.match(/for\s*\(.*;.*;.*\)/))
682
+ patterns.push("Contains explicit loops");
683
+ return patterns.length > 0 ? `Performance patterns: ${patterns.join(', ')}` : "No obvious performance optimization patterns detected";
684
+ }
685
+ function extractSecurityConsiderations(content) {
686
+ const risks = [];
687
+ if (content.includes('innerHTML'))
688
+ risks.push("Potential XSS risk (innerHTML usage)");
689
+ if (content.includes('eval('))
690
+ risks.push("Critical security risk (eval usage)");
691
+ if (content.includes('dangerouslySetInnerHTML'))
692
+ risks.push("Explicit React XSS risk");
693
+ return risks.length > 0 ? `Security alerts: ${risks.join(', ')}` : "No obvious security risks detected via static analysis";
694
+ }
695
+ function getFileTypeDescription(extension) {
696
+ const descriptions = {
697
+ '.ts': 'TypeScript source file',
698
+ '.js': 'JavaScript source file',
699
+ '.tsx': 'TypeScript React component',
700
+ '.jsx': 'JavaScript React component',
701
+ '.py': 'Python source file',
702
+ '.go': 'Go source file',
703
+ '.rs': 'Rust source file',
704
+ '.cpp': 'C++ source file',
705
+ '.c': 'C source file'
706
+ };
707
+ return descriptions[extension] || 'Unknown file type';
708
+ }
709
+ export async function extractDependencies(content, ast = null, filePath = '') {
710
+ // Extract dependency information from imports
711
+ if (ast) {
712
+ return extractImportsFromAST(ast);
713
+ }
714
+ if (filePath) {
715
+ const ext = path.extname(filePath);
716
+ if (ext === '.cpp' || ext === '.c' || ext === '.h' || ext === '.hpp' || ext === '.cc') {
717
+ return extractImportsCpp(content);
718
+ }
719
+ if (ext === '.swift') {
720
+ return extractImportsSwift(content);
721
+ }
722
+ }
723
+ return extractImports(content);
724
+ }
725
+ // =============================================================================
726
+ // AST EXTRACTION HELPERS
727
+ // =============================================================================
728
+ export function parseFileAST(filePath, content) {
729
+ if (filePath.endsWith('.ts') || filePath.endsWith('.tsx') || filePath.endsWith('.js') || filePath.endsWith('.jsx')) {
730
+ return ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
731
+ }
732
+ return null;
733
+ }
734
+ function extractFunctionsFromAST(sourceFile) {
735
+ const functions = [];
736
+ function visit(node) {
737
+ if (ts.isFunctionDeclaration(node) && node.name) {
738
+ functions.push({
739
+ name: node.name.text,
740
+ signature: node.getText(sourceFile).split('{')[0].trim(),
741
+ isExported: isNodeExported(node),
742
+ params: node.parameters.map(p => ({
743
+ name: p.name.getText(sourceFile),
744
+ type: p.type ? p.type.getText(sourceFile) : 'any',
745
+ description: ''
746
+ })),
747
+ returns: node.type ? node.type.getText(sourceFile) : 'void',
748
+ description: getJSDocDescription(node, sourceFile)
749
+ });
750
+ }
751
+ ts.forEachChild(node, visit);
752
+ }
753
+ visit(sourceFile);
754
+ return functions;
755
+ }
756
+ function extractClassesFromAST(sourceFile) {
757
+ const classes = [];
758
+ function visit(node) {
759
+ if (ts.isClassDeclaration(node) && node.name) {
760
+ const methods = [];
761
+ const properties = [];
762
+ node.members.forEach(member => {
763
+ if (ts.isMethodDeclaration(member) && member.name) {
764
+ methods.push({
765
+ name: member.name.getText(sourceFile),
766
+ signature: member.getText(sourceFile).split('{')[0].trim()
767
+ });
768
+ }
769
+ else if (ts.isPropertyDeclaration(member) && member.name) {
770
+ properties.push({
771
+ name: member.name.getText(sourceFile),
772
+ type: member.type ? member.type.getText(sourceFile) : 'any'
773
+ });
774
+ }
775
+ });
776
+ classes.push({
777
+ name: node.name.text,
778
+ signature: node.getText(sourceFile).split('{')[0].trim(),
779
+ extends: node.heritageClauses?.find(h => h.token === ts.SyntaxKind.ExtendsKeyword)?.types[0].expression.getText(sourceFile) || null,
780
+ description: getJSDocDescription(node, sourceFile),
781
+ methods,
782
+ properties
783
+ });
784
+ }
785
+ ts.forEachChild(node, visit);
786
+ }
787
+ visit(sourceFile);
788
+ return classes;
789
+ }
790
+ function extractInterfacesFromAST(sourceFile) {
791
+ const interfaces = [];
792
+ function visit(node) {
793
+ if (ts.isInterfaceDeclaration(node)) {
794
+ interfaces.push({
795
+ name: node.name.text,
796
+ signature: node.getText(sourceFile).split('{')[0].trim(),
797
+ description: getJSDocDescription(node, sourceFile)
798
+ });
799
+ }
800
+ ts.forEachChild(node, visit);
801
+ }
802
+ visit(sourceFile);
803
+ return interfaces;
804
+ }
805
+ function extractTypesFromAST(sourceFile) {
806
+ const types = [];
807
+ function visit(node) {
808
+ if (ts.isTypeAliasDeclaration(node)) {
809
+ types.push({
810
+ name: node.name.text,
811
+ signature: node.getText(sourceFile).split('=')[0].trim(),
812
+ description: getJSDocDescription(node, sourceFile)
813
+ });
814
+ }
815
+ ts.forEachChild(node, visit);
816
+ }
817
+ visit(sourceFile);
818
+ return types;
819
+ }
820
+ function extractImportsFromAST(sourceFile) {
821
+ const imports = [];
822
+ function visit(node) {
823
+ if (ts.isImportDeclaration(node)) {
824
+ const moduleSpecifier = node.moduleSpecifier;
825
+ if (ts.isStringLiteral(moduleSpecifier)) {
826
+ imports.push(moduleSpecifier.text);
827
+ }
828
+ }
829
+ ts.forEachChild(node, visit);
830
+ }
831
+ visit(sourceFile);
832
+ return imports;
833
+ }
834
+ function extractExportsFromAST(sourceFile) {
835
+ const exports = [];
836
+ function visit(node) {
837
+ if (isNodeExported(node)) {
838
+ if ((ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node)) && node.name) {
839
+ exports.push(node.name.text);
840
+ }
841
+ else if (ts.isVariableStatement(node)) {
842
+ node.declarationList.declarations.forEach(decl => {
843
+ if (ts.isIdentifier(decl.name)) {
844
+ exports.push(decl.name.text);
845
+ }
846
+ });
847
+ }
848
+ }
849
+ ts.forEachChild(node, visit);
850
+ }
851
+ visit(sourceFile);
852
+ return exports;
853
+ }
854
+ export function extractSymbolsFromAST(sourceFile, content) {
855
+ if (!sourceFile)
856
+ return null;
857
+ const symbols = [];
858
+ function visit(node) {
859
+ if ((ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node)) && node.name) {
860
+ symbols.push(node.name.text);
861
+ }
862
+ else if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name)) {
863
+ symbols.push(node.name.text);
864
+ }
865
+ ts.forEachChild(node, visit);
866
+ }
867
+ visit(sourceFile);
868
+ return symbols;
869
+ }
870
+ function isNodeExported(node) {
871
+ return ((ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Export) !== 0 ||
872
+ (!!node.parent && node.parent.kind === ts.SyntaxKind.SourceFile && ts.isExportAssignment(node)));
873
+ }
874
+ function getJSDocDescription(node, sourceFile) {
875
+ const jsDocTags = node.jsDoc;
876
+ if (jsDocTags && jsDocTags.length > 0) {
877
+ return jsDocTags[0].comment || "Documented in JSDoc";
878
+ }
879
+ return "No documentation found";
880
+ }
881
+ // =============================================================================
882
+ // C++ EXTRACTION HELPERS
883
+ // =============================================================================
884
+ function extractFunctionsCpp(content) {
885
+ const functions = [];
886
+ // Regex for C++ functions: returnType name(params) {
887
+ // Simplistic approximation
888
+ const regex = /((?:[\w:<>_]+\s+)+)(\w+)\s*\(([^)]*)\)\s*(?:const|noexcept|override|final)*\s*\{/g;
889
+ let match;
890
+ while ((match = regex.exec(content)) !== null) {
891
+ const returnType = match[1].trim();
892
+ // Skip if it looks like a control structure
893
+ if (['if', 'for', 'while', 'switch', 'catch'].includes(match[2]))
894
+ continue;
895
+ functions.push({
896
+ name: match[2],
897
+ signature: `${returnType} ${match[2]}(${match[3]})`,
898
+ isExported: true, // Assuming public/header
899
+ params: match[3].split(',').filter(Boolean).map(p => {
900
+ const parts = p.trim().split(/\s+/);
901
+ const name = parts.pop() || '';
902
+ return { name, type: parts.join(' '), description: '' };
903
+ }),
904
+ returns: returnType,
905
+ description: "C++ Function"
906
+ });
907
+ }
908
+ return functions;
909
+ }
910
+ function extractClassesCpp(content) {
911
+ const classes = [];
912
+ const regex = /(class|struct)\s+(\w+)(?:\s*:\s*(?:public|private|protected)\s+([^{]+))?\s*\{/g;
913
+ let match;
914
+ while ((match = regex.exec(content)) !== null) {
915
+ classes.push({
916
+ name: match[2],
917
+ signature: match[0].trim(),
918
+ extends: match[3] ? match[3].trim() : null,
919
+ description: `C++ ${match[1]}`,
920
+ methods: [], // Deep parsing requires more complex logic
921
+ properties: []
922
+ });
923
+ }
924
+ return classes;
925
+ }
926
+ function extractImportsCpp(content) {
927
+ const imports = [];
928
+ const regex = /#include\s*[<"]([^>"]+)[>"]/g;
929
+ let match;
930
+ while ((match = regex.exec(content)) !== null) {
931
+ imports.push(match[1]);
932
+ }
933
+ return imports;
934
+ }
935
+ // =============================================================================
936
+ // SWIFT EXTRACTION HELPERS
937
+ // =============================================================================
938
+ function extractFunctionsSwift(content) {
939
+ const functions = [];
940
+ // Regex for Swift functions: func name(params) -> ReturnType {
941
+ const regex = /(?:public|private|internal|fileprivate|open)?\s*func\s+(\w+)\s*\(([^)]*)\)(?:\s*->\s*([^{]+))?\s*\{/g;
942
+ let match;
943
+ while ((match = regex.exec(content)) !== null) {
944
+ functions.push({
945
+ name: match[1],
946
+ signature: match[0].split('{')[0].trim(),
947
+ isExported: !match[0].includes('private') && !match[0].includes('fileprivate'),
948
+ params: match[2].split(',').filter(Boolean).map(p => {
949
+ const parts = p.trim().split(':');
950
+ return { name: parts[0].trim(), type: parts[1]?.trim() || 'Any', description: '' };
951
+ }),
952
+ returns: match[3]?.trim() || 'Void',
953
+ description: "Swift Function"
954
+ });
955
+ }
956
+ return functions;
957
+ }
958
+ function extractClassesSwift(content) {
959
+ const classes = [];
960
+ const regex = /(?:public|private|internal|fileprivate|open)?\s*(class|struct|enum|extension|protocol)\s+(\w+)(?:\s*:\s*([^{]+))?\s*\{/g;
961
+ let match;
962
+ while ((match = regex.exec(content)) !== null) {
963
+ classes.push({
964
+ name: match[2],
965
+ signature: match[0].trim(),
966
+ extends: match[3] ? match[3].trim() : null,
967
+ description: `Swift ${match[1]}`,
968
+ methods: [],
969
+ properties: []
970
+ });
971
+ }
972
+ return classes;
973
+ }
974
+ function extractImportsSwift(content) {
975
+ const imports = [];
976
+ const regex = /import\s+(\w+)/g;
977
+ let match;
978
+ while ((match = regex.exec(content)) !== null) {
979
+ imports.push(match[1]);
980
+ }
981
+ return imports;
982
+ }