xonecode 0.3.0 → 0.5.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 (406) hide show
  1. package/README.md +49 -7
  2. package/THIRD_PARTY_NOTICES.md +128 -0
  3. package/apps/web/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  4. package/apps/web/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  5. package/apps/web/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  6. package/apps/web/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  7. package/apps/web/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  8. package/apps/web/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  9. package/apps/web/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  10. package/apps/web/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  11. package/apps/web/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  12. package/apps/web/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  13. package/apps/web/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  14. package/apps/web/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  15. package/apps/web/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  16. package/apps/web/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  17. package/apps/web/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  18. package/apps/web/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  19. package/apps/web/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  20. package/apps/web/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  21. package/apps/web/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  22. package/apps/web/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  23. package/apps/web/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  24. package/apps/web/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  25. package/apps/web/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  26. package/apps/web/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  27. package/apps/web/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  28. package/apps/web/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  29. package/apps/web/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  30. package/apps/web/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  31. package/apps/web/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  32. package/apps/web/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  33. package/apps/web/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  34. package/apps/web/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  35. package/apps/web/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  36. package/apps/web/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  37. package/apps/web/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  38. package/apps/web/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  39. package/apps/web/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  40. package/apps/web/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  41. package/apps/web/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  42. package/apps/web/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  43. package/apps/web/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  44. package/apps/web/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  45. package/apps/web/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  46. package/apps/web/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  47. package/apps/web/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  48. package/apps/web/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  49. package/apps/web/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  50. package/apps/web/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  51. package/apps/web/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  52. package/apps/web/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  53. package/apps/web/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  54. package/apps/web/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  55. package/apps/web/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  56. package/apps/web/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  57. package/apps/web/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  58. package/apps/web/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  59. package/apps/web/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  60. package/apps/web/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  61. package/apps/web/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  62. package/apps/web/dist/assets/c-BIGW1oBm.js +1 -0
  63. package/apps/web/dist/assets/cpp-DddhVWhK.js +1 -0
  64. package/apps/web/dist/assets/csharp-DSvCPggb.js +1 -0
  65. package/apps/web/dist/assets/css-CLj8gQPS.js +1 -0
  66. package/apps/web/dist/assets/go-C27-OAKa.js +1 -0
  67. package/apps/web/dist/assets/html-CfGypltT.js +1 -0
  68. package/apps/web/dist/assets/index-BB3SamhR.css +1 -0
  69. package/apps/web/dist/assets/index-CFYAoHdw.js +477 -0
  70. package/apps/web/dist/assets/ini-BEwlwnbL.js +1 -0
  71. package/apps/web/dist/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  72. package/apps/web/dist/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  73. package/apps/web/dist/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  74. package/apps/web/dist/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  75. package/apps/web/dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  76. package/apps/web/dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  77. package/apps/web/dist/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  78. package/apps/web/dist/assets/java-CylS5w8V.js +1 -0
  79. package/apps/web/dist/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
  80. package/apps/web/dist/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
  81. package/apps/web/dist/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  82. package/apps/web/dist/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  83. package/apps/web/dist/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
  84. package/apps/web/dist/assets/kotlin-BdnUsdx6.js +1 -0
  85. package/apps/web/dist/assets/less-B1dDrJ26.js +1 -0
  86. package/apps/web/dist/assets/lua-BaeVxFsk.js +1 -0
  87. package/apps/web/dist/assets/markdown-Cvjx9yec.js +1 -0
  88. package/apps/web/dist/assets/mdx-Cmh6b_Ma.js +1 -0
  89. package/apps/web/dist/assets/php-D_2--9WK.js +1 -0
  90. package/apps/web/dist/assets/python-B6aJPvgy.js +1 -0
  91. package/apps/web/dist/assets/ruby-DSHSDbk1.js +1 -0
  92. package/apps/web/dist/assets/rust-B1yitclQ.js +1 -0
  93. package/apps/web/dist/assets/scss-D5BDwBP9.js +1 -0
  94. package/apps/web/dist/assets/sql-CRqJ_cUM.js +1 -0
  95. package/apps/web/dist/assets/swift-C2oV4EkX.js +1 -0
  96. package/apps/web/dist/assets/toml-vGWfd6FD.js +1 -0
  97. package/apps/web/dist/assets/xml-sdJ4AIDG.js +1 -0
  98. package/apps/web/dist/assets/yaml-Buea-lGh.js +1 -0
  99. package/apps/web/dist/iconos/xonecode.png +0 -0
  100. package/apps/web/dist/index.html +20 -0
  101. package/dist/agent/agentesEnDisco.js +539 -0
  102. package/dist/agent/agentesEnDisco.js.map +1 -0
  103. package/dist/agent/arbolDeProyecto.js +245 -0
  104. package/dist/agent/arbolDeProyecto.js.map +1 -0
  105. package/dist/agent/artefactosEnDisco.js +139 -0
  106. package/dist/agent/artefactosEnDisco.js.map +1 -0
  107. package/dist/agent/aumentador.js +175 -0
  108. package/dist/agent/aumentador.js.map +1 -0
  109. package/dist/agent/authEnDisco.js +76 -1
  110. package/dist/agent/authEnDisco.js.map +1 -1
  111. package/dist/agent/busquedaRegex.js +92 -0
  112. package/dist/agent/busquedaRegex.js.map +1 -0
  113. package/dist/agent/catalogoModelos.js +345 -0
  114. package/dist/agent/catalogoModelos.js.map +1 -0
  115. package/dist/agent/checkpointer.js +127 -0
  116. package/dist/agent/checkpointer.js.map +1 -0
  117. package/dist/agent/cloudstudioClient.js +194 -0
  118. package/dist/agent/cloudstudioClient.js.map +1 -0
  119. package/dist/agent/cloudstudioMcp.js +722 -0
  120. package/dist/agent/cloudstudioMcp.js.map +1 -0
  121. package/dist/agent/configEnDisco.js +309 -11
  122. package/dist/agent/configEnDisco.js.map +1 -1
  123. package/dist/agent/consumoExterno.js +99 -0
  124. package/dist/agent/consumoExterno.js.map +1 -0
  125. package/dist/agent/descarga.js +149 -0
  126. package/dist/agent/descarga.js.map +1 -0
  127. package/dist/agent/diagnosticoDeTools.js +51 -0
  128. package/dist/agent/diagnosticoDeTools.js.map +1 -0
  129. package/dist/agent/dispositivosEnMaquina.js +596 -0
  130. package/dist/agent/dispositivosEnMaquina.js.map +1 -0
  131. package/dist/agent/escrituraDeCodex.js +134 -0
  132. package/dist/agent/escrituraDeCodex.js.map +1 -0
  133. package/dist/agent/escrituraDeOpencode.js +80 -0
  134. package/dist/agent/escrituraDeOpencode.js.map +1 -0
  135. package/dist/agent/escrituraExterna.js +742 -0
  136. package/dist/agent/escrituraExterna.js.map +1 -0
  137. package/dist/agent/gemini.js +81 -0
  138. package/dist/agent/gemini.js.map +1 -0
  139. package/dist/agent/git.js +83 -0
  140. package/dist/agent/git.js.map +1 -0
  141. package/dist/agent/gitSync.js +441 -0
  142. package/dist/agent/gitSync.js.map +1 -0
  143. package/dist/agent/hotswap.js +448 -0
  144. package/dist/agent/hotswap.js.map +1 -0
  145. package/dist/agent/instalacionEnMaquina.js +252 -0
  146. package/dist/agent/instalacionEnMaquina.js.map +1 -0
  147. package/dist/agent/instantanea.js +7 -10
  148. package/dist/agent/instantanea.js.map +1 -1
  149. package/dist/agent/interrupts.js +11 -2
  150. package/dist/agent/interrupts.js.map +1 -1
  151. package/dist/agent/juezDeTarea.js +345 -0
  152. package/dist/agent/juezDeTarea.js.map +1 -0
  153. package/dist/agent/lanzamientoEnMaquina.js +377 -0
  154. package/dist/agent/lanzamientoEnMaquina.js.map +1 -0
  155. package/dist/agent/manifiesto.js +59 -0
  156. package/dist/agent/manifiesto.js.map +1 -0
  157. package/dist/agent/memoriaDeProyecto.js +60 -0
  158. package/dist/agent/memoriaDeProyecto.js.map +1 -0
  159. package/dist/agent/modelos.js +105 -17
  160. package/dist/agent/modelos.js.map +1 -1
  161. package/dist/agent/modelosDeMotor.js +204 -0
  162. package/dist/agent/modelosDeMotor.js.map +1 -0
  163. package/dist/agent/paqueteDelProyecto.js +181 -0
  164. package/dist/agent/paqueteDelProyecto.js.map +1 -0
  165. package/dist/agent/perfiles.js +92 -31
  166. package/dist/agent/perfiles.js.map +1 -1
  167. package/dist/agent/persona.js +40 -0
  168. package/dist/agent/persona.js.map +1 -0
  169. package/dist/agent/procesosEnMaquina.js +204 -0
  170. package/dist/agent/procesosEnMaquina.js.map +1 -0
  171. package/dist/agent/proyecto.js +311 -13
  172. package/dist/agent/proyecto.js.map +1 -1
  173. package/dist/agent/puente.js +63 -6
  174. package/dist/agent/puente.js.map +1 -1
  175. package/dist/agent/resumenDeContexto.js +30 -0
  176. package/dist/agent/resumenDeContexto.js.map +1 -0
  177. package/dist/agent/resumenDeTool.js +64 -18
  178. package/dist/agent/resumenDeTool.js.map +1 -1
  179. package/dist/agent/sesionGit.js +481 -0
  180. package/dist/agent/sesionGit.js.map +1 -0
  181. package/dist/agent/settingsEnDisco.js +189 -0
  182. package/dist/agent/settingsEnDisco.js.map +1 -0
  183. package/dist/agent/subagenteCodex.js +368 -0
  184. package/dist/agent/subagenteCodex.js.map +1 -0
  185. package/dist/agent/subagenteExterno.js +423 -0
  186. package/dist/agent/subagenteExterno.js.map +1 -0
  187. package/dist/agent/subagenteOpencode.js +389 -0
  188. package/dist/agent/subagenteOpencode.js.map +1 -0
  189. package/dist/agent/subida.js +202 -0
  190. package/dist/agent/subida.js.map +1 -0
  191. package/dist/agent/tareasEnDisco.js +466 -0
  192. package/dist/agent/tareasEnDisco.js.map +1 -0
  193. package/dist/agent/turnoReal.js +663 -50
  194. package/dist/agent/turnoReal.js.map +1 -1
  195. package/dist/agent/versionEnDisco.js +48 -0
  196. package/dist/agent/versionEnDisco.js.map +1 -0
  197. package/dist/agent/xoneAgent.js +290 -49
  198. package/dist/agent/xoneAgent.js.map +1 -1
  199. package/dist/agent/zip.js +52 -0
  200. package/dist/agent/zip.js.map +1 -0
  201. package/dist/cli/acuseDeModelo.js +30 -0
  202. package/dist/cli/acuseDeModelo.js.map +1 -0
  203. package/dist/cli/aprobar.js +55 -2
  204. package/dist/cli/aprobar.js.map +1 -1
  205. package/dist/cli/config.js +9 -13
  206. package/dist/cli/config.js.map +1 -1
  207. package/dist/cli/consola.js +750 -19
  208. package/dist/cli/consola.js.map +1 -1
  209. package/dist/cli/main.js +740 -81
  210. package/dist/cli/main.js.map +1 -1
  211. package/dist/cli/markdown.js +188 -2
  212. package/dist/cli/markdown.js.map +1 -1
  213. package/dist/cli/run.js +32 -4
  214. package/dist/cli/run.js.map +1 -1
  215. package/dist/cli/stdio.js +46 -19
  216. package/dist/cli/stdio.js.map +1 -1
  217. package/dist/cli/tema.js +96 -8
  218. package/dist/cli/tema.js.map +1 -1
  219. package/dist/cli/tokens.js +29 -0
  220. package/dist/cli/tokens.js.map +1 -0
  221. package/dist/cli/tui/app.js +171 -0
  222. package/dist/cli/tui/app.js.map +1 -0
  223. package/dist/cli/tui/aprobarTui.js +80 -0
  224. package/dist/cli/tui/aprobarTui.js.map +1 -0
  225. package/dist/cli/tui/barra.js +37 -0
  226. package/dist/cli/tui/barra.js.map +1 -0
  227. package/dist/cli/tui/correrTui.js +478 -0
  228. package/dist/cli/tui/correrTui.js.map +1 -0
  229. package/dist/cli/tui/entrada.js +166 -0
  230. package/dist/cli/tui/entrada.js.map +1 -0
  231. package/dist/cli/tui/filas.js +24 -0
  232. package/dist/cli/tui/filas.js.map +1 -0
  233. package/dist/cli/tui/logo.js +49 -0
  234. package/dist/cli/tui/logo.js.map +1 -0
  235. package/dist/cli/tui/pielTui.js +14 -0
  236. package/dist/cli/tui/pielTui.js.map +1 -0
  237. package/dist/cli/tui/raton.js +126 -0
  238. package/dist/cli/tui/raton.js.map +1 -0
  239. package/dist/cli/tui/sidebar.js +86 -0
  240. package/dist/cli/tui/sidebar.js.map +1 -0
  241. package/dist/cli/tui/store.js +143 -0
  242. package/dist/cli/tui/store.js.map +1 -0
  243. package/dist/cli/tui/tarjeta.js +19 -0
  244. package/dist/cli/tui/tarjeta.js.map +1 -0
  245. package/dist/cli/tui/temaInk.js +114 -0
  246. package/dist/cli/tui/temaInk.js.map +1 -0
  247. package/dist/cli/tui/transcript.js +254 -0
  248. package/dist/cli/tui/transcript.js.map +1 -0
  249. package/dist/cli/wizardInicial.js +240 -0
  250. package/dist/cli/wizardInicial.js.map +1 -0
  251. package/dist/core/actos.js +113 -0
  252. package/dist/core/actos.js.map +1 -0
  253. package/dist/core/adjuntos.js +97 -0
  254. package/dist/core/adjuntos.js.map +1 -0
  255. package/dist/core/agentes.js +227 -0
  256. package/dist/core/agentes.js.map +1 -0
  257. package/dist/core/artefactos.js +149 -0
  258. package/dist/core/artefactos.js.map +1 -0
  259. package/dist/core/cloudstudio.js +7 -0
  260. package/dist/core/cloudstudio.js.map +1 -0
  261. package/dist/core/config.js +212 -5
  262. package/dist/core/config.js.map +1 -1
  263. package/dist/core/contextos.js +53 -1
  264. package/dist/core/contextos.js.map +1 -1
  265. package/dist/core/descargas.js +106 -0
  266. package/dist/core/descargas.js.map +1 -0
  267. package/dist/core/descriptoresDeApp.js +279 -0
  268. package/dist/core/descriptoresDeApp.js.map +1 -0
  269. package/dist/core/dispositivos.js +466 -0
  270. package/dist/core/dispositivos.js.map +1 -0
  271. package/dist/core/entrega.js +141 -0
  272. package/dist/core/entrega.js.map +1 -0
  273. package/dist/core/entrelazar.js +90 -0
  274. package/dist/core/entrelazar.js.map +1 -0
  275. package/dist/core/esqueleto.js +10 -0
  276. package/dist/core/esqueleto.js.map +1 -1
  277. package/dist/core/modelos.js +235 -3
  278. package/dist/core/modelos.js.map +1 -1
  279. package/dist/core/notify.js +27 -9
  280. package/dist/core/notify.js.map +1 -1
  281. package/dist/core/planDeSubida.js +127 -0
  282. package/dist/core/planDeSubida.js.map +1 -0
  283. package/dist/core/ports.js +189 -1
  284. package/dist/core/ports.js.map +1 -1
  285. package/dist/core/puedeLanzarse.js +239 -0
  286. package/dist/core/puedeLanzarse.js.map +1 -0
  287. package/dist/core/rutaVirtual.js +42 -0
  288. package/dist/core/rutaVirtual.js.map +1 -0
  289. package/dist/core/settings.js +246 -0
  290. package/dist/core/settings.js.map +1 -0
  291. package/dist/core/tareas.js +230 -0
  292. package/dist/core/tareas.js.map +1 -0
  293. package/dist/core/textos.js +36 -0
  294. package/dist/core/textos.js.map +1 -0
  295. package/dist/core/turno.js +56 -8
  296. package/dist/core/turno.js.map +1 -1
  297. package/dist/core/version.js +31 -0
  298. package/dist/core/version.js.map +1 -0
  299. package/dist/vendor/tokenTracking.js +22 -1
  300. package/dist/vendor/tokenTracking.js.map +1 -1
  301. package/dist/web/servidor/arranque.js +3964 -0
  302. package/dist/web/servidor/arranque.js.map +1 -0
  303. package/dist/web/servidor/consolaDeTarea.js +161 -0
  304. package/dist/web/servidor/consolaDeTarea.js.map +1 -0
  305. package/dist/web/servidor/consolaWeb.js +389 -0
  306. package/dist/web/servidor/consolaWeb.js.map +1 -0
  307. package/dist/web/servidor/corredorDeTareas.js +1119 -0
  308. package/dist/web/servidor/corredorDeTareas.js.map +1 -0
  309. package/dist/web/servidor/pielWeb.js +269 -0
  310. package/dist/web/servidor/pielWeb.js.map +1 -0
  311. package/dist/web/servidor/servidor.js +257 -0
  312. package/dist/web/servidor/servidor.js.map +1 -0
  313. package/dist/web/servidor/sesiones.js +440 -0
  314. package/dist/web/servidor/sesiones.js.map +1 -0
  315. package/dist/web/servidor/transporte.js +169 -0
  316. package/dist/web/servidor/transporte.js.map +1 -0
  317. package/dist/web/servidor/vestibulo.js +1276 -0
  318. package/dist/web/servidor/vestibulo.js.map +1 -0
  319. package/package.json +30 -8
  320. package/skills/archify/LICENSE +22 -0
  321. package/skills/archify/SKILL.md +109 -0
  322. package/skills/archify/THIRD_PARTY_NOTICES.md +56 -0
  323. package/skills/archify/assets/template.html +14787 -0
  324. package/skills/archify/bin/archify.mjs +1990 -0
  325. package/skills/archify/bin/open-artifact.mjs +86 -0
  326. package/skills/archify/bin/preview.mjs +648 -0
  327. package/skills/archify/bin/visual-check.mjs +829 -0
  328. package/skills/archify/brand-marks/README.md +31 -0
  329. package/skills/archify/brand-marks/catalog.json +131 -0
  330. package/skills/archify/delta/architecture-delta.mjs +1214 -0
  331. package/skills/archify/examples/agent-run.lifecycle.json +71 -0
  332. package/skills/archify/examples/agent-tool-call.workflow.json +94 -0
  333. package/skills/archify/examples/async-job-roundtrip.sequence.json +61 -0
  334. package/skills/archify/examples/brand-aware-delivery.architecture.json +47 -0
  335. package/skills/archify/examples/cache-miss-request.sequence.json +81 -0
  336. package/skills/archify/examples/checkout-platform.base.architecture.json +31 -0
  337. package/skills/archify/examples/checkout-platform.head.architecture.json +31 -0
  338. package/skills/archify/examples/dataflow-product-analytics.html +14898 -0
  339. package/skills/archify/examples/deployment-release.lifecycle.json +49 -0
  340. package/skills/archify/examples/event-stream.dataflow.json +57 -0
  341. package/skills/archify/examples/incident-response.workflow.json +64 -0
  342. package/skills/archify/examples/lifecycle-agent-run.html +14847 -0
  343. package/skills/archify/examples/product-analytics.dataflow.json +76 -0
  344. package/skills/archify/examples/production-deployment.architecture.json +71 -0
  345. package/skills/archify/examples/release-delivery.workflow.json +62 -0
  346. package/skills/archify/examples/sequence-cache-miss-request.html +14913 -0
  347. package/skills/archify/examples/web-app-rendered.html +14862 -0
  348. package/skills/archify/examples/web-app.architecture.json +46 -0
  349. package/skills/archify/examples/workflow-agent-tool-call-rendered.html +14904 -0
  350. package/skills/archify/migrations/workflow-v2.mjs +279 -0
  351. package/skills/archify/package.json +17 -0
  352. package/skills/archify/recipes/scenarios.mjs +391 -0
  353. package/skills/archify/references/authoring-contract.md +198 -0
  354. package/skills/archify/references/brand-marks.md +69 -0
  355. package/skills/archify/references/delivery-contract.md +116 -0
  356. package/skills/archify/references/viewer-runtime.md +45 -0
  357. package/skills/archify/renderers/architecture/grid.mjs +62 -0
  358. package/skills/archify/renderers/architecture/render-architecture.mjs +1078 -0
  359. package/skills/archify/renderers/dataflow/README.md +104 -0
  360. package/skills/archify/renderers/dataflow/render-dataflow.mjs +483 -0
  361. package/skills/archify/renderers/lifecycle/README.md +115 -0
  362. package/skills/archify/renderers/lifecycle/render-lifecycle.mjs +561 -0
  363. package/skills/archify/renderers/sequence/README.md +114 -0
  364. package/skills/archify/renderers/sequence/render-sequence.mjs +453 -0
  365. package/skills/archify/renderers/shared/brand-marks.mjs +563 -0
  366. package/skills/archify/renderers/shared/cli.mjs +218 -0
  367. package/skills/archify/renderers/shared/desktop-readability.mjs +26 -0
  368. package/skills/archify/renderers/shared/diagnostics.mjs +127 -0
  369. package/skills/archify/renderers/shared/engineering-profiles.mjs +157 -0
  370. package/skills/archify/renderers/shared/generated-brand-marks.mjs +2003 -0
  371. package/skills/archify/renderers/shared/generated-validators.mjs +13 -0
  372. package/skills/archify/renderers/shared/geometry.mjs +1423 -0
  373. package/skills/archify/renderers/shared/i18n.mjs +594 -0
  374. package/skills/archify/renderers/shared/layout-report.mjs +40 -0
  375. package/skills/archify/renderers/shared/legend.mjs +217 -0
  376. package/skills/archify/renderers/shared/output-path.mjs +321 -0
  377. package/skills/archify/renderers/shared/repository-evidence.mjs +235 -0
  378. package/skills/archify/renderers/shared/text-fit.mjs +49 -0
  379. package/skills/archify/renderers/shared/utils.mjs +232 -0
  380. package/skills/archify/renderers/shared/validator.mjs +86 -0
  381. package/skills/archify/renderers/workflow/README.md +223 -0
  382. package/skills/archify/renderers/workflow/render-workflow.mjs +35 -0
  383. package/skills/archify/renderers/workflow/workflow-compiler.mjs +4400 -0
  384. package/skills/archify/renderers/workflow/workflow-migration-geometry.mjs +144 -0
  385. package/skills/archify/schemas/README.md +211 -0
  386. package/skills/archify/schemas/architecture.schema.json +176 -0
  387. package/skills/archify/schemas/common.schema.json +115 -0
  388. package/skills/archify/schemas/dataflow.schema.json +243 -0
  389. package/skills/archify/schemas/lifecycle.schema.json +266 -0
  390. package/skills/archify/schemas/sequence.schema.json +223 -0
  391. package/skills/archify/schemas/workflow.schema.json +428 -0
  392. package/skills/archify/scripts/check-render-output.mjs +835 -0
  393. package/skills/archify/scripts/check-update.mjs +1667 -0
  394. package/skills/archify/scripts/render-examples.mjs +26 -0
  395. package/skills/archify/scripts/update-contract.mjs +182 -0
  396. package/skills/archify/skill-release.json +10 -0
  397. package/skills/artifacts-builder/SKILL.md +120 -0
  398. package/skills/artifacts-builder/reference/diagramas.md +123 -0
  399. package/skills/artifacts-builder/reference/estilo.md +123 -0
  400. package/skills/artifacts-builder/reference/exportar-pdf.md +56 -0
  401. package/skills/artifacts-builder/reference/graficas.md +22 -0
  402. package/skills/artifacts-builder/scripts/bundle_artifact.py +109 -0
  403. package/skills/artifacts-builder/scripts/validate_artifact.py +345 -0
  404. package/skills/xone-hotswap/SKILL.md +185 -0
  405. package/skills/xone-hotswap/references/comandos.md +706 -0
  406. package/skills/xone-hotswap/references/conexion-y-despliegue.md +226 -0
@@ -0,0 +1,1214 @@
1
+ const COMPARATOR_VERSION = 1;
2
+ const CANONICAL_VERSION = 1;
3
+
4
+ export class ArchitectureDeltaError extends Error {
5
+ constructor(code, message, details = {}) {
6
+ super(message);
7
+ this.name = 'ArchitectureDeltaError';
8
+ this.code = code;
9
+ this.details = details;
10
+ }
11
+ }
12
+
13
+ const codepointOrder = (left, right) => (left < right ? -1 : left > right ? 1 : 0);
14
+ const sorted = (values) => [...values].sort((left, right) => codepointOrder(String(left), String(right)));
15
+
16
+ function canonical(value) {
17
+ if (Array.isArray(value)) return `[${value.map(canonical).join(',')}]`;
18
+ if (value && typeof value === 'object') {
19
+ return `{${Object.keys(value).sort(codepointOrder).map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`).join(',')}}`;
20
+ }
21
+ return JSON.stringify(value);
22
+ }
23
+
24
+ const equal = (left, right) => canonical(left) === canonical(right);
25
+
26
+ function sortedObjects(values) {
27
+ return [...values].sort((left, right) => codepointOrder(canonical(left), canonical(right)));
28
+ }
29
+
30
+ function sortedBy(values, keyFor) {
31
+ return [...values].sort((left, right) => codepointOrder(String(keyFor(left)), String(keyFor(right))));
32
+ }
33
+
34
+ function normalizeRepository(repository) {
35
+ if (!repository) return undefined;
36
+ return {
37
+ url: String(repository.url || '').trim().replace(/\.git\/?$/i, '').replace(/\/$/, '').toLowerCase(),
38
+ revision: String(repository.revision || '').toLowerCase(),
39
+ };
40
+ }
41
+
42
+ function normalizeComponent(component) {
43
+ return {
44
+ ...component,
45
+ ...(Array.isArray(component.sources) ? { sources: sortedObjects(component.sources) } : {}),
46
+ };
47
+ }
48
+
49
+ function normalizeBoundary(boundary) {
50
+ return { ...boundary, wraps: sorted(boundary.wraps || []) };
51
+ }
52
+
53
+ export function canonicalArchitecture(diagram) {
54
+ const meta = { ...(diagram.meta || {}) };
55
+ delete meta.output;
56
+ if (meta.repository) meta.repository = normalizeRepository(meta.repository);
57
+ return {
58
+ schema_version: diagram.schema_version,
59
+ diagram_type: diagram.diagram_type,
60
+ meta,
61
+ ...(diagram.layout ? { layout: diagram.layout } : {}),
62
+ components: sortedBy((diagram.components || []).map(normalizeComponent), (component) => component.id),
63
+ boundaries: sortedBy((diagram.boundaries || []).map(normalizeBoundary), boundaryKey),
64
+ connections: sortedBy(diagram.connections || [], (connection) => connection.id || ''),
65
+ ...(diagram.cards ? { cards: diagram.cards } : {}),
66
+ };
67
+ }
68
+
69
+ export function canonicalArchitectureJson(diagram) {
70
+ return canonical(canonicalArchitecture(diagram));
71
+ }
72
+
73
+ function fail(code, message, details) {
74
+ throw new ArchitectureDeltaError(code, message, details);
75
+ }
76
+
77
+ function requireComparableShape(diagram, side) {
78
+ if (diagram?.schema_version !== 1) {
79
+ fail('delta/schema-version-mismatch', `${side} must use schema_version 1.`, { side, path: '/schema_version', actual: diagram?.schema_version });
80
+ }
81
+ if (diagram?.diagram_type !== 'architecture') {
82
+ fail('delta/type-mismatch', `${side} must use diagram_type architecture.`, { side, path: '/diagram_type', actual: diagram?.diagram_type });
83
+ }
84
+ }
85
+
86
+ function stableIndex(items, collection, side, missingCode = 'delta/stable-id-required') {
87
+ const index = new Map();
88
+ const missing = [];
89
+ const duplicates = [];
90
+ (items || []).forEach((item, itemIndex) => {
91
+ if (!item?.id) missing.push(`/${collection}/${itemIndex}/id`);
92
+ else if (index.has(item.id)) duplicates.push(item.id);
93
+ else index.set(item.id, item);
94
+ });
95
+ if (missing.length) {
96
+ fail(missingCode, `${side} ${collection} require authored stable ids for comparison.`, {
97
+ side,
98
+ paths: sorted(missing),
99
+ supportedFixes: [`add a unique id to every ${collection} item`],
100
+ });
101
+ }
102
+ if (duplicates.length) {
103
+ fail('delta/duplicate-stable-id', `${side} ${collection} contain duplicate ids.`, {
104
+ side,
105
+ collection,
106
+ ids: sorted(new Set(duplicates)),
107
+ supportedFixes: [`make every ${collection} id unique`],
108
+ });
109
+ }
110
+ return index;
111
+ }
112
+
113
+ const boundaryKey = (boundary) => `${boundary.kind}\u001f${boundary.label}`;
114
+
115
+ function boundaryIndex(boundaries, side) {
116
+ const index = new Map();
117
+ const ambiguous = [];
118
+ for (const boundary of boundaries || []) {
119
+ const key = boundaryKey(boundary);
120
+ if (index.has(key)) ambiguous.push(`${boundary.kind}:${boundary.label}`);
121
+ else index.set(key, boundary);
122
+ }
123
+ if (ambiguous.length) {
124
+ fail('delta/boundary-key-ambiguous', `${side} boundary kind + label keys must be unique.`, {
125
+ side,
126
+ boundaries: sorted(new Set(ambiguous)),
127
+ supportedFixes: ['rename one duplicate boundary or add stable boundary ids in a future schema version'],
128
+ });
129
+ }
130
+ return index;
131
+ }
132
+
133
+ function normalizedField(item, field) {
134
+ const value = item?.[field];
135
+ if (field === 'sources' && Array.isArray(value)) return sortedObjects(value);
136
+ if (field === 'wraps' && Array.isArray(value)) return sorted(value);
137
+ return value;
138
+ }
139
+
140
+ function fieldChanges(before, after, groups) {
141
+ const classifications = [];
142
+ const changedFields = [];
143
+ for (const [classification, fields] of Object.entries(groups)) {
144
+ const changed = fields.filter((field) => !equal(normalizedField(before, field), normalizedField(after, field)));
145
+ if (changed.length) classifications.push(classification);
146
+ changedFields.push(...changed.map((field) => `/${field}`));
147
+ }
148
+ return { classifications: sorted(classifications), changedFields: sorted(changedFields) };
149
+ }
150
+
151
+ const COMPONENT_FIELDS = {
152
+ semantic: ['type', 'label', 'sublabel', 'tag'],
153
+ evidence: ['sources'],
154
+ geometry: ['row', 'col', 'pos', 'size'],
155
+ };
156
+ const CONNECTION_FIELDS = {
157
+ topology: ['from', 'to'],
158
+ semantic: ['label', 'variant'],
159
+ geometry: ['fromSide', 'toSide', 'route', 'via', 'labelAt', 'labelDx', 'labelDy', 'labelSegment', 'width'],
160
+ };
161
+ const BOUNDARY_FIELDS = { scope: ['wraps'], geometry: ['pad'] };
162
+
163
+ function statusFor(classifications, kind) {
164
+ if (classifications.some((value) => ['topology', 'semantic', 'scope'].includes(value))) return 'changed';
165
+ if (classifications.includes('evidence')) return 'evidence-changed';
166
+ if (classifications.includes('geometry')) return kind === 'connection' ? 'rerouted' : kind === 'component' ? 'moved' : 'geometry-changed';
167
+ return 'same';
168
+ }
169
+
170
+ function compareEntities(baseIndex, headIndex, kind, groups, describe) {
171
+ const changes = [];
172
+ const identityClassification = kind === 'connection' ? 'topology' : kind === 'boundary' ? 'scope' : 'semantic';
173
+ for (const id of sorted(new Set([...baseIndex.keys(), ...headIndex.keys()]))) {
174
+ const base = baseIndex.get(id);
175
+ const head = headIndex.get(id);
176
+ if (!base) changes.push({ ...describe(id, undefined, head), status: 'added', classifications: [identityClassification], changedFields: [] });
177
+ else if (!head) changes.push({ ...describe(id, base, undefined), status: 'removed', classifications: [identityClassification], changedFields: [] });
178
+ else {
179
+ const fields = fieldChanges(base, head, groups);
180
+ const status = statusFor(fields.classifications, kind);
181
+ if (status !== 'same') changes.push({ ...describe(id, base, head), status, ...fields });
182
+ }
183
+ }
184
+ return changes;
185
+ }
186
+
187
+ function summaryFor(changes, shape) {
188
+ const summary = Object.fromEntries(shape.map((key) => [key, 0]));
189
+ for (const change of changes) {
190
+ const key = change.status.replace(/-([a-z])/g, (_all, letter) => letter.toUpperCase());
191
+ if (Object.hasOwn(summary, key)) summary[key] += 1;
192
+ }
193
+ return summary;
194
+ }
195
+
196
+ function presentationChanged(base, head) {
197
+ const basePresentation = {
198
+ title: base.meta?.title,
199
+ subtitle: base.meta?.subtitle,
200
+ animation: base.meta?.animation,
201
+ visual_preset: base.meta?.visual_preset,
202
+ quality_profile: base.meta?.quality_profile,
203
+ engineering_profile: base.meta?.engineering_profile,
204
+ legend: base.meta?.legend,
205
+ views: base.meta?.views,
206
+ viewBox: base.meta?.viewBox,
207
+ layout: base.layout,
208
+ cards: base.cards,
209
+ };
210
+ const headPresentation = {
211
+ title: head.meta?.title,
212
+ subtitle: head.meta?.subtitle,
213
+ animation: head.meta?.animation,
214
+ visual_preset: head.meta?.visual_preset,
215
+ quality_profile: head.meta?.quality_profile,
216
+ engineering_profile: head.meta?.engineering_profile,
217
+ legend: head.meta?.legend,
218
+ views: head.meta?.views,
219
+ viewBox: head.meta?.viewBox,
220
+ layout: head.layout,
221
+ cards: head.cards,
222
+ };
223
+ return !equal(basePresentation, headPresentation);
224
+ }
225
+
226
+ export function compareArchitecture(base, head, evidence = {}) {
227
+ requireComparableShape(base, 'base');
228
+ requireComparableShape(head, 'head');
229
+ const baseComponents = stableIndex(base.components, 'components', 'base');
230
+ const headComponents = stableIndex(head.components, 'components', 'head');
231
+ const shared = sorted([...baseComponents.keys()].filter((id) => headComponents.has(id)));
232
+ if (!shared.length) {
233
+ fail('delta/no-shared-component-id', 'The snapshots share no component id, so Archify cannot prove that they describe the same system.', {
234
+ supportedFixes: ['preserve at least one authored component id across snapshots'],
235
+ });
236
+ }
237
+
238
+ const baseConnections = stableIndex(base.connections, 'connections', 'base', 'delta/relationship-id-required');
239
+ const headConnections = stableIndex(head.connections, 'connections', 'head', 'delta/relationship-id-required');
240
+ const baseBoundaries = boundaryIndex(base.boundaries, 'base');
241
+ const headBoundaries = boundaryIndex(head.boundaries, 'head');
242
+
243
+ const baseRepository = normalizeRepository(base.meta?.repository);
244
+ const headRepository = normalizeRepository(head.meta?.repository);
245
+ if (baseRepository && headRepository && baseRepository.url !== headRepository.url) {
246
+ fail('delta/repository-mismatch', 'The snapshots name different repositories.', {
247
+ baseRepository: baseRepository.url,
248
+ headRepository: headRepository.url,
249
+ supportedFixes: ['compare snapshots from the same repository or remove repository evidence from both inputs'],
250
+ });
251
+ }
252
+ const proofLevel = baseRepository && headRepository
253
+ && evidence.baseVerified && evidence.headVerified
254
+ && /^[a-f0-9]{40}$/.test(baseRepository.revision)
255
+ && /^[a-f0-9]{40}$/.test(headRepository.revision)
256
+ ? 'revision-pinned'
257
+ : 'authored';
258
+
259
+ const components = compareEntities(baseComponents, headComponents, 'component', COMPONENT_FIELDS, (id, before, after) => ({
260
+ id,
261
+ baseLabel: before?.label,
262
+ headLabel: after?.label,
263
+ }));
264
+ const connections = compareEntities(baseConnections, headConnections, 'connection', CONNECTION_FIELDS, (id, before, after) => ({
265
+ id,
266
+ ...(before ? { base: { from: before.from, to: before.to, label: before.label || '' } } : {}),
267
+ ...(after ? { head: { from: after.from, to: after.to, label: after.label || '' } } : {}),
268
+ }));
269
+ const boundaries = compareEntities(baseBoundaries, headBoundaries, 'boundary', BOUNDARY_FIELDS, (_key, before, after) => ({
270
+ key: `${(after || before).kind}:${(after || before).label}`,
271
+ kind: (after || before).kind,
272
+ label: (after || before).label,
273
+ }));
274
+ const provenanceChanged = !equal(baseRepository, headRepository);
275
+
276
+ return {
277
+ schemaVersion: 1,
278
+ ok: true,
279
+ command: 'compare',
280
+ type: 'architecture',
281
+ comparatorVersion: COMPARATOR_VERSION,
282
+ canonicalVersion: CANONICAL_VERSION,
283
+ completeness: 'complete',
284
+ proofLevel,
285
+ base: {
286
+ title: base.meta?.title || '',
287
+ ...(evidence.baseRawSha256 ? { rawSha256: evidence.baseRawSha256 } : {}),
288
+ ...(evidence.baseSemanticSha256 ? { semanticSha256: evidence.baseSemanticSha256 } : {}),
289
+ ...(Number.isInteger(evidence.baseBytes) ? { bytes: evidence.baseBytes } : {}),
290
+ ...(baseRepository?.revision ? { revision: baseRepository.revision } : {}),
291
+ },
292
+ head: {
293
+ title: head.meta?.title || '',
294
+ ...(evidence.headRawSha256 ? { rawSha256: evidence.headRawSha256 } : {}),
295
+ ...(evidence.headSemanticSha256 ? { semanticSha256: evidence.headSemanticSha256 } : {}),
296
+ ...(Number.isInteger(evidence.headBytes) ? { bytes: evidence.headBytes } : {}),
297
+ ...(headRepository?.revision ? { revision: headRepository.revision } : {}),
298
+ },
299
+ summary: {
300
+ components: summaryFor(components, ['added', 'changed', 'evidenceChanged', 'removed', 'moved']),
301
+ connections: summaryFor(connections, ['added', 'changed', 'removed', 'rerouted']),
302
+ boundaries: summaryFor(boundaries, ['added', 'changed', 'removed', 'geometryChanged']),
303
+ presentationChanged: presentationChanged(base, head),
304
+ provenanceChanged,
305
+ },
306
+ changes: { components, connections, boundaries },
307
+ identity: {
308
+ components: 'components[].id',
309
+ connections: 'connections[].id (required)',
310
+ boundaries: 'boundaries[].kind + boundaries[].label (derived)',
311
+ },
312
+ view: { visualPreset: head.meta?.visual_preset || 'classic' },
313
+ limitations: [
314
+ 'Authored Architecture IR only; no runtime impact, causality, risk, or mergeability is inferred.',
315
+ 'Boundary identity is conservatively derived from kind + label.',
316
+ ],
317
+ };
318
+ }
319
+
320
+ function esc(value) {
321
+ return String(value ?? '').replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;').replaceAll('"', '&quot;').replaceAll("'", '&#39;');
322
+ }
323
+
324
+ const safeJson = (value) => JSON.stringify(value, null, 2).replaceAll('<', '\\u003c').replaceAll('>', '\\u003e').replaceAll('&', '\\u0026');
325
+
326
+ export function extractArchitectureSvg(html) {
327
+ const match = html.match(/<svg viewBox="0 0 [^"]+" role="img"[\s\S]*?<\/svg>/);
328
+ if (!match) fail('delta/svg-missing', 'A validated Architecture artifact did not contain its primary SVG.');
329
+ return match[0];
330
+ }
331
+
332
+ export function extractArtifactCss(html) {
333
+ const match = html.match(/<style>([\s\S]*?)<\/style>/);
334
+ if (!match) fail('delta/css-missing', 'A validated Architecture artifact did not contain its stylesheet.');
335
+ return match[1];
336
+ }
337
+
338
+ function changeMap(changes) {
339
+ return new Map(changes.map((change) => [change.id, change]));
340
+ }
341
+
342
+ function boundaryChangeMap(changes) {
343
+ return new Map(changes.map((change) => [`${change.kind}:${esc(change.label)}`, change]));
344
+ }
345
+
346
+ function addState(tag, change, side, forcedState) {
347
+ const append = (attributes) => tag.endsWith('/>')
348
+ ? tag.replace(/\/>$/, `${attributes}/>`)
349
+ : tag.replace(/>$/, `${attributes}>`);
350
+ if (!change && !forcedState) return append(' data-delta-state="same"');
351
+ let state = forcedState || change.status;
352
+ if (change?.status === 'added' && side === 'base') state = 'same';
353
+ if (change?.status === 'removed' && side === 'head') state = 'same';
354
+ const classes = change?.classifications?.join(',') || '';
355
+ return append(` data-delta-state="${esc(state)}"${classes ? ` data-delta-classifications="${esc(classes)}"` : ''}`);
356
+ }
357
+
358
+ function markerFor(state) {
359
+ return ({ added: '+', removed: '−', changed: '~', moved: '↔', 'moved-from': '↔', rerouted: '↔', 'geometry-changed': '↔', 'evidence-changed': 'E' })[state] || '';
360
+ }
361
+
362
+ function addNodeMarker(group, state) {
363
+ const symbol = markerFor(state);
364
+ if (!symbol) return group;
365
+ const box = group.match(/<rect x="([^"]+)" y="([^"]+)" width="([^"]+)"/);
366
+ if (!box) return group;
367
+ const x = Number(box[1]) + Number(box[3]) - 9;
368
+ const y = Number(box[2]) + 9;
369
+ return group.replace(/<\/g>$/, `\n <g class="delta-node-marker" aria-hidden="true"><circle cx="${x}" cy="${y}" r="8"/><text x="${x}" y="${y + 3}" text-anchor="middle">${symbol}</text></g>\n </g>`);
370
+ }
371
+
372
+ function prefixSvgIds(svg, prefix) {
373
+ const ids = [...svg.matchAll(/\sid="([^"]+)"/g)].map((match) => match[1]);
374
+ let result = svg.replace(/(\s)id="([^"]+)"/g, (_match, space, id) => `${space}id="${prefix}-${id}"`);
375
+ for (const id of ids) {
376
+ result = result.replaceAll(`url(#${id})`, `url(#${prefix}-${id})`).replaceAll(`href="#${id}"`, `href="#${prefix}-${id}"`);
377
+ }
378
+ result = result.replace(/aria-labelledby="([^"]+)"/g, (_match, value) => `aria-labelledby="${value.split(/\s+/).map((id) => `${prefix}-${id}`).join(' ')}"`);
379
+ return result;
380
+ }
381
+
382
+ function staticize(svg) {
383
+ return svg.replaceAll('tabindex="0" role="button"', 'role="group"').replaceAll(' aria-pressed="false"', '').replaceAll('aria-label="Focus ', 'aria-label="');
384
+ }
385
+
386
+ function nodeGroupRanges(svg) {
387
+ const ranges = [];
388
+ const opener = /<g\s+[^>]*\bdata-node-id="([^"]+)"[^>]*>/g;
389
+ let open;
390
+ while ((open = opener.exec(svg))) {
391
+ const tags = /<\/?g\b[^>]*>/g;
392
+ tags.lastIndex = open.index;
393
+ let depth = 0;
394
+ let tag;
395
+ while ((tag = tags.exec(svg))) {
396
+ depth += tag[0].startsWith('</') ? -1 : 1;
397
+ if (depth === 0) {
398
+ ranges.push({ id: open[1], start: open.index, end: tags.lastIndex });
399
+ opener.lastIndex = tags.lastIndex;
400
+ break;
401
+ }
402
+ }
403
+ }
404
+ return ranges;
405
+ }
406
+
407
+ function transformNodeGroups(svg, transform) {
408
+ const ranges = nodeGroupRanges(svg);
409
+ let cursor = 0;
410
+ const parts = [];
411
+ for (const range of ranges) {
412
+ parts.push(svg.slice(cursor, range.start));
413
+ parts.push(transform(svg.slice(range.start, range.end), range.id));
414
+ cursor = range.end;
415
+ }
416
+ parts.push(svg.slice(cursor));
417
+ return parts.join('');
418
+ }
419
+
420
+ const BOUNDARY_FRAME_RE = /<rect data-graph-role="structural-frame"[^>]*data-composition-frame-kind="([^"]+)"[^>]*data-composition-frame-label="([^"]+)"[^>]*\/>/g;
421
+ const BOUNDARY_LABEL_RE = /<g data-graph-role="structural-frame-label"[^>]*data-composition-frame-kind="([^"]+)"[^>]*data-composition-frame-label="([^"]+)"[^>]*>[\s\S]*?<\/g>/g;
422
+
423
+ function boundaryElements(svg) {
424
+ const collect = (pattern, part) => [...svg.matchAll(pattern)].map((match) => ({
425
+ start: match.index,
426
+ end: match.index + match[0].length,
427
+ markup: match[0],
428
+ key: `${match[1]}:${match[2]}`,
429
+ part,
430
+ }));
431
+ return [
432
+ ...collect(BOUNDARY_FRAME_RE, 'frame'),
433
+ ...collect(BOUNDARY_LABEL_RE, 'label'),
434
+ ].sort((left, right) => left.start - right.start);
435
+ }
436
+
437
+ function transformBoundaryElements(svg, transform) {
438
+ let cursor = 0;
439
+ const parts = [];
440
+ for (const element of boundaryElements(svg)) {
441
+ parts.push(svg.slice(cursor, element.start));
442
+ parts.push(transform(element.markup, element.key, element.part));
443
+ cursor = element.end;
444
+ }
445
+ parts.push(svg.slice(cursor));
446
+ return parts.join('');
447
+ }
448
+
449
+ export function annotateArchitectureSideSvg(svg, receipt, side) {
450
+ const nodes = changeMap(receipt.changes.components);
451
+ const edges = changeMap(receipt.changes.connections);
452
+ const boundaries = boundaryChangeMap(receipt.changes.boundaries);
453
+ let result = transformBoundaryElements(svg, (markup, key, part) => {
454
+ const change = boundaries.get(key);
455
+ if (!change) return markup;
456
+ if (part === 'frame') {
457
+ return addState(markup, change, side)
458
+ .replace(/\/>$/, ` data-delta-boundary-key="${esc(change.key)}"/>`);
459
+ }
460
+ return markup.replace(
461
+ /<text[^>]*>/,
462
+ (tag) => tag.replace(/>$/, ` data-delta-state="${change.status}" data-delta-boundary-state="${change.status}" data-delta-boundary-key="${esc(change.key)}">`),
463
+ );
464
+ });
465
+ result = transformNodeGroups(result, (group, id) => {
466
+ const change = nodes.get(id);
467
+ if ((side === 'base' && change?.status === 'added') || (side === 'head' && change?.status === 'removed')) return group;
468
+ const tagged = group.replace(/^<g[^>]+>/, (tag) => addState(tag, change, side));
469
+ return addNodeMarker(tagged, change?.status);
470
+ });
471
+ result = result.replace(/<(?:path|g)\s+[^>]*\bdata-edge-id="([^"]+)"[^>]*>/g, (tag, id) => addState(tag, edges.get(id), side));
472
+ return prefixSvgIds(staticize(result), side);
473
+ }
474
+
475
+ function elementById(svg, kind, id) {
476
+ const safe = id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
477
+ if (kind === 'node') {
478
+ const range = nodeGroupRanges(svg).find((candidate) => candidate.id === id);
479
+ return range ? svg.slice(range.start, range.end) : '';
480
+ }
481
+ const path = svg.match(new RegExp(`<path\\s+[^>]*\\bdata-edge-id="${safe}"[^>]*/>`))?.[0] || '';
482
+ const label = svg.match(new RegExp(`<g\\s+[^>]*\\bdata-edge-id="${safe}"[^>]*>[\\s\\S]*?<\\/g>`))?.[0] || '';
483
+ return [path, label].filter(Boolean).join('\n');
484
+ }
485
+
486
+ function forceElementState(markup, state, classifications = []) {
487
+ let result;
488
+ if (markup.includes('data-node-id=')) {
489
+ result = markup.replace(/^<g\s+[^>]*>/, (tag) => addState(tag, { classifications }, 'delta', state));
490
+ } else {
491
+ result = markup
492
+ .replace(/<path\s+[^>]*\bdata-edge-id="[^"]+"[^>]*\/>/g, (tag) => addState(tag, { classifications }, 'delta', state))
493
+ .replace(/<g\s+[^>]*\bdata-edge-id="[^"]+"[^>]*>/g, (tag) => addState(tag, { classifications }, 'delta', state));
494
+ }
495
+ result = result.replace(/\bid="node-/, 'id="base-node-');
496
+ if (markup.includes('data-node-id=')) result = addNodeMarker(result, state);
497
+ return result;
498
+ }
499
+
500
+ function boundaryMarkupByKey(svg, key) {
501
+ return boundaryElements(svg)
502
+ .filter((element) => element.key === key)
503
+ .map((element) => element.markup)
504
+ .join('\n');
505
+ }
506
+
507
+ function boundaryMarkupParts(markup) {
508
+ const parts = { frame: '', label: '' };
509
+ for (const element of boundaryElements(markup)) parts[element.part] = element.markup;
510
+ return parts;
511
+ }
512
+
513
+ function forceBoundaryState(markup, state, key, classifications = []) {
514
+ return markup
515
+ .replace(/^<rect[^>]+\/>/, (tag) => addState(tag, { classifications }, 'delta', state).replace(/\/>$/, ` data-delta-boundary-key="${esc(key)}"/>`))
516
+ .replace(
517
+ /<rect data-graph-role="structural-frame-label-mask"[^>]*\/>/,
518
+ (tag) => addState(tag, { classifications }, 'delta', state)
519
+ .replace(/\/>$/, ` data-delta-boundary-state="${state}" data-delta-boundary-mask-key="${esc(key)}"/>`),
520
+ )
521
+ .replace(/<text[^>]*>/, (tag) => tag.replace(/>$/, ` data-delta-state="${state}" data-delta-boundary-state="${state}" data-delta-boundary-key="${esc(key)}">`));
522
+ }
523
+
524
+ function viewBoxSize(svg) {
525
+ const match = svg.match(/viewBox="0 0 ([\d.]+) ([\d.]+)"/);
526
+ return match ? [Number(match[1]), Number(match[2])] : [0, 0];
527
+ }
528
+
529
+ function edgeSymbolMarkup(markup, state) {
530
+ const symbol = markerFor(state);
531
+ const point = markup.match(/data-composition-points="([\d.-]+),([\d.-]+)/);
532
+ const edgeId = markup.match(/\bdata-edge-id="([^"]+)"/)?.[1];
533
+ if (!symbol || !point) return '';
534
+ return `<text class="delta-edge-marker" data-delta-state="${state}"${edgeId ? ` data-edge-id="${esc(edgeId)}"` : ''} x="${Number(point[1]) + 9}" y="${Number(point[2]) - 7}" aria-hidden="true">${symbol}</text>`;
535
+ }
536
+
537
+ function boundarySymbolMarkup(markup, state) {
538
+ const symbol = markerFor(state);
539
+ const frame = markup.match(/<rect[^>]*\bx="([\d.-]+)"\s+y="([\d.-]+)"\s+width="([\d.-]+)"/);
540
+ if (!symbol || !frame) return '';
541
+ const x = Number(frame[1]) + Number(frame[3]) - 12;
542
+ const y = Number(frame[2]) + 16;
543
+ return `<text class="delta-boundary-marker" data-delta-state="${state}" x="${x}" y="${y}" text-anchor="middle" aria-hidden="true">${symbol}</text>`;
544
+ }
545
+
546
+ export function buildDeltaSvg(baseSvg, headSvg, receipt) {
547
+ const [baseW, baseH] = viewBoxSize(baseSvg);
548
+ const [headW, headH] = viewBoxSize(headSvg);
549
+ const nodes = changeMap(receipt.changes.components);
550
+ const edges = changeMap(receipt.changes.connections);
551
+ const boundaries = boundaryChangeMap(receipt.changes.boundaries);
552
+ const baseNodePhantoms = [];
553
+ const baseEdgePhantoms = [];
554
+ const baseBoundaryFramePhantoms = [];
555
+ const baseBoundaryLabelPhantoms = [];
556
+ const edgeMarkers = [];
557
+ const boundaryMarkers = [];
558
+
559
+ for (const change of nodes.values()) {
560
+ if (change.status === 'removed') baseNodePhantoms.push(forceElementState(elementById(baseSvg, 'node', change.id), 'removed', change.classifications));
561
+ else if (change.classifications.includes('geometry')) baseNodePhantoms.push(forceElementState(elementById(baseSvg, 'node', change.id), 'moved-from', change.classifications));
562
+ }
563
+ for (const change of edges.values()) {
564
+ if (change.status === 'removed' || change.classifications.includes('topology')) {
565
+ const phantom = forceElementState(elementById(baseSvg, 'edge', change.id), 'removed', change.classifications);
566
+ baseEdgePhantoms.push(phantom);
567
+ edgeMarkers.push(edgeSymbolMarkup(phantom, 'removed'));
568
+ } else if (change.classifications.includes('geometry')) {
569
+ const phantom = forceElementState(elementById(baseSvg, 'edge', change.id), 'moved-from', change.classifications);
570
+ baseEdgePhantoms.push(phantom);
571
+ edgeMarkers.push(edgeSymbolMarkup(phantom, 'moved-from'));
572
+ }
573
+ }
574
+ for (const change of boundaries.values()) {
575
+ const renderedKey = `${change.kind}:${esc(change.label)}`;
576
+ if (change.status === 'removed') {
577
+ const phantom = forceBoundaryState(boundaryMarkupByKey(baseSvg, renderedKey), 'removed', change.key, change.classifications);
578
+ const parts = boundaryMarkupParts(phantom);
579
+ baseBoundaryFramePhantoms.push(parts.frame);
580
+ baseBoundaryLabelPhantoms.push(parts.label);
581
+ boundaryMarkers.push(boundarySymbolMarkup(phantom, 'removed'));
582
+ }
583
+ else if (change.status === 'changed' || change.status === 'geometry-changed') {
584
+ const phantom = forceBoundaryState(boundaryMarkupByKey(baseSvg, renderedKey), 'moved-from', change.key, change.classifications);
585
+ const parts = boundaryMarkupParts(phantom);
586
+ baseBoundaryFramePhantoms.push(parts.frame);
587
+ baseBoundaryLabelPhantoms.push(parts.label);
588
+ }
589
+ }
590
+
591
+ let delta = annotateArchitectureSideSvg(headSvg, receipt, 'head');
592
+ delta = delta.replace(/^<svg[^>]+>/, (tag) => tag.replace(/viewBox="[^"]+"/, `viewBox="0 0 ${Math.max(baseW, headW) + 24} ${Math.max(baseH, headH) + 24}"`));
593
+ delta = delta.replace(' <!-- Boundaries (behind everything) -->', ` <!-- Baseline boundary frame phantoms -->\n${baseBoundaryFramePhantoms.filter(Boolean).join('\n')}\n\n <!-- Boundaries (behind everything) -->`);
594
+ delta = delta.replace(' <!-- Connection paths (before components for correct z-order) -->', ` <!-- Baseline relationship phantoms -->\n${baseEdgePhantoms.join('\n')}\n\n <!-- Connection paths (before components for correct z-order) -->`);
595
+ delta = delta.replace(' <!-- Components -->', ` <!-- Baseline boundary label phantoms (below current components) -->\n${baseBoundaryLabelPhantoms.filter(Boolean).join('\n')}\n\n <!-- Baseline removed and move-from component phantoms -->\n${baseNodePhantoms.join('\n')}\n\n <!-- Components -->`);
596
+
597
+ for (const change of edges.values()) {
598
+ if (change.status === 'added' || change.status === 'changed' || change.status === 'rerouted') {
599
+ const current = elementById(delta, 'edge', change.id);
600
+ edgeMarkers.push(edgeSymbolMarkup(current, change.status === 'changed' && change.classifications.includes('topology') ? 'added' : change.status));
601
+ }
602
+ }
603
+ for (const change of boundaries.values()) {
604
+ if (!['added', 'changed', 'geometry-changed'].includes(change.status)) continue;
605
+ const renderedKey = `${change.kind}:${esc(change.label)}`;
606
+ boundaryMarkers.push(boundarySymbolMarkup(boundaryMarkupByKey(delta, renderedKey), change.status));
607
+ }
608
+ delta = delta.replace(' <!-- Legend -->', ` <!-- Delta relationship symbols -->\n${edgeMarkers.filter(Boolean).join('\n')}\n\n <!-- Delta boundary symbols -->\n${boundaryMarkers.filter(Boolean).join('\n')}\n\n <!-- Legend -->`);
609
+ return prefixSvgIds(staticize(delta), 'delta');
610
+ }
611
+
612
+ export function architectureDeltaChangeRows(receipt) {
613
+ const rows = [];
614
+ for (const change of receipt.changes.components) rows.push({ ...change, kind: 'Component', kindKey: 'component', key: `component:${change.id}`, id: change.id });
615
+ for (const change of receipt.changes.connections) rows.push({ ...change, kind: 'Relationship', kindKey: 'relationship', key: `relationship:${change.id}`, id: change.id });
616
+ for (const change of receipt.changes.boundaries) rows.push({ ...change, kind: 'Boundary', kindKey: 'boundary', key: `boundary:${change.key}`, id: change.key });
617
+ return rows.sort((left, right) => codepointOrder(`${left.status}:${left.kind}:${left.id}`, `${right.status}:${right.kind}:${right.id}`));
618
+ }
619
+
620
+ function reviewPrimaryStates(row) {
621
+ if (row.kindKey === 'component' && row.classifications.includes('geometry')) return [row.status, 'moved-from'];
622
+ if (row.kindKey === 'relationship' && row.status === 'changed' && row.classifications.includes('topology')) return ['changed', 'removed'];
623
+ if (row.kindKey === 'relationship' && row.classifications.includes('geometry')) return ['moved-from', row.status];
624
+ if (row.kindKey === 'boundary' && ['changed', 'geometry-changed'].includes(row.status)) return [row.status, 'moved-from'].sort();
625
+ return [row.status];
626
+ }
627
+
628
+ function reviewIdentity(row) {
629
+ const attribute = row.kindKey === 'component' ? 'data-node-id' : row.kindKey === 'relationship' ? 'data-edge-id' : 'data-delta-boundary-key';
630
+ return { attribute, value: esc(row.id) };
631
+ }
632
+
633
+ function reviewTargetTags(deltaMarkup, row) {
634
+ const { attribute, value } = reviewIdentity(row);
635
+ const safeValue = value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
636
+ const identity = new RegExp(`\\b${attribute}="${safeValue}"`);
637
+ return [...deltaMarkup.matchAll(/<([a-z][\w:-]*)\s+[^>]*>/g)]
638
+ .map((match) => ({ name: match[1], tag: match[0] }))
639
+ .filter(({ tag }) => identity.test(tag));
640
+ }
641
+
642
+ function primaryReviewTags(deltaMarkup, row) {
643
+ const tagName = row.kindKey === 'component' ? 'g' : row.kindKey === 'relationship' ? 'path' : 'rect';
644
+ return reviewTargetTags(deltaMarkup, row).filter(({ name }) => name === tagName).map(({ tag }) => tag);
645
+ }
646
+
647
+ function reviewTargetSignature(tags) {
648
+ return tags.map(({ name, tag }) => {
649
+ const state = tag.match(/\bdata-delta-state="([^"]+)"/)?.[1] || '';
650
+ const classifications = tag.match(/\bdata-delta-classifications="([^"]*)"/)?.[1] || '';
651
+ return `${name}:${state}:${classifications}`;
652
+ }).sort(codepointOrder).join('|');
653
+ }
654
+
655
+ function expectedReviewTargetSignature(row) {
656
+ const classifications = row.classifications.join(',');
657
+ const descriptors = [];
658
+ if (row.kindKey === 'component') {
659
+ for (const state of reviewPrimaryStates(row)) descriptors.push(`g:${state}:${classifications}`);
660
+ } else if (row.kindKey === 'boundary') {
661
+ for (const state of reviewPrimaryStates(row)) {
662
+ descriptors.push(`rect:${state}:${classifications}`, `text:${state}:`);
663
+ }
664
+ } else {
665
+ const forms = row.status === 'added'
666
+ ? [{ state: 'added', marker: 'added', label: row.head?.label }]
667
+ : row.status === 'removed'
668
+ ? [{ state: 'removed', marker: 'removed', label: row.base?.label }]
669
+ : row.classifications.includes('topology')
670
+ ? [
671
+ { state: 'removed', marker: 'removed', label: row.base?.label },
672
+ { state: 'changed', marker: 'added', label: row.head?.label },
673
+ ]
674
+ : row.classifications.includes('geometry')
675
+ ? [
676
+ { state: 'moved-from', marker: 'moved-from', label: row.base?.label },
677
+ { state: row.status, marker: row.status, label: row.head?.label },
678
+ ]
679
+ : [{ state: 'changed', marker: 'changed', label: row.head?.label }];
680
+ for (const form of forms) {
681
+ descriptors.push(`path:${form.state}:${classifications}`, `text:${form.marker}:`);
682
+ if (form.label) descriptors.push(`g:${form.state}:${classifications}`);
683
+ }
684
+ }
685
+ return descriptors.sort(codepointOrder).join('|');
686
+ }
687
+
688
+ const total = (summary, key) => summary.components[key] + summary.connections[key] + summary.boundaries[key];
689
+
690
+ export function renderArchitectureDeltaHtml({ receipt, baseSvg, deltaSvg, headSvg, baseHtml = '', headHtml = '', artifactCss }) {
691
+ const rows = architectureDeltaChangeRows(receipt);
692
+ const changed = total(receipt.summary, 'changed');
693
+ const proof = receipt.proofLevel === 'revision-pinned' ? 'REVISION-PINNED INPUTS' : 'AUTHORED SNAPSHOTS';
694
+ const rowHtml = rows.length ? rows.map((row, index) => {
695
+ const label = row.headLabel || row.baseLabel || row.head?.label || row.base?.label || row.label || row.id;
696
+ const targetSignature = expectedReviewTargetSignature(row);
697
+ return `<li data-change-status="${esc(row.status)}"><button class="change-row" type="button" data-change-index="${index}" data-change-key="${esc(row.key)}" data-change-kind="${esc(row.kindKey)}" data-change-id="${esc(row.id)}" data-change-label="${esc(label)}" data-change-status="${esc(row.status)}" data-change-classifications="${esc(row.classifications.join(', '))}" data-change-target-signature="${esc(targetSignature)}"><span class="token">${esc(markerFor(row.status) || '~')}</span><span>${esc(row.kind)}</span><strong>${esc(label)}</strong><code>${esc(row.id)}</code><span>${esc(row.classifications.join(', '))}</span><span>${esc(row.changedFields.join(', ') || 'identity')}</span></button></li>`;
698
+ }).join('\n') : '<li class="empty">No authored architecture changes.</li>';
699
+ const baseView = baseHtml
700
+ ? `<iframe class="snapshot-frame" title="Before architecture explorer" srcdoc="${esc(baseHtml)}"></iframe>`
701
+ : baseSvg;
702
+ const headView = headHtml
703
+ ? `<iframe class="snapshot-frame" title="After architecture explorer" srcdoc="${esc(headHtml)}"></iframe>`
704
+ : headSvg;
705
+ const html = `<!doctype html>
706
+ <html lang="en" data-theme="dark" data-preset="${esc(receipt.view.visualPreset)}">
707
+ <head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${esc(receipt.head.title)} Architecture Delta</title>
708
+ <style>
709
+ ${artifactCss}
710
+ :root{color-scheme:dark;--d-add:#34d399;--d-remove:#fb7185;--d-change:#fbbf24;--d-move:#7dd3fc;--d-focus:#7dd3fc;--d-ink:#e6edf5;--d-muted:#8aa0b5;--d-line:#25384a}
711
+ *{box-sizing:border-box}body{margin:0;overflow-x:hidden;background:#071019;color:var(--d-ink);font-family:"JetBrains Mono",ui-monospace,SFMono-Regular,Menlo,monospace}.proof-page{width:min(1600px,calc(100vw - 64px));margin:auto;padding:30px 0 42px}.proof-head{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:28px;align-items:end;padding-bottom:20px;border-bottom:1px solid var(--d-line)}.eyebrow{margin:0 0 8px;color:#7dd3fc;font:700 11px/1.2 ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.14em}.proof-head h1{margin:0;font-size:clamp(32px,4vw,56px);line-height:.96;letter-spacing:-.04em}.subtitle{margin:12px 0 0;color:var(--d-muted);font-size:14px}.metrics{display:flex;gap:9px}.metric{min-width:86px;padding:11px 13px;border:1px solid var(--d-line);border-radius:8px;background:#0b1722}.metric strong{display:block;font:700 23px/1 ui-monospace,SFMono-Regular,Menlo,monospace}.metric span{display:block;margin-top:6px;color:var(--d-muted);font:700 9px/1 ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.1em}.add strong{color:var(--d-add)}.remove strong{color:var(--d-remove)}.change strong{color:var(--d-change)}
712
+ .proof-tools{display:flex;align-items:center;justify-content:space-between;gap:20px;margin:16px 0 10px}.view-switch{display:inline-flex;padding:3px;border:1px solid var(--d-line);border-radius:8px;background:#0a141e}.view-switch button,.utility,.review-step{border:0;border-radius:6px;background:transparent;color:var(--d-muted);padding:8px 14px;font:700 11px/1 ui-monospace,SFMono-Regular,Menlo,monospace;cursor:pointer}.view-switch button[aria-selected="true"]{background:#173047;color:#fff}.utility{border:1px solid var(--d-line)}.view-switch button:focus-visible,.utility:focus-visible,.review-step:focus-visible,.change-row:focus-visible{outline:2px solid var(--d-focus);outline-offset:2px}.utility:disabled,.review-step:disabled{cursor:not-allowed;opacity:.45}.legend{display:flex;gap:16px;color:var(--d-muted);font:650 10px/1 ui-monospace,SFMono-Regular,Menlo,monospace}.legend span{display:inline-flex;align-items:center;gap:6px}.legend i{width:22px;border-top:3px solid currentColor}.legend .add{color:var(--d-add)}.legend .remove{color:var(--d-remove)}.legend .remove i{border-top-style:dashed}.legend .change{color:var(--d-change)}.legend .change i{border-top-style:dotted}.legend .move{color:var(--d-move)}.legend .move i{border-top-style:double}
713
+ .review-strip{display:grid;grid-template-columns:auto auto auto auto minmax(0,1fr);align-items:center;gap:5px;margin:0 0 10px;padding:7px 8px;border-block:1px solid var(--d-line);font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.review-step{min-height:34px;border:1px solid var(--d-line);padding-inline:11px}.review-step[aria-pressed="true"]{border-color:var(--d-focus);color:var(--d-ink)}.review-status{min-width:0;padding-left:9px;color:var(--d-muted);font-size:10px;line-height:1.3;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.review-status strong{color:var(--d-ink);font-weight:750}.review-status[data-state="unavailable"]{color:var(--d-remove)}
714
+ .canvas{overflow:hidden;border:1px solid var(--d-line);border-radius:10px;background:#09141e;padding:12px;min-height:520px}.canvas svg{display:block;width:100%;height:auto;max-height:72vh}.canvas[hidden]{display:none}.snapshot-frame{display:block;width:100%;height:min(76vh,920px);min-height:620px;border:0;border-radius:6px;background:#071019}.canvas[data-view="base"],.canvas[data-view="head"]{padding:0}.canvas[data-view="delta"] [data-delta-state="same"]{opacity:.38}.canvas[data-delta-review-active]{--review-same-opacity:.14;--review-change-opacity:.28}.canvas[data-delta-review-active] [data-delta-state="same"]{opacity:var(--review-same-opacity)!important}.canvas[data-delta-review-active] [data-delta-state]:not([data-delta-state="same"]):not([data-delta-review-current]){opacity:var(--review-change-opacity)!important}.canvas[data-delta-review-active] [data-delta-review-current]{opacity:1!important;transition:opacity .16s ease-out}g[data-node-id][data-delta-state="added"]>rect:last-of-type{stroke:var(--d-add)!important;stroke-width:3!important}g[data-node-id][data-delta-state="removed"]>rect:last-of-type{stroke:var(--d-remove)!important;stroke-width:3!important;stroke-dasharray:7 5}g[data-node-id][data-delta-state="changed"]>rect:last-of-type{stroke:var(--d-change)!important;stroke-width:3!important;stroke-dasharray:2 3}g[data-node-id][data-delta-state="moved"]>rect:last-of-type,g[data-node-id][data-delta-state="moved-from"]>rect:last-of-type{stroke:var(--d-move)!important;stroke-width:3!important;stroke-dasharray:8 3 2 3}g[data-node-id][data-delta-state="moved-from"],path[data-delta-state="moved-from"]{opacity:.42}path[data-delta-state="added"]{stroke:var(--d-add)!important;stroke-width:3!important}path[data-delta-state="removed"]{stroke:var(--d-remove)!important;stroke-width:3!important;stroke-dasharray:7 5!important}path[data-delta-state="changed"]{stroke:var(--d-change)!important;stroke-width:3!important;stroke-dasharray:2 3!important}path[data-delta-state="rerouted"],path[data-delta-state="moved-from"]{stroke:var(--d-move)!important;stroke-width:2.5!important;stroke-dasharray:8 3 2 3!important}.delta-node-marker circle{fill:#071019;stroke:currentColor;stroke-width:1.5}.delta-node-marker text,.delta-edge-marker,.delta-boundary-marker{fill:currentColor;font:800 9px ui-monospace,SFMono-Regular,Menlo,monospace}[data-delta-state="added"] .delta-node-marker,.delta-edge-marker[data-delta-state="added"],.delta-boundary-marker[data-delta-state="added"]{color:var(--d-add)}[data-delta-state="removed"] .delta-node-marker,.delta-edge-marker[data-delta-state="removed"],.delta-boundary-marker[data-delta-state="removed"]{color:var(--d-remove)}[data-delta-state="changed"] .delta-node-marker,.delta-edge-marker[data-delta-state="changed"],.delta-boundary-marker[data-delta-state="changed"]{color:var(--d-change)}[data-delta-state="moved"] .delta-node-marker,[data-delta-state="moved-from"] .delta-node-marker,.delta-edge-marker[data-delta-state="moved-from"],.delta-edge-marker[data-delta-state="rerouted"],.delta-boundary-marker[data-delta-state="geometry-changed"]{color:var(--d-move)}.delta-edge-marker,.delta-boundary-marker{paint-order:stroke;stroke:#071019;stroke-width:3px}
715
+ rect[data-graph-role="structural-frame"][data-delta-state="added"]{stroke:var(--d-add)!important;stroke-width:2.5!important}rect[data-graph-role="structural-frame"][data-delta-state="removed"]{stroke:var(--d-remove)!important;stroke-width:2.5!important;stroke-dasharray:7 5!important}rect[data-graph-role="structural-frame"][data-delta-state="changed"]{stroke:var(--d-change)!important;stroke-width:2.5!important;stroke-dasharray:2 3!important}rect[data-graph-role="structural-frame"][data-delta-state="moved-from"]{stroke:var(--d-move)!important;stroke-width:2!important;stroke-dasharray:8 3 2 3!important;opacity:.42}text[data-delta-boundary-state="added"]{fill:var(--d-add)!important}text[data-delta-boundary-state="removed"]{fill:var(--d-remove)!important}text[data-delta-boundary-state="changed"]{fill:var(--d-change)!important}text[data-delta-boundary-state="moved-from"]{fill:var(--d-move)!important;opacity:.55}
716
+ details{margin-top:12px;border:1px solid var(--d-line);border-radius:9px;background:#0a141e}summary{padding:13px 15px;cursor:pointer;font-weight:700}.changes{list-style:none;margin:0;padding:0 8px 8px}.changes li{border-top:1px solid rgba(138,160,181,.16)}.change-row{display:grid;grid-template-columns:30px 90px minmax(140px,1fr) minmax(100px,.7fr) minmax(120px,.8fr) minmax(140px,1.2fr);gap:10px;width:100%;margin:0;padding:9px 7px;border:0;border-radius:5px;background:transparent;color:inherit;font:inherit;font-size:11px;text-align:left;align-items:baseline;cursor:pointer}.change-row:hover{background:rgba(125,211,252,.06)}.change-row[aria-current="step"]{background:rgba(125,211,252,.1);box-shadow:inset 0 0 0 1px var(--d-focus)}.change-row:disabled{cursor:default}.token{font:800 13px/1 ui-monospace,SFMono-Regular,Menlo,monospace}.changes code,.change-row>span:last-child{color:var(--d-muted)}.proof-foot{display:flex;justify-content:space-between;gap:24px;margin-top:14px;color:var(--d-muted);font:650 10px/1.4 ui-monospace,SFMono-Regular,Menlo,monospace}
717
+ html[data-theme="dark"] body{background:#071019!important;background-image:none!important}html[data-theme="light"]{color-scheme:light;--d-ink:#10283c;--d-muted:#587187;--d-line:#c8d6e2;--d-focus:#006b8f}html[data-theme="light"] body{background:#eef3f7!important;background-image:none!important;color:var(--d-ink)}html[data-theme="light"] .metric,html[data-theme="light"] .view-switch,html[data-theme="light"] details{background:#fff}html[data-theme="light"] .canvas{background:#f8fbfd}html[data-theme="light"] .view-switch button[aria-selected="true"]{background:#dbeaf5;color:#10283c}html[data-theme="light"] .delta-node-marker circle{fill:#fff}html[data-preset="blueprint"] body{background-image:none!important}
718
+ @media(max-width:760px){.proof-page{width:100%;padding:12px}.proof-head{grid-template-columns:1fr;gap:14px;align-items:start}.proof-head h1{font-size:32px}.metrics{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));width:100%}.metric{min-width:0}.proof-tools{align-items:stretch;flex-wrap:wrap;gap:8px}.view-switch{display:flex;flex:1 1 100%}.view-switch button{flex:1;padding-inline:8px}.legend{flex-wrap:wrap;gap:8px}.proof-tools>div:last-child{margin-left:auto}.review-strip{grid-template-columns:auto auto auto auto}.review-status{grid-column:1/-1;padding:4px 2px 0}.canvas{min-height:0;padding:6px;overflow:auto}.canvas svg{min-width:720px;max-height:none}.snapshot-frame{min-width:720px}.changes{overflow-x:auto}.change-row{min-width:820px}.proof-foot{flex-direction:column;gap:4px}}
719
+ @media(prefers-reduced-motion:reduce){*{scroll-behavior:auto!important}.canvas[data-delta-review-active] [data-delta-review-current]{transition:none!important}}@media print{body{min-width:0;background:#fff;color:#111}.proof-page{width:100%;padding:0}.proof-tools,.review-strip,details{display:none!important}.canvas{display:none!important}.canvas[data-view="delta"]{display:block!important;border:0}.canvas[data-view="delta"] [data-delta-state="same"]{opacity:1!important;transition:none!important}.canvas[data-delta-review-active]{--review-same-opacity:1;--review-change-opacity:1}.canvas[data-delta-review-active] [data-delta-review-current]{opacity:1!important;transition:none!important}.proof-foot{color:#444}}
720
+ </style></head>
721
+ <body><main class="proof-page"><header class="proof-head"><div><p class="eyebrow">ARCHITECTURE DELTA · ${proof}</p><h1>See what changed<br>before you merge.</h1><p class="subtitle">${esc(receipt.base.title)} → ${esc(receipt.head.title)}</p></div><div class="metrics"><div class="metric add"><strong>${total(receipt.summary, 'added')}</strong><span>ADDED</span></div><div class="metric remove"><strong>${total(receipt.summary, 'removed')}</strong><span>REMOVED</span></div><div class="metric change"><strong>${changed}</strong><span>CHANGED</span></div></div></header>
722
+ <div class="proof-tools"><div class="view-switch" role="tablist" aria-label="Architecture snapshot"><button role="tab" data-target="base" aria-selected="false">Before</button><button role="tab" data-target="delta" aria-selected="true">Delta</button><button role="tab" data-target="head" aria-selected="false">After</button></div><div class="legend"><span class="add"><i></i>+ ADD</span><span class="remove"><i></i>− DEL</span><span class="change"><i></i>~ MOD</span><span class="move"><i></i>↔ MOVE</span></div><div><button class="utility" id="export-svg" type="button">Export SVG</button> <button class="utility" id="share-card" type="button">Share Card</button> <button class="utility" id="preset" type="button">Preset</button> <button class="utility" id="theme" type="button">Theme</button></div></div>
723
+ <nav class="review-strip" aria-label="Authored change review"><button class="review-step" id="review-overview" type="button" disabled>Overview</button><button class="review-step" id="review-previous" type="button" aria-label="Previous authored change" disabled>←</button><button class="review-step" id="review-play" type="button" aria-pressed="false"${rows.length ? '' : ' disabled'}>Review</button><button class="review-step" id="review-next" type="button" aria-label="Next authored change" disabled>→</button><div class="review-status" id="review-status" role="status" aria-live="polite">Overview · ${rows.length} authored changes</div></nav>
724
+ <section class="canvas" data-view="base" hidden>${baseView}</section><section class="canvas" data-view="delta">${deltaSvg}</section><section class="canvas" data-view="head" hidden>${headView}</section>
725
+ <details${rows.length <= 10 ? ' open' : ''}><summary>Exact authored changes · ${rows.length}</summary><ul class="changes">${rowHtml}</ul></details>
726
+ <footer class="proof-foot"><span>Stable IDs only · completeness: complete · ${proof}</span><span>Authored IR only · no risk or mergeability inference</span></footer></main>
727
+ <script id="archify-compare-receipt" type="application/json">${safeJson(receipt)}</script>
728
+ <script>(()=>{
729
+ const REVIEW_DWELL_MS = 1400;
730
+ const tabs = [...document.querySelectorAll('[role="tab"]')];
731
+ const views = [...document.querySelectorAll('[data-view]')];
732
+ const deltaCanvas = document.querySelector('[data-view="delta"]');
733
+ const rowButtons = [...document.querySelectorAll('.change-row')];
734
+ const overviewButton = document.querySelector('#review-overview');
735
+ const previousButton = document.querySelector('#review-previous');
736
+ const playButton = document.querySelector('#review-play');
737
+ const nextButton = document.querySelector('#review-next');
738
+ const status = document.querySelector('#review-status');
739
+ const receiptNode = document.querySelector('#archify-compare-receipt');
740
+ const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
741
+ const exportCss = ${safeJson(artifactCss)};
742
+ let activeIndex = -1;
743
+ let playbackToken = 0;
744
+ let playbackTimer = 0;
745
+ let playing = false;
746
+ let reviewAvailable = false;
747
+ let reviewSources = [];
748
+
749
+ function show(id) {
750
+ tabs.forEach((tab) => tab.setAttribute('aria-selected', String(tab.dataset.target === id)));
751
+ views.forEach((view) => { view.hidden = view.dataset.view !== id; });
752
+ }
753
+
754
+ function stopPlayback() {
755
+ playbackToken += 1;
756
+ if (playbackTimer) window.clearTimeout(playbackTimer);
757
+ playbackTimer = 0;
758
+ playing = false;
759
+ playButton.setAttribute('aria-pressed', 'false');
760
+ playButton.textContent = activeIndex === rowButtons.length - 1 && rowButtons.length ? 'Replay' : 'Review';
761
+ status.setAttribute('aria-live', 'polite');
762
+ }
763
+
764
+ function failReview() {
765
+ stopPlayback();
766
+ reviewAvailable = false;
767
+ activeIndex = -1;
768
+ deltaCanvas?.removeAttribute('data-delta-review-active');
769
+ deltaCanvas?.querySelectorAll('[data-delta-review-current]').forEach((element) => element.removeAttribute('data-delta-review-current'));
770
+ rowButtons.forEach((row) => {
771
+ row.disabled = true;
772
+ row.tabIndex = -1;
773
+ row.removeAttribute('aria-current');
774
+ });
775
+ [overviewButton, previousButton, playButton, nextButton].forEach((button) => { button.disabled = true; });
776
+ status.dataset.state = 'unavailable';
777
+ status.textContent = 'Review unavailable · compare identity mismatch';
778
+ }
779
+
780
+ function receiptRows(receipt) {
781
+ const definitions = [
782
+ { collection: 'components', kind: 'component', id: 'id', statuses: ['added', 'changed', 'evidence-changed', 'removed', 'moved'] },
783
+ { collection: 'connections', kind: 'relationship', id: 'id', statuses: ['added', 'changed', 'removed', 'rerouted'] },
784
+ { collection: 'boundaries', kind: 'boundary', id: 'key', statuses: ['added', 'changed', 'removed', 'geometry-changed'] },
785
+ ];
786
+ const rows = [];
787
+ for (const definition of definitions) {
788
+ const changes = receipt.changes[definition.collection];
789
+ if (!Array.isArray(changes)) return null;
790
+ for (const change of changes) {
791
+ if (!change || typeof change !== 'object' || Array.isArray(change)) return null;
792
+ const id = change[definition.id];
793
+ if (typeof id !== 'string' || !id || !definition.statuses.includes(change.status)) return null;
794
+ if (!Array.isArray(change.classifications) || !change.classifications.length || change.classifications.some((value) => typeof value !== 'string')) return null;
795
+ if (new Set(change.classifications).size !== change.classifications.length) return null;
796
+ if (!Array.isArray(change.changedFields) || change.changedFields.some((value) => typeof value !== 'string')) return null;
797
+ rows.push({ ...change, kind: definition.kind, key: definition.kind + ':' + id, id });
798
+ }
799
+ }
800
+ return rows;
801
+ }
802
+
803
+ function exactTargets(row) {
804
+ const attribute = row.dataset.changeKind === 'component'
805
+ ? 'data-node-id'
806
+ : row.dataset.changeKind === 'relationship'
807
+ ? 'data-edge-id'
808
+ : 'data-delta-boundary-key';
809
+ return [...deltaCanvas.querySelectorAll('[' + attribute + ']')]
810
+ .filter((element) => element.getAttribute(attribute) === row.dataset.changeId);
811
+ }
812
+
813
+ function targetSignature(matches) {
814
+ return matches.map((element) => {
815
+ const tag = element.tagName.toLowerCase();
816
+ const state = element.dataset.deltaState || '';
817
+ const classifications = element.dataset.deltaClassifications || '';
818
+ return tag + ':' + state + ':' + classifications;
819
+ }).sort().join('|');
820
+ }
821
+
822
+ function targetsMatch(source, row, matches) {
823
+ if (!source || !matches.length || !row.dataset.changeTargetSignature) return false;
824
+ if (row.dataset.changeKind !== source.kind || row.dataset.changeId !== source.id || row.dataset.changeStatus !== source.status) return false;
825
+ if (row.dataset.changeClassifications !== source.classifications.join(', ')) return false;
826
+ return row.dataset.changeTargetSignature === targetSignature(matches);
827
+ }
828
+
829
+ function validateReview() {
830
+ try {
831
+ if (document.querySelectorAll('#archify-compare-receipt').length !== 1 || !receiptNode) return false;
832
+ if (!deltaCanvas || !['base', 'delta', 'head'].every((id) => document.querySelectorAll('[data-view="' + id + '"]').length === 1)) return false;
833
+ if (deltaCanvas.children.length !== 1 || deltaCanvas.firstElementChild?.tagName.toLowerCase() !== 'svg') return false;
834
+ const receipt = JSON.parse(receiptNode.textContent);
835
+ if (!receipt || receipt.schemaVersion !== 1 || receipt.command !== 'compare' || receipt.completeness !== 'complete' || !receipt.changes) return false;
836
+ const sources = receiptRows(receipt);
837
+ if (!sources || sources.length !== rowButtons.length) return false;
838
+ const sourceByKey = new Map(sources.map((source) => [source.key, source]));
839
+ if (sourceByKey.size !== sources.length) return false;
840
+ const seen = new Set();
841
+ const orderedSources = [];
842
+ for (let index = 0; index < rowButtons.length; index += 1) {
843
+ const row = rowButtons[index];
844
+ const source = sourceByKey.get(row.dataset.changeKey);
845
+ if (!source || seen.has(source.key) || row.dataset.changeIndex !== String(index)) return false;
846
+ const matches = exactTargets(row);
847
+ if (!targetsMatch(source, row, matches)) return false;
848
+ seen.add(source.key);
849
+ orderedSources.push(source);
850
+ }
851
+ if (seen.size !== sourceByKey.size) return false;
852
+ reviewSources = orderedSources;
853
+ return true;
854
+ } catch {
855
+ return false;
856
+ }
857
+ }
858
+
859
+ function updateControls() {
860
+ overviewButton.disabled = !reviewAvailable || activeIndex < 0;
861
+ previousButton.disabled = !reviewAvailable || activeIndex <= 0;
862
+ nextButton.disabled = !reviewAvailable || activeIndex < 0 || activeIndex >= rowButtons.length - 1;
863
+ }
864
+
865
+ function selectChange(index, fromPlayback = false) {
866
+ if (!reviewAvailable || index < 0 || index >= rowButtons.length) return false;
867
+ const row = rowButtons[index];
868
+ const matches = exactTargets(row);
869
+ if (!targetsMatch(reviewSources[index], row, matches)) { failReview(); return false; }
870
+ show('delta');
871
+ deltaCanvas.querySelectorAll('[data-delta-review-current]').forEach((element) => element.removeAttribute('data-delta-review-current'));
872
+ matches.forEach((element) => element.setAttribute('data-delta-review-current', 'true'));
873
+ deltaCanvas.setAttribute('data-delta-review-active', 'true');
874
+ rowButtons.forEach((button, rowIndex) => {
875
+ button.tabIndex = rowIndex === index ? 0 : -1;
876
+ if (rowIndex === index) button.setAttribute('aria-current', 'step');
877
+ else button.removeAttribute('aria-current');
878
+ });
879
+ activeIndex = index;
880
+ updateControls();
881
+ const kind = row.dataset.changeKind.charAt(0).toUpperCase() + row.dataset.changeKind.slice(1);
882
+ status.textContent = String(index + 1).padStart(2, '0') + ' / ' + String(rowButtons.length).padStart(2, '0') + ' · ' + kind + ' · ' + row.dataset.changeLabel + ' [' + row.dataset.changeId + '] · ' + row.dataset.changeClassifications;
883
+ if (fromPlayback) status.setAttribute('aria-live', 'off');
884
+ return true;
885
+ }
886
+
887
+ function overview() {
888
+ if (!reviewAvailable) { failReview(); return; }
889
+ stopPlayback();
890
+ activeIndex = -1;
891
+ deltaCanvas.removeAttribute('data-delta-review-active');
892
+ deltaCanvas.querySelectorAll('[data-delta-review-current]').forEach((element) => element.removeAttribute('data-delta-review-current'));
893
+ rowButtons.forEach((row, index) => {
894
+ row.tabIndex = index === 0 ? 0 : -1;
895
+ row.removeAttribute('aria-current');
896
+ });
897
+ show('delta');
898
+ status.removeAttribute('data-state');
899
+ status.textContent = 'Overview · ' + rowButtons.length + ' authored changes';
900
+ updateControls();
901
+ }
902
+
903
+ function schedulePlayback(token) {
904
+ if (!playing || token !== playbackToken || activeIndex >= rowButtons.length - 1) {
905
+ if (activeIndex >= rowButtons.length - 1) stopPlayback();
906
+ return;
907
+ }
908
+ playbackTimer = window.setTimeout(() => {
909
+ if (!playing || token !== playbackToken) return;
910
+ if (!selectChange(activeIndex + 1, true)) return;
911
+ schedulePlayback(token);
912
+ }, REVIEW_DWELL_MS);
913
+ }
914
+
915
+ function startReview() {
916
+ if (!reviewAvailable) return;
917
+ if (playing) { stopPlayback(); return; }
918
+ stopPlayback();
919
+ if (!selectChange(0, false)) return;
920
+ if (reducedMotion.matches || rowButtons.length < 2) return;
921
+ playing = true;
922
+ const token = ++playbackToken;
923
+ playButton.textContent = 'Pause';
924
+ playButton.setAttribute('aria-pressed', 'true');
925
+ status.setAttribute('aria-live', 'off');
926
+ schedulePlayback(token);
927
+ }
928
+
929
+ function canonicalDeltaSvg() {
930
+ const source = deltaCanvas?.querySelector(':scope > svg');
931
+ if (!source) throw new Error('Canonical Delta SVG is unavailable.');
932
+ const clone = source.cloneNode(true);
933
+ clone.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
934
+ clone.removeAttribute('style');
935
+ clone.querySelectorAll('[data-delta-review-current]').forEach((element) => element.removeAttribute('data-delta-review-current'));
936
+ const style = document.createElementNS('http://www.w3.org/2000/svg', 'style');
937
+ style.textContent = exportCss + '\\n' +
938
+ '[data-delta-state="same"]{opacity:.38}' +
939
+ '[data-delta-state="added"]{--delta:#34d399}' +
940
+ '[data-delta-state="removed"]{--delta:#fb7185}' +
941
+ '[data-delta-state="changed"]{--delta:#fbbf24}' +
942
+ '[data-delta-state="moved"],[data-delta-state="moved-from"],[data-delta-state="rerouted"],[data-delta-state="geometry-changed"]{--delta:#7dd3fc}' +
943
+ 'g[data-node-id][data-delta-state]:not([data-delta-state="same"])>rect:last-of-type{stroke:var(--delta)!important;stroke-width:3!important}' +
944
+ 'path[data-delta-state]:not([data-delta-state="same"]){stroke:var(--delta)!important;stroke-width:3!important}' +
945
+ 'rect[data-graph-role="structural-frame"][data-delta-state]:not([data-delta-state="same"]){stroke:var(--delta)!important;stroke-width:2.5!important}' +
946
+ 'rect[data-graph-role="structural-frame"][data-delta-state="changed"]{stroke-dasharray:2 3!important}' +
947
+ '[data-delta-state="removed"],[data-delta-state="moved-from"]{stroke-dasharray:7 5!important}' +
948
+ '[data-delta-state="moved-from"]{opacity:.42}' +
949
+ '[data-delta-state]:not([data-delta-state="same"]) .delta-node-marker,.delta-edge-marker[data-delta-state],.delta-boundary-marker[data-delta-state]{color:var(--delta)}' +
950
+ 'text[data-delta-boundary-state="added"]{fill:#34d399!important}text[data-delta-boundary-state="removed"]{fill:#fb7185!important}text[data-delta-boundary-state="changed"]{fill:#fbbf24!important}text[data-delta-boundary-state="moved-from"]{fill:#7dd3fc!important;opacity:.55}' +
951
+ '.delta-node-marker circle{fill:#071019;stroke:currentColor;stroke-width:1.5}.delta-node-marker text,.delta-edge-marker,.delta-boundary-marker{fill:currentColor;font:800 9px ui-monospace,SFMono-Regular,Menlo,monospace}';
952
+ clone.insertBefore(style, clone.firstChild);
953
+ return new XMLSerializer().serializeToString(clone);
954
+ }
955
+
956
+ function artifactName(suffix) {
957
+ const title = String(JSON.parse(receiptNode.textContent).head.title || 'architecture-delta')
958
+ .toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'architecture-delta';
959
+ return title + suffix;
960
+ }
961
+
962
+ function downloadBlob(blob, name) {
963
+ const url = URL.createObjectURL(blob);
964
+ const link = document.createElement('a');
965
+ link.href = url;
966
+ link.download = name;
967
+ document.body.appendChild(link);
968
+ link.click();
969
+ link.remove();
970
+ window.setTimeout(() => URL.revokeObjectURL(url), 1000);
971
+ }
972
+
973
+ function recordExport(format, blob, dimensions) {
974
+ document.documentElement.dataset.archifyDeltaExport = JSON.stringify({
975
+ ok: true,
976
+ format,
977
+ bytes: blob.size,
978
+ mime: blob.type,
979
+ ...(dimensions || {}),
980
+ });
981
+ }
982
+
983
+ function exportCanonicalSvg() {
984
+ const blob = new Blob([canonicalDeltaSvg()], { type: 'image/svg+xml;charset=utf-8' });
985
+ downloadBlob(blob, artifactName('-architecture-delta.svg'));
986
+ recordExport('svg', blob);
987
+ return blob;
988
+ }
989
+
990
+ function loadSvgImage(blob) {
991
+ return new Promise((resolve, reject) => {
992
+ const url = URL.createObjectURL(blob);
993
+ const image = new Image();
994
+ image.onload = () => { URL.revokeObjectURL(url); resolve(image); };
995
+ image.onerror = () => { URL.revokeObjectURL(url); reject(new Error('Could not rasterize canonical Delta SVG.')); };
996
+ image.src = url;
997
+ });
998
+ }
999
+
1000
+ function pngBlob(canvas) {
1001
+ return new Promise((resolve, reject) => canvas.toBlob((blob) => {
1002
+ if (blob) resolve(blob);
1003
+ else reject(new Error('Canvas returned no Share Card PNG.'));
1004
+ }, 'image/png'));
1005
+ }
1006
+
1007
+ async function shareCard() {
1008
+ const receipt = JSON.parse(receiptNode.textContent);
1009
+ const canvas = document.createElement('canvas');
1010
+ canvas.width = 1200;
1011
+ canvas.height = 630;
1012
+ const ctx = canvas.getContext('2d');
1013
+ if (!ctx) throw new Error('2D canvas context unavailable for Architecture Delta Share Card.');
1014
+ ctx.fillStyle = '#071019';
1015
+ ctx.fillRect(0, 0, 1200, 630);
1016
+ ctx.fillStyle = '#0b1722';
1017
+ ctx.fillRect(34, 28, 1132, 574);
1018
+ ctx.strokeStyle = '#25384a';
1019
+ ctx.lineWidth = 2;
1020
+ ctx.strokeRect(34, 28, 1132, 574);
1021
+ ctx.font = '700 18px ui-monospace, SFMono-Regular, Menlo, monospace';
1022
+ ctx.fillStyle = '#7dd3fc';
1023
+ ctx.fillText('ARCHITECTURE DELTA', 66, 68);
1024
+ ctx.font = '700 34px ui-monospace, SFMono-Regular, Menlo, monospace';
1025
+ ctx.fillStyle = '#e6edf5';
1026
+ const title = String(receipt.head.title || 'Architecture').slice(0, 46) + ' Architecture Delta';
1027
+ ctx.fillText(title.slice(0, 58), 66, 112);
1028
+ ctx.font = '700 17px ui-monospace, SFMono-Regular, Menlo, monospace';
1029
+ ctx.fillStyle = '#8aa0b5';
1030
+ const componentLine = 'COMPONENTS +' + receipt.summary.components.added + ' ~' + receipt.summary.components.changed + ' −' + receipt.summary.components.removed;
1031
+ const connectionLine = 'CONNECTIONS +' + receipt.summary.connections.added + ' ~' + receipt.summary.connections.changed + ' −' + receipt.summary.connections.removed;
1032
+ const boundaryLine = 'BOUNDARY SCOPE +' + receipt.summary.boundaries.added + ' ~' + receipt.summary.boundaries.changed + ' −' + receipt.summary.boundaries.removed;
1033
+ ctx.fillText(componentLine, 66, 151);
1034
+ ctx.fillText(connectionLine, 420, 151);
1035
+ ctx.fillText(boundaryLine, 786, 151);
1036
+ ctx.font = '650 14px ui-monospace, SFMono-Regular, Menlo, monospace';
1037
+ const authoredChanges = ['components', 'connections', 'boundaries'].reduce((sum, collection) => {
1038
+ const summary = receipt.summary[collection];
1039
+ return sum + summary.added + summary.changed + summary.removed;
1040
+ }, 0);
1041
+ const movementSummary = '↔ moved ' + receipt.summary.components.moved + ' · rerouted ' + receipt.summary.connections.rerouted + ' · presentation ' + (receipt.summary.presentationChanged ? 'changed' : 'unchanged');
1042
+ const secondary = authoredChanges === 0
1043
+ ? 'No authored architecture changes · ' + movementSummary
1044
+ : movementSummary;
1045
+ ctx.fillText(secondary, 66, 181);
1046
+ const proofLine = receipt.proofLevel === 'revision-pinned'
1047
+ ? 'REV ' + String(receipt.base.revision).slice(0, 8) + ' → ' + String(receipt.head.revision).slice(0, 8) + ' · REVISION-PINNED INPUTS'
1048
+ : 'AUTHORED SNAPSHOTS';
1049
+ ctx.textAlign = 'right';
1050
+ ctx.fillText(proofLine, 1134, 181);
1051
+ ctx.textAlign = 'left';
1052
+ ctx.fillStyle = '#09141e';
1053
+ ctx.fillRect(66, 206, 1068, 358);
1054
+ ctx.strokeStyle = '#25384a';
1055
+ ctx.strokeRect(66, 206, 1068, 358);
1056
+ const svgBlob = new Blob([canonicalDeltaSvg()], { type: 'image/svg+xml;charset=utf-8' });
1057
+ const image = await loadSvgImage(svgBlob);
1058
+ const ratio = Math.min(1024 / image.width, 322 / image.height);
1059
+ const width = image.width * ratio;
1060
+ const height = image.height * ratio;
1061
+ ctx.drawImage(image, 600 - width / 2, 385 - height / 2, width, height);
1062
+ ctx.font = '650 12px ui-monospace, SFMono-Regular, Menlo, monospace';
1063
+ ctx.fillStyle = '#8aa0b5';
1064
+ ctx.fillText('Stable authored IDs · static complete Delta · no risk or mergeability inference', 66, 588);
1065
+ return pngBlob(canvas);
1066
+ }
1067
+
1068
+ async function downloadShareCard() {
1069
+ const blob = await shareCard();
1070
+ downloadBlob(blob, artifactName('-architecture-delta-share-card.png'));
1071
+ recordExport('share-card', blob, { width: 1200, height: 630 });
1072
+ return blob;
1073
+ }
1074
+
1075
+ window.Archify = window.Archify || {};
1076
+ window.Archify.deltaExport = { canonicalSvg: canonicalDeltaSvg, shareCard, exportSvg: exportCanonicalSvg, downloadShareCard };
1077
+ window.Archify.exportMenu = {
1078
+ shareCard,
1079
+ run(format) {
1080
+ if (format === 'svg') return exportCanonicalSvg();
1081
+ if (format === 'share-card') return downloadShareCard();
1082
+ throw new Error('Unknown Architecture Delta export format: ' + format);
1083
+ },
1084
+ };
1085
+
1086
+ tabs.forEach((tab, index) => {
1087
+ tab.addEventListener('click', () => { stopPlayback(); show(tab.dataset.target); });
1088
+ tab.addEventListener('keydown', (event) => {
1089
+ if (!['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) return;
1090
+ event.preventDefault();
1091
+ stopPlayback();
1092
+ const next = event.key === 'Home' ? 0 : event.key === 'End' ? tabs.length - 1 : (index + (event.key === 'ArrowRight' ? 1 : -1) + tabs.length) % tabs.length;
1093
+ tabs[next].focus();
1094
+ show(tabs[next].dataset.target);
1095
+ });
1096
+ });
1097
+
1098
+ rowButtons.forEach((row, index) => {
1099
+ row.tabIndex = index === 0 ? 0 : -1;
1100
+ row.addEventListener('click', () => { stopPlayback(); selectChange(index); });
1101
+ row.addEventListener('keydown', (event) => {
1102
+ if (event.key === 'Enter' || event.key === ' ') {
1103
+ event.preventDefault();
1104
+ stopPlayback();
1105
+ selectChange(index);
1106
+ return;
1107
+ }
1108
+ if (!['ArrowUp', 'ArrowDown', 'Home', 'End'].includes(event.key)) return;
1109
+ event.preventDefault();
1110
+ stopPlayback();
1111
+ const next = event.key === 'Home' ? 0 : event.key === 'End' ? rowButtons.length - 1 : (index + (event.key === 'ArrowDown' ? 1 : -1) + rowButtons.length) % rowButtons.length;
1112
+ rowButtons.forEach((button, rowIndex) => { button.tabIndex = rowIndex === next ? 0 : -1; });
1113
+ rowButtons[next].focus();
1114
+ });
1115
+ });
1116
+
1117
+ overviewButton.addEventListener('click', overview);
1118
+ previousButton.addEventListener('click', () => { stopPlayback(); selectChange(activeIndex - 1); });
1119
+ nextButton.addEventListener('click', () => { stopPlayback(); selectChange(activeIndex + 1); });
1120
+ playButton.addEventListener('click', startReview);
1121
+ document.addEventListener('focusin', (event) => {
1122
+ if (playing && event.target.closest('button')) stopPlayback();
1123
+ });
1124
+ document.addEventListener('visibilitychange', () => { if (document.hidden) stopPlayback(); });
1125
+ window.addEventListener('beforeprint', overview);
1126
+ reducedMotion.addEventListener('change', () => { if (reducedMotion.matches) stopPlayback(); });
1127
+ document.addEventListener('keydown', (event) => {
1128
+ if (event.key === 'Escape' && activeIndex >= 0) {
1129
+ event.preventDefault();
1130
+ overview();
1131
+ playButton.focus();
1132
+ }
1133
+ });
1134
+
1135
+ document.querySelector('#theme').addEventListener('click', () => { document.documentElement.dataset.theme = document.documentElement.dataset.theme === 'dark' ? 'light' : 'dark'; });
1136
+ const presets = ['classic', 'signal-flow', 'blueprint'];
1137
+ document.querySelector('#preset').addEventListener('click', () => { const now = document.documentElement.dataset.preset; document.documentElement.dataset.preset = presets[(presets.indexOf(now) + 1) % presets.length]; });
1138
+ document.querySelector('#export-svg').addEventListener('click', exportCanonicalSvg);
1139
+ document.querySelector('#share-card').addEventListener('click', () => { downloadShareCard().catch((error) => window.alert(error.message)); });
1140
+
1141
+ reviewAvailable = validateReview();
1142
+ if (!reviewAvailable) failReview();
1143
+ else {
1144
+ status.removeAttribute('data-state');
1145
+ updateControls();
1146
+ }
1147
+ })();</script></body></html>`;
1148
+ return html.replace(/[ \t]+$/gm, '');
1149
+ }
1150
+
1151
+ export function validateArchitectureDeltaHtml(html, receipt) {
1152
+ const failures = [];
1153
+ const rows = architectureDeltaChangeRows(receipt);
1154
+ const deltaMarkup = html.match(/<section class="canvas" data-view="delta">([\s\S]*?)<\/section>/)?.[1] || '';
1155
+ const svgTags = [...deltaMarkup.matchAll(/<\/?svg\b[^>]*>/g)];
1156
+ let svgDepth = 0;
1157
+ let svgRoots = 0;
1158
+ let rootStart = -1;
1159
+ let rootEnd = -1;
1160
+ let svgBalanced = true;
1161
+ for (const match of svgTags) {
1162
+ if (match[0].startsWith('</')) {
1163
+ svgDepth -= 1;
1164
+ if (svgDepth < 0) svgBalanced = false;
1165
+ if (svgDepth === 0) rootEnd = match.index + match[0].length;
1166
+ } else {
1167
+ if (svgDepth === 0) {
1168
+ svgRoots += 1;
1169
+ if (rootStart < 0) rootStart = match.index;
1170
+ }
1171
+ svgDepth += 1;
1172
+ }
1173
+ }
1174
+ if (!['base', 'delta', 'head'].every((id) => (html.match(new RegExp(`<section class="canvas" data-view="${id}"`, 'g')) || []).length === 1)) failures.push('expected one Before, Delta, and After canvas');
1175
+ if ((html.match(/class="snapshot-frame" title="Before architecture explorer"/g) || []).length !== 1
1176
+ || (html.match(/class="snapshot-frame" title="After architecture explorer"/g) || []).length !== 1) {
1177
+ failures.push('Before and After must preserve one complete architecture explorer each');
1178
+ }
1179
+ if (!svgBalanced || svgDepth !== 0 || svgRoots !== 1 || deltaMarkup.slice(0, rootStart).trim() || deltaMarkup.slice(rootEnd).trim()) failures.push('expected exactly one root SVG in the Delta canvas');
1180
+ if ((html.match(/id="archify-compare-receipt"/g) || []).length !== 1) failures.push('expected exactly one embedded compare receipt');
1181
+ if (!html.includes('aria-label="Authored change review"')) failures.push('missing exact-ID change navigator');
1182
+ if ((html.match(/class="change-row"/g) || []).length !== rows.length) failures.push('change navigator row count does not match the receipt');
1183
+ if (!html.includes('id="export-svg"') || !html.includes('id="share-card"')
1184
+ || !html.includes('window.Archify.deltaExport = { canonicalSvg: canonicalDeltaSvg, shareCard')) {
1185
+ failures.push('missing canonical Delta SVG or Share Card export contract');
1186
+ }
1187
+ for (const [index, row] of rows.entries()) {
1188
+ const safeKey = esc(row.key).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1189
+ const rowMatches = [...html.matchAll(new RegExp(`<button class="change-row"[^>]*data-change-key="${safeKey}"[^>]*>`, 'g'))].map((match) => match[0]);
1190
+ if (rowMatches.length !== 1) failures.push(`expected exactly one change row ${row.key}`);
1191
+ const targets = reviewTargetTags(deltaMarkup, row);
1192
+ if (!targets.length) failures.push(`missing Delta identity ${row.key}`);
1193
+ if (targets.some(({ tag }) => !/\bdata-delta-state="[^"]+"/.test(tag))) failures.push(`missing Delta target state ${row.key}`);
1194
+ const signature = reviewTargetSignature(targets);
1195
+ const expectedSignature = expectedReviewTargetSignature(row);
1196
+ const storedSignature = rowMatches[0]?.match(/\bdata-change-target-signature="([^"]*)"/)?.[1];
1197
+ if (!signature || signature !== expectedSignature || storedSignature !== expectedSignature) failures.push(`ambiguous Delta target signature ${row.key}`);
1198
+ if (rowMatches[0]?.match(/\bdata-change-index="([^"]+)"/)?.[1] !== String(index)) failures.push(`incorrect change row order ${row.key}`);
1199
+ const primary = primaryReviewTags(deltaMarkup, row);
1200
+ const states = primary.map((tag) => tag.match(/\bdata-delta-state="([^"]+)"/)?.[1]).filter(Boolean).sort();
1201
+ if (JSON.stringify(states) !== JSON.stringify(reviewPrimaryStates(row).sort())) failures.push(`ambiguous Delta identity ${row.key}`);
1202
+ const classifications = row.classifications.join(',');
1203
+ if (primary.some((tag) => tag.match(/\bdata-delta-classifications="([^"]*)"/)?.[1] !== classifications)) failures.push(`conflicting Delta classification ${row.key}`);
1204
+ }
1205
+ // Before/After embed the complete existing explorer runtime. Validate claims
1206
+ // made by the Delta shell itself, not implementation vocabulary inside an
1207
+ // escaped srcdoc script (for example, "safe scale" in image export code).
1208
+ const deltaShell = html.replace(/<iframe\b[^>]*><\/iframe>/g, '');
1209
+ if (/\b(?:SAFE|LOW RISK|MERGEABLE|NO IMPACT|VERIFIED PR)\b/i.test(deltaShell)) failures.push('contains a forbidden risk or mergeability claim');
1210
+ if (/\b(?:NaN|Infinity)\b/.test(html)) failures.push('contains non-finite output');
1211
+ if (receipt.completeness !== 'complete') failures.push('receipt is not complete');
1212
+ if (failures.length) fail('delta/artifact-invalid', `Architecture Delta artifact failed validation: ${failures.join('; ')}.`, { failures });
1213
+ return { ok: true, checksPassed: 10, checkCount: 10 };
1214
+ }