gitnexus 1.6.0 → 1.6.2-rc.1

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 (145) hide show
  1. package/README.md +73 -0
  2. package/dist/cli/analyze.js +50 -3
  3. package/dist/core/group/extractors/fs-utils.d.ts +10 -0
  4. package/dist/core/group/extractors/fs-utils.js +24 -0
  5. package/dist/core/group/extractors/grpc-extractor.d.ts +17 -8
  6. package/dist/core/group/extractors/grpc-extractor.js +328 -191
  7. package/dist/core/group/extractors/grpc-patterns/go.d.ts +2 -0
  8. package/dist/core/group/extractors/grpc-patterns/go.js +97 -0
  9. package/dist/core/group/extractors/grpc-patterns/index.d.ts +19 -0
  10. package/dist/core/group/extractors/grpc-patterns/index.js +46 -0
  11. package/dist/core/group/extractors/grpc-patterns/java.d.ts +2 -0
  12. package/dist/core/group/extractors/grpc-patterns/java.js +173 -0
  13. package/dist/core/group/extractors/grpc-patterns/node.d.ts +4 -0
  14. package/dist/core/group/extractors/grpc-patterns/node.js +290 -0
  15. package/dist/core/group/extractors/grpc-patterns/proto.d.ts +9 -0
  16. package/dist/core/group/extractors/grpc-patterns/proto.js +134 -0
  17. package/dist/core/group/extractors/grpc-patterns/python.d.ts +2 -0
  18. package/dist/core/group/extractors/grpc-patterns/python.js +67 -0
  19. package/dist/core/group/extractors/grpc-patterns/types.d.ts +50 -0
  20. package/dist/core/group/extractors/grpc-patterns/types.js +1 -0
  21. package/dist/core/group/extractors/http-patterns/go.d.ts +2 -0
  22. package/dist/core/group/extractors/http-patterns/go.js +215 -0
  23. package/dist/core/group/extractors/http-patterns/index.d.ts +17 -0
  24. package/dist/core/group/extractors/http-patterns/index.js +44 -0
  25. package/dist/core/group/extractors/http-patterns/java.d.ts +2 -0
  26. package/dist/core/group/extractors/http-patterns/java.js +253 -0
  27. package/dist/core/group/extractors/http-patterns/node.d.ts +4 -0
  28. package/dist/core/group/extractors/http-patterns/node.js +354 -0
  29. package/dist/core/group/extractors/http-patterns/php.d.ts +2 -0
  30. package/dist/core/group/extractors/http-patterns/php.js +70 -0
  31. package/dist/core/group/extractors/http-patterns/python.d.ts +2 -0
  32. package/dist/core/group/extractors/http-patterns/python.js +133 -0
  33. package/dist/core/group/extractors/http-patterns/types.d.ts +61 -0
  34. package/dist/core/group/extractors/http-patterns/types.js +1 -0
  35. package/dist/core/group/extractors/http-route-extractor.d.ts +10 -13
  36. package/dist/core/group/extractors/http-route-extractor.js +231 -238
  37. package/dist/core/group/extractors/manifest-extractor.d.ts +54 -0
  38. package/dist/core/group/extractors/manifest-extractor.js +277 -0
  39. package/dist/core/group/extractors/topic-extractor.d.ts +0 -1
  40. package/dist/core/group/extractors/topic-extractor.js +55 -192
  41. package/dist/core/group/extractors/topic-patterns/go.d.ts +2 -0
  42. package/dist/core/group/extractors/topic-patterns/go.js +120 -0
  43. package/dist/core/group/extractors/topic-patterns/index.d.ts +14 -0
  44. package/dist/core/group/extractors/topic-patterns/index.js +38 -0
  45. package/dist/core/group/extractors/topic-patterns/java.d.ts +2 -0
  46. package/dist/core/group/extractors/topic-patterns/java.js +80 -0
  47. package/dist/core/group/extractors/topic-patterns/node.d.ts +4 -0
  48. package/dist/core/group/extractors/topic-patterns/node.js +155 -0
  49. package/dist/core/group/extractors/topic-patterns/python.d.ts +2 -0
  50. package/dist/core/group/extractors/topic-patterns/python.js +116 -0
  51. package/dist/core/group/extractors/topic-patterns/types.d.ts +25 -0
  52. package/dist/core/group/extractors/topic-patterns/types.js +10 -0
  53. package/dist/core/group/extractors/tree-sitter-scanner.d.ts +113 -0
  54. package/dist/core/group/extractors/tree-sitter-scanner.js +94 -0
  55. package/dist/core/ingestion/binding-accumulator.d.ts +22 -17
  56. package/dist/core/ingestion/binding-accumulator.js +29 -25
  57. package/dist/core/ingestion/cobol-processor.d.ts +1 -1
  58. package/dist/core/ingestion/import-processor.js +1 -1
  59. package/dist/core/ingestion/language-config.js +1 -1
  60. package/dist/core/ingestion/language-provider.d.ts +32 -5
  61. package/dist/core/ingestion/languages/c-cpp.js +2 -2
  62. package/dist/core/ingestion/languages/dart.d.ts +1 -1
  63. package/dist/core/ingestion/languages/dart.js +2 -2
  64. package/dist/core/ingestion/languages/go.d.ts +1 -1
  65. package/dist/core/ingestion/languages/go.js +2 -2
  66. package/dist/core/ingestion/languages/ruby.js +16 -1
  67. package/dist/core/ingestion/languages/swift.d.ts +1 -1
  68. package/dist/core/ingestion/languages/swift.js +2 -2
  69. package/dist/core/ingestion/markdown-processor.d.ts +1 -1
  70. package/dist/core/ingestion/method-extractors/configs/jvm.js +1 -0
  71. package/dist/core/ingestion/method-extractors/configs/ruby.js +1 -0
  72. package/dist/core/ingestion/method-extractors/generic.d.ts +6 -0
  73. package/dist/core/ingestion/method-extractors/generic.js +48 -4
  74. package/dist/core/ingestion/method-types.d.ts +4 -0
  75. package/dist/core/ingestion/model/resolve.js +103 -48
  76. package/dist/core/ingestion/model/semantic-model.d.ts +1 -1
  77. package/dist/core/ingestion/model/semantic-model.js +1 -1
  78. package/dist/core/ingestion/model/symbol-table.d.ts +7 -7
  79. package/dist/core/ingestion/model/symbol-table.js +7 -7
  80. package/dist/core/ingestion/mro-processor.d.ts +1 -1
  81. package/dist/core/ingestion/mro-processor.js +1 -1
  82. package/dist/core/ingestion/parsing-processor.js +54 -42
  83. package/dist/core/ingestion/pipeline-phases/cobol.d.ts +16 -0
  84. package/dist/core/ingestion/pipeline-phases/cobol.js +45 -0
  85. package/dist/core/ingestion/pipeline-phases/communities.d.ts +16 -0
  86. package/dist/core/ingestion/pipeline-phases/communities.js +62 -0
  87. package/dist/core/ingestion/pipeline-phases/cross-file-impl.d.ts +17 -0
  88. package/dist/core/ingestion/pipeline-phases/cross-file-impl.js +156 -0
  89. package/dist/core/ingestion/pipeline-phases/cross-file.d.ts +37 -0
  90. package/dist/core/ingestion/pipeline-phases/cross-file.js +63 -0
  91. package/dist/core/ingestion/pipeline-phases/index.d.ts +21 -0
  92. package/dist/core/ingestion/pipeline-phases/index.js +22 -0
  93. package/dist/core/ingestion/pipeline-phases/markdown.d.ts +17 -0
  94. package/dist/core/ingestion/pipeline-phases/markdown.js +33 -0
  95. package/dist/core/ingestion/pipeline-phases/mro.d.ts +18 -0
  96. package/dist/core/ingestion/pipeline-phases/mro.js +36 -0
  97. package/dist/core/ingestion/pipeline-phases/orm-extraction.d.ts +22 -0
  98. package/dist/core/ingestion/pipeline-phases/orm-extraction.js +92 -0
  99. package/dist/core/ingestion/pipeline-phases/orm.d.ts +15 -0
  100. package/dist/core/ingestion/pipeline-phases/orm.js +74 -0
  101. package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +47 -0
  102. package/dist/core/ingestion/pipeline-phases/parse-impl.js +437 -0
  103. package/dist/core/ingestion/pipeline-phases/parse.d.ts +49 -0
  104. package/dist/core/ingestion/pipeline-phases/parse.js +33 -0
  105. package/dist/core/ingestion/pipeline-phases/processes.d.ts +16 -0
  106. package/dist/core/ingestion/pipeline-phases/processes.js +143 -0
  107. package/dist/core/ingestion/pipeline-phases/routes.d.ts +21 -0
  108. package/dist/core/ingestion/pipeline-phases/routes.js +243 -0
  109. package/dist/core/ingestion/pipeline-phases/runner.d.ts +22 -0
  110. package/dist/core/ingestion/pipeline-phases/runner.js +203 -0
  111. package/dist/core/ingestion/pipeline-phases/scan.d.ts +21 -0
  112. package/dist/core/ingestion/pipeline-phases/scan.js +46 -0
  113. package/dist/core/ingestion/pipeline-phases/structure.d.ts +27 -0
  114. package/dist/core/ingestion/pipeline-phases/structure.js +35 -0
  115. package/dist/core/ingestion/pipeline-phases/tools.d.ts +20 -0
  116. package/dist/core/ingestion/pipeline-phases/tools.js +79 -0
  117. package/dist/core/ingestion/pipeline-phases/types.d.ts +79 -0
  118. package/dist/core/ingestion/pipeline-phases/types.js +37 -0
  119. package/dist/core/ingestion/pipeline-phases/wildcard-synthesis.d.ts +70 -0
  120. package/dist/core/ingestion/pipeline-phases/wildcard-synthesis.js +312 -0
  121. package/dist/core/ingestion/pipeline.d.ts +16 -10
  122. package/dist/core/ingestion/pipeline.js +66 -1534
  123. package/dist/core/ingestion/process-processor.js +1 -1
  124. package/dist/core/ingestion/tree-sitter-queries.d.ts +2 -2
  125. package/dist/core/ingestion/tree-sitter-queries.js +69 -0
  126. package/dist/core/ingestion/utils/ast-helpers.d.ts +1 -3
  127. package/dist/core/ingestion/utils/ast-helpers.js +48 -21
  128. package/dist/core/ingestion/utils/env.d.ts +10 -0
  129. package/dist/core/ingestion/utils/env.js +10 -0
  130. package/dist/core/ingestion/utils/graph-sort.d.ts +58 -0
  131. package/dist/core/ingestion/utils/graph-sort.js +100 -0
  132. package/dist/core/ingestion/workers/parse-worker.js +12 -8
  133. package/dist/core/lbug/lbug-adapter.d.ts +28 -0
  134. package/dist/core/lbug/lbug-adapter.js +162 -57
  135. package/package.json +3 -3
  136. package/vendor/tree-sitter-proto/binding.gyp +30 -0
  137. package/vendor/tree-sitter-proto/bindings/node/binding.cc +20 -0
  138. package/vendor/tree-sitter-proto/bindings/node/index.d.ts +28 -0
  139. package/vendor/tree-sitter-proto/bindings/node/index.js +7 -0
  140. package/vendor/tree-sitter-proto/package.json +18 -0
  141. package/vendor/tree-sitter-proto/src/node-types.json +1145 -0
  142. package/vendor/tree-sitter-proto/src/parser.c +10149 -0
  143. package/vendor/tree-sitter-proto/src/tree_sitter/alloc.h +54 -0
  144. package/vendor/tree-sitter-proto/src/tree_sitter/array.h +291 -0
  145. package/vendor/tree-sitter-proto/src/tree_sitter/parser.h +266 -0
@@ -0,0 +1,113 @@
1
+ import Parser from 'tree-sitter';
2
+ /**
3
+ * Shared, language-agnostic tree-sitter scanning utilities used by group
4
+ * extractors (topic, http, grpc, ...).
5
+ *
6
+ * Design goals:
7
+ * - The top-level extractors must not import any tree-sitter grammar.
8
+ * - Per-language plugins own their grammar import, their query sources,
9
+ * and the mapping from capture → meta.
10
+ * - This module provides the plumbing: compile queries once per plugin,
11
+ * parse a file with a given grammar, run all patterns, and return the
12
+ * captured `string_literal`-style nodes together with the plugin's meta.
13
+ */
14
+ /**
15
+ * One pattern owned by a language plugin. Each pattern owns a tree-sitter
16
+ * S-expression query. Plugins can freely choose which capture names to
17
+ * use — the scanner exposes every capture in the returned `captures`
18
+ * map and does not privilege any particular name.
19
+ *
20
+ * `TMeta` is the plugin-specific payload the orchestrator receives back
21
+ * when this pattern matches — e.g. for topic extraction it carries the
22
+ * broker name, role, confidence, symbol name.
23
+ */
24
+ export interface PatternSpec<TMeta> {
25
+ /** Tree-sitter S-expression. */
26
+ query: string;
27
+ /** Plugin-specific payload returned on every match. */
28
+ meta: TMeta;
29
+ }
30
+ /**
31
+ * A set of patterns owned by one language plugin, bound to a specific
32
+ * tree-sitter grammar.
33
+ *
34
+ * `language` is typed as `unknown` because tree-sitter's TypeScript
35
+ * declarations use `any` for the grammar object, and the grammar modules
36
+ * export different shapes (plain grammar vs. namespace with `typescript`
37
+ * / `tsx` members). Callers pass the concrete grammar object; this
38
+ * module forwards it to `parser.setLanguage` / `new Parser.Query`.
39
+ */
40
+ export interface LanguagePatterns<TMeta> {
41
+ /** Human-readable plugin name for diagnostics. */
42
+ name: string;
43
+ /** tree-sitter grammar object. */
44
+ language: unknown;
45
+ /** Patterns authored against `language`. */
46
+ patterns: PatternSpec<TMeta>[];
47
+ }
48
+ /**
49
+ * Compiled form of a `LanguagePatterns` bundle. Queries are compiled
50
+ * eagerly at module load time so a broken grammar/query pair fails
51
+ * loudly the first time the plugin is imported, instead of silently
52
+ * at scan time when no contract is produced.
53
+ */
54
+ export interface CompiledPatterns<TMeta> {
55
+ name: string;
56
+ language: unknown;
57
+ patterns: CompiledPattern<TMeta>[];
58
+ }
59
+ export interface CompiledPattern<TMeta> {
60
+ query: Parser.Query;
61
+ meta: TMeta;
62
+ }
63
+ /**
64
+ * Map from capture name → syntax node. Every named capture the query
65
+ * binds is exposed as an entry. If a query captures the same name more
66
+ * than once (unusual), the first occurrence wins — plugins that need
67
+ * all occurrences should use distinct capture names or fall back to
68
+ * `match.captures` array directly by iterating `query.matches()`
69
+ * themselves.
70
+ */
71
+ export type CaptureMap = Record<string, Parser.SyntaxNode>;
72
+ /**
73
+ * One match returned by `scanFile` / `runCompiledPatterns`. The caller
74
+ * receives the full capture map plus the plugin meta, and is
75
+ * responsible for turning it into a domain object.
76
+ */
77
+ export interface ScanMatch<TMeta> {
78
+ meta: TMeta;
79
+ captures: CaptureMap;
80
+ }
81
+ /**
82
+ * Compile a LanguagePatterns bundle. Call this once per plugin, at
83
+ * module load time, and export the result. Throws if any pattern
84
+ * fails to compile against the grammar — that's a bug in the plugin
85
+ * author's query, not a runtime condition.
86
+ */
87
+ export declare function compilePatterns<TMeta>(bundle: LanguagePatterns<TMeta>): CompiledPatterns<TMeta>;
88
+ /**
89
+ * Run every compiled pattern in `plugin` against an already-parsed
90
+ * tree. Use this when a plugin needs multiple query bundles against
91
+ * the same file (e.g. one query for class-level prefixes and another
92
+ * for method-level annotations) and wants to avoid re-parsing.
93
+ */
94
+ export declare function runCompiledPatterns<TMeta>(plugin: CompiledPatterns<TMeta>, tree: Parser.Tree): ScanMatch<TMeta>[];
95
+ /**
96
+ * Parse `content` with the plugin's grammar and run every compiled
97
+ * pattern against the AST. Returns one `ScanMatch` per matched query
98
+ * occurrence, carrying the plugin's meta payload.
99
+ *
100
+ * Errors are swallowed at the file level (malformed file must not abort
101
+ * the whole extract). Individual pattern failures are swallowed too so
102
+ * a single unusable query doesn't block the rest of the plugin.
103
+ */
104
+ export declare function scanFile<TMeta>(parser: Parser, plugin: CompiledPatterns<TMeta>, content: string): ScanMatch<TMeta>[];
105
+ /**
106
+ * Strip enclosing quotes from a tree-sitter string literal node's text.
107
+ * Handles single / double / template quotes, Python triple-quoted strings,
108
+ * and Go raw string literals (backticks).
109
+ *
110
+ * Returns null for empty/nullish input so callers can uniformly skip
111
+ * captures whose value is missing.
112
+ */
113
+ export declare function unquoteLiteral(raw: string): string | null;
@@ -0,0 +1,94 @@
1
+ import Parser from 'tree-sitter';
2
+ /**
3
+ * Compile a LanguagePatterns bundle. Call this once per plugin, at
4
+ * module load time, and export the result. Throws if any pattern
5
+ * fails to compile against the grammar — that's a bug in the plugin
6
+ * author's query, not a runtime condition.
7
+ */
8
+ export function compilePatterns(bundle) {
9
+ const compiled = [];
10
+ for (const spec of bundle.patterns) {
11
+ try {
12
+ const query = new Parser.Query(bundle.language, spec.query);
13
+ compiled.push({ query, meta: spec.meta });
14
+ }
15
+ catch (err) {
16
+ const message = err instanceof Error ? err.message : String(err);
17
+ throw new Error(`[tree-sitter-scanner] Failed to compile pattern in ${bundle.name}: ${message}\n` +
18
+ `Query source:\n${spec.query}`);
19
+ }
20
+ }
21
+ return { name: bundle.name, language: bundle.language, patterns: compiled };
22
+ }
23
+ /**
24
+ * Run every compiled pattern in `plugin` against an already-parsed
25
+ * tree. Use this when a plugin needs multiple query bundles against
26
+ * the same file (e.g. one query for class-level prefixes and another
27
+ * for method-level annotations) and wants to avoid re-parsing.
28
+ */
29
+ export function runCompiledPatterns(plugin, tree) {
30
+ const out = [];
31
+ for (const compiled of plugin.patterns) {
32
+ let matches;
33
+ try {
34
+ matches = compiled.query.matches(tree.rootNode);
35
+ }
36
+ catch {
37
+ continue;
38
+ }
39
+ for (const match of matches) {
40
+ const captures = {};
41
+ for (const cap of match.captures) {
42
+ if (!(cap.name in captures))
43
+ captures[cap.name] = cap.node;
44
+ }
45
+ out.push({ meta: compiled.meta, captures });
46
+ }
47
+ }
48
+ return out;
49
+ }
50
+ /**
51
+ * Parse `content` with the plugin's grammar and run every compiled
52
+ * pattern against the AST. Returns one `ScanMatch` per matched query
53
+ * occurrence, carrying the plugin's meta payload.
54
+ *
55
+ * Errors are swallowed at the file level (malformed file must not abort
56
+ * the whole extract). Individual pattern failures are swallowed too so
57
+ * a single unusable query doesn't block the rest of the plugin.
58
+ */
59
+ export function scanFile(parser, plugin, content) {
60
+ let tree;
61
+ try {
62
+ parser.setLanguage(plugin.language);
63
+ tree = parser.parse(content);
64
+ }
65
+ catch {
66
+ return [];
67
+ }
68
+ return runCompiledPatterns(plugin, tree);
69
+ }
70
+ /**
71
+ * Strip enclosing quotes from a tree-sitter string literal node's text.
72
+ * Handles single / double / template quotes, Python triple-quoted strings,
73
+ * and Go raw string literals (backticks).
74
+ *
75
+ * Returns null for empty/nullish input so callers can uniformly skip
76
+ * captures whose value is missing.
77
+ */
78
+ export function unquoteLiteral(raw) {
79
+ if (!raw)
80
+ return null;
81
+ // Python triple-quoted
82
+ if ((raw.startsWith('"""') && raw.endsWith('"""')) ||
83
+ (raw.startsWith("'''") && raw.endsWith("'''"))) {
84
+ return raw.slice(3, -3);
85
+ }
86
+ const first = raw[0];
87
+ const last = raw[raw.length - 1];
88
+ if ((first === '"' || first === "'" || first === '`') && last === first && raw.length >= 2) {
89
+ return raw.slice(1, -1);
90
+ }
91
+ // Some grammars expose the string content without quotes already (e.g.
92
+ // Python `string_content` child). Return as-is.
93
+ return raw;
94
+ }
@@ -36,9 +36,12 @@
36
36
  * or (b) post-process worker-path entries through a follow-up resolution
37
37
  * pass after the main-thread `SymbolTable` is complete.
38
38
  *
39
- * **Lifecycle contract**: `append → finalize → consume → dispose`. See
40
- * `finalize()` and `dispose()` for the state machine. Disposal is
41
- * orthogonal to finalization: either order is legal.
39
+ * **Lifecycle contract**: single-use — `append* → finalize → consume → dispose`.
40
+ * After `dispose()` the accumulator is permanently dead: any mutating call
41
+ * (`appendFile`) throws, and read methods return empty/undefined as if the
42
+ * accumulator had never been appended to. The instance is not recyclable;
43
+ * construct a new one for a new pipeline run. Finalization and disposal are
44
+ * orthogonal state dimensions and may be invoked in either order.
42
45
  */
43
46
  export interface BindingEntry {
44
47
  readonly scope: string;
@@ -119,28 +122,30 @@ export declare class BindingAccumulator {
119
122
  finalize(): void;
120
123
  /**
121
124
  * Release the accumulator's heap footprint. Clears both internal storage
122
- * maps and resets `_totalBindings` to zero. Idempotent and orthogonal to
123
- * `finalize()` — calling `dispose()` does not change the finalized state.
125
+ * maps and resets `_totalBindings` to zero. Idempotent calling twice
126
+ * is a no-op. Orthogonal to `finalize()` — calling `dispose()` does not
127
+ * change the finalized state.
124
128
  *
125
- * Post-dispose contract: all read methods return empty/undefined state
126
- * matching a never-appended-to accumulator. Specifically:
129
+ * **Single-use lifecycle.** This is a one-way terminal transition: the
130
+ * accumulator is not recyclable. Any subsequent `appendFile` call throws
131
+ * (`'BindingAccumulator: use after dispose'`), regardless of whether
132
+ * `finalize()` was called first. Post-dispose reads do not throw —
133
+ * they return empty/undefined state matching a never-appended-to
134
+ * accumulator:
127
135
  * - `fileCount === 0`
128
136
  * - `totalBindings === 0`
129
137
  * - `files()` yields an empty iterator
130
138
  * - `getFile(x)` returns `undefined` for all `x`
131
139
  * - `fileScopeEntries(x)` returns `[]` for all `x`
140
+ * - `fileScopeGet(x, y)` returns `undefined` for all `x, y`
132
141
  * - `estimateMemoryBytes()` returns `0`
133
142
  *
134
- * If `dispose()` is called **before** `finalize()`, subsequent `appendFile`
135
- * calls succeed the accumulator behaves like a fresh one. If called
136
- * **after** `finalize()`, subsequent `appendFile` calls throw the existing
137
- * "finalized" error.
138
- *
139
- * Lifecycle note: the pipeline disposes the accumulator after both Phase 9
140
- * consumers (`processCallsFromExtracted`, `processAssignmentsFromExtracted`)
141
- * and the ExportedTypeMap enrichment loop have completed, so the heap is
142
- * released before Phase 14 (`runCrossFileBindingPropagation`) and
143
- * `runGraphAnalysisPhases` begin their long-running work.
143
+ * Lifecycle note: the pipeline disposes the accumulator inside the
144
+ * `finally` of the `crossFile` phase, which is scheduled after every
145
+ * other accumulator consumer (Phase 9 call/assignment processing and
146
+ * the ExportedTypeMap enrichment loop). The dispose call therefore
147
+ * runs once, on both the happy path and the throw path of the
148
+ * crossFile phase.
144
149
  */
145
150
  dispose(): void;
146
151
  /** Get all bindings for a file, or undefined if the file is unknown. */
@@ -36,9 +36,12 @@
36
36
  * or (b) post-process worker-path entries through a follow-up resolution
37
37
  * pass after the main-thread `SymbolTable` is complete.
38
38
  *
39
- * **Lifecycle contract**: `append → finalize → consume → dispose`. See
40
- * `finalize()` and `dispose()` for the state machine. Disposal is
41
- * orthogonal to finalization: either order is legal.
39
+ * **Lifecycle contract**: single-use — `append* → finalize → consume → dispose`.
40
+ * After `dispose()` the accumulator is permanently dead: any mutating call
41
+ * (`appendFile`) throws, and read methods return empty/undefined as if the
42
+ * accumulator had never been appended to. The instance is not recyclable;
43
+ * construct a new one for a new pipeline run. Finalization and disposal are
44
+ * orthogonal state dimensions and may be invoked in either order.
42
45
  */
43
46
  /**
44
47
  * Merge file-scope bindings from a (finalized) `BindingAccumulator` into an
@@ -137,17 +140,16 @@ export class BindingAccumulator {
137
140
  if (this._finalized) {
138
141
  throw new Error('[BindingAccumulator] appendFile after finalize — no further appends allowed');
139
142
  }
143
+ // Single-use lifecycle: once disposed, the accumulator is dead. A
144
+ // post-dispose append almost always indicates a missed wiring step
145
+ // (the consumer is reading state that was supposed to be released),
146
+ // so convert the silent use-after-dispose into a loud failure.
147
+ if (this._disposed) {
148
+ throw new Error('BindingAccumulator: use after dispose');
149
+ }
140
150
  if (entries.length === 0) {
141
151
  return;
142
152
  }
143
- // Contract consistency: if this accumulator was previously disposed
144
- // without being finalized, `dispose()` is documented to leave it
145
- // "behaving like a fresh one" for subsequent appends. Clear the
146
- // `_disposed` flag here so the `disposed` getter tracks the actual
147
- // live state, not a stale signal from the prior lifecycle cycle.
148
- if (this._disposed) {
149
- this._disposed = false;
150
- }
151
153
  // Note on the file-scope-only invariant:
152
154
  // The accumulator does NOT reject function-scope entries at this
153
155
  // boundary. The narrowing contract is enforced by the two production
@@ -213,28 +215,30 @@ export class BindingAccumulator {
213
215
  }
214
216
  /**
215
217
  * Release the accumulator's heap footprint. Clears both internal storage
216
- * maps and resets `_totalBindings` to zero. Idempotent and orthogonal to
217
- * `finalize()` — calling `dispose()` does not change the finalized state.
218
+ * maps and resets `_totalBindings` to zero. Idempotent calling twice
219
+ * is a no-op. Orthogonal to `finalize()` — calling `dispose()` does not
220
+ * change the finalized state.
218
221
  *
219
- * Post-dispose contract: all read methods return empty/undefined state
220
- * matching a never-appended-to accumulator. Specifically:
222
+ * **Single-use lifecycle.** This is a one-way terminal transition: the
223
+ * accumulator is not recyclable. Any subsequent `appendFile` call throws
224
+ * (`'BindingAccumulator: use after dispose'`), regardless of whether
225
+ * `finalize()` was called first. Post-dispose reads do not throw —
226
+ * they return empty/undefined state matching a never-appended-to
227
+ * accumulator:
221
228
  * - `fileCount === 0`
222
229
  * - `totalBindings === 0`
223
230
  * - `files()` yields an empty iterator
224
231
  * - `getFile(x)` returns `undefined` for all `x`
225
232
  * - `fileScopeEntries(x)` returns `[]` for all `x`
233
+ * - `fileScopeGet(x, y)` returns `undefined` for all `x, y`
226
234
  * - `estimateMemoryBytes()` returns `0`
227
235
  *
228
- * If `dispose()` is called **before** `finalize()`, subsequent `appendFile`
229
- * calls succeed the accumulator behaves like a fresh one. If called
230
- * **after** `finalize()`, subsequent `appendFile` calls throw the existing
231
- * "finalized" error.
232
- *
233
- * Lifecycle note: the pipeline disposes the accumulator after both Phase 9
234
- * consumers (`processCallsFromExtracted`, `processAssignmentsFromExtracted`)
235
- * and the ExportedTypeMap enrichment loop have completed, so the heap is
236
- * released before Phase 14 (`runCrossFileBindingPropagation`) and
237
- * `runGraphAnalysisPhases` begin their long-running work.
236
+ * Lifecycle note: the pipeline disposes the accumulator inside the
237
+ * `finally` of the `crossFile` phase, which is scheduled after every
238
+ * other accumulator consumer (Phase 9 call/assignment processing and
239
+ * the ExportedTypeMap enrichment loop). The dispose call therefore
240
+ * runs once, on both the happy path and the throw path of the
241
+ * crossFile phase.
238
242
  */
239
243
  dispose() {
240
244
  this._allByFile.clear();
@@ -50,5 +50,5 @@ export declare function isJclFile(filePath: string): boolean;
50
50
  * @param allPathSet - Set of all file paths in the repository
51
51
  * @returns Summary of what was extracted
52
52
  */
53
- export declare const processCobol: (graph: KnowledgeGraph, files: CobolFile[], allPathSet: Set<string>) => CobolProcessResult;
53
+ export declare const processCobol: (graph: KnowledgeGraph, files: CobolFile[], allPathSet: ReadonlySet<string>) => CobolProcessResult;
54
54
  export {};
@@ -8,7 +8,7 @@ import { yieldToEventLoop } from './utils/event-loop.js';
8
8
  import { getTreeSitterBufferSize } from './constants.js';
9
9
  import { loadImportConfigs } from './language-config.js';
10
10
  import { buildSuffixIndex } from './import-resolvers/utils.js';
11
- const isDev = process.env.NODE_ENV === 'development';
11
+ import { isDev } from './utils/env.js';
12
12
  /** Group files by provider (only those with implicit import wiring), then call each wirer
13
13
  * with its own language's files. O(n) over files, O(1) per provider lookup. */
14
14
  function wireImplicitImports(files, importMap, addImportEdge, projectConfig) {
@@ -1,6 +1,6 @@
1
1
  import fs from 'fs/promises';
2
2
  import path from 'path';
3
- const isDev = process.env.NODE_ENV === 'development';
3
+ import { isDev } from './utils/env.js';
4
4
  // ============================================================================
5
5
  // LANGUAGE-SPECIFIC CONFIG LOADERS
6
6
  // ============================================================================
@@ -21,8 +21,25 @@ import type { SyntaxNode } from './utils/ast-helpers.js';
21
21
  import type { NodeLabel } from '../../_shared/index.js';
22
22
  /** Tree-sitter query captures: capture name → AST node (or undefined if not captured). */
23
23
  export type CaptureMap = Record<string, SyntaxNode | undefined>;
24
- /** How a language handles imports — determines wildcard synthesis behavior. */
25
- export type ImportSemantics = 'named' | 'wildcard' | 'namespace';
24
+ /**
25
+ * How a language handles imports — determines wildcard synthesis behavior.
26
+ *
27
+ * Import resolution is a graph-traversal policy with multiple distinct strategies,
28
+ * analogous to MRO for method resolution. Each tag picks a strategy:
29
+ *
30
+ * | Tag | Mechanism | Traversal | Languages |
31
+ * |-----------------------|------------------------------------------------|---------------------|--------------------------------------------|
32
+ * | `named` | Per-symbol imports | None (use-site) | JS/TS, Java, C#, Rust, PHP, Kotlin, Vue |
33
+ * | `wildcard-transitive` | Textual paste, symbols chain through files | BFS closure | C, C++ (future: Obj-C, Fortran, Nim) |
34
+ * | `wildcard-leaf` | Whole public API, single hop | None (direct only) | Go, Ruby, Swift, Dart |
35
+ * | `namespace` | Qualified handle; symbols resolved at call site| None at import | Python |
36
+ * | `explicit-reexport` | Opt-in per-symbol re-export (SCAFFOLD) | Topological DAG | (future: TS `export *`, Rust `pub use`) |
37
+ *
38
+ * The `explicit-reexport` tag is a compile-time scaffold; no provider claims it yet.
39
+ * It falls through to `wildcard-leaf` behavior in synthesis so today's TS/Rust
40
+ * handling is unchanged. A future PR will implement the DAG walk for `export *`.
41
+ */
42
+ export type ImportSemantics = 'named' | 'wildcard-transitive' | 'wildcard-leaf' | 'namespace' | 'explicit-reexport';
26
43
  /**
27
44
  * Everything a language needs to provide.
28
45
  * Required fields must be explicitly set; optional fields have defaults
@@ -51,10 +68,12 @@ interface LanguageProviderConfig {
51
68
  /** Named binding extraction from import statements.
52
69
  * Default: undefined (language uses wildcard/whole-module imports). */
53
70
  readonly namedBindingExtractor?: NamedBindingExtractorFn;
54
- /** How this language handles imports.
71
+ /** How this language handles imports. See `ImportSemantics` for the full taxonomy.
55
72
  * - 'named': per-symbol imports (JS/TS, Java, C#, Rust, PHP, Kotlin)
56
- * - 'wildcard': whole-module imports, needs synthesis (Go, Ruby, C/C++, Swift)
57
- * - 'namespace': namespace imports, needs moduleAliasMap (Python)
73
+ * - 'wildcard-transitive': textual-include closure; imports chain through files (C, C++)
74
+ * - 'wildcard-leaf': whole-module single-hop imports; no transitive chaining (Go, Ruby, Swift, Dart)
75
+ * - 'namespace': qualified namespace imports, needs moduleAliasMap (Python)
76
+ * - 'explicit-reexport': opt-in per-symbol re-export (scaffold; no provider uses yet)
58
77
  * Default: 'named'. */
59
78
  readonly importSemantics?: ImportSemantics;
60
79
  /** Language-specific transformation of raw import path text before resolution.
@@ -66,6 +85,14 @@ interface LanguageProviderConfig {
66
85
  * Called with only THIS language's files (pre-grouped by the processor).
67
86
  * Default: undefined (no implicit imports). */
68
87
  readonly implicitImportWirer?: (languageFiles: string[], importMap: ReadonlyMap<string, ReadonlySet<string>>, addImportEdge: (src: string, target: string) => void, projectConfig: unknown) => void;
88
+ /** Resolve a container node during enclosing-owner tree walks.
89
+ * Called when a CLASS_CONTAINER_TYPES node is found while walking up.
90
+ * - Return a different SyntaxNode to remap the container (e.g., Ruby
91
+ * singleton_class → enclosing class/module).
92
+ * - Return null to skip this container and keep walking up.
93
+ * - Omit (undefined) to use the container node as-is (default).
94
+ * Default: undefined (no remapping). */
95
+ readonly resolveEnclosingOwner?: (node: SyntaxNode) => SyntaxNode | null;
69
96
  /** Resolve the enclosing function name + label from an AST ancestor node
70
97
  * that is NOT a standard FUNCTION_NODE_TYPE. For languages where the
71
98
  * function body is a sibling of the signature (e.g. Dart: function_body ↔
@@ -293,7 +293,7 @@ export const cProvider = defineLanguage({
293
293
  typeConfig: cCppConfig,
294
294
  exportChecker: cCppExportChecker,
295
295
  importResolver: resolveCImport,
296
- importSemantics: 'wildcard',
296
+ importSemantics: 'wildcard-transitive',
297
297
  fieldExtractor: createFieldExtractor(cFieldConfig),
298
298
  methodExtractor: createMethodExtractor({
299
299
  ...cMethodConfig,
@@ -310,7 +310,7 @@ export const cppProvider = defineLanguage({
310
310
  typeConfig: cCppConfig,
311
311
  exportChecker: cCppExportChecker,
312
312
  importResolver: resolveCppImport,
313
- importSemantics: 'wildcard',
313
+ importSemantics: 'wildcard-transitive',
314
314
  mroStrategy: 'leftmost-base',
315
315
  fieldExtractor: createFieldExtractor(cppFieldConfig),
316
316
  methodExtractor: createMethodExtractor({
@@ -2,7 +2,7 @@
2
2
  * Dart Language Provider
3
3
  *
4
4
  * Dart traits:
5
- * - importSemantics: 'wildcard' (Dart imports bring everything public into scope)
5
+ * - importSemantics: 'wildcard-leaf' (Dart imports bring everything public into scope)
6
6
  * - exportChecker: public if no leading underscore
7
7
  * - Dart SDK imports (dart:*) and external packages are skipped
8
8
  * - enclosingFunctionFinder: Dart's tree-sitter grammar places function_body
@@ -2,7 +2,7 @@
2
2
  * Dart Language Provider
3
3
  *
4
4
  * Dart traits:
5
- * - importSemantics: 'wildcard' (Dart imports bring everything public into scope)
5
+ * - importSemantics: 'wildcard-leaf' (Dart imports bring everything public into scope)
6
6
  * - exportChecker: public if no leading underscore
7
7
  * - Dart SDK imports (dart:*) and external packages are skipped
8
8
  * - enclosingFunctionFinder: Dart's tree-sitter grammar places function_body
@@ -83,7 +83,7 @@ export const dartProvider = defineLanguage({
83
83
  typeConfig: dartConfig,
84
84
  exportChecker: dartExportChecker,
85
85
  importResolver: resolveDartImport,
86
- importSemantics: 'wildcard',
86
+ importSemantics: 'wildcard-leaf',
87
87
  fieldExtractor: createFieldExtractor(dartFieldConfig),
88
88
  methodExtractor: createMethodExtractor(dartMethodConfig),
89
89
  classExtractor: createClassExtractor({
@@ -5,7 +5,7 @@
5
5
  * LanguageProvider, following the Strategy pattern used by the pipeline.
6
6
  *
7
7
  * Key Go traits:
8
- * - importSemantics: 'wildcard' (Go imports entire packages)
8
+ * - importSemantics: 'wildcard-leaf' (Go imports entire packages)
9
9
  * - callRouter: present (Go method calls may need routing)
10
10
  */
11
11
  export declare const goProvider: import("../language-provider.js").LanguageProvider;
@@ -5,7 +5,7 @@
5
5
  * LanguageProvider, following the Strategy pattern used by the pipeline.
6
6
  *
7
7
  * Key Go traits:
8
- * - importSemantics: 'wildcard' (Go imports entire packages)
8
+ * - importSemantics: 'wildcard-leaf' (Go imports entire packages)
9
9
  * - callRouter: present (Go method calls may need routing)
10
10
  */
11
11
  import { SupportedLanguages } from '../../../_shared/index.js';
@@ -26,7 +26,7 @@ export const goProvider = defineLanguage({
26
26
  typeConfig: goConfig,
27
27
  exportChecker: goExportChecker,
28
28
  importResolver: resolveGoImport,
29
- importSemantics: 'wildcard',
29
+ importSemantics: 'wildcard-leaf',
30
30
  fieldExtractor: createFieldExtractor(goFieldConfig),
31
31
  methodExtractor: createMethodExtractor(goMethodConfig),
32
32
  classExtractor: createClassExtractor({
@@ -99,7 +99,22 @@ export const rubyProvider = defineLanguage({
99
99
  exportChecker: rubyExportChecker,
100
100
  importResolver: resolveRubyImport,
101
101
  callRouter: routeRubyCall,
102
- importSemantics: 'wildcard',
102
+ importSemantics: 'wildcard-leaf',
103
+ resolveEnclosingOwner(node) {
104
+ // Ruby singleton_class (class << self) should resolve to the enclosing
105
+ // class or module for owner/container resolution (HAS_METHOD edges, class IDs).
106
+ if (node.type === 'singleton_class') {
107
+ let ancestor = node.parent;
108
+ while (ancestor) {
109
+ if (ancestor.type === 'class' || ancestor.type === 'module') {
110
+ return ancestor;
111
+ }
112
+ ancestor = ancestor.parent;
113
+ }
114
+ return null; // no enclosing class/module — skip
115
+ }
116
+ return node; // use as-is for all other container types
117
+ },
103
118
  fieldExtractor: createFieldExtractor(rubyFieldConfig),
104
119
  methodExtractor: createMethodExtractor({
105
120
  ...rubyMethodConfig,
@@ -5,7 +5,7 @@
5
5
  * LanguageProvider, following the Strategy pattern used by the pipeline.
6
6
  *
7
7
  * Key Swift traits:
8
- * - importSemantics: 'wildcard' (Swift imports entire modules)
8
+ * - importSemantics: 'wildcard-leaf' (Swift imports entire modules)
9
9
  * - heritageDefaultEdge: 'IMPLEMENTS' (protocols are more common than class inheritance)
10
10
  * - implicitImportWirer: all files in the same SPM target see each other
11
11
  */
@@ -5,7 +5,7 @@
5
5
  * LanguageProvider, following the Strategy pattern used by the pipeline.
6
6
  *
7
7
  * Key Swift traits:
8
- * - importSemantics: 'wildcard' (Swift imports entire modules)
8
+ * - importSemantics: 'wildcard-leaf' (Swift imports entire modules)
9
9
  * - heritageDefaultEdge: 'IMPLEMENTS' (protocols are more common than class inheritance)
10
10
  * - implicitImportWirer: all files in the same SPM target see each other
11
11
  */
@@ -221,7 +221,7 @@ export const swiftProvider = defineLanguage({
221
221
  typeConfig: swiftConfig,
222
222
  exportChecker: swiftExportChecker,
223
223
  importResolver: resolveSwiftImport,
224
- importSemantics: 'wildcard',
224
+ importSemantics: 'wildcard-leaf',
225
225
  heritageDefaultEdge: 'IMPLEMENTS',
226
226
  fieldExtractor: createFieldExtractor(swiftFieldConfig),
227
227
  methodExtractor: createMethodExtractor({
@@ -10,7 +10,7 @@ interface MdFile {
10
10
  path: string;
11
11
  content: string;
12
12
  }
13
- export declare const processMarkdown: (graph: KnowledgeGraph, files: MdFile[], allPathSet: Set<string>) => {
13
+ export declare const processMarkdown: (graph: KnowledgeGraph, files: MdFile[], allPathSet: ReadonlySet<string>) => {
14
14
  sections: number;
15
15
  links: number;
16
16
  };
@@ -248,6 +248,7 @@ export const kotlinMethodConfig = {
248
248
  typeDeclarationNodes: ['class_declaration', 'object_declaration', 'companion_object'],
249
249
  methodNodeTypes: ['function_declaration'],
250
250
  bodyNodeTypes: ['class_body'],
251
+ staticOwnerTypes: new Set(['companion_object', 'object_declaration']),
251
252
  extractName(node) {
252
253
  for (let i = 0; i < node.namedChildCount; i++) {
253
254
  const child = node.namedChild(i);
@@ -202,6 +202,7 @@ export const rubyMethodConfig = {
202
202
  typeDeclarationNodes: ['class', 'module', 'singleton_class'],
203
203
  methodNodeTypes: ['method', 'singleton_method'],
204
204
  bodyNodeTypes: ['body_statement'],
205
+ staticOwnerTypes: new Set(['singleton_class']),
205
206
  extractOwnerName(node) {
206
207
  // singleton_class (class << self) inherits the enclosing class/module name
207
208
  if (node.type === 'singleton_class') {
@@ -1,5 +1,11 @@
1
1
  import type { MethodExtractor, MethodExtractionConfig } from '../method-types.js';
2
2
  /**
3
3
  * Create a MethodExtractor from a declarative config.
4
+ *
5
+ * @throws {Error} if `typeDeclarationNodes` contains a static-implying owner
6
+ * type (companion_object / object_declaration / singleton_class) that is
7
+ * not covered by `staticOwnerTypes`. The guard fires once per language at
8
+ * provider construction to prevent silent `isStatic=false` regressions. See
9
+ * `STATIC_IMPLYING_OWNER_TYPES` for the exact opt-out convention.
4
10
  */
5
11
  export declare function createMethodExtractor(config: MethodExtractionConfig): MethodExtractor;