@reddoorla/maintenance 0.95.0 → 0.96.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 (272) hide show
  1. package/README.md +37 -2
  2. package/dist/airtable-SJV4ZSMS.js +9 -0
  3. package/dist/{announce-PGT4SZZB.js → announce-45HVP4J4.js} +7 -7
  4. package/dist/{blux-4KNXUP2K.js → blux-RUFMNOUJ.js} +5 -5
  5. package/dist/{bump-deps-C5G2EFBB.js → bump-deps-EL3TKMIL.js} +6 -6
  6. package/dist/{chunk-XN37VTDB.js → chunk-37B4LVB3.js} +2 -2
  7. package/dist/{chunk-BVEDKMNG.js → chunk-3EA43S3R.js} +28 -10
  8. package/dist/chunk-3EA43S3R.js.map +1 -0
  9. package/dist/{chunk-XHHMM5HX.js → chunk-43SW3FGW.js} +63 -1
  10. package/dist/chunk-43SW3FGW.js.map +1 -0
  11. package/dist/{chunk-WMBLR5VA.js → chunk-44SDGPGQ.js} +3 -3
  12. package/dist/chunk-4UTJ4G6P.js +52 -0
  13. package/dist/chunk-4UTJ4G6P.js.map +1 -0
  14. package/dist/{chunk-R53VL3RI.js → chunk-5CJVUPJU.js} +1 -1
  15. package/dist/{chunk-R53VL3RI.js.map → chunk-5CJVUPJU.js.map} +1 -1
  16. package/dist/{chunk-4AIYWWKV.js → chunk-62YWOFVN.js} +36 -5
  17. package/dist/chunk-62YWOFVN.js.map +1 -0
  18. package/dist/{chunk-22GKJO5F.js → chunk-6INOBNRO.js} +2 -2
  19. package/dist/{chunk-BYGPUUCT.js → chunk-72FJZ6LO.js} +43 -11
  20. package/dist/chunk-72FJZ6LO.js.map +1 -0
  21. package/dist/{chunk-5GUJ36TL.js → chunk-74QRWE6G.js} +2 -2
  22. package/dist/chunk-A5QKNOXW.js +72 -0
  23. package/dist/chunk-A5QKNOXW.js.map +1 -0
  24. package/dist/{chunk-2NQUCS7O.js → chunk-BIYQN7FH.js} +3 -1
  25. package/dist/{chunk-2NQUCS7O.js.map → chunk-BIYQN7FH.js.map} +1 -1
  26. package/dist/{chunk-S7RYFUIK.js → chunk-CA6ADGOC.js} +2 -2
  27. package/dist/chunk-CGQZFQAY.js +38 -0
  28. package/dist/chunk-CGQZFQAY.js.map +1 -0
  29. package/dist/{chunk-YLYXW5PH.js → chunk-CXICCLNM.js} +34 -4
  30. package/dist/chunk-CXICCLNM.js.map +1 -0
  31. package/dist/{chunk-KZRR4NFG.js → chunk-D3CTOCUR.js} +3 -3
  32. package/dist/{chunk-CWXTNPNZ.js → chunk-DVJNYPFR.js} +3 -3
  33. package/dist/chunk-DVJNYPFR.js.map +1 -0
  34. package/dist/{chunk-AVEQUR44.js → chunk-EASYCD6F.js} +60 -17
  35. package/dist/chunk-EASYCD6F.js.map +1 -0
  36. package/dist/{chunk-BCKEP5S6.js → chunk-ECDJDHKT.js} +2 -2
  37. package/dist/{chunk-DMPHP7UP.js → chunk-EQQ4H6L3.js} +2 -2
  38. package/dist/{chunk-WVNDEYXL.js → chunk-GEUC5VGM.js} +11 -5
  39. package/dist/chunk-GEUC5VGM.js.map +1 -0
  40. package/dist/{chunk-Z2K4JPQ3.js → chunk-H445EH33.js} +2 -2
  41. package/dist/chunk-IHHAYXKY.js +351 -0
  42. package/dist/chunk-IHHAYXKY.js.map +1 -0
  43. package/dist/{chunk-FUWFLAWQ.js → chunk-IMYL67JV.js} +3 -3
  44. package/dist/{chunk-3Z6X3BHU.js → chunk-KN52PM63.js} +7 -2
  45. package/dist/{chunk-3Z6X3BHU.js.map → chunk-KN52PM63.js.map} +1 -1
  46. package/dist/{chunk-WJMPCVOR.js → chunk-LAJ3FF54.js} +12 -4
  47. package/dist/chunk-LAJ3FF54.js.map +1 -0
  48. package/dist/{chunk-5UINJNAJ.js → chunk-MZZDMBDT.js} +16 -3
  49. package/dist/chunk-MZZDMBDT.js.map +1 -0
  50. package/dist/{chunk-DFKBTBO3.js → chunk-NVLREB5R.js} +35 -6
  51. package/dist/chunk-NVLREB5R.js.map +1 -0
  52. package/dist/{chunk-AEUABPAJ.js → chunk-OTEVKRAH.js} +3 -3
  53. package/dist/{chunk-KQB2BX4V.js → chunk-P4A6D6HO.js} +4 -4
  54. package/dist/chunk-P7323YVW.js +134 -0
  55. package/dist/chunk-P7323YVW.js.map +1 -0
  56. package/dist/{chunk-4763KQ6O.js → chunk-PXRN2MER.js} +2 -2
  57. package/dist/{chunk-TR3HFH6J.js → chunk-QFSL2L2B.js} +67 -10
  58. package/dist/chunk-QFSL2L2B.js.map +1 -0
  59. package/dist/{chunk-2LYOAB2E.js → chunk-RRE74VWI.js} +2 -2
  60. package/dist/{chunk-LM2NHRCJ.js → chunk-SHGIDTYF.js} +4 -4
  61. package/dist/chunk-SHGIDTYF.js.map +1 -0
  62. package/dist/{chunk-ZQV7UQNE.js → chunk-SQ4FSNYL.js} +2 -2
  63. package/dist/{chunk-OCNSIBMP.js → chunk-T76MA2FH.js} +135 -1
  64. package/dist/{chunk-OCNSIBMP.js.map → chunk-T76MA2FH.js.map} +1 -1
  65. package/dist/{chunk-6R56OKJZ.js → chunk-UIITN37J.js} +2 -2
  66. package/dist/{chunk-CJ2RMTT7.js → chunk-VEIIUCYL.js} +2 -2
  67. package/dist/{chunk-SVAAONYB.js → chunk-WQLNUEZL.js} +12 -2
  68. package/dist/{chunk-SVAAONYB.js.map → chunk-WQLNUEZL.js.map} +1 -1
  69. package/dist/{chunk-WSJ7RO54.js → chunk-XED2DNAI.js} +23 -5
  70. package/dist/chunk-XED2DNAI.js.map +1 -0
  71. package/dist/{chunk-FSGICFXB.js → chunk-XG2A6GIG.js} +17 -5
  72. package/dist/chunk-XG2A6GIG.js.map +1 -0
  73. package/dist/{chunk-TO7DJYLZ.js → chunk-XY7YV2YQ.js} +178 -39
  74. package/dist/chunk-XY7YV2YQ.js.map +1 -0
  75. package/dist/{chunk-HTABUCF4.js → chunk-Y5BVR4HW.js} +3 -3
  76. package/dist/chunk-Y6FCXRIC.js +3972 -0
  77. package/dist/chunk-Y6FCXRIC.js.map +1 -0
  78. package/dist/{chunk-2SLED3NB.js → chunk-YDZ7XGPZ.js} +1 -1
  79. package/dist/{chunk-2SLED3NB.js.map → chunk-YDZ7XGPZ.js.map} +1 -1
  80. package/dist/{chunk-QF6IQQFP.js → chunk-Z7FDJ35L.js} +18 -12
  81. package/dist/chunk-Z7FDJ35L.js.map +1 -0
  82. package/dist/{chunk-5EEMTJMT.js → chunk-ZH5LOQET.js} +4 -4
  83. package/dist/cli/bin.js +49 -31
  84. package/dist/cli/bin.js.map +1 -1
  85. package/dist/cli/commands/audit.js +18 -18
  86. package/dist/client-L7NAUG6J.js +11 -0
  87. package/dist/configs/eslint.js +27 -1
  88. package/dist/configs/eslint.js.map +1 -1
  89. package/dist/configs/playwright-a11y.d.ts +11 -1
  90. package/dist/configs/playwright-a11y.js +3 -1
  91. package/dist/{convert-to-pnpm-YTFREUJK.js → convert-to-pnpm-4DON7IFS.js} +6 -6
  92. package/dist/{db-BBEM7CBX.js → db-5K4CDEGJ.js} +45 -20
  93. package/dist/db-5K4CDEGJ.js.map +1 -0
  94. package/dist/deadletter-G4M3HSVM.js +17 -0
  95. package/dist/{digest-65BOZ7AT.js → digest-5AJGWS33.js} +45 -22
  96. package/dist/digest-5AJGWS33.js.map +1 -0
  97. package/dist/{digest-collectors-EHFY2OQC.js → digest-collectors-MC5NWEUA.js} +12 -10
  98. package/dist/{digest-state-3KHZF7GD.js → digest-state-ZDPKR7MW.js} +18 -2
  99. package/dist/digest-state-ZDPKR7MW.js.map +1 -0
  100. package/dist/{ensure-site-WIDO6LGU.js → ensure-site-XESZHZEB.js} +39 -11
  101. package/dist/ensure-site-XESZHZEB.js.map +1 -0
  102. package/dist/{fleet-state-UEVZ3X3J.js → fleet-state-GJWGKTLD.js} +6 -4
  103. package/dist/{forms-notify-target-2LWF5C5B.js → forms-notify-target-4NPLX2UG.js} +5 -5
  104. package/dist/{freeze-A6MUCZU4.js → freeze-GUQ43EAU.js} +2 -2
  105. package/dist/{github-signals-GAHNOAHS.js → github-signals-DMU74SAX.js} +10 -10
  106. package/dist/{header-image-YN3AQRY3.js → header-image-BRTOLHJF.js} +6 -5
  107. package/dist/header-image-BRTOLHJF.js.map +1 -0
  108. package/dist/{health-endpoint-KVCJYWET.js → health-endpoint-UCLMSSCN.js} +8 -7
  109. package/dist/{health-endpoint-KVCJYWET.js.map → health-endpoint-UCLMSSCN.js.map} +1 -1
  110. package/dist/{health-mirror-EB76EERV.js → health-mirror-ONRT4DQ5.js} +7 -7
  111. package/dist/{import-airtable-TAKSN4NV.js → import-airtable-IIK2QUUF.js} +3 -3
  112. package/dist/index.d.ts +135 -4
  113. package/dist/index.js +68 -34
  114. package/dist/index.js.map +1 -1
  115. package/dist/{init-FFE4FCO2.js → init-3LMZTCVD.js} +19 -18
  116. package/dist/{init-FFE4FCO2.js.map → init-3LMZTCVD.js.map} +1 -1
  117. package/dist/{launch-4RVWPZGI.js → launch-XTDMC6LD.js} +209 -61
  118. package/dist/launch-XTDMC6LD.js.map +1 -0
  119. package/dist/{lighthouse-VHI77PFB.js → lighthouse-52WSPZ5D.js} +2 -2
  120. package/dist/{lighthouse-airtable-YMH4UA7R.js → lighthouse-airtable-3NOFB42B.js} +3 -3
  121. package/dist/{match-harness-2Q6ISEES.js → match-harness-OW45CB5H.js} +14 -7
  122. package/dist/match-harness-OW45CB5H.js.map +1 -0
  123. package/dist/migrate-NE3DUMTM.js +7 -0
  124. package/dist/{notify-IISTHNBR.js → notify-K3WVH3DM.js} +3 -3
  125. package/dist/{onboard-TM2KESGF.js → onboard-CZYBWVWH.js} +6 -6
  126. package/dist/{orchestrate-MWXENQOS.js → orchestrate-TGDFGY52.js} +8 -8
  127. package/dist/{parity-CAJ477PS.js → parity-AEKYSLN6.js} +5 -5
  128. package/dist/{pipeline-YROAY6DT.js → pipeline-UUPXOS2J.js} +3 -3
  129. package/dist/{preflight-4VQP7JJL.js → preflight-POZZI3GN.js} +9 -9
  130. package/dist/{prismic-ci-6GKKZGB5.js → prismic-ci-AHY5ULJR.js} +38 -8
  131. package/dist/prismic-ci-AHY5ULJR.js.map +1 -0
  132. package/dist/{prismic-models-W6CNLTWH.js → prismic-models-URYGODNY.js} +9 -9
  133. package/dist/prospect/types.d.ts +72 -0
  134. package/dist/prospect/types.js +1 -1
  135. package/dist/{prospect-audit-X4Z67LWW.js → prospect-audit-AJEE3Z4L.js} +16 -7
  136. package/dist/prospect-audit-AJEE3Z4L.js.map +1 -0
  137. package/dist/{prospect-audits-RVYEA4KH.js → prospect-audits-IZVQY3OQ.js} +12 -4
  138. package/dist/protection-audit-3J3UEOVT.js +413 -0
  139. package/dist/protection-audit-3J3UEOVT.js.map +1 -0
  140. package/dist/recipes/sync-configs.js +3 -3
  141. package/dist/{render-YGNGTX3W.js → render-MHUUHPLN.js} +36 -9
  142. package/dist/render-MHUUHPLN.js.map +1 -0
  143. package/dist/{render-from-row-IFSBHWDH.js → render-from-row-65DC6ZP3.js} +4 -4
  144. package/dist/{renovate-dispatch-U5Q52SKT.js → renovate-dispatch-ONJMBUHJ.js} +3 -3
  145. package/dist/{replay-PCSQOGCZ.js → replay-4FVFOMHH.js} +10 -23
  146. package/dist/replay-4FVFOMHH.js.map +1 -0
  147. package/dist/{report-4GFD4MW2.js → report-RC5LGPU3.js} +22 -20
  148. package/dist/report-RC5LGPU3.js.map +1 -0
  149. package/dist/{report-mirror-IP64R7B3.js → report-mirror-KKZZ6RMZ.js} +13 -9
  150. package/dist/{report-mirror-IP64R7B3.js.map → report-mirror-KKZZ6RMZ.js.map} +1 -1
  151. package/dist/{schema-2V7BGYVT.js → schema-KWUQEZ5O.js} +1 -1
  152. package/dist/schema-KWUQEZ5O.js.map +1 -0
  153. package/dist/{self-updating-MWXIZLL7.js → self-updating-RKKIYNV4.js} +8 -7
  154. package/dist/{self-updating-MWXIZLL7.js.map → self-updating-RKKIYNV4.js.map} +1 -1
  155. package/dist/{selftest-MAB2PBXQ.js → selftest-AFLR7YV7.js} +10 -10
  156. package/dist/site-lookup-DTOLJ4EA.js +27 -0
  157. package/dist/site-lookup-DTOLJ4EA.js.map +1 -0
  158. package/dist/{site-mirror-UKHREAD6.js → site-mirror-H2FYMN55.js} +23 -9
  159. package/dist/site-mirror-H2FYMN55.js.map +1 -0
  160. package/dist/{smoke-suite-7DGITCTZ.js → smoke-suite-BWHR3XX5.js} +8 -7
  161. package/dist/{smoke-suite-7DGITCTZ.js.map → smoke-suite-BWHR3XX5.js.map} +1 -1
  162. package/dist/{submissions-5GKJPCIH.js → submissions-II5J35QM.js} +4 -4
  163. package/dist/{submissions-3I3GAQAX.js → submissions-JDZP35JM.js} +49 -9
  164. package/dist/submissions-JDZP35JM.js.map +1 -0
  165. package/dist/{svelte-codemods-WSV2FGNG.js → svelte-codemods-QWGIRNN6.js} +6 -6
  166. package/dist/{sync-K3PKHVLH.js → sync-NKPDCMYW.js} +5 -5
  167. package/dist/{sync-configs-RRIHNLIJ.js → sync-configs-HGLQV55H.js} +6 -6
  168. package/dist/{upgrade-VTD6V4HS.js → upgrade-WZ44IARG.js} +6 -6
  169. package/dist/util/git.d.ts +48 -9
  170. package/dist/util/git.js +1 -1
  171. package/dist/{webflow-IRKOIT7S.js → webflow-624FEBFD.js} +4 -4
  172. package/dist/{websites-TB4TCAWM.js → websites-6DHIHJ2D.js} +4 -2
  173. package/dist/{write-audits-to-airtable-53EVXKMU.js → write-audits-to-airtable-BIK6LNVC.js} +5 -5
  174. package/dist/write-audits-to-airtable-BIK6LNVC.js.map +1 -0
  175. package/package.json +3 -2
  176. package/dist/airtable-JGCPS7JH.js +0 -9
  177. package/dist/chunk-4AIYWWKV.js.map +0 -1
  178. package/dist/chunk-5UINJNAJ.js.map +0 -1
  179. package/dist/chunk-6VKYQATC.js +0 -1708
  180. package/dist/chunk-6VKYQATC.js.map +0 -1
  181. package/dist/chunk-7MKT4I5T.js +0 -62
  182. package/dist/chunk-7MKT4I5T.js.map +0 -1
  183. package/dist/chunk-AVEQUR44.js.map +0 -1
  184. package/dist/chunk-BVEDKMNG.js.map +0 -1
  185. package/dist/chunk-BYGPUUCT.js.map +0 -1
  186. package/dist/chunk-CWXTNPNZ.js.map +0 -1
  187. package/dist/chunk-DFKBTBO3.js.map +0 -1
  188. package/dist/chunk-FSGICFXB.js.map +0 -1
  189. package/dist/chunk-GASBX52O.js +0 -12
  190. package/dist/chunk-GASBX52O.js.map +0 -1
  191. package/dist/chunk-LM2NHRCJ.js.map +0 -1
  192. package/dist/chunk-QF6IQQFP.js.map +0 -1
  193. package/dist/chunk-TO7DJYLZ.js.map +0 -1
  194. package/dist/chunk-TR3HFH6J.js.map +0 -1
  195. package/dist/chunk-WJMPCVOR.js.map +0 -1
  196. package/dist/chunk-WSJ7RO54.js.map +0 -1
  197. package/dist/chunk-WVNDEYXL.js.map +0 -1
  198. package/dist/chunk-XHHMM5HX.js.map +0 -1
  199. package/dist/chunk-YLYXW5PH.js.map +0 -1
  200. package/dist/client-M2V6FHH5.js +0 -11
  201. package/dist/db-BBEM7CBX.js.map +0 -1
  202. package/dist/digest-65BOZ7AT.js.map +0 -1
  203. package/dist/digest-state-3KHZF7GD.js.map +0 -1
  204. package/dist/ensure-site-WIDO6LGU.js.map +0 -1
  205. package/dist/header-image-YN3AQRY3.js.map +0 -1
  206. package/dist/launch-4RVWPZGI.js.map +0 -1
  207. package/dist/match-harness-2Q6ISEES.js.map +0 -1
  208. package/dist/migrate-K4JETR36.js +0 -7
  209. package/dist/prismic-ci-6GKKZGB5.js.map +0 -1
  210. package/dist/prospect-audit-X4Z67LWW.js.map +0 -1
  211. package/dist/protection-audit-SJO53MEQ.js +0 -168
  212. package/dist/protection-audit-SJO53MEQ.js.map +0 -1
  213. package/dist/render-YGNGTX3W.js.map +0 -1
  214. package/dist/replay-PCSQOGCZ.js.map +0 -1
  215. package/dist/report-4GFD4MW2.js.map +0 -1
  216. package/dist/schema-2V7BGYVT.js.map +0 -1
  217. package/dist/site-mirror-UKHREAD6.js.map +0 -1
  218. package/dist/submissions-3I3GAQAX.js.map +0 -1
  219. /package/dist/{airtable-JGCPS7JH.js.map → airtable-SJV4ZSMS.js.map} +0 -0
  220. /package/dist/{announce-PGT4SZZB.js.map → announce-45HVP4J4.js.map} +0 -0
  221. /package/dist/{blux-4KNXUP2K.js.map → blux-RUFMNOUJ.js.map} +0 -0
  222. /package/dist/{bump-deps-C5G2EFBB.js.map → bump-deps-EL3TKMIL.js.map} +0 -0
  223. /package/dist/{chunk-XN37VTDB.js.map → chunk-37B4LVB3.js.map} +0 -0
  224. /package/dist/{chunk-WMBLR5VA.js.map → chunk-44SDGPGQ.js.map} +0 -0
  225. /package/dist/{chunk-22GKJO5F.js.map → chunk-6INOBNRO.js.map} +0 -0
  226. /package/dist/{chunk-5GUJ36TL.js.map → chunk-74QRWE6G.js.map} +0 -0
  227. /package/dist/{chunk-S7RYFUIK.js.map → chunk-CA6ADGOC.js.map} +0 -0
  228. /package/dist/{chunk-KZRR4NFG.js.map → chunk-D3CTOCUR.js.map} +0 -0
  229. /package/dist/{chunk-BCKEP5S6.js.map → chunk-ECDJDHKT.js.map} +0 -0
  230. /package/dist/{chunk-DMPHP7UP.js.map → chunk-EQQ4H6L3.js.map} +0 -0
  231. /package/dist/{chunk-Z2K4JPQ3.js.map → chunk-H445EH33.js.map} +0 -0
  232. /package/dist/{chunk-FUWFLAWQ.js.map → chunk-IMYL67JV.js.map} +0 -0
  233. /package/dist/{chunk-AEUABPAJ.js.map → chunk-OTEVKRAH.js.map} +0 -0
  234. /package/dist/{chunk-KQB2BX4V.js.map → chunk-P4A6D6HO.js.map} +0 -0
  235. /package/dist/{chunk-4763KQ6O.js.map → chunk-PXRN2MER.js.map} +0 -0
  236. /package/dist/{chunk-2LYOAB2E.js.map → chunk-RRE74VWI.js.map} +0 -0
  237. /package/dist/{chunk-ZQV7UQNE.js.map → chunk-SQ4FSNYL.js.map} +0 -0
  238. /package/dist/{chunk-6R56OKJZ.js.map → chunk-UIITN37J.js.map} +0 -0
  239. /package/dist/{chunk-CJ2RMTT7.js.map → chunk-VEIIUCYL.js.map} +0 -0
  240. /package/dist/{chunk-HTABUCF4.js.map → chunk-Y5BVR4HW.js.map} +0 -0
  241. /package/dist/{chunk-5EEMTJMT.js.map → chunk-ZH5LOQET.js.map} +0 -0
  242. /package/dist/{client-M2V6FHH5.js.map → client-L7NAUG6J.js.map} +0 -0
  243. /package/dist/{convert-to-pnpm-YTFREUJK.js.map → convert-to-pnpm-4DON7IFS.js.map} +0 -0
  244. /package/dist/{digest-collectors-EHFY2OQC.js.map → deadletter-G4M3HSVM.js.map} +0 -0
  245. /package/dist/{fleet-state-UEVZ3X3J.js.map → digest-collectors-MC5NWEUA.js.map} +0 -0
  246. /package/dist/{freeze-A6MUCZU4.js.map → fleet-state-GJWGKTLD.js.map} +0 -0
  247. /package/dist/{forms-notify-target-2LWF5C5B.js.map → forms-notify-target-4NPLX2UG.js.map} +0 -0
  248. /package/dist/{import-airtable-TAKSN4NV.js.map → freeze-GUQ43EAU.js.map} +0 -0
  249. /package/dist/{github-signals-GAHNOAHS.js.map → github-signals-DMU74SAX.js.map} +0 -0
  250. /package/dist/{health-mirror-EB76EERV.js.map → health-mirror-ONRT4DQ5.js.map} +0 -0
  251. /package/dist/{lighthouse-VHI77PFB.js.map → import-airtable-IIK2QUUF.js.map} +0 -0
  252. /package/dist/{lighthouse-airtable-YMH4UA7R.js.map → lighthouse-52WSPZ5D.js.map} +0 -0
  253. /package/dist/{migrate-K4JETR36.js.map → lighthouse-airtable-3NOFB42B.js.map} +0 -0
  254. /package/dist/{notify-IISTHNBR.js.map → migrate-NE3DUMTM.js.map} +0 -0
  255. /package/dist/{orchestrate-MWXENQOS.js.map → notify-K3WVH3DM.js.map} +0 -0
  256. /package/dist/{onboard-TM2KESGF.js.map → onboard-CZYBWVWH.js.map} +0 -0
  257. /package/dist/{parity-CAJ477PS.js.map → orchestrate-TGDFGY52.js.map} +0 -0
  258. /package/dist/{pipeline-YROAY6DT.js.map → parity-AEKYSLN6.js.map} +0 -0
  259. /package/dist/{prospect-audits-RVYEA4KH.js.map → pipeline-UUPXOS2J.js.map} +0 -0
  260. /package/dist/{preflight-4VQP7JJL.js.map → preflight-POZZI3GN.js.map} +0 -0
  261. /package/dist/{prismic-models-W6CNLTWH.js.map → prismic-models-URYGODNY.js.map} +0 -0
  262. /package/dist/{render-from-row-IFSBHWDH.js.map → prospect-audits-IZVQY3OQ.js.map} +0 -0
  263. /package/dist/{websites-TB4TCAWM.js.map → render-from-row-65DC6ZP3.js.map} +0 -0
  264. /package/dist/{renovate-dispatch-U5Q52SKT.js.map → renovate-dispatch-ONJMBUHJ.js.map} +0 -0
  265. /package/dist/{selftest-MAB2PBXQ.js.map → selftest-AFLR7YV7.js.map} +0 -0
  266. /package/dist/{submissions-5GKJPCIH.js.map → submissions-II5J35QM.js.map} +0 -0
  267. /package/dist/{svelte-codemods-WSV2FGNG.js.map → svelte-codemods-QWGIRNN6.js.map} +0 -0
  268. /package/dist/{sync-K3PKHVLH.js.map → sync-NKPDCMYW.js.map} +0 -0
  269. /package/dist/{sync-configs-RRIHNLIJ.js.map → sync-configs-HGLQV55H.js.map} +0 -0
  270. /package/dist/{upgrade-VTD6V4HS.js.map → upgrade-WZ44IARG.js.map} +0 -0
  271. /package/dist/{webflow-IRKOIT7S.js.map → webflow-624FEBFD.js.map} +0 -0
  272. /package/dist/{write-audits-to-airtable-53EVXKMU.js.map → websites-6DHIHJ2D.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/recipes/match-harness/index.ts","../src/recipes/match-harness/template.ts"],"sourcesContent":["import { mkdir, readFile, rm, rmdir, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport type { RecipeResult, Site } from \"../../types.js\";\nimport { withRecipe } from \"../_with-recipe.js\";\nimport { ignoreRulesFor, pathsMissingFromHead } from \"../../util/git.js\";\nimport { defaultSpawn, type SpawnFn } from \"../../audits/util/spawn.js\";\nimport { formatWithPrettier, resolveTargetPrettier, PRETTIER_FLAG_NOTE } from \"../_prettier.js\";\nimport {\n MATCH_HARNESS_FILES,\n MATCH_HARNESS_PREVIOUS,\n HARNESS_JSON_RELATIVE,\n GITIGNORE_MARKER,\n GITIGNORE_BLOCK,\n PRETTIERIGNORE_MARKER,\n PRETTIERIGNORE_BLOCK,\n CLAUDE_MD_MARKER,\n CLAUDE_MD_BLOCK,\n} from \"./template.js\";\n\n/** How long the target's own prettier gets. Also what makes the fleet's default\n * spawn detach the child, so the kill reaches prettier and not just a wrapper.\n * Same budget as `prismic-ci` and the Prismic pull-down path. */\nconst PRETTIER_TIMEOUT_MS = 60_000;\n\nexport type MatchHarnessOptions = {\n /** The live reference URL the harness gates against. Required — a harness\n * with no reference cannot produce evidence, only an absence of errors. */\n ref: string;\n /** Candidate origin. Defaults to the SvelteKit dev server. */\n cand?: string;\n /** Breakpoint matrix. Defaults to the fleet's 1440 / 834 / 390. */\n matrix?: number[];\n};\n\nexport type MatchHarnessDeps = {\n spawn: SpawnFn;\n /** Previously shipped renders per relative path; injectable so the\n * safe-replace path is testable before a second version exists. */\n previous?: Readonly<Record<string, readonly string[]>>;\n /** Resolve the TARGET repo's own prettier. Injected so a test can assert the\n * absolute-path spawn without a populated `node_modules`. */\n resolvePrettier?: (repoRoot: string) => Promise<string | null>;\n};\n\ntype Plan = { ref: string; cand: string; matrix: number[] };\n\nasync function readIfExists(path: string): Promise<string | null> {\n try {\n return await readFile(path, \"utf-8\");\n } catch {\n return null;\n }\n}\n\n/** CRLF and trailing whitespace are the only differences a site's formatter is\n * allowed to introduce before a file stops counting as \"ours\". */\nfunction normalize(s: string): string {\n return s\n .replace(/\\r\\n/g, \"\\n\")\n .replace(/[ \\t]+$/gm, \"\")\n .trim();\n}\n\n/**\n * Append `block` under `marker` unless the marker is already present. The only\n * append-to-existing-file idiom in this repo is mergeGitignore, and it cannot\n * express a negated whitelist (`matching/*` then `!matching/*.sh`) as one unit:\n * it compares entries by normalized presence, so the ordering that makes the\n * block work would be lost. This keeps the block whole and idempotent.\n */\nexport function mergeBlock(existing: string | null, marker: string, block: string): string | null {\n if (existing === null) return `${marker}\\n${block}`;\n if (existing.includes(marker)) return null;\n const base = existing.endsWith(\"\\n\") ? existing : `${existing}\\n`;\n return `${base}\\n${marker}\\n${block}`;\n}\n\n/** What to do with one installed file. `flag` never writes. */\nexport function planFileWrite(\n existing: string | null,\n template: string,\n owner: \"recipe\" | \"site\",\n previous: readonly string[],\n): \"write\" | \"replace\" | \"skip\" | \"flag\" {\n if (existing === null) return \"write\";\n if (normalize(existing) === normalize(template)) return \"skip\";\n if (owner === \"site\") return \"skip\"; // records are never touched again\n if (previous.some((p) => normalize(existing) === normalize(p))) return \"replace\";\n return \"flag\";\n}\n\n/** The three files the recipe APPENDS a marked block to rather than writing whole. */\nconst APPENDED_BLOCKS = [\n [\".gitignore\", GITIGNORE_MARKER, GITIGNORE_BLOCK],\n [\".prettierignore\", PRETTIERIGNORE_MARKER, PRETTIERIGNORE_BLOCK],\n [\"CLAUDE.md\", CLAUDE_MD_MARKER, CLAUDE_MD_BLOCK],\n] as const;\n\n/**\n * Every path the installed harness requires to be IN THE COMMIT — deliberately\n * the whole manifest, NOT the paths written on this run. A re-run skips every\n * file already byte-correct on disk, so a check over the written delta would\n * find nothing missing and report a second hollow \"applied\" over the same\n * half-install.\n */\nexport const MATCH_HARNESS_INSTALLED_PATHS: readonly string[] = [\n ...MATCH_HARNESS_FILES.map((f) => f.rel),\n ...APPENDED_BLOCKS.map(([rel]) => rel),\n];\n\n/**\n * Installs the matching harness into a site: the dev-guarded `/dev/match/[uid]`\n * route, a `site-pages.js` scaffold and its fixture-vs-model test,\n * `matching/harness.json` + the read layer, the round scripts, the site records,\n * and the `.gitignore` / `.prettierignore` / `CLAUDE.md` blocks.\n *\n * Install-if-absent throughout. Files a site edits are never rewritten. A\n * recipe-owned script that matches a previously shipped render is safe-replaced;\n * one that has been hand-edited is FLAGGED in the notes and left alone.\n */\nexport async function matchHarness(\n site: Site,\n opts: MatchHarnessOptions,\n deps: MatchHarnessDeps = { spawn: defaultSpawn },\n): Promise<RecipeResult> {\n return withRecipe<Plan>({\n name: \"match-harness\",\n site,\n plan: async () => {\n if (!opts.ref || !/^https?:\\/\\//.test(opts.ref)) {\n return {\n kind: \"failed\",\n notes:\n \"--ref <url> is required: the harness gates against a live reference, and a harness with no reference can only ever report an absence of errors\",\n };\n }\n return {\n kind: \"apply\",\n plan: {\n ref: opts.ref.replace(/\\/$/, \"\"),\n cand: opts.cand ?? \"http://localhost:5173\",\n matrix: opts.matrix ?? [1440, 834, 390],\n },\n };\n },\n apply: async (planned, { commit, cwd }) => {\n const notes: string[] = [];\n const written: string[] = [];\n /** For every path this run writes: what was there BEFORE (null = the file\n * did not exist). The refusal below restores from this, so a refused run\n * leaves the checkout byte-identical to how it found it. */\n const before = new Map<string, string | null>();\n const previousAll = deps.previous ?? MATCH_HARNESS_PREVIOUS;\n\n for (const f of MATCH_HARNESS_FILES) {\n const target = join(cwd, f.rel);\n let template = f.template;\n if (f.rel === HARNESS_JSON_RELATIVE) {\n // Patch the PARSED object, never the text. The seed is pretty-printed\n // two-space JSON, so `matrix` renders as a four-line exploded array\n // and the literal \"[1440, 834, 390]\" does not occur — a string\n // replace on it is a no-op that drops --matrix with no error.\n const seed = JSON.parse(template) as Record<string, unknown>;\n seed.ref = planned.ref;\n seed.cand = planned.cand;\n seed.matrix = planned.matrix;\n template = JSON.stringify(seed, null, 2) + \"\\n\";\n }\n const existing = await readIfExists(target);\n const action = planFileWrite(existing, template, f.owner, previousAll[f.rel] ?? []);\n if (action === \"flag\") {\n notes.push(\n `${f.rel} differs from the shipped template and was left alone (hand-edited?)`,\n );\n continue;\n }\n if (action === \"skip\") continue;\n await mkdir(dirname(target), { recursive: true });\n before.set(f.rel, existing);\n await writeFile(target, template, \"utf-8\");\n written.push(f.rel);\n if (action === \"replace\") notes.push(`${f.rel} upgraded from a previous version`);\n }\n\n // Three appended blocks, each idempotent on its own marker.\n for (const [rel, marker, block] of APPENDED_BLOCKS) {\n const path = join(cwd, rel);\n const existing = await readIfExists(path);\n const merged = mergeBlock(existing, marker, block);\n if (merged !== null) {\n before.set(rel, existing);\n await writeFile(path, merged, \"utf-8\");\n written.push(rel);\n }\n }\n\n // Format only what the SITE owns, decided by OWNERSHIP and never by path.\n // A recipe-owned file that a site's prettier reformats is indistinguishable\n // from a hand edit on the next install, so planFileWrite flags it and the\n // recipe can never upgrade it again. A path prefix cannot express that:\n // three recipe-owned files live under src/. This is an ALLOW-list on\n // purpose — a file added to MATCH_HARNESS_FILES is not formatted until its\n // owner says \"site\", because the unsafe default is silent and permanent.\n // The same files are in the .prettierignore block written above, which\n // stops the site's OWN `prettier --write .` from doing the identical\n // damage; that block and this filter must agree, and a test asserts it.\n // CLAUDE.md is not in the file table — it is an append into a file the\n // site owns — so it is named here.\n const siteOwned = new Set(\n MATCH_HARNESS_FILES.filter((f) => f.owner === \"site\").map((f) => f.rel),\n );\n const toFormat = written.filter((p) => siteOwned.has(p) || p === \"CLAUDE.md\");\n\n // Run the SITE's own prettier, resolved POSITIVELY and invoked by absolute\n // path. `pnpm exec prettier` here would first run a full, unbounded\n // `pnpm install` in the client's checkout — which on `--fleet` is the\n // NORMAL path, because `prepareFleetSites` clones and never installs — and\n // would then, in a repo whose install left no prettier of its own, fall\n // through to the CALLING repo's binary and exit 0, reporting success for a\n // format the target never did. A `true` from here must mean the target's\n // own prettier ran, so nothing about which binary runs is left to\n // resolution.\n if (toFormat.length > 0) {\n const bin = await (deps.resolvePrettier ?? resolveTargetPrettier)(cwd);\n if (bin === null) {\n notes.push(PRETTIER_FLAG_NOTE);\n } else if (\n !(await formatWithPrettier(deps.spawn, cwd, toFormat, {\n bin,\n timeoutMs: PRETTIER_TIMEOUT_MS,\n }))\n ) {\n notes.push(PRETTIER_FLAG_NOTE);\n }\n }\n\n await commit(\n \"feat: install the matching harness (/dev/match route + matching/ gate scripts)\",\n );\n\n // POSITIVE EVIDENCE that the install is real: every installed path is\n // FOUND in HEAD's tree. `git add -A` honours the site's .gitignore and\n // exits 0 either way, and git cannot re-include a file whose PARENT\n // DIRECTORY is excluded — so a site that already ignores `matching/`\n // (or `**/matching/`, `/matching`, `src/lib/site-pages.js`, …) gets the\n // files on disk, nothing in the commit, and — before this check — a\n // result of \"applied\", plus 83 lines of CLAUDE.md rules naming scripts\n // that exist on no other machine.\n const missing = await pathsMissingFromHead(cwd, MATCH_HARNESS_INSTALLED_PATHS);\n if (missing.length > 0) {\n const rules = await ignoreRulesFor(cwd, missing);\n const sources = [...new Set(rules.map((r) => r.split(\"\\t\")[0]!))];\n const explained = new Set(rules.map((r) => r.split(\"\\t\")[1] ?? \"\"));\n const unexplained = missing.filter((p) => !explained.has(p));\n\n // Put the checkout back exactly as we found it. Only paths THIS RUN\n // wrote and git then refused are touched: one that reached the commit\n // is left to withRecipe's force-checkout, and one we never wrote is\n // never ours. `git checkout -f` cannot do this job — these paths are\n // ignored, so git does not know they exist.\n const removed: string[] = [];\n for (const rel of written.filter((p) => missing.includes(p))) {\n const prev = before.get(rel);\n if (prev === undefined) continue;\n if (prev === null) {\n await rm(join(cwd, rel), { force: true });\n removed.push(rel);\n } else {\n await writeFile(join(cwd, rel), prev, \"utf-8\");\n }\n }\n // Deepest-first, so `matching/spec-sections` goes before `matching`.\n // rmdir refuses a non-empty directory, which is exactly the guard wanted.\n for (const d of [...new Set(removed.map(dirname))].sort((a, b) => b.length - a.length)) {\n if (d !== \".\") await rmdir(join(cwd, d)).catch(() => undefined);\n }\n\n return {\n kind: \"failed\",\n notes:\n `the harness was NOT installed: ${missing.length} of ${MATCH_HARNESS_INSTALLED_PATHS.length} paths git refused to track, so a fresh clone, CI and the next agent get a harness that does not exist. ` +\n `Absent from the commit: ${missing.join(\", \")}. ` +\n (sources.length > 0\n ? `Excluded by ${sources.join(\", \")} — narrow or remove those rules and re-run. `\n : \"\") +\n (unexplained.length > 0\n ? `No ignore rule matched ${unexplained.join(\", \")} — check .git/info/exclude, core.excludesFile, and whether the directory is a nested repository. `\n : \"\") +\n \"Everything this run wrote to those paths has been put back as it was.\",\n };\n }\n\n return notes.length > 0 ? { kind: \"ok\", notes: notes.join(\"; \") } : { kind: \"ok\" };\n },\n });\n}\n","// GENERATED by scripts/gen-match-harness-template.mjs. Do NOT hand-edit the\n// string bodies — regenerate. Seven constants are byte-copies of a site with\n// the consolidated harness; the rest are stubs authored in the generator.\n\nexport type OwnedBy = \"recipe\" | \"site\";\nexport type HarnessFile = { rel: string; template: string; owner: OwnedBy };\n\nexport const HARNESS_MJS_RELATIVE = \"matching/harness.mjs\";\nexport const HARNESS_MJS_TEMPLATE = `// The single source for everything the matching gates need to know about this\n// site. DATA lives in matching/harness.json (site-edited); this file is the\n// READ LAYER — edit harness.json, not this. (It is installed and upgraded by\n// the \\`reddoor-maint match-harness\\` recipe, which owns these bytes: a hand\n// edit here is flagged on the next run and never silently overwritten.)\n//\n// It exists because the page table, the two hosts, the matrix, the threshold\n// and the skill path were hand-copied all over matching/. Re-measured\n// 2026-09-09 AFTER the six-probe conversion, over the 216 tracked scripts under\n// matching/ (214 top-level — 212 .mjs + 2 .sh — plus 2 in states/):\n//\n// • a hand-typed copy of the page table (three or more gate keys sitting next\n// to their route): 8 files. Exactly ONE of them, probe-chrome-count.mjs,\n// still carries all nine rows; probe-anchors.mjs carries five; the other\n// six are three-row detail triples (team/svc/qa).\n// • the skill path (~/.claude/skills/matching-a-page): 193 copies\n// • the viewport matrix (1440/834/390): 51 copies\n// • REF pointed at a host listed in selfHosts — i.e. comparing the candidate\n// with itself: 12 scripts, one of which (probe-chrome-count.mjs) is also\n// the last nine-row table carrier\n//\n// The first bullet read \"the nine-row page table: 5 copies — gate.sh,\n// probe-anchor-parity.mjs, sweep-all10.sh, sweep-all16.sh, sweep-final.sh\" when\n// it was written here at 922dde3. It was wrong within the hour and wrong on two\n// counts: 4e2cd7b took the table out of gate.sh, and the list never named\n// states/index.mjs or probe-chrome-count.mjs, which were both carrying nine-row\n// copies at the time. A census is a claim about code; it has to be measured\n// against the tree, not recalled.\n//\n// node matching/harness.mjs --env shell-safe KEY='value' lines\n// node matching/harness.mjs --table key<TAB>ref<TAB>cand<TAB>anchors\n// node matching/harness.mjs --check-ref the D11 preflight; exit 2 on failure\nimport { readFileSync, realpathSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { fileURLToPath, pathToFileURL } from \"node:url\";\n\n// fileURLToPath, not URL#pathname: pathname is percent-encoded, so a checkout\n// under a directory with a space in it would resolve to a path that does not\n// exist.\nconst DIR = fileURLToPath(new URL(\".\", import.meta.url));\nconst CFG = JSON.parse(readFileSync(join(DIR, \"harness.json\"), \"utf8\"));\n\n// Env overrides exist for one-off probes only. They are NOT how a site is\n// configured — harness.json is, so that what a gate ran against is committed.\nexport const REF = process.env.MATCH_REF ?? CFG.ref;\nexport const CAND = process.env.MATCH_CAND ?? process.env.CAND_BASE ?? CFG.cand;\nexport const MATRIX = CFG.matrix;\nexport const THRESHOLD = CFG.threshold;\nexport const MAX_HEIGHT_DELTA = CFG.maxHeightDelta;\nexport const REF_MARK = CFG.refMark;\nexport const CAND_MARK = CFG.candMark;\nexport const SELF_HOSTS = CFG.selfHosts ?? [];\n\n/** One record per gated page, in harness.json order. \\`key\\` is the gate key\n * (out-<TAG>-<key>, the SPEC heading, spec-sections/<key>.md); \\`uid\\` is the\n * Prismic uid or null where the page has no /dev/match twin. */\nexport const PAGES = Object.entries(CFG.pages).map(([key, p]) => ({ key, ...p }));\nexport const byKey = Object.fromEntries(PAGES.map((p) => [p.key, p]));\n\n// DERIVED, never hand-typed: page-diff cuts one region before the first anchor\n// (\"top\") plus one per anchor, at every viewport. The old hand-written map went\n// stale the moment an anchor list changed, and a wrong denominator makes the\n// score a lie in the flattering direction.\nexport const TOTALS = Object.fromEntries(\n PAGES.map((p) => [p.key, (p.anchors.length + 1) * MATRIX.length]),\n);\n\n/** The SPEC.md heading predicate, shared by gate.sh's preflight and\n * build-spec.mjs so a section can never build fine and then refuse at the\n * gate. Matches the key followed by any non-key character (\\`## team\\` matches,\n * \\`## teamfoo\\` does not, \\`## our-team\\` cannot match \\`team\\`). */\nexport const specHeadingRe = (key) => new RegExp(\\`^##+ +\\${key}([^A-Za-z0-9_-]|$)\\`, \"m\");\n\nexport const SKILL_DIR =\n process.env.MATCHING_SKILL_DIR ?? join(homedir(), \".claude/skills/matching-a-page\");\nexport const PD = join(SKILL_DIR, \"page-diff.mjs\");\nexport const SC = join(SKILL_DIR, \"style-census.mjs\");\nexport const PLAYWRIGHT = pathToFileURL(join(SKILL_DIR, \"node_modules/playwright/index.mjs\")).href;\n\n/** The report format this site's scripts can read, so gate.sh can compare it\n * with \\`page-diff --version\\` before spending a run and next.mjs can refuse\n * rather than quietly drop a page whose newest report came from another\n * schema. Both do that now — gate.sh preflights \\`page-diff --version\\` against\n * this value before spending a run, and next.mjs counts a foreign-schema\n * report as MISSING rather than skipping it. (This said \"neither does that\n * yet\" — true at 922dde3 where it was written, false from 4e2cd7b, which\n * gave gate.sh the preflight and did not come back here.) Checked 2026-09-09\n * against the installed skill: \\`page-diff --version\\` → \\`page-diff 0.1.0\n * report-schema 1\\`. */\nexport const REPORT_SCHEMA = 1;\n\n/**\n * Fail-closed reference preflight. A 200 is NOT evidence: a host that has been\n * repointed at our own build answers 200, and so does a staging host serving a\n * 404 page. Both have happened on a real site — see the dated measurement in\n * LEDGER.md. A pass here requires an artefact only the reference produces.\n */\nexport async function checkRef() {\n if (!REF_MARK) {\n return {\n ok: false,\n why: \"harness.json refMark is empty — set it to a string only the reference serves (a Webflow site id, a build hash). A 200 is not evidence.\",\n };\n }\n const host = new URL(REF).host;\n if (SELF_HOSTS.includes(host)) return { ok: false, why: \\`REF host \\${host} is in selfHosts\\` };\n if (host === new URL(CAND).host) return { ok: false, why: \\`REF host \\${host} equals CAND's host\\` };\n let res;\n try {\n res = await fetch(\\`\\${REF}/\\`, { redirect: \"manual\" });\n } catch (e) {\n return { ok: false, why: \\`GET \\${REF}/ failed: \\${e.message}\\` };\n }\n if (res.status !== 200)\n return { ok: false, why: \\`GET \\${REF}/ → HTTP \\${res.status}, expected 200\\` };\n const loc = res.headers.get(\"location\");\n if (loc) return { ok: false, why: \\`GET \\${REF}/ → \\${res.status} redirect to \\${loc}\\` };\n const body = await res.text();\n if (!body.includes(REF_MARK))\n return {\n ok: false,\n why: \\`\\${REF}/ served 200 but WITHOUT refMark \\${JSON.stringify(REF_MARK)} — that is not the reference\\`,\n };\n if (CAND_MARK && body.includes(CAND_MARK))\n return {\n ok: false,\n why: \\`\\${REF}/ contains candMark \\${JSON.stringify(CAND_MARK)} — REF is serving OUR build\\`,\n };\n return { ok: true, why: \\`\\${REF}/ → 200, no redirect, refMark present, candMark absent\\` };\n}\n\n// CLI. Both sides go through realpathSync. \\`import.meta.url\\` is ALREADY the\n// resolved real path (node resolves symlinks unless --preserve-symlinks) while\n// process.argv[1] is the path as typed, so a plain pathToFileURL compare goes\n// false the moment any component of the invoked path is a symlink — and then\n// the CLI prints nothing and exits 0, which every caller reads as success.\n// page-diff.mjs:184-189 records exactly that defect and the same fix: \"The\n// pathToFileURL compare that replaced the old template string is still false\n// whenever ANY component of the invoked path is a symlink — which is how this\n// skill is installed now (~/.claude/skills/matching-a-page -> the claude-skills\n// checkout). isMain() resolves the real path on both sides.\"\nconst isMain = () => {\n if (!process.argv[1]) return false;\n try {\n return realpathSync(fileURLToPath(import.meta.url)) === realpathSync(process.argv[1]);\n } catch {\n return false;\n }\n};\n\nif (isMain()) {\n const mode = process.argv[2];\n const q = (v) => \\`'\\${String(v).replace(/'/g, \\`'\\\\\\\\''\\`)}'\\`;\n if (mode === \"--env\") {\n const pairs = [\n [\"REF\", REF],\n [\"CAND\", CAND],\n [\"MATRIX\", MATRIX.join(\",\")],\n [\"VIEWPORTS_SP\", MATRIX.join(\" \")],\n [\"THRESHOLD\", THRESHOLD],\n [\"MAX_HEIGHT_DELTA\", MAX_HEIGHT_DELTA],\n [\"PD\", PD],\n [\"SC\", SC],\n [\"REPORT_SCHEMA\", REPORT_SCHEMA],\n ];\n for (const [k, v] of pairs) console.log(\\`\\${k}=\\${q(v)}\\`);\n } else if (mode === \"--table\") {\n for (const p of PAGES) console.log([p.key, p.ref, p.cand, p.anchors.join(\",\")].join(\"\\\\t\"));\n } else if (mode === \"--check-ref\") {\n const r = await checkRef();\n console.log(\\`\\${r.ok ? \"REF OK\" : \"REF REFUSED\"} — \\${r.why}\\`);\n process.exit(r.ok ? 0 : 2);\n } else {\n console.error(\"usage: harness.mjs --env | --table | --check-ref\");\n process.exit(2);\n }\n}\n`;\n\nexport const GATE_SH_RELATIVE = \"matching/gate.sh\";\nexport const GATE_SH_TEMPLATE = `#!/usr/bin/env bash\n# The matching gate. This file is generic — everything specific to a site lives\n# in matching/harness.json (the data) and matching/LEDGER.md (the why). It is\n# installed and upgraded by the \\`reddoor-maint match-harness\\` recipe, which\n# owns these bytes: a hand edit here is flagged on the next run, never\n# silently overwritten.\n#\n# bash matching/gate.sh <round-tag> [page ...]\n#\n# Runs page-diff for every page in the table (or just the named ones) at the\n# full breakpoint matrix and writes matching/out-<round-tag>-<page>/.\n#\n# The matrix, the anchor lists, the reference and the candidate are DATA. Why a\n# site chose them — which live breakpoint band hid what, why an anchor is a\n# heading and not a button label — belongs in matching/LEDGER.md, which is dated\n# and append-only, because JSON holds no comments.\n#\n# NO MASKS and the threshold from harness.json everywhere: the numbers stay\n# honest and a known floor stays visible as its own region. A media-neutralised\n# secondary read is \\`node \"$PD\" ... --neutralize-media\\`; next.mjs ignores such\n# runs on purpose (next.mjs:57-64).\n#\n# PREFLIGHT (see the matching rules in CLAUDE.md): a page with no section in\n# matching/SPEC.md has not had Phase 1 done, and its geometry must not be\n# touched. Skipping the spec is how a reference's root-font ladder and its\n# per-component height ladders get found reactively, after the region has\n# already failed several rounds. This refuses the run instead of trusting anyone\n# to remember.\nset -u\n# Everything configurable lives in matching/harness.json; harness.mjs is the one\n# reader. --env emits shell-safe assignments (REF, CAND, MATRIX, VIEWPORTS_SP,\n# THRESHOLD, MAX_HEIGHT_DELTA, PD, SC, REPORT_SCHEMA).\neval \"$(node \"$(dirname \"$0\")/harness.mjs\" --env)\"\n\n# The skill must be able to write reports this site's scripts can read. Cheap,\n# local, and it fails before any browser starts.\nPD_SCHEMA=\"$(node \"$PD\" --version 2>/dev/null | awk '{print $4}')\"\nif [ \"$PD_SCHEMA\" != \"$REPORT_SCHEMA\" ]; then\n echo \"gate.sh: page-diff writes report schema '\\${PD_SCHEMA:-none}', this harness reads $REPORT_SCHEMA.\" >&2\n echo \" Update matching/harness.mjs REPORT_SCHEMA or the matching-a-page skill.\" >&2\n exit 2\nfi\nSPEC=\"$(dirname \"$0\")/SPEC.md\"\nTAG=\"\\${1:?usage: gate.sh <round-tag> [page ...]}\"\n# The tag must not contain a hyphen. Output dirs are \"out-<TAG>-<page>\", and\n# next.mjs recovers the page with /^out-[^-]+-(.+)$/ — it splits on the FIRST\n# hyphen, so a tag like \"r-forms-2026-08-07\" yields the page key\n# \"forms-2026-08-07-yfv\" and that run is silently never counted. It cannot split\n# on the last hyphen instead, because page keys have hyphens of their own\n# (\"our-team\", \"ask-the-doctor\"). Failing here is the cheap end of that: a\n# mis-tagged round otherwise LOOKS green because next.mjs keeps reading an older\n# report for the page you just changed. (Cost this once, 2026-08-07.)\ncase \"$TAG\" in\n *-*)\n echo \"gate.sh: round tag must not contain a hyphen (got '$TAG').\" >&2\n echo \" out-<TAG>-<page> is parsed on the first hyphen, so a\" >&2\n echo \" hyphenated tag hides the run from next.mjs. Try '\\${TAG//-/}'.\" >&2\n exit 2\n ;;\nesac\nshift || true\nWANT=(\"$@\")\n\n# Fail closed on the reference before spending a single run. A 200 is NOT\n# evidence: a production host that has cut over to OUR build answers 200, and\n# every region then scores near zero against itself. --check-ref requires an\n# artefact only the reference serves (harness.json refMark) and refuses a\n# redirect, a host listed in selfHosts, and a body carrying candMark. Measured\n# on the site this harness was cut from, 2026-09-09 AFTER the consolidation:\n# 12 of its 214 top-level matching scripts still assign REF a host listed in\n# selfHosts — i.e. compare the candidate with itself. It was 33 of 229 before.\n# (This comment read \"33 of its 230\" while sitting in the post-consolidation\n# tree, contradicting harness.mjs's 12 three files away.)\nif ! node \"$(dirname \"$0\")/harness.mjs\" --check-ref; then\n echo \"gate.sh: refusing to gate against an unverified reference.\" >&2\n exit 2\nfi\n\n# Set SPEC_OPTIONAL=1 only for a read-only baseline sweep of pages you are not\n# about to edit. It is recorded in the round tag so the exemption is visible.\nSPEC_OPTIONAL=\"\\${SPEC_OPTIONAL:-0}\"\n\nhas_spec() { # page\n # NB: the obvious \\`( |$|\\\\b)\\` guard is rejected by ugrep as an empty\n # subexpression, and a preflight that errors out fails CLOSED — it refused all\n # 9 pages while SPEC.md was complete. Match the page key followed by any\n # non-key character (so \\`## team\\` matches but \\`## teamfoo\\` does not; \\`##\n # our-team\\` cannot match \\`team\\` because the key must follow the spaces).\n [ -f \"$SPEC\" ] && grep -qE \"^##+ +$1([^A-Za-z0-9_-]|$)\" \"$SPEC\"\n}\n\nrun() { # tag refpath candpath sections\n local page=\"$1\" refpath=\"$2\" candpath=\"$3\" sections=\"$4\"\n if [ \\${#WANT[@]} -gt 0 ]; then\n local hit=0\n for w in \"\\${WANT[@]}\"; do [ \"$w\" = \"$page\" ] && hit=1; done\n [ $hit -eq 1 ] || return 0\n fi\n if ! has_spec \"$page\"; then\n if [ \"$SPEC_OPTIONAL\" = \"1\" ]; then\n echo \"########## $page ##########\"\n echo \"WARNING: no '## $page' section in matching/SPEC.md — Phase 1 not done.\"\n echo \" Running anyway because SPEC_OPTIONAL=1 (baseline read only).\"\n echo \" Do NOT apply geometry fixes off this run.\"\n else\n echo \"########## $page ##########\"\n echo \"REFUSED: no '## $page' section in matching/SPEC.md.\"\n echo \" Phase 1 (section census + per-section spec, read from\"\n echo \" matching/spec/) comes before geometry.\"\n echo \" See CLAUDE.md rule 2. SPEC_OPTIONAL=1 for a baseline read.\"\n FAILED_PREFLIGHT=1\n return 0\n fi\n fi\n echo \"########## $page ##########\"\n node \"$PD\" --ref \"$REF$refpath\" --cand \"$CAND$candpath\" \\\\\n --viewports \"$MATRIX\" --threshold \"$THRESHOLD\" \\\\\n --sections \"$sections\" --out \"matching/out-$TAG-$page\" \\\\\n > \"matching/out-$TAG-$page.log\" 2>&1\n echo \"$page exit=$?\"\n}\n\n# The page table is matching/harness.json. Process substitution, NOT a pipe:\n# a pipe would run this loop in a subshell and FAILED_PREFLIGHT would not\n# survive to the check below, so a refused page would exit 0.\nwhile IFS=$'\\\\t' read -r key refpath candpath anchors; do\n run \"$key\" \"$refpath\" \"$candpath\" \"$anchors\"\ndone < <(node \"$(dirname \"$0\")/harness.mjs\" --table)\n\nif [ \"\\${FAILED_PREFLIGHT:-0}\" = \"1\" ]; then\n echo\n echo \"GATE INCOMPLETE ($TAG) — one or more pages were refused for a missing\"\n echo \"SPEC.md section. Those pages have NOT been measured; do not report a\"\n echo \"score for them.\"\n exit 2\nfi\necho \"ALL DONE ($TAG)\"\n`;\n\nexport const CENSUS_SH_RELATIVE = \"matching/census.sh\";\nexport const CENSUS_SH_TEMPLATE = `#!/usr/bin/env bash\n# Phase 3 style gate — the mechanical net for the type spec.\n#\n# bash matching/census.sh # all 9 pages, all 3 viewports\n# bash matching/census.sh home yfv # just these\n#\n# style-census diffs the computed type tuple (family, weight, size,\n# line-height, letter-spacing, transform, colour) of every text snippet present\n# on BOTH pages. It is the only gate that catches the 11px footer line and the\n# cyan-vs-teal link that page-diff is structurally blind to — a region can be\n# pixel-clean at 0.10 and still be wearing the wrong colour on small text.\n#\n# Prints a per-page/per-viewport mismatch count and leaves the full runs in\n# matching/census-<page>-<vw>.log. Exits 1 while any count is non-zero, and 2\n# when it cannot honestly report a count at all.\n#\n# EVIDENCE — read before adding a guard here, or removing one. A green here\n# has to be produced by a census that RAN. It was not: with style-census.mjs\n# absent this gate printed \"Phase 3 CLEAN — 0 undeclared type mismatches\" and\n# exited 0. Every run died on module resolution, census-count.mjs read each\n# crash log as \"0 0 0\" (it counts \\` y=\\` rows and a stack trace has none —\n# census-count.mjs:35-56), and no rows was reported as no mismatches. Measured\n# again with style-census.mjs PRESENT and the candidate host down: identical\n# output, because style-census exits 1 both when it finds mismatches\n# (style-census.mjs:187) and when it dies before it looks — so the exit status\n# cannot tell a finding from a crash and may not grant a green either.\n#\n# The guards below are what next.mjs:83-89 (no parseable gate run — refusing to\n# report a score), strikes.mjs:87-97 (refusing to report \"clear\") and\n# gate.sh:37-42 (preflight page-diff before spending a run) already do. Each\n# demands an artefact only a working census produces; none of them can turn a\n# red green.\nset -uo pipefail\n# Resolved BEFORE the cd. $0 is the path as TYPED, so a relatively invoked\n# census.sh that resolved the harness afterwards would look for it under\n# whatever root the cd landed on.\nHARNESS=\"$(cd \"$(dirname \"$0\")\" && pwd)/harness.mjs\"\ncd \"$(dirname \"$0\")/..\"\n\n# Everything configurable lives in matching/harness.json; harness.mjs is the one\n# reader. --env supplies REF, CAND, SC and VIEWPORTS_SP — this gate held its own\n# copy of all four, and its REF went stale when production cut over to our own\n# build (LEDGER \"REFERENCE MOVED\", verified 2026-08-10) while gate.sh's was\n# repointed the same day.\neval \"$(node \"$HARNESS\" --env)\"\nNODE=\"\\${NODE:-node}\"\n\n# The matrix is DATA, read as VIEWPORTS_SP, the way gate.sh reads MATRIX. The\n# old VIEWPORTS override is retired rather than silently ignored: a census run\n# over a matrix harness.json does not name is a column set the pixel gate never\n# measured, and it would look exactly like a clean one.\nif [ -n \"\\${VIEWPORTS:-}\" ]; then\n echo \"census.sh: the VIEWPORTS override is retired — the matrix is data now.\" >&2\n echo \" Edit \\\\\"matrix\\\\\" in matching/harness.json, then unset it.\" >&2\n exit 2\nfi\n\n# GUARD 1 — the tool is there and it starts. gate.sh compares \\`page-diff\n# --version\\` with the harness's REPORT_SCHEMA before spending a run\n# (gate.sh:37-42); style-census has no --version to compare (page-diff.mjs\n# defines one at 191-195; nothing in style-census.mjs writes one), so the\n# artefact demanded here is the other one only it produces: the usage banner it\n# prints on exit 2 when --ref/--cand are missing (style-census.mjs:151-157).\n# That banner is not free — \\`import { chromium } from \"playwright\"\\` runs first\n# (style-census.mjs:19) — so a skill directory with no playwright installed\n# fails HERE, once, instead of writing one crash log per page per viewport that\n# every reader downstream counts as zero.\nif [ ! -f \"$SC\" ]; then\n echo \"census.sh: no style-census at $SC — refusing to report a count.\" >&2\n echo \" Install the matching-a-page skill, or point MATCHING_SKILL_DIR\" >&2\n echo \" at a checkout that has style-census.mjs.\" >&2\n exit 2\nfi\nSC_USAGE=\"$(\"$NODE\" \"$SC\" 2>&1)\"\ncase \"$SC_USAGE\" in\n *\"style-census\"*\"--ref\"*\"--cand\"*\"--vw\"*) ;;\n *)\n echo \"census.sh: $SC did not answer with a style-census --ref/--cand/--vw usage\" >&2\n echo \" banner — refusing to report a count. It printed:\" >&2\n echo \" \\${SC_USAGE:-(nothing at all)}\" >&2\n exit 2\n ;;\nesac\n\ndeclare -a WANT=(\"$@\")\n\n# page -> \"refpath candpath\": the same table gate.sh drives, from the same file.\n# cut drops the anchors column, which the style census has no argument for\n# (style-census.mjs: --ref <url> --cand <url> [--vw 1440]).\npages() { node \"$HARNESS\" --table | cut -f1-3; }\n\nTOTAL=0\nAMB=0\nDECL=0\nROWS=0\nRUNS=0\nBROKEN=0\nSEEN=\"\"\n# One column per viewport, from the matrix. Flush with the data rows below, at\n# last: the old header hardcoded four fixed fields separated by a literal space,\n# which the rows do not have, so its labels sat 1/2/3 columns right of them.\nprintf '%-10s' page\nfor vw in $VIEWPORTS_SP; do printf '%8s' \"$vw\"; done\nprintf '\\\\n'\nwhile read -r page refpath candpath; do\n [ -z \"$page\" ] && continue\n SEEN=\"$SEEN $page\"\n if [ \\${#WANT[@]} -gt 0 ]; then\n hit=0\n for w in \"\\${WANT[@]}\"; do [ \"$w\" = \"$page\" ] && hit=1; done\n [ $hit -eq 1 ] || continue\n fi\n ROWS=$((ROWS + 1))\n line=$(printf '%-10s' \"$page\")\n for vw in $VIEWPORTS_SP; do\n log=\"matching/census-$page-$vw.log\"\n RUNS=$((RUNS + 1))\n \"$NODE\" \"$SC\" --ref \"$REF$refpath\" --cand \"$CAND$candpath\" --vw \"$vw\" >\"$log\" 2>&1\n # GUARD 2 — this run produced a census. The header and the counts line are\n # written together at style-census.mjs:166-169 and only after both pages\n # have been walked, so they are the artefact a completed run leaves and a\n # crashed one cannot. The viewport is read back out of the header because a\n # --vw that did not take would otherwise fill three differently named logs\n # with the same 1440 census. \\`ref runs\\`/\\`cand runs\\` must be non-zero: two\n # pages that rendered no text at all agree perfectly and mean nothing.\n if ! grep -qE \"^=== style census, viewport $vw ===\\\\$\" \"$log\" ||\n ! grep -qE \"^ref runs: [1-9][0-9]* +cand runs: [1-9][0-9]* +mismatches: [0-9]+ +ambiguous: [0-9]+\\\\$\" \"$log\"; then\n BROKEN=$((BROKEN + 1))\n echo \"census.sh: $page @$vw produced no usable census — see $log\" >&2\n line=\"$line$(printf '%8s' '!!')\"\n continue\n fi\n # census-count splits the log three ways: REAL mismatches, AMBIGUOUS\n # same-text collisions (style-census's own split), and DECLARED rows the\n # operator has already ruled on (matching/census-deviations.mjs, the same\n # contract as floors.mjs). Only the first is outstanding work — without the\n # third, this gate can never reach zero and its number means nothing.\n read -r n a d <<<\"$(\"$NODE\" matching/census-count.mjs \"$log\")\"\n # GUARD 2b — three integers, or the counter did not count. census-count.mjs\n # is the reader that turned a stack trace into \"0 0 0\"; a run of it that\n # dies (a broken census-deviations.mjs, a bad path) prints nothing at all,\n # and bash reads an empty $n as zero in the arithmetic below.\n counted=1\n for v in \"\\${n:-}\" \"\\${a:-}\" \"\\${d:-}\"; do\n case \"$v\" in \"\" | *[!0-9]*) counted=0 ;; esac\n done\n if [ \"$counted\" -eq 0 ]; then\n BROKEN=$((BROKEN + 1))\n echo \"census.sh: census-count.mjs did not return three counts for $log\" >&2\n line=\"$line$(printf '%8s' '!!')\"\n continue\n fi\n # GUARD 2c — the number reported is the number the census MEASURED. GUARD 2\n # matched the counts line for PRESENCE and threw its numbers away, so the\n # count printed below came from census-count's row parse and nothing ever\n # compared the two. Measured 2026-09-09: a COMPLETE census whose counts line\n # said \"mismatches: 3\", whose \\`y=\\` rows carried one leading space instead of\n # two (census-count.mjs:39 matches /^ {2}y=/), was reported as 0 and this\n # gate printed \"Phase 3 CLEAN — 0 undeclared type mismatches\", exit 0. The\n # printer lives in the SKILL (style-census.mjs:171-173) and the parser is\n # copied into every site, so the two version independently and drift needs\n # nobody to touch either repo.\n #\n # The identity is exact for mismatches: style-census prints at most 100 rows\n # and states the remainder on its own line (style-census.mjs:175-177), so\n # parsed + remainder == reported. Ambiguous rows are capped the same way\n # (:184) but the remainder is NOT stated, so only a floor is checkable\n # there — any is not none, and the parse may never exceed the report.\n counts=$(grep -E \"^ref runs: [1-9][0-9]* +cand runs: [1-9][0-9]* +mismatches: [0-9]+ +ambiguous: [0-9]+$\" \"$log\" | tail -1)\n said_m=\\${counts##*mismatches: }\n said_m=\\${said_m%% *}\n said_a=\\${counts##*ambiguous: }\n more=$(sed -n 's/^ *… and \\\\([0-9][0-9]*\\\\) more (truncated print, all counted)$/\\\\1/p' \"$log\" | tail -1)\n if [ $((n + d + \\${more:-0})) -ne \"$said_m\" ] ||\n [ \"$a\" -gt \"$said_a\" ] ||\n { [ \"$said_a\" -gt 0 ] && [ \"$a\" -eq 0 ]; }; then\n BROKEN=$((BROKEN + 1))\n echo \"census.sh: $log reports mismatches: $said_m ambiguous: $said_a, but\" >&2\n echo \" census-count.mjs read $((n + d)) mismatch row(s)\\${more:+ (+ $more truncated)} and $a ambiguous.\" >&2\n echo \" The census and its reader disagree; there is no count to report.\" >&2\n line=\"$line$(printf '%8s' '!!')\"\n continue\n fi\n AMB=$((AMB + a))\n DECL=$((DECL + d))\n TOTAL=$((TOTAL + n))\n line=\"$line$(printf '%8s' \"$n\")\"\n done\n echo \"$line\"\ndone < <(pages)\n\necho\n# GUARD 3 — something was actually censused. Process substitution above, NOT a\n# pipe: a pipe runs the loop in a subshell and every counter here would still\n# read 0, which is exactly the green this file exists to stop (gate.sh:123-125\n# records the same trap for FAILED_PREFLIGHT). A page name that matches nothing\n# must not report CLEAN either — strikes.mjs:147-156 is the same refusal, and\n# it was written because a typo silently greened rule 3 on six of nine pages.\nif [ \"$ROWS\" -eq 0 ]; then\n if [ -n \"$SEEN\" ]; then\n echo \"census.sh: \\\\\"\\${WANT[*]:-}\\\\\" matches no page — refusing to report CLEAN.\" >&2\n echo \" known pages:$SEEN\" >&2\n else\n echo \"census.sh: the page table is empty — refusing to report CLEAN.\" >&2\n echo \" node matching/harness.mjs --table printed no rows; check\" >&2\n echo \" \\\\\"pages\\\\\" in matching/harness.json.\" >&2\n fi\n exit 2\nfi\nif [ \"$BROKEN\" -gt 0 ]; then\n echo \"CENSUS INCOMPLETE — $BROKEN of $RUNS run(s) produced no usable census (marked !!).\"\n echo \"Those page/viewport pairs have NOT been measured; there is no count to\"\n echo \"report for them, and the totals below would be a floor, not a score.\"\n echo \"Full runs: matching/census-<page>-<vw>.log\"\n exit 2\nfi\nif [ \"$TOTAL\" -eq 0 ]; then\n echo \"Phase 3 CLEAN — 0 undeclared type mismatches ($DECL declared, $AMB ambiguous).\"\n echo \"($RUNS censused run(s) over $ROWS page(s), each one counted.)\"\n [ \"$AMB\" -gt 0 ] && echo \"($AMB ambiguous same-text rows remain: each needs ADJUDICATING,\" &&\n echo \" not fixing — our element matches, another sharing its text does not.)\"\n exit 0\nfi\necho \"$TOTAL type mismatch(es) remain (+ $AMB ambiguous, $DECL declared).\"\necho \"Full runs: matching/census-<page>-<vw>.log\"\necho \"A mismatch is a defect or a ledgered deviation. An ambiguous row is neither\"\necho \"until you look: the census keys on TEXT, so two different elements sharing a\"\necho \"string land under one key and only one of them may be wrong.\"\nexit 1\n`;\n\nexport const NEXT_MJS_RELATIVE = \"matching/next.mjs\";\nexport const NEXT_MJS_TEMPLATE = `// What is still broken, ranked. Exits 1 while work remains.\n//\n// node matching/next.mjs\n//\n// Round protocol step 0 (repo CLAUDE.md rule 5). A commit is a CHECKPOINT, not\n// a stopping point: after committing, run this. If it exits 1 there is a named\n// next action and the round continues without handing control back.\n//\n// Reads the most recent gate log per page rather than the whole corpus, so it\n// reflects HEAD rather than history (that is strikes.mjs's job).\nimport { existsSync, readdirSync, readFileSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nconst DIR = new URL(\".\", import.meta.url).pathname;\n\n// PAUSE SWITCH. While matching/PAUSED exists this hands out no agenda and\n// exits 0. It is deliberately the FIRST thing that runs: no report is read, no\n// score is printed, nothing tempting is put on screen to argue with.\n//\n// Why an exit code and not a note somewhere: rule 5 is a LOOP — \"after\n// committing, run next.mjs; while it exits 1 there is a named next action and\n// the round continues\". A pause written as prose loses to that loop, because\n// the loop is mechanical and the prose is not. Exiting 0 satisfies rule 5\n// truthfully rather than suspending it: there is no next action.\nconst PAUSE = join(DIR, \"PAUSED\");\nif (existsSync(PAUSE)) {\n console.log(\"MATCHING PAUSED — no agenda, and none is to be inferred.\\\\n\");\n console.log(readFileSync(PAUSE, \"utf8\").trimEnd());\n process.exit(0);\n}\n\nimport { FLOORS, ACCEPTED } from \"./floors.mjs\";\nimport { TOTALS, THRESHOLD, MAX_HEIGHT_DELTA, REPORT_SCHEMA } from \"./harness.mjs\";\n\n// Reports written by a different page-diff, by page key. Kept rather than\n// dropped: silently ignoring them is how a page vanishes from the score.\nconst schemaMismatch = new Set();\n\nconst latest = new Map();\nfor (const d of readdirSync(DIR).filter((d) => d.startsWith(\"out-\"))) {\n const m = /^out-[^-]+-(.+)$/.exec(d);\n if (!m || !TOTALS[m[1]]) continue;\n let report, mtime;\n try {\n const p = join(DIR, d, \"report.json\");\n report = JSON.parse(readFileSync(p, \"utf8\"));\n mtime = statSync(p).mtimeMs;\n } catch {\n continue;\n }\n // A masked / media-neutralised run is a DIAGNOSTIC, never the state of the\n // page. Picking one up as \"latest\" silently reports scores nobody can ship —\n // it happened immediately: an --mask-photos probe of yfv made \\`top\\` @834 read\n // 43.9% here while the real gate had it passing at 1.3%.\n const meta = report.meta ?? {};\n // Missing schemaVersion means \"written before the field existed\" = 0. It is\n // not an error on its own; it is only fatal when it would blank a page.\n if ((meta.schemaVersion ?? 0) !== REPORT_SCHEMA) {\n schemaMismatch.add(m[1]);\n continue;\n }\n if (\n (meta.mask?.length ?? 0) > 0 ||\n meta.neutralizeMedia ||\n meta.maskPhotos ||\n meta.truncated\n )\n continue;\n if (meta.threshold !== THRESHOLD) continue;\n const prev = latest.get(m[1]);\n if (!prev || mtime > prev.mtime) latest.set(m[1], { dir: d, mtime, report });\n}\n\nconst blanked = [...schemaMismatch].filter((p) => !latest.has(p));\nif (blanked.length) {\n console.error(\n \\`next: \\${blanked.length} page(s) have no run at report schema \\${REPORT_SCHEMA} — \\` +\n \\`their newest reports came from a different page-diff (\\${blanked.sort().join(\", \")}).\\\\n\\` +\n \\` Re-run: bash matching/gate.sh <tag> \\${blanked.sort().join(\" \")}\\`,\n );\n process.exit(2);\n}\nif (latest.size === 0) {\n console.error(\n \"next: no parseable gate run under matching/ — refusing to report a score.\\\\n\" +\n \" Run bash matching/gate.sh <tag> first.\",\n );\n process.exit(2);\n}\n\nconst rows = [];\nconst accepted = [];\nlet openTotal = 0;\nlet floorTotal = 0;\nfor (const [page, { dir, report }] of latest) {\n const fails = report.regions.filter((r) => !r.pass);\n for (const f of fails) {\n const floor = FLOORS.find((fl) => fl.match(f, page));\n if (floor) {\n floorTotal++;\n continue;\n }\n const ack = ACCEPTED.find((a) => a.match(f, page));\n if (ack) {\n accepted.push({ page, vw: f.viewport, label: f.label, why: ack.why });\n continue;\n }\n openTotal++;\n rows.push({\n page,\n vw: f.viewport,\n label: f.label,\n mm: f.mismatchFraction,\n dh: f.heightDeltaFraction ?? 0,\n dir,\n });\n }\n}\n\nconst scored = [...latest.entries()]\n .map(([p, v]) => ({\n p,\n pass: v.report.regions.filter((r) => r.pass).length,\n total: TOTALS[p],\n }))\n .sort((a, b) => a.pass / a.total - b.pass / b.total);\n\nconst sum = scored.reduce((a, s) => a + s.pass, 0);\nconst max = scored.reduce((a, s) => a + s.total, 0);\nconsole.log(\\`SCORE \\${sum}/\\${max} regions passing\\\\n\\`);\nconsole.log(\n scored\n .map((s) => \\` \\${s.p.padEnd(9)} \\${String(s.pass).padStart(2)}/\\${s.total}\\`)\n .join(\"\\\\n\"),\n);\n\nif (accepted.length) {\n console.log(\\`\\\\nOperator-ACCEPTED failures (left failing on purpose):\\`);\n for (const a of accepted)\n console.log(\\` \\${a.page} @\\${a.vw} \"\\${a.label}\" — \\${a.why.slice(0, 96)}…\\`);\n}\n\nif (!rows.length) {\n console.log(\n \\`\\\\nNo open geometry failures. \\${floorTotal} declared floor(s) remain.\\`,\n );\n console.log(\n \"Backlog is empty — Phases 5 (states) and 6 (adversarial review) are what is left.\",\n );\n process.exit(0);\n}\n\n// Worst page first, then worst region inside it: fix where the model is most wrong.\nconst worst = scored[0].p;\nrows.sort(\n (a, b) =>\n (a.page === worst ? -1 : 0) - (b.page === worst ? -1 : 0) || b.mm - a.mm,\n);\n\nconsole.log(\n \\`\\\\n\\${openTotal} open failure(s) + \\${floorTotal} declared floor(s).\\`,\n);\nconsole.log(\\`\\\\nNEXT: \\${worst} — worst page. Its open regions:\\\\n\\`);\nfor (const r of rows.filter((r) => r.page === worst)) {\n const why = [];\n if (r.mm > THRESHOLD) why.push(\\`pixels \\${(r.mm * 100).toFixed(1)}%\\`);\n if (Math.abs(r.dh) > MAX_HEIGHT_DELTA)\n why.push(\\`height \\${(r.dh * 100).toFixed(1)}%\\`);\n console.log(\n \\` @\\${String(r.vw).padEnd(5)} \\${r.label.slice(0, 44).padEnd(45)} \\${why.join(\" + \")}\\`,\n );\n}\nconsole.log(\\`\\\\nBefore treating any of these as geometry:\\`);\nconsole.log(\n \\` node matching/probe-anchor-parity.mjs \\${worst} # is the gate cutting comparably?\\`,\n);\nconsole.log(\n \\` node matching/strikes.mjs \\${worst} # has it stalled? then change the MODEL\\`,\n);\nconsole.log(\n \\`\\\\nRound continues. Do not hand back control with work outstanding.\\`,\n);\nprocess.exit(1);\n`;\n\nexport const STRIKES_MJS_RELATIVE = \"matching/strikes.mjs\";\nexport const STRIKES_MJS_TEMPLATE = `// The 3-strike rule, made countable.\n//\n// node matching/strikes.mjs [page]\n//\n// The matching skill says: \"A region that has not improved after 3 fix ->\n// re-run cycles: stop. Present the three attempts. Do not widen the threshold,\n// mask it, or reclassify it — a stuck region means your model of it is wrong.\"\n//\n// That rule was blown twice on this project (services \\`top\\`, yfv \\`top\\`) purely\n// because nobody was counting. This reads every matching/out-*/report.json,\n// reconstructs each region's history in chronological order, and prints the\n// regions that are OUT OF STRIKES. Exit 1 when any exist, so a round that\n// starts by running this cannot quietly attempt a fourth cycle.\n//\n// WHAT THIS ACTUALLY MEASURES (read before trusting a number): report.json\n// cannot record INTENT, so a \"cycle\" here is any completed gate run in which\n// the region was still failing and did not improve by IMPROVE_PP. Runs aimed at\n// a different region on the same page therefore count too. That makes this a\n// STALL detector, not a literal attempt counter — \"this has been failing,\n// unchanged, across N gate runs while you were working on this page.\" That is\n// the more useful discipline number anyway, and it is honest about being an\n// upper bound. Improvement resets the count: real progress earns a fresh start.\nimport { existsSync, readdirSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { FLOORS } from \"./floors.mjs\";\nimport {\n PAGES,\n THRESHOLD,\n MAX_HEIGHT_DELTA,\n REPORT_SCHEMA,\n} from \"./harness.mjs\";\n\n// PAUSE SWITCH — see matching/next.mjs for why this is an exit code. Round\n// protocol step 1 is \"run strikes.mjs; the stalled regions are the agenda\", so\n// this is the second door into the backlog and has to be shut too. Exits 0:\n// there are no strikes to spend when no round is being played.\nconst PAUSED = join(new URL(\".\", import.meta.url).pathname, \"PAUSED\");\nif (existsSync(PAUSED)) {\n console.log(\"MATCHING PAUSED — no agenda, and none is to be inferred.\\\\n\");\n console.log(readFileSync(PAUSED, \"utf8\").trimEnd());\n process.exit(0);\n}\n\nconst IMPROVE_PP = 0.01; // 1 percentage point = the smallest move worth calling progress\nconst MAX_STRIKES = 3;\nconst ROOT = new URL(\".\", import.meta.url).pathname;\nconst only = process.argv[2] ?? null;\n\n// The gate key for a report, from its ref path. Deriving it by string surgery\n// disagreed with the gate on exactly six of the nine rows — yfv, contact, atd,\n// svc, qa and team (measured 2026-09-09 against harness.json); the table knows.\n// keyOf (below) stays as the fallback for a run whose ref was rewritten.\nconst pageOf = (ref) => {\n const path = new URL(ref).pathname.replace(/\\\\/$/, \"\") || \"/\";\n return PAGES.find((p) => p.ref === path)?.key ?? null;\n};\n\nconst runs = [];\nconst unreadable = [];\nfor (const dir of readdirSync(ROOT).filter((d) => d.startsWith(\"out-\"))) {\n let report;\n try {\n report = JSON.parse(readFileSync(join(ROOT, dir, \"report.json\"), \"utf8\"));\n } catch {\n continue; // not a completed run dir\n }\n if (!report.meta?.ref || !Array.isArray(report.regions)) continue;\n // A report from another schema is still usable for a STALL count as long as\n // it carries the two fields this reads. Anything else is counted and named,\n // never silently dropped — an under-counted history reads as \"clear\".\n const usable = report.regions.every(\n (r) =>\n typeof r.mismatchFraction === \"number\" && typeof r.pass === \"boolean\",\n );\n if (!usable) {\n unreadable.push(\\`\\${dir} (schema \\${report.meta.schemaVersion ?? 0})\\`);\n continue;\n }\n runs.push({\n dir,\n at: report.meta.generatedAt,\n meta: report.meta,\n regions: report.regions,\n });\n}\n\n// The failure mode of a stall detector is always \"clear\", which is exactly the\n// answer that stops nobody. A corpus this could not read is not a clean one.\nif (runs.length === 0) {\n console.error(\n \\`strikes: no parseable gate run under matching/ — refusing to report \"clear\".\\` +\n (unreadable.length\n ? \\`\\\\n \\${unreadable.length} report(s) unreadable at schema \\${REPORT_SCHEMA}: \\${unreadable.slice(0, 5).join(\", \")}\\`\n : \"\"),\n );\n process.exit(2);\n}\nif (unreadable.length) {\n console.error(\n \\`strikes: ignored \\${unreadable.length} unreadable report(s) — the history below is incomplete.\\` +\n \\`\\\\n \\${unreadable.slice(0, 5).join(\", \")}\\`,\n );\n}\n\nruns.sort((a, b) => a.at.localeCompare(b.at));\n\n// The gate page KEY, recovered from the run dir the way next.mjs does it\n// (out-<TAG>-<page>, split on the first hyphen — the tag is hyphen-free by\n// gate.sh's own preflight). \\`pageOf\\` now returns that same key off the table,\n// so the two vocabularies agree and the round protocol in CLAUDE.md — \"run\n// \\`strikes.mjs <page>\\` with the key you just passed to gate.sh\", which until\n// 2026-08-13 silently matched nothing — works either way. This stays because\n// a dir key is not always a gate key: 69 of this corpus's 377 run dirs are\n// hand-named probes (out-band3, out-390masked), and FLOORS matches on it.\nconst keyOf = (dir) => /^out-[^-]+-(.+)$/.exec(dir)?.[1] ?? null;\n\n// key -> chronological list of {dir, at, mm, pass, masked}\nconst history = new Map();\nconst seenNames = new Set();\nfor (const run of runs) {\n const page = pageOf(run.meta.ref) ?? keyOf(run.dir) ?? \"unknown\";\n const gateKey = keyOf(run.dir);\n seenNames.add(page);\n if (gateKey) seenNames.add(gateKey);\n if (only && page !== only && gateKey !== only) continue;\n for (const r of run.regions) {\n // A DECLARED FLOOR is flat by definition — reporting it as stalled is noise\n // that hides a real stall. It stays in the LEDGER; it does not belong here.\n if (FLOORS.some((fl) => fl.match(r, gateKey || page))) continue;\n const key = \\`\\${page}|\\${r.viewport}|\\${r.label}\\`;\n if (!history.has(key)) history.set(key, []);\n history.get(key).push({\n dir: run.dir,\n at: run.at,\n mm: r.mismatchFraction,\n dh: r.heightDeltaFraction,\n pass: r.pass,\n // a run with masks/neutralised media is not comparable to a clean one\n dirty:\n (run.meta.mask?.length ?? 0) > 0 ||\n run.meta.neutralizeMedia ||\n run.meta.maskPhotos,\n });\n }\n}\n\n// A name that matches no run must NOT report \"clear\". This check exists to stop\n// work on a stalled region, so failing open is the one thing it may never do —\n// a typo'd or wrong-vocabulary page silently greened rule 3 for six of the nine\n// pages. Fail loud instead, and say what the vocabulary is.\nif (only && history.size === 0) {\n console.error(\n \\`strikes: \"\\${only}\" matches no gate run — refusing to report \"clear\".\\\\n\\` +\n \\` known pages: \\${[...seenNames].sort().join(\", \")}\\`,\n );\n process.exit(2);\n}\n\nconst stuck = [];\nfor (const [key, all] of history) {\n const h = all.filter((e) => !e.dirty); // compare like with like\n if (h.length === 0) continue;\n const last = h[h.length - 1];\n if (last.pass) continue;\n\n let best = Infinity;\n let strikes = 0;\n let sinceIdx = 0;\n for (let i = 0; i < h.length; i++) {\n if (h[i].mm < best - IMPROVE_PP) {\n best = Math.min(best, h[i].mm);\n strikes = 0;\n sinceIdx = i;\n } else {\n best = Math.min(best, h[i].mm);\n strikes++;\n }\n }\n if (strikes >= MAX_STRIKES) stuck.push({ key, strikes, h, sinceIdx, last });\n}\n\n// Triage order: the worst-matching region that has never moved is the one whose\n// model is most wrong. Sorting by strike count instead would put a 0.3% region\n// that only fails on height above a 76% region that fails on everything.\nstuck.sort((a, b) => b.last.mm - a.last.mm);\n\nif (stuck.length === 0) {\n console.log(\n \\`strikes: clear — no failing region has stalled for \\${MAX_STRIKES}+ gate runs\\` +\n (only ? \\` on \\${only}\\` : \"\") +\n \".\",\n );\n process.exit(0);\n}\n\nconst why = (e) => {\n const reasons = [];\n if (e.mm > THRESHOLD) reasons.push(\\`pixels \\${(e.mm * 100).toFixed(1)}%\\`);\n if (Math.abs(e.dh ?? 0) > MAX_HEIGHT_DELTA)\n reasons.push(\\`height \\${((e.dh ?? 0) * 100).toFixed(1)}%\\`);\n return reasons.join(\" + \") || \"marginal\";\n};\n\nconsole.log(\n \\`STALLED — \\${stuck.length} failing region(s) have not moved in \\${MAX_STRIKES}+ gate runs.\\\\n\\` +\n \\`These need a new model or the operator, not another attempt. Worst first:\\\\n\\`,\n);\nfor (const { key, strikes, h, sinceIdx, last } of stuck.slice(0, 20)) {\n const [page, vw, label] = key.split(\"|\");\n console.log(\n \\`\\${page} @\\${vw} \"\\${label}\" — \\${why(last)}, flat across \\${strikes} runs\\`,\n );\n const window = h.slice(Math.max(0, sinceIdx));\n const shown = window.length > 4 ? [window[0], ...window.slice(-3)] : window;\n for (const [i, e] of shown.entries()) {\n const gap =\n window.length > 4 && i === 1\n ? \\` ... \\${window.length - 4} more ...\\\\n\\`\n : \"\";\n console.log(\n gap +\n \\` \\${(e.mm * 100).toFixed(1).padStart(5)}% \\${e.at.slice(0, 16).replace(\"T\", \" \")} \\${e.dir}\\`,\n );\n }\n console.log();\n}\nif (stuck.length > 20) console.log(\\`... and \\${stuck.length - 20} more.\\\\n\\`);\nconsole.log(\n \"Per the matching skill: present the attempts to the operator.\\\\n\" +\n \"Do NOT widen the threshold, add a mask, or reclassify them as a floor.\",\n);\nprocess.exit(1);\n`;\n\nexport const BUILD_SPEC_MJS_RELATIVE = \"matching/build-spec.mjs\";\nexport const BUILD_SPEC_MJS_TEMPLATE = `// Assemble matching/SPEC.md from the per-page sections in spec-sections/.\n//\n// node matching/build-spec.mjs\n//\n// SPEC.md is the Phase 1 deliverable and the thing matching/gate.sh preflights\n// against, so it is GENERATED rather than hand-maintained: edit the section\n// file, rebuild. The page list comes from matching/harness.mjs (the same\n// reader gate.sh's \\`--table\\` loop uses) and the heading test from its\n// specHeadingRe, whose pattern is has_spec's grep verbatim — so a section can\n// no longer build fine here and then re-block its own page at the gate.\nimport { readFileSync, writeFileSync, existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { PAGES, specHeadingRe } from \"./harness.mjs\";\n\nconst DIR = new URL(\".\", import.meta.url).pathname;\nconst SECTIONS = join(DIR, \"spec-sections\");\n\n// Order = live's nav order, then the detail templates. Both fall out of the\n// table's own order within each group, so this is not a second list to keep.\nconst KEYS = [\n ...PAGES.filter((p) => p.group === \"nav\"),\n ...PAGES.filter((p) => p.group !== \"nav\"),\n].map((p) => p.spec ?? p.key);\n\nconst parts = [];\nconst missing = [];\n\nconst chrome = join(SECTIONS, \"_chrome.md\");\nif (existsSync(chrome)) parts.push(readFileSync(chrome, \"utf8\").trim());\nelse missing.push(\"_chrome\");\n\nfor (const page of KEYS) {\n const file = join(SECTIONS, \\`\\${page}.md\\`);\n if (!existsSync(file)) {\n missing.push(page);\n continue;\n }\n const body = readFileSync(file, \"utf8\").trim();\n // The preflight greps \\`^##+ +<page>\\`; a section that does not open with its\n // own key would build fine here and then refuse at the gate, which is the\n // most confusing possible failure. Catch it now.\n if (!specHeadingRe(page).test(body)) {\n console.error(\n \\`WARNING: \\${page}.md does not open with a '## \\${page}' heading — gate.sh will still refuse this page.\\`,\n );\n }\n parts.push(body);\n}\n\n// The header is a spec-section like any other, so a site can say what a\n// citation looks like there and what its own ladders are. DEFAULT_HEADER is\n// what a site gets before it writes one: the two paragraphs that are true of\n// every project, and nothing that is not.\nconst DEFAULT_HEADER = \\`# Reference spec\n\nGENERATED by \\\\\\`node matching/build-spec.mjs\\\\\\` from \\\\\\`matching/spec-sections/\\\\\\`.\nEdit the section files, not this one.\n\nThis is the Phase 1 deliverable of the \\\\\\`matching-a-page\\\\\\` skill and the\nprecondition \\\\\\`matching/gate.sh\\\\\\` enforces: a page with no section here does not\nget a geometry round (see the matching rules in CLAUDE.md).\n\n\\`;\nconst headerFile = join(SECTIONS, \"_header.md\");\nconst header = existsSync(headerFile)\n ? readFileSync(headerFile, \"utf8\").trimEnd() + \"\\\\n\\\\n\"\n : DEFAULT_HEADER;\n\nwriteFileSync(join(DIR, \"SPEC.md\"), header + parts.join(\"\\\\n\\\\n---\\\\n\\\\n\") + \"\\\\n\");\n\nconst built = KEYS.length - missing.filter((m) => m !== \"_chrome\").length;\nconsole.log(\n \\`SPEC.md built — \\${built}/\\${KEYS.length} pages\\${missing.length ? \\`, MISSING: \\${missing.join(\", \")}\\` : \"\"}\\`,\n);\nprocess.exit(missing.length ? 1 : 0);\n`;\n\nexport const CENSUS_COUNT_MJS_RELATIVE = \"matching/census-count.mjs\";\nexport const CENSUS_COUNT_MJS_TEMPLATE = `// Count one style-census log, splitting declared deviations out of the total.\n//\n// node matching/census-count.mjs matching/census-home-1440.log\n// -> \"<real> <ambiguous> <declared>\"\n//\n// census.sh calls this per page/viewport. Parsing the log rather than importing\n// style-census keeps the project's declared-deviation policy out of the shared\n// skill — the skill reports what it sees; the project decides what it has\n// already agreed to.\nimport { readFileSync } from \"node:fs\";\nimport { DECLARED } from \"./census-deviations.mjs\";\n\nconst path = process.argv[2];\nif (!path) {\n console.error(\"usage: census-count.mjs <census log>\");\n process.exit(2);\n}\n\n// A log that cannot be READ is not a log with nothing IN it. This printed\n// \"0 0 0\" and exited 0, and census.sh added that to its total — the same green\n// it used to grant for a style-census that never ran. There is no count to\n// give here; say so and let the caller decide, which is never zero.\nlet text = \"\";\ntry {\n text = readFileSync(path, \"utf8\");\n} catch (e) {\n console.error(\\`census-count: cannot read \\${path} — \\${e.message}\\`);\n console.error(\" a missing log is not a census with no findings.\");\n process.exit(2);\n}\n\nconst [head, tail = \"\"] = text.split(\"--- AMBIGUOUS\");\n\n/** Pull \\`{label, ref, cand}\\` rows out of a census section. */\nfunction rows(section) {\n const out = [];\n const lines = section.split(\"\\\\n\");\n for (let i = 0; i < lines.length; i++) {\n if (!/^ {2}y=/.test(lines[i])) continue;\n const label = lines[i].replace(/^\\\\s*y=\\\\s*\\\\d+\\\\s*/, \"\").trim();\n const refs = [];\n const cands = [];\n for (\n let j = i + 1;\n j < lines.length && /^ {4}(ref|cand):/.test(lines[j]);\n j++\n ) {\n const v = lines[j].replace(/^\\\\s*(ref|cand):\\\\s*/, \"\").trim();\n (lines[j].trim().startsWith(\"ref:\") ? refs : cands).push(v);\n }\n if (refs.length && cands.length)\n out.push({ label, ref: refs[0], cand: cands[0] });\n else out.push({ label, ref: refs[0] ?? \"\", cand: cands[0] ?? \"\" });\n }\n return out;\n}\n\nconst mism = rows(head);\nconst amb = rows(tail);\nconst declared = mism.filter((r) => DECLARED.some((d) => d.match(r)));\nconsole.log(\n \\`\\${mism.length - declared.length} \\${amb.length} \\${declared.length}\\`,\n);\n`;\n\nexport const HARNESS_JSON_RELATIVE = \"matching/harness.json\";\nexport const HARNESS_JSON_TEMPLATE = `{\n \"ref\": \"__REF__\",\n \"cand\": \"http://localhost:5173\",\n \"matrix\": [\n 1440,\n 834,\n 390\n ],\n \"threshold\": 0.1,\n \"maxHeightDelta\": 0.05,\n \"refMark\": \"\",\n \"candMark\": \"_app/immutable\",\n \"selfHosts\": [],\n \"pages\": {\n \"home\": {\n \"uid\": \"home\",\n \"ref\": \"/\",\n \"cand\": \"/dev/match/home\",\n \"published\": \"/\",\n \"anchors\": [],\n \"spec\": \"home\",\n \"group\": \"nav\"\n }\n }\n}\n`;\n\nexport const FLOORS_MJS_RELATIVE = \"matching/floors.mjs\";\nexport const FLOORS_MJS_TEMPLATE = `// The failures we have already established are NOT geometry. Shared by\n// next.mjs (which reports them separately from the backlog) and strikes.mjs\n// (which must not report them as stalled — a region that cannot move is flat\n// by definition, and three strikes on one is noise that hides a real stall).\n// Every entry needs a matching LEDGER entry to stay honest.\n//\n// NOTE the second matcher argument. Region records carry \\`label\\` and\n// \\`viewport\\` but not the page, so an entry keyed on label+viewport alone is\n// safe only where the label is unique site-wide. \"top\" is not — every page has\n// one — so such an entry would silently accept the \\`top\\` failure on EVERY page\n// and hide every real one. next.mjs and strikes.mjs pass the page key as the\n// second argument: take \\`(r, page)\\` unless you have checked the label.\n\n/** Reference behaviour we cannot reproduce (declared floors). */\nexport const FLOORS = [];\n\n/** Regions the OPERATOR has looked at and chosen to leave failing. These are\n * not floors and are not silently dropped: next.mjs prints them under their\n * own heading with the decision, so the number never quietly improves. */\nexport const ACCEPTED = [];\n`;\n\nexport const CENSUS_DEVIATIONS_MJS_RELATIVE = \"matching/census-deviations.mjs\";\nexport const CENSUS_DEVIATIONS_MJS_TEMPLATE = `// Style-census rows the OPERATOR has looked at and chosen to keep. Same\n// contract as matching/floors.mjs: nothing is hidden, everything is counted\n// under its own heading, and every entry needs a LEDGER entry to stay honest.\n//\n// node matching/census-count.mjs <log> -> \"<real> <ambiguous> <declared>\"\n//\n// Without this, \\`census.sh\\` can never reach zero and an \"N remaining\" figure\n// says nothing about how much work is left — one permanent decision is counted\n// once per page per viewport, so a single ACK can account for dozens of rows.\n\n/** A row is \\`{ label, ref, cand }\\`, each of ref/cand a \"fam | wt | size | lh |\n * ls | transform | colour\" tuple string. */\nexport const DECLARED = [];\n`;\n\nexport const SPEC_CHROME_MD_RELATIVE = \"matching/spec-sections/_chrome.md\";\nexport const SPEC_CHROME_MD_TEMPLATE = `## shared chrome\n\nSite-wide furniture that renders identically on every page — header, footer, and\nanything else the per-page sections would otherwise restate. Phase 1 of the\n\\`matching-a-page\\` skill fills this in; until it does, this section is a\nplaceholder and \\`matching/SPEC.md\\` has no shared-chrome contract to cite.\n`;\n\nexport const SPEC_HEADER_MD_RELATIVE = \"matching/spec-sections/_header.md\";\nexport const SPEC_HEADER_MD_TEMPLATE = `# Reference spec\n\nGENERATED by \\`node matching/build-spec.mjs\\` from \\`matching/spec-sections/\\`.\nEdit the section files, not this one.\n\nThis is the Phase 1 deliverable of the \\`matching-a-page\\` skill and the\nprecondition \\`matching/gate.sh\\` enforces: a page with no section here does not\nget a geometry round (see the matching rules in CLAUDE.md).\n`;\n\nexport const LEDGER_MD_RELATIVE = \"matching/LEDGER.md\";\nexport const LEDGER_MD_TEMPLATE = `# Deviations / masks / floors ledger\n\nAppend-only, and written at the moment a decision is made — not reconstructed at\nthe end of a round, when the reason has already been lost. An entry is never\nedited to be right: a later entry corrects an earlier one and says which.\n\nEvery entry in \\`matching/floors.mjs\\` and \\`matching/census-deviations.mjs\\`, and\nevery mask in \\`matching/harness.json\\`, needs a line here. Without one the gate\nhas been quietly widened and nothing records who widened it, or why.\n\n- [deviation | floor | mask | a11y] \\`<region or selector>\\` — what differs, why\n it is accepted, and the evidence: a spec citation, a census row, a gate run.\n`;\n\nexport const MATCH_ROUTE_SERVER_RELATIVE = \"src/routes/dev/match/[uid]/+page.server.ts\";\nexport const MATCH_ROUTE_SERVER_TEMPLATE = `import { error } from \"@sveltejs/kit\";\nimport { dev } from \"$app/environment\";\nimport { documents } from \"$lib/site-pages.js\";\n\n// Local matching surface: renders the EXACT assembly \\`reddoor-maint\n// prismic-seed\\` publishes, from the same module, so a fix made to pass a gate\n// is a fix to what ships. Not prerendered, SSR-on-demand, dev-only.\nexport const prerender = false;\n\n// The seed resolves images to asset ids; here they only need a URL. Dimensions\n// are nominal — slices size their own image boxes in CSS.\nconst devImg = (u: string) => ({\n url: u,\n alt: null,\n copyright: null,\n dimensions: { width: 1600, height: 1067 },\n edit: { x: 0, y: 0, zoom: 1, background: \"transparent\" },\n id: u,\n});\n\nexport async function load({ params }) {\n // FIRST statement: everything below reads fixtures that must not be reachable\n // from a production build. The launch recipe asserts this route 404s on the\n // deployed URL, with /dev/a11y-fixtures as the 200 control.\n if (!dev) error(404, { message: \"Not found\" });\n\n const docs = documents(devImg) as Array<{ uid: string; data: { slices?: unknown[] } }>;\n const doc = docs.find((d) => d.uid === params.uid);\n if (!doc)\n error(404, {\n message: \\`no assembly for \"\\${params.uid}\" (have: \\${docs.map((d) => d.uid).join(\", \") || \"none\"})\\`,\n });\n\n return { uid: params.uid, slices: doc.data.slices ?? [] };\n}\n`;\n\nexport const MATCH_ROUTE_PAGE_RELATIVE = \"src/routes/dev/match/[uid]/+page.svelte\";\nexport const MATCH_ROUTE_PAGE_TEMPLATE = `<script lang=\"ts\">\n import { SliceZone } from \"@prismicio/svelte\";\n import { components } from \"$lib/slices\";\n\n let { data } = $props();\n</script>\n\n<SliceZone slices={data.slices as never} {components} />\n`;\n\nexport const SITE_PAGES_JS_RELATIVE = \"src/lib/site-pages.js\";\nexport const SITE_PAGES_JS_TEMPLATE = `// The page assemblies for this site — the SINGLE source of truth for both\n// consumers: \\`reddoor-maint prismic-seed\\`, which publishes them through the\n// Migration API, and src/routes/dev/match/[uid], the local matching surface.\n// Because both read from here, any fix made to pass a gate is a fix to what\n// ships.\n//\n// THE MIGRATION API DROPS SILENTLY. It validates against the slice models\n// registered in Prismic and discards every field the model does not declare —\n// HTTP 200, no warning. A fixture field with no model behind it renders here and\n// vanishes on the published route. src/lib/site-pages.test.ts is the mechanical\n// check; run it before every seed.\n//\n// PURE by contract: no node:*, no fetch, no token, no side effects at import,\n// so Vite can bundle it into the dev route and node can import it into the seed.\n\nexport const lang = \"en-us\";\n\n/**\n * @param {(url: string) => unknown} img resolves an image URL to whatever the\n * caller needs — an asset \\`{id}\\` for the seed, a \\`{url}\\` for the dev route.\n * @returns {Array<{type: string, uid: string, title: string, data: Record<string, unknown>}>}\n */\nexport function documents(img) {\n void img;\n return [];\n}\n`;\n\nexport const SITE_PAGES_TEST_RELATIVE = \"src/lib/site-pages.test.ts\";\nexport const SITE_PAGES_TEST_TEMPLATE = `// The page assemblies in src/lib/site-pages.js are the SINGLE source of truth\n// shared by two consumers: the local matching route (src/routes/dev/match/[uid])\n// and \\`reddoor-maint prismic-seed\\`, which publishes them to Prismic.\n//\n// Those two consumers do NOT validate the same way. The dev route hands the\n// object straight to the slice components, so any field a fixture sets is\n// simply there. The Migration API validates against the slice models registered\n// in Prismic and SILENTLY DROPS every field the model does not declare — no\n// error, no warning, a 200. A page can gate green locally and publish wrong.\n//\n// This test is the mechanical check. It fails the moment a fixture carries a\n// field its slice model does not declare — before the seed runs, not after the\n// content is published. Run it before every seed.\nimport { describe, expect, it } from \"vitest\";\nimport { readFileSync, readdirSync, existsSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport { dirname, join } from \"node:path\";\nimport { documents } from \"./site-pages.js\";\n\nconst HERE = dirname(fileURLToPath(import.meta.url));\nconst SLICES = join(HERE, \"slices\");\n\ntype Variation = { primary: string[]; items: string[] };\ntype Slice = {\n slice_type: string;\n variation: string;\n primary?: Record<string, unknown>;\n items?: Array<Record<string, unknown>>;\n};\n\n/** Every slice model in src/lib/slices, indexed by its Prismic slice id. */\nfunction loadModels(): Record<string, Record<string, Variation>> {\n const out: Record<string, Record<string, Variation>> = {};\n if (!existsSync(SLICES)) return out;\n for (const dir of readdirSync(SLICES)) {\n const file = join(SLICES, dir, \"model.json\");\n if (!existsSync(file)) continue;\n const model = JSON.parse(readFileSync(file, \"utf8\"));\n out[model.id] = Object.fromEntries(\n (model.variations ?? []).map((v: Record<string, unknown>) => [\n v.id,\n {\n primary: Object.keys((v.primary as object) ?? {}),\n items: Object.keys((v.items as object) ?? {}),\n },\n ]),\n );\n }\n return out;\n}\n\n/** Image resolver stub — shape only; this test never reads image values. */\nconst stubImg = () => ({ url: \"https://example.test/x.jpg\" });\n\ndescribe(\"site-pages documents vs slice models\", () => {\n const models = loadModels();\n const docs = documents(stubImg) as Array<{ uid: string; data: { slices?: Slice[] } }>;\n const pages: Array<[string, Slice[]]> = docs.map((d) => [d.uid, d.data.slices ?? []]);\n\n it(\"declares every slice type the documents use\", () => {\n const missing = new Set<string>();\n for (const [, slices] of pages)\n for (const s of slices) if (!models[s.slice_type]) missing.add(s.slice_type);\n expect([...missing]).toEqual([]);\n });\n\n it(\"declares every variation the documents use\", () => {\n const missing: string[] = [];\n for (const [uid, slices] of pages)\n for (const s of slices) {\n const model = models[s.slice_type];\n if (model && !model[s.variation]) missing.push(\\`\\${uid}: \\${s.slice_type}/\\${s.variation}\\`);\n }\n expect(missing).toEqual([]);\n });\n\n // The one that catches a silent Migration-API drop.\n it(\"declares every field the documents set, so Prismic strips nothing\", () => {\n const stripped: string[] = [];\n for (const [uid, slices] of pages)\n for (const s of slices) {\n const variation = models[s.slice_type]?.[s.variation];\n if (!variation) continue;\n for (const key of Object.keys(s.primary ?? {}))\n if (!variation.primary.includes(key))\n stripped.push(\\`\\${uid} \\${s.slice_type}/\\${s.variation} primary.\\${key}\\`);\n const itemKeys = new Set((s.items ?? []).flatMap((i) => Object.keys(i)));\n for (const key of itemKeys)\n if (!variation.items.includes(key))\n stripped.push(\\`\\${uid} \\${s.slice_type}/\\${s.variation} items.\\${key}\\`);\n }\n expect(stripped).toEqual([]);\n });\n});\n`;\n\nexport const MATCH_HARNESS_FILES: readonly HarnessFile[] = [\n { rel: HARNESS_MJS_RELATIVE, template: HARNESS_MJS_TEMPLATE, owner: \"recipe\" },\n { rel: GATE_SH_RELATIVE, template: GATE_SH_TEMPLATE, owner: \"recipe\" },\n { rel: CENSUS_SH_RELATIVE, template: CENSUS_SH_TEMPLATE, owner: \"recipe\" },\n { rel: NEXT_MJS_RELATIVE, template: NEXT_MJS_TEMPLATE, owner: \"recipe\" },\n { rel: STRIKES_MJS_RELATIVE, template: STRIKES_MJS_TEMPLATE, owner: \"recipe\" },\n { rel: BUILD_SPEC_MJS_RELATIVE, template: BUILD_SPEC_MJS_TEMPLATE, owner: \"recipe\" },\n { rel: CENSUS_COUNT_MJS_RELATIVE, template: CENSUS_COUNT_MJS_TEMPLATE, owner: \"recipe\" },\n { rel: HARNESS_JSON_RELATIVE, template: HARNESS_JSON_TEMPLATE, owner: \"site\" },\n { rel: FLOORS_MJS_RELATIVE, template: FLOORS_MJS_TEMPLATE, owner: \"site\" },\n { rel: CENSUS_DEVIATIONS_MJS_RELATIVE, template: CENSUS_DEVIATIONS_MJS_TEMPLATE, owner: \"site\" },\n { rel: SPEC_CHROME_MD_RELATIVE, template: SPEC_CHROME_MD_TEMPLATE, owner: \"site\" },\n { rel: SPEC_HEADER_MD_RELATIVE, template: SPEC_HEADER_MD_TEMPLATE, owner: \"site\" },\n { rel: LEDGER_MD_RELATIVE, template: LEDGER_MD_TEMPLATE, owner: \"site\" },\n { rel: MATCH_ROUTE_SERVER_RELATIVE, template: MATCH_ROUTE_SERVER_TEMPLATE, owner: \"recipe\" },\n { rel: MATCH_ROUTE_PAGE_RELATIVE, template: MATCH_ROUTE_PAGE_TEMPLATE, owner: \"recipe\" },\n { rel: SITE_PAGES_JS_RELATIVE, template: SITE_PAGES_JS_TEMPLATE, owner: \"site\" },\n { rel: SITE_PAGES_TEST_RELATIVE, template: SITE_PAGES_TEST_TEMPLATE, owner: \"recipe\" },\n];\n\n/** Renders previously shipped by this recipe, per relative path. A file that\n * byte-matches one of these is SAFE-REPLACED on re-run; anything else that\n * differs is flagged, never overwritten. Empty at v1 — nothing has shipped. */\nexport const MATCH_HARNESS_PREVIOUS: Readonly<Record<string, readonly string[]>> = {};\n\nexport const GITIGNORE_MARKER =\n \"# reddoor-maint match-harness: scripts + records tracked, workspace ignored\";\nexport const GITIGNORE_BLOCK = `scratch-diff*/\nmatching/*\n!matching/*.sh\n!matching/*.mjs\n!matching/*.md\n# The pause switch. Extensionless on purpose (it is a sentinel, not a doc), so\n# the whitelist above misses it — and an ignored switch is not a switch: it\n# would work on one machine and be absent from every fresh clone, CI run and\n# agent that most needs to see it.\n!matching/PAUSED\n!matching/spec-sections/\n!matching/states/\nmatching/states/*\n!matching/states/*.mjs\n# derived artefacts, even where the extensions above would catch them\nmatching/*.log\nmatching/*.json\n# ...except the page table itself, which is the harness's configuration and the\n# one thing a fresh clone cannot reconstruct.\n!matching/harness.json\nmatching/*.png\n`;\n\nexport const PRETTIERIGNORE_MARKER = \"# reddoor-maint match-harness\";\nexport const PRETTIERIGNORE_BLOCK = `# Matching harness — every file the \\`match-harness\\` recipe OWNS. It installs\n# them byte-for-byte and byte-compares them on the next install, so a site whose\n# prettier config differs must not reformat them: a reformatted file is\n# indistinguishable from a hand edit, and the recipe refuses to overwrite a hand\n# edit. Measured on a \\`useTabs\\`/\\`singleQuote\\` site, all three src/ entries\n# below are rewritten by \\`prettier --write .\\`, and the next upgrade is refused\n# for all three.\n#\n# The bracketed route segment MUST stay backslash-escaped. This file is\n# gitignore glob syntax, in which \\`[uid]\\` is a CHARACTER CLASS matching one of\n# \\`u\\`, \\`i\\`, \\`d\\` — an unescaped entry silently ignores nothing at all.\n#\n# \\`matching/*.mjs\\` also catches two SITE-owned records (floors.mjs,\n# census-deviations.mjs). Accepted, not overlooked: eslint lints .mjs, so those\n# two keep a style check either way, and naming seven scripts instead of one\n# glob goes stale on the next script the harness gains.\n#\n# harness.json is here because it is a TABLE, not prose: it carries a\n# hand-chosen row layout that prettier reflows.\n#\n# Everything else under matching/ (SPEC.md, LEDGER.md, spec-sections/,\n# states/*.mjs) stays inside \\`prettier --check .\\`. That is deliberately narrow:\n# eslint does not lint Markdown, so for the records prettier is the ONLY style\n# check there is, and a blanket \\`matching/\\` entry would leave them with none.\nmatching/*.mjs\nmatching/*.sh\nmatching/harness.json\nsrc/routes/dev/match/\\\\[uid\\\\]/+page.server.ts\nsrc/routes/dev/match/\\\\[uid\\\\]/+page.svelte\nsrc/lib/site-pages.test.ts\n`;\n\nexport const CLAUDE_MD_MARKER = \"## Matching rules (installed by reddoor-maint match-harness)\";\nexport const CLAUDE_MD_BLOCK = `\nThe \\`matching-a-page\\` skill governs a live-reference rebuild. These five rules\nexist because the skill alone did not hold on the project this harness came\nfrom — each one is a drift that actually happened, with the mechanical check\nthat now catches it. \\`matching/harness.json\\` is this site's configuration;\n\\`matching/LEDGER.md\\` is the dated record of every deviation, floor and mask.\n\n### 1. Source prescribes, rects only verify\n\nEvery geometry fix must cite the rule it came from: a line in the captured\nreference stylesheet under \\`matching/spec/\\`, or the reference's HTML. \"The probe\nsays the gap is 40px\" is not a source. If you cannot name the line you are\nguessing — go read the stylesheet first.\n\n**Check:** the commit body must name the file:line for each fix.\n**Operator's challenge:** _\"which line of the reference stylesheet says that?\"_\n\n### 2. Phase 1 before Phase 4\n\nA page gets its section census and per-section spec in \\`matching/SPEC.md\\` BEFORE\nits geometry is touched. No SPEC section, no geometry round. The census is the\ncoverage denominator; skipping it is how a reference's root-font ladder and its\nper-component height ladders get discovered reactively, after the region has\nalready failed several rounds.\n\n**Check:** \\`matching/gate.sh\\` refuses to run a page with no \\`SPEC.md\\` section.\n**Operator's challenge:** _\"show me the SPEC section for that region.\"_\n\n### 3. Three strikes, then stop\n\nA failing region that has not improved across 3+ gate runs does not get a fourth\nattempt. Present the attempts. Never widen the threshold, add a mask, or\nreclassify it as a floor to make it go away.\n\n**Check:** \\`node matching/strikes.mjs <page>\\` — exits 1 while any region is\nstalled, and is the first thing a geometry round runs.\n**Operator's challenge:** _\"how many runs has that region been flat?\"_\n\n### 4. A gate closes an item, nothing else\n\nNo fix is \"done\" because the code changed. Paste the gate header — it is\nself-describing, so a nonstandard threshold or an undisclosed mask is visible.\nThe threshold and the matrix are whatever \\`matching/harness.json\\` says, on every\npage, never a subset.\n\n**Operator's challenge:** _\"paste the gate header.\"_\n\n### 5. A commit is a checkpoint, not a stopping point\n\nDo not hand control back between rounds. After committing, run\n\\`node matching/next.mjs\\`: while it exits 1 there is a named next action, and the\nround continues. Report when the backlog is empty, when a decision is genuinely\nthe operator's (three strikes, a novel floor, a threshold change), or when\nasked — not because a commit felt like a natural place to summarise.\n\nThis exists because the failure was habitual, not deliberate: a turn ends when\nuser-facing prose gets written, and \"I just committed something good\" is the\nmoment that invites writing it. The gate stops incorrect work; this stops\npremature stopping.\n\n**Pausing:** create \\`matching/PAUSED\\` holding a note that says what the pause\nmeans; \\`next.mjs\\` and \\`strikes.mjs\\` then exit 0 while it exists. This rule says\n\"do not stop while work is named\"; it never said \"find work to name\". An empty\nagenda and a paused one end the round the same way. Deleting the switch is the\noperator's call.\n\n**Check:** \\`node matching/next.mjs\\` — exits 1 while any non-floor region fails.\n**Operator's challenge:** _\"what does next.mjs say?\"_\n\n### Round protocol\n\n1. \\`node matching/strikes.mjs <page>\\` — if it exits 1, the stalled regions are\n the agenda, and stalled ones get escalated rather than re-attempted.\n2. Confirm the page has a \\`matching/SPEC.md\\` section; write\n \\`matching/spec-sections/<page>.md\\` and run \\`node matching/build-spec.mjs\\` if\n not.\n3. Fix, each change citing its source line.\n4. \\`bash matching/gate.sh <tag> <page>\\` — paste the header.\n5. Append to \\`matching/LEDGER.md\\` at the moment a deviation, floor or mask is\n decided, not reconstructed at the end.\n6. \\`pnpm verify\\`, then commit and push.\n`;\n"],"mappings":";;;;;;;;;;;;;;;;;AAAA,SAAS,OAAO,UAAU,IAAI,OAAO,iBAAiB;AACtD,SAAS,SAAS,YAAY;;;ACMvB,IAAM,uBAAuB;AAC7B,IAAM,uBAAuB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAoL7B,IAAM,mBAAmB;AACzB,IAAM,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA2IzB,IAAM,qBAAqB;AAC3B,IAAM,qBAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAuO3B,IAAM,oBAAoB;AAC1B,IAAM,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAyL1B,IAAM,uBAAuB;AAC7B,IAAM,uBAAuB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA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","names":[]}
@@ -1,62 +0,0 @@
1
- // src/db/prospect-audits.ts
2
- import { randomBytes } from "crypto";
3
- function generateToken() {
4
- return randomBytes(16).toString("base64url");
5
- }
6
- function isValidToken(s) {
7
- return /^[A-Za-z0-9_-]{22}$/.test(s);
8
- }
9
- function newProspectAuditId() {
10
- return `pa_${crypto.randomUUID()}`;
11
- }
12
- function siteKey(url) {
13
- const raw = url.trim();
14
- if (raw === "") return "";
15
- try {
16
- const u = new URL(/^[a-z][a-z0-9+.-]*:\/\//i.test(raw) ? raw : `https://${raw}`);
17
- const host = u.hostname.toLowerCase().replace(/^www\./, "");
18
- const path = u.pathname.replace(/\/+$/, "");
19
- return `${host}${path}`;
20
- } catch {
21
- return raw;
22
- }
23
- }
24
- async function createProspectAudit(db, audit) {
25
- const id = newProspectAuditId();
26
- const token = generateToken();
27
- await db.insertInto("prospect_audits").values({
28
- id,
29
- token,
30
- url: audit.url,
31
- site_key: siteKey(audit.url),
32
- business: audit.business,
33
- created_at: (/* @__PURE__ */ new Date()).toISOString(),
34
- status: audit.status ?? "complete",
35
- result_json: audit.resultJson
36
- }).execute();
37
- return { id, token };
38
- }
39
- async function getProspectAuditByToken(db, token) {
40
- const row = await db.selectFrom("prospect_audits").select(["id", "url", "business", "created_at", "status", "result_json"]).where("token", "=", token).executeTakeFirst();
41
- return row ?? null;
42
- }
43
- var MAX_RECENT_PROSPECT_AUDITS = 100;
44
- function clampLimit(limit) {
45
- const n = Number.isFinite(limit) ? Math.trunc(limit) : 1;
46
- return Math.min(Math.max(n, 1), MAX_RECENT_PROSPECT_AUDITS);
47
- }
48
- async function listRecentProspectAudits(db, limit) {
49
- return db.selectFrom("prospect_audits").select(["id", "token", "url", "business", "status", "created_at"]).orderBy("created_at", "desc").limit(clampLimit(limit)).execute();
50
- }
51
-
52
- export {
53
- generateToken,
54
- isValidToken,
55
- newProspectAuditId,
56
- siteKey,
57
- createProspectAudit,
58
- getProspectAuditByToken,
59
- MAX_RECENT_PROSPECT_AUDITS,
60
- listRecentProspectAudits
61
- };
62
- //# sourceMappingURL=chunk-7MKT4I5T.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/db/prospect-audits.ts"],"sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport type { Db } from \"./client.js\";\n\n/** 16 random bytes → 22-char base64url string: unguessable, URL-safe. */\nexport function generateToken(): string {\n return randomBytes(16).toString(\"base64url\");\n}\n\n/** Anything not exactly generateToken()-shaped is a probe, not a report. */\nexport function isValidToken(s: string): boolean {\n return /^[A-Za-z0-9_-]{22}$/.test(s);\n}\n\n/** Opaque, collision-free id (mirrors newSubmissionId / newDeadLetterId).\n * crypto is a Node 20 global — no new dep. Nothing reads this format; `token`\n * is the public handle. */\nexport function newProspectAuditId(): string {\n return `pa_${crypto.randomUUID()}`;\n}\n\n/** \"complete\" when every pipeline stage succeeded; \"partial\" when any stage\n * failed or was deliberately skipped (StageResult's `ok: false` covers\n * both — the pipeline doesn't distinguish them at this layer). The report\n * itself already degrades each failed section to \"not measured\"; this is\n * what lets a future dashboard tell complete from partial without\n * deserializing the whole result_json. */\nexport type ProspectAuditStatus = \"complete\" | \"partial\";\n\n/**\n * The lineage handle for a site: one key for every way of writing its address.\n *\n * `url` is kept exactly as it was given, because what we audited is a fact\n * about the run. It is the wrong thing to group by, though, and the corpus\n * already proves it — `beachfrontdentistry.com` and `beachfrontdentistry.com/`\n * are two separate histories in the table today, as are the two spellings of\n * ludlowkingsley.com. Nothing has broken only because nothing yet compares two\n * audits of one site. The moment the report offers a before and an after, a\n * trailing slash turns a returning client into a stranger.\n *\n * Scheme, a leading \"www.\", a trailing slash, a query string and a fragment all\n * go: none of them distinguish one site from another. The path stays, because a\n * site living in a subdirectory genuinely is a different site.\n *\n * Never throws. A key we cannot compute must not take an audit down with it —\n * an unparseable input falls back to its trimmed self, which groups nothing and\n * so is no worse than having no key at all.\n */\nexport function siteKey(url: string): string {\n const raw = url.trim();\n if (raw === \"\") return \"\";\n try {\n const u = new URL(/^[a-z][a-z0-9+.-]*:\\/\\//i.test(raw) ? raw : `https://${raw}`);\n const host = u.hostname.toLowerCase().replace(/^www\\./, \"\");\n const path = u.pathname.replace(/\\/+$/, \"\");\n return `${host}${path}`;\n } catch {\n return raw;\n }\n}\n\nexport type NewProspectAudit = {\n url: string;\n business: string | null;\n resultJson: string;\n /** Defaults to \"complete\" — the column's own SQL default — for callers\n * (tests, ad-hoc scripts) that don't track per-stage outcomes. The\n * prospect-audit CLI, the only real writer, always computes and passes\n * this explicitly. */\n status?: ProspectAuditStatus;\n};\n\nexport async function createProspectAudit(\n db: Db,\n audit: NewProspectAudit,\n): Promise<{ id: string; token: string }> {\n const id = newProspectAuditId();\n const token = generateToken();\n await db\n .insertInto(\"prospect_audits\")\n .values({\n id,\n token,\n url: audit.url,\n site_key: siteKey(audit.url),\n business: audit.business,\n created_at: new Date().toISOString(),\n status: audit.status ?? \"complete\",\n result_json: audit.resultJson,\n })\n .execute();\n return { id, token };\n}\n\nexport type ProspectAuditRow = {\n id: string;\n url: string;\n business: string | null;\n created_at: string;\n status: string;\n result_json: string;\n};\n\nexport async function getProspectAuditByToken(\n db: Db,\n token: string,\n): Promise<ProspectAuditRow | null> {\n const row = await db\n .selectFrom(\"prospect_audits\")\n .select([\"id\", \"url\", \"business\", \"created_at\", \"status\", \"result_json\"])\n .where(\"token\", \"=\", token)\n .executeTakeFirst();\n return row ?? null;\n}\n\n/** The columns the cockpit's /audits listing page shows. Deliberately NOT\n * `result_json` — large, and useless to a list — but `token` IS selected:\n * the listing needs it to build each row's `/r/{token}` link, so it's\n * rendered only into an href, never displayed as a raw value. */\nexport type ProspectAuditListItem = {\n id: string;\n token: string;\n url: string;\n business: string | null;\n status: string;\n created_at: string;\n};\n\n/** Ceiling on `listRecentProspectAudits`' `limit`, enforced defensively (a\n * caller passing 10_000 must not get 10_000 rows) — well past anything a\n * listing page would ever render on one screen. */\nexport const MAX_RECENT_PROSPECT_AUDITS = 100;\n\n/** Clamp a caller-supplied limit into [1, MAX_RECENT_PROSPECT_AUDITS], with a\n * safe fallback of 1 for anything non-finite (NaN, ±Infinity) rather than\n * letting a bad input reach the query unclamped. */\nfunction clampLimit(limit: number): number {\n const n = Number.isFinite(limit) ? Math.trunc(limit) : 1;\n return Math.min(Math.max(n, 1), MAX_RECENT_PROSPECT_AUDITS);\n}\n\n/** Newest-first audits for the cockpit's /audits listing page, capped both\n * defensively (see clampLimit) and by an index (migration 0010) so the\n * ORDER BY never falls back to a raw scan + a temp b-tree sort — see the\n * EXPLAIN-query-plan gate, tests/db/query-plans.test.ts. */\nexport async function listRecentProspectAudits(\n db: Db,\n limit: number,\n): Promise<ProspectAuditListItem[]> {\n return db\n .selectFrom(\"prospect_audits\")\n .select([\"id\", \"token\", \"url\", \"business\", \"status\", \"created_at\"])\n .orderBy(\"created_at\", \"desc\")\n .limit(clampLimit(limit))\n .execute();\n}\n"],"mappings":";AAAA,SAAS,mBAAmB;AAIrB,SAAS,gBAAwB;AACtC,SAAO,YAAY,EAAE,EAAE,SAAS,WAAW;AAC7C;AAGO,SAAS,aAAa,GAAoB;AAC/C,SAAO,sBAAsB,KAAK,CAAC;AACrC;AAKO,SAAS,qBAA6B;AAC3C,SAAO,MAAM,OAAO,WAAW,CAAC;AAClC;AA6BO,SAAS,QAAQ,KAAqB;AAC3C,QAAM,MAAM,IAAI,KAAK;AACrB,MAAI,QAAQ,GAAI,QAAO;AACvB,MAAI;AACF,UAAM,IAAI,IAAI,IAAI,2BAA2B,KAAK,GAAG,IAAI,MAAM,WAAW,GAAG,EAAE;AAC/E,UAAM,OAAO,EAAE,SAAS,YAAY,EAAE,QAAQ,UAAU,EAAE;AAC1D,UAAM,OAAO,EAAE,SAAS,QAAQ,QAAQ,EAAE;AAC1C,WAAO,GAAG,IAAI,GAAG,IAAI;AAAA,EACvB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAaA,eAAsB,oBACpB,IACA,OACwC;AACxC,QAAM,KAAK,mBAAmB;AAC9B,QAAM,QAAQ,cAAc;AAC5B,QAAM,GACH,WAAW,iBAAiB,EAC5B,OAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA,KAAK,MAAM;AAAA,IACX,UAAU,QAAQ,MAAM,GAAG;AAAA,IAC3B,UAAU,MAAM;AAAA,IAChB,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,IACnC,QAAQ,MAAM,UAAU;AAAA,IACxB,aAAa,MAAM;AAAA,EACrB,CAAC,EACA,QAAQ;AACX,SAAO,EAAE,IAAI,MAAM;AACrB;AAWA,eAAsB,wBACpB,IACA,OACkC;AAClC,QAAM,MAAM,MAAM,GACf,WAAW,iBAAiB,EAC5B,OAAO,CAAC,MAAM,OAAO,YAAY,cAAc,UAAU,aAAa,CAAC,EACvE,MAAM,SAAS,KAAK,KAAK,EACzB,iBAAiB;AACpB,SAAO,OAAO;AAChB;AAkBO,IAAM,6BAA6B;AAK1C,SAAS,WAAW,OAAuB;AACzC,QAAM,IAAI,OAAO,SAAS,KAAK,IAAI,KAAK,MAAM,KAAK,IAAI;AACvD,SAAO,KAAK,IAAI,KAAK,IAAI,GAAG,CAAC,GAAG,0BAA0B;AAC5D;AAMA,eAAsB,yBACpB,IACA,OACkC;AAClC,SAAO,GACJ,WAAW,iBAAiB,EAC5B,OAAO,CAAC,MAAM,SAAS,OAAO,YAAY,UAAU,YAAY,CAAC,EACjE,QAAQ,cAAc,MAAM,EAC5B,MAAM,WAAW,KAAK,CAAC,EACvB,QAAQ;AACb;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/util/git.ts"],"sourcesContent":["import { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport type { Site } from \"../types.js\";\n\nconst exec = promisify(execFile);\n\nasync function git(cwd: string, args: string[]): Promise<{ stdout: string; stderr: string }> {\n return exec(\"git\", args, { cwd, env: process.env });\n}\n\nexport function branchName(recipe: string, when: Date = new Date()): string {\n // ISO with millisecond precision: 2026-05-20T10:30:00.123Z → 20260520T103000123Z.\n // Millis (vs. second-precision) shrinks the collision window for parallel runs.\n const compact = when.toISOString().replace(/[-:.]/g, \"\");\n return `maint/${recipe}-${compact}`;\n}\n\nexport async function currentBranch(cwd: string): Promise<string> {\n const { stdout } = await git(cwd, [\"rev-parse\", \"--abbrev-ref\", \"HEAD\"]);\n return stdout.trim();\n}\n\nexport async function isWorkingTreeClean(cwd: string): Promise<boolean> {\n const { stdout } = await git(cwd, [\"status\", \"--porcelain\"]);\n return stdout.trim().length === 0;\n}\n\nexport async function createBranch(cwd: string, name: string): Promise<void> {\n await git(cwd, [\"checkout\", \"-b\", name]);\n}\n\n/** Check out an existing branch. Throws (via git) if the branch is missing or\n * the checkout is blocked (e.g. uncommitted changes that would be overwritten). */\nexport async function checkoutBranch(cwd: string, name: string): Promise<void> {\n await git(cwd, [\"checkout\", name]);\n}\n\n/**\n * Force-check-out an existing branch, DISCARDING any uncommitted changes on the\n * current branch. Used by the recipe failure path to return the operator to\n * their original branch even when a recipe left the work-in-progress branch\n * dirty. Only ever called with the operator's ORIGINAL branch as `name`. Does\n * not run `git clean`, so untracked operator files are left untouched.\n */\nexport async function forceCheckoutBranch(cwd: string, name: string): Promise<void> {\n await git(cwd, [\"checkout\", \"-f\", name]);\n}\n\n/**\n * Delete a local branch with `-D` (force). Used by the recipe failure path to\n * remove the branch the recipe itself created so a re-run starts clean. Callers\n * MUST only ever pass the recipe-created branch here, never the operator's\n * original branch.\n */\nexport async function deleteBranch(cwd: string, name: string): Promise<void> {\n await git(cwd, [\"branch\", \"-D\", name]);\n}\n\nexport async function stageAll(cwd: string): Promise<void> {\n await git(cwd, [\"add\", \"-A\"]);\n}\n\nexport async function listTrackedFiles(cwd: string): Promise<string[]> {\n const { stdout } = await git(cwd, [\"ls-files\"]);\n return stdout\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter((l) => l.length > 0);\n}\n\nexport async function removeFromIndex(cwd: string, paths: string[]): Promise<void> {\n if (paths.length === 0) return;\n await git(cwd, [\"rm\", \"-r\", \"--cached\", \"--\", ...paths]);\n}\n\n/**\n * The subset of `paths` that is NOT in HEAD's tree — what a fresh clone would\n * NOT get. POSITIVE evidence: a path counts as installed only when it is FOUND\n * in `git ls-tree`'s output. `git add -A` honours .gitignore and exits 0 either\n * way, so \"staging did not error\" proves nothing about what landed.\n *\n * `-z`, because `ls-tree` C-quotes any path outside core.quotePath's safe set\n * (`\"caf\\303\\251.txt\"`), which would read as missing. NOT `--full-tree`:\n * without it `ls-tree` reports paths relative to `cwd`, the same base the\n * caller's relative paths use.\n *\n * A git failure (no HEAD yet, not a repo) yields an EMPTY tree, so every path\n * comes back missing. An error may only ever DENY.\n */\nexport async function pathsMissingFromHead(\n cwd: string,\n paths: readonly string[],\n): Promise<string[]> {\n if (paths.length === 0) return [];\n let inTree: Set<string>;\n try {\n const { stdout } = await git(cwd, [\"ls-tree\", \"-r\", \"-z\", \"--name-only\", \"HEAD\"]);\n inTree = new Set(stdout.split(\"\\0\").filter((p) => p.length > 0));\n } catch {\n inTree = new Set();\n }\n return paths.filter((p) => !inTree.has(p));\n}\n\n/**\n * Why git is excluding `paths`: `<source>:<line>:<pattern>\\t<path>` rows from\n * `git check-ignore -v --no-index`. A path with no row is absent for some other\n * reason (a nested repository, say) and the caller must say so rather than\n * guess. Never throws: check-ignore exits 1 when NOTHING matches and 128 on\n * error, and both must read as \"no rule found\" — the caller is already\n * reporting a failure and must not lose it to a second one.\n */\nexport async function ignoreRulesFor(cwd: string, paths: readonly string[]): Promise<string[]> {\n if (paths.length === 0) return [];\n let out: string;\n try {\n ({ stdout: out } = await git(cwd, [\"check-ignore\", \"-v\", \"--no-index\", \"--\", ...paths]));\n } catch (err) {\n out = (err as { stdout?: string }).stdout ?? \"\";\n }\n return out.split(\"\\n\").filter((l) => l.length > 0);\n}\n\n/**\n * Stages all current changes and commits with `message`. Returns the commit SHA,\n * or `null` if there was nothing to commit.\n */\nexport async function commit(cwd: string, message: string): Promise<string | null> {\n await stageAll(cwd);\n const { stdout: status } = await git(cwd, [\"status\", \"--porcelain\"]);\n if (status.trim().length === 0) return null;\n await git(cwd, [\"commit\", \"-m\", message]);\n const { stdout: sha } = await git(cwd, [\"rev-parse\", \"HEAD\"]);\n return sha.trim();\n}\n\n/**\n * Strict GitHub repo identity: exactly two `[A-Za-z0-9._-]` segments separated\n * by a single slash. Rejects a scheme, host, extra path segment, traversal\n * (`..`), whitespace, or an argv flag — anything that could retarget a `gh`\n * write at an unintended (attacker/typo-controlled) repo.\n */\nexport const OWNER_REPO_RE = /^[A-Za-z0-9._-]+\\/[A-Za-z0-9._-]+$/;\n\n/** True when `repo` is a clean `owner/repo` (see {@link OWNER_REPO_RE}). The\n * explicit `..` reject covers traversal segments the char-class would otherwise\n * admit (`.` is a legal repo char, so `../evil` matches the shape regex). */\nexport function isOwnerRepo(repo: string): boolean {\n if (repo.includes(\"..\")) return false;\n return OWNER_REPO_RE.test(repo);\n}\n\n/**\n * True when two repo references name the same `owner/repo`. Each side may be a\n * full remote URL (https or scp-style), or already an `owner/repo`. Used to\n * verify an existing checkout's `origin` matches the site's expected repo\n * before reusing it. Returns false if either side is unparseable.\n */\nexport function sameOwnerRepo(a: string, b: string): boolean {\n const na = isOwnerRepo(a.trim()) ? a.trim() : parseOwnerRepo(a);\n const nb = isOwnerRepo(b.trim()) ? b.trim() : parseOwnerRepo(b);\n if (na === null || nb === null) return false;\n return na.toLowerCase() === nb.toLowerCase();\n}\n\n/** Derive `owner/repo` from a git remote URL (https or scp-style). Null if unparseable. */\nexport function parseOwnerRepo(remoteUrl: string): string | null {\n const trimmed = remoteUrl\n .trim()\n .replace(/\\.git$/, \"\")\n .replace(/\\/$/, \"\");\n // scp-style: git@github.com:owner/repo\n const scp = trimmed.match(/^[A-Za-z0-9._-]+@[A-Za-z0-9._-]+:(.+)$/);\n const path = scp ? scp[1]! : trimmed.replace(/^https?:\\/\\/[^/]+\\//, \"\");\n const segments = path.split(\"/\").filter(Boolean);\n if (segments.length < 2) return null;\n return `${segments[segments.length - 2]}/${segments[segments.length - 1]}`;\n}\n\n/** `origin` remote URL for a checkout, trimmed. Throws (via git) if there's no origin. */\nexport async function getRemoteUrl(cwd: string): Promise<string> {\n const { stdout } = await git(cwd, [\"remote\", \"get-url\", \"origin\"]);\n return stdout.trim();\n}\n\n/**\n * Resolve the `owner/repo` a recipe will act on. An explicit `site.gitRepo`\n * (Airtable, or a JSON inventory) wins; otherwise derive it from the checkout's\n * `origin`.\n *\n * Returns `null` when there is nothing to act on (no `gitRepo`, no origin) — a\n * benign \"nothing wired\" state. THROWS when a value IS present but does not\n * match the strict `owner/repo` shape: callers write repo secrets, branch\n * protection and pull requests at this identity, so a typo'd or\n * attacker-controlled value must be rejected here, before the first `gh` call.\n *\n * Extracted from `self-updating`'s private `resolveRepo` because `prismic-ci`\n * read `site.gitRepo` directly and therefore refused every POSITIONAL run with\n * \"no Git repo on this site\" — `localPath()` (src/inventory/local.ts:11) builds\n * `{ path, name }` and nothing else. A site being bootstrapped is exactly the\n * case that has no Airtable row yet (`--fleet airtable` filters pre-launch\n * statuses), so the positional path is the ONLY one `/new-site` can use.\n */\nexport async function resolveOwnerRepo(site: Site): Promise<string | null> {\n if (site.gitRepo) {\n if (!isOwnerRepo(site.gitRepo)) {\n throw new Error(\n `refusing to act on malformed repo identity: expected \"owner/repo\", got ${JSON.stringify(site.gitRepo)}`,\n );\n }\n return site.gitRepo;\n }\n let fromOrigin: string | null;\n try {\n fromOrigin = parseOwnerRepo(await getRemoteUrl(site.path));\n } catch {\n return null;\n }\n if (fromOrigin === null) return null;\n if (!isOwnerRepo(fromOrigin)) {\n throw new Error(\n `refusing to act on malformed repo identity from origin: ${JSON.stringify(fromOrigin)}`,\n );\n }\n return fromOrigin;\n}\n\n/** Push a branch to origin, setting upstream. Throws on non-zero (execFile rejects). */\nexport async function push(cwd: string, branch: string): Promise<void> {\n await git(cwd, [\"push\", \"-u\", \"origin\", branch]);\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/recipes/a11y-fixtures-page/index.ts","../src/recipes/a11y-fixtures-page/template.ts","../src/recipes/init.ts"],"sourcesContent":["import { access, mkdir, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport type { RecipeResult, Site } from \"../../types.js\";\nimport { withRecipe } from \"../_with-recipe.js\";\nimport { A11Y_FIXTURES_PAGE_RELATIVE, A11Y_FIXTURES_PAGE_TEMPLATE } from \"./template.js\";\n\nasync function fileExists(path: string): Promise<boolean> {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Writes a starter `src/routes/dev/a11y-fixtures/+page.svelte` if the route\n * doesn't already exist. The hardcoded URL in `src/configs/lighthouse.ts` +\n * `src/configs/playwright-a11y.ts` targets this path — newly-onboarded sites\n * need the route to exist for either audit to pass. Operator edits to an\n * existing page are never clobbered (noop on existing file).\n */\nexport async function a11yFixturesPage(site: Site): Promise<RecipeResult> {\n const target = join(site.path, A11Y_FIXTURES_PAGE_RELATIVE);\n return withRecipe<{ target: string }>({\n name: \"a11y-fixtures-page\",\n site,\n plan: async () => {\n if (await fileExists(target)) {\n return { kind: \"noop\", notes: `${A11Y_FIXTURES_PAGE_RELATIVE} already exists` };\n }\n return { kind: \"apply\", plan: { target } };\n },\n apply: async (planned, { commit }) => {\n await mkdir(dirname(planned.target), { recursive: true });\n await writeFile(planned.target, A11Y_FIXTURES_PAGE_TEMPLATE, \"utf-8\");\n await commit(\"feat: add /dev/a11y-fixtures starter route\");\n return { kind: \"ok\" };\n },\n });\n}\n","/** Relative path inside a site where the a11y fixtures route lives. The\n * hardcoded URL in `src/configs/lighthouse.ts` + `src/configs/playwright-a11y.ts`\n * is `/dev/a11y-fixtures`, so a SvelteKit `+page.svelte` here resolves. */\nexport const A11Y_FIXTURES_PAGE_RELATIVE = \"src/routes/dev/a11y-fixtures/+page.svelte\";\n\n/** Stub `+page.svelte` for newly-onboarded sites. Generic on purpose —\n * landmarks, heading hierarchy, and a relative link cover the axe-core +\n * lhci defaults without committing the operator to any specific fixture\n * shape. Replace with site-specific patterns over time. */\nexport const A11Y_FIXTURES_PAGE_TEMPLATE = `<svelte:head>\n <title>a11y fixtures — Reddoor</title>\n <meta\n name=\"description\"\n content=\"Reddoor accessibility fixtures — semantic landmarks, heading hierarchy, and a stable target for @lhci/cli and Playwright + axe-core coverage. Not linked from the public site.\"\n />\n</svelte:head>\n\n<main>\n <header>\n <h1>Accessibility fixtures</h1>\n <p>\n This page exists so <code>@lhci/cli</code> and Playwright + axe-core have a\n stable target with predictable a11y characteristics. It is not linked from\n the public site.\n </p>\n </header>\n\n <section aria-labelledby=\"landmarks-heading\">\n <h2 id=\"landmarks-heading\">Landmarks</h2>\n <p>\n A single <code>main</code> wraps the page; sections each declare\n <code>aria-labelledby</code> matched to their heading id so screen readers\n and axe both see a clean outline.\n </p>\n </section>\n\n <section aria-labelledby=\"links-heading\">\n <h2 id=\"links-heading\">Links</h2>\n <p>\n <a href=\"/\">Back to home</a> — relative link with descriptive visible text,\n so no <code>aria-label</code> override is needed.\n </p>\n </section>\n</main>\n`;\n","import type { AuditResult, RecipeResult, Site } from \"../types.js\";\nimport { siteLabel } from \"../util/site.js\";\nimport { convertToPnpm } from \"./convert-to-pnpm.js\";\nimport { onboard } from \"./onboard.js\";\nimport { syncConfigs } from \"./sync-configs.js\";\nimport { svelteCodemods } from \"./svelte-codemods.js\";\nimport { a11yFixturesPage } from \"./a11y-fixtures-page/index.js\";\nimport { healthEndpoint } from \"./health-endpoint/index.js\";\nimport { smokeSuite } from \"./smoke-suite/index.js\";\nimport { runAudits } from \"../audits/index.js\";\n\nexport type InitStepResult =\n | { kind: \"recipe\"; result: RecipeResult }\n | { kind: \"audit\"; results: AuditResult[] }\n | { kind: \"error\"; message: string };\n\nexport type InitStep = {\n name: string;\n run: (site: Site) => Promise<InitStepResult>;\n};\n\nexport type InitResult = {\n site: string;\n steps: Array<{ name: string; result: InitStepResult }>;\n /** True if every step ran; false if an `error` or `failed` recipe result\n * short-circuited the chain. `noop` recipes do not break completeness. */\n complete: boolean;\n};\n\nexport type InitOptions = {\n /** Override the default step list. Tests inject mocked steps; production\n * code relies on the default. */\n steps?: InitStep[];\n};\n\nfunction recipeStep(name: string, fn: (site: Site) => Promise<RecipeResult>): InitStep {\n return {\n name,\n run: async (site) => ({ kind: \"recipe\", result: await fn(site) }),\n };\n}\n\n/** convert-to-pnpm → onboard → sync-configs → svelte-codemods →\n * a11y-fixtures-page → health-endpoint → smoke-suite → audit. Order is\n * deliberate — every step depends on the prior one's output (pnpm before\n * onboard's installs, onboard's deps before sync-configs writes lighthouserc,\n * fixtures page before audit actually has a route to hit; smoke-suite's\n * `pnpm install` lands before the closing audit). */\nexport const DEFAULT_INIT_STEPS: InitStep[] = [\n recipeStep(\"convert-to-pnpm\", convertToPnpm),\n recipeStep(\"onboard\", onboard),\n recipeStep(\"sync-configs\", syncConfigs),\n recipeStep(\"svelte-codemods\", svelteCodemods),\n recipeStep(\"a11y-fixtures-page\", a11yFixturesPage),\n recipeStep(\"health-endpoint\", healthEndpoint),\n recipeStep(\"smoke-suite\", smokeSuite),\n {\n name: \"audit\",\n run: async (site) => ({ kind: \"audit\", results: await runAudits(site) }),\n },\n];\n\n/**\n * One-shot guided onboarding. Runs the default step sequence against a\n * site, collecting per-step results into an InitResult. Each underlying\n * recipe still creates its own branch — init is a thin orchestrator, not\n * a branch-collapser; the operator ends up with one stack of branches per\n * mutated step (recipes that noop don't branch).\n *\n * Stops the chain on the first uncaught error or `failed` recipe result.\n * `noop` results are expected (e.g. running init twice) and continue the\n * chain. The final audit pass runs if no prior step errored.\n */\nexport async function init(site: Site, opts: InitOptions = {}): Promise<InitResult> {\n const steps = opts.steps ?? DEFAULT_INIT_STEPS;\n const out: Array<{ name: string; result: InitStepResult }> = [];\n\n for (const step of steps) {\n let result: InitStepResult;\n try {\n result = await step.run(site);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n out.push({ name: step.name, result: { kind: \"error\", message } });\n return { site: siteLabel(site), steps: out, complete: false };\n }\n out.push({ name: step.name, result });\n if (result.kind === \"recipe\" && result.result.status === \"failed\") {\n return { site: siteLabel(site), steps: out, complete: false };\n }\n }\n\n return { site: siteLabel(site), steps: out, complete: true };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,QAAQ,OAAO,iBAAiB;AACzC,SAAS,SAAS,YAAY;;;ACEvB,IAAM,8BAA8B;AAMpC,IAAM,8BAA8B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ADH3C,eAAe,WAAW,MAAgC;AACxD,MAAI;AACF,UAAM,OAAO,IAAI;AACjB,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AASA,eAAsB,iBAAiB,MAAmC;AACxE,QAAM,SAAS,KAAK,KAAK,MAAM,2BAA2B;AAC1D,SAAO,WAA+B;AAAA,IACpC,MAAM;AAAA,IACN;AAAA,IACA,MAAM,YAAY;AAChB,UAAI,MAAM,WAAW,MAAM,GAAG;AAC5B,eAAO,EAAE,MAAM,QAAQ,OAAO,GAAG,2BAA2B,kBAAkB;AAAA,MAChF;AACA,aAAO,EAAE,MAAM,SAAS,MAAM,EAAE,OAAO,EAAE;AAAA,IAC3C;AAAA,IACA,OAAO,OAAO,SAAS,EAAE,OAAO,MAAM;AACpC,YAAM,MAAM,QAAQ,QAAQ,MAAM,GAAG,EAAE,WAAW,KAAK,CAAC;AACxD,YAAM,UAAU,QAAQ,QAAQ,6BAA6B,OAAO;AACpE,YAAM,OAAO,4CAA4C;AACzD,aAAO,EAAE,MAAM,KAAK;AAAA,IACtB;AAAA,EACF,CAAC;AACH;;;AELA,SAAS,WAAW,MAAc,IAAqD;AACrF,SAAO;AAAA,IACL;AAAA,IACA,KAAK,OAAO,UAAU,EAAE,MAAM,UAAU,QAAQ,MAAM,GAAG,IAAI,EAAE;AAAA,EACjE;AACF;AAQO,IAAM,qBAAiC;AAAA,EAC5C,WAAW,mBAAmB,aAAa;AAAA,EAC3C,WAAW,WAAW,OAAO;AAAA,EAC7B,WAAW,gBAAgB,WAAW;AAAA,EACtC,WAAW,mBAAmB,cAAc;AAAA,EAC5C,WAAW,sBAAsB,gBAAgB;AAAA,EACjD,WAAW,mBAAmB,cAAc;AAAA,EAC5C,WAAW,eAAe,UAAU;AAAA,EACpC;AAAA,IACE,MAAM;AAAA,IACN,KAAK,OAAO,UAAU,EAAE,MAAM,SAAS,SAAS,MAAM,UAAU,IAAI,EAAE;AAAA,EACxE;AACF;AAaA,eAAsB,KAAK,MAAY,OAAoB,CAAC,GAAwB;AAClF,QAAM,QAAQ,KAAK,SAAS;AAC5B,QAAM,MAAuD,CAAC;AAE9D,aAAW,QAAQ,OAAO;AACxB,QAAI;AACJ,QAAI;AACF,eAAS,MAAM,KAAK,IAAI,IAAI;AAAA,IAC9B,SAAS,KAAK;AACZ,YAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,UAAI,KAAK,EAAE,MAAM,KAAK,MAAM,QAAQ,EAAE,MAAM,SAAS,QAAQ,EAAE,CAAC;AAChE,aAAO,EAAE,MAAM,UAAU,IAAI,GAAG,OAAO,KAAK,UAAU,MAAM;AAAA,IAC9D;AACA,QAAI,KAAK,EAAE,MAAM,KAAK,MAAM,OAAO,CAAC;AACpC,QAAI,OAAO,SAAS,YAAY,OAAO,OAAO,WAAW,UAAU;AACjE,aAAO,EAAE,MAAM,UAAU,IAAI,GAAG,OAAO,KAAK,UAAU,MAAM;AAAA,IAC9D;AAAA,EACF;AAEA,SAAO,EAAE,MAAM,UAAU,IAAI,GAAG,OAAO,KAAK,UAAU,KAAK;AAC7D;","names":[]}