hex-audit 0.7.0 → 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 (584) hide show
  1. package/CLAUDE.md +137 -77
  2. package/DEMO.md +40 -117
  3. package/README.md +200 -402
  4. package/dashboard/.next/BUILD_ID +1 -1
  5. package/dashboard/.next/app-build-manifest.json +56 -101
  6. package/dashboard/.next/app-path-routes-manifest.json +1 -1
  7. package/dashboard/.next/build-manifest.json +9 -9
  8. package/dashboard/.next/next-server.js.nft.json +1 -1
  9. package/dashboard/.next/prerender-manifest.json +1 -1
  10. package/dashboard/.next/react-loadable-manifest.json +436 -15
  11. package/dashboard/.next/routes-manifest.json +1 -1
  12. package/dashboard/.next/server/app/_not-found/page.js +1 -1
  13. package/dashboard/.next/server/app/_not-found/page.js.nft.json +1 -1
  14. package/dashboard/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  15. package/dashboard/.next/server/app/api/code/route.js +1 -1
  16. package/dashboard/.next/server/app/api/code/route.js.nft.json +1 -1
  17. package/dashboard/.next/server/app/api/comments/route.js +1 -0
  18. package/dashboard/.next/server/app/api/comments/route.js.nft.json +1 -0
  19. package/dashboard/.next/server/app/api/diagrams/[name]/export/route.js +1 -0
  20. package/dashboard/.next/server/app/api/diagrams/[name]/export/route.js.nft.json +1 -0
  21. package/dashboard/.next/server/app/api/diagrams/[name]/resolve/route.js +1 -0
  22. package/dashboard/.next/server/app/api/diagrams/[name]/resolve/route.js.nft.json +1 -0
  23. package/dashboard/.next/server/app/api/diagrams/[name]/route.js +1 -0
  24. package/dashboard/.next/server/app/api/diagrams/[name]/route.js.nft.json +1 -0
  25. package/dashboard/.next/server/app/api/figures/[name]/route.js +1 -0
  26. package/dashboard/.next/server/app/api/figures/[name]/route.js.nft.json +1 -0
  27. package/dashboard/.next/server/app/api/focus/route.js +1 -0
  28. package/dashboard/.next/server/app/api/focus/route.js.nft.json +1 -0
  29. package/dashboard/.next/server/app/api/github-status/route.js +1 -1
  30. package/dashboard/.next/server/app/api/invariants/[id]/route.js +1 -0
  31. package/dashboard/.next/server/app/api/invariants/[id]/route.js.nft.json +1 -0
  32. package/dashboard/.next/server/app/api/issues/[id]/revert-auto/route.js +1 -0
  33. package/dashboard/.next/server/app/api/issues/[id]/revert-auto/route.js.nft.json +1 -0
  34. package/dashboard/.next/server/app/api/nav/route.js +1 -0
  35. package/dashboard/.next/server/app/api/nav/route.js.nft.json +1 -0
  36. package/dashboard/.next/server/app/api/notes/done/route.js +1 -1
  37. package/dashboard/.next/server/app/api/notes/done/route.js.nft.json +1 -1
  38. package/dashboard/.next/server/app/api/notes/lead/route.js +1 -1
  39. package/dashboard/.next/server/app/api/notes/lead/route.js.nft.json +1 -1
  40. package/dashboard/.next/server/app/api/notes/question/route.js +1 -1
  41. package/dashboard/.next/server/app/api/notes/question/route.js.nft.json +1 -1
  42. package/dashboard/.next/server/app/api/notes/record/route.js +3 -3
  43. package/dashboard/.next/server/app/api/notes/route.js +2 -2
  44. package/dashboard/.next/server/app/api/palette/route.js +1 -0
  45. package/dashboard/.next/server/app/api/palette/route.js.nft.json +1 -0
  46. package/dashboard/.next/server/app/api/progress/route.js +1 -1
  47. package/dashboard/.next/server/app/api/progress/route.js.nft.json +1 -1
  48. package/dashboard/.next/server/app/api/watch/route.js +2 -2
  49. package/dashboard/.next/server/app/api/watch/route.js.nft.json +1 -1
  50. package/dashboard/.next/server/app/api/xref/route.js +1 -0
  51. package/dashboard/.next/server/app/changes/page.js +1 -20
  52. package/dashboard/.next/server/app/changes/page.js.nft.json +1 -1
  53. package/dashboard/.next/server/app/changes/page_client-reference-manifest.js +1 -1
  54. package/dashboard/.next/server/app/excalidraw-assets/[...path]/route.js +1 -0
  55. package/dashboard/.next/server/app/issues/page.js +5 -28
  56. package/dashboard/.next/server/app/issues/page.js.nft.json +1 -1
  57. package/dashboard/.next/server/app/issues/page_client-reference-manifest.js +1 -1
  58. package/dashboard/.next/server/app/model/page.js +1 -0
  59. package/dashboard/.next/server/app/{calls → model}/page.js.nft.json +1 -1
  60. package/dashboard/.next/server/app/model/page_client-reference-manifest.js +1 -0
  61. package/dashboard/.next/server/app/page.js +1 -20
  62. package/dashboard/.next/server/app/page.js.nft.json +1 -1
  63. package/dashboard/.next/server/app/page_client-reference-manifest.js +1 -1
  64. package/dashboard/.next/server/app/report/page.js +1 -1
  65. package/dashboard/.next/server/app/report/page.js.nft.json +1 -1
  66. package/dashboard/.next/server/app/report/page_client-reference-manifest.js +1 -1
  67. package/dashboard/.next/server/app/review/page.js +1 -0
  68. package/dashboard/.next/server/app/{access → review}/page.js.nft.json +1 -1
  69. package/dashboard/.next/server/app/review/page_client-reference-manifest.js +1 -0
  70. package/dashboard/.next/server/app-paths-manifest.json +19 -16
  71. package/dashboard/.next/server/chunks/310.js +4 -0
  72. package/dashboard/.next/server/chunks/370.js +1 -0
  73. package/dashboard/.next/server/chunks/42.js +1 -1
  74. package/dashboard/.next/server/chunks/624.js +14 -0
  75. package/dashboard/.next/server/chunks/645.js +1 -1
  76. package/dashboard/.next/server/chunks/649.js +1 -1
  77. package/dashboard/.next/server/chunks/653.js +1 -0
  78. package/dashboard/.next/server/functions-config-manifest.json +1 -1
  79. package/dashboard/.next/server/middleware-build-manifest.js +1 -1
  80. package/dashboard/.next/server/middleware-react-loadable-manifest.js +1 -1
  81. package/dashboard/.next/server/pages/500.html +1 -1
  82. package/dashboard/.next/server/pages/_error.js +1 -1
  83. package/dashboard/.next/server/server-reference-manifest.js +1 -1
  84. package/dashboard/.next/server/server-reference-manifest.json +1 -1
  85. package/dashboard/.next/static/chunks/0bbe10ee.8e7ffa06d2170dfe.js +84 -0
  86. package/dashboard/.next/static/chunks/1026.0ef689a305302427.js +1 -0
  87. package/dashboard/.next/static/chunks/1164.34eb76767ebea46f.js +8 -0
  88. package/dashboard/.next/static/chunks/119.a9a79633900a235f.js +5 -0
  89. package/dashboard/.next/static/chunks/1291.6c486c9b1d4b1b3a.js +1 -0
  90. package/dashboard/.next/static/chunks/139.4691848fc5a46253.js +10 -0
  91. package/dashboard/.next/static/chunks/145.1bf74fecdb4022e3.js +11 -0
  92. package/dashboard/.next/static/chunks/1517.8e7075f78e22b3a0.js +8 -0
  93. package/dashboard/.next/static/chunks/165.6f174060a0ce2db4.js +6 -0
  94. package/dashboard/.next/static/chunks/1741.5c370b7702d2eb40.js +8 -0
  95. package/dashboard/.next/static/chunks/1e36816c.5748c83adc8a6bd6.js +108 -0
  96. package/dashboard/.next/static/chunks/2061.eab75b3c1dfa068a.js +1 -0
  97. package/dashboard/.next/static/chunks/2148.3ce718f62d1b2f7c.js +10 -0
  98. package/dashboard/.next/static/chunks/217-1d5b680ffd10e3f9.js +1 -0
  99. package/dashboard/.next/static/chunks/2208.fb884379aa3ae968.js +1 -0
  100. package/dashboard/.next/static/chunks/2276.112c7855333fca5f.js +6 -0
  101. package/dashboard/.next/static/chunks/2374-8c6f137938f8518a.js +1 -0
  102. package/dashboard/.next/static/chunks/2445.18a86507d2996b95.js +1 -0
  103. package/dashboard/.next/static/chunks/2712.15dd8577973baf22.js +10 -0
  104. package/dashboard/.next/static/chunks/2759.533dec5317e0a1d1.js +1 -0
  105. package/dashboard/.next/static/chunks/2812.62b213bef580b69a.js +1 -0
  106. package/dashboard/.next/static/chunks/2895.bb57cacb7aaf42a1.js +9 -0
  107. package/dashboard/.next/static/chunks/2972.e5a9251b538dcf19.js +9 -0
  108. package/dashboard/.next/static/chunks/3206.8d9a10db2b04b93f.js +1 -0
  109. package/dashboard/.next/static/chunks/3485.36766504793b7237.js +9 -0
  110. package/dashboard/.next/static/chunks/3725.de9376dc994735d4.js +9 -0
  111. package/dashboard/.next/static/chunks/3774.1df377928e8387f7.js +9 -0
  112. package/dashboard/.next/static/chunks/3963.39d72b3875dfe2e9.js +13 -0
  113. package/dashboard/.next/static/chunks/4013.73884b23bfaebdde.js +11 -0
  114. package/dashboard/.next/static/chunks/4117.278aff1fa23fd36e.js +6 -0
  115. package/dashboard/.next/static/chunks/4210.cc8594110017c579.js +11 -0
  116. package/dashboard/.next/static/chunks/4282.28485875540886cc.js +7 -0
  117. package/dashboard/.next/static/chunks/4318.a2f469a88d88f6d0.js +8 -0
  118. package/dashboard/.next/static/chunks/4363.8999a6e727c48003.js +1 -0
  119. package/dashboard/.next/static/chunks/4641.e03d118fffebd580.js +5 -0
  120. package/dashboard/.next/static/chunks/483.a910dab7734a1074.js +8 -0
  121. package/dashboard/.next/static/chunks/4926.76693f542e9cc062.js +6 -0
  122. package/dashboard/.next/static/chunks/4938.03f6d04dabf38745.js +10 -0
  123. package/dashboard/.next/static/chunks/5112.0757c2b0c285e62e.js +1 -0
  124. package/dashboard/.next/static/chunks/5199.c83ea7efb0eec705.js +11 -0
  125. package/dashboard/.next/static/chunks/530.849957a8907eb288.js +1 -0
  126. package/dashboard/.next/static/chunks/5424.4e8ca0711a32bccf.js +7 -0
  127. package/dashboard/.next/static/chunks/55.a593a78be9714679.js +1 -0
  128. package/dashboard/.next/static/chunks/5535.6cd0bd8d368d8902.js +4 -0
  129. package/dashboard/.next/static/chunks/5626.7d4e93d7acbeffc9.js +10 -0
  130. package/dashboard/.next/static/chunks/5775.a0d7c1b1e89b9a93.js +9 -0
  131. package/dashboard/.next/static/chunks/6118.266956b3333b1d7a.js +9 -0
  132. package/dashboard/.next/static/chunks/6121.a2e6d93720b1205e.js +77 -0
  133. package/dashboard/.next/static/chunks/6853.d3ad43614e8ca0ab.js +1 -0
  134. package/dashboard/.next/static/chunks/6951.02a3a158d02158c9.js +5 -0
  135. package/dashboard/.next/static/chunks/6970.013f24568c87d918.js +11 -0
  136. package/dashboard/.next/static/chunks/7013.39d41e82b37be63b.js +9 -0
  137. package/dashboard/.next/static/chunks/7107.1c5412197912b8ed.js +8 -0
  138. package/dashboard/.next/static/chunks/7508.dd60e2d06d149f5f.js +9 -0
  139. package/dashboard/.next/static/chunks/7524.0fde4c61553d1491.js +3 -0
  140. package/dashboard/.next/static/chunks/7834-47f318a04a05688f.js +1 -0
  141. package/dashboard/.next/static/chunks/8317.d0b3eddef4cae0d5.js +1 -0
  142. package/dashboard/.next/static/chunks/838.2b2aa006519ba44c.js +10 -0
  143. package/dashboard/.next/static/chunks/8398.924fd8f4c83417c0.js +7 -0
  144. package/dashboard/.next/static/chunks/8428b36f.8ac4e67061c01307.js +34 -0
  145. package/dashboard/.next/static/chunks/8514.85ccbbea8ab21d04.js +5 -0
  146. package/dashboard/.next/static/chunks/8538.5f7ebe76c4b63162.js +1 -0
  147. package/dashboard/.next/static/chunks/8687.9406baa8b5601fd0.js +1 -0
  148. package/dashboard/.next/static/chunks/8721.47346e2cb6412376.js +10 -0
  149. package/dashboard/.next/static/chunks/8770.4f544bd1a6f03a62.js +8 -0
  150. package/dashboard/.next/static/chunks/8812.3d96fd54dba566f8.js +9 -0
  151. package/dashboard/.next/static/chunks/8962.d22aa67a97970dd8.js +1 -0
  152. package/dashboard/.next/static/chunks/8986.b327224b83f4399a.js +5 -0
  153. package/dashboard/.next/static/chunks/9271.5f66c19a8ee1ff36.js +10 -0
  154. package/dashboard/.next/static/chunks/9282.f8d01442bab4c0da.js +2 -0
  155. package/dashboard/.next/static/chunks/9366.0ec24a0791b9b00f.js +1 -0
  156. package/dashboard/.next/static/chunks/9707.5cd7d2ee933a4af5.js +6 -0
  157. package/dashboard/.next/static/chunks/9716.15fffc0b0038601d.js +4 -0
  158. package/dashboard/.next/static/chunks/9791.e3b3f5a367f63c8a.js +1 -0
  159. package/dashboard/.next/static/chunks/9820.8c1efad5260e8137.js +1 -0
  160. package/dashboard/.next/static/chunks/985.50150e9eaa4bde33.js +39 -0
  161. package/dashboard/.next/static/chunks/997.56901b86e1b8a87b.js +8 -0
  162. package/dashboard/.next/static/chunks/app/changes/page-f8a860034221cad4.js +1 -0
  163. package/dashboard/.next/static/chunks/app/issues/page-da4da93188c414a6.js +1 -0
  164. package/dashboard/.next/static/chunks/app/layout-27a9da4610423c1e.js +1 -0
  165. package/dashboard/.next/static/chunks/app/model/page-9680e22fdd6ddb81.js +1 -0
  166. package/dashboard/.next/static/chunks/app/page-0e67750d99fb2836.js +1 -0
  167. package/dashboard/.next/static/chunks/app/report/page-b56b1db9b9dcf2b6.js +1 -0
  168. package/dashboard/.next/static/chunks/app/review/page-3ef69f0e0badd333.js +1 -0
  169. package/dashboard/.next/static/chunks/c75e29e3.d91220f782934d80.js +1 -0
  170. package/dashboard/.next/static/chunks/{main-cc3a889b25e9aa68.js → main-9c95a7ffedc7afb4.js} +1 -1
  171. package/dashboard/.next/static/chunks/webpack-6da70e4f9fcf1799.js +1 -0
  172. package/dashboard/.next/static/css/ceafd91ca8dd753d.css +3 -0
  173. package/dashboard/.next/static/css/e4aad1f9c715b79a.css +1 -0
  174. package/dashboard/.next/static/media/Assistant-Bold.c8f39f24.woff2 +0 -0
  175. package/dashboard/.next/static/media/Assistant-Medium.1e75d8e6.woff2 +0 -0
  176. package/dashboard/.next/static/media/Assistant-Regular.7acbabb8.woff2 +0 -0
  177. package/dashboard/.next/static/media/Assistant-SemiBold.167acbf8.woff2 +0 -0
  178. package/dashboard/app/api/code/route.ts +26 -20
  179. package/dashboard/app/api/comments/route.ts +158 -0
  180. package/dashboard/app/api/diagrams/[name]/export/route.ts +26 -0
  181. package/dashboard/app/api/diagrams/[name]/resolve/route.ts +25 -0
  182. package/dashboard/app/api/diagrams/[name]/route.ts +37 -0
  183. package/dashboard/app/api/figures/[name]/route.ts +15 -0
  184. package/dashboard/app/api/focus/route.ts +35 -0
  185. package/dashboard/app/api/invariants/[id]/route.ts +35 -0
  186. package/dashboard/app/api/issues/[id]/revert-auto/route.ts +18 -0
  187. package/dashboard/app/api/nav/route.ts +120 -0
  188. package/dashboard/app/api/palette/route.ts +68 -0
  189. package/dashboard/app/api/progress/route.ts +54 -23
  190. package/dashboard/app/api/xref/route.ts +30 -0
  191. package/dashboard/app/changes/ChangesClient.tsx +730 -356
  192. package/dashboard/app/changes/page.tsx +27 -19
  193. package/dashboard/app/excalidraw-assets/[...path]/route.ts +56 -0
  194. package/dashboard/app/globals.css +231 -77
  195. package/dashboard/app/issues/BoardClient.tsx +817 -0
  196. package/dashboard/app/issues/page.tsx +27 -272
  197. package/dashboard/app/layout.tsx +32 -17
  198. package/dashboard/app/model/ModelClient.tsx +1303 -0
  199. package/dashboard/app/model/page.tsx +98 -0
  200. package/dashboard/app/page.tsx +191 -277
  201. package/dashboard/app/report/ReportClient.tsx +147 -74
  202. package/dashboard/app/report/page.tsx +25 -14
  203. package/dashboard/app/review/ReviewClient.tsx +1180 -0
  204. package/dashboard/app/review/page.tsx +93 -0
  205. package/dashboard/components/AutoRefresh.tsx +22 -19
  206. package/dashboard/components/CodeComments.tsx +303 -0
  207. package/dashboard/components/CodeNav.tsx +523 -0
  208. package/dashboard/components/CodeReference.tsx +92 -99
  209. package/dashboard/components/CodeViewer.tsx +526 -0
  210. package/dashboard/components/CommandPalette.tsx +141 -66
  211. package/dashboard/components/DiagramCanvas.tsx +275 -0
  212. package/dashboard/components/DianeNote.tsx +244 -0
  213. package/dashboard/components/LiveStatus.tsx +30 -68
  214. package/dashboard/components/MarkdownRenderer.tsx +34 -32
  215. package/dashboard/components/RecordButton.tsx +132 -0
  216. package/dashboard/components/ui/Button.tsx +39 -0
  217. package/dashboard/components/ui/Chip.tsx +37 -0
  218. package/dashboard/components/ui/EmptyState.tsx +31 -0
  219. package/dashboard/components/ui/Icons.tsx +126 -0
  220. package/dashboard/components/ui/Inspector.tsx +122 -0
  221. package/dashboard/components/ui/Kbd.tsx +15 -0
  222. package/dashboard/components/ui/ListRow.tsx +92 -0
  223. package/dashboard/components/ui/LocalTime.tsx +14 -0
  224. package/dashboard/components/ui/ProgressRing.tsx +54 -0
  225. package/dashboard/components/ui/ResizeHandle.tsx +93 -0
  226. package/dashboard/components/ui/RiskMeter.tsx +27 -0
  227. package/dashboard/components/ui/SearchField.tsx +55 -0
  228. package/dashboard/components/ui/SegmentedControl.tsx +71 -0
  229. package/dashboard/components/ui/SeverityDot.tsx +57 -0
  230. package/dashboard/components/ui/Sidebar.tsx +236 -0
  231. package/dashboard/components/ui/SplitView.tsx +122 -0
  232. package/dashboard/components/ui/StateGlyph.tsx +66 -0
  233. package/dashboard/components/ui/Toast.tsx +91 -0
  234. package/dashboard/lib/api.ts +17 -0
  235. package/dashboard/lib/cn.ts +4 -0
  236. package/dashboard/lib/data.ts +2 -21
  237. package/dashboard/lib/finding-markdown.ts +1 -1
  238. package/dashboard/lib/focus.ts +42 -0
  239. package/dashboard/lib/hotkeys.ts +41 -0
  240. package/dashboard/lib/inline-markdown.test.ts +11 -0
  241. package/dashboard/lib/live.ts +32 -0
  242. package/dashboard/lib/pane-width.ts +36 -0
  243. package/dashboard/lib/server/board.ts +229 -0
  244. package/dashboard/lib/server/diagrams.ts +191 -0
  245. package/dashboard/lib/server/safe-path.ts +28 -0
  246. package/dashboard/lib/server/workspace.ts +113 -0
  247. package/dashboard/lib/server/xref.ts +42 -0
  248. package/dashboard/lib/solidity.test.ts +37 -0
  249. package/dashboard/lib/solidity.ts +191 -0
  250. package/dashboard/lib/source.ts +28 -0
  251. package/dashboard/lib/types.ts +164 -0
  252. package/dashboard/lib/url.ts +18 -0
  253. package/dashboard/lib/watcher.ts +31 -22
  254. package/dashboard/tailwind.config.ts +6 -3
  255. package/dist/analysis/code-model.d.ts.map +1 -1
  256. package/dist/analysis/code-model.js +4 -1
  257. package/dist/analysis/code-model.js.map +1 -1
  258. package/dist/analysis/diff.d.ts +12 -1
  259. package/dist/analysis/diff.d.ts.map +1 -1
  260. package/dist/analysis/diff.js +143 -0
  261. package/dist/analysis/diff.js.map +1 -1
  262. package/dist/commands/analyze.d.ts.map +1 -1
  263. package/dist/commands/analyze.js +17 -48
  264. package/dist/commands/analyze.js.map +1 -1
  265. package/dist/commands/claude.d.ts.map +1 -1
  266. package/dist/commands/claude.js +5 -0
  267. package/dist/commands/claude.js.map +1 -1
  268. package/dist/commands/dashboard.d.ts.map +1 -1
  269. package/dist/commands/dashboard.js +10 -2
  270. package/dist/commands/dashboard.js.map +1 -1
  271. package/dist/commands/diagram.d.ts +3 -0
  272. package/dist/commands/diagram.d.ts.map +1 -0
  273. package/dist/commands/diagram.js +71 -0
  274. package/dist/commands/diagram.js.map +1 -0
  275. package/dist/commands/diff.d.ts +4 -5
  276. package/dist/commands/diff.d.ts.map +1 -1
  277. package/dist/commands/diff.js +56 -17
  278. package/dist/commands/diff.js.map +1 -1
  279. package/dist/commands/doctor.d.ts.map +1 -1
  280. package/dist/commands/doctor.js +5 -18
  281. package/dist/commands/doctor.js.map +1 -1
  282. package/dist/commands/facts.d.ts +23 -0
  283. package/dist/commands/facts.d.ts.map +1 -0
  284. package/dist/commands/facts.js +121 -0
  285. package/dist/commands/facts.js.map +1 -0
  286. package/dist/commands/fix-review.d.ts +9 -0
  287. package/dist/commands/fix-review.d.ts.map +1 -0
  288. package/dist/commands/fix-review.js +367 -0
  289. package/dist/commands/fix-review.js.map +1 -0
  290. package/dist/commands/init.d.ts.map +1 -1
  291. package/dist/commands/init.js +51 -47
  292. package/dist/commands/init.js.map +1 -1
  293. package/dist/commands/issue.d.ts.map +1 -1
  294. package/dist/commands/issue.js +151 -2
  295. package/dist/commands/issue.js.map +1 -1
  296. package/dist/commands/mcp.d.ts +3 -0
  297. package/dist/commands/mcp.d.ts.map +1 -0
  298. package/dist/commands/mcp.js +11 -0
  299. package/dist/commands/mcp.js.map +1 -0
  300. package/dist/commands/model.d.ts +4 -0
  301. package/dist/commands/model.d.ts.map +1 -0
  302. package/dist/commands/model.js +275 -0
  303. package/dist/commands/model.js.map +1 -0
  304. package/dist/commands/note.d.ts.map +1 -1
  305. package/dist/commands/note.js +31 -1
  306. package/dist/commands/note.js.map +1 -1
  307. package/dist/commands/review.d.ts +4 -0
  308. package/dist/commands/review.d.ts.map +1 -0
  309. package/dist/commands/review.js +164 -0
  310. package/dist/commands/review.js.map +1 -0
  311. package/dist/core/comments.d.ts +59 -0
  312. package/dist/core/comments.d.ts.map +1 -0
  313. package/dist/core/comments.js +263 -0
  314. package/dist/core/comments.js.map +1 -0
  315. package/dist/core/config.d.ts.map +1 -1
  316. package/dist/core/config.js +0 -1
  317. package/dist/core/config.js.map +1 -1
  318. package/dist/core/dedup.d.ts +56 -0
  319. package/dist/core/dedup.d.ts.map +1 -0
  320. package/dist/core/dedup.js +208 -0
  321. package/dist/core/dedup.js.map +1 -0
  322. package/dist/core/errors.d.ts +1 -1
  323. package/dist/core/errors.d.ts.map +1 -1
  324. package/dist/core/errors.js +0 -15
  325. package/dist/core/errors.js.map +1 -1
  326. package/dist/core/external-tools.d.ts +0 -14
  327. package/dist/core/external-tools.d.ts.map +1 -1
  328. package/dist/core/external-tools.js +2 -52
  329. package/dist/core/external-tools.js.map +1 -1
  330. package/dist/core/fix-review.d.ts +54 -0
  331. package/dist/core/fix-review.d.ts.map +1 -0
  332. package/dist/core/fix-review.js +98 -0
  333. package/dist/core/fix-review.js.map +1 -0
  334. package/dist/core/fuzz-sync.d.ts +93 -0
  335. package/dist/core/fuzz-sync.d.ts.map +1 -0
  336. package/dist/core/fuzz-sync.js +516 -0
  337. package/dist/core/fuzz-sync.js.map +1 -0
  338. package/dist/core/git.d.ts +7 -0
  339. package/dist/core/git.d.ts.map +1 -1
  340. package/dist/core/git.js +42 -0
  341. package/dist/core/git.js.map +1 -1
  342. package/dist/core/issues.d.ts +32 -1
  343. package/dist/core/issues.d.ts.map +1 -1
  344. package/dist/core/issues.js +135 -3
  345. package/dist/core/issues.js.map +1 -1
  346. package/dist/core/mcp-config.d.ts +12 -0
  347. package/dist/core/mcp-config.d.ts.map +1 -0
  348. package/dist/core/mcp-config.js +29 -0
  349. package/dist/core/mcp-config.js.map +1 -0
  350. package/dist/core/model.d.ts +38 -0
  351. package/dist/core/model.d.ts.map +1 -0
  352. package/dist/core/model.js +160 -0
  353. package/dist/core/model.js.map +1 -0
  354. package/dist/core/notes.d.ts +2 -0
  355. package/dist/core/notes.d.ts.map +1 -1
  356. package/dist/core/notes.js +13 -3
  357. package/dist/core/notes.js.map +1 -1
  358. package/dist/core/review.d.ts +47 -0
  359. package/dist/core/review.d.ts.map +1 -1
  360. package/dist/core/review.js +177 -1
  361. package/dist/core/review.js.map +1 -1
  362. package/dist/core/rules.d.ts +12 -0
  363. package/dist/core/rules.d.ts.map +1 -0
  364. package/dist/core/rules.js +66 -0
  365. package/dist/core/rules.js.map +1 -0
  366. package/dist/core/schema.d.ts +4817 -2245
  367. package/dist/core/schema.d.ts.map +1 -1
  368. package/dist/core/schema.js +452 -113
  369. package/dist/core/schema.js.map +1 -1
  370. package/dist/core/scope.d.ts +8 -0
  371. package/dist/core/scope.d.ts.map +1 -0
  372. package/dist/core/scope.js +18 -0
  373. package/dist/core/scope.js.map +1 -0
  374. package/dist/diagrams/build.d.ts +18 -0
  375. package/dist/diagrams/build.d.ts.map +1 -0
  376. package/dist/diagrams/build.js +135 -0
  377. package/dist/diagrams/build.js.map +1 -0
  378. package/dist/diagrams/excalidraw.d.ts +27 -0
  379. package/dist/diagrams/excalidraw.d.ts.map +1 -0
  380. package/dist/diagrams/excalidraw.js +219 -0
  381. package/dist/diagrams/excalidraw.js.map +1 -0
  382. package/dist/diagrams/layout.d.ts +48 -0
  383. package/dist/diagrams/layout.d.ts.map +1 -0
  384. package/dist/diagrams/layout.js +686 -0
  385. package/dist/diagrams/layout.js.map +1 -0
  386. package/dist/facts/build.d.ts +23 -0
  387. package/dist/facts/build.d.ts.map +1 -0
  388. package/dist/facts/build.js +112 -0
  389. package/dist/facts/build.js.map +1 -0
  390. package/dist/facts/extract.d.ts +53 -0
  391. package/dist/facts/extract.d.ts.map +1 -0
  392. package/dist/facts/extract.js +635 -0
  393. package/dist/facts/extract.js.map +1 -0
  394. package/dist/facts/fallback.d.ts +7 -0
  395. package/dist/facts/fallback.d.ts.map +1 -0
  396. package/dist/facts/fallback.js +156 -0
  397. package/dist/facts/fallback.js.map +1 -0
  398. package/dist/facts/implementations.d.ts +29 -0
  399. package/dist/facts/implementations.d.ts.map +1 -0
  400. package/dist/facts/implementations.js +43 -0
  401. package/dist/facts/implementations.js.map +1 -0
  402. package/dist/facts/query.d.ts +35 -0
  403. package/dist/facts/query.d.ts.map +1 -0
  404. package/dist/facts/query.js +157 -0
  405. package/dist/facts/query.js.map +1 -0
  406. package/dist/facts/xref.d.ts +53 -0
  407. package/dist/facts/xref.d.ts.map +1 -0
  408. package/dist/facts/xref.js +282 -0
  409. package/dist/facts/xref.js.map +1 -0
  410. package/dist/index.js +14 -16
  411. package/dist/index.js.map +1 -1
  412. package/dist/mcp/server.d.ts +3 -0
  413. package/dist/mcp/server.d.ts.map +1 -0
  414. package/dist/mcp/server.js +400 -0
  415. package/dist/mcp/server.js.map +1 -0
  416. package/dist/skills/ai-audit/SKILL.md +16 -6
  417. package/dist/skills/ai-audit/UPSTREAM.md +9 -4
  418. package/dist/skills/ai-audit/references/hex-board.md +34 -46
  419. package/dist/skills/diane.md +26 -12
  420. package/dist/skills/fix-review.md +58 -0
  421. package/dist/skills/fuzz/LICENSE +21 -0
  422. package/dist/skills/fuzz/SKILL.md +655 -0
  423. package/dist/skills/fuzz/UPSTREAM.md +119 -0
  424. package/dist/skills/fuzz/agents/implementers/global-property-implementer.md +81 -0
  425. package/dist/skills/fuzz/agents/implementers/specific-property-implementer.md +121 -0
  426. package/dist/skills/fuzz/agents/invariant-discovery/adversarial-profit-maximizer.md +123 -0
  427. package/dist/skills/fuzz/agents/invariant-discovery/conservation-auditor.md +92 -0
  428. package/dist/skills/fuzz/agents/invariant-discovery/protocol-type-specialist.md +173 -0
  429. package/dist/skills/fuzz/agents/invariant-discovery/roundtrip-rounding-analyst.md +164 -0
  430. package/dist/skills/fuzz/agents/invariant-discovery/state-transition-mapper.md +92 -0
  431. package/dist/skills/fuzz/agents/invariant-discovery/synthesizer.md +261 -0
  432. package/dist/skills/fuzz/agents/protocol-analyzer.md +80 -0
  433. package/dist/skills/fuzz/agents/report-writer.md +135 -0
  434. package/dist/skills/fuzz/references/handler-patterns.md +222 -0
  435. package/dist/skills/fuzz/references/hex-invariants.md +102 -0
  436. package/dist/skills/fuzz/references/property-generation.md +149 -0
  437. package/dist/skills/fuzz/references/selection-policy.md +49 -0
  438. package/dist/skills/fuzz/references/setup-playbook.md +92 -0
  439. package/dist/skills/fuzz/references/template-map.md +121 -0
  440. package/dist/skills/fuzz/scripts/ensure_foundry.sh +125 -0
  441. package/dist/skills/fuzz/scripts/estimate_cost.js +157 -0
  442. package/dist/skills/fuzz/scripts/extract_abis.js +303 -0
  443. package/dist/skills/fuzz/scripts/fizz_sync.js +692 -0
  444. package/dist/skills/fuzz/scripts/generate_handlers.js +370 -0
  445. package/dist/skills/fuzz/scripts/generate_suite.js +254 -0
  446. package/dist/skills/fuzz/scripts/lib/log-viewer.js +332 -0
  447. package/dist/skills/fuzz/scripts/run_echidna.js +192 -0
  448. package/dist/skills/fuzz/scripts/run_medusa.js +329 -0
  449. package/dist/skills/fuzz/scripts/select_functions.js +637 -0
  450. package/dist/skills/fuzz/scripts/setup_fuzz_profile.sh +142 -0
  451. package/dist/skills/fuzz/templates/Actor.sol +63 -0
  452. package/dist/skills/fuzz/templates/Base.sol +121 -0
  453. package/dist/skills/fuzz/templates/FoundryTester.sol +29 -0
  454. package/dist/skills/fuzz/templates/FuzzTester.sol +11 -0
  455. package/dist/skills/fuzz/templates/Properties.sol +17 -0
  456. package/dist/skills/fuzz/templates/README.md +56 -0
  457. package/dist/skills/fuzz/templates/Snapshots.sol +26 -0
  458. package/dist/skills/fuzz/templates/echidna.yaml +18 -0
  459. package/dist/skills/fuzz/templates/handlers/Handlers.sol +13 -0
  460. package/dist/skills/fuzz/templates/medusa.json +93 -0
  461. package/dist/skills/fuzz/templates/utils/Clamp.sol +223 -0
  462. package/dist/skills/fuzz/templates/utils/DecimalPrinter.sol +63 -0
  463. package/dist/skills/fuzz/templates/utils/Deployer.sol +17 -0
  464. package/dist/skills/fuzz/templates/utils/EnumerableSet.sol +206 -0
  465. package/dist/skills/fuzz/templates/utils/Hevm.sol +62 -0
  466. package/dist/skills/fuzz/templates/utils/Logger.sol +619 -0
  467. package/dist/skills/fuzz/templates/utils/Math.sol +52 -0
  468. package/dist/skills/fuzz/templates/utils/MockERC20.sol +279 -0
  469. package/dist/skills/fuzz/templates/utils/PropertiesAsserts.sol +238 -0
  470. package/dist/skills/fuzz/templates/utils/StringUtils.sol +85 -0
  471. package/dist/skills/fuzz-convert/SKILL.md +249 -0
  472. package/dist/skills/fuzz-sync/SKILL.md +342 -0
  473. package/dist/skills/generate-poc.md +8 -26
  474. package/dist/skills/generate-report.md +17 -5
  475. package/dist/skills/ingest-aa-report.md +11 -30
  476. package/dist/skills/init-audit/SKILL.md +155 -0
  477. package/dist/skills/init-audit/references/agents/conformance-checker.md +36 -0
  478. package/dist/skills/init-audit/references/agents/contract-reader.md +59 -0
  479. package/dist/skills/init-audit/references/agents/diagram-composer.md +48 -0
  480. package/dist/skills/init-audit/references/agents/docs-extractor.md +26 -0
  481. package/dist/skills/init-audit/references/agents/prior-audit.md +32 -0
  482. package/dist/skills/init-audit/references/agents/regression-checker.md +28 -0
  483. package/dist/skills/init-audit/references/agents/skeptic.md +31 -0
  484. package/dist/skills/init-audit/references/agents/standards-checker.md +39 -0
  485. package/dist/skills/init-audit/references/agents/synthesizer.md +50 -0
  486. package/dist/skills/prevalidate.md +62 -0
  487. package/dist/skills/sync-issues.md +11 -25
  488. package/dist/skills/validate-all-findings.md +2 -2
  489. package/dist/skills/validate-finding.md +25 -24
  490. package/dist/skills/write-finding.md +6 -5
  491. package/dist/types/index.d.ts +16 -12
  492. package/dist/types/index.d.ts.map +1 -1
  493. package/package.json +5 -4
  494. package/dashboard/.next/server/app/access/page.js +0 -1
  495. package/dashboard/.next/server/app/access/page_client-reference-manifest.js +0 -1
  496. package/dashboard/.next/server/app/api/flows/route.js +0 -1
  497. package/dashboard/.next/server/app/api/mermaid/route.js +0 -1
  498. package/dashboard/.next/server/app/calls/page.js +0 -1
  499. package/dashboard/.next/server/app/calls/page_client-reference-manifest.js +0 -1
  500. package/dashboard/.next/server/app/conformance/page.js +0 -1
  501. package/dashboard/.next/server/app/conformance/page.js.nft.json +0 -1
  502. package/dashboard/.next/server/app/conformance/page_client-reference-manifest.js +0 -1
  503. package/dashboard/.next/server/app/diagram/page.js +0 -1
  504. package/dashboard/.next/server/app/diagram/page.js.nft.json +0 -1
  505. package/dashboard/.next/server/app/diagram/page_client-reference-manifest.js +0 -1
  506. package/dashboard/.next/server/app/flows/page.js +0 -1
  507. package/dashboard/.next/server/app/flows/page.js.nft.json +0 -1
  508. package/dashboard/.next/server/app/flows/page_client-reference-manifest.js +0 -1
  509. package/dashboard/.next/server/app/functions/page.js +0 -1
  510. package/dashboard/.next/server/app/functions/page.js.nft.json +0 -1
  511. package/dashboard/.next/server/app/functions/page_client-reference-manifest.js +0 -1
  512. package/dashboard/.next/server/app/notes/page.js +0 -22
  513. package/dashboard/.next/server/app/notes/page.js.nft.json +0 -1
  514. package/dashboard/.next/server/app/notes/page_client-reference-manifest.js +0 -1
  515. package/dashboard/.next/server/app/progress/page.js +0 -1
  516. package/dashboard/.next/server/app/progress/page.js.nft.json +0 -1
  517. package/dashboard/.next/server/app/progress/page_client-reference-manifest.js +0 -1
  518. package/dashboard/.next/server/app/stats/page.js +0 -1
  519. package/dashboard/.next/server/app/stats/page.js.nft.json +0 -1
  520. package/dashboard/.next/server/app/stats/page_client-reference-manifest.js +0 -1
  521. package/dashboard/.next/server/chunks/530.js +0 -1
  522. package/dashboard/.next/server/chunks/622.js +0 -3
  523. package/dashboard/.next/server/chunks/665.js +0 -13
  524. package/dashboard/.next/server/chunks/939.js +0 -1
  525. package/dashboard/.next/static/chunks/3017.4a5312e3c20a173f.js +0 -1
  526. package/dashboard/.next/static/chunks/7397-37adf76cad543297.js +0 -1
  527. package/dashboard/.next/static/chunks/7973.e34032723285a616.js +0 -79
  528. package/dashboard/.next/static/chunks/8452-6d77fa353402422c.js +0 -13
  529. package/dashboard/.next/static/chunks/8928-fc049decdfbdaa6d.js +0 -1
  530. package/dashboard/.next/static/chunks/app/access/page-f8403cbdfc1c176b.js +0 -1
  531. package/dashboard/.next/static/chunks/app/calls/page-41f20d01e12382ef.js +0 -1
  532. package/dashboard/.next/static/chunks/app/changes/page-82bf8b6f48cdc506.js +0 -1
  533. package/dashboard/.next/static/chunks/app/conformance/page-6bb8e3b372e38098.js +0 -1
  534. package/dashboard/.next/static/chunks/app/diagram/page-469cd7a6f183efba.js +0 -1
  535. package/dashboard/.next/static/chunks/app/flows/page-7872dd9141ba0b07.js +0 -1
  536. package/dashboard/.next/static/chunks/app/functions/page-5847bcb77a8e7edf.js +0 -1
  537. package/dashboard/.next/static/chunks/app/issues/page-1ffc64fe40439002.js +0 -1
  538. package/dashboard/.next/static/chunks/app/layout-6c6d2a0fd7c989d3.js +0 -1
  539. package/dashboard/.next/static/chunks/app/notes/page-8bcaa52304500098.js +0 -1
  540. package/dashboard/.next/static/chunks/app/page-d63800ddb072b77b.js +0 -1
  541. package/dashboard/.next/static/chunks/app/progress/page-e26554f655a62eef.js +0 -1
  542. package/dashboard/.next/static/chunks/app/report/page-18c04fb2f193b020.js +0 -1
  543. package/dashboard/.next/static/chunks/app/stats/page-67ff59fd69bed651.js +0 -1
  544. package/dashboard/.next/static/chunks/webpack-4de0e79bdd6a0418.js +0 -1
  545. package/dashboard/.next/static/css/1112cea2260b3926.css +0 -3
  546. package/dashboard/app/access/AccessClient.tsx +0 -402
  547. package/dashboard/app/access/ReadOnlyFunctions.tsx +0 -114
  548. package/dashboard/app/access/page.tsx +0 -125
  549. package/dashboard/app/api/flows/route.ts +0 -32
  550. package/dashboard/app/api/mermaid/route.ts +0 -25
  551. package/dashboard/app/calls/CallsClient.tsx +0 -170
  552. package/dashboard/app/calls/page.tsx +0 -57
  553. package/dashboard/app/conformance/ConformanceTable.tsx +0 -214
  554. package/dashboard/app/conformance/page.tsx +0 -186
  555. package/dashboard/app/diagram/page.tsx +0 -65
  556. package/dashboard/app/flows/page.tsx +0 -102
  557. package/dashboard/app/functions/FunctionsClient.tsx +0 -167
  558. package/dashboard/app/functions/page.tsx +0 -178
  559. package/dashboard/app/issues/IssuesBoardClient.tsx +0 -813
  560. package/dashboard/app/notes/NotesClient.tsx +0 -706
  561. package/dashboard/app/notes/page.tsx +0 -47
  562. package/dashboard/app/progress/ProgressClient.tsx +0 -354
  563. package/dashboard/app/progress/page.tsx +0 -127
  564. package/dashboard/app/stats/StatsClient.tsx +0 -452
  565. package/dashboard/app/stats/page.tsx +0 -50
  566. package/dashboard/components/ConfidenceBadge.tsx +0 -53
  567. package/dashboard/components/DashboardLayout.tsx +0 -510
  568. package/dashboard/components/FilterableTable.tsx +0 -293
  569. package/dashboard/components/MermaidViewer.tsx +0 -267
  570. package/dashboard/components/NotYetGenerated.tsx +0 -32
  571. package/dashboard/components/SeverityBadge.tsx +0 -28
  572. package/dashboard/components/SignalBars.tsx +0 -27
  573. package/dashboard/components/SortableTable.tsx +0 -122
  574. package/dashboard/lib/conformance-tokens.ts +0 -49
  575. package/dist/skills/init-audit.md +0 -386
  576. /package/dashboard/.next/server/app/api/{flows → xref}/route.js.nft.json +0 -0
  577. /package/dashboard/.next/server/app/{api/mermaid → excalidraw-assets/[...path]}/route.js.nft.json +0 -0
  578. /package/dashboard/.next/static/{z5XBev05xDAkDrxWxAjNu → bGV1ry1vvIbM5b506eVBR}/_buildManifest.js +0 -0
  579. /package/dashboard/.next/static/{z5XBev05xDAkDrxWxAjNu → bGV1ry1vvIbM5b506eVBR}/_ssgManifest.js +0 -0
  580. /package/dashboard/.next/static/chunks/{3205-47d09be47325709f.js → 3205-fd963acf2fdfd080.js} +0 -0
  581. /package/dashboard/.next/static/chunks/{3924-88d849290406ee6f.js → 3924-f1f85ccc7d59d7ab.js} +0 -0
  582. /package/dashboard/.next/static/chunks/{618f8807-69fc7bb97c2b87c1.js → 618f8807-fbb1466bc29c17c6.js} +0 -0
  583. /package/dashboard/.next/static/chunks/{7498-d1814d68ba346d13.js → 7498-7cf464d8af69cd0a.js} +0 -0
  584. /package/dashboard/.next/static/chunks/app/_not-found/{page-c299b935209993df.js → page-5ff5d54b6fd6f17e.js} +0 -0
@@ -1,13 +1,25 @@
1
1
  'use client';
2
2
 
3
+ import { LocalTime } from '@/components/ui/LocalTime';
3
4
  import Link from 'next/link';
4
- import { FilterableTable, type FilterableColumn } from '@/components/FilterableTable';
5
- import { ConfidenceBadge } from '@/components/ConfidenceBadge';
5
+ import { useEffect, useMemo, useState } from 'react';
6
+ import { useSearchParams } from 'next/navigation';
7
+ import { ScrollArea, SplitView, Toolbar } from '@/components/ui/SplitView';
8
+ import { Inspector, InspectorField, InspectorSection } from '@/components/ui/Inspector';
9
+ import { ListGroupLabel, ListRow } from '@/components/ui/ListRow';
10
+ import { SegmentedControl } from '@/components/ui/SegmentedControl';
11
+ import { Dot, SeverityDot } from '@/components/ui/SeverityDot';
12
+ import { StateGlyph } from '@/components/ui/StateGlyph';
13
+ import { EmptyState } from '@/components/ui/EmptyState';
6
14
  import { CodeReference } from '@/components/CodeReference';
7
- import { MarkdownRenderer, InlineMarkdown } from '@/components/MarkdownRenderer';
8
- import { SeverityBadge } from '@/components/SeverityBadge';
15
+ import { InlineMarkdown, MarkdownRenderer } from '@/components/MarkdownRenderer';
16
+ import { useHotkeys } from '@/lib/hotkeys';
17
+ import { setFocus } from '@/lib/focus';
18
+ import { replaceQuery } from '@/lib/url';
19
+ import { cn } from '@/lib/cn';
20
+ import type { FixReview, ReviewState } from '@/lib/types';
9
21
 
10
- // ─── Types (shape of .hex/diff.json, see src/core/schema.ts DiffSchema) ──
22
+ // ─── Shapes (src/core/schema.ts DiffSchema / PriorReviewSchema) ─────
11
23
 
12
24
  interface DiffChange {
13
25
  contract: string;
@@ -19,11 +31,6 @@ interface DiffChange {
19
31
  line_end?: number;
20
32
  }
21
33
 
22
- interface ImpactReason {
23
- kind: 'changed' | 'calls' | 'modifier' | 'reads_state' | 'override' | 'inherits';
24
- via: string;
25
- }
26
-
27
34
  interface Impacted {
28
35
  contract: string;
29
36
  function: string;
@@ -31,7 +38,7 @@ interface Impacted {
31
38
  visibility: string;
32
39
  is_entry_point: boolean;
33
40
  depth: number;
34
- reasons: ImpactReason[];
41
+ reasons: Array<{ kind: string; via: string }>;
35
42
  confidence: string;
36
43
  derived_from: string;
37
44
  line_start?: number;
@@ -65,55 +72,42 @@ export interface DiffData {
65
72
  };
66
73
  }
67
74
 
68
- type Severity = 'Critical' | 'High' | 'Medium' | 'Low' | 'Info';
69
-
70
75
  export interface PriorReview {
71
76
  reviewed_at: string;
72
77
  items: Array<{
73
78
  id: string;
74
79
  title: string;
75
- severity: Severity;
80
+ severity: string;
76
81
  status: string;
77
82
  reasoning: string;
78
83
  issue_id?: string;
79
84
  }>;
80
85
  }
81
86
 
82
- // ─── Small pieces ───────────────────────────────────────────────────
87
+ type Section = 'overview' | 'storage' | 'code' | 'impact' | 'prior' | 'fixes';
83
88
 
84
- const CHANGE_STYLES: Record<string, string> = {
85
- added: 'bg-accent-subtle text-accent',
86
- modified: 'bg-[var(--high)]/18 text-[var(--high)]',
87
- removed: 'bg-[var(--critical)]/18 text-[var(--critical)]',
88
- comments_only: 'bg-surface-3 text-text-tertiary',
89
- };
89
+ type Sel =
90
+ | { kind: 'change'; i: number }
91
+ | { kind: 'impact'; i: number }
92
+ | { kind: 'storage'; i: number }
93
+ | { kind: 'prior'; i: number }
94
+ | { kind: 'fix'; i: number }
95
+ | { kind: 'unmapped'; i: number };
90
96
 
91
- const CHANGE_LABELS: Record<string, string> = {
97
+ const CHANGE_WORD: Record<string, string> = {
92
98
  added: 'Added',
93
99
  modified: 'Modified',
94
100
  removed: 'Removed',
95
101
  comments_only: 'Comments only',
96
102
  };
103
+ const CHANGE_COLOR: Record<string, string> = {
104
+ added: 'var(--success)',
105
+ modified: 'var(--high)',
106
+ removed: 'var(--critical)',
107
+ comments_only: 'var(--neutral)',
108
+ };
97
109
 
98
- function Chip({ className, children }: { className: string; children: React.ReactNode }) {
99
- return (
100
- <span
101
- className={`inline-flex items-center rounded-md px-2 py-0.5 text-caption font-medium ${className}`}
102
- >
103
- {children}
104
- </span>
105
- );
106
- }
107
-
108
- function ChangeBadge({ change }: { change: string }) {
109
- return (
110
- <Chip className={CHANGE_STYLES[change] ?? CHANGE_STYLES.comments_only}>
111
- {CHANGE_LABELS[change] ?? change}
112
- </Chip>
113
- );
114
- }
115
-
116
- const REASON_LABELS: Record<ImpactReason['kind'], string> = {
110
+ const REASON: Record<string, string> = {
117
111
  changed: 'Changed',
118
112
  calls: 'Calls',
119
113
  modifier: 'Uses modifier',
@@ -122,343 +116,723 @@ const REASON_LABELS: Record<ImpactReason['kind'], string> = {
122
116
  inherits: 'Inherits',
123
117
  };
124
118
 
125
- const PRIOR_STATUS: Record<string, { label: string; className: string }> = {
126
- regressed: { label: 'Regressed', className: 'bg-[var(--critical)]/18 text-[var(--critical)]' },
127
- acknowledged_still_present: {
128
- label: 'Still present',
129
- className: 'bg-[var(--medium)]/18 text-[var(--medium)]',
130
- },
131
- fix_touched_ok: { label: 'Fix touched, holds', className: 'bg-accent-subtle text-accent' },
132
- fixed_unchanged: { label: 'Fixed, untouched', className: 'bg-surface-3 text-text-secondary' },
133
- code_removed: { label: 'Code removed', className: 'bg-surface-3 text-text-tertiary' },
119
+ const PRIOR: Record<string, { label: string; color: string }> = {
120
+ regressed: { label: 'Regressed', color: 'var(--critical)' },
121
+ acknowledged_still_present: { label: 'Still present', color: 'var(--medium)' },
122
+ fix_touched_ok: { label: 'Fix touched, holds', color: 'var(--success)' },
123
+ fixed_unchanged: { label: 'Fixed, untouched', color: 'var(--neutral)' },
124
+ code_removed: { label: 'Code removed', color: 'var(--neutral)' },
134
125
  };
135
126
 
136
- function Stat({ label, value, tone }: { label: string; value: number | string; tone?: 'warn' }) {
137
- return (
138
- <div
139
- className={`rounded-lg border p-4 ${
140
- tone === 'warn'
141
- ? 'border-[var(--high)]/30 bg-[var(--high)]/5'
142
- : 'border-border-subtle bg-surface-1'
143
- }`}
144
- >
145
- <div
146
- className={`text-display scale-in ${tone === 'warn' ? 'text-[var(--high)]' : 'text-text-primary'}`}
147
- >
148
- {value}
149
- </div>
150
- <div className="mt-1 text-caption text-text-secondary">{label}</div>
151
- </div>
152
- );
153
- }
154
-
155
- function Section({ title, children }: { title: string; children: React.ReactNode }) {
156
- return (
157
- <section className="mb-sp-6">
158
- <h3 className="mb-sp-3 text-heading font-medium text-text-primary">{title}</h3>
159
- {children}
160
- </section>
161
- );
162
- }
127
+ const FIX: Record<string, { label: string; color: string }> = {
128
+ fixed: { label: 'Fixed', color: 'var(--success)' },
129
+ partially_fixed: { label: 'Partially fixed', color: 'var(--medium)' },
130
+ not_fixed: { label: 'Not fixed', color: 'var(--critical)' },
131
+ unaddressed: { label: 'Unaddressed', color: 'var(--high)' },
132
+ unknown: { label: 'Unknown', color: 'var(--neutral)' },
133
+ };
163
134
 
164
- // ─── Columns ────────────────────────────────────────────────────────
165
-
166
- const changeColumns: FilterableColumn<DiffChange>[] = [
167
- {
168
- id: 'contract',
169
- header: 'Contract',
170
- accessorKey: 'contract',
171
- enableColumnFilter: true,
172
- cell: (row) => <span className="font-medium text-text-primary">{row.contract}</span>,
173
- },
174
- {
175
- id: 'entity',
176
- header: 'Entity',
177
- accessorKey: 'entity',
178
- cell: (row) => <code className="font-mono text-body text-text-secondary">{row.entity}</code>,
179
- },
180
- {
181
- id: 'kind',
182
- header: 'Kind',
183
- accessorKey: 'kind',
184
- enableColumnFilter: true,
185
- cell: (row) => (
186
- <span className="text-body text-text-secondary">{row.kind.replace('_', ' ')}</span>
187
- ),
188
- },
189
- {
190
- id: 'change',
191
- header: 'Change',
192
- accessorKey: 'change',
193
- enableColumnFilter: true,
194
- filterOrder: ['added', 'modified', 'removed', 'comments_only'],
195
- cell: (row) => <ChangeBadge change={row.change} />,
196
- },
197
- {
198
- id: 'location',
199
- header: 'Location',
200
- accessorKey: 'file',
201
- cell: (row) =>
202
- row.change === 'removed' || !row.line_start ? (
203
- <code className="font-mono text-caption text-text-tertiary">{row.file}</code>
204
- ) : (
205
- <CodeReference file={row.file} lineStart={row.line_start} lineEnd={row.line_end} />
206
- ),
207
- },
208
- ];
209
-
210
- interface ImpactRow extends Impacted {
211
- entry: string;
212
- reason: string;
213
- }
135
+ const POC_WORD: Record<string, string> = {
136
+ passing: 'passes',
137
+ failing: 'fails',
138
+ error: 'errors',
139
+ missing: 'missing',
140
+ };
214
141
 
215
- const impactColumns: FilterableColumn<ImpactRow>[] = [
216
- {
217
- id: 'contract',
218
- header: 'Contract',
219
- accessorKey: 'contract',
220
- enableColumnFilter: true,
221
- cell: (row) => <span className="font-medium text-text-primary">{row.contract}</span>,
222
- },
223
- {
224
- id: 'function',
225
- header: 'Function',
226
- accessorKey: 'function',
227
- cell: (row) => <code className="font-mono text-body text-text-secondary">{row.function}</code>,
228
- },
229
- {
230
- id: 'entry',
231
- header: 'Entry point',
232
- accessorKey: 'entry',
233
- enableColumnFilter: true,
234
- cell: (row) =>
235
- row.is_entry_point ? (
236
- <Chip className="bg-accent-subtle text-accent">Entry point</Chip>
237
- ) : (
238
- <span className="text-caption text-text-tertiary">{row.visibility}</span>
239
- ),
240
- },
241
- {
242
- id: 'depth',
243
- header: 'Depth',
244
- accessorKey: 'depth',
245
- cell: (row) => (
246
- <span className="text-body text-text-secondary">
247
- {row.depth === 0 ? 'changed' : row.depth}
248
- </span>
249
- ),
250
- },
251
- {
252
- id: 'reason',
253
- header: 'Why',
254
- accessorKey: 'reason',
255
- enableColumnFilter: true,
256
- cell: (row) => (
257
- <div className="space-y-0.5">
258
- {row.reasons.slice(0, 2).map((r, i) => (
259
- <div key={i} className="text-caption text-text-secondary">
260
- {REASON_LABELS[r.kind] ?? r.kind}{' '}
261
- {r.kind !== 'changed' && <code className="font-mono text-text-primary">{r.via}</code>}
262
- {r.kind === 'changed' && <span className="text-text-tertiary">({r.via})</span>}
263
- </div>
264
- ))}
265
- {row.reasons.length > 2 && (
266
- <div className="text-caption text-text-tertiary">+{row.reasons.length - 2} more</div>
267
- )}
268
- </div>
269
- ),
270
- },
271
- {
272
- id: 'confidence',
273
- header: 'Confidence',
274
- accessorKey: 'confidence',
275
- enableColumnFilter: true,
276
- filterOrder: ['high', 'medium', 'low'],
277
- cell: (row) => <ConfidenceBadge level={row.confidence} derivedFrom={row.derived_from} />,
278
- },
279
- {
280
- id: 'location',
281
- header: 'Location',
282
- accessorKey: 'file',
283
- cell: (row) =>
284
- row.line_start ? (
285
- <CodeReference file={row.file} lineStart={row.line_start} />
286
- ) : (
287
- <code className="font-mono text-caption text-text-tertiary">{row.file}</code>
288
- ),
289
- },
290
- ];
291
-
292
- // ─── Component ──────────────────────────────────────────────────────
142
+ const plural = (n: number, w: string) => `${n} ${w}${n === 1 ? '' : 's'}`;
293
143
 
294
144
  export function ChangesClient({
295
145
  diff,
296
146
  overview,
297
- review,
147
+ prior,
148
+ fixReview,
149
+ reviewStates,
298
150
  }: {
299
- diff: DiffData;
151
+ diff: DiffData | null;
300
152
  overview: string | null;
301
- review: PriorReview | null;
153
+ prior: PriorReview | null;
154
+ fixReview: FixReview | null;
155
+ reviewStates: Record<string, string>;
302
156
  }) {
303
- const s = diff.summary;
304
- const base = diff.base_commit.slice(0, 8);
305
- const head = diff.head_commit.slice(0, 8);
306
-
307
- const impactRows: ImpactRow[] = diff.impacted.map((i) => ({
308
- ...i,
309
- entry: i.is_entry_point ? 'Entry point' : 'Internal',
310
- reason: REASON_LABELS[i.reasons[0]?.kind] ?? '',
311
- }));
312
- const regressed = review?.items.filter((i) => i.status === 'regressed').length ?? 0;
157
+ const params = useSearchParams();
158
+
159
+ const sections = useMemo(() => {
160
+ // A full audit has no diff; its fix review is then the only section.
161
+ const out: Array<{ id: Section; label: string; count?: number }> = diff
162
+ ? [{ id: 'overview', label: 'Overview' }]
163
+ : [];
164
+ if (fixReview)
165
+ out.push({
166
+ id: 'fixes',
167
+ label: 'Fix review',
168
+ count: fixReview.cards.length + fixReview.unmapped_changes.length,
169
+ });
170
+ if (diff?.storage_layout_changes.length)
171
+ out.push({
172
+ id: 'storage',
173
+ label: 'Storage layout',
174
+ count: diff.storage_layout_changes.length,
175
+ });
176
+ if (diff) {
177
+ out.push({ id: 'code', label: 'Changed code', count: diff.changes.length });
178
+ out.push({ id: 'impact', label: 'Impact', count: diff.impacted.length });
179
+ }
180
+ if (prior?.items.length)
181
+ out.push({ id: 'prior', label: 'Prior findings', count: prior.items.length });
182
+ return out;
183
+ }, [diff, prior, fixReview]);
184
+
185
+ const [section, setSection] = useState<Section>(() => {
186
+ const q = params.get('s') as Section | null;
187
+ return q && sections.some((x) => x.id === q) ? q : (sections[0]?.id ?? 'overview');
188
+ });
189
+ const [sel, setSel] = useState<Sel | null>(null);
190
+ const [impactScope, setImpactScope] = useState<'entry' | 'all'>('entry');
191
+
192
+ useEffect(() => {
193
+ replaceQuery({ s: section === sections[0]?.id ? null : section });
194
+ setSel(null);
195
+ }, [section]);
196
+
197
+ const impactRows = useMemo(
198
+ () =>
199
+ (diff?.impacted ?? [])
200
+ .map((it, i) => ({ it, i }))
201
+ .filter(({ it }) => impactScope === 'all' || it.is_entry_point || it.depth === 0)
202
+ .sort(
203
+ (a, b) =>
204
+ a.it.depth - b.it.depth || Number(b.it.is_entry_point) - Number(a.it.is_entry_point),
205
+ ),
206
+ [diff, impactScope],
207
+ );
313
208
 
314
- return (
315
- <div>
316
- <h2 className="mb-sp-2 text-title font-semibold text-text-primary">Changes</h2>
317
- <div className="mb-sp-5 flex flex-wrap items-center gap-2 text-caption text-text-tertiary">
318
- <span>Differential audit</span>
319
- <code className="rounded-md bg-surface-2 px-2 py-0.5 font-mono text-text-secondary">
320
- {base}
321
- </code>
322
- <span aria-hidden="true">→</span>
323
- <code className="rounded-md bg-surface-2 px-2 py-0.5 font-mono text-text-secondary">
324
- {head}
325
- </code>
326
- <span>
327
- · +{s.additions} / −{s.deletions} lines
328
- </span>
329
- </div>
209
+ // Selected item → focus bridge.
210
+ useEffect(() => {
211
+ if (!sel) return;
212
+ if (sel.kind === 'impact' && diff) {
213
+ const it = diff.impacted[sel.i];
214
+ setFocus({
215
+ ref: `${it.contract}.${it.function}`,
216
+ kind: 'function',
217
+ label: `${it.contract}.${it.function}`,
218
+ file: it.file,
219
+ ...(it.line_start ? { line_start: it.line_start } : {}),
220
+ });
221
+ } else if (sel.kind === 'change' && diff) {
222
+ const c = diff.changes[sel.i];
223
+ if (c.kind === 'function' || c.kind === 'modifier') {
224
+ setFocus({
225
+ ref: `${c.contract}.${c.entity}`,
226
+ kind: 'function',
227
+ label: `${c.contract}.${c.entity}`,
228
+ file: c.file,
229
+ ...(c.line_start
230
+ ? { line_start: c.line_start, line_end: c.line_end ?? c.line_start }
231
+ : {}),
232
+ });
233
+ }
234
+ } else if (sel.kind === 'fix' && fixReview) {
235
+ const c = fixReview.cards[sel.i];
236
+ setFocus({ ref: c.id, kind: 'card', label: c.title });
237
+ }
238
+ }, [sel]); // eslint-disable-line react-hooks/exhaustive-deps
239
+
240
+ useHotkeys({
241
+ Escape: () => setSel(null),
242
+ j: () => stepSection(1),
243
+ k: () => stepSection(-1),
244
+ });
245
+ const stepSection = (d: number) => {
246
+ const i = sections.findIndex((x) => x.id === section);
247
+ const next = sections[Math.max(0, Math.min(sections.length - 1, i + d))];
248
+ if (next) setSection(next.id);
249
+ };
330
250
 
331
- {(diff.warnings ?? []).map((w, i) => (
332
- <div
333
- key={i}
334
- className="mb-sp-3 rounded-lg border border-[var(--high)]/30 bg-[var(--high)]/5 px-4 py-3 text-body text-text-secondary"
335
- >
336
- {w}
337
- </div>
251
+ const list = (
252
+ <div className="min-h-0 flex-1 overflow-y-auto p-2">
253
+ {sections.map((x) => (
254
+ <ListRow
255
+ key={x.id}
256
+ dense
257
+ selected={x.id === section}
258
+ onClick={() => setSection(x.id)}
259
+ title={x.label}
260
+ trailing={
261
+ x.count !== undefined ? <span className="tabular-nums">{x.count}</span> : undefined
262
+ }
263
+ />
338
264
  ))}
265
+ </div>
266
+ );
339
267
 
340
- <div className="mb-sp-6 grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-6 stagger">
341
- <Stat label="Files changed" value={s.files_changed} />
342
- <Stat label="Functions added" value={s.functions_added} />
343
- <Stat label="Functions modified" value={s.functions_modified} />
344
- <Stat label="Functions removed" value={s.functions_removed} />
345
- <Stat label="Impacted entry points" value={s.impacted_entry_points} />
346
- <Stat
347
- label="Storage layout changes"
348
- value={s.storage_layout_changes}
349
- tone={s.storage_layout_changes > 0 ? 'warn' : undefined}
268
+ return (
269
+ <SplitView
270
+ toolbar={
271
+ <Toolbar
272
+ title="Changes"
273
+ meta={
274
+ diff ? (
275
+ <span>
276
+ <span className="font-mono text-caption">{diff.base_commit.slice(0, 8)}</span> →{' '}
277
+ <span className="font-mono text-caption">{diff.head_commit.slice(0, 8)}</span>
278
+ <span className="ml-2">
279
+ +{diff.summary.additions} −{diff.summary.deletions}
280
+ </span>
281
+ </span>
282
+ ) : fixReview ? (
283
+ <span>
284
+ Fix review ·{' '}
285
+ <span className="font-mono text-caption">{fixReview.from_commit.slice(0, 8)}</span>{' '}
286
+ → <span className="font-mono text-caption">{fixReview.to_commit.slice(0, 8)}</span>
287
+ </span>
288
+ ) : undefined
289
+ }
350
290
  />
351
- {review && (
352
- <Stat
353
- label="Prior findings regressed"
354
- value={regressed}
355
- tone={regressed > 0 ? 'warn' : undefined}
291
+ }
292
+ list={list}
293
+ listWidth={220}
294
+ inspector={
295
+ sel ? (
296
+ <ChangeInspector
297
+ sel={sel}
298
+ diff={diff}
299
+ prior={prior}
300
+ fixReview={fixReview}
301
+ reviewStates={reviewStates}
302
+ onClose={() => setSel(null)}
356
303
  />
357
- )}
304
+ ) : undefined
305
+ }
306
+ >
307
+ <ScrollArea>
308
+ <div className="mx-auto max-w-[760px] px-8 pb-16 pt-8">
309
+ {section === 'overview' && diff && (
310
+ <div className="rise-in">
311
+ <p className="text-heading font-medium text-text-primary">
312
+ {plural(diff.summary.files_changed, 'file')} changed ·{' '}
313
+ {diff.summary.functions_added} added, {diff.summary.functions_modified} modified,{' '}
314
+ {diff.summary.functions_removed} removed
315
+ </p>
316
+ <p className="mt-1 text-body text-text-secondary">
317
+ The change reaches {plural(diff.summary.impacted_entry_points, 'entry point')}
318
+ {diff.summary.storage_layout_changes
319
+ ? ` and moves ${plural(diff.summary.storage_layout_changes, 'storage slot')}`
320
+ : ''}
321
+ .
322
+ </p>
323
+ {(diff.warnings ?? []).map((w, i) => (
324
+ <p key={i} className="mt-3 flex gap-2 text-caption text-text-secondary">
325
+ <Dot color="var(--medium)" className="mt-1" />
326
+ {w}
327
+ </p>
328
+ ))}
329
+ {overview && (
330
+ <div className="mt-8 border-t border-border-subtle pt-6">
331
+ <MarkdownRenderer content={overview.replace(/^# .*\n+/, '')} />
332
+ </div>
333
+ )}
334
+ {fixReview && (
335
+ <button
336
+ type="button"
337
+ onClick={() => setSection('fixes')}
338
+ className="mt-8 flex w-full items-center gap-3 border-y border-border-subtle py-3 text-left hover:border-border-default"
339
+ >
340
+ <span className="flex-1 text-body text-text-primary">
341
+ Fix review of {fixReview.to_commit.slice(0, 8)}:{' '}
342
+ <span className="text-text-secondary">{fixSummary(fixReview)}</span>
343
+ </span>
344
+ </button>
345
+ )}
346
+ </div>
347
+ )}
348
+
349
+ {section === 'storage' && diff && (
350
+ <Rows
351
+ title="Storage layout"
352
+ hint="Slots that moved. Behind a proxy, an upgrade would read these from the wrong place."
353
+ >
354
+ {diff.storage_layout_changes.map((l, i) => (
355
+ <ContentRow
356
+ key={i}
357
+ selected={sel?.kind === 'storage' && sel.i === i}
358
+ onClick={() => setSel({ kind: 'storage', i })}
359
+ leading={<Dot color="var(--high)" />}
360
+ title={<InlineMarkdown>{l.detail}</InlineMarkdown>}
361
+ subtitle={`${l.contract} · ${l.kind.replace(/_/g, ' ')}`}
362
+ />
363
+ ))}
364
+ </Rows>
365
+ )}
366
+
367
+ {section === 'code' && diff && (
368
+ <Rows
369
+ title="Changed code"
370
+ hint={diff.files
371
+ .map((f) => `${f.path} (+${f.additions} −${f.deletions})`)
372
+ .join(' · ')}
373
+ >
374
+ {groupBy(
375
+ diff.changes.map((c, i) => ({ c, i })),
376
+ (x) => x.c.contract,
377
+ ).map(([contract, items]) => (
378
+ <div key={contract}>
379
+ <ListGroupLabel>{contract}</ListGroupLabel>
380
+ {items.map(({ c, i }) => (
381
+ <ContentRow
382
+ key={i}
383
+ selected={sel?.kind === 'change' && sel.i === i}
384
+ onClick={() => setSel({ kind: 'change', i })}
385
+ leading={<Dot color={CHANGE_COLOR[c.change]} title={CHANGE_WORD[c.change]} />}
386
+ title={
387
+ <span className="font-mono text-caption text-text-primary">{c.entity}</span>
388
+ }
389
+ subtitle={`${c.kind.replace('_', ' ')} · ${CHANGE_WORD[c.change] ?? c.change}`}
390
+ />
391
+ ))}
392
+ </div>
393
+ ))}
394
+ </Rows>
395
+ )}
396
+
397
+ {section === 'impact' && diff && (
398
+ <Rows
399
+ title="Impact"
400
+ hint="Every function the change reaches: changed code first, then callers, modifier users, readers of changed state and inherited entry points."
401
+ control={
402
+ <SegmentedControl
403
+ segments={[
404
+ { value: 'entry', label: 'Entry points' },
405
+ { value: 'all', label: 'All', count: diff.impacted.length },
406
+ ]}
407
+ value={impactScope}
408
+ onChange={setImpactScope}
409
+ />
410
+ }
411
+ >
412
+ {impactRows.map(({ it, i }) => {
413
+ const id = `${it.contract}.${it.function}`;
414
+ const st = reviewStates[id] as ReviewState | undefined;
415
+ return (
416
+ <ContentRow
417
+ key={i}
418
+ selected={sel?.kind === 'impact' && sel.i === i}
419
+ onClick={() => setSel({ kind: 'impact', i })}
420
+ leading={st ? <StateGlyph state={st} /> : <span className="h-3.5 w-3.5" />}
421
+ title={
422
+ <span>
423
+ {it.contract}.
424
+ <span className="font-medium">{it.function.split('(')[0]}</span>
425
+ </span>
426
+ }
427
+ subtitle={
428
+ it.depth === 0
429
+ ? `Changed · ${it.reasons[0]?.via ?? ''}`
430
+ : `${plural(it.depth, 'hop')} · ${REASON[it.reasons[0]?.kind] ?? ''} ${it.reasons[0]?.via ?? ''}`
431
+ }
432
+ trailing={it.is_entry_point ? 'Entry point' : it.visibility}
433
+ />
434
+ );
435
+ })}
436
+ </Rows>
437
+ )}
438
+
439
+ {section === 'prior' && prior && (
440
+ <Rows
441
+ title="Prior findings"
442
+ hint="Findings from the previous audit, checked against this change."
443
+ >
444
+ {prior.items.map((p, i) => (
445
+ <ContentRow
446
+ key={p.id}
447
+ selected={sel?.kind === 'prior' && sel.i === i}
448
+ onClick={() => setSel({ kind: 'prior', i })}
449
+ leading={<SeverityDot severity={p.severity} label={false} />}
450
+ title={p.title}
451
+ subtitle={`${p.id} · ${PRIOR[p.status]?.label ?? p.status}`}
452
+ trailing={
453
+ <Dot
454
+ color={PRIOR[p.status]?.color ?? 'var(--neutral)'}
455
+ title={PRIOR[p.status]?.label}
456
+ />
457
+ }
458
+ />
459
+ ))}
460
+ </Rows>
461
+ )}
462
+
463
+ {section === 'fixes' && fixReview && (
464
+ <>
465
+ <Rows
466
+ title="Fix review"
467
+ hint={`${fixReview.from_commit.slice(0, 8)} → ${fixReview.to_commit.slice(0, 8)} · ${fixSummary(fixReview)}. Read-only here; the CLI skill applies the proposals.`}
468
+ >
469
+ {fixReview.cards.map((c, i) => (
470
+ <ContentRow
471
+ key={c.id}
472
+ selected={sel?.kind === 'fix' && sel.i === i}
473
+ onClick={() => setSel({ kind: 'fix', i })}
474
+ leading={
475
+ <Dot
476
+ color={FIX[c.status]?.color ?? 'var(--neutral)'}
477
+ title={FIX[c.status]?.label}
478
+ />
479
+ }
480
+ title={c.title}
481
+ subtitle={`${c.id}${c.github_issue ? ` · #${c.github_issue}` : ''} · ${FIX[c.status]?.label ?? c.status}${c.applied_at ? ' · applied' : ''}`}
482
+ trailing={<SeverityDot severity={c.severity} />}
483
+ />
484
+ ))}
485
+ </Rows>
486
+ {fixReview.unmapped_changes.length > 0 && (
487
+ <Rows
488
+ title="Changes no card explains"
489
+ hint="Fix-commit code that needs a review of its own."
490
+ >
491
+ {fixReview.unmapped_changes.map((u, i) => (
492
+ <ContentRow
493
+ key={i}
494
+ selected={sel?.kind === 'unmapped' && sel.i === i}
495
+ onClick={() => setSel({ kind: 'unmapped', i })}
496
+ leading={
497
+ <Dot
498
+ color={u.reviewed ? 'var(--success)' : 'var(--medium)'}
499
+ title={u.reviewed ? 'Reviewed' : 'Needs review'}
500
+ />
501
+ }
502
+ title={
503
+ <span className="font-mono text-caption text-text-primary">{u.entity}</span>
504
+ }
505
+ subtitle={`${u.contract} · ${u.kind} ${u.change}${u.reviewed ? ' · reviewed' : ''}`}
506
+ />
507
+ ))}
508
+ </Rows>
509
+ )}
510
+ </>
511
+ )}
512
+ </div>
513
+ </ScrollArea>
514
+ </SplitView>
515
+ );
516
+ }
517
+
518
+ function fixSummary(f: FixReview): string {
519
+ const s = f.summary;
520
+ return [
521
+ s.fixed ? `${s.fixed} fixed` : null,
522
+ s.partially_fixed ? `${s.partially_fixed} partially fixed` : null,
523
+ s.not_fixed ? `${s.not_fixed} not fixed` : null,
524
+ s.unaddressed ? `${s.unaddressed} unaddressed` : null,
525
+ s.unknown ? `${s.unknown} unknown` : null,
526
+ s.unmapped ? plural(s.unmapped, 'unmapped change') : null,
527
+ ]
528
+ .filter(Boolean)
529
+ .join(' · ');
530
+ }
531
+
532
+ function groupBy<T>(items: T[], key: (t: T) => string): Array<[string, T[]]> {
533
+ const m = new Map<string, T[]>();
534
+ for (const it of items) m.set(key(it), [...(m.get(key(it)) ?? []), it]);
535
+ return Array.from(m.entries());
536
+ }
537
+
538
+ function Rows({
539
+ title,
540
+ hint,
541
+ control,
542
+ children,
543
+ }: {
544
+ title: string;
545
+ hint?: string;
546
+ control?: React.ReactNode;
547
+ children: React.ReactNode;
548
+ }) {
549
+ return (
550
+ <section className="rise-in mb-10">
551
+ <div className="mb-3 flex flex-wrap items-end justify-between gap-3">
552
+ <div className="min-w-0">
553
+ <h2 className="text-heading font-medium text-text-primary">{title}</h2>
554
+ {hint && <p className="mt-0.5 text-caption text-text-tertiary">{hint}</p>}
555
+ </div>
556
+ {control}
358
557
  </div>
558
+ <div className="-mx-2">{children}</div>
559
+ </section>
560
+ );
561
+ }
359
562
 
360
- {overview && (
361
- <Section title="What changed">
362
- <div className="rounded-lg border border-border-subtle bg-surface-1 p-6">
363
- <MarkdownRenderer content={overview} />
364
- </div>
365
- </Section>
563
+ function ContentRow({
564
+ selected,
565
+ onClick,
566
+ leading,
567
+ title,
568
+ subtitle,
569
+ trailing,
570
+ }: {
571
+ selected: boolean;
572
+ onClick: () => void;
573
+ leading?: React.ReactNode;
574
+ title: React.ReactNode;
575
+ subtitle?: React.ReactNode;
576
+ trailing?: React.ReactNode;
577
+ }) {
578
+ return (
579
+ <button
580
+ type="button"
581
+ onClick={onClick}
582
+ className={cn(
583
+ 'flex w-full items-center gap-3 rounded-md px-2 py-2 text-left',
584
+ selected ? 'bg-accent-subtle' : 'hover:bg-surface-1',
366
585
  )}
586
+ >
587
+ {leading && <span className="flex w-4 shrink-0 justify-center">{leading}</span>}
588
+ <span className="min-w-0 flex-1">
589
+ <span
590
+ className={cn('block truncate text-body', selected ? 'text-accent' : 'text-text-primary')}
591
+ >
592
+ {title}
593
+ </span>
594
+ {subtitle && (
595
+ <span className="block truncate text-caption text-text-tertiary">{subtitle}</span>
596
+ )}
597
+ </span>
598
+ {trailing && <span className="shrink-0 text-caption text-text-tertiary">{trailing}</span>}
599
+ </button>
600
+ );
601
+ }
367
602
 
368
- {diff.storage_layout_changes.length > 0 && (
369
- <Section title="Storage layout">
370
- <ul className="divide-y divide-border-subtle rounded-lg border border-border-subtle bg-surface-1">
371
- {diff.storage_layout_changes.map((l, i) => (
372
- <li key={i} className="flex flex-wrap items-baseline gap-x-3 gap-y-1 px-4 py-3">
373
- <span className="font-medium text-text-primary">{l.contract}</span>
374
- <Chip className="bg-[var(--high)]/18 text-[var(--high)]">
375
- {l.kind.replace(/_/g, ' ')}
376
- </Chip>
377
- <span className="text-body text-text-secondary">
378
- <InlineMarkdown>{l.detail}</InlineMarkdown>
379
- </span>
603
+ // ─── Inspector ──────────────────────────────────────────────────────
604
+
605
+ function ReviewLink({ id, states }: { id: string; states: Record<string, string> }) {
606
+ const st = states[id] as ReviewState | undefined;
607
+ if (!st) return null;
608
+ return (
609
+ <Link
610
+ href={`/review?fn=${encodeURIComponent(id)}`}
611
+ className="inline-flex h-8 items-center gap-2 rounded-md border border-border-default bg-surface-0 px-3 text-body font-medium text-text-primary shadow-sm hover:bg-surface-2"
612
+ >
613
+ <StateGlyph state={st} /> Open in Review
614
+ </Link>
615
+ );
616
+ }
617
+
618
+ function ChangeInspector({
619
+ sel,
620
+ diff,
621
+ prior,
622
+ fixReview,
623
+ reviewStates,
624
+ onClose,
625
+ }: {
626
+ sel: Sel;
627
+ diff: DiffData | null;
628
+ prior: PriorReview | null;
629
+ fixReview: FixReview | null;
630
+ reviewStates: Record<string, string>;
631
+ onClose: () => void;
632
+ }) {
633
+ if (sel.kind === 'change' && diff) {
634
+ const c = diff.changes[sel.i];
635
+ const id = `${c.contract}.${c.entity}`;
636
+ return (
637
+ <Inspector
638
+ eyebrow={`${c.contract} · ${c.kind.replace('_', ' ')}`}
639
+ title={<span className="font-mono text-body">{c.entity}</span>}
640
+ subtitle={CHANGE_WORD[c.change]}
641
+ onClose={onClose}
642
+ actions={<ReviewLink id={id} states={reviewStates} />}
643
+ >
644
+ <InspectorSection label="Where">
645
+ {c.change !== 'removed' && c.line_start ? (
646
+ <CodeReference file={c.file} lineStart={c.line_start} lineEnd={c.line_end} />
647
+ ) : (
648
+ <span className="font-mono text-caption text-text-secondary">{c.file}</span>
649
+ )}
650
+ </InspectorSection>
651
+ </Inspector>
652
+ );
653
+ }
654
+ if (sel.kind === 'impact' && diff) {
655
+ const it = diff.impacted[sel.i];
656
+ const id = `${it.contract}.${it.function}`;
657
+ return (
658
+ <Inspector
659
+ eyebrow={`${it.contract} · ${it.is_entry_point ? 'Entry point' : it.visibility}`}
660
+ title={it.function.split('(')[0]}
661
+ subtitle={<span className="font-mono">{it.function}</span>}
662
+ onClose={onClose}
663
+ actions={<ReviewLink id={id} states={reviewStates} />}
664
+ >
665
+ <InspectorSection label="Why it is impacted">
666
+ <ul className="space-y-1.5">
667
+ {it.reasons.map((r, i) => (
668
+ <li key={i} className="text-body text-text-secondary">
669
+ {REASON[r.kind] ?? r.kind}{' '}
670
+ <span className="font-mono text-caption text-text-primary">{r.via}</span>
380
671
  </li>
381
672
  ))}
382
673
  </ul>
383
- </Section>
384
- )}
385
-
386
- <Section title="Changed code">
387
- <p className="mb-sp-3 text-body text-text-secondary">
388
- {diff.files.length} file{diff.files.length !== 1 ? 's' : ''}:{' '}
389
- {diff.files.map((f, i) => (
390
- <span key={f.path}>
391
- {i > 0 && ', '}
392
- <code className="font-mono text-caption text-text-primary">{f.path}</code>{' '}
393
- <span className="text-caption text-text-tertiary">
394
- ({f.status}
395
- {f.old_path ? ` from ${f.old_path}` : ''}, +{f.additions}/−{f.deletions})
396
- </span>
397
- </span>
398
- ))}
399
- </p>
400
- <FilterableTable columns={changeColumns} data={diff.changes} />
401
- </Section>
402
-
403
- <Section title="Impacted flows">
404
- <p className="mb-sp-3 text-body text-text-secondary">
405
- Every function the change reaches: changed code (depth 0), then callers, modifier users,
406
- readers of state the change writes, and inherited entry points. {s.impacted_functions}{' '}
407
- impacted beyond the changed code.
408
- </p>
409
- <FilterableTable columns={impactColumns} data={impactRows} />
410
- </Section>
411
-
412
- {review && review.items.length > 0 && (
413
- <Section title="Prior findings">
414
- <div className="overflow-x-auto rounded-lg border border-border-subtle bg-surface-1">
415
- <table className="w-full text-left">
416
- <thead>
417
- <tr className="border-b border-border-subtle text-caption text-text-tertiary">
418
- <th className="px-4 py-2 font-normal">ID</th>
419
- <th className="px-4 py-2 font-normal">Title</th>
420
- <th className="px-4 py-2 font-normal">Severity</th>
421
- <th className="px-4 py-2 font-normal">Status</th>
422
- <th className="px-4 py-2 font-normal">Card</th>
423
- </tr>
424
- </thead>
425
- <tbody>
426
- {review.items.map((item) => {
427
- const st = PRIOR_STATUS[item.status] ?? {
428
- label: item.status,
429
- className: 'bg-surface-3 text-text-secondary',
430
- };
431
- return (
432
- <tr
433
- key={item.id}
434
- className="border-b border-border-subtle last:border-0 hover:bg-surface-3"
435
- title={item.reasoning}
436
- >
437
- <td className="px-4 py-2 text-body text-text-secondary">{item.id}</td>
438
- <td className="px-4 py-2 text-body text-text-primary">{item.title}</td>
439
- <td className="px-4 py-2">
440
- <SeverityBadge severity={item.severity} />
441
- </td>
442
- <td className="px-4 py-2">
443
- <Chip className={st.className}>{st.label}</Chip>
444
- </td>
445
- <td className="px-4 py-2 text-body">
446
- {item.issue_id ? (
447
- <Link href="/issues" className="text-accent hover:underline">
448
- {item.issue_id}
449
- </Link>
450
- ) : (
451
- <span className="text-text-tertiary">—</span>
452
- )}
453
- </td>
454
- </tr>
455
- );
456
- })}
457
- </tbody>
458
- </table>
459
- </div>
460
- </Section>
461
- )}
462
- </div>
463
- );
674
+ <p className="mt-2 text-caption text-text-tertiary">
675
+ {it.depth === 0
676
+ ? 'Changed itself'
677
+ : `${plural(it.depth, 'hop')} from the nearest change`}{' '}
678
+ · {it.confidence} confidence
679
+ </p>
680
+ </InspectorSection>
681
+ <InspectorSection label="Where">
682
+ <CodeReference file={it.file} lineStart={it.line_start} />
683
+ </InspectorSection>
684
+ </Inspector>
685
+ );
686
+ }
687
+ if (sel.kind === 'storage' && diff) {
688
+ const l = diff.storage_layout_changes[sel.i];
689
+ return (
690
+ <Inspector
691
+ eyebrow={l.contract}
692
+ title={l.kind.replace(/_/g, ' ').replace(/^./, (m) => m.toUpperCase())}
693
+ onClose={onClose}
694
+ >
695
+ <InspectorSection label="Detail">
696
+ <p className="text-body text-text-secondary">
697
+ <InlineMarkdown>{l.detail}</InlineMarkdown>
698
+ </p>
699
+ <p className="mt-2 font-mono text-caption text-text-tertiary">{l.file}</p>
700
+ </InspectorSection>
701
+ </Inspector>
702
+ );
703
+ }
704
+ if (sel.kind === 'prior' && prior) {
705
+ const p = prior.items[sel.i];
706
+ return (
707
+ <Inspector
708
+ eyebrow={`${p.id} · prior audit`}
709
+ title={p.title}
710
+ subtitle={<SeverityDot severity={p.severity} />}
711
+ onClose={onClose}
712
+ actions={
713
+ p.issue_id ? (
714
+ <Link
715
+ href={`/issues?card=${encodeURIComponent(p.issue_id)}`}
716
+ className="inline-flex h-8 items-center rounded-md border border-border-default bg-surface-0 px-3 text-body font-medium text-text-primary shadow-sm hover:bg-surface-2"
717
+ >
718
+ Open card {p.issue_id}
719
+ </Link>
720
+ ) : undefined
721
+ }
722
+ >
723
+ <InspectorSection label={PRIOR[p.status]?.label ?? p.status}>
724
+ <p className="text-body text-text-secondary">{p.reasoning}</p>
725
+ </InspectorSection>
726
+ </Inspector>
727
+ );
728
+ }
729
+ if (sel.kind === 'fix' && fixReview) {
730
+ const c = fixReview.cards[sel.i];
731
+ return (
732
+ <Inspector
733
+ eyebrow={`${c.id}${c.github_issue ? ` · #${c.github_issue}` : ''}`}
734
+ title={c.title}
735
+ subtitle={<SeverityDot severity={c.severity} />}
736
+ onClose={onClose}
737
+ actions={
738
+ <Link
739
+ href={`/issues?card=${encodeURIComponent(c.id)}`}
740
+ className="inline-flex h-8 items-center rounded-md border border-border-default bg-surface-0 px-3 text-body font-medium text-text-primary shadow-sm hover:bg-surface-2"
741
+ >
742
+ Open card
743
+ </Link>
744
+ }
745
+ >
746
+ <InspectorSection label="Status">
747
+ <p className="flex items-center gap-2 text-body text-text-primary">
748
+ <Dot color={FIX[c.status]?.color ?? 'var(--neutral)'} />
749
+ {FIX[c.status]?.label ?? c.status}
750
+ </p>
751
+ {c.evidence && <p className="mt-1.5 text-body text-text-secondary">{c.evidence}</p>}
752
+ </InspectorSection>
753
+ {(c.poc.file || c.poc.before || c.poc.after) && (
754
+ <InspectorSection label="Proof of concept">
755
+ {c.poc.file && (
756
+ <p className="truncate font-mono text-caption text-text-secondary">{c.poc.file}</p>
757
+ )}
758
+ {c.poc.before && (
759
+ <InspectorField label="Audited">
760
+ {POC_WORD[c.poc.before] ?? c.poc.before}
761
+ </InspectorField>
762
+ )}
763
+ {c.poc.after && (
764
+ <InspectorField label="Fixed">{POC_WORD[c.poc.after] ?? c.poc.after}</InspectorField>
765
+ )}
766
+ </InspectorSection>
767
+ )}
768
+ {(c.changed_functions.length > 0 || c.commits.length > 0) && (
769
+ <InspectorSection label="Changed by">
770
+ {c.changed_functions.map((f) => (
771
+ <Link
772
+ key={f}
773
+ href={`/review?fn=${encodeURIComponent(f)}`}
774
+ className="-mx-1.5 block truncate rounded-md px-1.5 py-1 text-body text-text-primary hover:bg-surface-2"
775
+ >
776
+ {f.split('(')[0]}
777
+ </Link>
778
+ ))}
779
+ {c.commits.length > 0 && (
780
+ <p className="mt-1 font-mono text-caption text-text-tertiary">
781
+ {c.commits.join(', ')}
782
+ </p>
783
+ )}
784
+ </InspectorSection>
785
+ )}
786
+ {(c.proposed.resolution || c.proposed.update_from_client) && (
787
+ <InspectorSection label={c.applied_at ? 'Applied to GitHub' : 'Proposed'}>
788
+ {c.proposed.resolution && (
789
+ <InspectorField label="Resolution">{c.proposed.resolution}</InspectorField>
790
+ )}
791
+ {c.proposed.update_from_client && (
792
+ <p className="mt-1 text-body text-text-secondary">{c.proposed.update_from_client}</p>
793
+ )}
794
+ {c.applied_at && (
795
+ <p className="mt-1 text-caption text-text-tertiary">
796
+ <LocalTime iso={c.applied_at} />
797
+ </p>
798
+ )}
799
+ </InspectorSection>
800
+ )}
801
+ </Inspector>
802
+ );
803
+ }
804
+ if (sel.kind === 'unmapped' && fixReview) {
805
+ const u = fixReview.unmapped_changes[sel.i];
806
+ return (
807
+ <Inspector
808
+ eyebrow={`${u.contract} · ${u.kind} ${u.change}`}
809
+ title={<span className="font-mono text-body">{u.entity}</span>}
810
+ onClose={onClose}
811
+ >
812
+ <InspectorSection label="Where">
813
+ <p className="font-mono text-caption text-text-secondary">{u.file}</p>
814
+ </InspectorSection>
815
+ {u.impacted_entry_points.length > 0 && (
816
+ <InspectorSection label="Reaches">
817
+ {u.impacted_entry_points.map((e) => (
818
+ <Link
819
+ key={e}
820
+ href={`/review?fn=${encodeURIComponent(e)}`}
821
+ className="-mx-1.5 block truncate rounded-md px-1.5 py-1 text-body text-text-primary hover:bg-surface-2"
822
+ >
823
+ {e.split('(')[0]}
824
+ </Link>
825
+ ))}
826
+ </InspectorSection>
827
+ )}
828
+ <InspectorSection label="Review">
829
+ <p className="text-body text-text-secondary">
830
+ {u.reviewed ? 'Reviewed.' : 'Not reviewed yet.'}
831
+ </p>
832
+ {u.note && <p className="mt-1 text-body text-text-secondary">{u.note}</p>}
833
+ </InspectorSection>
834
+ </Inspector>
835
+ );
836
+ }
837
+ return <EmptyState sentence="Nothing selected." />;
464
838
  }