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
@@ -1,10 +1,53 @@
1
1
  // gitnexus/src/core/ingestion/method-extractors/generic.ts
2
- /** Owner node types where member functions are effectively static (JVM/Ruby semantics). */
3
- const STATIC_OWNER_TYPES = new Set(['companion_object', 'object_declaration', 'singleton_class']);
2
+ /**
3
+ * Node types that imply static member semantics when they appear as the owner
4
+ * of a method (Kotlin companion objects, Kotlin top-level `object` declarations,
5
+ * Ruby `class << self` singleton classes). A config that lists any of these in
6
+ * `typeDeclarationNodes` MUST also include the same node type in
7
+ * `staticOwnerTypes` — otherwise methods inside these containers silently get
8
+ * `isStatic=false`, which is a correctness bug that previously only surfaced
9
+ * at analysis time on large repos.
10
+ *
11
+ * Opt-out: a config that sets `staticOwnerTypes: new Set()` (explicit empty
12
+ * set) signals "I handle static-ness entirely via isStatic()" and is exempt
13
+ * from the guard.
14
+ */
15
+ const STATIC_IMPLYING_OWNER_TYPES = new Set([
16
+ 'companion_object',
17
+ 'object_declaration',
18
+ 'singleton_class',
19
+ ]);
4
20
  /**
5
21
  * Create a MethodExtractor from a declarative config.
22
+ *
23
+ * @throws {Error} if `typeDeclarationNodes` contains a static-implying owner
24
+ * type (companion_object / object_declaration / singleton_class) that is
25
+ * not covered by `staticOwnerTypes`. The guard fires once per language at
26
+ * provider construction to prevent silent `isStatic=false` regressions. See
27
+ * `STATIC_IMPLYING_OWNER_TYPES` for the exact opt-out convention.
6
28
  */
7
29
  export function createMethodExtractor(config) {
30
+ // Runtime invariant: each static-implying container type declared in
31
+ // typeDeclarationNodes must be covered by staticOwnerTypes. An explicit
32
+ // empty Set is treated as intentional opt-out.
33
+ if (config.staticOwnerTypes === undefined) {
34
+ const missing = config.typeDeclarationNodes.filter((t) => STATIC_IMPLYING_OWNER_TYPES.has(t));
35
+ if (missing.length > 0) {
36
+ throw new Error(`[MethodExtractionConfig:${config.language}] typeDeclarationNodes includes static-implying owner type(s) ` +
37
+ `${JSON.stringify(missing)} but staticOwnerTypes is not set. Add ` +
38
+ `'staticOwnerTypes: new Set([${missing.map((t) => `'${t}'`).join(', ')}])' ` +
39
+ `to the config, or set 'staticOwnerTypes: new Set()' to opt out explicitly.`);
40
+ }
41
+ }
42
+ else {
43
+ const missing = config.typeDeclarationNodes.filter((t) => STATIC_IMPLYING_OWNER_TYPES.has(t) && !config.staticOwnerTypes.has(t));
44
+ // Explicit empty Set is the opt-out signal; don't second-guess it.
45
+ if (missing.length > 0 && config.staticOwnerTypes.size > 0) {
46
+ throw new Error(`[MethodExtractionConfig:${config.language}] typeDeclarationNodes includes static-implying owner type(s) ` +
47
+ `${JSON.stringify(missing)} that are missing from staticOwnerTypes. ` +
48
+ `Either add them to staticOwnerTypes, or set 'staticOwnerTypes: new Set()' to opt out explicitly.`);
49
+ }
50
+ }
8
51
  const typeDeclarationSet = new Set(config.typeDeclarationNodes);
9
52
  const methodNodeSet = new Set(config.methodNodeTypes);
10
53
  const bodyNodeSet = new Set(config.bodyNodeTypes);
@@ -137,8 +180,9 @@ function buildMethod(node, ownerNode, context, config) {
137
180
  // Domain invariant: abstract methods cannot be final
138
181
  if (isAbstract)
139
182
  isFinal = false;
140
- // companion_object / object_declaration members are effectively static on JVM
141
- const isStatic = STATIC_OWNER_TYPES.has(ownerNode.type) || config.isStatic(node);
183
+ // Static-owner detection is config-driven: each language declares which
184
+ // container node types imply static (e.g. Ruby singleton_class, Kotlin companion_object).
185
+ const isStatic = (config.staticOwnerTypes?.has(ownerNode.type) ?? false) || config.isStatic(node);
142
186
  return {
143
187
  name,
144
188
  receiverType: config.extractReceiverType?.(node) ?? null,
@@ -73,6 +73,10 @@ export interface MethodExtractionConfig {
73
73
  isAsync?: (node: SyntaxNode) => boolean;
74
74
  isPartial?: (node: SyntaxNode) => boolean;
75
75
  isConst?: (node: SyntaxNode) => boolean;
76
+ /** Owner node types where member functions are effectively static (e.g.
77
+ * Ruby singleton_class, Kotlin companion_object / object_declaration).
78
+ * When the ownerNode matches one of these types, isStatic is forced true. */
79
+ staticOwnerTypes?: ReadonlySet<string>;
76
80
  /** Resolve the owner name from a standalone method node (e.g. Go receiver type). */
77
81
  extractOwnerName?: (node: SyntaxNode) => string | undefined;
78
82
  /** Extract a primary constructor from the owner node itself (e.g. C# 12 class Point(int x, int y)). */
@@ -47,63 +47,118 @@ function gatherAncestors(classId, parentMap) {
47
47
  export function c3Linearize(classId, parentMap, cache, inProgress) {
48
48
  if (cache.has(classId))
49
49
  return cache.get(classId);
50
- // Cycle detection: if we're already computing this class, the hierarchy is cyclic
50
+ // Iterative C3 linearization using an explicit work stack. The recursive
51
+ // version overflows the call stack on deep class hierarchies (10K+
52
+ // levels in large Android/Java codebases).
53
+ //
54
+ // Strategy: maintain a stack of { classId, phase } frames. Each frame
55
+ // goes through two phases:
56
+ // ENTER (0) – check cache / cycle, push parent frames to compute first
57
+ // MERGE (1) – all parent linearizations are cached, merge them C3-style
51
58
  const visiting = inProgress ?? new Set();
52
- if (visiting.has(classId)) {
53
- cache.set(classId, null);
54
- return null;
55
- }
56
- visiting.add(classId);
57
- const directParents = parentMap.get(classId);
58
- if (!directParents || directParents.length === 0) {
59
- visiting.delete(classId);
60
- cache.set(classId, []);
61
- return [];
62
- }
63
- // Compute linearization for each parent first
64
- const parentLinearizations = [];
65
- for (const pid of directParents) {
66
- const pLin = c3Linearize(pid, parentMap, cache, visiting);
67
- if (pLin === null) {
68
- visiting.delete(classId);
69
- cache.set(classId, null);
70
- return null;
71
- }
72
- parentLinearizations.push([pid, ...pLin]);
73
- }
74
- // Add the direct parents list as the final sequence
75
- const sequences = [...parentLinearizations, [...directParents]];
76
- const result = [];
77
- while (sequences.some((s) => s.length > 0)) {
78
- // Find a good head: one that doesn't appear in the tail of any other sequence
79
- let head = null;
80
- for (const seq of sequences) {
81
- if (seq.length === 0)
59
+ const ENTER = 0;
60
+ const MERGE = 1;
61
+ const stack = [{ id: classId, phase: ENTER }];
62
+ while (stack.length > 0) {
63
+ const frame = stack[stack.length - 1];
64
+ if (frame.phase === ENTER) {
65
+ // ── ENTER phase ─────────────────────────────────────────────
66
+ if (cache.has(frame.id)) {
67
+ stack.pop();
68
+ continue;
69
+ }
70
+ if (visiting.has(frame.id)) {
71
+ // Cycle detected
72
+ cache.set(frame.id, null);
73
+ stack.pop();
74
+ continue;
75
+ }
76
+ visiting.add(frame.id);
77
+ const directParents = parentMap.get(frame.id);
78
+ if (!directParents || directParents.length === 0) {
79
+ visiting.delete(frame.id);
80
+ cache.set(frame.id, []);
81
+ stack.pop();
82
+ continue;
83
+ }
84
+ // Switch to MERGE phase and push parents that still need computing
85
+ frame.phase = MERGE;
86
+ let allParentsCached = true;
87
+ for (let i = directParents.length - 1; i >= 0; i--) {
88
+ const pid = directParents[i];
89
+ if (!cache.has(pid)) {
90
+ stack.push({ id: pid, phase: ENTER });
91
+ allParentsCached = false;
92
+ }
93
+ }
94
+ // If all parents are already cached, proceed directly to the MERGE
95
+ // phase below (frame.phase is already MERGE, frame is at stack top).
96
+ // Otherwise, loop back to process the newly-pushed parent frames first.
97
+ if (!allParentsCached) {
82
98
  continue;
83
- const candidate = seq[0];
84
- const inTail = sequences.some((other) => other.length > 1 && other.indexOf(candidate, 1) !== -1);
85
- if (!inTail) {
86
- head = candidate;
99
+ }
100
+ }
101
+ // ── MERGE phase ───────────────────────────────────────────────
102
+ // directParents is guaranteed non-empty here — the ENTER phase already
103
+ // handles the empty-parents case and pops the frame before switching
104
+ // to MERGE.
105
+ stack.pop();
106
+ const directParents = parentMap.get(frame.id);
107
+ // Build parent linearizations from cache
108
+ const parentLinearizations = [];
109
+ let failed = false;
110
+ for (const pid of directParents) {
111
+ const pLin = cache.get(pid);
112
+ if (pLin === undefined) {
113
+ // Should not happen if phases are ordered correctly, but guard anyway
114
+ failed = true;
115
+ break;
116
+ }
117
+ if (pLin === null) {
118
+ // Parent linearization failed (cycle or inconsistent)
119
+ failed = true;
87
120
  break;
88
121
  }
122
+ parentLinearizations.push([pid, ...pLin]);
89
123
  }
90
- if (head === null) {
91
- // Inconsistent hierarchy
92
- visiting.delete(classId);
93
- cache.set(classId, null);
94
- return null;
124
+ if (failed) {
125
+ visiting.delete(frame.id);
126
+ cache.set(frame.id, null);
127
+ continue;
95
128
  }
96
- result.push(head);
97
- // Remove the chosen head from all sequences
98
- for (const seq of sequences) {
99
- if (seq.length > 0 && seq[0] === head) {
100
- seq.shift();
129
+ // Add the direct parents list as the final sequence
130
+ const sequences = [...parentLinearizations, [...directParents]];
131
+ const result = [];
132
+ let inconsistent = false;
133
+ while (sequences.some((s) => s.length > 0)) {
134
+ // Find a good head: one that doesn't appear in the tail of any other sequence
135
+ let head = null;
136
+ for (const seq of sequences) {
137
+ if (seq.length === 0)
138
+ continue;
139
+ const candidate = seq[0];
140
+ const inTail = sequences.some((other) => other.length > 1 && other.indexOf(candidate, 1) !== -1);
141
+ if (!inTail) {
142
+ head = candidate;
143
+ break;
144
+ }
145
+ }
146
+ if (head === null) {
147
+ inconsistent = true;
148
+ break;
149
+ }
150
+ result.push(head);
151
+ // Remove the chosen head from all sequences
152
+ for (const seq of sequences) {
153
+ if (seq.length > 0 && seq[0] === head) {
154
+ seq.shift();
155
+ }
101
156
  }
102
157
  }
158
+ visiting.delete(frame.id);
159
+ cache.set(frame.id, inconsistent ? null : result);
103
160
  }
104
- visiting.delete(classId);
105
- cache.set(classId, result);
106
- return result;
161
+ return cache.get(classId) ?? null;
107
162
  }
108
163
  // `gatherAncestors` is exported so mro-processor.ts can reuse the same
109
164
  // BFS traversal for graph-level MRO emission.
@@ -7,7 +7,7 @@
7
7
  * - A nested SymbolTable (file + callable name indexes) wrapped so
8
8
  * that `add()` fans out into the registries via the dispatch table
9
9
  *
10
- * ## DAG direction
10
+ * ## Dependency direction
11
11
  *
12
12
  * gitnexus-shared (NodeLabel) — leaf
13
13
  * ↑
@@ -7,7 +7,7 @@
7
7
  * - A nested SymbolTable (file + callable name indexes) wrapped so
8
8
  * that `add()` fans out into the registries via the dispatch table
9
9
  *
10
- * ## DAG direction
10
+ * ## Dependency direction
11
11
  *
12
12
  * gitnexus-shared (NodeLabel) — leaf
13
13
  * ↑
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Symbol Table — file-indexed + callable-name symbol storage.
3
3
  *
4
- * This module is a PURE LEAF in the ingestion DAG. It owns two orthogonal
5
- * O(1) indexes:
4
+ * This module is a PURE LEAF in the ingestion dependency hierarchy. It owns
5
+ * two orthogonal O(1) indexes:
6
6
  *
7
7
  * 1. fileIndex — Map<filePath, Map<name, SymbolDefinition[]>>
8
8
  * for same-file lookups (Tier 1 resolution)
@@ -10,12 +10,12 @@
10
10
  * for name-keyed callable lookups (Tier 3 widen)
11
11
  *
12
12
  * SymbolTable deliberately knows NOTHING about the owner-scoped registries
13
- * (types, methods, fields) that sit above it in the DAG. Those registries
14
- * live in `model/` and depend on SymbolTable, not the other way around.
15
- * {@link createSemanticModel} composes this pure SymbolTable with the
13
+ * (types, methods, fields) that sit above it in the dependency graph. Those
14
+ * registries live in `model/` and depend on SymbolTable, not the other way
15
+ * around. {@link createSemanticModel} composes this pure SymbolTable with the
16
16
  * registries and wraps `add()` to fan out registrations into both layers.
17
17
  *
18
- * DAG direction (strictly enforced):
18
+ * Dependency direction (strictly enforced):
19
19
  *
20
20
  * gitnexus-shared (NodeLabel) — leaf type
21
21
  * ↑
@@ -31,7 +31,7 @@
31
31
  *
32
32
  * No arrow ever points downward from this file. If you are tempted to
33
33
  * import from `./model/` here, you are going the wrong way — move the
34
- * logic up the DAG instead.
34
+ * logic up the dependency chain instead.
35
35
  */
36
36
  import type { NodeLabel } from '../../../_shared/index.js';
37
37
  /**
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Symbol Table — file-indexed + callable-name symbol storage.
3
3
  *
4
- * This module is a PURE LEAF in the ingestion DAG. It owns two orthogonal
5
- * O(1) indexes:
4
+ * This module is a PURE LEAF in the ingestion dependency hierarchy. It owns
5
+ * two orthogonal O(1) indexes:
6
6
  *
7
7
  * 1. fileIndex — Map<filePath, Map<name, SymbolDefinition[]>>
8
8
  * for same-file lookups (Tier 1 resolution)
@@ -10,12 +10,12 @@
10
10
  * for name-keyed callable lookups (Tier 3 widen)
11
11
  *
12
12
  * SymbolTable deliberately knows NOTHING about the owner-scoped registries
13
- * (types, methods, fields) that sit above it in the DAG. Those registries
14
- * live in `model/` and depend on SymbolTable, not the other way around.
15
- * {@link createSemanticModel} composes this pure SymbolTable with the
13
+ * (types, methods, fields) that sit above it in the dependency graph. Those
14
+ * registries live in `model/` and depend on SymbolTable, not the other way
15
+ * around. {@link createSemanticModel} composes this pure SymbolTable with the
16
16
  * registries and wraps `add()` to fan out registrations into both layers.
17
17
  *
18
- * DAG direction (strictly enforced):
18
+ * Dependency direction (strictly enforced):
19
19
  *
20
20
  * gitnexus-shared (NodeLabel) — leaf type
21
21
  * ↑
@@ -31,7 +31,7 @@
31
31
  *
32
32
  * No arrow ever points downward from this file. If you are tempted to
33
33
  * import from `./model/` here, you are going the wrong way — move the
34
- * logic up the DAG instead.
34
+ * logic up the dependency chain instead.
35
35
  */
36
36
  /**
37
37
  * Class-like NodeLabels — used for qualifiedName fallback inside
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * MRO (Method Resolution Order) Processor
3
3
  *
4
- * Walks the inheritance DAG (EXTENDS/IMPLEMENTS edges), collects methods from
4
+ * Walks the inheritance graph (EXTENDS/IMPLEMENTS edges), collects methods from
5
5
  * each ancestor via HAS_METHOD edges, detects method-name collisions across
6
6
  * parents, and applies language-specific resolution rules to emit METHOD_OVERRIDES edges.
7
7
  *
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * MRO (Method Resolution Order) Processor
3
3
  *
4
- * Walks the inheritance DAG (EXTENDS/IMPLEMENTS edges), collects methods from
4
+ * Walks the inheritance graph (EXTENDS/IMPLEMENTS edges), collects methods from
5
5
  * each ancestor via HAS_METHOD edges, detects method-name collisions across
6
6
  * parents, and applies language-specific resolution rules to emit METHOD_OVERRIDES edges.
7
7
  *
@@ -74,30 +74,30 @@ const processParsingWithWorkers = async (graph, files, symbolTable, astCache, wo
74
74
  qualifiedName: sym.qualifiedName,
75
75
  });
76
76
  }
77
- for (const _item of result.imports)
78
- allImports.push(_item);
79
- for (const _item of result.calls)
80
- allCalls.push(_item);
81
- for (const _item of result.assignments)
82
- allAssignments.push(_item);
83
- for (const _item of result.heritage)
84
- allHeritage.push(_item);
85
- for (const _item of result.routes)
86
- allRoutes.push(_item);
87
- for (const _item of result.fetchCalls)
88
- allFetchCalls.push(_item);
89
- for (const _item of result.decoratorRoutes)
90
- allDecoratorRoutes.push(_item);
91
- for (const _item of result.toolDefs)
92
- allToolDefs.push(_item);
77
+ for (const item of result.imports)
78
+ allImports.push(item);
79
+ for (const item of result.calls)
80
+ allCalls.push(item);
81
+ for (const item of result.assignments)
82
+ allAssignments.push(item);
83
+ for (const item of result.heritage)
84
+ allHeritage.push(item);
85
+ for (const item of result.routes)
86
+ allRoutes.push(item);
87
+ for (const item of result.fetchCalls)
88
+ allFetchCalls.push(item);
89
+ for (const item of result.decoratorRoutes)
90
+ allDecoratorRoutes.push(item);
91
+ for (const item of result.toolDefs)
92
+ allToolDefs.push(item);
93
93
  if (result.ormQueries)
94
- for (const _item of result.ormQueries)
95
- allORMQueries.push(_item);
96
- for (const _item of result.constructorBindings)
97
- allConstructorBindings.push(_item);
94
+ for (const item of result.ormQueries)
95
+ allORMQueries.push(item);
96
+ for (const item of result.constructorBindings)
97
+ allConstructorBindings.push(item);
98
98
  if (result.fileScopeBindings)
99
- for (const _item of result.fileScopeBindings)
100
- fileScopeBindingsByFile.push(_item);
99
+ for (const item of result.fileScopeBindings)
100
+ fileScopeBindingsByFile.push(item);
101
101
  }
102
102
  // Merge and log skipped languages from workers
103
103
  const skippedLanguages = new Map();
@@ -135,11 +135,11 @@ const processParsingWithWorkers = async (graph, files, symbolTable, astCache, wo
135
135
  // Keyed by tree-sitter node reference — cleared at the start of each file.
136
136
  const classInfoCache = new Map();
137
137
  const exportCache = new Map();
138
- const cachedFindEnclosingClassInfo = (node, filePath) => {
138
+ const cachedFindEnclosingClassInfo = (node, filePath, resolveEnclosingOwner) => {
139
139
  const cached = classInfoCache.get(node);
140
140
  if (cached !== undefined)
141
141
  return cached;
142
- const result = findEnclosingClassInfo(node, filePath);
142
+ const result = findEnclosingClassInfo(node, filePath, resolveEnclosingOwner);
143
143
  classInfoCache.set(node, result);
144
144
  return result;
145
145
  };
@@ -158,24 +158,34 @@ const seqFieldInfoCache = new Map();
158
158
  const seqMethodExtractCache = new Map();
159
159
  // Derived method map + collision groups cache — avoids rebuilding per method.
160
160
  const seqMethodMapCache = new Map();
161
- function seqFindEnclosingClassNode(node) {
161
+ /** Provider-aware enclosing container lookup.
162
+ * Walks up from `node` until a CLASS_CONTAINER_TYPES node is found.
163
+ * When `resolveEnclosingOwner` is provided, delegates language-specific
164
+ * container remapping (e.g., Ruby singleton_class → enclosing class).
165
+ * Without the hook, returns the first matching container directly (raw lookup). */
166
+ function seqFindEnclosingOwnerNode(node, resolveEnclosingOwner) {
162
167
  let current = node.parent;
163
168
  while (current) {
164
169
  if (CLASS_CONTAINER_TYPES.has(current.type)) {
165
- // Return singleton_class directly so the method extractor sees it as
166
- // the owner node and correctly marks methods as static. Name resolution
167
- // for qualified names is handled separately by findEnclosingClassInfo.
170
+ if (resolveEnclosingOwner) {
171
+ const resolved = resolveEnclosingOwner(current);
172
+ if (resolved === null) {
173
+ // Provider says skip this container — keep walking up.
174
+ current = current.parent;
175
+ continue;
176
+ }
177
+ return resolved;
178
+ }
168
179
  return current;
169
180
  }
170
181
  current = current.parent;
171
182
  }
172
183
  return null;
173
184
  }
174
- /** Minimal no-op SymbolTable stub for FieldExtractorContext (sequential
175
- * path has a real SymbolTable, but it's incomplete at this stage use
176
- * the stub for safety). Implements the full {@link SymbolTableReader}
177
- * surface so future extractor additions don't silently fall off an
178
- * `as unknown as` cast. */
185
+ /** Minimal no-op SymbolTable stub for sequential extractor contexts. The real
186
+ * SymbolTable is not fully populated yet at this stage, so use the stub for safety.
187
+ * Implements the full {@link SymbolTableReader} surface so future extractor additions
188
+ * don't silently fall off an `as unknown as` cast. */
179
189
  const NOOP_SYMBOL_TABLE_SEQ = {
180
190
  lookupExact: () => undefined,
181
191
  lookupExactFull: () => undefined,
@@ -318,7 +328,7 @@ const processParsingSequential = async (graph, files, symbolTable, astCache, onF
318
328
  nodeLabel === 'Property' ||
319
329
  nodeLabel === 'Function';
320
330
  const enclosingClassInfo = needsOwner
321
- ? cachedFindEnclosingClassInfo(nameNode || definitionNodeForRange, file.path)
331
+ ? cachedFindEnclosingClassInfo(nameNode || definitionNodeForRange, file.path, provider.resolveEnclosingOwner)
322
332
  : null;
323
333
  const enclosingClassId = enclosingClassInfo?.classId ?? null;
324
334
  // Qualify method/property IDs with enclosing class name to avoid collisions
@@ -339,19 +349,21 @@ const processParsingSequential = async (graph, files, symbolTable, astCache, onF
339
349
  if (isMethodLike && definitionNode) {
340
350
  let enriched = false;
341
351
  if (provider.methodExtractor) {
342
- // Try class-based extraction (method inside a class/struct/trait body)
343
- const classNode = seqFindEnclosingClassNode(definitionNode);
344
- if (classNode) {
352
+ // Try class-based extraction (method inside a class/struct/trait body).
353
+ // Raw lookup (no resolveEnclosingOwner) so the method extractor sees
354
+ // the actual container node (e.g. singleton_class) for static detection.
355
+ const methodOwnerNode = seqFindEnclosingOwnerNode(definitionNode);
356
+ if (methodOwnerNode) {
345
357
  // Cache extract() results per class node to avoid re-traversing the
346
358
  // same class body for every method it contains (O(N) -> O(1) per hit).
347
- let result = seqMethodExtractCache.get(classNode.id);
359
+ let result = seqMethodExtractCache.get(methodOwnerNode.id);
348
360
  if (result === undefined) {
349
361
  result =
350
- provider.methodExtractor.extract(classNode, {
362
+ provider.methodExtractor.extract(methodOwnerNode, {
351
363
  filePath: file.path,
352
364
  language,
353
365
  }) ?? null;
354
- seqMethodExtractCache.set(classNode.id, result);
366
+ seqMethodExtractCache.set(methodOwnerNode.id, result);
355
367
  }
356
368
  if (result?.methods?.length) {
357
369
  const defLine = definitionNode.startPosition.row + 1;
@@ -362,7 +374,7 @@ const processParsingSequential = async (graph, files, symbolTable, astCache, onF
362
374
  methodProps = buildMethodProps(info);
363
375
  seqDefMethodInfo = info;
364
376
  seqDefMethods = result.methods;
365
- seqClassNodeId = classNode.id;
377
+ seqClassNodeId = methodOwnerNode.id;
366
378
  }
367
379
  }
368
380
  }
@@ -443,7 +455,7 @@ const processParsingSequential = async (graph, files, symbolTable, astCache, onF
443
455
  if (nodeLabel === 'Property' && definitionNode) {
444
456
  // FieldExtractor is the single source of truth when available
445
457
  if (provider.fieldExtractor && typeEnv) {
446
- const classNode = seqFindEnclosingClassNode(definitionNode);
458
+ const classNode = seqFindEnclosingOwnerNode(definitionNode, provider.resolveEnclosingOwner);
447
459
  if (classNode) {
448
460
  const fieldMap = seqGetFieldInfo(classNode, provider, {
449
461
  typeEnv,
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Phase: cobol
3
+ *
4
+ * Processes COBOL and JCL files via regex extraction (no tree-sitter).
5
+ *
6
+ * @deps structure
7
+ * @reads scannedFiles, allPaths (from structure phase)
8
+ * @writes graph (COBOL program/paragraph/section nodes, JCL job/step nodes)
9
+ */
10
+ import type { PipelinePhase } from './types.js';
11
+ export interface CobolOutput {
12
+ programs: number;
13
+ paragraphs: number;
14
+ sections: number;
15
+ }
16
+ export declare const cobolPhase: PipelinePhase<CobolOutput>;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Phase: cobol
3
+ *
4
+ * Processes COBOL and JCL files via regex extraction (no tree-sitter).
5
+ *
6
+ * @deps structure
7
+ * @reads scannedFiles, allPaths (from structure phase)
8
+ * @writes graph (COBOL program/paragraph/section nodes, JCL job/step nodes)
9
+ */
10
+ import { getPhaseOutput } from './types.js';
11
+ import { processCobol, isCobolFile, isJclFile } from '../cobol-processor.js';
12
+ import { readFileContents } from '../filesystem-walker.js';
13
+ import { isDev } from '../utils/env.js';
14
+ export const cobolPhase = {
15
+ name: 'cobol',
16
+ deps: ['structure'],
17
+ async execute(ctx, deps) {
18
+ const { scannedFiles, allPathSet } = getPhaseOutput(deps, 'structure');
19
+ const cobolScanned = scannedFiles.filter((f) => isCobolFile(f.path) || isJclFile(f.path));
20
+ if (cobolScanned.length === 0) {
21
+ return { programs: 0, paragraphs: 0, sections: 0 };
22
+ }
23
+ const cobolContents = await readFileContents(ctx.repoPath, cobolScanned.map((f) => f.path));
24
+ const cobolFiles = cobolScanned
25
+ .filter((f) => cobolContents.has(f.path))
26
+ .map((f) => ({ path: f.path, content: cobolContents.get(f.path) }));
27
+ const cobolResult = processCobol(ctx.graph, cobolFiles, allPathSet);
28
+ if (isDev) {
29
+ console.log(` COBOL: ${cobolResult.programs} programs, ${cobolResult.paragraphs} paragraphs, ${cobolResult.sections} sections from ${cobolFiles.length} files`);
30
+ if (cobolResult.execSqlBlocks > 0 ||
31
+ cobolResult.execCicsBlocks > 0 ||
32
+ cobolResult.entryPoints > 0) {
33
+ console.log(` COBOL enriched: ${cobolResult.execSqlBlocks} SQL blocks, ${cobolResult.execCicsBlocks} CICS blocks, ${cobolResult.entryPoints} entry points, ${cobolResult.moves} moves, ${cobolResult.fileDeclarations} file declarations`);
34
+ }
35
+ if (cobolResult.jclJobs > 0) {
36
+ console.log(` JCL: ${cobolResult.jclJobs} jobs, ${cobolResult.jclSteps} steps`);
37
+ }
38
+ }
39
+ return {
40
+ programs: cobolResult.programs,
41
+ paragraphs: cobolResult.paragraphs,
42
+ sections: cobolResult.sections,
43
+ };
44
+ },
45
+ };
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Phase: communities
3
+ *
4
+ * Detects code communities via Leiden algorithm and creates
5
+ * Community nodes + MEMBER_OF edges.
6
+ *
7
+ * @deps mro
8
+ * @reads graph (all nodes and relationships)
9
+ * @writes graph (Community nodes, MEMBER_OF edges)
10
+ */
11
+ import type { PipelinePhase } from './types.js';
12
+ import { type CommunityDetectionResult } from '../community-processor.js';
13
+ export interface CommunitiesOutput {
14
+ communityResult: CommunityDetectionResult;
15
+ }
16
+ export declare const communitiesPhase: PipelinePhase<CommunitiesOutput>;