@danielblomma/cortex-mcp 1.3.2 → 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 (153) 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/ingest.mjs +323 -50
  7. package/scaffold/scripts/parsers/bash-treesitter.mjs +229 -0
  8. package/scaffold/scripts/parsers/cpp-dispatch.mjs +56 -0
  9. package/scaffold/scripts/parsers/cpp-treesitter.mjs +333 -0
  10. package/scaffold/scripts/parsers/csharp.mjs +197 -10
  11. package/scaffold/scripts/parsers/dotnet/CSharpParser/CSharpParser.csproj +1 -0
  12. package/scaffold/scripts/parsers/dotnet/CSharpParser/Program.cs +126 -21
  13. package/scaffold/scripts/parsers/go-treesitter.mjs +283 -0
  14. package/scaffold/scripts/parsers/java-treesitter.mjs +250 -0
  15. package/scaffold/scripts/parsers/javascript/ast.mjs +118 -12
  16. package/scaffold/scripts/parsers/javascript/chunks.mjs +4 -0
  17. package/scaffold/scripts/parsers/javascript/patterns.mjs +6 -0
  18. package/scaffold/scripts/parsers/javascript.mjs +4 -19
  19. package/scaffold/scripts/parsers/node_modules/.package-lock.json +57 -0
  20. package/scaffold/scripts/parsers/node_modules/acorn/CHANGELOG.md +972 -0
  21. package/scaffold/scripts/parsers/node_modules/acorn/LICENSE +21 -0
  22. package/scaffold/scripts/parsers/node_modules/acorn/README.md +301 -0
  23. package/scaffold/scripts/parsers/node_modules/acorn/bin/acorn +4 -0
  24. package/scaffold/scripts/parsers/node_modules/acorn/dist/acorn.d.mts +883 -0
  25. package/scaffold/scripts/parsers/node_modules/acorn/dist/acorn.d.ts +883 -0
  26. package/scaffold/scripts/parsers/node_modules/acorn/dist/acorn.js +6295 -0
  27. package/scaffold/scripts/parsers/node_modules/acorn/dist/acorn.mjs +6266 -0
  28. package/scaffold/scripts/parsers/node_modules/acorn/dist/bin.js +90 -0
  29. package/scaffold/scripts/parsers/node_modules/acorn/package.json +50 -0
  30. package/scaffold/scripts/parsers/node_modules/acorn-typescript/CHANGELOG.md +421 -0
  31. package/scaffold/scripts/parsers/node_modules/acorn-typescript/LICENSE +21 -0
  32. package/scaffold/scripts/parsers/node_modules/acorn-typescript/README.md +81 -0
  33. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/error.d.ts +103 -0
  34. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/error.js +78 -0
  35. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/error.js.map +1 -0
  36. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/decorators.d.ts +167 -0
  37. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/decorators.js +75 -0
  38. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/decorators.js.map +1 -0
  39. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/import-assertions.d.ts +177 -0
  40. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/import-assertions.js +56 -0
  41. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/import-assertions.js.map +1 -0
  42. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/jsx/index.d.ts +198 -0
  43. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/jsx/index.js +327 -0
  44. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/jsx/index.js.map +1 -0
  45. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/jsx/xhtml.d.ts +256 -0
  46. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/jsx/xhtml.js +256 -0
  47. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/jsx/xhtml.js.map +1 -0
  48. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/index.d.ts +472 -0
  49. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/index.js +1 -0
  50. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/index.js.map +1 -0
  51. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/index.mjs +1 -0
  52. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/middleware.d.ts +159 -0
  53. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/middleware.js +2 -0
  54. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/middleware.js.map +1 -0
  55. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/parseutil.d.ts +10 -0
  56. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/parseutil.js +38 -0
  57. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/parseutil.js.map +1 -0
  58. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/scopeflags.d.ts +12 -0
  59. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/scopeflags.js +29 -0
  60. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/scopeflags.js.map +1 -0
  61. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/tokenType.d.ts +2 -0
  62. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/tokenType.js +118 -0
  63. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/tokenType.js.map +1 -0
  64. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/types.d.ts +60 -0
  65. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/types.js +2 -0
  66. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/types.js.map +1 -0
  67. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/whitespace.d.ts +2 -0
  68. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/whitespace.js +19 -0
  69. package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/whitespace.js.map +1 -0
  70. package/scaffold/scripts/parsers/node_modules/acorn-typescript/package.json +53 -0
  71. package/scaffold/scripts/parsers/node_modules/acorn-typescript/tsconfig.json +19 -0
  72. package/scaffold/scripts/parsers/node_modules/acorn-walk/CHANGELOG.md +209 -0
  73. package/scaffold/scripts/parsers/node_modules/acorn-walk/LICENSE +21 -0
  74. package/scaffold/scripts/parsers/node_modules/acorn-walk/README.md +124 -0
  75. package/scaffold/scripts/parsers/node_modules/acorn-walk/dist/walk.d.mts +152 -0
  76. package/scaffold/scripts/parsers/node_modules/acorn-walk/dist/walk.d.ts +152 -0
  77. package/scaffold/scripts/parsers/node_modules/acorn-walk/dist/walk.js +485 -0
  78. package/scaffold/scripts/parsers/node_modules/acorn-walk/dist/walk.mjs +467 -0
  79. package/scaffold/scripts/parsers/node_modules/acorn-walk/package.json +50 -0
  80. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/LICENSE +24 -0
  81. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/README.md +23 -0
  82. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-bash.wasm +0 -0
  83. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-c.wasm +0 -0
  84. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-c_sharp.wasm +0 -0
  85. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-cpp.wasm +0 -0
  86. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-css.wasm +0 -0
  87. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-dart.wasm +0 -0
  88. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-elisp.wasm +0 -0
  89. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-elixir.wasm +0 -0
  90. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-elm.wasm +0 -0
  91. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-embedded_template.wasm +0 -0
  92. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-go.wasm +0 -0
  93. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-html.wasm +0 -0
  94. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-java.wasm +0 -0
  95. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-javascript.wasm +0 -0
  96. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-json.wasm +0 -0
  97. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-kotlin.wasm +0 -0
  98. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-lua.wasm +0 -0
  99. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-objc.wasm +0 -0
  100. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-ocaml.wasm +0 -0
  101. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-php.wasm +0 -0
  102. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-python.wasm +0 -0
  103. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-ql.wasm +0 -0
  104. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-rescript.wasm +0 -0
  105. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-ruby.wasm +0 -0
  106. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-rust.wasm +0 -0
  107. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-scala.wasm +0 -0
  108. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-solidity.wasm +0 -0
  109. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-swift.wasm +0 -0
  110. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-systemrdl.wasm +0 -0
  111. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-tlaplus.wasm +0 -0
  112. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-toml.wasm +0 -0
  113. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-tsx.wasm +0 -0
  114. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-typescript.wasm +0 -0
  115. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-vue.wasm +0 -0
  116. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-yaml.wasm +0 -0
  117. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-zig.wasm +0 -0
  118. package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/package.json +64 -0
  119. package/scaffold/scripts/parsers/node_modules/web-tree-sitter/LICENSE +21 -0
  120. package/scaffold/scripts/parsers/node_modules/web-tree-sitter/README.md +198 -0
  121. package/scaffold/scripts/parsers/node_modules/web-tree-sitter/package.json +37 -0
  122. package/scaffold/scripts/parsers/node_modules/web-tree-sitter/tree-sitter-web.d.ts +242 -0
  123. package/scaffold/scripts/parsers/node_modules/web-tree-sitter/tree-sitter.js +1 -0
  124. package/scaffold/scripts/parsers/node_modules/web-tree-sitter/tree-sitter.wasm +0 -0
  125. package/scaffold/scripts/parsers/package-lock.json +19 -1
  126. package/scaffold/scripts/parsers/package.json +3 -1
  127. package/scaffold/scripts/parsers/python-treesitter.mjs +271 -0
  128. package/scaffold/scripts/parsers/ruby-treesitter.mjs +271 -0
  129. package/scaffold/scripts/parsers/rust-dispatch.mjs +43 -0
  130. package/scaffold/scripts/parsers/rust-treesitter.mjs +291 -0
  131. package/scaffold/scripts/parsers/tree-sitter/base.mjs +163 -0
  132. package/scaffold/scripts/parsers/tree-sitter/queries/bash.calls.scm +7 -0
  133. package/scaffold/scripts/parsers/tree-sitter/queries/bash.chunks.scm +6 -0
  134. package/scaffold/scripts/parsers/tree-sitter/queries/bash.imports.scm +5 -0
  135. package/scaffold/scripts/parsers/tree-sitter/queries/cpp.calls.scm +17 -0
  136. package/scaffold/scripts/parsers/tree-sitter/queries/cpp.chunks.scm +30 -0
  137. package/scaffold/scripts/parsers/tree-sitter/queries/cpp.imports.scm +6 -0
  138. package/scaffold/scripts/parsers/tree-sitter/queries/go.calls.scm +11 -0
  139. package/scaffold/scripts/parsers/tree-sitter/queries/go.chunks.scm +19 -0
  140. package/scaffold/scripts/parsers/tree-sitter/queries/go.imports.scm +6 -0
  141. package/scaffold/scripts/parsers/tree-sitter/queries/java.calls.scm +6 -0
  142. package/scaffold/scripts/parsers/tree-sitter/queries/java.chunks.scm +23 -0
  143. package/scaffold/scripts/parsers/tree-sitter/queries/java.imports.scm +6 -0
  144. package/scaffold/scripts/parsers/tree-sitter/queries/python.calls.scm +11 -0
  145. package/scaffold/scripts/parsers/tree-sitter/queries/python.chunks.scm +11 -0
  146. package/scaffold/scripts/parsers/tree-sitter/queries/python.imports.scm +13 -0
  147. package/scaffold/scripts/parsers/tree-sitter/queries/ruby.calls.scm +6 -0
  148. package/scaffold/scripts/parsers/tree-sitter/queries/ruby.chunks.scm +16 -0
  149. package/scaffold/scripts/parsers/tree-sitter/queries/ruby.imports.scm +8 -0
  150. package/scaffold/scripts/parsers/tree-sitter/queries/rust.calls.scm +31 -0
  151. package/scaffold/scripts/parsers/tree-sitter/queries/rust.chunks.scm +29 -0
  152. package/scaffold/scripts/parsers/tree-sitter/queries/rust.imports.scm +5 -0
  153. package/scaffold/scripts/parsers/vb6.mjs +395 -0
@@ -0,0 +1,271 @@
1
+ /**
2
+ * Tree-sitter Ruby parser for Cortex.
3
+ *
4
+ * Extracts class, module, method (instance `def`), and singleton_method
5
+ * (`def self.foo`) as chunks. Methods are qualified by enclosing
6
+ * class/module path, using Ruby-standard notation:
7
+ *
8
+ * top-level method: name = "foo"
9
+ * class instance method: name = "Foo#bar"
10
+ * class singleton method: name = "Foo.baz" (called as Foo.baz)
11
+ * nested module/class: name = "Outer::Inner"
12
+ * method in nested class: name = "Outer::Inner#run"
13
+ * singleton in nested: name = "Outer::Inner.load"
14
+ *
15
+ * The `#` vs `.` distinction is the long-standing Ruby documentation
16
+ * convention (e.g. "String#length" vs "String.new") and lets the
17
+ * call-graph distinguish overloads that share a bare method name.
18
+ *
19
+ * Imports: require / require_relative / load / autoload are captured
20
+ * as `call` nodes at program scope; the adapter filters by method
21
+ * name and extracts the string path argument (autoload takes the
22
+ * path as second arg).
23
+ *
24
+ * parseCode is async; the WASM grammar is lazily loaded on first call
25
+ * and cached for subsequent calls.
26
+ */
27
+
28
+ import path from "node:path";
29
+ import fs from "node:fs";
30
+ import { fileURLToPath } from "node:url";
31
+ import {
32
+ dedupe,
33
+ initTreeSitter,
34
+ lineRangeOf,
35
+ loadGrammar,
36
+ parseSource,
37
+ runQuery
38
+ } from "./tree-sitter/base.mjs";
39
+
40
+ const __filename = fileURLToPath(import.meta.url);
41
+ const __dirname = path.dirname(__filename);
42
+ const QUERY_DIR = path.join(__dirname, "tree-sitter", "queries");
43
+
44
+ let RUBY_LANG = null;
45
+ let langPromise = null;
46
+
47
+ async function ensureLanguage() {
48
+ if (RUBY_LANG) return RUBY_LANG;
49
+ if (!langPromise) {
50
+ langPromise = (async () => {
51
+ await initTreeSitter();
52
+ RUBY_LANG = await loadGrammar("ruby");
53
+ return RUBY_LANG;
54
+ })();
55
+ }
56
+ await langPromise;
57
+ return RUBY_LANG;
58
+ }
59
+
60
+ export async function isAvailable() {
61
+ try {
62
+ await ensureLanguage();
63
+ return true;
64
+ } catch {
65
+ return false;
66
+ }
67
+ }
68
+
69
+ const CHUNK_QUERY = fs.readFileSync(path.join(QUERY_DIR, "ruby.chunks.scm"), "utf8");
70
+ const CALL_QUERY = fs.readFileSync(path.join(QUERY_DIR, "ruby.calls.scm"), "utf8");
71
+ const IMPORT_QUERY = fs.readFileSync(path.join(QUERY_DIR, "ruby.imports.scm"), "utf8");
72
+
73
+ const LOADER_NAMES = new Set(["require", "require_relative", "load", "autoload"]);
74
+
75
+ // Keywords that parse as call nodes but aren't real call edges.
76
+ // `puts`, `print`, `p` are stdlib IO helpers — keep them out so the
77
+ // graph isn't dominated by debug/logging noise.
78
+ const CALL_FILTER = new Set([
79
+ "puts", "print", "p", "pp",
80
+ "require", "require_relative", "load", "autoload",
81
+ "attr_reader", "attr_writer", "attr_accessor",
82
+ "private", "protected", "public",
83
+ "raise", "throw", "catch",
84
+ "lambda", "proc"
85
+ ]);
86
+
87
+ function normalizeWhitespace(value) {
88
+ return String(value).replace(/\s+/g, " ").trim();
89
+ }
90
+
91
+ function signatureOfDecl(node) {
92
+ const firstNewline = node.text.indexOf("\n");
93
+ if (firstNewline === -1) return normalizeWhitespace(node.text);
94
+ return normalizeWhitespace(node.text.slice(0, firstNewline));
95
+ }
96
+
97
+ function unquoteString(node) {
98
+ // tree-sitter-ruby string nodes contain a string_content child.
99
+ for (let i = 0; i < node.namedChildCount; i += 1) {
100
+ const c = node.namedChild(i);
101
+ if (c.type === "string_content") return c.text;
102
+ }
103
+ // Fallback: strip surrounding quotes.
104
+ const text = node.text;
105
+ if (text.length >= 2 && (text.startsWith("'") || text.startsWith('"'))) {
106
+ return text.slice(1, -1);
107
+ }
108
+ return text;
109
+ }
110
+
111
+ function enclosingModulePath(node) {
112
+ const parts = [];
113
+ let cur = node.parent;
114
+ while (cur && cur.type !== "program") {
115
+ if (cur.type === "class" || cur.type === "module") {
116
+ const nameNode = cur.childForFieldName("name");
117
+ if (nameNode) parts.unshift(nameNode.text);
118
+ }
119
+ cur = cur.parent;
120
+ }
121
+ return parts;
122
+ }
123
+
124
+ function collectImports(rootNode) {
125
+ const captures = runQuery(RUBY_LANG, IMPORT_QUERY, rootNode);
126
+ const imports = [];
127
+
128
+ // Only top-level require calls count as imports (not calls made
129
+ // inside methods that happen to be named `require`).
130
+ const callNodes = new Map();
131
+ for (const cap of captures) {
132
+ if (cap.name === "import.call") {
133
+ callNodes.set(cap.node.id, cap.node);
134
+ }
135
+ }
136
+
137
+ for (const [, callNode] of callNodes) {
138
+ // Walk up: an import call's ancestors should be only program /
139
+ // body_statement, never a method body.
140
+ let ancestor = callNode.parent;
141
+ let isTopLevel = true;
142
+ while (ancestor) {
143
+ if (
144
+ ancestor.type === "method" ||
145
+ ancestor.type === "singleton_method" ||
146
+ ancestor.type === "block" ||
147
+ ancestor.type === "do_block"
148
+ ) {
149
+ isTopLevel = false;
150
+ break;
151
+ }
152
+ ancestor = ancestor.parent;
153
+ }
154
+ if (!isTopLevel) continue;
155
+
156
+ const methodNode = callNode.childForFieldName("method");
157
+ const argumentsNode = callNode.childForFieldName("arguments");
158
+ if (!methodNode || !argumentsNode) continue;
159
+ if (!LOADER_NAMES.has(methodNode.text)) continue;
160
+
161
+ const isAutoload = methodNode.text === "autoload";
162
+ let targetArgIndex = 0;
163
+ if (isAutoload) targetArgIndex = 1;
164
+ const stringArgs = [];
165
+ for (let i = 0; i < argumentsNode.namedChildCount; i += 1) {
166
+ const arg = argumentsNode.namedChild(i);
167
+ if (arg.type === "string") stringArgs.push(arg);
168
+ }
169
+ const target = stringArgs[targetArgIndex] ?? stringArgs[0];
170
+ if (target) imports.push(unquoteString(target));
171
+ }
172
+
173
+ return dedupe(imports);
174
+ }
175
+
176
+ function collectCallsInNode(node) {
177
+ const captures = runQuery(RUBY_LANG, CALL_QUERY, node);
178
+ const names = captures
179
+ .filter((c) => c.name === "call.name")
180
+ .map((c) => c.node.text)
181
+ .filter((name) => name && !CALL_FILTER.has(name));
182
+ return dedupe(names);
183
+ }
184
+
185
+ function buildTypeChunk(node, kind, language) {
186
+ const nameNode = node.childForFieldName("name");
187
+ if (!nameNode) return null;
188
+ const baseName = nameNode.text;
189
+ const parentPath = enclosingModulePath(node);
190
+ const qualifiedName = parentPath.length > 0
191
+ ? `${parentPath.join("::")}::${baseName}`
192
+ : baseName;
193
+ const { startLine, endLine } = lineRangeOf(node);
194
+ return {
195
+ name: qualifiedName,
196
+ kind,
197
+ signature: signatureOfDecl(node),
198
+ body: node.text,
199
+ startLine,
200
+ endLine,
201
+ language,
202
+ exported: true,
203
+ calls: [],
204
+ imports: []
205
+ };
206
+ }
207
+
208
+ function buildMethodChunk(node, imports, language, isSingleton) {
209
+ const nameNode = node.childForFieldName("name");
210
+ if (!nameNode) return null;
211
+ const methodName = nameNode.text;
212
+ const parentPath = enclosingModulePath(node);
213
+ const owner = parentPath.length > 0 ? parentPath.join("::") : "";
214
+ const separator = isSingleton ? "." : "#";
215
+ const qualifiedName = owner ? `${owner}${separator}${methodName}` : methodName;
216
+ const { startLine, endLine } = lineRangeOf(node);
217
+ return {
218
+ name: qualifiedName,
219
+ kind: isSingleton ? "class_method" : "method",
220
+ signature: signatureOfDecl(node),
221
+ body: node.text,
222
+ startLine,
223
+ endLine,
224
+ language,
225
+ exported: !methodName.startsWith("_"),
226
+ calls: collectCallsInNode(node),
227
+ imports
228
+ };
229
+ }
230
+
231
+ export async function parseCode(code, filePath, language = "ruby") {
232
+ await ensureLanguage();
233
+ const { tree } = parseSource(RUBY_LANG, code);
234
+ const root = tree.rootNode;
235
+ const imports = collectImports(root);
236
+
237
+ const captures = runQuery(RUBY_LANG, CHUNK_QUERY, root);
238
+ const declCaptures = captures.filter((c) => c.name.endsWith(".decl"));
239
+
240
+ const chunks = [];
241
+ for (const cap of declCaptures) {
242
+ const kind = cap.name.split(".")[0];
243
+ let chunk = null;
244
+ if (kind === "class") chunk = buildTypeChunk(cap.node, "class", language);
245
+ else if (kind === "module") chunk = buildTypeChunk(cap.node, "module", language);
246
+ else if (kind === "method") chunk = buildMethodChunk(cap.node, imports, language, false);
247
+ else if (kind === "singleton") chunk = buildMethodChunk(cap.node, imports, language, true);
248
+ if (chunk) chunks.push(chunk);
249
+ }
250
+
251
+ const seen = new Set();
252
+ const deduped = chunks.filter((chunk) => {
253
+ const key = `${chunk.kind}|${chunk.name}|${chunk.startLine}|${chunk.endLine}`;
254
+ if (seen.has(key)) return false;
255
+ seen.add(key);
256
+ return true;
257
+ });
258
+
259
+ return { chunks: deduped, errors: [] };
260
+ }
261
+
262
+ if (import.meta.url === `file://${process.argv[1]}`) {
263
+ const target = process.argv[2];
264
+ if (!target) {
265
+ console.error("Usage: ruby-treesitter.mjs <file.rb>");
266
+ process.exit(1);
267
+ }
268
+ const code = fs.readFileSync(target, "utf8");
269
+ const result = await parseCode(code, target, "ruby");
270
+ console.log(JSON.stringify(result, null, 2));
271
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Rust parser dispatcher.
3
+ *
4
+ * Selects between the tree-sitter parser (default, richer output) and
5
+ * the regex parser (fallback, zero deps) based on the CORTEX_RUST_PARSER
6
+ * environment variable. Selection is deferred until the first parseCode
7
+ * call so no WASM is loaded if the project contains no .rs files.
8
+ *
9
+ * CORTEX_RUST_PARSER=regex → always use regex parser
10
+ * CORTEX_RUST_PARSER=tree-sitter → force tree-sitter (error if unavailable)
11
+ * unset / other → tree-sitter with regex auto-fallback
12
+ */
13
+
14
+ const choice = process.env.CORTEX_RUST_PARSER;
15
+
16
+ let activeParser = null;
17
+ let resolvePromise = null;
18
+
19
+ async function resolveParser() {
20
+ if (activeParser) return activeParser;
21
+ if (resolvePromise) return resolvePromise;
22
+ resolvePromise = (async () => {
23
+ if (choice === "regex") {
24
+ activeParser = await import("./rust.mjs");
25
+ } else if (choice === "tree-sitter") {
26
+ activeParser = await import("./rust-treesitter.mjs");
27
+ } else {
28
+ const ts = await import("./rust-treesitter.mjs");
29
+ if (await ts.isAvailable()) {
30
+ activeParser = ts;
31
+ } else {
32
+ activeParser = await import("./rust.mjs");
33
+ }
34
+ }
35
+ return activeParser;
36
+ })();
37
+ return resolvePromise;
38
+ }
39
+
40
+ export async function parseCode(code, filePath, language = "rust") {
41
+ const parser = await resolveParser();
42
+ return parser.parseCode(code, filePath, language);
43
+ }
@@ -0,0 +1,291 @@
1
+ /**
2
+ * Tree-sitter Rust parser for Cortex.
3
+ *
4
+ * Produces the same chunk shape as scripts/parsers/rust.mjs (the regex
5
+ * parser) but uses tree-sitter-rust via web-tree-sitter WASM. This is
6
+ * the pilot language for the tree-sitter infrastructure; behavioral
7
+ * parity with the regex parser is verified by the existing rust-parser
8
+ * test suite run against this module.
9
+ *
10
+ * parseCode is async; the WASM grammar is lazily loaded on first call
11
+ * and cached for subsequent calls. Callers must `await parseCode(...)`.
12
+ */
13
+
14
+ import path from "node:path";
15
+ import fs from "node:fs";
16
+ import { fileURLToPath } from "node:url";
17
+ import {
18
+ dedupe,
19
+ initTreeSitter,
20
+ lineRangeOf,
21
+ loadGrammar,
22
+ parseSource,
23
+ runQuery
24
+ } from "./tree-sitter/base.mjs";
25
+
26
+ const __filename = fileURLToPath(import.meta.url);
27
+ const __dirname = path.dirname(__filename);
28
+ const QUERY_DIR = path.join(__dirname, "tree-sitter", "queries");
29
+
30
+ let RUST_LANG = null;
31
+ let langPromise = null;
32
+
33
+ async function ensureLanguage() {
34
+ if (RUST_LANG) return RUST_LANG;
35
+ if (!langPromise) {
36
+ langPromise = (async () => {
37
+ await initTreeSitter();
38
+ RUST_LANG = await loadGrammar("rust");
39
+ return RUST_LANG;
40
+ })();
41
+ }
42
+ await langPromise;
43
+ return RUST_LANG;
44
+ }
45
+
46
+ export async function isAvailable() {
47
+ try {
48
+ await ensureLanguage();
49
+ return true;
50
+ } catch {
51
+ return false;
52
+ }
53
+ }
54
+
55
+ const CHUNK_QUERY = fs.readFileSync(path.join(QUERY_DIR, "rust.chunks.scm"), "utf8");
56
+ const CALL_QUERY = fs.readFileSync(path.join(QUERY_DIR, "rust.calls.scm"), "utf8");
57
+ const IMPORT_QUERY = fs.readFileSync(path.join(QUERY_DIR, "rust.imports.scm"), "utf8");
58
+
59
+ const CALL_KEYWORDS = new Set([
60
+ "if", "for", "while", "loop", "match", "return",
61
+ "Some", "None", "Ok", "Err", "Box", "Vec", "String",
62
+ "println", "eprintln", "format", "write", "writeln",
63
+ "panic", "todo", "unimplemented", "unreachable",
64
+ "assert", "assert_eq", "assert_ne", "debug_assert",
65
+ "debug_assert_eq", "debug_assert_ne",
66
+ "cfg", "derive", "allow", "warn", "deny"
67
+ ]);
68
+
69
+ function normalizeWhitespace(value) {
70
+ return String(value).replace(/\s+/g, " ").trim();
71
+ }
72
+
73
+ function buildSignature(bodyText) {
74
+ const braceIndex = bodyText.indexOf("{");
75
+ if (braceIndex === -1) return normalizeWhitespace(bodyText);
76
+ return normalizeWhitespace(bodyText.slice(0, braceIndex));
77
+ }
78
+
79
+ function collectImports(rootNode) {
80
+ const captures = runQuery(RUST_LANG, IMPORT_QUERY, rootNode);
81
+ const imports = captures
82
+ .filter((c) => c.name === "use.path")
83
+ .map((c) => normalizeWhitespace(c.node.text));
84
+ return dedupe(imports);
85
+ }
86
+
87
+ const MACRO_INNER_CALL_PATTERN = /\b([A-Za-z_][A-Za-z0-9_]*)\s*\(/g;
88
+
89
+ function collectCallsFromMacroBodies(node) {
90
+ const names = [];
91
+ const visit = (n) => {
92
+ if (n.type === "macro_invocation") {
93
+ for (let i = 0; i < n.namedChildCount; i += 1) {
94
+ const child = n.namedChild(i);
95
+ if (child.type !== "token_tree") continue;
96
+ const text = child.text;
97
+ MACRO_INNER_CALL_PATTERN.lastIndex = 0;
98
+ let m;
99
+ while ((m = MACRO_INNER_CALL_PATTERN.exec(text)) !== null) {
100
+ names.push(m[1]);
101
+ }
102
+ }
103
+ return;
104
+ }
105
+ for (let i = 0; i < n.namedChildCount; i += 1) visit(n.namedChild(i));
106
+ };
107
+ visit(node);
108
+ return names.filter((n) => !CALL_KEYWORDS.has(n));
109
+ }
110
+
111
+ function collectCallsInNode(node) {
112
+ const captures = runQuery(RUST_LANG, CALL_QUERY, node);
113
+ const astNames = captures
114
+ .filter((c) => c.name === "call.name")
115
+ .map((c) => c.node.text)
116
+ .filter((name) => name && !CALL_KEYWORDS.has(name));
117
+ const macroInnerNames = collectCallsFromMacroBodies(node);
118
+ return dedupe([...astNames, ...macroInnerNames]);
119
+ }
120
+
121
+ /**
122
+ * Walk captures from CHUNK_QUERY and group companion captures with
123
+ * their decl anchor. Returns [{ kind, decl, name, typeNode }] entries
124
+ * in document order.
125
+ */
126
+ function groupDeclarations(rootNode) {
127
+ const captures = runQuery(RUST_LANG, CHUNK_QUERY, rootNode);
128
+
129
+ const declCaptures = captures.filter((c) => c.name.endsWith(".decl"));
130
+ declCaptures.sort((a, b) => a.node.startIndex - b.node.startIndex);
131
+
132
+ const entries = declCaptures.map((c) => ({
133
+ kind: c.name.split(".")[0],
134
+ node: c.node,
135
+ meta: {}
136
+ }));
137
+
138
+ for (const cap of captures) {
139
+ if (cap.name.endsWith(".decl")) continue;
140
+ const [kind, field] = cap.name.split(".");
141
+ let closest = null;
142
+ let closestSize = Infinity;
143
+ for (const entry of entries) {
144
+ if (entry.kind !== kind) continue;
145
+ if (cap.node.startIndex < entry.node.startIndex) continue;
146
+ if (cap.node.endIndex > entry.node.endIndex) continue;
147
+ const size = entry.node.endIndex - entry.node.startIndex;
148
+ if (size < closestSize) {
149
+ closest = entry;
150
+ closestSize = size;
151
+ }
152
+ }
153
+ if (!closest) continue;
154
+ if (!(field in closest.meta)) {
155
+ closest.meta[field] = cap.node;
156
+ }
157
+ }
158
+
159
+ return entries;
160
+ }
161
+
162
+ function chunkFrom(kind, node, name, signatureOverride, calls, imports, language) {
163
+ const { startLine, endLine } = lineRangeOf(node);
164
+ return {
165
+ name,
166
+ kind,
167
+ signature: signatureOverride ?? buildSignature(node.text),
168
+ body: node.text,
169
+ startLine,
170
+ endLine,
171
+ language,
172
+ calls,
173
+ imports
174
+ };
175
+ }
176
+
177
+ function isInsideAny(node, others) {
178
+ return others.some(
179
+ (o) =>
180
+ node.startIndex >= o.startIndex &&
181
+ node.endIndex <= o.endIndex &&
182
+ node !== o
183
+ );
184
+ }
185
+
186
+ function extractFunctionCalls(functionNode) {
187
+ const bodyNode = functionNode.childForFieldName("body");
188
+ if (!bodyNode) return [];
189
+ return collectCallsInNode(bodyNode);
190
+ }
191
+
192
+ export async function parseCode(code, filePath, language = "rust") {
193
+ await ensureLanguage();
194
+ const { tree } = parseSource(RUST_LANG, code);
195
+ const root = tree.rootNode;
196
+
197
+ const imports = collectImports(root);
198
+ const decls = groupDeclarations(root);
199
+
200
+ const implNodes = decls.filter((d) => d.kind === "impl").map((d) => d.node);
201
+
202
+ const chunks = [];
203
+
204
+ for (const entry of decls) {
205
+ const { kind, node, meta } = entry;
206
+
207
+ if (kind === "fn") {
208
+ if (isInsideAny(node, implNodes)) continue;
209
+ const name = meta.name?.text;
210
+ if (!name) continue;
211
+ chunks.push(
212
+ chunkFrom("function", node, name, null, extractFunctionCalls(node), imports, language)
213
+ );
214
+ } else if (kind === "struct") {
215
+ const name = meta.name?.text;
216
+ if (!name) continue;
217
+ const isUnit = !node.text.includes("{");
218
+ const signature = isUnit ? normalizeWhitespace(node.text) : buildSignature(node.text);
219
+ chunks.push(chunkFrom("struct", node, name, signature, [], [], language));
220
+ } else if (kind === "enum") {
221
+ const name = meta.name?.text;
222
+ if (!name) continue;
223
+ chunks.push(chunkFrom("enum", node, name, null, [], [], language));
224
+ } else if (kind === "trait") {
225
+ const name = meta.name?.text;
226
+ if (!name) continue;
227
+ chunks.push(chunkFrom("trait", node, name, null, [], [], language));
228
+ } else if (kind === "mod") {
229
+ const name = meta.name?.text;
230
+ if (!name) continue;
231
+ chunks.push(chunkFrom("module", node, name, null, [], [], language));
232
+ } else if (kind === "macro") {
233
+ const name = meta.name?.text;
234
+ if (!name) continue;
235
+ chunks.push(
236
+ chunkFrom("macro", node, name, `macro_rules! ${name}`, [], [], language)
237
+ );
238
+ } else if (kind === "impl") {
239
+ const typeName = meta.type?.text;
240
+ if (!typeName) continue;
241
+ const traitNode = node.childForFieldName("trait");
242
+ const implName = traitNode ? `${traitNode.text} for ${typeName}` : typeName;
243
+ chunks.push(chunkFrom("impl", node, implName, null, [], [], language));
244
+
245
+ const bodyNode = node.childForFieldName("body");
246
+ if (!bodyNode) continue;
247
+ for (let i = 0; i < bodyNode.namedChildCount; i += 1) {
248
+ const child = bodyNode.namedChild(i);
249
+ if (child.type !== "function_item") continue;
250
+ const fnName = child.childForFieldName("name")?.text;
251
+ if (!fnName) continue;
252
+ const hasBody = child.childForFieldName("body");
253
+ if (!hasBody) continue;
254
+ const qualifiedName = `${typeName}::${fnName}`;
255
+ const { startLine, endLine } = lineRangeOf(child);
256
+ chunks.push({
257
+ name: qualifiedName,
258
+ kind: "method",
259
+ signature: buildSignature(child.text),
260
+ body: child.text,
261
+ startLine,
262
+ endLine,
263
+ language,
264
+ calls: extractFunctionCalls(child),
265
+ imports
266
+ });
267
+ }
268
+ }
269
+ }
270
+
271
+ const seen = new Set();
272
+ const deduped = chunks.filter((chunk) => {
273
+ const key = `${chunk.kind}|${chunk.name}|${chunk.startLine}|${chunk.endLine}`;
274
+ if (seen.has(key)) return false;
275
+ seen.add(key);
276
+ return true;
277
+ });
278
+
279
+ return { chunks: deduped, errors: [] };
280
+ }
281
+
282
+ if (import.meta.url === `file://${process.argv[1]}`) {
283
+ const filePath = process.argv[2];
284
+ if (!filePath) {
285
+ console.error("Usage: rust-treesitter.mjs <file.rs>");
286
+ process.exit(1);
287
+ }
288
+ const code = fs.readFileSync(filePath, "utf8");
289
+ const result = await parseCode(code, filePath, "rust");
290
+ console.log(JSON.stringify(result, null, 2));
291
+ }