@remnic/coding-graph 9.3.759

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 (86) hide show
  1. package/README.md +130 -0
  2. package/dist/chunk-5I2DBHOQ.js +1042 -0
  3. package/dist/chunk-5I2DBHOQ.js.map +1 -0
  4. package/dist/chunk-CPYJACC5.js +1838 -0
  5. package/dist/chunk-CPYJACC5.js.map +1 -0
  6. package/dist/chunk-ZVCMIM4T.js +216 -0
  7. package/dist/chunk-ZVCMIM4T.js.map +1 -0
  8. package/dist/cypher/query-parser.d.ts +253 -0
  9. package/dist/cypher/query-parser.js +17 -0
  10. package/dist/cypher/query-parser.js.map +1 -0
  11. package/dist/graph-schema.d.ts +84 -0
  12. package/dist/graph-schema.js +17 -0
  13. package/dist/graph-schema.js.map +1 -0
  14. package/dist/graph-store.d.ts +938 -0
  15. package/dist/graph-store.js +16 -0
  16. package/dist/graph-store.js.map +1 -0
  17. package/dist/index.d.ts +1953 -0
  18. package/dist/index.js +3509 -0
  19. package/dist/index.js.map +1 -0
  20. package/grammars/tree-sitter-bash.wasm +0 -0
  21. package/grammars/tree-sitter-c.wasm +0 -0
  22. package/grammars/tree-sitter-c_sharp.wasm +0 -0
  23. package/grammars/tree-sitter-cpp.wasm +0 -0
  24. package/grammars/tree-sitter-go.wasm +0 -0
  25. package/grammars/tree-sitter-java.wasm +0 -0
  26. package/grammars/tree-sitter-javascript.wasm +0 -0
  27. package/grammars/tree-sitter-kotlin.wasm +0 -0
  28. package/grammars/tree-sitter-php.wasm +0 -0
  29. package/grammars/tree-sitter-python.wasm +0 -0
  30. package/grammars/tree-sitter-ruby.wasm +0 -0
  31. package/grammars/tree-sitter-rust.wasm +0 -0
  32. package/grammars/tree-sitter-swift.wasm +0 -0
  33. package/grammars/tree-sitter-tsx.wasm +0 -0
  34. package/grammars/tree-sitter-typescript.wasm +0 -0
  35. package/package.json +79 -0
  36. package/src/co-change.test.ts +175 -0
  37. package/src/co-change.ts +167 -0
  38. package/src/cypher/query-parser.test.ts +1107 -0
  39. package/src/cypher/query-parser.ts +1692 -0
  40. package/src/detect-changes.test.ts +533 -0
  41. package/src/detect-changes.ts +367 -0
  42. package/src/engine/emit.ts +556 -0
  43. package/src/engine/engine.test.ts +1417 -0
  44. package/src/engine/engine.ts +182 -0
  45. package/src/engine/extractors.ts +486 -0
  46. package/src/engine/fixtures.ts +364 -0
  47. package/src/engine/language-sniff.ts +56 -0
  48. package/src/engine/parser-backend.ts +206 -0
  49. package/src/engine/utf16-offsets.ts +68 -0
  50. package/src/git-invoker.test.ts +116 -0
  51. package/src/git-invoker.ts +426 -0
  52. package/src/graph-schema.test.ts +541 -0
  53. package/src/graph-schema.ts +383 -0
  54. package/src/graph-store-pr2.test.ts +1879 -0
  55. package/src/graph-store.test.ts +1420 -0
  56. package/src/graph-store.ts +3489 -0
  57. package/src/index-status.test.ts +303 -0
  58. package/src/index-status.ts +135 -0
  59. package/src/index.ts +384 -0
  60. package/src/lsp/byte-position.ts +173 -0
  61. package/src/lsp/characterization.test.ts +174 -0
  62. package/src/lsp/client.test.ts +275 -0
  63. package/src/lsp/client.ts +484 -0
  64. package/src/lsp/config.ts +219 -0
  65. package/src/lsp/degradation.ts +86 -0
  66. package/src/lsp/fixtures/fake-server.mjs +198 -0
  67. package/src/lsp/framing.test.ts +180 -0
  68. package/src/lsp/framing.ts +177 -0
  69. package/src/lsp/resolution.test.ts +497 -0
  70. package/src/lsp/resolution.ts +483 -0
  71. package/src/lsp/status.ts +140 -0
  72. package/src/lsp/types.ts +167 -0
  73. package/src/reindex.test.ts +1038 -0
  74. package/src/reindex.ts +908 -0
  75. package/src/row-types.ts +45 -0
  76. package/src/semantic/canonical-text.test.ts +150 -0
  77. package/src/semantic/canonical-text.ts +219 -0
  78. package/src/semantic/config.ts +235 -0
  79. package/src/semantic/index.ts +78 -0
  80. package/src/semantic/minhash.test.ts +197 -0
  81. package/src/semantic/minhash.ts +261 -0
  82. package/src/semantic/semantic-query.ts +173 -0
  83. package/src/semantic/semantic.test.ts +1315 -0
  84. package/src/semantic/similarity.ts +268 -0
  85. package/src/semantic/types.ts +145 -0
  86. package/src/semantic/vectors.ts +235 -0
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Engine factory — constructs a `CodingGraphEngine` backed by the WASM
3
+ * tree-sitter parser + tier-1 extractors.
4
+ *
5
+ * Rule 11 (no module-level state): the backend, query caches, and parser
6
+ * instances all live on the returned engine object. Two `createCodingGraphEngine`
7
+ * calls in the same process have fully isolated state.
8
+ *
9
+ * Rule 30/48: `codingGraph.enabled` defaults `false` in @remnic/core; the
10
+ * loader is never invoked when disabled, so this factory is only reached when
11
+ * the user has explicitly opted in.
12
+ */
13
+ import {
14
+ CODING_GRAPH_ENGINE_VERSION,
15
+ TIER_1_LANGUAGES,
16
+ type CodingGraphEngine,
17
+ type CodingGraphLanguage,
18
+ type CreateCodingGraphEngineOptions,
19
+ type FileIR,
20
+ type ParseFileInput,
21
+ type ParseResult,
22
+ } from "@remnic/core";
23
+
24
+ import { WasmTreeSitterBackend, type ParserBackend } from "./parser-backend.js";
25
+ import { emitFileIR } from "./emit.js";
26
+ import { sniffLanguage, isTier1Language } from "./language-sniff.js";
27
+
28
+ /**
29
+ * The internal engine object. Implements `CodingGraphEngine` from @remnic/core.
30
+ */
31
+ class CodingGraphEngineImpl implements CodingGraphEngine {
32
+ readonly engineVersion: string;
33
+ readonly supportedLanguages: readonly CodingGraphLanguage[];
34
+ private readonly backend: ParserBackend;
35
+ private disposed = false;
36
+ /**
37
+ * Serialize parse calls. The backend's single Parser instance is shared
38
+ * across all languages, so concurrent setLanguage/parse calls would race.
39
+ * Each parseFile call awaits the previous before touching the parser.
40
+ */
41
+ private parseChain: Promise<void> = Promise.resolve();
42
+
43
+ constructor(backend: ParserBackend) {
44
+ this.engineVersion = CODING_GRAPH_ENGINE_VERSION;
45
+ this.supportedLanguages = TIER_1_LANGUAGES;
46
+ this.backend = backend;
47
+ }
48
+ async parseFile(input: ParseFileInput): Promise<ParseResult> {
49
+ if (this.disposed) {
50
+ return {
51
+ ok: false,
52
+ code: "parse_failed",
53
+ path: input.path,
54
+ message: "engine has been disposed",
55
+ };
56
+ }
57
+
58
+ // Serialize: wait for any in-flight parse before touching the shared parser.
59
+ const previous = this.parseChain;
60
+ let release!: () => void;
61
+ this.parseChain = new Promise<void>((resolve) => {
62
+ release = resolve;
63
+ });
64
+ await previous;
65
+ // Re-check disposed: another caller may have disposed the engine
66
+ // while we were waiting for the previous parse to finish.
67
+ if (this.disposed) {
68
+ release();
69
+ return {
70
+ ok: false,
71
+ code: "parse_failed",
72
+ path: input.path,
73
+ message: "engine has been disposed",
74
+ };
75
+ }
76
+ try {
77
+ return await this.doParseFile(input);
78
+ } finally {
79
+ release();
80
+ }
81
+ }
82
+
83
+ private async doParseFile(input: ParseFileInput): Promise<ParseResult> {
84
+
85
+ // Resolve the language — explicit override wins, then sniff from path.
86
+ const lang: CodingGraphLanguage | null =
87
+ input.language ?? sniffLanguage(input.path);
88
+ if (!lang || !isTier1Language(lang)) {
89
+ return {
90
+ ok: false,
91
+ code: "parse_failed",
92
+ path: input.path,
93
+ message: `unsupported language for path "${input.path}"; ` +
94
+ `supported extensions map to: ${TIER_1_LANGUAGES.join(", ")}`,
95
+ };
96
+ }
97
+
98
+ // Ensure the grammar is loaded (rule 51 — error, not silent skip).
99
+ try {
100
+ await this.backend.ensureLanguage(lang);
101
+ } catch (err) {
102
+ return {
103
+ ok: false,
104
+ code: "parse_failed",
105
+ path: input.path,
106
+ message: `failed to load grammar for ${lang}: ${err instanceof Error ? err.message : String(err)}`,
107
+ };
108
+ }
109
+
110
+ // web-tree-sitter parses UTF-16 internally, so node offsets are UTF-16
111
+ // code-unit offsets. We convert to UTF-8 byte offsets after extraction
112
+ // (issue #1659 item 3 — multibyte span accuracy).
113
+ const contentStr = Buffer.from(input.content).toString("utf-8");
114
+ const tree = this.backend.parse(lang, contentStr);
115
+ if (!tree) {
116
+ return {
117
+ ok: false,
118
+ code: "parse_failed",
119
+ path: input.path,
120
+ message: `tree-sitter returned null for ${lang} (grammar may be corrupt)`,
121
+ };
122
+ }
123
+
124
+ try {
125
+ const root = tree.rootNode;
126
+ const language = this.backend.getLanguage(lang);
127
+ if (!language) {
128
+ return {
129
+ ok: false,
130
+ code: "parse_failed",
131
+ path: input.path,
132
+ message: `language object unavailable for ${lang}`,
133
+ };
134
+ }
135
+
136
+ const ir: FileIR = emitFileIR(
137
+ input.path,
138
+ lang,
139
+ input.content,
140
+ root,
141
+ language,
142
+ contentStr,
143
+ );
144
+ return { ok: true, ir };
145
+ } catch (err) {
146
+ // Rule 44: extraction/query errors surface as tagged parse_failed,
147
+ // not thrown exceptions that abort batch reindex.
148
+ return {
149
+ ok: false,
150
+ code: "parse_failed",
151
+ path: input.path,
152
+ message: `extraction failed for ${lang}: ${err instanceof Error ? err.message : String(err)}`,
153
+ };
154
+ } finally {
155
+ tree.delete();
156
+ }
157
+ }
158
+
159
+ async dispose(): Promise<void> {
160
+ if (this.disposed) return;
161
+ this.disposed = true;
162
+ // Wait for any in-flight parse to finish before tearing down the
163
+ // backend. Without this, a parseFile call that already passed the
164
+ // disposed guard can touch freed parser/grammar state.
165
+ await this.parseChain;
166
+ await this.backend.dispose();
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Construct a coding-graph engine. PR2 implementation: real WASM tree-sitter
172
+ * parser with tier-1 extractors. No longer throws `not_implemented`.
173
+ *
174
+ * @throws never — load failures surface as `{ ok: false, code: "parse_failed" }`
175
+ * per-file, not as constructor exceptions.
176
+ */
177
+ export function createCodingGraphEngine(
178
+ _options: CreateCodingGraphEngineOptions = {},
179
+ ): CodingGraphEngine {
180
+ const backend = new WasmTreeSitterBackend();
181
+ return new CodingGraphEngineImpl(backend);
182
+ }
@@ -0,0 +1,486 @@
1
+ /**
2
+ * Per-language symbol extractors for the tier-1 languages.
3
+ *
4
+ * Each tier-1 language has a `LanguageExtractor` providing tree-sitter query
5
+ * strings. The `emit.ts` module compiles these against the loaded grammar,
6
+ * runs them against the root node, and walks captures into FileIR.
7
+ *
8
+ * Query capture conventions (fixed across all languages):
9
+ *
10
+ * Definitions — every definition pattern captures:
11
+ * @def.<kind> the definition node (span = [startIndex, endIndex))
12
+ * @name the identifier node whose .text is the symbol name
13
+ * <kind> ∈ function | method | class | interface | enum | type | module
14
+ *
15
+ * Imports — every import pattern captures:
16
+ * @__import.stmt the import-statement node (grouping key)
17
+ * @import.module the module specifier text (cleaned in emit.ts)
18
+ * @import.name zero or more imported-name identifiers
19
+ *
20
+ * Exports — @export.name
21
+ * Call sites — @call.callee
22
+ * Routes — @route.verb + @route.path + @route.handler
23
+ */
24
+ import type { CodingGraphLanguage } from "@remnic/core";
25
+
26
+ export type DefKind = "function" | "method" | "class" | "interface" | "enum" | "type" | "module";
27
+
28
+ export interface LanguageExtractor {
29
+ readonly definitionsQuery: string;
30
+ readonly importsQuery: string;
31
+ readonly exportsQuery: string;
32
+ readonly callSitesQuery: string;
33
+ readonly routesQuery: string;
34
+ }
35
+
36
+ // ===========================================================================
37
+ // JS family (JavaScript, TypeScript, TSX) — shared definition + call patterns.
38
+ // ===========================================================================
39
+
40
+ const JS_FAMILY_DEFINITIONS = `
41
+ (function_declaration name: (identifier) @name) @def.function
42
+ (method_definition name: (property_identifier) @name) @def.method
43
+ (class_declaration name: (type_identifier) @name) @def.class
44
+ (interface_declaration name: (type_identifier) @name) @def.interface
45
+ (enum_declaration name: (identifier) @name) @def.enum
46
+ (type_alias_declaration name: (type_identifier) @name) @def.type
47
+
48
+ ; const handler = () => {} / const handler = function () {}
49
+ ; Indexes arrow-function and function-expression declarations so route
50
+ ; handlers and React components defined this way appear as symbols.
51
+ (variable_declarator
52
+ name: (identifier) @name
53
+ value: [(arrow_function) (function_expression)]) @def.function
54
+ `.trim();
55
+
56
+ const JS_IMPORTS = `
57
+ (import_statement
58
+ source: (string (string_fragment) @import.module)) @__import.stmt
59
+ (import_statement
60
+ (import_clause (identifier) @import.name)
61
+ source: (string (string_fragment) @import.module)) @__import.stmt
62
+ (import_statement
63
+ (import_clause (namespace_import (identifier) @import.name))
64
+ source: (string (string_fragment) @import.module)) @__import.stmt
65
+ (import_statement
66
+ (import_clause (named_imports (import_specifier name: (identifier) @import.name)))
67
+ source: (string (string_fragment) @import.module)) @__import.stmt
68
+
69
+ ; CommonJS require("...") — capture the module specifier so dependency
70
+ ; edges exist for Node/CommonJS codebases, not just ES-module imports.
71
+ (call_expression
72
+ function: (identifier) @__import.require
73
+ arguments: (arguments (string (string_fragment) @import.module))
74
+ (#eq? @__import.require "require")) @__import.stmt
75
+ `.trim();
76
+
77
+ // TS/TSX exports — class/interface names use type_identifier.
78
+ const TS_EXPORTS = `
79
+ (export_statement declaration: (function_declaration name: (identifier) @export.name))
80
+ (export_statement declaration: (class_declaration name: (type_identifier) @export.name))
81
+ (export_statement (lexical_declaration (variable_declarator name: (identifier) @export.name)))
82
+ (export_statement declaration: (enum_declaration name: (identifier) @export.name))
83
+ (export_statement declaration: (interface_declaration name: (type_identifier) @export.name))
84
+ (export_statement (export_clause (export_specifier name: (identifier) @export.name)))
85
+
86
+ ; export default App; (bare identifier re-export)
87
+ (export_statement (identifier) @export.name)
88
+ `.trim();
89
+
90
+ // JavaScript exports — class names use identifier (no type_identifier in JS grammar).
91
+ // Includes CommonJS export patterns (module.exports / exports.X) so
92
+ // Node/CommonJS public APIs are not marked unexported.
93
+ const JS_EXPORTS = `
94
+ (export_statement declaration: (function_declaration name: (identifier) @export.name))
95
+ (export_statement declaration: (class_declaration name: (identifier) @export.name))
96
+ (export_statement (lexical_declaration (variable_declarator name: (identifier) @export.name)))
97
+ (export_statement (export_clause (export_specifier name: (identifier) @export.name)))
98
+
99
+ ; export default App; (bare identifier re-export)
100
+ (export_statement (identifier) @export.name)
101
+
102
+ ; CommonJS: module.exports = { App, createRouter }
103
+ (assignment_expression
104
+ left: (member_expression object: (identifier) @__cjs.mod property: (property_identifier) @__cjs.exp)
105
+ right: (object (shorthand_property_identifier) @export.name)
106
+ (#eq? @__cjs.mod "module") (#eq? @__cjs.exp "exports"))
107
+ ; CommonJS: module.exports = { publicName: createRouter }
108
+ ; Capture the VALUE identifier (the actual symbol), not the key (public alias).
109
+ (assignment_expression
110
+ left: (member_expression object: (identifier) @__cjs.mod2 property: (property_identifier) @__cjs.exp2)
111
+ right: (object (pair key: (property_identifier) @__cjs.key2 value: (identifier) @export.name))
112
+ (#eq? @__cjs.mod2 "module") (#eq? @__cjs.exp2 "exports"))
113
+ ; Fallback: { foo: <non-identifier-value> } — capture the key as the public name
114
+ ; when the value is not a named symbol (arrow fn, literal, etc).
115
+ (assignment_expression
116
+ left: (member_expression object: (identifier) @__cjs.mod2b property: (property_identifier) @__cjs.exp2b)
117
+ right: (object (pair key: (property_identifier) @export.name value: (_) @__cjs.nonId))
118
+ (#eq? @__cjs.mod2b "module") (#eq? @__cjs.exp2b "exports")
119
+ (#not-match? @__cjs.nonId "^[A-Za-z_$][A-Za-z0-9_$]*$"))
120
+ ; CommonJS: module.exports = App
121
+ (assignment_expression
122
+ left: (member_expression object: (identifier) @__cjs.mod3 property: (property_identifier) @__cjs.exp3)
123
+ right: (identifier) @export.name
124
+ (#eq? @__cjs.mod3 "module") (#eq? @__cjs.exp3 "exports"))
125
+ ; CommonJS: exports.handler = handler
126
+ (assignment_expression
127
+ left: (member_expression object: (identifier) @__cjs.exp4 property: (property_identifier) @export.name)
128
+ (#eq? @__cjs.exp4 "exports"))
129
+ `.trim();
130
+
131
+ const JS_CALLS = `
132
+ (call_expression function: (identifier) @call.callee)
133
+ (call_expression function: (member_expression property: (property_identifier) @call.callee))
134
+ `.trim();
135
+
136
+ const JS_ROUTES = `
137
+ ; Unified route pattern: captures verb + path + the arguments node.
138
+ ; emit.ts extracts the handler from the LAST argument (identifier,
139
+ ; arrow_function, or function_expression), correctly handling middleware:
140
+ ; app.get("/users", requireAuth, getUsers) → handler=getUsers
141
+ (call_expression
142
+ function: (member_expression
143
+ object: (_) @__route.app
144
+ property: (property_identifier) @route.verb)
145
+ arguments: (arguments
146
+ . (string (string_fragment) @route.path)
147
+ ) @route.args
148
+ (#match? @route.verb "^(get|post|put|patch|delete|head|options|all|use)$"))
149
+ `.trim();
150
+
151
+ // ===========================================================================
152
+ // Per-language extractors.
153
+ // ===========================================================================
154
+
155
+ const TYPESCRIPT_EXTRACTOR: LanguageExtractor = {
156
+ definitionsQuery: JS_FAMILY_DEFINITIONS,
157
+ importsQuery: JS_IMPORTS,
158
+ exportsQuery: TS_EXPORTS,
159
+ callSitesQuery: JS_CALLS,
160
+ routesQuery: JS_ROUTES,
161
+ };
162
+
163
+ const TSX_EXTRACTOR: LanguageExtractor = TYPESCRIPT_EXTRACTOR;
164
+
165
+ const JAVASCRIPT_EXTRACTOR: LanguageExtractor = {
166
+ definitionsQuery: `
167
+ (function_declaration name: (identifier) @name) @def.function
168
+ (method_definition name: (property_identifier) @name) @def.method
169
+ (class_declaration name: (identifier) @name) @def.class
170
+
171
+ ; const handler = () => {} / const handler = function () {}
172
+ (variable_declarator
173
+ name: (identifier) @name
174
+ value: [(arrow_function) (function_expression)]) @def.function
175
+ `.trim(),
176
+ importsQuery: JS_IMPORTS,
177
+ exportsQuery: JS_EXPORTS,
178
+ callSitesQuery: JS_CALLS,
179
+ routesQuery: JS_ROUTES,
180
+ };
181
+
182
+ const PYTHON_EXTRACTOR: LanguageExtractor = {
183
+ definitionsQuery: `
184
+ (function_definition name: (identifier) @name) @def.function
185
+ (class_definition name: (identifier) @name) @def.class
186
+ `.trim(),
187
+ importsQuery: `
188
+ (import_statement (dotted_name) @import.module) @__import.stmt
189
+ (import_from_statement module_name: (dotted_name) @import.module) @__import.stmt
190
+ ; Python relative imports: from .models import User / from ..parent import X
191
+ ; tree-sitter-python wraps the module inside a relative_import node, so the
192
+ ; module_name field is NOT set. Capture the relative_import node itself so
193
+ ; the prefix dots are preserved (..parent, not just parent) — different
194
+ ; relative levels must not collapse to the same module name
195
+ ; (chatgpt-codex-connector #1688 P2: 'Preserve dots in Python relative imports').
196
+ (import_from_statement (relative_import) @import.module) @__import.stmt
197
+ `.trim(),
198
+ exportsQuery: ``,
199
+ callSitesQuery: `
200
+ (call function: (identifier) @call.callee)
201
+ (call function: (attribute attribute: (identifier) @call.callee))
202
+ `.trim(),
203
+ routesQuery: `
204
+ (decorated_definition
205
+ (decorator
206
+ (call function: (attribute attribute: (identifier) @route.verb)
207
+ arguments: (argument_list (string) @route.path)))
208
+ definition: (function_definition name: (identifier) @route.handler)
209
+ (#match? @route.verb "^(get|post|put|patch|delete|route|api_route)$"))
210
+ `.trim(),
211
+ };
212
+
213
+ const GO_EXTRACTOR: LanguageExtractor = {
214
+ definitionsQuery: `
215
+ (function_declaration name: (identifier) @name) @def.function
216
+
217
+ ; Go methods sit outside their receiver struct, so byte-span nesting
218
+ ; cannot compute qualified names. Capture the receiver type_identifier
219
+ ; so extractSymbols can prefix the method name (Server.Start).
220
+ (method_declaration
221
+ receiver: (parameter_list
222
+ (parameter_declaration type: (type_identifier) @__receiver.type))
223
+ name: (field_identifier) @name) @def.method
224
+ (method_declaration
225
+ receiver: (parameter_list
226
+ (parameter_declaration type: (pointer_type (type_identifier) @__receiver.type)))
227
+ name: (field_identifier) @name) @def.method
228
+ (type_spec name: (type_identifier) @name type: (struct_type)) @def.class
229
+ (type_spec name: (type_identifier) @name type: (interface_type)) @def.interface
230
+ (type_spec name: (type_identifier) @name type: (type_identifier)) @def.type
231
+ `.trim(),
232
+ importsQuery: `
233
+ (import_spec path: (interpreted_string_literal) @import.module) @__import.stmt
234
+ `.trim(),
235
+ exportsQuery: ``,
236
+ callSitesQuery: `
237
+ (call_expression function: (identifier) @call.callee)
238
+ (call_expression function: (selector_expression field: (field_identifier) @call.callee))
239
+ `.trim(),
240
+ routesQuery: ``,
241
+ };
242
+
243
+ const RUST_EXTRACTOR: LanguageExtractor = {
244
+ definitionsQuery: `
245
+ (function_item name: (identifier) @name) @def.function
246
+ (function_signature_item name: (identifier) @name) @def.function
247
+ (struct_item name: (type_identifier) @name) @def.class
248
+ (enum_item name: (type_identifier) @name) @def.enum
249
+ (trait_item name: (type_identifier) @name) @def.interface
250
+ (type_item name: (type_identifier) @name) @def.type
251
+ (mod_item name: (identifier) @name) @def.module
252
+
253
+ ; Rust impl methods — the impl block sits outside the struct's byte span,
254
+ ; so byte-span nesting cannot compute the parent struct. Capture the impl
255
+ ; type_identifier so extractSymbols can prefix qualified names (Config.new).
256
+ ; These also match functions caught by the general patterns above;
257
+ ; extractSymbols deduplicates by node identity (startByte+endByte+name).
258
+ (impl_item
259
+ type: (type_identifier) @__receiver.type
260
+ body: (declaration_list
261
+ (function_item name: (identifier) @name) @def.method))
262
+ (impl_item
263
+ type: (type_identifier) @__receiver.type
264
+ body: (declaration_list
265
+ (function_signature_item name: (identifier) @name) @def.method))
266
+ `.trim(),
267
+ importsQuery: `
268
+ (use_declaration (scoped_identifier) @import.module) @__import.stmt
269
+ (use_declaration (scoped_use_list) @import.module) @__import.stmt
270
+ `.trim(),
271
+ exportsQuery: ``,
272
+ callSitesQuery: `
273
+ (call_expression function: (identifier) @call.callee)
274
+ (call_expression function: (field_expression field: (field_identifier) @call.callee))
275
+ (call_expression function: (scoped_identifier) @call.callee)
276
+ `.trim(),
277
+ routesQuery: ``,
278
+ };
279
+
280
+ const JAVA_EXTRACTOR: LanguageExtractor = {
281
+ definitionsQuery: `
282
+ (class_declaration name: (identifier) @name) @def.class
283
+ (interface_declaration name: (identifier) @name) @def.interface
284
+ (enum_declaration name: (identifier) @name) @def.enum
285
+ (record_declaration name: (identifier) @name) @def.class
286
+ (method_declaration name: (identifier) @name) @def.method
287
+ (constructor_declaration name: (identifier) @name) @def.method
288
+ `.trim(),
289
+ importsQuery: `
290
+ (import_declaration (scoped_identifier) @import.module) @__import.stmt
291
+ `.trim(),
292
+ exportsQuery: ``,
293
+ callSitesQuery: `
294
+ (method_invocation name: (identifier) @call.callee)
295
+ `.trim(),
296
+ routesQuery: ``,
297
+ };
298
+
299
+ const C_EXTRACTOR: LanguageExtractor = {
300
+ definitionsQuery: `
301
+ (function_definition declarator: (function_declarator declarator: (identifier) @name)) @def.function
302
+ (type_definition declarator: (type_identifier) @name) @def.type
303
+ `.trim(),
304
+ importsQuery: `
305
+ (preproc_include path: (system_lib_string) @import.module) @__import.stmt
306
+ (preproc_include path: (string_literal) @import.module) @__import.stmt
307
+ `.trim(),
308
+ exportsQuery: ``,
309
+ callSitesQuery: `
310
+ (call_expression function: (identifier) @call.callee)
311
+ `.trim(),
312
+ routesQuery: ``,
313
+ };
314
+
315
+ const CPP_EXTRACTOR: LanguageExtractor = {
316
+ definitionsQuery: `
317
+ (function_definition declarator: (function_declarator declarator: (identifier) @name)) @def.function
318
+ (function_definition declarator: (function_declarator declarator: (field_identifier) @name)) @def.method
319
+ ; C++ out-of-class method definitions: void A::start() {}
320
+ ; The qualified_identifier (A::start) encodes the full qualified name.
321
+ (function_definition declarator: (function_declarator declarator: (qualified_identifier) @name)) @def.method
322
+ (class_specifier name: (type_identifier) @name) @def.class
323
+ (struct_specifier name: (type_identifier) @name) @def.class
324
+ (enum_specifier name: (type_identifier) @name) @def.enum
325
+ (namespace_definition name: (namespace_identifier) @name) @def.module
326
+ `.trim(),
327
+ importsQuery: `
328
+ (preproc_include path: (system_lib_string) @import.module) @__import.stmt
329
+ (preproc_include path: (string_literal) @import.module) @__import.stmt
330
+ `.trim(),
331
+ exportsQuery: ``,
332
+ callSitesQuery: `
333
+ (call_expression function: (identifier) @call.callee)
334
+ (call_expression function: (field_expression field: (field_identifier) @call.callee))
335
+ `.trim(),
336
+ routesQuery: ``,
337
+ };
338
+
339
+ const CSHARP_EXTRACTOR: LanguageExtractor = {
340
+ definitionsQuery: `
341
+ (class_declaration name: (identifier) @name) @def.class
342
+ (interface_declaration name: (identifier) @name) @def.interface
343
+ (enum_declaration name: (identifier) @name) @def.enum
344
+ (struct_declaration name: (identifier) @name) @def.class
345
+ (method_declaration name: (identifier) @name) @def.method
346
+ `.trim(),
347
+ importsQuery: `
348
+ (using_directive (identifier) @import.module) @__import.stmt
349
+ (using_directive (qualified_name) @import.module) @__import.stmt
350
+ `.trim(),
351
+ exportsQuery: ``,
352
+ callSitesQuery: `
353
+ (invocation_expression function: (identifier) @call.callee)
354
+ (invocation_expression function: (member_access_expression name: (identifier) @call.callee))
355
+ `.trim(),
356
+ routesQuery: ``,
357
+ };
358
+
359
+ const RUBY_EXTRACTOR: LanguageExtractor = {
360
+ definitionsQuery: `
361
+ (class name: (constant) @name) @def.class
362
+ (module name: (constant) @name) @def.module
363
+ (method name: (identifier) @name) @def.method
364
+ (singleton_method name: (identifier) @name) @def.method
365
+ `.trim(),
366
+ importsQuery: `
367
+ (call
368
+ method: (identifier) @__import.method
369
+ arguments: (argument_list (string) @import.module)
370
+ (#match? @__import.method "^(require|require_relative|load)$")) @__import.stmt
371
+ `.trim(),
372
+ exportsQuery: ``,
373
+ callSitesQuery: `
374
+ (call method: (identifier) @call.callee)
375
+ `.trim(),
376
+ routesQuery: ``,
377
+ };
378
+
379
+ const PHP_EXTRACTOR: LanguageExtractor = {
380
+ definitionsQuery: `
381
+ (class_declaration name: (name) @name) @def.class
382
+ (interface_declaration name: (name) @name) @def.interface
383
+ (trait_declaration name: (name) @name) @def.class
384
+ (function_definition name: (name) @name) @def.function
385
+ (method_declaration name: (name) @name) @def.method
386
+ `.trim(),
387
+ importsQuery: `
388
+ (namespace_use_declaration (namespace_use_clause (qualified_name) @import.module)) @__import.stmt
389
+ `.trim(),
390
+ exportsQuery: ``,
391
+ callSitesQuery: `
392
+ (function_call_expression function: (name) @call.callee)
393
+ (member_call_expression (name) @call.callee)
394
+ `.trim(),
395
+ routesQuery: ``,
396
+ };
397
+
398
+ const KOTLIN_EXTRACTOR: LanguageExtractor = {
399
+ definitionsQuery: `
400
+ (class_declaration (type_identifier) @name) @def.class
401
+ (object_declaration (type_identifier) @name) @def.module
402
+ (function_declaration (simple_identifier) @name) @def.function
403
+ `.trim(),
404
+ importsQuery: `
405
+ ; Capture the full identifier node — its .text is the complete import
406
+ ; path (e.g. "kotlin.collections"). Do NOT capture nested simple_identifier
407
+ ; children, which would emit bogus segment-level modules.
408
+ (import_header (identifier) @import.module) @__import.stmt
409
+ `.trim(),
410
+ exportsQuery: ``,
411
+ callSitesQuery: `
412
+ (call_expression (simple_identifier) @call.callee)
413
+ `.trim(),
414
+ routesQuery: ``,
415
+ };
416
+
417
+ const SWIFT_EXTRACTOR: LanguageExtractor = {
418
+ definitionsQuery: `
419
+ (class_declaration name: (type_identifier) @name) @def.class
420
+ (protocol_declaration name: (type_identifier) @name) @def.interface
421
+ (function_declaration name: (simple_identifier) @name) @def.function
422
+ `.trim(),
423
+ importsQuery: `
424
+ (import_declaration (identifier) @import.module) @__import.stmt
425
+ `.trim(),
426
+ exportsQuery: ``,
427
+ callSitesQuery: `
428
+ (call_expression (simple_identifier) @call.callee)
429
+ `.trim(),
430
+ routesQuery: ``,
431
+ };
432
+
433
+ const BASH_EXTRACTOR: LanguageExtractor = {
434
+ definitionsQuery: `
435
+ (function_definition name: (word) @name) @def.function
436
+ `.trim(),
437
+ importsQuery: ``,
438
+ exportsQuery: ``,
439
+ callSitesQuery: `
440
+ (command name: (command_name (word) @call.callee))
441
+ `.trim(),
442
+ routesQuery: ``,
443
+ };
444
+
445
+ // ===========================================================================
446
+ // Registry — single source of truth.
447
+ // ===========================================================================
448
+
449
+ export const EXTRACTORS: Record<CodingGraphLanguage, LanguageExtractor> = {
450
+ typescript: TYPESCRIPT_EXTRACTOR,
451
+ tsx: TSX_EXTRACTOR,
452
+ javascript: JAVASCRIPT_EXTRACTOR,
453
+ python: PYTHON_EXTRACTOR,
454
+ go: GO_EXTRACTOR,
455
+ rust: RUST_EXTRACTOR,
456
+ java: JAVA_EXTRACTOR,
457
+ c: C_EXTRACTOR,
458
+ cpp: CPP_EXTRACTOR,
459
+ csharp: CSHARP_EXTRACTOR,
460
+ ruby: RUBY_EXTRACTOR,
461
+ php: PHP_EXTRACTOR,
462
+ kotlin: KOTLIN_EXTRACTOR,
463
+ swift: SWIFT_EXTRACTOR,
464
+ bash: BASH_EXTRACTOR,
465
+ };
466
+
467
+ const VALID_KINDS = new Set<string>([
468
+ "function",
469
+ "method",
470
+ "class",
471
+ "interface",
472
+ "enum",
473
+ "type",
474
+ "module",
475
+ ]);
476
+
477
+ /**
478
+ * Extract the SymbolIR kind from a `@def.<kind>` capture name.
479
+ * Returns `null` for non-definition captures or invalid kinds.
480
+ */
481
+ export function kindFromCapture(captureName: string): DefKind | null {
482
+ if (!captureName.startsWith("def.")) return null;
483
+ const kind = captureName.slice(4);
484
+ if (!VALID_KINDS.has(kind)) return null;
485
+ return kind as DefKind;
486
+ }