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.
- package/README.md +346 -76
- package/dist/activeset.d.ts +3 -0
- package/dist/activeset.js +498 -0
- package/dist/chunk-cards.d.ts +29 -0
- package/dist/chunk-cards.js +982 -0
- package/dist/git-worktree.d.ts +3 -0
- package/dist/git-worktree.js +384 -0
- package/dist/index.d.ts +5 -4
- package/dist/index.js +16 -3
- package/dist/module-summaries.d.ts +3 -0
- package/dist/module-summaries.js +510 -0
- package/dist/performance-optimization.d.ts +3 -0
- package/dist/performance-optimization.js +748 -0
- package/dist/testing-infrastructure.d.ts +3 -0
- package/dist/testing-infrastructure.js +603 -0
- package/package.json +27 -3
|
@@ -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
|
+
}
|