gitnexus 1.6.8-rc.2 → 1.6.8-rc.21

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 (193) hide show
  1. package/README.md +20 -0
  2. package/dist/_shared/scope-resolution/parsed-file.d.ts +21 -0
  3. package/dist/_shared/scope-resolution/parsed-file.d.ts.map +1 -1
  4. package/dist/_shared/scope-resolution/symbol-definition.d.ts +4 -0
  5. package/dist/_shared/scope-resolution/symbol-definition.d.ts.map +1 -1
  6. package/dist/cli/ai-context.js +1 -0
  7. package/dist/cli/analyze-config.js +40 -0
  8. package/dist/cli/analyze.d.ts +22 -0
  9. package/dist/cli/analyze.js +36 -6
  10. package/dist/cli/clean.d.ts +1 -0
  11. package/dist/cli/clean.js +43 -1
  12. package/dist/cli/eval-server.js +43 -0
  13. package/dist/cli/help-i18n.js +11 -0
  14. package/dist/cli/i18n/en.d.ts +13 -0
  15. package/dist/cli/i18n/en.js +13 -0
  16. package/dist/cli/i18n/resources.d.ts +26 -0
  17. package/dist/cli/i18n/zh-CN.d.ts +13 -0
  18. package/dist/cli/i18n/zh-CN.js +13 -0
  19. package/dist/cli/index.js +19 -0
  20. package/dist/cli/list.js +12 -0
  21. package/dist/cli/optional-grammars.d.ts +6 -8
  22. package/dist/cli/optional-grammars.js +8 -11
  23. package/dist/cli/skill-gen.js +1 -0
  24. package/dist/cli/status.js +26 -5
  25. package/dist/cli/tool.d.ts +12 -1
  26. package/dist/cli/tool.js +41 -1
  27. package/dist/core/embeddings/embedder.js +4 -0
  28. package/dist/core/embeddings/embedding-pipeline.js +27 -16
  29. package/dist/core/embeddings/onnxruntime-common-resolver.d.ts +6 -0
  30. package/dist/core/embeddings/onnxruntime-common-resolver.js +130 -0
  31. package/dist/core/graph/import-cycles.d.ts +10 -0
  32. package/dist/core/graph/import-cycles.js +103 -0
  33. package/dist/core/group/extractors/grpc-patterns/proto.js +10 -6
  34. package/dist/core/group/extractors/http-patterns/java.js +3 -50
  35. package/dist/core/group/extractors/http-patterns/kotlin.js +7 -7
  36. package/dist/core/group/extractors/include-extractor.js +7 -7
  37. package/dist/core/ingestion/cfg/cfg-builder.d.ts +64 -0
  38. package/dist/core/ingestion/cfg/cfg-builder.js +129 -0
  39. package/dist/core/ingestion/cfg/collect.d.ts +30 -0
  40. package/dist/core/ingestion/cfg/collect.js +34 -0
  41. package/dist/core/ingestion/cfg/control-flow-context.d.ts +97 -0
  42. package/dist/core/ingestion/cfg/control-flow-context.js +113 -0
  43. package/dist/core/ingestion/cfg/emit.d.ts +144 -0
  44. package/dist/core/ingestion/cfg/emit.js +315 -0
  45. package/dist/core/ingestion/cfg/reaching-defs.d.ts +90 -0
  46. package/dist/core/ingestion/cfg/reaching-defs.js +364 -0
  47. package/dist/core/ingestion/cfg/traversal-result.d.ts +20 -0
  48. package/dist/core/ingestion/cfg/traversal-result.js +2 -0
  49. package/dist/core/ingestion/cfg/types.d.ts +231 -0
  50. package/dist/core/ingestion/cfg/types.js +13 -0
  51. package/dist/core/ingestion/cfg/visitors/typescript-harvest.d.ts +144 -0
  52. package/dist/core/ingestion/cfg/visitors/typescript-harvest.js +1032 -0
  53. package/dist/core/ingestion/cfg/visitors/typescript.d.ts +66 -0
  54. package/dist/core/ingestion/cfg/visitors/typescript.js +584 -0
  55. package/dist/core/ingestion/language-provider.d.ts +36 -2
  56. package/dist/core/ingestion/languages/c-cpp.js +11 -4
  57. package/dist/core/ingestion/languages/cpp/arity-metadata.js +6 -2
  58. package/dist/core/ingestion/languages/cpp/captures.js +24 -1
  59. package/dist/core/ingestion/languages/cpp/query.js +23 -0
  60. package/dist/core/ingestion/languages/java.js +3 -0
  61. package/dist/core/ingestion/languages/kotlin/query.js +3 -2
  62. package/dist/core/ingestion/languages/kotlin.js +5 -1
  63. package/dist/core/ingestion/languages/typescript.js +5 -0
  64. package/dist/core/ingestion/method-extractors/configs/c-cpp.js +6 -13
  65. package/dist/core/ingestion/method-extractors/generic.js +1 -0
  66. package/dist/core/ingestion/method-types.d.ts +2 -0
  67. package/dist/core/ingestion/model/symbol-table.d.ts +1 -0
  68. package/dist/core/ingestion/model/symbol-table.js +1 -0
  69. package/dist/core/ingestion/parsing-processor.js +22 -0
  70. package/dist/core/ingestion/pipeline-phases/parse-impl.js +18 -1
  71. package/dist/core/ingestion/pipeline-phases/routes.js +64 -14
  72. package/dist/core/ingestion/pipeline.d.ts +57 -0
  73. package/dist/core/ingestion/route-extractors/spring-shared.d.ts +50 -0
  74. package/dist/core/ingestion/route-extractors/spring-shared.js +80 -0
  75. package/dist/core/ingestion/route-extractors/spring.d.ts +35 -0
  76. package/dist/core/ingestion/route-extractors/spring.js +136 -0
  77. package/dist/core/ingestion/scope-extractor.js +3 -0
  78. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +12 -0
  79. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +145 -24
  80. package/dist/core/ingestion/scope-resolution/pipeline/phase.js +6 -0
  81. package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.js +43 -3
  82. package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +21 -0
  83. package/dist/core/ingestion/scope-resolution/pipeline/run.js +207 -0
  84. package/dist/core/ingestion/scope-resolution/resolution-outcome.d.ts +1 -1
  85. package/dist/core/ingestion/taint/emit.d.ts +124 -0
  86. package/dist/core/ingestion/taint/emit.js +204 -0
  87. package/dist/core/ingestion/taint/match.d.ts +153 -0
  88. package/dist/core/ingestion/taint/match.js +278 -0
  89. package/dist/core/ingestion/taint/path-codec.d.ts +134 -0
  90. package/dist/core/ingestion/taint/path-codec.js +190 -0
  91. package/dist/core/ingestion/taint/propagate.d.ts +216 -0
  92. package/dist/core/ingestion/taint/propagate.js +664 -0
  93. package/dist/core/ingestion/taint/site-safety.d.ts +29 -0
  94. package/dist/core/ingestion/taint/site-safety.js +98 -0
  95. package/dist/core/ingestion/taint/source-sink-config.d.ts +94 -23
  96. package/dist/core/ingestion/taint/source-sink-config.js +11 -11
  97. package/dist/core/ingestion/taint/source-sink-registry.d.ts +6 -4
  98. package/dist/core/ingestion/taint/source-sink-registry.js +6 -4
  99. package/dist/core/ingestion/taint/typescript-model.d.ts +38 -0
  100. package/dist/core/ingestion/taint/typescript-model.js +102 -0
  101. package/dist/core/ingestion/utils/method-props.js +1 -0
  102. package/dist/core/ingestion/workers/clone-safety.d.ts +109 -0
  103. package/dist/core/ingestion/workers/clone-safety.js +465 -0
  104. package/dist/core/ingestion/workers/parse-worker.d.ts +11 -0
  105. package/dist/core/ingestion/workers/parse-worker.js +70 -53
  106. package/dist/core/ingestion/workers/post-result.d.ts +22 -0
  107. package/dist/core/ingestion/workers/post-result.js +87 -0
  108. package/dist/core/ingestion/workers/result-merge.d.ts +20 -0
  109. package/dist/core/ingestion/workers/result-merge.js +43 -0
  110. package/dist/core/ingestion/workers/worker-pool.d.ts +15 -0
  111. package/dist/core/ingestion/workers/worker-pool.js +33 -16
  112. package/dist/core/lbug/lbug-adapter.d.ts +19 -0
  113. package/dist/core/lbug/lbug-adapter.js +56 -1
  114. package/dist/core/run-analyze.d.ts +87 -0
  115. package/dist/core/run-analyze.js +280 -25
  116. package/dist/core/tree-sitter/parser-loader.js +5 -4
  117. package/dist/core/tree-sitter/vendored-grammars.d.ts +39 -0
  118. package/dist/core/tree-sitter/vendored-grammars.js +57 -0
  119. package/dist/mcp/core/embedder.js +4 -0
  120. package/dist/mcp/local/local-backend.d.ts +86 -2
  121. package/dist/mcp/local/local-backend.js +657 -27
  122. package/dist/mcp/resources.js +1 -0
  123. package/dist/mcp/tools.d.ts +9 -0
  124. package/dist/mcp/tools.js +109 -0
  125. package/dist/server/analyze-launch.d.ts +29 -0
  126. package/dist/server/analyze-launch.js +137 -0
  127. package/dist/server/analyze-upload.d.ts +33 -0
  128. package/dist/server/analyze-upload.js +123 -0
  129. package/dist/server/analyze-worker-ipc.d.ts +58 -0
  130. package/dist/server/analyze-worker-ipc.js +16 -0
  131. package/dist/server/analyze-worker.d.ts +17 -1
  132. package/dist/server/analyze-worker.js +7 -1
  133. package/dist/server/api.js +46 -140
  134. package/dist/server/git-clone.d.ts +1 -0
  135. package/dist/server/git-clone.js +1 -1
  136. package/dist/server/middleware.d.ts +11 -0
  137. package/dist/server/middleware.js +27 -0
  138. package/dist/server/upload-ingest.d.ts +56 -0
  139. package/dist/server/upload-ingest.js +276 -0
  140. package/dist/server/upload-paths.d.ts +31 -0
  141. package/dist/server/upload-paths.js +51 -0
  142. package/dist/server/upload-sweep.d.ts +20 -0
  143. package/dist/server/upload-sweep.js +57 -0
  144. package/dist/storage/branch-index.d.ts +52 -0
  145. package/dist/storage/branch-index.js +65 -0
  146. package/dist/storage/git.d.ts +11 -0
  147. package/dist/storage/git.js +28 -0
  148. package/dist/storage/parse-cache.d.ts +22 -1
  149. package/dist/storage/parse-cache.js +32 -10
  150. package/dist/storage/repo-manager.d.ts +122 -10
  151. package/dist/storage/repo-manager.js +162 -21
  152. package/hooks/antigravity/gitnexus-antigravity-hook.cjs +42 -8
  153. package/hooks/claude/gitnexus-hook.cjs +36 -8
  154. package/hooks/claude/hook-db-lock-probe.cjs +123 -2
  155. package/package.json +4 -2
  156. package/scripts/assert-publish-grammar-coverage.cjs +31 -0
  157. package/scripts/build-tree-sitter-grammars.cjs +16 -10
  158. package/skills/gitnexus-guide.md +11 -0
  159. package/vendor/tree-sitter-c/package.json +1 -1
  160. package/vendor/tree-sitter-dart/package.json +1 -1
  161. package/vendor/tree-sitter-kotlin/package.json +1 -1
  162. package/vendor/tree-sitter-proto/package.json +1 -1
  163. package/vendor/tree-sitter-swift/package.json +1 -1
  164. package/web/assets/{agent-CKPMqImC.js → agent-ay4LD70X.js} +1 -1
  165. package/web/assets/{architectureDiagram-UL44E2DR-D1EXI0zA.js → architectureDiagram-UL44E2DR-Dc-viYhd.js} +1 -1
  166. package/web/assets/{chunk-LCXTWHL2-Dfmux4m1.js → chunk-LCXTWHL2-4rpojOyj.js} +1 -1
  167. package/web/assets/{chunk-RG4AUYOV-CAkzcoRj.js → chunk-RG4AUYOV-BnOy944n.js} +1 -1
  168. package/web/assets/{classDiagram-KGZ6W3CR-Bv93af_b.js → classDiagram-KGZ6W3CR-Bsgpy98Q.js} +1 -1
  169. package/web/assets/{classDiagram-v2-72OJOZXJ-CLfEqHUa.js → classDiagram-v2-72OJOZXJ-D5atDGjc.js} +1 -1
  170. package/web/assets/{diagram-3NCE3AQN-BqAtKUpW.js → diagram-3NCE3AQN-Cz1OEMVi.js} +1 -1
  171. package/web/assets/{diagram-GF46GFSD-CaBG6n6o.js → diagram-GF46GFSD-CWYwzfP8.js} +1 -1
  172. package/web/assets/{diagram-QXG6HAR7-CZ-O3rcV.js → diagram-QXG6HAR7-DtBObr8L.js} +1 -1
  173. package/web/assets/{diagram-WEQXMOUZ-P4lSL4GH.js → diagram-WEQXMOUZ-BUV44Ov_.js} +1 -1
  174. package/web/assets/{erDiagram-L5TCEMPS-QN2eEP1e.js → erDiagram-L5TCEMPS-Ds9s-sRF.js} +1 -1
  175. package/web/assets/{flowDiagram-H6V6AXG4-CXbXImlN.js → flowDiagram-H6V6AXG4-CupStHQb.js} +1 -1
  176. package/web/assets/index-COMMmbxW.css +2 -0
  177. package/web/assets/{index-CG6q8eTs.js → index-Do8AE5yF.js} +85 -85
  178. package/web/assets/{infoDiagram-3YFTVSEB-DmLICZx1.js → infoDiagram-3YFTVSEB-DPTXSbgu.js} +1 -1
  179. package/web/assets/{ishikawaDiagram-BNXS4ZKH-6LKOvBfp.js → ishikawaDiagram-BNXS4ZKH-CeK-Qhet.js} +1 -1
  180. package/web/assets/{kanban-definition-75IXJCU3-DgDi9oJT.js → kanban-definition-75IXJCU3-D4tx6QQ-.js} +1 -1
  181. package/web/assets/{mindmap-definition-2TDM6QVE-CwR5sBB-.js → mindmap-definition-2TDM6QVE-Bcto_kGa.js} +1 -1
  182. package/web/assets/{pieDiagram-CU6KROY3-By8g6f6B.js → pieDiagram-CU6KROY3-DPIeBTG6.js} +1 -1
  183. package/web/assets/{requirementDiagram-JXO7QTGE-7oDcJ1_J.js → requirementDiagram-JXO7QTGE-DDGXjVKO.js} +1 -1
  184. package/web/assets/{sequenceDiagram-VS2MUI6T-FySeKCUy.js → sequenceDiagram-VS2MUI6T-BzDqpcVW.js} +1 -1
  185. package/web/assets/{stateDiagram-7D4R322I-CB2nABwH.js → stateDiagram-7D4R322I-Bx__als3.js} +1 -1
  186. package/web/assets/{stateDiagram-v2-36443NZ5-COBGd2RL.js → stateDiagram-v2-36443NZ5-QqOh2yO-.js} +1 -1
  187. package/web/assets/{timeline-definition-O6YCAMPW-Ds2CnVZK.js → timeline-definition-O6YCAMPW-C1eogTOG.js} +1 -1
  188. package/web/assets/{vennDiagram-MWXL3ELB-DUIEwXWp.js → vennDiagram-MWXL3ELB-D20F4rSW.js} +1 -1
  189. package/web/assets/{wardleyDiagram-CUQ6CDDI-DEiFPQih.js → wardleyDiagram-CUQ6CDDI-BeBHg7ST.js} +1 -1
  190. package/web/assets/{xychartDiagram-N2JHSOCM-BUbayhST.js → xychartDiagram-N2JHSOCM-CdOWeoNI.js} +1 -1
  191. package/web/index.html +2 -2
  192. package/scripts/materialize-vendor-grammars.cjs +0 -97
  193. package/web/assets/index-BKWA-m7o.css +0 -2
@@ -0,0 +1,315 @@
1
+ import { generateId } from '../../../lib/utils.js';
2
+ import { computeReachingDefs } from './reaching-defs.js';
3
+ /**
4
+ * Default per-function CFG edge cap. A pathological generated function could
5
+ * otherwise emit an unbounded edge set; the cap bounds graph growth and is
6
+ * overridable via `--pdg` options. `0` (in options) means no cap (unlimited
7
+ * — see the `cap` mapping in {@link emitFileCfgs}); `undefined` means this
8
+ * default.
9
+ */
10
+ export const DEFAULT_MAX_CFG_EDGES_PER_FUNCTION = 5000;
11
+ /**
12
+ * Default per-function REACHING_DEF edge cap (#2082 M2 KTD9). 4000 mirrors
13
+ * Joern's per-method `maxNumberOfDefinitions` — the closest production prior
14
+ * art — but truncates-and-warns instead of silently skipping the function.
15
+ * Counts (defBlock, useBlock, binding) DEDUPED edges, not statement-level
16
+ * facts. `0` ⇒ unlimited; `undefined` ⇒ this default.
17
+ */
18
+ export const DEFAULT_PDG_MAX_REACHING_DEF_EDGES_PER_FUNCTION = 4000;
19
+ /**
20
+ * Fact-materialization headroom over the edge cap (#2082 M2 U3/F3): facts are
21
+ * O(defs×uses) BY SPEC in merge-heavy code, and the edge cap alone bounds the
22
+ * GRAPH, not the per-function memory spike of materializing facts before
23
+ * dedup. {@link emitFileReachingDefs} hands `edgeCap × this` to
24
+ * `computeReachingDefs` as `maxFacts` (unlimited when the edge cap is 0) —
25
+ * single source of truth; the DEFAULT constant below is derived, never the
26
+ * mechanism.
27
+ */
28
+ export const REACHING_DEF_FACTS_PER_EDGE_CAP = 4;
29
+ /** Derived emit-path fact limit at the default edge cap (bench/doc anchor). */
30
+ export const DEFAULT_PDG_MAX_REACHING_DEF_FACTS_PER_FUNCTION = REACHING_DEF_FACTS_PER_EDGE_CAP * DEFAULT_PDG_MAX_REACHING_DEF_EDGES_PER_FUNCTION;
31
+ /**
32
+ * The single BasicBlock id template (module doc). Exported for the M3 taint
33
+ * emit path (taint/emit.ts), whose TAINTED/SANITIZES edges must address the
34
+ * SAME persisted block nodes — a re-derived copy of this template would
35
+ * silently dangle the moment either drifted.
36
+ */
37
+ export const basicBlockId = (filePath, functionStartLine, functionStartColumn, blockIndex) => `BasicBlock:${filePath}:${functionStartLine}:${functionStartColumn}:${blockIndex}`;
38
+ /**
39
+ * Whether an untrusted `cfgSideChannel` element is safe to feed to
40
+ * {@link emitFileCfgs}. Deliberately NOT full FunctionCfg validation — it
41
+ * checks exactly the fields whose corruption is SILENT given emit's
42
+ * mechanics: {@link basicBlockId} string-templates every id-anchor value
43
+ * (filePath, function start line/column, block index, edge endpoints) and
44
+ * the graph's addNode/addRelationship are no-throw Map inserts. Unchecked,
45
+ * a missing anchor field cross-wires same-`undefined`-id blocks across
46
+ * functions (addNode is first-writer-wins), and an edge endpoint that
47
+ * matches no block index becomes a dangling `BasicBlock:…:<n>` edge that
48
+ * detonates much later at DB bulk-load instead of throwing here — so
49
+ * endpoints are checked for MEMBERSHIP in the block-index set, not just
50
+ * integer-ness. Lives in this module so the guard evolves with the id
51
+ * templating it defends (#2099 F4; M2 fields that join the id path must
52
+ * join this check).
53
+ */
54
+ export const isEmitSafeCfg = (cfg) => {
55
+ if (typeof cfg?.filePath !== 'string' ||
56
+ !Number.isInteger(cfg.functionStartLine) ||
57
+ !Number.isInteger(cfg.functionStartColumn) ||
58
+ !Array.isArray(cfg.blocks) ||
59
+ !Array.isArray(cfg.edges)) {
60
+ return false;
61
+ }
62
+ // Contiguity (index === position), not just integer-ness: every consumer —
63
+ // this module's id templating AND the reaching-defs solver's
64
+ // position-indexed adjacency arrays — assumes blocks[i].index === i. A
65
+ // membership-only check would admit a compacted channel ({index:0},{index:5})
66
+ // whose edge 0→5 passes membership but indexes past the arrays downstream.
67
+ for (let i = 0; i < cfg.blocks.length; i++) {
68
+ if (cfg.blocks[i]?.index !== i)
69
+ return false;
70
+ }
71
+ const n = cfg.blocks.length;
72
+ // entry/exit must land on real blocks — the solver feeds entryIndex straight
73
+ // into its RPO walk, where an out-of-range index throws and (worse than this
74
+ // one element) costs the whole FILE's REACHING_DEF pass (tri-review P3).
75
+ if (!Number.isInteger(cfg.entryIndex) ||
76
+ cfg.entryIndex < 0 ||
77
+ cfg.entryIndex >= n ||
78
+ !Number.isInteger(cfg.exitIndex) ||
79
+ cfg.exitIndex < 0 ||
80
+ cfg.exitIndex >= n) {
81
+ return false;
82
+ }
83
+ return cfg.edges.every((e) => Number.isInteger(e?.from) &&
84
+ Number.isInteger(e?.to) &&
85
+ e.from >= 0 &&
86
+ e.from < n &&
87
+ e.to >= 0 &&
88
+ e.to < n);
89
+ };
90
+ /**
91
+ * Whether a structurally-valid CFG's M2 statement facts are safe to feed to
92
+ * the reaching-defs solver + REACHING_DEF id templating (#2082 U1/U4): the
93
+ * binding table's name/declLine/declColumn template into edge ids, and
94
+ * statement def/use indices must stay IN RANGE of the table (an escaping
95
+ * index would fabricate `undefined`-keyed ids). Deliberately SEPARATE from
96
+ * {@link isEmitSafeCfg}: malformed facts must cost only the function's
97
+ * REACHING_DEF projection — degrading to M1 behavior (CFG emitted, no facts)
98
+ * — never the BasicBlock/CFG layer itself.
99
+ */
100
+ export const hasEmitSafeFacts = (cfg) => {
101
+ const bindings = cfg.bindings;
102
+ if (bindings === undefined) {
103
+ // Pre-M2 channel — statements must be absent too.
104
+ return cfg.blocks.every((b) => b.statements === undefined);
105
+ }
106
+ if (!Array.isArray(bindings))
107
+ return false;
108
+ for (const b of bindings) {
109
+ if (typeof b?.name !== 'string' ||
110
+ !Number.isInteger(b.declLine) ||
111
+ !Number.isInteger(b.declColumn)) {
112
+ return false;
113
+ }
114
+ }
115
+ const bindingCount = bindings.length;
116
+ const inRange = (i) => Number.isInteger(i) && i >= 0 && i < bindingCount;
117
+ for (const b of cfg.blocks) {
118
+ const stmts = b.statements;
119
+ if (stmts === undefined)
120
+ continue;
121
+ if (!Array.isArray(stmts))
122
+ return false;
123
+ for (const s of stmts) {
124
+ if (!Number.isInteger(s?.line) || !Array.isArray(s.defs) || !Array.isArray(s.uses)) {
125
+ return false;
126
+ }
127
+ if (!s.defs.every(inRange) || !s.uses.every(inRange))
128
+ return false;
129
+ if (s.mayDefs !== undefined) {
130
+ if (!Array.isArray(s.mayDefs) || !s.mayDefs.every(inRange))
131
+ return false;
132
+ }
133
+ }
134
+ }
135
+ return true;
136
+ };
137
+ /**
138
+ * Emit BasicBlock nodes + CFG edges for every function CFG in `cfgs`.
139
+ *
140
+ * `maxEdgesPerFunction` caps edges per function. On overflow we stop emitting
141
+ * that function's remaining edges and call `onWarn` naming the dropped count —
142
+ * no silent truncation (KTD6/R6). Block nodes are always fully emitted (their
143
+ * count is bounded by the function's statement count); only edges are capped.
144
+ */
145
+ export function emitFileCfgs(graph, cfgs, maxEdgesPerFunction = DEFAULT_MAX_CFG_EDGES_PER_FUNCTION, onWarn) {
146
+ const result = { blocks: 0, edges: 0, droppedEdges: 0, cappedFunctions: 0 };
147
+ const cap = maxEdgesPerFunction > 0 ? maxEdgesPerFunction : Infinity;
148
+ for (const cfg of cfgs) {
149
+ const { filePath, functionStartLine, functionStartColumn } = cfg;
150
+ for (const b of cfg.blocks) {
151
+ graph.addNode({
152
+ id: basicBlockId(filePath, functionStartLine, functionStartColumn, b.index),
153
+ label: 'BasicBlock',
154
+ properties: {
155
+ name: '', // BasicBlock has no name column; identified by id + span
156
+ filePath,
157
+ startLine: b.startLine,
158
+ endLine: b.endLine,
159
+ text: b.text,
160
+ },
161
+ });
162
+ result.blocks++;
163
+ }
164
+ let emittedForFn = 0;
165
+ for (const e of cfg.edges) {
166
+ if (emittedForFn >= cap) {
167
+ const dropped = cfg.edges.length - emittedForFn;
168
+ result.droppedEdges += dropped;
169
+ result.cappedFunctions++;
170
+ onWarn?.(`[cfg] ${filePath}:${functionStartLine}: per-function CFG edge cap ` +
171
+ `(${maxEdgesPerFunction}) reached — dropped ${dropped} of ${cfg.edges.length} edges`);
172
+ break;
173
+ }
174
+ const sourceId = basicBlockId(filePath, functionStartLine, functionStartColumn, e.from);
175
+ const targetId = basicBlockId(filePath, functionStartLine, functionStartColumn, e.to);
176
+ graph.addRelationship({
177
+ id: generateId('CFG', `${sourceId}->${targetId}:${e.kind}`),
178
+ type: 'CFG',
179
+ sourceId,
180
+ targetId,
181
+ confidence: 1.0,
182
+ reason: e.kind, // CfgEdgeKind (seq/cond-true/loop-back/…) — queryable
183
+ });
184
+ result.edges++;
185
+ emittedForFn++;
186
+ }
187
+ }
188
+ return result;
189
+ }
190
+ /**
191
+ * Stable identity for a binding inside edge ids (#2082 M2 KTD3/KTD9):
192
+ * `name:declLine:declCol` for declared bindings, `name@module` for synthetic
193
+ * ones. Distinct same-name bindings never share a key; identifier characters
194
+ * cannot contain the id separators. Exported for the M3 taint emit path —
195
+ * TAINTED/SANITIZES ids key bindings with the same discipline.
196
+ */
197
+ export const bindingKey = (b) => b.synthetic ? `${b.name}@module` : `${b.name}:${b.declLine}:${b.declColumn}`;
198
+ /**
199
+ * Compute reaching definitions per function and persist the bounded
200
+ * REACHING_DEF projection (#2082 M2 U4).
201
+ *
202
+ * Facts are DEDUPED to (defBlock, useBlock, binding) before budgeting — the
203
+ * persisted columns (`from,to,type,confidence,reason,step`; relationship ids
204
+ * are in-memory-only, the CodeRelation table has no id column) cannot
205
+ * distinguish finer rows, so statement-indexed ids would only manufacture
206
+ * byte-identical duplicate rows that burn budget. Statement granularity lives
207
+ * in the in-memory {@link computeReachingDefs} result, which the M3 taint
208
+ * engine recomputes on demand — the budget here governs only this projection
209
+ * and can never drop a taint fact.
210
+ *
211
+ * R7 (no silent truncation) covers BOTH layers: the per-function edge cap AND
212
+ * the solver's fact-materialization limit (which can fire without the edge
213
+ * cap ever being reached, since dedup is many-to-one) each produce one
214
+ * unconditional `onWarn`. The edge-cap warn names the top bindings by fact
215
+ * count — overflow is almost always one variable, which is exactly the datum
216
+ * M3 tuning wants.
217
+ */
218
+ export function emitFileReachingDefs(graph, cfgs, maxEdgesPerFunction = DEFAULT_PDG_MAX_REACHING_DEF_EDGES_PER_FUNCTION, onWarn) {
219
+ const result = {
220
+ edges: 0,
221
+ droppedEdges: 0,
222
+ cappedFunctions: 0,
223
+ truncatedFunctions: 0,
224
+ malformedFactFunctions: 0,
225
+ facts: 0,
226
+ };
227
+ const cap = maxEdgesPerFunction > 0 ? maxEdgesPerFunction : Infinity;
228
+ const maxFacts = Number.isFinite(cap) ? cap * REACHING_DEF_FACTS_PER_EDGE_CAP : 0; // 0 ⇒ unlimited
229
+ for (const cfg of cfgs) {
230
+ // Graceful degradation: malformed M2 facts cost only this function's
231
+ // REACHING_DEF projection — its BasicBlock/CFG layer was already emitted.
232
+ if (!hasEmitSafeFacts(cfg)) {
233
+ result.malformedFactFunctions++;
234
+ onWarn?.(`[reaching-defs] ${cfg.filePath}:${cfg.functionStartLine}: malformed ` +
235
+ `statement facts (bad binding table or out-of-range fact indices) — ` +
236
+ `REACHING_DEF skipped for this function; its CFG is unaffected`);
237
+ continue;
238
+ }
239
+ const r = computeReachingDefs(cfg, { maxFacts });
240
+ if (r.status === 'no-facts')
241
+ continue;
242
+ result.facts += r.facts.length;
243
+ const { filePath, functionStartLine, functionStartColumn } = cfg;
244
+ if (r.status === 'truncated') {
245
+ result.truncatedFunctions++;
246
+ onWarn?.(`[reaching-defs] ${filePath}:${functionStartLine}: fact materialization ` +
247
+ `limit (${maxFacts}) reached — facts beyond it were not computed; ` +
248
+ `the persisted REACHING_DEF projection for this function is sparse`);
249
+ }
250
+ else if (r.status === 'overflow') {
251
+ result.truncatedFunctions++;
252
+ onWarn?.(`[reaching-defs] ${filePath}:${functionStartLine}: a basic block exceeds ` +
253
+ `the def-key stride (≥2^21 coalesced statements — minified/generated ` +
254
+ `code) — REACHING_DEF skipped for this function (computing any facts ` +
255
+ `would risk wrong-block aliasing); its CFG is unaffected`);
256
+ continue;
257
+ }
258
+ // Dedup to (defBlock, useBlock, binding) — facts arrive sorted, so the
259
+ // deduped order (and therefore cap truncation) is deterministic.
260
+ const seen = new Set();
261
+ const deduped = [];
262
+ for (const f of r.facts) {
263
+ const key = `${f.def.blockIndex}:${f.use.blockIndex}:${f.bindingIdx}`;
264
+ if (seen.has(key))
265
+ continue;
266
+ seen.add(key);
267
+ deduped.push({
268
+ defBlock: f.def.blockIndex,
269
+ useBlock: f.use.blockIndex,
270
+ bindingIdx: f.bindingIdx,
271
+ });
272
+ }
273
+ let emittedForFn = 0;
274
+ for (const edge of deduped) {
275
+ if (emittedForFn >= cap) {
276
+ const dropped = deduped.length - emittedForFn;
277
+ result.droppedEdges += dropped;
278
+ result.cappedFunctions++;
279
+ // Tallied lazily — cap overflow is the rare path; the common uncapped
280
+ // case must not pay a per-fact counting pass.
281
+ const factsPerBinding = new Map();
282
+ for (const f of r.facts) {
283
+ factsPerBinding.set(f.bindingIdx, (factsPerBinding.get(f.bindingIdx) ?? 0) + 1);
284
+ }
285
+ const top = [...factsPerBinding.entries()]
286
+ .sort((a, b) => b[1] - a[1] || a[0] - b[0])
287
+ .slice(0, 2)
288
+ .map(([idx, count]) => `${r.bindings[idx]?.name ?? `#${idx}`}(${count} facts)`)
289
+ .join(', ');
290
+ onWarn?.(`[reaching-defs] ${filePath}:${functionStartLine}: per-function ` +
291
+ `REACHING_DEF edge cap (${maxEdgesPerFunction}) reached — dropped ` +
292
+ `${dropped} of ${deduped.length} edges; top bindings: ${top}`);
293
+ break;
294
+ }
295
+ const binding = r.bindings[edge.bindingIdx];
296
+ const sourceId = basicBlockId(filePath, functionStartLine, functionStartColumn, edge.defBlock);
297
+ const targetId = basicBlockId(filePath, functionStartLine, functionStartColumn, edge.useBlock);
298
+ graph.addRelationship({
299
+ // Single function anchor — the two block ids share it, so templating
300
+ // it once halves the id size (ids are in-memory-only but ~4000 of
301
+ // them per capped function is real transient heap).
302
+ id: generateId('REACHING_DEF', `${filePath}:${functionStartLine}:${functionStartColumn}:` +
303
+ `${edge.defBlock}->${edge.useBlock}:${bindingKey(binding)}`),
304
+ type: 'REACHING_DEF',
305
+ sourceId,
306
+ targetId,
307
+ confidence: 1.0,
308
+ reason: binding.name, // plain source-level name (M0/S1 verdict) — queryable
309
+ });
310
+ result.edges++;
311
+ emittedForFn++;
312
+ }
313
+ }
314
+ return result;
315
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Reaching definitions (#2082 M2 U3) — classic GEN/KILL monotone fixpoint over
3
+ * one function's CFG, plus the canonical intra-block statement sweep that
4
+ * recovers statement-granular def→use facts from M1's coalesced blocks
5
+ * WITHOUT re-splitting the CFG.
6
+ *
7
+ * PURE AND DETERMINISTIC (load-bearing contract):
8
+ * - Pure function of its inputs — no graph, no logger (warnings are the
9
+ * caller's job), importable outside the worker. The M3 taint engine calls
10
+ * this same function in-phase (facts are recomputed on demand, never
11
+ * retained run-wide — the persisted REACHING_DEF edges are a bounded
12
+ * projection, never the taint substrate).
13
+ * - Deterministic — predecessors merge in sorted block-index order,
14
+ * insertion-ordered Maps/Sets throughout, and the output fact array is
15
+ * explicitly sorted. Snapshot tests and content-derived edge ids rely on it.
16
+ *
17
+ * COMPLEXITY DISCIPLINE (the four-times-repeated repo bug shape is per-item
18
+ * re-derivation inside the loop): def-sets are SHARED BY REFERENCE, never
19
+ * deep-copied — a MUST def's kill is total per binding, so a transfer either
20
+ * aliases the incoming set or replaces it; a MAY def (conditional context —
21
+ * see StatementFacts.mayDefs) unions WITHOUT killing via a copy-on-extend.
22
+ * Single-predecessor blocks alias the predecessor's OUT map outright;
23
+ * multi-pred merges union only bindings whose incoming sets differ by
24
+ * reference. Iteration is reverse post-order, seeded with every block
25
+ * (unreachable blocks keep ⊥ IN — correct, their defs reach nothing).
26
+ * Convergence: sets grow monotonically within the finite def-site universe ⇒
27
+ * ≤ loop-depth+1 passes in practice.
28
+ *
29
+ * `limits.maxFacts` bounds materialization: facts are O(defs×uses) BY SPEC in
30
+ * merge-heavy code (N branch-arm defs × N later uses = N² facts), and a
31
+ * 2000-line function can spike 100k+ fact objects on the main thread. The
32
+ * emit path passes DEFAULT_PDG_MAX_REACHING_DEF_FACTS_PER_FUNCTION (emit.ts);
33
+ * M3 passes its own large-but-finite limit and treats `status: 'truncated'`
34
+ * as a per-function taint-coverage gap.
35
+ */
36
+ import type { BindingEntry, FunctionCfg } from './types.js';
37
+ /** A statement-granular program point within one function's CFG. */
38
+ export interface ProgramPoint {
39
+ readonly blockIndex: number;
40
+ /** Statement index within the block's `statements` array. */
41
+ readonly stmtIndex: number;
42
+ readonly line: number;
43
+ }
44
+ /**
45
+ * Canonical `block:stmt` string key for a program point. Colon-separated to
46
+ * match the codebase's `blockIndex:stmtIndex` id conventions. Shared by the
47
+ * taint propagation engine (dedup/state keys) and the taint emit path
48
+ * (persisted edge-id material) so the two never drift.
49
+ */
50
+ export declare function pointKey(p: ProgramPoint): string;
51
+ /** One def→use fact: the definition at `def` reaches the use at `use`. */
52
+ export interface DefUseFact {
53
+ /** Index into {@link FunctionDefUse.bindings}. */
54
+ readonly bindingIdx: number;
55
+ readonly def: ProgramPoint;
56
+ readonly use: ProgramPoint;
57
+ }
58
+ export interface ReachingDefsLimits {
59
+ /**
60
+ * Maximum number of facts to materialize; the sweep stops early and reports
61
+ * `status: 'truncated'`. `undefined`/0 ⇒ unlimited.
62
+ */
63
+ readonly maxFacts?: number;
64
+ }
65
+ export interface FunctionDefUse {
66
+ /**
67
+ * `computed` — full facts.
68
+ * `no-facts` — the CFG carries no statement facts (hand-built or pre-M2
69
+ * side channel); empty facts, NOT an error.
70
+ * `truncated` — `limits.maxFacts` hit; `facts` is a deterministic prefix.
71
+ * `overflow` — a block's statement count breaches the def-key stride; no
72
+ * facts at all (computing any would risk key aliasing —
73
+ * wrong-block facts are strictly worse than none). Distinct
74
+ * from `truncated` so the caller's diagnostic doesn't
75
+ * misname it as the fact-materialization limit.
76
+ */
77
+ readonly status: 'computed' | 'no-facts' | 'truncated' | 'overflow';
78
+ /** Pass-through of the CFG's binding table (empty for `no-facts`). */
79
+ readonly bindings: readonly BindingEntry[];
80
+ /** Sorted by (def block, def stmt, use block, use stmt, binding). */
81
+ readonly facts: readonly DefUseFact[];
82
+ /** Total def / use sites seen (telemetry; independent of truncation). */
83
+ readonly defCount: number;
84
+ readonly useCount: number;
85
+ }
86
+ /**
87
+ * Compute reaching definitions for one function. See the module doc for the
88
+ * purity/determinism/sharing contract.
89
+ */
90
+ export declare function computeReachingDefs(cfg: FunctionCfg, limits?: ReachingDefsLimits): FunctionDefUse;