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.
@@ -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;
@@ -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
- const functions = extractFunctions(content);
304
- const classes = extractClasses(content);
305
- const imports = extractImports(content);
306
- const exports = extractExports(content);
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
- const functions = extractFunctions(content);
342
- const classes = extractClasses(content);
343
- const interfaces = extractInterfaces(content);
344
- const types = extractTypes(content);
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
- // Extract invariants from comments and code
547
- return [];
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
- // Extract constraints from comments and code
551
- return [];
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
- // Extract assumptions from comments and code
555
- return [];
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
- return "State management analysis not implemented";
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
- return "Error handling analysis not implemented";
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
- return "Performance considerations not analyzed";
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
- return "Security considerations not analyzed";
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
@@ -1,5 +1,6 @@
1
- export default function plugin(): {
2
- tools: {
3
- [key: string]: any;
1
+ export declare const AutognosisPlugin: () => Promise<{
2
+ tool: {
3
+ [x: string]: any;
4
4
  };
5
- };
5
+ }>;
6
+ export default AutognosisPlugin;
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 default function plugin() {
8
+ export const AutognosisPlugin = async () => {
9
9
  return {
10
- tools: {
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
- // Simplified indexing - would create chunk cards, analyze symbols, etc.
597
- // For now, just touch the file to update its timestamp
598
- const stats = await fs.stat(filePath);
599
- // Indexing logic would go here
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
- // Simulate background indexing work
610
- for (let i = 0; i < 10; i++) {
611
- task.progress = (i + 1) * 10;
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
- await new Promise(resolve => setTimeout(resolve, 1000));
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
- const task = JSON.parse(await fs.readFile(taskPath, 'utf-8'));
625
- task.status = "failed";
626
- task.error = error instanceof Error ? error.message : `${error}`;
627
- task.completed_at = new Date().toISOString();
628
- await fs.writeFile(taskPath, JSON.stringify(task, null, 2));
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.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",