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
@@ -285,6 +285,12 @@ export const scopeResolutionPhase = {
285
285
  prebuiltNodeLookup: sharedNodeLookup,
286
286
  preExtractedParsedFiles: preExtractedByPath,
287
287
  scopeIndexStorePath: parsedFileStorePath,
288
+ // CFG/PDG emission (#2081 M1) — opt-in; off ⇒ byte-identical graph.
289
+ pdg: ctx.options?.pdg === true,
290
+ pdgMaxEdgesPerFunction: ctx.options?.pdgMaxEdgesPerFunction,
291
+ pdgMaxReachingDefEdgesPerFunction: ctx.options?.pdgMaxReachingDefEdgesPerFunction,
292
+ pdgMaxTaintFindingsPerFunction: ctx.options?.pdgMaxTaintFindingsPerFunction,
293
+ pdgMaxTaintHops: ctx.options?.pdgMaxTaintHops,
288
294
  recordResolutionOutcome: (outcome) => {
289
295
  resolutionOutcomes.push(outcome);
290
296
  },
@@ -64,14 +64,39 @@ export function reconcileOwnership(parsedFiles, model) {
64
64
  for (const parsed of parsedFiles) {
65
65
  for (const def of parsed.localDefs) {
66
66
  const ownerId = def.ownerId;
67
- if (ownerId === undefined)
68
- continue;
69
67
  const simple = simpleQualifiedName(def);
70
68
  if (simple === undefined)
71
69
  continue;
72
70
  if (def.type === 'Method' || def.type === 'Function' || def.type === 'Constructor') {
71
+ if (ownerId === undefined) {
72
+ if (def.isDeleted !== true)
73
+ continue;
74
+ const existingDef = model.symbols
75
+ .lookupExactAll(def.filePath, simple)
76
+ .find((candidate) => callableSignatureMatches(candidate, def));
77
+ if (existingDef !== undefined) {
78
+ existingDef.isDeleted = true;
79
+ skippedAlreadyPresent++;
80
+ continue;
81
+ }
82
+ model.symbols.add(def.filePath, simple, def.nodeId, def.type, {
83
+ parameterCount: def.parameterCount,
84
+ requiredParameterCount: def.requiredParameterCount,
85
+ parameterTypes: def.parameterTypes,
86
+ parameterTypeClasses: def.parameterTypeClasses,
87
+ returnType: def.returnType,
88
+ qualifiedName: def.qualifiedName,
89
+ isDeleted: true,
90
+ });
91
+ continue;
92
+ }
73
93
  const existing = model.methods.lookupAllByOwner(ownerId, simple);
74
- if (existing.some((e) => e.nodeId === def.nodeId)) {
94
+ const existingDef = existing.find((candidate) => candidate.nodeId === def.nodeId ||
95
+ (def.isDeleted === true && callableSignatureMatches(candidate, def)));
96
+ if (existingDef !== undefined) {
97
+ if (def.isDeleted === true) {
98
+ existingDef.isDeleted = true;
99
+ }
75
100
  skippedAlreadyPresent++;
76
101
  continue;
77
102
  }
@@ -103,6 +128,21 @@ export function reconcileOwnership(parsedFiles, model) {
103
128
  }
104
129
  return { methodsRegistered, fieldsRegistered, nestedTypesRegistered, skippedAlreadyPresent };
105
130
  }
131
+ function callableSignatureMatches(left, right) {
132
+ if (left.filePath !== right.filePath)
133
+ return false;
134
+ if (left.parameterCount !== right.parameterCount)
135
+ return false;
136
+ if (left.requiredParameterCount !== right.requiredParameterCount)
137
+ return false;
138
+ const leftTypes = left.parameterTypes;
139
+ const rightTypes = right.parameterTypes;
140
+ if (leftTypes === undefined || rightTypes === undefined) {
141
+ return leftTypes === rightTypes;
142
+ }
143
+ return (leftTypes.length === rightTypes.length &&
144
+ leftTypes.every((parameterType, index) => parameterType === rightTypes[index]));
145
+ }
106
146
  /**
107
147
  * Debug-mode parity validator. Runs only when
108
148
  * `VALIDATE_SEMANTIC_MODEL !== '0'` AND `NODE_ENV !== 'production'`.
@@ -58,6 +58,27 @@ interface RunScopeResolutionInput {
58
58
  readonly treeCache?: {
59
59
  get(filePath: string): unknown;
60
60
  };
61
+ /**
62
+ * CFG/PDG opt-in (#2081 M1). When true, emit BasicBlock nodes + CFG edges
63
+ * from each ParsedFile's worker-built `cfgSideChannel` during Phase-4 graph
64
+ * emission (while the disk store is still live). Default/false ⇒ no CFG
65
+ * nodes or edges and a byte-identical graph.
66
+ */
67
+ readonly pdg?: boolean;
68
+ /** Per-function CFG edge cap. `undefined` ⇒ {@link DEFAULT_MAX_CFG_EDGES_PER_FUNCTION};
69
+ * `0` ⇒ no cap (unlimited). */
70
+ readonly pdgMaxEdgesPerFunction?: number;
71
+ /** Per-function REACHING_DEF edge cap (#2082 M2). `undefined` ⇒
72
+ * {@link DEFAULT_PDG_MAX_REACHING_DEF_EDGES_PER_FUNCTION}; `0` ⇒ no cap. */
73
+ readonly pdgMaxReachingDefEdgesPerFunction?: number;
74
+ /** Per-function taint findings cap (#2083 M3, consumed by the U4 taint
75
+ * emit step in the pdg window). `undefined` ⇒
76
+ * `DEFAULT_PDG_MAX_TAINT_FINDINGS_PER_FUNCTION` (200); `0` ⇒ no cap. */
77
+ readonly pdgMaxTaintFindingsPerFunction?: number;
78
+ /** Per-finding taint hop cap (#2083 M3 KTD6 — bounds the hop-encoded
79
+ * `reason`; consumed by the U4 taint emit step). `undefined` ⇒
80
+ * `DEFAULT_PDG_MAX_TAINT_HOPS` (32); `0` ⇒ no cap. */
81
+ readonly pdgMaxTaintHops?: number;
61
82
  /**
62
83
  * Optional graph-node lookup built ONCE by the caller and shared across
63
84
  * every language pass. `buildGraphNodeLookup` scans the whole graph and is
@@ -30,6 +30,10 @@ import { extractParsedFile } from '../../scope-extractor-bridge.js';
30
30
  import { finalizeScopeModel } from '../../finalize-orchestrator.js';
31
31
  import { resolveReferenceSites } from '../../resolve-references.js';
32
32
  import { buildGraphNodeLookup } from '../graph-bridge/node-lookup.js';
33
+ import { emitFileCfgs, emitFileReachingDefs, isEmitSafeCfg, DEFAULT_MAX_CFG_EDGES_PER_FUNCTION, DEFAULT_PDG_MAX_REACHING_DEF_EDGES_PER_FUNCTION, REACHING_DEF_FACTS_PER_EDGE_CAP, } from '../../cfg/emit.js';
34
+ import { emitFileTaint, DEFAULT_PDG_MAX_TAINT_FINDINGS_PER_FUNCTION, DEFAULT_PDG_MAX_TAINT_HOPS, } from '../../taint/emit.js';
35
+ import { registerBuiltinTaintModels } from '../../taint/typescript-model.js';
36
+ import { getSourceSinkConfig } from '../../taint/source-sink-registry.js';
33
37
  import { resolveDefGraphId } from '../graph-bridge/ids.js';
34
38
  import { buildPopulatedMethodDispatch } from '../graph-bridge/method-dispatch.js';
35
39
  import { propagateImportedReturnTypes } from '../passes/imported-return-types.js';
@@ -454,6 +458,206 @@ export function runScopeResolution(input, provider) {
454
458
  resolutionConfig,
455
459
  });
456
460
  }
461
+ // ── CFG/PDG emission (#2081 M1, opt-in via `--pdg`) ──────────────────────
462
+ // Emit BasicBlock nodes + CFG edges from each ParsedFile's worker-built
463
+ // `cfgSideChannel`, HERE — the last point inside scope-resolution where the
464
+ // ParsedFiles are still loaded (`emitParsedFiles` carries the channel; the
465
+ // disk store is cleared right after this orchestrator returns, see phase.ts).
466
+ // A post-`mro` phase would read empty data (KTD1). Off by default ⇒ zero
467
+ // BasicBlock/CFG nodes/edges and a byte-identical graph.
468
+ // Accumulated M2 reaching-defs time (solve + dedup + REACHING_DEF emit),
469
+ // reported as the PROF `pdg=` segment. It is a SUBSET of `emit=` — the M1
470
+ // CFG emit and the M2 solve interleave per file, so a separate checkpoint
471
+ // pair can't bracket them; without this accumulator the M2 cost would
472
+ // silently disappear into `emit=` and field regressions would be invisible.
473
+ let pdgMs = 0;
474
+ // M3 (#2083 U4): accumulated taint time (match + taint-side solve +
475
+ // propagate + TAINTED/SANITIZES emit), a sibling of `pdgMs` for the same
476
+ // reason — it interleaves per file inside `emit=`, so only an accumulator
477
+ // can bracket it. Printed as the PROF `taint=` segment.
478
+ let taintMs = 0;
479
+ if (input.pdg === true) {
480
+ let cfgBlocks = 0;
481
+ let cfgEdges = 0;
482
+ let cfgDroppedEdges = 0;
483
+ let rdEdges = 0;
484
+ let rdDropped = 0;
485
+ let rdFacts = 0;
486
+ let rdTruncated = 0;
487
+ // ── M3 taint setup (#2083 U4) ────────────────────────────────────────
488
+ // Explicit model-registration seam (idempotent, cheap) — the registry
489
+ // stays empty on non-pdg runs, preserving default-run parity. The
490
+ // registry is keyed by `SupportedLanguages` enum VALUES ('typescript' /
491
+ // 'javascript'), and `ScopeResolver.language` IS a `SupportedLanguages`
492
+ // member registered under those same constants — the join is direct
493
+ // equality, no mapping table. A language without a registered spec
494
+ // (python, go, …) skips taint entirely: no work, no warn spam (KTD8).
495
+ registerBuiltinTaintModels();
496
+ const taintSpec = getSourceSinkConfig(provider.language);
497
+ // Taint-side solver fact cap: the SAME derivation emitFileReachingDefs
498
+ // uses for the RD projection (edge cap × headroom factor, 0 ⇒ unlimited),
499
+ // so taint coverage and RD coverage truncate together — a function is
500
+ // never a taint coverage gap while its RD projection computed, and the
501
+ // RD layer's per-function truncation warn already names it.
502
+ const rdEdgeCap = input.pdgMaxReachingDefEdgesPerFunction ?? DEFAULT_PDG_MAX_REACHING_DEF_EDGES_PER_FUNCTION;
503
+ const taintLimits = {
504
+ maxFindingsPerFunction: input.pdgMaxTaintFindingsPerFunction ?? DEFAULT_PDG_MAX_TAINT_FINDINGS_PER_FUNCTION,
505
+ maxHops: input.pdgMaxTaintHops ?? DEFAULT_PDG_MAX_TAINT_HOPS,
506
+ maxFacts: rdEdgeCap > 0 ? rdEdgeCap * REACHING_DEF_FACTS_PER_EDGE_CAP : 0,
507
+ };
508
+ // Cross-file aggregate of EVERY TaintEmitResult counter (the M2 emit
509
+ // result shipped with two fields dropped on the floor — R4 forbids that
510
+ // here; gaps/drops feed the unconditional warn below, volume feeds the
511
+ // per-language debug line).
512
+ const taintTotals = {
513
+ analyzed: 0,
514
+ noMatch: 0,
515
+ unsafeSites: 0,
516
+ gapTruncated: 0,
517
+ gapOverflow: 0,
518
+ gapNoFacts: 0,
519
+ findings: 0,
520
+ kills: 0,
521
+ dropped: 0,
522
+ hopsTruncated: 0,
523
+ gapExamples: [],
524
+ dropExamples: [],
525
+ };
526
+ for (const pf of emitParsedFiles) {
527
+ const cfgs = pf.cfgSideChannel;
528
+ // Defensive: cfgSideChannel is opaque (`unknown`) and crosses the cache /
529
+ // durable store. A stale or wrong-shape value (e.g. a pre-SCHEMA_BUMP
530
+ // shard that slipped the version gate) must skip emission, not throw a
531
+ // TypeError mid-graph-build and abort scope-resolution for the language.
532
+ if (!Array.isArray(cfgs) || cfgs.length === 0)
533
+ continue;
534
+ try {
535
+ // Per-element emit-safety filter (mirrors the parsedfile-store
536
+ // reviver's POLICY: valid elements in a mixed array still emit; junk
537
+ // is warned and skipped). isEmitSafeCfg lives in cfg/emit.ts next to
538
+ // the id templating it defends — see its doc for why anchor-field and
539
+ // endpoint-membership checks are load-bearing. Runs INSIDE the try so
540
+ // even a predicate-time throw (e.g. a hostile getter) is isolated.
541
+ const wellFormed = cfgs.filter(isEmitSafeCfg);
542
+ if (wellFormed.length < cfgs.length) {
543
+ logger.warn(`[cfg] ${pf.filePath}: skipped ${cfgs.length - wellFormed.length} malformed ` +
544
+ `cfgSideChannel element(s) (bad shape, missing id-anchor fields, or edge ` +
545
+ `endpoints matching no block) — CFG for those functions omitted`);
546
+ }
547
+ if (wellFormed.length === 0)
548
+ continue;
549
+ const emitted = emitFileCfgs(graph, wellFormed, input.pdgMaxEdgesPerFunction ?? DEFAULT_MAX_CFG_EDGES_PER_FUNCTION,
550
+ // Log cap-overflow drops UNCONDITIONALLY (not via input.onWarn, which is
551
+ // gated behind the semantic-model validator and silent in production) so
552
+ // the per-function edge cap never truncates the CFG silently (R6/KTD6).
553
+ (message) => logger.warn(message));
554
+ cfgBlocks += emitted.blocks;
555
+ cfgEdges += emitted.edges;
556
+ cfgDroppedEdges += emitted.droppedEdges;
557
+ // M2 (#2082 U4): reaching definitions over the same validated CFGs.
558
+ // In-memory facts are computed per function and dropped after the
559
+ // bounded (defBlock, useBlock, binding) projection is persisted —
560
+ // M3 recomputes via the same pure solver in-phase (KTD8). Timing is
561
+ // PROF-gated like every other checkpoint here (zero cost when off).
562
+ const t0 = PROF ? performance.now() : 0;
563
+ const rd = emitFileReachingDefs(graph, wellFormed, input.pdgMaxReachingDefEdgesPerFunction ??
564
+ DEFAULT_PDG_MAX_REACHING_DEF_EDGES_PER_FUNCTION, (message) => logger.warn(message));
565
+ if (PROF)
566
+ pdgMs += performance.now() - t0;
567
+ rdEdges += rd.edges;
568
+ rdDropped += rd.droppedEdges;
569
+ rdFacts += rd.facts;
570
+ rdTruncated += rd.truncatedFunctions;
571
+ // M3 (#2083 U4): taint over the SAME validated CFGs, inside the SAME
572
+ // per-file try (a taint throw costs this file's taint layer only —
573
+ // its CFG/REACHING_DEF edges above are already in the graph). Skipped
574
+ // entirely when the language has no registered model.
575
+ if (taintSpec !== undefined) {
576
+ const t1 = PROF ? performance.now() : 0;
577
+ const taint = emitFileTaint(graph, wellFormed, pf.parsedImports, taintSpec, taintLimits, (message) => logger.warn(message));
578
+ if (PROF)
579
+ taintMs += performance.now() - t1;
580
+ taintTotals.analyzed += taint.functionsAnalyzed;
581
+ taintTotals.noMatch += taint.functionsSkippedNoMatch;
582
+ taintTotals.unsafeSites += taint.functionsSkippedUnsafeSites;
583
+ taintTotals.gapTruncated += taint.functionsCoverageGap.truncated;
584
+ taintTotals.gapOverflow += taint.functionsCoverageGap.overflow;
585
+ taintTotals.gapNoFacts += taint.functionsCoverageGap['no-facts'];
586
+ taintTotals.findings += taint.findingsEmitted;
587
+ taintTotals.kills += taint.killsEmitted;
588
+ taintTotals.dropped += taint.findingsDropped;
589
+ taintTotals.hopsTruncated += taint.hopsTruncatedFindings;
590
+ for (const ex of taint.coverageGapExamples) {
591
+ if (taintTotals.gapExamples.length < 5)
592
+ taintTotals.gapExamples.push(ex);
593
+ }
594
+ for (const ex of taint.droppedExamples) {
595
+ if (taintTotals.dropExamples.length < 5)
596
+ taintTotals.dropExamples.push(ex);
597
+ }
598
+ }
599
+ }
600
+ catch (err) {
601
+ // Last-resort isolation, mirroring the worker-side per-file try/catch:
602
+ // a shape the predicate misses must cost this one file's CFG, not
603
+ // abort the language's whole scope-resolution pass mid-graph-build.
604
+ // NOTE a mid-emit throw can leave this file's already-inserted
605
+ // BasicBlock nodes in the graph (addNode is not transactional) —
606
+ // orphaned but inert; the predicate keeps every JSON-representable
607
+ // bad shape from reaching this path at all.
608
+ logger.warn(`[cfg] ${pf.filePath}: CFG emission failed (${err instanceof Error ? err.message : String(err)}) — ` +
609
+ `this file's CFG is partial or absent`);
610
+ }
611
+ }
612
+ if (cfgBlocks > 0) {
613
+ logger.debug(`[scope-resolution] CFG emit (lang=${provider.language}): ` +
614
+ `${cfgBlocks} BasicBlock nodes, ${cfgEdges} CFG edges` +
615
+ (cfgDroppedEdges > 0 ? `, ${cfgDroppedEdges} edges dropped (per-function cap)` : '') +
616
+ `; ${rdEdges} REACHING_DEF edges (${rdFacts} facts)` +
617
+ (rdDropped > 0 ? `, ${rdDropped} REACHING_DEF edges dropped (per-function cap)` : '') +
618
+ (rdTruncated > 0 ? `, ${rdTruncated} function(s) hit the fact limit` : '') +
619
+ // M3 volume telemetry — only for languages with a registered model.
620
+ (taintSpec !== undefined
621
+ ? `; taint: ${taintTotals.findings} TAINTED, ${taintTotals.kills} SANITIZES ` +
622
+ `(${taintTotals.analyzed} function(s) analyzed, ` +
623
+ `${taintTotals.noMatch} skipped: no source/sink match` +
624
+ (taintTotals.hopsTruncated > 0
625
+ ? `, ${taintTotals.hopsTruncated} finding(s) with truncated hop paths`
626
+ : '') +
627
+ `)`
628
+ : ''));
629
+ }
630
+ // R4: taint coverage gaps and cap drops surface UNCONDITIONALLY (never
631
+ // logger.debug, never input.onWarn) at the per-language aggregate, with
632
+ // counts and up to 5 example functions. Per-function warns above cover
633
+ // the rare/actionable cases (unsafe sites, cap drops); solver-status gaps
634
+ // were already per-function-warned by the RD layer (same solver, same
635
+ // fact cap), so this aggregate is their single taint-side surface.
636
+ if (taintSpec !== undefined) {
637
+ const gapCount = taintTotals.unsafeSites +
638
+ taintTotals.gapTruncated +
639
+ taintTotals.gapOverflow +
640
+ taintTotals.gapNoFacts;
641
+ if (gapCount > 0 || taintTotals.dropped > 0) {
642
+ const parts = [];
643
+ if (gapCount > 0) {
644
+ parts.push(`${gapCount} function(s) skipped for taint ` +
645
+ `(${taintTotals.gapTruncated} fact-limit, ${taintTotals.gapOverflow} overflow, ` +
646
+ `${taintTotals.gapNoFacts} no-facts, ${taintTotals.unsafeSites} malformed sites)` +
647
+ (taintTotals.gapExamples.length > 0
648
+ ? ` — e.g. ${taintTotals.gapExamples.join(', ')}`
649
+ : ''));
650
+ }
651
+ if (taintTotals.dropped > 0) {
652
+ parts.push(`${taintTotals.dropped} finding(s) dropped by the per-function cap` +
653
+ (taintTotals.dropExamples.length > 0
654
+ ? ` — e.g. ${taintTotals.dropExamples.join(', ')}`
655
+ : ''));
656
+ }
657
+ logger.warn(`[taint] lang=${provider.language}: ${parts.join('; ')}`);
658
+ }
659
+ }
660
+ }
457
661
  if (PROF) {
458
662
  const tEnd = process.hrtime.bigint();
459
663
  const ns = (a, b) => Number(b - a) / 1_000_000;
@@ -462,6 +666,9 @@ export function runScopeResolution(input, provider) {
462
666
  ` propagate=${ns(tFinalize, tPropagate).toFixed(0)}ms` +
463
667
  ` resolve=${ns(tPropagate, tResolve).toFixed(0)}ms` +
464
668
  ` emit=${ns(tResolve, tEnd).toFixed(0)}ms` +
669
+ // pdg ⊆ emit: the M2 reaching-defs share of the emit bucket (#2082 U4).
670
+ // taint ⊆ emit likewise: the M3 match+solve+propagate+emit share (#2083 U4).
671
+ (input.pdg === true ? ` pdg=${pdgMs.toFixed(0)}ms taint=${taintMs.toFixed(0)}ms` : '') +
465
672
  ` total=${ns(tStart, tEnd).toFixed(0)}ms` +
466
673
  ` (${parsedFiles.length} files)`);
467
674
  }
@@ -1,5 +1,5 @@
1
1
  import type { Range } from '../../../_shared/index.js';
2
- export type ResolutionSuppressionReason = 'adl-ordinary-lookup-blocked' | 'conversion-rank-tied' | 'inline-ns-ambiguous' | 'member-lookup-ambiguous' | 'overload-ambiguous' | 'overload-ambiguous-normalization';
2
+ export type ResolutionSuppressionReason = 'adl-ordinary-lookup-blocked' | 'conversion-rank-tied' | 'inline-ns-ambiguous' | 'member-lookup-ambiguous' | 'selected-callable-deleted' | 'overload-ambiguous' | 'overload-ambiguous-normalization';
3
3
  export type ResolutionOutcome = {
4
4
  readonly kind: 'resolved';
5
5
  readonly targetId: string;
@@ -0,0 +1,124 @@
1
+ /**
2
+ * In-phase taint emission (#2083 M3 U4, plan KTD1/KTD6).
3
+ *
4
+ * Per-file driver for the M3 taint pass: gate → match → solve → propagate →
5
+ * persist sparse `TAINTED` + `SANITIZES` edges. Invoked from the pdg window in
6
+ * scope-resolution (`pipeline/run.ts`), immediately after `emitFileReachingDefs`
7
+ * inside the SAME per-file try — per-file isolation for free (KTD1). Mirrors
8
+ * `emitFileReachingDefs` (cfg/emit.ts) for the budget/dedup/warn discipline and
9
+ * the telemetry-result shape.
10
+ *
11
+ * ## Per-function pipeline (ordering is load-bearing)
12
+ *
13
+ * 1. `hasTaintSafeSites` — a corrupted-store site annotation degrades to
14
+ * SKIP-TAINT-KEEP-RD for this function (counted + warned), never a crash
15
+ * (KTD2; the matcher/propagator dereference indices unvalidated).
16
+ * 2. `matchFunctionSites` against the language spec (the import index is
17
+ * built ONCE per file — imports are a file-level fact).
18
+ * 3. ZERO-MATCH FAST PATH: the solver runs only when the function has at
19
+ * least one matched source AND one matched sink. In a typical repo almost
20
+ * no function has both; an unconditional second `computeReachingDefs` per
21
+ * function would ship a near-2× solve cost to every `--pdg` user.
22
+ * 4. `computeReachingDefs` with the taint `maxFacts` — by DEFAULT the M2
23
+ * derived `DEFAULT_PDG_MAX_REACHING_DEF_FACTS_PER_FUNCTION` (deliberate
24
+ * reuse, not a new constant: the fact-materialization envelope is a
25
+ * memory question, O(defs×uses), orthogonal to the findings cap, and M2
26
+ * already validated exactly this envelope on the same solver in the same
27
+ * window). The run.ts caller derives `limits.maxFacts` from the SAME
28
+ * RD-edge-cap formula `emitFileReachingDefs` uses, so taint coverage and
29
+ * RD coverage truncate together — a function is never `truncated` for one
30
+ * layer and `computed` for the other.
31
+ * 5. `computeTaintFlows` — a non-`computed` status is a per-function
32
+ * COVERAGE GAP (R4: counted by `gapReason`, function skipped entirely,
33
+ * never partially analyzed).
34
+ * 6. Emit one `TAINTED` edge per finding and one `SANITIZES` edge per kill.
35
+ * Kills are emitted even when findings are zero — a fully-sanitized
36
+ * function's kills are exactly its evidence of safety.
37
+ *
38
+ * ## Identity, dedup, budget (KTD6)
39
+ *
40
+ * Findings carry STATEMENT-LEVEL identity — function anchor + sink kind +
41
+ * source occurrence (point/site/object-binding/property) + sink occurrence
42
+ * (point/site/arg/binding) — NOT the REACHING_DEF block-level key (block-pair
43
+ * conflation would drop `exec(req.body, req.query)`'s second finding). The
44
+ * propagation engine dedups by this exact key BEFORE its deterministic cap
45
+ * (`maxFindingsPerFunction`) and counts the overflow; this module templates
46
+ * the same coordinates into the edge id (binding identity via the shared
47
+ * `bindingKey`; the free-text `property` rides LAST so it can never collide
48
+ * into another component) and warns with the drop count on truncation.
49
+ *
50
+ * `reason` carries the versioned hop encoding (`taint/path-codec.ts` — U6's
51
+ * `explain` decodes the same module) for `TAINTED`, and the killed binding's
52
+ * plain name for `SANITIZES` (M0/S1 queryability verdict, like REACHING_DEF).
53
+ *
54
+ * ## Warn split (R4 vs noise)
55
+ *
56
+ * Unsafe-site skips and cap drops warn PER FUNCTION here (rare, actionable —
57
+ * mirrors `emitFileReachingDefs`' malformed/cap warns). Solver coverage gaps
58
+ * (`truncated`/`overflow`) do NOT re-warn per function: the RD layer already
59
+ * warned for the same function with the same solver status (same `maxFacts`
60
+ * derivation — see step 4), and a duplicate `[taint]` line per mega-function
61
+ * would be pure spam. They are counted (+ exampled) in the result and the
62
+ * run.ts caller aggregates them into ONE unconditional `logger.warn` per
63
+ * language (R4) — never dropped on the floor (the M2 lesson).
64
+ */
65
+ import type { ParsedImport } from '../../../_shared/index.js';
66
+ import type { KnowledgeGraph } from '../../graph/types.js';
67
+ import type { FunctionCfg } from '../cfg/types.js';
68
+ export { DEFAULT_PDG_MAX_TAINT_FINDINGS_PER_FUNCTION, DEFAULT_PDG_MAX_TAINT_HOPS, } from './propagate.js';
69
+ import type { SourceSinkSanitizerSpec } from './source-sink-config.js';
70
+ export interface TaintEmitLimits {
71
+ /** Per-function findings cap (post-dedup). `undefined` ⇒
72
+ * {@link DEFAULT_PDG_MAX_TAINT_FINDINGS_PER_FUNCTION}; `0` ⇒ unlimited. */
73
+ readonly maxFindingsPerFunction?: number;
74
+ /** Per-finding hop cap (source-side prefix kept). `undefined` ⇒
75
+ * {@link DEFAULT_PDG_MAX_TAINT_HOPS}; `0` ⇒ unlimited. */
76
+ readonly maxHops?: number;
77
+ /**
78
+ * Solver fact-materialization cap for the taint-side `computeReachingDefs`
79
+ * call. `undefined` ⇒ {@link DEFAULT_PDG_MAX_REACHING_DEF_FACTS_PER_FUNCTION}
80
+ * (the M2 derived default — see the module doc for why it is REUSED rather
81
+ * than derived from the findings cap); `0` ⇒ unlimited.
82
+ */
83
+ readonly maxFacts?: number;
84
+ }
85
+ /**
86
+ * Full taint-emit telemetry for one file. EVERY counter is surfaced by the
87
+ * run.ts aggregate (the M2 emit result had two fields dropped on the floor —
88
+ * the plan names that mistake; don't repeat it).
89
+ */
90
+ export interface TaintEmitResult {
91
+ /** Functions fully propagated (`computeTaintFlows` returned `computed`). */
92
+ functionsAnalyzed: number;
93
+ /** Functions skipped by the zero-match fast path (no solver call). */
94
+ functionsSkippedNoMatch: number;
95
+ /** Functions whose `sites` failed {@link hasTaintSafeSites} (skip-taint-keep-RD). */
96
+ functionsSkippedUnsafeSites: number;
97
+ /** Source+sink functions skipped on a non-`computed` solver status (R4). */
98
+ functionsCoverageGap: {
99
+ truncated: number;
100
+ overflow: number;
101
+ 'no-facts': number;
102
+ };
103
+ /** TAINTED edges persisted. */
104
+ findingsEmitted: number;
105
+ /** SANITIZES edges persisted (emitted even when findings are zero). */
106
+ killsEmitted: number;
107
+ /** Findings dropped by the per-function cap (post-dedup), summed. */
108
+ findingsDropped: number;
109
+ /** Findings whose persisted hop path is a truncated prefix (hop/byte cap). */
110
+ hopsTruncatedFindings: number;
111
+ /** ≤{@link MAX_EXAMPLES} `file:line` anchors of gap/unsafe-site functions. */
112
+ coverageGapExamples: string[];
113
+ /** ≤{@link MAX_EXAMPLES} `file:line` anchors of cap-dropped functions. */
114
+ droppedExamples: string[];
115
+ }
116
+ /**
117
+ * Run the taint pass over one file's emit-safe CFGs and persist TAINTED +
118
+ * SANITIZES edges. `cfgs` MUST already be `isEmitSafeCfg`-filtered (the same
119
+ * `wellFormed` array the caller fed `emitFileCfgs`/`emitFileReachingDefs`) —
120
+ * block/edge anchors are trusted here; only the M3 `sites` layer is
121
+ * re-validated (`hasTaintSafeSites`). Never throws on well-formed input;
122
+ * the caller's per-file try isolates the rest.
123
+ */
124
+ export declare function emitFileTaint(graph: KnowledgeGraph, cfgs: readonly FunctionCfg[], parsedImports: readonly ParsedImport[], spec: SourceSinkSanitizerSpec, limits?: TaintEmitLimits, onWarn?: (message: string) => void): TaintEmitResult;