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,129 @@
1
+ export class CfgBuilder {
2
+ filePath;
3
+ functionStartLine;
4
+ functionEndLine;
5
+ functionStartColumn;
6
+ blocks = [];
7
+ edges = [];
8
+ edgeKeys = new Set();
9
+ entryIndex;
10
+ exitIndex;
11
+ constructor(filePath, functionStartLine, functionEndLine,
12
+ /** Start column of the owning function — disambiguates same-line functions
13
+ * in the BasicBlock ids (see {@link FunctionCfg.functionStartColumn}).
14
+ * Defaults to 0 for hand-built test CFGs that don't model columns. */
15
+ functionStartColumn = 0) {
16
+ this.filePath = filePath;
17
+ this.functionStartLine = functionStartLine;
18
+ this.functionEndLine = functionEndLine;
19
+ this.functionStartColumn = functionStartColumn;
20
+ this.entryIndex = this.newBlock(functionStartLine, functionStartLine, '', 'entry');
21
+ this.exitIndex = this.newBlock(functionEndLine, functionEndLine, '', 'exit');
22
+ }
23
+ /** Create a block and return its index. */
24
+ newBlock(startLine, endLine, text, kind = 'normal', facts) {
25
+ this.blocks.push({
26
+ startLine,
27
+ endLine,
28
+ textParts: text ? [text] : [],
29
+ kind,
30
+ statements: facts ? [facts] : [],
31
+ });
32
+ return this.blocks.length - 1;
33
+ }
34
+ /** Add a single edge (idempotent on from+to+kind). */
35
+ edge(from, to, kind) {
36
+ const key = `${from}->${to}:${kind}`;
37
+ if (this.edgeKeys.has(key))
38
+ return;
39
+ this.edgeKeys.add(key);
40
+ this.edges.push({ from, to, kind });
41
+ }
42
+ /** Wire a set of dangling exits to a single target block with one kind. */
43
+ connect(exits, to, kind = 'seq') {
44
+ for (const from of exits)
45
+ this.edge(from, to, kind);
46
+ }
47
+ /** Extend a block's end line as more statements accrue to it. */
48
+ extendBlock(index, endLine, appendText, facts) {
49
+ const b = this.blocks[index];
50
+ if (!b)
51
+ return;
52
+ if (endLine > b.endLine)
53
+ b.endLine = endLine;
54
+ if (appendText)
55
+ b.textParts.push(appendText);
56
+ if (facts)
57
+ b.statements.push(facts);
58
+ }
59
+ /**
60
+ * Attach a facts-only statement record to a block WITHOUT touching its text
61
+ * or line span (#2082 M2 U1) — bench fingerprints and CFG snapshots include
62
+ * block text, so harvesting must never perturb it (ENTRY-block param defs
63
+ * are the canonical use; records that must precede a walked body get their
64
+ * own facts-only block instead, see the catch-param handling in visitTry).
65
+ */
66
+ attachFacts(index, facts) {
67
+ const b = this.blocks[index];
68
+ if (!b)
69
+ return;
70
+ b.statements.push(facts);
71
+ }
72
+ get blockCount() {
73
+ return this.blocks.length;
74
+ }
75
+ /** Produce the serializable CFG. Caller is responsible for having wired the
76
+ * function's dangling exits to {@link exitIndex} before calling.
77
+ *
78
+ * Pass `bindings` (the function's binding table, possibly empty) to emit
79
+ * statement facts (#2082 M2 U1) — every block then carries a `statements`
80
+ * array. Omit it (hand-built test CFGs, pre-M2 producers) and both fields
81
+ * are absent, which the reaching-defs solver reports as `no-facts`. */
82
+ finish(bindings) {
83
+ const withFacts = bindings !== undefined;
84
+ return {
85
+ filePath: this.filePath,
86
+ functionStartLine: this.functionStartLine,
87
+ functionEndLine: this.functionEndLine,
88
+ functionStartColumn: this.functionStartColumn,
89
+ entryIndex: this.entryIndex,
90
+ exitIndex: this.exitIndex,
91
+ blocks: this.blocks.map((b, index) => ({
92
+ index,
93
+ startLine: b.startLine,
94
+ endLine: b.endLine,
95
+ text: b.textParts.join('\n'),
96
+ kind: b.kind,
97
+ ...(withFacts ? { statements: b.statements } : {}),
98
+ })),
99
+ edges: [...this.edges],
100
+ ...(withFacts ? { bindings } : {}),
101
+ };
102
+ }
103
+ }
104
+ /**
105
+ * Block indices reachable from `entryIndex` by following edges. Backs the
106
+ * reachability property tests (R9) over hand-built and visitor-produced CFGs.
107
+ */
108
+ export const reachableBlocks = (cfg) => {
109
+ const adj = new Map();
110
+ for (const e of cfg.edges) {
111
+ const list = adj.get(e.from);
112
+ if (list)
113
+ list.push(e.to);
114
+ else
115
+ adj.set(e.from, [e.to]);
116
+ }
117
+ const seen = new Set([cfg.entryIndex]);
118
+ const stack = [cfg.entryIndex];
119
+ while (stack.length) {
120
+ const n = stack.pop();
121
+ for (const next of adj.get(n) ?? []) {
122
+ if (!seen.has(next)) {
123
+ seen.add(next);
124
+ stack.push(next);
125
+ }
126
+ }
127
+ }
128
+ return seen;
129
+ };
@@ -0,0 +1,30 @@
1
+ /**
2
+ * collectFunctionCfgs (issue #2081, M1).
3
+ *
4
+ * Walks a parsed file's tree-sitter tree and builds one {@link FunctionCfg} per
5
+ * CFG-bearing function via the language's {@link CfgVisitor}. Runs IN THE PARSE
6
+ * WORKER (where the AST lives — KTD1/KTD7); the result rides on
7
+ * `ParsedFile.cfgSideChannel` across the worker→main boundary.
8
+ *
9
+ * Nested functions are enumerated independently — each gets its own CFG, and
10
+ * appears as an opaque straight-line block in its enclosing function's CFG (the
11
+ * visitor does not descend into nested function bodies). `maxFunctionLines`
12
+ * bounds per-function cost: a function whose source span exceeds the cap is
13
+ * skipped (and counted) rather than walked, so a pathological mega-function
14
+ * cannot blow up worker time/memory. A cap of `0` means no limit.
15
+ */
16
+ import type { SyntaxNode } from '../utils/ast-helpers.js';
17
+ import type { CfgVisitor, FunctionCfg } from './types.js';
18
+ /**
19
+ * Default per-function source-line cap used by the worker when the `--pdg` run
20
+ * does not specify `pdgMaxFunctionLines`. A function longer than this (almost
21
+ * always minified/generated code) is skipped rather than walked — its CFG is
22
+ * both expensive and low-value. Overridable via `PipelineOptions.pdgMaxFunctionLines`.
23
+ */
24
+ export declare const DEFAULT_PDG_MAX_FUNCTION_LINES = 2000;
25
+ export interface CollectedCfgs {
26
+ readonly cfgs: readonly FunctionCfg[];
27
+ /** Functions skipped for exceeding `maxFunctionLines` (0 ⇒ none skipped). */
28
+ readonly skipped: number;
29
+ }
30
+ export declare function collectFunctionCfgs(root: SyntaxNode, visitor: CfgVisitor<SyntaxNode>, filePath: string, maxFunctionLines?: number): CollectedCfgs;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Default per-function source-line cap used by the worker when the `--pdg` run
3
+ * does not specify `pdgMaxFunctionLines`. A function longer than this (almost
4
+ * always minified/generated code) is skipped rather than walked — its CFG is
5
+ * both expensive and low-value. Overridable via `PipelineOptions.pdgMaxFunctionLines`.
6
+ */
7
+ export const DEFAULT_PDG_MAX_FUNCTION_LINES = 2000;
8
+ export function collectFunctionCfgs(root, visitor, filePath, maxFunctionLines = 0) {
9
+ const cfgs = [];
10
+ let skipped = 0;
11
+ const stack = [root];
12
+ while (stack.length) {
13
+ const node = stack.pop();
14
+ if (visitor.isFunction(node)) {
15
+ const lines = node.endPosition.row - node.startPosition.row + 1;
16
+ if (maxFunctionLines > 0 && lines > maxFunctionLines) {
17
+ skipped++;
18
+ }
19
+ else {
20
+ const cfg = visitor.buildFunctionCfg(node, filePath);
21
+ if (cfg)
22
+ cfgs.push(cfg);
23
+ }
24
+ }
25
+ // Descend regardless (a skipped mega-function may still contain small
26
+ // nested functions that are worth a CFG of their own).
27
+ for (let i = node.namedChildCount - 1; i >= 0; i--) {
28
+ const child = node.namedChild(i);
29
+ if (child)
30
+ stack.push(child);
31
+ }
32
+ }
33
+ return { cfgs, skipped };
34
+ }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * ControlFlowContext (issue #2081 M1; finalizer frames added by #2082 M2 U2).
3
+ *
4
+ * Resolves the targets of `break`/`continue` (plain and labeled) as the visitor
5
+ * descends through loops and switches. Loops and switches push a target frame
6
+ * on entry and pop it on exit; a labeled statement attaches its label to the
7
+ * frame of the construct it labels, so `break outer` / `continue outer` resolve
8
+ * against the right enclosing loop/switch rather than the nearest one.
9
+ *
10
+ * M2 adds FINALIZER frames, interleaved on the SAME stack as loop/switch frames
11
+ * — interleaving is load-bearing: a jump must route through exactly the
12
+ * `finally` bodies lexically BETWEEN it and its target (target-relative
13
+ * threading). A `break` whose loop lives entirely inside the `try` crosses no
14
+ * finally and must keep its direct edge; re-routing it anyway would force the
15
+ * only path to the in-try continuation through the finally, letting a finally
16
+ * redefinition falsely KILL in-loop definitions for the downstream
17
+ * reaching-defs pass (a taint false negative). A parallel stack cannot express
18
+ * that between-ness, which is why the frames live here.
19
+ */
20
+ import type { CfgBuilder } from './cfg-builder.js';
21
+ import type { CfgEdgeKind } from './types.js';
22
+ /** A `finally` whose body any crossing jump must route through. */
23
+ export interface FinalizerFrame {
24
+ readonly kind: 'finalizer';
25
+ /** Entry block of the finally body. */
26
+ readonly entry: number;
27
+ /**
28
+ * Completion legs registered by jumps that crossed this finally: once the
29
+ * owning try pops the frame, it wires `finally-exits → to` with `kind` for
30
+ * each entry. Mutated by the jump handlers via {@link ControlFlowContext}.
31
+ */
32
+ readonly pending: {
33
+ to: number;
34
+ kind: CfgEdgeKind;
35
+ }[];
36
+ }
37
+ /** A resolved jump: its ultimate target + the finallys it crosses (inner→outer). */
38
+ export interface JumpResolution {
39
+ readonly target: number;
40
+ readonly finalizers: readonly FinalizerFrame[];
41
+ }
42
+ export declare class ControlFlowContext {
43
+ private readonly stack;
44
+ pushLoop(continueTo: number, breakTo: number, labels?: readonly string[]): void;
45
+ pushSwitch(breakTo: number, labels?: readonly string[]): void;
46
+ /** Push a labeled non-loop statement's break-target frame. */
47
+ pushLabeledBlock(breakTo: number, labels: readonly string[]): void;
48
+ /**
49
+ * Push a finalizer frame and return it — the owning `visitTry` keeps the
50
+ * reference to wire {@link FinalizerFrame.pending} after popping it.
51
+ */
52
+ pushFinalizer(entry: number): FinalizerFrame;
53
+ pop(): void;
54
+ /**
55
+ * Resolve a `break`: the nearest enclosing loop/switch frame (or, with a
56
+ * label, the nearest frame carrying that label) plus every finalizer frame
57
+ * stacked ABOVE it — i.e. exactly the finallys the jump crosses, innermost
58
+ * first. Returns `undefined` if there is no valid target (malformed input or
59
+ * an unmodeled label) — the caller falls back to its conservative routing and
60
+ * threads nothing.
61
+ */
62
+ resolveBreak(label?: string): JumpResolution | undefined;
63
+ /** Resolve a `continue`: like {@link resolveBreak} but only loop frames match. */
64
+ resolveContinue(label?: string): JumpResolution | undefined;
65
+ /** Every active finalizer, innermost first — what a `return` must cross. */
66
+ finalizersForReturn(): readonly FinalizerFrame[];
67
+ /**
68
+ * Target block for a `break` (no finalizer info) — see {@link resolveBreak}.
69
+ * Prefer `resolveBreak` + {@link wireJumpThroughFinalizers} in visitors: a
70
+ * target-only lookup silently loses finalizer threading (the M2 soundness
71
+ * fix). Kept for target-shape assertions in tests.
72
+ */
73
+ breakTarget(label?: string): number | undefined;
74
+ /** Target block for a `continue` — same caveat as {@link breakTarget}. */
75
+ continueTarget(label?: string): number | undefined;
76
+ private resolve;
77
+ }
78
+ /**
79
+ * Wire a jump from `from` to `target`, routing through the finallys it
80
+ * crosses (innermost first). The first leg keeps the bare jump `kind`
81
+ * (preserving the "kind ⟹ source-block terminator" invariant in types.ts);
82
+ * each finally's completion leg is registered as pending on its frame with the
83
+ * matching `finally-*` kind and wired by the owning try via
84
+ * {@link drainFinalizerPending} once the finally's exits are known.
85
+ *
86
+ * Language-agnostic on purpose (#2082 M2): the threading protocol encodes
87
+ * three subtle invariants every future language visitor needs identically —
88
+ * keeping it here means a new visitor cannot drift on any of them.
89
+ */
90
+ export declare function wireJumpThroughFinalizers(builder: CfgBuilder, from: number, finalizers: readonly FinalizerFrame[], target: number, kind: 'return' | 'break' | 'continue'): void;
91
+ /**
92
+ * Wire a popped finalizer frame's pending completion legs from the finally's
93
+ * exit blocks. A finally that itself always jumps (`finally { return 2; }`)
94
+ * has no exits — its pending legs wire nowhere, matching JS's
95
+ * finally-override semantics.
96
+ */
97
+ export declare function drainFinalizerPending(builder: CfgBuilder, frame: FinalizerFrame, finallyExits: readonly number[]): void;
@@ -0,0 +1,113 @@
1
+ export class ControlFlowContext {
2
+ stack = [];
3
+ pushLoop(continueTo, breakTo, labels = []) {
4
+ this.stack.push({ kind: 'loop', continueTo, breakTo, labels });
5
+ }
6
+ pushSwitch(breakTo, labels = []) {
7
+ this.stack.push({ kind: 'switch', breakTo, labels });
8
+ }
9
+ /** Push a labeled non-loop statement's break-target frame. */
10
+ pushLabeledBlock(breakTo, labels) {
11
+ this.stack.push({ kind: 'block', breakTo, labels });
12
+ }
13
+ /**
14
+ * Push a finalizer frame and return it — the owning `visitTry` keeps the
15
+ * reference to wire {@link FinalizerFrame.pending} after popping it.
16
+ */
17
+ pushFinalizer(entry) {
18
+ const frame = { kind: 'finalizer', entry, pending: [] };
19
+ this.stack.push(frame);
20
+ return frame;
21
+ }
22
+ pop() {
23
+ this.stack.pop();
24
+ }
25
+ /**
26
+ * Resolve a `break`: the nearest enclosing loop/switch frame (or, with a
27
+ * label, the nearest frame carrying that label) plus every finalizer frame
28
+ * stacked ABOVE it — i.e. exactly the finallys the jump crosses, innermost
29
+ * first. Returns `undefined` if there is no valid target (malformed input or
30
+ * an unmodeled label) — the caller falls back to its conservative routing and
31
+ * threads nothing.
32
+ */
33
+ resolveBreak(label) {
34
+ return this.resolve((f) => label === undefined
35
+ ? f.kind !== 'block' // an unlabeled break never targets a labeled block
36
+ : f.labels.includes(label));
37
+ }
38
+ /** Resolve a `continue`: like {@link resolveBreak} but only loop frames match. */
39
+ resolveContinue(label) {
40
+ return this.resolve((f) => f.kind === 'loop' && (label === undefined || f.labels.includes(label)), (f) => f.continueTo);
41
+ }
42
+ /** Every active finalizer, innermost first — what a `return` must cross. */
43
+ finalizersForReturn() {
44
+ const fins = [];
45
+ for (let i = this.stack.length - 1; i >= 0; i--) {
46
+ const f = this.stack[i];
47
+ if (f.kind === 'finalizer')
48
+ fins.push(f);
49
+ }
50
+ return fins;
51
+ }
52
+ /**
53
+ * Target block for a `break` (no finalizer info) — see {@link resolveBreak}.
54
+ * Prefer `resolveBreak` + {@link wireJumpThroughFinalizers} in visitors: a
55
+ * target-only lookup silently loses finalizer threading (the M2 soundness
56
+ * fix). Kept for target-shape assertions in tests.
57
+ */
58
+ breakTarget(label) {
59
+ return this.resolveBreak(label)?.target;
60
+ }
61
+ /** Target block for a `continue` — same caveat as {@link breakTarget}. */
62
+ continueTarget(label) {
63
+ return this.resolveContinue(label)?.target;
64
+ }
65
+ resolve(matches, targetOf = (f) => f.breakTo) {
66
+ const crossed = [];
67
+ for (let i = this.stack.length - 1; i >= 0; i--) {
68
+ const f = this.stack[i];
69
+ if (f.kind === 'finalizer') {
70
+ crossed.push(f);
71
+ continue;
72
+ }
73
+ if (matches(f))
74
+ return { target: targetOf(f), finalizers: crossed };
75
+ }
76
+ return undefined;
77
+ }
78
+ }
79
+ /**
80
+ * Wire a jump from `from` to `target`, routing through the finallys it
81
+ * crosses (innermost first). The first leg keeps the bare jump `kind`
82
+ * (preserving the "kind ⟹ source-block terminator" invariant in types.ts);
83
+ * each finally's completion leg is registered as pending on its frame with the
84
+ * matching `finally-*` kind and wired by the owning try via
85
+ * {@link drainFinalizerPending} once the finally's exits are known.
86
+ *
87
+ * Language-agnostic on purpose (#2082 M2): the threading protocol encodes
88
+ * three subtle invariants every future language visitor needs identically —
89
+ * keeping it here means a new visitor cannot drift on any of them.
90
+ */
91
+ export function wireJumpThroughFinalizers(builder, from, finalizers, target, kind) {
92
+ if (finalizers.length === 0) {
93
+ builder.edge(from, target, kind);
94
+ return;
95
+ }
96
+ const completionKind = `finally-${kind}`;
97
+ builder.edge(from, finalizers[0].entry, kind);
98
+ for (let i = 0; i < finalizers.length; i++) {
99
+ const to = i + 1 < finalizers.length ? finalizers[i + 1].entry : target;
100
+ finalizers[i].pending.push({ to, kind: completionKind });
101
+ }
102
+ }
103
+ /**
104
+ * Wire a popped finalizer frame's pending completion legs from the finally's
105
+ * exit blocks. A finally that itself always jumps (`finally { return 2; }`)
106
+ * has no exits — its pending legs wire nowhere, matching JS's
107
+ * finally-override semantics.
108
+ */
109
+ export function drainFinalizerPending(builder, frame, finallyExits) {
110
+ for (const p of frame.pending) {
111
+ builder.connect(finallyExits, p.to, p.kind);
112
+ }
113
+ }
@@ -0,0 +1,144 @@
1
+ /**
2
+ * cfg/emit.ts (issue #2081, M1) — serialized side-channel → graph.
3
+ *
4
+ * Pure helper: given a file's per-function CFGs (off `ParsedFile.cfgSideChannel`,
5
+ * produced by the worker in U3), emit one persisted `BasicBlock` node per block
6
+ * and one `CFG` edge per edge into the {@link KnowledgeGraph}. Invoked from
7
+ * scope-resolution (run.ts Phase 4) while the disk-backed ParsedFile store is
8
+ * still live — the only window where the worker-built CFGs are loaded (KTD1/
9
+ * KTD5). Default (`--pdg` off) runs never call this, so the emitted graph stays
10
+ * byte-identical to a pre-#2081 run.
11
+ *
12
+ * BasicBlock id: `BasicBlock:<filePath>:<functionStartLine>:<functionStartColumn>:<blockIndex>`
13
+ * (KTD3). The function start line+column segments disambiguate blocks across
14
+ * multiple functions in one file — including same-line functions — since each
15
+ * function's block indices restart at 0; blocks carry no `name` (the
16
+ * BasicBlock table has no such column). The edge KIND
17
+ * (`seq`/`cond-true`/…) rides in the relationship `reason` — CFG edges are
18
+ * values of the single `CodeRelation` table's `type` column (`'CFG'`), so the
19
+ * kind cannot be its own edge type and is queried via `reason`.
20
+ */
21
+ import type { KnowledgeGraph } from '../../graph/types.js';
22
+ import type { BindingEntry, FunctionCfg } from './types.js';
23
+ /**
24
+ * Default per-function CFG edge cap. A pathological generated function could
25
+ * otherwise emit an unbounded edge set; the cap bounds graph growth and is
26
+ * overridable via `--pdg` options. `0` (in options) means no cap (unlimited
27
+ * — see the `cap` mapping in {@link emitFileCfgs}); `undefined` means this
28
+ * default.
29
+ */
30
+ export declare const DEFAULT_MAX_CFG_EDGES_PER_FUNCTION = 5000;
31
+ /**
32
+ * Default per-function REACHING_DEF edge cap (#2082 M2 KTD9). 4000 mirrors
33
+ * Joern's per-method `maxNumberOfDefinitions` — the closest production prior
34
+ * art — but truncates-and-warns instead of silently skipping the function.
35
+ * Counts (defBlock, useBlock, binding) DEDUPED edges, not statement-level
36
+ * facts. `0` ⇒ unlimited; `undefined` ⇒ this default.
37
+ */
38
+ export declare const DEFAULT_PDG_MAX_REACHING_DEF_EDGES_PER_FUNCTION = 4000;
39
+ /**
40
+ * Fact-materialization headroom over the edge cap (#2082 M2 U3/F3): facts are
41
+ * O(defs×uses) BY SPEC in merge-heavy code, and the edge cap alone bounds the
42
+ * GRAPH, not the per-function memory spike of materializing facts before
43
+ * dedup. {@link emitFileReachingDefs} hands `edgeCap × this` to
44
+ * `computeReachingDefs` as `maxFacts` (unlimited when the edge cap is 0) —
45
+ * single source of truth; the DEFAULT constant below is derived, never the
46
+ * mechanism.
47
+ */
48
+ export declare const REACHING_DEF_FACTS_PER_EDGE_CAP = 4;
49
+ /** Derived emit-path fact limit at the default edge cap (bench/doc anchor). */
50
+ export declare const DEFAULT_PDG_MAX_REACHING_DEF_FACTS_PER_FUNCTION: number;
51
+ export interface CfgEmitResult {
52
+ blocks: number;
53
+ edges: number;
54
+ /** Edges dropped because a function's edge count exceeded the cap. */
55
+ droppedEdges: number;
56
+ /** Number of functions that hit the cap. */
57
+ cappedFunctions: number;
58
+ }
59
+ /**
60
+ * The single BasicBlock id template (module doc). Exported for the M3 taint
61
+ * emit path (taint/emit.ts), whose TAINTED/SANITIZES edges must address the
62
+ * SAME persisted block nodes — a re-derived copy of this template would
63
+ * silently dangle the moment either drifted.
64
+ */
65
+ export declare const basicBlockId: (filePath: string, functionStartLine: number, functionStartColumn: number, blockIndex: number) => string;
66
+ /**
67
+ * Whether an untrusted `cfgSideChannel` element is safe to feed to
68
+ * {@link emitFileCfgs}. Deliberately NOT full FunctionCfg validation — it
69
+ * checks exactly the fields whose corruption is SILENT given emit's
70
+ * mechanics: {@link basicBlockId} string-templates every id-anchor value
71
+ * (filePath, function start line/column, block index, edge endpoints) and
72
+ * the graph's addNode/addRelationship are no-throw Map inserts. Unchecked,
73
+ * a missing anchor field cross-wires same-`undefined`-id blocks across
74
+ * functions (addNode is first-writer-wins), and an edge endpoint that
75
+ * matches no block index becomes a dangling `BasicBlock:…:<n>` edge that
76
+ * detonates much later at DB bulk-load instead of throwing here — so
77
+ * endpoints are checked for MEMBERSHIP in the block-index set, not just
78
+ * integer-ness. Lives in this module so the guard evolves with the id
79
+ * templating it defends (#2099 F4; M2 fields that join the id path must
80
+ * join this check).
81
+ */
82
+ export declare const isEmitSafeCfg: (cfg: FunctionCfg | undefined | null) => cfg is FunctionCfg;
83
+ /**
84
+ * Whether a structurally-valid CFG's M2 statement facts are safe to feed to
85
+ * the reaching-defs solver + REACHING_DEF id templating (#2082 U1/U4): the
86
+ * binding table's name/declLine/declColumn template into edge ids, and
87
+ * statement def/use indices must stay IN RANGE of the table (an escaping
88
+ * index would fabricate `undefined`-keyed ids). Deliberately SEPARATE from
89
+ * {@link isEmitSafeCfg}: malformed facts must cost only the function's
90
+ * REACHING_DEF projection — degrading to M1 behavior (CFG emitted, no facts)
91
+ * — never the BasicBlock/CFG layer itself.
92
+ */
93
+ export declare const hasEmitSafeFacts: (cfg: FunctionCfg) => boolean;
94
+ /**
95
+ * Emit BasicBlock nodes + CFG edges for every function CFG in `cfgs`.
96
+ *
97
+ * `maxEdgesPerFunction` caps edges per function. On overflow we stop emitting
98
+ * that function's remaining edges and call `onWarn` naming the dropped count —
99
+ * no silent truncation (KTD6/R6). Block nodes are always fully emitted (their
100
+ * count is bounded by the function's statement count); only edges are capped.
101
+ */
102
+ export declare function emitFileCfgs(graph: KnowledgeGraph, cfgs: readonly FunctionCfg[], maxEdgesPerFunction?: number, onWarn?: (message: string) => void): CfgEmitResult;
103
+ export interface ReachingDefEmitResult {
104
+ /** Deduped (defBlock, useBlock, binding) edges persisted. */
105
+ edges: number;
106
+ /** Deduped edges dropped by the per-function edge cap. */
107
+ droppedEdges: number;
108
+ cappedFunctions: number;
109
+ /** Functions whose FACT materialization hit the solver's maxFacts limit. */
110
+ truncatedFunctions: number;
111
+ /** Functions whose facts failed {@link hasEmitSafeFacts} (CFG kept, facts skipped). */
112
+ malformedFactFunctions: number;
113
+ /** Total statement-level facts the solver produced (pre-dedup telemetry). */
114
+ facts: number;
115
+ }
116
+ /**
117
+ * Stable identity for a binding inside edge ids (#2082 M2 KTD3/KTD9):
118
+ * `name:declLine:declCol` for declared bindings, `name@module` for synthetic
119
+ * ones. Distinct same-name bindings never share a key; identifier characters
120
+ * cannot contain the id separators. Exported for the M3 taint emit path —
121
+ * TAINTED/SANITIZES ids key bindings with the same discipline.
122
+ */
123
+ export declare const bindingKey: (b: BindingEntry) => string;
124
+ /**
125
+ * Compute reaching definitions per function and persist the bounded
126
+ * REACHING_DEF projection (#2082 M2 U4).
127
+ *
128
+ * Facts are DEDUPED to (defBlock, useBlock, binding) before budgeting — the
129
+ * persisted columns (`from,to,type,confidence,reason,step`; relationship ids
130
+ * are in-memory-only, the CodeRelation table has no id column) cannot
131
+ * distinguish finer rows, so statement-indexed ids would only manufacture
132
+ * byte-identical duplicate rows that burn budget. Statement granularity lives
133
+ * in the in-memory {@link computeReachingDefs} result, which the M3 taint
134
+ * engine recomputes on demand — the budget here governs only this projection
135
+ * and can never drop a taint fact.
136
+ *
137
+ * R7 (no silent truncation) covers BOTH layers: the per-function edge cap AND
138
+ * the solver's fact-materialization limit (which can fire without the edge
139
+ * cap ever being reached, since dedup is many-to-one) each produce one
140
+ * unconditional `onWarn`. The edge-cap warn names the top bindings by fact
141
+ * count — overflow is almost always one variable, which is exactly the datum
142
+ * M3 tuning wants.
143
+ */
144
+ export declare function emitFileReachingDefs(graph: KnowledgeGraph, cfgs: readonly FunctionCfg[], maxEdgesPerFunction?: number, onWarn?: (message: string) => void): ReachingDefEmitResult;