@cdxgen/cdxgen 13.5.0 → 13.7.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 (380) hide show
  1. package/README.md +17 -10
  2. package/bin/audit.js +7 -4
  3. package/bin/cdxgen.js +260 -37
  4. package/bin/convert.js +146 -22
  5. package/bin/evinse.js +28 -3
  6. package/bin/repl.js +47 -3
  7. package/bin/sign.js +211 -34
  8. package/bin/tracebom.js +18 -6
  9. package/bin/validate.js +24 -3
  10. package/bin/verify.js +113 -5
  11. package/data/behavior-taxonomy.schema.json +1491 -0
  12. package/data/cyclonedx-2.0-bundled.schema.json +18606 -5528
  13. package/data/cyclonedx-2.0-cryptography-defs.schema.json +857 -0
  14. package/data/license-aliases.json +24 -2
  15. package/data/pqc-migration.json +239 -0
  16. package/data/remediations.json +176 -0
  17. package/data/rules/ai-bom.yaml +1 -1
  18. package/data/rules/brotli-decoder.yaml +107 -0
  19. package/data/rules/cbom-security.yaml +4 -3
  20. package/data/rules/package-integrity.yaml +28 -0
  21. package/data/rules/vscode-extensions.yaml +75 -0
  22. package/data/spdx-license-list.json +121 -7
  23. package/data/spdx-licenses.json +69 -0
  24. package/data/spdx.schema.json +845 -2
  25. package/lib/audit/index.js +25 -3
  26. package/lib/cli/bomAssembly.js +172 -4
  27. package/lib/cli/cliOptions.js +121 -17
  28. package/lib/cli/index.js +720 -902
  29. package/lib/cli/jsBom.js +156 -64
  30. package/lib/cli/jvmBom.js +602 -86
  31. package/lib/cli/managedBom.js +161 -19
  32. package/lib/cli/nativeBom.js +52 -5
  33. package/lib/core/boundedFile.js +344 -0
  34. package/lib/core/brotliProbe.js +182 -0
  35. package/lib/core/env.js +34 -0
  36. package/lib/core/fs.js +138 -3
  37. package/lib/core/globPatterns.js +218 -0
  38. package/lib/core/httpClient.js +98 -1
  39. package/lib/core/lockPaths.js +38 -0
  40. package/lib/core/paths.js +24 -1
  41. package/lib/core/processTree.js +100 -8
  42. package/lib/core/projectConfig.js +332 -25
  43. package/lib/core/toolRegistry.js +184 -0
  44. package/lib/ecosystems/cppEvidence.js +263 -106
  45. package/lib/ecosystems/cppIncludeResolver.js +16 -5
  46. package/lib/ecosystems/denoutils.js +2 -2
  47. package/lib/ecosystems/ecosystems.js +25 -10
  48. package/lib/ecosystems/gemHostProbe.js +301 -0
  49. package/lib/ecosystems/golem.js +3 -8
  50. package/lib/ecosystems/gradleutils.js +1909 -229
  51. package/lib/ecosystems/jsEvidence.js +90 -38
  52. package/lib/ecosystems/kosi.js +3 -5
  53. package/lib/ecosystems/npmVersionResolution.js +376 -0
  54. package/lib/ecosystems/parsers-bazel.js +107 -6
  55. package/lib/ecosystems/parsers-js.js +505 -22
  56. package/lib/ecosystems/parsers-misc.js +209 -20
  57. package/lib/ecosystems/parsers-python.js +178 -30
  58. package/lib/ecosystems/parsers-rust.js +117 -15
  59. package/lib/ecosystems/pythonutils.js +128 -2
  60. package/lib/ecosystems/remote/huggingface.js +22 -0
  61. package/lib/ecosystems/rubyutils.js +538 -31
  62. package/lib/ecosystems/rusi.js +2 -4
  63. package/lib/ecosystems/scalacliutils.js +2 -0
  64. package/lib/ecosystems/scalasem.js +798 -152
  65. package/lib/ecosystems/vendoredCode.js +237 -3
  66. package/lib/ecosystems/venvProbe.js +360 -0
  67. package/lib/evinser/evinser.js +536 -71
  68. package/lib/evinser/scalasem.js +5 -1
  69. package/lib/helpers/agenticToolutils.js +525 -94
  70. package/lib/helpers/bomSigner.js +43 -9
  71. package/lib/helpers/jssSigner.js +1276 -0
  72. package/lib/helpers/vsixutils.js +1574 -315
  73. package/lib/inventory/agentBlueprint.js +193 -0
  74. package/lib/inventory/agentToolNames.js +145 -0
  75. package/lib/inventory/aiCollector.js +153 -7
  76. package/lib/inventory/aiInventory.js +44 -8
  77. package/lib/inventory/aiProviders.js +324 -0
  78. package/lib/inventory/atomUtils.js +77 -0
  79. package/lib/inventory/bomUtils.js +8 -1
  80. package/lib/inventory/brotliDecoder.js +142 -0
  81. package/lib/inventory/buildToolRateLimit.js +1 -1
  82. package/lib/inventory/cbomutils.js +56 -7
  83. package/lib/inventory/ciParsers/githubActionsLock.js +821 -0
  84. package/lib/inventory/citations.js +31 -2
  85. package/lib/inventory/communityAiConfigParser.js +978 -80
  86. package/lib/inventory/cyclonedx20.js +152 -0
  87. package/lib/inventory/cyclonedx20Common.js +286 -0
  88. package/lib/inventory/cyclonedx20Derived.js +159 -0
  89. package/lib/inventory/cyclonedx20Parties.js +552 -0
  90. package/lib/inventory/cyclonedx20Read.js +768 -0
  91. package/lib/inventory/cyclonedx20ReadCommon.js +374 -0
  92. package/lib/inventory/cyclonedx20ReadComponents.js +692 -0
  93. package/lib/inventory/cyclonedx20ReadContext.js +380 -0
  94. package/lib/inventory/cyclonedx20ReadEvidence.js +466 -0
  95. package/lib/inventory/cyclonedx20ReadSections.js +599 -0
  96. package/lib/inventory/cyclonedx20Render.js +766 -0
  97. package/lib/inventory/cyclonedx20RenderComponents.js +716 -0
  98. package/lib/inventory/cyclonedx20RenderDefinitions.js +405 -0
  99. package/lib/inventory/cyclonedx20RenderEvidence.js +726 -0
  100. package/lib/inventory/cyclonedx20RenderHardware.js +203 -0
  101. package/lib/inventory/cyclonedx20RenderPatents.js +174 -0
  102. package/lib/inventory/cyclonedx20RenderRefIds.js +314 -0
  103. package/lib/inventory/cyclonedx20RenderSections.js +499 -0
  104. package/lib/inventory/cyclonedx20Tables.js +594 -0
  105. package/lib/inventory/deps.js +141 -13
  106. package/lib/inventory/dosai.js +375 -6
  107. package/lib/inventory/dosaiParsers.js +3 -6
  108. package/lib/inventory/dotnetRuntime.js +367 -0
  109. package/lib/inventory/ecosystemFormulationParsers.js +979 -0
  110. package/lib/inventory/envcontext.js +436 -48
  111. package/lib/inventory/evidenceUtils.js +13 -1
  112. package/lib/inventory/fetchBatch.js +1 -1
  113. package/lib/inventory/formulationParsers.js +124 -14
  114. package/lib/inventory/gtfobins.js +13 -0
  115. package/lib/inventory/hbom.js +45 -6
  116. package/lib/inventory/hbomLoader.js +166 -0
  117. package/lib/inventory/hostRuntimeProbe.js +686 -0
  118. package/lib/inventory/jvmLocalRepos.js +9 -2
  119. package/lib/inventory/jvmToolEnv.js +29 -0
  120. package/lib/inventory/licenseEnhancer.js +12 -0
  121. package/lib/inventory/mavenCentralMirror.js +534 -0
  122. package/lib/inventory/mcpConfigParser.js +5 -0
  123. package/lib/inventory/nodeBuildInfo.js +218 -0
  124. package/lib/inventory/nodeHostProbe.js +202 -0
  125. package/lib/inventory/occurrenceKey.js +90 -0
  126. package/lib/inventory/osPackageResolver.js +182 -0
  127. package/lib/inventory/phpBuildInfo.js +10 -59
  128. package/lib/inventory/phpHostProbe.js +32 -488
  129. package/lib/inventory/pqcMigration.js +426 -0
  130. package/lib/inventory/rubyBuildInfo.js +972 -0
  131. package/lib/inventory/rubyHostProbe.js +218 -0
  132. package/lib/inventory/rustFormulationParser.js +21 -10
  133. package/lib/inventory/scalaCoords.js +4 -3
  134. package/lib/inventory/scalaDedupe.js +440 -0
  135. package/lib/inventory/source.js +114 -8
  136. package/lib/managers/binary.js +337 -3
  137. package/lib/managers/docker.js +79 -0
  138. package/lib/parsers/huggingfaceManifest.js +9 -1
  139. package/lib/parsers/huggingfaceUtils.js +18 -0
  140. package/lib/parsers/kiroPermissions.js +166 -0
  141. package/lib/parsers/largeJson.js +28 -11
  142. package/lib/parsers/vscodeExtensionId.js +183 -0
  143. package/lib/server/server.js +48 -35
  144. package/lib/stages/postgen/postgen.js +41 -1
  145. package/lib/stages/postgen/ruleEngine.js +13 -5
  146. package/lib/stages/postgen/specVersionCompat.js +76 -0
  147. package/lib/stages/pregen/envAudit.js +1 -0
  148. package/lib/stages/pregen/pregen.js +114 -57
  149. package/lib/validator/bomValidator.js +14 -2
  150. package/lib/validator/complianceEngine.js +30 -3
  151. package/lib/validator/complianceRules.js +1416 -4
  152. package/lib/validator/generated/validate-1.6.mjs +1 -1
  153. package/lib/validator/generated/validate-1.7.mjs +1 -1
  154. package/lib/validator/generated/validate-2.0.mjs +57554 -13851
  155. package/lib/validator/index.js +49 -8
  156. package/lib/validator/reporters/json.js +6 -0
  157. package/package.json +11 -10
  158. package/types/bin/repl.d.ts.map +1 -1
  159. package/types/lib/audit/index.d.ts +6 -2
  160. package/types/lib/audit/index.d.ts.map +1 -1
  161. package/types/lib/cli/bomAssembly.d.ts +29 -1
  162. package/types/lib/cli/bomAssembly.d.ts.map +1 -1
  163. package/types/lib/cli/cliOptions.d.ts +6 -0
  164. package/types/lib/cli/cliOptions.d.ts.map +1 -1
  165. package/types/lib/cli/index.d.ts.map +1 -1
  166. package/types/lib/cli/jsBom.d.ts +3 -18
  167. package/types/lib/cli/jsBom.d.ts.map +1 -1
  168. package/types/lib/cli/jvmBom.d.ts +15 -0
  169. package/types/lib/cli/jvmBom.d.ts.map +1 -1
  170. package/types/lib/cli/managedBom.d.ts.map +1 -1
  171. package/types/lib/cli/nativeBom.d.ts.map +1 -1
  172. package/types/lib/core/boundedFile.d.ts +100 -0
  173. package/types/lib/core/boundedFile.d.ts.map +1 -0
  174. package/types/lib/core/brotliProbe.d.ts +37 -0
  175. package/types/lib/core/brotliProbe.d.ts.map +1 -0
  176. package/types/lib/core/env.d.ts +14 -0
  177. package/types/lib/core/env.d.ts.map +1 -1
  178. package/types/lib/core/fs.d.ts +4 -0
  179. package/types/lib/core/fs.d.ts.map +1 -1
  180. package/types/lib/core/globPatterns.d.ts +70 -0
  181. package/types/lib/core/globPatterns.d.ts.map +1 -0
  182. package/types/lib/core/httpClient.d.ts +24 -0
  183. package/types/lib/core/httpClient.d.ts.map +1 -1
  184. package/types/lib/core/lockPaths.d.ts +19 -0
  185. package/types/lib/core/lockPaths.d.ts.map +1 -0
  186. package/types/lib/core/paths.d.ts +9 -0
  187. package/types/lib/core/paths.d.ts.map +1 -1
  188. package/types/lib/core/processTree.d.ts +17 -4
  189. package/types/lib/core/processTree.d.ts.map +1 -1
  190. package/types/lib/core/projectConfig.d.ts +47 -8
  191. package/types/lib/core/projectConfig.d.ts.map +1 -1
  192. package/types/lib/core/toolRegistry.d.ts +36 -0
  193. package/types/lib/core/toolRegistry.d.ts.map +1 -0
  194. package/types/lib/ecosystems/cppEvidence.d.ts +40 -13
  195. package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
  196. package/types/lib/ecosystems/cppIncludeResolver.d.ts +2 -1
  197. package/types/lib/ecosystems/cppIncludeResolver.d.ts.map +1 -1
  198. package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
  199. package/types/lib/ecosystems/gemHostProbe.d.ts +28 -0
  200. package/types/lib/ecosystems/gemHostProbe.d.ts.map +1 -0
  201. package/types/lib/ecosystems/golem.d.ts.map +1 -1
  202. package/types/lib/ecosystems/gradleutils.d.ts +294 -20
  203. package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
  204. package/types/lib/ecosystems/jsEvidence.d.ts +14 -1
  205. package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
  206. package/types/lib/ecosystems/kosi.d.ts.map +1 -1
  207. package/types/lib/ecosystems/npmVersionResolution.d.ts +119 -0
  208. package/types/lib/ecosystems/npmVersionResolution.d.ts.map +1 -0
  209. package/types/lib/ecosystems/parsers-bazel.d.ts.map +1 -1
  210. package/types/lib/ecosystems/parsers-js.d.ts +17 -20
  211. package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
  212. package/types/lib/ecosystems/parsers-misc.d.ts +19 -0
  213. package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
  214. package/types/lib/ecosystems/parsers-python.d.ts +10 -0
  215. package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
  216. package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
  217. package/types/lib/ecosystems/pythonutils.d.ts +19 -1
  218. package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
  219. package/types/lib/ecosystems/remote/huggingface.d.ts.map +1 -1
  220. package/types/lib/ecosystems/rubyutils.d.ts +144 -12
  221. package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
  222. package/types/lib/ecosystems/rusi.d.ts.map +1 -1
  223. package/types/lib/ecosystems/scalacliutils.d.ts.map +1 -1
  224. package/types/lib/ecosystems/scalasem.d.ts +58 -16
  225. package/types/lib/ecosystems/scalasem.d.ts.map +1 -1
  226. package/types/lib/ecosystems/vendoredCode.d.ts +26 -0
  227. package/types/lib/ecosystems/vendoredCode.d.ts.map +1 -1
  228. package/types/lib/ecosystems/venvProbe.d.ts +40 -0
  229. package/types/lib/ecosystems/venvProbe.d.ts.map +1 -0
  230. package/types/lib/evinser/evinser.d.ts +120 -13
  231. package/types/lib/evinser/evinser.d.ts.map +1 -1
  232. package/types/lib/evinser/scalasem.d.ts.map +1 -1
  233. package/types/lib/helpers/agenticToolutils.d.ts +53 -19
  234. package/types/lib/helpers/agenticToolutils.d.ts.map +1 -1
  235. package/types/lib/helpers/bomSigner.d.ts +20 -3
  236. package/types/lib/helpers/bomSigner.d.ts.map +1 -1
  237. package/types/lib/helpers/jssSigner.d.ts +288 -0
  238. package/types/lib/helpers/jssSigner.d.ts.map +1 -0
  239. package/types/lib/helpers/vsixutils.d.ts +303 -19
  240. package/types/lib/helpers/vsixutils.d.ts.map +1 -1
  241. package/types/lib/inventory/agentBlueprint.d.ts +70 -0
  242. package/types/lib/inventory/agentBlueprint.d.ts.map +1 -0
  243. package/types/lib/inventory/agentToolNames.d.ts +51 -0
  244. package/types/lib/inventory/agentToolNames.d.ts.map +1 -0
  245. package/types/lib/inventory/aiCollector.d.ts.map +1 -1
  246. package/types/lib/inventory/aiInventory.d.ts.map +1 -1
  247. package/types/lib/inventory/aiProviders.d.ts +85 -0
  248. package/types/lib/inventory/aiProviders.d.ts.map +1 -0
  249. package/types/lib/inventory/atomUtils.d.ts +19 -0
  250. package/types/lib/inventory/atomUtils.d.ts.map +1 -1
  251. package/types/lib/inventory/bomUtils.d.ts.map +1 -1
  252. package/types/lib/inventory/brotliDecoder.d.ts +58 -0
  253. package/types/lib/inventory/brotliDecoder.d.ts.map +1 -0
  254. package/types/lib/inventory/cbomutils.d.ts +17 -0
  255. package/types/lib/inventory/cbomutils.d.ts.map +1 -1
  256. package/types/lib/inventory/ciParsers/githubActionsLock.d.ts +101 -0
  257. package/types/lib/inventory/ciParsers/githubActionsLock.d.ts.map +1 -0
  258. package/types/lib/inventory/citations.d.ts +5 -0
  259. package/types/lib/inventory/citations.d.ts.map +1 -1
  260. package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
  261. package/types/lib/inventory/cyclonedx20.d.ts +99 -0
  262. package/types/lib/inventory/cyclonedx20.d.ts.map +1 -0
  263. package/types/lib/inventory/cyclonedx20Common.d.ts +102 -0
  264. package/types/lib/inventory/cyclonedx20Common.d.ts.map +1 -0
  265. package/types/lib/inventory/cyclonedx20Derived.d.ts +74 -0
  266. package/types/lib/inventory/cyclonedx20Derived.d.ts.map +1 -0
  267. package/types/lib/inventory/cyclonedx20Parties.d.ts +91 -0
  268. package/types/lib/inventory/cyclonedx20Parties.d.ts.map +1 -0
  269. package/types/lib/inventory/cyclonedx20Read.d.ts +51 -0
  270. package/types/lib/inventory/cyclonedx20Read.d.ts.map +1 -0
  271. package/types/lib/inventory/cyclonedx20ReadCommon.d.ts +69 -0
  272. package/types/lib/inventory/cyclonedx20ReadCommon.d.ts.map +1 -0
  273. package/types/lib/inventory/cyclonedx20ReadComponents.d.ts +77 -0
  274. package/types/lib/inventory/cyclonedx20ReadComponents.d.ts.map +1 -0
  275. package/types/lib/inventory/cyclonedx20ReadContext.d.ts +118 -0
  276. package/types/lib/inventory/cyclonedx20ReadContext.d.ts.map +1 -0
  277. package/types/lib/inventory/cyclonedx20ReadEvidence.d.ts +40 -0
  278. package/types/lib/inventory/cyclonedx20ReadEvidence.d.ts.map +1 -0
  279. package/types/lib/inventory/cyclonedx20ReadSections.d.ts +45 -0
  280. package/types/lib/inventory/cyclonedx20ReadSections.d.ts.map +1 -0
  281. package/types/lib/inventory/cyclonedx20Render.d.ts +53 -0
  282. package/types/lib/inventory/cyclonedx20Render.d.ts.map +1 -0
  283. package/types/lib/inventory/cyclonedx20RenderComponents.d.ts +73 -0
  284. package/types/lib/inventory/cyclonedx20RenderComponents.d.ts.map +1 -0
  285. package/types/lib/inventory/cyclonedx20RenderDefinitions.d.ts +29 -0
  286. package/types/lib/inventory/cyclonedx20RenderDefinitions.d.ts.map +1 -0
  287. package/types/lib/inventory/cyclonedx20RenderEvidence.d.ts +110 -0
  288. package/types/lib/inventory/cyclonedx20RenderEvidence.d.ts.map +1 -0
  289. package/types/lib/inventory/cyclonedx20RenderHardware.d.ts +42 -0
  290. package/types/lib/inventory/cyclonedx20RenderHardware.d.ts.map +1 -0
  291. package/types/lib/inventory/cyclonedx20RenderPatents.d.ts +26 -0
  292. package/types/lib/inventory/cyclonedx20RenderPatents.d.ts.map +1 -0
  293. package/types/lib/inventory/cyclonedx20RenderRefIds.d.ts +55 -0
  294. package/types/lib/inventory/cyclonedx20RenderRefIds.d.ts.map +1 -0
  295. package/types/lib/inventory/cyclonedx20RenderSections.d.ts +45 -0
  296. package/types/lib/inventory/cyclonedx20RenderSections.d.ts.map +1 -0
  297. package/types/lib/inventory/cyclonedx20Tables.d.ts +134 -0
  298. package/types/lib/inventory/cyclonedx20Tables.d.ts.map +1 -0
  299. package/types/lib/inventory/deps.d.ts +35 -2
  300. package/types/lib/inventory/deps.d.ts.map +1 -1
  301. package/types/lib/inventory/dosai.d.ts +36 -1
  302. package/types/lib/inventory/dosai.d.ts.map +1 -1
  303. package/types/lib/inventory/dosaiParsers.d.ts.map +1 -1
  304. package/types/lib/inventory/dotnetRuntime.d.ts +91 -0
  305. package/types/lib/inventory/dotnetRuntime.d.ts.map +1 -0
  306. package/types/lib/inventory/ecosystemFormulationParsers.d.ts +73 -0
  307. package/types/lib/inventory/ecosystemFormulationParsers.d.ts.map +1 -0
  308. package/types/lib/inventory/envcontext.d.ts +74 -12
  309. package/types/lib/inventory/envcontext.d.ts.map +1 -1
  310. package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
  311. package/types/lib/inventory/formulationParsers.d.ts.map +1 -1
  312. package/types/lib/inventory/gtfobins.d.ts.map +1 -1
  313. package/types/lib/inventory/hbom.d.ts +11 -0
  314. package/types/lib/inventory/hbom.d.ts.map +1 -1
  315. package/types/lib/inventory/hbomLoader.d.ts +54 -0
  316. package/types/lib/inventory/hbomLoader.d.ts.map +1 -1
  317. package/types/lib/inventory/hostRuntimeProbe.d.ts +44 -0
  318. package/types/lib/inventory/hostRuntimeProbe.d.ts.map +1 -0
  319. package/types/lib/inventory/jvmLocalRepos.d.ts +5 -1
  320. package/types/lib/inventory/jvmLocalRepos.d.ts.map +1 -1
  321. package/types/lib/inventory/jvmToolEnv.d.ts +12 -0
  322. package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -1
  323. package/types/lib/inventory/licenseEnhancer.d.ts.map +1 -1
  324. package/types/lib/inventory/mavenCentralMirror.d.ts +170 -0
  325. package/types/lib/inventory/mavenCentralMirror.d.ts.map +1 -0
  326. package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
  327. package/types/lib/inventory/nodeBuildInfo.d.ts +95 -0
  328. package/types/lib/inventory/nodeBuildInfo.d.ts.map +1 -0
  329. package/types/lib/inventory/nodeHostProbe.d.ts +15 -0
  330. package/types/lib/inventory/nodeHostProbe.d.ts.map +1 -0
  331. package/types/lib/inventory/occurrenceKey.d.ts +52 -0
  332. package/types/lib/inventory/occurrenceKey.d.ts.map +1 -0
  333. package/types/lib/inventory/osPackageResolver.d.ts +38 -0
  334. package/types/lib/inventory/osPackageResolver.d.ts.map +1 -1
  335. package/types/lib/inventory/phpBuildInfo.d.ts.map +1 -1
  336. package/types/lib/inventory/phpHostProbe.d.ts.map +1 -1
  337. package/types/lib/inventory/pqcMigration.d.ts +84 -0
  338. package/types/lib/inventory/pqcMigration.d.ts.map +1 -0
  339. package/types/lib/inventory/rubyBuildInfo.d.ts +271 -0
  340. package/types/lib/inventory/rubyBuildInfo.d.ts.map +1 -0
  341. package/types/lib/inventory/rubyHostProbe.d.ts +47 -0
  342. package/types/lib/inventory/rubyHostProbe.d.ts.map +1 -0
  343. package/types/lib/inventory/rustFormulationParser.d.ts +7 -2
  344. package/types/lib/inventory/rustFormulationParser.d.ts.map +1 -1
  345. package/types/lib/inventory/scalaCoords.d.ts.map +1 -1
  346. package/types/lib/inventory/scalaDedupe.d.ts +43 -0
  347. package/types/lib/inventory/scalaDedupe.d.ts.map +1 -0
  348. package/types/lib/inventory/source.d.ts +25 -0
  349. package/types/lib/inventory/source.d.ts.map +1 -1
  350. package/types/lib/managers/binary.d.ts.map +1 -1
  351. package/types/lib/managers/docker.d.ts +12 -0
  352. package/types/lib/managers/docker.d.ts.map +1 -1
  353. package/types/lib/parsers/huggingfaceManifest.d.ts +10 -1
  354. package/types/lib/parsers/huggingfaceManifest.d.ts.map +1 -1
  355. package/types/lib/parsers/huggingfaceUtils.d.ts +12 -0
  356. package/types/lib/parsers/huggingfaceUtils.d.ts.map +1 -1
  357. package/types/lib/parsers/kiroPermissions.d.ts +62 -0
  358. package/types/lib/parsers/kiroPermissions.d.ts.map +1 -0
  359. package/types/lib/parsers/largeJson.d.ts +4 -3
  360. package/types/lib/parsers/largeJson.d.ts.map +1 -1
  361. package/types/lib/parsers/vscodeExtensionId.d.ts +48 -0
  362. package/types/lib/parsers/vscodeExtensionId.d.ts.map +1 -0
  363. package/types/lib/server/server.d.ts.map +1 -1
  364. package/types/lib/stages/postgen/postgen.d.ts +7 -0
  365. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  366. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  367. package/types/lib/stages/postgen/specVersionCompat.d.ts +25 -0
  368. package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
  369. package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
  370. package/types/lib/stages/pregen/pregen.d.ts +12 -8
  371. package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
  372. package/types/lib/validator/bomValidator.d.ts +5 -1
  373. package/types/lib/validator/bomValidator.d.ts.map +1 -1
  374. package/types/lib/validator/complianceEngine.d.ts +4 -4
  375. package/types/lib/validator/complianceEngine.d.ts.map +1 -1
  376. package/types/lib/validator/complianceRules.d.ts +14 -2
  377. package/types/lib/validator/complianceRules.d.ts.map +1 -1
  378. package/types/lib/validator/index.d.ts +10 -0
  379. package/types/lib/validator/index.d.ts.map +1 -1
  380. package/types/lib/validator/reporters/json.d.ts.map +1 -1
@@ -2,12 +2,21 @@ import { Buffer } from "node:buffer";
2
2
  import {
3
3
  closeSync,
4
4
  openSync,
5
+ readdirSync,
5
6
  readFileSync,
6
7
  readSync,
7
8
  realpathSync,
8
9
  statSync,
9
10
  } from "node:fs";
10
- import { basename, delimiter, isAbsolute, join, resolve } from "node:path";
11
+ import {
12
+ basename,
13
+ delimiter,
14
+ extname,
15
+ isAbsolute,
16
+ join,
17
+ relative,
18
+ resolve,
19
+ } from "node:path";
11
20
  import process from "node:process";
12
21
 
13
22
  import { DEBUG_MODE, readEnvironmentVariable } from "../core/activity.js";
@@ -24,12 +33,22 @@ import {
24
33
  } from "../core/fs.js";
25
34
  import { formatDuration, reapProcessTree } from "../core/processTree.js";
26
35
  import { encodeUriReference } from "../core/propertySanitizer.js";
36
+ import {
37
+ normalizeHuggingFaceReference,
38
+ toHuggingFaceSupplier,
39
+ } from "../parsers/huggingfaceUtils.js";
27
40
  import {
28
41
  indexJsonObject,
29
42
  MAX_JSON_TEXT_BYTES,
30
43
  readJsonMember,
31
44
  readJsonRange,
32
45
  } from "../parsers/largeJson.js";
46
+ import {
47
+ modelCreatorEntity,
48
+ modelCreatorFromOrganization,
49
+ resolveModelCreator,
50
+ } from "./aiProviders.js";
51
+ import { isPathExcludedByGlobPatterns } from "./atomUtils.js";
33
52
  import {
34
53
  addDosaiSetValue,
35
54
  buildDosaiPurlAliasMap,
@@ -712,6 +731,198 @@ export function dosaiSectionsCdxgenReads(omittedSections) {
712
731
  );
713
732
  }
714
733
 
734
+ // The files dosai reads from a source tree whose changes make an existing
735
+ // methods slice stale: the C#, VB, and F# sources it analyses, the project
736
+ // and MSBuild files that shape the analysis, and the Razor and WebForms
737
+ // templates whose call sites it maps from build output. packages.config is
738
+ // read by cdxgen's own dotnet analysis and holds the same package list a
739
+ // csproj does, so a change to it can change the slice too. Solution files are
740
+ // left out: dosai opens them only to budget its restore runs, and a changed
741
+ // solution does not change the occurrences a slice holds.
742
+ const DOSAI_SLICE_SOURCE_EXTENSIONS = new Set([
743
+ ".cs",
744
+ ".cshtml",
745
+ ".csproj",
746
+ ".fs",
747
+ ".fsi",
748
+ ".fsproj",
749
+ ".fsx",
750
+ ".props",
751
+ ".razor",
752
+ ".targets",
753
+ ".vb",
754
+ ".vbproj",
755
+ ]);
756
+ const DOSAI_SLICE_SOURCE_FILES = new Set(["packages.config"]);
757
+
758
+ // Directories that hold nothing dosai analyses. Its own tree walk skips the
759
+ // build-output directories (obj for every file, bin for sources) and the
760
+ // package and version-control directories, so a build cannot make a slice
761
+ // stale and a dependency tree cannot slow the check down.
762
+ const DOSAI_SLICE_SOURCE_SKIP_DIRS = new Set([
763
+ ".git",
764
+ "bin",
765
+ "node_modules",
766
+ "obj",
767
+ ]);
768
+
769
+ // The tree walk is bounded so a pathological tree (a symlink farm, or entries
770
+ // that appear faster than the walk reads them) cannot keep a scan from
771
+ // finishing. A walk that reaches a bound leaves freshness unknown, and the
772
+ // callers regenerate the slice, which is the run a fresh scan pays anyway.
773
+ const DOSAI_FRESHNESS_MAX_DIRS = 50_000;
774
+ const DOSAI_FRESHNESS_MAX_FILES = 200_000;
775
+
776
+ /**
777
+ * The newest mtime among the source files dosai analyses under a directory.
778
+ *
779
+ * The walk is deterministic and bounded: directory entries are read and then
780
+ * visited depth-first in sorted name order, symlinked directories are not
781
+ * followed, and counts of visited directories and analysed files cap the
782
+ * work. Excluded paths are pruned rather than filtered, so nothing beneath an
783
+ * excluded directory is read.
784
+ *
785
+ * @param {string} projectDir Absolute directory dosai analyses
786
+ * @param {string[]} excludePatterns Exclude globs relative to projectDir
787
+ * @returns {{mtimeMs: number|undefined, complete: boolean}} The newest mtime seen (undefined when no analysed file was found), and whether the walk finished, which is false for an unreadable directory or file, or a tree past the bounds
788
+ */
789
+ function newestDosaiSourceMtimeSync(projectDir, excludePatterns) {
790
+ const newest = { mtimeMs: undefined, complete: true };
791
+ const stack = [[projectDir, ""]];
792
+ let dirsVisited = 0;
793
+ let filesVisited = 0;
794
+ while (newest.complete && stack.length) {
795
+ if (dirsVisited++ >= DOSAI_FRESHNESS_MAX_DIRS) {
796
+ newest.complete = false;
797
+ break;
798
+ }
799
+ const [dir, relDir] = stack.pop();
800
+ let entries;
801
+ try {
802
+ entries = readdirSync(dir, { withFileTypes: true });
803
+ } catch (_err) {
804
+ newest.complete = false;
805
+ break;
806
+ }
807
+ entries.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
808
+ for (const entry of entries) {
809
+ const relPath = relDir ? `${relDir}/${entry.name}` : entry.name;
810
+ if (entry.isDirectory()) {
811
+ if (
812
+ !DOSAI_SLICE_SOURCE_SKIP_DIRS.has(entry.name.toLowerCase()) &&
813
+ !(
814
+ excludePatterns.length &&
815
+ isPathExcludedByGlobPatterns(relPath, excludePatterns)
816
+ )
817
+ ) {
818
+ stack.push([join(dir, entry.name), relPath]);
819
+ }
820
+ continue;
821
+ }
822
+ if (
823
+ !DOSAI_SLICE_SOURCE_EXTENSIONS.has(extname(entry.name).toLowerCase()) &&
824
+ !DOSAI_SLICE_SOURCE_FILES.has(entry.name.toLowerCase())
825
+ ) {
826
+ continue;
827
+ }
828
+ if (filesVisited++ >= DOSAI_FRESHNESS_MAX_FILES) {
829
+ newest.complete = false;
830
+ break;
831
+ }
832
+ try {
833
+ // statSync follows a symlink to the source it points at, which is
834
+ // the file dosai reads.
835
+ const mtimeMs = statSync(join(dir, entry.name)).mtimeMs;
836
+ if (newest.mtimeMs === undefined || mtimeMs > newest.mtimeMs) {
837
+ newest.mtimeMs = mtimeMs;
838
+ }
839
+ } catch (_err) {
840
+ newest.complete = false;
841
+ break;
842
+ }
843
+ }
844
+ }
845
+ return newest;
846
+ }
847
+
848
+ /**
849
+ * The cdxgen exclude patterns that apply under a directory, as globs relative
850
+ * to it.
851
+ *
852
+ * The patterns are matched the way the atom evidence filter matches
853
+ * `options.exclude`: a relative pattern matches at any depth, and an absolute
854
+ * pattern is anchored to the scanned directory, which for the walk means the
855
+ * patterns that sit beneath it are rebased to relative ones. Patterns that
856
+ * fall outside the directory are dropped.
857
+ *
858
+ * @param {string[]} excludes cdxgen exclude globs
859
+ * @param {string} projectDir Absolute directory dosai analyses
860
+ * @returns {string[]} Exclude globs relative to projectDir
861
+ */
862
+ function dosaiSourceExcludePatterns(excludes, projectDir) {
863
+ const rebased = [];
864
+ if (!Array.isArray(excludes)) {
865
+ return rebased;
866
+ }
867
+ for (const pattern of excludes) {
868
+ if (typeof pattern !== "string" || !pattern.trim()) {
869
+ continue;
870
+ }
871
+ // resolve() leaves a relative pattern where it was and turns an absolute
872
+ // one into a path the relative() call drops the scanned directory from.
873
+ const underRoot = resolve(projectDir, pattern);
874
+ const rel = relative(projectDir, underRoot);
875
+ if (rel && !rel.startsWith("..") && !isAbsolute(rel)) {
876
+ rebased.push(rel.replaceAll("\\", "/"));
877
+ }
878
+ }
879
+ return rebased;
880
+ }
881
+
882
+ /**
883
+ * Whether an existing dosai methods slice is newer than the sources it
884
+ * describes.
885
+ *
886
+ * A slice written by an earlier scan holds the occurrences of the sources as
887
+ * they were then, so a slice that predates an edit must be regenerated rather
888
+ * than reused: reusing one keeps occurrences of call sites that no longer
889
+ * exist (issue 4675). The slice is fresh only when its mtime is not older
890
+ * than the newest mtime among the files dosai analyses under the directory.
891
+ * A missing slice, a slice whose mtime cannot be read, a source that cannot
892
+ * be stat-ed, and a tree that exceeds the walk bounds all count as stale: the
893
+ * callers regenerate the slice, which is the run a fresh scan pays anyway.
894
+ *
895
+ * @param {string} slicesFile Candidate dosai methods slice
896
+ * @param {string} projectDir Directory dosai analyses
897
+ * @param {Object} [options] Scan options; options.exclude prunes the walk
898
+ * @returns {boolean} True when the slice is newer than every source it describes
899
+ */
900
+ export function dosaiSlicesFileIsFresh(slicesFile, projectDir, options = {}) {
901
+ if (!slicesFile || !projectDir || !safeExistsSync(slicesFile)) {
902
+ return false;
903
+ }
904
+ let sliceMtimeMs;
905
+ try {
906
+ sliceMtimeMs = statSync(slicesFile).mtimeMs;
907
+ } catch (_err) {
908
+ return false;
909
+ }
910
+ const excludePatterns = dosaiSourceExcludePatterns(
911
+ options.exclude,
912
+ resolve(projectDir),
913
+ );
914
+ const newest = newestDosaiSourceMtimeSync(
915
+ resolve(projectDir),
916
+ excludePatterns,
917
+ );
918
+ if (!newest.complete) {
919
+ return false;
920
+ }
921
+ // A directory with no file dosai analyses holds nothing that can go stale,
922
+ // so the slice counts as fresh.
923
+ return newest.mtimeMs === undefined || sliceMtimeMs >= newest.mtimeMs;
924
+ }
925
+
715
926
  // The file each report returned by readDosaiReport came from, and whether it
716
927
  // was trimmed to what cdxgen reads. persistDosaiSemanticsReport copies the
717
928
  // file rather than re-serialising a trimmed or oversized report.
@@ -727,6 +938,105 @@ function referencedIds(entries, field) {
727
938
  return ids;
728
939
  }
729
940
 
941
+ // The fields of a MethodCalls row cdxgen reads: the component it names (Purl
942
+ // or Module), where the call site is (Path, FileName, LineNumber) and what it
943
+ // calls (ClassName, CalledMethod). Everything else a report row carries -
944
+ // arguments, evidence kinds, caller identities - no cdxgen consumer reads,
945
+ // and a report with millions of calls cannot afford to hold them.
946
+ const DOSAI_METHOD_CALL_FIELDS = [
947
+ "Purl",
948
+ "Module",
949
+ "Path",
950
+ "FileName",
951
+ "LineNumber",
952
+ "ClassName",
953
+ "CalledMethod",
954
+ ];
955
+
956
+ /**
957
+ * The largest number of distinct method calls cdxgen keeps from one report.
958
+ *
959
+ * Reports in the millions of calls repeat the same call sites heavily, so the
960
+ * aggregated rows stay far below this; the cap exists so a report whose calls
961
+ * are all distinct degrades the occurrence evidence instead of the process.
962
+ *
963
+ * @returns {number} Cap on distinct kept rows
964
+ */
965
+ export function maxDosaiMethodCalls() {
966
+ const configured = Number.parseInt(
967
+ readEnvironmentVariable("CDXGEN_DOSAI_MAX_METHOD_CALLS") || "",
968
+ 10,
969
+ );
970
+ if (Number.isInteger(configured) && configured > 0) {
971
+ return configured;
972
+ }
973
+ return 1_000_000;
974
+ }
975
+
976
+ /**
977
+ * Build the `map` that turns report MethodCalls rows into what cdxgen keeps:
978
+ * rows the caller rejects are dropped, the rest are reduced to
979
+ * {@link DOSAI_METHOD_CALL_FIELDS} and aggregated by those fields, so a call
980
+ * site reached a million times is held once with a `CallCount`. Past
981
+ * {@link maxDosaiMethodCalls} distinct rows, further rows are dropped and one
982
+ * warning names the cap.
983
+ *
984
+ * @param {string} reportFile Report the rows come from, named in the warning
985
+ * @param {Object} [options] Options
986
+ * @param {Function} [options.keepMethodCall] Predicate choosing the rows to keep
987
+ * @param {number} [options.maxMethodCalls] Cap override, for tests
988
+ * @returns {{map: Function, finish: Function}} The row mapper, and the
989
+ * `finish` that reports a cap reached
990
+ */
991
+ function dosaiMethodCallCollector(reportFile, options = {}) {
992
+ const keep = options.keepMethodCall;
993
+ const max = options.maxMethodCalls ?? maxDosaiMethodCalls();
994
+ const keptByKey = new Map();
995
+ let dropped = 0;
996
+ return {
997
+ map(methodCall) {
998
+ if (keep && !keep(methodCall)) {
999
+ return undefined;
1000
+ }
1001
+ const compact = {};
1002
+ for (const field of DOSAI_METHOD_CALL_FIELDS) {
1003
+ if (methodCall?.[field] !== undefined) {
1004
+ compact[field] = methodCall[field];
1005
+ }
1006
+ }
1007
+ const key = DOSAI_METHOD_CALL_FIELDS.map((field) => compact[field]).join(
1008
+ "\u0000",
1009
+ );
1010
+ const kept = keptByKey.get(key);
1011
+ if (kept) {
1012
+ kept.CallCount = (kept.CallCount ?? 1) + 1;
1013
+ return undefined;
1014
+ }
1015
+ if (keptByKey.size >= max) {
1016
+ dropped++;
1017
+ return undefined;
1018
+ }
1019
+ keptByKey.set(key, compact);
1020
+ return compact;
1021
+ },
1022
+ finish() {
1023
+ if (!dropped) {
1024
+ return;
1025
+ }
1026
+ console.warn(
1027
+ `The dosai report "${reportFile}" holds more distinct method calls than cdxgen keeps (${max}); ${dropped} call sites were dropped, so occurrence evidence may be incomplete. Raise the cap with CDXGEN_DOSAI_MAX_METHOD_CALLS.`,
1028
+ );
1029
+ recordDegradation("dosai.method-calls-capped", {
1030
+ ecosystem: "csharp",
1031
+ tool: "dosai",
1032
+ impact: "components",
1033
+ command: reportFile,
1034
+ detail: `The dosai report holds more distinct method calls than the cap of ${max}, so ${dropped} call sites were dropped from the occurrence evidence.`,
1035
+ });
1036
+ },
1037
+ };
1038
+ }
1039
+
730
1040
  function readDosaiReport(reportFile, options, readTrimmed) {
731
1041
  if (!reportFile || !safeExistsSync(reportFile)) {
732
1042
  return undefined;
@@ -761,7 +1071,13 @@ function readDosaiReport(reportFile, options, readTrimmed) {
761
1071
  }
762
1072
  return report;
763
1073
  } catch (err) {
764
- if (DEBUG_MODE) {
1074
+ if (err instanceof RangeError) {
1075
+ // A heap or string limit is a report-size problem the user can act on,
1076
+ // unlike a malformed report, so it is named even without debug output.
1077
+ console.warn(
1078
+ `The dosai report "${reportFile}" is too large to read: ${err.message}. Raise the Node.js heap, for example NODE_OPTIONS="--max-old-space-size=8192", or write the report with dosai's --sections so it holds less.`,
1079
+ );
1080
+ } else if (DEBUG_MODE) {
765
1081
  console.log(
766
1082
  `Unable to read the dosai report "${reportFile}": ${err.message}`,
767
1083
  );
@@ -776,25 +1092,34 @@ function readDosaiReport(reportFile, options, readTrimmed) {
776
1092
  * A report that fits in one JavaScript string is parsed whole. A larger one is
777
1093
  * read in bounded runs, keeping the requested sections, the CallGraph nodes and
778
1094
  * edges that PackageReachability references, and the MethodCalls accepted by
779
- * `keepMethodCall` (none when it is omitted). Nothing cdxgen reads is lost.
1095
+ * `keepMethodCall` (none when it is omitted). A kept MethodCalls row is
1096
+ * reduced to the fields cdxgen reads and aggregated by them, so a call site
1097
+ * reached many times is held once with a `CallCount`; distinct rows past
1098
+ * {@link maxDosaiMethodCalls} are dropped with one warning. Nothing cdxgen
1099
+ * reads is lost.
780
1100
  *
781
1101
  * @param {string} reportFile Path to the dosai methods JSON
782
1102
  * @param {Object} [options] Options
783
1103
  * @param {string[]} [options.sections] Sections to keep from a large report (default: every section cdxgen reads)
784
1104
  * @param {Function} [options.keepMethodCall] Predicate choosing the MethodCalls to keep from a large report
1105
+ * @param {number} [options.maxMethodCalls] Cap on distinct kept MethodCalls, for tests
785
1106
  * @param {number} [options.maxTextBytes] Largest report parsed whole, for tests
786
1107
  * @returns {Object|undefined} Parsed report, or undefined when missing or invalid
787
1108
  */
788
1109
  export function readDosaiMethodsReport(reportFile, options = {}) {
789
1110
  const sections = options.sections || DOSAI_METHODS_SECTIONS;
790
- return readDosaiReport(reportFile, options, (report, read) => {
1111
+ const collector =
1112
+ sections.includes("MethodCalls") && options.keepMethodCall
1113
+ ? dosaiMethodCallCollector(reportFile, options)
1114
+ : undefined;
1115
+ const report = readDosaiReport(reportFile, options, (report, read) => {
791
1116
  for (const section of sections) {
792
1117
  if (section !== "CallGraph" && section !== "MethodCalls") {
793
1118
  read(section);
794
1119
  }
795
1120
  }
796
- if (sections.includes("MethodCalls") && options.keepMethodCall) {
797
- read("MethodCalls", { filter: options.keepMethodCall });
1121
+ if (collector) {
1122
+ read("MethodCalls", { map: collector.map });
798
1123
  }
799
1124
  if (sections.includes("CallGraph")) {
800
1125
  // CallGraph precedes PackageReachability in the file, which is why the
@@ -809,6 +1134,26 @@ export function readDosaiMethodsReport(reportFile, options = {}) {
809
1134
  });
810
1135
  }
811
1136
  });
1137
+ // A report parsed whole kept every row, as it always has: reduce them to
1138
+ // the fields cdxgen reads and aggregate repeats, without the caller's
1139
+ // predicate, which the whole parse has never applied. A report that took
1140
+ // the trimmed path already went through the collector above.
1141
+ if (report && !dosaiReportSources.get(report)?.trimmed) {
1142
+ const wholeCollector = dosaiMethodCallCollector(reportFile, {
1143
+ maxMethodCalls: options.maxMethodCalls,
1144
+ });
1145
+ const reduced = [];
1146
+ for (const methodCall of report.MethodCalls || []) {
1147
+ const kept = wholeCollector.map(methodCall);
1148
+ if (kept !== undefined) {
1149
+ reduced.push(kept);
1150
+ }
1151
+ }
1152
+ report.MethodCalls = reduced;
1153
+ wholeCollector.finish();
1154
+ }
1155
+ collector?.finish();
1156
+ return report;
812
1157
  }
813
1158
 
814
1159
  /**
@@ -1844,6 +2189,23 @@ export function collectDosaiAiComponents(methodsSlice, components = []) {
1844
2189
  format,
1845
2190
  }));
1846
2191
  }
2192
+ const hubReference = String(ai.Purl || "").startsWith("pkg:huggingface/")
2193
+ ? normalizeHuggingFaceReference(ai.Purl)
2194
+ : undefined;
2195
+ const hubNamespace = hubReference?.repoId?.split("/")[0];
2196
+ // A Hub model is credited to the namespace only when it is the
2197
+ // creator's own organisation, as the source collectors do. Otherwise
2198
+ // dosai reports the provider the code calls, which names the client
2199
+ // rather than the model, so the creator is resolved from the model
2200
+ // name.
2201
+ const manufacturer = modelCreatorEntity(
2202
+ hubNamespace
2203
+ ? modelCreatorFromOrganization(hubNamespace)
2204
+ : resolveModelCreator(ai.Name, {
2205
+ fileProviders: ai.Provider ? [ai.Provider] : [],
2206
+ provider: ai.Provider,
2207
+ }),
2208
+ );
1847
2209
  const component = {
1848
2210
  "bom-ref": ai.Id,
1849
2211
  type: "machine-learning-model",
@@ -1851,6 +2213,13 @@ export function collectDosaiAiComponents(methodsSlice, components = []) {
1851
2213
  version: ai.Version,
1852
2214
  purl: ai.Purl,
1853
2215
  modelCard,
2216
+ // The creator is the manufacturer; a Hugging Face namespace that
2217
+ // published the model is the supplier. The provider stays a
2218
+ // property either way.
2219
+ manufacturer,
2220
+ supplier: hubNamespace
2221
+ ? toHuggingFaceSupplier(hubNamespace)
2222
+ : undefined,
1854
2223
  properties: [
1855
2224
  { name: "cdx:ai:provider", value: ai.Provider || "unknown" },
1856
2225
  { name: "cdx:ai:deployment", value: ai.Deployment || "unknown" },
@@ -12,6 +12,7 @@ import {
12
12
  import { Purl } from "@cdxgen/cdx-purl";
13
13
 
14
14
  import { safeExistsSync } from "../core/fs.js";
15
+ import { occurrenceLocationKey } from "./occurrenceKey.js";
15
16
 
16
17
  /**
17
18
  * Build a lowercase type/namespace/name lookup key for a purl.
@@ -68,10 +69,7 @@ export function dosaiLocation(item) {
68
69
  return undefined;
69
70
  }
70
71
  const lineNumber = location?.LineNumber || item?.LineNumber;
71
- if (lineNumber && lineNumber > 0) {
72
- return `${fileName}#${lineNumber}`;
73
- }
74
- return fileName;
72
+ return occurrenceLocationKey(fileName, lineNumber);
75
73
  }
76
74
 
77
75
  function dosaiSourceFileName(item) {
@@ -110,8 +108,7 @@ export function dosaiCallSiteLocation(methodCall, srcPath) {
110
108
  if (!file || lowerFile.endsWith(".dll") || lowerFile.endsWith(".exe")) {
111
109
  return undefined;
112
110
  }
113
- const lineNumber = methodCall?.LineNumber;
114
- return lineNumber > 0 ? `${file}#${lineNumber}` : file;
111
+ return occurrenceLocationKey(file, methodCall?.LineNumber);
115
112
  }
116
113
 
117
114
  function dosaiCallSiteFile(methodCall, srcPath) {