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
@@ -45,6 +45,43 @@ async function generateExport(projectPath, opts) {
45
45
  const cg = await KiroGraph.open(target);
46
46
  const nodes = cg.getAllNodes();
47
47
  const edges = cg.getAllEdges();
48
+ let securityOverlay;
49
+ try {
50
+ const db = cg.getDatabase();
51
+ db.applySecuritySchema();
52
+ const depRows = db.getRawDb().all(
53
+ `SELECT d.node_id, r.verdict
54
+ FROM sec_dependencies d
55
+ LEFT JOIN edges e ON e.source = d.node_id AND e.kind = 'has_vulnerability'
56
+ LEFT JOIN sec_reachability r ON r.vulnerability_node_id = e.target
57
+ ORDER BY
58
+ CASE r.verdict
59
+ WHEN 'affected' THEN 0
60
+ WHEN 'under_investigation' THEN 1
61
+ WHEN 'not_affected' THEN 2
62
+ ELSE 3
63
+ END`
64
+ );
65
+ const depVerdicts = /* @__PURE__ */ new Map();
66
+ for (const row of depRows) {
67
+ if (!depVerdicts.has(row.node_id)) {
68
+ const status = row.verdict === "affected" ? "affected" : row.verdict === "under_investigation" ? "under_investigation" : row.verdict === "not_affected" ? "not_affected" : "none";
69
+ depVerdicts.set(row.node_id, status);
70
+ }
71
+ }
72
+ const vulnRows = db.getRawDb().all(
73
+ `SELECT node_id, verdict FROM sec_reachability`
74
+ );
75
+ const vulnVerdicts = /* @__PURE__ */ new Map();
76
+ for (const row of vulnRows) {
77
+ const status = row.verdict === "affected" ? "affected" : row.verdict === "under_investigation" ? "under_investigation" : row.verdict === "not_affected" ? "not_affected" : "none";
78
+ vulnVerdicts.set(row.node_id, status);
79
+ }
80
+ if (depVerdicts.size > 0 || vulnVerdicts.size > 0) {
81
+ securityOverlay = { depVerdicts, vulnVerdicts };
82
+ }
83
+ } catch {
84
+ }
48
85
  cg.close();
49
86
  const projectName = path.basename(target);
50
87
  let logoBase64;
@@ -69,7 +106,7 @@ async function generateExport(projectPath, opts) {
69
106
  }
70
107
  const outDir = opts.output ? path.resolve(opts.output) : path.join(target, ".kirograph", "export");
71
108
  fs.mkdirSync(outDir, { recursive: true });
72
- const { html, css, js } = buildFiles(nodes, edges, projectName, opts.includeContains, logoBase64, fileModified);
109
+ const { html, css, js } = buildFiles(nodes, edges, projectName, opts.includeContains, logoBase64, fileModified, securityOverlay);
73
110
  fs.writeFileSync(path.join(outDir, "index.html"), html, "utf8");
74
111
  fs.writeFileSync(path.join(outDir, "app.css"), css, "utf8");
75
112
  fs.writeFileSync(path.join(outDir, "app.js"), js, "utf8");
@@ -137,7 +174,18 @@ function lighten(hex) {
137
174
  function escHtml(s) {
138
175
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
139
176
  }
140
- function buildFiles(nodes, edges, projectName, includeContains, logoBase64, fileModified) {
177
+ const SEC_DEP_COLOR = {
178
+ affected: "#ef4444",
179
+ under_investigation: "#f59e0b",
180
+ not_affected: "#22c55e",
181
+ none: "#64748b"
182
+ };
183
+ const SEC_VULN_COLOR = {
184
+ affected: "#dc2626",
185
+ under_investigation: "#d97706",
186
+ not_affected: "#16a34a"
187
+ };
188
+ function buildFiles(nodes, edges, projectName, includeContains, logoBase64, fileModified, securityOverlay) {
141
189
  const filteredEdges = includeContains ? edges : edges.filter((e) => e.kind !== "contains");
142
190
  const degree = /* @__PURE__ */ new Map();
143
191
  for (const e of filteredEdges) {
@@ -149,14 +197,28 @@ function buildFiles(nodes, edges, projectName, includeContains, logoBase64, file
149
197
  const fp = n.filePath ?? "";
150
198
  const parts = fp.split("/").filter(Boolean);
151
199
  const dir = parts.length >= 2 ? parts.slice(0, 2).join("/") : parts[0] ?? "";
200
+ let securityStatus = "none";
201
+ let baseColor = KIND_COLOR[n.kind] ?? "#424242";
202
+ if (securityOverlay) {
203
+ if (n.kind === "dependency") {
204
+ securityStatus = securityOverlay.depVerdicts.get(n.id) ?? "none";
205
+ baseColor = SEC_DEP_COLOR[securityStatus];
206
+ } else if (n.kind === "vulnerability") {
207
+ const verdict = securityOverlay.vulnVerdicts.get(n.id);
208
+ if (verdict) {
209
+ securityStatus = verdict;
210
+ baseColor = SEC_VULN_COLOR[verdict] ?? baseColor;
211
+ }
212
+ }
213
+ }
152
214
  return {
153
215
  id: n.id,
154
216
  label: n.name,
155
217
  color: {
156
- background: KIND_COLOR[n.kind] ?? "#424242",
157
- border: lighten(KIND_COLOR[n.kind] ?? "#424242"),
218
+ background: baseColor,
219
+ border: lighten(baseColor),
158
220
  highlight: { background: "#e040fb", border: "#ea80fc" },
159
- hover: { background: lighten(KIND_COLOR[n.kind] ?? "#424242"), border: "#ea80fc" }
221
+ hover: { background: lighten(baseColor), border: "#ea80fc" }
160
222
  },
161
223
  size: Math.max(8, Math.min(40, 8 + (degree.get(n.id) ?? 0) * 1.5)),
162
224
  font: { size: 11, color: "#e0e0e0", face: "monospace" },
@@ -170,7 +232,8 @@ function buildFiles(nodes, edges, projectName, includeContains, logoBase64, file
170
232
  degree: degree.get(n.id) ?? 0,
171
233
  borderWidth: n.isExported ? 2 : 1,
172
234
  borderWidthSelected: 3,
173
- lastModified: fileModified ? fileModified[fp] ?? 0 : 0
235
+ lastModified: fileModified ? fileModified[fp] ?? 0 : 0,
236
+ securityStatus
174
237
  };
175
238
  });
176
239
  const visEdges = filteredEdges.map((e, i) => ({
@@ -188,6 +251,7 @@ function buildFiles(nodes, edges, projectName, includeContains, logoBase64, file
188
251
  }));
189
252
  const allNodeKinds = [...new Set(nodes.map((n) => n.kind))].sort();
190
253
  const allEdgeKinds = [...new Set(filteredEdges.map((e) => e.kind))].sort();
254
+ const hasSecurityNodes = visNodes.some((n) => n.kind === "dependency" || n.kind === "vulnerability");
191
255
  const logoTag = logoBase64 ? `<img src="data:image/png;base64,${logoBase64}" alt="KiroGraph">` : `<h1>\u2B21 KiroGraph</h1>`;
192
256
  const loaderLogoTag = logoBase64 ? `<img src="data:image/png;base64,${logoBase64}" alt="KiroGraph">` : `<div style="color:#c792ea;font-size:18px;font-weight:600;letter-spacing:.05em">\u2B21 KiroGraph</div>`;
193
257
  const nodeLegend = allNodeKinds.map((k) => `<div class="legend-item" data-nkind="${k}">
@@ -254,7 +318,8 @@ function buildFiles(nodes, edges, projectName, includeContains, logoBase64, file
254
318
  <button class="fbtn" id="btn-focus" title="Focus on node and neighbors">\u25CE <span class="fbtn-label">Focus</span></button>
255
319
  <button class="fbtn" id="btn-path" title="Find path between two nodes">\u27F6 <span class="fbtn-label">Path</span></button>
256
320
  <button class="fbtn" id="btn-cluster" title="Cluster by directory">\u2B21 <span class="fbtn-label">Cluster</span></button>
257
- <button class="fbtn" id="btn-heat" title="Heat map by file recency">\u{1F321} <span class="fbtn-label">Heat</span></button>
321
+ <button class="fbtn" id="btn-heat" title="Heat map by file recency">\u{1F321} <span class="fbtn-label">Heat</span></button>${hasSecurityNodes ? `
322
+ <button class="fbtn" id="btn-security" title="Security overlay \u2014 highlight dependency/vulnerability nodes">\u{1F512} <span class="fbtn-label">Security</span></button>` : ""}
258
323
  <button class="fbtn" id="btn-charts" title="Analytics charts">\u{1F4CA} <span class="fbtn-label">Charts</span></button>
259
324
  </div>
260
325
 
@@ -269,7 +334,20 @@ function buildFiles(nodes, edges, projectName, includeContains, logoBase64, file
269
334
  <div class="heat-stop" data-tip="Modified more than 6 months ago"><span class="heat-stop-dot" style="background:#2980b9"></span>Older</div>
270
335
  </div>
271
336
  <div id="heat-tip"></div>
272
- </div>
337
+ </div>${hasSecurityNodes ? `
338
+
339
+ <div id="security-legend">
340
+ <div class="security-legend-title">\u{1F512} Security status</div>
341
+ <div class="security-stops">
342
+ <div class="security-stop"><span class="security-stop-dot" style="background:#ef4444"></span>Affected</div>
343
+ <div class="security-stop"><span class="security-stop-dot" style="background:#f59e0b"></span>Under investigation</div>
344
+ <div class="security-stop"><span class="security-stop-dot" style="background:#22c55e"></span>Not affected</div>
345
+ <div class="security-stop"><span class="security-stop-dot" style="background:#64748b"></span>No vuln data</div>
346
+ <div class="security-stop"><span class="security-stop-dot" style="background:#dc2626;border:2px solid #fff"></span>Vuln: affected</div>
347
+ <div class="security-stop"><span class="security-stop-dot" style="background:#d97706;border:2px solid #fff"></span>Vuln: investigating</div>
348
+ <div class="security-stop"><span class="security-stop-dot" style="background:#16a34a;border:2px solid #fff"></span>Vuln: not affected</div>
349
+ </div>
350
+ </div>` : ""}
273
351
  </div>
274
352
  </div><!-- /float-sidebar -->
275
353
 
@@ -415,7 +493,7 @@ function buildFiles(nodes, edges, projectName, includeContains, logoBase64, file
415
493
  </body>
416
494
  </html>`;
417
495
  const css = buildCss();
418
- const js = buildJs(visNodes, visEdges, KIND_COLOR, EDGE_COLOR);
496
+ const js = buildJs(visNodes, visEdges, KIND_COLOR, EDGE_COLOR, hasSecurityNodes);
419
497
  return { html, css, js };
420
498
  }
421
499
  function buildCss() {
@@ -941,9 +1019,47 @@ input[type=range] {
941
1019
  min-height: 14px;
942
1020
  font-style: italic;
943
1021
  }
1022
+
1023
+ /* \u2500\u2500 Security legend (inside sidebar) \u2500\u2500 */
1024
+ #security-legend {
1025
+ display: none;
1026
+ background: rgba(8, 8, 18, 0.97);
1027
+ border: 1px solid #3a3a5e;
1028
+ border-radius: 12px;
1029
+ padding: 12px 14px;
1030
+ box-shadow: 0 4px 24px rgba(0,0,0,.75);
1031
+ width: 200px;
1032
+ }
1033
+ .security-legend-title {
1034
+ font-size: 11px;
1035
+ font-weight: 600;
1036
+ color: #9da8cc;
1037
+ margin-bottom: 10px;
1038
+ }
1039
+ .security-stops {
1040
+ display: flex;
1041
+ flex-direction: column;
1042
+ gap: 5px;
1043
+ }
1044
+ .security-stop {
1045
+ display: flex;
1046
+ align-items: center;
1047
+ gap: 8px;
1048
+ font-size: 11px;
1049
+ color: #7a86aa;
1050
+ cursor: default;
1051
+ padding: 2px 4px;
1052
+ border-radius: 4px;
1053
+ }
1054
+ .security-stop-dot {
1055
+ width: 9px; height: 9px;
1056
+ border-radius: 50%;
1057
+ flex-shrink: 0;
1058
+ box-sizing: border-box;
1059
+ }
944
1060
  `;
945
1061
  }
946
- function buildJs(visNodes, visEdges, kindColors, edgeColors) {
1062
+ function buildJs(visNodes, visEdges, kindColors, edgeColors, hasSecurityNodes) {
947
1063
  return `// \u2500\u2500 Data \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
948
1064
  const NODES_DATA = ${JSON.stringify(visNodes)};
949
1065
  const EDGES_DATA = ${JSON.stringify(visEdges)};
@@ -1551,6 +1667,7 @@ document.getElementById('btn-cluster').addEventListener('click', () => {
1551
1667
  document.getElementById('heat-legend').style.display = 'none';
1552
1668
  nodesDS.update(NODES_DATA.map(n => ({ id: n.id, color: originalColors[n.id] })));
1553
1669
  }
1670
+ ${hasSecurityNodes ? `if (securityActive) { exitSecurityOverlay(); }` : ""}
1554
1671
  if (focusActive) exitFocus();
1555
1672
  if (pathMode) exitPath(true);
1556
1673
  network.unselectAll();
@@ -1769,6 +1886,55 @@ document.querySelectorAll('.heat-stop').forEach(function(el) {
1769
1886
  el.addEventListener('mouseleave', function() { if (tip) tip.textContent = ''; });
1770
1887
  });
1771
1888
 
1889
+ ${hasSecurityNodes ? `// \u2500\u2500 Security overlay \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
1890
+ let securityActive = false;
1891
+
1892
+ // Colours embedded from build-time palette
1893
+ const SEC_DEP_COLORS = { affected: '#ef4444', under_investigation: '#f59e0b', not_affected: '#22c55e', none: '#64748b' };
1894
+ const SEC_VULN_COLORS = { affected: '#dc2626', under_investigation: '#d97706', not_affected: '#16a34a' };
1895
+
1896
+ function enterSecurityOverlay() {
1897
+ securityActive = true;
1898
+ document.getElementById('btn-security').classList.add('active');
1899
+ document.getElementById('security-legend').style.display = 'block';
1900
+
1901
+ const DIM = { background: '#111118', border: '#1a1a2a', highlight: { background: '#111118', border: '#1a1a2a' }, hover: { background: '#111118', border: '#1a1a2a' } };
1902
+ const secNodeIds = new Set(
1903
+ NODES_DATA
1904
+ .filter(n => n.kind === 'dependency' || n.kind === 'vulnerability')
1905
+ .map(n => n.id)
1906
+ );
1907
+
1908
+ // Dim edges that don't connect to any security node
1909
+ const secEdgeIds = new Set(
1910
+ EDGES_DATA
1911
+ .filter(e => secNodeIds.has(e.from) || secNodeIds.has(e.to))
1912
+ .map(e => e.id)
1913
+ );
1914
+
1915
+ nodesDS.update(NODES_DATA.map(n => ({
1916
+ id: n.id,
1917
+ color: secNodeIds.has(n.id) ? originalColors[n.id] : DIM,
1918
+ })));
1919
+ edgesDS.update(EDGES_DATA.map(e => ({
1920
+ id: e.id,
1921
+ color: secEdgeIds.has(e.id) ? originalEdgeColors[e.id] : { color: '#111118', opacity: 0.08 },
1922
+ })));
1923
+ }
1924
+
1925
+ function exitSecurityOverlay() {
1926
+ securityActive = false;
1927
+ document.getElementById('btn-security').classList.remove('active');
1928
+ document.getElementById('security-legend').style.display = 'none';
1929
+ nodesDS.update(NODES_DATA.map(n => ({ id: n.id, color: originalColors[n.id] })));
1930
+ edgesDS.update(EDGES_DATA.map(e => ({ id: e.id, color: originalEdgeColors[e.id] })));
1931
+ }
1932
+
1933
+ document.getElementById('btn-security').addEventListener('click', () => {
1934
+ if (securityActive) { exitSecurityOverlay(); } else { enterSecurityOverlay(); }
1935
+ });
1936
+ ` : "// Security overlay: no security nodes in this graph"}
1937
+
1772
1938
  // \u2500\u2500 Keyboard shortcuts \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
1773
1939
  document.addEventListener('keydown', e => {
1774
1940
  if (e.key === 'Escape') {
@@ -1821,6 +1987,9 @@ function resetToEmpty() {
1821
1987
  document.getElementById('heat-legend').style.display = 'none';
1822
1988
  }
1823
1989
 
1990
+ // Reset security overlay
1991
+ ${hasSecurityNodes ? `if (securityActive) { exitSecurityOverlay(); }` : ""}
1992
+
1824
1993
  // Reset context menu
1825
1994
  document.getElementById('ctx-menu').style.display = 'none';
1826
1995
  ctxNodeId = null;
@@ -2612,6 +2781,131 @@ function register(program) {
2612
2781
  `);
2613
2782
  openBrowser(indexPath);
2614
2783
  });
2784
+ exportCmd.command("graphml [projectPath]").description("Export graph as GraphML (for Gephi, yEd)").option("-o, --output <file>", "Output file path").action(async (projectPath, opts) => {
2785
+ const KiroGraph = (await Promise.resolve().then(() => require("../../index.js"))).default;
2786
+ const target = path.resolve(projectPath ?? process.cwd());
2787
+ const cg = await KiroGraph.open(target);
2788
+ const nodes = cg.getAllNodes();
2789
+ const edges = cg.getAllEdges();
2790
+ cg.close();
2791
+ const outPath = opts.output ?? path.join(target, ".kirograph", "export", "graph.graphml");
2792
+ fs.mkdirSync(path.dirname(outPath), { recursive: true });
2793
+ const lines = [
2794
+ '<?xml version="1.0" encoding="UTF-8"?>',
2795
+ '<graphml xmlns="http://graphml.graphstruct.org/graphml">',
2796
+ ' <key id="kind" for="node" attr.name="kind" attr.type="string"/>',
2797
+ ' <key id="name" for="node" attr.name="name" attr.type="string"/>',
2798
+ ' <key id="file" for="node" attr.name="file" attr.type="string"/>',
2799
+ ' <key id="line" for="node" attr.name="line" attr.type="int"/>',
2800
+ ' <key id="edgeKind" for="edge" attr.name="kind" attr.type="string"/>',
2801
+ ' <graph id="G" edgedefault="directed">'
2802
+ ];
2803
+ for (const n of nodes) {
2804
+ lines.push(` <node id="${escHtml(n.id)}">`);
2805
+ lines.push(` <data key="kind">${escHtml(n.kind)}</data>`);
2806
+ lines.push(` <data key="name">${escHtml(n.name)}</data>`);
2807
+ lines.push(` <data key="file">${escHtml(n.filePath)}</data>`);
2808
+ lines.push(` <data key="line">${n.startLine}</data>`);
2809
+ lines.push(` </node>`);
2810
+ }
2811
+ for (const e of edges) {
2812
+ lines.push(` <edge source="${escHtml(e.source)}" target="${escHtml(e.target)}">`);
2813
+ lines.push(` <data key="edgeKind">${escHtml(e.kind)}</data>`);
2814
+ lines.push(` </edge>`);
2815
+ }
2816
+ lines.push(" </graph>", "</graphml>");
2817
+ fs.writeFileSync(outPath, lines.join("\n"), "utf8");
2818
+ console.log(`
2819
+ ${import_ui.green}GraphML exported${import_ui.reset}: ${outPath}`);
2820
+ console.log(` ${import_ui.dim}${nodes.length} nodes, ${edges.length} edges${import_ui.reset}
2821
+ `);
2822
+ });
2823
+ exportCmd.command("cypher [projectPath]").description("Export graph as Neo4j Cypher statements").option("-o, --output <file>", "Output file path").action(async (projectPath, opts) => {
2824
+ const KiroGraph = (await Promise.resolve().then(() => require("../../index.js"))).default;
2825
+ const target = path.resolve(projectPath ?? process.cwd());
2826
+ const cg = await KiroGraph.open(target);
2827
+ const nodes = cg.getAllNodes();
2828
+ const edges = cg.getAllEdges();
2829
+ cg.close();
2830
+ const outPath = opts.output ?? path.join(target, ".kirograph", "export", "graph.cypher");
2831
+ fs.mkdirSync(path.dirname(outPath), { recursive: true });
2832
+ const lines = ["// Generated by KiroGraph", "// Import with: cat graph.cypher | cypher-shell", ""];
2833
+ for (const n of nodes) {
2834
+ const label = n.kind.charAt(0).toUpperCase() + n.kind.slice(1);
2835
+ const props = `{id: "${n.id.replace(/"/g, '\\"')}", name: "${n.name.replace(/"/g, '\\"')}", file: "${n.filePath.replace(/"/g, '\\"')}", line: ${n.startLine}}`;
2836
+ lines.push(`CREATE (:${label} ${props});`);
2837
+ }
2838
+ lines.push("");
2839
+ for (const e of edges) {
2840
+ const relType = e.kind.toUpperCase();
2841
+ lines.push(`MATCH (a {id: "${e.source.replace(/"/g, '\\"')}"}), (b {id: "${e.target.replace(/"/g, '\\"')}"}) CREATE (a)-[:${relType}]->(b);`);
2842
+ }
2843
+ fs.writeFileSync(outPath, lines.join("\n"), "utf8");
2844
+ console.log(`
2845
+ ${import_ui.green}Neo4j Cypher exported${import_ui.reset}: ${outPath}`);
2846
+ console.log(` ${import_ui.dim}${nodes.length} nodes, ${edges.length} edges${import_ui.reset}
2847
+ `);
2848
+ });
2849
+ exportCmd.command("obsidian [projectPath]").description("Export graph as an Obsidian vault with wikilinks").option("-o, --output <dir>", "Output directory path").action(async (projectPath, opts) => {
2850
+ const KiroGraph = (await Promise.resolve().then(() => require("../../index.js"))).default;
2851
+ const target = path.resolve(projectPath ?? process.cwd());
2852
+ const cg = await KiroGraph.open(target);
2853
+ const nodes = cg.getAllNodes();
2854
+ const edges = cg.getAllEdges();
2855
+ cg.close();
2856
+ const outDir = opts.output ?? path.join(target, ".kirograph", "export", "obsidian");
2857
+ fs.mkdirSync(outDir, { recursive: true });
2858
+ const callersMap = /* @__PURE__ */ new Map();
2859
+ const calleesMap = /* @__PURE__ */ new Map();
2860
+ const nodeById = new Map(nodes.map((n) => [n.id, n]));
2861
+ for (const e of edges) {
2862
+ if (e.kind === "calls") {
2863
+ const targetNode = nodeById.get(e.target);
2864
+ const sourceNode = nodeById.get(e.source);
2865
+ if (targetNode) {
2866
+ if (!calleesMap.has(e.source)) calleesMap.set(e.source, []);
2867
+ calleesMap.get(e.source).push(targetNode.name);
2868
+ }
2869
+ if (sourceNode) {
2870
+ if (!callersMap.has(e.target)) callersMap.set(e.target, []);
2871
+ callersMap.get(e.target).push(sourceNode.name);
2872
+ }
2873
+ }
2874
+ }
2875
+ let fileCount = 0;
2876
+ for (const n of nodes) {
2877
+ if (n.kind === "variable" || n.kind === "parameter") continue;
2878
+ const callers = callersMap.get(n.id) ?? [];
2879
+ const callees = calleesMap.get(n.id) ?? [];
2880
+ const lines = [
2881
+ `# ${n.name}`,
2882
+ "",
2883
+ `- **Kind**: ${n.kind}`,
2884
+ `- **File**: \`${n.filePath}:${n.startLine}\``
2885
+ ];
2886
+ if (n.signature) lines.push(`- **Signature**: \`${n.signature}\``);
2887
+ if (n.isExported) lines.push(`- **Exported**: yes`);
2888
+ if (callees.length > 0) {
2889
+ lines.push("", "## Calls", "");
2890
+ for (const c of [...new Set(callees)].slice(0, 20)) {
2891
+ lines.push(`- [[${c}]]`);
2892
+ }
2893
+ }
2894
+ if (callers.length > 0) {
2895
+ lines.push("", "## Called by", "");
2896
+ for (const c of [...new Set(callers)].slice(0, 20)) {
2897
+ lines.push(`- [[${c}]]`);
2898
+ }
2899
+ }
2900
+ const safeName = n.name.replace(/[<>:"/\\|?*]/g, "_");
2901
+ fs.writeFileSync(path.join(outDir, `${safeName}.md`), lines.join("\n"), "utf8");
2902
+ fileCount++;
2903
+ }
2904
+ console.log(`
2905
+ ${import_ui.green}Obsidian vault exported${import_ui.reset}: ${outDir}`);
2906
+ console.log(` ${import_ui.dim}${fileCount} markdown files with wikilinks${import_ui.reset}
2907
+ `);
2908
+ });
2615
2909
  }
2616
2910
  // Annotate the CommonJS export names for ESM import in node:
2617
2911
  0 && (module.exports = {