kirograph 0.16.1 → 0.19.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 (365) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +103 -1605
  3. package/dist/architecture/layers/dart.js +112 -0
  4. package/dist/architecture/layers/dart.js.map +7 -0
  5. package/dist/architecture/layers/index.js +3 -1
  6. package/dist/architecture/layers/index.js.map +2 -2
  7. package/dist/bin/banner.js +74 -6
  8. package/dist/bin/banner.js.map +2 -2
  9. package/dist/bin/commands/affected.js +40 -5
  10. package/dist/bin/commands/affected.js.map +2 -2
  11. package/dist/bin/commands/attack-surface.js +157 -0
  12. package/dist/bin/commands/attack-surface.js.map +7 -0
  13. package/dist/bin/commands/benchmark.js +206 -0
  14. package/dist/bin/commands/benchmark.js.map +7 -0
  15. package/dist/bin/commands/budget.js +70 -0
  16. package/dist/bin/commands/budget.js.map +7 -0
  17. package/dist/bin/commands/communities.js +77 -0
  18. package/dist/bin/commands/communities.js.map +7 -0
  19. package/dist/bin/commands/data.js +665 -0
  20. package/dist/bin/commands/data.js.map +7 -0
  21. package/dist/bin/commands/dep-confusion.js +119 -0
  22. package/dist/bin/commands/dep-confusion.js.map +7 -0
  23. package/dist/bin/commands/export.js +304 -10
  24. package/dist/bin/commands/export.js.map +2 -2
  25. package/dist/bin/commands/flows.js +97 -0
  26. package/dist/bin/commands/flows.js.map +7 -0
  27. package/dist/bin/commands/help.js +386 -100
  28. package/dist/bin/commands/help.js.map +2 -2
  29. package/dist/bin/commands/install.js +90 -8
  30. package/dist/bin/commands/install.js.map +2 -2
  31. package/dist/bin/commands/licenses.js +201 -0
  32. package/dist/bin/commands/licenses.js.map +7 -0
  33. package/dist/bin/commands/reachability.js +160 -0
  34. package/dist/bin/commands/reachability.js.map +7 -0
  35. package/dist/bin/commands/read.js +100 -0
  36. package/dist/bin/commands/read.js.map +7 -0
  37. package/dist/bin/commands/refactor.js +116 -0
  38. package/dist/bin/commands/refactor.js.map +7 -0
  39. package/dist/bin/commands/remediation.js +133 -0
  40. package/dist/bin/commands/remediation.js.map +7 -0
  41. package/dist/bin/commands/sbom.js +76 -0
  42. package/dist/bin/commands/sbom.js.map +7 -0
  43. package/dist/bin/commands/security-ci-report.js +267 -0
  44. package/dist/bin/commands/security-ci-report.js.map +7 -0
  45. package/dist/bin/commands/security-export.js +1349 -0
  46. package/dist/bin/commands/security-export.js.map +7 -0
  47. package/dist/bin/commands/security-flows.js +126 -0
  48. package/dist/bin/commands/security-flows.js.map +7 -0
  49. package/dist/bin/commands/security-secrets.js +170 -0
  50. package/dist/bin/commands/security-secrets.js.map +7 -0
  51. package/dist/bin/commands/security.js +193 -0
  52. package/dist/bin/commands/security.js.map +7 -0
  53. package/dist/bin/commands/staleness.js +144 -0
  54. package/dist/bin/commands/staleness.js.map +7 -0
  55. package/dist/bin/commands/status.js +102 -2
  56. package/dist/bin/commands/status.js.map +2 -2
  57. package/dist/bin/commands/supply-chain.js +138 -0
  58. package/dist/bin/commands/supply-chain.js.map +7 -0
  59. package/dist/bin/commands/uninit.js +109 -60
  60. package/dist/bin/commands/uninit.js.map +2 -2
  61. package/dist/bin/commands/vex.js +76 -0
  62. package/dist/bin/commands/vex.js.map +7 -0
  63. package/dist/bin/commands/vuln-suppress.js +95 -0
  64. package/dist/bin/commands/vuln-suppress.js.map +7 -0
  65. package/dist/bin/commands/vulns.js +382 -0
  66. package/dist/bin/commands/vulns.js.map +7 -0
  67. package/dist/bin/installer/auto-detect.js +125 -0
  68. package/dist/bin/installer/auto-detect.js.map +7 -0
  69. package/dist/bin/installer/cli-agent.js +28 -5
  70. package/dist/bin/installer/cli-agent.js.map +2 -2
  71. package/dist/bin/installer/common.js +47 -0
  72. package/dist/bin/installer/common.js.map +2 -2
  73. package/dist/bin/installer/config-prompt.js +39 -1
  74. package/dist/bin/installer/config-prompt.js.map +2 -2
  75. package/dist/bin/installer/detect.js +309 -0
  76. package/dist/bin/installer/detect.js.map +7 -0
  77. package/dist/bin/installer/index.js +36 -1
  78. package/dist/bin/installer/index.js.map +2 -2
  79. package/dist/bin/installer/instructions.js +195 -3
  80. package/dist/bin/installer/instructions.js.map +2 -2
  81. package/dist/bin/installer/steering.js +440 -0
  82. package/dist/bin/installer/steering.js.map +2 -2
  83. package/dist/bin/installer/targets/aider.js +3 -3
  84. package/dist/bin/installer/targets/aider.js.map +2 -2
  85. package/dist/bin/installer/targets/amp.js +3 -3
  86. package/dist/bin/installer/targets/amp.js.map +2 -2
  87. package/dist/bin/installer/targets/antigravity.js +35 -6
  88. package/dist/bin/installer/targets/antigravity.js.map +2 -2
  89. package/dist/bin/installer/targets/augment.js +3 -3
  90. package/dist/bin/installer/targets/augment.js.map +2 -2
  91. package/dist/bin/installer/targets/claude.js +49 -5
  92. package/dist/bin/installer/targets/claude.js.map +2 -2
  93. package/dist/bin/installer/targets/cline.js +32 -6
  94. package/dist/bin/installer/targets/cline.js.map +2 -2
  95. package/dist/bin/installer/targets/codex.js +64 -12
  96. package/dist/bin/installer/targets/codex.js.map +2 -2
  97. package/dist/bin/installer/targets/continue.js +2 -2
  98. package/dist/bin/installer/targets/continue.js.map +2 -2
  99. package/dist/bin/installer/targets/copilot-cli.js +101 -0
  100. package/dist/bin/installer/targets/copilot-cli.js.map +7 -0
  101. package/dist/bin/installer/targets/copilot.js +31 -5
  102. package/dist/bin/installer/targets/copilot.js.map +2 -2
  103. package/dist/bin/installer/targets/cursor.js +4 -4
  104. package/dist/bin/installer/targets/cursor.js.map +2 -2
  105. package/dist/bin/installer/targets/devin.js +2 -2
  106. package/dist/bin/installer/targets/devin.js.map +2 -2
  107. package/dist/bin/installer/targets/gemini-cli.js +2 -2
  108. package/dist/bin/installer/targets/gemini-cli.js.map +2 -2
  109. package/dist/bin/installer/targets/generic.js +2 -14
  110. package/dist/bin/installer/targets/generic.js.map +2 -2
  111. package/dist/bin/installer/targets/goose.js +3 -3
  112. package/dist/bin/installer/targets/goose.js.map +2 -2
  113. package/dist/bin/installer/targets/index.js +250 -0
  114. package/dist/bin/installer/targets/index.js.map +2 -2
  115. package/dist/bin/installer/targets/junie.js +2 -2
  116. package/dist/bin/installer/targets/junie.js.map +2 -2
  117. package/dist/bin/installer/targets/kilo.js +2 -2
  118. package/dist/bin/installer/targets/kilo.js.map +2 -2
  119. package/dist/bin/installer/targets/kiro.js +3 -3
  120. package/dist/bin/installer/targets/kiro.js.map +2 -2
  121. package/dist/bin/installer/targets/opencode.js +2 -2
  122. package/dist/bin/installer/targets/opencode.js.map +2 -2
  123. package/dist/bin/installer/targets/openhands.js +3 -3
  124. package/dist/bin/installer/targets/openhands.js.map +2 -2
  125. package/dist/bin/installer/targets/qoder.js +91 -0
  126. package/dist/bin/installer/targets/qoder.js.map +7 -0
  127. package/dist/bin/installer/targets/qwen.js +96 -0
  128. package/dist/bin/installer/targets/qwen.js.map +7 -0
  129. package/dist/bin/installer/targets/replit.js +3 -3
  130. package/dist/bin/installer/targets/replit.js.map +2 -2
  131. package/dist/bin/installer/targets/roo.js +2 -2
  132. package/dist/bin/installer/targets/roo.js.map +2 -2
  133. package/dist/bin/installer/targets/tabnine.js +3 -3
  134. package/dist/bin/installer/targets/tabnine.js.map +2 -2
  135. package/dist/bin/installer/targets/trae.js +5 -5
  136. package/dist/bin/installer/targets/trae.js.map +2 -2
  137. package/dist/bin/installer/targets/warp.js +2 -2
  138. package/dist/bin/installer/targets/warp.js.map +2 -2
  139. package/dist/bin/installer/targets/windsurf.js +35 -6
  140. package/dist/bin/installer/targets/windsurf.js.map +2 -2
  141. package/dist/bin/kirograph.js +74 -6
  142. package/dist/bin/kirograph.js.map +3 -3
  143. package/dist/bin/progress.js +30 -0
  144. package/dist/bin/progress.js.map +2 -2
  145. package/dist/compression/naive-cost.js +25 -0
  146. package/dist/compression/naive-cost.js.map +2 -2
  147. package/dist/compression/tracker.js +91 -2
  148. package/dist/compression/tracker.js.map +2 -2
  149. package/dist/compression/types.js.map +1 -1
  150. package/dist/config.js +126 -3
  151. package/dist/config.js.map +2 -2
  152. package/dist/core/pipeline.js +92 -4
  153. package/dist/core/pipeline.js.map +2 -2
  154. package/dist/data/filters.js +105 -0
  155. package/dist/data/filters.js.map +7 -0
  156. package/dist/data/indexer.js +225 -0
  157. package/dist/data/indexer.js.map +7 -0
  158. package/dist/data/linker.js +150 -0
  159. package/dist/data/linker.js.map +7 -0
  160. package/dist/data/lint.js +109 -0
  161. package/dist/data/lint.js.map +7 -0
  162. package/dist/data/parsers/csv.js +132 -0
  163. package/dist/data/parsers/csv.js.map +7 -0
  164. package/dist/data/parsers/excel.js +88 -0
  165. package/dist/data/parsers/excel.js.map +7 -0
  166. package/dist/data/parsers/index.js +79 -0
  167. package/dist/data/parsers/index.js.map +7 -0
  168. package/dist/data/parsers/json-array.js +89 -0
  169. package/dist/data/parsers/json-array.js.map +7 -0
  170. package/dist/data/parsers/jsonl.js +84 -0
  171. package/dist/data/parsers/jsonl.js.map +7 -0
  172. package/dist/data/parsers/parquet.js +95 -0
  173. package/dist/data/parsers/parquet.js.map +7 -0
  174. package/dist/data/profiler.js +182 -0
  175. package/dist/data/profiler.js.map +7 -0
  176. package/dist/data/queries.js +512 -0
  177. package/dist/data/queries.js.map +7 -0
  178. package/dist/data/types.js +17 -0
  179. package/dist/data/types.js.map +7 -0
  180. package/dist/db/data-schema.sql +61 -0
  181. package/dist/db/database.js +67 -5
  182. package/dist/db/database.js.map +2 -2
  183. package/dist/db/memory-schema.sql +5 -1
  184. package/dist/db/schema.sql +3 -1
  185. package/dist/db/security-schema.sql +72 -0
  186. package/dist/extraction/extractor.js +81 -0
  187. package/dist/extraction/extractor.js.map +2 -2
  188. package/dist/extraction/grammars.js +32 -8
  189. package/dist/extraction/grammars.js.map +2 -2
  190. package/dist/extraction/languages.js +43 -1
  191. package/dist/extraction/languages.js.map +2 -2
  192. package/dist/extraction/notebook.js +300 -0
  193. package/dist/extraction/notebook.js.map +7 -0
  194. package/dist/extraction/wasm/tree-sitter-astro.wasm +0 -0
  195. package/dist/extraction/wasm/tree-sitter-gdscript.wasm +0 -0
  196. package/dist/extraction/wasm/tree-sitter-julia.wasm +0 -0
  197. package/dist/extraction/wasm/tree-sitter-nix.wasm +0 -0
  198. package/dist/extraction/wasm/tree-sitter-perl.wasm +0 -0
  199. package/dist/extraction/wasm/tree-sitter-powershell.wasm +0 -0
  200. package/dist/extraction/wasm/tree-sitter-r.wasm +0 -0
  201. package/dist/extraction/wasm/tree-sitter-sql.wasm +0 -0
  202. package/dist/extraction/wasm/tree-sitter-verilog.wasm +0 -0
  203. package/dist/frameworks/flutter.js +130 -0
  204. package/dist/frameworks/flutter.js.map +7 -0
  205. package/dist/frameworks/index.js +7 -1
  206. package/dist/frameworks/index.js.map +3 -3
  207. package/dist/graph/communities.js +317 -0
  208. package/dist/graph/communities.js.map +7 -0
  209. package/dist/graph/flows.js +138 -0
  210. package/dist/graph/flows.js.map +7 -0
  211. package/dist/graph/refactor.js +155 -0
  212. package/dist/graph/refactor.js.map +7 -0
  213. package/dist/index.js +1 -1
  214. package/dist/index.js.map +2 -2
  215. package/dist/mcp/cache.js +172 -0
  216. package/dist/mcp/cache.js.map +7 -0
  217. package/dist/mcp/read-modes.js +295 -0
  218. package/dist/mcp/read-modes.js.map +7 -0
  219. package/dist/mcp/server.js +2 -1
  220. package/dist/mcp/server.js.map +2 -2
  221. package/dist/mcp/tool-names.js +29 -1
  222. package/dist/mcp/tool-names.js.map +2 -2
  223. package/dist/mcp/tools.js +1588 -6
  224. package/dist/mcp/tools.js.map +3 -3
  225. package/dist/memory/database.js +32 -2
  226. package/dist/memory/database.js.map +2 -2
  227. package/dist/memory/types.js.map +1 -1
  228. package/dist/resolution/bridges/android-rn.js +298 -0
  229. package/dist/resolution/bridges/android-rn.js.map +7 -0
  230. package/dist/resolution/bridges/expo-modules.js +155 -0
  231. package/dist/resolution/bridges/expo-modules.js.map +7 -0
  232. package/dist/resolution/bridges/flutter-channel.js +385 -0
  233. package/dist/resolution/bridges/flutter-channel.js.map +7 -0
  234. package/dist/resolution/bridges/index.js +80 -0
  235. package/dist/resolution/bridges/index.js.map +7 -0
  236. package/dist/resolution/bridges/native-events.js +188 -0
  237. package/dist/resolution/bridges/native-events.js.map +7 -0
  238. package/dist/resolution/bridges/native-views.js +244 -0
  239. package/dist/resolution/bridges/native-views.js.map +7 -0
  240. package/dist/resolution/bridges/react-native.js +161 -0
  241. package/dist/resolution/bridges/react-native.js.map +7 -0
  242. package/dist/resolution/bridges/swift-objc.js +227 -0
  243. package/dist/resolution/bridges/swift-objc.js.map +7 -0
  244. package/dist/resolution/bridges/turbomodules.js +216 -0
  245. package/dist/resolution/bridges/turbomodules.js.map +7 -0
  246. package/dist/resolution/index.js +61 -2
  247. package/dist/resolution/index.js.map +2 -2
  248. package/dist/security/attack-surface.js +164 -0
  249. package/dist/security/attack-surface.js.map +7 -0
  250. package/dist/security/context-warnings.js +123 -0
  251. package/dist/security/context-warnings.js.map +7 -0
  252. package/dist/security/context-warnings.test.js +300 -0
  253. package/dist/security/context-warnings.test.js.map +7 -0
  254. package/dist/security/data-flows.js +228 -0
  255. package/dist/security/data-flows.js.map +7 -0
  256. package/dist/security/dep-confusion.js +255 -0
  257. package/dist/security/dep-confusion.js.map +7 -0
  258. package/dist/security/errors.js +51 -0
  259. package/dist/security/errors.js.map +7 -0
  260. package/dist/security/export/fix-suggestions.js +58 -0
  261. package/dist/security/export/fix-suggestions.js.map +7 -0
  262. package/dist/security/export/fix-suggestions.test.js +74 -0
  263. package/dist/security/export/fix-suggestions.test.js.map +7 -0
  264. package/dist/security/export/sbom.js +230 -0
  265. package/dist/security/export/sbom.js.map +7 -0
  266. package/dist/security/export/sbom.test.js +288 -0
  267. package/dist/security/export/sbom.test.js.map +7 -0
  268. package/dist/security/export/serialization.js +142 -0
  269. package/dist/security/export/serialization.js.map +7 -0
  270. package/dist/security/export/serialization.test.js +310 -0
  271. package/dist/security/export/serialization.test.js.map +7 -0
  272. package/dist/security/export/vex.js +220 -0
  273. package/dist/security/export/vex.js.map +7 -0
  274. package/dist/security/export/vex.test.js +278 -0
  275. package/dist/security/export/vex.test.js.map +7 -0
  276. package/dist/security/index.js +67 -0
  277. package/dist/security/index.js.map +7 -0
  278. package/dist/security/integrator-transitives.test.js +359 -0
  279. package/dist/security/integrator-transitives.test.js.map +7 -0
  280. package/dist/security/integrator.js +491 -0
  281. package/dist/security/integrator.js.map +7 -0
  282. package/dist/security/integrator.test.js +237 -0
  283. package/dist/security/integrator.test.js.map +7 -0
  284. package/dist/security/license.js +70 -0
  285. package/dist/security/license.js.map +7 -0
  286. package/dist/security/manifest/adapter.js +243 -0
  287. package/dist/security/manifest/adapter.js.map +7 -0
  288. package/dist/security/manifest/adapter.test.js +272 -0
  289. package/dist/security/manifest/adapter.test.js.map +7 -0
  290. package/dist/security/manifest/parser.js +376 -0
  291. package/dist/security/manifest/parser.js.map +7 -0
  292. package/dist/security/manifest/parser.test.js +110 -0
  293. package/dist/security/manifest/parser.test.js.map +7 -0
  294. package/dist/security/manifest/plugins/cargo.js +233 -0
  295. package/dist/security/manifest/plugins/cargo.js.map +7 -0
  296. package/dist/security/manifest/plugins/cargo.test.js +304 -0
  297. package/dist/security/manifest/plugins/cargo.test.js.map +7 -0
  298. package/dist/security/manifest/plugins/composer.js +152 -0
  299. package/dist/security/manifest/plugins/composer.js.map +7 -0
  300. package/dist/security/manifest/plugins/go.js +146 -0
  301. package/dist/security/manifest/plugins/go.js.map +7 -0
  302. package/dist/security/manifest/plugins/go.test.js +341 -0
  303. package/dist/security/manifest/plugins/go.test.js.map +7 -0
  304. package/dist/security/manifest/plugins/gradle.js +159 -0
  305. package/dist/security/manifest/plugins/gradle.js.map +7 -0
  306. package/dist/security/manifest/plugins/hex.js +159 -0
  307. package/dist/security/manifest/plugins/hex.js.map +7 -0
  308. package/dist/security/manifest/plugins/maven.js +128 -0
  309. package/dist/security/manifest/plugins/maven.js.map +7 -0
  310. package/dist/security/manifest/plugins/maven.test.js +389 -0
  311. package/dist/security/manifest/plugins/maven.test.js.map +7 -0
  312. package/dist/security/manifest/plugins/npm.js +284 -0
  313. package/dist/security/manifest/plugins/npm.js.map +7 -0
  314. package/dist/security/manifest/plugins/npm.test.js +298 -0
  315. package/dist/security/manifest/plugins/npm.test.js.map +7 -0
  316. package/dist/security/manifest/plugins/nuget.js +186 -0
  317. package/dist/security/manifest/plugins/nuget.js.map +7 -0
  318. package/dist/security/manifest/plugins/pip.js +123 -0
  319. package/dist/security/manifest/plugins/pip.js.map +7 -0
  320. package/dist/security/manifest/plugins/pip.test.js +208 -0
  321. package/dist/security/manifest/plugins/pip.test.js.map +7 -0
  322. package/dist/security/manifest/plugins/pubspec.js +212 -0
  323. package/dist/security/manifest/plugins/pubspec.js.map +7 -0
  324. package/dist/security/manifest/plugins/pyproject.js +261 -0
  325. package/dist/security/manifest/plugins/pyproject.js.map +7 -0
  326. package/dist/security/manifest/plugins/rubygems.js +185 -0
  327. package/dist/security/manifest/plugins/rubygems.js.map +7 -0
  328. package/dist/security/manifest/plugins/swift.js +200 -0
  329. package/dist/security/manifest/plugins/swift.js.map +7 -0
  330. package/dist/security/owasp.js +104 -0
  331. package/dist/security/owasp.js.map +7 -0
  332. package/dist/security/pipeline.js +125 -0
  333. package/dist/security/pipeline.js.map +7 -0
  334. package/dist/security/reachability.js +276 -0
  335. package/dist/security/reachability.js.map +7 -0
  336. package/dist/security/reachability.test.js +394 -0
  337. package/dist/security/reachability.test.js.map +7 -0
  338. package/dist/security/remediation.js +126 -0
  339. package/dist/security/remediation.js.map +7 -0
  340. package/dist/security/secrets.js +176 -0
  341. package/dist/security/secrets.js.map +7 -0
  342. package/dist/security/staleness.js +231 -0
  343. package/dist/security/staleness.js.map +7 -0
  344. package/dist/security/supply-chain.js +301 -0
  345. package/dist/security/supply-chain.js.map +7 -0
  346. package/dist/security/suppressions.js +106 -0
  347. package/dist/security/suppressions.js.map +7 -0
  348. package/dist/security/types.js +17 -0
  349. package/dist/security/types.js.map +7 -0
  350. package/dist/security/vuln/client.js +355 -0
  351. package/dist/security/vuln/client.js.map +7 -0
  352. package/dist/security/vuln/client.test.js +288 -0
  353. package/dist/security/vuln/client.test.js.map +7 -0
  354. package/dist/security/vuln/epss-client.js +95 -0
  355. package/dist/security/vuln/epss-client.js.map +7 -0
  356. package/dist/security/vuln/index.js +29 -0
  357. package/dist/security/vuln/index.js.map +7 -0
  358. package/dist/security/vuln/osv-adapter.js +321 -0
  359. package/dist/security/vuln/osv-adapter.js.map +7 -0
  360. package/dist/security/vuln/osv-adapter.test.js +391 -0
  361. package/dist/security/vuln/osv-adapter.test.js.map +7 -0
  362. package/dist/security/vuln/types.js +17 -0
  363. package/dist/security/vuln/types.js.map +7 -0
  364. package/dist/types.js.map +2 -2
  365. package/package.json +5 -2
@@ -0,0 +1,394 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (let key of __getOwnPropNames(from))
11
+ if (!__hasOwnProp.call(to, key) && key !== except)
12
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
13
+ }
14
+ return to;
15
+ };
16
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
17
+ // If the importer is in node compatibility mode or this is not an ESM
18
+ // file that has been converted to a CommonJS file using a Babel-
19
+ // compatible transform (i.e. "__esModule" has not been set), then set
20
+ // "default" to the CommonJS "module.exports" for node compatibility.
21
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
+ mod
23
+ ));
24
+ var import_vitest = require("vitest");
25
+ var fs = __toESM(require("fs"));
26
+ var path = __toESM(require("path"));
27
+ var os = __toESM(require("os"));
28
+ var import_reachability = require("./reachability");
29
+ var import_database = require("../db/database");
30
+ var import_config = require("../config");
31
+ (0, import_vitest.describe)("ReachabilityAnalyzer", () => {
32
+ let db;
33
+ let config;
34
+ let analyzer;
35
+ let tmpDir;
36
+ (0, import_vitest.beforeEach)(() => {
37
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "kirograph-reachability-test-"));
38
+ db = new import_database.GraphDatabase(tmpDir);
39
+ db.applySecuritySchema();
40
+ config = (0, import_config.createDefaultConfig)();
41
+ config.enableArchitecture = true;
42
+ config.enableSecurity = true;
43
+ analyzer = new import_reachability.ReachabilityAnalyzer(db, config);
44
+ });
45
+ (0, import_vitest.afterEach)(() => {
46
+ db.close();
47
+ fs.rmSync(tmpDir, { recursive: true, force: true });
48
+ });
49
+ function createNode(id, kind, opts) {
50
+ const node = {
51
+ id,
52
+ kind,
53
+ name: opts?.name ?? id,
54
+ qualifiedName: opts?.qualifiedName ?? id,
55
+ filePath: opts?.filePath ?? `src/${id}.ts`,
56
+ language: "typescript",
57
+ startLine: 0,
58
+ endLine: 0,
59
+ startColumn: 0,
60
+ endColumn: 0,
61
+ isExported: opts?.isExported ?? false,
62
+ updatedAt: Date.now()
63
+ };
64
+ db.upsertNode(node);
65
+ }
66
+ function createEdge(source, target, kind) {
67
+ db.insertEdge({ source, target, kind });
68
+ }
69
+ function createDependencyNode(name) {
70
+ const nodeId = `dep:npm:${name}`;
71
+ createNode(nodeId, "dependency", { name });
72
+ const rawDb = db.getRawDb();
73
+ rawDb.run(
74
+ `INSERT OR REPLACE INTO sec_dependencies
75
+ (node_id, ecosystem, package_name, declared_constraint, resolved_version, scope, source_manifests)
76
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
77
+ [nodeId, "npm", name, "^1.0.0", "1.0.0", "production", '["package.json"]']
78
+ );
79
+ return nodeId;
80
+ }
81
+ function createVulnerabilityNode(cveId, depNodeId) {
82
+ const nodeId = `vuln:${cveId}`;
83
+ createNode(nodeId, "vulnerability", { name: cveId });
84
+ const rawDb = db.getRawDb();
85
+ rawDb.run(
86
+ `INSERT OR REPLACE INTO sec_vulnerabilities
87
+ (node_id, cve_id, severity_score, affected_ranges, fixed_version, summary, source_database)
88
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
89
+ [nodeId, cveId, 7.5, "[]", "1.0.1", "Test vulnerability", "OSV"]
90
+ );
91
+ createEdge(depNodeId, nodeId, "has_vulnerability");
92
+ return nodeId;
93
+ }
94
+ function createEntryPoint(id, kind = "route") {
95
+ createNode(id, kind, { isExported: true });
96
+ }
97
+ function assignLayer(filePath, layerId) {
98
+ const rawDb = db.getRawDb();
99
+ rawDb.run(
100
+ `INSERT OR IGNORE INTO arch_layers (id, name, source, patterns, updated_at)
101
+ VALUES (?, ?, 'auto', '[]', ?)`,
102
+ [layerId, layerId, Date.now()]
103
+ );
104
+ rawDb.run(
105
+ `INSERT OR REPLACE INTO arch_file_layers (file_path, layer_id, confidence, matched_pattern)
106
+ VALUES (?, ?, 1.0, '**')`,
107
+ [filePath, layerId]
108
+ );
109
+ }
110
+ (0, import_vitest.describe)("analyze() - core reachability", () => {
111
+ (0, import_vitest.it)("should return affected when a path exists from entry point to dependency", async () => {
112
+ createEntryPoint("ep1");
113
+ createNode("mid1", "function");
114
+ const depId = createDependencyNode("vulnerable-pkg");
115
+ const vulnId = createVulnerabilityNode("CVE-2023-001", depId);
116
+ createEdge("ep1", "mid1", "calls");
117
+ createEdge("mid1", depId, "imports");
118
+ const result = await analyzer.analyze(vulnId);
119
+ (0, import_vitest.expect)(result.verdict).toBe("affected");
120
+ (0, import_vitest.expect)(result.paths.length).toBe(1);
121
+ (0, import_vitest.expect)(result.paths[0].entryPoint).toBe("ep1");
122
+ (0, import_vitest.expect)(result.paths[0].path).toEqual(["ep1", "mid1", depId]);
123
+ (0, import_vitest.expect)(result.reachingEntryPointCount).toBe(1);
124
+ });
125
+ (0, import_vitest.it)("should return not_affected when no path exists", async () => {
126
+ createEntryPoint("ep1");
127
+ createNode("mid1", "function");
128
+ const depId = createDependencyNode("isolated-pkg");
129
+ const vulnId = createVulnerabilityNode("CVE-2023-002", depId);
130
+ createEdge("ep1", "mid1", "calls");
131
+ const result = await analyzer.analyze(vulnId);
132
+ (0, import_vitest.expect)(result.verdict).toBe("not_affected");
133
+ (0, import_vitest.expect)(result.paths.length).toBe(0);
134
+ (0, import_vitest.expect)(result.reachingEntryPointCount).toBe(0);
135
+ });
136
+ (0, import_vitest.it)("should return under_investigation when unresolved imports are encountered", async () => {
137
+ createEntryPoint("ep1");
138
+ createNode("unresolved1", "import");
139
+ const depId = createDependencyNode("maybe-pkg");
140
+ const vulnId = createVulnerabilityNode("CVE-2023-003", depId);
141
+ createEdge("ep1", "unresolved1", "imports");
142
+ const result = await analyzer.analyze(vulnId);
143
+ (0, import_vitest.expect)(result.verdict).toBe("under_investigation");
144
+ (0, import_vitest.expect)(result.unresolvedSymbols).toContain("unresolved1");
145
+ (0, import_vitest.expect)(result.paths.length).toBe(0);
146
+ });
147
+ (0, import_vitest.it)("should handle cycles without infinite loops", async () => {
148
+ createEntryPoint("ep1");
149
+ createNode("nodeA", "function");
150
+ createNode("nodeB", "function");
151
+ const depId = createDependencyNode("unreachable-pkg");
152
+ const vulnId = createVulnerabilityNode("CVE-2023-004", depId);
153
+ createEdge("ep1", "nodeA", "calls");
154
+ createEdge("nodeA", "nodeB", "calls");
155
+ createEdge("nodeB", "nodeA", "calls");
156
+ const result = await analyzer.analyze(vulnId);
157
+ (0, import_vitest.expect)(result.verdict).toBe("not_affected");
158
+ (0, import_vitest.expect)(result.paths.length).toBe(0);
159
+ });
160
+ (0, import_vitest.it)("should visit each node at most once", async () => {
161
+ createEntryPoint("ep1");
162
+ createNode("nodeA", "function");
163
+ createNode("nodeB", "function");
164
+ const depId = createDependencyNode("shared-pkg");
165
+ const vulnId = createVulnerabilityNode("CVE-2023-005", depId);
166
+ createEdge("ep1", "nodeA", "calls");
167
+ createEdge("ep1", "nodeB", "calls");
168
+ createEdge("nodeA", depId, "imports");
169
+ createEdge("nodeB", "nodeA", "calls");
170
+ const result = await analyzer.analyze(vulnId);
171
+ (0, import_vitest.expect)(result.verdict).toBe("affected");
172
+ (0, import_vitest.expect)(result.paths[0].path).toEqual(["ep1", "nodeA", depId]);
173
+ });
174
+ (0, import_vitest.it)("should traverse calls, imports, and references edges", async () => {
175
+ createEntryPoint("ep1");
176
+ createNode("mid1", "function");
177
+ createNode("mid2", "class");
178
+ const depId = createDependencyNode("ref-pkg");
179
+ const vulnId = createVulnerabilityNode("CVE-2023-006", depId);
180
+ createEdge("ep1", "mid1", "calls");
181
+ createEdge("mid1", "mid2", "references");
182
+ createEdge("mid2", depId, "imports");
183
+ const result = await analyzer.analyze(vulnId);
184
+ (0, import_vitest.expect)(result.verdict).toBe("affected");
185
+ (0, import_vitest.expect)(result.paths[0].path).toEqual(["ep1", "mid1", "mid2", depId]);
186
+ });
187
+ (0, import_vitest.it)("should return under_investigation when no dependency is linked", async () => {
188
+ const vulnId = "vuln:CVE-2023-orphan";
189
+ createNode(vulnId, "vulnerability", { name: "CVE-2023-orphan" });
190
+ const rawDb = db.getRawDb();
191
+ rawDb.run(
192
+ `INSERT OR REPLACE INTO sec_vulnerabilities
193
+ (node_id, cve_id, severity_score, affected_ranges, summary, source_database)
194
+ VALUES (?, ?, ?, ?, ?, ?)`,
195
+ [vulnId, "CVE-2023-orphan", 5, "[]", "Orphan vuln", "OSV"]
196
+ );
197
+ const result = await analyzer.analyze(vulnId);
198
+ (0, import_vitest.expect)(result.verdict).toBe("under_investigation");
199
+ });
200
+ (0, import_vitest.it)("should return not_affected when no entry points exist", async () => {
201
+ const depId = createDependencyNode("no-ep-pkg");
202
+ const vulnId = createVulnerabilityNode("CVE-2023-007", depId);
203
+ const result = await analyzer.analyze(vulnId);
204
+ (0, import_vitest.expect)(result.verdict).toBe("not_affected");
205
+ });
206
+ (0, import_vitest.it)("should limit unresolved symbols to 50", async () => {
207
+ createEntryPoint("ep1");
208
+ const depId = createDependencyNode("many-unresolved-pkg");
209
+ const vulnId = createVulnerabilityNode("CVE-2023-008", depId);
210
+ for (let i = 0; i < 60; i++) {
211
+ const importId = `unresolved-import-${i}`;
212
+ createNode(importId, "import");
213
+ createEdge("ep1", importId, "imports");
214
+ }
215
+ const result = await analyzer.analyze(vulnId);
216
+ (0, import_vitest.expect)(result.verdict).toBe("under_investigation");
217
+ (0, import_vitest.expect)(result.unresolvedSymbols.length).toBeLessThanOrEqual(50);
218
+ });
219
+ (0, import_vitest.it)("should store result in sec_reachability table", async () => {
220
+ createEntryPoint("ep1");
221
+ const depId = createDependencyNode("stored-pkg");
222
+ const vulnId = createVulnerabilityNode("CVE-2023-009", depId);
223
+ createEdge("ep1", depId, "imports");
224
+ await analyzer.analyze(vulnId);
225
+ const rawDb = db.getRawDb();
226
+ const row = rawDb.get(
227
+ `SELECT * FROM sec_reachability WHERE vulnerability_node_id = ?`,
228
+ [vulnId]
229
+ );
230
+ (0, import_vitest.expect)(row).not.toBeNull();
231
+ (0, import_vitest.expect)(row.verdict).toBe("affected");
232
+ (0, import_vitest.expect)(row.reaching_entry_point_count).toBe(1);
233
+ });
234
+ });
235
+ (0, import_vitest.describe)("analyze() - multi-entry-point paths", () => {
236
+ (0, import_vitest.it)("should record shortest path from each reaching entry point", async () => {
237
+ createEntryPoint("ep1");
238
+ createEntryPoint("ep2");
239
+ createNode("mid1", "function");
240
+ createNode("mid2", "function");
241
+ const depId = createDependencyNode("multi-ep-pkg");
242
+ const vulnId = createVulnerabilityNode("CVE-2023-010", depId);
243
+ createEdge("ep1", "mid1", "calls");
244
+ createEdge("mid1", depId, "imports");
245
+ createEdge("ep2", depId, "imports");
246
+ const result = await analyzer.analyze(vulnId);
247
+ (0, import_vitest.expect)(result.verdict).toBe("affected");
248
+ (0, import_vitest.expect)(result.reachingEntryPointCount).toBe(2);
249
+ (0, import_vitest.expect)(result.paths.length).toBe(2);
250
+ const ep1Path = result.paths.find((p) => p.entryPoint === "ep1");
251
+ const ep2Path = result.paths.find((p) => p.entryPoint === "ep2");
252
+ (0, import_vitest.expect)(ep1Path).toBeDefined();
253
+ (0, import_vitest.expect)(ep1Path.path).toEqual(["ep1", "mid1", depId]);
254
+ (0, import_vitest.expect)(ep2Path).toBeDefined();
255
+ (0, import_vitest.expect)(ep2Path.path).toEqual(["ep2", depId]);
256
+ });
257
+ (0, import_vitest.it)("should include total count of reaching entry points", async () => {
258
+ const depId = createDependencyNode("counted-pkg");
259
+ const vulnId = createVulnerabilityNode("CVE-2023-011", depId);
260
+ for (let i = 0; i < 5; i++) {
261
+ createEntryPoint(`ep${i}`);
262
+ if (i < 3) {
263
+ createEdge(`ep${i}`, depId, "imports");
264
+ }
265
+ }
266
+ const result = await analyzer.analyze(vulnId);
267
+ (0, import_vitest.expect)(result.verdict).toBe("affected");
268
+ (0, import_vitest.expect)(result.reachingEntryPointCount).toBe(3);
269
+ (0, import_vitest.expect)(result.paths.length).toBe(3);
270
+ });
271
+ (0, import_vitest.it)("should handle exported functions as entry points", async () => {
272
+ createEntryPoint("exported-fn", "function");
273
+ const depId = createDependencyNode("fn-ep-pkg");
274
+ const vulnId = createVulnerabilityNode("CVE-2023-012", depId);
275
+ createEdge("exported-fn", depId, "references");
276
+ const result = await analyzer.analyze(vulnId);
277
+ (0, import_vitest.expect)(result.verdict).toBe("affected");
278
+ (0, import_vitest.expect)(result.paths[0].entryPoint).toBe("exported-fn");
279
+ });
280
+ });
281
+ (0, import_vitest.describe)("getImpactSummary()", () => {
282
+ (0, import_vitest.it)("should return null for non-affected vulnerabilities", async () => {
283
+ createEntryPoint("ep1");
284
+ const depId = createDependencyNode("not-affected-pkg");
285
+ const vulnId = createVulnerabilityNode("CVE-2023-020", depId);
286
+ await analyzer.analyze(vulnId);
287
+ const summary = await analyzer.getImpactSummary(vulnId);
288
+ (0, import_vitest.expect)(summary).toBeNull();
289
+ });
290
+ (0, import_vitest.it)("should identify affected layers on reachable paths", async () => {
291
+ createEntryPoint("ep1");
292
+ createNode("mid1", "function", { filePath: "src/api/handler.ts" });
293
+ createNode("mid2", "function", { filePath: "src/service/logic.ts" });
294
+ const depId = createDependencyNode("layered-pkg");
295
+ const vulnId = createVulnerabilityNode("CVE-2023-021", depId);
296
+ createEdge("ep1", "mid1", "calls");
297
+ createEdge("mid1", "mid2", "calls");
298
+ createEdge("mid2", depId, "imports");
299
+ assignLayer("src/api/handler.ts", "api");
300
+ assignLayer("src/service/logic.ts", "service");
301
+ await analyzer.analyze(vulnId);
302
+ const summary = await analyzer.getImpactSummary(vulnId);
303
+ (0, import_vitest.expect)(summary).not.toBeNull();
304
+ (0, import_vitest.expect)(summary.affectedLayers).toContain("api");
305
+ (0, import_vitest.expect)(summary.affectedLayers).toContain("service");
306
+ });
307
+ (0, import_vitest.it)("should identify all affected entry points", async () => {
308
+ createEntryPoint("ep1");
309
+ createEntryPoint("ep2");
310
+ const depId = createDependencyNode("multi-ep-impact-pkg");
311
+ const vulnId = createVulnerabilityNode("CVE-2023-022", depId);
312
+ createEdge("ep1", depId, "imports");
313
+ createEdge("ep2", depId, "imports");
314
+ await analyzer.analyze(vulnId);
315
+ const summary = await analyzer.getImpactSummary(vulnId);
316
+ (0, import_vitest.expect)(summary).not.toBeNull();
317
+ (0, import_vitest.expect)(summary.affectedEntryPoints).toContain("ep1");
318
+ (0, import_vitest.expect)(summary.affectedEntryPoints).toContain("ep2");
319
+ });
320
+ (0, import_vitest.it)("should cap distinct path count at 100", async () => {
321
+ const depId = createDependencyNode("many-paths-pkg");
322
+ const vulnId = createVulnerabilityNode("CVE-2023-023", depId);
323
+ for (let i = 0; i < 110; i++) {
324
+ createEntryPoint(`ep${i}`);
325
+ createEdge(`ep${i}`, depId, "imports");
326
+ }
327
+ await analyzer.analyze(vulnId);
328
+ const summary = await analyzer.getImpactSummary(vulnId);
329
+ (0, import_vitest.expect)(summary).not.toBeNull();
330
+ (0, import_vitest.expect)(summary.distinctPathCount).toBeLessThanOrEqual(100);
331
+ });
332
+ (0, import_vitest.it)("should classify symbols with no assigned layer as unclassified", async () => {
333
+ createEntryPoint("ep1");
334
+ createNode("mid1", "function", { filePath: "src/unknown/file.ts" });
335
+ const depId = createDependencyNode("unclassified-pkg");
336
+ const vulnId = createVulnerabilityNode("CVE-2023-024", depId);
337
+ createEdge("ep1", "mid1", "calls");
338
+ createEdge("mid1", depId, "imports");
339
+ await analyzer.analyze(vulnId);
340
+ const summary = await analyzer.getImpactSummary(vulnId);
341
+ (0, import_vitest.expect)(summary).not.toBeNull();
342
+ (0, import_vitest.expect)(summary.affectedLayers).toContain("unclassified");
343
+ });
344
+ (0, import_vitest.it)("should omit layer classification when enableArchitecture is false", async () => {
345
+ config.enableArchitecture = false;
346
+ analyzer = new import_reachability.ReachabilityAnalyzer(db, config);
347
+ createEntryPoint("ep1");
348
+ createNode("mid1", "function", { filePath: "src/api/handler.ts" });
349
+ const depId = createDependencyNode("no-arch-pkg");
350
+ const vulnId = createVulnerabilityNode("CVE-2023-025", depId);
351
+ createEdge("ep1", "mid1", "calls");
352
+ createEdge("mid1", depId, "imports");
353
+ assignLayer("src/api/handler.ts", "api");
354
+ await analyzer.analyze(vulnId);
355
+ const summary = await analyzer.getImpactSummary(vulnId);
356
+ (0, import_vitest.expect)(summary).not.toBeNull();
357
+ (0, import_vitest.expect)(summary.affectedLayers).toEqual([]);
358
+ });
359
+ (0, import_vitest.it)("should store impact summary in sec_impact table", async () => {
360
+ createEntryPoint("ep1");
361
+ const depId = createDependencyNode("stored-impact-pkg");
362
+ const vulnId = createVulnerabilityNode("CVE-2023-026", depId);
363
+ createEdge("ep1", depId, "imports");
364
+ await analyzer.analyze(vulnId);
365
+ await analyzer.getImpactSummary(vulnId);
366
+ const rawDb = db.getRawDb();
367
+ const row = rawDb.get(
368
+ `SELECT * FROM sec_impact WHERE vulnerability_node_id = ?`,
369
+ [vulnId]
370
+ );
371
+ (0, import_vitest.expect)(row).not.toBeNull();
372
+ (0, import_vitest.expect)(row.distinct_path_count).toBeGreaterThan(0);
373
+ });
374
+ });
375
+ (0, import_vitest.describe)("analyzeAll()", () => {
376
+ (0, import_vitest.it)("should analyze all vulnerability nodes", async () => {
377
+ createEntryPoint("ep1");
378
+ const dep1 = createDependencyNode("pkg1");
379
+ const dep2 = createDependencyNode("pkg2");
380
+ const vuln1 = createVulnerabilityNode("CVE-2023-030", dep1);
381
+ const vuln2 = createVulnerabilityNode("CVE-2023-031", dep2);
382
+ createEdge("ep1", dep1, "imports");
383
+ const results = await analyzer.analyzeAll();
384
+ (0, import_vitest.expect)(results.size).toBe(2);
385
+ (0, import_vitest.expect)(results.get(vuln1).verdict).toBe("affected");
386
+ (0, import_vitest.expect)(results.get(vuln2).verdict).toBe("not_affected");
387
+ });
388
+ (0, import_vitest.it)("should return empty map when no vulnerabilities exist", async () => {
389
+ const results = await analyzer.analyzeAll();
390
+ (0, import_vitest.expect)(results.size).toBe(0);
391
+ });
392
+ });
393
+ });
394
+ //# sourceMappingURL=reachability.test.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/security/reachability.test.ts"],
4
+ "sourcesContent": ["/**\n * Tests for ReachabilityAnalyzer\n *\n * Verifies BFS traversal, verdict assignment, multi-entry-point path recording,\n * and impact analysis with architecture context.\n *\n * Requirements: 4.1, 4.2, 4.3, 4.4, 4.6, 5.1, 5.2, 5.3, 5.4, 5.5\n */\nimport { describe, it, expect, beforeEach, afterEach } from 'vitest';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport * as os from 'os';\nimport { ReachabilityAnalyzer } from './reachability';\nimport { GraphDatabase } from '../db/database';\nimport { createDefaultConfig, type KiroGraphConfig } from '../config';\nimport type { Node, Edge } from '../types';\n\ndescribe('ReachabilityAnalyzer', () => {\n let db: GraphDatabase;\n let config: KiroGraphConfig;\n let analyzer: ReachabilityAnalyzer;\n let tmpDir: string;\n\n beforeEach(() => {\n tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'kirograph-reachability-test-'));\n db = new GraphDatabase(tmpDir);\n db.applySecuritySchema();\n config = createDefaultConfig();\n config.enableArchitecture = true;\n config.enableSecurity = true;\n analyzer = new ReachabilityAnalyzer(db, config);\n });\n\n afterEach(() => {\n db.close();\n fs.rmSync(tmpDir, { recursive: true, force: true });\n });\n\n // \u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n function createNode(id: string, kind: Node['kind'], opts?: Partial<Node>): void {\n const node: Node = {\n id,\n kind,\n name: opts?.name ?? id,\n qualifiedName: opts?.qualifiedName ?? id,\n filePath: opts?.filePath ?? `src/${id}.ts`,\n language: 'typescript',\n startLine: 0,\n endLine: 0,\n startColumn: 0,\n endColumn: 0,\n isExported: opts?.isExported ?? false,\n updatedAt: Date.now(),\n };\n db.upsertNode(node);\n }\n\n function createEdge(source: string, target: string, kind: Edge['kind']): void {\n db.insertEdge({ source, target, kind });\n }\n\n function createDependencyNode(name: string): string {\n const nodeId = `dep:npm:${name}`;\n createNode(nodeId, 'dependency', { name });\n const rawDb = db.getRawDb();\n rawDb.run(\n `INSERT OR REPLACE INTO sec_dependencies\n (node_id, ecosystem, package_name, declared_constraint, resolved_version, scope, source_manifests)\n VALUES (?, ?, ?, ?, ?, ?, ?)`,\n [nodeId, 'npm', name, '^1.0.0', '1.0.0', 'production', '[\"package.json\"]'],\n );\n return nodeId;\n }\n\n function createVulnerabilityNode(cveId: string, depNodeId: string): string {\n const nodeId = `vuln:${cveId}`;\n createNode(nodeId, 'vulnerability', { name: cveId });\n const rawDb = db.getRawDb();\n rawDb.run(\n `INSERT OR REPLACE INTO sec_vulnerabilities\n (node_id, cve_id, severity_score, affected_ranges, fixed_version, summary, source_database)\n VALUES (?, ?, ?, ?, ?, ?, ?)`,\n [nodeId, cveId, 7.5, '[]', '1.0.1', 'Test vulnerability', 'OSV'],\n );\n // Create has_vulnerability edge: dependency \u2192 vulnerability\n createEdge(depNodeId, nodeId, 'has_vulnerability');\n return nodeId;\n }\n\n function createEntryPoint(id: string, kind: 'route' | 'function' = 'route'): void {\n createNode(id, kind, { isExported: true });\n }\n\n function assignLayer(filePath: string, layerId: string): void {\n const rawDb = db.getRawDb();\n // Ensure the layer exists in arch_layers (FK constraint)\n rawDb.run(\n `INSERT OR IGNORE INTO arch_layers (id, name, source, patterns, updated_at)\n VALUES (?, ?, 'auto', '[]', ?)`,\n [layerId, layerId, Date.now()],\n );\n rawDb.run(\n `INSERT OR REPLACE INTO arch_file_layers (file_path, layer_id, confidence, matched_pattern)\n VALUES (?, ?, 1.0, '**')`,\n [filePath, layerId],\n );\n }\n\n // \u2500\u2500 Tests: Core Reachability Traversal (Task 7.1) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n describe('analyze() - core reachability', () => {\n it('should return affected when a path exists from entry point to dependency', async () => {\n // Graph: entryPoint \u2192 intermediate \u2192 dependency\n createEntryPoint('ep1');\n createNode('mid1', 'function');\n const depId = createDependencyNode('vulnerable-pkg');\n const vulnId = createVulnerabilityNode('CVE-2023-001', depId);\n\n createEdge('ep1', 'mid1', 'calls');\n createEdge('mid1', depId, 'imports');\n\n const result = await analyzer.analyze(vulnId);\n\n expect(result.verdict).toBe('affected');\n expect(result.paths.length).toBe(1);\n expect(result.paths[0].entryPoint).toBe('ep1');\n expect(result.paths[0].path).toEqual(['ep1', 'mid1', depId]);\n expect(result.reachingEntryPointCount).toBe(1);\n });\n\n it('should return not_affected when no path exists', async () => {\n // Graph: entryPoint \u2192 mid1 (no connection to dep)\n createEntryPoint('ep1');\n createNode('mid1', 'function');\n const depId = createDependencyNode('isolated-pkg');\n const vulnId = createVulnerabilityNode('CVE-2023-002', depId);\n\n createEdge('ep1', 'mid1', 'calls');\n // No edge from mid1 to depId\n\n const result = await analyzer.analyze(vulnId);\n\n expect(result.verdict).toBe('not_affected');\n expect(result.paths.length).toBe(0);\n expect(result.reachingEntryPointCount).toBe(0);\n });\n\n it('should return under_investigation when unresolved imports are encountered', async () => {\n // Graph: entryPoint \u2192 unresolvedImport (import node with no outgoing edges)\n createEntryPoint('ep1');\n createNode('unresolved1', 'import');\n const depId = createDependencyNode('maybe-pkg');\n const vulnId = createVulnerabilityNode('CVE-2023-003', depId);\n\n createEdge('ep1', 'unresolved1', 'imports');\n // unresolved1 has no outgoing edges \u2192 unresolved import\n\n const result = await analyzer.analyze(vulnId);\n\n expect(result.verdict).toBe('under_investigation');\n expect(result.unresolvedSymbols).toContain('unresolved1');\n expect(result.paths.length).toBe(0);\n });\n\n it('should handle cycles without infinite loops', async () => {\n // Graph: ep1 \u2192 A \u2192 B \u2192 A (cycle), no path to dep\n createEntryPoint('ep1');\n createNode('nodeA', 'function');\n createNode('nodeB', 'function');\n const depId = createDependencyNode('unreachable-pkg');\n const vulnId = createVulnerabilityNode('CVE-2023-004', depId);\n\n createEdge('ep1', 'nodeA', 'calls');\n createEdge('nodeA', 'nodeB', 'calls');\n createEdge('nodeB', 'nodeA', 'calls'); // cycle\n\n const result = await analyzer.analyze(vulnId);\n\n expect(result.verdict).toBe('not_affected');\n expect(result.paths.length).toBe(0);\n });\n\n it('should visit each node at most once', async () => {\n // Graph: ep1 \u2192 A \u2192 dep, ep1 \u2192 B \u2192 A \u2192 dep\n // A should only be visited once per BFS from ep1\n createEntryPoint('ep1');\n createNode('nodeA', 'function');\n createNode('nodeB', 'function');\n const depId = createDependencyNode('shared-pkg');\n const vulnId = createVulnerabilityNode('CVE-2023-005', depId);\n\n createEdge('ep1', 'nodeA', 'calls');\n createEdge('ep1', 'nodeB', 'calls');\n createEdge('nodeA', depId, 'imports');\n createEdge('nodeB', 'nodeA', 'calls');\n\n const result = await analyzer.analyze(vulnId);\n\n expect(result.verdict).toBe('affected');\n // Should find shortest path: ep1 \u2192 nodeA \u2192 dep\n expect(result.paths[0].path).toEqual(['ep1', 'nodeA', depId]);\n });\n\n it('should traverse calls, imports, and references edges', async () => {\n createEntryPoint('ep1');\n createNode('mid1', 'function');\n createNode('mid2', 'class');\n const depId = createDependencyNode('ref-pkg');\n const vulnId = createVulnerabilityNode('CVE-2023-006', depId);\n\n createEdge('ep1', 'mid1', 'calls');\n createEdge('mid1', 'mid2', 'references');\n createEdge('mid2', depId, 'imports');\n\n const result = await analyzer.analyze(vulnId);\n\n expect(result.verdict).toBe('affected');\n expect(result.paths[0].path).toEqual(['ep1', 'mid1', 'mid2', depId]);\n });\n\n it('should return under_investigation when no dependency is linked', async () => {\n // Vulnerability with no has_vulnerability edge\n const vulnId = 'vuln:CVE-2023-orphan';\n createNode(vulnId, 'vulnerability', { name: 'CVE-2023-orphan' });\n const rawDb = db.getRawDb();\n rawDb.run(\n `INSERT OR REPLACE INTO sec_vulnerabilities\n (node_id, cve_id, severity_score, affected_ranges, summary, source_database)\n VALUES (?, ?, ?, ?, ?, ?)`,\n [vulnId, 'CVE-2023-orphan', 5.0, '[]', 'Orphan vuln', 'OSV'],\n );\n\n const result = await analyzer.analyze(vulnId);\n\n expect(result.verdict).toBe('under_investigation');\n });\n\n it('should return not_affected when no entry points exist', async () => {\n // No entry points in the graph\n const depId = createDependencyNode('no-ep-pkg');\n const vulnId = createVulnerabilityNode('CVE-2023-007', depId);\n\n const result = await analyzer.analyze(vulnId);\n\n expect(result.verdict).toBe('not_affected');\n });\n\n it('should limit unresolved symbols to 50', async () => {\n createEntryPoint('ep1');\n const depId = createDependencyNode('many-unresolved-pkg');\n const vulnId = createVulnerabilityNode('CVE-2023-008', depId);\n\n // Create 60 unresolved import nodes\n for (let i = 0; i < 60; i++) {\n const importId = `unresolved-import-${i}`;\n createNode(importId, 'import');\n createEdge('ep1', importId, 'imports');\n }\n\n const result = await analyzer.analyze(vulnId);\n\n expect(result.verdict).toBe('under_investigation');\n expect(result.unresolvedSymbols.length).toBeLessThanOrEqual(50);\n });\n\n it('should store result in sec_reachability table', async () => {\n createEntryPoint('ep1');\n const depId = createDependencyNode('stored-pkg');\n const vulnId = createVulnerabilityNode('CVE-2023-009', depId);\n createEdge('ep1', depId, 'imports');\n\n await analyzer.analyze(vulnId);\n\n const rawDb = db.getRawDb();\n const row = rawDb.get(\n `SELECT * FROM sec_reachability WHERE vulnerability_node_id = ?`,\n [vulnId],\n );\n expect(row).not.toBeNull();\n expect(row.verdict).toBe('affected');\n expect(row.reaching_entry_point_count).toBe(1);\n });\n });\n\n // \u2500\u2500 Tests: Multi-Entry-Point Path Recording (Task 7.2) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n describe('analyze() - multi-entry-point paths', () => {\n it('should record shortest path from each reaching entry point', async () => {\n createEntryPoint('ep1');\n createEntryPoint('ep2');\n createNode('mid1', 'function');\n createNode('mid2', 'function');\n const depId = createDependencyNode('multi-ep-pkg');\n const vulnId = createVulnerabilityNode('CVE-2023-010', depId);\n\n // ep1 \u2192 mid1 \u2192 dep (length 3)\n createEdge('ep1', 'mid1', 'calls');\n createEdge('mid1', depId, 'imports');\n\n // ep2 \u2192 dep (length 2, shorter)\n createEdge('ep2', depId, 'imports');\n\n const result = await analyzer.analyze(vulnId);\n\n expect(result.verdict).toBe('affected');\n expect(result.reachingEntryPointCount).toBe(2);\n expect(result.paths.length).toBe(2);\n\n const ep1Path = result.paths.find(p => p.entryPoint === 'ep1');\n const ep2Path = result.paths.find(p => p.entryPoint === 'ep2');\n\n expect(ep1Path).toBeDefined();\n expect(ep1Path!.path).toEqual(['ep1', 'mid1', depId]);\n\n expect(ep2Path).toBeDefined();\n expect(ep2Path!.path).toEqual(['ep2', depId]);\n });\n\n it('should include total count of reaching entry points', async () => {\n const depId = createDependencyNode('counted-pkg');\n const vulnId = createVulnerabilityNode('CVE-2023-011', depId);\n\n // Create 5 entry points, 3 of which reach the dep\n for (let i = 0; i < 5; i++) {\n createEntryPoint(`ep${i}`);\n if (i < 3) {\n createEdge(`ep${i}`, depId, 'imports');\n }\n }\n\n const result = await analyzer.analyze(vulnId);\n\n expect(result.verdict).toBe('affected');\n expect(result.reachingEntryPointCount).toBe(3);\n expect(result.paths.length).toBe(3);\n });\n\n it('should handle exported functions as entry points', async () => {\n createEntryPoint('exported-fn', 'function');\n const depId = createDependencyNode('fn-ep-pkg');\n const vulnId = createVulnerabilityNode('CVE-2023-012', depId);\n\n createEdge('exported-fn', depId, 'references');\n\n const result = await analyzer.analyze(vulnId);\n\n expect(result.verdict).toBe('affected');\n expect(result.paths[0].entryPoint).toBe('exported-fn');\n });\n });\n\n // \u2500\u2500 Tests: Impact Analysis (Task 7.3) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n describe('getImpactSummary()', () => {\n it('should return null for non-affected vulnerabilities', async () => {\n createEntryPoint('ep1');\n const depId = createDependencyNode('not-affected-pkg');\n const vulnId = createVulnerabilityNode('CVE-2023-020', depId);\n // No path to dep \u2192 not_affected\n\n await analyzer.analyze(vulnId);\n const summary = await analyzer.getImpactSummary(vulnId);\n\n expect(summary).toBeNull();\n });\n\n it('should identify affected layers on reachable paths', async () => {\n createEntryPoint('ep1');\n createNode('mid1', 'function', { filePath: 'src/api/handler.ts' });\n createNode('mid2', 'function', { filePath: 'src/service/logic.ts' });\n const depId = createDependencyNode('layered-pkg');\n const vulnId = createVulnerabilityNode('CVE-2023-021', depId);\n\n createEdge('ep1', 'mid1', 'calls');\n createEdge('mid1', 'mid2', 'calls');\n createEdge('mid2', depId, 'imports');\n\n // Assign layers\n assignLayer('src/api/handler.ts', 'api');\n assignLayer('src/service/logic.ts', 'service');\n\n await analyzer.analyze(vulnId);\n const summary = await analyzer.getImpactSummary(vulnId);\n\n expect(summary).not.toBeNull();\n expect(summary!.affectedLayers).toContain('api');\n expect(summary!.affectedLayers).toContain('service');\n });\n\n it('should identify all affected entry points', async () => {\n createEntryPoint('ep1');\n createEntryPoint('ep2');\n const depId = createDependencyNode('multi-ep-impact-pkg');\n const vulnId = createVulnerabilityNode('CVE-2023-022', depId);\n\n createEdge('ep1', depId, 'imports');\n createEdge('ep2', depId, 'imports');\n\n await analyzer.analyze(vulnId);\n const summary = await analyzer.getImpactSummary(vulnId);\n\n expect(summary).not.toBeNull();\n expect(summary!.affectedEntryPoints).toContain('ep1');\n expect(summary!.affectedEntryPoints).toContain('ep2');\n });\n\n it('should cap distinct path count at 100', async () => {\n const depId = createDependencyNode('many-paths-pkg');\n const vulnId = createVulnerabilityNode('CVE-2023-023', depId);\n\n // Create 110 entry points each with a direct path\n for (let i = 0; i < 110; i++) {\n createEntryPoint(`ep${i}`);\n createEdge(`ep${i}`, depId, 'imports');\n }\n\n await analyzer.analyze(vulnId);\n const summary = await analyzer.getImpactSummary(vulnId);\n\n expect(summary).not.toBeNull();\n expect(summary!.distinctPathCount).toBeLessThanOrEqual(100);\n });\n\n it('should classify symbols with no assigned layer as unclassified', async () => {\n createEntryPoint('ep1');\n createNode('mid1', 'function', { filePath: 'src/unknown/file.ts' });\n const depId = createDependencyNode('unclassified-pkg');\n const vulnId = createVulnerabilityNode('CVE-2023-024', depId);\n\n createEdge('ep1', 'mid1', 'calls');\n createEdge('mid1', depId, 'imports');\n\n // No layer assigned to src/unknown/file.ts\n\n await analyzer.analyze(vulnId);\n const summary = await analyzer.getImpactSummary(vulnId);\n\n expect(summary).not.toBeNull();\n expect(summary!.affectedLayers).toContain('unclassified');\n });\n\n it('should omit layer classification when enableArchitecture is false', async () => {\n config.enableArchitecture = false;\n analyzer = new ReachabilityAnalyzer(db, config);\n\n createEntryPoint('ep1');\n createNode('mid1', 'function', { filePath: 'src/api/handler.ts' });\n const depId = createDependencyNode('no-arch-pkg');\n const vulnId = createVulnerabilityNode('CVE-2023-025', depId);\n\n createEdge('ep1', 'mid1', 'calls');\n createEdge('mid1', depId, 'imports');\n\n assignLayer('src/api/handler.ts', 'api');\n\n await analyzer.analyze(vulnId);\n const summary = await analyzer.getImpactSummary(vulnId);\n\n expect(summary).not.toBeNull();\n // When enableArchitecture is false, affectedLayers should be empty\n expect(summary!.affectedLayers).toEqual([]);\n });\n\n it('should store impact summary in sec_impact table', async () => {\n createEntryPoint('ep1');\n const depId = createDependencyNode('stored-impact-pkg');\n const vulnId = createVulnerabilityNode('CVE-2023-026', depId);\n createEdge('ep1', depId, 'imports');\n\n await analyzer.analyze(vulnId);\n await analyzer.getImpactSummary(vulnId);\n\n const rawDb = db.getRawDb();\n const row = rawDb.get(\n `SELECT * FROM sec_impact WHERE vulnerability_node_id = ?`,\n [vulnId],\n );\n expect(row).not.toBeNull();\n expect(row.distinct_path_count).toBeGreaterThan(0);\n });\n });\n\n // \u2500\u2500 Tests: analyzeAll() \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n describe('analyzeAll()', () => {\n it('should analyze all vulnerability nodes', async () => {\n createEntryPoint('ep1');\n const dep1 = createDependencyNode('pkg1');\n const dep2 = createDependencyNode('pkg2');\n const vuln1 = createVulnerabilityNode('CVE-2023-030', dep1);\n const vuln2 = createVulnerabilityNode('CVE-2023-031', dep2);\n\n createEdge('ep1', dep1, 'imports');\n // dep2 is not reachable\n\n const results = await analyzer.analyzeAll();\n\n expect(results.size).toBe(2);\n expect(results.get(vuln1)!.verdict).toBe('affected');\n expect(results.get(vuln2)!.verdict).toBe('not_affected');\n });\n\n it('should return empty map when no vulnerabilities exist', async () => {\n const results = await analyzer.analyzeAll();\n expect(results.size).toBe(0);\n });\n });\n});\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,126 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var remediation_exports = {};
20
+ __export(remediation_exports, {
21
+ RemediationTracker: () => RemediationTracker
22
+ });
23
+ module.exports = __toCommonJS(remediation_exports);
24
+ class RemediationTracker {
25
+ constructor(db) {
26
+ this.db = db;
27
+ }
28
+ /**
29
+ * Called when a vulnerability is first inserted.
30
+ * Sets first_detected_at if not already set, and fix_available_since if a
31
+ * fixed_version is provided and fix_available_since is not yet recorded.
32
+ */
33
+ markDetected(vulnNodeId, fixedVersion) {
34
+ const rawDb = this.db.getRawDb();
35
+ const now = Date.now();
36
+ rawDb.run(
37
+ `UPDATE sec_vulnerabilities
38
+ SET first_detected_at = COALESCE(first_detected_at, ?)
39
+ WHERE node_id = ?`,
40
+ [now, vulnNodeId]
41
+ );
42
+ if (fixedVersion) {
43
+ rawDb.run(
44
+ `UPDATE sec_vulnerabilities
45
+ SET fix_available_since = COALESCE(fix_available_since, ?)
46
+ WHERE node_id = ? AND fixed_version IS NOT NULL`,
47
+ [now, vulnNodeId]
48
+ );
49
+ }
50
+ }
51
+ /**
52
+ * Get remediation status for all open (non-remediated, non-suppressed)
53
+ * vulnerabilities that have been detected.
54
+ */
55
+ getStatus() {
56
+ const rawDb = this.db.getRawDb();
57
+ const now = Date.now();
58
+ const rows = rawDb.all(
59
+ `SELECT v.node_id, v.cve_id, v.severity_score, v.risk_score,
60
+ v.fixed_version, v.first_detected_at, v.fix_available_since,
61
+ v.suppressed_at, v.remediated_at,
62
+ d.package_name
63
+ FROM sec_vulnerabilities v
64
+ LEFT JOIN edges e ON e.target = v.node_id AND e.kind = 'has_vulnerability'
65
+ LEFT JOIN sec_dependencies d ON d.node_id = e.source
66
+ WHERE v.remediated_at IS NULL
67
+ AND v.suppressed_at IS NULL
68
+ AND v.first_detected_at IS NOT NULL`
69
+ );
70
+ return rows.map((row) => {
71
+ const firstDetectedAt = row.first_detected_at;
72
+ const daysOpen = firstDetectedAt !== null ? Math.floor((now - firstDetectedAt) / (1e3 * 60 * 60 * 24)) : null;
73
+ const fixAvailableSince = row.fix_available_since;
74
+ const daysWithFixAvailable = fixAvailableSince !== null ? Math.floor((now - fixAvailableSince) / (1e3 * 60 * 60 * 24)) : null;
75
+ const slaThreshold = this.getSlaThresholdDays(row.severity_score);
76
+ const slaDeadline = fixAvailableSince !== null ? fixAvailableSince + slaThreshold * 24 * 60 * 60 * 1e3 : firstDetectedAt !== null ? firstDetectedAt + slaThreshold * 24 * 60 * 60 * 1e3 : null;
77
+ let slaStatus;
78
+ let isOverdue = false;
79
+ if (!row.fixed_version && !fixAvailableSince) {
80
+ slaStatus = "no_fix";
81
+ isOverdue = false;
82
+ } else if (slaDeadline !== null && now > slaDeadline) {
83
+ slaStatus = "overdue";
84
+ isOverdue = true;
85
+ } else if (slaDeadline !== null && now > slaDeadline - slaThreshold * 0.5 * 24 * 60 * 60 * 1e3) {
86
+ slaStatus = "warning";
87
+ isOverdue = false;
88
+ } else {
89
+ slaStatus = "ok";
90
+ isOverdue = false;
91
+ }
92
+ return {
93
+ cveId: row.cve_id,
94
+ packageName: row.package_name ?? "unknown",
95
+ severity: row.severity_score,
96
+ riskScore: row.risk_score,
97
+ firstDetectedAt,
98
+ daysOpen,
99
+ fixAvailableSince,
100
+ daysWithFixAvailable,
101
+ isOverdue,
102
+ slaStatus,
103
+ slaDeadline
104
+ };
105
+ });
106
+ }
107
+ /**
108
+ * SLA thresholds by CVSS severity:
109
+ * critical (≥ 9) → 7 days
110
+ * high (≥ 7) → 30 days
111
+ * medium (≥ 4) → 90 days
112
+ * low / unknown → 180 days
113
+ */
114
+ getSlaThresholdDays(severity) {
115
+ if (severity === null) return 180;
116
+ if (severity >= 9) return 7;
117
+ if (severity >= 7) return 30;
118
+ if (severity >= 4) return 90;
119
+ return 180;
120
+ }
121
+ }
122
+ // Annotate the CommonJS export names for ESM import in node:
123
+ 0 && (module.exports = {
124
+ RemediationTracker
125
+ });
126
+ //# sourceMappingURL=remediation.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/security/remediation.ts"],
4
+ "sourcesContent": ["/**\n * Remediation SLA Tracker\n *\n * Tracks how long open vulnerabilities have been known, when a fix became\n * available, and whether remediation is overdue based on severity-based SLA\n * thresholds.\n *\n * SLA thresholds:\n * critical (CVSS \u2265 9) \u2192 7 days\n * high (CVSS \u2265 7) \u2192 30 days\n * medium (CVSS \u2265 4) \u2192 90 days\n * low / unknown \u2192 180 days\n */\n\nimport type { GraphDatabase } from '../db/database';\n\n// \u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface RemediationStatus {\n cveId: string;\n packageName: string;\n severity: number | null;\n riskScore: number | null;\n firstDetectedAt: number | null;\n daysOpen: number | null;\n fixAvailableSince: number | null;\n daysWithFixAvailable: number | null;\n isOverdue: boolean;\n slaStatus: 'ok' | 'warning' | 'overdue' | 'no_fix';\n slaDeadline: number | null; // epoch ms\n}\n\n// \u2500\u2500 RemediationTracker \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport class RemediationTracker {\n constructor(private readonly db: GraphDatabase) {}\n\n /**\n * Called when a vulnerability is first inserted.\n * Sets first_detected_at if not already set, and fix_available_since if a\n * fixed_version is provided and fix_available_since is not yet recorded.\n */\n markDetected(vulnNodeId: string, fixedVersion: string | null): void {\n const rawDb = this.db.getRawDb();\n const now = Date.now();\n\n // Set first_detected_at only if not already set\n rawDb.run(\n `UPDATE sec_vulnerabilities\n SET first_detected_at = COALESCE(first_detected_at, ?)\n WHERE node_id = ?`,\n [now, vulnNodeId],\n );\n\n // Set fix_available_since if a fix is known and not yet recorded\n if (fixedVersion) {\n rawDb.run(\n `UPDATE sec_vulnerabilities\n SET fix_available_since = COALESCE(fix_available_since, ?)\n WHERE node_id = ? AND fixed_version IS NOT NULL`,\n [now, vulnNodeId],\n );\n }\n }\n\n /**\n * Get remediation status for all open (non-remediated, non-suppressed)\n * vulnerabilities that have been detected.\n */\n getStatus(): RemediationStatus[] {\n const rawDb = this.db.getRawDb();\n const now = Date.now();\n\n const rows: Array<{\n node_id: string;\n cve_id: string;\n severity_score: number | null;\n risk_score: number | null;\n fixed_version: string | null;\n first_detected_at: number | null;\n fix_available_since: number | null;\n suppressed_at: number | null;\n remediated_at: number | null;\n package_name: string | null;\n }> = rawDb.all(\n `SELECT v.node_id, v.cve_id, v.severity_score, v.risk_score,\n v.fixed_version, v.first_detected_at, v.fix_available_since,\n v.suppressed_at, v.remediated_at,\n d.package_name\n FROM sec_vulnerabilities v\n LEFT JOIN edges e ON e.target = v.node_id AND e.kind = 'has_vulnerability'\n LEFT JOIN sec_dependencies d ON d.node_id = e.source\n WHERE v.remediated_at IS NULL\n AND v.suppressed_at IS NULL\n AND v.first_detected_at IS NOT NULL`,\n );\n\n return rows.map(row => {\n const firstDetectedAt = row.first_detected_at;\n const daysOpen = firstDetectedAt !== null\n ? Math.floor((now - firstDetectedAt) / (1000 * 60 * 60 * 24))\n : null;\n\n const fixAvailableSince = row.fix_available_since;\n const daysWithFixAvailable = fixAvailableSince !== null\n ? Math.floor((now - fixAvailableSince) / (1000 * 60 * 60 * 24))\n : null;\n\n const slaThreshold = this.getSlaThresholdDays(row.severity_score);\n const slaDeadline = fixAvailableSince !== null\n ? fixAvailableSince + slaThreshold * 24 * 60 * 60 * 1000\n : firstDetectedAt !== null\n ? firstDetectedAt + slaThreshold * 24 * 60 * 60 * 1000\n : null;\n\n let slaStatus: RemediationStatus['slaStatus'];\n let isOverdue = false;\n\n if (!row.fixed_version && !fixAvailableSince) {\n slaStatus = 'no_fix';\n isOverdue = false;\n } else if (slaDeadline !== null && now > slaDeadline) {\n slaStatus = 'overdue';\n isOverdue = true;\n } else if (slaDeadline !== null && now > slaDeadline - slaThreshold * 0.5 * 24 * 60 * 60 * 1000) {\n // Over 50% of SLA elapsed\n slaStatus = 'warning';\n isOverdue = false;\n } else {\n slaStatus = 'ok';\n isOverdue = false;\n }\n\n return {\n cveId: row.cve_id,\n packageName: row.package_name ?? 'unknown',\n severity: row.severity_score,\n riskScore: row.risk_score,\n firstDetectedAt,\n daysOpen,\n fixAvailableSince,\n daysWithFixAvailable,\n isOverdue,\n slaStatus,\n slaDeadline,\n };\n });\n }\n\n /**\n * SLA thresholds by CVSS severity:\n * critical (\u2265 9) \u2192 7 days\n * high (\u2265 7) \u2192 30 days\n * medium (\u2265 4) \u2192 90 days\n * low / unknown \u2192 180 days\n */\n getSlaThresholdDays(severity: number | null): number {\n if (severity === null) return 180;\n if (severity >= 9) return 7;\n if (severity >= 7) return 30;\n if (severity >= 4) return 90;\n return 180;\n }\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAkCO,MAAM,mBAAmB;AAAA,EAC9B,YAA6B,IAAmB;AAAnB;AAAA,EAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOjD,aAAa,YAAoB,cAAmC;AAClE,UAAM,QAAQ,KAAK,GAAG,SAAS;AAC/B,UAAM,MAAM,KAAK,IAAI;AAGrB,UAAM;AAAA,MACJ;AAAA;AAAA;AAAA,MAGA,CAAC,KAAK,UAAU;AAAA,IAClB;AAGA,QAAI,cAAc;AAChB,YAAM;AAAA,QACJ;AAAA;AAAA;AAAA,QAGA,CAAC,KAAK,UAAU;AAAA,MAClB;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,YAAiC;AAC/B,UAAM,QAAQ,KAAK,GAAG,SAAS;AAC/B,UAAM,MAAM,KAAK,IAAI;AAErB,UAAM,OAWD,MAAM;AAAA,MACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAUF;AAEA,WAAO,KAAK,IAAI,SAAO;AACrB,YAAM,kBAAkB,IAAI;AAC5B,YAAM,WAAW,oBAAoB,OACjC,KAAK,OAAO,MAAM,oBAAoB,MAAO,KAAK,KAAK,GAAG,IAC1D;AAEJ,YAAM,oBAAoB,IAAI;AAC9B,YAAM,uBAAuB,sBAAsB,OAC/C,KAAK,OAAO,MAAM,sBAAsB,MAAO,KAAK,KAAK,GAAG,IAC5D;AAEJ,YAAM,eAAe,KAAK,oBAAoB,IAAI,cAAc;AAChE,YAAM,cAAc,sBAAsB,OACtC,oBAAoB,eAAe,KAAK,KAAK,KAAK,MAClD,oBAAoB,OAClB,kBAAkB,eAAe,KAAK,KAAK,KAAK,MAChD;AAEN,UAAI;AACJ,UAAI,YAAY;AAEhB,UAAI,CAAC,IAAI,iBAAiB,CAAC,mBAAmB;AAC5C,oBAAY;AACZ,oBAAY;AAAA,MACd,WAAW,gBAAgB,QAAQ,MAAM,aAAa;AACpD,oBAAY;AACZ,oBAAY;AAAA,MACd,WAAW,gBAAgB,QAAQ,MAAM,cAAc,eAAe,MAAM,KAAK,KAAK,KAAK,KAAM;AAE/F,oBAAY;AACZ,oBAAY;AAAA,MACd,OAAO;AACL,oBAAY;AACZ,oBAAY;AAAA,MACd;AAEA,aAAO;AAAA,QACL,OAAO,IAAI;AAAA,QACX,aAAa,IAAI,gBAAgB;AAAA,QACjC,UAAU,IAAI;AAAA,QACd,WAAW,IAAI;AAAA,QACf;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,oBAAoB,UAAiC;AACnD,QAAI,aAAa,KAAM,QAAO;AAC9B,QAAI,YAAY,EAAG,QAAO;AAC1B,QAAI,YAAY,EAAG,QAAO;AAC1B,QAAI,YAAY,EAAG,QAAO;AAC1B,WAAO;AAAA,EACT;AACF;",
6
+ "names": []
7
+ }