legacyver 2.1.0 → 2.1.2

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.
Files changed (126) hide show
  1. package/.agent/skills/openspec-apply-change/SKILL.md +156 -0
  2. package/.agent/skills/openspec-archive-change/SKILL.md +114 -0
  3. package/.agent/skills/openspec-bulk-archive-change/SKILL.md +246 -0
  4. package/.agent/skills/openspec-continue-change/SKILL.md +118 -0
  5. package/.agent/skills/openspec-explore/SKILL.md +290 -0
  6. package/.agent/skills/openspec-ff-change/SKILL.md +101 -0
  7. package/.agent/skills/openspec-new-change/SKILL.md +74 -0
  8. package/.agent/skills/openspec-onboard/SKILL.md +529 -0
  9. package/.agent/skills/openspec-sync-specs/SKILL.md +138 -0
  10. package/.agent/skills/openspec-verify-change/SKILL.md +168 -0
  11. package/.agent/workflows/opsx-apply.md +149 -0
  12. package/.agent/workflows/opsx-archive.md +154 -0
  13. package/.agent/workflows/opsx-bulk-archive.md +239 -0
  14. package/.agent/workflows/opsx-continue.md +111 -0
  15. package/.agent/workflows/opsx-explore.md +171 -0
  16. package/.agent/workflows/opsx-ff.md +91 -0
  17. package/.agent/workflows/opsx-new.md +66 -0
  18. package/.agent/workflows/opsx-onboard.md +522 -0
  19. package/.agent/workflows/opsx-sync.md +131 -0
  20. package/.agent/workflows/opsx-verify.md +161 -0
  21. package/.github/prompts/opsx-apply.prompt.md +149 -0
  22. package/.github/prompts/opsx-archive.prompt.md +154 -0
  23. package/.github/prompts/opsx-bulk-archive.prompt.md +239 -0
  24. package/.github/prompts/opsx-continue.prompt.md +111 -0
  25. package/.github/prompts/opsx-explore.prompt.md +171 -0
  26. package/.github/prompts/opsx-ff.prompt.md +91 -0
  27. package/.github/prompts/opsx-new.prompt.md +66 -0
  28. package/.github/prompts/opsx-onboard.prompt.md +522 -0
  29. package/.github/prompts/opsx-sync.prompt.md +131 -0
  30. package/.github/prompts/opsx-verify.prompt.md +161 -0
  31. package/.github/skills/openspec-apply-change/SKILL.md +156 -0
  32. package/.github/skills/openspec-archive-change/SKILL.md +114 -0
  33. package/.github/skills/openspec-bulk-archive-change/SKILL.md +246 -0
  34. package/.github/skills/openspec-continue-change/SKILL.md +118 -0
  35. package/.github/skills/openspec-explore/SKILL.md +290 -0
  36. package/.github/skills/openspec-ff-change/SKILL.md +101 -0
  37. package/.github/skills/openspec-new-change/SKILL.md +74 -0
  38. package/.github/skills/openspec-onboard/SKILL.md +529 -0
  39. package/.github/skills/openspec-sync-specs/SKILL.md +138 -0
  40. package/.github/skills/openspec-verify-change/SKILL.md +168 -0
  41. package/.legacyverignore.example +43 -0
  42. package/.legacyverrc +7 -0
  43. package/.opencode/command/opsx-apply.md +149 -0
  44. package/.opencode/command/opsx-archive.md +154 -0
  45. package/.opencode/command/opsx-bulk-archive.md +239 -0
  46. package/.opencode/command/opsx-continue.md +111 -0
  47. package/.opencode/command/opsx-explore.md +171 -0
  48. package/.opencode/command/opsx-ff.md +91 -0
  49. package/.opencode/command/opsx-new.md +66 -0
  50. package/.opencode/command/opsx-onboard.md +522 -0
  51. package/.opencode/command/opsx-sync.md +131 -0
  52. package/.opencode/command/opsx-verify.md +161 -0
  53. package/.opencode/skills/openspec-apply-change/SKILL.md +156 -0
  54. package/.opencode/skills/openspec-archive-change/SKILL.md +114 -0
  55. package/.opencode/skills/openspec-bulk-archive-change/SKILL.md +246 -0
  56. package/.opencode/skills/openspec-continue-change/SKILL.md +118 -0
  57. package/.opencode/skills/openspec-explore/SKILL.md +290 -0
  58. package/.opencode/skills/openspec-ff-change/SKILL.md +101 -0
  59. package/.opencode/skills/openspec-new-change/SKILL.md +74 -0
  60. package/.opencode/skills/openspec-onboard/SKILL.md +529 -0
  61. package/.opencode/skills/openspec-sync-specs/SKILL.md +138 -0
  62. package/.opencode/skills/openspec-verify-change/SKILL.md +168 -0
  63. package/LICENSE +1 -1
  64. package/README.md +128 -83
  65. package/bin/legacyver.js +48 -25
  66. package/legacyver-docs/SUMMARY.md +3 -0
  67. package/legacyver-docs/components.md +57 -0
  68. package/legacyver-docs/index.md +15 -0
  69. package/nul +2 -0
  70. package/package.json +23 -25
  71. package/src/cache/hash.js +9 -10
  72. package/src/cache/index.js +43 -65
  73. package/src/cli/commands/analyze.js +212 -190
  74. package/src/cli/commands/cache.js +15 -35
  75. package/src/cli/commands/init.js +63 -107
  76. package/src/cli/commands/providers.js +56 -81
  77. package/src/cli/commands/version.js +7 -10
  78. package/src/cli/ui.js +58 -77
  79. package/src/crawler/filters.js +41 -40
  80. package/src/crawler/index.js +52 -36
  81. package/src/crawler/manifest.js +31 -43
  82. package/src/crawler/walk.js +32 -38
  83. package/src/llm/chunker.js +34 -56
  84. package/src/llm/cost-estimator.js +68 -51
  85. package/src/llm/free-model.js +67 -0
  86. package/src/llm/index.js +22 -43
  87. package/src/llm/prompts.js +45 -33
  88. package/src/llm/providers/gemini.js +94 -0
  89. package/src/llm/providers/groq.js +55 -40
  90. package/src/llm/providers/ollama.js +38 -65
  91. package/src/llm/providers/openrouter.js +67 -0
  92. package/src/llm/queue.js +59 -88
  93. package/src/llm/re-prompter.js +41 -0
  94. package/src/llm/validator.js +72 -0
  95. package/src/parser/ast/generic.js +45 -222
  96. package/src/parser/ast/go.js +86 -205
  97. package/src/parser/ast/java.js +76 -146
  98. package/src/parser/ast/javascript.js +173 -241
  99. package/src/parser/ast/laravel/blade.js +56 -0
  100. package/src/parser/ast/laravel/classifier.js +30 -0
  101. package/src/parser/ast/laravel/controller.js +35 -0
  102. package/src/parser/ast/laravel/index.js +54 -0
  103. package/src/parser/ast/laravel/model.js +41 -0
  104. package/src/parser/ast/laravel/provider.js +28 -0
  105. package/src/parser/ast/laravel/routes.js +45 -0
  106. package/src/parser/ast/php.js +129 -0
  107. package/src/parser/ast/python.js +76 -199
  108. package/src/parser/ast/typescript.js +10 -244
  109. package/src/parser/body-extractor.js +40 -0
  110. package/src/parser/call-graph.js +50 -67
  111. package/src/parser/complexity-scorer.js +59 -0
  112. package/src/parser/index.js +61 -86
  113. package/src/parser/pattern-detector.js +71 -0
  114. package/src/parser/pkg-builder.js +36 -83
  115. package/src/renderer/html.js +63 -135
  116. package/src/renderer/index.js +23 -35
  117. package/src/renderer/json.js +17 -35
  118. package/src/renderer/markdown.js +83 -117
  119. package/src/utils/config.js +52 -53
  120. package/src/utils/errors.js +26 -41
  121. package/src/utils/logger.js +32 -53
  122. package/src/cli/flags.js +0 -87
  123. package/src/llm/providers/anthropic.js +0 -57
  124. package/src/llm/providers/google.js +0 -65
  125. package/src/llm/providers/openai.js +0 -52
  126. package/src/parser/ast/tree-sitter-init.js +0 -80
@@ -1,236 +1,117 @@
1
- /**
2
- * Go AST parser using web-tree-sitter.
3
- * Extracts functions, structs, interfaces, import blocks.
4
- */
1
+ 'use strict';
5
2
 
6
- import { initTreeSitter, loadLanguage, createParser } from './tree-sitter-init.js';
7
- import { parseGeneric } from './generic.js';
8
-
9
- let goParser = null;
10
- let goInitialized = false;
11
-
12
- async function ensureParser() {
13
- if (goInitialized) return goParser;
14
- goInitialized = true;
15
-
16
- const Parser = await initTreeSitter();
17
- if (!Parser) return null;
18
-
19
- const language = await loadLanguage('go');
20
- if (!language) return null;
21
-
22
- goParser = createParser(language);
23
- return goParser;
24
- }
25
-
26
- /**
27
- * Parse a Go file and extract structural facts.
28
- * @param {string} source - Raw file content
29
- * @param {string} relativePath - Relative file path
30
- * @returns {Promise<import('../../parser/index.js').FileFacts>}
31
- */
32
- export async function parseGo(source, relativePath) {
33
- const parser = await ensureParser();
34
-
35
- if (!parser) {
36
- return parseGeneric(source, relativePath, 'go');
37
- }
38
-
39
- const tree = parser.parse(source);
40
- const rootNode = tree.rootNode;
3
+ const { scoreComplexity } = require('../complexity-scorer');
4
+ const { extractBodySnippet } = require('../body-extractor');
41
5
 
6
+ function parse(sourceText, relativePath) {
7
+ const lines = sourceText.split('\n');
42
8
  const functions = [];
43
9
  const classes = []; // structs/interfaces in Go
44
10
  const imports = [];
45
11
  const exports = [];
46
- const lines = source.split('\n');
47
- const linesOfCode = lines.filter((l) => l.trim().length > 0).length;
48
12
 
49
- walkNode(rootNode, { functions, classes, imports, exports });
13
+ for (let i = 0; i < lines.length; i++) {
14
+ const line = lines[i];
15
+ const trimmed = line.trim();
16
+
17
+ // Imports
18
+ let m = trimmed.match(/^import\s+"([^"]+)"/);
19
+ if (m) { imports.push({ module: m[1], specifiers: [] }); continue; }
20
+
21
+ // Import blocks
22
+ if (trimmed === 'import (') {
23
+ i++;
24
+ while (i < lines.length && lines[i].trim() !== ')') {
25
+ const imp = lines[i].trim().replace(/['"]/g, '').split(/\s+/).pop();
26
+ if (imp) imports.push({ module: imp, specifiers: [] });
27
+ i++;
28
+ }
29
+ continue;
30
+ }
50
31
 
51
- // In Go, exported names start with uppercase
52
- for (const fn of functions) {
53
- if (fn.name[0] === fn.name[0].toUpperCase() && fn.name[0] !== fn.name[0].toLowerCase()) {
54
- fn.isExported = true;
55
- exports.push(fn.name);
32
+ // Struct / Interface
33
+ m = trimmed.match(/^type\s+(\w+)\s+(struct|interface)\s*\{/);
34
+ if (m) {
35
+ classes.push({ name: m[1], methods: [], extends: null });
36
+ continue;
56
37
  }
57
- }
58
- for (const cls of classes) {
59
- if (cls.name[0] === cls.name[0].toUpperCase()) {
60
- exports.push(cls.name);
38
+
39
+ // Functions and methods
40
+ m = trimmed.match(/^func\s+(?:\((\w+)\s+\*?(\w+)\)\s+)?(\w+)\s*\(([^)]*)\)(?:\s+(?:\(([^)]*)\)|(\w+)))?\s*\{/);
41
+ if (m) {
42
+ const receiver = m[2] || null;
43
+ const name = m[3];
44
+ const paramsStr = m[4] || '';
45
+ const params = paramsStr.split(',').map(p => {
46
+ const parts = p.trim().split(/\s+/);
47
+ return { name: parts[0] || '', type: parts[1] || null };
48
+ }).filter(p => p.name);
49
+ const returnType = m[5] || m[6] || null;
50
+ const isExported = /^[A-Z]/.test(name); // Go export convention
51
+ const lineEnd = findBlockEnd(lines, i);
52
+ const body = lines.slice(i, lineEnd).join('\n');
53
+ const complexity = scoreComplexity(body);
54
+ const snippet = extractBodySnippet(sourceText, i + 1, lineEnd, complexity.complexityScore);
55
+ functions.push({
56
+ name,
57
+ params,
58
+ returnType,
59
+ isExported,
60
+ isAsync: false,
61
+ receiver,
62
+ lineStart: i + 1,
63
+ lineEnd,
64
+ calls: extractCalls(body),
65
+ ...complexity,
66
+ bodySnippet: snippet.bodySnippet,
67
+ bodySnippetTruncated: snippet.bodySnippetTruncated,
68
+ });
69
+ if (receiver) {
70
+ const cls = classes.find(c => c.name === receiver);
71
+ if (cls) cls.methods.push(name);
72
+ }
61
73
  }
62
74
  }
63
75
 
76
+ const exportedFns = functions.filter(f => f.isExported).map(f => f.name);
77
+
64
78
  return {
65
79
  relativePath,
66
80
  language: 'go',
67
- linesOfCode,
68
- parserType: 'tree-sitter',
81
+ linesOfCode: lines.length,
69
82
  functions,
70
83
  classes,
71
84
  imports,
72
- exports,
85
+ exports: exportedFns,
73
86
  callsTo: [],
74
87
  calledBy: [],
75
- hash: '',
88
+ hash: null,
89
+ parserType: 'ast',
76
90
  };
77
91
  }
78
92
 
79
- function walkNode(node, ctx) {
80
- switch (node.type) {
81
- case 'function_declaration':
82
- extractFunction(node, ctx);
83
- break;
84
- case 'method_declaration':
85
- extractMethod(node, ctx);
86
- break;
87
- case 'type_declaration':
88
- extractTypeDecl(node, ctx);
89
- break;
90
- case 'import_declaration':
91
- extractImport(node, ctx);
92
- break;
93
- }
94
-
95
- for (let i = 0; i < node.childCount; i++) {
96
- walkNode(node.child(i), ctx);
97
- }
98
- }
99
-
100
- function extractFunction(node, ctx) {
101
- const nameNode = node.childForFieldName('name');
102
- const paramsNode = node.childForFieldName('parameters');
103
- const resultNode = node.childForFieldName('result');
104
-
105
- if (nameNode) {
106
- ctx.functions.push({
107
- name: nameNode.text,
108
- params: extractParams(paramsNode),
109
- returnType: resultNode ? resultNode.text : null,
110
- isExported: false, // determined later by capitalization
111
- isAsync: false,
112
- lineStart: node.startPosition.row + 1,
113
- lineEnd: node.endPosition.row + 1,
114
- calls: [],
115
- });
116
- }
117
- }
118
-
119
- function extractMethod(node, ctx) {
120
- const nameNode = node.childForFieldName('name');
121
- const paramsNode = node.childForFieldName('parameters');
122
- const resultNode = node.childForFieldName('result');
123
-
124
- if (nameNode) {
125
- // Get receiver type
126
- let receiver = null;
127
- for (let i = 0; i < node.childCount; i++) {
128
- const child = node.child(i);
129
- if (child.type === 'parameter_list' && child !== paramsNode) {
130
- receiver = child.text;
131
- break;
93
+ function findBlockEnd(lines, startIdx) {
94
+ let depth = 0;
95
+ for (let i = startIdx; i < lines.length; i++) {
96
+ for (const ch of lines[i]) {
97
+ if (ch === '{') depth++;
98
+ else if (ch === '}') {
99
+ depth--;
100
+ if (depth === 0) return i + 1;
132
101
  }
133
102
  }
134
-
135
- ctx.functions.push({
136
- name: nameNode.text,
137
- params: extractParams(paramsNode),
138
- returnType: resultNode ? resultNode.text : null,
139
- isExported: false,
140
- isAsync: false,
141
- lineStart: node.startPosition.row + 1,
142
- lineEnd: node.endPosition.row + 1,
143
- calls: [],
144
- receiver,
145
- });
146
103
  }
104
+ return lines.length;
147
105
  }
148
106
 
149
- function extractTypeDecl(node, ctx) {
150
- for (let i = 0; i < node.childCount; i++) {
151
- const child = node.child(i);
152
- if (child.type === 'type_spec') {
153
- const nameNode = child.childForFieldName('name');
154
- const typeNode = child.childForFieldName('type');
155
-
156
- if (nameNode && typeNode) {
157
- if (typeNode.type === 'struct_type') {
158
- const methods = [];
159
- ctx.classes.push({
160
- name: nameNode.text,
161
- methods,
162
- extends: null,
163
- kind: 'struct',
164
- });
165
- } else if (typeNode.type === 'interface_type') {
166
- const methods = [];
167
- for (let j = 0; j < typeNode.childCount; j++) {
168
- const member = typeNode.child(j);
169
- if (member.type === 'method_spec') {
170
- const methodName = member.childForFieldName('name');
171
- if (methodName) methods.push(methodName.text);
172
- }
173
- }
174
- ctx.classes.push({
175
- name: nameNode.text,
176
- methods,
177
- extends: null,
178
- kind: 'interface',
179
- });
180
- }
181
- }
182
- }
107
+ function extractCalls(body) {
108
+ const calls = new Set();
109
+ const keywords = new Set(['if', 'for', 'range', 'switch', 'case', 'return', 'defer', 'go', 'select', 'func', 'var', 'const', 'type', 'struct', 'interface', 'map', 'chan', 'make', 'new', 'len', 'cap', 'append', 'copy', 'delete', 'close', 'panic', 'recover', 'print', 'println', 'error']);
110
+ for (const match of body.matchAll(/\b(\w+)\s*\(/g)) {
111
+ const name = match[1];
112
+ if (!keywords.has(name) && name.length > 1) calls.add(name);
183
113
  }
114
+ return [...calls];
184
115
  }
185
116
 
186
- function extractImport(node, ctx) {
187
- for (let i = 0; i < node.childCount; i++) {
188
- const child = node.child(i);
189
- if (child.type === 'import_spec_list') {
190
- for (let j = 0; j < child.childCount; j++) {
191
- const spec = child.child(j);
192
- if (spec.type === 'import_spec') {
193
- const pathNode = spec.childForFieldName('path');
194
- if (pathNode) {
195
- ctx.imports.push({
196
- module: pathNode.text.replace(/"/g, ''),
197
- specifiers: [],
198
- });
199
- }
200
- }
201
- }
202
- } else if (child.type === 'import_spec') {
203
- const pathNode = child.childForFieldName('path');
204
- if (pathNode) {
205
- ctx.imports.push({
206
- module: pathNode.text.replace(/"/g, ''),
207
- specifiers: [],
208
- });
209
- }
210
- } else if (child.type === 'interpreted_string_literal') {
211
- ctx.imports.push({
212
- module: child.text.replace(/"/g, ''),
213
- specifiers: [],
214
- });
215
- }
216
- }
217
- }
218
-
219
- function extractParams(paramsNode) {
220
- if (!paramsNode) return [];
221
- const params = [];
222
- for (let i = 0; i < paramsNode.childCount; i++) {
223
- const child = paramsNode.child(i);
224
- if (child.type === 'parameter_declaration') {
225
- const nameNode = child.childForFieldName('name');
226
- const typeNode = child.childForFieldName('type');
227
- if (nameNode) {
228
- params.push({
229
- name: nameNode.text,
230
- type: typeNode ? typeNode.text : null,
231
- });
232
- }
233
- }
234
- }
235
- return params;
236
- }
117
+ module.exports = { parse };
@@ -1,173 +1,103 @@
1
- /**
2
- * Java AST parser using web-tree-sitter.
3
- * Extracts methods, class hierarchy, import statements.
4
- */
1
+ 'use strict';
5
2
 
6
- import { initTreeSitter, loadLanguage, createParser } from './tree-sitter-init.js';
7
- import { parseGeneric } from './generic.js';
8
-
9
- let javaParser = null;
10
- let javaInitialized = false;
11
-
12
- async function ensureParser() {
13
- if (javaInitialized) return javaParser;
14
- javaInitialized = true;
15
-
16
- const Parser = await initTreeSitter();
17
- if (!Parser) return null;
18
-
19
- const language = await loadLanguage('java');
20
- if (!language) return null;
21
-
22
- javaParser = createParser(language);
23
- return javaParser;
24
- }
25
-
26
- /**
27
- * Parse a Java file and extract structural facts.
28
- * @param {string} source - Raw file content
29
- * @param {string} relativePath - Relative file path
30
- * @returns {Promise<import('../../parser/index.js').FileFacts>}
31
- */
32
- export async function parseJava(source, relativePath) {
33
- const parser = await ensureParser();
34
-
35
- if (!parser) {
36
- return parseGeneric(source, relativePath, 'java');
37
- }
38
-
39
- const tree = parser.parse(source);
40
- const rootNode = tree.rootNode;
3
+ const { scoreComplexity } = require('../complexity-scorer');
4
+ const { extractBodySnippet } = require('../body-extractor');
41
5
 
6
+ function parse(sourceText, relativePath) {
7
+ const lines = sourceText.split('\n');
42
8
  const functions = [];
43
9
  const classes = [];
44
10
  const imports = [];
45
11
  const exports = [];
46
- const lines = source.split('\n');
47
- const linesOfCode = lines.filter((l) => l.trim().length > 0).length;
48
12
 
49
- walkNode(rootNode, { functions, classes, imports, exports });
13
+ for (let i = 0; i < lines.length; i++) {
14
+ const line = lines[i];
15
+ const trimmed = line.trim();
16
+
17
+ // Imports
18
+ let m = trimmed.match(/^import\s+([\w.]+(?:\s*,\s*[\w.]+)*)/);
19
+ if (m) { m[1].split(',').forEach(imp => imports.push({ module: imp.trim(), specifiers: [] })); continue; }
20
+ m = trimmed.match(/^import\s+[\w.]+\.\{([^}]+)\}/);
21
+ if (m) { imports.push({ module: trimmed.replace(/^import\s+/, '').split('.')[0], specifiers: m[1].split(',').map(s => s.trim()) }); continue; }
22
+
23
+ // Class
24
+ m = trimmed.match(/^(?:public\s+|private\s+|abstract\s+)*class\s+(\w+)(?:\s+extends\s+(\w+))?(?:\s+implements\s+([\w,\s]+))?/);
25
+ if (m) {
26
+ classes.push({ name: m[1], methods: [], extends: m[2] || null });
27
+ continue;
28
+ }
29
+
30
+ // Methods
31
+ m = trimmed.match(/^(?:(public|private|protected|static|final|abstract|synchronized)\s+)*(?:(\w+(?:<[^>]+>)?)\s+)?(\w+)\s*\(([^)]*)\)\s*(?:throws\s+[\w,\s]+)?\s*\{/);
32
+ if (m && m[3] && m[3] !== 'if' && m[3] !== 'for' && m[3] !== 'while' && m[3] !== 'switch' && m[3] !== 'catch') {
33
+ const name = m[3];
34
+ const visibility = m[1] || 'package';
35
+ const returnType = m[2] || 'void';
36
+ const paramsStr = m[4] || '';
37
+ const params = paramsStr.split(',').map(p => {
38
+ const parts = p.trim().split(/\s+/);
39
+ return { name: parts[parts.length - 1] || '', type: parts[parts.length - 2] || null };
40
+ }).filter(p => p.name);
41
+ const lineEnd = findBlockEnd(lines, i);
42
+ const body = lines.slice(i, lineEnd).join('\n');
43
+ const complexity = scoreComplexity(body);
44
+ const snippet = extractBodySnippet(sourceText, i + 1, lineEnd, complexity.complexityScore);
45
+ functions.push({
46
+ name,
47
+ params,
48
+ returnType,
49
+ isExported: visibility === 'public',
50
+ isAsync: false,
51
+ lineStart: i + 1,
52
+ lineEnd,
53
+ calls: extractCalls(body),
54
+ ...complexity,
55
+ bodySnippet: snippet.bodySnippet,
56
+ bodySnippetTruncated: snippet.bodySnippetTruncated,
57
+ });
58
+ if (classes.length > 0) classes[classes.length - 1].methods.push(name);
59
+ }
60
+ }
61
+
62
+ const publicFns = functions.filter(f => f.isExported).map(f => f.name);
50
63
 
51
64
  return {
52
65
  relativePath,
53
66
  language: 'java',
54
- linesOfCode,
55
- parserType: 'tree-sitter',
67
+ linesOfCode: lines.length,
56
68
  functions,
57
69
  classes,
58
70
  imports,
59
- exports: classes.map((c) => c.name),
71
+ exports: publicFns,
60
72
  callsTo: [],
61
73
  calledBy: [],
62
- hash: '',
74
+ hash: null,
75
+ parserType: 'ast',
63
76
  };
64
77
  }
65
78
 
66
- function walkNode(node, ctx) {
67
- switch (node.type) {
68
- case 'class_declaration':
69
- extractClass(node, ctx);
70
- return; // Don't recurse further; extractClass handles methods
71
- case 'import_declaration':
72
- extractImport(node, ctx);
73
- break;
74
- }
75
-
76
- for (let i = 0; i < node.childCount; i++) {
77
- walkNode(node.child(i), ctx);
78
- }
79
- }
80
-
81
- function extractClass(node, ctx) {
82
- const nameNode = node.childForFieldName('name');
83
- const superclassNode = node.childForFieldName('superclass');
84
- const bodyNode = node.childForFieldName('body');
85
-
86
- if (!nameNode) return;
87
-
88
- const methods = [];
89
- const className = nameNode.text;
90
- let superclass = superclassNode ? superclassNode.text : null;
91
-
92
- if (bodyNode) {
93
- for (let i = 0; i < bodyNode.childCount; i++) {
94
- const child = bodyNode.child(i);
95
- if (child.type === 'method_declaration' || child.type === 'constructor_declaration') {
96
- const method = extractMethod(child, className);
97
- if (method) {
98
- methods.push(method.name);
99
- ctx.functions.push(method);
100
- }
79
+ function findBlockEnd(lines, startIdx) {
80
+ let depth = 0;
81
+ for (let i = startIdx; i < lines.length; i++) {
82
+ for (const ch of lines[i]) {
83
+ if (ch === '{') depth++;
84
+ else if (ch === '}') {
85
+ depth--;
86
+ if (depth === 0) return i + 1;
101
87
  }
102
88
  }
103
89
  }
104
-
105
- ctx.classes.push({
106
- name: className,
107
- methods,
108
- extends: superclass,
109
- });
110
- }
111
-
112
- function extractMethod(node, className) {
113
- const nameNode = node.childForFieldName('name');
114
- const paramsNode = node.childForFieldName('parameters');
115
- const typeNode = node.childForFieldName('type');
116
-
117
- // Detect visibility
118
- let visibility = 'package';
119
- for (let i = 0; i < node.childCount; i++) {
120
- const child = node.child(i);
121
- if (child.type === 'modifiers') {
122
- if (child.text.includes('public')) visibility = 'public';
123
- else if (child.text.includes('private')) visibility = 'private';
124
- else if (child.text.includes('protected')) visibility = 'protected';
125
- }
126
- }
127
-
128
- const name = nameNode ? nameNode.text : (node.type === 'constructor_declaration' ? className : null);
129
- if (!name) return null;
130
-
131
- return {
132
- name,
133
- params: extractParams(paramsNode),
134
- returnType: typeNode ? typeNode.text : (node.type === 'constructor_declaration' ? className : 'void'),
135
- isExported: visibility === 'public',
136
- isAsync: false,
137
- lineStart: node.startPosition.row + 1,
138
- lineEnd: node.endPosition.row + 1,
139
- calls: [],
140
- visibility,
141
- };
90
+ return lines.length;
142
91
  }
143
92
 
144
- function extractImport(node, ctx) {
145
- const text = node.text;
146
- const match = text.match(/import\s+([\w.]+(?:\.\*)?)\s*;/);
147
- if (match) {
148
- const parts = match[1].split('.');
149
- ctx.imports.push({
150
- module: match[1],
151
- specifiers: [parts[parts.length - 1]],
152
- });
93
+ function extractCalls(body) {
94
+ const calls = new Set();
95
+ const keywords = new Set(['if', 'for', 'while', 'switch', 'catch', 'new', 'return', 'throw', 'instanceof', 'class', 'void', 'int', 'long', 'double', 'float', 'boolean', 'String', 'System', 'else', 'try', 'finally']);
96
+ for (const match of body.matchAll(/\b(\w+)\s*\(/g)) {
97
+ const name = match[1];
98
+ if (!keywords.has(name) && name.length > 1) calls.add(name);
153
99
  }
100
+ return [...calls];
154
101
  }
155
102
 
156
- function extractParams(paramsNode) {
157
- if (!paramsNode) return [];
158
- const params = [];
159
- for (let i = 0; i < paramsNode.childCount; i++) {
160
- const child = paramsNode.child(i);
161
- if (child.type === 'formal_parameter') {
162
- const nameNode = child.childForFieldName('name');
163
- const typeNode = child.childForFieldName('type');
164
- if (nameNode) {
165
- params.push({
166
- name: nameNode.text,
167
- type: typeNode ? typeNode.text : null,
168
- });
169
- }
170
- }
171
- }
172
- return params;
173
- }
103
+ module.exports = { parse };