gitnexus 1.6.10-rc.21 → 1.6.10-rc.211

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 (870) hide show
  1. package/README.md +212 -22
  2. package/dist/_shared/graph/types.d.ts +27 -8
  3. package/dist/_shared/graph/types.d.ts.map +1 -1
  4. package/dist/_shared/index.d.ts +4 -3
  5. package/dist/_shared/index.d.ts.map +1 -1
  6. package/dist/_shared/index.js +1 -1
  7. package/dist/_shared/index.js.map +1 -1
  8. package/dist/_shared/integrations/circuit-breaker.d.ts.map +1 -1
  9. package/dist/_shared/integrations/circuit-breaker.js.map +1 -1
  10. package/dist/_shared/integrations/resilient-fetch.d.ts +19 -1
  11. package/dist/_shared/integrations/resilient-fetch.d.ts.map +1 -1
  12. package/dist/_shared/integrations/resilient-fetch.js +23 -10
  13. package/dist/_shared/integrations/resilient-fetch.js.map +1 -1
  14. package/dist/_shared/language-detection.d.ts.map +1 -1
  15. package/dist/_shared/lbug/schema-constants.d.ts +2 -2
  16. package/dist/_shared/lbug/schema-constants.d.ts.map +1 -1
  17. package/dist/_shared/lbug/schema-constants.js +3 -0
  18. package/dist/_shared/lbug/schema-constants.js.map +1 -1
  19. package/dist/_shared/scope-resolution/callable-flow-site.d.ts +110 -0
  20. package/dist/_shared/scope-resolution/callable-flow-site.d.ts.map +1 -0
  21. package/dist/_shared/scope-resolution/callable-flow-site.js +11 -0
  22. package/dist/_shared/scope-resolution/callable-flow-site.js.map +1 -0
  23. package/dist/_shared/scope-resolution/evidence-weights.d.ts.map +1 -1
  24. package/dist/_shared/scope-resolution/finalize-algorithm.d.ts +6 -1
  25. package/dist/_shared/scope-resolution/finalize-algorithm.d.ts.map +1 -1
  26. package/dist/_shared/scope-resolution/finalize-algorithm.js +316 -37
  27. package/dist/_shared/scope-resolution/finalize-algorithm.js.map +1 -1
  28. package/dist/_shared/scope-resolution/parsed-file.d.ts +7 -0
  29. package/dist/_shared/scope-resolution/parsed-file.d.ts.map +1 -1
  30. package/dist/_shared/scope-resolution/reference-site.d.ts +124 -1
  31. package/dist/_shared/scope-resolution/reference-site.d.ts.map +1 -1
  32. package/dist/_shared/scope-resolution/registries/field-registry.js.map +1 -1
  33. package/dist/_shared/scope-resolution/registries/lookup-core.d.ts.map +1 -1
  34. package/dist/_shared/scope-resolution/registries/lookup-core.js +58 -2
  35. package/dist/_shared/scope-resolution/registries/lookup-core.js.map +1 -1
  36. package/dist/_shared/scope-resolution/registries/method-registry.js.map +1 -1
  37. package/dist/_shared/scope-resolution/scope-tree.d.ts.map +1 -1
  38. package/dist/_shared/scope-resolution/scope-tree.js.map +1 -1
  39. package/dist/_shared/scope-resolution/symbol-definition.d.ts +47 -0
  40. package/dist/_shared/scope-resolution/symbol-definition.d.ts.map +1 -1
  41. package/dist/_shared/scope-resolution/types.d.ts +297 -5
  42. package/dist/_shared/scope-resolution/types.d.ts.map +1 -1
  43. package/dist/cli/ai-context.d.ts +10 -2
  44. package/dist/cli/ai-context.js +128 -61
  45. package/dist/cli/analyze-config.d.ts +1 -1
  46. package/dist/cli/analyze-options.d.ts +131 -0
  47. package/dist/cli/analyze-options.js +1 -0
  48. package/dist/cli/analyze.d.ts +25 -114
  49. package/dist/cli/analyze.js +194 -36
  50. package/dist/cli/cli-message.d.ts +1 -1
  51. package/dist/cli/detect-changes-format.js +19 -2
  52. package/dist/cli/doctor.d.ts +23 -0
  53. package/dist/cli/doctor.js +72 -9
  54. package/dist/cli/editor-targets.js +6 -0
  55. package/dist/cli/eval-server.d.ts +23 -1
  56. package/dist/cli/eval-server.js +128 -9
  57. package/dist/cli/format-symbol.d.ts +15 -0
  58. package/dist/cli/format-symbol.js +17 -0
  59. package/dist/cli/generated-skill.d.ts +17 -0
  60. package/dist/cli/generated-skill.js +1 -0
  61. package/dist/cli/group.js +23 -2
  62. package/dist/cli/help-i18n.js +3 -0
  63. package/dist/cli/i18n/en.d.ts +17 -8
  64. package/dist/cli/i18n/en.js +19 -8
  65. package/dist/cli/i18n/resources.d.ts +26 -8
  66. package/dist/cli/i18n/zh-CN.d.ts +9 -0
  67. package/dist/cli/i18n/zh-CN.js +17 -8
  68. package/dist/cli/index.js +16 -9
  69. package/dist/cli/lazy-action.d.ts +7 -0
  70. package/dist/cli/lazy-action.js +27 -0
  71. package/dist/cli/mcp.js +6 -3
  72. package/dist/cli/resolve-invocation.d.ts +7 -3
  73. package/dist/cli/resolve-invocation.js +7 -3
  74. package/dist/cli/setup.d.ts +8 -0
  75. package/dist/cli/setup.js +30 -7
  76. package/dist/cli/skill-gen.d.ts +1 -6
  77. package/dist/cli/skill-gen.js +88 -18
  78. package/dist/cli/standard-skills.d.ts +76 -0
  79. package/dist/cli/standard-skills.js +45 -0
  80. package/dist/cli/status.d.ts +4 -1
  81. package/dist/cli/status.js +57 -4
  82. package/dist/cli/tool.d.ts +1 -0
  83. package/dist/cli/tool.js +75 -7
  84. package/dist/cli/uninstall.js +7 -2
  85. package/dist/cli/wiki.d.ts +1 -0
  86. package/dist/cli/wiki.js +59 -10
  87. package/dist/config/ignore-service.d.ts +2 -0
  88. package/dist/config/ignore-service.js +31 -4
  89. package/dist/core/analysis-features.d.ts +21 -0
  90. package/dist/core/analysis-features.js +59 -0
  91. package/dist/core/analyzer-identity.d.ts +154 -0
  92. package/dist/core/analyzer-identity.js +2158 -0
  93. package/dist/core/augmentation/engine.js +95 -76
  94. package/dist/core/embedding-checkpoint.d.ts +143 -0
  95. package/dist/core/embedding-checkpoint.js +221 -0
  96. package/dist/core/embedding-count.d.ts +61 -0
  97. package/dist/core/embedding-count.js +69 -0
  98. package/dist/core/embeddings/ast-utils.js +9 -1
  99. package/dist/core/embeddings/embedder.d.ts +3 -2
  100. package/dist/core/embeddings/embedder.js +8 -5
  101. package/dist/core/embeddings/embedding-identity.d.ts +11 -0
  102. package/dist/core/embeddings/embedding-identity.js +24 -0
  103. package/dist/core/embeddings/embedding-pipeline.d.ts +24 -1
  104. package/dist/core/embeddings/embedding-pipeline.js +341 -19
  105. package/dist/core/embeddings/http-client.d.ts +10 -2
  106. package/dist/core/embeddings/http-client.js +282 -47
  107. package/dist/core/embeddings/node-module-compat.d.ts +4 -2
  108. package/dist/core/embeddings/node-module-compat.js +4 -2
  109. package/dist/core/embeddings/onnxruntime-common-resolver.js +5 -5
  110. package/dist/core/embeddings/onnxruntime-node-resolver.js +2 -2
  111. package/dist/core/embeddings/runtime-install.js +1 -1
  112. package/dist/core/git-staleness.js +1 -1
  113. package/dist/core/graph/graph.js +3 -0
  114. package/dist/core/graph/import-cycles.d.ts +194 -3
  115. package/dist/core/graph/import-cycles.js +522 -73
  116. package/dist/core/graph/types.d.ts +18 -0
  117. package/dist/core/group/bridge-db.js +5 -27
  118. package/dist/core/group/cross-impact.d.ts +21 -0
  119. package/dist/core/group/cross-impact.js +128 -15
  120. package/dist/core/group/cross-trace.js +27 -16
  121. package/dist/core/group/extractors/http-patterns/java.js +91 -14
  122. package/dist/core/group/extractors/http-patterns/node.js +19 -0
  123. package/dist/core/group/extractors/http-patterns/types.d.ts +8 -0
  124. package/dist/core/group/extractors/http-route-extractor.d.ts +1 -0
  125. package/dist/core/group/extractors/http-route-extractor.js +122 -48
  126. package/dist/core/group/extractors/include-extractor.js +20 -2
  127. package/dist/core/group/extractors/manifest-extractor.d.ts +1 -1
  128. package/dist/core/group/extractors/manifest-extractor.js +7 -7
  129. package/dist/core/group/service.js +10 -1
  130. package/dist/core/group/storage.js +2 -33
  131. package/dist/core/group/types.d.ts +18 -0
  132. package/dist/core/incremental/subgraph-extract.d.ts +9 -6
  133. package/dist/core/incremental/subgraph-extract.js +33 -10
  134. package/dist/core/index-freshness.d.ts +80 -0
  135. package/dist/core/index-freshness.js +120 -0
  136. package/dist/core/ingestion/call-processor.d.ts +13 -1
  137. package/dist/core/ingestion/call-processor.js +110 -3
  138. package/dist/core/ingestion/cfg/callee-cell-format.d.ts +42 -0
  139. package/dist/core/ingestion/cfg/callee-cell-format.js +42 -0
  140. package/dist/core/ingestion/cfg/emit.d.ts +8 -23
  141. package/dist/core/ingestion/cfg/emit.js +9 -23
  142. package/dist/core/ingestion/class-extractors/configs/go.js +10 -5
  143. package/dist/core/ingestion/class-extractors/configs/jvm.js +29 -0
  144. package/dist/core/ingestion/cluster-enricher.js +8 -4
  145. package/dist/core/ingestion/cobol/cobol-preprocessor.js +1 -1
  146. package/dist/core/ingestion/community-processor.d.ts +21 -3
  147. package/dist/core/ingestion/community-processor.js +88 -83
  148. package/dist/core/ingestion/di-extractors/index.d.ts +22 -34
  149. package/dist/core/ingestion/di-extractors/index.js +17 -18
  150. package/dist/core/ingestion/di-extractors/spring.d.ts +27 -5
  151. package/dist/core/ingestion/di-extractors/spring.js +87 -3
  152. package/dist/core/ingestion/di-extractors/types.d.ts +60 -0
  153. package/dist/core/ingestion/di-extractors/types.js +15 -0
  154. package/dist/core/ingestion/emit-references.js +4 -0
  155. package/dist/core/ingestion/filesystem-walker.js +16 -12
  156. package/dist/core/ingestion/finalize-orchestrator.js +1 -0
  157. package/dist/core/ingestion/frameworks/spring/analysis-features.d.ts +9 -0
  158. package/dist/core/ingestion/frameworks/spring/analysis-features.js +41 -0
  159. package/dist/core/ingestion/frameworks/spring/annotation-arguments.d.ts +13 -0
  160. package/dist/core/ingestion/frameworks/spring/annotation-arguments.js +227 -0
  161. package/dist/core/ingestion/frameworks/spring/aop-candidates.d.ts +15 -0
  162. package/dist/core/ingestion/frameworks/spring/aop-candidates.js +85 -0
  163. package/dist/core/ingestion/frameworks/spring/aop.d.ts +118 -0
  164. package/dist/core/ingestion/frameworks/spring/aop.js +476 -0
  165. package/dist/core/ingestion/frameworks/spring/auto-configuration.d.ts +8 -0
  166. package/dist/core/ingestion/frameworks/spring/auto-configuration.js +16 -0
  167. package/dist/core/ingestion/frameworks/spring/bean-candidates.d.ts +30 -0
  168. package/dist/core/ingestion/frameworks/spring/bean-candidates.js +182 -0
  169. package/dist/core/ingestion/frameworks/spring/bean-catalog.d.ts +12 -0
  170. package/dist/core/ingestion/frameworks/spring/bean-catalog.js +27 -0
  171. package/dist/core/ingestion/frameworks/spring/bean-factories.d.ts +33 -0
  172. package/dist/core/ingestion/frameworks/spring/bean-factories.js +65 -0
  173. package/dist/core/ingestion/frameworks/spring/conditionals.d.ts +25 -0
  174. package/dist/core/ingestion/frameworks/spring/conditionals.js +319 -0
  175. package/dist/core/ingestion/frameworks/spring/config-bindings.d.ts +24 -0
  176. package/dist/core/ingestion/frameworks/spring/config-bindings.js +115 -0
  177. package/dist/core/ingestion/frameworks/spring/di-metadata.d.ts +48 -0
  178. package/dist/core/ingestion/frameworks/spring/di-metadata.js +379 -0
  179. package/dist/core/ingestion/frameworks/spring/non-http-handlers.d.ts +28 -0
  180. package/dist/core/ingestion/frameworks/spring/non-http-handlers.js +152 -0
  181. package/dist/core/ingestion/frameworks/spring/resource-injection.d.ts +5 -0
  182. package/dist/core/ingestion/frameworks/spring/resource-injection.js +91 -0
  183. package/dist/core/ingestion/import-resolvers/configs/csharp.js +5 -1
  184. package/dist/core/ingestion/import-resolvers/configs/swift.js +13 -0
  185. package/dist/core/ingestion/import-resolvers/csharp.d.ts +9 -1
  186. package/dist/core/ingestion/import-resolvers/csharp.js +234 -27
  187. package/dist/core/ingestion/import-resolvers/go.d.ts +11 -1
  188. package/dist/core/ingestion/import-resolvers/go.js +44 -17
  189. package/dist/core/ingestion/import-resolvers/jvm.d.ts +2 -2
  190. package/dist/core/ingestion/import-resolvers/node-workspace-packages.d.ts +90 -0
  191. package/dist/core/ingestion/import-resolvers/node-workspace-packages.js +428 -0
  192. package/dist/core/ingestion/import-resolvers/package-dir-index.d.ts +124 -0
  193. package/dist/core/ingestion/import-resolvers/package-dir-index.js +223 -0
  194. package/dist/core/ingestion/import-resolvers/pass-cache.d.ts +50 -0
  195. package/dist/core/ingestion/import-resolvers/pass-cache.js +60 -0
  196. package/dist/core/ingestion/import-resolvers/per-file-set.d.ts +71 -0
  197. package/dist/core/ingestion/import-resolvers/per-file-set.js +11 -0
  198. package/dist/core/ingestion/import-resolvers/php.d.ts +1 -1
  199. package/dist/core/ingestion/import-resolvers/php.js +27 -8
  200. package/dist/core/ingestion/import-resolvers/python-file-index.d.ts +201 -0
  201. package/dist/core/ingestion/import-resolvers/python-file-index.js +302 -0
  202. package/dist/core/ingestion/import-resolvers/python.js +25 -6
  203. package/dist/core/ingestion/import-resolvers/ruby.d.ts +1 -1
  204. package/dist/core/ingestion/import-resolvers/rust.d.ts +2 -2
  205. package/dist/core/ingestion/import-resolvers/standard.d.ts +1 -1
  206. package/dist/core/ingestion/import-resolvers/types.d.ts +1 -12
  207. package/dist/core/ingestion/import-resolvers/utils.d.ts +65 -6
  208. package/dist/core/ingestion/import-resolvers/utils.js +215 -36
  209. package/dist/core/ingestion/import-resolvers/workspace-file-index.d.ts +72 -0
  210. package/dist/core/ingestion/import-resolvers/workspace-file-index.js +58 -0
  211. package/dist/core/ingestion/language-config.d.ts +20 -1
  212. package/dist/core/ingestion/language-config.js +18 -23
  213. package/dist/core/ingestion/language-provider.d.ts +133 -7
  214. package/dist/core/ingestion/languages/c/captures.js +63 -0
  215. package/dist/core/ingestion/languages/c/import-target.js +12 -16
  216. package/dist/core/ingestion/languages/c/query.js +17 -0
  217. package/dist/core/ingestion/languages/c/scope-resolver.js +26 -19
  218. package/dist/core/ingestion/languages/c/static-linkage.js +12 -16
  219. package/dist/core/ingestion/languages/c-cpp.js +13 -0
  220. package/dist/core/ingestion/languages/cobol/captures.js +93 -0
  221. package/dist/core/ingestion/languages/cobol/scope-resolver.js +34 -20
  222. package/dist/core/ingestion/languages/cobol.js +13 -0
  223. package/dist/core/ingestion/languages/cpp/adl.d.ts +2 -2
  224. package/dist/core/ingestion/languages/cpp/adl.js +67 -44
  225. package/dist/core/ingestion/languages/cpp/captures.js +222 -3
  226. package/dist/core/ingestion/languages/cpp/file-local-linkage.js +11 -15
  227. package/dist/core/ingestion/languages/cpp/inline-namespaces.d.ts +28 -8
  228. package/dist/core/ingestion/languages/cpp/inline-namespaces.js +242 -68
  229. package/dist/core/ingestion/languages/cpp/interpret.d.ts +2 -0
  230. package/dist/core/ingestion/languages/cpp/interpret.js +126 -6
  231. package/dist/core/ingestion/languages/cpp/query.js +106 -0
  232. package/dist/core/ingestion/languages/cpp/scope-resolver.js +62 -20
  233. package/dist/core/ingestion/languages/csharp/accessor-unwrap.d.ts +2 -15
  234. package/dist/core/ingestion/languages/csharp/accessor-unwrap.js +18 -10
  235. package/dist/core/ingestion/languages/csharp/captures.js +44 -0
  236. package/dist/core/ingestion/languages/csharp/import-target.js +46 -72
  237. package/dist/core/ingestion/languages/csharp/namespace-siblings.js +5 -1
  238. package/dist/core/ingestion/languages/csharp/query.js +25 -5
  239. package/dist/core/ingestion/languages/csharp/scope-resolver.js +75 -2
  240. package/dist/core/ingestion/languages/dart/captures.d.ts +10 -0
  241. package/dist/core/ingestion/languages/dart/captures.js +686 -16
  242. package/dist/core/ingestion/languages/dart/extension-type-preprocess.d.ts +7 -0
  243. package/dist/core/ingestion/languages/dart/extension-type-preprocess.js +30 -0
  244. package/dist/core/ingestion/languages/dart/import-target.js +32 -9
  245. package/dist/core/ingestion/languages/dart/index.d.ts +1 -1
  246. package/dist/core/ingestion/languages/dart/index.js +1 -1
  247. package/dist/core/ingestion/languages/dart/query.js +98 -1
  248. package/dist/core/ingestion/languages/dart/scope-resolver.js +14 -3
  249. package/dist/core/ingestion/languages/dart/simple-hooks.js +9 -0
  250. package/dist/core/ingestion/languages/dart.js +8 -1
  251. package/dist/core/ingestion/languages/go/captures.js +142 -3
  252. package/dist/core/ingestion/languages/go/generic-type-parameters.d.ts +31 -0
  253. package/dist/core/ingestion/languages/go/generic-type-parameters.js +140 -0
  254. package/dist/core/ingestion/languages/go/import-target.d.ts +3 -10
  255. package/dist/core/ingestion/languages/go/import-target.js +34 -30
  256. package/dist/core/ingestion/languages/go/index.d.ts +1 -1
  257. package/dist/core/ingestion/languages/go/interface-impls.d.ts +9 -1
  258. package/dist/core/ingestion/languages/go/interface-impls.js +693 -87
  259. package/dist/core/ingestion/languages/go/interpret.d.ts +2 -0
  260. package/dist/core/ingestion/languages/go/interpret.js +22 -8
  261. package/dist/core/ingestion/languages/go/method-owners.js +31 -12
  262. package/dist/core/ingestion/languages/go/package-clause.d.ts +46 -0
  263. package/dist/core/ingestion/languages/go/package-clause.js +75 -0
  264. package/dist/core/ingestion/languages/go/package-siblings.js +7 -11
  265. package/dist/core/ingestion/languages/go/query.js +56 -3
  266. package/dist/core/ingestion/languages/go/scope-resolver.js +39 -0
  267. package/dist/core/ingestion/languages/go.js +9 -0
  268. package/dist/core/ingestion/languages/java/analysis-features.d.ts +7 -0
  269. package/dist/core/ingestion/languages/java/analysis-features.js +25 -0
  270. package/dist/core/ingestion/languages/java/capture-side-channel.d.ts +38 -0
  271. package/dist/core/ingestion/languages/java/capture-side-channel.js +124 -0
  272. package/dist/core/ingestion/languages/java/captures.d.ts +4 -3
  273. package/dist/core/ingestion/languages/java/captures.js +321 -49
  274. package/dist/core/ingestion/languages/java/import-target.d.ts +49 -9
  275. package/dist/core/ingestion/languages/java/import-target.js +68 -88
  276. package/dist/core/ingestion/languages/java/interpret.js +3 -2
  277. package/dist/core/ingestion/languages/java/module-resolution.d.ts +62 -0
  278. package/dist/core/ingestion/languages/java/module-resolution.js +142 -0
  279. package/dist/core/ingestion/languages/java/package-facts.d.ts +5 -0
  280. package/dist/core/ingestion/languages/java/package-facts.js +9 -0
  281. package/dist/core/ingestion/languages/java/package-siblings.d.ts +3 -16
  282. package/dist/core/ingestion/languages/java/package-siblings.js +8 -147
  283. package/dist/core/ingestion/languages/java/query.js +39 -3
  284. package/dist/core/ingestion/languages/java/record-components.d.ts +14 -0
  285. package/dist/core/ingestion/languages/java/record-components.js +185 -0
  286. package/dist/core/ingestion/languages/java/scope-resolver.js +35 -2
  287. package/dist/core/ingestion/languages/java/spring-aop.d.ts +12 -0
  288. package/dist/core/ingestion/languages/java/spring-aop.js +49 -0
  289. package/dist/core/ingestion/languages/java/spring-bean-metadata.d.ts +2 -0
  290. package/dist/core/ingestion/languages/java/spring-bean-metadata.js +8 -0
  291. package/dist/core/ingestion/languages/java/spring-conditionals.d.ts +11 -0
  292. package/dist/core/ingestion/languages/java/spring-conditionals.js +48 -0
  293. package/dist/core/ingestion/languages/java/spring-config-bindings.d.ts +23 -0
  294. package/dist/core/ingestion/languages/java/spring-config-bindings.js +215 -0
  295. package/dist/core/ingestion/languages/java/spring-di.d.ts +20 -0
  296. package/dist/core/ingestion/languages/java/spring-di.js +160 -0
  297. package/dist/core/ingestion/languages/java/spring-non-http-handlers.d.ts +7 -0
  298. package/dist/core/ingestion/languages/java/spring-non-http-handlers.js +32 -0
  299. package/dist/core/ingestion/languages/java.js +76 -3
  300. package/dist/core/ingestion/languages/javascript/captures.d.ts +6 -1
  301. package/dist/core/ingestion/languages/javascript/captures.js +278 -4
  302. package/dist/core/ingestion/languages/javascript/import-target.d.ts +17 -21
  303. package/dist/core/ingestion/languages/javascript/import-target.js +24 -41
  304. package/dist/core/ingestion/languages/javascript/index.d.ts +54 -5
  305. package/dist/core/ingestion/languages/javascript/index.js +54 -5
  306. package/dist/core/ingestion/languages/javascript/query.d.ts +1 -0
  307. package/dist/core/ingestion/languages/javascript/query.js +232 -5
  308. package/dist/core/ingestion/languages/javascript/scope-resolver.js +12 -0
  309. package/dist/core/ingestion/languages/javascript/simple-hooks.js +2 -1
  310. package/dist/core/ingestion/languages/jvm/package-facts.d.ts +35 -0
  311. package/dist/core/ingestion/languages/jvm/package-facts.js +40 -0
  312. package/dist/core/ingestion/languages/jvm/package-siblings.d.ts +14 -0
  313. package/dist/core/ingestion/languages/jvm/package-siblings.js +197 -0
  314. package/dist/core/ingestion/languages/kotlin/capture-side-channel.d.ts +40 -3
  315. package/dist/core/ingestion/languages/kotlin/capture-side-channel.js +111 -8
  316. package/dist/core/ingestion/languages/kotlin/captures.js +112 -0
  317. package/dist/core/ingestion/languages/kotlin/import-target.d.ts +8 -1
  318. package/dist/core/ingestion/languages/kotlin/import-target.js +20 -123
  319. package/dist/core/ingestion/languages/kotlin/index.d.ts +1 -0
  320. package/dist/core/ingestion/languages/kotlin/index.js +1 -0
  321. package/dist/core/ingestion/languages/kotlin/module-resolution.d.ts +22 -0
  322. package/dist/core/ingestion/languages/kotlin/module-resolution.js +108 -0
  323. package/dist/core/ingestion/languages/kotlin/package-facts.d.ts +5 -0
  324. package/dist/core/ingestion/languages/kotlin/package-facts.js +9 -0
  325. package/dist/core/ingestion/languages/kotlin/package-siblings.d.ts +4 -0
  326. package/dist/core/ingestion/languages/kotlin/package-siblings.js +8 -0
  327. package/dist/core/ingestion/languages/kotlin/query.js +47 -2
  328. package/dist/core/ingestion/languages/kotlin/scope-resolver.js +36 -3
  329. package/dist/core/ingestion/languages/kotlin/spring-aop.d.ts +12 -0
  330. package/dist/core/ingestion/languages/kotlin/spring-aop.js +64 -0
  331. package/dist/core/ingestion/languages/kotlin/spring-bean-metadata.d.ts +2 -0
  332. package/dist/core/ingestion/languages/kotlin/spring-bean-metadata.js +8 -0
  333. package/dist/core/ingestion/languages/kotlin/spring-conditionals.d.ts +12 -0
  334. package/dist/core/ingestion/languages/kotlin/spring-conditionals.js +49 -0
  335. package/dist/core/ingestion/languages/kotlin/spring-di.d.ts +20 -0
  336. package/dist/core/ingestion/languages/kotlin/spring-di.js +299 -0
  337. package/dist/core/ingestion/languages/kotlin/spring-non-http-handlers.d.ts +10 -0
  338. package/dist/core/ingestion/languages/kotlin/spring-non-http-handlers.js +41 -0
  339. package/dist/core/ingestion/languages/kotlin.js +5 -6
  340. package/dist/core/ingestion/languages/php/captures.d.ts +5 -0
  341. package/dist/core/ingestion/languages/php/captures.js +359 -51
  342. package/dist/core/ingestion/languages/php/import-decomposer.js +12 -0
  343. package/dist/core/ingestion/languages/php/import-target.d.ts +2 -1
  344. package/dist/core/ingestion/languages/php/import-target.js +229 -7
  345. package/dist/core/ingestion/languages/php/interpret.js +10 -0
  346. package/dist/core/ingestion/languages/php/query.js +31 -0
  347. package/dist/core/ingestion/languages/php/scope-resolver.js +1 -1
  348. package/dist/core/ingestion/languages/python/arity-metadata.d.ts +1 -0
  349. package/dist/core/ingestion/languages/python/arity-metadata.js +1 -0
  350. package/dist/core/ingestion/languages/python/captures.d.ts +3 -4
  351. package/dist/core/ingestion/languages/python/captures.js +53 -5
  352. package/dist/core/ingestion/languages/python/import-decomposer.js +22 -1
  353. package/dist/core/ingestion/languages/python/import-target.d.ts +42 -1
  354. package/dist/core/ingestion/languages/python/import-target.js +277 -91
  355. package/dist/core/ingestion/languages/python/index.d.ts +1 -1
  356. package/dist/core/ingestion/languages/python/index.js +1 -1
  357. package/dist/core/ingestion/languages/python/interpret.d.ts +52 -0
  358. package/dist/core/ingestion/languages/python/interpret.js +249 -3
  359. package/dist/core/ingestion/languages/python/query.js +9 -0
  360. package/dist/core/ingestion/languages/python/receiver-binding.d.ts +17 -2
  361. package/dist/core/ingestion/languages/python/receiver-binding.js +192 -2
  362. package/dist/core/ingestion/languages/python/scope-resolver.js +27 -4
  363. package/dist/core/ingestion/languages/python/simple-hooks.d.ts +5 -5
  364. package/dist/core/ingestion/languages/python/simple-hooks.js +9 -5
  365. package/dist/core/ingestion/languages/ruby/captures.js +200 -0
  366. package/dist/core/ingestion/languages/ruby/import-target.js +7 -5
  367. package/dist/core/ingestion/languages/ruby/query.js +93 -0
  368. package/dist/core/ingestion/languages/ruby/scope-resolver.js +2 -0
  369. package/dist/core/ingestion/languages/ruby/simple-hooks.d.ts +1 -1
  370. package/dist/core/ingestion/languages/ruby/simple-hooks.js +22 -1
  371. package/dist/core/ingestion/languages/rust/captures.js +52 -1
  372. package/dist/core/ingestion/languages/rust/interpret.js +18 -0
  373. package/dist/core/ingestion/languages/rust/method-owners.js +11 -1
  374. package/dist/core/ingestion/languages/rust/module-path.d.ts +126 -0
  375. package/dist/core/ingestion/languages/rust/module-path.js +214 -0
  376. package/dist/core/ingestion/languages/rust/qualified-call.d.ts +41 -0
  377. package/dist/core/ingestion/languages/rust/qualified-call.js +481 -0
  378. package/dist/core/ingestion/languages/rust/query.js +48 -6
  379. package/dist/core/ingestion/languages/rust/range-binding.d.ts +1 -11
  380. package/dist/core/ingestion/languages/rust/range-binding.js +149 -63
  381. package/dist/core/ingestion/languages/rust/scope-resolver.js +21 -2
  382. package/dist/core/ingestion/languages/rust.js +17 -0
  383. package/dist/core/ingestion/languages/swift/captures.js +99 -3
  384. package/dist/core/ingestion/languages/swift/conditional-directive-preprocess.d.ts +48 -0
  385. package/dist/core/ingestion/languages/swift/conditional-directive-preprocess.js +253 -0
  386. package/dist/core/ingestion/languages/swift/import-target.js +4 -9
  387. package/dist/core/ingestion/languages/swift/query.js +17 -0
  388. package/dist/core/ingestion/languages/swift/receiver-binding.d.ts +8 -0
  389. package/dist/core/ingestion/languages/swift/receiver-binding.js +6 -2
  390. package/dist/core/ingestion/languages/swift.js +2 -0
  391. package/dist/core/ingestion/languages/typescript/captures.d.ts +90 -1
  392. package/dist/core/ingestion/languages/typescript/captures.js +288 -5
  393. package/dist/core/ingestion/languages/typescript/cjs-export-assignment.d.ts +134 -0
  394. package/dist/core/ingestion/languages/typescript/cjs-export-assignment.js +523 -0
  395. package/dist/core/ingestion/languages/typescript/cjs-module-exports.d.ts +54 -0
  396. package/dist/core/ingestion/languages/typescript/cjs-module-exports.js +191 -0
  397. package/dist/core/ingestion/languages/typescript/file-candidates.d.ts +29 -0
  398. package/dist/core/ingestion/languages/typescript/file-candidates.js +70 -0
  399. package/dist/core/ingestion/languages/typescript/import-decomposer.d.ts +28 -1
  400. package/dist/core/ingestion/languages/typescript/import-decomposer.js +125 -10
  401. package/dist/core/ingestion/languages/typescript/import-target.d.ts +25 -37
  402. package/dist/core/ingestion/languages/typescript/import-target.js +24 -26
  403. package/dist/core/ingestion/languages/typescript/interpret.d.ts +21 -1
  404. package/dist/core/ingestion/languages/typescript/interpret.js +45 -5
  405. package/dist/core/ingestion/languages/typescript/module-resolution.d.ts +48 -0
  406. package/dist/core/ingestion/languages/typescript/module-resolution.js +171 -0
  407. package/dist/core/ingestion/languages/typescript/nuxt-auto-imports.js +2 -1
  408. package/dist/core/ingestion/languages/typescript/query.d.ts +1 -0
  409. package/dist/core/ingestion/languages/typescript/query.js +340 -14
  410. package/dist/core/ingestion/languages/typescript/receiver-binding.d.ts +17 -0
  411. package/dist/core/ingestion/languages/typescript/receiver-binding.js +27 -0
  412. package/dist/core/ingestion/languages/typescript/scope-resolver.js +27 -40
  413. package/dist/core/ingestion/languages/typescript/simple-hooks.d.ts +0 -10
  414. package/dist/core/ingestion/languages/typescript/simple-hooks.js +22 -22
  415. package/dist/core/ingestion/languages/typescript/tsconfig.d.ts +59 -0
  416. package/dist/core/ingestion/languages/typescript/tsconfig.js +291 -0
  417. package/dist/core/ingestion/languages/typescript.js +87 -1
  418. package/dist/core/ingestion/languages/vue/import-target.d.ts +6 -22
  419. package/dist/core/ingestion/languages/vue/import-target.js +11 -47
  420. package/dist/core/ingestion/languages/vue/scope-resolver.js +4 -2
  421. package/dist/core/ingestion/local-symbol-pruner.js +11 -11
  422. package/dist/core/ingestion/method-extractors/configs/csharp.js +1 -0
  423. package/dist/core/ingestion/method-extractors/generic.js +1 -0
  424. package/dist/core/ingestion/method-types.d.ts +16 -0
  425. package/dist/core/ingestion/parsing-processor.js +32 -1
  426. package/dist/core/ingestion/pipeline-phases/di.d.ts +8 -33
  427. package/dist/core/ingestion/pipeline-phases/di.js +275 -157
  428. package/dist/core/ingestion/pipeline-phases/index.d.ts +3 -0
  429. package/dist/core/ingestion/pipeline-phases/index.js +3 -0
  430. package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +15 -0
  431. package/dist/core/ingestion/pipeline-phases/parse-impl.js +158 -3
  432. package/dist/core/ingestion/pipeline-phases/parse.js +5 -0
  433. package/dist/core/ingestion/pipeline-phases/processes.d.ts +11 -2
  434. package/dist/core/ingestion/pipeline-phases/processes.js +102 -7
  435. package/dist/core/ingestion/pipeline-phases/routes.js +56 -10
  436. package/dist/core/ingestion/pipeline-phases/spring-aop.d.ts +34 -0
  437. package/dist/core/ingestion/pipeline-phases/spring-aop.js +439 -0
  438. package/dist/core/ingestion/pipeline-phases/spring-auto-configuration.d.ts +34 -0
  439. package/dist/core/ingestion/pipeline-phases/spring-auto-configuration.js +255 -0
  440. package/dist/core/ingestion/pipeline-phases/spring-config.d.ts +37 -0
  441. package/dist/core/ingestion/pipeline-phases/spring-config.js +433 -0
  442. package/dist/core/ingestion/pipeline-phases/types.d.ts +7 -0
  443. package/dist/core/ingestion/pipeline.d.ts +27 -3
  444. package/dist/core/ingestion/pipeline.js +62 -12
  445. package/dist/core/ingestion/process-processor.d.ts +109 -2
  446. package/dist/core/ingestion/process-processor.js +464 -52
  447. package/dist/core/ingestion/resolve-references.d.ts +6 -1
  448. package/dist/core/ingestion/resolve-references.js +78 -6
  449. package/dist/core/ingestion/route-extractors/data-route-table.d.ts +25 -0
  450. package/dist/core/ingestion/route-extractors/data-route-table.js +864 -0
  451. package/dist/core/ingestion/route-extractors/dispatch-guard.d.ts +96 -0
  452. package/dist/core/ingestion/route-extractors/dispatch-guard.js +1184 -0
  453. package/dist/core/ingestion/route-extractors/spring-shared.d.ts +11 -0
  454. package/dist/core/ingestion/route-extractors/spring-shared.js +89 -0
  455. package/dist/core/ingestion/route-extractors/spring.js +109 -24
  456. package/dist/core/ingestion/scope-extractor-bridge.js +9 -1
  457. package/dist/core/ingestion/scope-extractor.d.ts +22 -7
  458. package/dist/core/ingestion/scope-extractor.js +617 -59
  459. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +381 -22
  460. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.js +16 -5
  461. package/dist/core/ingestion/scope-resolution/graph-bridge/callee-id-sink.d.ts +7 -7
  462. package/dist/core/ingestion/scope-resolution/graph-bridge/callee-id-sink.js +8 -7
  463. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.d.ts +15 -1
  464. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.js +53 -0
  465. package/dist/core/ingestion/scope-resolution/graph-bridge/ids.d.ts +20 -15
  466. package/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +275 -41
  467. package/dist/core/ingestion/scope-resolution/graph-bridge/imports-to-edges.d.ts +123 -0
  468. package/dist/core/ingestion/scope-resolution/graph-bridge/imports-to-edges.js +171 -17
  469. package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.d.ts +56 -1
  470. package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +130 -32
  471. package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.d.ts +30 -1
  472. package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.js +46 -3
  473. package/dist/core/ingestion/scope-resolution/passes/callable-value-flow.d.ts +64 -0
  474. package/dist/core/ingestion/scope-resolution/passes/callable-value-flow.js +1233 -0
  475. package/dist/core/ingestion/scope-resolution/passes/compound-receiver.d.ts +107 -5
  476. package/dist/core/ingestion/scope-resolution/passes/compound-receiver.js +590 -33
  477. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +19 -4
  478. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +160 -4
  479. package/dist/core/ingestion/scope-resolution/passes/imported-value-refs.d.ts +34 -0
  480. package/dist/core/ingestion/scope-resolution/passes/imported-value-refs.js +62 -0
  481. package/dist/core/ingestion/scope-resolution/passes/property-dispatch.d.ts +64 -0
  482. package/dist/core/ingestion/scope-resolution/passes/property-dispatch.js +118 -0
  483. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +109 -8
  484. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +931 -92
  485. package/dist/core/ingestion/scope-resolution/passes/return-shape-members.d.ts +58 -0
  486. package/dist/core/ingestion/scope-resolution/passes/return-shape-members.js +458 -0
  487. package/dist/core/ingestion/scope-resolution/passes/unique-name-properties.d.ts +180 -0
  488. package/dist/core/ingestion/scope-resolution/passes/unique-name-properties.js +418 -0
  489. package/dist/core/ingestion/scope-resolution/pipeline/phase.d.ts +34 -0
  490. package/dist/core/ingestion/scope-resolution/pipeline/phase.js +82 -38
  491. package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +61 -1
  492. package/dist/core/ingestion/scope-resolution/pipeline/run.js +384 -48
  493. package/dist/core/ingestion/scope-resolution/resolution-outcome.d.ts +112 -2
  494. package/dist/core/ingestion/scope-resolution/resolution-outcome.js +38 -1
  495. package/dist/core/ingestion/scope-resolution/scope/namespace-targets.d.ts +25 -3
  496. package/dist/core/ingestion/scope-resolution/scope/namespace-targets.js +36 -16
  497. package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +210 -9
  498. package/dist/core/ingestion/scope-resolution/scope/walkers.js +968 -56
  499. package/dist/core/ingestion/scope-resolution/summary-maps.d.ts +28 -0
  500. package/dist/core/ingestion/scope-resolution/summary-maps.js +50 -0
  501. package/dist/core/ingestion/scope-resolution/undecided-satisfaction.d.ts +55 -0
  502. package/dist/core/ingestion/scope-resolution/undecided-satisfaction.js +37 -0
  503. package/dist/core/ingestion/scope-resolution/unresolved-receivers.d.ts +109 -0
  504. package/dist/core/ingestion/scope-resolution/unresolved-receivers.js +204 -0
  505. package/dist/core/ingestion/scope-resolution/utils/definition-id.d.ts +13 -0
  506. package/dist/core/ingestion/scope-resolution/utils/definition-id.js +19 -0
  507. package/dist/core/ingestion/scope-resolution/utils/generic-instantiation.d.ts +171 -0
  508. package/dist/core/ingestion/scope-resolution/utils/generic-instantiation.js +218 -0
  509. package/dist/core/ingestion/scope-resolution/workspace-index-types.d.ts +34 -0
  510. package/dist/core/ingestion/scope-resolution/workspace-index-types.js +15 -0
  511. package/dist/core/ingestion/scope-resolution/workspace-index.d.ts +7 -20
  512. package/dist/core/ingestion/taint/typescript-model.js +34 -2
  513. package/dist/core/ingestion/tree-sitter-queries.d.ts +11 -11
  514. package/dist/core/ingestion/tree-sitter-queries.js +688 -5
  515. package/dist/core/ingestion/type-env.js +11 -7
  516. package/dist/core/ingestion/type-extractors/jvm.js +17 -2
  517. package/dist/core/ingestion/type-extractors/shared.d.ts +17 -0
  518. package/dist/core/ingestion/type-extractors/shared.js +16 -0
  519. package/dist/core/ingestion/type-extractors/types.d.ts +12 -0
  520. package/dist/core/ingestion/type-extractors/typescript.d.ts +20 -0
  521. package/dist/core/ingestion/type-extractors/typescript.js +26 -0
  522. package/dist/core/ingestion/utils/ast-helpers.d.ts +311 -14
  523. package/dist/core/ingestion/utils/ast-helpers.js +745 -48
  524. package/dist/core/ingestion/utils/call-analysis.d.ts +27 -22
  525. package/dist/core/ingestion/utils/call-analysis.js +202 -7
  526. package/dist/core/ingestion/utils/callable-flow-captures.d.ts +153 -0
  527. package/dist/core/ingestion/utils/callable-flow-captures.js +928 -0
  528. package/dist/core/ingestion/utils/callable-labels.d.ts +25 -0
  529. package/dist/core/ingestion/utils/callable-labels.js +31 -0
  530. package/dist/core/ingestion/utils/effective-ram.d.ts +40 -0
  531. package/dist/core/ingestion/utils/effective-ram.js +58 -0
  532. package/dist/core/ingestion/utils/line-base.d.ts +14 -0
  533. package/dist/core/ingestion/utils/line-base.js +14 -0
  534. package/dist/core/ingestion/utils/method-props.d.ts +27 -4
  535. package/dist/core/ingestion/utils/method-props.js +29 -4
  536. package/dist/core/ingestion/utils/receiver-chain-captures.d.ts +31 -0
  537. package/dist/core/ingestion/utils/receiver-chain-captures.js +57 -0
  538. package/dist/core/ingestion/utils/receiver-chain-codec.d.ts +89 -0
  539. package/dist/core/ingestion/utils/receiver-chain-codec.js +182 -0
  540. package/dist/core/ingestion/utils/scope-tree-walk.d.ts +22 -0
  541. package/dist/core/ingestion/utils/scope-tree-walk.js +33 -0
  542. package/dist/core/ingestion/utils/template-arguments.d.ts +96 -0
  543. package/dist/core/ingestion/utils/template-arguments.js +196 -0
  544. package/dist/core/ingestion/utils/type-parameters.d.ts +49 -0
  545. package/dist/core/ingestion/utils/type-parameters.js +206 -0
  546. package/dist/core/ingestion/workers/callable-id.d.ts +72 -0
  547. package/dist/core/ingestion/workers/callable-id.js +116 -0
  548. package/dist/core/ingestion/workers/parse-worker.d.ts +14 -1
  549. package/dist/core/ingestion/workers/parse-worker.js +409 -39
  550. package/dist/core/ingestion/workers/worker-pool.d.ts +34 -27
  551. package/dist/core/ingestion/workers/worker-pool.js +133 -25
  552. package/dist/core/lbug/csv-generator.d.ts +27 -0
  553. package/dist/core/lbug/csv-generator.js +80 -17
  554. package/dist/core/lbug/extension-load-error.d.ts +2 -2
  555. package/dist/core/lbug/extension-load-error.js +44 -27
  556. package/dist/core/lbug/extension-loader.d.ts +29 -0
  557. package/dist/core/lbug/extension-loader.js +32 -7
  558. package/dist/core/lbug/graph-emit-sink.d.ts +264 -0
  559. package/dist/core/lbug/graph-emit-sink.js +581 -0
  560. package/dist/core/lbug/lbug-adapter.d.ts +337 -3
  561. package/dist/core/lbug/lbug-adapter.js +772 -67
  562. package/dist/core/lbug/lbug-config.d.ts +93 -6
  563. package/dist/core/lbug/lbug-config.js +269 -9
  564. package/dist/core/lbug/native-check.d.ts +39 -0
  565. package/dist/core/lbug/native-check.js +323 -46
  566. package/dist/core/lbug/pdg-emit-sink.d.ts +13 -4
  567. package/dist/core/lbug/pdg-emit-sink.js +21 -111
  568. package/dist/core/lbug/pool-adapter.d.ts +23 -1
  569. package/dist/core/lbug/pool-adapter.js +115 -7
  570. package/dist/core/lbug/query-batch.d.ts +75 -0
  571. package/dist/core/lbug/query-batch.js +102 -0
  572. package/dist/core/lbug/rel-pair-routing.d.ts +160 -2
  573. package/dist/core/lbug/rel-pair-routing.js +228 -10
  574. package/dist/core/lbug/schema.d.ts +162 -3
  575. package/dist/core/lbug/schema.js +392 -157
  576. package/dist/core/lbug/sidecar-recovery.d.ts +1 -0
  577. package/dist/core/lbug/sidecar-recovery.js +1 -1
  578. package/dist/core/lbug/sync-csv-writer.d.ts +47 -0
  579. package/dist/core/lbug/sync-csv-writer.js +108 -0
  580. package/dist/core/lbug/wal-checkpoint-driver.js +3 -0
  581. package/dist/core/platform/capabilities.d.ts +0 -1
  582. package/dist/core/platform/capabilities.js +12 -7
  583. package/dist/core/run-analyze.d.ts +150 -3
  584. package/dist/core/run-analyze.js +1562 -151
  585. package/dist/core/search/bm25-index.d.ts +11 -0
  586. package/dist/core/search/bm25-index.js +60 -26
  587. package/dist/core/search/fts-indexes.d.ts +167 -2
  588. package/dist/core/search/fts-indexes.js +264 -10
  589. package/dist/core/wiki/graph-queries.js +145 -112
  590. package/dist/core/wiki/llm-client.d.ts +37 -5
  591. package/dist/core/wiki/llm-client.js +88 -17
  592. package/dist/core/wiki/prompts.d.ts +19 -0
  593. package/dist/core/wiki/prompts.js +20 -1
  594. package/dist/lib/utils.d.ts +113 -0
  595. package/dist/lib/utils.js +185 -0
  596. package/dist/mcp/http-transport.d.ts +5 -0
  597. package/dist/mcp/http-transport.js +19 -5
  598. package/dist/mcp/local/aop-metadata.d.ts +77 -0
  599. package/dist/mcp/local/aop-metadata.js +227 -0
  600. package/dist/mcp/local/bean-metadata.d.ts +3 -0
  601. package/dist/mcp/local/bean-metadata.js +46 -0
  602. package/dist/mcp/local/local-backend.d.ts +167 -6
  603. package/dist/mcp/local/local-backend.js +1756 -405
  604. package/dist/mcp/local/path-predicate.d.ts +19 -0
  605. package/dist/mcp/local/path-predicate.js +21 -0
  606. package/dist/mcp/local/pdg-impact.d.ts +132 -10
  607. package/dist/mcp/local/pdg-impact.js +391 -98
  608. package/dist/mcp/output-budget.d.ts +5 -0
  609. package/dist/mcp/output-budget.js +52 -0
  610. package/dist/mcp/read-only-policy.d.ts +12 -0
  611. package/dist/mcp/read-only-policy.js +102 -0
  612. package/dist/mcp/repository-policy.d.ts +35 -0
  613. package/dist/mcp/repository-policy.js +333 -0
  614. package/dist/mcp/resources.js +34 -7
  615. package/dist/mcp/server.d.ts +5 -2
  616. package/dist/mcp/server.js +48 -18
  617. package/dist/mcp/tools.d.ts +8 -8
  618. package/dist/mcp/tools.js +91 -20
  619. package/dist/server/analyze-job.d.ts +40 -2
  620. package/dist/server/analyze-job.js +30 -2
  621. package/dist/server/analyze-launch.d.ts +1 -1
  622. package/dist/server/analyze-launch.js +79 -10
  623. package/dist/server/analyze-worker-core.d.ts +3 -2
  624. package/dist/server/analyze-worker-core.js +15 -3
  625. package/dist/server/analyze-worker-ipc.d.ts +1 -1
  626. package/dist/server/analyze-worker-ipc.js +4 -0
  627. package/dist/server/analyze-worker-protocol.d.ts +59 -0
  628. package/dist/server/analyze-worker-protocol.js +1 -0
  629. package/dist/server/analyze-worker.d.ts +1 -17
  630. package/dist/server/analyze-worker.js +29 -6
  631. package/dist/server/api.d.ts +2 -11
  632. package/dist/server/api.js +200 -155
  633. package/dist/server/embed-run-outcome.d.ts +89 -0
  634. package/dist/server/embed-run-outcome.js +88 -0
  635. package/dist/server/git-clone.js +6 -2
  636. package/dist/server/mcp-http.d.ts +1 -1
  637. package/dist/server/mcp-http.js +4 -2
  638. package/dist/server/middleware.d.ts +116 -9
  639. package/dist/server/middleware.js +291 -22
  640. package/dist/server/sse-progress.d.ts +41 -0
  641. package/dist/server/sse-progress.js +125 -0
  642. package/dist/server/upload-ingest.js +2 -1
  643. package/dist/server/validation.js +2 -1
  644. package/dist/storage/branch-index.d.ts +11 -6
  645. package/dist/storage/branch-index.js +15 -7
  646. package/dist/storage/file-hash.js +2 -2
  647. package/dist/storage/fs-atomic.d.ts +25 -0
  648. package/dist/storage/fs-atomic.js +42 -0
  649. package/dist/storage/git.d.ts +165 -0
  650. package/dist/storage/git.js +352 -3
  651. package/dist/storage/index-lock.d.ts +83 -0
  652. package/dist/storage/index-lock.js +654 -0
  653. package/dist/storage/parse-cache.js +482 -1
  654. package/dist/storage/parsedfile-store.d.ts +8 -0
  655. package/dist/storage/parsedfile-store.js +235 -1
  656. package/dist/storage/repo-manager.d.ts +29 -309
  657. package/dist/storage/repo-manager.js +187 -165
  658. package/dist/storage/repo-meta.d.ts +530 -0
  659. package/dist/storage/repo-meta.js +100 -0
  660. package/dist/types/pipeline.d.ts +37 -0
  661. package/hooks/antigravity/gitnexus-antigravity-hook.cjs +1 -1
  662. package/hooks/claude/gitnexus-hook.cjs +1 -1
  663. package/hooks/claude/resolve-analyze-cmd.cjs +80 -13
  664. package/package.json +20 -12
  665. package/scripts/cross-platform-shard.ts +143 -0
  666. package/scripts/cross-platform-tests.ts +94 -0
  667. package/scripts/ensure-fts.ts +14 -3
  668. package/scripts/run-cross-platform.ts +36 -10
  669. package/scripts/sync-plugin-manifests.mjs +192 -0
  670. package/skills/gitnexus-cli.md +3 -3
  671. package/skills/gitnexus-debugging.md +37 -7
  672. package/skills/gitnexus-exploring.md +26 -4
  673. package/skills/gitnexus-impact-analysis.md +71 -10
  674. package/skills/gitnexus-lfg/README.md +55 -0
  675. package/skills/gitnexus-lfg/SKILL.md +86 -0
  676. package/skills/gitnexus-plan/README.md +147 -0
  677. package/skills/gitnexus-plan/SKILL.md +348 -0
  678. package/skills/gitnexus-plan/references/context-ledger.md +137 -0
  679. package/skills/gitnexus-plan/references/context-pack.md +126 -0
  680. package/skills/gitnexus-plan/references/evidence-provenance.md +309 -0
  681. package/skills/gitnexus-plan/references/pdg-slice.md +109 -0
  682. package/skills/gitnexus-plan/references/plan-template.md +201 -0
  683. package/skills/gitnexus-plan/scripts/evidence-provenance.mjs +2366 -0
  684. package/skills/gitnexus-refactoring.md +48 -5
  685. package/skills/gitnexus-review/SKILL.md +279 -0
  686. package/skills/gitnexus-review/ci-personas/ci-adversarial-lens.md +42 -0
  687. package/skills/gitnexus-review/ci-personas/ci-blast-radius-lens.md +39 -0
  688. package/skills/gitnexus-review/ci-personas/ci-correctness-lens.md +37 -0
  689. package/skills/gitnexus-review/ci-personas/ci-coverage-lens.md +40 -0
  690. package/skills/gitnexus-review/ci-personas/ci-critic-lens.md +42 -0
  691. package/skills/gitnexus-review/ci-personas/ci-security-lens.md +39 -0
  692. package/skills/gitnexus-taint-analysis.md +13 -7
  693. package/skills/gitnexus-work/README.md +71 -0
  694. package/skills/gitnexus-work/SKILL.md +272 -0
  695. package/skills/gitnexus-work/references/evidence-provenance.md +309 -0
  696. package/skills/gitnexus-work/scripts/evidence-provenance.mjs +2366 -0
  697. package/vendor/leiden/utils.cjs +28 -1
  698. package/web/assets/ProcessFlowModal-Badh2Pbg.js +24 -0
  699. package/web/assets/__vite-browser-external-bMKyYQkO.js +1 -0
  700. package/web/assets/abnfDiagram-QNN2D4NM-Cq2djWWe.js +1 -0
  701. package/web/assets/agent-DIzEQ1JJ.js +626 -0
  702. package/web/assets/architecture-TIHT7OUA-V6N4J3SY-fTePvLoe.js +1 -0
  703. package/web/assets/architectureDiagram-OT7VKSJ7-mKEXxkeF.js +36 -0
  704. package/web/assets/blockDiagram-54WLDVWM-Dg_hrRWE.js +132 -0
  705. package/web/assets/{c4Diagram-Y2BXMSZH-DOXVs7-C.js → c4Diagram-X2LASM7V-CnhoT4aV.js} +6 -6
  706. package/web/assets/chunk-224SPVON-B8gxtXpy.js +1 -0
  707. package/web/assets/chunk-2AEHWXPW-CV4r6-1I.js +127 -0
  708. package/web/assets/chunk-2SREHG4O-Dip42xOq.js +1 -0
  709. package/web/assets/chunk-4RFN2BYJ-DHHWuC-n.js +1 -0
  710. package/web/assets/chunk-66DQ2XMT-CM0xjFAN.js +1 -0
  711. package/web/assets/chunk-6BELYETK-ChsYtfHW.js +1 -0
  712. package/web/assets/chunk-6L755F7B-CMhLBxi2.js +1 -0
  713. package/web/assets/chunk-7CWYLC5S-DRgGkkpF.js +2 -0
  714. package/web/assets/chunk-A7VWPJGB-B4O3oQdQ.js +1 -0
  715. package/web/assets/chunk-BI6VK774-D0oaWk8V.js +1 -0
  716. package/web/assets/chunk-D6VWDJW2-BxcBdZ9c.js +2 -0
  717. package/web/assets/chunk-DMV4VAQV-Bn6WXDSX.js +4 -0
  718. package/web/assets/chunk-E5QJAATJ-dh-5CtIV.js +2 -0
  719. package/web/assets/chunk-EQFTRU2I-1ix7TzW9.js +2 -0
  720. package/web/assets/chunk-FWYVLQTC-BgDItRzJ.js +1 -0
  721. package/web/assets/chunk-HNC4WDU7-DoU3KnA7.js +1 -0
  722. package/web/assets/chunk-L5GCOVLC-CE2w8Nm_.js +161 -0
  723. package/web/assets/chunk-LAHSG3N2-Bz4hcn0N.js +321 -0
  724. package/web/assets/chunk-LORZLZWG-B57uGw2N.js +88 -0
  725. package/web/assets/chunk-NLANEA3F-XpH_NPA5.js +62 -0
  726. package/web/assets/chunk-NV3KIAZN-5yvcpMiu.js +1 -0
  727. package/web/assets/chunk-OIYT25JQ-CQ6KOZGX.js +1 -0
  728. package/web/assets/chunk-PE7DX7ZZ-CCvRVj43.js +10 -0
  729. package/web/assets/chunk-Q67WD55A-BWCH2SVz.js +156 -0
  730. package/web/assets/chunk-RRFMTAIC-CMN0wcYA.js +1 -0
  731. package/web/assets/{chunk-RLI5ZMPA-CkXZ0y0U.js → chunk-SATU7PGQ-B1zhgOvc.js} +1 -1
  732. package/web/assets/chunk-STOV2HOB-GnExfzIX.js +1 -0
  733. package/web/assets/chunk-SZD42YQK-ijyKKVr1.js +70 -0
  734. package/web/assets/chunk-TFPGFQPA-Dj6a-0pC.js +231 -0
  735. package/web/assets/chunk-TGVD4F4B-BMc31Gj2.js +2 -0
  736. package/web/assets/chunk-TZUXEDM2-B7p9AcTw.js +1 -0
  737. package/web/assets/chunk-UAT7B5JY-DW-A_fpI.js +1 -0
  738. package/web/assets/{chunk-6764PJDD-C7xxk063.js → chunk-VE5CLXGZ-C1vB7wpY.js} +1 -1
  739. package/web/assets/chunk-VJFVEZTB-CX66F2ep.js +206 -0
  740. package/web/assets/chunk-VY5UBI4V-yxPQAWxw.js +1 -0
  741. package/web/assets/chunk-WJBAP47W-BckNkuEN.js +1 -0
  742. package/web/assets/chunk-XNMVGMAZ-Cg_LnPtj.js +1 -0
  743. package/web/assets/classDiagram-VPOSVPXH-BjOiXA_C.js +1 -0
  744. package/web/assets/classDiagram-v2-5VFNUXQA-BjOiXA_C.js +1 -0
  745. package/web/assets/{cose-bilkent-UX7MHV2Q-BmnZ7Mvj.js → cose-bilkent-RZA4XIPR-BHpK4Crp.js} +1 -1
  746. package/web/assets/cynefin-VYW2F7L2-2VAHYKR6-DAsF6GZv.js +1 -0
  747. package/web/assets/cynefinDiagram-TR7LZUCG-C3Iar9lZ.js +62 -0
  748. package/web/assets/dagre-K64A6Z3X-CUwi79mA.js +4 -0
  749. package/web/assets/diagram-2TLGJV2N-C2F5Nfym.js +30 -0
  750. package/web/assets/diagram-JCD7PQ7C-DC5-9FRr.js +24 -0
  751. package/web/assets/diagram-KFBZAVDW-Dfmf_4ti.js +3 -0
  752. package/web/assets/diagram-L7DVE3PN-GIRyVUtt.js +41 -0
  753. package/web/assets/diagram-YP3FGXHI-CyNFYtkd.js +24 -0
  754. package/web/assets/ebnfDiagram-DRHRQJWO-D8_CI4ZF.js +1 -0
  755. package/web/assets/erDiagram-SZLJLQMB-CohQjznC.js +85 -0
  756. package/web/assets/eventmodeling-45OFAUF4-AOUJWSLX-BvTc6iQi.js +1 -0
  757. package/web/assets/flowDiagram-BWE6NHOH-B6RnEvD9.js +1 -0
  758. package/web/assets/ganttDiagram-2QTCJSNI-Cks_mEcF.js +292 -0
  759. package/web/assets/gitGraph-TEB2WS4Q-U5VWYIBC-Dy7CCSZ5.js +1 -0
  760. package/web/assets/gitGraphDiagram-VVZAVV5G-CG41cIVc.js +106 -0
  761. package/web/assets/{index-B4eB4dNZ.js → index-BBNl0qfX.js} +51 -51
  762. package/web/assets/index-CJDpyH-n.css +2 -0
  763. package/web/assets/info-DKCQHKI2-DX6KST54-BLJg1ikX.js +1 -0
  764. package/web/assets/infoDiagram-FY4PZKRE-BJ3vPVwW.js +2 -0
  765. package/web/assets/{ishikawaDiagram-BNXS4ZKH-BLjivjXL.js → ishikawaDiagram-4JDSWWDM-DmqWtjkl.js} +6 -6
  766. package/web/assets/{journeyDiagram-M6C3CM3L-Cetgs3NH.js → journeyDiagram-QUS3FAKT-XAzVzrQW.js} +6 -6
  767. package/web/assets/{kanban-definition-75IXJCU3-DMlCaIpf.js → kanban-definition-D4L7SQKK-YurRnmYn.js} +27 -27
  768. package/web/assets/katex-C5OPUE3Q-Bnbavtmu.js +257 -0
  769. package/web/assets/{mindmap-definition-2TDM6QVE-B4GbXQBc.js → mindmap-definition-66QP5TD6-ChrWWnil.js} +29 -29
  770. package/web/assets/node-Ct_zna8U.js +8 -0
  771. package/web/assets/packet-7NZHBO7P-5WG45QPS-KUxcuvaA.js +1 -0
  772. package/web/assets/pegDiagram-YKIO5C6U-QGeQLEq2.js +1 -0
  773. package/web/assets/pie-RZYD4A2V-2OCZ2VTD-DPs08kJY.js +1 -0
  774. package/web/assets/pieDiagram-YPY3TKG5-BuT3MYzm.js +39 -0
  775. package/web/assets/preload-helper-Czpn1I53.js +1 -0
  776. package/web/assets/{quadrantDiagram-VICAPDV7-B1TQnlm0.js → quadrantDiagram-VO5QLCXA-B7098wSc.js} +6 -6
  777. package/web/assets/radar-I7S5WNFK-EPLSA35B-w5F0K3a7.js +1 -0
  778. package/web/assets/railroad-3IZDKUUU-PWFQV6Y6-Bu668yqY.js +1 -0
  779. package/web/assets/railroad-abnf-AHOZXSZD-JYYJX3BS-B6ghpETS.js +1 -0
  780. package/web/assets/railroad-ebnf-EBAXGLYW-RBZ4ONAP-D-XmHh3M.js +1 -0
  781. package/web/assets/railroad-peg-LSFZ7HO6-KHNPXGPG-mhkrhzEn.js +1 -0
  782. package/web/assets/railroadDiagram-M77LTMSB-AXu4vxk_.js +1 -0
  783. package/web/assets/requirementDiagram-RASO5HTO-B-l8InpK.js +84 -0
  784. package/web/assets/sankeyDiagram-F32LOJ6L-Kd7zjJgX.js +40 -0
  785. package/web/assets/sdk-CzHHH8tD.js +37 -0
  786. package/web/assets/sequenceDiagram-URATNSBD-BlcdY9DS.js +162 -0
  787. package/web/assets/sizeCapture-5GORAGP4-BEqQzsxs.js +1 -0
  788. package/web/assets/src-C5sx_gMp.js +1 -0
  789. package/web/assets/stateDiagram-DODFXNG5-DTdHc-fF.js +1 -0
  790. package/web/assets/stateDiagram-v2-JNMM5JRA-DioNLDDo.js +1 -0
  791. package/web/assets/swimlanes-OSWOYLSK-CA0ofdwY.js +2 -0
  792. package/web/assets/swimlanesDiagram-RHGX2RDE-PkwUdckC.js +8 -0
  793. package/web/assets/timeline-definition-WJXWRH4I-DUfp1R3E.js +120 -0
  794. package/web/assets/treeView-QDETBFTQ-EHYTSQNO-C5lpBgPm.js +1 -0
  795. package/web/assets/treemap-6X3UGDF4-CCWCF7EK-BB0ZyO-B.js +1 -0
  796. package/web/assets/vennDiagram-K3VCMQLI-Bj-FN-XS.js +34 -0
  797. package/web/assets/wardley-OPB4EBWU-6OWERDVT-CEA3Nq0j.js +1 -0
  798. package/web/assets/wardleyDiagram-XK6FUAWV-CVV0dHFr.js +78 -0
  799. package/web/assets/xychartDiagram-5VRUVLCW-CK_rbSWE.js +7 -0
  800. package/web/index.html +17 -15
  801. package/dist/core/ingestion/languages/python/index-stats.d.ts +0 -18
  802. package/dist/core/ingestion/languages/python/index-stats.js +0 -25
  803. package/skills/gitnexus-pr-review.md +0 -163
  804. package/web/assets/ProcessFlowModal-Dna7GkwO.js +0 -22
  805. package/web/assets/agent-DNYDy-KA.js +0 -660
  806. package/web/assets/architecture-7EHR7CIX-6QZW5X65-BVWFvYz2.js +0 -1
  807. package/web/assets/architectureDiagram-UL44E2DR-BU5ZRITg.js +0 -36
  808. package/web/assets/blockDiagram-7IZFK4PR-Dut1BpTV.js +0 -132
  809. package/web/assets/chunk-2T2R6R2M-pkw2oXHH.js +0 -4
  810. package/web/assets/chunk-2UTLFMKG-CXlhF3Pt.js +0 -1
  811. package/web/assets/chunk-3SSMPTDK-rPsRM1x0.js +0 -321
  812. package/web/assets/chunk-4R4BOZG6-Be_F0JoX.js +0 -159
  813. package/web/assets/chunk-67TQ5CYL-zxhDWgyF.js +0 -124
  814. package/web/assets/chunk-7J6CGLKN-AmkN8jJN.js +0 -10
  815. package/web/assets/chunk-C62D2QBJ-3gdL8EKc.js +0 -1
  816. package/web/assets/chunk-CEXFNPSA-CjwtMDkc.js +0 -1
  817. package/web/assets/chunk-INKRHTLW-D9BM9HWZ.js +0 -70
  818. package/web/assets/chunk-J5EP6P6S-L19oz55R.js +0 -1
  819. package/web/assets/chunk-KGFNY3KK-tD2F6wxK.js +0 -62
  820. package/web/assets/chunk-KGYTTC2M-zm6n242I.js +0 -1
  821. package/web/assets/chunk-KNLZD3CH-BGlP6X9R.js +0 -1
  822. package/web/assets/chunk-LCXTWHL2-Ba9JK-6F.js +0 -231
  823. package/web/assets/chunk-LII3EMHJ-EGx-eBIu.js +0 -1
  824. package/web/assets/chunk-LRIF4GLE-C3_PqUBq.js +0 -1
  825. package/web/assets/chunk-QA3QBVWF-D4cSJBf6.js +0 -2
  826. package/web/assets/chunk-RERM46MO-B5dAdWpP.js +0 -1
  827. package/web/assets/chunk-RG4AUYOV-OqBBMwuQ.js +0 -206
  828. package/web/assets/chunk-RKZBBQEN-MyIQbGgD.js +0 -1
  829. package/web/assets/chunk-T2UQINTJ-BJusEiGe.js +0 -1
  830. package/web/assets/chunk-UP6H54XL-BG-n7qDp.js +0 -1
  831. package/web/assets/chunk-UXSXWOXI-CW1_r5cS.js +0 -1
  832. package/web/assets/chunk-UY5QBCOK-BCpUlARQ.js +0 -1
  833. package/web/assets/chunk-VU6ZFW4Y-BeXVk45_.js +0 -2
  834. package/web/assets/chunk-ZXARS5L4-BI_qR2Nm.js +0 -1
  835. package/web/assets/classDiagram-KGZ6W3CR-D9zSIg0x.js +0 -1
  836. package/web/assets/classDiagram-v2-72OJOZXJ-D9zSIg0x.js +0 -1
  837. package/web/assets/dagre-ND4H6XIP-CpcV5kbl.js +0 -4
  838. package/web/assets/diagram-3NCE3AQN-6SMxBRpj.js +0 -43
  839. package/web/assets/diagram-GF46GFSD-BZEdNcRZ.js +0 -24
  840. package/web/assets/diagram-HNR7UZ2L-DxKW2a2Y.js +0 -3
  841. package/web/assets/diagram-QXG6HAR7-HfTHcXX_.js +0 -24
  842. package/web/assets/diagram-WEQXMOUZ-CyijUS8r.js +0 -10
  843. package/web/assets/erDiagram-L5TCEMPS-D0fvpWEX.js +0 -85
  844. package/web/assets/eventmodeling-FCH6USID-MREXMVOE-CgFBM7e-.js +0 -1
  845. package/web/assets/flowDiagram-H6V6AXG4-Bvr9bbhk.js +0 -162
  846. package/web/assets/ganttDiagram-JCBTUEKG-TzODrLzv.js +0 -292
  847. package/web/assets/gitGraph-WXDBUCRP-R675I2BI-DCJv7SII.js +0 -1
  848. package/web/assets/gitGraphDiagram-S2ZK5IYY-ONQSwJIY.js +0 -106
  849. package/web/assets/index-CX_fADmQ.css +0 -2
  850. package/web/assets/info-J43DQDTF-KCYPFFUO-BfNy-9Nd.js +0 -1
  851. package/web/assets/infoDiagram-3YFTVSEB-DIPVUIQ6.js +0 -2
  852. package/web/assets/katex-K3KEBU37-qRQBy-EZ.js +0 -261
  853. package/web/assets/packet-YPE3B663-LP52Z2RK-D4qkeG9k.js +0 -1
  854. package/web/assets/pie-LRSECV5Y-TCRJHUBD-B_yMKyQa.js +0 -1
  855. package/web/assets/pieDiagram-CU6KROY3-D3YC_xyg.js +0 -30
  856. package/web/assets/radar-GUYGQ44K-RDLRG3WG-DeD6yN_0.js +0 -1
  857. package/web/assets/requirementDiagram-JXO7QTGE-f8wczLQf.js +0 -84
  858. package/web/assets/sankeyDiagram-URQDO5SZ-BTxzG5j0.js +0 -40
  859. package/web/assets/sequenceDiagram-VS2MUI6T-DuamM-IR.js +0 -162
  860. package/web/assets/src-BNCXICdA.js +0 -1
  861. package/web/assets/stateDiagram-7D4R322I-Ch2pEUcw.js +0 -1
  862. package/web/assets/stateDiagram-v2-36443NZ5-7ANuoZLL.js +0 -1
  863. package/web/assets/timeline-definition-O6YCAMPW-07BR8R8G.js +0 -120
  864. package/web/assets/treeView-BLDUP644-QA4HXRO3-CDuSYnd4.js +0 -1
  865. package/web/assets/treemap-LRROVOQU-LLAWBHMP-imazucyJ.js +0 -1
  866. package/web/assets/vennDiagram-MWXL3ELB-BQX2ossk.js +0 -34
  867. package/web/assets/wardley-L42UT6IY-5TKZOOLJ-DqrT9tB8.js +0 -1
  868. package/web/assets/wardleyDiagram-CUQ6CDDI-CGIJBBiK.js +0 -78
  869. package/web/assets/xychartDiagram-N2JHSOCM-B3HNMuWp.js +0 -7
  870. /package/web/assets/{chunk-7W6UQGC5-pyKGssV3.js → chunk-LIEV3EAG-pyKGssV3.js} +0 -0
@@ -0,0 +1,2158 @@
1
+ /**
2
+ * Reproducible analyzer identity stamped into RepoMeta after a successful run.
3
+ *
4
+ * Schema v4 uses length-prefixed canonical frames. Build files and runtime
5
+ * artifacts contribute SHA-256 payload digests, so a validated stat inventory
6
+ * can safely reuse those expensive per-file digests across short-lived CLI and
7
+ * server-worker processes. The cache is only an optimization: malformed,
8
+ * mismatched, or missing entries are rehashed, and every identity calculation
9
+ * performs a final return-boundary inventory before returning.
10
+ *
11
+ * `invokedArtifact` remains in the receipt for diagnostics, but is deliberately
12
+ * excluded from semantic freshness. The CLI and the server analyze worker are
13
+ * different entry files inside the same build tree; alternating between them
14
+ * must not make an otherwise identical index stale.
15
+ */
16
+ import { closeSync, constants as fsConstants, existsSync, fstatSync, lstatSync, mkdirSync, openSync, readSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync, } from 'node:fs';
17
+ import { createHash, randomBytes } from 'node:crypto';
18
+ import { spawnSync } from 'node:child_process';
19
+ import { isDeepStrictEqual } from 'node:util';
20
+ import os from 'node:os';
21
+ import path from 'node:path';
22
+ import { fileURLToPath } from 'node:url';
23
+ export const ANALYZER_RUNNER_IDENTITY_SCHEMA_VERSION = 4;
24
+ const BUILD_CANONICALIZATION = 'gitnexus-analyzer-build-v2';
25
+ const DEPENDENCY_RUNTIME_CANONICALIZATION = 'gitnexus-analyzer-dependency-runtime-v4';
26
+ const IDENTITY_CACHE_SCHEMA_VERSION = 6;
27
+ const MAX_CACHE_ENTRIES = 100_000;
28
+ const MAX_CACHE_FILE_BYTES = 64 * 1024 * 1024;
29
+ const HASH_BUFFER_BYTES = 256 * 1024;
30
+ const SHA256_PATTERN = /^sha256:[a-f0-9]{64}$/;
31
+ const DEFAULT_TRAVERSAL_LIMITS = {
32
+ buildEntries: 100_000,
33
+ buildDepth: 128,
34
+ buildBytes: 512 * 1024 * 1024,
35
+ runtimePackages: 10_000,
36
+ runtimeEdges: 100_000,
37
+ runtimeEntries: 250_000,
38
+ runtimeDepth: 64,
39
+ runtimePayloads: 100_000,
40
+ runtimeBytes: 2 * 1024 * 1024 * 1024,
41
+ resolutionAncestors: 256,
42
+ };
43
+ function toBuffer(value) {
44
+ return Buffer.isBuffer(value) ? value : Buffer.from(value);
45
+ }
46
+ /** Update a hash with one unambiguous, length-prefixed canonical record. */
47
+ function updateCanonicalFrame(hash, fields) {
48
+ const fieldCount = Buffer.allocUnsafe(4);
49
+ fieldCount.writeUInt32BE(fields.length);
50
+ hash.update(fieldCount);
51
+ for (const field of fields) {
52
+ const bytes = toBuffer(field);
53
+ const length = Buffer.allocUnsafe(8);
54
+ length.writeBigUInt64BE(BigInt(bytes.length));
55
+ hash.update(length);
56
+ hash.update(bytes);
57
+ }
58
+ }
59
+ function hashCanonicalFrames(frames) {
60
+ const hash = createHash('sha256');
61
+ for (const frame of frames)
62
+ updateCanonicalFrame(hash, frame);
63
+ return `sha256:${hash.digest('hex')}`;
64
+ }
65
+ /** @internal Regression seam for adversarial canonical-framing tests. */
66
+ export const _hashAnalyzerIdentityFramesForTests = hashCanonicalFrames;
67
+ function sha256(payload) {
68
+ return `sha256:${createHash('sha256').update(payload).digest('hex')}`;
69
+ }
70
+ function digestBytes(digest) {
71
+ if (!SHA256_PATTERN.test(digest))
72
+ throw new Error(`Invalid SHA-256 digest: ${digest}`);
73
+ return Buffer.from(digest.slice('sha256:'.length), 'hex');
74
+ }
75
+ function statState(stat) {
76
+ const bigintStat = stat;
77
+ return {
78
+ dev: String(bigintStat.dev),
79
+ ino: String(bigintStat.ino),
80
+ mode: String(bigintStat.mode),
81
+ nlink: String(bigintStat.nlink),
82
+ size: String(bigintStat.size),
83
+ mtimeNs: String(bigintStat.mtimeNs),
84
+ ctimeNs: String(bigintStat.ctimeNs),
85
+ };
86
+ }
87
+ function resolveTraversalLimits(options) {
88
+ const overrides = options.traversalLimits ?? {};
89
+ const resolved = { ...DEFAULT_TRAVERSAL_LIMITS };
90
+ for (const key of Object.keys(DEFAULT_TRAVERSAL_LIMITS)) {
91
+ const value = overrides[key];
92
+ if (value === undefined)
93
+ continue;
94
+ if (!Number.isSafeInteger(value) || value < 1) {
95
+ throw new Error(`Analyzer identity traversal limit ${key} must be a positive safe integer`);
96
+ }
97
+ resolved[key] = Math.min(value, DEFAULT_TRAVERSAL_LIMITS[key]);
98
+ }
99
+ return resolved;
100
+ }
101
+ function stateSize(state, label) {
102
+ const value = BigInt(state.size);
103
+ if (value < 0n || value > BigInt(Number.MAX_SAFE_INTEGER)) {
104
+ throw new Error(`Analyzer identity input has an unsupported size: ${label}`);
105
+ }
106
+ return Number(value);
107
+ }
108
+ function detectLibcVariant() {
109
+ if (process.platform !== 'linux')
110
+ return 'not-applicable';
111
+ try {
112
+ const report = process.report?.getReport();
113
+ const glibc = report?.header?.glibcVersionRuntime;
114
+ if (typeof glibc === 'string' && glibc.length > 0)
115
+ return `glibc:${glibc}`;
116
+ if (Array.isArray(report?.sharedObjects)) {
117
+ const musl = report.sharedObjects.find((entry) => typeof entry === 'string' && /(?:^|[/\\])(?:ld-)?musl[^/\\]*\.so/i.test(entry));
118
+ if (musl)
119
+ return `musl:${path.basename(musl)}`;
120
+ }
121
+ }
122
+ catch {
123
+ // Runtime reporting is optional on some embedded Node builds. Unknown is
124
+ // still a distinct, fail-closed variant rather than being conflated with
125
+ // glibc or a known musl loader.
126
+ }
127
+ return 'linux-libc:unknown';
128
+ }
129
+ const LIBC_VARIANT = detectLibcVariant();
130
+ function resolveRuntimeVariant() {
131
+ return {
132
+ // Normalized like build.rootPath (#2668): executablePath is a compared
133
+ // identity field (only invokedArtifact is stripped in the comparison), and
134
+ // process.execPath carries the same Windows drive-letter case ambiguity —
135
+ // so leaving it un-normalized would reintroduce the false-stale via runtime.
136
+ executablePath: normalizeAnalyzerRootPath(resolveExistingPath(process.execPath), process.platform),
137
+ nodeVersion: process.version,
138
+ platform: process.platform,
139
+ architecture: process.arch,
140
+ endianness: os.endianness(),
141
+ modulesAbi: process.versions.modules ?? 'unknown',
142
+ napiAbi: process.versions.napi ?? 'unknown',
143
+ libc: LIBC_VARIANT,
144
+ };
145
+ }
146
+ function snapshotReadableFile(candidate) {
147
+ const link = lstatSync(candidate, { bigint: true });
148
+ const target = statSync(candidate, { bigint: true });
149
+ if (!target.isFile())
150
+ throw new Error(`Analyzer identity input is not a file: ${candidate}`);
151
+ return {
152
+ link: statState(link),
153
+ target: statState(target),
154
+ ...(link.isSymbolicLink() ? { symlinkTarget: readlinkSync(candidate) } : {}),
155
+ };
156
+ }
157
+ /**
158
+ * Snapshot a symbolic link without resolving it. Unlike
159
+ * {@link snapshotReadableFile} this never stats the target, so it is total over
160
+ * linked directories, dangling links, and links to device nodes — the inputs
161
+ * that make the readable-file snapshot throw.
162
+ */
163
+ function snapshotSymlinkArtifact(candidate) {
164
+ const link = lstatSync(candidate, { bigint: true });
165
+ if (!link.isSymbolicLink()) {
166
+ throw new Error(`Analyzer identity input is not a symbolic link: ${candidate}`);
167
+ }
168
+ return { link: statState(link), symlinkTarget: readlinkSync(candidate) };
169
+ }
170
+ function snapshotRuntimeArtifact(artifact) {
171
+ return artifact.kind === 'unfollowed-symlink'
172
+ ? snapshotSymlinkArtifact(artifact.absolutePath)
173
+ : snapshotReadableFile(artifact.absolutePath);
174
+ }
175
+ function snapshotDirectory(candidate) {
176
+ const stat = lstatSync(candidate, { bigint: true });
177
+ if (!stat.isDirectory() || stat.isSymbolicLink()) {
178
+ throw new Error(`Analyzer identity input is not a directory: ${candidate}`);
179
+ }
180
+ return statState(stat);
181
+ }
182
+ function readDirectory(candidate, options) {
183
+ options.onCacheMissWork?.({ kind: 'directory-walk', path: candidate });
184
+ return readdirSync(candidate, { withFileTypes: true });
185
+ }
186
+ function directoryEntriesDigestFrom(entriesInput) {
187
+ const entries = entriesInput
188
+ .map((entry) => ({
189
+ name: entry.name,
190
+ kind: entry.isDirectory()
191
+ ? 'directory'
192
+ : entry.isFile()
193
+ ? 'file'
194
+ : entry.isSymbolicLink()
195
+ ? 'symlink'
196
+ : 'other',
197
+ }))
198
+ .sort((a, b) => compareBytes(a.name, b.name));
199
+ const hash = createHash('sha256');
200
+ updateCanonicalFrame(hash, ['directory-entries-v1']);
201
+ for (const entry of entries)
202
+ updateCanonicalFrame(hash, [entry.name, entry.kind]);
203
+ return `sha256:${hash.digest('hex')}`;
204
+ }
205
+ function directoryEntriesDigest(candidate) {
206
+ return directoryEntriesDigestFrom(readdirSync(candidate, { withFileTypes: true }));
207
+ }
208
+ function snapshotDirectoryInventory(candidate) {
209
+ for (let attempt = 0; attempt < 2; attempt += 1) {
210
+ const before = snapshotDirectory(candidate);
211
+ const entriesDigest = directoryEntriesDigest(candidate);
212
+ const after = snapshotDirectory(candidate);
213
+ if (isDeepStrictEqual(before, after))
214
+ return { state: after, entriesDigest };
215
+ }
216
+ throw new Error(`Analyzer identity directory changed while it was read: ${candidate}`);
217
+ }
218
+ function readStableFile(candidate) {
219
+ for (let attempt = 0; attempt < 2; attempt += 1) {
220
+ const before = snapshotReadableFile(candidate);
221
+ const bytes = readFileSync(candidate);
222
+ const after = snapshotReadableFile(candidate);
223
+ if (isDeepStrictEqual(before, after))
224
+ return { bytes, state: after };
225
+ }
226
+ throw new Error(`Analyzer identity input changed while it was being read: ${candidate}`);
227
+ }
228
+ function readStableFileWithinBudget(candidate, budget, maxBytes) {
229
+ const before = snapshotReadableFile(candidate);
230
+ const bytes = stateSize(before.target, candidate);
231
+ if (budget.bytes + bytes > maxBytes) {
232
+ throw new Error(`Analyzer runtime scan exceeded ${maxBytes} bytes: ${candidate}`);
233
+ }
234
+ const stable = readStableFile(candidate);
235
+ budget.bytes += stable.bytes.length;
236
+ return stable;
237
+ }
238
+ /** Hash a stable file through a fixed-size buffer instead of materializing it. */
239
+ function hashStableFile(candidate) {
240
+ for (let attempt = 0; attempt < 2; attempt += 1) {
241
+ const before = snapshotReadableFile(candidate);
242
+ const expectedBytes = stateSize(before.target, candidate);
243
+ let descriptor = null;
244
+ try {
245
+ descriptor = openSync(candidate, fsConstants.O_RDONLY);
246
+ const openedBefore = statState(fstatSync(descriptor, { bigint: true }));
247
+ if (!isDeepStrictEqual(openedBefore, before.target))
248
+ continue;
249
+ const hash = createHash('sha256');
250
+ const buffer = Buffer.allocUnsafe(HASH_BUFFER_BYTES);
251
+ let bytes = 0;
252
+ while (true) {
253
+ const read = readSync(descriptor, buffer, 0, buffer.length, null);
254
+ if (read === 0)
255
+ break;
256
+ hash.update(buffer.subarray(0, read));
257
+ bytes += read;
258
+ if (bytes > expectedBytes)
259
+ break;
260
+ }
261
+ const openedAfter = statState(fstatSync(descriptor, { bigint: true }));
262
+ closeSync(descriptor);
263
+ descriptor = null;
264
+ const after = snapshotReadableFile(candidate);
265
+ if (bytes === expectedBytes &&
266
+ isDeepStrictEqual(openedBefore, openedAfter) &&
267
+ isDeepStrictEqual(before, after)) {
268
+ return { digest: `sha256:${hash.digest('hex')}`, state: after, bytes };
269
+ }
270
+ }
271
+ finally {
272
+ if (descriptor !== null)
273
+ closeSync(descriptor);
274
+ }
275
+ }
276
+ throw new Error(`Analyzer identity input changed while it was being hashed: ${candidate}`);
277
+ }
278
+ function resolveExistingPath(candidate) {
279
+ return realpathSync.native(path.resolve(candidate));
280
+ }
281
+ /**
282
+ * Case-stabilize a path's Windows drive letter so two processes that observed
283
+ * the same directory under different drive-letter casing (`c:\…` vs `C:\…`)
284
+ * produce byte-identical analyzer-identity path fields (#2668).
285
+ *
286
+ * `realpathSync.native` canonicalizes 8.3 short names and symlinks but does not
287
+ * guarantee the drive-letter case it returns — it can preserve whatever casing
288
+ * the caller's path carried, and `import.meta.url` casing depends on how each
289
+ * entry process (CLI shim vs `npx`/npm wrapper vs server worker) was launched.
290
+ * When `analyze` stamps `build.rootPath` under one casing and `status`
291
+ * recomputes it under another, `analyzerRunnerIdentitiesEqual` deep-compares
292
+ * unequal and `status` reports a freshly-analyzed, untouched repo as stale.
293
+ * Uppercasing the drive letter (drive letters are case-insensitive; uppercase
294
+ * is the conventional form) collapses that variance. POSIX paths are returned
295
+ * unchanged. `platform` is explicit so the transform is unit-testable off
296
+ * Windows.
297
+ *
298
+ * The optional `\\?\` extended-length prefix is preserved and the drive letter
299
+ * after it is still normalized; UNC paths (`\\server\share`, `\\?\UNC\...`)
300
+ * have no drive letter and are left untouched.
301
+ *
302
+ * That optional group is defensive, not a case `realpathSync.native` produces:
303
+ * libuv's `fs__realpath_handle` strips `\\?\` (and rewrites `\\?\UNC\` back to
304
+ * `\\`) before returning, so the prefix can only reach here from caller-supplied
305
+ * input, which `path.resolve` preserves (#2667).
306
+ *
307
+ * Preserving it is load-bearing. The roots this normalizes are not just compared —
308
+ * they are READ FROM: `resolveBuildRoot` joins `package.json` onto `packageRoot`,
309
+ * `collectBuildEntries` walks `buildRoot`, and the lockfile lookup walks
310
+ * `packageRoot`'s ancestors. Node does not re-add `\\?\` for over-MAX_PATH paths,
311
+ * so stripping here would break analyzer-identity resolution on a deep checkout
312
+ * exactly as it would at any other filesystem boundary. (These fields are also
313
+ * compared between an `analyze` and a later `status` run, so a shape change would
314
+ * additionally risk the #2668 false-stale class — but the filesystem reads are the
315
+ * reason that matters.)
316
+ *
317
+ * Registry-style path COMPARISON is a different domain, never opens what it
318
+ * canonicalizes, and does normalize the prefix away: see
319
+ * `stripWindowsLongPathPrefix` in `src/lib/utils.ts` and its use in
320
+ * `canonicalizePath`.
321
+ */
322
+ export function normalizeAnalyzerRootPath(p, platform) {
323
+ if (platform !== 'win32')
324
+ return p;
325
+ return p.replace(/^(\\\\\?\\)?([a-z]):/, (_match, prefix, drive) => `${prefix ?? ''}${drive.toUpperCase()}:`);
326
+ }
327
+ function isFile(candidate) {
328
+ try {
329
+ return statSync(candidate).isFile();
330
+ }
331
+ catch {
332
+ return false;
333
+ }
334
+ }
335
+ function readManifest(manifestPath, options, budget, limits) {
336
+ options.onCacheMissWork?.({ kind: 'manifest-read', path: manifestPath });
337
+ const { bytes, state } = readStableFileWithinBudget(manifestPath, budget, limits.runtimeBytes);
338
+ return { bytes, state, manifest: JSON.parse(bytes.toString('utf8')) };
339
+ }
340
+ function manifestLabel(manifest) {
341
+ const name = typeof manifest.name === 'string' ? manifest.name : '<unnamed>';
342
+ const version = typeof manifest.version === 'string' ? manifest.version : '<unversioned>';
343
+ return `${name}@${version}`;
344
+ }
345
+ /**
346
+ * Whether `candidate` is `parent` itself or lives beneath it.
347
+ *
348
+ * The absolute-result rejection is load-bearing on Windows: `path.relative`
349
+ * cannot express a relative path between two different drives, so it returns the
350
+ * absolute target instead — `path.win32.relative('C:\\parent', 'D:\\other')` is
351
+ * `'D:\\other'`. That string does not start with `..`, so the `..` checks alone
352
+ * would report an unrelated drive as *inside* the parent. This mirrors the
353
+ * containment guards elsewhere in the repo (`server/api.ts`,
354
+ * `server/git-clone.ts`, `group/extractors/fs-utils.ts`), which all pair the
355
+ * `..` check with `path.isAbsolute`.
356
+ *
357
+ * `pathApi` is injectable so the win32 semantics are unit-testable from a POSIX
358
+ * runner; production callers always use the platform-bound `path`.
359
+ */
360
+ function isInside(parent, candidate, pathApi = path) {
361
+ const relative = pathApi.relative(parent, candidate);
362
+ if (pathApi.isAbsolute(relative))
363
+ return false;
364
+ return relative === '' || (!relative.startsWith(`..${pathApi.sep}`) && relative !== '..');
365
+ }
366
+ /** Test seam for {@link isInside} (see `_hashAnalyzerIdentityFramesForTests`). */
367
+ export const _isInsideForTests = isInside;
368
+ function resolveBuildRoot(analyzerModulePath) {
369
+ let cursor = path.dirname(analyzerModulePath);
370
+ while (true) {
371
+ const base = path.basename(cursor);
372
+ if (base === 'src' || base === 'dist') {
373
+ const packageRoot = path.dirname(cursor);
374
+ const packageJson = path.join(packageRoot, 'package.json');
375
+ if (lstatSync(packageJson).isFile()) {
376
+ // Normalize the drive-letter case at this single upstream source so
377
+ // every derived identity path field — build.rootPath, identityCacheKey,
378
+ // and (via collectDependencyInputs) dependencyRuntime.manifestPath /
379
+ // lockfilePath — inherits a case-stable root and analyze-stamp equals
380
+ // status-recompute regardless of launch-path casing (#2668).
381
+ // Migration: a Windows index stamped before this fix carries the old,
382
+ // un-normalized casing, so the first post-upgrade `status` sees one
383
+ // spurious "stale" flip — self-healing on the next `analyze`, which
384
+ // re-stamps the normalized (idempotent) form.
385
+ return {
386
+ packageRoot: normalizeAnalyzerRootPath(packageRoot, process.platform),
387
+ buildRoot: normalizeAnalyzerRootPath(cursor, process.platform),
388
+ kind: base === 'src' ? 'source' : 'distribution',
389
+ };
390
+ }
391
+ }
392
+ const parent = path.dirname(cursor);
393
+ if (parent === cursor)
394
+ break;
395
+ cursor = parent;
396
+ }
397
+ throw new Error(`Cannot resolve GitNexus package root from analyzer module: ${analyzerModulePath}`);
398
+ }
399
+ function compareBytes(a, b) {
400
+ return Buffer.compare(Buffer.from(a), Buffer.from(b));
401
+ }
402
+ function collectBuildEntries(buildRoot, options, limits) {
403
+ const entries = [];
404
+ const pending = [
405
+ { absoluteDir: buildRoot, depth: 0 },
406
+ ];
407
+ let scannedEntries = 0;
408
+ let scannedBytes = 0;
409
+ while (pending.length > 0) {
410
+ const next = pending.pop();
411
+ if (!next)
412
+ break;
413
+ const { absoluteDir, depth } = next;
414
+ const directoryEntries = readDirectory(absoluteDir, options);
415
+ scannedEntries += directoryEntries.length;
416
+ if (scannedEntries > limits.buildEntries) {
417
+ throw new Error(`Analyzer build scan exceeded ${limits.buildEntries} entries: ${buildRoot}`);
418
+ }
419
+ for (const entry of directoryEntries) {
420
+ const absolutePath = path.join(absoluteDir, entry.name);
421
+ const relativePath = path.relative(buildRoot, absolutePath).split(path.sep).join('/');
422
+ const link = lstatSync(absolutePath, { bigint: true });
423
+ if (link.isDirectory()) {
424
+ entries.push({
425
+ absolutePath,
426
+ relativePath,
427
+ kind: 'directory',
428
+ state: statState(link),
429
+ });
430
+ if (depth >= limits.buildDepth) {
431
+ throw new Error(`Analyzer build scan exceeded depth ${limits.buildDepth}: ${absolutePath}`);
432
+ }
433
+ pending.push({ absoluteDir: absolutePath, depth: depth + 1 });
434
+ }
435
+ else if (link.isFile()) {
436
+ const state = statState(link);
437
+ scannedBytes += stateSize(state, absolutePath);
438
+ if (scannedBytes > limits.buildBytes) {
439
+ throw new Error(`Analyzer build scan exceeded ${limits.buildBytes} bytes: ${buildRoot}`);
440
+ }
441
+ entries.push({ absolutePath, relativePath, kind: 'file', state });
442
+ }
443
+ else if (link.isSymbolicLink()) {
444
+ entries.push({ absolutePath, relativePath, kind: 'symlink', state: statState(link) });
445
+ }
446
+ else {
447
+ throw new Error(`Unsupported analyzer build entry: ${absolutePath}`);
448
+ }
449
+ }
450
+ }
451
+ entries.sort((a, b) => compareBytes(a.relativePath, b.relativePath));
452
+ return entries;
453
+ }
454
+ function buildSnapshot(entries) {
455
+ return entries.map(({ relativePath, kind, state }) => ({ relativePath, kind, state }));
456
+ }
457
+ function buildCacheKey(entry) {
458
+ return JSON.stringify([entry.kind, entry.relativePath]);
459
+ }
460
+ function hashBuildTree(buildRoot, cache, options, limits) {
461
+ const entries = collectBuildEntries(buildRoot, options, limits);
462
+ const cachedEntries = new Map((cache?.buildEntries ?? []).map((entry) => [buildCacheKey(entry), entry]));
463
+ const nextEntries = [];
464
+ const hash = createHash('sha256');
465
+ updateCanonicalFrame(hash, ['domain', BUILD_CANONICALIZATION]);
466
+ for (const entry of entries) {
467
+ const cached = cachedEntries.get(buildCacheKey(entry));
468
+ let digest;
469
+ if (entry.kind !== 'directory' &&
470
+ cached?.digest &&
471
+ SHA256_PATTERN.test(cached.digest) &&
472
+ isDeepStrictEqual(cached.state, entry.state)) {
473
+ digest = cached.digest;
474
+ }
475
+ else if (entry.kind === 'file') {
476
+ const stable = hashStableFile(entry.absolutePath);
477
+ entry.state = stable.state.link;
478
+ digest = stable.digest;
479
+ options.onHashedInput?.({
480
+ kind: 'build',
481
+ path: entry.absolutePath,
482
+ bytes: stable.bytes,
483
+ });
484
+ }
485
+ else if (entry.kind === 'symlink') {
486
+ // Imported files and directories are resolved through symlinks by Node.
487
+ // Hashing only link text would let bytes outside buildRoot change without
488
+ // changing the receipt. Reject them instead of inventing an incomplete
489
+ // recursive trust boundary (target containment, cycles, and TOCTOU).
490
+ throw new Error(`Analyzer build symbolic links are not supported; materialize the build tree: ${entry.absolutePath}`);
491
+ }
492
+ updateCanonicalFrame(hash, [
493
+ 'build-entry',
494
+ entry.relativePath,
495
+ entry.kind,
496
+ digest ? digestBytes(digest) : Buffer.alloc(0),
497
+ ]);
498
+ nextEntries.push({
499
+ relativePath: entry.relativePath,
500
+ kind: entry.kind,
501
+ state: entry.state,
502
+ ...(digest ? { digest } : {}),
503
+ });
504
+ }
505
+ const directoryGuards = [
506
+ { relativePath: '', state: snapshotDirectory(buildRoot), entriesDigest: '' },
507
+ ...nextEntries
508
+ .filter((entry) => entry.kind === 'directory')
509
+ .map((entry) => ({
510
+ relativePath: entry.relativePath,
511
+ state: entry.state,
512
+ entriesDigest: '',
513
+ })),
514
+ ].map((guard) => {
515
+ const absolutePath = guard.relativePath
516
+ ? path.join(buildRoot, ...guard.relativePath.split('/'))
517
+ : buildRoot;
518
+ const inventory = snapshotDirectoryInventory(absolutePath);
519
+ return {
520
+ relativePath: guard.relativePath,
521
+ state: inventory.state,
522
+ entriesDigest: inventory.entriesDigest,
523
+ };
524
+ });
525
+ return {
526
+ digest: `sha256:${hash.digest('hex')}`,
527
+ entries: nextEntries,
528
+ snapshot: buildSnapshot(entries),
529
+ rootState: directoryGuards[0].state,
530
+ directoryGuards,
531
+ };
532
+ }
533
+ function recordDirectoryGuard(guards, candidate) {
534
+ if (guards.has(candidate))
535
+ return true;
536
+ try {
537
+ guards.set(candidate, snapshotDirectoryInventory(candidate));
538
+ return true;
539
+ }
540
+ catch {
541
+ return false;
542
+ }
543
+ }
544
+ function snapshotDependencyPathGuard(candidate) {
545
+ try {
546
+ const stat = lstatSync(candidate, { bigint: true });
547
+ const type = stat.isDirectory()
548
+ ? 'directory'
549
+ : stat.isFile()
550
+ ? 'file'
551
+ : stat.isSymbolicLink()
552
+ ? 'symlink'
553
+ : 'other';
554
+ return {
555
+ type,
556
+ state: statState(stat),
557
+ ...(type === 'symlink' ? { symlinkTarget: readlinkSync(candidate) } : {}),
558
+ };
559
+ }
560
+ catch {
561
+ return null;
562
+ }
563
+ }
564
+ function recordDependencyPathGuard(guards, candidate) {
565
+ if (guards.has(candidate))
566
+ return guards.get(candidate) ?? null;
567
+ const result = snapshotDependencyPathGuard(candidate);
568
+ guards.set(candidate, result);
569
+ return result;
570
+ }
571
+ function findNearestPackageLock(packageRoot, pathGuards, limits) {
572
+ let cursor = packageRoot;
573
+ let ancestors = 0;
574
+ while (true) {
575
+ ancestors += 1;
576
+ if (ancestors > limits.resolutionAncestors) {
577
+ throw new Error(`Analyzer package-lock lookup exceeded ${limits.resolutionAncestors} ancestors: ${packageRoot}`);
578
+ }
579
+ const candidate = path.join(cursor, 'package-lock.json');
580
+ recordDependencyPathGuard(pathGuards, candidate);
581
+ // Preserve the link path so ReadableFileState guards both the link and its
582
+ // resolved target. Realpathing here would miss a later retarget while the
583
+ // old target remained unchanged.
584
+ try {
585
+ const link = lstatSync(candidate);
586
+ if (link.isFile())
587
+ return path.resolve(candidate);
588
+ if (link.isSymbolicLink()) {
589
+ try {
590
+ const target = statSync(candidate);
591
+ if (!target.isFile()) {
592
+ throw new Error(`Analyzer package lock symbolic link does not resolve to a file: ${candidate}`);
593
+ }
594
+ }
595
+ catch {
596
+ throw new Error(`Analyzer package lock symbolic link does not resolve to a file: ${candidate}`);
597
+ }
598
+ return path.resolve(candidate);
599
+ }
600
+ throw new Error(`Analyzer package lock is not a regular file: ${candidate}`);
601
+ }
602
+ catch (error) {
603
+ if (error.code !== 'ENOENT')
604
+ throw error;
605
+ }
606
+ const parent = path.dirname(cursor);
607
+ if (parent === cursor)
608
+ return null;
609
+ cursor = parent;
610
+ }
611
+ }
612
+ function runtimePackageLocator(packageRoot, runtimeRoot) {
613
+ if (runtimeRoot === packageRoot)
614
+ return 'root:.';
615
+ const relative = path.relative(packageRoot, runtimeRoot).split(path.sep).join('/');
616
+ return `relative:${relative}`;
617
+ }
618
+ /** Protocols that name a checkout-local package instead of a registry tarball. */
619
+ const LOCAL_LINK_PROTOCOL_PATTERN = /^(?:file|link|workspace|portal):/;
620
+ /** npm's bare local-path shorthands: `./x`, `../x`, `/x`, `~/x`, `C:\x`. */
621
+ const LOCAL_LINK_PATH_PATTERN = /^(?:\.\.?[/\\]|~[/\\]|[/\\]|[A-Za-z]:)/;
622
+ function isLocallyLinkedSpecifier(specifier) {
623
+ if (typeof specifier !== 'string')
624
+ return false;
625
+ const value = specifier.trim();
626
+ return LOCAL_LINK_PROTOCOL_PATTERN.test(value) || LOCAL_LINK_PATH_PATTERN.test(value);
627
+ }
628
+ /**
629
+ * Whether a REALPATH'd package root lives inside some installed dependency
630
+ * tree. Used as the resolved-location half of "is this dependency a checkout
631
+ * this repository owns?" (see {@link undeclaredLocalDevDependencyNames}).
632
+ *
633
+ * The input must already be realpath'd: `resolveDependencyPackageRoot` returns
634
+ * `realpathSync.native`, so a package reached through a link out of
635
+ * `node_modules` reports its checkout location and a package that merely lives
636
+ * in `node_modules` reports a path that still carries the segment.
637
+ *
638
+ * `pathApi` is injectable so the Windows separator handling is unit-testable
639
+ * from a POSIX runner, exactly as {@link isInside} does. The separator sets
640
+ * differ deliberately: `\` is a legal filename character on POSIX, so only
641
+ * win32 may treat it as a boundary.
642
+ */
643
+ function hasNodeModulesSegment(candidate, pathApi = path) {
644
+ const segments = pathApi.sep === '\\' ? candidate.split(/[\\/]+/) : candidate.split('/');
645
+ return segments.includes('node_modules');
646
+ }
647
+ /** Test seam for {@link hasNodeModulesSegment} (see {@link _isInsideForTests}). */
648
+ export const _hasNodeModulesSegmentForTests = hasNodeModulesSegment;
649
+ /**
650
+ * How many dev dependencies may be admitted by RESOLVED LOCATION alone before
651
+ * the whole resolved-location channel is treated as untrustworthy and disabled.
652
+ *
653
+ * "Realpath carries no `node_modules` segment" is a proxy for "checkout-local",
654
+ * and a layout that materializes packages outside `node_modules` — pnpm with a
655
+ * relocated `virtual-store-dir`, a custom linker — makes every dev dependency
656
+ * pass it. Folding an entire dev tree into the receipt is not a graceful
657
+ * degradation: `runtimePackages`/`runtimeEntries`/`runtimeBytes` THROW, so a
658
+ * mis-fired proxy on a legitimate install would abort analyze outright.
659
+ *
660
+ * The bound is therefore on ADMISSIONS, and overflow admits NONE of them rather
661
+ * than an arbitrary prefix. A prefix would not bound the failure — the abort
662
+ * comes from the transitive payload of whichever trees get folded in — and it
663
+ * would make the receipt depend on an arbitrary slice of a sorted name list.
664
+ * Dropping the channel wholesale falls back to the specifier-only receipt,
665
+ * which is the behaviour that ships today and is known not to abort, and leaves
666
+ * the declared-intent half in {@link dependencyNames} untouched.
667
+ *
668
+ * Four is measured, not guessed. Monorepo and workspace links are declared
669
+ * (`file:`/`link:`/`workspace:`) and travel the uncapped declared half, so this
670
+ * channel only ever carries UNDECLARED `npm link <pkg>` — a manual, per-package
671
+ * developer action, in practice one or two packages. A mis-fire admits the
672
+ * entire dev-only set instead: 13 names in this repository's own install, tens
673
+ * in a typical application. The cap sits an order of magnitude below the
674
+ * mis-fire population and comfortably above realistic link counts.
675
+ */
676
+ const MAX_UNDECLARED_LOCAL_DEV_DEPENDENCIES = 4;
677
+ /**
678
+ * Dependency names whose resolved packages can contribute analyzer semantics.
679
+ *
680
+ * The three runtime sections are enumerated wholesale. `devDependencies` are
681
+ * deliberately not: a registry dev tool (vitest, eslint, typescript) is never
682
+ * loaded by the analyzer, and folding the dev tree into the receipt would churn
683
+ * `dependencyRuntime.digest` — and force a full re-analysis — on every unrelated
684
+ * devDependency bump.
685
+ *
686
+ * Locally linked dev dependencies are the exception. A `file:`/`link:`/
687
+ * `workspace:` sibling is part of this checkout and ships code the analyzer
688
+ * imports at runtime: GitNexus links `gitnexus-shared`, whose schema constants
689
+ * feed `RELATION_SCHEMA`/`NODE_SCHEMA_QUERIES`. In `kind: 'source'` runs that
690
+ * sibling sits outside `buildRoot`, so leaving it out let a semantic change
691
+ * there alter analyzer behaviour while moving neither `build.digest` nor
692
+ * `dependencyRuntime.digest` — DDL-affecting edits were still caught by the
693
+ * schema fingerprint, semantics-only edits by nothing.
694
+ *
695
+ * An unresolvable link (a published install, where the sibling checkout does not
696
+ * exist) still contributes its `<missing>` edge, so the linked package appearing
697
+ * or disappearing remains a receipt change rather than a silent one. That is why
698
+ * the specifier check cannot be replaced by resolution: resolution returns
699
+ * `null` for an absent linked checkout exactly as it does for an uninstalled
700
+ * registry dev tool, and the two must not be conflated.
701
+ *
702
+ * This function is the DECLARED-INTENT half and is enumerated for every package
703
+ * in the dependency BFS, so it must stay a pure function of the manifest. The
704
+ * RESOLVED-LOCATION half — `npm link <pkg>`, which leaves the specifier a
705
+ * registry range — lives in {@link undeclaredLocalDevDependencyNames} and is
706
+ * applied to the root package only.
707
+ */
708
+ function dependencyNames(manifest) {
709
+ const names = new Set();
710
+ for (const section of [
711
+ manifest.dependencies,
712
+ manifest.optionalDependencies,
713
+ manifest.peerDependencies,
714
+ ]) {
715
+ if (!section || typeof section !== 'object')
716
+ continue;
717
+ for (const name of Object.keys(section))
718
+ names.add(name);
719
+ }
720
+ const development = manifest.devDependencies;
721
+ if (development && typeof development === 'object') {
722
+ for (const [name, specifier] of Object.entries(development)) {
723
+ if (isLocallyLinkedSpecifier(specifier))
724
+ names.add(name);
725
+ }
726
+ }
727
+ return [...names].sort(compareBytes);
728
+ }
729
+ function resolveDependencyPackageRoot(fromRoot, packageName, pathGuards, limits) {
730
+ let cursor = fromRoot;
731
+ const segments = packageName.split('/');
732
+ let ancestors = 0;
733
+ while (true) {
734
+ ancestors += 1;
735
+ if (ancestors > limits.resolutionAncestors) {
736
+ throw new Error(`Analyzer dependency resolution exceeded ${limits.resolutionAncestors} ancestors: ${packageName}`);
737
+ }
738
+ const nodeModulesRoot = path.join(cursor, 'node_modules');
739
+ recordDependencyPathGuard(pathGuards, nodeModulesRoot);
740
+ let candidateParent = nodeModulesRoot;
741
+ for (const segment of segments) {
742
+ candidateParent = path.join(candidateParent, segment);
743
+ // Guard every lexical hop, not only the final manifest. Package
744
+ // managers commonly expose packages through symlinks; a retarget can
745
+ // otherwise preserve a hard-linked manifest's stat identity while
746
+ // changing the runtime payload tree selected by Node.
747
+ recordDependencyPathGuard(pathGuards, candidateParent);
748
+ }
749
+ const manifestPath = path.join(candidateParent, 'package.json');
750
+ recordDependencyPathGuard(pathGuards, manifestPath);
751
+ if (isFile(manifestPath))
752
+ return resolveExistingPath(path.dirname(manifestPath));
753
+ const parent = path.dirname(cursor);
754
+ if (parent === cursor)
755
+ return null;
756
+ cursor = parent;
757
+ }
758
+ }
759
+ /**
760
+ * Dev dependencies that are locally linked by INSTALLED LOCATION rather than by
761
+ * declared specifier — the `npm link <pkg>` shape, where the manifest still
762
+ * carries a registry range while `node_modules/<pkg>` is a symlink into a
763
+ * working checkout. {@link isLocallyLinkedSpecifier} is blind to those, yet the
764
+ * linked code is exactly as load-bearing for analyzer semantics as a declared
765
+ * `file:` sibling, so a semantic-only edit there would move neither digest.
766
+ *
767
+ * The resolver already knows: {@link resolveDependencyPackageRoot} returns a
768
+ * realpath, so a linked package reports a root outside every `node_modules`
769
+ * tree while an ordinary installed package cannot.
770
+ *
771
+ * Two properties are load-bearing and must not be relaxed:
772
+ *
773
+ * 1. ROOT ONLY. {@link dependencyNames} runs for every package in the BFS, and
774
+ * published tarballs keep their `devDependencies`, so probing dev-only names
775
+ * everywhere costs 1998 resolutions rather than the ~13 this manifest
776
+ * declares — measured on this install, with 0 true positives. Persisted
777
+ * `dependencyPathGuards` grow 2220 → 11049, and every guard is re-probed on
778
+ * each warm validation, so the cost is recurring and on the `status` path.
779
+ * Root-only costs 13 resolutions and ~29 guards.
780
+ * 2. An unresolvable name is NEVER admitted. `null` here means "uninstalled
781
+ * registry dev tool" far more often than "broken link", and admitting it
782
+ * would emit a `<missing>` edge for every dev tool absent from a published
783
+ * install. Declared links keep that edge through {@link dependencyNames};
784
+ * undeclared ones have no declaration to honour.
785
+ *
786
+ * The admission count is bounded by {@link MAX_UNDECLARED_LOCAL_DEV_DEPENDENCIES}.
787
+ */
788
+ function undeclaredLocalDevDependencyNames(rootPackage, pathGuards, limits) {
789
+ const development = rootPackage.manifest.devDependencies;
790
+ if (!development || typeof development !== 'object')
791
+ return [];
792
+ const admitted = [];
793
+ for (const [name, specifier] of Object.entries(development)) {
794
+ // Already carried by the declared half; resolving again would only add
795
+ // guards. Its `<missing>` edge is that half's responsibility.
796
+ if (isLocallyLinkedSpecifier(specifier))
797
+ continue;
798
+ const resolved = resolveDependencyPackageRoot(rootPackage.root, name, pathGuards, limits);
799
+ if (resolved !== null && !hasNodeModulesSegment(resolved))
800
+ admitted.push(name);
801
+ }
802
+ return admitted.length <= MAX_UNDECLARED_LOCAL_DEV_DEPENDENCIES ? admitted : [];
803
+ }
804
+ function collectRuntimePackages(packageRoot, directoryGuards, pathGuards, options, budget, limits) {
805
+ const rootManifestPath = path.join(packageRoot, 'package.json');
806
+ recordDirectoryGuard(directoryGuards, packageRoot);
807
+ const rootRead = readManifest(rootManifestPath, options, budget, limits);
808
+ const rootPackage = {
809
+ root: packageRoot,
810
+ locator: runtimePackageLocator(packageRoot, packageRoot),
811
+ manifestPath: rootManifestPath,
812
+ manifestBytes: rootRead.bytes,
813
+ manifestState: rootRead.state,
814
+ manifest: rootRead.manifest,
815
+ label: manifestLabel(rootRead.manifest),
816
+ };
817
+ const queue = [rootPackage];
818
+ const packages = new Map([[packageRoot, rootPackage]]);
819
+ const edges = [];
820
+ budget.packages = 1;
821
+ for (let index = 0; index < queue.length; index += 1) {
822
+ const parent = queue[index];
823
+ // The declared half is enumerated for every package; the resolved-location
824
+ // half is scoped to the root package, where the 1998-resolution /
825
+ // 8829-extra-guard blow-up documented on
826
+ // `undeclaredLocalDevDependencyNames` cannot occur. Dropping this scope is
827
+ // the expensive regression, so it is pinned by a guard-count test.
828
+ const dependencies = parent.root === packageRoot
829
+ ? [
830
+ ...new Set([
831
+ ...dependencyNames(parent.manifest),
832
+ ...undeclaredLocalDevDependencyNames(parent, pathGuards, limits),
833
+ ]),
834
+ ].sort(compareBytes)
835
+ : dependencyNames(parent.manifest);
836
+ for (const dependencyName of dependencies) {
837
+ budget.edges += 1;
838
+ if (budget.edges > limits.runtimeEdges) {
839
+ throw new Error(`Analyzer dependency graph exceeded ${limits.runtimeEdges} edges: ${packageRoot}`);
840
+ }
841
+ const childRoot = resolveDependencyPackageRoot(parent.root, dependencyName, pathGuards, limits);
842
+ if (!childRoot) {
843
+ edges.push({
844
+ parentLocator: parent.locator,
845
+ parentLabel: parent.label,
846
+ dependencyName,
847
+ childLocator: '<missing>',
848
+ childLabel: '<missing>',
849
+ });
850
+ continue;
851
+ }
852
+ let child = packages.get(childRoot);
853
+ if (!child) {
854
+ budget.packages += 1;
855
+ if (budget.packages > limits.runtimePackages) {
856
+ throw new Error(`Analyzer dependency graph exceeded ${limits.runtimePackages} packages: ${packageRoot}`);
857
+ }
858
+ const manifestPath = path.join(childRoot, 'package.json');
859
+ recordDirectoryGuard(directoryGuards, childRoot);
860
+ const read = readManifest(manifestPath, options, budget, limits);
861
+ child = {
862
+ root: childRoot,
863
+ locator: runtimePackageLocator(packageRoot, childRoot),
864
+ manifestPath,
865
+ manifestBytes: read.bytes,
866
+ manifestState: read.state,
867
+ manifest: read.manifest,
868
+ label: manifestLabel(read.manifest),
869
+ };
870
+ packages.set(childRoot, child);
871
+ queue.push(child);
872
+ }
873
+ edges.push({
874
+ parentLocator: parent.locator,
875
+ parentLabel: parent.label,
876
+ dependencyName,
877
+ childLocator: child.locator,
878
+ childLabel: child.label,
879
+ });
880
+ }
881
+ }
882
+ return { packages: [...packages.values()], edges };
883
+ }
884
+ const PRUNED_RUNTIME_DIRECTORIES = new Set(['node_modules', '.git', '.hg', '.svn']);
885
+ function shouldHashRuntimePayload(relativePath) {
886
+ const lower = relativePath.toLowerCase();
887
+ // Each package manifest is already hashed as a separately framed dependency
888
+ // input. Avoid counting/reading it twice while still hashing every other
889
+ // package payload: JavaScript modules, JSON data, native/Wasm binaries,
890
+ // extensionless exports, and files loaded explicitly through fs APIs. File
891
+ // and directory names are not authoritative platform-selection metadata:
892
+ // loaders may select or read a payload whose name mentions another target.
893
+ return lower !== 'package.json';
894
+ }
895
+ function collectArtifacts(root, canonicalPrefix, directoryGuards, options, budget, limits) {
896
+ const artifacts = [];
897
+ const pending = [{ absoluteDir: root, depth: 0 }];
898
+ while (pending.length > 0) {
899
+ const next = pending.pop();
900
+ if (!next)
901
+ break;
902
+ const { absoluteDir, depth } = next;
903
+ if (!recordDirectoryGuard(directoryGuards, absoluteDir)) {
904
+ throw new Error(`Analyzer runtime payload directory is unavailable: ${absoluteDir}`);
905
+ }
906
+ const entries = readDirectory(absoluteDir, options);
907
+ budget.entries += entries.length;
908
+ if (budget.entries > limits.runtimeEntries) {
909
+ throw new Error(`Analyzer runtime payload scan exceeded ${limits.runtimeEntries} entries: ${root}`);
910
+ }
911
+ for (const entry of entries) {
912
+ const absolutePath = path.join(absoluteDir, entry.name);
913
+ const relativePath = path.relative(root, absolutePath).split(path.sep).join('/');
914
+ const stat = lstatSync(absolutePath);
915
+ // Nested dependencies are collected from their manifests as separate
916
+ // packages. Only prune those separately traversed trees and VCS
917
+ // metadata; generic cache/model directories can contain loadable code,
918
+ // native addons, Wasm modules, or data consumed by the runtime.
919
+ //
920
+ // Pruning is decided by NAME alone. These four names never carry analyzer
921
+ // payload in any form: `node_modules` is traversed separately through
922
+ // `resolveDependencyPackageRoot` (which follows links and guards each
923
+ // hop), and a `.git`/`.hg`/`.svn` entry is VCS metadata whether it is a
924
+ // directory, a symbolic link into a shared store, or — inside a submodule
925
+ // or linked worktree checkout — a regular file holding a gitdir pointer.
926
+ // Hashing that pointer would make analyzer identity depend on where the
927
+ // checkout happens to live, which is a false-stale source, not a
928
+ // semantic input.
929
+ if (PRUNED_RUNTIME_DIRECTORIES.has(entry.name))
930
+ continue;
931
+ if (stat.isDirectory()) {
932
+ if (depth >= limits.runtimeDepth) {
933
+ throw new Error(`Analyzer runtime payload scan exceeded depth ${limits.runtimeDepth}: ${absolutePath}`);
934
+ }
935
+ pending.push({ absoluteDir: absolutePath, depth: depth + 1 });
936
+ }
937
+ else if (stat.isSymbolicLink() && !isFile(absolutePath)) {
938
+ // A symbolic link that does not resolve to a regular file must never
939
+ // reach the payload branch below: `snapshotReadableFile` stats the
940
+ // target, and a directory (or a dangling link) makes it throw, aborting
941
+ // the entire analyze. Workspace-linked checkouts made this reachable
942
+ // for every name, not just the pruned four — `dist -> build`, a
943
+ // vendored-grammar link, anything a sibling checkout ships.
944
+ //
945
+ // Such links are RECORDED by their link text rather than followed.
946
+ // Following them would (a) recurse without cycle protection — this
947
+ // traversal has none, so `self -> .` would ride the depth limit, which
948
+ // THROWS, trading one hard abort for another; (b) re-scan trees already
949
+ // reached by their real path, inflating the entry/byte budgets that
950
+ // also throw; and (c) need a whole containment/TOCTOU trust boundary
951
+ // for targets outside the package. Recording the text is cycle-free,
952
+ // costs one `readlink`, and still moves the receipt when the link is
953
+ // retargeted. The trade-off is that a link's target contributes no
954
+ // content of its own: when it points outside the package, only the
955
+ // link text is covered. Links that DO resolve to a regular file keep
956
+ // their content digest below, unchanged.
957
+ if (shouldHashRuntimePayload(relativePath)) {
958
+ budget.artifacts += 1;
959
+ if (budget.artifacts > limits.runtimePayloads) {
960
+ throw new Error(`Analyzer runtime payload scan exceeded ${limits.runtimePayloads} payloads: ${root}`);
961
+ }
962
+ artifacts.push({
963
+ absolutePath,
964
+ canonicalPath: `${canonicalPrefix}/${relativePath}`,
965
+ kind: 'unfollowed-symlink',
966
+ });
967
+ }
968
+ }
969
+ else if ((stat.isFile() || stat.isSymbolicLink()) &&
970
+ shouldHashRuntimePayload(relativePath)) {
971
+ const readableState = snapshotReadableFile(absolutePath);
972
+ const payloadBytes = stateSize(readableState.target, absolutePath);
973
+ budget.artifacts += 1;
974
+ if (budget.artifacts > limits.runtimePayloads) {
975
+ throw new Error(`Analyzer runtime payload scan exceeded ${limits.runtimePayloads} payloads: ${root}`);
976
+ }
977
+ if (budget.bytes + payloadBytes > limits.runtimeBytes) {
978
+ throw new Error(`Analyzer runtime scan exceeded ${limits.runtimeBytes} bytes: ${absolutePath}`);
979
+ }
980
+ budget.bytes += payloadBytes;
981
+ artifacts.push({
982
+ absolutePath,
983
+ canonicalPath: `${canonicalPrefix}/${relativePath}`,
984
+ kind: stat.isSymbolicLink() ? 'symlink' : 'file',
985
+ });
986
+ }
987
+ else if (!stat.isFile() && !stat.isSymbolicLink()) {
988
+ throw new Error(`Unsupported analyzer runtime payload entry: ${absolutePath}`);
989
+ }
990
+ }
991
+ }
992
+ return artifacts;
993
+ }
994
+ function collectVendoredGrammarInputs(packageRoot, directoryGuards, options, budget, limits) {
995
+ const vendorRoot = path.join(packageRoot, 'vendor');
996
+ if (!existsSync(vendorRoot) || !lstatSync(vendorRoot).isDirectory()) {
997
+ recordDirectoryGuard(directoryGuards, packageRoot);
998
+ return { manifests: [], artifacts: [] };
999
+ }
1000
+ const manifests = [];
1001
+ const artifacts = [];
1002
+ if (!recordDirectoryGuard(directoryGuards, vendorRoot)) {
1003
+ throw new Error(`Analyzer vendored runtime directory is unavailable: ${vendorRoot}`);
1004
+ }
1005
+ const vendorEntries = readDirectory(vendorRoot, options);
1006
+ budget.entries += vendorEntries.length;
1007
+ if (budget.entries > limits.runtimeEntries) {
1008
+ throw new Error(`Analyzer runtime payload scan exceeded ${limits.runtimeEntries} entries: ${vendorRoot}`);
1009
+ }
1010
+ for (const entry of vendorEntries) {
1011
+ if (!entry.isDirectory() || !entry.name.startsWith('tree-sitter-'))
1012
+ continue;
1013
+ const grammarRoot = path.join(vendorRoot, entry.name);
1014
+ const manifestPath = path.join(grammarRoot, 'package.json');
1015
+ if (isFile(manifestPath)) {
1016
+ options.onCacheMissWork?.({ kind: 'manifest-read', path: manifestPath });
1017
+ const read = readStableFileWithinBudget(manifestPath, budget, limits.runtimeBytes);
1018
+ manifests.push({
1019
+ canonicalPath: `vendor:${entry.name}/package.json`,
1020
+ absolutePath: manifestPath,
1021
+ bytes: read.bytes,
1022
+ state: read.state,
1023
+ });
1024
+ }
1025
+ artifacts.push(...collectArtifacts(grammarRoot, `vendor:${entry.name}`, directoryGuards, options, budget, limits));
1026
+ }
1027
+ return { manifests, artifacts };
1028
+ }
1029
+ function collectDependencyInputs(packageRoot, options, limits) {
1030
+ const directoryGuards = new Map();
1031
+ const pathGuards = new Map();
1032
+ const artifactScanBudget = {
1033
+ entries: 0,
1034
+ artifacts: 0,
1035
+ bytes: 0,
1036
+ packages: 0,
1037
+ edges: 0,
1038
+ };
1039
+ const manifestPath = resolveExistingPath(path.join(packageRoot, 'package.json'));
1040
+ const lockfilePath = findNearestPackageLock(packageRoot, pathGuards, limits);
1041
+ if (lockfilePath)
1042
+ options.onCacheMissWork?.({ kind: 'manifest-read', path: lockfilePath });
1043
+ const lockfile = lockfilePath
1044
+ ? readStableFileWithinBudget(lockfilePath, artifactScanBudget, limits.runtimeBytes)
1045
+ : null;
1046
+ const { packages, edges } = collectRuntimePackages(packageRoot, directoryGuards, pathGuards, options, artifactScanBudget, limits);
1047
+ const vendored = collectVendoredGrammarInputs(packageRoot, directoryGuards, options, artifactScanBudget, limits);
1048
+ // The root build and vendored grammars are covered separately. Every
1049
+ // resolved external runtime package is scanned, regardless of package name:
1050
+ // native loaders are not constrained to a permanent allowlist (for example,
1051
+ // Transformers resolves Sharp's @img platform packages).
1052
+ const artifacts = packages
1053
+ .slice(1)
1054
+ .flatMap((runtimePackage) => collectArtifacts(runtimePackage.root, `package:${runtimePackage.locator}`, directoryGuards, options, artifactScanBudget, limits));
1055
+ artifacts.push(...vendored.artifacts);
1056
+ artifacts.sort((a, b) => compareBytes(`${a.canonicalPath}\u0000${a.absolutePath}`, `${b.canonicalPath}\u0000${b.absolutePath}`));
1057
+ return {
1058
+ manifestPath,
1059
+ lockfilePath,
1060
+ lockfileBytes: lockfile?.bytes ?? null,
1061
+ lockfileState: lockfile?.state ?? null,
1062
+ packages,
1063
+ edges,
1064
+ vendoredManifests: vendored.manifests,
1065
+ artifacts,
1066
+ directoryGuards,
1067
+ pathGuards,
1068
+ };
1069
+ }
1070
+ function dependencySnapshot(inputs) {
1071
+ const edgeKey = (edge) => JSON.stringify([
1072
+ edge.parentLocator,
1073
+ edge.parentLabel,
1074
+ edge.dependencyName,
1075
+ edge.childLocator,
1076
+ edge.childLabel,
1077
+ ]);
1078
+ return {
1079
+ manifestPath: inputs.manifestPath,
1080
+ lockfilePath: inputs.lockfilePath,
1081
+ lockfileDigest: inputs.lockfileBytes ? sha256(inputs.lockfileBytes) : null,
1082
+ lockfileState: inputs.lockfileState,
1083
+ packages: inputs.packages
1084
+ .map((runtimePackage) => ({
1085
+ root: runtimePackage.root,
1086
+ locator: runtimePackage.locator,
1087
+ manifestPath: runtimePackage.manifestPath,
1088
+ manifestDigest: sha256(runtimePackage.manifestBytes),
1089
+ manifestState: runtimePackage.manifestState,
1090
+ label: runtimePackage.label,
1091
+ }))
1092
+ .sort((a, b) => compareBytes(a.locator, b.locator)),
1093
+ edges: inputs.edges.map(edgeKey).sort(compareBytes),
1094
+ vendoredManifests: inputs.vendoredManifests
1095
+ .map((entry) => ({
1096
+ canonicalPath: entry.canonicalPath,
1097
+ absolutePath: entry.absolutePath,
1098
+ digest: sha256(entry.bytes),
1099
+ state: entry.state,
1100
+ }))
1101
+ .sort((a, b) => compareBytes(a.canonicalPath, b.canonicalPath)),
1102
+ artifacts: inputs.artifacts.map((artifact) => ({
1103
+ absolutePath: artifact.absolutePath,
1104
+ canonicalPath: artifact.canonicalPath,
1105
+ kind: artifact.kind,
1106
+ state: snapshotRuntimeArtifact(artifact),
1107
+ })),
1108
+ directories: [...inputs.directoryGuards.entries()]
1109
+ .map(([absolutePath, guard]) => ({ absolutePath, ...guard }))
1110
+ .sort((a, b) => compareBytes(a.absolutePath, b.absolutePath)),
1111
+ paths: [...inputs.pathGuards.entries()]
1112
+ .map(([absolutePath, result]) => ({ absolutePath, result }))
1113
+ .sort((a, b) => compareBytes(a.absolutePath, b.absolutePath)),
1114
+ };
1115
+ }
1116
+ function artifactCacheKey(artifact) {
1117
+ return JSON.stringify([artifact.kind, artifact.canonicalPath, artifact.absolutePath]);
1118
+ }
1119
+ function hashRuntimeArtifact(artifact, cache, options) {
1120
+ if (artifact.kind === 'unfollowed-symlink') {
1121
+ // The link text is the entire payload, so there is no file read for a
1122
+ // cached digest to amortize: recompute it and stay independent of the
1123
+ // cache's freshness. The distinct frame label keeps a link recording from
1124
+ // ever colliding with a content digest.
1125
+ const state = snapshotSymlinkArtifact(artifact.absolutePath);
1126
+ return {
1127
+ ...artifact,
1128
+ state,
1129
+ digest: hashCanonicalFrames([
1130
+ ['runtime-payload-link-v1', artifact.kind, state.symlinkTarget],
1131
+ ]),
1132
+ };
1133
+ }
1134
+ const before = snapshotReadableFile(artifact.absolutePath);
1135
+ if (cache &&
1136
+ cache.kind !== 'unfollowed-symlink' &&
1137
+ SHA256_PATTERN.test(cache.digest) &&
1138
+ isDeepStrictEqual(cache.state, before)) {
1139
+ return { ...artifact, state: before, digest: cache.digest };
1140
+ }
1141
+ const stable = hashStableFile(artifact.absolutePath);
1142
+ const digest = hashCanonicalFrames([
1143
+ [
1144
+ 'runtime-payload-content-v1',
1145
+ artifact.kind,
1146
+ stable.state.symlinkTarget ?? '',
1147
+ digestBytes(stable.digest),
1148
+ ],
1149
+ ]);
1150
+ options.onHashedInput?.({
1151
+ kind: 'runtime-artifact',
1152
+ path: artifact.absolutePath,
1153
+ bytes: stable.bytes,
1154
+ });
1155
+ return { ...artifact, state: stable.state, digest };
1156
+ }
1157
+ function compareEdges(a, b) {
1158
+ return compareBytes(JSON.stringify([
1159
+ a.parentLocator,
1160
+ a.parentLabel,
1161
+ a.dependencyName,
1162
+ a.childLocator,
1163
+ a.childLabel,
1164
+ ]), JSON.stringify([
1165
+ b.parentLocator,
1166
+ b.parentLabel,
1167
+ b.dependencyName,
1168
+ b.childLocator,
1169
+ b.childLabel,
1170
+ ]));
1171
+ }
1172
+ function hashDependencyRuntime(inputs, cache, options, runtimeVariant) {
1173
+ const cachedArtifacts = new Map((cache?.artifactEntries ?? []).map((entry) => [artifactCacheKey(entry), entry]));
1174
+ const nextArtifacts = [];
1175
+ const hash = createHash('sha256');
1176
+ updateCanonicalFrame(hash, ['domain', DEPENDENCY_RUNTIME_CANONICALIZATION]);
1177
+ updateCanonicalFrame(hash, [
1178
+ 'runtime-variant',
1179
+ runtimeVariant.nodeVersion,
1180
+ runtimeVariant.platform,
1181
+ runtimeVariant.architecture,
1182
+ runtimeVariant.endianness,
1183
+ runtimeVariant.modulesAbi,
1184
+ runtimeVariant.napiAbi,
1185
+ runtimeVariant.libc,
1186
+ ]);
1187
+ updateCanonicalFrame(hash, [
1188
+ 'lockfile',
1189
+ inputs.lockfilePath ? 'present' : 'absent',
1190
+ inputs.lockfileBytes ?? Buffer.alloc(0),
1191
+ ]);
1192
+ const packageEntries = inputs.packages
1193
+ .map((runtimePackage) => ({
1194
+ canonicalPath: `package:${runtimePackage.locator}/package.json`,
1195
+ bytes: runtimePackage.manifestBytes,
1196
+ }))
1197
+ .concat(inputs.vendoredManifests.map(({ canonicalPath, bytes }) => ({ canonicalPath, bytes })))
1198
+ .sort((a, b) => compareBytes(a.canonicalPath, b.canonicalPath));
1199
+ for (const entry of packageEntries) {
1200
+ updateCanonicalFrame(hash, ['package-manifest', entry.canonicalPath, entry.bytes]);
1201
+ }
1202
+ for (const edge of [...inputs.edges].sort(compareEdges)) {
1203
+ updateCanonicalFrame(hash, [
1204
+ 'dependency-edge',
1205
+ edge.parentLocator,
1206
+ edge.parentLabel,
1207
+ edge.dependencyName,
1208
+ edge.childLocator,
1209
+ edge.childLabel,
1210
+ ]);
1211
+ }
1212
+ for (const artifact of inputs.artifacts) {
1213
+ const hashed = hashRuntimeArtifact(artifact, cachedArtifacts.get(artifactCacheKey(artifact)), options);
1214
+ updateCanonicalFrame(hash, [
1215
+ 'runtime-artifact',
1216
+ artifact.canonicalPath,
1217
+ artifact.kind,
1218
+ digestBytes(hashed.digest),
1219
+ ]);
1220
+ nextArtifacts.push(hashed);
1221
+ }
1222
+ return {
1223
+ identity: {
1224
+ manifestPath: inputs.manifestPath,
1225
+ lockfilePath: inputs.lockfilePath,
1226
+ canonicalization: DEPENDENCY_RUNTIME_CANONICALIZATION,
1227
+ packageCount: inputs.packages.length,
1228
+ artifactCount: inputs.artifacts.length,
1229
+ digest: `sha256:${hash.digest('hex')}`,
1230
+ },
1231
+ entries: nextArtifacts,
1232
+ };
1233
+ }
1234
+ function isStatState(value) {
1235
+ if (typeof value !== 'object' || value === null)
1236
+ return false;
1237
+ const record = value;
1238
+ return ['dev', 'ino', 'mode', 'nlink', 'size', 'mtimeNs', 'ctimeNs'].every((key) => typeof record[key] === 'string');
1239
+ }
1240
+ function isReadableFileState(value) {
1241
+ if (typeof value !== 'object' || value === null)
1242
+ return false;
1243
+ const record = value;
1244
+ return (isStatState(record.link) &&
1245
+ isStatState(record.target) &&
1246
+ (record.symlinkTarget === undefined || typeof record.symlinkTarget === 'string'));
1247
+ }
1248
+ function isSymlinkArtifactState(value) {
1249
+ if (typeof value !== 'object' || value === null)
1250
+ return false;
1251
+ const record = value;
1252
+ // `target === undefined` keeps a readable-file state from masquerading as an
1253
+ // unresolved link recording, which would otherwise be validated against the
1254
+ // wrong guard mode on the warm path.
1255
+ return (isStatState(record.link) &&
1256
+ typeof record.symlinkTarget === 'string' &&
1257
+ record.target === undefined);
1258
+ }
1259
+ function isDependencyPathGuardResult(value) {
1260
+ if (value === null)
1261
+ return true;
1262
+ if (typeof value !== 'object')
1263
+ return false;
1264
+ const record = value;
1265
+ return (['directory', 'file', 'symlink', 'other'].includes(String(record.type)) &&
1266
+ isStatState(record.state) &&
1267
+ (record.type === 'symlink'
1268
+ ? typeof record.symlinkTarget === 'string'
1269
+ : record.symlinkTarget === undefined));
1270
+ }
1271
+ function isRuntimeVariant(value) {
1272
+ if (typeof value !== 'object' || value === null)
1273
+ return false;
1274
+ const record = value;
1275
+ return [
1276
+ 'executablePath',
1277
+ 'nodeVersion',
1278
+ 'platform',
1279
+ 'architecture',
1280
+ 'endianness',
1281
+ 'modulesAbi',
1282
+ 'napiAbi',
1283
+ 'libc',
1284
+ ].every((key) => typeof record[key] === 'string' && record[key].length > 0);
1285
+ }
1286
+ function isTraversalLimits(value) {
1287
+ if (typeof value !== 'object' || value === null)
1288
+ return false;
1289
+ const record = value;
1290
+ return Object.keys(DEFAULT_TRAVERSAL_LIMITS).every((key) => Number.isSafeInteger(record[key]) &&
1291
+ Number(record[key]) >= 1 &&
1292
+ Number(record[key]) <= DEFAULT_TRAVERSAL_LIMITS[key]);
1293
+ }
1294
+ function isDependencyRuntimeIdentity(value) {
1295
+ if (typeof value !== 'object' || value === null)
1296
+ return false;
1297
+ const dependency = value;
1298
+ return (typeof dependency.manifestPath === 'string' &&
1299
+ dependency.manifestPath.length > 0 &&
1300
+ (dependency.lockfilePath === null ||
1301
+ (typeof dependency.lockfilePath === 'string' && dependency.lockfilePath.length > 0)) &&
1302
+ dependency.canonicalization === DEPENDENCY_RUNTIME_CANONICALIZATION &&
1303
+ Number.isSafeInteger(dependency.packageCount) &&
1304
+ Number(dependency.packageCount) >= 1 &&
1305
+ Number.isSafeInteger(dependency.artifactCount) &&
1306
+ Number(dependency.artifactCount) >= 0 &&
1307
+ typeof dependency.digest === 'string' &&
1308
+ SHA256_PATTERN.test(dependency.digest));
1309
+ }
1310
+ function isSafeBuildRelativePath(value) {
1311
+ if (typeof value !== 'string' || value.length === 0 || path.posix.isAbsolute(value)) {
1312
+ return false;
1313
+ }
1314
+ const normalized = path.posix.normalize(value);
1315
+ return normalized === value && normalized !== '..' && !normalized.startsWith('../');
1316
+ }
1317
+ function isSafeBuildDirectoryGuardPath(value) {
1318
+ return value === '' || isSafeBuildRelativePath(value);
1319
+ }
1320
+ function isIdentityCachePayload(value, packageRoot, buildRoot, runtimeVariant, traversalLimits) {
1321
+ if (typeof value !== 'object' || value === null)
1322
+ return false;
1323
+ const record = value;
1324
+ if (record.schemaVersion !== IDENTITY_CACHE_SCHEMA_VERSION ||
1325
+ record.packageRoot !== packageRoot ||
1326
+ record.buildRoot !== buildRoot ||
1327
+ typeof record.packageVersion !== 'string' ||
1328
+ record.packageVersion.length === 0 ||
1329
+ (record.buildKind !== 'source' && record.buildKind !== 'distribution') ||
1330
+ record.buildCanonicalization !== BUILD_CANONICALIZATION ||
1331
+ record.dependencyCanonicalization !== DEPENDENCY_RUNTIME_CANONICALIZATION ||
1332
+ !isTraversalLimits(record.traversalLimits) ||
1333
+ !isDeepStrictEqual(record.traversalLimits, traversalLimits) ||
1334
+ !isRuntimeVariant(record.runtimeVariant) ||
1335
+ !isDeepStrictEqual(record.runtimeVariant, runtimeVariant) ||
1336
+ !isStatState(record.buildRootState) ||
1337
+ typeof record.buildDigest !== 'string' ||
1338
+ !SHA256_PATTERN.test(record.buildDigest) ||
1339
+ !isDependencyRuntimeIdentity(record.dependencyIdentity) ||
1340
+ !Array.isArray(record.buildEntries) ||
1341
+ !Array.isArray(record.buildDirectoryGuards) ||
1342
+ !Array.isArray(record.dependencyFileGuards) ||
1343
+ !Array.isArray(record.dependencyDirectoryGuards) ||
1344
+ !Array.isArray(record.dependencyPathGuards) ||
1345
+ !Array.isArray(record.artifactEntries) ||
1346
+ record.buildEntries.length > MAX_CACHE_ENTRIES ||
1347
+ record.buildDirectoryGuards.length > MAX_CACHE_ENTRIES ||
1348
+ record.dependencyFileGuards.length > MAX_CACHE_ENTRIES ||
1349
+ record.dependencyDirectoryGuards.length > MAX_CACHE_ENTRIES ||
1350
+ record.dependencyPathGuards.length > MAX_CACHE_ENTRIES ||
1351
+ record.artifactEntries.length > MAX_CACHE_ENTRIES) {
1352
+ return false;
1353
+ }
1354
+ const buildEntriesValid = record.buildEntries.every((entry) => {
1355
+ if (typeof entry !== 'object' || entry === null)
1356
+ return false;
1357
+ const item = entry;
1358
+ return (isSafeBuildRelativePath(item.relativePath) &&
1359
+ ['directory', 'file'].includes(String(item.kind)) &&
1360
+ isStatState(item.state) &&
1361
+ (item.kind === 'directory'
1362
+ ? item.digest === undefined
1363
+ : typeof item.digest === 'string' && SHA256_PATTERN.test(item.digest)));
1364
+ });
1365
+ const buildDirectoryGuardsValid = record.buildDirectoryGuards.every((entry) => {
1366
+ if (typeof entry !== 'object' || entry === null)
1367
+ return false;
1368
+ const item = entry;
1369
+ return (isSafeBuildDirectoryGuardPath(item.relativePath) &&
1370
+ isStatState(item.state) &&
1371
+ typeof item.entriesDigest === 'string' &&
1372
+ SHA256_PATTERN.test(item.entriesDigest));
1373
+ });
1374
+ const dependencyFileGuardsValid = record.dependencyFileGuards.every((entry) => {
1375
+ if (typeof entry !== 'object' || entry === null)
1376
+ return false;
1377
+ const item = entry;
1378
+ return (typeof item.absolutePath === 'string' &&
1379
+ path.isAbsolute(item.absolutePath) &&
1380
+ isReadableFileState(item.state));
1381
+ });
1382
+ const dependencyDirectoryGuardsValid = record.dependencyDirectoryGuards.every((entry) => {
1383
+ if (typeof entry !== 'object' || entry === null)
1384
+ return false;
1385
+ const item = entry;
1386
+ return (typeof item.absolutePath === 'string' &&
1387
+ path.isAbsolute(item.absolutePath) &&
1388
+ isStatState(item.state) &&
1389
+ typeof item.entriesDigest === 'string' &&
1390
+ SHA256_PATTERN.test(item.entriesDigest));
1391
+ });
1392
+ const dependencyPathGuardsValid = record.dependencyPathGuards.every((entry) => {
1393
+ if (typeof entry !== 'object' || entry === null)
1394
+ return false;
1395
+ const item = entry;
1396
+ return (typeof item.absolutePath === 'string' &&
1397
+ path.isAbsolute(item.absolutePath) &&
1398
+ isDependencyPathGuardResult(item.result));
1399
+ });
1400
+ const artifactEntriesValid = record.artifactEntries.every((entry) => {
1401
+ if (typeof entry !== 'object' || entry === null)
1402
+ return false;
1403
+ const item = entry;
1404
+ if (typeof item.absolutePath !== 'string' ||
1405
+ !path.isAbsolute(item.absolutePath) ||
1406
+ typeof item.canonicalPath !== 'string' ||
1407
+ typeof item.digest !== 'string' ||
1408
+ !SHA256_PATTERN.test(item.digest)) {
1409
+ return false;
1410
+ }
1411
+ return item.kind === 'unfollowed-symlink'
1412
+ ? isSymlinkArtifactState(item.state)
1413
+ : (item.kind === 'file' || item.kind === 'symlink') && isReadableFileState(item.state);
1414
+ });
1415
+ const hasBuildRootGuard = record.buildDirectoryGuards.some((entry) => typeof entry === 'object' &&
1416
+ entry !== null &&
1417
+ entry.relativePath === '');
1418
+ return (buildEntriesValid &&
1419
+ buildDirectoryGuardsValid &&
1420
+ hasBuildRootGuard &&
1421
+ dependencyFileGuardsValid &&
1422
+ dependencyDirectoryGuardsValid &&
1423
+ dependencyPathGuardsValid &&
1424
+ artifactEntriesValid);
1425
+ }
1426
+ function currentUid() {
1427
+ return typeof process.getuid === 'function' ? process.getuid() : null;
1428
+ }
1429
+ function isOwnedPrivateDirectory(candidate) {
1430
+ try {
1431
+ const stat = lstatSync(candidate);
1432
+ if (!stat.isDirectory() || stat.isSymbolicLink())
1433
+ return false;
1434
+ const uid = currentUid();
1435
+ return uid !== null && stat.uid === uid && (stat.mode & 0o077) === 0;
1436
+ }
1437
+ catch {
1438
+ return false;
1439
+ }
1440
+ }
1441
+ function isSafeTemporaryParent(candidate) {
1442
+ try {
1443
+ const stat = lstatSync(candidate);
1444
+ if (!stat.isDirectory() || stat.isSymbolicLink())
1445
+ return false;
1446
+ if (currentUid() === null)
1447
+ return false;
1448
+ // A shared temp root is safe only with the sticky bit: another UID then
1449
+ // cannot replace the private child after our atomic mkdir + owner check.
1450
+ const writableByOthers = (stat.mode & 0o022) !== 0;
1451
+ return !writableByOthers || (stat.mode & 0o1000) !== 0;
1452
+ }
1453
+ catch {
1454
+ return false;
1455
+ }
1456
+ }
1457
+ function ensurePrivateChild(parent, childName) {
1458
+ let resolvedParent;
1459
+ try {
1460
+ resolvedParent = realpathSync.native(parent);
1461
+ }
1462
+ catch {
1463
+ return null;
1464
+ }
1465
+ if (!isSafeTemporaryParent(resolvedParent))
1466
+ return null;
1467
+ const candidate = path.join(resolvedParent, childName);
1468
+ try {
1469
+ // Non-recursive mkdir is intentional: it cannot follow an attacker-made
1470
+ // intermediate symlink. EEXIST is accepted only after the ownership/mode
1471
+ // validation below.
1472
+ mkdirSync(candidate, { mode: 0o700 });
1473
+ }
1474
+ catch (error) {
1475
+ if (error.code !== 'EEXIST')
1476
+ return null;
1477
+ }
1478
+ return isOwnedPrivateDirectory(candidate) ? candidate : null;
1479
+ }
1480
+ function defaultCacheDirectory() {
1481
+ // On platforms without POSIX ownership APIs we cannot prove that a default
1482
+ // cache file is private to this process's user. Persistence is therefore
1483
+ // disabled unless the operator supplied an explicit trusted override.
1484
+ const uid = currentUid();
1485
+ if (uid === null)
1486
+ return null;
1487
+ // XDG_RUNTIME_DIR is already per-user and normally 0700. Use it only when
1488
+ // that contract is true; a spoofed/insecure value falls back to the sticky
1489
+ // OS temp root rather than becoming a cache-poisoning surface.
1490
+ const runtimeDir = process.env.XDG_RUNTIME_DIR;
1491
+ if (runtimeDir) {
1492
+ try {
1493
+ const resolvedRuntime = realpathSync.native(runtimeDir);
1494
+ if (isOwnedPrivateDirectory(resolvedRuntime)) {
1495
+ const runtimeCache = ensurePrivateChild(resolvedRuntime, 'gitnexus-analyzer-identity');
1496
+ if (runtimeCache)
1497
+ return runtimeCache;
1498
+ }
1499
+ }
1500
+ catch {
1501
+ /* fall through to the OS temp directory */
1502
+ }
1503
+ }
1504
+ let tempRoot;
1505
+ try {
1506
+ tempRoot = realpathSync.native(os.tmpdir());
1507
+ }
1508
+ catch {
1509
+ return null;
1510
+ }
1511
+ return ensurePrivateChild(tempRoot, `gitnexus-analyzer-identity-${uid}`);
1512
+ }
1513
+ const TRUSTED_CACHE_DIRECTORY_ENV = 'GITNEXUS_ANALYZER_IDENTITY_CACHE_DIR';
1514
+ function pathsEqual(left, right) {
1515
+ return process.platform === 'win32' ? left.toLowerCase() === right.toLowerCase() : left === right;
1516
+ }
1517
+ function trustedEnvironmentCacheDirectory() {
1518
+ const configured = process.env[TRUSTED_CACHE_DIRECTORY_ENV];
1519
+ if (configured === undefined)
1520
+ return null;
1521
+ if (configured.length === 0 || configured.includes('\0') || !path.isAbsolute(configured)) {
1522
+ throw new Error(`${TRUSTED_CACHE_DIRECTORY_ENV} must name an absolute protected directory`);
1523
+ }
1524
+ const normalized = path.normalize(configured);
1525
+ let resolved;
1526
+ try {
1527
+ const link = lstatSync(normalized);
1528
+ if (!link.isDirectory() || link.isSymbolicLink()) {
1529
+ throw new Error('not a real directory');
1530
+ }
1531
+ resolved = realpathSync.native(normalized);
1532
+ }
1533
+ catch {
1534
+ throw new Error(`${TRUSTED_CACHE_DIRECTORY_ENV} must name a pre-existing protected non-symlink directory`);
1535
+ }
1536
+ // Reject junctions/symlinked ancestors as well as a symlink final component.
1537
+ // The environment variable is an explicit trust assertion, but its spelling
1538
+ // must still bind exactly to the directory the cache will use.
1539
+ if (!pathsEqual(path.resolve(normalized), resolved)) {
1540
+ throw new Error(`${TRUSTED_CACHE_DIRECTORY_ENV} must not traverse symbolic links or junctions`);
1541
+ }
1542
+ return resolved;
1543
+ }
1544
+ function cacheDirectory(options, packageRoot, buildRoot) {
1545
+ // An explicit location is a trusted operator/test override and therefore
1546
+ // remains authoritative, including when the secure default is unavailable.
1547
+ if (options.cacheDirectory) {
1548
+ const explicit = path.resolve(options.cacheDirectory);
1549
+ try {
1550
+ // Create it before any build/dependency directory guards are captured.
1551
+ // A cache nested immediately under a package root then changes that
1552
+ // parent's directory state once, not after we persist the first entry.
1553
+ mkdirSync(explicit, { recursive: true, mode: 0o700 });
1554
+ }
1555
+ catch {
1556
+ /* persistence remains optional and will fail closed */
1557
+ }
1558
+ return explicit;
1559
+ }
1560
+ const configured = trustedEnvironmentCacheDirectory();
1561
+ if (configured) {
1562
+ if (isInside(packageRoot, configured) || isInside(buildRoot, configured)) {
1563
+ throw new Error(`${TRUSTED_CACHE_DIRECTORY_ENV} must be outside the analyzer package and build roots`);
1564
+ }
1565
+ return configured;
1566
+ }
1567
+ return defaultCacheDirectory();
1568
+ }
1569
+ function hasTrustedCacheOverride(options) {
1570
+ return (options.cacheDirectory !== undefined || process.env[TRUSTED_CACHE_DIRECTORY_ENV] !== undefined);
1571
+ }
1572
+ function identityCacheKey(packageRoot, buildRoot, runtimeVariant, traversalLimits) {
1573
+ return hashCanonicalFrames([
1574
+ [
1575
+ 'analyzer-identity-cache-key-v3',
1576
+ String(IDENTITY_CACHE_SCHEMA_VERSION),
1577
+ packageRoot,
1578
+ buildRoot,
1579
+ runtimeVariant.executablePath,
1580
+ runtimeVariant.nodeVersion,
1581
+ runtimeVariant.platform,
1582
+ runtimeVariant.architecture,
1583
+ runtimeVariant.endianness,
1584
+ runtimeVariant.modulesAbi,
1585
+ runtimeVariant.napiAbi,
1586
+ runtimeVariant.libc,
1587
+ JSON.stringify(traversalLimits),
1588
+ ],
1589
+ ]).slice('sha256:'.length);
1590
+ }
1591
+ const MAX_PROCESS_CACHE_ENTRIES = 12;
1592
+ const processIdentityCache = new Map();
1593
+ /** @internal Clear process-local reuse between simulated-process unit tests. */
1594
+ export function _clearAnalyzerIdentityProcessCacheForTests() {
1595
+ processIdentityCache.clear();
1596
+ }
1597
+ function cachePathFor(packageRoot, buildRoot, runtimeVariant, traversalLimits, options) {
1598
+ const directory = cacheDirectory(options, packageRoot, buildRoot);
1599
+ if (!directory)
1600
+ return null;
1601
+ const key = identityCacheKey(packageRoot, buildRoot, runtimeVariant, traversalLimits);
1602
+ return path.join(directory, `${key}.json`);
1603
+ }
1604
+ function readCacheFile(target) {
1605
+ let descriptor = null;
1606
+ try {
1607
+ const noFollow = 'O_NOFOLLOW' in fsConstants ? fsConstants.O_NOFOLLOW : 0;
1608
+ descriptor = openSync(target, fsConstants.O_RDONLY | noFollow);
1609
+ const stat = fstatSync(descriptor);
1610
+ const uid = currentUid();
1611
+ if (!stat.isFile() ||
1612
+ stat.isSymbolicLink() ||
1613
+ stat.size > MAX_CACHE_FILE_BYTES ||
1614
+ (uid !== null && (stat.uid !== uid || (stat.mode & 0o077) !== 0))) {
1615
+ return null;
1616
+ }
1617
+ return readFileSync(descriptor, 'utf8');
1618
+ }
1619
+ catch {
1620
+ return null;
1621
+ }
1622
+ finally {
1623
+ if (descriptor !== null)
1624
+ closeSync(descriptor);
1625
+ }
1626
+ }
1627
+ function loadIdentityCache(packageRoot, buildRoot, runtimeVariant, traversalLimits, options) {
1628
+ // Invalid explicit cache configuration is an operator error, not an optional
1629
+ // cache miss. Resolve it outside the best-effort envelope read below.
1630
+ const target = cachePathFor(packageRoot, buildRoot, runtimeVariant, traversalLimits, options);
1631
+ try {
1632
+ // Validate any operator-selected persistence location on every call, even
1633
+ // when the payload itself is reusable from this process's guarded LRU.
1634
+ const key = identityCacheKey(packageRoot, buildRoot, runtimeVariant, traversalLimits);
1635
+ const local = processIdentityCache.get(key);
1636
+ if (local)
1637
+ return local;
1638
+ if (!target)
1639
+ return null;
1640
+ const raw = readCacheFile(target);
1641
+ if (raw === null)
1642
+ return null;
1643
+ const parsed = JSON.parse(raw);
1644
+ if (typeof parsed !== 'object' ||
1645
+ parsed === null ||
1646
+ typeof parsed.checksum !== 'string' ||
1647
+ !isIdentityCachePayload(parsed.payload, packageRoot, buildRoot, runtimeVariant, traversalLimits) ||
1648
+ parsed.checksum !== sha256(JSON.stringify(parsed.payload))) {
1649
+ return null;
1650
+ }
1651
+ return parsed.payload;
1652
+ }
1653
+ catch {
1654
+ return null;
1655
+ }
1656
+ }
1657
+ const CACHE_GUARD_PROBE_SCRIPT = String.raw `
1658
+ const fs = require('node:fs/promises');
1659
+ const crypto = require('node:crypto');
1660
+ const state = (value) => ({
1661
+ dev: String(value.dev), ino: String(value.ino), mode: String(value.mode),
1662
+ nlink: String(value.nlink), size: String(value.size),
1663
+ mtimeNs: String(value.mtimeNs), ctimeNs: String(value.ctimeNs),
1664
+ });
1665
+ const frame = (hash, fields) => {
1666
+ const fieldCount = Buffer.allocUnsafe(4);
1667
+ fieldCount.writeUInt32BE(fields.length);
1668
+ hash.update(fieldCount);
1669
+ for (const field of fields) {
1670
+ const bytes = Buffer.from(field);
1671
+ const length = Buffer.allocUnsafe(8);
1672
+ length.writeBigUInt64BE(BigInt(bytes.length));
1673
+ hash.update(length);
1674
+ hash.update(bytes);
1675
+ }
1676
+ };
1677
+ const inventoryDigest = (entries) => {
1678
+ const normalized = entries.map((entry) => ({
1679
+ name: entry.name,
1680
+ kind: entry.isDirectory() ? 'directory'
1681
+ : entry.isFile() ? 'file'
1682
+ : entry.isSymbolicLink() ? 'symlink' : 'other',
1683
+ })).sort((a, b) => Buffer.compare(Buffer.from(a.name), Buffer.from(b.name)));
1684
+ const hash = crypto.createHash('sha256');
1685
+ frame(hash, ['directory-entries-v1']);
1686
+ for (const entry of normalized) frame(hash, [entry.name, entry.kind]);
1687
+ return 'sha256:' + hash.digest('hex');
1688
+ };
1689
+ const probe = async (request) => {
1690
+ try {
1691
+ const link = await fs.lstat(request.absolutePath, { bigint: true });
1692
+ const type = link.isDirectory() ? 'directory'
1693
+ : link.isFile() ? 'file'
1694
+ : link.isSymbolicLink() ? 'symlink' : 'other';
1695
+ if (request.mode === 'link') return {
1696
+ type,
1697
+ state: state(link),
1698
+ ...(type === 'symlink' ? { symlinkTarget: await fs.readlink(request.absolutePath) } : {}),
1699
+ };
1700
+ if (request.mode === 'directory-inventory') {
1701
+ if (!link.isDirectory() || link.isSymbolicLink()) return null;
1702
+ const entriesDigest = inventoryDigest(
1703
+ await fs.readdir(request.absolutePath, { withFileTypes: true }),
1704
+ );
1705
+ const after = await fs.lstat(request.absolutePath, { bigint: true });
1706
+ if (JSON.stringify(state(link)) !== JSON.stringify(state(after))) return null;
1707
+ return { type: 'directory-inventory', state: state(after), entriesDigest };
1708
+ }
1709
+ const target = await fs.stat(request.absolutePath, { bigint: true });
1710
+ if (!target.isFile()) return null;
1711
+ const result = { type: 'readable-file', state: { link: state(link), target: state(target) } };
1712
+ if (link.isSymbolicLink()) result.state.symlinkTarget = await fs.readlink(request.absolutePath);
1713
+ return result;
1714
+ } catch { return null; }
1715
+ };
1716
+ let input = '';
1717
+ process.stdin.setEncoding('utf8');
1718
+ process.stdin.on('data', (chunk) => { input += chunk; });
1719
+ process.stdin.on('end', async () => {
1720
+ try {
1721
+ const requests = JSON.parse(input);
1722
+ const results = [];
1723
+ for (let offset = 0; offset < requests.length; offset += 512) {
1724
+ results.push(...await Promise.all(requests.slice(offset, offset + 512).map(probe)));
1725
+ }
1726
+ process.stdout.write(JSON.stringify(results));
1727
+ } catch { process.exitCode = 1; }
1728
+ });
1729
+ `;
1730
+ function snapshotCacheGuardDirect(request) {
1731
+ try {
1732
+ if (request.mode === 'readable-file') {
1733
+ return { type: 'readable-file', state: snapshotReadableFile(request.absolutePath) };
1734
+ }
1735
+ if (request.mode === 'directory-inventory') {
1736
+ const inventory = snapshotDirectoryInventory(request.absolutePath);
1737
+ return { type: 'directory-inventory', ...inventory };
1738
+ }
1739
+ const stat = lstatSync(request.absolutePath, { bigint: true });
1740
+ const type = stat.isDirectory()
1741
+ ? 'directory'
1742
+ : stat.isFile()
1743
+ ? 'file'
1744
+ : stat.isSymbolicLink()
1745
+ ? 'symlink'
1746
+ : 'other';
1747
+ return {
1748
+ type,
1749
+ state: statState(stat),
1750
+ ...(type === 'symlink' ? { symlinkTarget: readlinkSync(request.absolutePath) } : {}),
1751
+ };
1752
+ }
1753
+ catch {
1754
+ return null;
1755
+ }
1756
+ }
1757
+ function snapshotCacheGuards(requests) {
1758
+ if (requests.length < 128)
1759
+ return requests.map(snapshotCacheGuardDirect);
1760
+ try {
1761
+ const probe = spawnSync(process.execPath, ['--input-type=commonjs', '-e', CACHE_GUARD_PROBE_SCRIPT], {
1762
+ input: JSON.stringify(requests),
1763
+ encoding: 'utf8',
1764
+ maxBuffer: MAX_CACHE_FILE_BYTES,
1765
+ timeout: 30_000,
1766
+ windowsHide: true,
1767
+ });
1768
+ if (probe.status === 0 && !probe.error) {
1769
+ const parsed = JSON.parse(probe.stdout);
1770
+ if (Array.isArray(parsed) && parsed.length === requests.length) {
1771
+ return parsed;
1772
+ }
1773
+ }
1774
+ }
1775
+ catch {
1776
+ /* fall through to the slower in-process validator */
1777
+ }
1778
+ return requests.map(snapshotCacheGuardDirect);
1779
+ }
1780
+ function validateIdentityCache(cache, options) {
1781
+ const expected = new Map();
1782
+ const add = (request, result) => {
1783
+ const key = JSON.stringify([request.mode, request.absolutePath]);
1784
+ const prior = expected.get(key);
1785
+ if (expected.has(key) && !isDeepStrictEqual(prior, result)) {
1786
+ options.onCacheValidationFailure?.({ mode: request.mode, path: request.absolutePath });
1787
+ return false;
1788
+ }
1789
+ expected.set(key, result);
1790
+ return true;
1791
+ };
1792
+ if (!add({ absolutePath: cache.buildRoot, mode: 'link' }, { type: 'directory', state: cache.buildRootState })) {
1793
+ return false;
1794
+ }
1795
+ for (const entry of cache.buildEntries) {
1796
+ const absolutePath = path.join(cache.buildRoot, ...entry.relativePath.split('/'));
1797
+ if (!isInside(cache.buildRoot, absolutePath) ||
1798
+ !add({ absolutePath, mode: 'link' }, { type: entry.kind, state: entry.state })) {
1799
+ return false;
1800
+ }
1801
+ }
1802
+ for (const guard of cache.buildDirectoryGuards) {
1803
+ const absolutePath = guard.relativePath
1804
+ ? path.join(cache.buildRoot, ...guard.relativePath.split('/'))
1805
+ : cache.buildRoot;
1806
+ if (!isInside(cache.buildRoot, absolutePath) ||
1807
+ !add({ absolutePath, mode: 'directory-inventory' }, {
1808
+ type: 'directory-inventory',
1809
+ state: guard.state,
1810
+ entriesDigest: guard.entriesDigest,
1811
+ })) {
1812
+ return false;
1813
+ }
1814
+ }
1815
+ for (const guard of cache.dependencyDirectoryGuards) {
1816
+ if (!add({ absolutePath: guard.absolutePath, mode: 'directory-inventory' }, {
1817
+ type: 'directory-inventory',
1818
+ state: guard.state,
1819
+ entriesDigest: guard.entriesDigest,
1820
+ })) {
1821
+ return false;
1822
+ }
1823
+ }
1824
+ for (const guard of cache.dependencyPathGuards) {
1825
+ if (!add({ absolutePath: guard.absolutePath, mode: 'link' }, guard.result)) {
1826
+ return false;
1827
+ }
1828
+ }
1829
+ for (const guard of cache.dependencyFileGuards) {
1830
+ if (!add({ absolutePath: guard.absolutePath, mode: 'readable-file' }, { type: 'readable-file', state: guard.state })) {
1831
+ return false;
1832
+ }
1833
+ }
1834
+ for (const artifact of cache.artifactEntries) {
1835
+ // A recorded link is re-probed as a link, never as a readable file: the
1836
+ // readable-file probe resolves the target and would report `null` for the
1837
+ // very inputs this kind exists to describe, failing every warm validation.
1838
+ const probe = artifact.kind === 'unfollowed-symlink'
1839
+ ? {
1840
+ request: { absolutePath: artifact.absolutePath, mode: 'link' },
1841
+ expected: {
1842
+ type: 'symlink',
1843
+ state: artifact.state.link,
1844
+ symlinkTarget: artifact.state.symlinkTarget,
1845
+ },
1846
+ }
1847
+ : {
1848
+ request: { absolutePath: artifact.absolutePath, mode: 'readable-file' },
1849
+ expected: { type: 'readable-file', state: artifact.state },
1850
+ };
1851
+ if (!add(probe.request, probe.expected))
1852
+ return false;
1853
+ }
1854
+ const entries = [...expected.entries()];
1855
+ const requests = entries.map(([key]) => {
1856
+ const [mode, absolutePath] = JSON.parse(key);
1857
+ return { mode, absolutePath };
1858
+ });
1859
+ options.onCacheValidationPass?.({ guardCount: requests.length });
1860
+ const actual = snapshotCacheGuards(requests);
1861
+ const mismatch = actual.findIndex((result, index) => !isDeepStrictEqual(result, entries[index][1]));
1862
+ if (mismatch !== -1) {
1863
+ const [mode, absolutePath] = JSON.parse(entries[mismatch][0]);
1864
+ options.onCacheValidationFailure?.({ mode, path: absolutePath });
1865
+ return false;
1866
+ }
1867
+ return true;
1868
+ }
1869
+ function cachedBuildDigestForPath(cache, absolutePath) {
1870
+ if (!isInside(cache.buildRoot, absolutePath))
1871
+ return null;
1872
+ const relativePath = path.relative(cache.buildRoot, absolutePath).split(path.sep).join('/');
1873
+ const entry = cache.buildEntries.find((candidate) => candidate.kind === 'file' && candidate.relativePath === relativePath);
1874
+ return entry?.digest ?? null;
1875
+ }
1876
+ function dependencyFileGuards(inputs) {
1877
+ const guards = new Map();
1878
+ if (inputs.lockfilePath && inputs.lockfileState) {
1879
+ guards.set(inputs.lockfilePath, inputs.lockfileState);
1880
+ }
1881
+ for (const runtimePackage of inputs.packages) {
1882
+ guards.set(runtimePackage.manifestPath, runtimePackage.manifestState);
1883
+ }
1884
+ for (const manifest of inputs.vendoredManifests) {
1885
+ guards.set(manifest.absolutePath, manifest.state);
1886
+ }
1887
+ return [...guards.entries()]
1888
+ .map(([absolutePath, state]) => ({ absolutePath, state }))
1889
+ .sort((a, b) => compareBytes(a.absolutePath, b.absolutePath));
1890
+ }
1891
+ function dependencyDirectoryGuards(inputs) {
1892
+ return [...inputs.directoryGuards.entries()]
1893
+ .map(([absolutePath, guard]) => ({ absolutePath, ...guard }))
1894
+ .sort((a, b) => compareBytes(a.absolutePath, b.absolutePath));
1895
+ }
1896
+ function dependencyPathGuards(inputs) {
1897
+ return [...inputs.pathGuards.entries()]
1898
+ .map(([absolutePath, result]) => ({ absolutePath, result }))
1899
+ .sort((a, b) => compareBytes(a.absolutePath, b.absolutePath));
1900
+ }
1901
+ function persistIdentityCache(packageRoot, buildRoot, payload, previous, options) {
1902
+ if (previous && isDeepStrictEqual(previous, payload))
1903
+ return;
1904
+ const target = cachePathFor(packageRoot, buildRoot, payload.runtimeVariant, payload.traversalLimits, options);
1905
+ if (!target)
1906
+ return;
1907
+ const temporary = `${target}.${process.pid}.${randomBytes(6).toString('hex')}.tmp`;
1908
+ try {
1909
+ if (options.cacheDirectory) {
1910
+ mkdirSync(path.dirname(target), { recursive: true, mode: 0o700 });
1911
+ }
1912
+ else if (!hasTrustedCacheOverride(options) &&
1913
+ !isOwnedPrivateDirectory(path.dirname(target))) {
1914
+ return;
1915
+ }
1916
+ const envelope = {
1917
+ payload,
1918
+ checksum: sha256(JSON.stringify(payload)),
1919
+ };
1920
+ writeFileSync(temporary, `${JSON.stringify(envelope)}\n`, {
1921
+ encoding: 'utf8',
1922
+ flag: 'wx',
1923
+ mode: 0o600,
1924
+ });
1925
+ renameSync(temporary, target);
1926
+ }
1927
+ catch {
1928
+ // The cache is optional (notably for read-only package/container setups).
1929
+ // A failed write only loses the optimization; identity remains fail-closed.
1930
+ try {
1931
+ unlinkSync(temporary);
1932
+ }
1933
+ catch {
1934
+ /* already renamed or never created */
1935
+ }
1936
+ }
1937
+ }
1938
+ function rememberIdentityCache(payload) {
1939
+ const key = identityCacheKey(payload.packageRoot, payload.buildRoot, payload.runtimeVariant, payload.traversalLimits);
1940
+ processIdentityCache.delete(key);
1941
+ processIdentityCache.set(key, payload);
1942
+ while (processIdentityCache.size > MAX_PROCESS_CACHE_ENTRIES) {
1943
+ const oldest = processIdentityCache.keys().next().value;
1944
+ if (oldest === undefined)
1945
+ break;
1946
+ processIdentityCache.delete(oldest);
1947
+ }
1948
+ }
1949
+ function resolveInvokedArtifact(buildRoot, analyzerModulePath) {
1950
+ const argvEntry = process.argv[1];
1951
+ if (!argvEntry)
1952
+ return analyzerModulePath;
1953
+ try {
1954
+ const resolved = resolveExistingPath(argvEntry);
1955
+ return isInside(buildRoot, resolved) && lstatSync(resolved).isFile()
1956
+ ? resolved
1957
+ : analyzerModulePath;
1958
+ }
1959
+ catch {
1960
+ return analyzerModulePath;
1961
+ }
1962
+ }
1963
+ function isNonEmptyString(value) {
1964
+ return typeof value === 'string' && value.length > 0;
1965
+ }
1966
+ function runnerRuntimeIdentity(runtimeVariant) {
1967
+ return {
1968
+ executablePath: runtimeVariant.executablePath,
1969
+ version: runtimeVariant.nodeVersion,
1970
+ platform: runtimeVariant.platform,
1971
+ architecture: runtimeVariant.architecture,
1972
+ modulesAbi: runtimeVariant.modulesAbi,
1973
+ libc: runtimeVariant.libc,
1974
+ };
1975
+ }
1976
+ function isAnalyzerRunnerIdentity(value) {
1977
+ if (typeof value !== 'object' || value === null)
1978
+ return false;
1979
+ const identity = value;
1980
+ const runtime = identity.runtime;
1981
+ const invoked = identity.invokedArtifact;
1982
+ const build = identity.build;
1983
+ const dependency = identity.dependencyRuntime;
1984
+ return (identity.schemaVersion === ANALYZER_RUNNER_IDENTITY_SCHEMA_VERSION &&
1985
+ !!runtime &&
1986
+ isNonEmptyString(runtime.executablePath) &&
1987
+ isNonEmptyString(runtime.version) &&
1988
+ isNonEmptyString(runtime.platform) &&
1989
+ isNonEmptyString(runtime.architecture) &&
1990
+ isNonEmptyString(runtime.modulesAbi) &&
1991
+ isNonEmptyString(runtime.libc) &&
1992
+ isNonEmptyString(identity.cliVersion) &&
1993
+ !!invoked &&
1994
+ isNonEmptyString(invoked.path) &&
1995
+ typeof invoked.digest === 'string' &&
1996
+ SHA256_PATTERN.test(invoked.digest) &&
1997
+ !!build &&
1998
+ (build.kind === 'source' || build.kind === 'distribution') &&
1999
+ isNonEmptyString(build.rootPath) &&
2000
+ build.canonicalization === BUILD_CANONICALIZATION &&
2001
+ typeof build.digest === 'string' &&
2002
+ SHA256_PATTERN.test(build.digest) &&
2003
+ !!dependency &&
2004
+ isNonEmptyString(dependency.manifestPath) &&
2005
+ (dependency.lockfilePath === null || isNonEmptyString(dependency.lockfilePath)) &&
2006
+ dependency.canonicalization === DEPENDENCY_RUNTIME_CANONICALIZATION &&
2007
+ Number.isSafeInteger(dependency.packageCount) &&
2008
+ Number(dependency.packageCount) >= 1 &&
2009
+ Number.isSafeInteger(dependency.artifactCount) &&
2010
+ Number(dependency.artifactCount) >= 0 &&
2011
+ typeof dependency.digest === 'string' &&
2012
+ SHA256_PATTERN.test(dependency.digest));
2013
+ }
2014
+ /**
2015
+ * Normalize a raw diagnostic receipt for freshness comparison. The entrypoint
2016
+ * is the only excluded field; malformed/legacy receipts never compare equal.
2017
+ */
2018
+ export function normalizeAnalyzerRunnerIdentityForComparison(identity) {
2019
+ if (!isAnalyzerRunnerIdentity(identity))
2020
+ return null;
2021
+ const { invokedArtifact: _diagnosticEntrypoint, ...semantic } = identity;
2022
+ return semantic;
2023
+ }
2024
+ /** Resolve the identity of the analyzer build and runtime executing now. */
2025
+ export function resolveAnalyzerRunnerIdentity(analyzerModuleUrl, options = {}) {
2026
+ const analyzerModulePath = resolveExistingPath(fileURLToPath(analyzerModuleUrl));
2027
+ const { packageRoot, buildRoot, kind } = resolveBuildRoot(analyzerModulePath);
2028
+ const runtimeVariant = resolveRuntimeVariant();
2029
+ const traversalLimits = resolveTraversalLimits(options);
2030
+ const previousCache = loadIdentityCache(packageRoot, buildRoot, runtimeVariant, traversalLimits, options);
2031
+ const invokedArtifactPath = resolveInvokedArtifact(buildRoot, analyzerModulePath);
2032
+ if (previousCache?.buildKind === kind) {
2033
+ const invokedDigest = cachedBuildDigestForPath(previousCache, invokedArtifactPath);
2034
+ if (invokedDigest) {
2035
+ const cachedIdentity = {
2036
+ schemaVersion: ANALYZER_RUNNER_IDENTITY_SCHEMA_VERSION,
2037
+ runtime: runnerRuntimeIdentity(runtimeVariant),
2038
+ cliVersion: previousCache.packageVersion,
2039
+ invokedArtifact: { path: invokedArtifactPath, digest: invokedDigest },
2040
+ build: {
2041
+ kind,
2042
+ rootPath: buildRoot,
2043
+ canonicalization: BUILD_CANONICALIZATION,
2044
+ digest: previousCache.buildDigest,
2045
+ },
2046
+ dependencyRuntime: previousCache.dependencyIdentity,
2047
+ };
2048
+ // One batched pass is deliberately the last filesystem observation on
2049
+ // a warm hit. Repeating the complete inventory both doubles status
2050
+ // latency and still cannot close the post-check scheduling window.
2051
+ if (validateIdentityCache(previousCache, options)) {
2052
+ rememberIdentityCache(previousCache);
2053
+ return cachedIdentity;
2054
+ }
2055
+ }
2056
+ }
2057
+ const build = hashBuildTree(buildRoot, previousCache, options, traversalLimits);
2058
+ const dependencyInputs = collectDependencyInputs(packageRoot, options, traversalLimits);
2059
+ const dependencySnapshotBefore = dependencySnapshot(dependencyInputs);
2060
+ const dependency = hashDependencyRuntime(dependencyInputs, previousCache, options, runtimeVariant);
2061
+ const packageVersion = dependencyInputs.packages[0]?.manifest.version;
2062
+ if (typeof packageVersion !== 'string' || packageVersion.trim() === '') {
2063
+ throw new Error(`GitNexus package version is unavailable in ${packageRoot}`);
2064
+ }
2065
+ const buildSnapshotAfter = buildSnapshot(collectBuildEntries(buildRoot, options, traversalLimits));
2066
+ if (!isDeepStrictEqual(build.snapshot, buildSnapshotAfter)) {
2067
+ throw new Error(`Analyzer build changed while its identity was being computed: ${buildRoot}`);
2068
+ }
2069
+ const dependencySnapshotAfter = dependencySnapshot(collectDependencyInputs(packageRoot, options, traversalLimits));
2070
+ if (!isDeepStrictEqual(dependencySnapshotBefore, dependencySnapshotAfter)) {
2071
+ throw new Error(`Analyzer dependency runtime changed while its identity was being computed: ${packageRoot}`);
2072
+ }
2073
+ const nextCache = {
2074
+ schemaVersion: IDENTITY_CACHE_SCHEMA_VERSION,
2075
+ packageRoot,
2076
+ buildRoot,
2077
+ packageVersion,
2078
+ buildKind: kind,
2079
+ buildCanonicalization: BUILD_CANONICALIZATION,
2080
+ dependencyCanonicalization: DEPENDENCY_RUNTIME_CANONICALIZATION,
2081
+ traversalLimits,
2082
+ runtimeVariant,
2083
+ buildRootState: build.rootState,
2084
+ buildDigest: build.digest,
2085
+ buildEntries: build.entries,
2086
+ buildDirectoryGuards: build.directoryGuards,
2087
+ dependencyIdentity: dependency.identity,
2088
+ dependencyFileGuards: dependencyFileGuards(dependencyInputs),
2089
+ dependencyDirectoryGuards: dependencyDirectoryGuards(dependencyInputs),
2090
+ dependencyPathGuards: dependencyPathGuards(dependencyInputs),
2091
+ artifactEntries: dependency.entries,
2092
+ };
2093
+ const invokedDigest = cachedBuildDigestForPath(nextCache, invokedArtifactPath);
2094
+ if (!invokedDigest) {
2095
+ throw new Error(`Invoked analyzer artifact is absent from the validated build: ${invokedArtifactPath}`);
2096
+ }
2097
+ const identity = {
2098
+ schemaVersion: ANALYZER_RUNNER_IDENTITY_SCHEMA_VERSION,
2099
+ runtime: runnerRuntimeIdentity(runtimeVariant),
2100
+ cliVersion: packageVersion,
2101
+ invokedArtifact: {
2102
+ path: invokedArtifactPath,
2103
+ digest: invokedDigest,
2104
+ },
2105
+ build: {
2106
+ kind,
2107
+ rootPath: buildRoot,
2108
+ canonicalization: BUILD_CANONICALIZATION,
2109
+ digest: build.digest,
2110
+ },
2111
+ dependencyRuntime: dependency.identity,
2112
+ };
2113
+ let validationFailure;
2114
+ const validationOptions = {
2115
+ ...options,
2116
+ onCacheValidationFailure: (failure) => {
2117
+ validationFailure = failure;
2118
+ options.onCacheValidationFailure?.(failure);
2119
+ },
2120
+ };
2121
+ if (!validateIdentityCache(nextCache, validationOptions)) {
2122
+ const mismatch = validationFailure
2123
+ ? ` (failed ${validationFailure.mode} guard: ${validationFailure.path})`
2124
+ : '';
2125
+ throw new Error(`Analyzer build or dependency runtime changed while its identity was being computed: ${packageRoot}${mismatch}`);
2126
+ }
2127
+ rememberIdentityCache(nextCache);
2128
+ persistIdentityCache(packageRoot, buildRoot, nextCache, previousCache, options);
2129
+ return identity;
2130
+ }
2131
+ /**
2132
+ * Semantic freshness comparison. Both receipts must be well-formed schema-v4
2133
+ * values; only the diagnostic entrypoint field is normalized away.
2134
+ */
2135
+ export function analyzerRunnerIdentitiesEqual(indexedIdentity, currentIdentity) {
2136
+ const indexed = normalizeAnalyzerRunnerIdentityForComparison(indexedIdentity);
2137
+ const current = normalizeAnalyzerRunnerIdentityForComparison(currentIdentity);
2138
+ return indexed !== null && current !== null && isDeepStrictEqual(indexed, current);
2139
+ }
2140
+ /**
2141
+ * Capture analyzer identity before invoking a loader that may evaluate the
2142
+ * analyzer module graph. The explicit receipt is then threaded into analysis
2143
+ * and checked again immediately before metadata commit.
2144
+ */
2145
+ export async function captureAnalyzerIdentityBeforeLoad(analyzerModuleUrl, loader, options = {}) {
2146
+ const runnerIdentity = resolveAnalyzerRunnerIdentity(analyzerModuleUrl, options);
2147
+ const loaded = await loader();
2148
+ return { runnerIdentity, loaded };
2149
+ }
2150
+ /** Re-resolve immediately before commit and reject analyzer mutation mid-run. */
2151
+ export function finalizeAnalyzerRunnerIdentity(analyzerModuleUrl, startedWith, options = {}) {
2152
+ const finalIdentity = resolveAnalyzerRunnerIdentity(analyzerModuleUrl, options);
2153
+ if (!analyzerRunnerIdentitiesEqual(startedWith, finalIdentity)) {
2154
+ throw new Error('Analyzer build or dependency runtime changed during analysis; refusing to stamp metadata. ' +
2155
+ 'Retry with a stable GitNexus installation.');
2156
+ }
2157
+ return finalIdentity;
2158
+ }