pkgviz 0.7.7 → 0.8.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 (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/19mx3mg6lkumu.js +1 -0
  121. package/.next/static/chunks/1las_6j17xa9p.js +1 -0
  122. package/.next/static/chunks/1xk6snnh6e5_j.css +3 -0
  123. package/.next/static/chunks/2-rtuqsgzmno4.js +22 -0
  124. package/.next/static/chunks/27t_qfc-3_lzs.js +1 -0
  125. package/.next/static/chunks/2sswp23h6zh08.js +345 -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/rjdzHqzPw2GCNhDbjcu0-/_buildManifest.js +11 -0
  142. package/.next/static/rjdzHqzPw2GCNhDbjcu0-/_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 +94 -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 → rjdzHqzPw2GCNhDbjcu0-}/_ssgManifest.js +0 -0
@@ -1,26 +1,35 @@
1
1
  'use server';
2
- import { Language, type ParsedDirectory } from '@/shared/types';
3
-
4
- import { existsSync, readdirSync } from 'node:fs';
2
+ import { existsSync } from 'node:fs';
5
3
  import path from 'node:path';
6
- import { toPosix } from '@/shared/utils/toPosix';
7
- import { detectLanguage } from '@/shared/utils/detectLanguage';
8
- import { parseJavaFile } from '@/app/utils/parser/java/parseJavaFile';
9
- import { parseFile as parseTypeScriptFile } from '@/app/utils/parser/typescript/parseFile';
4
+
5
+ import {
6
+ readDirectoryWithinRoot,
7
+ resolveFileSystemPathWithinRoot,
8
+ } from '@ankhorage/utility/node/fs';
9
+
10
10
  import { parseCppFile } from '@/app/utils/parser/cpp/parseCppFile';
11
- import { parsePythonFile } from '@/app/utils/parser/python/parseFile';
12
11
  import { parseDelphiFile } from '@/app/utils/parser/delphi/parseFile';
12
+ import { parseJavaFile } from '@/app/utils/parser/java/parseJavaFile';
13
13
  import { parseKotlinFile } from '@/app/utils/parser/kotlin/parseFile';
14
- import { parseProjectPath } from '@/shared/utils/parseProjectPath';
14
+ import { parsePythonFile } from '@/app/utils/parser/python/parseFile';
15
+ import { parseFile as parseTypeScriptFile } from '@/app/utils/parser/typescript/parseFile';
16
+ import { analyzeDependencyImportsAsync } from '@/features/dependency-analysis/adapters/outbound/dependency-graph/analyzeDependencyImportsAsync';
15
17
  import { JAVA_ROOT } from '@/shared/constants';
18
+ import { type ImportDefinition, Language, type ParsedDirectory } from '@/shared/types';
19
+ import { parseProjectPath } from '@/shared/utils/parseProjectPath';
20
+ import { toPosix } from '@/shared/utils/toPosix';
16
21
 
17
- /**
22
+ import { inspectParserLanguageAsync } from './inspectParserLanguageAsync';
23
+
24
+ /***
18
25
  * Returns resolved root
19
26
  */
20
- export async function resolveRoot(dir: string, detectedLanguage: Language) {
27
+ async function resolveRoot(dir: string, detectedLanguage: Language) {
28
+ const projectRoot = resolveFileSystemPathWithinRoot(dir, '.', { allowRoot: true });
29
+
21
30
  switch (detectedLanguage) {
22
31
  case Language.Java: {
23
- const javaRoot = toPosix(path.resolve(dir, JAVA_ROOT));
32
+ const javaRoot = toPosix(resolveFileSystemPathWithinRoot(projectRoot, JAVA_ROOT));
24
33
  if (!existsSync(javaRoot)) {
25
34
  console.error('Failed to find:', JAVA_ROOT);
26
35
  throw new Error(`Invalid Java project structure. Missing ${JAVA_ROOT}`);
@@ -30,93 +39,91 @@ export async function resolveRoot(dir: string, detectedLanguage: Language) {
30
39
 
31
40
  case Language.TypeScript:
32
41
  // Normalize to an absolute project root
33
- return toPosix(path.resolve(dir));
42
+ return toPosix(projectRoot);
34
43
 
35
44
  case Language.Cpp:
36
45
  // For C++, look for src directory or use project root
37
- const cppSrcRoot = toPosix(path.resolve(dir, 'src'));
46
+ const cppSrcRoot = toPosix(resolveFileSystemPathWithinRoot(projectRoot, 'src'));
38
47
  if (existsSync(cppSrcRoot)) {
39
48
  return cppSrcRoot;
40
49
  }
41
- return toPosix(path.resolve(dir));
50
+ return toPosix(projectRoot);
42
51
 
43
52
  case Language.Python:
44
53
  // For Python, look for src directory or use project root
45
- const pythonSrcRoot = toPosix(path.resolve(dir, 'src'));
54
+ const pythonSrcRoot = toPosix(resolveFileSystemPathWithinRoot(projectRoot, 'src'));
46
55
  if (existsSync(pythonSrcRoot)) {
47
56
  return pythonSrcRoot;
48
57
  }
49
58
  // Also check for common Python app structure
50
- const appRoot = toPosix(path.resolve(dir, 'app'));
59
+ const appRoot = toPosix(resolveFileSystemPathWithinRoot(projectRoot, 'app'));
51
60
  if (existsSync(appRoot)) {
52
61
  return appRoot;
53
62
  }
54
- return toPosix(path.resolve(dir));
63
+ return toPosix(projectRoot);
55
64
 
56
65
  case Language.Delphi:
57
66
  // For Delphi, look for common source directories
58
- const delphiSrcRoot = toPosix(path.resolve(dir, 'src'));
67
+ const delphiSrcRoot = toPosix(resolveFileSystemPathWithinRoot(projectRoot, 'src'));
59
68
  if (existsSync(delphiSrcRoot)) {
60
69
  return delphiSrcRoot;
61
70
  }
62
71
  // Also check for Source directory (common in Delphi projects)
63
- const sourceRoot = toPosix(path.resolve(dir, 'Source'));
72
+ const sourceRoot = toPosix(resolveFileSystemPathWithinRoot(projectRoot, 'Source'));
64
73
  if (existsSync(sourceRoot)) {
65
74
  return sourceRoot;
66
75
  }
67
- return toPosix(path.resolve(dir));
76
+ return toPosix(projectRoot);
68
77
 
69
78
  case Language.Kotlin:
70
79
  // For Kotlin, look for src/main/kotlin directory (Gradle/Maven structure)
71
- const kotlinSrcRoot = toPosix(path.resolve(dir, 'src/main/kotlin'));
80
+ const kotlinSrcRoot = toPosix(
81
+ resolveFileSystemPathWithinRoot(projectRoot, 'src/main/kotlin')
82
+ );
72
83
  if (existsSync(kotlinSrcRoot)) {
73
84
  return kotlinSrcRoot;
74
85
  }
75
86
  // Fallback to src directory
76
- const kotlinAltSrcRoot = toPosix(path.resolve(dir, 'src'));
87
+ const kotlinAltSrcRoot = toPosix(resolveFileSystemPathWithinRoot(projectRoot, 'src'));
77
88
  if (existsSync(kotlinAltSrcRoot)) {
78
89
  return kotlinAltSrcRoot;
79
90
  }
80
- return toPosix(path.resolve(dir));
91
+ return toPosix(projectRoot);
81
92
 
82
93
  default:
83
94
  throw new Error(`Invalid file structure for ${detectedLanguage}`);
84
95
  }
85
96
  }
86
97
 
87
- /**
98
+ /***
88
99
  * Read directory recursively
89
100
  */
90
- export async function readDirRecursively(
101
+ async function readDirRecursively(
91
102
  dir: string,
92
- result: ParsedDirectory = {},
93
103
  projectRoot: string,
94
- language: Language
104
+ language: Language,
105
+ analysisRoot: string,
106
+ importsByFile?: ReadonlyMap<string, readonly ImportDefinition[]>
95
107
  ): Promise<ParsedDirectory> {
96
- const resolvedRoot = path.resolve(projectRoot);
97
- const resolvedDir = path.resolve(dir);
108
+ const result: ParsedDirectory = Object.create(null);
98
109
 
99
- // Validate dir is inside projectRoot (avoid path traversal / accidental escapes)
100
- const relative = path.relative(resolvedRoot, resolvedDir);
101
- if (relative.startsWith('..') || path.isAbsolute(relative)) {
102
- throw new Error(`Path traversal detected: ${dir} is outside of project root ${projectRoot}`);
103
- }
104
-
105
- // 1. Read current directory
106
- const entries = readdirSync(resolvedDir, { withFileTypes: true });
110
+ // 1. Read the current directory through the shared rooted-filesystem boundary.
111
+ const { entries, path: resolvedDir } = readDirectoryWithinRoot({
112
+ rootPath: projectRoot,
113
+ directoryPath: dir,
114
+ });
107
115
 
108
116
  const ignores = [
109
117
  '@types',
110
118
  '.cache',
111
119
  '.git',
112
120
  '.github',
113
- '.meta',
121
+ '.generated',
114
122
  '.next',
115
- '.vscode',
123
+ 'dist',
116
124
  'coverage',
117
125
  'examples',
118
126
  'node_modules',
119
- 'packages',
120
127
  'test',
121
128
  ];
122
129
 
@@ -127,7 +134,13 @@ export async function readDirRecursively(
127
134
 
128
135
  // Directory: Recursively continue to read
129
136
  if (entry.isDirectory()) {
130
- result[entry.name] = await readDirRecursively(fullPath, {}, projectRoot, language);
137
+ result[entry.name] = await readDirRecursively(
138
+ fullPath,
139
+ projectRoot,
140
+ language,
141
+ analysisRoot,
142
+ importsByFile
143
+ );
131
144
  continue;
132
145
  }
133
146
 
@@ -136,14 +149,24 @@ export async function readDirRecursively(
136
149
  // Java
137
150
  case Language.Java:
138
151
  if (entry.name.endsWith('.java')) {
139
- result[entry.name] = await parseJavaFile(fullPath, projectRoot);
152
+ if (importsByFile === undefined) {
153
+ throw new Error('Missing canonical Java dependency analysis.');
154
+ }
155
+ const relativeFile = toPosix(path.relative(projectRoot, fullPath));
156
+ const imports = importsByFile.get(relativeFile) ?? [];
157
+ result[entry.name] = await parseJavaFile(fullPath, projectRoot, imports);
140
158
  }
141
159
  break;
142
160
 
143
161
  // TypeScript
144
162
  case Language.TypeScript:
145
163
  if (entry.name.endsWith('.ts') || entry.name.endsWith('.tsx')) {
146
- result[entry.name] = await parseTypeScriptFile(fullPath, projectRoot);
164
+ if (importsByFile === undefined) {
165
+ throw new Error('Missing canonical TypeScript dependency analysis.');
166
+ }
167
+ const relativeFile = toPosix(path.relative(projectRoot, fullPath));
168
+ const imports = importsByFile.get(relativeFile) ?? [];
169
+ result[entry.name] = await parseTypeScriptFile(fullPath, projectRoot, imports);
147
170
  }
148
171
  break;
149
172
 
@@ -157,14 +180,24 @@ export async function readDirRecursively(
157
180
  entry.name.endsWith('.hpp') ||
158
181
  entry.name.endsWith('.hxx')
159
182
  ) {
160
- result[entry.name] = await parseCppFile(fullPath, projectRoot);
183
+ if (importsByFile === undefined) {
184
+ throw new Error('Missing canonical C++ dependency analysis.');
185
+ }
186
+ const relativeFile = toPosix(path.relative(projectRoot, fullPath));
187
+ const imports = importsByFile.get(relativeFile) ?? [];
188
+ result[entry.name] = await parseCppFile(fullPath, projectRoot, imports);
161
189
  }
162
190
  break;
163
191
 
164
192
  // Python
165
193
  case Language.Python:
166
194
  if (entry.name.endsWith('.py')) {
167
- result[entry.name] = await parsePythonFile(fullPath, projectRoot);
195
+ if (importsByFile === undefined) {
196
+ throw new Error('Missing canonical Python dependency analysis.');
197
+ }
198
+ const relativeFile = toPosix(path.relative(projectRoot, fullPath));
199
+ const imports = importsByFile.get(relativeFile) ?? [];
200
+ result[entry.name] = await parsePythonFile(fullPath, projectRoot, imports);
168
201
  }
169
202
  break;
170
203
 
@@ -175,14 +208,24 @@ export async function readDirRecursively(
175
208
  entry.name.endsWith('.pp') ||
176
209
  entry.name.endsWith('.dpr')
177
210
  ) {
178
- result[entry.name] = await parseDelphiFile(fullPath, projectRoot);
211
+ if (importsByFile === undefined) {
212
+ throw new Error('Missing canonical Delphi dependency analysis.');
213
+ }
214
+ const relativeFile = toPosix(path.relative(projectRoot, fullPath));
215
+ const imports = importsByFile.get(relativeFile) ?? [];
216
+ result[entry.name] = await parseDelphiFile(fullPath, projectRoot, imports);
179
217
  }
180
218
  break;
181
219
 
182
220
  // Kotlin
183
221
  case Language.Kotlin:
184
222
  if (entry.name.endsWith('.kt') || entry.name.endsWith('.kts')) {
185
- result[entry.name] = await parseKotlinFile(fullPath, projectRoot);
223
+ if (importsByFile === undefined) {
224
+ throw new Error('Missing canonical Kotlin dependency analysis.');
225
+ }
226
+ const relativeFile = toPosix(path.relative(projectRoot, fullPath));
227
+ const imports = importsByFile.get(relativeFile) ?? [];
228
+ result[entry.name] = await parseKotlinFile(fullPath, projectRoot, imports);
186
229
  }
187
230
  break;
188
231
  }
@@ -191,14 +234,15 @@ export async function readDirRecursively(
191
234
  return result;
192
235
  }
193
236
 
194
- /**
237
+ /***
195
238
  * Entrypoint
196
239
  */
197
- export async function getParsedFileStructure() {
198
- const projectPath = parseProjectPath();
199
-
240
+ export async function getParsedFileStructure(
241
+ language?: Language,
242
+ projectPath: string = parseProjectPath()
243
+ ) {
200
244
  // 1. Detect language & filter non-supported
201
- const detectedLanguage = (await detectLanguage(projectPath)).language;
245
+ const detectedLanguage = language ?? (await inspectParserLanguageAsync(projectPath)).language;
202
246
  console.log('1. Detected language:', detectedLanguage);
203
247
 
204
248
  if (
@@ -220,6 +264,36 @@ export async function getParsedFileStructure() {
220
264
  const rootDir = await resolveRoot(projectPath, detectedLanguage);
221
265
  console.log('2. rootDir:', rootDir);
222
266
 
267
+ const importsByFile =
268
+ detectedLanguage === Language.TypeScript
269
+ ? await analyzeDependencyImportsAsync(projectPath)
270
+ : detectedLanguage === Language.Java
271
+ ? await analyzeDependencyImportsAsync(projectPath, rootDir, 'specifier')
272
+ : detectedLanguage === Language.Kotlin
273
+ ? await analyzeDependencyImportsAsync(
274
+ projectPath,
275
+ rootDir,
276
+ 'specifier',
277
+ 'kotlin-standard-library'
278
+ )
279
+ : detectedLanguage === Language.Python
280
+ ? await analyzeDependencyImportsAsync(
281
+ projectPath,
282
+ rootDir,
283
+ 'specifier',
284
+ 'python-legacy'
285
+ )
286
+ : detectedLanguage === Language.Cpp
287
+ ? await analyzeDependencyImportsAsync(projectPath, rootDir, 'specifier')
288
+ : detectedLanguage === Language.Delphi
289
+ ? await analyzeDependencyImportsAsync(
290
+ projectPath,
291
+ rootDir,
292
+ 'specifier',
293
+ 'delphi-standard-library'
294
+ )
295
+ : undefined;
296
+
223
297
  // 3. Read directory recursively (pass resolved root as both dir and projectRoot)
224
- return await readDirRecursively(rootDir, {}, rootDir, detectedLanguage);
298
+ return await readDirRecursively(rootDir, rootDir, detectedLanguage, projectPath, importsByFile);
225
299
  }
@@ -0,0 +1,39 @@
1
+ import { inspectProjectAsync } from '@ankhorage/project-detector/node';
2
+
3
+ import type { ParserSelection } from '../../types/parserSelection';
4
+ import { selectParserLanguage } from './selectParserLanguage';
5
+
6
+ const PROJECT_PATH_ERROR_CODES: ReadonlySet<string> = new Set(['EACCES', 'ENOENT', 'ENOTDIR']);
7
+
8
+ /*** Inspect safely through the canonical detector, then apply pkgviz's parser selection policy. */
9
+ export async function inspectParserLanguageAsync(projectPath: string): Promise<ParserSelection> {
10
+ try {
11
+ const inspection = await inspectProjectAsync(projectPath, {
12
+ excludeDirectories: ['@types', '.github', 'examples', 'test'],
13
+ });
14
+ if (!inspection.complete) {
15
+ throw new Error(
16
+ `Project inspection is incomplete: ${inspection.diagnostics.map(diagnostic => diagnostic.message).join('; ')}`
17
+ );
18
+ }
19
+ return selectParserLanguage(inspection.detection);
20
+ } catch (error) {
21
+ if (!isProjectPathUnavailableError(error)) throw error;
22
+
23
+ const projectPathError = new Error(`Invalid or unavailable project path: ${projectPath}`, {
24
+ cause: error,
25
+ });
26
+ projectPathError.name = 'ProjectPathUnavailableError';
27
+ throw projectPathError;
28
+ }
29
+ }
30
+
31
+ /*** Identify filesystem errors that mean the configured inspection root cannot be used. */
32
+ function isProjectPathUnavailableError(error: unknown): boolean {
33
+ return (
34
+ error instanceof Error &&
35
+ 'code' in error &&
36
+ typeof error.code === 'string' &&
37
+ PROJECT_PATH_ERROR_CODES.has(error.code)
38
+ );
39
+ }
@@ -1,9 +1,13 @@
1
+ import { findCyclePath, findCyclicComponents, type Graph } from '@ankhorage/graph';
1
2
  import type { ElementsDefinition } from 'cytoscape';
3
+
2
4
  import type { ParsedDirectory, ParsedFile } from '@/shared/types';
5
+ import type { CycleEdgeEvidence, ImportEvidence, PackageCycleDetail } from '@/types/audit';
3
6
 
4
- /** Collect all files from your ParsedDirectory tree. */
7
+ /*** Collect all files from your ParsedDirectory tree. */
5
8
  function collectFiles(root: ParsedDirectory): ParsedFile[] {
6
9
  const files: ParsedFile[] = [];
10
+ /*** Collects parsed files by traversing the parsed directory tree. */
7
11
  const walk = (dir: ParsedDirectory) => {
8
12
  for (const key in dir) {
9
13
  const entry = (dir as Record<string, unknown>)[key];
@@ -18,15 +22,15 @@ function collectFiles(root: ParsedDirectory): ParsedFile[] {
18
22
  return files;
19
23
  }
20
24
 
21
- /** Build per-edge evidence: key "from->to" → list of ImportEvidence. */
25
+ /*** Build per-edge evidence: key "from->to" → list of ImportEvidence. */
22
26
  function buildEdgeEvidence(dir: ParsedDirectory): Map<string, ImportEvidence[]> {
23
27
  const files = collectFiles(dir);
24
28
  const map = new Map<string, ImportEvidence[]>();
25
29
 
26
30
  for (const f of files) {
27
- const from = f.package as TUniquePackageName;
31
+ const from = f.package;
28
32
  for (const imp of f.imports ?? []) {
29
- const to = imp.pkg as TUniquePackageName;
33
+ const to = imp.pkg;
30
34
  if (!to || to === from) continue;
31
35
  const key = `${from}->${to}`;
32
36
  if (!map.has(key)) map.set(key, []);
@@ -41,16 +45,16 @@ function buildEdgeEvidence(dir: ParsedDirectory): Map<string, ImportEvidence[]>
41
45
  return map;
42
46
  }
43
47
 
44
- /** Convert ElementsDefinition (from buildGraph) to adjacency map. */
48
+ /*** Convert ElementsDefinition (from buildGraph) to adjacency map. */
45
49
  function elementsToAdj(elements: ElementsDefinition) {
46
50
  const adj = new Map<TUniquePackageName, Set<TUniquePackageName>>();
47
51
  for (const n of elements.nodes) {
48
- const id = String(n.data.id) as TUniquePackageName;
52
+ const id = String(n.data.id);
49
53
  if (!adj.has(id)) adj.set(id, new Set());
50
54
  }
51
55
  for (const e of elements.edges) {
52
- const s = String(e.data.source) as TUniquePackageName;
53
- const t = String(e.data.target) as TUniquePackageName;
56
+ const s = String(e.data.source);
57
+ const t = String(e.data.target);
54
58
  if (!adj.has(s)) adj.set(s, new Set());
55
59
  if (!adj.has(t)) adj.set(t, new Set());
56
60
  adj.get(s)!.add(t);
@@ -58,79 +62,24 @@ function elementsToAdj(elements: ElementsDefinition) {
58
62
  return adj;
59
63
  }
60
64
 
61
- /** Tarjan SCC on adjacency. */
62
- function tarjanSCC(
63
- graph: Map<TUniquePackageName, Set<TUniquePackageName>>
64
- ): TUniquePackageName[][] {
65
- let index = 0;
66
- const idx = new Map<TUniquePackageName, number>();
67
- const low = new Map<TUniquePackageName, number>();
68
- const stack: TUniquePackageName[] = [];
69
- const onStack = new Set<TUniquePackageName>();
70
- const out: TUniquePackageName[][] = [];
71
-
72
- function strong(v: TUniquePackageName) {
73
- idx.set(v, index);
74
- low.set(v, index);
75
- index++;
76
- stack.push(v);
77
- onStack.add(v);
78
-
79
- for (const w of graph.get(v) ?? []) {
80
- if (!idx.has(w)) {
81
- strong(w);
82
- low.set(v, Math.min(low.get(v)!, low.get(w)!));
83
- } else if (onStack.has(w)) {
84
- low.set(v, Math.min(low.get(v)!, idx.get(w)!));
85
- }
86
- }
87
-
88
- if (low.get(v) === idx.get(v)) {
89
- const comp: TUniquePackageName[] = [];
90
- let w: TUniquePackageName;
91
- do {
92
- w = stack.pop()!;
93
- onStack.delete(w);
94
- comp.push(w);
95
- } while (w !== v);
96
- out.push(comp);
97
- }
98
- }
99
-
100
- for (const v of graph.keys()) if (!idx.has(v)) strong(v);
101
- return out;
102
- }
103
-
104
- /** Find one simple cycle ordering inside a given SCC. */
105
- function findOneCycleInScc(
106
- graph: Map<TUniquePackageName, Set<TUniquePackageName>>,
107
- sccSet: Set<TUniquePackageName>
108
- ): TUniquePackageName[] | null {
109
- const nodes = Array.from(sccSet);
110
- for (const start of nodes) {
111
- const path: TUniquePackageName[] = [];
112
- const seen = new Set<TUniquePackageName>();
113
- function dfs(v: TUniquePackageName): TUniquePackageName[] | null {
114
- path.push(v);
115
- seen.add(v);
116
- for (const w of graph.get(v) ?? []) {
117
- if (!sccSet.has(w)) continue;
118
- if (w === start && path.length > 1) return [...path, start];
119
- if (!seen.has(w)) {
120
- const r = dfs(w);
121
- if (r) return r;
122
- }
123
- }
124
- path.pop();
125
- return null;
126
- }
127
- const cyc = dfs(start);
128
- if (cyc) return cyc;
129
- }
130
- return null;
65
+ /*** Convert adjacency to the canonical Ankhorage graph model for shared algorithms. */
66
+ function adjacencyToGraph(
67
+ adjacency: ReadonlyMap<TUniquePackageName, ReadonlySet<TUniquePackageName>>
68
+ ): Graph<undefined, undefined> {
69
+ return {
70
+ nodes: Array.from(adjacency.keys(), id => ({ id, data: undefined })),
71
+ edges: Array.from(adjacency.entries()).flatMap(([source, targets]) =>
72
+ Array.from(targets, target => ({
73
+ id: `${source}->${target}`,
74
+ source,
75
+ target,
76
+ data: undefined,
77
+ }))
78
+ ),
79
+ };
131
80
  }
132
81
 
133
- /**
82
+ /***
134
83
  * High-level API: build graph via `buildGraph`, detect package cycles,
135
84
  * and attach **member evidence** (files/imports) per cycle edge.
136
85
  */
@@ -143,107 +92,36 @@ export function getPackageCyclesWithMembers(
143
92
  graph: ElementsDefinition; // for convenience (already built)
144
93
  } {
145
94
  const adj = elementsToAdj(graph);
146
- const sccs = tarjanSCC(adj);
95
+ const canonicalGraph = adjacencyToGraph(adj);
96
+ const sccs = findCyclicComponents(canonicalGraph);
147
97
  const evidence = buildEdgeEvidence(dir);
148
98
 
149
99
  const cycles: PackageCycleDetail[] = [];
150
100
  const packageSet = new Set<TUniquePackageName>();
151
101
 
152
102
  for (const scc of sccs) {
153
- const selfLoop = scc.length === 1 && (adj.get(scc[0])?.has(scc[0]) ?? false);
154
- if (scc.length > 1 || selfLoop) {
155
- scc.forEach(p => packageSet.add(p));
156
- const sccSet = new Set(scc);
157
- const cycle = findOneCycleInScc(adj, sccSet) ?? [...scc, scc[0]];
103
+ scc.forEach(p => packageSet.add(p));
104
+ const cycle = [...(findCyclePath(canonicalGraph, scc) ?? [...scc, scc[0]])];
158
105
 
159
- const edges: CycleEdgeEvidence[] = [];
160
- for (let i = 0; i < cycle.length - 1; i++) {
161
- const from = cycle[i];
162
- const to = cycle[i + 1];
163
- const key = `${from}->${to}`;
164
- edges.push({
165
- from,
166
- to,
167
- via: evidence.get(key) ?? [],
168
- });
169
- }
170
-
171
- cycles.push({ packages: cycle, edges });
106
+ const edges: CycleEdgeEvidence[] = [];
107
+ for (let i = 0; i < cycle.length - 1; i++) {
108
+ const from = cycle[i];
109
+ const to = cycle[i + 1];
110
+ const key = `${from}->${to}`;
111
+ edges.push({
112
+ from,
113
+ to,
114
+ via: evidence.get(key) ?? [],
115
+ });
172
116
  }
173
- }
174
117
 
175
- return { cycles, packageSet, graph };
176
- }
177
-
178
- /**
179
- * Annotate Cytoscape nodes:
180
- * - adds class "packageCycle" for cyclic packages
181
- * - sets data.packageCycle (boolean)
182
- * - sets data.cycleEvidence (edges where this pkg is the "from" side)
183
- */
184
- export function markCyclicPackagesWithEvidence(
185
- elements: ElementsDefinition,
186
- dir: ParsedDirectory
187
- ): ElementsDefinition {
188
- const { cycles, packageSet } = getPackageCyclesWithMembers(dir, elements);
189
-
190
- // pkg → list of outgoing cycle edges (evidence)
191
- const pkgToEdges = new Map<TUniquePackageName, CycleEdgeEvidence[]>();
192
-
193
- // Track cycle edges by (from → to) for quick lookup when mapping Cytoscape edges
194
- const cycleEdgeKeys = new Set<string>();
195
-
196
- for (const c of cycles) {
197
- for (const e of c.edges) {
198
- if (!pkgToEdges.has(e.from)) pkgToEdges.set(e.from, []);
199
- pkgToEdges.get(e.from)!.push(e);
200
- cycleEdgeKeys.add(`${e.from}→${e.to}`);
201
- }
118
+ cycles.push({ packages: cycle, edges });
202
119
  }
203
120
 
204
- const nodes = (elements.nodes ?? []).map(n => {
205
- const id = String(n.data.id) as TUniquePackageName;
206
- const isCyclic = packageSet.has(id);
207
- const existing = n.classes ? String(n.classes) : '';
208
- const classes = isCyclic ? (existing ? `${existing} packageCycle` : 'packageCycle') : existing;
209
-
210
- return {
211
- ...n,
212
- classes,
213
- data: {
214
- ...n.data,
215
- packageCycle: isCyclic,
216
- cycleEvidence: isCyclic ? (pkgToEdges.get(id) ?? []) : undefined,
217
- },
218
- };
219
- });
220
-
221
- const edges = (elements.edges ?? []).map(e => {
222
- const source = String(e.data.source) as TUniquePackageName;
223
- const target = String(e.data.target) as TUniquePackageName;
224
- const isCycleEdge = cycleEdgeKeys.has(`${source}→${target}`);
225
-
226
- const existing = e.classes ? String(e.classes) : '';
227
- const classes = isCycleEdge
228
- ? existing
229
- ? `${existing} packageCycle`
230
- : 'packageCycle'
231
- : existing;
232
-
233
- return {
234
- ...e,
235
- classes,
236
- data: {
237
- ...e.data,
238
- packageCycle: isCycleEdge,
239
- },
240
- };
241
- });
242
-
243
- return { ...elements, nodes, edges };
121
+ return { cycles, packageSet, graph };
244
122
  }
245
123
 
246
- /**
124
+ /***
247
125
  * Convenience that only returns the set of cyclic packages (no evidence)
248
126
  */
249
127
  export function getCyclicPackageSet(
@@ -255,21 +133,3 @@ export function getCyclicPackageSet(
255
133
  }
256
134
 
257
135
  type TUniquePackageName = string;
258
-
259
- interface ImportEvidence {
260
- readonly filePath: string; // ParsedFile.path (relative to project)
261
- readonly fileClass: string; // ParsedFile.className
262
- readonly importName: string; // IJavaImport.name
263
- readonly isIntrinsic?: boolean; // IJavaImport.isIntrinsic
264
- }
265
-
266
- interface CycleEdgeEvidence {
267
- readonly from: TUniquePackageName;
268
- readonly to: TUniquePackageName;
269
- readonly via: ImportEvidence[]; // files in `from` that import `to`
270
- }
271
-
272
- export interface PackageCycleDetail {
273
- readonly packages: TUniquePackageName[]; // ordered cycle incl. closing node, e.g. ["A","B","A"]
274
- readonly edges: CycleEdgeEvidence[]; // evidence aligned with packages[i] -> packages[i+1]
275
- }