hex-audit 0.6.3 → 0.7.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 (332) hide show
  1. package/DEMO.md +13 -0
  2. package/README.md +60 -6
  3. package/dashboard/.next/BUILD_ID +1 -1
  4. package/dashboard/.next/app-build-manifest.json +100 -89
  5. package/dashboard/.next/app-path-routes-manifest.json +1 -1
  6. package/dashboard/.next/build-manifest.json +14 -14
  7. package/dashboard/.next/next-minimal-server.js.nft.json +1 -1
  8. package/dashboard/.next/next-server.js.nft.json +1 -1
  9. package/dashboard/.next/prerender-manifest.js +1 -1
  10. package/dashboard/.next/prerender-manifest.json +1 -1
  11. package/dashboard/.next/react-loadable-manifest.json +118 -118
  12. package/dashboard/.next/routes-manifest.json +1 -1
  13. package/dashboard/.next/server/app/_not-found/page.js +1 -1
  14. package/dashboard/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  15. package/dashboard/.next/server/app/access/page.js +1 -1
  16. package/dashboard/.next/server/app/access/page.js.nft.json +1 -1
  17. package/dashboard/.next/server/app/access/page_client-reference-manifest.js +1 -1
  18. package/dashboard/.next/server/app/calls/page.js +1 -1
  19. package/dashboard/.next/server/app/calls/page.js.nft.json +1 -1
  20. package/dashboard/.next/server/app/calls/page_client-reference-manifest.js +1 -1
  21. package/dashboard/.next/server/app/changes/page.js +20 -0
  22. package/dashboard/.next/server/app/changes/page.js.nft.json +1 -0
  23. package/dashboard/.next/server/app/changes/page_client-reference-manifest.js +1 -0
  24. package/dashboard/.next/server/app/conformance/page.js +1 -1
  25. package/dashboard/.next/server/app/conformance/page_client-reference-manifest.js +1 -1
  26. package/dashboard/.next/server/app/diagram/page.js +1 -1
  27. package/dashboard/.next/server/app/diagram/page_client-reference-manifest.js +1 -1
  28. package/dashboard/.next/server/app/flows/page.js +1 -1
  29. package/dashboard/.next/server/app/flows/page_client-reference-manifest.js +1 -1
  30. package/dashboard/.next/server/app/functions/page.js +1 -1
  31. package/dashboard/.next/server/app/functions/page.js.nft.json +1 -1
  32. package/dashboard/.next/server/app/functions/page_client-reference-manifest.js +1 -1
  33. package/dashboard/.next/server/app/issues/page.js +6 -6
  34. package/dashboard/.next/server/app/issues/page_client-reference-manifest.js +1 -1
  35. package/dashboard/.next/server/app/notes/page.js +3 -3
  36. package/dashboard/.next/server/app/notes/page_client-reference-manifest.js +1 -1
  37. package/dashboard/.next/server/app/page.js +2 -2
  38. package/dashboard/.next/server/app/page_client-reference-manifest.js +1 -1
  39. package/dashboard/.next/server/app/progress/page.js +1 -1
  40. package/dashboard/.next/server/app/progress/page_client-reference-manifest.js +1 -1
  41. package/dashboard/.next/server/app/report/page.js +1 -1
  42. package/dashboard/.next/server/app/report/page_client-reference-manifest.js +1 -1
  43. package/dashboard/.next/server/app/stats/page.js +1 -1
  44. package/dashboard/.next/server/app/stats/page.js.nft.json +1 -1
  45. package/dashboard/.next/server/app/stats/page_client-reference-manifest.js +1 -1
  46. package/dashboard/.next/server/app-paths-manifest.json +8 -7
  47. package/dashboard/.next/server/chunks/42.js +1 -1
  48. package/dashboard/.next/server/chunks/530.js +1 -1
  49. package/dashboard/.next/server/chunks/649.js +1 -1
  50. package/dashboard/.next/server/chunks/939.js +1 -0
  51. package/dashboard/.next/server/functions-config-manifest.json +1 -1
  52. package/dashboard/.next/server/middleware-build-manifest.js +1 -1
  53. package/dashboard/.next/server/middleware-react-loadable-manifest.js +1 -1
  54. package/dashboard/.next/server/next-font-manifest.js +1 -1
  55. package/dashboard/.next/server/next-font-manifest.json +1 -1
  56. package/dashboard/.next/server/pages/500.html +1 -1
  57. package/dashboard/.next/server/pages-manifest.json +1 -1
  58. package/dashboard/.next/server/server-reference-manifest.js +1 -1
  59. package/dashboard/.next/server/server-reference-manifest.json +1 -1
  60. package/dashboard/.next/static/chunks/{1.8868c4d131111be7.js → 1.a666047ea18866fb.js} +2 -2
  61. package/dashboard/.next/static/chunks/1015.39008440ab15e196.js +226 -0
  62. package/dashboard/.next/static/chunks/1151.497fa63c5b9eba52.js +1 -0
  63. package/dashboard/.next/static/chunks/1199.f30cbfa151e56076.js +148 -0
  64. package/dashboard/.next/static/chunks/1375.64c20f1064a38a10.js +43 -0
  65. package/dashboard/.next/static/chunks/1799.8dc56017eede704e.js +9 -0
  66. package/dashboard/.next/static/chunks/20154aaa.e87f69fe10483f8b.js +1 -0
  67. package/dashboard/.next/static/chunks/2077.5603adeb156c7d10.js +1 -0
  68. package/dashboard/.next/static/chunks/2166.9f6592b9399eadd8.js +215 -0
  69. package/dashboard/.next/static/chunks/{2291.85a70cb0c228157b.js → 2291.584b42ccad079b99.js} +1 -1
  70. package/dashboard/.next/static/chunks/2325.7decd077edfb81d9.js +24 -0
  71. package/dashboard/.next/static/chunks/{2383.62c035a052272594.js → 2383.ca81f980ce6528a4.js} +1 -1
  72. package/dashboard/.next/static/chunks/2694.7dfa5fef28851903.js +1 -0
  73. package/dashboard/.next/static/chunks/275.38af87389c1d33d7.js +34 -0
  74. package/dashboard/.next/static/chunks/2781.fba46046dbf2693b.js +93 -0
  75. package/dashboard/.next/static/chunks/2805.35b4cd8a94978aaf.js +1 -0
  76. package/dashboard/.next/static/chunks/{2879.51436986f93981d9.js → 2879.74d65e33e60505ee.js} +1 -1
  77. package/dashboard/.next/static/chunks/3017.4a5312e3c20a173f.js +1 -0
  78. package/dashboard/.next/static/chunks/3205-47d09be47325709f.js +2 -0
  79. package/dashboard/.next/static/chunks/3236.eac9855778a54518.js +1 -0
  80. package/dashboard/.next/static/chunks/3480.853a445a3784642a.js +1 -0
  81. package/dashboard/.next/static/chunks/3626.c5dacee11c8fbc4d.js +64 -0
  82. package/dashboard/.next/static/chunks/3865.5e6d2d501380e03b.js +90 -0
  83. package/dashboard/.next/static/chunks/3887.073cece834a4eae2.js +1 -0
  84. package/dashboard/.next/static/chunks/3924-88d849290406ee6f.js +1 -0
  85. package/dashboard/.next/static/chunks/3948.74201b9f0672234d.js +287 -0
  86. package/dashboard/.next/static/chunks/4728.e69372b5e5b61b8d.js +24 -0
  87. package/dashboard/.next/static/chunks/4787.821a6ad2816cdc60.js +23 -0
  88. package/dashboard/.next/static/chunks/5060.47402abc10273699.js +1 -0
  89. package/dashboard/.next/static/chunks/5284.334c7c0587d5d47a.js +1 -0
  90. package/dashboard/.next/static/chunks/5715.e16b1230cc4b4652.js +115 -0
  91. package/dashboard/.next/static/chunks/6014.641a2c53ba506241.js +104 -0
  92. package/dashboard/.next/static/chunks/6024.f099c15f75aa6722.js +79 -0
  93. package/dashboard/.next/static/chunks/{6052.033a6f1013c666eb.js → 6052.3001318955e5b36c.js} +2 -2
  94. package/dashboard/.next/static/chunks/6177.a01cf0a03734cf09.js +1 -0
  95. package/dashboard/.next/static/chunks/618f8807-69fc7bb97c2b87c1.js +1 -0
  96. package/dashboard/.next/static/chunks/6434.f560693253fea8de.js +1 -0
  97. package/dashboard/.next/static/chunks/6468.976d89ba133185f1.js +77 -0
  98. package/dashboard/.next/static/chunks/6540.6f3c4f2c6877ed89.js +1 -0
  99. package/dashboard/.next/static/chunks/6549.e3c4c1766cff1449.js +1 -0
  100. package/dashboard/.next/static/chunks/690f35d0.ab57e02a04c7e9e0.js +119 -0
  101. package/dashboard/.next/static/chunks/{7020.831e16e26d91abc1.js → 7020.b718b1b9cea36e60.js} +2 -2
  102. package/dashboard/.next/static/chunks/7397-37adf76cad543297.js +1 -0
  103. package/dashboard/.next/static/chunks/7498-d1814d68ba346d13.js +1 -0
  104. package/dashboard/.next/static/chunks/7802.f0134c792fccd382.js +80 -0
  105. package/dashboard/.next/static/chunks/7973.e34032723285a616.js +79 -0
  106. package/dashboard/.next/static/chunks/{7a04ac15.1c6cea694301806c.js → 7a04ac15.e49de94ece135900.js} +1 -1
  107. package/dashboard/.next/static/chunks/8216.1138ec313908a708.js +5 -0
  108. package/dashboard/.next/static/chunks/{8233babc.780b8e272eda298f.js → 8233babc.09872dcb3ff44064.js} +1 -1
  109. package/dashboard/.next/static/chunks/{8452-e616b5f43bceb95f.js → 8452-6d77fa353402422c.js} +2 -2
  110. package/dashboard/.next/static/chunks/8645.98a43217d406500e.js +1 -0
  111. package/dashboard/.next/static/chunks/8928-fc049decdfbdaa6d.js +1 -0
  112. package/dashboard/.next/static/chunks/9173.a7160f7d0b685fa7.js +1 -0
  113. package/dashboard/.next/static/chunks/9178.998a8c49996de8e4.js +1 -0
  114. package/dashboard/.next/static/chunks/9466.a914ce0de7d3bb84.js +1 -0
  115. package/dashboard/.next/static/chunks/9660.b1c8deae2c449d14.js +1 -0
  116. package/dashboard/.next/static/chunks/{9717.6cf5c8c89403b651.js → 9717.bba51b78ebd8fd29.js} +1 -1
  117. package/dashboard/.next/static/chunks/9720.d63a0696776bcc60.js +1 -0
  118. package/dashboard/.next/static/chunks/9811.2993c4cbd28e8d1b.js +1 -0
  119. package/dashboard/.next/static/chunks/app/_not-found/page-c299b935209993df.js +1 -0
  120. package/dashboard/.next/static/chunks/app/access/page-f8403cbdfc1c176b.js +1 -0
  121. package/dashboard/.next/static/chunks/app/calls/page-41f20d01e12382ef.js +1 -0
  122. package/dashboard/.next/static/chunks/app/changes/page-82bf8b6f48cdc506.js +1 -0
  123. package/dashboard/.next/static/chunks/app/conformance/page-6bb8e3b372e38098.js +1 -0
  124. package/dashboard/.next/static/chunks/app/diagram/page-469cd7a6f183efba.js +1 -0
  125. package/dashboard/.next/static/chunks/app/flows/page-7872dd9141ba0b07.js +1 -0
  126. package/dashboard/.next/static/chunks/app/functions/page-5847bcb77a8e7edf.js +1 -0
  127. package/dashboard/.next/static/chunks/app/issues/page-1ffc64fe40439002.js +1 -0
  128. package/dashboard/.next/static/chunks/app/layout-6c6d2a0fd7c989d3.js +1 -0
  129. package/dashboard/.next/static/chunks/app/notes/page-8bcaa52304500098.js +1 -0
  130. package/dashboard/.next/static/chunks/app/page-d63800ddb072b77b.js +1 -0
  131. package/dashboard/.next/static/chunks/app/progress/page-e26554f655a62eef.js +1 -0
  132. package/dashboard/.next/static/chunks/app/report/page-18c04fb2f193b020.js +1 -0
  133. package/dashboard/.next/static/chunks/app/stats/page-67ff59fd69bed651.js +1 -0
  134. package/dashboard/.next/static/chunks/{b7167ec1.7afbab6f609e3326.js → b7167ec1.708a3d61698ca2e6.js} +1 -1
  135. package/dashboard/.next/static/chunks/c7ea4e13.3073a2f551aa5ac8.js +1 -0
  136. package/dashboard/.next/static/chunks/framework-a117d81bf1b91f9a.js +33 -0
  137. package/dashboard/.next/static/chunks/main-app-5662c64b50ba40d1.js +1 -0
  138. package/dashboard/.next/static/chunks/main-cc3a889b25e9aa68.js +1 -0
  139. package/dashboard/.next/static/chunks/pages/_app-308fef0bf9b328f2.js +1 -0
  140. package/dashboard/.next/static/chunks/pages/_error-c91b5eabd7ac9382.js +1 -0
  141. package/dashboard/.next/static/chunks/webpack-4de0e79bdd6a0418.js +1 -0
  142. package/dashboard/.next/static/css/1112cea2260b3926.css +3 -0
  143. package/dashboard/.next/static/media/39f449c8730c2295-s.woff2 +0 -0
  144. package/dashboard/.next/static/media/86f65e8637cc94e3-s.woff2 +0 -0
  145. package/dashboard/.next/static/media/8760c4774489e4ba-s.p.woff2 +0 -0
  146. package/dashboard/.next/static/media/9fabb9c65b63d798-s.woff2 +0 -0
  147. package/dashboard/.next/static/media/a1f87af3f44fadbb-s.woff2 +0 -0
  148. package/dashboard/.next/static/media/f9e1dbe9676d9bb6-s.woff2 +0 -0
  149. package/dashboard/.next/static/z5XBev05xDAkDrxWxAjNu/_buildManifest.js +1 -0
  150. package/dashboard/app/changes/ChangesClient.tsx +464 -0
  151. package/dashboard/app/changes/page.tsx +36 -0
  152. package/dashboard/app/issues/IssuesBoardClient.tsx +11 -1
  153. package/dashboard/app/layout.tsx +13 -1
  154. package/dashboard/app/notes/NotesClient.tsx +5 -0
  155. package/dashboard/app/page.tsx +42 -0
  156. package/dashboard/components/DashboardLayout.tsx +43 -18
  157. package/dist/analysis/code-model.d.ts +113 -0
  158. package/dist/analysis/code-model.d.ts.map +1 -0
  159. package/dist/analysis/code-model.js +529 -0
  160. package/dist/analysis/code-model.js.map +1 -0
  161. package/dist/analysis/diff.d.ts +64 -0
  162. package/dist/analysis/diff.d.ts.map +1 -0
  163. package/dist/analysis/diff.js +644 -0
  164. package/dist/analysis/diff.js.map +1 -0
  165. package/dist/commands/analyze.d.ts.map +1 -1
  166. package/dist/commands/analyze.js +18 -2
  167. package/dist/commands/analyze.js.map +1 -1
  168. package/dist/commands/diff.d.ts +26 -0
  169. package/dist/commands/diff.d.ts.map +1 -0
  170. package/dist/commands/diff.js +218 -0
  171. package/dist/commands/diff.js.map +1 -0
  172. package/dist/commands/init.d.ts.map +1 -1
  173. package/dist/commands/init.js +66 -6
  174. package/dist/commands/init.js.map +1 -1
  175. package/dist/commands/issue.d.ts.map +1 -1
  176. package/dist/commands/issue.js +4 -4
  177. package/dist/commands/issue.js.map +1 -1
  178. package/dist/commands/note.d.ts.map +1 -1
  179. package/dist/commands/note.js +30 -2
  180. package/dist/commands/note.js.map +1 -1
  181. package/dist/core/config.d.ts +9 -0
  182. package/dist/core/config.d.ts.map +1 -1
  183. package/dist/core/config.js +18 -0
  184. package/dist/core/config.js.map +1 -1
  185. package/dist/core/errors.d.ts +1 -1
  186. package/dist/core/errors.d.ts.map +1 -1
  187. package/dist/core/errors.js +16 -0
  188. package/dist/core/errors.js.map +1 -1
  189. package/dist/core/git.d.ts +24 -0
  190. package/dist/core/git.d.ts.map +1 -0
  191. package/dist/core/git.js +115 -0
  192. package/dist/core/git.js.map +1 -0
  193. package/dist/core/issues.d.ts +4 -2
  194. package/dist/core/issues.d.ts.map +1 -1
  195. package/dist/core/issues.js +67 -28
  196. package/dist/core/issues.js.map +1 -1
  197. package/dist/core/notes.d.ts +25 -1
  198. package/dist/core/notes.d.ts.map +1 -1
  199. package/dist/core/notes.js +34 -2
  200. package/dist/core/notes.js.map +1 -1
  201. package/dist/core/schema.d.ts +750 -53
  202. package/dist/core/schema.d.ts.map +1 -1
  203. package/dist/core/schema.js +153 -1
  204. package/dist/core/schema.js.map +1 -1
  205. package/dist/core/skills.d.ts +10 -4
  206. package/dist/core/skills.d.ts.map +1 -1
  207. package/dist/core/skills.js +100 -37
  208. package/dist/core/skills.js.map +1 -1
  209. package/dist/index.js +2 -0
  210. package/dist/index.js.map +1 -1
  211. package/dist/skills/ai-audit/LICENSE +21 -0
  212. package/dist/skills/ai-audit/SKILL.md +476 -0
  213. package/dist/skills/ai-audit/UPSTREAM.md +46 -0
  214. package/dist/skills/ai-audit/references/agent-prompts.md +133 -0
  215. package/dist/skills/ai-audit/references/assemble.sh +357 -0
  216. package/dist/skills/ai-audit/references/dedup-and-assembly.md +420 -0
  217. package/dist/skills/ai-audit/references/hacking-agents/access-control-agent.md +27 -0
  218. package/dist/skills/ai-audit/references/hacking-agents/asymmetry-agent.md +77 -0
  219. package/dist/skills/ai-audit/references/hacking-agents/boundary-agent.md +79 -0
  220. package/dist/skills/ai-audit/references/hacking-agents/economic-security-agent.md +35 -0
  221. package/dist/skills/ai-audit/references/hacking-agents/execution-trace-agent.md +33 -0
  222. package/dist/skills/ai-audit/references/hacking-agents/first-principles-agent.md +36 -0
  223. package/dist/skills/ai-audit/references/hacking-agents/flow-gap-agent.md +49 -0
  224. package/dist/skills/ai-audit/references/hacking-agents/invariant-agent.md +44 -0
  225. package/dist/skills/ai-audit/references/hacking-agents/math-precision-agent.md +48 -0
  226. package/dist/skills/ai-audit/references/hacking-agents/numerical-gap-agent.md +46 -0
  227. package/dist/skills/ai-audit/references/hacking-agents/periphery-agent.md +27 -0
  228. package/dist/skills/ai-audit/references/hacking-agents/shared-rules.md +61 -0
  229. package/dist/skills/ai-audit/references/hacking-agents/trust-gap-agent.md +40 -0
  230. package/dist/skills/ai-audit/references/hex-board.md +223 -0
  231. package/dist/skills/ai-audit/references/hex-export.sh +71 -0
  232. package/dist/skills/ai-audit/references/judging.md +75 -0
  233. package/dist/skills/ai-audit/references/report-formatting.md +385 -0
  234. package/dist/skills/ai-audit/references/report-language.md +151 -0
  235. package/dist/skills/ai-audit/references/senior-auditor-sop.md +63 -0
  236. package/dist/skills/generate-report.md +7 -3
  237. package/dist/skills/init-audit.md +104 -2
  238. package/dist/skills/validate-all-findings.md +1 -1
  239. package/dist/skills/validate-finding.md +8 -4
  240. package/dist/types/index.d.ts +7 -1
  241. package/dist/types/index.d.ts.map +1 -1
  242. package/package.json +2 -1
  243. package/dashboard/.next/server/app/_not-found.html +0 -1
  244. package/dashboard/.next/server/app/_not-found.meta +0 -6
  245. package/dashboard/.next/server/app/_not-found.rsc +0 -12
  246. package/dashboard/.next/server/app/diagram.html +0 -1
  247. package/dashboard/.next/server/app/diagram.meta +0 -5
  248. package/dashboard/.next/server/app/diagram.rsc +0 -10
  249. package/dashboard/.next/server/app/flows.html +0 -1
  250. package/dashboard/.next/server/app/flows.meta +0 -5
  251. package/dashboard/.next/server/app/flows.rsc +0 -10
  252. package/dashboard/.next/server/chunks/44.js +0 -1
  253. package/dashboard/.next/server/pages/404.html +0 -1
  254. package/dashboard/.next/static/AyrRAwu4mazjp8iX4Yjbb/_buildManifest.js +0 -1
  255. package/dashboard/.next/static/chunks/1015.3ab89142e5f44120.js +0 -226
  256. package/dashboard/.next/static/chunks/1151.b6ef5c786e587ed7.js +0 -1
  257. package/dashboard/.next/static/chunks/1199.a072929dbec89965.js +0 -148
  258. package/dashboard/.next/static/chunks/1375.d4d2f7fc9c9c2ff1.js +0 -43
  259. package/dashboard/.next/static/chunks/1799.83206776abbbf1fc.js +0 -9
  260. package/dashboard/.next/static/chunks/20154aaa.ccc347c04ab5ad4a.js +0 -1
  261. package/dashboard/.next/static/chunks/2077.7011a570c87aa7b1.js +0 -1
  262. package/dashboard/.next/static/chunks/2166.cc0fabeb2e89b840.js +0 -215
  263. package/dashboard/.next/static/chunks/2325.2c55cb9a6282ae3f.js +0 -24
  264. package/dashboard/.next/static/chunks/2694.92bbe252063e4786.js +0 -1
  265. package/dashboard/.next/static/chunks/275.0d761aa0e5b030b8.js +0 -34
  266. package/dashboard/.next/static/chunks/2781.8536b644a480aede.js +0 -93
  267. package/dashboard/.next/static/chunks/2805.a8d71d2f92bb6ba9.js +0 -1
  268. package/dashboard/.next/static/chunks/3017.e55781da9dd32d37.js +0 -1
  269. package/dashboard/.next/static/chunks/3205-728c260fbb4f89c6.js +0 -2
  270. package/dashboard/.next/static/chunks/3236.dff44fa7961d9d5e.js +0 -1
  271. package/dashboard/.next/static/chunks/3480.9451396ad7ee7613.js +0 -1
  272. package/dashboard/.next/static/chunks/3626.ba1cf848a9f2e93d.js +0 -64
  273. package/dashboard/.next/static/chunks/3865.a432a7910a2f5281.js +0 -90
  274. package/dashboard/.next/static/chunks/3887.c22cbfa3f095826c.js +0 -1
  275. package/dashboard/.next/static/chunks/3924-4a05057b5450abe1.js +0 -1
  276. package/dashboard/.next/static/chunks/3948.fe3c7d758568d98b.js +0 -287
  277. package/dashboard/.next/static/chunks/4728.07d87640a3e7dd0d.js +0 -24
  278. package/dashboard/.next/static/chunks/4787.4e5f6a0e0e40a583.js +0 -23
  279. package/dashboard/.next/static/chunks/5060.baca226af35ed016.js +0 -1
  280. package/dashboard/.next/static/chunks/5284.f949ac90c95ff143.js +0 -1
  281. package/dashboard/.next/static/chunks/5715.1524bcc9591e180b.js +0 -115
  282. package/dashboard/.next/static/chunks/6014.700ae4969b64d728.js +0 -104
  283. package/dashboard/.next/static/chunks/6024.4d23707c65514679.js +0 -79
  284. package/dashboard/.next/static/chunks/6177.e5c84cfc4c0dea51.js +0 -1
  285. package/dashboard/.next/static/chunks/618f8807-547c5614e1318888.js +0 -1
  286. package/dashboard/.next/static/chunks/6434.9481771c33195d29.js +0 -1
  287. package/dashboard/.next/static/chunks/6468.f4a3e8fa6accc6da.js +0 -77
  288. package/dashboard/.next/static/chunks/6540.86f367d7390738d4.js +0 -1
  289. package/dashboard/.next/static/chunks/6549.4d0d472df96429a5.js +0 -1
  290. package/dashboard/.next/static/chunks/690f35d0.3e40ca0930e4bbdb.js +0 -119
  291. package/dashboard/.next/static/chunks/7397-f6f4dddc7a20bfc1.js +0 -1
  292. package/dashboard/.next/static/chunks/7498-ea83cb9e30ec5491.js +0 -1
  293. package/dashboard/.next/static/chunks/7802.89aec9ee11680850.js +0 -80
  294. package/dashboard/.next/static/chunks/7973.8b526f589aca5d84.js +0 -79
  295. package/dashboard/.next/static/chunks/8216.1bead812eb126c35.js +0 -5
  296. package/dashboard/.next/static/chunks/8645.a5ca76f11edc9633.js +0 -1
  297. package/dashboard/.next/static/chunks/8928-60c623ee89d2e6ad.js +0 -1
  298. package/dashboard/.next/static/chunks/9173.2486ffca31131ec5.js +0 -1
  299. package/dashboard/.next/static/chunks/9178.469341860bf8caec.js +0 -1
  300. package/dashboard/.next/static/chunks/9466.e904660d38c03eea.js +0 -1
  301. package/dashboard/.next/static/chunks/9660.84a85811380c5f33.js +0 -1
  302. package/dashboard/.next/static/chunks/9720.67d4d0d15a96d691.js +0 -1
  303. package/dashboard/.next/static/chunks/9811.65e1d080c2d256af.js +0 -1
  304. package/dashboard/.next/static/chunks/app/_not-found/page-548ab392d0e890d1.js +0 -1
  305. package/dashboard/.next/static/chunks/app/access/page-3668562e7464eb5d.js +0 -1
  306. package/dashboard/.next/static/chunks/app/calls/page-d07f5b8664cc8303.js +0 -1
  307. package/dashboard/.next/static/chunks/app/conformance/page-2fede1d7b5f9265d.js +0 -1
  308. package/dashboard/.next/static/chunks/app/diagram/page-b228444265546495.js +0 -1
  309. package/dashboard/.next/static/chunks/app/flows/page-fa67783d8cd79cab.js +0 -1
  310. package/dashboard/.next/static/chunks/app/functions/page-686f0296639133a3.js +0 -1
  311. package/dashboard/.next/static/chunks/app/issues/page-98a298e1c1045c91.js +0 -1
  312. package/dashboard/.next/static/chunks/app/layout-49f2496c7637d931.js +0 -1
  313. package/dashboard/.next/static/chunks/app/notes/page-17af94c67ba79c00.js +0 -1
  314. package/dashboard/.next/static/chunks/app/page-24bac51dfe007c66.js +0 -1
  315. package/dashboard/.next/static/chunks/app/progress/page-427fb18da3afb061.js +0 -1
  316. package/dashboard/.next/static/chunks/app/report/page-796f2f4a8a5ac8f7.js +0 -1
  317. package/dashboard/.next/static/chunks/app/stats/page-db81f600a086f252.js +0 -1
  318. package/dashboard/.next/static/chunks/c7ea4e13.5effeeba6618bdb7.js +0 -1
  319. package/dashboard/.next/static/chunks/framework-cf6f5bdca6ba0184.js +0 -33
  320. package/dashboard/.next/static/chunks/main-63ff429eab01f5bf.js +0 -1
  321. package/dashboard/.next/static/chunks/main-app-9d2f6b8a47133e9b.js +0 -1
  322. package/dashboard/.next/static/chunks/pages/_app-6ad2b50514c24ab4.js +0 -1
  323. package/dashboard/.next/static/chunks/pages/_error-d5eb11c5c4626811.js +0 -1
  324. package/dashboard/.next/static/chunks/webpack-08869f2b18792e51.js +0 -1
  325. package/dashboard/.next/static/css/4a38d8f6f6b7a68f.css +0 -3
  326. package/dashboard/.next/static/media/438aa629764e75f3-s.woff2 +0 -0
  327. package/dashboard/.next/static/media/4c9affa5bc8f420e-s.p.woff2 +0 -0
  328. package/dashboard/.next/static/media/51251f8b9793cdb3-s.woff2 +0 -0
  329. package/dashboard/.next/static/media/875ae681bfde4580-s.woff2 +0 -0
  330. package/dashboard/.next/static/media/cc978ac5ee68c2b6-s.woff2 +0 -0
  331. package/dashboard/.next/static/media/e857b654a2caa584-s.woff2 +0 -0
  332. /package/dashboard/.next/static/{AyrRAwu4mazjp8iX4Yjbb → z5XBev05xDAkDrxWxAjNu}/_ssgManifest.js +0 -0
@@ -0,0 +1,420 @@
1
+ # Dedup, record and assembly — Turn 4 and Turn 5
2
+
3
+ This file holds the two turns that run **after the 12 agents return**: Turn 4
4
+ deduplicates, gates, tags and records each pass, and Turn 5 assembles and prints
5
+ the one report. `SKILL.md` keeps the name of each turn and the condition that
6
+ decides whether it runs; the procedure is here.
7
+
8
+ The orchestrator reads this file **once, in Turn 3b**, while it waits for the
9
+ agents. It stays in context for the later passes of a loop.
10
+
11
+ Both turns use `{stamp}`, `{bundle_dir}`, `{passes}`, `{resolved_path}` and
12
+ `{project-name}` exactly as `SKILL.md` set them.
13
+
14
+ ---
15
+
16
+ ## Turn 4
17
+
18
+ **Turn 4 — Deduplicate, validate & record.** Runs **every pass**, and it is the end of the loop body: deduplicate this pass's agent results, gate-evaluate, tag, record the pass, write the ledger. **It prints no report** — there is exactly one report per scan and Turn 5 prints it. Do NOT print an intermediate dedup list.
19
+
20
+ > **This turn used to print the report and clean up, and both moved.** The report moved to Turn 5 so that a 1-pass scan and an N-pass scan take the **same** path through this turn — the "1 pass is byte-identical to today" constraint is easier to hold with one report writer than with a last pass that doubles as one. The auto-clean moved to Turn 5 because, left here, pass 1 deletes what pass 2 needs: `source.md`, the ledger photocopy and the accumulating rows file. A later editor must not move either back.
21
+
22
+ Then, after the loop body has run `{passes}` times (or stopped early), go to Turn 5 once.
23
+
24
+ 1. **Dedup.** Parse every FINDING and LEAD from the 12 agents. Group by `group_key` (Contract | function | bug-class). Exact-match first; merge synonymous bug_class within same (Contract, function). Keep best per group, number sequentially, annotate `[agents: N]`.
25
+
26
+ **MANDATORY — Canonicalise the bug-class label (HARD GATE).** Before grouping, for every (Contract, function) whose agents used **more than one** bug-class label, choose **one** label and rewrite every one of those findings to carry it. Then group.
27
+
28
+ Choose in this order, and stop at the first that applies:
29
+ 1. A label already listed for that same contract and function in `{bundle_dir}/known-findings.md` — the repository's own word wins, always.
30
+ 2. Otherwise the label that most agents used.
31
+ 3. Otherwise the shortest label that names the **defect**, not its consequence. `zero-abort-address` over `abort-address-zero-burns-funds`.
32
+
33
+ Merging under one label is only correct when the labels name the **same class of bug**. Two genuinely different bugs in one function keep two labels and stay two records — function isolation is about functions, and this rule is about words.
34
+
35
+ **Why this is a gate and not a nicety.** The per-function vocabulary that keeps a key stable only constrains what an **earlier scan** wrote. A class discovered mid-scan is constrained by nothing, and a real scan proved what follows: three agents in one pass named one bug `zero-abort-address`, `unregistered-abort-address` and `abort-address-zero-burns-funds`, and the over-approval class drew three labels across three passes. Unfixed, that is three ledger records for one bug, three lines in every future `known-findings.md`, and one bug that is never recognised again. This step is the only thing standing between the fork and the ledger. Canonicalise here and the next pass inherits the canonical label, so the fork closes after one scan.
36
+
37
+ **MANDATORY — Wide-description (group_key).** Merged group with distinct mechanisms (different `fix:`, code-level cause, or attack path) MUST list every mechanism. No dropping. Same function can have multiple coexisting bugs at the same group_key — all MUST appear.
38
+
39
+ **MANDATORY — Function-level second pass (after group_key dedup).** Run at (Contract, function), ignoring bug_class. Agents often label coexisting bugs with different bug_class tags but reference multiple mechanisms in the body. For every (Contract, function) with multiple final findings: scan body (description, path, proof, fix) of every constituent for distinct mechanisms across bug_class boundaries. Every mechanism in any constituent body MUST appear in ≥1 final finding.
40
+
41
+ **MANDATORY — Function isolation (HARD).** NEVER merge across different `function:` fields. Dedup only within (Contract, function). Different function = different bug. Second pass above stays WITHIN (Contract, function), never across.
42
+
43
+ **MANDATORY — Fix preservation (HARD GATE).** Before writing merged `fix:` on a multi-finding (Contract, function):
44
+ 1. Collect every raw `fix:` from agents flagging the tuple.
45
+ 2. Group by ADD-lines (`+` lines, or equivalent require/assignment).
46
+ 3. Distinct if ADD-lines differ in: called function/expression (e.g., `require(msg.value == amount)` vs `require(zrc20 != _ETH_ADDRESS_)`), check direction (validate/restrict/ban), or checked parameter.
47
+ 4. ≥2 distinct → present as Option A, B, … — one block per distinct fix, verbatim from agent text (no paraphrase).
48
+ 5. Label intuitively: validate / restrict / allow-and-handle / ban-path.
49
+
50
+ **Output format when 2+ distinct fixes exist:**
51
+
52
+ ```
53
+ **Fix (Option A — <label>)**:
54
+
55
+ ```diff
56
+ <verbatim diff from raw agent N1's fix>
57
+ ```
58
+
59
+ **Fix (Option B — <label>)**:
60
+
61
+ ```diff
62
+ <verbatim diff from raw agent N2's fix>
63
+ ```
64
+ ```
65
+
66
+ **Inline check before printing**: count distinct fixes from raw for this (Contract, function). ≥2 distinct but merged shows 1 → violation, add alternatives.
67
+
68
+ **MANDATORY — Completeness (HARD GATE).** Before print: list every unique (Contract, function, bug-class) in any raw FINDING/LEAD across the 12 agents. Every unique (Contract, function) MUST have ≥1 item in final. Zero = silent drop, fix it. Multiple bug-class within same (Contract, function) MAY collapse to one item (wide-description), but the (Contract, function) MUST survive. Print inline before report: `Completeness: N unique (Contract, function) in raw, N covered in final.`
69
+
70
+ Composite chains: if A's output feeds B's precondition AND combined impact > either alone, add `Chain: [A] + [B]` at conf = min(A, B). Most audits: 0–2.
71
+
72
+ 2. **Gate.** Run each deduped finding through the four gates in `judging.md` (no skip, no reorder, no revisit after verdict).
73
+
74
+ **Single-pass:** every relevant code path ONCE in fixed order (constructor → setters → swap → mint → burn → liquidate). One-line verdict: `BLOCKS` / `ALLOWS` / `IRRELEVANT` / `UNCERTAIN`. `UNCERTAIN = ALLOWS`. Commit, no re-examination.
75
+
76
+ 3. **Lead promotion / rejection.**
77
+ - LEAD → FINDING (conf 75) if: full exploit chain in source, OR `[agents: 2+]` demoted (not rejected) same issue.
78
+ - `[agents: 2+]` does NOT override a code path that interrupts attack before harm — demote to LEAD if execution uncertain.
79
+ - No deployer-intent reasoning — what code allows, not how deployer might use it.
80
+
81
+ 4. **Memory tag.** **SKIP this step entirely when memory is off.** It comes before the report, not after it, because the report prints what it decides. It is a **lookup, never a judgment** — no finding is re-argued here, and no verdict from the gate is revisited.
82
+
83
+ a. **Build the key** for every gated FINDING and every gated LEAD — the three segments of Turn 4's `group_key`, each normalised **on its own** (lower case, every run of non-alphanumeric characters to one hyphen), then joined with `|`: `vault|withdraw|reentrancy`. Normalise the segments separately, never the joined string, or the separators become hyphens too.
84
+
85
+ **The bug-class segment is where memory succeeds or fails.** Before writing a key, read the per-function vocabulary back from `{bundle_dir}/known-findings.md` — the same file the agents were given: *if this finding is one of the bug classes already listed for this same contract and function, write that exact label. Invent a new label only when none of them is the same class of bug.* This is the only judgment in the whole memory path, and it happens when the label is **written** — never at merge time. The keys built here are the keys step 6 writes; build them once.
86
+
87
+ b. **Tag each finding and each lead.** Look its key up in the pre-scan key list:
88
+
89
+ ```bash
90
+ tail -n +2 {bundle_dir}/memory-before.tsv | cut -f1,3 | sort > {bundle_dir}/known-keys.tsv
91
+ ```
92
+
93
+ Key present → **KNOWN**, printed as `KNOWN (n scans)` where **n is column 2 plus one**. The scan that is printing has found it again, so the count the report shows is the count the ledger will hold when step 6 has written it — report and file say the same number. Key absent → **NEW**. Nothing else decides this tag.
94
+
95
+ The lookup is against the **pre-scan photocopy**, in every pass of a loop. `KNOWN` means "an earlier **scan** recorded this"; a finding first raised by pass 1 of this scan is still `NEW` in pass 3, and how many passes saw it is what `seen in k/N runs` says. Tagging against the live ledger would make pass 2 call this scan's own work old news.
96
+
97
+ c. **Write this run's keys** to `{bundle_dir}/run-keys.tsv`, one per line, from step 4a.
98
+
99
+ The "Known from earlier scans" rows are **not** built here. They are report work, and a pass that built them could not know what a later pass is about to raise. They are built once, in Turn 5.
100
+
101
+ 5. **Record the pass.** Do NOT print the report.
102
+
103
+ a. **Write the run file** to `.hex/ai-audit/runs/{stamp}/run-K.md`, where `K` is the pass number. **Every pass of every scan writes one**, at any pass count, `{passes}` of 1 included. This is the only place this pass's work is recorded, and Turn 5 assembles the report out of these files — a pass that writes no run file has produced nothing the report can print.
104
+
105
+ One directory per scan, one file per run; `ls` is history in order. The git SHA goes **inside** the file, not in the directory name, so two scans of the same commit sit side by side instead of colliding. Nothing deletes these files, and `--file-output` does not govern them — they are memory, not reports.
106
+
107
+ **The file, exactly:**
108
+
109
+ ````markdown
110
+ # Run K — ai-audit
111
+
112
+ <!--RUN pass=K of=N stamp={stamp} sha=abc1234 agents=11/12-->
113
+
114
+ Pass K of N · <date> · `abc1234` · 11/12 agents returned — the access-control agent died.
115
+
116
+ ## Findings
117
+
118
+ <finding blocks, kind=FINDING>
119
+
120
+ ## Leads
121
+
122
+ <finding blocks, kind=LEAD>
123
+ ````
124
+
125
+ The sentence is for the human; the `<!--RUN-->` marker is what shell reads. `agents=11/12` is where the `Passes` row gets its degradation text, so a lost agent is on disk and not only in a printed line that scrolls away — and the dead agent's **name** stays here, in the sentence, and never enters the Scope table.
126
+
127
+ **These two headings and no others, in every run.** No `## New findings` in a later run — newness is already carried by the `NEW` / `KNOWN` tag and by `seen in k/N runs`, and a heading saying it again is a second source of truth that can disagree with the first. **No "verified clean", "checked" or "sound" section, under any name.** A path a pass did not raise is not a path a pass cleared, and a real scan invented such a section unasked. Nothing ever reads it back, and it is the same overstated-coverage defect this whole design exists to kill.
128
+
129
+ **The finding block, exactly:**
130
+
131
+ ````markdown
132
+ <!--F key=contract|function|bug-class conf=95 kind=FINDING agents=8-->
133
+
134
+ [95] **<Title>**
135
+
136
+ `ContractName.functionName` · Confidence: 95
137
+
138
+ **Description**
139
+ <the vulnerable pattern and why it is exploitable, 1 short sentence>
140
+
141
+ **Fix**
142
+
143
+ ```diff
144
+ - vulnerable line(s)
145
+ + fixed line(s)
146
+ ```
147
+
148
+ <!--/F-->
149
+ ````
150
+
151
+ - **The markers bound the block.** `<!--F ` opens it, `<!--/F-->` closes it, each on its own line at the start of the line. **Never `---`**: today's separator also appears inside a unified diff (`--- a/Vault.sol`), so it cannot cut a block that now carries one. The HTML comment renders invisible, so the file still reads as human memory.
152
+ - **`key`** — exactly the key step 4a built, unchanged. Never re-derived here. The same key the ledger stores, so the run file and `memory.tsv` agree by construction.
153
+ **Hex: a key is built on every scan, memory on or off.** Turn 6 uses it to keep the board idempotent: a re-scan that raises the same key does not file a second card. When memory is off, step 4 is skipped, so build the key here exactly as step 4a describes (normalise each segment on its own, join with `|`) using the label step 1 canonicalised; with no `known-findings.md` there is no vocabulary to read back. Building a key reads no ledger and writes no file, so the plain path stays plain.
154
+ - **`conf`** — an integer, on `kind=FINDING` blocks only. **A `kind=LEAD` block carries no `conf` attribute at all** — a lead is not scored, and writing `0` would sort it against real numbers.
155
+ - **`kind`** — `FINDING` or `LEAD`.
156
+ - **`agents`** — carried from `[agents: 8]`. Informational; nothing parses it.
157
+
158
+ **Write the markers exactly as printed.** They are the only thing standing between a finding and a silent loss: the assembler counts open markers against close markers against readable blocks, and a disagreement becomes a visible `**Run files**` row in the report saying how many findings could not be read. The report may print **less** than the scan found; it may never claim to print **more**. A fudged marker costs a visible warning, not a hidden hole — but it still costs the finding.
159
+
160
+ **Above and below the threshold, the block shape is identical.** A block whose `conf` is below 75 simply has no `**Fix**` section. The run file does **not** mark which side of the line a finding sits on; the assembler re-derives that from `conf`. One source of truth.
161
+
162
+ **Write the Description and the diff Fix block here, in full.** This is the expensive per-finding writing, and it is paid **now**, in the pass that found the bug, while that pass still has context to spare. Turn 5 re-words nothing — it pastes these bytes into the report. An orchestrator drafting seventy findings at the end of a long scan is the failure this ordering exists to prevent.
163
+
164
+ **The title and the Description are written in Simplified Technical English**, per `{resolved_path}/report-language.md`, which Turn 2 read. Because Turn 5 re-words nothing, this is the **only** step where the report's sentences can be made readable — there is no later cleanup pass and there must never be one. Four pieces of text obey that file: the title, the Description, the Lead description, and the Lead's code-smell list.
165
+
166
+ Three things it does **not** reach, and a later editor must not extend it to them: the **diff** inside the Fix block, which is pasted verbatim from the agent, the **bug-class label** in the `key` attribute, which is a memory key and is chosen by step 1 and step 4a, and the **identifiers** on the location line, which are spelled as the source spells them. Softening a label to read better writes a second ledger record for one bug.
167
+
168
+ Where the agent handed up a sentence that already meets the rules, keep the agent's words. Where it did not — a metaphor, an `-ing` clause, forty words, `catastrophic` — rewrite the sentence and keep the claim. Rewriting is about the wording only: the mechanism, the actor and the effect the agent proved are not up for revision here, and the gate has already run.
169
+
170
+ **Then record the agent count**, once per pass, always, `12/12` included:
171
+
172
+ ```bash
173
+ printf '%s\t%s\n' pass_{K}_agents "11/12" >> .hex/ai-audit/runs/{stamp}/scope.tsv
174
+ ```
175
+
176
+ Written only when a pass ran short, a missing key would mean two different things — a whole pass and a lost pass — and the assembler could not tell them apart.
177
+
178
+ b. **Print one summary line**, only when `{passes}` is above 1, and nothing else — the last thing the pass does, after step 6 has written the ledger. It is the only thing the runner sees for minutes at a time, so it answers two questions: how far along, and is the loop still learning?
179
+
180
+ ```
181
+ Pass 2/3 — 17 findings (3 new) · 6 leads (2 new) · ledger 41 records
182
+ ```
183
+
184
+ - The counts are post-dedup and post-gate — the numbers `run-2.md` holds, not raw agent output.
185
+ - **new** = the key was not in the ledger before this pass. A lookup, not a judgment.
186
+ - `ledger N records` is the row count of `.hex/ai-audit/memory.tsv` after step 6 has written it, so print this line after step 6.
187
+ - The parentheses are printed **only when the ledger was non-empty when the scan started**. On a first-ever scan every key is new and they say nothing: `Pass 1/3 — 17 findings · 6 leads · ledger 17 records`.
188
+ - When a pass lost an agent, the line gains a clause: `Pass 2/3 — 15 findings (2 new) · 6 leads (0 new) · ledger 39 records · 11/12 agents`.
189
+ - When **both** new-counts are zero, the line ends with `· no new ground`. That is the loop's most informative outcome — the passes have converged — and it must not have to be inferred from two zeros:
190
+
191
+ ```
192
+ Pass 3/3 — 17 findings (0 new) · 6 leads (0 new) · ledger 41 records · no new ground
193
+ ```
194
+
195
+ Elapsed time is deliberately not printed: it needs a `date` call either side of every pass, and the runner's terminal already shows wall-clock.
196
+
197
+ > **On a 1-pass scan this whole step prints nothing.** Step 5a still writes `run-1.md` — silently. `Pass 1/1 — …` is a progress line for a loop that is not running, and the plain path prints the bytes it printed before loop mode existed. Writing a file is not printing; the freeze covers printed output.
198
+
199
+ 6. **Memory write.** **SKIP this step entirely when memory is off.** It is a step of Turn 4 and not a turn of its own because it can only be built from this turn's gated findings — the rows do not exist until the gate has run.
200
+
201
+ a. **Build this run's rows** from the keys step 4 already built. One row per gated FINDING and per gated LEAD, four tab-separated columns:
202
+
203
+ ```
204
+ key sha title kind
205
+ ```
206
+
207
+ - `key` — exactly the key step 4a built and step 4b tagged. Never rebuilt here: a key that differs between the tag and the row would report `NEW` and store a duplicate.
208
+ - `sha` — `git rev-parse --short HEAD`. Not a git repo, or a repo with no commits: write `none` and carry on. The SHA is never part of the match, so a missing one costs nothing.
209
+ - `title` — the finding title, tabs and newlines replaced by a single space, cut to 80 characters. Bars are safe: only the key is ever split on bars.
210
+ - `kind` — `FINDING` or `LEAD`.
211
+
212
+ Append the rows to the accumulating file, do not overwrite it:
213
+
214
+ ```bash
215
+ cat >> {bundle_dir}/scan-rows.tsv
216
+ ```
217
+
218
+ b. **Merge.** One `awk` pass over the photocopy and this scan's accumulated rows. `sort` + `join` is not used: `join` returns only matching rows, so keeping old-only and new-only rows would need three invocations and identical sort order across locales.
219
+
220
+ ```bash
221
+ awk -F'\t' -v OFS='\t' '
222
+ FILENAME==ARGV[1] {
223
+ if (FNR==1) next
224
+ o_st[$1]=$2; o_sc[$1]=$3; o_sha[$1]=$4; o_ti[$1]=$5; o_ki[$1]=$6
225
+ if (!($1 in o_seen)) { o_seen[$1]=1; oord[++on]=$1 }
226
+ next
227
+ }
228
+ {
229
+ if (!($1 in n_seen)) { n_seen[$1]=1; nord[++nn]=$1 }
230
+ n_sha[$1]=$2; n_ti[$1]=$3; n_ki[$1]=$4
231
+ }
232
+ END {
233
+ print "#hex-ai-audit-memory v1", "key", "status", "scans", "sha", "title", "kind"
234
+ for (i=1; i<=on; i++) {
235
+ k = oord[i]
236
+ if (k in n_seen) print k, "KNOWN", o_sc[k]+1, n_sha[k], n_ti[k], n_ki[k]
237
+ else print k, o_st[k], o_sc[k], o_sha[k], o_ti[k], o_ki[k]
238
+ }
239
+ for (i=1; i<=nn; i++) {
240
+ k = nord[i]
241
+ if (!(k in o_seen)) print k, "NEW", 1, n_sha[k], n_ti[k], n_ki[k]
242
+ }
243
+ }
244
+ ' {bundle_dir}/memory-before.tsv {bundle_dir}/scan-rows.tsv > .hex/ai-audit/memory.tsv.tmp
245
+ ```
246
+
247
+ The three cases, all visible in one command:
248
+
249
+ | Case | Result |
250
+ |---|---|
251
+ | Old row re-found | `status` `KNOWN`, `scans` + 1, `sha` / `title` / `kind` taken from **this scan's** row |
252
+ | Old row not re-found | carried over completely unchanged |
253
+ | No old row | `status` `NEW`, `scans` 1 |
254
+
255
+ Two rules the merge carries, and a later editor must not simplify away:
256
+
257
+ - **`kind` is not part of the key.** A Lead an earlier scan recorded and this scan proved is the *same bug*: its `kind` flips to `FINDING` in place, `scans` keeps counting, `status` stays `KNOWN`. Demotion is the same rule running backwards and needs no special case. Putting `kind` in the key would remember one bug twice and list it twice.
258
+ - **The merge is idempotent.** It always reads the pre-scan photocopy plus everything this scan has found so far, so `scans` rises by exactly 1 however many times it runs inside one scan. "Have I already counted this key?" is a question that cannot arise. Where one key appears more than once in `scan-rows.tsv`, the last row wins — which is what makes a Lead proven by a later pass land as `FINDING`.
259
+
260
+ c. **Move it into place.**
261
+
262
+ ```bash
263
+ mv .hex/ai-audit/memory.tsv.tmp .hex/ai-audit/memory.tsv
264
+ ```
265
+
266
+ The temporary file sits beside the real one so the move stays on one disk. **The new file is always written before the old one is replaced** — an interrupted scan leaves one whole ledger, never half of one. Never write `memory.tsv` directly.
267
+
268
+ `mkdir -p .hex/ai-audit` before the first write. The ledger is local working state inside the Hex output directory; it is not shared between machines or people.
269
+
270
+ **Then write the other two `mem_` keys**, every pass, immediately after the move:
271
+
272
+ ```bash
273
+ printf '%s\t%s\n' mem_after "$(( $(wc -l < .hex/ai-audit/memory.tsv) - 1 ))" \
274
+ >> .hex/ai-audit/runs/{stamp}/scope.tsv
275
+ printf '%s\t%s\n' mem_sha "$(git rev-parse --short HEAD 2>/dev/null || echo none)" \
276
+ >> .hex/ai-audit/runs/{stamp}/scope.tsv
277
+ ```
278
+
279
+ Both are re-written on every pass and **last wins**, so the `Memory` row ends up describing the ledger as the scan left it, with no delete step and no "have I written this already?" flag. `mem_before` is not touched here — it was written once, in Turn 2 step 2c, and the row's arithmetic depends on it staying put.
280
+
281
+ d. **In a loop, this step runs after every pass, and it rebuilds rather than appends.** Pass K+1 learns what pass K found by reading the merged ledger, so the write cannot wait for the end. But re-running the merge N times must not raise `scans` by N, because `scans` counts scans. The command above already holds this: it always reads the **pre-scan photocopy** plus **everything this scan has accumulated so far**, so `scans` rises by exactly 1 however many passes ran. Nothing new is invented per pass, and no "have I counted this key already?" flag exists to get wrong. It also keeps the interrupt rule for the whole loop: a loop killed at pass 3 keeps what passes 1 and 2 learned, written atomically.
282
+
283
+ Then the next pass starts at **Turn 2 step 2c**, which rebuilds `known-findings.md` from the freshly merged `.hex/ai-audit/memory.tsv` — not from the photocopy, or pass K+1 would never see pass K's findings — and re-cats the twelve bundles at Turn 2 step 3.
284
+
285
+ ## Turn 5
286
+
287
+ **Turn 5 — Assemble, print, clean.** Runs **once per scan**, at any pass count, including a 1-pass scan and including a loop that stopped early. There is no path where a scan ends without this turn.
288
+
289
+ > **This turn no longer composes a report, and must never be re-taught to.** It used to combine the runs, dedup them, build the "Known from earlier scans" rows and format the whole thing — all of it model work, all of it at the moment of least remaining context. On a real 3-pass scan that produced 71 findings, it printed 14 of them in full, collapsed the rest into two lines, and **claimed a coverage it did not have**. Nothing was lost from disk; the report lied about what it was showing. A prose instruction cannot fix that, because the orchestrator under context pressure is the thing that failed.
290
+ >
291
+ > So the step that must not lose a finding holds **no model judgment**. The dedup, the `seen in k/N runs` counts, the strongest-kind rule, the "Known from earlier scans" rows and the whole report body are `assemble.sh`'s work now, and it is shell. This turn copies two files, runs it, counts, and prints. If you are reading this turn and it looks too thin to be a reporting step — that is the point.
292
+
293
+ 1. **Copy the memory inputs in.** **SKIP when memory is off.** The assembler reads one directory, so anything it needs that lives in `{bundle_dir}` has to be in the runs directory before it runs:
294
+
295
+ ```bash
296
+ cp {bundle_dir}/memory-before.tsv {bundle_dir}/source-names.tsv .hex/ai-audit/runs/{stamp}/
297
+ ```
298
+
299
+ `memory-before.tsv` is the **pruned** photocopy — the "Known from earlier scans" section is built from it, so a record the prune dropped must not reappear here. `source-names.tsv` turns a normalised key back into the spelling the source uses, so no reader is ever shown `gatewaytransfernative.withdrawtonativechain`.
300
+
301
+ Only these two. `run-keys.tsv` and `scan-rows.tsv` are **not** copied: the keys this scan raised come from the assembler's own index of the run files, which is the same data with no copy step.
302
+
303
+ 2. **Assemble the report.**
304
+
305
+ ```bash
306
+ bash {resolved_path}/assemble.sh --dir .hex/ai-audit/runs/{stamp}
307
+ ```
308
+
309
+ It writes `.hex/ai-audit/runs/{stamp}/full-report.md` — the whole report, banner line down to the disclaimer, ready to print word for word. It writes to a `.part` file and moves it into place, so a half-report never exists.
310
+
311
+ **This is the only producer of a report in this skill.** Do not re-word its output, do not re-order it, do not add to it and do not summarise it in your own words. Do not re-read source to "verify the most critical claim" either — the agents did that and the gate filtered it; re-verification costs about five minutes and rarely changes a verdict.
312
+
313
+ If it exits non-zero, print its stderr and stop. It has told you it could not read its own inputs, and there is nothing for a model to salvage by hand.
314
+
315
+ 3. **Count the findings, then branch.** One `awk` over the assembled file, no model arithmetic anywhere:
316
+
317
+ ```bash
318
+ awk '
319
+ /^## Leads$/ { sec="leads"; next }
320
+ /^## / { sec=""; next }
321
+ /^\[[0-9]+\]/ { F++; next }
322
+ sec=="leads" && /^- \*\*/ { L++ }
323
+ /Confidence: [0-9]+/ && /^`/ {
324
+ match($0, /Confidence: [0-9]+/)
325
+ c = substr($0, RSTART+12, RLENGTH-12) + 0
326
+ if (c >= 90) A++; else if (c >= 75) B++; else C++
327
+ }
328
+ END { printf "%d %d %d %d %d\n", F, L, A, B, C }
329
+ ' .hex/ai-audit/runs/{stamp}/full-report.md
330
+ ```
331
+
332
+ `F` is findings, `L` leads, and `A` / `B` / `C` the confidence buckets `100-90`, `89-75` and `below 75`.
333
+
334
+ **Only `F` is printed** — it is the trigger and it is the total in `top 3 of {F}`. `L`, `A`, `B` and `C` are computed anyway and kept as a **self-check**, not as output.
335
+
336
+ > **`A + B + C` must equal `F`.** The buckets are the whole set, in every mode. If they do not sum, the assembled file is malformed — a finding block the counter could not read is a finding block a reader may not get either. Say so and print the full report rather than a three-row slice of a file you cannot stand behind.
337
+
338
+ **The trigger is more than 20 findings** — `F` alone. Leads are excluded, so a scan with 18 findings and 40 leads still prints in full.
339
+
340
+ a. **`F` is 20 or fewer — print `full-report.md` word for word.** Every byte, nothing added, nothing cut, no preface. This is the path a plain scan of a small contract takes, and its printed bytes are exactly what this skill printed before any of this existed.
341
+
342
+ b. **`F` is above 20 — print this instead**, and nothing else:
343
+
344
+ ````markdown
345
+ # 🔐 Security Review — {project-name}
346
+
347
+ ---
348
+
349
+ ## Scope
350
+
351
+ {the Scope table from full-report.md, copied unchanged}
352
+
353
+ ---
354
+
355
+ Findings List — top 3 of {F}
356
+
357
+ | # | Confidence | Title | Location | Seen |
358
+ |---|---|---|---|---|
359
+ {the three rows the extractor below printed}
360
+
361
+ **→ Full findings list, every description and every fix:** {path}
362
+
363
+ ---
364
+
365
+ > ⚠️ {the disclaimer line from full-report.md, copied unchanged}
366
+ ````
367
+
368
+ - **`{path}`** — `.hex/ai-audit/runs/{stamp}/full-report.md`, or, when `--file-output` was passed, the **copy** and only the copy: `{project-name}-hex-ai-audit-report-{stamp}.md`. Never both. Two paths to identical bytes answers a question the runner did not ask at the moment they want one filename. This line is the only part of the block a flag changes.
369
+ - **The Scope table's `Files reviewed` row is copied unchanged**, full paths and all. It is the one row that proves *which* `SafeMath.sol` was read, and a terminal that shortens it can no longer be diffed against the file it claims to summarise.
370
+ - **No counts summary.** The `{F} findings, {L} leads` line and the `100-90 / 89-75 / below 75` split are **not** printed. `top 3 of {F}` already carries the only total a reader needs at this moment, and the bucket split answered a question nobody asked while standing between them and the path.
371
+
372
+ > **The `{F}` in `top 3 of {F}` is what makes the slice safe, and it is not optional.** The rule this block obeys is that a report may print **less** than the scan found and may never look like it printed **more**. A bare list of three findings breaks that rule — a reader cannot tell a truncated list from a complete one. `top 3 of 52` cannot be misread, and it sits in the header rather than in a footnote for exactly that reason. An editor who drops the total, or moves it below the table, has re-created the failure this sentence exists to prevent.
373
+ >
374
+ > **This replaces the older "nothing is enumerated above the trigger" rule.** That rule was right about bare truncation and wrong to conclude that no slice can ever be honest; a counted slice is. Still enumerated **nowhere**: the Leads section, the "Known from earlier scans" section, and findings 4 and beyond. Three rows, and the header says three.
375
+
376
+ **The three rows are extracted by shell, never typed from memory.** The orchestrator has by this point read dozens of findings and is the least reliable thing in the room; one `awk` over the assembled file cannot mis-rank, mis-quote a function name or invent a confidence. Pass `-v withseen=1` when the `Passes` row is printed — that is, when `{passes}` is above 1 — and `0` otherwise, because a plain scan's location line carries no `seen in k/N runs` and an empty column would print as a stub:
377
+
378
+ ```bash
379
+ awk -v withseen={1 or 0} '
380
+ /^\[[0-9]+\] \*\*/ {
381
+ if (n >= 3) exit
382
+ line = $0
383
+ match(line, /^\[[0-9]+\]/); conf = substr(line, RSTART+1, RLENGTH-2)
384
+ sub(/^\[[0-9]+\] \*\*[0-9]+\. /, "", line); sub(/\*\*[ \t]*$/, "", line)
385
+ title = line; want = 1; next
386
+ }
387
+ want && /^`/ {
388
+ loc = $0
389
+ match(loc, /^`[^`]*`/); fn = substr(loc, RSTART+1, RLENGTH-2)
390
+ n++
391
+ if (withseen) {
392
+ seen = ""
393
+ if (match(loc, /seen in [0-9]+\/[0-9]+ runs/)) seen = substr(loc, RSTART+8, RLENGTH-13)
394
+ printf "| %d | [%s] | %s | `%s` | %s |\n", n, conf, title, fn, seen
395
+ } else printf "| %d | [%s] | %s | `%s` |\n", n, conf, title, fn
396
+ want = 0
397
+ }
398
+ ' .hex/ai-audit/runs/{stamp}/full-report.md
399
+ ```
400
+
401
+ With `withseen=0` the awk prints four columns, so **drop `Seen` from the header row and from the separator row too** — a five-column header over four-column rows renders as a broken table in every terminal that draws one.
402
+
403
+ The assembled file is already sorted highest-confidence first, so "top 3" is the first three finding blocks and no sort happens here. `exit` after the third block means the extractor does not read the rest of the file.
404
+
405
+ **Fewer than 3 findings cannot reach this branch** — `F` is above 20 or this block is not printed — so the extractor needs no short-list case.
406
+
407
+ 4. **`--file-output` — copy the assembled file.** Only when the flag was passed:
408
+
409
+ ```bash
410
+ cp .hex/ai-audit/runs/{stamp}/full-report.md "{project-name}-hex-ai-audit-report-{stamp}.md"
411
+ ```
412
+
413
+ A copy, not a second build. **Nothing regenerates**, so the flag cannot collapse the report the way the terminal once did — the file and the terminal are the same bytes by construction, whatever the finding count. The run files stay in `.hex/ai-audit/runs/` and are never copied into the working directory.
414
+
415
+ 5. **Auto-clean.** `rm -rf {bundle_dir}`. Bundle dir = transient build state, not an artifact. Don't skip. It runs once per scan, at any pass count, and it runs when the loop stopped early too. For debugging a loop: copy the bundle elsewhere before re-running.
416
+
417
+ > **It runs last, and the order is load-bearing.** Step 1 copies two files **out of** `{bundle_dir}`, and step 2 reads them. Deleting the bundle before either has run destroys the "Known from earlier scans" section and the name map that keeps it readable — silently, because the assembler treats a missing input as an absent section and carries on. Steps 1 → 2 → 5 is not a style preference; moving this step earlier breaks a report that will still look finished.
418
+
419
+ Do **not** add a per-pass partial clean — reclaiming disk between passes would mean rebuilding `source.md`, the one thing built once. Do **not** add a startup sweep of stale `.hex/ai-audit/tmp-*` directories: a crashed scan leaks one, as it does today, and a sweep would delete the working directory of a second scan running at the same time on the same repo.
420
+
@@ -0,0 +1,27 @@
1
+ # Access Control Agent
2
+
3
+ You are an attacker that exploits permission models. Map the complete access control surface, then exploit every gap: unprotected functions, escalation chains, broken initialization, inconsistent guards.
4
+
5
+ Other agents cover known patterns, math, state consistency, and economics. You break the permission model.
6
+
7
+ ## Attack plan
8
+
9
+ **Map the permission model.** Every role, modifier, and inline access check. Who grants what to whom. This map is your weapon — every attack below references it.
10
+
11
+ **Exploit inconsistent guards.** For every storage variable written by 2+ functions, find the one with the weakest guard. If function A requires `onlyOwner` but function B writes the same variable unguarded — use B. Check inherited functions, overrides, and `internal` helpers reachable from differently-guarded `external` functions.
12
+
13
+ **Hijack initialization.** Call `initialize()` on the implementation contract directly. Front-run deployment to initialize with your own roles. Pass `address(0)` as a role parameter to permanently lock out admins.
14
+
15
+ **Escalate privileges.** Find routes where role A grants role B to itself. Chain grant/revoke paths to reach `grantRole` without triggering guards. Find upgrade paths that bypass timelock. Trigger `renounceRole` to leave the system unrecoverable.
16
+
17
+ **Exploit confused deputies.** When contract A calls contract B with A's privileges, trigger that path to make A act on your behalf. Find contracts holding token approvals and exploit unguarded functions to spend them.
18
+
19
+ **Abuse delegatecall/proxy.** Collide storage layouts. Self-destruct implementation contracts. Collide admin slots with business logic storage.
20
+
21
+ ## Output fields
22
+
23
+ Add to FINDINGs:
24
+ ```
25
+ guard_gap: the guard that's missing — show the parallel function that has it
26
+ proof: concrete call sequence achieving unauthorized access
27
+ ```
@@ -0,0 +1,77 @@
1
+ # Asymmetry Agent
2
+
3
+ You are an attacker that exploits asymmetries — between paired functions, between branches within a function, and between writers and readers of the same storage variable. The bug is not in one wrong line; it's in what's missing or different across two places that should match.
4
+
5
+ Other agents trace execution, check arithmetic, verify access control, analyze economics, scan known patterns, audit periphery, break invariants, and question assumptions. You exclusively hunt asymmetries.
6
+
7
+ ## Step 1 — Enumerate every paired surface
8
+
9
+ For each contract in scope, list:
10
+
11
+ - **Operation pairs:** deposit ↔ withdraw, mint ↔ burn, lock ↔ unlock, set ↔ get, encode ↔ decode, approve ↔ pull, request ↔ fulfill, open ↔ close, stake ↔ unstake.
12
+ - **Walk pairs:** modify ↔ settle, view ↔ modify, simulate ↔ execute, pre ↔ post, init ↔ teardown.
13
+ - **Branch pairs (within a function):** native vs ERC20, normal vs admin/force, happy path vs revert path, first-time vs subsequent, empty vs non-empty input.
14
+ - **Variant pairs:** user `X()` ↔ admin `forceX()`, normal `X()` ↔ batch `XBatch()`, sync ↔ async.
15
+
16
+ For each pair, note `file:line` of both sides. This list is your work plan.
17
+
18
+ ## Step 2 — Storage-write symmetry diff
19
+
20
+ For each pair, side-by-side:
21
+
22
+ 1. List every storage variable each side writes (mark direction: `=`, `+=`, `-=`, push, delete).
23
+ 2. List every storage variable each side reads.
24
+ 3. Diff the two lists. Surface:
25
+ - Same variable written by both, but in non-mirror direction (e.g., user variant sets `settleAmount=0`, admin variant sets `settleAmount=totalBalance` — invariant break)
26
+ - Variable written by one side but not the other (state coupling broken)
27
+ - Variable read by one but not the other (stale-read risk)
28
+ - Mirror functions that mutate entirely different slot sets
29
+
30
+ The bug: developer copied structure but forgot to mirror one update.
31
+
32
+ ## Step 3 — Branch-symmetry diff
33
+
34
+ For each function with internal branches (`if/else`, `if-revert`, sentinel-vs-real, native-vs-ERC20, payable vs non-payable), your job is COMPARISON: are the two branches doing equivalent work? (The boundary agent walks each branch's behavior individually under corner cases — your job is the diff between them.)
35
+
36
+ 1. Per branch list: validation run, storage written, fee deducted, downstream call made.
37
+ 2. Diff branches. Find:
38
+ - Validation in A missing in B (skip-validation bug)
39
+ - Fee deduction in A missing in B (free path)
40
+ - Downstream call shape differs (one passes `amount`, other passes `msg.value`)
41
+ - One branch reverts on edge, other silently no-ops
42
+
43
+ ## Step 4 — Storage-variable lifecycle audit
44
+
45
+ For each storage variable used across the contract:
46
+
47
+ 1. Find ALL writers.
48
+ 2. Find ALL readers.
49
+ 3. Flag:
50
+ - Variable written but never read → forgotten state
51
+ - Variable read but never written → defaults to zero silently
52
+ - Multiple writers with different validation shapes → exploit the weakest
53
+
54
+ ## Step 5 — Admin-function variants
55
+
56
+ For every admin function, check if it's a variant of a user-side function (`mint` ↔ `adminMint`, `swap` ↔ `forceSwap`, `pause/unpause` for any guarded op, `set*` for parameters that gate user behavior):
57
+
58
+ 1. Diff against the user-side function for missing manipulation guards (slippage, deadline, manipulation locks), missing input validation, asymmetric state updates, missing emit.
59
+ 2. The Beefy pattern: `deposit` had `onlyCompPeriods`, but `setPositionWidth` and `unpause` mirrored the same liquidity-rebalancing flow without that guard → sandwich drains TVL on admin parameter change.
60
+ 3. Devs under-test admin functions. They view them as "trusted actor only" and skip layered defenses. For every admin parameter change that affects user-relevant state, ask: can a user sandwich the admin transaction?
61
+
62
+ ## Step 6 — Bad symmetry (defensive checks that should not exist)
63
+
64
+ Redundant or over-restrictive checks:
65
+
66
+ - Two checks of the same invariant in adjacent functions where the second is now over-restrictive (e.g., `prepareBoxes` decrements counter, `redeemBoxes` re-checks counter > 0 → permanent DoS once preparation finishes)
67
+ - Comments saying "safety check" — frequently the safety claim is wrong
68
+ - Symmetric validation in functions that should be asymmetric
69
+
70
+ ## Output fields
71
+
72
+ Add to FINDINGs:
73
+ ```
74
+ pair_or_branch: which pair (deposit/withdraw, modify/settle, native-branch/ERC20-branch, admin-variant/user-version, ...) or branch you compared
75
+ asymmetry: the exact write/read/check that's in one side but missing or inverted in the other
76
+ proof: side-by-side citation showing the asymmetry with concrete state values illustrating the break
77
+ ```
@@ -0,0 +1,79 @@
1
+ # Boundary Agent
2
+
3
+ You are an attacker that exploits the gap between assumed and actual behavior at external boundaries. Your method is disciplined enumeration: walk every call site, every branch, every input source, and apply a fixed set of corner-case questions to each.
4
+
5
+ Other agents specialize by bug category. You specialize in **methodology**: applying the same questions to EVERY boundary point in the codebase until none are unexamined.
6
+
7
+ ## Step 1 — Enumerate every boundary
8
+
9
+ For each contract in scope, list every:
10
+ - External call site (`target.foo(...)`, `.call{...}(...)`, `.staticcall(...)`, `.delegatecall(...)`)
11
+ - Payable function (`external payable` or `public payable`)
12
+ - Function with a sentinel-address branch (`if (addr == address(0))`, `if (token == _ETH_ADDRESS_)`, similar)
13
+ - Function that takes a token/contract address as parameter (from caller, decoded message, or storage)
14
+ - Function with a `bytes` / `bytes calldata` input that is decoded
15
+ - Any place an external return value is consumed by caller logic
16
+
17
+ This list is your work plan. Apply Steps 2-5 to every entry.
18
+
19
+ ## Step 2 — For every external call: four corner cases
20
+
21
+ For each call site identified in Step 1, ask:
22
+
23
+ 1. **No code at receiver.** What if `address.code.length == 0`? Low-level `.call(...)` returns `(true, "")`. `IERC20(addr).approve(...)` reverts. `safeTransfer(addr, ...)` silently succeeds with no transfer because the empty return data passes the `success && (data.length == 0 || decoded == true)` check.
24
+ 2. **Non-standard token.** Void return (USDT-style) breaks `require(token.transferFrom())`. Fee-on-transfer makes received amount ≠ requested amount. Rebasing makes cached balance stale. Blacklist/pausable makes standard transfers revert unexpectedly. Some tokens revert on zero-value approval.
25
+ 3. **Empty / zero / max input.** Zero amount — does the code skip, revert, or proceed wrongly? Empty bytes — does abi.decode revert? Max uint — does the math overflow before the check?
26
+ 4. **Return-value handling.** Does the caller validate the return? Ignored bool return = silent failure. Misinterpreted custom-error returndata.
27
+ 5. **Sentinel-placeholder used in IERC20 op.** Native-token placeholder addresses (`_ETH_ADDRESS_`, `address(0xeee...)`) flow into raw `IERC20(token).approve(...)` and revert because the placeholder has no contract code. For every sentinel-branch, walk forward — any downstream `IERC20` op on the same `token` is broken.
28
+ 6. **False-returning ERC20.** Tokens that return `false` instead of reverting (Tether Gold class) silently corrupt state when `require(token.transfer(...))` is omitted. Distinct from USDT-style void return — both must be checked.
29
+ 7. **ERC165 dispatch fallback.** Decoders or wrappers using `supportsInterface` to dispatch between fallback branches fall through to default behavior when the wrapped contract omits ERC165; downstream code paths assume the wrong interface.
30
+ 8. **ERC721 hook re-entry.** `safeTransferFrom` calls `onERC721Received` on the receiver before state finalizes; the receiver re-enters the originating contract and observes inconsistent state.
31
+ 9. **Unrestricted external call from custody.** A contract holding tokens or NFTs performs an external call whose target and calldata are attacker-controlled; attacker calls back into the held-asset contract (`safeTransferFrom`) using the holding contract's authority.
32
+ 10. **Caller-supplied fee/bonus has no upper bound.** External entry-points accept a fee or bonus parameter without an upper bound; downstream economics assume reasonable values but the caller sets arbitrary, draining or bricking the path.
33
+
34
+ For every call site that fails any of the questions in a way the calling code doesn't account for — finding.
35
+
36
+ ## Step 3 — For every payable function: three branch cases
37
+
38
+ For each `payable` function:
39
+
40
+ 1. `msg.value > 0` — is the value spent, refunded, or forwarded? Where does it end up?
41
+ 2. `msg.value == 0` — does the operation still proceed when no native was sent? Does it skip a fee that should have been paid? Does it pull tokens it shouldn't?
42
+ 3. `msg.value != amount` (when both exist as inputs) — is the relationship between `msg.value` and an `amount` parameter enforced? `msg.value > amount` (excess stuck in contract). `msg.value < amount` (under-payment proceeds while accounting believes amount was paid).
43
+ 4. **Native-path fee not deducted.** When both `amount` and `fee` exist in scope, the native branch often forwards `msg.value` raw while the ERC20 branch deducts `fee` from `amount`. Downstream consumers assume pre-fee value was paid.
44
+
45
+ ## Step 4 — For every sentinel-address branch: walk both sides
46
+
47
+ For every check like `if (token == _ETH_ADDRESS_)`, `if (asset == address(0))`, custom placeholders:
48
+
49
+ 1. Native-side branch: does it pay/refund via `call{value:}` (correct) or via `safeTransfer(SENTINEL, ...)` (silent no-op)?
50
+ 2. ERC20 branch: does it use the token's actual decimals, return value, transfer semantics?
51
+ 3. The branch is your enumeration, not a comparison — for each branch, what does this specific path do under inputs the developer didn't anticipate?
52
+
53
+ ## Step 5 — For every bytes input / abi.decode: corruption cases
54
+
55
+ For every `bytes` input or `abi.decode`:
56
+ 1. Empty input — does the code panic? Bypass a loop? Return defaults that look like valid empty state?
57
+ 2. Length-prefixed array where the length is attacker-supplied — attacker writes a length larger than the buffer; OOB reads return zero-padded or trailing bytes.
58
+ 3. `bytes20(longerBytes)` cast — silent truncation. Source can be longer than 20 (BTC bech32, Solana 32-byte, attacker-chosen length).
59
+ 4. `abi.encodePacked` followed by `abi.decode` — packed encoding is ambiguous; decode returns wrong field boundaries.
60
+ 5. Field-order mismatches across encode and decode sites in different files — silent reinterpretation of attacker bytes.
61
+
62
+ ## Discipline
63
+
64
+ For each finding, state THREE things:
65
+ - The **boundary** you exercised (which call site / branch / input)
66
+ - The **assumption** the calling code makes about the boundary's behavior
67
+ - The **actual behavior** under the corner-case input you supply
68
+
69
+ Without all three, it's a LEAD.
70
+
71
+ ## Output fields
72
+
73
+ Add to FINDINGs:
74
+ ```
75
+ boundary: which call site / branch / input you exercised
76
+ assumption: what the calling code assumes the boundary does
77
+ actual: what the boundary actually does under your corner-case input
78
+ proof: concrete trigger and resulting state delta
79
+ ```