gitnexus 1.6.9 → 1.6.10-aptos.0

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 (539) hide show
  1. package/README.md +211 -53
  2. package/dist/_shared/graph/types.d.ts +67 -3
  3. package/dist/_shared/graph/types.d.ts.map +1 -1
  4. package/dist/_shared/index.d.ts +1 -0
  5. package/dist/_shared/index.d.ts.map +1 -1
  6. package/dist/_shared/index.js.map +1 -1
  7. package/dist/_shared/integrations/resilient-fetch.d.ts +3 -1
  8. package/dist/_shared/integrations/resilient-fetch.d.ts.map +1 -1
  9. package/dist/_shared/integrations/resilient-fetch.js +4 -3
  10. package/dist/_shared/integrations/resilient-fetch.js.map +1 -1
  11. package/dist/_shared/language-detection.d.ts.map +1 -1
  12. package/dist/_shared/language-detection.js +2 -0
  13. package/dist/_shared/language-detection.js.map +1 -1
  14. package/dist/_shared/languages.d.ts +2 -0
  15. package/dist/_shared/languages.d.ts.map +1 -1
  16. package/dist/_shared/languages.js +2 -0
  17. package/dist/_shared/languages.js.map +1 -1
  18. package/dist/_shared/lbug/schema-constants.d.ts +2 -2
  19. package/dist/_shared/lbug/schema-constants.d.ts.map +1 -1
  20. package/dist/_shared/lbug/schema-constants.js +8 -0
  21. package/dist/_shared/lbug/schema-constants.js.map +1 -1
  22. package/dist/_shared/pipeline.d.ts +1 -1
  23. package/dist/_shared/pipeline.d.ts.map +1 -1
  24. package/dist/_shared/scope-resolution/callable-flow-site.d.ts +110 -0
  25. package/dist/_shared/scope-resolution/callable-flow-site.d.ts.map +1 -0
  26. package/dist/_shared/scope-resolution/callable-flow-site.js +11 -0
  27. package/dist/_shared/scope-resolution/callable-flow-site.js.map +1 -0
  28. package/dist/_shared/scope-resolution/finalize-algorithm.d.ts +1 -1
  29. package/dist/_shared/scope-resolution/finalize-algorithm.d.ts.map +1 -1
  30. package/dist/_shared/scope-resolution/finalize-algorithm.js +1 -1
  31. package/dist/_shared/scope-resolution/finalize-algorithm.js.map +1 -1
  32. package/dist/_shared/scope-resolution/language-classification.d.ts.map +1 -1
  33. package/dist/_shared/scope-resolution/language-classification.js +1 -0
  34. package/dist/_shared/scope-resolution/language-classification.js.map +1 -1
  35. package/dist/_shared/scope-resolution/parsed-file.d.ts +7 -0
  36. package/dist/_shared/scope-resolution/parsed-file.d.ts.map +1 -1
  37. package/dist/_shared/scope-resolution/reference-site.d.ts +9 -1
  38. package/dist/_shared/scope-resolution/reference-site.d.ts.map +1 -1
  39. package/dist/_shared/scope-resolution/types.d.ts +20 -3
  40. package/dist/_shared/scope-resolution/types.d.ts.map +1 -1
  41. package/dist/cli/ai-context.d.ts +1 -1
  42. package/dist/cli/ai-context.js +25 -44
  43. package/dist/cli/analyze.d.ts +5 -2
  44. package/dist/cli/analyze.js +197 -11
  45. package/dist/cli/clean.js +17 -3
  46. package/dist/cli/cli-message.d.ts +1 -1
  47. package/dist/cli/doctor.d.ts +18 -0
  48. package/dist/cli/doctor.js +96 -5
  49. package/dist/cli/editor-targets.d.ts +17 -2
  50. package/dist/cli/editor-targets.js +44 -0
  51. package/dist/cli/embeddings.d.ts +12 -0
  52. package/dist/cli/embeddings.js +49 -0
  53. package/dist/cli/eval-server.d.ts +23 -1
  54. package/dist/cli/eval-server.js +126 -8
  55. package/dist/cli/help-i18n.js +6 -0
  56. package/dist/cli/i18n/en.d.ts +18 -8
  57. package/dist/cli/i18n/en.js +18 -8
  58. package/dist/cli/i18n/resources.d.ts +28 -8
  59. package/dist/cli/i18n/zh-CN.d.ts +10 -0
  60. package/dist/cli/i18n/zh-CN.js +18 -8
  61. package/dist/cli/index.js +21 -7
  62. package/dist/cli/lazy-action.d.ts +7 -0
  63. package/dist/cli/lazy-action.js +27 -0
  64. package/dist/cli/mcp.js +6 -3
  65. package/dist/cli/move-availability.d.ts +5 -0
  66. package/dist/cli/move-availability.js +11 -0
  67. package/dist/cli/setup.d.ts +8 -0
  68. package/dist/cli/setup.js +230 -32
  69. package/dist/cli/skill-gen.js +38 -18
  70. package/dist/cli/standard-skills.d.ts +76 -0
  71. package/dist/cli/standard-skills.js +45 -0
  72. package/dist/cli/status.d.ts +4 -1
  73. package/dist/cli/status.js +57 -4
  74. package/dist/cli/tool.js +11 -0
  75. package/dist/cli/uninstall.js +77 -18
  76. package/dist/cli/wiki.d.ts +1 -0
  77. package/dist/cli/wiki.js +7 -1
  78. package/dist/config/ignore-service.d.ts +2 -0
  79. package/dist/config/ignore-service.js +27 -0
  80. package/dist/core/analysis-features.d.ts +21 -0
  81. package/dist/core/analysis-features.js +59 -0
  82. package/dist/core/analyzer-identity.d.ts +74 -0
  83. package/dist/core/analyzer-identity.js +1820 -0
  84. package/dist/core/augmentation/engine.js +13 -8
  85. package/dist/core/embeddings/embedder.d.ts +3 -2
  86. package/dist/core/embeddings/embedder.js +24 -7
  87. package/dist/core/embeddings/embedding-identity.d.ts +11 -0
  88. package/dist/core/embeddings/embedding-identity.js +24 -0
  89. package/dist/core/embeddings/embedding-pipeline.d.ts +36 -1
  90. package/dist/core/embeddings/embedding-pipeline.js +100 -9
  91. package/dist/core/embeddings/http-client.d.ts +33 -3
  92. package/dist/core/embeddings/http-client.js +237 -25
  93. package/dist/core/embeddings/node-module-compat.d.ts +25 -0
  94. package/dist/core/embeddings/node-module-compat.js +25 -0
  95. package/dist/core/embeddings/onnxruntime-common-resolver.js +8 -5
  96. package/dist/core/embeddings/onnxruntime-node-resolver.js +33 -7
  97. package/dist/core/embeddings/runtime-install.d.ts +119 -0
  98. package/dist/core/embeddings/runtime-install.js +372 -0
  99. package/dist/core/embeddings/runtime-support.d.ts +48 -17
  100. package/dist/core/embeddings/runtime-support.js +96 -0
  101. package/dist/core/git-staleness.js +1 -1
  102. package/dist/core/group/bridge-db.js +2 -0
  103. package/dist/core/group/extractors/http-patterns/python.js +246 -108
  104. package/dist/core/group/extractors/manifest-extractor.js +4 -0
  105. package/dist/core/incremental/escalation-gate.d.ts +38 -0
  106. package/dist/core/incremental/escalation-gate.js +48 -0
  107. package/dist/core/incremental/shadow-candidates.d.ts +3 -3
  108. package/dist/core/incremental/shadow-candidates.js +3 -3
  109. package/dist/core/incremental/subgraph-extract.d.ts +2 -2
  110. package/dist/core/incremental/subgraph-extract.js +2 -2
  111. package/dist/core/index-freshness.d.ts +5 -0
  112. package/dist/core/index-freshness.js +13 -0
  113. package/dist/core/ingestion/class-extractors/configs/jvm.js +29 -0
  114. package/dist/core/ingestion/cobol/cobol-preprocessor.js +1 -1
  115. package/dist/core/ingestion/cobol/jcl-processor.js +9 -8
  116. package/dist/core/ingestion/cobol-processor.js +27 -26
  117. package/dist/core/ingestion/community-processor.d.ts +51 -1
  118. package/dist/core/ingestion/community-processor.js +323 -64
  119. package/dist/core/ingestion/emit-references.js +7 -2
  120. package/dist/core/ingestion/filesystem-walker.js +4 -0
  121. package/dist/core/ingestion/frameworks/spring/analysis-features.d.ts +3 -0
  122. package/dist/core/ingestion/frameworks/spring/analysis-features.js +7 -0
  123. package/dist/core/ingestion/frameworks/spring/bean-candidates.d.ts +30 -0
  124. package/dist/core/ingestion/frameworks/spring/bean-candidates.js +182 -0
  125. package/dist/core/ingestion/frameworks/spring/bean-catalog.d.ts +12 -0
  126. package/dist/core/ingestion/frameworks/spring/bean-catalog.js +26 -0
  127. package/dist/core/ingestion/frameworks/spring/config-bindings.d.ts +24 -0
  128. package/dist/core/ingestion/frameworks/spring/config-bindings.js +115 -0
  129. package/dist/core/ingestion/languages/c/captures.js +56 -0
  130. package/dist/core/ingestion/languages/c/query.js +17 -0
  131. package/dist/core/ingestion/languages/c/scope-resolver.js +6 -0
  132. package/dist/core/ingestion/languages/cobol/captures.js +93 -0
  133. package/dist/core/ingestion/languages/cobol/scope-resolver.js +7 -0
  134. package/dist/core/ingestion/languages/cpp/captures.js +365 -75
  135. package/dist/core/ingestion/languages/cpp/scope-resolver.js +14 -0
  136. package/dist/core/ingestion/languages/csharp/captures.js +25 -0
  137. package/dist/core/ingestion/languages/dart/captures.js +90 -2
  138. package/dist/core/ingestion/languages/dart/extension-type-preprocess.d.ts +7 -0
  139. package/dist/core/ingestion/languages/dart/extension-type-preprocess.js +30 -0
  140. package/dist/core/ingestion/languages/dart.js +2 -0
  141. package/dist/core/ingestion/languages/go/captures.js +34 -0
  142. package/dist/core/ingestion/languages/index.d.ts +1 -0
  143. package/dist/core/ingestion/languages/index.js +2 -0
  144. package/dist/core/ingestion/languages/java/analysis-features.d.ts +3 -0
  145. package/dist/core/ingestion/languages/java/analysis-features.js +13 -0
  146. package/dist/core/ingestion/languages/java/capture-side-channel.d.ts +22 -0
  147. package/dist/core/ingestion/languages/java/capture-side-channel.js +58 -0
  148. package/dist/core/ingestion/languages/java/captures.d.ts +4 -3
  149. package/dist/core/ingestion/languages/java/captures.js +193 -17
  150. package/dist/core/ingestion/languages/java/package-facts.d.ts +5 -0
  151. package/dist/core/ingestion/languages/java/package-facts.js +9 -0
  152. package/dist/core/ingestion/languages/java/package-siblings.d.ts +3 -16
  153. package/dist/core/ingestion/languages/java/package-siblings.js +8 -147
  154. package/dist/core/ingestion/languages/java/query.js +23 -0
  155. package/dist/core/ingestion/languages/java/scope-resolver.js +23 -0
  156. package/dist/core/ingestion/languages/java/spring-bean-metadata.d.ts +2 -0
  157. package/dist/core/ingestion/languages/java/spring-bean-metadata.js +8 -0
  158. package/dist/core/ingestion/languages/java/spring-config-bindings.d.ts +23 -0
  159. package/dist/core/ingestion/languages/java/spring-config-bindings.js +215 -0
  160. package/dist/core/ingestion/languages/java.js +3 -0
  161. package/dist/core/ingestion/languages/javascript/captures.js +16 -0
  162. package/dist/core/ingestion/languages/javascript/query.js +21 -0
  163. package/dist/core/ingestion/languages/jvm/package-facts.d.ts +35 -0
  164. package/dist/core/ingestion/languages/jvm/package-facts.js +40 -0
  165. package/dist/core/ingestion/languages/jvm/package-siblings.d.ts +14 -0
  166. package/dist/core/ingestion/languages/jvm/package-siblings.js +129 -0
  167. package/dist/core/ingestion/languages/kotlin/capture-side-channel.d.ts +17 -3
  168. package/dist/core/ingestion/languages/kotlin/capture-side-channel.js +42 -8
  169. package/dist/core/ingestion/languages/kotlin/captures.js +59 -0
  170. package/dist/core/ingestion/languages/kotlin/package-facts.d.ts +5 -0
  171. package/dist/core/ingestion/languages/kotlin/package-facts.js +9 -0
  172. package/dist/core/ingestion/languages/kotlin/package-siblings.d.ts +4 -0
  173. package/dist/core/ingestion/languages/kotlin/package-siblings.js +8 -0
  174. package/dist/core/ingestion/languages/kotlin/query.js +19 -0
  175. package/dist/core/ingestion/languages/kotlin/scope-resolver.js +9 -1
  176. package/dist/core/ingestion/languages/kotlin/spring-bean-metadata.d.ts +2 -0
  177. package/dist/core/ingestion/languages/kotlin/spring-bean-metadata.js +8 -0
  178. package/dist/core/ingestion/languages/kotlin.js +5 -6
  179. package/dist/core/ingestion/languages/move.d.ts +1 -0
  180. package/dist/core/ingestion/languages/move.js +27 -0
  181. package/dist/core/ingestion/languages/php/captures.js +40 -0
  182. package/dist/core/ingestion/languages/php/import-decomposer.js +12 -0
  183. package/dist/core/ingestion/languages/php/import-target.d.ts +2 -1
  184. package/dist/core/ingestion/languages/php/import-target.js +110 -2
  185. package/dist/core/ingestion/languages/php/interpret.js +10 -0
  186. package/dist/core/ingestion/languages/php/scope-resolver.js +1 -1
  187. package/dist/core/ingestion/languages/python/captures.d.ts +3 -4
  188. package/dist/core/ingestion/languages/python/captures.js +24 -5
  189. package/dist/core/ingestion/languages/python/interpret.js +4 -0
  190. package/dist/core/ingestion/languages/python/receiver-binding.d.ts +15 -2
  191. package/dist/core/ingestion/languages/python/receiver-binding.js +96 -2
  192. package/dist/core/ingestion/languages/python/simple-hooks.d.ts +5 -5
  193. package/dist/core/ingestion/languages/python/simple-hooks.js +15 -5
  194. package/dist/core/ingestion/languages/ruby/captures.js +42 -0
  195. package/dist/core/ingestion/languages/rust/captures.js +13 -0
  196. package/dist/core/ingestion/languages/rust/interpret.js +18 -0
  197. package/dist/core/ingestion/languages/rust/query.js +9 -0
  198. package/dist/core/ingestion/languages/rust/range-binding.js +6 -2
  199. package/dist/core/ingestion/languages/swift/captures.js +12 -0
  200. package/dist/core/ingestion/languages/typescript/captures.js +21 -0
  201. package/dist/core/ingestion/languages/typescript/nuxt-auto-imports.js +2 -1
  202. package/dist/core/ingestion/languages/typescript/query.js +37 -0
  203. package/dist/core/ingestion/markdown-processor.js +3 -2
  204. package/dist/core/ingestion/model/registration-table.js +1 -0
  205. package/dist/core/ingestion/parsing-processor.d.ts +3 -1
  206. package/dist/core/ingestion/parsing-processor.js +36 -1
  207. package/dist/core/ingestion/pipeline-phases/index.d.ts +2 -0
  208. package/dist/core/ingestion/pipeline-phases/index.js +2 -0
  209. package/dist/core/ingestion/pipeline-phases/parse-impl.js +56 -2
  210. package/dist/core/ingestion/pipeline-phases/parse.d.ts +0 -2
  211. package/dist/core/ingestion/pipeline-phases/parse.js +12 -3
  212. package/dist/core/ingestion/pipeline-phases/processes.d.ts +8 -0
  213. package/dist/core/ingestion/pipeline-phases/processes.js +11 -1
  214. package/dist/core/ingestion/pipeline-phases/spring-config.d.ts +37 -0
  215. package/dist/core/ingestion/pipeline-phases/spring-config.js +433 -0
  216. package/dist/core/ingestion/pipeline-phases/standalone-ingest.d.ts +10 -0
  217. package/dist/core/ingestion/pipeline-phases/standalone-ingest.js +6 -0
  218. package/dist/core/ingestion/pipeline.d.ts +4 -2
  219. package/dist/core/ingestion/pipeline.js +5 -2
  220. package/dist/core/ingestion/resolve-references.js +12 -0
  221. package/dist/core/ingestion/route-extractors/constant-resolver.d.ts +83 -0
  222. package/dist/core/ingestion/route-extractors/constant-resolver.js +131 -0
  223. package/dist/core/ingestion/route-extractors/python-const-resolver.d.ts +75 -0
  224. package/dist/core/ingestion/route-extractors/python-const-resolver.js +299 -0
  225. package/dist/core/ingestion/scope-extractor.js +306 -43
  226. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +71 -2
  227. package/dist/core/ingestion/scope-resolution/graph-bridge/callee-id-sink.d.ts +7 -7
  228. package/dist/core/ingestion/scope-resolution/graph-bridge/callee-id-sink.js +8 -7
  229. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.d.ts +3 -1
  230. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.js +6 -0
  231. package/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +1 -0
  232. package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +4 -1
  233. package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.js +7 -2
  234. package/dist/core/ingestion/scope-resolution/passes/callable-value-flow.d.ts +47 -0
  235. package/dist/core/ingestion/scope-resolution/passes/callable-value-flow.js +1138 -0
  236. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +16 -4
  237. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +97 -1
  238. package/dist/core/ingestion/scope-resolution/passes/property-dispatch.d.ts +60 -0
  239. package/dist/core/ingestion/scope-resolution/passes/property-dispatch.js +111 -0
  240. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +4 -3
  241. package/dist/core/ingestion/scope-resolution/pipeline/phase.d.ts +5 -0
  242. package/dist/core/ingestion/scope-resolution/pipeline/phase.js +25 -38
  243. package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +6 -0
  244. package/dist/core/ingestion/scope-resolution/pipeline/run.js +147 -35
  245. package/dist/core/ingestion/scope-resolution/resolution-outcome.d.ts +1 -1
  246. package/dist/core/ingestion/scope-resolution/scope/walkers.js +73 -44
  247. package/dist/core/ingestion/taint/typescript-model.js +34 -2
  248. package/dist/core/ingestion/tree-sitter-queries.d.ts +2 -2
  249. package/dist/core/ingestion/tree-sitter-queries.js +23 -2
  250. package/dist/core/ingestion/type-extractors/jvm.js +17 -2
  251. package/dist/core/ingestion/utils/ast-helpers.d.ts +1 -0
  252. package/dist/core/ingestion/utils/ast-helpers.js +153 -2
  253. package/dist/core/ingestion/utils/callable-flow-captures.d.ts +94 -0
  254. package/dist/core/ingestion/utils/callable-flow-captures.js +878 -0
  255. package/dist/core/ingestion/utils/line-base.d.ts +20 -0
  256. package/dist/core/ingestion/utils/line-base.js +20 -0
  257. package/dist/core/ingestion/utils/symbol-labels.d.ts +21 -0
  258. package/dist/core/ingestion/utils/symbol-labels.js +45 -0
  259. package/dist/core/ingestion/workers/parse-worker.d.ts +34 -0
  260. package/dist/core/ingestion/workers/parse-worker.js +40 -5
  261. package/dist/core/ingestion/workers/result-merge.js +4 -0
  262. package/dist/core/ingestion/workers/worker-pool.d.ts +28 -0
  263. package/dist/core/ingestion/workers/worker-pool.js +128 -28
  264. package/dist/core/lbug/csv-generator.d.ts +15 -0
  265. package/dist/core/lbug/csv-generator.js +124 -27
  266. package/dist/core/lbug/cypher-escape.d.ts +20 -0
  267. package/dist/core/lbug/cypher-escape.js +20 -0
  268. package/dist/core/lbug/extension-load-error.d.ts +67 -0
  269. package/dist/core/lbug/extension-load-error.js +326 -0
  270. package/dist/core/lbug/extension-loader.d.ts +17 -2
  271. package/dist/core/lbug/extension-loader.js +38 -13
  272. package/dist/core/lbug/lbug-adapter.d.ts +203 -2
  273. package/dist/core/lbug/lbug-adapter.js +527 -86
  274. package/dist/core/lbug/lbug-config.d.ts +56 -2
  275. package/dist/core/lbug/lbug-config.js +267 -7
  276. package/dist/core/lbug/move-columns.d.ts +7 -0
  277. package/dist/core/lbug/move-columns.js +86 -0
  278. package/dist/core/lbug/native-check.d.ts +40 -0
  279. package/dist/core/lbug/native-check.js +90 -0
  280. package/dist/core/lbug/node-table-layout.d.ts +29 -0
  281. package/dist/core/lbug/node-table-layout.js +119 -0
  282. package/dist/core/lbug/pool-adapter.d.ts +23 -1
  283. package/dist/core/lbug/pool-adapter.js +115 -8
  284. package/dist/core/lbug/schema.d.ts +4 -3
  285. package/dist/core/lbug/schema.js +16 -16
  286. package/dist/core/lbug/sidecar-recovery.d.ts +172 -0
  287. package/dist/core/lbug/sidecar-recovery.js +418 -16
  288. package/dist/core/lbug/wal-checkpoint-driver.js +3 -0
  289. package/dist/core/move/artifact-download.d.ts +19 -0
  290. package/dist/core/move/artifact-download.js +179 -0
  291. package/dist/core/move/compiler-facts.d.ts +120 -0
  292. package/dist/core/move/compiler-facts.js +9 -0
  293. package/dist/core/move/consistency.d.ts +28 -0
  294. package/dist/core/move/consistency.js +98 -0
  295. package/dist/core/move/constants.d.ts +98 -0
  296. package/dist/core/move/constants.js +136 -0
  297. package/dist/core/move/discovery.d.ts +10 -0
  298. package/dist/core/move/discovery.js +22 -0
  299. package/dist/core/move/entry-points.d.ts +14 -0
  300. package/dist/core/move/entry-points.js +40 -0
  301. package/dist/core/move/facts-mapper.d.ts +75 -0
  302. package/dist/core/move/facts-mapper.js +534 -0
  303. package/dist/core/move/install-lock.d.ts +14 -0
  304. package/dist/core/move/install-lock.js +102 -0
  305. package/dist/core/move/install.d.ts +39 -0
  306. package/dist/core/move/install.js +391 -0
  307. package/dist/core/move/mcp-client.d.ts +115 -0
  308. package/dist/core/move/mcp-client.js +436 -0
  309. package/dist/core/move/move-ingest.d.ts +49 -0
  310. package/dist/core/move/move-ingest.js +320 -0
  311. package/dist/core/move/provision.d.ts +19 -0
  312. package/dist/core/move/provision.js +91 -0
  313. package/dist/core/move/release.d.ts +6 -0
  314. package/dist/core/move/release.js +10 -0
  315. package/dist/core/move/symbol-id.d.ts +35 -0
  316. package/dist/core/move/symbol-id.js +65 -0
  317. package/dist/core/move/type-parser.d.ts +11 -0
  318. package/dist/core/move/type-parser.js +38 -0
  319. package/dist/core/platform/capabilities.d.ts +0 -1
  320. package/dist/core/platform/capabilities.js +39 -8
  321. package/dist/core/run-analyze.d.ts +3 -3
  322. package/dist/core/run-analyze.js +1001 -184
  323. package/dist/core/search/fts-indexes.d.ts +31 -0
  324. package/dist/core/search/fts-indexes.js +73 -0
  325. package/dist/core/search/fts-schema.js +2 -0
  326. package/dist/core/tree-sitter/safe-parse.d.ts +8 -3
  327. package/dist/core/tree-sitter/safe-parse.js +21 -7
  328. package/dist/core/wiki/graph-queries.js +6 -5
  329. package/dist/core/wiki/llm-client.d.ts +8 -2
  330. package/dist/core/wiki/llm-client.js +41 -5
  331. package/dist/mcp/core/embedder.js +16 -2
  332. package/dist/mcp/http-transport.d.ts +5 -0
  333. package/dist/mcp/http-transport.js +19 -5
  334. package/dist/mcp/local/bean-metadata.d.ts +2 -0
  335. package/dist/mcp/local/bean-metadata.js +25 -0
  336. package/dist/mcp/local/line-display.d.ts +22 -0
  337. package/dist/mcp/local/line-display.js +3 -0
  338. package/dist/mcp/local/local-backend.d.ts +19 -1
  339. package/dist/mcp/local/local-backend.js +402 -155
  340. package/dist/mcp/local/pdg-impact.d.ts +5 -3
  341. package/dist/mcp/local/pdg-impact.js +5 -2
  342. package/dist/mcp/output-budget.d.ts +5 -0
  343. package/dist/mcp/output-budget.js +52 -0
  344. package/dist/mcp/read-only-policy.d.ts +12 -0
  345. package/dist/mcp/read-only-policy.js +102 -0
  346. package/dist/mcp/repository-policy.d.ts +34 -0
  347. package/dist/mcp/repository-policy.js +328 -0
  348. package/dist/mcp/resources.js +32 -5
  349. package/dist/mcp/server.d.ts +5 -2
  350. package/dist/mcp/server.js +29 -9
  351. package/dist/mcp/tools.d.ts +0 -8
  352. package/dist/mcp/tools.js +102 -5
  353. package/dist/server/analyze-job.d.ts +3 -0
  354. package/dist/server/analyze-job.js +17 -0
  355. package/dist/server/analyze-launch.d.ts +6 -0
  356. package/dist/server/analyze-launch.js +82 -6
  357. package/dist/server/analyze-worker-core.d.ts +2 -1
  358. package/dist/server/analyze-worker-core.js +5 -2
  359. package/dist/server/analyze-worker.js +29 -6
  360. package/dist/server/api.d.ts +30 -0
  361. package/dist/server/api.js +120 -34
  362. package/dist/server/mcp-http.d.ts +1 -1
  363. package/dist/server/mcp-http.js +4 -2
  364. package/dist/server/upload-ingest.js +2 -1
  365. package/dist/server/validation.js +2 -1
  366. package/dist/storage/git.d.ts +31 -0
  367. package/dist/storage/git.js +111 -1
  368. package/dist/storage/parse-cache.js +7 -1
  369. package/dist/storage/parsedfile-store.d.ts +8 -0
  370. package/dist/storage/parsedfile-store.js +165 -1
  371. package/dist/storage/repo-manager.d.ts +194 -2
  372. package/dist/storage/repo-manager.js +56 -1
  373. package/hooks/antigravity/gitnexus-antigravity-hook.cjs +50 -3
  374. package/hooks/claude/gitnexus-hook.cjs +59 -9
  375. package/hooks/claude/pre-tool-use.sh +0 -0
  376. package/hooks/claude/session-start.sh +0 -0
  377. package/package.json +21 -15
  378. package/scripts/cross-platform-tests.ts +56 -0
  379. package/scripts/ensure-fts.ts +43 -0
  380. package/scripts/install-duckdb-extension.mjs +85 -24
  381. package/scripts/run-cross-platform.ts +61 -6
  382. package/scripts/shard-arg.ts +30 -0
  383. package/scripts/sync-plugin-manifests.mjs +192 -0
  384. package/skills/gitnexus-lfg/README.md +55 -0
  385. package/skills/gitnexus-lfg/SKILL.md +86 -0
  386. package/skills/gitnexus-plan/README.md +142 -0
  387. package/skills/gitnexus-plan/SKILL.md +348 -0
  388. package/skills/gitnexus-plan/references/context-ledger.md +137 -0
  389. package/skills/gitnexus-plan/references/context-pack.md +126 -0
  390. package/skills/gitnexus-plan/references/evidence-provenance.md +272 -0
  391. package/skills/gitnexus-plan/references/pdg-slice.md +109 -0
  392. package/skills/gitnexus-plan/references/plan-template.md +201 -0
  393. package/skills/gitnexus-plan/scripts/evidence-provenance.mjs +2084 -0
  394. package/skills/gitnexus-review/SKILL.md +273 -0
  395. package/skills/gitnexus-review/ci-personas/ci-adversarial-lens.md +42 -0
  396. package/skills/gitnexus-review/ci-personas/ci-blast-radius-lens.md +39 -0
  397. package/skills/gitnexus-review/ci-personas/ci-correctness-lens.md +37 -0
  398. package/skills/gitnexus-review/ci-personas/ci-coverage-lens.md +40 -0
  399. package/skills/gitnexus-review/ci-personas/ci-critic-lens.md +42 -0
  400. package/skills/gitnexus-review/ci-personas/ci-security-lens.md +39 -0
  401. package/skills/gitnexus-work/README.md +71 -0
  402. package/skills/gitnexus-work/SKILL.md +269 -0
  403. package/skills/gitnexus-work/references/evidence-provenance.md +272 -0
  404. package/skills/gitnexus-work/scripts/evidence-provenance.mjs +2084 -0
  405. package/web/assets/ProcessFlowModal-B_bfBySy.js +22 -0
  406. package/web/assets/__vite-browser-external-bMKyYQkO.js +1 -0
  407. package/web/assets/agent-Bqxn-abU.js +626 -0
  408. package/web/assets/architecture-7EHR7CIX-6QZW5X65-BVWFvYz2.js +1 -0
  409. package/web/assets/architectureDiagram-UL44E2DR-CmG3uPWf.js +36 -0
  410. package/web/assets/{blockDiagram-7IZFK4PR-BkWhpzWB.js → blockDiagram-7IZFK4PR-DBefcjof.js} +7 -7
  411. package/web/assets/{c4Diagram-Y2BXMSZH-DtBvpzWD.js → c4Diagram-Y2BXMSZH-EjWnjEJl.js} +6 -6
  412. package/web/assets/chunk-2T2R6R2M-Bb6Sy3_j.js +4 -0
  413. package/web/assets/chunk-2UTLFMKG-CXlhF3Pt.js +1 -0
  414. package/web/assets/chunk-3SSMPTDK-rPsRM1x0.js +321 -0
  415. package/web/assets/chunk-4R4BOZG6-Be_F0JoX.js +159 -0
  416. package/web/assets/{chunk-6764PJDD-DpIS7hxD.js → chunk-6764PJDD-C7xxk063.js} +1 -1
  417. package/web/assets/chunk-67TQ5CYL-CA7OhIEf.js +124 -0
  418. package/web/assets/chunk-7FYTHRHK-CsmYIswB.js +32 -0
  419. package/web/assets/chunk-7J6CGLKN-BlKwgXKs.js +10 -0
  420. package/web/assets/chunk-7W6UQGC5-pyKGssV3.js +1 -0
  421. package/web/assets/chunk-C62D2QBJ-3gdL8EKc.js +1 -0
  422. package/web/assets/chunk-CEXFNPSA-CjwtMDkc.js +1 -0
  423. package/web/assets/{chunk-INKRHTLW-Nu4ri9P-.js → chunk-INKRHTLW-Ckl3_KkO.js} +13 -13
  424. package/web/assets/chunk-J5EP6P6S-L19oz55R.js +1 -0
  425. package/web/assets/{chunk-KGFNY3KK-JGMJvKCK.js → chunk-KGFNY3KK-BZeP-55u.js} +17 -17
  426. package/web/assets/chunk-KGYTTC2M-zm6n242I.js +1 -0
  427. package/web/assets/{chunk-KRXBNO2N-BW5o0KUN.js → chunk-KRXBNO2N-C-UNuKKf.js} +1 -1
  428. package/web/assets/{chunk-LCXTWHL2-CfLLCywY.js → chunk-LCXTWHL2-C162wE5z.js} +5 -5
  429. package/web/assets/chunk-LII3EMHJ-BL6lKJ_P.js +1 -0
  430. package/web/assets/chunk-LRIF4GLE-n59lhprL.js +1 -0
  431. package/web/assets/chunk-QA3QBVWF-DVXPOIxe.js +2 -0
  432. package/web/assets/chunk-RERM46MO-B5dAdWpP.js +1 -0
  433. package/web/assets/chunk-RG4AUYOV-ZNgyLFq6.js +206 -0
  434. package/web/assets/chunk-RKZBBQEN-MyIQbGgD.js +1 -0
  435. package/web/assets/chunk-RLI5ZMPA-CkXZ0y0U.js +1 -0
  436. package/web/assets/chunk-T2UQINTJ-BEyXhZWe.js +1 -0
  437. package/web/assets/chunk-UP6H54XL-BG-n7qDp.js +1 -0
  438. package/web/assets/chunk-UXSXWOXI-CW1_r5cS.js +1 -0
  439. package/web/assets/chunk-UY5QBCOK-ByqTVLvh.js +1 -0
  440. package/web/assets/chunk-VU6ZFW4Y-BLeasdkU.js +2 -0
  441. package/web/assets/{chunk-W44A43WB-JqMLIpR0.js → chunk-W44A43WB-BN8EQc_u.js} +1 -1
  442. package/web/assets/chunk-ZXARS5L4-C3v-4Fsw.js +1 -0
  443. package/web/assets/classDiagram-KGZ6W3CR-olpO3fMe.js +1 -0
  444. package/web/assets/classDiagram-v2-72OJOZXJ-olpO3fMe.js +1 -0
  445. package/web/assets/cose-bilkent-UX7MHV2Q-BmnZ7Mvj.js +1 -0
  446. package/web/assets/dagre-ND4H6XIP-CY4iIF7v.js +4 -0
  447. package/web/assets/diagram-3NCE3AQN-CO6QpzEE.js +43 -0
  448. package/web/assets/diagram-GF46GFSD-B2atbzmU.js +24 -0
  449. package/web/assets/diagram-HNR7UZ2L-DVRltWbL.js +3 -0
  450. package/web/assets/diagram-QXG6HAR7-DR6AHD2h.js +24 -0
  451. package/web/assets/diagram-WEQXMOUZ-DkpoMwTL.js +10 -0
  452. package/web/assets/erDiagram-L5TCEMPS-n8g_90bc.js +85 -0
  453. package/web/assets/eventmodeling-FCH6USID-MREXMVOE-CgFBM7e-.js +1 -0
  454. package/web/assets/flowDiagram-H6V6AXG4-SGMDiCkB.js +162 -0
  455. package/web/assets/ganttDiagram-JCBTUEKG-Cb3UBqRS.js +292 -0
  456. package/web/assets/gitGraph-WXDBUCRP-R675I2BI-DCJv7SII.js +1 -0
  457. package/web/assets/gitGraphDiagram-S2ZK5IYY-Bs9CR7tq.js +106 -0
  458. package/web/assets/index-CX_fADmQ.css +2 -0
  459. package/web/assets/index-D1Z3dGGz.js +575 -0
  460. package/web/assets/info-J43DQDTF-KCYPFFUO-BfNy-9Nd.js +1 -0
  461. package/web/assets/infoDiagram-3YFTVSEB-T368on4D.js +2 -0
  462. package/web/assets/{ishikawaDiagram-BNXS4ZKH-DfYpxnE5.js → ishikawaDiagram-BNXS4ZKH-XpjJRU9h.js} +6 -6
  463. package/web/assets/journeyDiagram-M6C3CM3L-C6wjooas.js +139 -0
  464. package/web/assets/kanban-definition-75IXJCU3-5_UVY79g.js +89 -0
  465. package/web/assets/{katex-K3KEBU37-CTc5BslQ.js → katex-K3KEBU37-qRQBy-EZ.js} +2 -2
  466. package/web/assets/{mindmap-definition-2TDM6QVE-DypvbOMQ.js → mindmap-definition-2TDM6QVE-8xDLzk2e.js} +29 -29
  467. package/web/assets/node-Ct_zna8U.js +8 -0
  468. package/web/assets/packet-YPE3B663-LP52Z2RK-D4qkeG9k.js +1 -0
  469. package/web/assets/pie-LRSECV5Y-TCRJHUBD-B_yMKyQa.js +1 -0
  470. package/web/assets/pieDiagram-CU6KROY3-Bv4LpAbr.js +30 -0
  471. package/web/assets/preload-helper-Czpn1I53.js +1 -0
  472. package/web/assets/{quadrantDiagram-VICAPDV7-yXSZ2lLz.js → quadrantDiagram-VICAPDV7-DR54jOeR.js} +6 -6
  473. package/web/assets/radar-GUYGQ44K-RDLRG3WG-DeD6yN_0.js +1 -0
  474. package/web/assets/requirementDiagram-JXO7QTGE-PEkg7sKC.js +84 -0
  475. package/web/assets/rolldown-runtime-QTnfLwEv.js +1 -0
  476. package/web/assets/sankeyDiagram-URQDO5SZ-DDWEtjcs.js +40 -0
  477. package/web/assets/sdk-CzHHH8tD.js +37 -0
  478. package/web/assets/sequenceDiagram-VS2MUI6T-Bv-uwANx.js +162 -0
  479. package/web/assets/src-JV30wyV8.js +1 -0
  480. package/web/assets/stateDiagram-7D4R322I-DVNjmkEq.js +1 -0
  481. package/web/assets/stateDiagram-v2-36443NZ5-CyN4Fh7_.js +1 -0
  482. package/web/assets/timeline-definition-O6YCAMPW-Qmsuo7qS.js +120 -0
  483. package/web/assets/treeView-BLDUP644-QA4HXRO3-CDuSYnd4.js +1 -0
  484. package/web/assets/treemap-LRROVOQU-LLAWBHMP-imazucyJ.js +1 -0
  485. package/web/assets/vennDiagram-MWXL3ELB-BD1iK1SX.js +34 -0
  486. package/web/assets/wardley-L42UT6IY-5TKZOOLJ-DqrT9tB8.js +1 -0
  487. package/web/assets/wardleyDiagram-CUQ6CDDI-D6TzLVob.js +78 -0
  488. package/web/assets/{xychartDiagram-N2JHSOCM-BOLm0eNm.js → xychartDiagram-N2JHSOCM-CoZ_Tits.js} +6 -6
  489. package/web/index.html +18 -10
  490. package/skills/gitnexus-pr-review.md +0 -163
  491. package/web/assets/agent-Dli_x7bU.js +0 -660
  492. package/web/assets/architecture-7EHR7CIX-6QZW5X65-DxJw65fT.js +0 -1
  493. package/web/assets/architectureDiagram-UL44E2DR-dEkqUZN9.js +0 -36
  494. package/web/assets/chunk-3SSMPTDK-B4_etUhr.js +0 -321
  495. package/web/assets/chunk-67TQ5CYL-4sNPJU1q.js +0 -124
  496. package/web/assets/chunk-7W6UQGC5-KyEG0HQg.js +0 -1
  497. package/web/assets/chunk-KGYTTC2M-CWC_c3H9.js +0 -161
  498. package/web/assets/chunk-LII3EMHJ-D-jm-dLa.js +0 -1
  499. package/web/assets/chunk-QA3QBVWF-BgRW3SXd.js +0 -2
  500. package/web/assets/chunk-RG4AUYOV-CqWzWsV3.js +0 -206
  501. package/web/assets/chunk-T2UQINTJ-DSBqkzH7.js +0 -1
  502. package/web/assets/chunk-UY5QBCOK-DyF0vyGd.js +0 -1
  503. package/web/assets/chunk-ZXARS5L4-CxN8oiwI.js +0 -1
  504. package/web/assets/classDiagram-KGZ6W3CR-CMne4tG9.js +0 -1
  505. package/web/assets/classDiagram-v2-72OJOZXJ-CMne4tG9.js +0 -1
  506. package/web/assets/cose-bilkent-UX7MHV2Q-D71wNYRJ.js +0 -1
  507. package/web/assets/dagre-ND4H6XIP-Co5rRx9X.js +0 -4
  508. package/web/assets/diagram-3NCE3AQN-DBp4O00j.js +0 -43
  509. package/web/assets/diagram-GF46GFSD-6N_8yS0i.js +0 -24
  510. package/web/assets/diagram-HNR7UZ2L-DFCfd5LI.js +0 -3
  511. package/web/assets/diagram-QXG6HAR7-Di093rFg.js +0 -24
  512. package/web/assets/diagram-WEQXMOUZ-DZWi4SUN.js +0 -10
  513. package/web/assets/erDiagram-L5TCEMPS-8X1y6R4Y.js +0 -85
  514. package/web/assets/eventmodeling-FCH6USID-MREXMVOE-BQm9QzEa.js +0 -1
  515. package/web/assets/flowDiagram-H6V6AXG4-CMpY8Ufn.js +0 -162
  516. package/web/assets/ganttDiagram-JCBTUEKG-D3iL2Aet.js +0 -292
  517. package/web/assets/gitGraph-WXDBUCRP-R675I2BI-Cr_Bm2Nb.js +0 -1
  518. package/web/assets/gitGraphDiagram-S2ZK5IYY-CPIDgxGm.js +0 -106
  519. package/web/assets/index-KctnI3Vq.js +0 -635
  520. package/web/assets/index-_lgn7hs5.css +0 -2
  521. package/web/assets/info-J43DQDTF-KCYPFFUO-Cs4p2oyk.js +0 -1
  522. package/web/assets/infoDiagram-3YFTVSEB-BaLMZkUg.js +0 -2
  523. package/web/assets/journeyDiagram-M6C3CM3L-CvArszLo.js +0 -139
  524. package/web/assets/kanban-definition-75IXJCU3-CpfVLLf9.js +0 -89
  525. package/web/assets/packet-YPE3B663-LP52Z2RK-CKCrztD2.js +0 -1
  526. package/web/assets/pie-LRSECV5Y-TCRJHUBD-BB7pHqoD.js +0 -1
  527. package/web/assets/pieDiagram-CU6KROY3-s27EFWgP.js +0 -30
  528. package/web/assets/radar-GUYGQ44K-RDLRG3WG-DhoTOcuK.js +0 -1
  529. package/web/assets/requirementDiagram-JXO7QTGE-CWhqInuf.js +0 -84
  530. package/web/assets/sankeyDiagram-URQDO5SZ-1qEjlFIB.js +0 -40
  531. package/web/assets/sequenceDiagram-VS2MUI6T-RyTlZ5cR.js +0 -162
  532. package/web/assets/stateDiagram-7D4R322I-DTjVfX4A.js +0 -1
  533. package/web/assets/stateDiagram-v2-36443NZ5-xY9_k9_Q.js +0 -1
  534. package/web/assets/timeline-definition-O6YCAMPW-DrrqLYAf.js +0 -120
  535. package/web/assets/treeView-BLDUP644-QA4HXRO3-ODehyGKL.js +0 -1
  536. package/web/assets/treemap-LRROVOQU-LLAWBHMP-BfpgC-7o.js +0 -1
  537. package/web/assets/vennDiagram-MWXL3ELB-B0RkWAim.js +0 -34
  538. package/web/assets/wardley-L42UT6IY-5TKZOOLJ-CNeL6VHE.js +0 -1
  539. package/web/assets/wardleyDiagram-CUQ6CDDI-boUxiQ5F.js +0 -78
@@ -0,0 +1,1820 @@
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
+ executablePath: resolveExistingPath(process.execPath),
133
+ nodeVersion: process.version,
134
+ platform: process.platform,
135
+ architecture: process.arch,
136
+ endianness: os.endianness(),
137
+ modulesAbi: process.versions.modules ?? 'unknown',
138
+ napiAbi: process.versions.napi ?? 'unknown',
139
+ libc: LIBC_VARIANT,
140
+ };
141
+ }
142
+ function snapshotReadableFile(candidate) {
143
+ const link = lstatSync(candidate, { bigint: true });
144
+ const target = statSync(candidate, { bigint: true });
145
+ if (!target.isFile())
146
+ throw new Error(`Analyzer identity input is not a file: ${candidate}`);
147
+ return {
148
+ link: statState(link),
149
+ target: statState(target),
150
+ ...(link.isSymbolicLink() ? { symlinkTarget: readlinkSync(candidate) } : {}),
151
+ };
152
+ }
153
+ function snapshotDirectory(candidate) {
154
+ const stat = lstatSync(candidate, { bigint: true });
155
+ if (!stat.isDirectory() || stat.isSymbolicLink()) {
156
+ throw new Error(`Analyzer identity input is not a directory: ${candidate}`);
157
+ }
158
+ return statState(stat);
159
+ }
160
+ function readDirectory(candidate, options) {
161
+ options.onCacheMissWork?.({ kind: 'directory-walk', path: candidate });
162
+ return readdirSync(candidate, { withFileTypes: true });
163
+ }
164
+ function directoryEntriesDigestFrom(entriesInput) {
165
+ const entries = entriesInput
166
+ .map((entry) => ({
167
+ name: entry.name,
168
+ kind: entry.isDirectory()
169
+ ? 'directory'
170
+ : entry.isFile()
171
+ ? 'file'
172
+ : entry.isSymbolicLink()
173
+ ? 'symlink'
174
+ : 'other',
175
+ }))
176
+ .sort((a, b) => compareBytes(a.name, b.name));
177
+ const hash = createHash('sha256');
178
+ updateCanonicalFrame(hash, ['directory-entries-v1']);
179
+ for (const entry of entries)
180
+ updateCanonicalFrame(hash, [entry.name, entry.kind]);
181
+ return `sha256:${hash.digest('hex')}`;
182
+ }
183
+ function directoryEntriesDigest(candidate) {
184
+ return directoryEntriesDigestFrom(readdirSync(candidate, { withFileTypes: true }));
185
+ }
186
+ function snapshotDirectoryInventory(candidate) {
187
+ for (let attempt = 0; attempt < 2; attempt += 1) {
188
+ const before = snapshotDirectory(candidate);
189
+ const entriesDigest = directoryEntriesDigest(candidate);
190
+ const after = snapshotDirectory(candidate);
191
+ if (isDeepStrictEqual(before, after))
192
+ return { state: after, entriesDigest };
193
+ }
194
+ throw new Error(`Analyzer identity directory changed while it was read: ${candidate}`);
195
+ }
196
+ function readStableFile(candidate) {
197
+ for (let attempt = 0; attempt < 2; attempt += 1) {
198
+ const before = snapshotReadableFile(candidate);
199
+ const bytes = readFileSync(candidate);
200
+ const after = snapshotReadableFile(candidate);
201
+ if (isDeepStrictEqual(before, after))
202
+ return { bytes, state: after };
203
+ }
204
+ throw new Error(`Analyzer identity input changed while it was being read: ${candidate}`);
205
+ }
206
+ function readStableFileWithinBudget(candidate, budget, maxBytes) {
207
+ const before = snapshotReadableFile(candidate);
208
+ const bytes = stateSize(before.target, candidate);
209
+ if (budget.bytes + bytes > maxBytes) {
210
+ throw new Error(`Analyzer runtime scan exceeded ${maxBytes} bytes: ${candidate}`);
211
+ }
212
+ const stable = readStableFile(candidate);
213
+ budget.bytes += stable.bytes.length;
214
+ return stable;
215
+ }
216
+ /** Hash a stable file through a fixed-size buffer instead of materializing it. */
217
+ function hashStableFile(candidate) {
218
+ for (let attempt = 0; attempt < 2; attempt += 1) {
219
+ const before = snapshotReadableFile(candidate);
220
+ const expectedBytes = stateSize(before.target, candidate);
221
+ let descriptor = null;
222
+ try {
223
+ descriptor = openSync(candidate, fsConstants.O_RDONLY);
224
+ const openedBefore = statState(fstatSync(descriptor, { bigint: true }));
225
+ if (!isDeepStrictEqual(openedBefore, before.target))
226
+ continue;
227
+ const hash = createHash('sha256');
228
+ const buffer = Buffer.allocUnsafe(HASH_BUFFER_BYTES);
229
+ let bytes = 0;
230
+ while (true) {
231
+ const read = readSync(descriptor, buffer, 0, buffer.length, null);
232
+ if (read === 0)
233
+ break;
234
+ hash.update(buffer.subarray(0, read));
235
+ bytes += read;
236
+ if (bytes > expectedBytes)
237
+ break;
238
+ }
239
+ const openedAfter = statState(fstatSync(descriptor, { bigint: true }));
240
+ closeSync(descriptor);
241
+ descriptor = null;
242
+ const after = snapshotReadableFile(candidate);
243
+ if (bytes === expectedBytes &&
244
+ isDeepStrictEqual(openedBefore, openedAfter) &&
245
+ isDeepStrictEqual(before, after)) {
246
+ return { digest: `sha256:${hash.digest('hex')}`, state: after, bytes };
247
+ }
248
+ }
249
+ finally {
250
+ if (descriptor !== null)
251
+ closeSync(descriptor);
252
+ }
253
+ }
254
+ throw new Error(`Analyzer identity input changed while it was being hashed: ${candidate}`);
255
+ }
256
+ function resolveExistingPath(candidate) {
257
+ return realpathSync.native(path.resolve(candidate));
258
+ }
259
+ function isFile(candidate) {
260
+ try {
261
+ return statSync(candidate).isFile();
262
+ }
263
+ catch {
264
+ return false;
265
+ }
266
+ }
267
+ function readManifest(manifestPath, options, budget, limits) {
268
+ options.onCacheMissWork?.({ kind: 'manifest-read', path: manifestPath });
269
+ const { bytes, state } = readStableFileWithinBudget(manifestPath, budget, limits.runtimeBytes);
270
+ return { bytes, state, manifest: JSON.parse(bytes.toString('utf8')) };
271
+ }
272
+ function manifestLabel(manifest) {
273
+ const name = typeof manifest.name === 'string' ? manifest.name : '<unnamed>';
274
+ const version = typeof manifest.version === 'string' ? manifest.version : '<unversioned>';
275
+ return `${name}@${version}`;
276
+ }
277
+ function isInside(parent, candidate) {
278
+ const relative = path.relative(parent, candidate);
279
+ return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..');
280
+ }
281
+ function resolveBuildRoot(analyzerModulePath) {
282
+ let cursor = path.dirname(analyzerModulePath);
283
+ while (true) {
284
+ const base = path.basename(cursor);
285
+ if (base === 'src' || base === 'dist') {
286
+ const packageRoot = path.dirname(cursor);
287
+ const packageJson = path.join(packageRoot, 'package.json');
288
+ if (lstatSync(packageJson).isFile()) {
289
+ return {
290
+ packageRoot,
291
+ buildRoot: cursor,
292
+ kind: base === 'src' ? 'source' : 'distribution',
293
+ };
294
+ }
295
+ }
296
+ const parent = path.dirname(cursor);
297
+ if (parent === cursor)
298
+ break;
299
+ cursor = parent;
300
+ }
301
+ throw new Error(`Cannot resolve GitNexus package root from analyzer module: ${analyzerModulePath}`);
302
+ }
303
+ function compareBytes(a, b) {
304
+ return Buffer.compare(Buffer.from(a), Buffer.from(b));
305
+ }
306
+ function collectBuildEntries(buildRoot, options, limits) {
307
+ const entries = [];
308
+ const pending = [
309
+ { absoluteDir: buildRoot, depth: 0 },
310
+ ];
311
+ let scannedEntries = 0;
312
+ let scannedBytes = 0;
313
+ while (pending.length > 0) {
314
+ const next = pending.pop();
315
+ if (!next)
316
+ break;
317
+ const { absoluteDir, depth } = next;
318
+ const directoryEntries = readDirectory(absoluteDir, options);
319
+ scannedEntries += directoryEntries.length;
320
+ if (scannedEntries > limits.buildEntries) {
321
+ throw new Error(`Analyzer build scan exceeded ${limits.buildEntries} entries: ${buildRoot}`);
322
+ }
323
+ for (const entry of directoryEntries) {
324
+ const absolutePath = path.join(absoluteDir, entry.name);
325
+ const relativePath = path.relative(buildRoot, absolutePath).split(path.sep).join('/');
326
+ const link = lstatSync(absolutePath, { bigint: true });
327
+ if (link.isDirectory()) {
328
+ entries.push({
329
+ absolutePath,
330
+ relativePath,
331
+ kind: 'directory',
332
+ state: statState(link),
333
+ });
334
+ if (depth >= limits.buildDepth) {
335
+ throw new Error(`Analyzer build scan exceeded depth ${limits.buildDepth}: ${absolutePath}`);
336
+ }
337
+ pending.push({ absoluteDir: absolutePath, depth: depth + 1 });
338
+ }
339
+ else if (link.isFile()) {
340
+ const state = statState(link);
341
+ scannedBytes += stateSize(state, absolutePath);
342
+ if (scannedBytes > limits.buildBytes) {
343
+ throw new Error(`Analyzer build scan exceeded ${limits.buildBytes} bytes: ${buildRoot}`);
344
+ }
345
+ entries.push({ absolutePath, relativePath, kind: 'file', state });
346
+ }
347
+ else if (link.isSymbolicLink()) {
348
+ entries.push({ absolutePath, relativePath, kind: 'symlink', state: statState(link) });
349
+ }
350
+ else {
351
+ throw new Error(`Unsupported analyzer build entry: ${absolutePath}`);
352
+ }
353
+ }
354
+ }
355
+ entries.sort((a, b) => compareBytes(a.relativePath, b.relativePath));
356
+ return entries;
357
+ }
358
+ function buildSnapshot(entries) {
359
+ return entries.map(({ relativePath, kind, state }) => ({ relativePath, kind, state }));
360
+ }
361
+ function buildCacheKey(entry) {
362
+ return JSON.stringify([entry.kind, entry.relativePath]);
363
+ }
364
+ function hashBuildTree(buildRoot, cache, options, limits) {
365
+ const entries = collectBuildEntries(buildRoot, options, limits);
366
+ const cachedEntries = new Map((cache?.buildEntries ?? []).map((entry) => [buildCacheKey(entry), entry]));
367
+ const nextEntries = [];
368
+ const hash = createHash('sha256');
369
+ updateCanonicalFrame(hash, ['domain', BUILD_CANONICALIZATION]);
370
+ for (const entry of entries) {
371
+ const cached = cachedEntries.get(buildCacheKey(entry));
372
+ let digest;
373
+ if (entry.kind !== 'directory' &&
374
+ cached?.digest &&
375
+ SHA256_PATTERN.test(cached.digest) &&
376
+ isDeepStrictEqual(cached.state, entry.state)) {
377
+ digest = cached.digest;
378
+ }
379
+ else if (entry.kind === 'file') {
380
+ const stable = hashStableFile(entry.absolutePath);
381
+ entry.state = stable.state.link;
382
+ digest = stable.digest;
383
+ options.onHashedInput?.({
384
+ kind: 'build',
385
+ path: entry.absolutePath,
386
+ bytes: stable.bytes,
387
+ });
388
+ }
389
+ else if (entry.kind === 'symlink') {
390
+ // Imported files and directories are resolved through symlinks by Node.
391
+ // Hashing only link text would let bytes outside buildRoot change without
392
+ // changing the receipt. Reject them instead of inventing an incomplete
393
+ // recursive trust boundary (target containment, cycles, and TOCTOU).
394
+ throw new Error(`Analyzer build symbolic links are not supported; materialize the build tree: ${entry.absolutePath}`);
395
+ }
396
+ updateCanonicalFrame(hash, [
397
+ 'build-entry',
398
+ entry.relativePath,
399
+ entry.kind,
400
+ digest ? digestBytes(digest) : Buffer.alloc(0),
401
+ ]);
402
+ nextEntries.push({
403
+ relativePath: entry.relativePath,
404
+ kind: entry.kind,
405
+ state: entry.state,
406
+ ...(digest ? { digest } : {}),
407
+ });
408
+ }
409
+ const directoryGuards = [
410
+ { relativePath: '', state: snapshotDirectory(buildRoot), entriesDigest: '' },
411
+ ...nextEntries
412
+ .filter((entry) => entry.kind === 'directory')
413
+ .map((entry) => ({
414
+ relativePath: entry.relativePath,
415
+ state: entry.state,
416
+ entriesDigest: '',
417
+ })),
418
+ ].map((guard) => {
419
+ const absolutePath = guard.relativePath
420
+ ? path.join(buildRoot, ...guard.relativePath.split('/'))
421
+ : buildRoot;
422
+ const inventory = snapshotDirectoryInventory(absolutePath);
423
+ return {
424
+ relativePath: guard.relativePath,
425
+ state: inventory.state,
426
+ entriesDigest: inventory.entriesDigest,
427
+ };
428
+ });
429
+ return {
430
+ digest: `sha256:${hash.digest('hex')}`,
431
+ entries: nextEntries,
432
+ snapshot: buildSnapshot(entries),
433
+ rootState: directoryGuards[0].state,
434
+ directoryGuards,
435
+ };
436
+ }
437
+ function recordDirectoryGuard(guards, candidate) {
438
+ if (guards.has(candidate))
439
+ return true;
440
+ try {
441
+ guards.set(candidate, snapshotDirectoryInventory(candidate));
442
+ return true;
443
+ }
444
+ catch {
445
+ return false;
446
+ }
447
+ }
448
+ function snapshotDependencyPathGuard(candidate) {
449
+ try {
450
+ const stat = lstatSync(candidate, { bigint: true });
451
+ const type = stat.isDirectory()
452
+ ? 'directory'
453
+ : stat.isFile()
454
+ ? 'file'
455
+ : stat.isSymbolicLink()
456
+ ? 'symlink'
457
+ : 'other';
458
+ return {
459
+ type,
460
+ state: statState(stat),
461
+ ...(type === 'symlink' ? { symlinkTarget: readlinkSync(candidate) } : {}),
462
+ };
463
+ }
464
+ catch {
465
+ return null;
466
+ }
467
+ }
468
+ function recordDependencyPathGuard(guards, candidate) {
469
+ if (guards.has(candidate))
470
+ return guards.get(candidate) ?? null;
471
+ const result = snapshotDependencyPathGuard(candidate);
472
+ guards.set(candidate, result);
473
+ return result;
474
+ }
475
+ function findNearestPackageLock(packageRoot, pathGuards, limits) {
476
+ let cursor = packageRoot;
477
+ let ancestors = 0;
478
+ while (true) {
479
+ ancestors += 1;
480
+ if (ancestors > limits.resolutionAncestors) {
481
+ throw new Error(`Analyzer package-lock lookup exceeded ${limits.resolutionAncestors} ancestors: ${packageRoot}`);
482
+ }
483
+ const candidate = path.join(cursor, 'package-lock.json');
484
+ recordDependencyPathGuard(pathGuards, candidate);
485
+ // Preserve the link path so ReadableFileState guards both the link and its
486
+ // resolved target. Realpathing here would miss a later retarget while the
487
+ // old target remained unchanged.
488
+ try {
489
+ const link = lstatSync(candidate);
490
+ if (link.isFile())
491
+ return path.resolve(candidate);
492
+ if (link.isSymbolicLink()) {
493
+ try {
494
+ const target = statSync(candidate);
495
+ if (!target.isFile()) {
496
+ throw new Error(`Analyzer package lock symbolic link does not resolve to a file: ${candidate}`);
497
+ }
498
+ }
499
+ catch {
500
+ throw new Error(`Analyzer package lock symbolic link does not resolve to a file: ${candidate}`);
501
+ }
502
+ return path.resolve(candidate);
503
+ }
504
+ throw new Error(`Analyzer package lock is not a regular file: ${candidate}`);
505
+ }
506
+ catch (error) {
507
+ if (error.code !== 'ENOENT')
508
+ throw error;
509
+ }
510
+ const parent = path.dirname(cursor);
511
+ if (parent === cursor)
512
+ return null;
513
+ cursor = parent;
514
+ }
515
+ }
516
+ function runtimePackageLocator(packageRoot, runtimeRoot) {
517
+ if (runtimeRoot === packageRoot)
518
+ return 'root:.';
519
+ const relative = path.relative(packageRoot, runtimeRoot).split(path.sep).join('/');
520
+ return `relative:${relative}`;
521
+ }
522
+ function dependencyNames(manifest) {
523
+ const names = new Set();
524
+ for (const section of [
525
+ manifest.dependencies,
526
+ manifest.optionalDependencies,
527
+ manifest.peerDependencies,
528
+ ]) {
529
+ if (!section || typeof section !== 'object')
530
+ continue;
531
+ for (const name of Object.keys(section))
532
+ names.add(name);
533
+ }
534
+ return [...names].sort(compareBytes);
535
+ }
536
+ function resolveDependencyPackageRoot(fromRoot, packageName, pathGuards, limits) {
537
+ let cursor = fromRoot;
538
+ const segments = packageName.split('/');
539
+ let ancestors = 0;
540
+ while (true) {
541
+ ancestors += 1;
542
+ if (ancestors > limits.resolutionAncestors) {
543
+ throw new Error(`Analyzer dependency resolution exceeded ${limits.resolutionAncestors} ancestors: ${packageName}`);
544
+ }
545
+ const nodeModulesRoot = path.join(cursor, 'node_modules');
546
+ recordDependencyPathGuard(pathGuards, nodeModulesRoot);
547
+ let candidateParent = nodeModulesRoot;
548
+ for (const segment of segments) {
549
+ candidateParent = path.join(candidateParent, segment);
550
+ // Guard every lexical hop, not only the final manifest. Package
551
+ // managers commonly expose packages through symlinks; a retarget can
552
+ // otherwise preserve a hard-linked manifest's stat identity while
553
+ // changing the runtime payload tree selected by Node.
554
+ recordDependencyPathGuard(pathGuards, candidateParent);
555
+ }
556
+ const manifestPath = path.join(candidateParent, 'package.json');
557
+ recordDependencyPathGuard(pathGuards, manifestPath);
558
+ if (isFile(manifestPath))
559
+ return resolveExistingPath(path.dirname(manifestPath));
560
+ const parent = path.dirname(cursor);
561
+ if (parent === cursor)
562
+ return null;
563
+ cursor = parent;
564
+ }
565
+ }
566
+ function collectRuntimePackages(packageRoot, directoryGuards, pathGuards, options, budget, limits) {
567
+ const rootManifestPath = path.join(packageRoot, 'package.json');
568
+ recordDirectoryGuard(directoryGuards, packageRoot);
569
+ const rootRead = readManifest(rootManifestPath, options, budget, limits);
570
+ const rootPackage = {
571
+ root: packageRoot,
572
+ locator: runtimePackageLocator(packageRoot, packageRoot),
573
+ manifestPath: rootManifestPath,
574
+ manifestBytes: rootRead.bytes,
575
+ manifestState: rootRead.state,
576
+ manifest: rootRead.manifest,
577
+ label: manifestLabel(rootRead.manifest),
578
+ };
579
+ const queue = [rootPackage];
580
+ const packages = new Map([[packageRoot, rootPackage]]);
581
+ const edges = [];
582
+ budget.packages = 1;
583
+ for (let index = 0; index < queue.length; index += 1) {
584
+ const parent = queue[index];
585
+ for (const dependencyName of dependencyNames(parent.manifest)) {
586
+ budget.edges += 1;
587
+ if (budget.edges > limits.runtimeEdges) {
588
+ throw new Error(`Analyzer dependency graph exceeded ${limits.runtimeEdges} edges: ${packageRoot}`);
589
+ }
590
+ const childRoot = resolveDependencyPackageRoot(parent.root, dependencyName, pathGuards, limits);
591
+ if (!childRoot) {
592
+ edges.push({
593
+ parentLocator: parent.locator,
594
+ parentLabel: parent.label,
595
+ dependencyName,
596
+ childLocator: '<missing>',
597
+ childLabel: '<missing>',
598
+ });
599
+ continue;
600
+ }
601
+ let child = packages.get(childRoot);
602
+ if (!child) {
603
+ budget.packages += 1;
604
+ if (budget.packages > limits.runtimePackages) {
605
+ throw new Error(`Analyzer dependency graph exceeded ${limits.runtimePackages} packages: ${packageRoot}`);
606
+ }
607
+ const manifestPath = path.join(childRoot, 'package.json');
608
+ recordDirectoryGuard(directoryGuards, childRoot);
609
+ const read = readManifest(manifestPath, options, budget, limits);
610
+ child = {
611
+ root: childRoot,
612
+ locator: runtimePackageLocator(packageRoot, childRoot),
613
+ manifestPath,
614
+ manifestBytes: read.bytes,
615
+ manifestState: read.state,
616
+ manifest: read.manifest,
617
+ label: manifestLabel(read.manifest),
618
+ };
619
+ packages.set(childRoot, child);
620
+ queue.push(child);
621
+ }
622
+ edges.push({
623
+ parentLocator: parent.locator,
624
+ parentLabel: parent.label,
625
+ dependencyName,
626
+ childLocator: child.locator,
627
+ childLabel: child.label,
628
+ });
629
+ }
630
+ }
631
+ return { packages: [...packages.values()], edges };
632
+ }
633
+ const PRUNED_RUNTIME_DIRECTORIES = new Set(['node_modules', '.git', '.hg', '.svn']);
634
+ function shouldHashRuntimePayload(relativePath) {
635
+ const lower = relativePath.toLowerCase();
636
+ // Each package manifest is already hashed as a separately framed dependency
637
+ // input. Avoid counting/reading it twice while still hashing every other
638
+ // package payload: JavaScript modules, JSON data, native/Wasm binaries,
639
+ // extensionless exports, and files loaded explicitly through fs APIs. File
640
+ // and directory names are not authoritative platform-selection metadata:
641
+ // loaders may select or read a payload whose name mentions another target.
642
+ return lower !== 'package.json';
643
+ }
644
+ function collectArtifacts(root, canonicalPrefix, directoryGuards, options, budget, limits) {
645
+ const artifacts = [];
646
+ const pending = [{ absoluteDir: root, depth: 0 }];
647
+ while (pending.length > 0) {
648
+ const next = pending.pop();
649
+ if (!next)
650
+ break;
651
+ const { absoluteDir, depth } = next;
652
+ if (!recordDirectoryGuard(directoryGuards, absoluteDir)) {
653
+ throw new Error(`Analyzer runtime payload directory is unavailable: ${absoluteDir}`);
654
+ }
655
+ const entries = readDirectory(absoluteDir, options);
656
+ budget.entries += entries.length;
657
+ if (budget.entries > limits.runtimeEntries) {
658
+ throw new Error(`Analyzer runtime payload scan exceeded ${limits.runtimeEntries} entries: ${root}`);
659
+ }
660
+ for (const entry of entries) {
661
+ // Nested dependencies are collected from their manifests as separate
662
+ // packages. Only prune those separately traversed trees and VCS
663
+ // metadata; generic cache/model directories can contain loadable code,
664
+ // native addons, Wasm modules, or data consumed by the runtime.
665
+ if (entry.isDirectory() && PRUNED_RUNTIME_DIRECTORIES.has(entry.name)) {
666
+ continue;
667
+ }
668
+ const absolutePath = path.join(absoluteDir, entry.name);
669
+ const relativePath = path.relative(root, absolutePath).split(path.sep).join('/');
670
+ const stat = lstatSync(absolutePath);
671
+ if (stat.isDirectory()) {
672
+ if (depth >= limits.runtimeDepth) {
673
+ throw new Error(`Analyzer runtime payload scan exceeded depth ${limits.runtimeDepth}: ${absolutePath}`);
674
+ }
675
+ pending.push({ absoluteDir: absolutePath, depth: depth + 1 });
676
+ }
677
+ else if ((stat.isFile() || stat.isSymbolicLink()) &&
678
+ shouldHashRuntimePayload(relativePath)) {
679
+ const readableState = snapshotReadableFile(absolutePath);
680
+ const payloadBytes = stateSize(readableState.target, absolutePath);
681
+ budget.artifacts += 1;
682
+ if (budget.artifacts > limits.runtimePayloads) {
683
+ throw new Error(`Analyzer runtime payload scan exceeded ${limits.runtimePayloads} payloads: ${root}`);
684
+ }
685
+ if (budget.bytes + payloadBytes > limits.runtimeBytes) {
686
+ throw new Error(`Analyzer runtime scan exceeded ${limits.runtimeBytes} bytes: ${absolutePath}`);
687
+ }
688
+ budget.bytes += payloadBytes;
689
+ artifacts.push({
690
+ absolutePath,
691
+ canonicalPath: `${canonicalPrefix}/${relativePath}`,
692
+ kind: stat.isSymbolicLink() ? 'symlink' : 'file',
693
+ });
694
+ }
695
+ else if (!stat.isFile() && !stat.isSymbolicLink()) {
696
+ throw new Error(`Unsupported analyzer runtime payload entry: ${absolutePath}`);
697
+ }
698
+ }
699
+ }
700
+ return artifacts;
701
+ }
702
+ function collectVendoredGrammarInputs(packageRoot, directoryGuards, options, budget, limits) {
703
+ const vendorRoot = path.join(packageRoot, 'vendor');
704
+ if (!existsSync(vendorRoot) || !lstatSync(vendorRoot).isDirectory()) {
705
+ recordDirectoryGuard(directoryGuards, packageRoot);
706
+ return { manifests: [], artifacts: [] };
707
+ }
708
+ const manifests = [];
709
+ const artifacts = [];
710
+ if (!recordDirectoryGuard(directoryGuards, vendorRoot)) {
711
+ throw new Error(`Analyzer vendored runtime directory is unavailable: ${vendorRoot}`);
712
+ }
713
+ const vendorEntries = readDirectory(vendorRoot, options);
714
+ budget.entries += vendorEntries.length;
715
+ if (budget.entries > limits.runtimeEntries) {
716
+ throw new Error(`Analyzer runtime payload scan exceeded ${limits.runtimeEntries} entries: ${vendorRoot}`);
717
+ }
718
+ for (const entry of vendorEntries) {
719
+ if (!entry.isDirectory() || !entry.name.startsWith('tree-sitter-'))
720
+ continue;
721
+ const grammarRoot = path.join(vendorRoot, entry.name);
722
+ const manifestPath = path.join(grammarRoot, 'package.json');
723
+ if (isFile(manifestPath)) {
724
+ options.onCacheMissWork?.({ kind: 'manifest-read', path: manifestPath });
725
+ const read = readStableFileWithinBudget(manifestPath, budget, limits.runtimeBytes);
726
+ manifests.push({
727
+ canonicalPath: `vendor:${entry.name}/package.json`,
728
+ absolutePath: manifestPath,
729
+ bytes: read.bytes,
730
+ state: read.state,
731
+ });
732
+ }
733
+ artifacts.push(...collectArtifacts(grammarRoot, `vendor:${entry.name}`, directoryGuards, options, budget, limits));
734
+ }
735
+ return { manifests, artifacts };
736
+ }
737
+ function collectDependencyInputs(packageRoot, options, limits) {
738
+ const directoryGuards = new Map();
739
+ const pathGuards = new Map();
740
+ const artifactScanBudget = {
741
+ entries: 0,
742
+ artifacts: 0,
743
+ bytes: 0,
744
+ packages: 0,
745
+ edges: 0,
746
+ };
747
+ const manifestPath = resolveExistingPath(path.join(packageRoot, 'package.json'));
748
+ const lockfilePath = findNearestPackageLock(packageRoot, pathGuards, limits);
749
+ if (lockfilePath)
750
+ options.onCacheMissWork?.({ kind: 'manifest-read', path: lockfilePath });
751
+ const lockfile = lockfilePath
752
+ ? readStableFileWithinBudget(lockfilePath, artifactScanBudget, limits.runtimeBytes)
753
+ : null;
754
+ const { packages, edges } = collectRuntimePackages(packageRoot, directoryGuards, pathGuards, options, artifactScanBudget, limits);
755
+ const vendored = collectVendoredGrammarInputs(packageRoot, directoryGuards, options, artifactScanBudget, limits);
756
+ // The root build and vendored grammars are covered separately. Every
757
+ // resolved external runtime package is scanned, regardless of package name:
758
+ // native loaders are not constrained to a permanent allowlist (for example,
759
+ // Transformers resolves Sharp's @img platform packages).
760
+ const artifacts = packages
761
+ .slice(1)
762
+ .flatMap((runtimePackage) => collectArtifacts(runtimePackage.root, `package:${runtimePackage.locator}`, directoryGuards, options, artifactScanBudget, limits));
763
+ artifacts.push(...vendored.artifacts);
764
+ artifacts.sort((a, b) => compareBytes(`${a.canonicalPath}\u0000${a.absolutePath}`, `${b.canonicalPath}\u0000${b.absolutePath}`));
765
+ return {
766
+ manifestPath,
767
+ lockfilePath,
768
+ lockfileBytes: lockfile?.bytes ?? null,
769
+ lockfileState: lockfile?.state ?? null,
770
+ packages,
771
+ edges,
772
+ vendoredManifests: vendored.manifests,
773
+ artifacts,
774
+ directoryGuards,
775
+ pathGuards,
776
+ };
777
+ }
778
+ function dependencySnapshot(inputs) {
779
+ const edgeKey = (edge) => JSON.stringify([
780
+ edge.parentLocator,
781
+ edge.parentLabel,
782
+ edge.dependencyName,
783
+ edge.childLocator,
784
+ edge.childLabel,
785
+ ]);
786
+ return {
787
+ manifestPath: inputs.manifestPath,
788
+ lockfilePath: inputs.lockfilePath,
789
+ lockfileDigest: inputs.lockfileBytes ? sha256(inputs.lockfileBytes) : null,
790
+ lockfileState: inputs.lockfileState,
791
+ packages: inputs.packages
792
+ .map((runtimePackage) => ({
793
+ root: runtimePackage.root,
794
+ locator: runtimePackage.locator,
795
+ manifestPath: runtimePackage.manifestPath,
796
+ manifestDigest: sha256(runtimePackage.manifestBytes),
797
+ manifestState: runtimePackage.manifestState,
798
+ label: runtimePackage.label,
799
+ }))
800
+ .sort((a, b) => compareBytes(a.locator, b.locator)),
801
+ edges: inputs.edges.map(edgeKey).sort(compareBytes),
802
+ vendoredManifests: inputs.vendoredManifests
803
+ .map((entry) => ({
804
+ canonicalPath: entry.canonicalPath,
805
+ absolutePath: entry.absolutePath,
806
+ digest: sha256(entry.bytes),
807
+ state: entry.state,
808
+ }))
809
+ .sort((a, b) => compareBytes(a.canonicalPath, b.canonicalPath)),
810
+ artifacts: inputs.artifacts.map((artifact) => ({
811
+ absolutePath: artifact.absolutePath,
812
+ canonicalPath: artifact.canonicalPath,
813
+ kind: artifact.kind,
814
+ state: snapshotReadableFile(artifact.absolutePath),
815
+ })),
816
+ directories: [...inputs.directoryGuards.entries()]
817
+ .map(([absolutePath, guard]) => ({ absolutePath, ...guard }))
818
+ .sort((a, b) => compareBytes(a.absolutePath, b.absolutePath)),
819
+ paths: [...inputs.pathGuards.entries()]
820
+ .map(([absolutePath, result]) => ({ absolutePath, result }))
821
+ .sort((a, b) => compareBytes(a.absolutePath, b.absolutePath)),
822
+ };
823
+ }
824
+ function artifactCacheKey(artifact) {
825
+ return JSON.stringify([artifact.kind, artifact.canonicalPath, artifact.absolutePath]);
826
+ }
827
+ function hashRuntimeArtifact(artifact, cache, options) {
828
+ const before = snapshotReadableFile(artifact.absolutePath);
829
+ if (cache && SHA256_PATTERN.test(cache.digest) && isDeepStrictEqual(cache.state, before)) {
830
+ return { digest: cache.digest, state: before };
831
+ }
832
+ const stable = hashStableFile(artifact.absolutePath);
833
+ const digest = hashCanonicalFrames([
834
+ [
835
+ 'runtime-payload-content-v1',
836
+ artifact.kind,
837
+ stable.state.symlinkTarget ?? '',
838
+ digestBytes(stable.digest),
839
+ ],
840
+ ]);
841
+ options.onHashedInput?.({
842
+ kind: 'runtime-artifact',
843
+ path: artifact.absolutePath,
844
+ bytes: stable.bytes,
845
+ });
846
+ return { digest, state: stable.state };
847
+ }
848
+ function compareEdges(a, b) {
849
+ return compareBytes(JSON.stringify([
850
+ a.parentLocator,
851
+ a.parentLabel,
852
+ a.dependencyName,
853
+ a.childLocator,
854
+ a.childLabel,
855
+ ]), JSON.stringify([
856
+ b.parentLocator,
857
+ b.parentLabel,
858
+ b.dependencyName,
859
+ b.childLocator,
860
+ b.childLabel,
861
+ ]));
862
+ }
863
+ function hashDependencyRuntime(inputs, cache, options, runtimeVariant) {
864
+ const cachedArtifacts = new Map((cache?.artifactEntries ?? []).map((entry) => [artifactCacheKey(entry), entry]));
865
+ const nextArtifacts = [];
866
+ const hash = createHash('sha256');
867
+ updateCanonicalFrame(hash, ['domain', DEPENDENCY_RUNTIME_CANONICALIZATION]);
868
+ updateCanonicalFrame(hash, [
869
+ 'runtime-variant',
870
+ runtimeVariant.nodeVersion,
871
+ runtimeVariant.platform,
872
+ runtimeVariant.architecture,
873
+ runtimeVariant.endianness,
874
+ runtimeVariant.modulesAbi,
875
+ runtimeVariant.napiAbi,
876
+ runtimeVariant.libc,
877
+ ]);
878
+ updateCanonicalFrame(hash, [
879
+ 'lockfile',
880
+ inputs.lockfilePath ? 'present' : 'absent',
881
+ inputs.lockfileBytes ?? Buffer.alloc(0),
882
+ ]);
883
+ const packageEntries = inputs.packages
884
+ .map((runtimePackage) => ({
885
+ canonicalPath: `package:${runtimePackage.locator}/package.json`,
886
+ bytes: runtimePackage.manifestBytes,
887
+ }))
888
+ .concat(inputs.vendoredManifests.map(({ canonicalPath, bytes }) => ({ canonicalPath, bytes })))
889
+ .sort((a, b) => compareBytes(a.canonicalPath, b.canonicalPath));
890
+ for (const entry of packageEntries) {
891
+ updateCanonicalFrame(hash, ['package-manifest', entry.canonicalPath, entry.bytes]);
892
+ }
893
+ for (const edge of [...inputs.edges].sort(compareEdges)) {
894
+ updateCanonicalFrame(hash, [
895
+ 'dependency-edge',
896
+ edge.parentLocator,
897
+ edge.parentLabel,
898
+ edge.dependencyName,
899
+ edge.childLocator,
900
+ edge.childLabel,
901
+ ]);
902
+ }
903
+ for (const artifact of inputs.artifacts) {
904
+ const hashed = hashRuntimeArtifact(artifact, cachedArtifacts.get(artifactCacheKey(artifact)), options);
905
+ updateCanonicalFrame(hash, [
906
+ 'runtime-artifact',
907
+ artifact.canonicalPath,
908
+ artifact.kind,
909
+ digestBytes(hashed.digest),
910
+ ]);
911
+ nextArtifacts.push({ ...artifact, state: hashed.state, digest: hashed.digest });
912
+ }
913
+ return {
914
+ identity: {
915
+ manifestPath: inputs.manifestPath,
916
+ lockfilePath: inputs.lockfilePath,
917
+ canonicalization: DEPENDENCY_RUNTIME_CANONICALIZATION,
918
+ packageCount: inputs.packages.length,
919
+ artifactCount: inputs.artifacts.length,
920
+ digest: `sha256:${hash.digest('hex')}`,
921
+ },
922
+ entries: nextArtifacts,
923
+ };
924
+ }
925
+ function isStatState(value) {
926
+ if (typeof value !== 'object' || value === null)
927
+ return false;
928
+ const record = value;
929
+ return ['dev', 'ino', 'mode', 'nlink', 'size', 'mtimeNs', 'ctimeNs'].every((key) => typeof record[key] === 'string');
930
+ }
931
+ function isReadableFileState(value) {
932
+ if (typeof value !== 'object' || value === null)
933
+ return false;
934
+ const record = value;
935
+ return (isStatState(record.link) &&
936
+ isStatState(record.target) &&
937
+ (record.symlinkTarget === undefined || typeof record.symlinkTarget === 'string'));
938
+ }
939
+ function isDependencyPathGuardResult(value) {
940
+ if (value === null)
941
+ return true;
942
+ if (typeof value !== 'object')
943
+ return false;
944
+ const record = value;
945
+ return (['directory', 'file', 'symlink', 'other'].includes(String(record.type)) &&
946
+ isStatState(record.state) &&
947
+ (record.type === 'symlink'
948
+ ? typeof record.symlinkTarget === 'string'
949
+ : record.symlinkTarget === undefined));
950
+ }
951
+ function isRuntimeVariant(value) {
952
+ if (typeof value !== 'object' || value === null)
953
+ return false;
954
+ const record = value;
955
+ return [
956
+ 'executablePath',
957
+ 'nodeVersion',
958
+ 'platform',
959
+ 'architecture',
960
+ 'endianness',
961
+ 'modulesAbi',
962
+ 'napiAbi',
963
+ 'libc',
964
+ ].every((key) => typeof record[key] === 'string' && record[key].length > 0);
965
+ }
966
+ function isTraversalLimits(value) {
967
+ if (typeof value !== 'object' || value === null)
968
+ return false;
969
+ const record = value;
970
+ return Object.keys(DEFAULT_TRAVERSAL_LIMITS).every((key) => Number.isSafeInteger(record[key]) &&
971
+ Number(record[key]) >= 1 &&
972
+ Number(record[key]) <= DEFAULT_TRAVERSAL_LIMITS[key]);
973
+ }
974
+ function isDependencyRuntimeIdentity(value) {
975
+ if (typeof value !== 'object' || value === null)
976
+ return false;
977
+ const dependency = value;
978
+ return (typeof dependency.manifestPath === 'string' &&
979
+ dependency.manifestPath.length > 0 &&
980
+ (dependency.lockfilePath === null ||
981
+ (typeof dependency.lockfilePath === 'string' && dependency.lockfilePath.length > 0)) &&
982
+ dependency.canonicalization === DEPENDENCY_RUNTIME_CANONICALIZATION &&
983
+ Number.isSafeInteger(dependency.packageCount) &&
984
+ Number(dependency.packageCount) >= 1 &&
985
+ Number.isSafeInteger(dependency.artifactCount) &&
986
+ Number(dependency.artifactCount) >= 0 &&
987
+ typeof dependency.digest === 'string' &&
988
+ SHA256_PATTERN.test(dependency.digest));
989
+ }
990
+ function isSafeBuildRelativePath(value) {
991
+ if (typeof value !== 'string' || value.length === 0 || path.posix.isAbsolute(value)) {
992
+ return false;
993
+ }
994
+ const normalized = path.posix.normalize(value);
995
+ return normalized === value && normalized !== '..' && !normalized.startsWith('../');
996
+ }
997
+ function isSafeBuildDirectoryGuardPath(value) {
998
+ return value === '' || isSafeBuildRelativePath(value);
999
+ }
1000
+ function isIdentityCachePayload(value, packageRoot, buildRoot, runtimeVariant, traversalLimits) {
1001
+ if (typeof value !== 'object' || value === null)
1002
+ return false;
1003
+ const record = value;
1004
+ if (record.schemaVersion !== IDENTITY_CACHE_SCHEMA_VERSION ||
1005
+ record.packageRoot !== packageRoot ||
1006
+ record.buildRoot !== buildRoot ||
1007
+ typeof record.packageVersion !== 'string' ||
1008
+ record.packageVersion.length === 0 ||
1009
+ (record.buildKind !== 'source' && record.buildKind !== 'distribution') ||
1010
+ record.buildCanonicalization !== BUILD_CANONICALIZATION ||
1011
+ record.dependencyCanonicalization !== DEPENDENCY_RUNTIME_CANONICALIZATION ||
1012
+ !isTraversalLimits(record.traversalLimits) ||
1013
+ !isDeepStrictEqual(record.traversalLimits, traversalLimits) ||
1014
+ !isRuntimeVariant(record.runtimeVariant) ||
1015
+ !isDeepStrictEqual(record.runtimeVariant, runtimeVariant) ||
1016
+ !isStatState(record.buildRootState) ||
1017
+ typeof record.buildDigest !== 'string' ||
1018
+ !SHA256_PATTERN.test(record.buildDigest) ||
1019
+ !isDependencyRuntimeIdentity(record.dependencyIdentity) ||
1020
+ !Array.isArray(record.buildEntries) ||
1021
+ !Array.isArray(record.buildDirectoryGuards) ||
1022
+ !Array.isArray(record.dependencyFileGuards) ||
1023
+ !Array.isArray(record.dependencyDirectoryGuards) ||
1024
+ !Array.isArray(record.dependencyPathGuards) ||
1025
+ !Array.isArray(record.artifactEntries) ||
1026
+ record.buildEntries.length > MAX_CACHE_ENTRIES ||
1027
+ record.buildDirectoryGuards.length > MAX_CACHE_ENTRIES ||
1028
+ record.dependencyFileGuards.length > MAX_CACHE_ENTRIES ||
1029
+ record.dependencyDirectoryGuards.length > MAX_CACHE_ENTRIES ||
1030
+ record.dependencyPathGuards.length > MAX_CACHE_ENTRIES ||
1031
+ record.artifactEntries.length > MAX_CACHE_ENTRIES) {
1032
+ return false;
1033
+ }
1034
+ const buildEntriesValid = record.buildEntries.every((entry) => {
1035
+ if (typeof entry !== 'object' || entry === null)
1036
+ return false;
1037
+ const item = entry;
1038
+ return (isSafeBuildRelativePath(item.relativePath) &&
1039
+ ['directory', 'file'].includes(String(item.kind)) &&
1040
+ isStatState(item.state) &&
1041
+ (item.kind === 'directory'
1042
+ ? item.digest === undefined
1043
+ : typeof item.digest === 'string' && SHA256_PATTERN.test(item.digest)));
1044
+ });
1045
+ const buildDirectoryGuardsValid = record.buildDirectoryGuards.every((entry) => {
1046
+ if (typeof entry !== 'object' || entry === null)
1047
+ return false;
1048
+ const item = entry;
1049
+ return (isSafeBuildDirectoryGuardPath(item.relativePath) &&
1050
+ isStatState(item.state) &&
1051
+ typeof item.entriesDigest === 'string' &&
1052
+ SHA256_PATTERN.test(item.entriesDigest));
1053
+ });
1054
+ const dependencyFileGuardsValid = record.dependencyFileGuards.every((entry) => {
1055
+ if (typeof entry !== 'object' || entry === null)
1056
+ return false;
1057
+ const item = entry;
1058
+ return (typeof item.absolutePath === 'string' &&
1059
+ path.isAbsolute(item.absolutePath) &&
1060
+ isReadableFileState(item.state));
1061
+ });
1062
+ const dependencyDirectoryGuardsValid = record.dependencyDirectoryGuards.every((entry) => {
1063
+ if (typeof entry !== 'object' || entry === null)
1064
+ return false;
1065
+ const item = entry;
1066
+ return (typeof item.absolutePath === 'string' &&
1067
+ path.isAbsolute(item.absolutePath) &&
1068
+ isStatState(item.state) &&
1069
+ typeof item.entriesDigest === 'string' &&
1070
+ SHA256_PATTERN.test(item.entriesDigest));
1071
+ });
1072
+ const dependencyPathGuardsValid = record.dependencyPathGuards.every((entry) => {
1073
+ if (typeof entry !== 'object' || entry === null)
1074
+ return false;
1075
+ const item = entry;
1076
+ return (typeof item.absolutePath === 'string' &&
1077
+ path.isAbsolute(item.absolutePath) &&
1078
+ isDependencyPathGuardResult(item.result));
1079
+ });
1080
+ const artifactEntriesValid = record.artifactEntries.every((entry) => {
1081
+ if (typeof entry !== 'object' || entry === null)
1082
+ return false;
1083
+ const item = entry;
1084
+ return (typeof item.absolutePath === 'string' &&
1085
+ path.isAbsolute(item.absolutePath) &&
1086
+ typeof item.canonicalPath === 'string' &&
1087
+ (item.kind === 'file' || item.kind === 'symlink') &&
1088
+ isReadableFileState(item.state) &&
1089
+ typeof item.digest === 'string' &&
1090
+ SHA256_PATTERN.test(item.digest));
1091
+ });
1092
+ const hasBuildRootGuard = record.buildDirectoryGuards.some((entry) => typeof entry === 'object' &&
1093
+ entry !== null &&
1094
+ entry.relativePath === '');
1095
+ return (buildEntriesValid &&
1096
+ buildDirectoryGuardsValid &&
1097
+ hasBuildRootGuard &&
1098
+ dependencyFileGuardsValid &&
1099
+ dependencyDirectoryGuardsValid &&
1100
+ dependencyPathGuardsValid &&
1101
+ artifactEntriesValid);
1102
+ }
1103
+ function currentUid() {
1104
+ return typeof process.getuid === 'function' ? process.getuid() : null;
1105
+ }
1106
+ function isOwnedPrivateDirectory(candidate) {
1107
+ try {
1108
+ const stat = lstatSync(candidate);
1109
+ if (!stat.isDirectory() || stat.isSymbolicLink())
1110
+ return false;
1111
+ const uid = currentUid();
1112
+ return uid !== null && stat.uid === uid && (stat.mode & 0o077) === 0;
1113
+ }
1114
+ catch {
1115
+ return false;
1116
+ }
1117
+ }
1118
+ function isSafeTemporaryParent(candidate) {
1119
+ try {
1120
+ const stat = lstatSync(candidate);
1121
+ if (!stat.isDirectory() || stat.isSymbolicLink())
1122
+ return false;
1123
+ if (currentUid() === null)
1124
+ return false;
1125
+ // A shared temp root is safe only with the sticky bit: another UID then
1126
+ // cannot replace the private child after our atomic mkdir + owner check.
1127
+ const writableByOthers = (stat.mode & 0o022) !== 0;
1128
+ return !writableByOthers || (stat.mode & 0o1000) !== 0;
1129
+ }
1130
+ catch {
1131
+ return false;
1132
+ }
1133
+ }
1134
+ function ensurePrivateChild(parent, childName) {
1135
+ let resolvedParent;
1136
+ try {
1137
+ resolvedParent = realpathSync.native(parent);
1138
+ }
1139
+ catch {
1140
+ return null;
1141
+ }
1142
+ if (!isSafeTemporaryParent(resolvedParent))
1143
+ return null;
1144
+ const candidate = path.join(resolvedParent, childName);
1145
+ try {
1146
+ // Non-recursive mkdir is intentional: it cannot follow an attacker-made
1147
+ // intermediate symlink. EEXIST is accepted only after the ownership/mode
1148
+ // validation below.
1149
+ mkdirSync(candidate, { mode: 0o700 });
1150
+ }
1151
+ catch (error) {
1152
+ if (error.code !== 'EEXIST')
1153
+ return null;
1154
+ }
1155
+ return isOwnedPrivateDirectory(candidate) ? candidate : null;
1156
+ }
1157
+ function defaultCacheDirectory() {
1158
+ // On platforms without POSIX ownership APIs we cannot prove that a default
1159
+ // cache file is private to this process's user. Persistence is therefore
1160
+ // disabled unless the operator supplied an explicit trusted override.
1161
+ const uid = currentUid();
1162
+ if (uid === null)
1163
+ return null;
1164
+ // XDG_RUNTIME_DIR is already per-user and normally 0700. Use it only when
1165
+ // that contract is true; a spoofed/insecure value falls back to the sticky
1166
+ // OS temp root rather than becoming a cache-poisoning surface.
1167
+ const runtimeDir = process.env.XDG_RUNTIME_DIR;
1168
+ if (runtimeDir) {
1169
+ try {
1170
+ const resolvedRuntime = realpathSync.native(runtimeDir);
1171
+ if (isOwnedPrivateDirectory(resolvedRuntime)) {
1172
+ const runtimeCache = ensurePrivateChild(resolvedRuntime, 'gitnexus-analyzer-identity');
1173
+ if (runtimeCache)
1174
+ return runtimeCache;
1175
+ }
1176
+ }
1177
+ catch {
1178
+ /* fall through to the OS temp directory */
1179
+ }
1180
+ }
1181
+ let tempRoot;
1182
+ try {
1183
+ tempRoot = realpathSync.native(os.tmpdir());
1184
+ }
1185
+ catch {
1186
+ return null;
1187
+ }
1188
+ return ensurePrivateChild(tempRoot, `gitnexus-analyzer-identity-${uid}`);
1189
+ }
1190
+ const TRUSTED_CACHE_DIRECTORY_ENV = 'GITNEXUS_ANALYZER_IDENTITY_CACHE_DIR';
1191
+ function pathsEqual(left, right) {
1192
+ return process.platform === 'win32' ? left.toLowerCase() === right.toLowerCase() : left === right;
1193
+ }
1194
+ function trustedEnvironmentCacheDirectory() {
1195
+ const configured = process.env[TRUSTED_CACHE_DIRECTORY_ENV];
1196
+ if (configured === undefined)
1197
+ return null;
1198
+ if (configured.length === 0 || configured.includes('\0') || !path.isAbsolute(configured)) {
1199
+ throw new Error(`${TRUSTED_CACHE_DIRECTORY_ENV} must name an absolute protected directory`);
1200
+ }
1201
+ const normalized = path.normalize(configured);
1202
+ let resolved;
1203
+ try {
1204
+ const link = lstatSync(normalized);
1205
+ if (!link.isDirectory() || link.isSymbolicLink()) {
1206
+ throw new Error('not a real directory');
1207
+ }
1208
+ resolved = realpathSync.native(normalized);
1209
+ }
1210
+ catch {
1211
+ throw new Error(`${TRUSTED_CACHE_DIRECTORY_ENV} must name a pre-existing protected non-symlink directory`);
1212
+ }
1213
+ // Reject junctions/symlinked ancestors as well as a symlink final component.
1214
+ // The environment variable is an explicit trust assertion, but its spelling
1215
+ // must still bind exactly to the directory the cache will use.
1216
+ if (!pathsEqual(path.resolve(normalized), resolved)) {
1217
+ throw new Error(`${TRUSTED_CACHE_DIRECTORY_ENV} must not traverse symbolic links or junctions`);
1218
+ }
1219
+ return resolved;
1220
+ }
1221
+ function cacheDirectory(options, packageRoot, buildRoot) {
1222
+ // An explicit location is a trusted operator/test override and therefore
1223
+ // remains authoritative, including when the secure default is unavailable.
1224
+ if (options.cacheDirectory) {
1225
+ const explicit = path.resolve(options.cacheDirectory);
1226
+ try {
1227
+ // Create it before any build/dependency directory guards are captured.
1228
+ // A cache nested immediately under a package root then changes that
1229
+ // parent's directory state once, not after we persist the first entry.
1230
+ mkdirSync(explicit, { recursive: true, mode: 0o700 });
1231
+ }
1232
+ catch {
1233
+ /* persistence remains optional and will fail closed */
1234
+ }
1235
+ return explicit;
1236
+ }
1237
+ const configured = trustedEnvironmentCacheDirectory();
1238
+ if (configured) {
1239
+ if (isInside(packageRoot, configured) || isInside(buildRoot, configured)) {
1240
+ throw new Error(`${TRUSTED_CACHE_DIRECTORY_ENV} must be outside the analyzer package and build roots`);
1241
+ }
1242
+ return configured;
1243
+ }
1244
+ return defaultCacheDirectory();
1245
+ }
1246
+ function hasTrustedCacheOverride(options) {
1247
+ return (options.cacheDirectory !== undefined || process.env[TRUSTED_CACHE_DIRECTORY_ENV] !== undefined);
1248
+ }
1249
+ function identityCacheKey(packageRoot, buildRoot, runtimeVariant, traversalLimits) {
1250
+ return hashCanonicalFrames([
1251
+ [
1252
+ 'analyzer-identity-cache-key-v3',
1253
+ String(IDENTITY_CACHE_SCHEMA_VERSION),
1254
+ packageRoot,
1255
+ buildRoot,
1256
+ runtimeVariant.executablePath,
1257
+ runtimeVariant.nodeVersion,
1258
+ runtimeVariant.platform,
1259
+ runtimeVariant.architecture,
1260
+ runtimeVariant.endianness,
1261
+ runtimeVariant.modulesAbi,
1262
+ runtimeVariant.napiAbi,
1263
+ runtimeVariant.libc,
1264
+ JSON.stringify(traversalLimits),
1265
+ ],
1266
+ ]).slice('sha256:'.length);
1267
+ }
1268
+ const MAX_PROCESS_CACHE_ENTRIES = 12;
1269
+ const processIdentityCache = new Map();
1270
+ /** @internal Clear process-local reuse between simulated-process unit tests. */
1271
+ export function _clearAnalyzerIdentityProcessCacheForTests() {
1272
+ processIdentityCache.clear();
1273
+ }
1274
+ function cachePathFor(packageRoot, buildRoot, runtimeVariant, traversalLimits, options) {
1275
+ const directory = cacheDirectory(options, packageRoot, buildRoot);
1276
+ if (!directory)
1277
+ return null;
1278
+ const key = identityCacheKey(packageRoot, buildRoot, runtimeVariant, traversalLimits);
1279
+ return path.join(directory, `${key}.json`);
1280
+ }
1281
+ function readCacheFile(target) {
1282
+ let descriptor = null;
1283
+ try {
1284
+ const noFollow = 'O_NOFOLLOW' in fsConstants ? fsConstants.O_NOFOLLOW : 0;
1285
+ descriptor = openSync(target, fsConstants.O_RDONLY | noFollow);
1286
+ const stat = fstatSync(descriptor);
1287
+ const uid = currentUid();
1288
+ if (!stat.isFile() ||
1289
+ stat.isSymbolicLink() ||
1290
+ stat.size > MAX_CACHE_FILE_BYTES ||
1291
+ (uid !== null && (stat.uid !== uid || (stat.mode & 0o077) !== 0))) {
1292
+ return null;
1293
+ }
1294
+ return readFileSync(descriptor, 'utf8');
1295
+ }
1296
+ catch {
1297
+ return null;
1298
+ }
1299
+ finally {
1300
+ if (descriptor !== null)
1301
+ closeSync(descriptor);
1302
+ }
1303
+ }
1304
+ function loadIdentityCache(packageRoot, buildRoot, runtimeVariant, traversalLimits, options) {
1305
+ // Invalid explicit cache configuration is an operator error, not an optional
1306
+ // cache miss. Resolve it outside the best-effort envelope read below.
1307
+ const target = cachePathFor(packageRoot, buildRoot, runtimeVariant, traversalLimits, options);
1308
+ try {
1309
+ // Validate any operator-selected persistence location on every call, even
1310
+ // when the payload itself is reusable from this process's guarded LRU.
1311
+ const key = identityCacheKey(packageRoot, buildRoot, runtimeVariant, traversalLimits);
1312
+ const local = processIdentityCache.get(key);
1313
+ if (local)
1314
+ return local;
1315
+ if (!target)
1316
+ return null;
1317
+ const raw = readCacheFile(target);
1318
+ if (raw === null)
1319
+ return null;
1320
+ const parsed = JSON.parse(raw);
1321
+ if (typeof parsed !== 'object' ||
1322
+ parsed === null ||
1323
+ typeof parsed.checksum !== 'string' ||
1324
+ !isIdentityCachePayload(parsed.payload, packageRoot, buildRoot, runtimeVariant, traversalLimits) ||
1325
+ parsed.checksum !== sha256(JSON.stringify(parsed.payload))) {
1326
+ return null;
1327
+ }
1328
+ return parsed.payload;
1329
+ }
1330
+ catch {
1331
+ return null;
1332
+ }
1333
+ }
1334
+ const CACHE_GUARD_PROBE_SCRIPT = String.raw `
1335
+ const fs = require('node:fs/promises');
1336
+ const crypto = require('node:crypto');
1337
+ const state = (value) => ({
1338
+ dev: String(value.dev), ino: String(value.ino), mode: String(value.mode),
1339
+ nlink: String(value.nlink), size: String(value.size),
1340
+ mtimeNs: String(value.mtimeNs), ctimeNs: String(value.ctimeNs),
1341
+ });
1342
+ const frame = (hash, fields) => {
1343
+ const fieldCount = Buffer.allocUnsafe(4);
1344
+ fieldCount.writeUInt32BE(fields.length);
1345
+ hash.update(fieldCount);
1346
+ for (const field of fields) {
1347
+ const bytes = Buffer.from(field);
1348
+ const length = Buffer.allocUnsafe(8);
1349
+ length.writeBigUInt64BE(BigInt(bytes.length));
1350
+ hash.update(length);
1351
+ hash.update(bytes);
1352
+ }
1353
+ };
1354
+ const inventoryDigest = (entries) => {
1355
+ const normalized = entries.map((entry) => ({
1356
+ name: entry.name,
1357
+ kind: entry.isDirectory() ? 'directory'
1358
+ : entry.isFile() ? 'file'
1359
+ : entry.isSymbolicLink() ? 'symlink' : 'other',
1360
+ })).sort((a, b) => Buffer.compare(Buffer.from(a.name), Buffer.from(b.name)));
1361
+ const hash = crypto.createHash('sha256');
1362
+ frame(hash, ['directory-entries-v1']);
1363
+ for (const entry of normalized) frame(hash, [entry.name, entry.kind]);
1364
+ return 'sha256:' + hash.digest('hex');
1365
+ };
1366
+ const probe = async (request) => {
1367
+ try {
1368
+ const link = await fs.lstat(request.absolutePath, { bigint: true });
1369
+ const type = link.isDirectory() ? 'directory'
1370
+ : link.isFile() ? 'file'
1371
+ : link.isSymbolicLink() ? 'symlink' : 'other';
1372
+ if (request.mode === 'link') return {
1373
+ type,
1374
+ state: state(link),
1375
+ ...(type === 'symlink' ? { symlinkTarget: await fs.readlink(request.absolutePath) } : {}),
1376
+ };
1377
+ if (request.mode === 'directory-inventory') {
1378
+ if (!link.isDirectory() || link.isSymbolicLink()) return null;
1379
+ const entriesDigest = inventoryDigest(
1380
+ await fs.readdir(request.absolutePath, { withFileTypes: true }),
1381
+ );
1382
+ const after = await fs.lstat(request.absolutePath, { bigint: true });
1383
+ if (JSON.stringify(state(link)) !== JSON.stringify(state(after))) return null;
1384
+ return { type: 'directory-inventory', state: state(after), entriesDigest };
1385
+ }
1386
+ const target = await fs.stat(request.absolutePath, { bigint: true });
1387
+ if (!target.isFile()) return null;
1388
+ const result = { type: 'readable-file', state: { link: state(link), target: state(target) } };
1389
+ if (link.isSymbolicLink()) result.state.symlinkTarget = await fs.readlink(request.absolutePath);
1390
+ return result;
1391
+ } catch { return null; }
1392
+ };
1393
+ let input = '';
1394
+ process.stdin.setEncoding('utf8');
1395
+ process.stdin.on('data', (chunk) => { input += chunk; });
1396
+ process.stdin.on('end', async () => {
1397
+ try {
1398
+ const requests = JSON.parse(input);
1399
+ const results = [];
1400
+ for (let offset = 0; offset < requests.length; offset += 512) {
1401
+ results.push(...await Promise.all(requests.slice(offset, offset + 512).map(probe)));
1402
+ }
1403
+ process.stdout.write(JSON.stringify(results));
1404
+ } catch { process.exitCode = 1; }
1405
+ });
1406
+ `;
1407
+ function snapshotCacheGuardDirect(request) {
1408
+ try {
1409
+ if (request.mode === 'readable-file') {
1410
+ return { type: 'readable-file', state: snapshotReadableFile(request.absolutePath) };
1411
+ }
1412
+ if (request.mode === 'directory-inventory') {
1413
+ const inventory = snapshotDirectoryInventory(request.absolutePath);
1414
+ return { type: 'directory-inventory', ...inventory };
1415
+ }
1416
+ const stat = lstatSync(request.absolutePath, { bigint: true });
1417
+ const type = stat.isDirectory()
1418
+ ? 'directory'
1419
+ : stat.isFile()
1420
+ ? 'file'
1421
+ : stat.isSymbolicLink()
1422
+ ? 'symlink'
1423
+ : 'other';
1424
+ return {
1425
+ type,
1426
+ state: statState(stat),
1427
+ ...(type === 'symlink' ? { symlinkTarget: readlinkSync(request.absolutePath) } : {}),
1428
+ };
1429
+ }
1430
+ catch {
1431
+ return null;
1432
+ }
1433
+ }
1434
+ function snapshotCacheGuards(requests) {
1435
+ if (requests.length < 128)
1436
+ return requests.map(snapshotCacheGuardDirect);
1437
+ try {
1438
+ const probe = spawnSync(process.execPath, ['--input-type=commonjs', '-e', CACHE_GUARD_PROBE_SCRIPT], {
1439
+ input: JSON.stringify(requests),
1440
+ encoding: 'utf8',
1441
+ maxBuffer: MAX_CACHE_FILE_BYTES,
1442
+ timeout: 30_000,
1443
+ windowsHide: true,
1444
+ });
1445
+ if (probe.status === 0 && !probe.error) {
1446
+ const parsed = JSON.parse(probe.stdout);
1447
+ if (Array.isArray(parsed) && parsed.length === requests.length) {
1448
+ return parsed;
1449
+ }
1450
+ }
1451
+ }
1452
+ catch {
1453
+ /* fall through to the slower in-process validator */
1454
+ }
1455
+ return requests.map(snapshotCacheGuardDirect);
1456
+ }
1457
+ function validateIdentityCache(cache, options) {
1458
+ const expected = new Map();
1459
+ const add = (request, result) => {
1460
+ const key = JSON.stringify([request.mode, request.absolutePath]);
1461
+ const prior = expected.get(key);
1462
+ if (expected.has(key) && !isDeepStrictEqual(prior, result)) {
1463
+ options.onCacheValidationFailure?.({ mode: request.mode, path: request.absolutePath });
1464
+ return false;
1465
+ }
1466
+ expected.set(key, result);
1467
+ return true;
1468
+ };
1469
+ if (!add({ absolutePath: cache.buildRoot, mode: 'link' }, { type: 'directory', state: cache.buildRootState })) {
1470
+ return false;
1471
+ }
1472
+ for (const entry of cache.buildEntries) {
1473
+ const absolutePath = path.join(cache.buildRoot, ...entry.relativePath.split('/'));
1474
+ if (!isInside(cache.buildRoot, absolutePath) ||
1475
+ !add({ absolutePath, mode: 'link' }, { type: entry.kind, state: entry.state })) {
1476
+ return false;
1477
+ }
1478
+ }
1479
+ for (const guard of cache.buildDirectoryGuards) {
1480
+ const absolutePath = guard.relativePath
1481
+ ? path.join(cache.buildRoot, ...guard.relativePath.split('/'))
1482
+ : cache.buildRoot;
1483
+ if (!isInside(cache.buildRoot, absolutePath) ||
1484
+ !add({ absolutePath, mode: 'directory-inventory' }, {
1485
+ type: 'directory-inventory',
1486
+ state: guard.state,
1487
+ entriesDigest: guard.entriesDigest,
1488
+ })) {
1489
+ return false;
1490
+ }
1491
+ }
1492
+ for (const guard of cache.dependencyDirectoryGuards) {
1493
+ if (!add({ absolutePath: guard.absolutePath, mode: 'directory-inventory' }, {
1494
+ type: 'directory-inventory',
1495
+ state: guard.state,
1496
+ entriesDigest: guard.entriesDigest,
1497
+ })) {
1498
+ return false;
1499
+ }
1500
+ }
1501
+ for (const guard of cache.dependencyPathGuards) {
1502
+ if (!add({ absolutePath: guard.absolutePath, mode: 'link' }, guard.result)) {
1503
+ return false;
1504
+ }
1505
+ }
1506
+ for (const guard of cache.dependencyFileGuards) {
1507
+ if (!add({ absolutePath: guard.absolutePath, mode: 'readable-file' }, { type: 'readable-file', state: guard.state })) {
1508
+ return false;
1509
+ }
1510
+ }
1511
+ for (const artifact of cache.artifactEntries) {
1512
+ if (!add({ absolutePath: artifact.absolutePath, mode: 'readable-file' }, { type: 'readable-file', state: artifact.state })) {
1513
+ return false;
1514
+ }
1515
+ }
1516
+ const entries = [...expected.entries()];
1517
+ const requests = entries.map(([key]) => {
1518
+ const [mode, absolutePath] = JSON.parse(key);
1519
+ return { mode, absolutePath };
1520
+ });
1521
+ options.onCacheValidationPass?.({ guardCount: requests.length });
1522
+ const actual = snapshotCacheGuards(requests);
1523
+ const mismatch = actual.findIndex((result, index) => !isDeepStrictEqual(result, entries[index][1]));
1524
+ if (mismatch !== -1) {
1525
+ const [mode, absolutePath] = JSON.parse(entries[mismatch][0]);
1526
+ options.onCacheValidationFailure?.({ mode, path: absolutePath });
1527
+ return false;
1528
+ }
1529
+ return true;
1530
+ }
1531
+ function cachedBuildDigestForPath(cache, absolutePath) {
1532
+ if (!isInside(cache.buildRoot, absolutePath))
1533
+ return null;
1534
+ const relativePath = path.relative(cache.buildRoot, absolutePath).split(path.sep).join('/');
1535
+ const entry = cache.buildEntries.find((candidate) => candidate.kind === 'file' && candidate.relativePath === relativePath);
1536
+ return entry?.digest ?? null;
1537
+ }
1538
+ function dependencyFileGuards(inputs) {
1539
+ const guards = new Map();
1540
+ if (inputs.lockfilePath && inputs.lockfileState) {
1541
+ guards.set(inputs.lockfilePath, inputs.lockfileState);
1542
+ }
1543
+ for (const runtimePackage of inputs.packages) {
1544
+ guards.set(runtimePackage.manifestPath, runtimePackage.manifestState);
1545
+ }
1546
+ for (const manifest of inputs.vendoredManifests) {
1547
+ guards.set(manifest.absolutePath, manifest.state);
1548
+ }
1549
+ return [...guards.entries()]
1550
+ .map(([absolutePath, state]) => ({ absolutePath, state }))
1551
+ .sort((a, b) => compareBytes(a.absolutePath, b.absolutePath));
1552
+ }
1553
+ function dependencyDirectoryGuards(inputs) {
1554
+ return [...inputs.directoryGuards.entries()]
1555
+ .map(([absolutePath, guard]) => ({ absolutePath, ...guard }))
1556
+ .sort((a, b) => compareBytes(a.absolutePath, b.absolutePath));
1557
+ }
1558
+ function dependencyPathGuards(inputs) {
1559
+ return [...inputs.pathGuards.entries()]
1560
+ .map(([absolutePath, result]) => ({ absolutePath, result }))
1561
+ .sort((a, b) => compareBytes(a.absolutePath, b.absolutePath));
1562
+ }
1563
+ function persistIdentityCache(packageRoot, buildRoot, payload, previous, options) {
1564
+ if (previous && isDeepStrictEqual(previous, payload))
1565
+ return;
1566
+ const target = cachePathFor(packageRoot, buildRoot, payload.runtimeVariant, payload.traversalLimits, options);
1567
+ if (!target)
1568
+ return;
1569
+ const temporary = `${target}.${process.pid}.${randomBytes(6).toString('hex')}.tmp`;
1570
+ try {
1571
+ if (options.cacheDirectory) {
1572
+ mkdirSync(path.dirname(target), { recursive: true, mode: 0o700 });
1573
+ }
1574
+ else if (!hasTrustedCacheOverride(options) &&
1575
+ !isOwnedPrivateDirectory(path.dirname(target))) {
1576
+ return;
1577
+ }
1578
+ const envelope = {
1579
+ payload,
1580
+ checksum: sha256(JSON.stringify(payload)),
1581
+ };
1582
+ writeFileSync(temporary, `${JSON.stringify(envelope)}\n`, {
1583
+ encoding: 'utf8',
1584
+ flag: 'wx',
1585
+ mode: 0o600,
1586
+ });
1587
+ renameSync(temporary, target);
1588
+ }
1589
+ catch {
1590
+ // The cache is optional (notably for read-only package/container setups).
1591
+ // A failed write only loses the optimization; identity remains fail-closed.
1592
+ try {
1593
+ unlinkSync(temporary);
1594
+ }
1595
+ catch {
1596
+ /* already renamed or never created */
1597
+ }
1598
+ }
1599
+ }
1600
+ function rememberIdentityCache(payload) {
1601
+ const key = identityCacheKey(payload.packageRoot, payload.buildRoot, payload.runtimeVariant, payload.traversalLimits);
1602
+ processIdentityCache.delete(key);
1603
+ processIdentityCache.set(key, payload);
1604
+ while (processIdentityCache.size > MAX_PROCESS_CACHE_ENTRIES) {
1605
+ const oldest = processIdentityCache.keys().next().value;
1606
+ if (oldest === undefined)
1607
+ break;
1608
+ processIdentityCache.delete(oldest);
1609
+ }
1610
+ }
1611
+ function resolveInvokedArtifact(buildRoot, analyzerModulePath) {
1612
+ const argvEntry = process.argv[1];
1613
+ if (!argvEntry)
1614
+ return analyzerModulePath;
1615
+ try {
1616
+ const resolved = resolveExistingPath(argvEntry);
1617
+ return isInside(buildRoot, resolved) && lstatSync(resolved).isFile()
1618
+ ? resolved
1619
+ : analyzerModulePath;
1620
+ }
1621
+ catch {
1622
+ return analyzerModulePath;
1623
+ }
1624
+ }
1625
+ function isNonEmptyString(value) {
1626
+ return typeof value === 'string' && value.length > 0;
1627
+ }
1628
+ function runnerRuntimeIdentity(runtimeVariant) {
1629
+ return {
1630
+ executablePath: runtimeVariant.executablePath,
1631
+ version: runtimeVariant.nodeVersion,
1632
+ platform: runtimeVariant.platform,
1633
+ architecture: runtimeVariant.architecture,
1634
+ modulesAbi: runtimeVariant.modulesAbi,
1635
+ libc: runtimeVariant.libc,
1636
+ };
1637
+ }
1638
+ function isAnalyzerRunnerIdentity(value) {
1639
+ if (typeof value !== 'object' || value === null)
1640
+ return false;
1641
+ const identity = value;
1642
+ const runtime = identity.runtime;
1643
+ const invoked = identity.invokedArtifact;
1644
+ const build = identity.build;
1645
+ const dependency = identity.dependencyRuntime;
1646
+ return (identity.schemaVersion === ANALYZER_RUNNER_IDENTITY_SCHEMA_VERSION &&
1647
+ !!runtime &&
1648
+ isNonEmptyString(runtime.executablePath) &&
1649
+ isNonEmptyString(runtime.version) &&
1650
+ isNonEmptyString(runtime.platform) &&
1651
+ isNonEmptyString(runtime.architecture) &&
1652
+ isNonEmptyString(runtime.modulesAbi) &&
1653
+ isNonEmptyString(runtime.libc) &&
1654
+ isNonEmptyString(identity.cliVersion) &&
1655
+ !!invoked &&
1656
+ isNonEmptyString(invoked.path) &&
1657
+ typeof invoked.digest === 'string' &&
1658
+ SHA256_PATTERN.test(invoked.digest) &&
1659
+ !!build &&
1660
+ (build.kind === 'source' || build.kind === 'distribution') &&
1661
+ isNonEmptyString(build.rootPath) &&
1662
+ build.canonicalization === BUILD_CANONICALIZATION &&
1663
+ typeof build.digest === 'string' &&
1664
+ SHA256_PATTERN.test(build.digest) &&
1665
+ !!dependency &&
1666
+ isNonEmptyString(dependency.manifestPath) &&
1667
+ (dependency.lockfilePath === null || isNonEmptyString(dependency.lockfilePath)) &&
1668
+ dependency.canonicalization === DEPENDENCY_RUNTIME_CANONICALIZATION &&
1669
+ Number.isSafeInteger(dependency.packageCount) &&
1670
+ Number(dependency.packageCount) >= 1 &&
1671
+ Number.isSafeInteger(dependency.artifactCount) &&
1672
+ Number(dependency.artifactCount) >= 0 &&
1673
+ typeof dependency.digest === 'string' &&
1674
+ SHA256_PATTERN.test(dependency.digest));
1675
+ }
1676
+ /**
1677
+ * Normalize a raw diagnostic receipt for freshness comparison. The entrypoint
1678
+ * is the only excluded field; malformed/legacy receipts never compare equal.
1679
+ */
1680
+ export function normalizeAnalyzerRunnerIdentityForComparison(identity) {
1681
+ if (!isAnalyzerRunnerIdentity(identity))
1682
+ return null;
1683
+ const { invokedArtifact: _diagnosticEntrypoint, ...semantic } = identity;
1684
+ return semantic;
1685
+ }
1686
+ /** Resolve the identity of the analyzer build and runtime executing now. */
1687
+ export function resolveAnalyzerRunnerIdentity(analyzerModuleUrl, options = {}) {
1688
+ const analyzerModulePath = resolveExistingPath(fileURLToPath(analyzerModuleUrl));
1689
+ const { packageRoot, buildRoot, kind } = resolveBuildRoot(analyzerModulePath);
1690
+ const runtimeVariant = resolveRuntimeVariant();
1691
+ const traversalLimits = resolveTraversalLimits(options);
1692
+ const previousCache = loadIdentityCache(packageRoot, buildRoot, runtimeVariant, traversalLimits, options);
1693
+ const invokedArtifactPath = resolveInvokedArtifact(buildRoot, analyzerModulePath);
1694
+ if (previousCache?.buildKind === kind) {
1695
+ const invokedDigest = cachedBuildDigestForPath(previousCache, invokedArtifactPath);
1696
+ if (invokedDigest) {
1697
+ const cachedIdentity = {
1698
+ schemaVersion: ANALYZER_RUNNER_IDENTITY_SCHEMA_VERSION,
1699
+ runtime: runnerRuntimeIdentity(runtimeVariant),
1700
+ cliVersion: previousCache.packageVersion,
1701
+ invokedArtifact: { path: invokedArtifactPath, digest: invokedDigest },
1702
+ build: {
1703
+ kind,
1704
+ rootPath: buildRoot,
1705
+ canonicalization: BUILD_CANONICALIZATION,
1706
+ digest: previousCache.buildDigest,
1707
+ },
1708
+ dependencyRuntime: previousCache.dependencyIdentity,
1709
+ };
1710
+ // One batched pass is deliberately the last filesystem observation on
1711
+ // a warm hit. Repeating the complete inventory both doubles status
1712
+ // latency and still cannot close the post-check scheduling window.
1713
+ if (validateIdentityCache(previousCache, options)) {
1714
+ rememberIdentityCache(previousCache);
1715
+ return cachedIdentity;
1716
+ }
1717
+ }
1718
+ }
1719
+ const build = hashBuildTree(buildRoot, previousCache, options, traversalLimits);
1720
+ const dependencyInputs = collectDependencyInputs(packageRoot, options, traversalLimits);
1721
+ const dependencySnapshotBefore = dependencySnapshot(dependencyInputs);
1722
+ const dependency = hashDependencyRuntime(dependencyInputs, previousCache, options, runtimeVariant);
1723
+ const packageVersion = dependencyInputs.packages[0]?.manifest.version;
1724
+ if (typeof packageVersion !== 'string' || packageVersion.trim() === '') {
1725
+ throw new Error(`GitNexus package version is unavailable in ${packageRoot}`);
1726
+ }
1727
+ const buildSnapshotAfter = buildSnapshot(collectBuildEntries(buildRoot, options, traversalLimits));
1728
+ if (!isDeepStrictEqual(build.snapshot, buildSnapshotAfter)) {
1729
+ throw new Error(`Analyzer build changed while its identity was being computed: ${buildRoot}`);
1730
+ }
1731
+ const dependencySnapshotAfter = dependencySnapshot(collectDependencyInputs(packageRoot, options, traversalLimits));
1732
+ if (!isDeepStrictEqual(dependencySnapshotBefore, dependencySnapshotAfter)) {
1733
+ throw new Error(`Analyzer dependency runtime changed while its identity was being computed: ${packageRoot}`);
1734
+ }
1735
+ const nextCache = {
1736
+ schemaVersion: IDENTITY_CACHE_SCHEMA_VERSION,
1737
+ packageRoot,
1738
+ buildRoot,
1739
+ packageVersion,
1740
+ buildKind: kind,
1741
+ buildCanonicalization: BUILD_CANONICALIZATION,
1742
+ dependencyCanonicalization: DEPENDENCY_RUNTIME_CANONICALIZATION,
1743
+ traversalLimits,
1744
+ runtimeVariant,
1745
+ buildRootState: build.rootState,
1746
+ buildDigest: build.digest,
1747
+ buildEntries: build.entries,
1748
+ buildDirectoryGuards: build.directoryGuards,
1749
+ dependencyIdentity: dependency.identity,
1750
+ dependencyFileGuards: dependencyFileGuards(dependencyInputs),
1751
+ dependencyDirectoryGuards: dependencyDirectoryGuards(dependencyInputs),
1752
+ dependencyPathGuards: dependencyPathGuards(dependencyInputs),
1753
+ artifactEntries: dependency.entries,
1754
+ };
1755
+ const invokedDigest = cachedBuildDigestForPath(nextCache, invokedArtifactPath);
1756
+ if (!invokedDigest) {
1757
+ throw new Error(`Invoked analyzer artifact is absent from the validated build: ${invokedArtifactPath}`);
1758
+ }
1759
+ const identity = {
1760
+ schemaVersion: ANALYZER_RUNNER_IDENTITY_SCHEMA_VERSION,
1761
+ runtime: runnerRuntimeIdentity(runtimeVariant),
1762
+ cliVersion: packageVersion,
1763
+ invokedArtifact: {
1764
+ path: invokedArtifactPath,
1765
+ digest: invokedDigest,
1766
+ },
1767
+ build: {
1768
+ kind,
1769
+ rootPath: buildRoot,
1770
+ canonicalization: BUILD_CANONICALIZATION,
1771
+ digest: build.digest,
1772
+ },
1773
+ dependencyRuntime: dependency.identity,
1774
+ };
1775
+ let validationFailure;
1776
+ const validationOptions = {
1777
+ ...options,
1778
+ onCacheValidationFailure: (failure) => {
1779
+ validationFailure = failure;
1780
+ options.onCacheValidationFailure?.(failure);
1781
+ },
1782
+ };
1783
+ if (!validateIdentityCache(nextCache, validationOptions)) {
1784
+ const mismatch = validationFailure
1785
+ ? ` (failed ${validationFailure.mode} guard: ${validationFailure.path})`
1786
+ : '';
1787
+ throw new Error(`Analyzer build or dependency runtime changed while its identity was being computed: ${packageRoot}${mismatch}`);
1788
+ }
1789
+ rememberIdentityCache(nextCache);
1790
+ persistIdentityCache(packageRoot, buildRoot, nextCache, previousCache, options);
1791
+ return identity;
1792
+ }
1793
+ /**
1794
+ * Semantic freshness comparison. Both receipts must be well-formed schema-v4
1795
+ * values; only the diagnostic entrypoint field is normalized away.
1796
+ */
1797
+ export function analyzerRunnerIdentitiesEqual(indexedIdentity, currentIdentity) {
1798
+ const indexed = normalizeAnalyzerRunnerIdentityForComparison(indexedIdentity);
1799
+ const current = normalizeAnalyzerRunnerIdentityForComparison(currentIdentity);
1800
+ return indexed !== null && current !== null && isDeepStrictEqual(indexed, current);
1801
+ }
1802
+ /**
1803
+ * Capture analyzer identity before invoking a loader that may evaluate the
1804
+ * analyzer module graph. The explicit receipt is then threaded into analysis
1805
+ * and checked again immediately before metadata commit.
1806
+ */
1807
+ export async function captureAnalyzerIdentityBeforeLoad(analyzerModuleUrl, loader, options = {}) {
1808
+ const runnerIdentity = resolveAnalyzerRunnerIdentity(analyzerModuleUrl, options);
1809
+ const loaded = await loader();
1810
+ return { runnerIdentity, loaded };
1811
+ }
1812
+ /** Re-resolve immediately before commit and reject analyzer mutation mid-run. */
1813
+ export function finalizeAnalyzerRunnerIdentity(analyzerModuleUrl, startedWith, options = {}) {
1814
+ const finalIdentity = resolveAnalyzerRunnerIdentity(analyzerModuleUrl, options);
1815
+ if (!analyzerRunnerIdentitiesEqual(startedWith, finalIdentity)) {
1816
+ throw new Error('Analyzer build or dependency runtime changed during analysis; refusing to stamp metadata. ' +
1817
+ 'Retry with a stable GitNexus installation.');
1818
+ }
1819
+ return finalIdentity;
1820
+ }