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
@@ -0,0 +1,692 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * fizz_sync.js
4
+ *
5
+ * Deterministic drift detector for projects previously processed by the
6
+ * Fizz skill. Compares the stored snapshot (.hex/fuzz/fizz_data/last-run.json)
7
+ * against the current source/ABI state and emits:
8
+ * - a human-readable report to stdout
9
+ * - a machine-readable JSON report for consumption by the /fizz-sync skill
10
+ *
11
+ * Modes:
12
+ * (default) Dry-run. Prints delta + writes sync-report.json. No file edits.
13
+ * --init Writes a fresh last-run.json from current state. Refuses to
14
+ * overwrite an existing snapshot unless --force is passed.
15
+ * --refresh-snapshot Overwrites last-run.json with current state (used after
16
+ * --apply to "bless" the new state). Requires an existing snapshot.
17
+ * --apply-handlers Regenerates handler files for contracts whose signatures drifted.
18
+ * Backs up the old handler file to <name>.pre-sync.bak before
19
+ * overwriting. User edits to clamping bodies WILL be lost — intended
20
+ * to be driven by the skill doc which then reseeds them via the LLM.
21
+ *
22
+ * Options:
23
+ * --only <name> Scope diff/apply to a single contract name.
24
+ * --force Allow destructive operations (--init over existing snapshot).
25
+ * --meta-dir <dir> Metadata directory (default: .hex/fuzz/fizz_data).
26
+ * --suite-dir <dir> Suite directory (default: test/fizz).
27
+ * --report <path> Explicit JSON report path (default: <meta>/sync-report.json).
28
+ *
29
+ * Exit codes:
30
+ * 0 — no drift detected (or --init/--refresh-snapshot/--apply-handlers succeeded)
31
+ * 1 — drift detected in dry-run mode
32
+ * 2 — precondition error (missing file, bad input)
33
+ * 3 — unexpected error
34
+ */
35
+
36
+ const fs = require('fs');
37
+ const path = require('path');
38
+ const crypto = require('crypto');
39
+
40
+ // ──────────────────────────────── CLI ────────────────────────────────
41
+
42
+ const args = process.argv.slice(2);
43
+ if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
44
+ printUsage();
45
+ process.exit(0);
46
+ }
47
+
48
+ const projectRoot = path.resolve(args[0]);
49
+ const opts = {
50
+ init: false,
51
+ refreshSnapshot: false,
52
+ applyHandlers: false,
53
+ force: false,
54
+ only: null,
55
+ metaRelDir: '.hex/fuzz/fizz_data', // Hex: was 'fizz_data'
56
+ suiteRelDir: path.join('test', 'fizz'),
57
+ reportPath: null,
58
+ };
59
+
60
+ for (let i = 1; i < args.length; i++) {
61
+ const a = args[i];
62
+ if (a === '--init') opts.init = true;
63
+ else if (a === '--refresh-snapshot') opts.refreshSnapshot = true;
64
+ else if (a === '--apply-handlers') opts.applyHandlers = true;
65
+ else if (a === '--force') opts.force = true;
66
+ else if (a === '--only' && args[i + 1]) opts.only = args[++i];
67
+ else if (a === '--meta-dir' && args[i + 1]) opts.metaRelDir = args[++i];
68
+ else if (a === '--suite-dir' && args[i + 1]) opts.suiteRelDir = args[++i];
69
+ else if (a === '--report' && args[i + 1]) opts.reportPath = path.resolve(args[++i]);
70
+ else {
71
+ console.error(`Unknown argument: ${a}`);
72
+ printUsage();
73
+ process.exit(2);
74
+ }
75
+ }
76
+
77
+ const metaDir = path.join(projectRoot, opts.metaRelDir);
78
+ const suiteDir = path.join(projectRoot, opts.suiteRelDir);
79
+ const snapshotPath = path.join(metaDir, 'last-run.json');
80
+ const contractsJsonPath = path.join(metaDir, 'contracts.json');
81
+ const selectionPath = path.join(metaDir, 'entry-point-selection.json');
82
+ const propertiesMdPath = path.join(projectRoot, '.hex', 'fuzz', 'PROPERTIES.md'); // Hex: was <root>/PROPERTIES.md
83
+ const propertiesSolPath = path.join(suiteDir, 'Properties.sol');
84
+ const handlersDir = path.join(suiteDir, 'handlers');
85
+ const reportPath = opts.reportPath || path.join(metaDir, 'sync-report.json');
86
+
87
+ function printUsage() {
88
+ console.log(
89
+ `Usage: node fizz_sync.js <PROJECT_ROOT> [mode] [options]
90
+
91
+ Modes (choose at most one):
92
+ (default) Dry-run drift report.
93
+ --init Write last-run.json from current state.
94
+ --refresh-snapshot Overwrite last-run.json with current state.
95
+ --apply-handlers Regenerate drifted handler files (creates .pre-sync.bak).
96
+
97
+ Options:
98
+ --only <contract> Scope to a single contract name.
99
+ --force Allow destructive ops (overwrite snapshot in --init).
100
+ --meta-dir <dir> Metadata directory (default: .hex/fuzz/fizz_data).
101
+ --suite-dir <dir> Suite directory (default: test/fizz).
102
+ --report <path> Explicit JSON report path.`
103
+ );
104
+ }
105
+
106
+ // ────────────────────────── Helpers ──────────────────────────
107
+
108
+ function fail(code, msg) {
109
+ console.error(`fizz-sync: ${msg}`);
110
+ process.exit(code);
111
+ }
112
+
113
+ function loadJson(p) {
114
+ if (!fs.existsSync(p)) return null;
115
+ try {
116
+ return JSON.parse(fs.readFileSync(p, 'utf8'));
117
+ } catch (e) {
118
+ fail(2, `failed to parse ${p}: ${e.message}`);
119
+ }
120
+ }
121
+
122
+ function sha256File(p) {
123
+ if (!fs.existsSync(p)) return null;
124
+ return 'sha256:' + crypto.createHash('sha256').update(fs.readFileSync(p)).digest('hex');
125
+ }
126
+
127
+ /** Canonical Solidity-style function signature used as the match key. */
128
+ function canonicalSignature(fn) {
129
+ const parts = (fn.inputs || []).map(i => {
130
+ // Prefer internalType when present so struct types and arrays are distinguishable.
131
+ // Fall back to raw ABI type otherwise.
132
+ const t = i.internalType || i.type || 'unknown';
133
+ // Strip leading 'struct ' / 'contract ' / 'enum ' — extract_abis already does this
134
+ // for contracts.json, but belt-and-suspenders for snapshots written from elsewhere.
135
+ return t.replace(/^(struct|contract|enum)\s+/, '');
136
+ });
137
+ return `${fn.name}(${parts.join(',')})`;
138
+ }
139
+
140
+ /** camelCase instance name (matches generate_handlers.js). */
141
+ function lcFirst(s) { return s ? s.charAt(0).toLowerCase() + s.slice(1) : s; }
142
+
143
+ // ────────────────────── Snapshot build/read ──────────────────────
144
+
145
+ /**
146
+ * Build a snapshot object from current metadata + source state.
147
+ *
148
+ * Scope comes from entry-point-selection.json (the user's prior choice of what to fuzz).
149
+ * Signatures come from contracts.json (refreshed by extract_abis before sync runs).
150
+ * Tier comes from entry-point-selection.json.
151
+ *
152
+ * Contracts in selection but no longer in contracts.json become "removed".
153
+ * New functions that appear in contracts.json (for in-scope contracts) inherit tier "primary".
154
+ */
155
+ function buildSnapshot() {
156
+ const selection = loadJson(selectionPath);
157
+ if (!selection) fail(2, `missing ${path.relative(projectRoot, selectionPath)} — run Step 4 of the Fizz skill first.`);
158
+
159
+ const contractsJson = loadJson(contractsJsonPath);
160
+ if (!contractsJson) fail(2, `missing ${path.relative(projectRoot, contractsJsonPath)} — run extract_abis.js first.`);
161
+ const contractsJsonByName = new Map(contractsJson.map(c => [c.name, c]));
162
+
163
+ // Tier lookup: selection[contract].function.name → tier
164
+ const tierByContract = new Map();
165
+ for (const sel of selection) {
166
+ const m = new Map();
167
+ for (const f of (sel.functions || [])) m.set(canonicalSignature(f), f.tier || 'primary');
168
+ tierByContract.set(sel.name, m);
169
+ }
170
+
171
+ const contracts = [];
172
+ for (const sel of selection) {
173
+ const fresh = contractsJsonByName.get(sel.name);
174
+ if (!fresh) {
175
+ // Contract removed from source — keep the old entry shape with an empty function list
176
+ // so diffSnapshots flags it as removed.
177
+ contracts.push({
178
+ name: sel.name,
179
+ sourcePath: sel.sourcePath,
180
+ sourceHash: null,
181
+ constructor: sel.constructor || { inputs: [], stateMutability: 'nonpayable' },
182
+ functions: [],
183
+ _removedFromSource: true,
184
+ });
185
+ continue;
186
+ }
187
+ const srcAbs = path.join(projectRoot, fresh.sourcePath);
188
+ const tierMap = tierByContract.get(sel.name) || new Map();
189
+ contracts.push({
190
+ name: fresh.name,
191
+ sourcePath: fresh.sourcePath,
192
+ sourceHash: sha256File(srcAbs),
193
+ constructor: fresh.constructor || { inputs: [], stateMutability: 'nonpayable' },
194
+ functions: (fresh.functions || []).map(f => {
195
+ const sig = canonicalSignature(f);
196
+ return {
197
+ name: f.name,
198
+ inputs: f.inputs || [],
199
+ stateMutability: f.stateMutability || 'nonpayable',
200
+ tier: tierMap.get(sig) || 'primary',
201
+ signature: sig,
202
+ };
203
+ }),
204
+ });
205
+ }
206
+
207
+ // Handler file hashes (all .sol files under handlers/, excluding Handlers.sol aggregator).
208
+ const handlerFiles = {};
209
+ if (fs.existsSync(handlersDir)) {
210
+ for (const entry of fs.readdirSync(handlersDir)) {
211
+ if (!entry.endsWith('.sol')) continue;
212
+ const full = path.join(handlersDir, entry);
213
+ handlerFiles[entry] = sha256File(full);
214
+ }
215
+ }
216
+
217
+ // Property registry from PROPERTIES.md + tagged Solidity in Properties.sol.
218
+ const propertiesFromMd = parsePropertiesMd();
219
+ const taggedSolidity = scanTaggedProperties();
220
+ const properties = propertiesFromMd.map(p => ({
221
+ id: p.id,
222
+ checkbox: p.checkbox,
223
+ functionName: taggedSolidity.get(p.id)?.functionName || null,
224
+ file: taggedSolidity.get(p.id)?.file || null,
225
+ }));
226
+
227
+ return {
228
+ version: 1,
229
+ createdAt: new Date().toISOString(),
230
+ projectRoot: path.relative(projectRoot, projectRoot) || '.',
231
+ suiteDir: opts.suiteRelDir,
232
+ metaDir: opts.metaRelDir,
233
+ contracts,
234
+ handlers: handlerFiles,
235
+ properties,
236
+ };
237
+ }
238
+
239
+ /**
240
+ * Parse .hex/fuzz/PROPERTIES.md. Returns [{id, checkbox}].
241
+ * Checkbox states: " " (pending), "x" (implemented), "~" (quarantined/stale),
242
+ * "-" (manually dropped).
243
+ */
244
+ function parsePropertiesMd() {
245
+ if (!fs.existsSync(propertiesMdPath)) return [];
246
+ const content = fs.readFileSync(propertiesMdPath, 'utf8');
247
+ const re = /^- \[([ x~\-])\]\s+\*\*(GL-\d+|SP-\d+)\*\*/gm;
248
+ const out = [];
249
+ let m;
250
+ while ((m = re.exec(content)) !== null) {
251
+ out.push({ id: m[2], checkbox: m[1] });
252
+ }
253
+ return out;
254
+ }
255
+
256
+ /**
257
+ * Scan Properties.sol + handler files for `/// @notice <ID>:` tagged functions.
258
+ * Returns Map<id, {functionName, file}>.
259
+ */
260
+ function scanTaggedProperties() {
261
+ const out = new Map();
262
+ const files = [];
263
+ if (fs.existsSync(propertiesSolPath)) files.push(propertiesSolPath);
264
+ if (fs.existsSync(handlersDir)) {
265
+ for (const entry of fs.readdirSync(handlersDir)) {
266
+ if (entry.endsWith('.sol')) files.push(path.join(handlersDir, entry));
267
+ }
268
+ }
269
+ const re = /\/\/\/ @notice\s+(GL-\d+|SP-\d+):[^\n]*\n(?:\s*\/\/\/[^\n]*\n)*\s*function\s+(\w+)\s*\(/g;
270
+ for (const f of files) {
271
+ const content = fs.readFileSync(f, 'utf8');
272
+ let m;
273
+ while ((m = re.exec(content)) !== null) {
274
+ out.set(m[1], { functionName: m[2], file: path.relative(projectRoot, f) });
275
+ }
276
+ }
277
+ return out;
278
+ }
279
+
280
+ // ────────────────────── Diff logic ──────────────────────
281
+
282
+ /**
283
+ * Compute diff between stored snapshot and freshly-built snapshot.
284
+ * Returns an object with categorised deltas + a `hasDrift` boolean.
285
+ */
286
+ function diffSnapshots(oldSnap, newSnap) {
287
+ const oldByName = new Map((oldSnap.contracts || []).map(c => [c.name, c]));
288
+ const newByName = new Map((newSnap.contracts || []).map(c => [c.name, c]));
289
+
290
+ const contractsAdded = [];
291
+ const contractsRemoved = [];
292
+ const contractsChanged = [];
293
+ const sourcesChanged = [];
294
+
295
+ for (const [name, newC] of newByName) {
296
+ if (opts.only && name !== opts.only) continue;
297
+ if (!oldByName.has(name)) {
298
+ contractsAdded.push({
299
+ name,
300
+ sourcePath: newC.sourcePath,
301
+ functions: newC.functions.map(f => ({ signature: f.signature, tier: f.tier })),
302
+ });
303
+ continue;
304
+ }
305
+ const oldC = oldByName.get(name);
306
+ if (oldC.sourceHash !== newC.sourceHash) {
307
+ sourcesChanged.push({ name, sourcePath: newC.sourcePath });
308
+ }
309
+
310
+ const oldFns = new Map(oldC.functions.map(f => [f.signature, f]));
311
+ const newFns = new Map(newC.functions.map(f => [f.signature, f]));
312
+
313
+ // Also index by bare name so we can detect signature changes
314
+ // (same name + different inputs counts as "changed", not add+remove).
315
+ const oldByNameOnly = new Map();
316
+ for (const f of oldC.functions) {
317
+ if (!oldByNameOnly.has(f.name)) oldByNameOnly.set(f.name, []);
318
+ oldByNameOnly.get(f.name).push(f);
319
+ }
320
+ const newByNameOnly = new Map();
321
+ for (const f of newC.functions) {
322
+ if (!newByNameOnly.has(f.name)) newByNameOnly.set(f.name, []);
323
+ newByNameOnly.get(f.name).push(f);
324
+ }
325
+
326
+ const fnAdded = [];
327
+ const fnRemoved = [];
328
+ const fnChanged = [];
329
+ const fnTierChanged = [];
330
+
331
+ for (const [sig, f] of newFns) {
332
+ if (oldFns.has(sig)) {
333
+ const oldF = oldFns.get(sig);
334
+ if (oldF.tier !== f.tier) {
335
+ fnTierChanged.push({ signature: sig, oldTier: oldF.tier, newTier: f.tier });
336
+ }
337
+ continue;
338
+ }
339
+ // Not found by sig — was it a rename of a same-named function?
340
+ if (oldByNameOnly.has(f.name) && !newByNameOnly.get(f.name).every(x => oldFns.has(x.signature))) {
341
+ const oldMatches = oldByNameOnly.get(f.name).filter(x => !newFns.has(x.signature));
342
+ if (oldMatches.length === 1) {
343
+ fnChanged.push({
344
+ name: f.name,
345
+ oldSignature: oldMatches[0].signature,
346
+ newSignature: sig,
347
+ handlerMethodName: `${lcFirst(name)}_${f.name}`,
348
+ });
349
+ continue;
350
+ }
351
+ }
352
+ fnAdded.push({ signature: sig, name: f.name, tier: f.tier, handlerMethodName: `${lcFirst(name)}_${f.name}` });
353
+ }
354
+
355
+ for (const [sig, f] of oldFns) {
356
+ if (newFns.has(sig)) continue;
357
+ // Already reported as signature change?
358
+ if (fnChanged.some(c => c.oldSignature === sig)) continue;
359
+ fnRemoved.push({ signature: sig, name: f.name, handlerMethodName: `${lcFirst(name)}_${f.name}` });
360
+ }
361
+
362
+ if (fnAdded.length || fnRemoved.length || fnChanged.length || fnTierChanged.length) {
363
+ contractsChanged.push({
364
+ name,
365
+ sourcePath: newC.sourcePath,
366
+ functionsAdded: fnAdded,
367
+ functionsRemoved: fnRemoved,
368
+ functionsChanged: fnChanged,
369
+ tierChanged: fnTierChanged,
370
+ });
371
+ }
372
+ }
373
+
374
+ for (const [name, oldC] of oldByName) {
375
+ if (opts.only && name !== opts.only) continue;
376
+ if (!newByName.has(name)) {
377
+ contractsRemoved.push({
378
+ name,
379
+ sourcePath: oldC.sourcePath,
380
+ handlerFile: `${name}Handler.sol`,
381
+ });
382
+ }
383
+ }
384
+
385
+ // Handler file drift — files that existed at snapshot-time and were user-edited since,
386
+ // plus orphaned handlers for removed contracts.
387
+ const handlerOrphans = [];
388
+ const handlerModified = [];
389
+ const handlerMissing = [];
390
+
391
+ const oldHandlers = oldSnap.handlers || {};
392
+ const newHandlers = newSnap.handlers || {};
393
+
394
+ for (const [file, oldHash] of Object.entries(oldHandlers)) {
395
+ const newHash = newHandlers[file];
396
+ if (newHash === undefined) {
397
+ handlerMissing.push({ file });
398
+ continue;
399
+ }
400
+ if (newHash !== oldHash) {
401
+ handlerModified.push({ file });
402
+ }
403
+ }
404
+ for (const rm of contractsRemoved) {
405
+ if (newHandlers[rm.handlerFile] !== undefined) {
406
+ handlerOrphans.push({ file: rm.handlerFile, contract: rm.name });
407
+ }
408
+ }
409
+
410
+ // Property drift: any property whose tagged handler call site references a removed/changed function.
411
+ const propDrift = [];
412
+ const removedHandlerMethods = new Set();
413
+ for (const c of contractsChanged) {
414
+ for (const f of c.functionsRemoved) removedHandlerMethods.add(f.handlerMethodName);
415
+ for (const f of c.functionsChanged) removedHandlerMethods.add(f.handlerMethodName);
416
+ }
417
+ for (const c of contractsRemoved) {
418
+ // Every handler method of a removed contract is dead — we approximate by the
419
+ // contract instance prefix.
420
+ removedHandlerMethods.add(`__contract_removed__:${c.name}`);
421
+ }
422
+
423
+ if (removedHandlerMethods.size > 0) {
424
+ // Scan property function bodies for references to the dead handler methods.
425
+ const scanFiles = [];
426
+ if (fs.existsSync(propertiesSolPath)) scanFiles.push(propertiesSolPath);
427
+ if (fs.existsSync(handlersDir)) {
428
+ for (const entry of fs.readdirSync(handlersDir)) {
429
+ if (entry.endsWith('.sol')) scanFiles.push(path.join(handlersDir, entry));
430
+ }
431
+ }
432
+ for (const file of scanFiles) {
433
+ const content = fs.readFileSync(file, 'utf8');
434
+ for (const dead of removedHandlerMethods) {
435
+ if (dead.startsWith('__contract_removed__:')) continue;
436
+ const re = new RegExp(`\\b${dead}\\s*\\(`);
437
+ if (re.test(content)) {
438
+ propDrift.push({
439
+ file: path.relative(projectRoot, file),
440
+ reference: dead,
441
+ reason: 'handler method signature drifted or function removed',
442
+ });
443
+ }
444
+ }
445
+ }
446
+ }
447
+
448
+ const hasDrift =
449
+ contractsAdded.length +
450
+ contractsRemoved.length +
451
+ contractsChanged.length +
452
+ sourcesChanged.length +
453
+ handlerOrphans.length +
454
+ handlerModified.length +
455
+ handlerMissing.length +
456
+ propDrift.length > 0;
457
+
458
+ return {
459
+ generatedAt: new Date().toISOString(),
460
+ snapshotPath: path.relative(projectRoot, snapshotPath),
461
+ hasDrift,
462
+ contracts: {
463
+ added: contractsAdded,
464
+ removed: contractsRemoved,
465
+ changed: contractsChanged,
466
+ sourcesChanged,
467
+ },
468
+ handlers: {
469
+ orphan: handlerOrphans,
470
+ modified: handlerModified,
471
+ missing: handlerMissing,
472
+ },
473
+ properties: {
474
+ referencingRemoved: propDrift,
475
+ fromSnapshot: oldSnap.properties || [],
476
+ },
477
+ };
478
+ }
479
+
480
+ // ────────────────────── Pretty printer ──────────────────────
481
+
482
+ function printReport(report) {
483
+ const lines = [];
484
+ lines.push('');
485
+ lines.push('╭────────────────── fizz-sync drift report ──────────────────╮');
486
+ if (!report.hasDrift) {
487
+ lines.push('│ ✓ No drift detected. Snapshot is up to date. │');
488
+ lines.push('╰────────────────────────────────────────────────────────────╯');
489
+ console.log(lines.join('\n'));
490
+ return;
491
+ }
492
+
493
+ const cs = report.contracts;
494
+ if (cs.added.length) {
495
+ lines.push('│ + Contracts added (new in selection since snapshot): │');
496
+ for (const c of cs.added) {
497
+ lines.push(`│ ${c.name} (${c.functions.length} functions)`);
498
+ }
499
+ }
500
+ if (cs.removed.length) {
501
+ lines.push('│ - Contracts removed (in snapshot, gone from selection): │');
502
+ for (const c of cs.removed) {
503
+ lines.push(`│ ${c.name} — handler: ${c.handlerFile}`);
504
+ }
505
+ }
506
+ if (cs.changed.length) {
507
+ lines.push('│ ~ Contracts with drift: │');
508
+ for (const c of cs.changed) {
509
+ lines.push(`│ ${c.name}`);
510
+ for (const f of c.functionsAdded) lines.push(`│ + added: ${f.signature} [${f.tier}]`);
511
+ for (const f of c.functionsRemoved) lines.push(`│ - removed: ${f.signature}`);
512
+ for (const f of c.functionsChanged) lines.push(`│ ~ changed: ${f.oldSignature} → ${f.newSignature}`);
513
+ for (const f of c.tierChanged) lines.push(`│ ~ tier: ${f.signature} ${f.oldTier} → ${f.newTier}`);
514
+ }
515
+ }
516
+ if (cs.sourcesChanged.length) {
517
+ lines.push('│ • Source hash changed (semantic review recommended): │');
518
+ for (const c of cs.sourcesChanged) {
519
+ lines.push(`│ ${c.name} (${c.sourcePath})`);
520
+ }
521
+ }
522
+ const hs = report.handlers;
523
+ if (hs.orphan.length) {
524
+ lines.push('│ ✗ Orphan handlers (contract removed): │');
525
+ for (const h of hs.orphan) lines.push(`│ ${h.file}`);
526
+ }
527
+ if (hs.modified.length) {
528
+ lines.push('│ • Handler files modified since snapshot (user edits): │');
529
+ for (const h of hs.modified) lines.push(`│ ${h.file}`);
530
+ }
531
+ if (hs.missing.length) {
532
+ lines.push('│ ✗ Handler files missing (deleted since snapshot): │');
533
+ for (const h of hs.missing) lines.push(`│ ${h.file}`);
534
+ }
535
+ const pd = report.properties.referencingRemoved;
536
+ if (pd.length) {
537
+ lines.push('│ ✗ Property/handler files referencing removed functions: │');
538
+ for (const p of pd) lines.push(`│ ${p.file} → ${p.reference}`);
539
+ }
540
+ lines.push('╰────────────────────────────────────────────────────────────╯');
541
+ console.log(lines.join('\n'));
542
+ }
543
+
544
+ // ────────────────────── Handler apply ──────────────────────
545
+
546
+ /**
547
+ * Spawn generate_handlers.js to regenerate handlers for drifted contracts only.
548
+ *
549
+ * Scoped selection is built from the FRESH contracts.json (not the stale selection file),
550
+ * so newly-added functions are included in the regenerated handler. Tier is preserved from
551
+ * the old selection for unchanged functions and defaults to "primary" for added ones.
552
+ *
553
+ * Also updates entry-point-selection.json in place so future dry-runs diff against a
554
+ * consistent scope (otherwise the newly-added functions would show up as drift forever).
555
+ */
556
+ function applyHandlerRegen(report) {
557
+ const { execSync } = require('child_process');
558
+
559
+ const oldSelection = loadJson(selectionPath);
560
+ if (!oldSelection) fail(2, 'missing entry-point-selection.json — cannot regenerate handlers.');
561
+ const contractsJson = loadJson(contractsJsonPath);
562
+ if (!contractsJson) fail(2, 'missing contracts.json — run extract_abis.js first.');
563
+ const contractsJsonByName = new Map(contractsJson.map(c => [c.name, c]));
564
+
565
+ const driftedNames = new Set([
566
+ ...report.contracts.added.map(c => c.name),
567
+ ...report.contracts.changed.map(c => c.name),
568
+ ]);
569
+ if (opts.only) {
570
+ for (const n of [...driftedNames]) if (n !== opts.only) driftedNames.delete(n);
571
+ }
572
+ if (driftedNames.size === 0) {
573
+ console.log('fizz-sync: no added/changed contracts to regenerate — nothing to do.');
574
+ return { regenerated: [], backedUp: [] };
575
+ }
576
+
577
+ // Back up existing handler files first.
578
+ const backedUp = [];
579
+ for (const name of driftedNames) {
580
+ const file = path.join(handlersDir, `${name}Handler.sol`);
581
+ if (fs.existsSync(file)) {
582
+ const bak = `${file}.pre-sync.bak`;
583
+ fs.copyFileSync(file, bak);
584
+ backedUp.push(path.relative(projectRoot, bak));
585
+ }
586
+ }
587
+
588
+ // Build scoped selection from fresh contracts.json, preserving tiers from the old selection.
589
+ // Unchanged functions keep their tier. Added functions default to "primary".
590
+ // Removed functions are dropped entirely.
591
+ const oldSelectionByName = new Map(oldSelection.map(c => [c.name, c]));
592
+ const scopedSelection = [];
593
+ for (const name of driftedNames) {
594
+ const fresh = contractsJsonByName.get(name);
595
+ if (!fresh) continue; // contract disappeared — handled as "removed" elsewhere
596
+ const oldEntry = oldSelectionByName.get(name);
597
+ const oldTierBySig = new Map();
598
+ if (oldEntry) {
599
+ for (const f of (oldEntry.functions || [])) {
600
+ oldTierBySig.set(canonicalSignature(f), f.tier || 'primary');
601
+ }
602
+ }
603
+ scopedSelection.push({
604
+ ...fresh,
605
+ functions: (fresh.functions || []).map(f => ({
606
+ ...f,
607
+ tier: oldTierBySig.get(canonicalSignature(f)) || 'primary',
608
+ })),
609
+ });
610
+ }
611
+ const scopedPath = path.join(metaDir, 'sync-scoped-selection.json');
612
+ fs.writeFileSync(scopedPath, JSON.stringify(scopedSelection, null, 2));
613
+
614
+ // Also update the main selection file: replace drifted entries with the fresh ones,
615
+ // keep untouched contracts as-is. This keeps the authoritative selection in sync.
616
+ const newSelection = oldSelection.map(c => {
617
+ if (driftedNames.has(c.name)) {
618
+ const fresh = scopedSelection.find(s => s.name === c.name);
619
+ return fresh || c;
620
+ }
621
+ return c;
622
+ });
623
+ // Also append entries for contracts that were added in drift but were missing from old selection.
624
+ for (const fresh of scopedSelection) {
625
+ if (!oldSelectionByName.has(fresh.name)) newSelection.push(fresh);
626
+ }
627
+ fs.writeFileSync(selectionPath, JSON.stringify(newSelection, null, 2));
628
+
629
+ const genScript = path.join(__dirname, 'generate_handlers.js');
630
+ const cmd = `node "${genScript}" "${projectRoot}" --suite-dir "${opts.suiteRelDir}" --meta-dir "${opts.metaRelDir}" --selection "${scopedPath}" --force`;
631
+ console.log(`fizz-sync: running ${cmd}`);
632
+ execSync(cmd, { stdio: 'inherit' });
633
+ // Clean up scoped selection artifact.
634
+ try { fs.unlinkSync(scopedPath); } catch (_) {}
635
+
636
+ return { regenerated: [...driftedNames], backedUp };
637
+ }
638
+
639
+ // ────────────────────── Main ──────────────────────
640
+
641
+ function main() {
642
+ if (!fs.existsSync(projectRoot)) fail(2, `project root not found: ${projectRoot}`);
643
+ if (!fs.existsSync(metaDir)) fail(2, `meta dir not found: ${opts.metaRelDir} — has the Fizz skill been run?`);
644
+
645
+ // Modes that do not require a stored snapshot.
646
+ if (opts.init) {
647
+ if (fs.existsSync(snapshotPath) && !opts.force) {
648
+ fail(2, `snapshot already exists at ${path.relative(projectRoot, snapshotPath)} — use --force to overwrite, or use --refresh-snapshot.`);
649
+ }
650
+ const snap = buildSnapshot();
651
+ fs.writeFileSync(snapshotPath, JSON.stringify(snap, null, 2));
652
+ console.log(`fizz-sync: wrote initial snapshot to ${path.relative(projectRoot, snapshotPath)} (${snap.contracts.length} contracts, ${snap.properties.length} properties).`);
653
+ return;
654
+ }
655
+
656
+ const oldSnap = loadJson(snapshotPath);
657
+ if (!oldSnap) fail(2, `no snapshot at ${path.relative(projectRoot, snapshotPath)} — run: node fizz_sync.js ${opts.metaRelDir === '.hex/fuzz/fizz_data' ? '.' : projectRoot} --init`);
658
+
659
+ if (opts.refreshSnapshot) {
660
+ const snap = buildSnapshot();
661
+ fs.writeFileSync(snapshotPath, JSON.stringify(snap, null, 2));
662
+ console.log(`fizz-sync: refreshed snapshot at ${path.relative(projectRoot, snapshotPath)} (${snap.contracts.length} contracts, ${snap.properties.length} properties).`);
663
+ return;
664
+ }
665
+
666
+ const newSnap = buildSnapshot();
667
+ const report = diffSnapshots(oldSnap, newSnap);
668
+
669
+ // Always write the machine-readable report for skill consumption.
670
+ fs.mkdirSync(path.dirname(reportPath), { recursive: true });
671
+ fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
672
+
673
+ if (opts.applyHandlers) {
674
+ const result = applyHandlerRegen(report);
675
+ console.log(`fizz-sync: regenerated ${result.regenerated.length} handler(s); backups: ${result.backedUp.length}`);
676
+ if (result.backedUp.length) {
677
+ for (const b of result.backedUp) console.log(` bak: ${b}`);
678
+ }
679
+ return;
680
+ }
681
+
682
+ printReport(report);
683
+ console.log(`\nMachine-readable report: ${path.relative(projectRoot, reportPath)}`);
684
+ if (report.hasDrift) process.exit(1);
685
+ }
686
+
687
+ try {
688
+ main();
689
+ } catch (e) {
690
+ console.error(`fizz-sync: unexpected error: ${e.stack || e.message}`);
691
+ process.exit(3);
692
+ }