pkgviz 0.7.7 → 0.8.1

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 (367) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +2 -1
  3. package/.next/build-manifest.json +15 -24
  4. package/.next/fallback-build-manifest.json +15 -0
  5. package/.next/images-manifest.json +14 -3
  6. package/.next/next-minimal-server.js.nft.json +1 -1
  7. package/.next/next-server.js.nft.json +1 -1
  8. package/.next/prerender-manifest.json +52 -16
  9. package/.next/required-server-files.js +349 -0
  10. package/.next/required-server-files.json +106 -75
  11. package/.next/routes-manifest.json +16 -6
  12. package/.next/server/app/_global-error/page/app-paths-manifest.json +3 -0
  13. package/.next/server/app/_global-error/page/build-manifest.json +20 -0
  14. package/.next/server/app/_global-error/page/next-font-manifest.json +6 -0
  15. package/.next/server/app/_global-error/page/server-reference-manifest.json +4 -0
  16. package/.next/server/app/_global-error/page.js +9 -0
  17. package/.next/server/app/_global-error/page.js.map +5 -0
  18. package/.next/server/app/_global-error/page.js.nft.json +1 -0
  19. package/.next/server/app/_global-error/page_client-reference-manifest.js +3 -0
  20. package/.next/server/app/_global-error.html +1 -0
  21. package/.next/server/app/_global-error.meta +22 -0
  22. package/.next/server/app/_global-error.rsc +15 -0
  23. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +20 -0
  24. package/.next/server/app/_global-error.segments/_full.segment.rsc +15 -0
  25. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -0
  26. package/.next/server/app/_not-found/page/app-paths-manifest.json +3 -0
  27. package/.next/server/app/_not-found/page/build-manifest.json +20 -0
  28. package/.next/server/app/_not-found/page/next-font-manifest.json +11 -0
  29. package/.next/server/app/_not-found/page/react-loadable-manifest.json +1 -0
  30. package/.next/server/app/_not-found/page/server-reference-manifest.json +4 -0
  31. package/.next/server/app/_not-found/page.js +13 -2
  32. package/.next/server/app/_not-found/page.js.map +5 -0
  33. package/.next/server/app/_not-found/page.js.nft.json +1 -1
  34. package/.next/server/app/_not-found/page_client-reference-manifest.js +3 -1
  35. package/.next/server/app/_not-found.html +1 -1
  36. package/.next/server/app/_not-found.meta +19 -0
  37. package/.next/server/app/_not-found.rsc +18 -19
  38. package/.next/server/app/_not-found.segments/_full.segment.rsc +18 -0
  39. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +24 -0
  40. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -0
  41. package/.next/server/app/favicon.ico/route/app-paths-manifest.json +3 -0
  42. package/.next/server/app/favicon.ico/route/build-manifest.json +12 -0
  43. package/.next/server/app/favicon.ico/route.js +6 -1
  44. package/.next/server/app/favicon.ico/route.js.map +5 -0
  45. package/.next/server/app/favicon.ico/route.js.nft.json +1 -1
  46. package/.next/server/app/page/app-paths-manifest.json +3 -0
  47. package/.next/server/app/page/build-manifest.json +20 -0
  48. package/.next/server/app/page/next-font-manifest.json +11 -0
  49. package/.next/server/app/page/react-loadable-manifest.json +1 -0
  50. package/.next/server/app/page/server-reference-manifest.json +93 -0
  51. package/.next/server/app/page.js +16 -13
  52. package/.next/server/app/page.js.map +5 -0
  53. package/.next/server/app/page.js.nft.json +1 -1
  54. package/.next/server/app/page_client-reference-manifest.js +3 -1
  55. package/.next/server/app-paths-manifest.json +2 -1
  56. package/.next/server/chunks/[externals]__0l8ei7u._.js +3 -0
  57. package/.next/server/chunks/[externals]__0l8ei7u._.js.map +1 -0
  58. package/.next/server/chunks/[root-of-the-server]__1to-yfd._.js +160 -0
  59. package/.next/server/chunks/[root-of-the-server]__1to-yfd._.js.map +1 -0
  60. package/.next/server/chunks/[turbopack]_runtime.js +807 -0
  61. package/.next/server/chunks/[turbopack]_runtime.js.map +10 -0
  62. package/.next/server/chunks/_next-internal_server_app_favicon_ico_route_actions_0g2jjls.js +3 -0
  63. package/.next/server/chunks/_next-internal_server_app_favicon_ico_route_actions_0g2jjls.js.map +1 -0
  64. package/.next/server/chunks/ssr/[root-of-the-server]__0agsrep._.js +28 -0
  65. package/.next/server/chunks/ssr/[root-of-the-server]__0agsrep._.js.map +1 -0
  66. package/.next/server/chunks/ssr/[root-of-the-server]__0f_e7c8._.js +33 -0
  67. package/.next/server/chunks/ssr/[root-of-the-server]__0f_e7c8._.js.map +1 -0
  68. package/.next/server/chunks/ssr/[root-of-the-server]__0yqn68e._.js +3 -0
  69. package/.next/server/chunks/ssr/[root-of-the-server]__0yqn68e._.js.map +1 -0
  70. package/.next/server/chunks/ssr/[root-of-the-server]__112mz-6._.js +14 -0
  71. package/.next/server/chunks/ssr/[root-of-the-server]__112mz-6._.js.map +1 -0
  72. package/.next/server/chunks/ssr/[root-of-the-server]__11_76b7._.js +3 -0
  73. package/.next/server/chunks/ssr/[root-of-the-server]__11_76b7._.js.map +1 -0
  74. package/.next/server/chunks/ssr/[root-of-the-server]__12ovu0c._.js +3 -0
  75. package/.next/server/chunks/ssr/[root-of-the-server]__12ovu0c._.js.map +1 -0
  76. package/.next/server/chunks/ssr/[root-of-the-server]__16pmw__._.js +3 -0
  77. package/.next/server/chunks/ssr/[root-of-the-server]__16pmw__._.js.map +1 -0
  78. package/.next/server/chunks/ssr/[root-of-the-server]__1f2jx51._.js +33 -0
  79. package/.next/server/chunks/ssr/[root-of-the-server]__1f2jx51._.js.map +1 -0
  80. package/.next/server/chunks/ssr/[root-of-the-server]__1rn09vt._.js +3 -0
  81. package/.next/server/chunks/ssr/[root-of-the-server]__1rn09vt._.js.map +1 -0
  82. package/.next/server/chunks/ssr/[root-of-the-server]__20e4gkp._.js +3 -0
  83. package/.next/server/chunks/ssr/[root-of-the-server]__20e4gkp._.js.map +1 -0
  84. package/.next/server/chunks/ssr/[turbopack]_runtime.js +807 -0
  85. package/.next/server/chunks/ssr/[turbopack]_runtime.js.map +10 -0
  86. package/.next/server/chunks/ssr/_1acy_2s._.js +158 -0
  87. package/.next/server/chunks/ssr/_1acy_2s._.js.map +1 -0
  88. package/.next/server/chunks/ssr/_1rh2tf9._.js +367 -0
  89. package/.next/server/chunks/ssr/_1rh2tf9._.js.map +1 -0
  90. package/.next/server/chunks/ssr/_next-internal_server_app__global-error_page_actions_0zi5s8-.js +3 -0
  91. package/.next/server/chunks/ssr/_next-internal_server_app__global-error_page_actions_0zi5s8-.js.map +1 -0
  92. package/.next/server/chunks/ssr/_next-internal_server_app__not-found_page_actions_0pt47yr.js +3 -0
  93. package/.next/server/chunks/ssr/_next-internal_server_app__not-found_page_actions_0pt47yr.js.map +1 -0
  94. package/.next/server/chunks/ssr/node_modules_01xbj_9._.js +33 -0
  95. package/.next/server/chunks/ssr/node_modules_01xbj_9._.js.map +1 -0
  96. package/.next/server/chunks/ssr/node_modules_next_dist_1c5ky4f._.js +3 -0
  97. package/.next/server/chunks/ssr/node_modules_next_dist_1c5ky4f._.js.map +1 -0
  98. package/.next/server/chunks/ssr/node_modules_next_dist_1n3w9lb._.js +6 -0
  99. package/.next/server/chunks/ssr/node_modules_next_dist_1n3w9lb._.js.map +1 -0
  100. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_0wpq8j3._.js +3 -0
  101. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_0wpq8j3._.js.map +1 -0
  102. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_forbidden_0symwr9.js +3 -0
  103. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_forbidden_0symwr9.js.map +1 -0
  104. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_global-error_0q-w892.js +3 -0
  105. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_global-error_0q-w892.js.map +1 -0
  106. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_unauthorized_0l_sp0x.js +3 -0
  107. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_unauthorized_0l_sp0x.js.map +1 -0
  108. package/.next/server/chunks/ssr/src_app_loading_tsx_01g_-m0._.js +3 -0
  109. package/.next/server/chunks/ssr/src_app_loading_tsx_01g_-m0._.js.map +1 -0
  110. package/.next/server/middleware-build-manifest.js +24 -1
  111. package/.next/server/middleware-manifest.json +2 -2
  112. package/.next/server/next-font-manifest.js +1 -1
  113. package/.next/server/next-font-manifest.json +15 -1
  114. package/.next/server/pages/404.html +1 -1
  115. package/.next/server/pages/500.html +1 -1
  116. package/.next/server/pages-manifest.json +2 -4
  117. package/.next/server/prefetch-hints.json +25 -0
  118. package/.next/server/server-reference-manifest.js +1 -1
  119. package/.next/server/server-reference-manifest.json +94 -1
  120. package/.next/static/chunks/0_j-_aavigxqr.js +345 -0
  121. package/.next/static/chunks/19mx3mg6lkumu.js +1 -0
  122. package/.next/static/chunks/1las_6j17xa9p.js +1 -0
  123. package/.next/static/chunks/1xk6snnh6e5_j.css +3 -0
  124. package/.next/static/chunks/2-rtuqsgzmno4.js +22 -0
  125. package/.next/static/chunks/27t_qfc-3_lzs.js +1 -0
  126. package/.next/static/chunks/310vm2bl3xxpt.js +31 -0
  127. package/.next/static/chunks/3fntmmi971322.js +1 -0
  128. package/.next/static/chunks/turbopack-1b6uko1cu_h42.js +1 -0
  129. package/.next/static/media/4fa387ec64143e14-s.2tuy5pz7dlieh.woff2 +0 -0
  130. package/.next/static/media/53b9e256198e5412-s.390ncx5urfkfu.woff2 +0 -0
  131. package/.next/static/media/5ce348bf30bf5439-s.31988l_ccedte.woff2 +0 -0
  132. package/.next/static/media/6306c77e7c8268e4-s.2dbetqa9o8jxf.woff2 +0 -0
  133. package/.next/static/media/7178b3e590c64307-s.21jp631_3pja2.woff2 +0 -0
  134. package/.next/static/media/797e433ab948586e-s.p.0r6juujl39pe6.woff2 +0 -0
  135. package/.next/static/media/7d817b4c03b0c5f1-s.1uyisp29ctx0d.woff2 +0 -0
  136. package/.next/static/media/8a480f0b521d4e75-s.1qq4vpdcun5oj.woff2 +0 -0
  137. package/.next/static/media/bbc41e54d2fcbd21-s.1rgnod-3esatf.woff2 +0 -0
  138. package/.next/static/media/caa3a2e1cccd8315-s.p.0wgildi0cnwt9.woff2 +0 -0
  139. package/.next/static/media/favicon.2246eezznj2ab.ico +0 -0
  140. package/.next/static/media/fef07dbb0973bf53-s.3p2_lha1f2xer.woff2 +0 -0
  141. package/.next/static/qznDSVYlkQxVdRx-TE_Wk/_buildManifest.js +11 -0
  142. package/.next/static/qznDSVYlkQxVdRx-TE_Wk/_clientMiddlewareManifest.js +1 -0
  143. package/README.md +176 -4
  144. package/bin/pkgviz.ts +57 -29
  145. package/package.json +57 -36
  146. package/src/app/actions/audit.actions.ts +7 -49
  147. package/src/app/globals.css +0 -2
  148. package/src/app/layout.tsx +3 -2
  149. package/src/app/loading.tsx +8 -0
  150. package/src/app/page.tsx +16 -2
  151. package/src/app/utils/buildGraph.ts +99 -100
  152. package/src/app/utils/getParsedFileStructure.ts +128 -54
  153. package/src/app/utils/inspectParserLanguageAsync.ts +39 -0
  154. package/src/app/utils/markCyclicPackages.ts +45 -185
  155. package/src/app/utils/parser/cpp/parseCppFile.ts +68 -86
  156. package/src/app/utils/parser/delphi/parseFile.ts +36 -120
  157. package/src/app/utils/parser/java/findEntryPoint.ts +2 -1
  158. package/src/app/utils/parser/java/parseJavaFile.ts +33 -45
  159. package/src/app/utils/parser/kotlin/parseFile.ts +34 -87
  160. package/src/app/utils/parser/python/parseFile.ts +30 -100
  161. package/src/app/utils/parser/typescript/parseFile.ts +17 -52
  162. package/src/app/utils/selectParserLanguage.ts +39 -0
  163. package/src/cli/formatAuditRuleFailures.ts +21 -0
  164. package/src/cli/runAuditAsync.ts +41 -0
  165. package/src/components/Breadcrumb.tsx +5 -4
  166. package/src/components/ExportPanel.tsx +53 -0
  167. package/src/components/Header.tsx +1 -0
  168. package/src/components/Loader.tsx +8 -2
  169. package/src/components/ProjectLoadError.tsx +15 -0
  170. package/src/components/SettingsPanel.tsx +184 -0
  171. package/src/components/Switch.tsx +9 -17
  172. package/src/components/ThemeToggle.tsx +2 -1
  173. package/src/components/ToggleSwitch.tsx +36 -0
  174. package/src/components/sidebar/Sidebar.tsx +14 -0
  175. package/src/components/sidebar/SidebarBadge.tsx +25 -0
  176. package/src/components/sidebar/SidebarRow.tsx +14 -0
  177. package/src/components/sidebar/SidebarSection.tsx +16 -0
  178. package/src/components/sidebar/SidebarTabs.tsx +58 -0
  179. package/src/contexts/SettingsContext.tsx +19 -20
  180. package/src/features/audit/adapters/inbound/react/AuditRuleList.tsx +76 -0
  181. package/src/features/audit/adapters/inbound/react/AuditRulePanel.tsx +30 -0
  182. package/src/features/audit/adapters/inbound/react/CycleInspector.tsx +116 -0
  183. package/src/features/audit/adapters/inbound/react/CyclicDependenciesRuleDetails.tsx +124 -0
  184. package/src/features/audit/adapters/inbound/react/useCycleSelection.ts +47 -0
  185. package/src/features/audit/application/use-cases/createAuditFromSnapshot.ts +28 -0
  186. package/src/features/audit/application/use-cases/resolveSelectedCycleIds.ts +18 -0
  187. package/src/features/audit/composition/createAuditAsync.ts +11 -0
  188. package/src/features/audit/domain/evaluateAuditRules.ts +42 -0
  189. package/src/features/audit/domain/hasBlockingAuditRuleFailure.ts +6 -0
  190. package/src/features/audit/domain/resolveAuditConfiguration.ts +27 -0
  191. package/src/features/audit/utils/cycleVisualization.ts +86 -0
  192. package/src/features/dependency-analysis/adapters/outbound/dependency-graph/analyzeDependencyImportsAsync.ts +172 -0
  193. package/src/features/dependency-analysis/adapters/outbound/dependency-graph/analyzeTypeScriptImportsAsync.ts +6 -0
  194. package/src/features/graph-view/adapters/inbound/react/DependencyGraphView.tsx +184 -0
  195. package/src/features/graph-view/adapters/inbound/react/GraphZoomControls.tsx +48 -0
  196. package/src/features/graph-view/adapters/inbound/react/applyGraphInteractionPresentation.ts +35 -0
  197. package/src/features/graph-view/adapters/inbound/react/createGraphViewModel.ts +240 -0
  198. package/src/features/graph-view/adapters/inbound/react/createGraphViewStyles.ts +37 -0
  199. package/src/features/graph-view/adapters/inbound/react/useGraphFocus.ts +104 -0
  200. package/src/features/graph-view/adapters/inbound/react/useGraphInteractions.ts +50 -0
  201. package/src/features/graph-view/adapters/inbound/react/useGraphProjection.ts +71 -0
  202. package/src/features/graph-view/adapters/inbound/react/useGraphViewport.ts +48 -0
  203. package/src/features/graph-view/domain/getDependencyHighlightClasses.ts +39 -0
  204. package/src/features/graph-view/domain/reduceGraphInteraction.ts +23 -0
  205. package/src/features/graph-view/utils/createCompoundOpacityIndex.ts +38 -0
  206. package/src/features/graph-view/utils/projectVisibleGraph.ts +109 -0
  207. package/src/features/graph-view/utils/readNodeDefinitionId.ts +7 -0
  208. package/src/features/graph-view/utils/removeEmptyStructuralNodes.ts +27 -0
  209. package/src/features/project-analysis/adapters/outbound/filesystem/readProjectSnapshotAsync.ts +12 -0
  210. package/src/features/project-analysis/application/use-cases/createProjectSnapshotReader.ts +29 -0
  211. package/src/features/project-analysis/composition/loadProjectOverviewAsync.ts +14 -0
  212. package/src/features/project-analysis/composition/loadProjectSnapshotAsync.ts +12 -0
  213. package/src/features/project-tree/adapters/inbound/react/ProjectTreePanel.tsx +52 -0
  214. package/src/features/project-tree/adapters/inbound/react/useProjectTreeExpansion.ts +46 -0
  215. package/src/features/project-tree/application/use-cases/buildProjectTree.ts +61 -0
  216. package/src/features/project-tree/application/use-cases/resolveProjectTreeNavigation.ts +13 -0
  217. package/src/features/project-tree/utils/findProjectTreeNode.ts +14 -0
  218. package/src/features/project-tree/utils/findProjectTreeNodeByGraphPackage.ts +21 -0
  219. package/src/features/project-tree/utils/getProjectTreeAncestorIds.ts +26 -0
  220. package/src/features/settings/utils/readSettingsEnvironment.ts +53 -0
  221. package/src/i18n/en.ts +21 -2
  222. package/src/i18n/i18n.ts +2 -3
  223. package/src/layouts/breadthfirst/style.ts +1 -0
  224. package/src/layouts/circle/style.ts +1 -0
  225. package/src/layouts/concentric/style.ts +1 -0
  226. package/src/layouts/elk/layout.ts +1 -3
  227. package/src/layouts/elk/style.ts +1 -0
  228. package/src/layouts/getWeightBuckets.ts +3 -1
  229. package/src/layouts/grid/style.ts +1 -0
  230. package/src/layouts/index.ts +0 -4
  231. package/src/layouts/style.ts +172 -25
  232. package/src/screens/home/Home.tsx +73 -25
  233. package/src/screens/home/HomeGraph.tsx +43 -0
  234. package/src/screens/home/HomeSidebar.tsx +129 -0
  235. package/src/shared/types/index.ts +0 -9
  236. package/src/shared/utils/getProjectName.ts +4 -1
  237. package/src/shared/utils/parseProjectPath.ts +1 -0
  238. package/src/shared/utils/toPosix.ts +1 -1
  239. package/src/types/audit.ts +76 -0
  240. package/src/types/auditVisualization.ts +22 -0
  241. package/src/types/graphInteraction.ts +9 -0
  242. package/src/types/parserSelection.ts +11 -0
  243. package/src/types/projectAnalysis.ts +20 -0
  244. package/src/types/projectAnalysisActionResult.ts +2 -0
  245. package/src/types/projectTree.ts +7 -0
  246. package/src/utils/filter/filterByPackagePrefix.ts +25 -7
  247. package/src/utils/filter/filterEmptyPackages.ts +28 -29
  248. package/src/utils/filter/filterSubPackagesFromDepth.ts +20 -17
  249. package/src/utils/filter/filterVendorPackages.ts +1 -1
  250. package/src/utils/filter/isIntrinsicGraphNode.ts +8 -0
  251. package/src/utils/filter/toggleCompoundNodes.ts +41 -31
  252. package/src/utils/runProjectAnalysisActionAsync.ts +14 -0
  253. package/tsconfig.json +11 -3
  254. package/.next/app-build-manifest.json +0 -29
  255. package/.next/cache/.previewinfo +0 -1
  256. package/.next/cache/.rscinfo +0 -1
  257. package/.next/cache/.tsbuildinfo +0 -1
  258. package/.next/cache/chrome-devtools-workspace-uuid +0 -1
  259. package/.next/cache/eslint/.cache_6t30dc +0 -1
  260. package/.next/cache/webpack/client-production/0.pack +0 -4
  261. package/.next/cache/webpack/client-production/1.pack +0 -0
  262. package/.next/cache/webpack/client-production/10.pack +0 -0
  263. package/.next/cache/webpack/client-production/11.pack +0 -0
  264. package/.next/cache/webpack/client-production/12.pack +0 -0
  265. package/.next/cache/webpack/client-production/13.pack +0 -0
  266. package/.next/cache/webpack/client-production/14.pack +0 -0
  267. package/.next/cache/webpack/client-production/15.pack +0 -0
  268. package/.next/cache/webpack/client-production/16.pack +0 -0
  269. package/.next/cache/webpack/client-production/2.pack +0 -0
  270. package/.next/cache/webpack/client-production/3.pack +0 -0
  271. package/.next/cache/webpack/client-production/4.pack +0 -0
  272. package/.next/cache/webpack/client-production/5.pack +0 -0
  273. package/.next/cache/webpack/client-production/6.pack +0 -0
  274. package/.next/cache/webpack/client-production/7.pack +0 -0
  275. package/.next/cache/webpack/client-production/8.pack +0 -0
  276. package/.next/cache/webpack/client-production/9.pack +0 -0
  277. package/.next/cache/webpack/client-production/index.pack +0 -0
  278. package/.next/cache/webpack/client-production/index.pack.old +0 -0
  279. package/.next/cache/webpack/edge-server-production/0.pack +0 -0
  280. package/.next/cache/webpack/edge-server-production/index.pack +0 -0
  281. package/.next/cache/webpack/edge-server-production/index.pack.old +0 -0
  282. package/.next/cache/webpack/server-production/0.pack +0 -0
  283. package/.next/cache/webpack/server-production/1.pack +0 -0
  284. package/.next/cache/webpack/server-production/10.pack +0 -4
  285. package/.next/cache/webpack/server-production/11.pack +0 -0
  286. package/.next/cache/webpack/server-production/12.pack +0 -0
  287. package/.next/cache/webpack/server-production/13.pack +0 -0
  288. package/.next/cache/webpack/server-production/14.pack +0 -0
  289. package/.next/cache/webpack/server-production/15.pack +0 -0
  290. package/.next/cache/webpack/server-production/16.pack +0 -0
  291. package/.next/cache/webpack/server-production/17.pack +0 -0
  292. package/.next/cache/webpack/server-production/18.pack +0 -4
  293. package/.next/cache/webpack/server-production/2.pack +0 -0
  294. package/.next/cache/webpack/server-production/3.pack +0 -0
  295. package/.next/cache/webpack/server-production/4.pack +0 -0
  296. package/.next/cache/webpack/server-production/5.pack +0 -0
  297. package/.next/cache/webpack/server-production/6.pack +0 -0
  298. package/.next/cache/webpack/server-production/7.pack +0 -0
  299. package/.next/cache/webpack/server-production/8.pack +0 -0
  300. package/.next/cache/webpack/server-production/9.pack +0 -0
  301. package/.next/cache/webpack/server-production/index.pack +0 -0
  302. package/.next/cache/webpack/server-production/index.pack.old +0 -0
  303. package/.next/diagnostics/build-diagnostics.json +0 -6
  304. package/.next/diagnostics/framework.json +0 -1
  305. package/.next/server/app/index.html +0 -1
  306. package/.next/server/app/index.meta +0 -7
  307. package/.next/server/app/index.rsc +0 -20
  308. package/.next/server/chunks/548.js +0 -6
  309. package/.next/server/chunks/610.js +0 -7
  310. package/.next/server/chunks/985.js +0 -22
  311. package/.next/server/middleware-react-loadable-manifest.js +0 -1
  312. package/.next/server/pages/_app.js +0 -1
  313. package/.next/server/pages/_app.js.nft.json +0 -1
  314. package/.next/server/pages/_document.js +0 -1
  315. package/.next/server/pages/_document.js.nft.json +0 -1
  316. package/.next/server/pages/_error.js +0 -19
  317. package/.next/server/pages/_error.js.nft.json +0 -1
  318. package/.next/server/webpack-runtime.js +0 -1
  319. package/.next/static/_YC2p40ZiYT2Gcw-h_tjC/_buildManifest.js +0 -1
  320. package/.next/static/chunks/4bd1b696-cf72ae8a39fa05aa.js +0 -1
  321. package/.next/static/chunks/680-55ad17895c8fb70b.js +0 -1
  322. package/.next/static/chunks/90542734-29e255269788f1bd.js +0 -1
  323. package/.next/static/chunks/91dbc596-b9cdccd3f5cba95d.js +0 -1
  324. package/.next/static/chunks/964-b440f7af22c41ca5.js +0 -1
  325. package/.next/static/chunks/app/_not-found/page-11e4fea8e290f304.js +0 -1
  326. package/.next/static/chunks/app/layout-8aae7fba5ed8afb3.js +0 -1
  327. package/.next/static/chunks/app/page-91450b1230da8bca.js +0 -1
  328. package/.next/static/chunks/framework-bc35dade6ff9684e.js +0 -1
  329. package/.next/static/chunks/main-9e86d333c1e02dd1.js +0 -1
  330. package/.next/static/chunks/main-app-69a9ff75ff3465ca.js +0 -1
  331. package/.next/static/chunks/pages/_app-0a0020ddd67f79cf.js +0 -1
  332. package/.next/static/chunks/pages/_error-03529f2c21436739.js +0 -1
  333. package/.next/static/chunks/webpack-7adfafc806738767.js +0 -1
  334. package/.next/static/css/ec31779168107a11.css +0 -3
  335. package/.next/static/media/4cf2300e9c8272f7-s.p.woff2 +0 -0
  336. package/.next/static/media/747892c23ea88013-s.woff2 +0 -0
  337. package/.next/static/media/8d697b304b401681-s.woff2 +0 -0
  338. package/.next/static/media/93f479601ee12b01-s.p.woff2 +0 -0
  339. package/.next/static/media/9610d9e46709d722-s.woff2 +0 -0
  340. package/.next/static/media/ba015fad6dcf6784-s.woff2 +0 -0
  341. package/.next/trace +0 -2
  342. package/.next/types/app/layout.ts +0 -84
  343. package/.next/types/app/page.ts +0 -84
  344. package/.next/types/cache-life.d.ts +0 -141
  345. package/.next/types/package.json +0 -1
  346. package/src/app/actions/graph.actions.ts +0 -25
  347. package/src/app/utils/parser/cpp/extractCppPackageFromImport.ts +0 -18
  348. package/src/app/utils/parser/delphi/extractPackageFromImport.ts +0 -21
  349. package/src/app/utils/parser/java/extractJavaPackageFromImport.ts +0 -39
  350. package/src/app/utils/parser/java/getIntrinsicPackagesRecursive.ts +0 -33
  351. package/src/app/utils/parser/kotlin/extractPackageFromImport.ts +0 -19
  352. package/src/app/utils/parser/python/extractPythonPackageFromImport.ts +0 -18
  353. package/src/app/utils/parser/typescript/extractTypeScriptPackageFromImport.ts +0 -18
  354. package/src/components/Cytoscape.tsx +0 -23
  355. package/src/components/Setting.tsx +0 -17
  356. package/src/components/Settings.tsx +0 -189
  357. package/src/components/ZoomInput.tsx +0 -94
  358. package/src/components/useCytoscape.ts +0 -343
  359. package/src/layouts/getLayoutStyle.ts +0 -19
  360. package/src/shared/utils/detectLanguage.ts +0 -255
  361. package/src/shared/utils/getJsonAsync.ts +0 -13
  362. package/src/shared/utils/parseEnv.ts +0 -91
  363. package/src/store/useLocalStorage.ts +0 -29
  364. package/src/utils/hasChildren.ts +0 -7
  365. /package/.next/{react-loadable-manifest.json → server/app/_global-error/page/react-loadable-manifest.json} +0 -0
  366. /package/.next/static/chunks/{polyfills-42372ed130431b0a.js → 0cz1d0mv5g_q7.js} +0 -0
  367. /package/.next/static/{_YC2p40ZiYT2Gcw-h_tjC → qznDSVYlkQxVdRx-TE_Wk}/_ssgManifest.js +0 -0
@@ -0,0 +1,116 @@
1
+ 'use client';
2
+ import { XIcon } from 'lucide-react';
3
+ import React from 'react';
4
+
5
+ import { t } from '@/i18n/i18n';
6
+ import type { CycleEdgeEvidence } from '@/types/audit';
7
+ import type { CycleInspection } from '@/types/auditVisualization';
8
+
9
+ /*** Renders detailed cycle evidence outside the constrained settings sidebar. */
10
+ export function CycleInspector({ inspection, onClose }: CycleInspectorProps) {
11
+ return (
12
+ <aside className="absolute top-4 right-4 z-20 w-[24rem] max-w-[calc(100%-2rem)] rounded-lg border border-neutral-200 bg-white/95 shadow-xl backdrop-blur dark:border-neutral-700 dark:bg-neutral-950/95">
13
+ <InspectorHeader inspection={inspection} onClose={onClose} />
14
+ <div className="max-h-[60vh] overflow-y-auto p-4">
15
+ <section>
16
+ <h4 className="text-xs font-semibold uppercase tracking-wide text-neutral-500">
17
+ {t('audit.cyclePath')}
18
+ </h4>
19
+ <code className="mt-2 block break-words text-xs leading-5">
20
+ {inspection.cycle.packages.join(' → ')}
21
+ </code>
22
+ </section>
23
+ <CycleEvidence edges={inspection.cycle.edges} />
24
+ </div>
25
+ </aside>
26
+ );
27
+ }
28
+
29
+ /*** Renders cycle identity and summary metadata. */
30
+ function InspectorHeader({ inspection, onClose }: CycleInspectorProps) {
31
+ const packageCount = new Set(inspection.cycle.packages).size;
32
+
33
+ return (
34
+ <header className="flex items-start gap-3 border-b border-neutral-200 px-4 py-3 dark:border-neutral-800">
35
+ <span
36
+ aria-hidden="true"
37
+ className="mt-1 h-2.5 w-2.5 shrink-0 rounded-full"
38
+ style={{ backgroundColor: inspection.color }}
39
+ />
40
+ <div className="min-w-0 flex-1">
41
+ <h3 className="text-sm font-semibold">{inspection.label}</h3>
42
+ <p className="mt-0.5 text-xs text-neutral-500 dark:text-neutral-400">
43
+ {packageCount} {t('audit.packages')} · {inspection.cycle.edges.length}{' '}
44
+ {t('audit.dependencyEdges')}
45
+ </p>
46
+ </div>
47
+ <button
48
+ type="button"
49
+ aria-label={t('audit.closeInspector')}
50
+ onClick={onClose}
51
+ className="rounded p-1 text-neutral-500 hover:bg-neutral-100 hover:text-neutral-900 dark:hover:bg-neutral-800 dark:hover:text-neutral-100"
52
+ >
53
+ <XIcon size={16} />
54
+ </button>
55
+ </header>
56
+ );
57
+ }
58
+
59
+ /*** Renders collapsible evidence for every directed edge in the cycle. */
60
+ function CycleEvidence({ edges }: { readonly edges: readonly CycleEdgeEvidence[] }) {
61
+ return (
62
+ <section className="mt-4">
63
+ <h4 className="text-xs font-semibold uppercase tracking-wide text-neutral-500">
64
+ {t('audit.dependencyEvidence')}
65
+ </h4>
66
+ <div className="mt-2 space-y-2">
67
+ {edges.map((edge, index) => (
68
+ <EvidenceEdge edge={edge} index={index} key={edge.from + '→' + edge.to + ':' + index} />
69
+ ))}
70
+ </div>
71
+ </section>
72
+ );
73
+ }
74
+
75
+ /*** Renders one directed dependency edge and its source/import evidence. */
76
+ function EvidenceEdge({ edge, index }: EvidenceEdgeProps) {
77
+ return (
78
+ <details className="rounded border border-neutral-200 bg-neutral-50 px-3 py-2 text-xs dark:border-neutral-800 dark:bg-neutral-900">
79
+ <summary className="cursor-pointer font-medium">
80
+ {index + 1}. {edge.from} → {edge.to}
81
+ </summary>
82
+ <div className="mt-2 space-y-1 text-[11px] text-neutral-500 dark:text-neutral-400">
83
+ {edge.via.length === 0
84
+ ? t('audit.noEvidence')
85
+ : edge.via.map((evidence, evidenceIndex) => (
86
+ <div
87
+ key={
88
+ evidence.filePath +
89
+ ':' +
90
+ evidence.fileClass +
91
+ ':' +
92
+ evidence.importName +
93
+ ':' +
94
+ evidenceIndex
95
+ }
96
+ className="break-all"
97
+ >
98
+ <code>{evidence.filePath}</code>
99
+ <span> {t('audit.imports')} </span>
100
+ <code>{evidence.importName}</code>
101
+ </div>
102
+ ))}
103
+ </div>
104
+ </details>
105
+ );
106
+ }
107
+
108
+ interface CycleInspectorProps {
109
+ readonly inspection: CycleInspection;
110
+ readonly onClose: () => void;
111
+ }
112
+
113
+ interface EvidenceEdgeProps {
114
+ readonly edge: CycleEdgeEvidence;
115
+ readonly index: number;
116
+ }
@@ -0,0 +1,124 @@
1
+ 'use client';
2
+ import React from 'react';
3
+
4
+ import { SidebarRow } from '@/components/sidebar/SidebarRow';
5
+ import { SidebarSection } from '@/components/sidebar/SidebarSection';
6
+ import { ToggleSwitch } from '@/components/ToggleSwitch';
7
+ import {
8
+ createCycleInspection,
9
+ getCycleColor,
10
+ getCycleId,
11
+ } from '@/features/audit/utils/cycleVisualization';
12
+ import { t } from '@/i18n/i18n';
13
+ import type { PackageCycleDetail } from '@/types/audit';
14
+ import type { CycleInspection, CycleSelection } from '@/types/auditVisualization';
15
+
16
+ /*** Renders cycle findings with independent highlight switches and toggleable evidence selection. */
17
+ export function CyclicDependenciesRuleDetails({
18
+ cycles,
19
+ cycleSelection,
20
+ inspectedCycleId,
21
+ onCycleInspectionChange,
22
+ }: CyclicDependenciesRuleDetailsProps) {
23
+ return (
24
+ <SidebarSection
25
+ title={
26
+ <span className="flex items-center gap-2">
27
+ <span>{t('audit.rule.cyclicDependencies')}</span>
28
+ <span className="text-xs font-normal text-neutral-500 dark:text-neutral-400">
29
+ {cycles.length} {t('audit.cycles')}
30
+ </span>
31
+ </span>
32
+ }
33
+ >
34
+ {cycles.map((cycle, index) => (
35
+ <SidebarRow key={getCycleId(cycle)}>
36
+ <CycleRow
37
+ color={getCycleColor(index)}
38
+ cycle={cycle}
39
+ index={index}
40
+ inspected={inspectedCycleId === getCycleId(cycle)}
41
+ selected={cycleSelection.selectedIds.includes(getCycleId(cycle))}
42
+ onInspect={() =>
43
+ onCycleInspectionChange(
44
+ inspectedCycleId === getCycleId(cycle)
45
+ ? null
46
+ : createCycleInspection(cycle, index, t('audit.cycle') + ' ' + (index + 1))
47
+ )
48
+ }
49
+ onSelectedChange={selected => {
50
+ cycleSelection.setSelected(getCycleId(cycle), selected);
51
+ if (!selected && inspectedCycleId === getCycleId(cycle))
52
+ onCycleInspectionChange(null);
53
+ }}
54
+ />
55
+ </SidebarRow>
56
+ ))}
57
+ </SidebarSection>
58
+ );
59
+ }
60
+
61
+ /*** Renders one compact cycle switch; full evidence stays in the graph inspector. */
62
+ function CycleRow({
63
+ color,
64
+ cycle,
65
+ index,
66
+ inspected,
67
+ onInspect,
68
+ onSelectedChange,
69
+ selected,
70
+ }: CycleRowProps) {
71
+ const route = cycle.packages.join(' → ');
72
+ const packageCount = new Set(cycle.packages).size;
73
+ const label = t('audit.cycle') + ' ' + (index + 1);
74
+
75
+ return (
76
+ <div
77
+ className="flex items-start gap-2 rounded px-1 py-1"
78
+ style={selected || inspected ? { backgroundColor: color + '1a' } : undefined}
79
+ >
80
+ <button
81
+ type="button"
82
+ onClick={onInspect}
83
+ aria-expanded={inspected}
84
+ className="min-w-0 flex-1 cursor-pointer rounded text-left focus-visible:outline-2 focus-visible:outline-offset-2"
85
+ style={inspected ? { boxShadow: `inset 3px 0 ${color}`, paddingLeft: 6 } : undefined}
86
+ title={route}
87
+ >
88
+ <span className="flex items-center gap-1.5 text-xs font-medium">
89
+ <span>{label}</span>
90
+ <span className="ml-auto shrink-0 text-[10px] font-normal text-neutral-500 dark:text-neutral-400">
91
+ {packageCount} pkg
92
+ </span>
93
+ </span>
94
+ <code className="mt-1 block truncate text-[11px] text-neutral-500 dark:text-neutral-400">
95
+ {route}
96
+ </code>
97
+ </button>
98
+ <ToggleSwitch
99
+ ariaLabel={label}
100
+ checkedColor={color}
101
+ id={'switch-audit-cycle-' + index}
102
+ onToggle={() => onSelectedChange(!selected)}
103
+ value={selected}
104
+ />
105
+ </div>
106
+ );
107
+ }
108
+
109
+ interface CyclicDependenciesRuleDetailsProps {
110
+ readonly inspectedCycleId?: string | null;
111
+ readonly cycles: readonly PackageCycleDetail[];
112
+ readonly cycleSelection: CycleSelection;
113
+ readonly onCycleInspectionChange: (inspection: CycleInspection | null) => void;
114
+ }
115
+
116
+ interface CycleRowProps {
117
+ readonly inspected: boolean;
118
+ readonly color: string;
119
+ readonly cycle: PackageCycleDetail;
120
+ readonly index: number;
121
+ readonly onInspect: () => void;
122
+ readonly onSelectedChange: (selected: boolean) => void;
123
+ readonly selected: boolean;
124
+ }
@@ -0,0 +1,47 @@
1
+ 'use client';
2
+ import { isRecord } from '@ankhorage/utility/object';
3
+ import { useLocalStorage } from 'ankh-hooks/store';
4
+ import { useCallback, useMemo } from 'react';
5
+
6
+ import { resolveSelectedCycleIds } from '@/features/audit/application/use-cases/resolveSelectedCycleIds';
7
+ import { createCycleHighlights } from '@/features/audit/utils/cycleVisualization';
8
+ import { readSettingsEnvironment } from '@/features/settings/utils/readSettingsEnvironment';
9
+ import type { PackageCycleDetail } from '@/types/audit';
10
+ import type { CycleSelection } from '@/types/auditVisualization';
11
+
12
+ /*** Owns persisted cycle choices independently of sidebar mounting and navigation. */
13
+ export function useCycleSelection(cycles: readonly PackageCycleDetail[]): CycleSelection {
14
+ const project = process.env.NEXT_PUBLIC_PROJECT_PATH ?? 'default';
15
+ const [stored, setStored] = useLocalStorage<string>('pkgviz:cycles:v1:' + project, '{}');
16
+ const choices = useMemo(() => readChoices(stored), [stored]);
17
+ const enabledByDefault = readSettingsEnvironment().showCycles;
18
+ const selectedIds = useMemo(
19
+ () => resolveSelectedCycleIds(cycles, choices, enabledByDefault),
20
+ [cycles, choices, enabledByDefault]
21
+ );
22
+ const highlights = useMemo(
23
+ () => createCycleHighlights(cycles, selectedIds),
24
+ [cycles, selectedIds]
25
+ );
26
+ const setSelected = useCallback(
27
+ (id: string, selected: boolean) => {
28
+ setStored(previous => JSON.stringify({ ...readChoices(previous), [id]: selected }));
29
+ },
30
+ [setStored]
31
+ );
32
+
33
+ return useMemo(
34
+ () => ({ highlights, selectedIds, setSelected }),
35
+ [highlights, selectedIds, setSelected]
36
+ );
37
+ }
38
+
39
+ /*** Parses the versioned persistence payload at the browser boundary. */
40
+ function readChoices(value: string): Record<string, unknown> {
41
+ try {
42
+ const parsed: unknown = JSON.parse(value);
43
+ return isRecord(parsed) ? parsed : {};
44
+ } catch {
45
+ return {};
46
+ }
47
+ }
@@ -0,0 +1,28 @@
1
+ import { getPackageCyclesWithMembers } from '@/app/utils/markCyclicPackages';
2
+ import { evaluateAuditRules } from '@/features/audit/domain/evaluateAuditRules';
3
+ import { resolveAuditConfiguration } from '@/features/audit/domain/resolveAuditConfiguration';
4
+ import { getProjectName } from '@/shared/utils/getProjectName';
5
+ import type { Audit, ResolveAuditConfigurationInput } from '@/types/audit';
6
+ import type { ProjectSnapshot } from '@/types/projectAnalysis';
7
+
8
+ /*** Evaluates audit policy against the same files and weighted graph used by the visualization. */
9
+ export function createAuditFromSnapshot(
10
+ snapshot: ProjectSnapshot,
11
+ configurationInput: ResolveAuditConfigurationInput = {}
12
+ ): Audit {
13
+ const configuration = resolveAuditConfiguration(configurationInput);
14
+ const cyclicPackages = getPackageCyclesWithMembers(snapshot.files, snapshot.graph).cycles;
15
+ const rules = evaluateAuditRules({ configuration, cyclicPackages });
16
+
17
+ return {
18
+ configuration,
19
+ evaluation: { cyclicPackages, rules },
20
+ files: snapshot.files,
21
+ meta: {
22
+ language: snapshot.language,
23
+ projectName: getProjectName(snapshot.projectPath),
24
+ timeStart: snapshot.timeStart,
25
+ timeEnd: Date.now(),
26
+ },
27
+ };
28
+ }
@@ -0,0 +1,18 @@
1
+ import { isRecord } from '@ankhorage/utility/object';
2
+
3
+ import { getCycleId } from '@/features/audit/utils/cycleVisualization';
4
+ import type { PackageCycleDetail } from '@/types/audit';
5
+
6
+ /*** Resolves explicit per-cycle choices before the environment default, ignoring invalid storage. */
7
+ export function resolveSelectedCycleIds(
8
+ cycles: readonly PackageCycleDetail[],
9
+ stored: unknown,
10
+ enabledByDefault: boolean
11
+ ): readonly string[] {
12
+ const choices = new Map(isRecord(stored) ? Object.entries(stored) : []);
13
+ return cycles.flatMap(cycle => {
14
+ const id = getCycleId(cycle);
15
+ const choice = choices.get(id);
16
+ return (typeof choice === 'boolean' ? choice : enabledByDefault) ? [id] : [];
17
+ });
18
+ }
@@ -0,0 +1,11 @@
1
+ import { createAuditFromSnapshot } from '@/features/audit/application/use-cases/createAuditFromSnapshot';
2
+ import { loadProjectSnapshotAsync } from '@/features/project-analysis/composition/loadProjectSnapshotAsync';
3
+ import type { Audit, ResolveAuditConfigurationInput } from '@/types/audit';
4
+
5
+ /*** Composes a fresh project snapshot and audit evaluation without UI environment state. */
6
+ export async function createAuditAsync(
7
+ projectPath: string,
8
+ configurationInput: ResolveAuditConfigurationInput = {}
9
+ ): Promise<Audit> {
10
+ return createAuditFromSnapshot(await loadProjectSnapshotAsync(projectPath), configurationInput);
11
+ }
@@ -0,0 +1,42 @@
1
+ import type {
2
+ AuditRuleConfiguration,
3
+ AuditRuleResult,
4
+ CyclicDependenciesEvidence,
5
+ EvaluateAuditRulesInput,
6
+ PackageCycleDetail,
7
+ } from '@/types/audit';
8
+
9
+ /*** Evaluates every enabled PKGViz audit rule against one analysis result. */
10
+ export function evaluateAuditRules(input: EvaluateAuditRulesInput): readonly AuditRuleResult[] {
11
+ const mode = findRuleMode(input.configuration.rules, 'cyclic-dependencies');
12
+ if (mode === 'off') return [];
13
+
14
+ return [evaluateCyclicDependencies(input.cyclicPackages, mode)];
15
+ }
16
+
17
+ /*** Evaluates cyclic dependencies while applying the configured enforcement mode. */
18
+ function evaluateCyclicDependencies(
19
+ cycles: readonly PackageCycleDetail[],
20
+ mode: Exclude<AuditRuleConfiguration['mode'], 'off'>
21
+ ): AuditRuleResult<CyclicDependenciesEvidence> {
22
+ const failed = cycles.length > 0;
23
+
24
+ return {
25
+ id: 'cyclic-dependencies',
26
+ status: failed ? 'failed' : 'passed',
27
+ policy: mode === 'block' ? 'blocking' : 'advisory',
28
+ message: failed
29
+ ? `Detected ${cycles.length} cyclic package dependenc${cycles.length === 1 ? 'y' : 'ies'}.`
30
+ : 'No cyclic package dependencies detected.',
31
+ details: cycles.map(cycle => cycle.packages.join(' → ')),
32
+ evidence: { cycles },
33
+ };
34
+ }
35
+
36
+ /*** Returns the effective mode for one known audit rule. */
37
+ function findRuleMode(
38
+ rules: readonly AuditRuleConfiguration[],
39
+ id: AuditRuleConfiguration['id']
40
+ ): AuditRuleConfiguration['mode'] {
41
+ return rules.find(rule => rule.id === id)?.mode ?? 'off';
42
+ }
@@ -0,0 +1,6 @@
1
+ import type { AuditRuleResult } from '@/types/audit';
2
+
3
+ /*** Reports whether any blocking audit rule failed. */
4
+ export function hasBlockingAuditRuleFailure(results: readonly AuditRuleResult[]): boolean {
5
+ return results.some(result => result.policy === 'blocking' && result.status === 'failed');
6
+ }
@@ -0,0 +1,27 @@
1
+ import type {
2
+ AuditConfiguration,
3
+ AuditRuleConfiguration,
4
+ ResolveAuditConfigurationInput,
5
+ } from '@/types/audit';
6
+
7
+ /*** Resolves explicit audit overrides against the stable default rule policy. */
8
+ export function resolveAuditConfiguration(
9
+ input: ResolveAuditConfigurationInput = {}
10
+ ): AuditConfiguration {
11
+ let cyclicDependenciesMode: AuditRuleConfiguration['mode'] = 'block';
12
+
13
+ for (const rule of input.rules ?? []) {
14
+ switch (rule.id) {
15
+ case 'cyclic-dependencies':
16
+ cyclicDependenciesMode = rule.mode;
17
+ break;
18
+ default:
19
+ throw new Error(`Unknown audit rule "${rule.id}".`);
20
+ }
21
+ }
22
+
23
+ return {
24
+ failOnRuleViolation: input.failOnRuleViolation ?? true,
25
+ rules: [{ id: 'cyclic-dependencies', mode: cyclicDependenciesMode }],
26
+ };
27
+ }
@@ -0,0 +1,86 @@
1
+ import type { PackageCycleDetail } from '@/types/audit';
2
+ import type { CycleFocus, CycleHighlight, CycleInspection } from '@/types/auditVisualization';
3
+
4
+ const CYCLE_ERROR_COLORS = [
5
+ '#dc2626',
6
+ '#7f1d1d',
7
+ '#fb7185',
8
+ '#be123c',
9
+ '#ef4444',
10
+ '#450a0a',
11
+ '#f43f5e',
12
+ '#991b1b',
13
+ ] as const;
14
+
15
+ /*** Returns a stable distinct error-red color for one cycle occurrence. */
16
+ export function getCycleColor(index: number): string {
17
+ return CYCLE_ERROR_COLORS[index % CYCLE_ERROR_COLORS.length];
18
+ }
19
+
20
+ /*** Returns the stable UI identity for one cycle occurrence. */
21
+ export function getCycleId(cycle: PackageCycleDetail): string {
22
+ return JSON.stringify([
23
+ [...new Set(cycle.packages)].sort(),
24
+ cycle.edges
25
+ .map(edge => [edge.from, edge.to])
26
+ .sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b))),
27
+ ]);
28
+ }
29
+
30
+ /*** Creates one stable cycle highlight descriptor. */
31
+ function createCycleHighlight(cycle: PackageCycleDetail, index: number): CycleHighlight {
32
+ return {
33
+ id: getCycleId(cycle),
34
+ color: getCycleColor(index),
35
+ cycle,
36
+ };
37
+ }
38
+
39
+ /*** Creates graph highlights for the selected cycle occurrence IDs. */
40
+ export function createCycleHighlights(
41
+ cycles: readonly PackageCycleDetail[],
42
+ selectedCycleIds: readonly string[]
43
+ ): readonly CycleHighlight[] {
44
+ return cycles.flatMap((cycle, index) => {
45
+ const id = getCycleId(cycle);
46
+ return selectedCycleIds.includes(id) ? [createCycleHighlight(cycle, index)] : [];
47
+ });
48
+ }
49
+
50
+ /*** Creates the narrowest scope and least depth that expose every active cycle package. */
51
+ export function createCycleFocus(highlights: readonly CycleHighlight[]): CycleFocus | null {
52
+ const packageNames = [...new Set(highlights.flatMap(highlight => highlight.cycle.packages))];
53
+ if (packageNames.length === 0) return null;
54
+
55
+ const packageSegments = packageNames.map(packageName => packageName.split('.'));
56
+ const [firstSegments, ...remainingSegments] = packageSegments;
57
+ const mismatchIndex = firstSegments.findIndex((segment, index) =>
58
+ remainingSegments.some(segments => segments.at(index) !== segment)
59
+ );
60
+ const commonDepth = mismatchIndex === -1 ? firstSegments.length : mismatchIndex;
61
+ const commonSegments = firstSegments.slice(0, commonDepth);
62
+ const commonPackage = commonSegments.join('.');
63
+ const focusSegments = packageNames.includes(commonPackage)
64
+ ? commonSegments.slice(0, -1)
65
+ : commonSegments;
66
+
67
+ return {
68
+ currentPackage: focusSegments.join('.'),
69
+ packageDepth: Math.max(
70
+ 1,
71
+ ...packageSegments.map(segments => segments.length - focusSegments.length)
72
+ ),
73
+ };
74
+ }
75
+
76
+ /*** Creates the inspector descriptor for one cycle row. */
77
+ export function createCycleInspection(
78
+ cycle: PackageCycleDetail,
79
+ index: number,
80
+ label: string
81
+ ): CycleInspection {
82
+ return {
83
+ ...createCycleHighlight(cycle, index),
84
+ label,
85
+ };
86
+ }
@@ -0,0 +1,172 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+
4
+ import {
5
+ createDependencyGraphAsync,
6
+ type DependencyGraphNodeData,
7
+ type DependencyImportEvidence,
8
+ } from '@ankhorage/dependency-graph';
9
+ import { resolveFileSystemPathWithinRoot } from '@ankhorage/utility/node/fs';
10
+
11
+ import type { ImportDefinition } from '@/shared/types';
12
+ import { toPosix } from '@/shared/utils/toPosix';
13
+
14
+ interface OrderedImport {
15
+ readonly definition: ImportDefinition;
16
+ readonly position: number;
17
+ readonly ordinal: number;
18
+ }
19
+
20
+ interface AppendEvidenceInput {
21
+ readonly analysisRoot: string;
22
+ readonly evidence: DependencyImportEvidence;
23
+ readonly importNameMode: ImportNameMode;
24
+ readonly imports: Map<string, OrderedImport[]>;
25
+ readonly intrinsicMode: ImportIntrinsicMode;
26
+ readonly ordinal: number;
27
+ readonly pkg: string;
28
+ readonly projectRoot: string;
29
+ readonly sourceTextByFile: Map<string, Promise<string>>;
30
+ }
31
+
32
+ /*** Returns PKGViz import definitions from the canonical dependency analyzer. */
33
+ export async function analyzeDependencyImportsAsync(
34
+ projectRoot: string,
35
+ analysisRoot: string = projectRoot,
36
+ importNameMode: ImportNameMode = 'package',
37
+ intrinsicMode: ImportIntrinsicMode = 'canonical'
38
+ ): Promise<ReadonlyMap<string, readonly ImportDefinition[]>> {
39
+ const canonicalProjectRoot = resolveFileSystemPathWithinRoot(projectRoot, '.', {
40
+ allowRoot: true,
41
+ });
42
+ const canonicalAnalysisRoot = resolveFileSystemPathWithinRoot(
43
+ canonicalProjectRoot,
44
+ resolveFileSystemPathWithinRoot(analysisRoot, '.', { allowRoot: true }),
45
+ { allowRoot: true }
46
+ );
47
+ const graph = await createDependencyGraphAsync({
48
+ projects: [{ id: 'current', rootPath: canonicalProjectRoot }],
49
+ });
50
+ const nodes = new Map(graph.nodes.map(node => [node.id, node.data] as const));
51
+ const imports = new Map<string, OrderedImport[]>();
52
+ const sourceTextByFile = new Map<string, Promise<string>>();
53
+ let ordinal = 0;
54
+
55
+ for (const edge of graph.edges) {
56
+ const target = nodes.get(edge.target);
57
+ if (target === undefined) {
58
+ throw new Error(`Missing dependency-graph target node: ${edge.target}`);
59
+ }
60
+
61
+ const pkg = targetPackage(target);
62
+ for (const evidence of edge.data.evidence) {
63
+ if (pkg === '') continue;
64
+ ordinal = await appendEvidenceAsync({
65
+ analysisRoot: canonicalAnalysisRoot,
66
+ evidence,
67
+ importNameMode,
68
+ imports,
69
+ intrinsicMode,
70
+ ordinal,
71
+ pkg,
72
+ projectRoot: canonicalProjectRoot,
73
+ sourceTextByFile,
74
+ });
75
+ }
76
+ }
77
+
78
+ return materializeImports(imports);
79
+ }
80
+
81
+ type ImportNameMode = 'package' | 'specifier';
82
+ type ImportIntrinsicMode =
83
+ 'canonical' | 'delphi-standard-library' | 'kotlin-standard-library' | 'python-legacy';
84
+
85
+ /*** Adds one canonical evidence item while retaining the original source declaration position. */
86
+ async function appendEvidenceAsync(input: AppendEvidenceInput): Promise<number> {
87
+ const absoluteSourceFile = path.resolve(input.projectRoot, input.evidence.sourceFile);
88
+ const sourceFile = toPosix(path.relative(input.analysisRoot, absoluteSourceFile));
89
+ const sourceText = await readSourceTextAsync(input.sourceTextByFile, absoluteSourceFile);
90
+ const current = input.imports.get(sourceFile) ?? [];
91
+
92
+ current.push({
93
+ definition: {
94
+ name: input.importNameMode === 'specifier' ? input.evidence.specifier : input.pkg,
95
+ pkg: input.pkg,
96
+ isIntrinsic: isIntrinsicImport(input.evidence, input.intrinsicMode),
97
+ },
98
+ position: sourceText.indexOf(input.evidence.specifier),
99
+ ordinal: input.ordinal,
100
+ });
101
+ input.imports.set(sourceFile, current);
102
+ return input.ordinal + 1;
103
+ }
104
+
105
+ /*** Converts collected import evidence into source-order PKGViz definitions. */
106
+ function materializeImports(
107
+ imports: ReadonlyMap<string, readonly OrderedImport[]>
108
+ ): ReadonlyMap<string, readonly ImportDefinition[]> {
109
+ return new Map(
110
+ [...imports].map(([sourceFile, entries]) => [
111
+ sourceFile,
112
+ [...entries]
113
+ .sort((left, right) => {
114
+ const leftPosition = left.position < 0 ? Number.MAX_SAFE_INTEGER : left.position;
115
+ const rightPosition = right.position < 0 ? Number.MAX_SAFE_INTEGER : right.position;
116
+ return leftPosition - rightPosition || left.ordinal - right.ordinal;
117
+ })
118
+ .map(({ definition }) => definition),
119
+ ])
120
+ );
121
+ }
122
+
123
+ /*** Reads source content once so adapter output preserves source declaration order. */
124
+ function readSourceTextAsync(
125
+ cache: Map<string, Promise<string>>,
126
+ sourceFile: string
127
+ ): Promise<string> {
128
+ const cached = cache.get(sourceFile);
129
+ if (cached !== undefined) return cached;
130
+
131
+ const sourceText = readOptionalSourceTextAsync(sourceFile);
132
+ cache.set(sourceFile, sourceText);
133
+ return sourceText;
134
+ }
135
+
136
+ /*** Missing evidence source files retain canonical ordering instead of failing analysis. */
137
+ async function readOptionalSourceTextAsync(sourceFile: string): Promise<string> {
138
+ try {
139
+ return await readFile(sourceFile, 'utf8');
140
+ } catch (error) {
141
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT') return '';
142
+ throw error;
143
+ }
144
+ }
145
+
146
+ /*** Preserves PKGViz presentation semantics independently from canonical graph classification. */
147
+ function isIntrinsicImport(evidence: DependencyImportEvidence, mode: ImportIntrinsicMode): boolean {
148
+ if (mode === 'canonical') return evidence.classification === 'intrinsic';
149
+ if (mode === 'python-legacy') return evidence.specifier.startsWith('.');
150
+ if (mode === 'delphi-standard-library') {
151
+ return (
152
+ evidence.specifier.startsWith('System.') ||
153
+ evidence.specifier.startsWith('Vcl.') ||
154
+ evidence.specifier.startsWith('FMX.') ||
155
+ evidence.specifier.startsWith('Data.') ||
156
+ evidence.specifier.startsWith('Web.')
157
+ );
158
+ }
159
+
160
+ return (
161
+ evidence.specifier.startsWith('kotlin.') ||
162
+ evidence.specifier.startsWith('java.') ||
163
+ evidence.specifier.startsWith('javax.')
164
+ );
165
+ }
166
+
167
+ /*** Maps canonical dependency-graph node metadata to PKGViz package notation. */
168
+ function targetPackage(target: DependencyGraphNodeData): string {
169
+ if (target.kind === 'module') return target.path ?? '';
170
+ if (target.classification === 'intrinsic' && target.focus && target.path === '.') return '';
171
+ return target.packageName ?? target.label;
172
+ }