opencode-autognosis 2.0.0 → 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/dist/chunk-cards.d.ts +26 -0
- package/dist/chunk-cards.js +428 -32
- package/dist/index.d.ts +5 -4
- package/dist/index.js +4 -3
- package/dist/performance-optimization.js +108 -13
- package/package.json +1 -1
package/dist/chunk-cards.d.ts
CHANGED
|
@@ -1,3 +1,29 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
export declare const CHUNK_DIR: string;
|
|
3
|
+
export interface ChunkCard {
|
|
4
|
+
id: string;
|
|
5
|
+
file_path: string;
|
|
6
|
+
chunk_type: "summary" | "api" | "invariant";
|
|
7
|
+
content: string;
|
|
8
|
+
metadata: {
|
|
9
|
+
created_at: string;
|
|
10
|
+
updated_at: string;
|
|
11
|
+
hash: string;
|
|
12
|
+
dependencies: string[];
|
|
13
|
+
symbols: string[];
|
|
14
|
+
complexity_score: number;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export declare function ensureChunkDir(): Promise<void>;
|
|
18
|
+
export declare function calculateHash(content: string): string;
|
|
19
|
+
export declare function calculateComplexity(content: string): number;
|
|
20
|
+
export declare function extractSymbols(content: string, filePath?: string): string[];
|
|
1
21
|
export declare function chunkCardsTools(): {
|
|
2
22
|
[key: string]: any;
|
|
3
23
|
};
|
|
24
|
+
export declare function generateSummaryChunk(content: string, filePath: string, ast: ts.SourceFile | null): Promise<string>;
|
|
25
|
+
export declare function generateApiChunk(content: string, filePath: string, ast: ts.SourceFile | null): Promise<string>;
|
|
26
|
+
export declare function generateInvariantChunk(content: string, filePath: string, ast: ts.SourceFile | null): Promise<string>;
|
|
27
|
+
export declare function extractDependencies(content: string, ast?: ts.SourceFile | null, filePath?: string): Promise<string[]>;
|
|
28
|
+
export declare function parseFileAST(filePath: string, content: string): ts.SourceFile | null;
|
|
29
|
+
export declare function extractSymbolsFromAST(sourceFile: ts.SourceFile | null, content: string): string[] | null;
|
package/dist/chunk-cards.js
CHANGED
|
@@ -5,10 +5,11 @@ import * as fsSync from "node:fs";
|
|
|
5
5
|
import * as path from "node:path";
|
|
6
6
|
import { promisify } from "node:util";
|
|
7
7
|
import * as crypto from "node:crypto";
|
|
8
|
+
import ts from "typescript";
|
|
8
9
|
const execAsync = promisify(exec);
|
|
9
10
|
const PROJECT_ROOT = process.cwd();
|
|
10
11
|
const OPENCODE_DIR = path.join(PROJECT_ROOT, ".opencode");
|
|
11
|
-
const CHUNK_DIR = path.join(OPENCODE_DIR, "chunks");
|
|
12
|
+
export const CHUNK_DIR = path.join(OPENCODE_DIR, "chunks");
|
|
12
13
|
const CACHE_DIR = path.join(OPENCODE_DIR, "cache");
|
|
13
14
|
// Internal logging
|
|
14
15
|
function log(message, data) {
|
|
@@ -33,22 +34,39 @@ async function runCmd(cmd, cwd = PROJECT_ROOT, timeoutMs = 30000) {
|
|
|
33
34
|
return { stdout: "", stderr: error.message, error };
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
|
-
async function ensureChunkDir() {
|
|
37
|
+
export async function ensureChunkDir() {
|
|
37
38
|
await fs.mkdir(CHUNK_DIR, { recursive: true });
|
|
38
39
|
}
|
|
39
|
-
function calculateHash(content) {
|
|
40
|
+
export function calculateHash(content) {
|
|
40
41
|
return crypto.createHash('sha256').update(content).digest('hex');
|
|
41
42
|
}
|
|
42
|
-
function calculateComplexity(content) {
|
|
43
|
+
export function calculateComplexity(content) {
|
|
43
44
|
// Simple complexity calculation based on code metrics
|
|
44
45
|
const lines = content.split('\n').length;
|
|
45
46
|
const cyclomaticComplexity = (content.match(/\b(if|while|for|switch|case|catch)\b/g) || []).length;
|
|
46
47
|
const nestingDepth = Math.max(...content.split('\n').map(line => (line.match(/^\s*/)?.[0]?.length || 0)));
|
|
47
48
|
return Math.min(100, (lines * 0.1) + (cyclomaticComplexity * 5) + (nestingDepth * 2));
|
|
48
49
|
}
|
|
49
|
-
function extractSymbols(content) {
|
|
50
|
+
export function extractSymbols(content, filePath = '') {
|
|
50
51
|
// Extract function names, class names, and variable names
|
|
51
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
|
+
}
|
|
52
70
|
// Functions
|
|
53
71
|
const functionMatches = content.match(/(?:function|const|let|var)\s+(\w+)\s*=/g);
|
|
54
72
|
if (functionMatches) {
|
|
@@ -108,17 +126,19 @@ export function chunkCardsTools() {
|
|
|
108
126
|
if (!sourceContent) {
|
|
109
127
|
sourceContent = await fs.readFile(file_path, 'utf-8');
|
|
110
128
|
}
|
|
129
|
+
// Parse AST for JS/TS files
|
|
130
|
+
const ast = parseFileAST(file_path, sourceContent);
|
|
111
131
|
// Generate chunk content based on type
|
|
112
132
|
let chunkContent = "";
|
|
113
133
|
switch (chunk_type) {
|
|
114
134
|
case "summary":
|
|
115
|
-
chunkContent = await generateSummaryChunk(sourceContent, file_path);
|
|
135
|
+
chunkContent = await generateSummaryChunk(sourceContent, file_path, ast);
|
|
116
136
|
break;
|
|
117
137
|
case "api":
|
|
118
|
-
chunkContent = await generateApiChunk(sourceContent, file_path);
|
|
138
|
+
chunkContent = await generateApiChunk(sourceContent, file_path, ast);
|
|
119
139
|
break;
|
|
120
140
|
case "invariant":
|
|
121
|
-
chunkContent = await generateInvariantChunk(sourceContent, file_path);
|
|
141
|
+
chunkContent = await generateInvariantChunk(sourceContent, file_path, ast);
|
|
122
142
|
break;
|
|
123
143
|
}
|
|
124
144
|
// Create chunk card
|
|
@@ -131,8 +151,8 @@ export function chunkCardsTools() {
|
|
|
131
151
|
created_at: new Date().toISOString(),
|
|
132
152
|
updated_at: new Date().toISOString(),
|
|
133
153
|
hash: calculateHash(chunkContent),
|
|
134
|
-
dependencies: await extractDependencies(sourceContent),
|
|
135
|
-
symbols: extractSymbols(sourceContent),
|
|
154
|
+
dependencies: await extractDependencies(sourceContent, ast, file_path),
|
|
155
|
+
symbols: extractSymbolsFromAST(ast, sourceContent) || extractSymbols(sourceContent, file_path),
|
|
136
156
|
complexity_score: calculateComplexity(sourceContent)
|
|
137
157
|
}
|
|
138
158
|
};
|
|
@@ -295,15 +315,37 @@ export function chunkCardsTools() {
|
|
|
295
315
|
// =============================================================================
|
|
296
316
|
// CHUNK GENERATION HELPERS
|
|
297
317
|
// =============================================================================
|
|
298
|
-
async function generateSummaryChunk(content, filePath) {
|
|
318
|
+
export async function generateSummaryChunk(content, filePath, ast) {
|
|
299
319
|
const lines = content.split('\n');
|
|
300
320
|
const fileName = path.basename(filePath);
|
|
301
321
|
const fileExtension = path.extname(filePath);
|
|
302
322
|
// Extract key information
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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
|
+
}
|
|
307
349
|
const summary = `# Summary: ${fileName}
|
|
308
350
|
|
|
309
351
|
## File Type
|
|
@@ -337,11 +379,34 @@ ${exports.length > 0 ? exports.map(exp => `- ${exp}`).join('\n') : 'No exports'}
|
|
|
337
379
|
${extractNotes(content)}`;
|
|
338
380
|
return summary;
|
|
339
381
|
}
|
|
340
|
-
async function generateApiChunk(content, filePath) {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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
|
+
}
|
|
345
410
|
const api = `# API Surface: ${path.basename(filePath)}
|
|
346
411
|
|
|
347
412
|
## Public Functions
|
|
@@ -394,7 +459,7 @@ ${type.description}
|
|
|
394
459
|
`).join('\n')}`;
|
|
395
460
|
return api;
|
|
396
461
|
}
|
|
397
|
-
async function generateInvariantChunk(content, filePath) {
|
|
462
|
+
export async function generateInvariantChunk(content, filePath, ast) {
|
|
398
463
|
const invariants = extractInvariants(content);
|
|
399
464
|
const constraints = extractConstraints(content);
|
|
400
465
|
const assumptions = extractAssumptions(content);
|
|
@@ -543,28 +608,89 @@ function extractTypeDescription(content, typeName) {
|
|
|
543
608
|
return "Type description not available";
|
|
544
609
|
}
|
|
545
610
|
function extractInvariants(content) {
|
|
546
|
-
|
|
547
|
-
|
|
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;
|
|
548
627
|
}
|
|
549
628
|
function extractConstraints(content) {
|
|
550
|
-
|
|
551
|
-
|
|
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;
|
|
552
639
|
}
|
|
553
640
|
function extractAssumptions(content) {
|
|
554
|
-
|
|
555
|
-
|
|
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;
|
|
556
650
|
}
|
|
557
651
|
function extractStateManagement(content) {
|
|
558
|
-
|
|
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";
|
|
559
664
|
}
|
|
560
665
|
function extractErrorHandling(content) {
|
|
561
|
-
|
|
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`;
|
|
562
672
|
}
|
|
563
673
|
function extractPerformanceConsiderations(content) {
|
|
564
|
-
|
|
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";
|
|
565
684
|
}
|
|
566
685
|
function extractSecurityConsiderations(content) {
|
|
567
|
-
|
|
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";
|
|
568
694
|
}
|
|
569
695
|
function getFileTypeDescription(extension) {
|
|
570
696
|
const descriptions = {
|
|
@@ -580,7 +706,277 @@ function getFileTypeDescription(extension) {
|
|
|
580
706
|
};
|
|
581
707
|
return descriptions[extension] || 'Unknown file type';
|
|
582
708
|
}
|
|
583
|
-
async function extractDependencies(content) {
|
|
709
|
+
export async function extractDependencies(content, ast = null, filePath = '') {
|
|
584
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
|
+
}
|
|
585
723
|
return extractImports(content);
|
|
586
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
|
+
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -5,9 +5,9 @@ import { chunkCardsTools } from "./chunk-cards.js";
|
|
|
5
5
|
import { activeSetTools } from "./activeset.js";
|
|
6
6
|
import { moduleSummariesTools } from "./module-summaries.js";
|
|
7
7
|
import { performanceTools } from "./performance-optimization.js";
|
|
8
|
-
export
|
|
8
|
+
export const AutognosisPlugin = async () => {
|
|
9
9
|
return {
|
|
10
|
-
|
|
10
|
+
tool: {
|
|
11
11
|
...systemTools(),
|
|
12
12
|
...gitWorktreeTools(),
|
|
13
13
|
...testingTools(),
|
|
@@ -17,4 +17,5 @@ export default function plugin() {
|
|
|
17
17
|
...performanceTools(),
|
|
18
18
|
},
|
|
19
19
|
};
|
|
20
|
-
}
|
|
20
|
+
};
|
|
21
|
+
export default AutognosisPlugin;
|
|
@@ -5,6 +5,7 @@ import * as fsSync from "node:fs";
|
|
|
5
5
|
import * as path from "node:path";
|
|
6
6
|
import { promisify } from "node:util";
|
|
7
7
|
import * as crypto from "node:crypto";
|
|
8
|
+
import { CHUNK_DIR, ensureChunkDir, calculateHash, calculateComplexity, parseFileAST, generateSummaryChunk, generateApiChunk, generateInvariantChunk, extractDependencies, extractSymbolsFromAST, extractSymbols } from "./chunk-cards.js";
|
|
8
9
|
const execAsync = promisify(exec);
|
|
9
10
|
const PROJECT_ROOT = process.cwd();
|
|
10
11
|
const OPENCODE_DIR = path.join(PROJECT_ROOT, ".opencode");
|
|
@@ -593,10 +594,43 @@ async function getAllSourceFiles() {
|
|
|
593
594
|
return sourceFiles;
|
|
594
595
|
}
|
|
595
596
|
async function indexFile(filePath) {
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
597
|
+
try {
|
|
598
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
599
|
+
await ensureChunkDir();
|
|
600
|
+
const ast = parseFileAST(filePath, content);
|
|
601
|
+
const chunkTypes = ["summary", "api", "invariant"];
|
|
602
|
+
// Generate file hash for ID consistency
|
|
603
|
+
const fileHash = calculateHash(filePath);
|
|
604
|
+
for (const chunkType of chunkTypes) {
|
|
605
|
+
const cardId = `${path.basename(filePath)}-${chunkType}-${fileHash.slice(0, 8)}`;
|
|
606
|
+
const cardPath = path.join(CHUNK_DIR, `${cardId}.json`);
|
|
607
|
+
let chunkContent = "";
|
|
608
|
+
if (chunkType === "summary")
|
|
609
|
+
chunkContent = await generateSummaryChunk(content, filePath, ast);
|
|
610
|
+
else if (chunkType === "api")
|
|
611
|
+
chunkContent = await generateApiChunk(content, filePath, ast);
|
|
612
|
+
else if (chunkType === "invariant")
|
|
613
|
+
chunkContent = await generateInvariantChunk(content, filePath, ast);
|
|
614
|
+
const chunkCard = {
|
|
615
|
+
id: cardId,
|
|
616
|
+
file_path: filePath,
|
|
617
|
+
chunk_type: chunkType,
|
|
618
|
+
content: chunkContent,
|
|
619
|
+
metadata: {
|
|
620
|
+
created_at: new Date().toISOString(),
|
|
621
|
+
updated_at: new Date().toISOString(),
|
|
622
|
+
hash: calculateHash(chunkContent),
|
|
623
|
+
dependencies: await extractDependencies(content, ast, filePath),
|
|
624
|
+
symbols: extractSymbolsFromAST(ast, content) || extractSymbols(content, filePath),
|
|
625
|
+
complexity_score: calculateComplexity(content)
|
|
626
|
+
}
|
|
627
|
+
};
|
|
628
|
+
await fs.writeFile(cardPath, JSON.stringify(chunkCard, null, 2));
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
catch (error) {
|
|
632
|
+
log(`Failed to index file ${filePath}`, error);
|
|
633
|
+
}
|
|
600
634
|
}
|
|
601
635
|
async function runBackgroundIndexing(taskId, indexingState) {
|
|
602
636
|
try {
|
|
@@ -606,11 +640,65 @@ async function runBackgroundIndexing(taskId, indexingState) {
|
|
|
606
640
|
task.status = "running";
|
|
607
641
|
task.started_at = new Date().toISOString();
|
|
608
642
|
await fs.writeFile(taskPath, JSON.stringify(task, null, 2));
|
|
609
|
-
//
|
|
610
|
-
|
|
611
|
-
|
|
643
|
+
// Determine files to index
|
|
644
|
+
const force_full = task.metadata?.force_full || false;
|
|
645
|
+
let filesToIndex = [];
|
|
646
|
+
if (force_full) {
|
|
647
|
+
filesToIndex = await getAllSourceFiles();
|
|
648
|
+
}
|
|
649
|
+
else {
|
|
650
|
+
// For incremental, we try to use git diff. If that fails or returns empty,
|
|
651
|
+
// we might default to all files or just recent ones. For robustness here:
|
|
652
|
+
try {
|
|
653
|
+
const { stdout: gitDiff } = await runCmd(`git diff --name-only --since="${indexingState.last_indexed}"`);
|
|
654
|
+
const changedFiles = gitDiff.split('\n').filter(Boolean);
|
|
655
|
+
if (changedFiles.length > 0) {
|
|
656
|
+
filesToIndex = changedFiles;
|
|
657
|
+
}
|
|
658
|
+
else {
|
|
659
|
+
// If no changes detected by git, maybe we don't need to do anything?
|
|
660
|
+
// But if forced or state is stale, maybe we should.
|
|
661
|
+
// For background task simplicity, if not full, and no git changes, we index nothing or check simple timestamps.
|
|
662
|
+
// Let's rely on getAllSourceFiles filtering if we wanted robust check.
|
|
663
|
+
// Here, we'll just check timestamps of all source files against last_indexed.
|
|
664
|
+
const allFiles = await getAllSourceFiles();
|
|
665
|
+
filesToIndex = [];
|
|
666
|
+
for (const f of allFiles) {
|
|
667
|
+
const fp = path.join(PROJECT_ROOT, f);
|
|
668
|
+
if (fsSync.existsSync(fp)) {
|
|
669
|
+
const stats = await fs.stat(fp);
|
|
670
|
+
if (stats.mtime.toISOString() > indexingState.last_indexed) {
|
|
671
|
+
filesToIndex.push(f);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
catch (e) {
|
|
678
|
+
// Fallback to full scan if git fails
|
|
679
|
+
filesToIndex = await getAllSourceFiles();
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
const total = filesToIndex.length;
|
|
683
|
+
let processed = 0;
|
|
684
|
+
if (total === 0) {
|
|
685
|
+
task.progress = 100;
|
|
686
|
+
task.status = "completed";
|
|
687
|
+
task.completed_at = new Date().toISOString();
|
|
612
688
|
await fs.writeFile(taskPath, JSON.stringify(task, null, 2));
|
|
613
|
-
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
for (const file of filesToIndex) {
|
|
692
|
+
const filePath = path.join(PROJECT_ROOT, file);
|
|
693
|
+
if (fsSync.existsSync(filePath)) {
|
|
694
|
+
await indexFile(filePath);
|
|
695
|
+
}
|
|
696
|
+
processed++;
|
|
697
|
+
// Update progress periodically
|
|
698
|
+
if (processed % 5 === 0 || processed === total) {
|
|
699
|
+
task.progress = Math.round((processed / total) * 100);
|
|
700
|
+
await fs.writeFile(taskPath, JSON.stringify(task, null, 2));
|
|
701
|
+
}
|
|
614
702
|
}
|
|
615
703
|
// Complete task
|
|
616
704
|
task.status = "completed";
|
|
@@ -621,11 +709,18 @@ async function runBackgroundIndexing(taskId, indexingState) {
|
|
|
621
709
|
catch (error) {
|
|
622
710
|
// Update task with error
|
|
623
711
|
const taskPath = path.join(PERF_DIR, `${taskId}.json`);
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
712
|
+
try {
|
|
713
|
+
if (fsSync.existsSync(taskPath)) {
|
|
714
|
+
const task = JSON.parse(await fs.readFile(taskPath, 'utf-8'));
|
|
715
|
+
task.status = "failed";
|
|
716
|
+
task.error = error instanceof Error ? error.message : `${error}`;
|
|
717
|
+
task.completed_at = new Date().toISOString();
|
|
718
|
+
await fs.writeFile(taskPath, JSON.stringify(task, null, 2));
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
catch (writeError) {
|
|
722
|
+
console.error("Failed to update task error state", writeError);
|
|
723
|
+
}
|
|
629
724
|
}
|
|
630
725
|
}
|
|
631
726
|
function calculateMetricsSummary(metrics) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-autognosis",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "Advanced RAG-powered codebase awareness for OpenCode agents. Features Chunk Cards synthesis, hierarchical reasoning, ActiveSet working memory, and performance optimization for enterprise-scale repositories.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|