opencode-autognosis 1.0.1 → 2.0.0
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 +3 -0
- package/dist/chunk-cards.js +586 -0
- package/dist/git-worktree.d.ts +3 -0
- package/dist/git-worktree.js +384 -0
- package/dist/index.js +12 -0
- 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 +653 -0
- package/dist/testing-infrastructure.d.ts +3 -0
- package/dist/testing-infrastructure.js +603 -0
- package/package.json +27 -3
|
@@ -0,0 +1,586 @@
|
|
|
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
|
+
const execAsync = promisify(exec);
|
|
9
|
+
const PROJECT_ROOT = process.cwd();
|
|
10
|
+
const OPENCODE_DIR = path.join(PROJECT_ROOT, ".opencode");
|
|
11
|
+
const CHUNK_DIR = path.join(OPENCODE_DIR, "chunks");
|
|
12
|
+
const CACHE_DIR = path.join(OPENCODE_DIR, "cache");
|
|
13
|
+
// Internal logging
|
|
14
|
+
function log(message, data) {
|
|
15
|
+
console.error(`[ChunkCards] ${message}`, data || '');
|
|
16
|
+
}
|
|
17
|
+
// =============================================================================
|
|
18
|
+
// HELPERS
|
|
19
|
+
// =============================================================================
|
|
20
|
+
async function runCmd(cmd, cwd = PROJECT_ROOT, timeoutMs = 30000) {
|
|
21
|
+
try {
|
|
22
|
+
const { stdout, stderr } = await execAsync(cmd, {
|
|
23
|
+
cwd,
|
|
24
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
25
|
+
timeout: timeoutMs
|
|
26
|
+
});
|
|
27
|
+
return { stdout: stdout.trim(), stderr: stderr.trim() };
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
if (error.signal === 'SIGTERM' && error.code === undefined) {
|
|
31
|
+
return { stdout: "", stderr: `Command timed out after ${timeoutMs}ms`, error, timedOut: true };
|
|
32
|
+
}
|
|
33
|
+
return { stdout: "", stderr: error.message, error };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async function ensureChunkDir() {
|
|
37
|
+
await fs.mkdir(CHUNK_DIR, { recursive: true });
|
|
38
|
+
}
|
|
39
|
+
function calculateHash(content) {
|
|
40
|
+
return crypto.createHash('sha256').update(content).digest('hex');
|
|
41
|
+
}
|
|
42
|
+
function calculateComplexity(content) {
|
|
43
|
+
// Simple complexity calculation based on code metrics
|
|
44
|
+
const lines = content.split('\n').length;
|
|
45
|
+
const cyclomaticComplexity = (content.match(/\b(if|while|for|switch|case|catch)\b/g) || []).length;
|
|
46
|
+
const nestingDepth = Math.max(...content.split('\n').map(line => (line.match(/^\s*/)?.[0]?.length || 0)));
|
|
47
|
+
return Math.min(100, (lines * 0.1) + (cyclomaticComplexity * 5) + (nestingDepth * 2));
|
|
48
|
+
}
|
|
49
|
+
function extractSymbols(content) {
|
|
50
|
+
// Extract function names, class names, and variable names
|
|
51
|
+
const symbols = [];
|
|
52
|
+
// Functions
|
|
53
|
+
const functionMatches = content.match(/(?:function|const|let|var)\s+(\w+)\s*=/g);
|
|
54
|
+
if (functionMatches) {
|
|
55
|
+
symbols.push(...functionMatches.map(m => m.split(/\s+/)[1]));
|
|
56
|
+
}
|
|
57
|
+
// Classes
|
|
58
|
+
const classMatches = content.match(/class\s+(\w+)/g);
|
|
59
|
+
if (classMatches) {
|
|
60
|
+
symbols.push(...classMatches.map(m => m.split(/\s+/)[1]));
|
|
61
|
+
}
|
|
62
|
+
// Interfaces/Types
|
|
63
|
+
const typeMatches = content.match(/(?:interface|type)\s+(\w+)/g);
|
|
64
|
+
if (typeMatches) {
|
|
65
|
+
symbols.push(...typeMatches.map(m => m.split(/\s+/)[1]));
|
|
66
|
+
}
|
|
67
|
+
return symbols.filter(s => s && s.length > 0);
|
|
68
|
+
}
|
|
69
|
+
// =============================================================================
|
|
70
|
+
// CHUNK CARDS IMPLEMENTATION
|
|
71
|
+
// =============================================================================
|
|
72
|
+
export function chunkCardsTools() {
|
|
73
|
+
return {
|
|
74
|
+
chunk_create_card: tool({
|
|
75
|
+
description: "Create a Chunk Card for code analysis. Supports summary, API, and invariant card types with automatic metadata extraction.",
|
|
76
|
+
args: {
|
|
77
|
+
file_path: tool.schema.string().describe("Absolute path to the source file"),
|
|
78
|
+
chunk_type: tool.schema.enum(["summary", "api", "invariant"]).describe("Type of chunk card to create"),
|
|
79
|
+
content: tool.schema.string().optional().describe("Custom content (auto-generated if not provided)"),
|
|
80
|
+
force_recreate: tool.schema.boolean().optional().default(false).describe("Force recreation even if card exists")
|
|
81
|
+
},
|
|
82
|
+
async execute({ file_path, chunk_type, content, force_recreate }) {
|
|
83
|
+
log("Tool call: chunk_create_card", { file_path, chunk_type, force_recreate });
|
|
84
|
+
try {
|
|
85
|
+
await ensureChunkDir();
|
|
86
|
+
// Validate file exists
|
|
87
|
+
if (!fsSync.existsSync(file_path)) {
|
|
88
|
+
return JSON.stringify({
|
|
89
|
+
status: "ERROR",
|
|
90
|
+
message: `File not found: ${file_path}`
|
|
91
|
+
}, null, 2);
|
|
92
|
+
}
|
|
93
|
+
// Generate card ID
|
|
94
|
+
const fileHash = calculateHash(file_path);
|
|
95
|
+
const cardId = `${path.basename(file_path)}-${chunk_type}-${fileHash.slice(0, 8)}`;
|
|
96
|
+
const cardPath = path.join(CHUNK_DIR, `${cardId}.json`);
|
|
97
|
+
// Check if card already exists
|
|
98
|
+
if (!force_recreate && fsSync.existsSync(cardPath)) {
|
|
99
|
+
const existingCard = JSON.parse(await fs.readFile(cardPath, 'utf-8'));
|
|
100
|
+
return JSON.stringify({
|
|
101
|
+
status: "EXISTS",
|
|
102
|
+
card: existingCard,
|
|
103
|
+
message: "Card already exists. Use force_recreate=true to override."
|
|
104
|
+
}, null, 2);
|
|
105
|
+
}
|
|
106
|
+
// Read source file if no custom content provided
|
|
107
|
+
let sourceContent = content;
|
|
108
|
+
if (!sourceContent) {
|
|
109
|
+
sourceContent = await fs.readFile(file_path, 'utf-8');
|
|
110
|
+
}
|
|
111
|
+
// Generate chunk content based on type
|
|
112
|
+
let chunkContent = "";
|
|
113
|
+
switch (chunk_type) {
|
|
114
|
+
case "summary":
|
|
115
|
+
chunkContent = await generateSummaryChunk(sourceContent, file_path);
|
|
116
|
+
break;
|
|
117
|
+
case "api":
|
|
118
|
+
chunkContent = await generateApiChunk(sourceContent, file_path);
|
|
119
|
+
break;
|
|
120
|
+
case "invariant":
|
|
121
|
+
chunkContent = await generateInvariantChunk(sourceContent, file_path);
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
// Create chunk card
|
|
125
|
+
const chunkCard = {
|
|
126
|
+
id: cardId,
|
|
127
|
+
file_path,
|
|
128
|
+
chunk_type,
|
|
129
|
+
content: chunkContent,
|
|
130
|
+
metadata: {
|
|
131
|
+
created_at: new Date().toISOString(),
|
|
132
|
+
updated_at: new Date().toISOString(),
|
|
133
|
+
hash: calculateHash(chunkContent),
|
|
134
|
+
dependencies: await extractDependencies(sourceContent),
|
|
135
|
+
symbols: extractSymbols(sourceContent),
|
|
136
|
+
complexity_score: calculateComplexity(sourceContent)
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
// Save chunk card
|
|
140
|
+
await fs.writeFile(cardPath, JSON.stringify(chunkCard, null, 2));
|
|
141
|
+
return JSON.stringify({
|
|
142
|
+
status: "SUCCESS",
|
|
143
|
+
card: chunkCard,
|
|
144
|
+
saved_to: cardPath
|
|
145
|
+
}, null, 2);
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
return JSON.stringify({
|
|
149
|
+
status: "ERROR",
|
|
150
|
+
message: error instanceof Error ? error.message : `${error}`
|
|
151
|
+
}, null, 2);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}),
|
|
155
|
+
chunk_get_card: tool({
|
|
156
|
+
description: "Retrieve a Chunk Card by ID or file path and type.",
|
|
157
|
+
args: {
|
|
158
|
+
card_id: tool.schema.string().optional().describe("Card ID to retrieve"),
|
|
159
|
+
file_path: tool.schema.string().optional().describe("File path to search for cards"),
|
|
160
|
+
chunk_type: tool.schema.enum(["summary", "api", "invariant"]).optional().describe("Filter by chunk type")
|
|
161
|
+
},
|
|
162
|
+
async execute({ card_id, file_path, chunk_type }) {
|
|
163
|
+
log("Tool call: chunk_get_card", { card_id, file_path, chunk_type });
|
|
164
|
+
try {
|
|
165
|
+
await ensureChunkDir();
|
|
166
|
+
if (card_id) {
|
|
167
|
+
// Direct card lookup
|
|
168
|
+
const cardPath = path.join(CHUNK_DIR, `${card_id}.json`);
|
|
169
|
+
if (!fsSync.existsSync(cardPath)) {
|
|
170
|
+
return JSON.stringify({
|
|
171
|
+
status: "NOT_FOUND",
|
|
172
|
+
message: `Card not found: ${card_id}`
|
|
173
|
+
}, null, 2);
|
|
174
|
+
}
|
|
175
|
+
const card = JSON.parse(await fs.readFile(cardPath, 'utf-8'));
|
|
176
|
+
return JSON.stringify({
|
|
177
|
+
status: "SUCCESS",
|
|
178
|
+
card
|
|
179
|
+
}, null, 2);
|
|
180
|
+
}
|
|
181
|
+
else if (file_path) {
|
|
182
|
+
// Search by file path
|
|
183
|
+
const files = await fs.readdir(CHUNK_DIR);
|
|
184
|
+
const matchingCards = [];
|
|
185
|
+
for (const file of files) {
|
|
186
|
+
if (file.endsWith('.json')) {
|
|
187
|
+
const cardPath = path.join(CHUNK_DIR, file);
|
|
188
|
+
const card = JSON.parse(await fs.readFile(cardPath, 'utf-8'));
|
|
189
|
+
if (card.file_path === file_path && (!chunk_type || card.chunk_type === chunk_type)) {
|
|
190
|
+
matchingCards.push(card);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return JSON.stringify({
|
|
195
|
+
status: "SUCCESS",
|
|
196
|
+
cards: matchingCards,
|
|
197
|
+
count: matchingCards.length
|
|
198
|
+
}, null, 2);
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
return JSON.stringify({
|
|
202
|
+
status: "ERROR",
|
|
203
|
+
message: "Either card_id or file_path must be provided"
|
|
204
|
+
}, null, 2);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
return JSON.stringify({
|
|
209
|
+
status: "ERROR",
|
|
210
|
+
message: error instanceof Error ? error.message : `${error}`
|
|
211
|
+
}, null, 2);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}),
|
|
215
|
+
chunk_list_cards: tool({
|
|
216
|
+
description: "List all Chunk Cards with optional filtering and pagination.",
|
|
217
|
+
args: {
|
|
218
|
+
chunk_type: tool.schema.enum(["summary", "api", "invariant"]).optional().describe("Filter by chunk type"),
|
|
219
|
+
file_pattern: tool.schema.string().optional().describe("Filter by file path pattern (regex)"),
|
|
220
|
+
limit: tool.schema.number().optional().default(50).describe("Maximum number of cards to return"),
|
|
221
|
+
offset: tool.schema.number().optional().default(0).describe("Number of cards to skip")
|
|
222
|
+
},
|
|
223
|
+
async execute({ chunk_type, file_pattern, limit, offset }) {
|
|
224
|
+
log("Tool call: chunk_list_cards", { chunk_type, file_pattern, limit, offset });
|
|
225
|
+
try {
|
|
226
|
+
await ensureChunkDir();
|
|
227
|
+
const files = await fs.readdir(CHUNK_DIR);
|
|
228
|
+
const cards = [];
|
|
229
|
+
for (const file of files) {
|
|
230
|
+
if (file.endsWith('.json')) {
|
|
231
|
+
const cardPath = path.join(CHUNK_DIR, file);
|
|
232
|
+
const card = JSON.parse(await fs.readFile(cardPath, 'utf-8'));
|
|
233
|
+
// Apply filters
|
|
234
|
+
if (chunk_type && card.chunk_type !== chunk_type)
|
|
235
|
+
continue;
|
|
236
|
+
if (file_pattern && !new RegExp(file_pattern).test(card.file_path))
|
|
237
|
+
continue;
|
|
238
|
+
cards.push(card);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
// Sort by creation date (newest first)
|
|
242
|
+
cards.sort((a, b) => new Date(b.metadata.created_at).getTime() - new Date(a.metadata.created_at).getTime());
|
|
243
|
+
// Apply pagination
|
|
244
|
+
const paginatedCards = cards.slice(offset, offset + limit);
|
|
245
|
+
return JSON.stringify({
|
|
246
|
+
status: "SUCCESS",
|
|
247
|
+
cards: paginatedCards,
|
|
248
|
+
pagination: {
|
|
249
|
+
total: cards.length,
|
|
250
|
+
limit,
|
|
251
|
+
offset,
|
|
252
|
+
has_more: offset + limit < cards.length
|
|
253
|
+
}
|
|
254
|
+
}, null, 2);
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
return JSON.stringify({
|
|
258
|
+
status: "ERROR",
|
|
259
|
+
message: error instanceof Error ? error.message : `${error}`
|
|
260
|
+
}, null, 2);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}),
|
|
264
|
+
chunk_delete_card: tool({
|
|
265
|
+
description: "Delete a Chunk Card by ID.",
|
|
266
|
+
args: {
|
|
267
|
+
card_id: tool.schema.string().describe("Card ID to delete")
|
|
268
|
+
},
|
|
269
|
+
async execute({ card_id }) {
|
|
270
|
+
log("Tool call: chunk_delete_card", { card_id });
|
|
271
|
+
try {
|
|
272
|
+
const cardPath = path.join(CHUNK_DIR, `${card_id}.json`);
|
|
273
|
+
if (!fsSync.existsSync(cardPath)) {
|
|
274
|
+
return JSON.stringify({
|
|
275
|
+
status: "NOT_FOUND",
|
|
276
|
+
message: `Card not found: ${card_id}`
|
|
277
|
+
}, null, 2);
|
|
278
|
+
}
|
|
279
|
+
await fs.unlink(cardPath);
|
|
280
|
+
return JSON.stringify({
|
|
281
|
+
status: "SUCCESS",
|
|
282
|
+
message: `Card deleted: ${card_id}`
|
|
283
|
+
}, null, 2);
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
return JSON.stringify({
|
|
287
|
+
status: "ERROR",
|
|
288
|
+
message: error instanceof Error ? error.message : `${error}`
|
|
289
|
+
}, null, 2);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
})
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
// =============================================================================
|
|
296
|
+
// CHUNK GENERATION HELPERS
|
|
297
|
+
// =============================================================================
|
|
298
|
+
async function generateSummaryChunk(content, filePath) {
|
|
299
|
+
const lines = content.split('\n');
|
|
300
|
+
const fileName = path.basename(filePath);
|
|
301
|
+
const fileExtension = path.extname(filePath);
|
|
302
|
+
// Extract key information
|
|
303
|
+
const functions = extractFunctions(content);
|
|
304
|
+
const classes = extractClasses(content);
|
|
305
|
+
const imports = extractImports(content);
|
|
306
|
+
const exports = extractExports(content);
|
|
307
|
+
const summary = `# Summary: ${fileName}
|
|
308
|
+
|
|
309
|
+
## File Type
|
|
310
|
+
${fileExtension} - ${getFileTypeDescription(fileExtension)}
|
|
311
|
+
|
|
312
|
+
## Purpose
|
|
313
|
+
${extractPurpose(content)}
|
|
314
|
+
|
|
315
|
+
## Key Components
|
|
316
|
+
${functions.length > 0 ? `
|
|
317
|
+
### Functions (${functions.length})
|
|
318
|
+
${functions.map(fn => `- **${fn.name}**: ${fn.description}`).join('\n')}
|
|
319
|
+
` : ''}
|
|
320
|
+
|
|
321
|
+
${classes.length > 0 ? `
|
|
322
|
+
### Classes (${classes.length})
|
|
323
|
+
${classes.map(cls => `- **${cls.name}**: ${cls.description}`).join('\n')}
|
|
324
|
+
` : ''}
|
|
325
|
+
|
|
326
|
+
## Dependencies
|
|
327
|
+
${imports.length > 0 ? imports.map(imp => `- ${imp}`).join('\n') : 'No external dependencies'}
|
|
328
|
+
|
|
329
|
+
## Exports
|
|
330
|
+
${exports.length > 0 ? exports.map(exp => `- ${exp}`).join('\n') : 'No exports'}
|
|
331
|
+
|
|
332
|
+
## Complexity Metrics
|
|
333
|
+
- Lines of code: ${lines.length}
|
|
334
|
+
- Estimated complexity: ${calculateComplexity(content)}/100
|
|
335
|
+
|
|
336
|
+
## Notes
|
|
337
|
+
${extractNotes(content)}`;
|
|
338
|
+
return summary;
|
|
339
|
+
}
|
|
340
|
+
async function generateApiChunk(content, filePath) {
|
|
341
|
+
const functions = extractFunctions(content);
|
|
342
|
+
const classes = extractClasses(content);
|
|
343
|
+
const interfaces = extractInterfaces(content);
|
|
344
|
+
const types = extractTypes(content);
|
|
345
|
+
const api = `# API Surface: ${path.basename(filePath)}
|
|
346
|
+
|
|
347
|
+
## Public Functions
|
|
348
|
+
${functions.filter(fn => fn.isExported).map(fn => `
|
|
349
|
+
### ${fn.name}
|
|
350
|
+
\`\`\`typescript
|
|
351
|
+
${fn.signature}
|
|
352
|
+
\`\`\`
|
|
353
|
+
${fn.description}
|
|
354
|
+
${fn.params.length > 0 ? `
|
|
355
|
+
**Parameters:**
|
|
356
|
+
${fn.params.map(p => `- \`${p.name}\`: ${p.type} - ${p.description}`).join('\n')}
|
|
357
|
+
` : ''}
|
|
358
|
+
${fn.returns ? `**Returns:** ${fn.returns}` : ''}
|
|
359
|
+
`).join('\n')}
|
|
360
|
+
|
|
361
|
+
## Classes
|
|
362
|
+
${classes.map(cls => `
|
|
363
|
+
### ${cls.name}
|
|
364
|
+
\`\`\`typescript
|
|
365
|
+
${cls.signature}
|
|
366
|
+
\`\`\`
|
|
367
|
+
${cls.description}
|
|
368
|
+
${cls.methods.length > 0 ? `
|
|
369
|
+
**Methods:**
|
|
370
|
+
${cls.methods.map(method => `- \`${method.name}\`: ${method.signature}`).join('\n')}
|
|
371
|
+
` : ''}
|
|
372
|
+
${cls.properties.length > 0 ? `
|
|
373
|
+
**Properties:**
|
|
374
|
+
${cls.properties.map(prop => `- \`${prop.name}\`: ${prop.type}`).join('\n')}
|
|
375
|
+
` : ''}
|
|
376
|
+
`).join('\n')}
|
|
377
|
+
|
|
378
|
+
## Interfaces
|
|
379
|
+
${interfaces.map(iface => `
|
|
380
|
+
### ${iface.name}
|
|
381
|
+
\`\`\`typescript
|
|
382
|
+
${iface.signature}
|
|
383
|
+
\`\`\`
|
|
384
|
+
${iface.description}
|
|
385
|
+
`).join('\n')}
|
|
386
|
+
|
|
387
|
+
## Types
|
|
388
|
+
${types.map(type => `
|
|
389
|
+
### ${type.name}
|
|
390
|
+
\`\`\`typescript
|
|
391
|
+
${type.signature}
|
|
392
|
+
\`\`\`
|
|
393
|
+
${type.description}
|
|
394
|
+
`).join('\n')}`;
|
|
395
|
+
return api;
|
|
396
|
+
}
|
|
397
|
+
async function generateInvariantChunk(content, filePath) {
|
|
398
|
+
const invariants = extractInvariants(content);
|
|
399
|
+
const constraints = extractConstraints(content);
|
|
400
|
+
const assumptions = extractAssumptions(content);
|
|
401
|
+
const invariant = `# Invariants: ${path.basename(filePath)}
|
|
402
|
+
|
|
403
|
+
## Core Invariants
|
|
404
|
+
${invariants.map(inv => `- **${inv.name}**: ${inv.description}`).join('\n')}
|
|
405
|
+
|
|
406
|
+
## Constraints
|
|
407
|
+
${constraints.map(con => `- **${con.name}**: ${con.description}`).join('\n')}
|
|
408
|
+
|
|
409
|
+
## Assumptions
|
|
410
|
+
${assumptions.map(ass => `- **${ass.name}**: ${ass.description}`).join('\n')}
|
|
411
|
+
|
|
412
|
+
## State Management
|
|
413
|
+
${extractStateManagement(content)}
|
|
414
|
+
|
|
415
|
+
## Error Handling
|
|
416
|
+
${extractErrorHandling(content)}
|
|
417
|
+
|
|
418
|
+
## Performance Considerations
|
|
419
|
+
${extractPerformanceConsiderations(content)}
|
|
420
|
+
|
|
421
|
+
## Security Considerations
|
|
422
|
+
${extractSecurityConsiderations(content)}`;
|
|
423
|
+
return invariant;
|
|
424
|
+
}
|
|
425
|
+
// =============================================================================
|
|
426
|
+
// EXTRACTION HELPERS
|
|
427
|
+
// =============================================================================
|
|
428
|
+
function extractFunctions(content) {
|
|
429
|
+
const functions = [];
|
|
430
|
+
const functionRegex = /(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)\s*:\s*([^;{]+)/g;
|
|
431
|
+
let match;
|
|
432
|
+
while ((match = functionRegex.exec(content)) !== null) {
|
|
433
|
+
functions.push({
|
|
434
|
+
name: match[1],
|
|
435
|
+
signature: match[0],
|
|
436
|
+
isExported: match[0].includes('export'),
|
|
437
|
+
params: parseParameters(match[2]),
|
|
438
|
+
returns: match[3]?.trim() || 'void',
|
|
439
|
+
description: extractFunctionDescription(content, match[1])
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
return functions;
|
|
443
|
+
}
|
|
444
|
+
function extractClasses(content) {
|
|
445
|
+
const classes = [];
|
|
446
|
+
const classRegex = /(?:export\s+)?class\s+(\w+)(?:\s+extends\s+(\w+))?\s*{([^}]*)}/g;
|
|
447
|
+
let match;
|
|
448
|
+
while ((match = classRegex.exec(content)) !== null) {
|
|
449
|
+
classes.push({
|
|
450
|
+
name: match[1],
|
|
451
|
+
signature: match[0],
|
|
452
|
+
extends: match[2] || null,
|
|
453
|
+
description: extractClassDescription(content, match[1]),
|
|
454
|
+
methods: extractMethods(match[3]),
|
|
455
|
+
properties: extractProperties(match[3])
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
return classes;
|
|
459
|
+
}
|
|
460
|
+
function extractImports(content) {
|
|
461
|
+
const imports = [];
|
|
462
|
+
const importRegex = /import\s+.*?\s+from\s+['"]([^'"]+)['"]/g;
|
|
463
|
+
let match;
|
|
464
|
+
while ((match = importRegex.exec(content)) !== null) {
|
|
465
|
+
imports.push(match[1]);
|
|
466
|
+
}
|
|
467
|
+
return imports;
|
|
468
|
+
}
|
|
469
|
+
function extractExports(content) {
|
|
470
|
+
const exports = [];
|
|
471
|
+
const exportRegex = /export\s+(?:const|let|var|function|class|interface|type)\s+(\w+)/g;
|
|
472
|
+
let match;
|
|
473
|
+
while ((match = exportRegex.exec(content)) !== null) {
|
|
474
|
+
exports.push(match[1]);
|
|
475
|
+
}
|
|
476
|
+
return exports;
|
|
477
|
+
}
|
|
478
|
+
function extractInterfaces(content) {
|
|
479
|
+
const interfaces = [];
|
|
480
|
+
const interfaceRegex = /(?:export\s+)?interface\s+(\w+)\s*{([^}]*)}/g;
|
|
481
|
+
let match;
|
|
482
|
+
while ((match = interfaceRegex.exec(content)) !== null) {
|
|
483
|
+
interfaces.push({
|
|
484
|
+
name: match[1],
|
|
485
|
+
signature: match[0],
|
|
486
|
+
description: extractInterfaceDescription(content, match[1])
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
return interfaces;
|
|
490
|
+
}
|
|
491
|
+
function extractTypes(content) {
|
|
492
|
+
const types = [];
|
|
493
|
+
const typeRegex = /(?:export\s+)?type\s+(\w+)\s*=\s*([^;]+)/g;
|
|
494
|
+
let match;
|
|
495
|
+
while ((match = typeRegex.exec(content)) !== null) {
|
|
496
|
+
types.push({
|
|
497
|
+
name: match[1],
|
|
498
|
+
signature: match[0],
|
|
499
|
+
description: extractTypeDescription(content, match[1])
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
return types;
|
|
503
|
+
}
|
|
504
|
+
function extractPurpose(content) {
|
|
505
|
+
// Extract purpose from comments or make an educated guess
|
|
506
|
+
const purposeMatch = content.match(/\/\*\*[^*]*\*[^*]*\*\/\s*(?:function|class|const)/s);
|
|
507
|
+
return purposeMatch ? "Documented purpose found in JSDoc" : "Purpose not explicitly documented";
|
|
508
|
+
}
|
|
509
|
+
function extractNotes(content) {
|
|
510
|
+
// Extract important notes from comments
|
|
511
|
+
return "No specific notes found";
|
|
512
|
+
}
|
|
513
|
+
function parseParameters(params) {
|
|
514
|
+
if (!params.trim())
|
|
515
|
+
return [];
|
|
516
|
+
return params.split(',').map(param => {
|
|
517
|
+
const [name, type] = param.trim().split(':').map(s => s.trim());
|
|
518
|
+
return { name: name || '', type: type || 'any', description: '' };
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
function extractFunctionDescription(content, functionName) {
|
|
522
|
+
// Extract JSDoc description for function
|
|
523
|
+
const jsdocMatch = content.match(new RegExp(`\\/\\*\\*[^*]*\\*[^*]*\\*\\/\\s*(?:async\\s+)?function\\s+${functionName}`, 's'));
|
|
524
|
+
return jsdocMatch ? "Function has JSDoc documentation" : "No documentation found";
|
|
525
|
+
}
|
|
526
|
+
function extractClassDescription(content, className) {
|
|
527
|
+
// Extract JSDoc description for class
|
|
528
|
+
const jsdocMatch = content.match(new RegExp(`\\/\\*\\*[^*]*\\*[^*]*\\*\\/\\s*class\\s+${className}`, 's'));
|
|
529
|
+
return jsdocMatch ? "Class has JSDoc documentation" : "No documentation found";
|
|
530
|
+
}
|
|
531
|
+
function extractMethods(classBody) {
|
|
532
|
+
// Extract methods from class body
|
|
533
|
+
return [];
|
|
534
|
+
}
|
|
535
|
+
function extractProperties(classBody) {
|
|
536
|
+
// Extract properties from class body
|
|
537
|
+
return [];
|
|
538
|
+
}
|
|
539
|
+
function extractInterfaceDescription(content, interfaceName) {
|
|
540
|
+
return "Interface description not available";
|
|
541
|
+
}
|
|
542
|
+
function extractTypeDescription(content, typeName) {
|
|
543
|
+
return "Type description not available";
|
|
544
|
+
}
|
|
545
|
+
function extractInvariants(content) {
|
|
546
|
+
// Extract invariants from comments and code
|
|
547
|
+
return [];
|
|
548
|
+
}
|
|
549
|
+
function extractConstraints(content) {
|
|
550
|
+
// Extract constraints from comments and code
|
|
551
|
+
return [];
|
|
552
|
+
}
|
|
553
|
+
function extractAssumptions(content) {
|
|
554
|
+
// Extract assumptions from comments and code
|
|
555
|
+
return [];
|
|
556
|
+
}
|
|
557
|
+
function extractStateManagement(content) {
|
|
558
|
+
return "State management analysis not implemented";
|
|
559
|
+
}
|
|
560
|
+
function extractErrorHandling(content) {
|
|
561
|
+
return "Error handling analysis not implemented";
|
|
562
|
+
}
|
|
563
|
+
function extractPerformanceConsiderations(content) {
|
|
564
|
+
return "Performance considerations not analyzed";
|
|
565
|
+
}
|
|
566
|
+
function extractSecurityConsiderations(content) {
|
|
567
|
+
return "Security considerations not analyzed";
|
|
568
|
+
}
|
|
569
|
+
function getFileTypeDescription(extension) {
|
|
570
|
+
const descriptions = {
|
|
571
|
+
'.ts': 'TypeScript source file',
|
|
572
|
+
'.js': 'JavaScript source file',
|
|
573
|
+
'.tsx': 'TypeScript React component',
|
|
574
|
+
'.jsx': 'JavaScript React component',
|
|
575
|
+
'.py': 'Python source file',
|
|
576
|
+
'.go': 'Go source file',
|
|
577
|
+
'.rs': 'Rust source file',
|
|
578
|
+
'.cpp': 'C++ source file',
|
|
579
|
+
'.c': 'C source file'
|
|
580
|
+
};
|
|
581
|
+
return descriptions[extension] || 'Unknown file type';
|
|
582
|
+
}
|
|
583
|
+
async function extractDependencies(content) {
|
|
584
|
+
// Extract dependency information from imports
|
|
585
|
+
return extractImports(content);
|
|
586
|
+
}
|