@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,229 @@
1
+ /**
2
+ * Tree-sitter Bash parser for Cortex.
3
+ *
4
+ * Extracts `function_definition` nodes as chunks — both
5
+ * `function foo { ... }` and `foo() { ... }` styles are handled by
6
+ * tree-sitter-bash as the same node type.
7
+ *
8
+ * Imports cover `source path.sh` and `. path.sh` commands at program
9
+ * scope. Dynamic path expressions like `. "$(dirname "$0")/lib.sh"`
10
+ * are skipped — only static `word` arguments are extracted, since
11
+ * anything else can't be resolved at parse time.
12
+ *
13
+ * Call extraction captures the `command_name` of every `command`
14
+ * node, filtered against a large list of shell builtins and
15
+ * ubiquitous system commands so the call graph reflects user-defined
16
+ * function calls rather than shell plumbing.
17
+ *
18
+ * Naming:
19
+ * top-level function: name = "deploy"
20
+ * nested function: name = "inner" (shell has no real nesting scope)
21
+ *
22
+ * exported: true iff the function name does NOT start with an
23
+ * underscore. Shell has no export/import model per se; this mirrors
24
+ * the convention used by Python and Ruby parsers.
25
+ *
26
+ * parseCode is async; the WASM grammar is lazily loaded on first call
27
+ * and cached for subsequent calls.
28
+ */
29
+
30
+ import path from "node:path";
31
+ import fs from "node:fs";
32
+ import { fileURLToPath } from "node:url";
33
+ import {
34
+ dedupe,
35
+ initTreeSitter,
36
+ lineRangeOf,
37
+ loadGrammar,
38
+ parseSource,
39
+ runQuery
40
+ } from "./tree-sitter/base.mjs";
41
+
42
+ const __filename = fileURLToPath(import.meta.url);
43
+ const __dirname = path.dirname(__filename);
44
+ const QUERY_DIR = path.join(__dirname, "tree-sitter", "queries");
45
+
46
+ let BASH_LANG = null;
47
+ let langPromise = null;
48
+
49
+ async function ensureLanguage() {
50
+ if (BASH_LANG) return BASH_LANG;
51
+ if (!langPromise) {
52
+ langPromise = (async () => {
53
+ await initTreeSitter();
54
+ BASH_LANG = await loadGrammar("bash");
55
+ return BASH_LANG;
56
+ })();
57
+ }
58
+ await langPromise;
59
+ return BASH_LANG;
60
+ }
61
+
62
+ export async function isAvailable() {
63
+ try {
64
+ await ensureLanguage();
65
+ return true;
66
+ } catch {
67
+ return false;
68
+ }
69
+ }
70
+
71
+ const CHUNK_QUERY = fs.readFileSync(path.join(QUERY_DIR, "bash.chunks.scm"), "utf8");
72
+ const CALL_QUERY = fs.readFileSync(path.join(QUERY_DIR, "bash.calls.scm"), "utf8");
73
+ const IMPORT_QUERY = fs.readFileSync(path.join(QUERY_DIR, "bash.imports.scm"), "utf8");
74
+
75
+ const LOADER_COMMANDS = new Set(["source", "."]);
76
+
77
+ // Shell builtins and ubiquitous system commands. Filtered out of the
78
+ // call graph so it reflects user-defined function calls, not shell
79
+ // plumbing. Keeping this list deliberately broad — the goal is graph
80
+ // signal-to-noise, not exhaustive coverage.
81
+ const CALL_FILTER = new Set([
82
+ // Builtins
83
+ "echo", "printf", "read", "test", "[", "[[", "true", "false",
84
+ "exit", "return", "break", "continue", "shift", "trap",
85
+ "export", "unset", "set", "local", "declare", "readonly", "typeset",
86
+ "let", "eval", "exec", "source", ".", "alias", "unalias", "type",
87
+ "command", "builtin", "hash", "help", "jobs", "fg", "bg", "kill",
88
+ "pwd", "cd", "pushd", "popd", "dirs", "umask", "ulimit",
89
+ "getopts", "shopt", "enable", "history", "fc", "logout", "suspend",
90
+ "wait", "times", "login", "complete", "compgen",
91
+ // Common system commands
92
+ "ls", "cat", "grep", "sed", "awk", "cut", "sort", "uniq", "wc",
93
+ "head", "tail", "tee", "find", "xargs", "tr", "rev",
94
+ "cp", "mv", "rm", "mkdir", "rmdir", "ln", "touch", "chmod", "chown",
95
+ "tar", "gzip", "gunzip", "zip", "unzip", "curl", "wget",
96
+ "git", "docker", "kubectl", "make", "npm", "yarn",
97
+ "python", "python3", "node", "ruby", "go", "bash", "sh", "zsh",
98
+ "env", "which", "whereis", "whoami", "id", "uname", "hostname",
99
+ "date", "sleep", "ps", "top", "df", "du", "mount", "umount",
100
+ "ssh", "scp", "rsync", "ping", "netstat", "ifconfig", "ip",
101
+ "basename", "dirname", "realpath", "readlink", "file", "stat",
102
+ "awk", "perl", "dd"
103
+ ]);
104
+
105
+ function normalizeWhitespace(value) {
106
+ return String(value).replace(/\s+/g, " ").trim();
107
+ }
108
+
109
+ function signatureOfDecl(node) {
110
+ const braceIndex = node.text.indexOf("{");
111
+ if (braceIndex === -1) return normalizeWhitespace(node.text);
112
+ return normalizeWhitespace(node.text.slice(0, braceIndex));
113
+ }
114
+
115
+ function collectCallsInNode(node) {
116
+ const captures = runQuery(BASH_LANG, CALL_QUERY, node);
117
+ const names = captures
118
+ .filter((c) => c.name === "call.name")
119
+ .map((c) => c.node.text)
120
+ .filter((name) => {
121
+ if (!name) return false;
122
+ if (CALL_FILTER.has(name)) return false;
123
+ // Strip absolute paths to compare: /usr/bin/foo -> foo
124
+ const trimmed = name.includes("/") ? name.slice(name.lastIndexOf("/") + 1) : name;
125
+ if (CALL_FILTER.has(trimmed)) return false;
126
+ return true;
127
+ });
128
+ return dedupe(names);
129
+ }
130
+
131
+ function collectImports(rootNode) {
132
+ const captures = runQuery(BASH_LANG, IMPORT_QUERY, rootNode);
133
+ const imports = [];
134
+ for (const cap of captures) {
135
+ if (cap.name !== "import.cmd") continue;
136
+ const cmd = cap.node;
137
+
138
+ // Only top-level sourcing counts as an "import" — nested
139
+ // source-calls inside function bodies are conditional runtime
140
+ // behavior rather than declared dependencies.
141
+ let ancestor = cmd.parent;
142
+ let isTopLevel = true;
143
+ while (ancestor) {
144
+ if (
145
+ ancestor.type === "function_definition" ||
146
+ ancestor.type === "compound_statement" ||
147
+ ancestor.type === "subshell"
148
+ ) {
149
+ isTopLevel = false;
150
+ break;
151
+ }
152
+ ancestor = ancestor.parent;
153
+ }
154
+ if (!isTopLevel) continue;
155
+
156
+ const nameNode = cmd.childForFieldName("name") ?? cmd.namedChild(0);
157
+ if (!nameNode) continue;
158
+ const commandText = nameNode.text;
159
+ if (!LOADER_COMMANDS.has(commandText)) continue;
160
+
161
+ // First static `word` argument is the sourced path. Dynamic
162
+ // expressions (strings, substitutions) are skipped.
163
+ for (let i = 0; i < cmd.namedChildCount; i += 1) {
164
+ const child = cmd.namedChild(i);
165
+ if (child === nameNode) continue;
166
+ if (child.type === "word") {
167
+ imports.push(child.text);
168
+ break;
169
+ }
170
+ }
171
+ }
172
+ return dedupe(imports);
173
+ }
174
+
175
+ function buildFunctionChunk(node, imports, language) {
176
+ const nameNode = node.childForFieldName("name");
177
+ if (!nameNode) return null;
178
+ const name = nameNode.text;
179
+ const { startLine, endLine } = lineRangeOf(node);
180
+ return {
181
+ name,
182
+ kind: "function",
183
+ signature: signatureOfDecl(node),
184
+ body: node.text,
185
+ startLine,
186
+ endLine,
187
+ language,
188
+ exported: !name.startsWith("_"),
189
+ calls: collectCallsInNode(node),
190
+ imports
191
+ };
192
+ }
193
+
194
+ export async function parseCode(code, filePath, language = "bash") {
195
+ await ensureLanguage();
196
+ const { tree } = parseSource(BASH_LANG, code);
197
+ const root = tree.rootNode;
198
+ const imports = collectImports(root);
199
+
200
+ const captures = runQuery(BASH_LANG, CHUNK_QUERY, root);
201
+ const declCaptures = captures.filter((c) => c.name.endsWith(".decl"));
202
+
203
+ const chunks = [];
204
+ for (const cap of declCaptures) {
205
+ const chunk = buildFunctionChunk(cap.node, imports, language);
206
+ if (chunk) chunks.push(chunk);
207
+ }
208
+
209
+ const seen = new Set();
210
+ const deduped = chunks.filter((chunk) => {
211
+ const key = `${chunk.kind}|${chunk.name}|${chunk.startLine}|${chunk.endLine}`;
212
+ if (seen.has(key)) return false;
213
+ seen.add(key);
214
+ return true;
215
+ });
216
+
217
+ return { chunks: deduped, errors: [] };
218
+ }
219
+
220
+ if (import.meta.url === `file://${process.argv[1]}`) {
221
+ const target = process.argv[2];
222
+ if (!target) {
223
+ console.error("Usage: bash-treesitter.mjs <file.sh>");
224
+ process.exit(1);
225
+ }
226
+ const code = fs.readFileSync(target, "utf8");
227
+ const result = await parseCode(code, target, "bash");
228
+ console.log(JSON.stringify(result, null, 2));
229
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * C/C++ parser dispatcher.
3
+ *
4
+ * Selects between the tree-sitter parser (default, no runtime deps)
5
+ * and the legacy clang-bridge parser based on the CORTEX_CPP_PARSER
6
+ * environment variable. Selection is deferred until the first parseCode
7
+ * call so no WASM is loaded if the project contains no C/C++ files.
8
+ *
9
+ * CORTEX_CPP_PARSER=clang → always use clang-bridge
10
+ * CORTEX_CPP_PARSER=tree-sitter → force tree-sitter (error if unavailable)
11
+ * unset / other → tree-sitter with clang auto-fallback
12
+ */
13
+
14
+ const choice = process.env.CORTEX_CPP_PARSER;
15
+
16
+ let activeParser = null;
17
+ let resolvePromise = null;
18
+
19
+ function availabilityOf(parser) {
20
+ if (typeof parser.isAvailable === "function") return parser.isAvailable;
21
+ if (typeof parser.isCppParserAvailable === "function") return parser.isCppParserAvailable;
22
+ return () => typeof parser.parseCode === "function";
23
+ }
24
+
25
+ async function resolveParser() {
26
+ if (activeParser) return activeParser;
27
+ if (resolvePromise) return resolvePromise;
28
+ resolvePromise = (async () => {
29
+ if (choice === "clang") {
30
+ activeParser = await import("./cpp.mjs");
31
+ } else if (choice === "tree-sitter") {
32
+ activeParser = await import("./cpp-treesitter.mjs");
33
+ } else {
34
+ const ts = await import("./cpp-treesitter.mjs");
35
+ if (await ts.isAvailable()) {
36
+ activeParser = ts;
37
+ } else {
38
+ activeParser = await import("./cpp.mjs");
39
+ }
40
+ }
41
+ return activeParser;
42
+ })();
43
+ return resolvePromise;
44
+ }
45
+
46
+ export async function parseCode(code, filePath, language = "cpp") {
47
+ const parser = await resolveParser();
48
+ return parser.parseCode(code, filePath, language);
49
+ }
50
+
51
+ export async function isCppParserAvailable() {
52
+ const parser = await resolveParser();
53
+ const check = availabilityOf(parser);
54
+ const result = check();
55
+ return result && typeof result.then === "function" ? await result : result;
56
+ }
@@ -0,0 +1,333 @@
1
+ /**
2
+ * Tree-sitter C/C++ parser for Cortex.
3
+ *
4
+ * Uses tree-sitter-cpp (a superset that parses C correctly) as a
5
+ * single grammar for .c, .h, .cpp, .cc, .hpp, .hh files. This removes
6
+ * the clang runtime dependency that the legacy cpp.mjs parser required.
7
+ *
8
+ * Chunk shape matches the Cortex convention. Methods and nested types
9
+ * are qualified by their enclosing class/struct/union/namespace path
10
+ * using `::` as the separator, matching C++ source syntax.
11
+ *
12
+ * Naming:
13
+ * free function: name = "add"
14
+ * method in class body: name = "Foo::bar"
15
+ * out-of-class method: name = "Foo::bar" (def like `Foo::bar()`)
16
+ * nested class: name = "Outer::Inner"
17
+ * namespace function: name = "app::handler"
18
+ * namespace class: name = "app::Service"
19
+ *
20
+ * parseCode is async; the WASM grammar is lazily loaded on first call
21
+ * and cached for subsequent calls.
22
+ */
23
+
24
+ import path from "node:path";
25
+ import fs from "node:fs";
26
+ import { fileURLToPath } from "node:url";
27
+ import {
28
+ dedupe,
29
+ initTreeSitter,
30
+ lineRangeOf,
31
+ loadGrammar,
32
+ parseSource,
33
+ runQuery
34
+ } from "./tree-sitter/base.mjs";
35
+
36
+ const __filename = fileURLToPath(import.meta.url);
37
+ const __dirname = path.dirname(__filename);
38
+ const QUERY_DIR = path.join(__dirname, "tree-sitter", "queries");
39
+
40
+ let CPP_LANG = null;
41
+ let langPromise = null;
42
+
43
+ async function ensureLanguage() {
44
+ if (CPP_LANG) return CPP_LANG;
45
+ if (!langPromise) {
46
+ langPromise = (async () => {
47
+ await initTreeSitter();
48
+ CPP_LANG = await loadGrammar("cpp");
49
+ return CPP_LANG;
50
+ })();
51
+ }
52
+ await langPromise;
53
+ return CPP_LANG;
54
+ }
55
+
56
+ const CHUNK_QUERY = fs.readFileSync(path.join(QUERY_DIR, "cpp.chunks.scm"), "utf8");
57
+ const CALL_QUERY = fs.readFileSync(path.join(QUERY_DIR, "cpp.calls.scm"), "utf8");
58
+ const IMPORT_QUERY = fs.readFileSync(path.join(QUERY_DIR, "cpp.imports.scm"), "utf8");
59
+
60
+ // Calls that are control-flow, builtins, or stdlib logging noise —
61
+ // kept out of the graph so real function-to-function edges stand out.
62
+ const CALL_FILTER = new Set([
63
+ "sizeof", "alignof", "typeid", "decltype", "typeof",
64
+ "static_cast", "dynamic_cast", "reinterpret_cast", "const_cast",
65
+ "printf", "fprintf", "sprintf", "snprintf", "puts",
66
+ "malloc", "free", "calloc", "realloc", "memcpy", "memset", "memmove"
67
+ ]);
68
+
69
+ function normalizeWhitespace(value) {
70
+ return String(value).replace(/\s+/g, " ").trim();
71
+ }
72
+
73
+ function signatureOfDecl(node) {
74
+ const braceIndex = node.text.indexOf("{");
75
+ const semiIndex = node.text.indexOf(";");
76
+ const end = braceIndex === -1 ? semiIndex : (semiIndex === -1 ? braceIndex : Math.min(braceIndex, semiIndex));
77
+ if (end === -1) return normalizeWhitespace(node.text);
78
+ return normalizeWhitespace(node.text.slice(0, end));
79
+ }
80
+
81
+ function collectCallsInNode(node) {
82
+ const captures = runQuery(CPP_LANG, CALL_QUERY, node);
83
+ const names = captures
84
+ .filter((c) => c.name === "call.name")
85
+ .map((c) => c.node.text)
86
+ .filter((name) => name && !CALL_FILTER.has(name));
87
+ return dedupe(names);
88
+ }
89
+
90
+ function collectImports(rootNode) {
91
+ const captures = runQuery(CPP_LANG, IMPORT_QUERY, rootNode);
92
+ const imports = [];
93
+ for (const cap of captures) {
94
+ if (cap.name !== "include.decl") continue;
95
+ const decl = cap.node;
96
+ for (let i = 0; i < decl.namedChildCount; i += 1) {
97
+ const child = decl.namedChild(i);
98
+ if (child.type === "system_lib_string") {
99
+ // <vector> — strip angle brackets
100
+ const text = child.text;
101
+ imports.push(text.startsWith("<") && text.endsWith(">") ? text.slice(1, -1) : text);
102
+ } else if (child.type === "string_literal") {
103
+ // "local.h" — walk for string_content
104
+ for (let j = 0; j < child.namedChildCount; j += 1) {
105
+ const inner = child.namedChild(j);
106
+ if (inner.type === "string_content") {
107
+ imports.push(inner.text);
108
+ break;
109
+ }
110
+ }
111
+ }
112
+ }
113
+ }
114
+ return dedupe(imports);
115
+ }
116
+
117
+ /**
118
+ * Walk up the tree and collect names of enclosing classes, structs,
119
+ * unions, and namespaces. Nested namespaces like `namespace a::b`
120
+ * contribute both `a` and `b` to the path.
121
+ */
122
+ function enclosingScopePath(node) {
123
+ const parts = [];
124
+ let cur = node.parent;
125
+ while (cur && cur.type !== "translation_unit") {
126
+ if (
127
+ cur.type === "class_specifier" ||
128
+ cur.type === "struct_specifier" ||
129
+ cur.type === "union_specifier"
130
+ ) {
131
+ const nameNode = cur.childForFieldName("name");
132
+ if (nameNode) parts.unshift(nameNode.text);
133
+ } else if (cur.type === "namespace_definition") {
134
+ parts.unshift(...namespaceDefinitionNames(cur));
135
+ }
136
+ cur = cur.parent;
137
+ }
138
+ return parts;
139
+ }
140
+
141
+ function namespaceDefinitionNames(nsNode) {
142
+ const names = [];
143
+ // Could have a single namespace_identifier OR a nested_namespace_specifier
144
+ for (let i = 0; i < nsNode.namedChildCount; i += 1) {
145
+ const child = nsNode.namedChild(i);
146
+ if (child.type === "namespace_identifier") {
147
+ names.push(child.text);
148
+ } else if (child.type === "nested_namespace_specifier") {
149
+ for (let j = 0; j < child.namedChildCount; j += 1) {
150
+ const inner = child.namedChild(j);
151
+ if (inner.type === "namespace_identifier") names.push(inner.text);
152
+ }
153
+ }
154
+ }
155
+ return names;
156
+ }
157
+
158
+ /**
159
+ * Extract the function name and any qualifying path from a
160
+ * function_definition's function_declarator. Returns { name, isMethod }.
161
+ *
162
+ * - identifier inside declarator → free function, name = identifier
163
+ * - field_identifier inside declarator (in class body) → method,
164
+ * name = field identifier, qualifies with enclosing class
165
+ * - qualified_identifier (like `Foo::bar`) → out-of-class method,
166
+ * name encoded as `Foo::bar` directly
167
+ */
168
+ function functionNameFrom(fnDefNode) {
169
+ let declarator = null;
170
+ for (let i = 0; i < fnDefNode.namedChildCount; i += 1) {
171
+ const child = fnDefNode.namedChild(i);
172
+ if (child.type === "function_declarator") {
173
+ declarator = child;
174
+ break;
175
+ }
176
+ if (child.type === "pointer_declarator" || child.type === "reference_declarator") {
177
+ for (let j = 0; j < child.namedChildCount; j += 1) {
178
+ const inner = child.namedChild(j);
179
+ if (inner.type === "function_declarator") {
180
+ declarator = inner;
181
+ break;
182
+ }
183
+ }
184
+ if (declarator) break;
185
+ }
186
+ }
187
+ if (!declarator) return null;
188
+
189
+ for (let i = 0; i < declarator.namedChildCount; i += 1) {
190
+ const child = declarator.namedChild(i);
191
+ if (child.type === "identifier") {
192
+ return { name: child.text, qualifiedForm: null };
193
+ }
194
+ if (child.type === "field_identifier") {
195
+ return { name: child.text, qualifiedForm: null };
196
+ }
197
+ if (child.type === "qualified_identifier") {
198
+ return { name: null, qualifiedForm: child.text };
199
+ }
200
+ }
201
+ return null;
202
+ }
203
+
204
+ function buildFunctionChunk(node, imports, language) {
205
+ const nameInfo = functionNameFrom(node);
206
+ if (!nameInfo) return null;
207
+
208
+ let qualifiedName;
209
+ let kind;
210
+
211
+ if (nameInfo.qualifiedForm) {
212
+ qualifiedName = nameInfo.qualifiedForm;
213
+ kind = "method";
214
+ } else {
215
+ const scope = enclosingScopePath(node);
216
+ const baseName = nameInfo.name;
217
+ if (scope.length > 0) {
218
+ qualifiedName = `${scope.join("::")}::${baseName}`;
219
+ kind = "method";
220
+ } else {
221
+ qualifiedName = baseName;
222
+ kind = "function";
223
+ }
224
+ }
225
+
226
+ const { startLine, endLine } = lineRangeOf(node);
227
+ return {
228
+ name: qualifiedName,
229
+ kind,
230
+ signature: signatureOfDecl(node),
231
+ body: node.text,
232
+ startLine,
233
+ endLine,
234
+ language,
235
+ exported: true,
236
+ calls: collectCallsInNode(node),
237
+ imports
238
+ };
239
+ }
240
+
241
+ function buildTypeChunk(node, kind, language) {
242
+ const nameNode = node.childForFieldName("name");
243
+ if (!nameNode) return null;
244
+ const baseName = nameNode.text;
245
+ const scope = enclosingScopePath(node);
246
+ const qualifiedName = scope.length > 0 ? `${scope.join("::")}::${baseName}` : baseName;
247
+ const { startLine, endLine } = lineRangeOf(node);
248
+ return {
249
+ name: qualifiedName,
250
+ kind,
251
+ signature: signatureOfDecl(node),
252
+ body: node.text,
253
+ startLine,
254
+ endLine,
255
+ language,
256
+ exported: true,
257
+ calls: [],
258
+ imports: []
259
+ };
260
+ }
261
+
262
+ function buildNamespaceChunk(node, language) {
263
+ const names = namespaceDefinitionNames(node);
264
+ if (names.length === 0) return null;
265
+ const scope = enclosingScopePath(node);
266
+ const fullPath = [...scope, ...names].join("::");
267
+ const { startLine, endLine } = lineRangeOf(node);
268
+ return {
269
+ name: fullPath,
270
+ kind: "namespace",
271
+ signature: signatureOfDecl(node),
272
+ body: node.text,
273
+ startLine,
274
+ endLine,
275
+ language,
276
+ exported: true,
277
+ calls: [],
278
+ imports: []
279
+ };
280
+ }
281
+
282
+ export async function parseCode(code, filePath, language = "cpp") {
283
+ await ensureLanguage();
284
+ const { tree } = parseSource(CPP_LANG, code);
285
+ const root = tree.rootNode;
286
+ const imports = collectImports(root);
287
+
288
+ const captures = runQuery(CPP_LANG, CHUNK_QUERY, root);
289
+ const declCaptures = captures.filter((c) => c.name.endsWith(".decl"));
290
+
291
+ const chunks = [];
292
+ for (const cap of declCaptures) {
293
+ const kindTag = cap.name.split(".")[0];
294
+ let chunk = null;
295
+ if (kindTag === "fn") chunk = buildFunctionChunk(cap.node, imports, language);
296
+ else if (kindTag === "class") chunk = buildTypeChunk(cap.node, "class", language);
297
+ else if (kindTag === "struct") chunk = buildTypeChunk(cap.node, "struct", language);
298
+ else if (kindTag === "union") chunk = buildTypeChunk(cap.node, "union", language);
299
+ else if (kindTag === "enum") chunk = buildTypeChunk(cap.node, "enum", language);
300
+ else if (kindTag === "namespace") chunk = buildNamespaceChunk(cap.node, language);
301
+ if (chunk) chunks.push(chunk);
302
+ }
303
+
304
+ const seen = new Set();
305
+ const deduped = chunks.filter((chunk) => {
306
+ const key = `${chunk.kind}|${chunk.name}|${chunk.startLine}|${chunk.endLine}`;
307
+ if (seen.has(key)) return false;
308
+ seen.add(key);
309
+ return true;
310
+ });
311
+
312
+ return { chunks: deduped, errors: [] };
313
+ }
314
+
315
+ export async function isAvailable() {
316
+ try {
317
+ await ensureLanguage();
318
+ return true;
319
+ } catch {
320
+ return false;
321
+ }
322
+ }
323
+
324
+ if (import.meta.url === `file://${process.argv[1]}`) {
325
+ const target = process.argv[2];
326
+ if (!target) {
327
+ console.error("Usage: cpp-treesitter.mjs <file.{c,cpp,h,hpp}>");
328
+ process.exit(1);
329
+ }
330
+ const code = fs.readFileSync(target, "utf8");
331
+ const result = await parseCode(code, target, target.endsWith(".c") || target.endsWith(".h") ? "c" : "cpp");
332
+ console.log(JSON.stringify(result, null, 2));
333
+ }