@danielblomma/cortex-mcp 1.3.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/README.md +62 -14
  2. package/package.json +2 -2
  3. package/scaffold/mcp/package-lock.json +3 -7
  4. package/scaffold/mcp/package.json +1 -1
  5. package/scaffold/scripts/dashboard.mjs +15 -1
  6. package/scaffold/scripts/doctor.sh +64 -10
  7. package/scaffold/scripts/ingest.mjs +323 -50
  8. package/scaffold/scripts/parsers/bash-treesitter.mjs +229 -0
  9. package/scaffold/scripts/parsers/cpp-dispatch.mjs +56 -0
  10. package/scaffold/scripts/parsers/cpp-treesitter.mjs +333 -0
  11. package/scaffold/scripts/parsers/csharp.mjs +197 -10
  12. package/scaffold/scripts/parsers/dotnet/CSharpParser/CSharpParser.csproj +1 -0
  13. package/scaffold/scripts/parsers/dotnet/CSharpParser/Program.cs +126 -21
  14. package/scaffold/scripts/parsers/go-treesitter.mjs +283 -0
  15. package/scaffold/scripts/parsers/java-treesitter.mjs +250 -0
  16. package/scaffold/scripts/parsers/javascript/ast.mjs +118 -12
  17. package/scaffold/scripts/parsers/javascript/chunks.mjs +4 -0
  18. package/scaffold/scripts/parsers/javascript/patterns.mjs +6 -0
  19. package/scaffold/scripts/parsers/javascript.mjs +4 -19
  20. package/scaffold/scripts/parsers/node_modules/.package-lock.json +57 -0
  21. package/scaffold/scripts/parsers/node_modules/acorn/CHANGELOG.md +972 -0
  22. package/scaffold/scripts/parsers/node_modules/acorn/LICENSE +21 -0
  23. package/scaffold/scripts/parsers/node_modules/acorn/README.md +301 -0
  24. package/scaffold/scripts/parsers/node_modules/acorn/bin/acorn +4 -0
  25. package/scaffold/scripts/parsers/node_modules/acorn/dist/acorn.d.mts +883 -0
  26. package/scaffold/scripts/parsers/node_modules/acorn/dist/acorn.d.ts +883 -0
  27. package/scaffold/scripts/parsers/node_modules/acorn/dist/acorn.js +6295 -0
  28. package/scaffold/scripts/parsers/node_modules/acorn/dist/acorn.mjs +6266 -0
  29. package/scaffold/scripts/parsers/node_modules/acorn/dist/bin.js +90 -0
  30. package/scaffold/scripts/parsers/node_modules/acorn/package.json +50 -0
  31. package/scaffold/scripts/parsers/node_modules/acorn-typescript/CHANGELOG.md +421 -0
  32. package/scaffold/scripts/parsers/node_modules/acorn-typescript/LICENSE +21 -0
  33. package/scaffold/scripts/parsers/node_modules/acorn-typescript/README.md +81 -0
  34. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/error.d.ts +103 -0
  35. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/error.js +78 -0
  36. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/error.js.map +1 -0
  37. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/decorators.d.ts +167 -0
  38. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/decorators.js +75 -0
  39. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/decorators.js.map +1 -0
  40. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/import-assertions.d.ts +177 -0
  41. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/import-assertions.js +56 -0
  42. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/import-assertions.js.map +1 -0
  43. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/jsx/index.d.ts +198 -0
  44. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/jsx/index.js +327 -0
  45. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/jsx/index.js.map +1 -0
  46. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/jsx/xhtml.d.ts +256 -0
  47. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/jsx/xhtml.js +256 -0
  48. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/jsx/xhtml.js.map +1 -0
  49. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/index.d.ts +472 -0
  50. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/index.js +1 -0
  51. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/index.js.map +1 -0
  52. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/index.mjs +1 -0
  53. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/middleware.d.ts +159 -0
  54. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/middleware.js +2 -0
  55. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/middleware.js.map +1 -0
  56. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/parseutil.d.ts +10 -0
  57. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/parseutil.js +38 -0
  58. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/parseutil.js.map +1 -0
  59. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/scopeflags.d.ts +12 -0
  60. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/scopeflags.js +29 -0
  61. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/scopeflags.js.map +1 -0
  62. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/tokenType.d.ts +2 -0
  63. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/tokenType.js +118 -0
  64. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/tokenType.js.map +1 -0
  65. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/types.d.ts +60 -0
  66. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/types.js +2 -0
  67. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/types.js.map +1 -0
  68. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/whitespace.d.ts +2 -0
  69. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/whitespace.js +19 -0
  70. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/whitespace.js.map +1 -0
  71. package/scaffold/scripts/parsers/node_modules/acorn-typescript/package.json +53 -0
  72. package/scaffold/scripts/parsers/node_modules/acorn-typescript/tsconfig.json +19 -0
  73. package/scaffold/scripts/parsers/node_modules/acorn-walk/CHANGELOG.md +209 -0
  74. package/scaffold/scripts/parsers/node_modules/acorn-walk/LICENSE +21 -0
  75. package/scaffold/scripts/parsers/node_modules/acorn-walk/README.md +124 -0
  76. package/scaffold/scripts/parsers/node_modules/acorn-walk/dist/walk.d.mts +152 -0
  77. package/scaffold/scripts/parsers/node_modules/acorn-walk/dist/walk.d.ts +152 -0
  78. package/scaffold/scripts/parsers/node_modules/acorn-walk/dist/walk.js +485 -0
  79. package/scaffold/scripts/parsers/node_modules/acorn-walk/dist/walk.mjs +467 -0
  80. package/scaffold/scripts/parsers/node_modules/acorn-walk/package.json +50 -0
  81. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/LICENSE +24 -0
  82. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/README.md +23 -0
  83. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-bash.wasm +0 -0
  84. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-c.wasm +0 -0
  85. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-c_sharp.wasm +0 -0
  86. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-cpp.wasm +0 -0
  87. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-css.wasm +0 -0
  88. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-dart.wasm +0 -0
  89. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-elisp.wasm +0 -0
  90. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-elixir.wasm +0 -0
  91. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-elm.wasm +0 -0
  92. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-embedded_template.wasm +0 -0
  93. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-go.wasm +0 -0
  94. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-html.wasm +0 -0
  95. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-java.wasm +0 -0
  96. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-javascript.wasm +0 -0
  97. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-json.wasm +0 -0
  98. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-kotlin.wasm +0 -0
  99. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-lua.wasm +0 -0
  100. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-objc.wasm +0 -0
  101. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-ocaml.wasm +0 -0
  102. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-php.wasm +0 -0
  103. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-python.wasm +0 -0
  104. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-ql.wasm +0 -0
  105. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-rescript.wasm +0 -0
  106. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-ruby.wasm +0 -0
  107. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-rust.wasm +0 -0
  108. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-scala.wasm +0 -0
  109. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-solidity.wasm +0 -0
  110. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-swift.wasm +0 -0
  111. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-systemrdl.wasm +0 -0
  112. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-tlaplus.wasm +0 -0
  113. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-toml.wasm +0 -0
  114. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-tsx.wasm +0 -0
  115. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-typescript.wasm +0 -0
  116. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-vue.wasm +0 -0
  117. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-yaml.wasm +0 -0
  118. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-zig.wasm +0 -0
  119. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/package.json +64 -0
  120. package/scaffold/scripts/parsers/node_modules/web-tree-sitter/LICENSE +21 -0
  121. package/scaffold/scripts/parsers/node_modules/web-tree-sitter/README.md +198 -0
  122. package/scaffold/scripts/parsers/node_modules/web-tree-sitter/package.json +37 -0
  123. package/scaffold/scripts/parsers/node_modules/web-tree-sitter/tree-sitter-web.d.ts +242 -0
  124. package/scaffold/scripts/parsers/node_modules/web-tree-sitter/tree-sitter.js +1 -0
  125. package/scaffold/scripts/parsers/node_modules/web-tree-sitter/tree-sitter.wasm +0 -0
  126. package/scaffold/scripts/parsers/package-lock.json +19 -1
  127. package/scaffold/scripts/parsers/package.json +3 -1
  128. package/scaffold/scripts/parsers/python-treesitter.mjs +271 -0
  129. package/scaffold/scripts/parsers/ruby-treesitter.mjs +271 -0
  130. package/scaffold/scripts/parsers/rust-dispatch.mjs +43 -0
  131. package/scaffold/scripts/parsers/rust-treesitter.mjs +291 -0
  132. package/scaffold/scripts/parsers/tree-sitter/base.mjs +163 -0
  133. package/scaffold/scripts/parsers/tree-sitter/queries/bash.calls.scm +7 -0
  134. package/scaffold/scripts/parsers/tree-sitter/queries/bash.chunks.scm +6 -0
  135. package/scaffold/scripts/parsers/tree-sitter/queries/bash.imports.scm +5 -0
  136. package/scaffold/scripts/parsers/tree-sitter/queries/cpp.calls.scm +17 -0
  137. package/scaffold/scripts/parsers/tree-sitter/queries/cpp.chunks.scm +30 -0
  138. package/scaffold/scripts/parsers/tree-sitter/queries/cpp.imports.scm +6 -0
  139. package/scaffold/scripts/parsers/tree-sitter/queries/go.calls.scm +11 -0
  140. package/scaffold/scripts/parsers/tree-sitter/queries/go.chunks.scm +19 -0
  141. package/scaffold/scripts/parsers/tree-sitter/queries/go.imports.scm +6 -0
  142. package/scaffold/scripts/parsers/tree-sitter/queries/java.calls.scm +6 -0
  143. package/scaffold/scripts/parsers/tree-sitter/queries/java.chunks.scm +23 -0
  144. package/scaffold/scripts/parsers/tree-sitter/queries/java.imports.scm +6 -0
  145. package/scaffold/scripts/parsers/tree-sitter/queries/python.calls.scm +11 -0
  146. package/scaffold/scripts/parsers/tree-sitter/queries/python.chunks.scm +11 -0
  147. package/scaffold/scripts/parsers/tree-sitter/queries/python.imports.scm +13 -0
  148. package/scaffold/scripts/parsers/tree-sitter/queries/ruby.calls.scm +6 -0
  149. package/scaffold/scripts/parsers/tree-sitter/queries/ruby.chunks.scm +16 -0
  150. package/scaffold/scripts/parsers/tree-sitter/queries/ruby.imports.scm +8 -0
  151. package/scaffold/scripts/parsers/tree-sitter/queries/rust.calls.scm +31 -0
  152. package/scaffold/scripts/parsers/tree-sitter/queries/rust.chunks.scm +29 -0
  153. package/scaffold/scripts/parsers/tree-sitter/queries/rust.imports.scm +5 -0
  154. package/scaffold/scripts/parsers/vb6.mjs +395 -0
@@ -0,0 +1,395 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Classic Visual Basic 6 parser for Cortex.
4
+ *
5
+ * VB6 has no tree-sitter grammar (the tree-sitter-wasms bundle ships
6
+ * nothing for VB, and tree-sitter-vb-dotnet targets VB.NET which has
7
+ * materially different syntax). Roslyn can only parse VB.NET, not
8
+ * VB6. So this is a regex-based "lightweight first-pass" — same
9
+ * approach the legacy cpp.mjs and pre-tree-sitter rust.mjs used.
10
+ *
11
+ * Covered extensions:
12
+ * .bas — standard module
13
+ * .cls — class module
14
+ * .frm — form
15
+ * .ctl — user control
16
+ *
17
+ * Extracts Sub / Function / Property (Get|Let|Set) / Type / Enum
18
+ * declarations. Strips the VB6 binary-ish header block (VERSION ...,
19
+ * BEGIN ... END, Attribute ...) that .cls/.frm/.ctl files carry
20
+ * before real code. `.frm` designer BEGIN ... END property blocks
21
+ * are also stripped so the parser only sees code.
22
+ *
23
+ * Naming:
24
+ * .bas -> ModuleName.Proc (ModuleName from `Attribute VB_Name` or filename)
25
+ * .cls -> ClassName.Method
26
+ * .frm -> FormName.EventHandler / FormName.Helper
27
+ * .ctl -> ControlName.Method
28
+ *
29
+ * VB6 has no imports in source code — references live in the .vbp
30
+ * project file. So chunk.imports is always [].
31
+ */
32
+
33
+ import path from "node:path";
34
+ import fs from "node:fs";
35
+
36
+ const KIND_BY_EXT = {
37
+ ".bas": "module",
38
+ ".cls": "class",
39
+ ".frm": "form",
40
+ ".ctl": "usercontrol"
41
+ };
42
+
43
+ const VBP_HEADER_PREFIXES = ["VERSION ", "Attribute ", "Object="];
44
+
45
+ const ATTR_VB_NAME = /Attribute\s+VB_Name\s*=\s*"([^"]+)"/i;
46
+
47
+ // VB6 builtins / intrinsics / common API surfaces — not user calls.
48
+ const CALL_FILTER = new Set([
49
+ "MsgBox", "InputBox", "Debug", "Err", "Me", "Nothing", "New",
50
+ "Len", "LenB", "Str", "Val", "CStr", "CInt", "CLng", "CDbl",
51
+ "CBool", "CByte", "CSng", "CDec", "CVar", "CDate",
52
+ "Left", "Right", "Mid", "UCase", "LCase", "Trim", "LTrim", "RTrim",
53
+ "Chr", "Asc", "IsEmpty", "IsNull", "IsNumeric", "IsDate", "IsArray",
54
+ "IsObject", "VarType", "TypeName", "UBound", "LBound",
55
+ "Array", "Split", "Join", "Replace", "InStr", "InStrRev",
56
+ "Abs", "Int", "Fix", "Sgn", "Sqr", "Exp", "Log", "Sin", "Cos", "Tan",
57
+ "Now", "Date", "Time", "DateAdd", "DateDiff", "DatePart", "Format",
58
+ "Dir", "FileExists", "GetAttr", "FileCopy", "Kill", "MkDir", "RmDir",
59
+ "Open", "Close", "Input", "Print", "Write", "LOF", "EOF", "Loc",
60
+ "If", "Else", "ElseIf", "End", "Do", "Loop", "While", "Wend",
61
+ "For", "Next", "Each", "To", "Step", "Exit", "Select", "Case",
62
+ "With", "GoTo", "GoSub", "Return", "Resume", "On", "Error",
63
+ "DoEvents", "RaiseEvent", "Event", "Call", "Stop", "Beep",
64
+ "Set", "Get", "Let", "Dim", "ReDim", "Preserve", "Static",
65
+ "Const", "Public", "Private", "Friend", "Sub", "Function", "Property",
66
+ "True", "False", "And", "Or", "Not", "Xor", "Eqv", "Imp", "Mod",
67
+ "App", "Screen", "Forms", "Printer", "Clipboard"
68
+ ]);
69
+
70
+ const SUPPORTED_EXTS = new Set([".bas", ".cls", ".frm", ".ctl"]);
71
+
72
+ function normalizeWhitespace(value) {
73
+ return String(value).replace(/\s+/g, " ").trim();
74
+ }
75
+
76
+ function extractModuleName(rawSource, filePath) {
77
+ const attrMatch = rawSource.match(ATTR_VB_NAME);
78
+ if (attrMatch) return attrMatch[1];
79
+ // Fall back to filename without extension.
80
+ const base = path.basename(filePath);
81
+ const dot = base.lastIndexOf(".");
82
+ return dot === -1 ? base : base.slice(0, dot);
83
+ }
84
+
85
+ /**
86
+ * Strip the VB6 binary-ish header that .cls/.frm/.ctl files carry
87
+ * before real source code. Only applied to those extensions — .bas
88
+ * files begin directly with code (or `Attribute VB_Name` lines).
89
+ * For .frm / .ctl we also strip the designer BEGIN ... END block
90
+ * that describes controls and property values.
91
+ */
92
+ function stripHeader(source, ext) {
93
+ let out = source;
94
+
95
+ if (ext === ".bas") {
96
+ // Strip `Attribute VB_Name = "..."` and similar leading Attribute
97
+ // lines. Keep the rest intact.
98
+ const lines = out.split("\n");
99
+ let firstCodeLine = 0;
100
+ for (let i = 0; i < lines.length; i += 1) {
101
+ const trimmed = lines[i].trim();
102
+ if (trimmed === "" || trimmed.startsWith("Attribute ")) {
103
+ firstCodeLine = i + 1;
104
+ } else {
105
+ break;
106
+ }
107
+ }
108
+ // Preserve original line numbers by blanking (not deleting) headers.
109
+ for (let i = 0; i < firstCodeLine; i += 1) lines[i] = "";
110
+ return lines.join("\n");
111
+ }
112
+
113
+ // .cls / .frm / .ctl — strip VERSION + BEGIN/END designer + Attribute lines.
114
+ const lines = out.split("\n");
115
+ let beginDepth = 0;
116
+ for (let i = 0; i < lines.length; i += 1) {
117
+ const line = lines[i];
118
+ const trimmed = line.trim();
119
+ const trimmedLower = trimmed.toLowerCase();
120
+
121
+ if (beginDepth > 0) {
122
+ if (/^begin\b/i.test(trimmed)) beginDepth += 1;
123
+ else if (/^end\s*$/i.test(trimmed)) beginDepth -= 1;
124
+ lines[i] = "";
125
+ continue;
126
+ }
127
+
128
+ if (VBP_HEADER_PREFIXES.some((p) => trimmed.startsWith(p))) {
129
+ lines[i] = "";
130
+ continue;
131
+ }
132
+
133
+ if (/^begin\b/i.test(trimmed)) {
134
+ beginDepth = 1;
135
+ lines[i] = "";
136
+ continue;
137
+ }
138
+ }
139
+ return lines.join("\n");
140
+ }
141
+
142
+ function countLinesBefore(text, index) {
143
+ let count = 1;
144
+ for (let i = 0; i < index; i += 1) {
145
+ if (text[i] === "\n") count += 1;
146
+ }
147
+ return count;
148
+ }
149
+
150
+ function findBlockEnd(source, startIndex, endKeyword) {
151
+ const endPattern = new RegExp(
152
+ `^[ \\t]*End\\s+${endKeyword}\\b`,
153
+ "im"
154
+ );
155
+ endPattern.lastIndex = startIndex;
156
+ const slice = source.slice(startIndex);
157
+ const match = slice.match(endPattern);
158
+ if (!match) return -1;
159
+ // match.index is offset within slice
160
+ const endLineStart = startIndex + match.index;
161
+ const newlineAfter = source.indexOf("\n", endLineStart + match[0].length);
162
+ return newlineAfter === -1 ? source.length : newlineAfter;
163
+ }
164
+
165
+ function extractCallsFromBody(body) {
166
+ const calls = new Set();
167
+ // Identifier followed by `(` — function/sub call
168
+ const callPattern = /\b([A-Za-z_][A-Za-z0-9_]*)\s*\(/g;
169
+ let m;
170
+ while ((m = callPattern.exec(body)) !== null) {
171
+ const name = m[1];
172
+ if (!CALL_FILTER.has(name)) calls.add(name);
173
+ }
174
+ // object.method — no parens needed in VB6
175
+ const methodPattern = /\.([A-Za-z_][A-Za-z0-9_]*)\b/g;
176
+ while ((m = methodPattern.exec(body)) !== null) {
177
+ const name = m[1];
178
+ if (!CALL_FILTER.has(name)) calls.add(name);
179
+ }
180
+ // Call <Ident>
181
+ const callKeywordPattern = /\bCall\s+([A-Za-z_][A-Za-z0-9_]*)/gi;
182
+ while ((m = callKeywordPattern.exec(body)) !== null) {
183
+ const name = m[1];
184
+ if (!CALL_FILTER.has(name)) calls.add(name);
185
+ }
186
+ // Bareword Sub call at start of line: VB6 lets you invoke a Sub
187
+ // without parens or Call keyword. The identifier must be alone on
188
+ // the line or followed by whitespace + argument list, and must not
189
+ // be an assignment (`x = ...`) or a declaration (`Dim x`).
190
+ const barewordPattern = /^[ \t]*([A-Za-z_][A-Za-z0-9_]*)(?:[ \t]+[^=\n:]|[ \t]*$)/gm;
191
+ while ((m = barewordPattern.exec(body)) !== null) {
192
+ const name = m[1];
193
+ if (!CALL_FILTER.has(name)) calls.add(name);
194
+ }
195
+ return [...calls];
196
+ }
197
+
198
+ function buildBlockChunk({ source, strippedSource, ownerName, kind, keyword, pattern, language }) {
199
+ const chunks = [];
200
+ pattern.lastIndex = 0;
201
+ let match;
202
+ while ((match = pattern.exec(strippedSource)) !== null) {
203
+ const matchStart = match.index;
204
+ const endOfBlock = findBlockEnd(strippedSource, matchStart + match[0].length, keyword);
205
+ if (endOfBlock === -1) continue;
206
+ const body = strippedSource.slice(matchStart, endOfBlock);
207
+ const startLine = countLinesBefore(strippedSource, matchStart);
208
+ const endLine = countLinesBefore(strippedSource, endOfBlock);
209
+ const visibility = match[1] ? match[1].toLowerCase() : "";
210
+ const exported = visibility !== "private";
211
+ const memberName = match[match.length - 1];
212
+ const qualifiedName = ownerName ? `${ownerName}.${memberName}` : memberName;
213
+
214
+ chunks.push({
215
+ name: qualifiedName,
216
+ kind,
217
+ signature: normalizeWhitespace(body.split("\n")[0]),
218
+ body,
219
+ startLine,
220
+ endLine,
221
+ language,
222
+ exported,
223
+ calls: extractCallsFromBody(body),
224
+ imports: []
225
+ });
226
+ }
227
+ return chunks;
228
+ }
229
+
230
+ function buildTypeOrEnumChunks({ strippedSource, ownerName, kind, keyword, pattern, language }) {
231
+ const chunks = [];
232
+ pattern.lastIndex = 0;
233
+ let match;
234
+ while ((match = pattern.exec(strippedSource)) !== null) {
235
+ const matchStart = match.index;
236
+ const endOfBlock = findBlockEnd(strippedSource, matchStart + match[0].length, keyword);
237
+ if (endOfBlock === -1) continue;
238
+ const body = strippedSource.slice(matchStart, endOfBlock);
239
+ const startLine = countLinesBefore(strippedSource, matchStart);
240
+ const endLine = countLinesBefore(strippedSource, endOfBlock);
241
+ const typeName = match[match.length - 1];
242
+ const qualifiedName = ownerName ? `${ownerName}.${typeName}` : typeName;
243
+ const visibility = match[1] ? match[1].toLowerCase() : "";
244
+ chunks.push({
245
+ name: qualifiedName,
246
+ kind,
247
+ signature: normalizeWhitespace(body.split("\n")[0]),
248
+ body,
249
+ startLine,
250
+ endLine,
251
+ language,
252
+ exported: visibility !== "private",
253
+ calls: [],
254
+ imports: []
255
+ });
256
+ }
257
+ return chunks;
258
+ }
259
+
260
+ function buildOwnerChunk({ source, strippedSource, ownerName, kind, language }) {
261
+ // One chunk for the whole file representing the module/class/form/control.
262
+ const lines = strippedSource.split("\n");
263
+ // Find first non-blank line as start; last non-blank as end.
264
+ let startLine = 1;
265
+ let endLine = lines.length;
266
+ for (let i = 0; i < lines.length; i += 1) {
267
+ if (lines[i].trim() !== "") { startLine = i + 1; break; }
268
+ }
269
+ for (let i = lines.length - 1; i >= 0; i -= 1) {
270
+ if (lines[i].trim() !== "") { endLine = i + 1; break; }
271
+ }
272
+ return {
273
+ name: ownerName,
274
+ kind,
275
+ signature: `${kind} ${ownerName}`,
276
+ body: source,
277
+ startLine,
278
+ endLine,
279
+ language,
280
+ exported: true,
281
+ calls: [],
282
+ imports: []
283
+ };
284
+ }
285
+
286
+ export function parseCode(code, filePath, language = "vb6") {
287
+ const ext = path.extname(filePath).toLowerCase();
288
+ if (!SUPPORTED_EXTS.has(ext)) {
289
+ return { chunks: [], errors: [] };
290
+ }
291
+
292
+ const ownerName = extractModuleName(code, filePath);
293
+ const ownerKind = KIND_BY_EXT[ext] ?? "module";
294
+ const memberKind = ext === ".bas" ? "function" : "method";
295
+ const strippedSource = stripHeader(code, ext);
296
+
297
+ const subPattern = /^[ \t]*(?:(Public|Private|Friend)\s+)?(?:Static\s+)?Sub\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(/gim;
298
+ const functionPattern = /^[ \t]*(?:(Public|Private|Friend)\s+)?(?:Static\s+)?Function\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(/gim;
299
+ const propertyPattern = /^[ \t]*(?:(Public|Private|Friend)\s+)?Property\s+(?:Get|Let|Set)\s+([A-Za-z_][A-Za-z0-9_]*)/gim;
300
+ const typePattern = /^[ \t]*(?:(Public|Private)\s+)?Type\s+([A-Za-z_][A-Za-z0-9_]*)/gim;
301
+ const enumPattern = /^[ \t]*(?:(Public|Private)\s+)?Enum\s+([A-Za-z_][A-Za-z0-9_]*)/gim;
302
+
303
+ const chunks = [];
304
+
305
+ chunks.push(buildOwnerChunk({
306
+ source: code,
307
+ strippedSource,
308
+ ownerName,
309
+ kind: ownerKind,
310
+ language
311
+ }));
312
+
313
+ chunks.push(...buildBlockChunk({
314
+ source: code,
315
+ strippedSource,
316
+ ownerName,
317
+ kind: memberKind,
318
+ keyword: "Sub",
319
+ pattern: subPattern,
320
+ language
321
+ }));
322
+
323
+ chunks.push(...buildBlockChunk({
324
+ source: code,
325
+ strippedSource,
326
+ ownerName,
327
+ kind: memberKind,
328
+ keyword: "Function",
329
+ pattern: functionPattern,
330
+ language
331
+ }));
332
+
333
+ const propertyChunks = buildBlockChunk({
334
+ source: code,
335
+ strippedSource,
336
+ ownerName,
337
+ kind: "property",
338
+ keyword: "Property",
339
+ pattern: propertyPattern,
340
+ language
341
+ });
342
+ // Property Get/Let/Set with same name collapse to one property chunk —
343
+ // keep only the first occurrence per qualified name so the graph
344
+ // doesn't show three property chunks for one logical property.
345
+ const seenProps = new Set();
346
+ for (const chunk of propertyChunks) {
347
+ if (seenProps.has(chunk.name)) continue;
348
+ seenProps.add(chunk.name);
349
+ chunks.push(chunk);
350
+ }
351
+
352
+ chunks.push(...buildTypeOrEnumChunks({
353
+ strippedSource,
354
+ ownerName,
355
+ kind: "type",
356
+ keyword: "Type",
357
+ pattern: typePattern,
358
+ language
359
+ }));
360
+
361
+ chunks.push(...buildTypeOrEnumChunks({
362
+ strippedSource,
363
+ ownerName,
364
+ kind: "enum",
365
+ keyword: "Enum",
366
+ pattern: enumPattern,
367
+ language
368
+ }));
369
+
370
+ // Dedupe by (kind, name, startLine, endLine) — mirrors other parsers.
371
+ const seen = new Set();
372
+ const deduped = chunks.filter((chunk) => {
373
+ const key = `${chunk.kind}|${chunk.name}|${chunk.startLine}|${chunk.endLine}`;
374
+ if (seen.has(key)) return false;
375
+ seen.add(key);
376
+ return true;
377
+ });
378
+
379
+ return { chunks: deduped, errors: [] };
380
+ }
381
+
382
+ export function isAvailable() {
383
+ return true;
384
+ }
385
+
386
+ if (import.meta.url === `file://${process.argv[1]}`) {
387
+ const target = process.argv[2];
388
+ if (!target) {
389
+ console.error("Usage: vb6.mjs <file.{bas,cls,frm,ctl}>");
390
+ process.exit(1);
391
+ }
392
+ const code = fs.readFileSync(target, "utf8");
393
+ const result = parseCode(code, target, "vb6");
394
+ console.log(JSON.stringify(result, null, 2));
395
+ }