@reddoorla/maintenance 0.96.0 → 0.98.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 +5 -6
  2. package/dist/{announce-45HVP4J4.js → announce-2FH2KBQQ.js} +39 -31
  3. package/dist/announce-2FH2KBQQ.js.map +1 -0
  4. package/dist/attachments-JFGFQRXQ.js +10 -0
  5. package/dist/{blux-RUFMNOUJ.js → blux-NXQLMS4W.js} +5 -5
  6. package/dist/{bump-deps-EL3TKMIL.js → bump-deps-54K4ECP3.js} +6 -6
  7. package/dist/{chunk-IMYL67JV.js → chunk-3RLDX3Q2.js} +5 -5
  8. package/dist/{chunk-EQQ4H6L3.js → chunk-3YALLSBF.js} +2 -2
  9. package/dist/{chunk-LAJ3FF54.js → chunk-5UI4FERC.js} +16 -5
  10. package/dist/chunk-5UI4FERC.js.map +1 -0
  11. package/dist/{chunk-CA6ADGOC.js → chunk-6PGXKN6F.js} +2 -2
  12. package/dist/{chunk-Y5BVR4HW.js → chunk-6THEZQLA.js} +3 -3
  13. package/dist/chunk-ASLDCWBF.js +41 -0
  14. package/dist/chunk-ASLDCWBF.js.map +1 -0
  15. package/dist/{chunk-DVJNYPFR.js → chunk-AYDQQBNG.js} +2 -2
  16. package/dist/{chunk-OTEVKRAH.js → chunk-B2BRFWJY.js} +7 -16
  17. package/dist/chunk-B2BRFWJY.js.map +1 -0
  18. package/dist/{chunk-GEUC5VGM.js → chunk-BINFZQ2T.js} +49 -4
  19. package/dist/chunk-BINFZQ2T.js.map +1 -0
  20. package/dist/{chunk-T76MA2FH.js → chunk-CP6MVZ5X.js} +27 -1
  21. package/dist/{chunk-T76MA2FH.js.map → chunk-CP6MVZ5X.js.map} +1 -1
  22. package/dist/{chunk-72FJZ6LO.js → chunk-DRNZVORX.js} +26 -22
  23. package/dist/chunk-DRNZVORX.js.map +1 -0
  24. package/dist/{chunk-XY7YV2YQ.js → chunk-DXZRWW2F.js} +65 -10
  25. package/dist/chunk-DXZRWW2F.js.map +1 -0
  26. package/dist/{chunk-SHGIDTYF.js → chunk-DYTYPKFN.js} +2 -2
  27. package/dist/chunk-DZZ5KRTB.js +23 -0
  28. package/dist/chunk-DZZ5KRTB.js.map +1 -0
  29. package/dist/{chunk-NVLREB5R.js → chunk-EN6CTBUN.js} +7 -7
  30. package/dist/{chunk-ZH5LOQET.js → chunk-FHFXJ3EM.js} +7 -5
  31. package/dist/chunk-FHFXJ3EM.js.map +1 -0
  32. package/dist/{chunk-3EA43S3R.js → chunk-GRF2RYEO.js} +12 -12
  33. package/dist/{chunk-RRE74VWI.js → chunk-HSMN35LU.js} +2 -2
  34. package/dist/chunk-ICSRWW5Q.js +148 -0
  35. package/dist/chunk-ICSRWW5Q.js.map +1 -0
  36. package/dist/{chunk-XG2A6GIG.js → chunk-IDBPII6G.js} +4 -4
  37. package/dist/{chunk-XG2A6GIG.js.map → chunk-IDBPII6G.js.map} +1 -1
  38. package/dist/{chunk-ECDJDHKT.js → chunk-JHJNNJLG.js} +2 -2
  39. package/dist/{chunk-QFSL2L2B.js → chunk-L5F3WYIY.js} +81 -31
  40. package/dist/chunk-L5F3WYIY.js.map +1 -0
  41. package/dist/{chunk-XED2DNAI.js → chunk-LAG6IP7Y.js} +7 -7
  42. package/dist/{chunk-UIITN37J.js → chunk-LF2TJN4X.js} +2 -2
  43. package/dist/chunk-LF2TJN4X.js.map +1 -0
  44. package/dist/{chunk-SQ4FSNYL.js → chunk-LUF52EIY.js} +2 -2
  45. package/dist/{chunk-MZZDMBDT.js → chunk-LZMAG73U.js} +19 -19
  46. package/dist/chunk-LZMAG73U.js.map +1 -0
  47. package/dist/{chunk-6INOBNRO.js → chunk-MNRWHSFS.js} +2 -2
  48. package/dist/{chunk-CGQZFQAY.js → chunk-MVKNU4QD.js} +2 -3
  49. package/dist/chunk-MVKNU4QD.js.map +1 -0
  50. package/dist/{chunk-62YWOFVN.js → chunk-ORTOMEYR.js} +3 -3
  51. package/dist/{chunk-62YWOFVN.js.map → chunk-ORTOMEYR.js.map} +1 -1
  52. package/dist/{chunk-44SDGPGQ.js → chunk-PZZ6HM5T.js} +3 -3
  53. package/dist/chunk-RQJFGK3A.js +51 -0
  54. package/dist/chunk-RQJFGK3A.js.map +1 -0
  55. package/dist/{chunk-H445EH33.js → chunk-S2PLX4XK.js} +10 -7
  56. package/dist/chunk-S2PLX4XK.js.map +1 -0
  57. package/dist/{chunk-KN52PM63.js → chunk-TC4GANMX.js} +47 -149
  58. package/dist/chunk-TC4GANMX.js.map +1 -0
  59. package/dist/{chunk-F3JWOPTG.js → chunk-TEMQPSCM.js} +45 -105
  60. package/dist/chunk-TEMQPSCM.js.map +1 -0
  61. package/dist/{chunk-4UTJ4G6P.js → chunk-TJLKWQOX.js} +2 -2
  62. package/dist/{chunk-A5QKNOXW.js → chunk-TTT6HYPW.js} +47 -10
  63. package/dist/chunk-TTT6HYPW.js.map +1 -0
  64. package/dist/{chunk-VEIIUCYL.js → chunk-TV76A6GN.js} +2 -2
  65. package/dist/{chunk-D3CTOCUR.js → chunk-TVZ6QCOE.js} +3 -3
  66. package/dist/chunk-UPE6D4AT.js +69 -0
  67. package/dist/chunk-UPE6D4AT.js.map +1 -0
  68. package/dist/{chunk-BIYQN7FH.js → chunk-VCPTOZ56.js} +31 -1
  69. package/dist/{chunk-BIYQN7FH.js.map → chunk-VCPTOZ56.js.map} +1 -1
  70. package/dist/chunk-VQMAAQ5M.js +53 -0
  71. package/dist/chunk-VQMAAQ5M.js.map +1 -0
  72. package/dist/{chunk-EASYCD6F.js → chunk-WMF5S2MB.js} +1 -1
  73. package/dist/{chunk-EASYCD6F.js.map → chunk-WMF5S2MB.js.map} +1 -1
  74. package/dist/{chunk-37B4LVB3.js → chunk-X56LAKPR.js} +3 -3
  75. package/dist/chunk-X56LAKPR.js.map +1 -0
  76. package/dist/{chunk-IY6256GK.js → chunk-YJBSB4LP.js} +6 -1
  77. package/dist/chunk-YJBSB4LP.js.map +1 -0
  78. package/dist/{chunk-P4A6D6HO.js → chunk-ZEE5WQYU.js} +32 -18
  79. package/dist/chunk-ZEE5WQYU.js.map +1 -0
  80. package/dist/cli/bin.js +92 -50
  81. package/dist/cli/bin.js.map +1 -1
  82. package/dist/cli/commands/audit.d.ts +33 -3
  83. package/dist/cli/commands/audit.js +26 -23
  84. package/dist/cli/commands/audit.js.map +1 -1
  85. package/dist/client-3XGSZMLU.js +11 -0
  86. package/dist/{convert-to-pnpm-4DON7IFS.js → convert-to-pnpm-U2CRNKNY.js} +6 -6
  87. package/dist/{db-5K4CDEGJ.js → db-7XYTBIPH.js} +25 -18
  88. package/dist/db-7XYTBIPH.js.map +1 -0
  89. package/dist/{deadletter-G4M3HSVM.js → deadletter-A7RQVNG3.js} +8 -4
  90. package/dist/{digest-5AJGWS33.js → digest-AMXRGWZA.js} +34 -33
  91. package/dist/digest-AMXRGWZA.js.map +1 -0
  92. package/dist/{digest-collectors-MC5NWEUA.js → digest-collectors-CX2NCGS7.js} +15 -12
  93. package/dist/ensure-site-F2WFCWWA.js +70 -0
  94. package/dist/ensure-site-F2WFCWWA.js.map +1 -0
  95. package/dist/ensure-site-JEKV2ODE.js +105 -0
  96. package/dist/ensure-site-JEKV2ODE.js.map +1 -0
  97. package/dist/{ensure-site-XESZHZEB.js → ensure-site-OO4UPWNQ.js} +19 -42
  98. package/dist/ensure-site-OO4UPWNQ.js.map +1 -0
  99. package/dist/fleet-events-CkOZpMVC.d.ts +435 -0
  100. package/dist/{fleet-state-GJWGKTLD.js → fleet-state-RJROYXKO.js} +17 -7
  101. package/dist/{forms-notify-target-4NPLX2UG.js → forms-notify-target-DK5R326N.js} +17 -13
  102. package/dist/forms-notify-target-DK5R326N.js.map +1 -0
  103. package/dist/{github-signals-DMU74SAX.js → github-signals-RGRPYTUB.js} +20 -15
  104. package/dist/github-signals-RGRPYTUB.js.map +1 -0
  105. package/dist/{header-image-BRTOLHJF.js → header-image-43ZIEJKP.js} +13 -17
  106. package/dist/header-image-43ZIEJKP.js.map +1 -0
  107. package/dist/{health-endpoint-UCLMSSCN.js → health-endpoint-K5LXDPGE.js} +7 -7
  108. package/dist/{health-mirror-ONRT4DQ5.js → health-mirror-WZUHWCCV.js} +10 -10
  109. package/dist/{import-airtable-IIK2QUUF.js → import-airtable-UBF5GYAL.js} +3 -3
  110. package/dist/index.d.ts +102 -494
  111. package/dist/index.js +62 -52
  112. package/dist/index.js.map +1 -1
  113. package/dist/{init-3LMZTCVD.js → init-OFITGMOP.js} +16 -16
  114. package/dist/{launch-XTDMC6LD.js → launch-AJY3TGTQ.js} +37 -35
  115. package/dist/launch-AJY3TGTQ.js.map +1 -0
  116. package/dist/{lighthouse-52WSPZ5D.js → lighthouse-RFMFREEX.js} +2 -2
  117. package/dist/lighthouse-airtable-M45OPVRV.js +14 -0
  118. package/dist/{chunk-IHHAYXKY.js → match-harness-CFELJA57.js} +70 -4
  119. package/dist/match-harness-CFELJA57.js.map +1 -0
  120. package/dist/migrate-EZL55AWA.js +7 -0
  121. package/dist/notify-K3WVH3DM.js +1 -1
  122. package/dist/{onboard-CZYBWVWH.js → onboard-F7RTLMDZ.js} +6 -6
  123. package/dist/{orchestrate-TGDFGY52.js → orchestrate-SAIFXZIW.js} +13 -10
  124. package/dist/parity-2JGVUCZD.js +14 -0
  125. package/dist/{pipeline-UUPXOS2J.js → pipeline-TCSNVDQZ.js} +3 -3
  126. package/dist/{preflight-POZZI3GN.js → preflight-6XGVVO2U.js} +35 -11
  127. package/dist/preflight-6XGVVO2U.js.map +1 -0
  128. package/dist/{prismic-ci-AHY5ULJR.js → prismic-ci-XMX7A5E4.js} +11 -11
  129. package/dist/prismic-ci-XMX7A5E4.js.map +1 -0
  130. package/dist/{prismic-models-URYGODNY.js → prismic-models-7GKFAAFA.js} +19 -16
  131. package/dist/prismic-models-7GKFAAFA.js.map +1 -0
  132. package/dist/prospect/types.d.ts +44 -2
  133. package/dist/prospect/types.js +1 -1
  134. package/dist/{prospect-audit-AJEE3Z4L.js → prospect-audit-KWP5P5U2.js} +37 -5
  135. package/dist/prospect-audit-KWP5P5U2.js.map +1 -0
  136. package/dist/{protection-audit-3J3UEOVT.js → protection-audit-YXJKFU6O.js} +3 -3
  137. package/dist/{protection-audit-3J3UEOVT.js.map → protection-audit-YXJKFU6O.js.map} +1 -1
  138. package/dist/recipes/sync-configs.js +3 -3
  139. package/dist/{render-MHUUHPLN.js → render-NWUAX6RN.js} +3 -3
  140. package/dist/{render-from-row-65DC6ZP3.js → render-from-row-75JTED2Z.js} +6 -4
  141. package/dist/{renovate-dispatch-ONJMBUHJ.js → renovate-dispatch-ZZQXT7DA.js} +13 -8
  142. package/dist/{renovate-dispatch-ONJMBUHJ.js.map → renovate-dispatch-ZZQXT7DA.js.map} +1 -1
  143. package/dist/replay-HB44TAII.js +77 -0
  144. package/dist/replay-HB44TAII.js.map +1 -0
  145. package/dist/{report-RC5LGPU3.js → report-MVPWS7WP.js} +102 -58
  146. package/dist/report-MVPWS7WP.js.map +1 -0
  147. package/dist/{report-mirror-KKZZ6RMZ.js → report-mirror-O6IDFDWU.js} +28 -10
  148. package/dist/report-mirror-O6IDFDWU.js.map +1 -0
  149. package/dist/roster-EJ3ILR52.js +19 -0
  150. package/dist/roster-EJ3ILR52.js.map +1 -0
  151. package/dist/{screenouts-7P5GOZMV.js → screenouts-7NQUWHOJ.js} +14 -1
  152. package/dist/screenouts-7NQUWHOJ.js.map +1 -0
  153. package/dist/{self-updating-RKKIYNV4.js → self-updating-KX5FMGEO.js} +7 -7
  154. package/dist/{selftest-AFLR7YV7.js → selftest-KABY2CYJ.js} +21 -17
  155. package/dist/{selftest-AFLR7YV7.js.map → selftest-KABY2CYJ.js.map} +1 -1
  156. package/dist/site-create-ZU3ACBTO.js +45 -0
  157. package/dist/site-create-ZU3ACBTO.js.map +1 -0
  158. package/dist/{site-mirror-H2FYMN55.js → site-mirror-TKTDWYP3.js} +10 -10
  159. package/dist/site-mirror-TKTDWYP3.js.map +1 -0
  160. package/dist/{smoke-suite-BWHR3XX5.js → smoke-suite-QQMV7TNA.js} +7 -7
  161. package/dist/{submissions-JDZP35JM.js → submissions-4I66W6QK.js} +11 -1
  162. package/dist/{submissions-JDZP35JM.js.map → submissions-4I66W6QK.js.map} +1 -1
  163. package/dist/{submissions-II5J35QM.js → submissions-K5UXGZUN.js} +12 -7
  164. package/dist/submissions-K5UXGZUN.js.map +1 -0
  165. package/dist/{svelte-codemods-QWGIRNN6.js → svelte-codemods-YFN4TEGV.js} +6 -6
  166. package/dist/{sync-NKPDCMYW.js → sync-F4USL4K5.js} +6 -6
  167. package/dist/{sync-configs-HGLQV55H.js → sync-configs-HBXS3D7C.js} +6 -6
  168. package/dist/turso-KQKBBQCK.js +24 -0
  169. package/dist/turso-KQKBBQCK.js.map +1 -0
  170. package/dist/{upgrade-WZ44IARG.js → upgrade-XOJMDIT4.js} +6 -6
  171. package/dist/util/git.d.ts +1 -1
  172. package/dist/util/git.js +1 -1
  173. package/dist/{websites-6DHIHJ2D.js → websites-Y6FN2XFD.js} +26 -21
  174. package/dist/{write-audits-to-airtable-BIK6LNVC.js → write-audits-to-airtable-TI3JMWSY.js} +6 -4
  175. package/package.json +1 -1
  176. package/dist/airtable-SJV4ZSMS.js +0 -9
  177. package/dist/announce-45HVP4J4.js.map +0 -1
  178. package/dist/attachments-NLZIJ5NU.js +0 -9
  179. package/dist/chunk-37B4LVB3.js.map +0 -1
  180. package/dist/chunk-72FJZ6LO.js.map +0 -1
  181. package/dist/chunk-74QRWE6G.js +0 -53
  182. package/dist/chunk-74QRWE6G.js.map +0 -1
  183. package/dist/chunk-A5QKNOXW.js.map +0 -1
  184. package/dist/chunk-CGQZFQAY.js.map +0 -1
  185. package/dist/chunk-F3JWOPTG.js.map +0 -1
  186. package/dist/chunk-GEUC5VGM.js.map +0 -1
  187. package/dist/chunk-H445EH33.js.map +0 -1
  188. package/dist/chunk-IHHAYXKY.js.map +0 -1
  189. package/dist/chunk-IY6256GK.js.map +0 -1
  190. package/dist/chunk-KN52PM63.js.map +0 -1
  191. package/dist/chunk-LAJ3FF54.js.map +0 -1
  192. package/dist/chunk-MZZDMBDT.js.map +0 -1
  193. package/dist/chunk-OTEVKRAH.js.map +0 -1
  194. package/dist/chunk-P4A6D6HO.js.map +0 -1
  195. package/dist/chunk-QFSL2L2B.js.map +0 -1
  196. package/dist/chunk-UIITN37J.js.map +0 -1
  197. package/dist/chunk-XY7YV2YQ.js.map +0 -1
  198. package/dist/chunk-YFAXOD7Z.js +0 -42
  199. package/dist/chunk-YFAXOD7Z.js.map +0 -1
  200. package/dist/chunk-ZH5LOQET.js.map +0 -1
  201. package/dist/client-L7NAUG6J.js +0 -11
  202. package/dist/db-5K4CDEGJ.js.map +0 -1
  203. package/dist/digest-5AJGWS33.js.map +0 -1
  204. package/dist/ensure-site-XESZHZEB.js.map +0 -1
  205. package/dist/fleet-events-cF2Om88w.d.ts +0 -24
  206. package/dist/forms-notify-target-4NPLX2UG.js.map +0 -1
  207. package/dist/github-signals-DMU74SAX.js.map +0 -1
  208. package/dist/header-image-BRTOLHJF.js.map +0 -1
  209. package/dist/launch-XTDMC6LD.js.map +0 -1
  210. package/dist/lighthouse-airtable-3NOFB42B.js +0 -12
  211. package/dist/match-harness-OW45CB5H.js +0 -77
  212. package/dist/match-harness-OW45CB5H.js.map +0 -1
  213. package/dist/migrate-NE3DUMTM.js +0 -7
  214. package/dist/parity-AEKYSLN6.js +0 -14
  215. package/dist/preflight-POZZI3GN.js.map +0 -1
  216. package/dist/prismic-ci-AHY5ULJR.js.map +0 -1
  217. package/dist/prismic-models-URYGODNY.js.map +0 -1
  218. package/dist/prospect-audit-AJEE3Z4L.js.map +0 -1
  219. package/dist/replay-4FVFOMHH.js +0 -54
  220. package/dist/replay-4FVFOMHH.js.map +0 -1
  221. package/dist/report-RC5LGPU3.js.map +0 -1
  222. package/dist/report-mirror-KKZZ6RMZ.js.map +0 -1
  223. package/dist/screenouts-7P5GOZMV.js.map +0 -1
  224. package/dist/site-mirror-H2FYMN55.js.map +0 -1
  225. package/dist/submissions-II5J35QM.js.map +0 -1
  226. package/dist/write-audits-to-airtable-BIK6LNVC.js.map +0 -1
  227. /package/dist/{airtable-SJV4ZSMS.js.map → attachments-JFGFQRXQ.js.map} +0 -0
  228. /package/dist/{blux-RUFMNOUJ.js.map → blux-NXQLMS4W.js.map} +0 -0
  229. /package/dist/{bump-deps-EL3TKMIL.js.map → bump-deps-54K4ECP3.js.map} +0 -0
  230. /package/dist/{chunk-IMYL67JV.js.map → chunk-3RLDX3Q2.js.map} +0 -0
  231. /package/dist/{chunk-EQQ4H6L3.js.map → chunk-3YALLSBF.js.map} +0 -0
  232. /package/dist/{chunk-CA6ADGOC.js.map → chunk-6PGXKN6F.js.map} +0 -0
  233. /package/dist/{chunk-Y5BVR4HW.js.map → chunk-6THEZQLA.js.map} +0 -0
  234. /package/dist/{chunk-DVJNYPFR.js.map → chunk-AYDQQBNG.js.map} +0 -0
  235. /package/dist/{chunk-SHGIDTYF.js.map → chunk-DYTYPKFN.js.map} +0 -0
  236. /package/dist/{chunk-NVLREB5R.js.map → chunk-EN6CTBUN.js.map} +0 -0
  237. /package/dist/{chunk-3EA43S3R.js.map → chunk-GRF2RYEO.js.map} +0 -0
  238. /package/dist/{chunk-RRE74VWI.js.map → chunk-HSMN35LU.js.map} +0 -0
  239. /package/dist/{chunk-ECDJDHKT.js.map → chunk-JHJNNJLG.js.map} +0 -0
  240. /package/dist/{chunk-XED2DNAI.js.map → chunk-LAG6IP7Y.js.map} +0 -0
  241. /package/dist/{chunk-SQ4FSNYL.js.map → chunk-LUF52EIY.js.map} +0 -0
  242. /package/dist/{chunk-6INOBNRO.js.map → chunk-MNRWHSFS.js.map} +0 -0
  243. /package/dist/{chunk-44SDGPGQ.js.map → chunk-PZZ6HM5T.js.map} +0 -0
  244. /package/dist/{chunk-4UTJ4G6P.js.map → chunk-TJLKWQOX.js.map} +0 -0
  245. /package/dist/{chunk-VEIIUCYL.js.map → chunk-TV76A6GN.js.map} +0 -0
  246. /package/dist/{chunk-D3CTOCUR.js.map → chunk-TVZ6QCOE.js.map} +0 -0
  247. /package/dist/{attachments-NLZIJ5NU.js.map → client-3XGSZMLU.js.map} +0 -0
  248. /package/dist/{convert-to-pnpm-4DON7IFS.js.map → convert-to-pnpm-U2CRNKNY.js.map} +0 -0
  249. /package/dist/{client-L7NAUG6J.js.map → deadletter-A7RQVNG3.js.map} +0 -0
  250. /package/dist/{deadletter-G4M3HSVM.js.map → digest-collectors-CX2NCGS7.js.map} +0 -0
  251. /package/dist/{digest-collectors-MC5NWEUA.js.map → fleet-state-RJROYXKO.js.map} +0 -0
  252. /package/dist/{health-endpoint-UCLMSSCN.js.map → health-endpoint-K5LXDPGE.js.map} +0 -0
  253. /package/dist/{health-mirror-ONRT4DQ5.js.map → health-mirror-WZUHWCCV.js.map} +0 -0
  254. /package/dist/{fleet-state-GJWGKTLD.js.map → import-airtable-UBF5GYAL.js.map} +0 -0
  255. /package/dist/{init-3LMZTCVD.js.map → init-OFITGMOP.js.map} +0 -0
  256. /package/dist/{import-airtable-IIK2QUUF.js.map → lighthouse-RFMFREEX.js.map} +0 -0
  257. /package/dist/{lighthouse-52WSPZ5D.js.map → lighthouse-airtable-M45OPVRV.js.map} +0 -0
  258. /package/dist/{lighthouse-airtable-3NOFB42B.js.map → migrate-EZL55AWA.js.map} +0 -0
  259. /package/dist/{onboard-CZYBWVWH.js.map → onboard-F7RTLMDZ.js.map} +0 -0
  260. /package/dist/{migrate-NE3DUMTM.js.map → orchestrate-SAIFXZIW.js.map} +0 -0
  261. /package/dist/{orchestrate-TGDFGY52.js.map → parity-2JGVUCZD.js.map} +0 -0
  262. /package/dist/{parity-AEKYSLN6.js.map → pipeline-TCSNVDQZ.js.map} +0 -0
  263. /package/dist/{render-MHUUHPLN.js.map → render-NWUAX6RN.js.map} +0 -0
  264. /package/dist/{pipeline-UUPXOS2J.js.map → render-from-row-75JTED2Z.js.map} +0 -0
  265. /package/dist/{self-updating-RKKIYNV4.js.map → self-updating-KX5FMGEO.js.map} +0 -0
  266. /package/dist/{smoke-suite-BWHR3XX5.js.map → smoke-suite-QQMV7TNA.js.map} +0 -0
  267. /package/dist/{svelte-codemods-QWGIRNN6.js.map → svelte-codemods-YFN4TEGV.js.map} +0 -0
  268. /package/dist/{sync-NKPDCMYW.js.map → sync-F4USL4K5.js.map} +0 -0
  269. /package/dist/{sync-configs-HGLQV55H.js.map → sync-configs-HBXS3D7C.js.map} +0 -0
  270. /package/dist/{upgrade-WZ44IARG.js.map → upgrade-XOJMDIT4.js.map} +0 -0
  271. /package/dist/{render-from-row-65DC6ZP3.js.map → websites-Y6FN2XFD.js.map} +0 -0
  272. /package/dist/{websites-6DHIHJ2D.js.map → write-audits-to-airtable-TI3JMWSY.js.map} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/recipes/self-updating/index.ts"],"sourcesContent":["import { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport type { RecipeResult, Site } from \"../../types.js\";\nimport { refusedByGit, undoRefusedWrites, RESTORED_NOTE } from \"../_head-guard.js\";\nimport { templatesByName } from \"../sync-configs/templates.js\";\nimport {\n renovateActionGaps,\n withRenovatePinsFrom,\n RENOVATE_ACTION_CONFIG,\n} from \"../sync-configs/renovate-action.js\";\nimport {\n resolveOwnerRepo,\n push as gitPush,\n branchName,\n checkoutBranch,\n createBranch,\n currentBranch,\n commit as gitCommit,\n isWorkingTreeClean,\n} from \"../../util/git.js\";\nimport { siteLabel } from \"../../util/site.js\";\nimport { readGitHubConfig } from \"../../github/config.js\";\nimport { makeGitHub, type GitHub } from \"../../github/gh.js\";\nimport {\n FLEET_RULESET_NAME,\n desiredRuleset,\n healRuleset,\n rulesetGaps,\n} from \"../../github/rulesets.js\";\n\n// Deliberately NOT ci.yml: that file is per-site parameterized (netlify-site,\n// node-version, permissions), so an exact-match heal against a static template\n// would strip those values in a green auto-mergeable PR — the armed clobber\n// the 2026-08-02 architecture review found. The starter clone owns ci.yml's\n// shape. Its pinned reusable-workflow ref is sometimes bumped by Renovate\n// (v1.2.0 -> v1.3.0 across 17 repos, Jul-Aug 2026) but was not for v1.4.0 or\n// v1.4.1 — the latter was swept by hand on 2026-09-01 — so propagation is a\n// manual step to verify, never a guarantee.\nconst SELF_UPDATING_CONFIGS = [\"renovate-action\", \"renovate-config\"] as const;\n\n// Reusable-workflow jobs report their check as \"<caller-job> / <reusable-job>\".\n// The thin `ci` caller (job `ci`) calls reddoorla/.github's reusable workflow (job `ci`),\n// so the required context is \"ci / ci\", NOT \"ci\". (Provisional — verified empirically on the\n// starter during M7.1 rollout; correct here if the live checks API reports a different string.)\nconst REQUIRED_CHECK = \"ci / ci\";\n\nexport type SelfUpdatingDeps = {\n github?: GitHub;\n pushBranch?: (cwd: string, branch: string) => Promise<void>;\n};\n\nfunction resultOf(\n site: Site,\n status: RecipeResult[\"status\"],\n notes: string,\n commits: string[] = [],\n): RecipeResult {\n return { recipe: \"self-updating\", site: siteLabel(site), status, commits, notes };\n}\n\n/** True when a file's current content matches the canonical template for drift\n * purposes. Normalizes line endings + trailing whitespace so a stray trailing\n * newline (or CRLF) can't read as drift and open a needless PR every nightly run;\n * any real content change still differs. */\nfunction sameConfigContents(current: string, canonical: string): boolean {\n const norm = (s: string) => s.replace(/\\r\\n/g, \"\\n\").replace(/\\s+$/, \"\");\n return norm(current) === norm(canonical);\n}\n\nexport async function selfUpdating(site: Site, deps: SelfUpdatingDeps = {}): Promise<RecipeResult> {\n const templates = templatesByName([...SELF_UPDATING_CONFIGS]);\n\n let repo: string | null;\n try {\n repo = await resolveOwnerRepo(site);\n } catch (err) {\n // A malformed repo identity must abort before any `gh` write — surface it\n // as a recipe failure rather than letting the token reach an unintended repo.\n return resultOf(site, \"failed\", err instanceof Error ? err.message : String(err));\n }\n if (!repo) {\n return resultOf(\n site,\n \"failed\",\n \"could not determine a GitHub repo for this site — set Airtable 'Git repo', or give \" +\n \"the checkout an origin remote whose URL is a GitHub owner/repo\",\n );\n }\n\n const cfg = readGitHubConfig();\n if (!deps.github && !cfg) return resultOf(site, \"failed\", \"GITHUB_TOKEN not set\");\n const github = deps.github ?? makeGitHub({ token: cfg!.token });\n\n const base = await github.defaultBranch(repo).catch(() => \"main\");\n const actions: string[] = [];\n const commits: string[] = [];\n // Hoisted so the `finally` can restore the operator's branch even when a\n // failure (push/PR error) aborts AFTER we created + checked out the maint\n // branch. `maintBranch` stays null until createBranch succeeds, so the\n // restore is a no-op on every path that never left the operator's branch.\n let original: string | null = null;\n let maintBranch: string | null = null;\n\n try {\n // A. Renovate config on the default branch — compare CONTENT, not just\n // existence, so a present-but-STALE config (a pre-App workflow, a drifted\n // Renovate schedule window) is corrected rather than left silently\n // forever. The prior existence-only gate reported \"already self-updating\" for any\n // repo that merely HAD the files, however out of date — the very drift this\n // recipe exists to repair (e.g. the Renovate schedule-window regression).\n //\n // renovate.yml specifically is compliance-checked rather than byte-matched\n // (see renovateActionGaps / withRenovatePinsFrom in\n // ../sync-configs/renovate-action.js): Renovate legitimately bumps its own\n // digest pins forward, and a site's prettier may legitimately quote the\n // cron / RENOVATE_* scalars differently — neither is real drift (issue\n // #651; a byte-match recipe opened reddoor-starter-blux#1, a PR that\n // DOWNGRADED Renovate's own pin).\n const drifted: string[] = [];\n // Captured so the write loop below can carry a drifted renovate.yml's own\n // pins forward instead of re-fetching (and so a compliant-but-non-byte-\n // identical file's contents are available for that carry-forward too,\n // though it won't need healing).\n const currentContents = new Map<string, string | null>();\n for (const t of templates) {\n const current = await github.fileContentsOnBranch(repo, base, t.path);\n currentContents.set(t.path, current);\n if (current === null) {\n drifted.push(t.path);\n continue;\n }\n if (sameConfigContents(current, t.contents)) continue;\n if (t.config === RENOVATE_ACTION_CONFIG && renovateActionGaps(current).length === 0) {\n continue;\n }\n drifted.push(t.path);\n }\n if (drifted.length > 0) {\n const existingPR = await github.findOpenSelfUpdatingPR(repo);\n if (existingPR) {\n actions.push(`self-updating PR already open: ${existingPR}`);\n } else {\n if (!(await isWorkingTreeClean(site.path))) {\n return resultOf(site, \"failed\", \"working tree not clean — commit or stash first\");\n }\n // Capture the operator's branch BEFORE creating the maint branch so the\n // `finally` can return them to it (#2). Best-effort capture: if it fails\n // we just skip the restore rather than guess.\n try {\n original = await currentBranch(site.path);\n } catch {\n original = null;\n }\n maintBranch = branchName(\"self-updating\");\n await createBranch(site.path, maintBranch);\n // Write only the paths that actually drifted — a repo whose\n // renovate.json is stale but whose renovate.yml already passes\n // `renovateActionGaps` must not have that compliant renovate.yml\n // silently rewritten (e.g. un-quoting its scalars) in the same PR.\n // On a fresh repo every template path is missing, so `drifted`\n // already contains all of them and bootstrap is unaffected.\n // What each written path held BEFORE this run, so one git then refuses\n // can be put back exactly as it was (null = the file did not exist).\n const before = new Map<string, string | null>();\n for (const t of templates) {\n if (!drifted.includes(t.path)) continue;\n const dest = join(site.path, t.path);\n before.set(t.path, await readFile(dest, \"utf-8\").catch(() => null));\n await mkdir(dirname(dest), { recursive: true });\n // For a genuinely non-compliant renovate.yml, carry the site's own\n // (still-digest-pinned) action refs forward onto the template before\n // writing it — writing the template verbatim would put its OLDER\n // pins over the site's newer ones, re-introducing the downgrade\n // this compliance check exists to prevent (issue #651).\n const contents =\n t.config === RENOVATE_ACTION_CONFIG\n ? withRenovatePinsFrom(t.contents, currentContents.get(t.path) ?? null)\n : t.contents;\n await writeFile(dest, contents, \"utf-8\");\n }\n const sha = await gitCommit(site.path, \"ci: enable self-updating (Renovate auto-merge)\");\n if (sha) commits.push(sha);\n\n // Did git TAKE the configs? The PARTIAL drop is the one that bites: with\n // one of the two paths ignored the other still commits, so `commit()`\n // returns a SHA and — before this check — the branch was pushed and a PR\n // opened for a repo that would never become self-updating, after which\n // every later run reported \"self-updating PR already open\" forever.\n // Checked BEFORE the push, so nothing reaches GitHub on a refusal (#741).\n const refusal = await refusedByGit(site.path, drifted, \"the Renovate configuration\");\n if (refusal) {\n await undoRefusedWrites(site.path, before, refusal.missing);\n return resultOf(site, \"failed\", refusal.notes + RESTORED_NOTE, commits);\n }\n\n await (deps.pushBranch ?? gitPush)(site.path, maintBranch);\n const pr = await github.openPullRequest(repo, {\n head: maintBranch,\n base,\n title: \"Enable self-updating (Renovate)\",\n body: \"Adds scheduled Renovate and Renovate-owned merges for patch/minor updates. (ci.yml is deliberately not managed here — its shape comes from the starter and its reusable-workflow pin from Renovate.)\",\n });\n actions.push(`opened PR ${pr.url}`);\n // (Branch restore happens in the `finally` below — on success AND on a\n // failure that aborts after createBranch.)\n }\n }\n\n // B. Repo settings — check-then-ensure, each independent (self-healing).\n //\n // Platform auto-merge is DISABLED fleet-wide, and this block self-heals toward\n // OFF (it used to drive repos ON — do not \"fix\" it back).\n //\n // Why: GitHub's platform auto-merge is a per-PR flag that anyone with write\n // access can arm; the PR then merges itself later, unattended, once checks go\n // green. The org Renovate preset forbids auto-merging majors and was working\n // correctly — Renovate never armed them — but the preset has no authority over\n // a flag someone else set. On 2026-07-26 a bulk `gh pr merge --auto` sweep\n // armed it across 8 fleet repos, and the next morning two `actions/checkout`\n // MAJOR bumps merged with zero reviews (reddoor-starter#77,\n // reddoor-website#111) as soon as Renovate's own rebase cleared their\n // conflicts. Realized damage, not a hypothetical.\n //\n // The fix is to keep merges INSIDE Renovate's own run, where its `packageRules`\n // actually govern the decision. Renovate can only merge while it is running, so\n // the workflow cron (see sync-configs templates) is the merge cadence.\n //\n // Leaving the self-heal pointed at OFF makes this a drift ALARM: if someone\n // re-enables platform auto-merge, the next `self-updating` / `launch` run turns\n // it back off and records the action. `enableRepoAutoMerge` is retained in\n // `src/github/gh.ts` as the documented rollback path.\n if (await github.autoMergeEnabled(repo)) {\n await github.disableRepoAutoMerge(repo);\n actions.push(\"disabled auto-merge (platform auto-merge is not permitted fleet-wide)\");\n }\n const existingContexts = await github.branchProtectionContexts(repo, base);\n if (!existingContexts.includes(REQUIRED_CHECK)) {\n // protectBranch issues a full PUT that REPLACES required-status-check contexts.\n // Send the UNION of the branch's existing required contexts + our REQUIRED_CHECK\n // so we ADD the CI gate without silently dropping any other checks the repo\n // already requires. (When the branch has no protection yet, existingContexts is\n // [] and this is just [REQUIRED_CHECK] — the original behavior.) Dedupe defends\n // against REQUIRED_CHECK already appearing among the existing contexts.\n const contexts = [...new Set([...existingContexts, REQUIRED_CHECK])];\n await github.protectBranch(repo, base, contexts);\n actions.push(`required \"${REQUIRED_CHECK}\" check on ${base}`);\n }\n // Ruleset ensure — the layer that binds ADMINS. Classic protection above\n // keeps `enforce_admins: false`, which is exactly the hole the 2026-07-26\n // bulk `gh pr merge --auto` sweep walked through (two unreviewed MAJORS\n // merged). The 2026-08-01 audit then found the six NEWEST repos with no\n // ruleset at all — every hand-applied sweep regrows its gap at the next\n // onboard, so the recipe owns the invariant now. Spec + invariants:\n // docs/superpowers/specs/2026-08-02-ruleset-self-healing-design.md.\n //\n // Private repos are skipped BEFORE any attempt: rulesets there need a paid\n // plan, so trying would either fail every run or spam a phantom action\n // that flips this recipe to \"applied\" forever. Classic protection remains\n // their floor.\n if ((await github.repoVisibility(repo)) === \"public\") {\n // Evidence gate: only require \"ci / ci\" once that check-run has actually\n // been OBSERVED on the default branch. With an empty bypass list, a\n // required context that never fires would make the repo permanently\n // unmergeable by EVERYONE — e.g. a workflow_call-only ci.yml emits no\n // check runs. Unobserved ⇒ refs rules only; the check upgrades on a\n // later pass once real evidence exists.\n //\n // Residual risk, accepted: this proves the check fires on default-branch\n // PUSHES, not on pull_request events — a hand-edited push-only ci.yml\n // would pass the gate and then block every PR. ci.yml is deliberately\n // NOT healed by block A (per-site parameterized; see\n // SELF_UPDATING_CONFIGS), so this recipe cannot correct that drift —\n // the exposure is one bricked-PR repo, immediately visible on its next\n // PR. Un-brick path: edit the ruleset via API (ruleset ADMIN is not\n // ref-gated), then fix the ci.yml triggers by push.\n const check = (await github.checkContextObserved(repo, base, REQUIRED_CHECK))\n ? REQUIRED_CHECK\n : null;\n const existingRuleset = (await github.listRepoRulesets(repo)).find(\n (r) => r.name === FLEET_RULESET_NAME,\n );\n if (!existingRuleset) {\n await github.createRuleset(repo, desiredRuleset(check));\n actions.push(\n check === null\n ? `created ruleset \"${FLEET_RULESET_NAME}\" (refs rules only — \"${REQUIRED_CHECK}\" not yet observed on ${base})`\n : `created ruleset \"${FLEET_RULESET_NAME}\"`,\n );\n } else {\n const full = await github.getRuleset(repo, existingRuleset.id);\n const gaps = rulesetGaps(full, check);\n if (gaps.length > 0) {\n // PUT the healed payload (merge TOWARD the floor, never away — see\n // healRuleset). Recording the specific gaps makes this the drift\n // ALARM, same as the auto-merge-off action above.\n await github.updateRuleset(repo, existingRuleset.id, healRuleset(full, check));\n actions.push(`healed ruleset drift: ${gaps.join(\"; \")}`);\n }\n }\n }\n // (No per-repo RENOVATE_TOKEN planting anymore: Renovate authenticates as\n // the reddoor-renovate GitHub App, whose RENOVATE_APP_ID / _PRIVATE_KEY are\n // org-level with all-repos visibility — a new repo needs NOTHING set. The\n // old step was already redundant for new sites (the PAT was an org secret\n // too); existing per-repo copies are inert and left to rot harmlessly.\n // docs/runbooks/renovate-app-identity.md covers the migration + rollback.)\n } catch (err) {\n const done = actions.length ? ` (completed: ${actions.join(\"; \")})` : \"\";\n const message = err instanceof Error ? err.message : String(err);\n return resultOf(site, \"failed\", `${message}${done}`, commits);\n } finally {\n // Restore the operator's branch whenever we created + checked out a maint\n // branch — on SUCCESS and, critically, on FAILURE. Without this, a push/PR\n // error after createBranch strands the checkout on the maint branch with an\n // unpushed commit, and the retry then fails at createBranch (\"branch already\n // exists\"). Best-effort: a restore failure must not mask the real outcome.\n if (maintBranch !== null && original !== null && original !== maintBranch) {\n try {\n await checkoutBranch(site.path, original);\n } catch (err) {\n console.warn(\n `warning: could not restore branch ${original} after self-updating: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n }\n }\n\n return actions.length\n ? resultOf(site, \"applied\", actions.join(\"; \"), commits)\n : resultOf(site, \"noop\", \"already self-updating\", commits);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/recipes/self-updating/index.ts"],"sourcesContent":["import { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport type { RecipeResult, Site } from \"../../types.js\";\nimport { refusedByGit, undoRefusedWrites, RESTORED_NOTE } from \"../_head-guard.js\";\nimport { templatesByName } from \"../sync-configs/templates.js\";\nimport {\n renovateActionGaps,\n withRenovatePinsFrom,\n RENOVATE_ACTION_CONFIG,\n} from \"../sync-configs/renovate-action.js\";\nimport {\n resolveOwnerRepo,\n push as gitPush,\n branchName,\n checkoutBranch,\n createBranch,\n currentBranch,\n commit as gitCommit,\n isWorkingTreeClean,\n} from \"../../util/git.js\";\nimport { siteLabel } from \"../../util/site.js\";\nimport { readGitHubConfig } from \"../../github/config.js\";\nimport { makeGitHub, type GitHub } from \"../../github/gh.js\";\nimport {\n FLEET_RULESET_NAME,\n desiredRuleset,\n healRuleset,\n rulesetGaps,\n} from \"../../github/rulesets.js\";\n\n// Deliberately NOT ci.yml: that file is per-site parameterized (netlify-site,\n// node-version, permissions), so an exact-match heal against a static template\n// would strip those values in a green auto-mergeable PR — the armed clobber\n// the 2026-08-02 architecture review found. The starter clone owns ci.yml's\n// shape. Its pinned reusable-workflow ref is sometimes bumped by Renovate\n// (v1.2.0 -> v1.3.0 across 17 repos, Jul-Aug 2026) but was not for v1.4.0 or\n// v1.4.1 — the latter was swept by hand on 2026-09-01 — so propagation is a\n// manual step to verify, never a guarantee.\nconst SELF_UPDATING_CONFIGS = [\"renovate-action\", \"renovate-config\"] as const;\n\n// Reusable-workflow jobs report their check as \"<caller-job> / <reusable-job>\".\n// The thin `ci` caller (job `ci`) calls reddoorla/.github's reusable workflow (job `ci`),\n// so the required context is \"ci / ci\", NOT \"ci\". (Provisional — verified empirically on the\n// starter during M7.1 rollout; correct here if the live checks API reports a different string.)\nconst REQUIRED_CHECK = \"ci / ci\";\n\nexport type SelfUpdatingDeps = {\n github?: GitHub;\n pushBranch?: (cwd: string, branch: string) => Promise<void>;\n};\n\nfunction resultOf(\n site: Site,\n status: RecipeResult[\"status\"],\n notes: string,\n commits: string[] = [],\n): RecipeResult {\n return { recipe: \"self-updating\", site: siteLabel(site), status, commits, notes };\n}\n\n/** True when a file's current content matches the canonical template for drift\n * purposes. Normalizes line endings + trailing whitespace so a stray trailing\n * newline (or CRLF) can't read as drift and open a needless PR every nightly run;\n * any real content change still differs. */\nfunction sameConfigContents(current: string, canonical: string): boolean {\n const norm = (s: string) => s.replace(/\\r\\n/g, \"\\n\").replace(/\\s+$/, \"\");\n return norm(current) === norm(canonical);\n}\n\nexport async function selfUpdating(site: Site, deps: SelfUpdatingDeps = {}): Promise<RecipeResult> {\n const templates = templatesByName([...SELF_UPDATING_CONFIGS]);\n\n let repo: string | null;\n try {\n repo = await resolveOwnerRepo(site);\n } catch (err) {\n // A malformed repo identity must abort before any `gh` write — surface it\n // as a recipe failure rather than letting the token reach an unintended repo.\n return resultOf(site, \"failed\", err instanceof Error ? err.message : String(err));\n }\n if (!repo) {\n return resultOf(\n site,\n \"failed\",\n \"could not determine a GitHub repo for this site — set Airtable 'Git repo', or give \" +\n \"the checkout an origin remote whose URL is a GitHub owner/repo\",\n );\n }\n\n const cfg = readGitHubConfig();\n if (!deps.github && !cfg) return resultOf(site, \"failed\", \"GITHUB_TOKEN not set\");\n const github = deps.github ?? makeGitHub({ token: cfg!.token });\n\n const base = await github.defaultBranch(repo).catch(() => \"main\");\n const actions: string[] = [];\n const commits: string[] = [];\n // Hoisted so the `finally` can restore the operator's branch even when a\n // failure (push/PR error) aborts AFTER we created + checked out the maint\n // branch. `maintBranch` stays null until createBranch succeeds, so the\n // restore is a no-op on every path that never left the operator's branch.\n let original: string | null = null;\n let maintBranch: string | null = null;\n\n try {\n // A. Renovate config on the default branch — compare CONTENT, not just\n // existence, so a present-but-STALE config (a pre-App workflow, a drifted\n // Renovate schedule window) is corrected rather than left silently\n // forever. The prior existence-only gate reported \"already self-updating\" for any\n // repo that merely HAD the files, however out of date — the very drift this\n // recipe exists to repair (e.g. the Renovate schedule-window regression).\n //\n // renovate.yml specifically is compliance-checked rather than byte-matched\n // (see renovateActionGaps / withRenovatePinsFrom in\n // ../sync-configs/renovate-action.js): Renovate legitimately bumps its own\n // digest pins forward, and a site's prettier may legitimately quote the\n // cron / RENOVATE_* scalars differently — neither is real drift (issue\n // #651; a byte-match recipe opened reddoor-starter-blux#1, a PR that\n // DOWNGRADED Renovate's own pin).\n const drifted: string[] = [];\n // Captured so the write loop below can carry a drifted renovate.yml's own\n // pins forward instead of re-fetching (and so a compliant-but-non-byte-\n // identical file's contents are available for that carry-forward too,\n // though it won't need healing).\n const currentContents = new Map<string, string | null>();\n for (const t of templates) {\n const current = await github.fileContentsOnBranch(repo, base, t.path);\n currentContents.set(t.path, current);\n if (current === null) {\n drifted.push(t.path);\n continue;\n }\n if (sameConfigContents(current, t.contents)) continue;\n if (t.config === RENOVATE_ACTION_CONFIG && renovateActionGaps(current).length === 0) {\n continue;\n }\n drifted.push(t.path);\n }\n if (drifted.length > 0) {\n const existingPR = await github.findOpenSelfUpdatingPR(repo);\n if (existingPR) {\n actions.push(`self-updating PR already open: ${existingPR}`);\n } else {\n if (!(await isWorkingTreeClean(site.path))) {\n return resultOf(site, \"failed\", \"working tree not clean — commit or stash first\");\n }\n // Capture the operator's branch BEFORE creating the maint branch so the\n // `finally` can return them to it (#2). Best-effort capture: if it fails\n // we just skip the restore rather than guess.\n try {\n original = await currentBranch(site.path);\n } catch {\n original = null;\n }\n maintBranch = branchName(\"self-updating\");\n await createBranch(site.path, maintBranch);\n // Write only the paths that actually drifted — a repo whose\n // renovate.json is stale but whose renovate.yml already passes\n // `renovateActionGaps` must not have that compliant renovate.yml\n // silently rewritten (e.g. un-quoting its scalars) in the same PR.\n // On a fresh repo every template path is missing, so `drifted`\n // already contains all of them and bootstrap is unaffected.\n // What each written path held BEFORE this run, so one git then refuses\n // can be put back exactly as it was (null = the file did not exist).\n const before = new Map<string, string | null>();\n for (const t of templates) {\n if (!drifted.includes(t.path)) continue;\n const dest = join(site.path, t.path);\n before.set(t.path, await readFile(dest, \"utf-8\").catch(() => null));\n await mkdir(dirname(dest), { recursive: true });\n // For a genuinely non-compliant renovate.yml, carry the site's own\n // (still-digest-pinned) action refs forward onto the template before\n // writing it — writing the template verbatim would put its OLDER\n // pins over the site's newer ones, re-introducing the downgrade\n // this compliance check exists to prevent (issue #651).\n const contents =\n t.config === RENOVATE_ACTION_CONFIG\n ? withRenovatePinsFrom(t.contents, currentContents.get(t.path) ?? null)\n : t.contents;\n await writeFile(dest, contents, \"utf-8\");\n }\n const sha = await gitCommit(site.path, \"ci: enable self-updating (Renovate auto-merge)\");\n if (sha) commits.push(sha);\n\n // Did git TAKE the configs? The PARTIAL drop is the one that bites: with\n // one of the two paths ignored the other still commits, so `commit()`\n // returns a SHA and — before this check — the branch was pushed and a PR\n // opened for a repo that would never become self-updating, after which\n // every later run reported \"self-updating PR already open\" forever.\n // Checked BEFORE the push, so nothing reaches GitHub on a refusal (#741).\n const refusal = await refusedByGit(site.path, drifted, \"the Renovate configuration\");\n if (refusal) {\n await undoRefusedWrites(site.path, before, refusal.missing);\n return resultOf(site, \"failed\", refusal.notes + RESTORED_NOTE, commits);\n }\n\n await (deps.pushBranch ?? gitPush)(site.path, maintBranch);\n const pr = await github.openPullRequest(repo, {\n head: maintBranch,\n base,\n title: \"Enable self-updating (Renovate)\",\n body: \"Adds scheduled Renovate and Renovate-owned merges for patch/minor updates. (ci.yml is deliberately not managed here — its shape comes from the starter and its reusable-workflow pin from Renovate.)\",\n });\n actions.push(`opened PR ${pr.url}`);\n // (Branch restore happens in the `finally` below — on success AND on a\n // failure that aborts after createBranch.)\n }\n }\n\n // B. Repo settings — check-then-ensure, each independent (self-healing).\n //\n // Platform auto-merge is DISABLED fleet-wide, and this block self-heals toward\n // OFF (it used to drive repos ON — do not \"fix\" it back).\n //\n // Why: GitHub's platform auto-merge is a per-PR flag that anyone with write\n // access can arm; the PR then merges itself later, unattended, once checks go\n // green. The org Renovate preset forbids auto-merging majors and was working\n // correctly — Renovate never armed them — but the preset has no authority over\n // a flag someone else set. On 2026-07-26 a bulk `gh pr merge --auto` sweep\n // armed it across 8 fleet repos, and the next morning two `actions/checkout`\n // MAJOR bumps merged with zero reviews (reddoor-starter#77,\n // reddoor-website#111) as soon as Renovate's own rebase cleared their\n // conflicts. Realized damage, not a hypothetical.\n //\n // The fix is to keep merges INSIDE Renovate's own run, where its `packageRules`\n // actually govern the decision. Renovate can only merge while it is running, so\n // the workflow cron (see sync-configs templates) is the merge cadence.\n //\n // Leaving the self-heal pointed at OFF makes this a drift ALARM: if someone\n // re-enables platform auto-merge, the next `self-updating` / `launch` run turns\n // it back off and records the action. `enableRepoAutoMerge` is retained in\n // `src/github/gh.ts` as the documented rollback path.\n if (await github.autoMergeEnabled(repo)) {\n await github.disableRepoAutoMerge(repo);\n actions.push(\"disabled auto-merge (platform auto-merge is not permitted fleet-wide)\");\n }\n const existingContexts = await github.branchProtectionContexts(repo, base);\n if (!existingContexts.includes(REQUIRED_CHECK)) {\n // protectBranch issues a full PUT that REPLACES required-status-check contexts.\n // Send the UNION of the branch's existing required contexts + our REQUIRED_CHECK\n // so we ADD the CI gate without silently dropping any other checks the repo\n // already requires. (When the branch has no protection yet, existingContexts is\n // [] and this is just [REQUIRED_CHECK] — the original behavior.) Dedupe defends\n // against REQUIRED_CHECK already appearing among the existing contexts.\n const contexts = [...new Set([...existingContexts, REQUIRED_CHECK])];\n await github.protectBranch(repo, base, contexts);\n actions.push(`required \"${REQUIRED_CHECK}\" check on ${base}`);\n }\n // Ruleset ensure — the layer that binds ADMINS. Classic protection above\n // keeps `enforce_admins: false`, which is exactly the hole the 2026-07-26\n // bulk `gh pr merge --auto` sweep walked through (two unreviewed MAJORS\n // merged). The 2026-08-01 audit then found the six NEWEST repos with no\n // ruleset at all — every hand-applied sweep regrows its gap at the next\n // onboard, so the recipe owns the invariant now. Spec + invariants:\n // docs/superpowers/specs/2026-08-02-ruleset-self-healing-design.md.\n //\n // Private repos are skipped BEFORE any attempt: rulesets there need a paid\n // plan, so trying would either fail every run or spam a phantom action\n // that flips this recipe to \"applied\" forever. Classic protection remains\n // their floor.\n if ((await github.repoVisibility(repo)) === \"public\") {\n // Evidence gate: only require \"ci / ci\" once that check-run has actually\n // been OBSERVED on the default branch. With an empty bypass list, a\n // required context that never fires would make the repo permanently\n // unmergeable by EVERYONE — e.g. a workflow_call-only ci.yml emits no\n // check runs. Unobserved ⇒ refs rules only; the check upgrades on a\n // later pass once real evidence exists.\n //\n // Residual risk, accepted: this proves the check fires on default-branch\n // PUSHES, not on pull_request events — a hand-edited push-only ci.yml\n // would pass the gate and then block every PR. ci.yml is deliberately\n // NOT healed by block A (per-site parameterized; see\n // SELF_UPDATING_CONFIGS), so this recipe cannot correct that drift —\n // the exposure is one bricked-PR repo, immediately visible on its next\n // PR. Un-brick path: edit the ruleset via API (ruleset ADMIN is not\n // ref-gated), then fix the ci.yml triggers by push.\n const check = (await github.checkContextObserved(repo, base, REQUIRED_CHECK))\n ? REQUIRED_CHECK\n : null;\n const existingRuleset = (await github.listRepoRulesets(repo)).find(\n (r) => r.name === FLEET_RULESET_NAME,\n );\n if (!existingRuleset) {\n await github.createRuleset(repo, desiredRuleset(check));\n actions.push(\n check === null\n ? `created ruleset \"${FLEET_RULESET_NAME}\" (refs rules only — \"${REQUIRED_CHECK}\" not yet observed on ${base})`\n : `created ruleset \"${FLEET_RULESET_NAME}\"`,\n );\n } else {\n const full = await github.getRuleset(repo, existingRuleset.id);\n const gaps = rulesetGaps(full, check);\n if (gaps.length > 0) {\n // PUT the healed payload (merge TOWARD the floor, never away — see\n // healRuleset). Recording the specific gaps makes this the drift\n // ALARM, same as the auto-merge-off action above.\n await github.updateRuleset(repo, existingRuleset.id, healRuleset(full, check));\n actions.push(`healed ruleset drift: ${gaps.join(\"; \")}`);\n }\n }\n }\n // (No per-repo Renovate PAT secret planting anymore: Renovate authenticates as\n // the reddoor-renovate GitHub App, whose RENOVATE_APP_ID / _PRIVATE_KEY are\n // org-level with all-repos visibility — a new repo needs NOTHING set. The\n // old step was already redundant for new sites (the PAT was an org secret\n // too); existing per-repo copies are inert and left to rot harmlessly.\n // docs/runbooks/renovate-app-identity.md covers the migration + rollback.)\n } catch (err) {\n const done = actions.length ? ` (completed: ${actions.join(\"; \")})` : \"\";\n const message = err instanceof Error ? err.message : String(err);\n return resultOf(site, \"failed\", `${message}${done}`, commits);\n } finally {\n // Restore the operator's branch whenever we created + checked out a maint\n // branch — on SUCCESS and, critically, on FAILURE. Without this, a push/PR\n // error after createBranch strands the checkout on the maint branch with an\n // unpushed commit, and the retry then fails at createBranch (\"branch already\n // exists\"). Best-effort: a restore failure must not mask the real outcome.\n if (maintBranch !== null && original !== null && original !== maintBranch) {\n try {\n await checkoutBranch(site.path, original);\n } catch (err) {\n console.warn(\n `warning: could not restore branch ${original} after self-updating: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n }\n }\n\n return actions.length\n ? resultOf(site, \"applied\", actions.join(\"; \"), commits)\n : resultOf(site, \"noop\", \"already self-updating\", commits);\n}\n"],"mappings":"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","names":[]}
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-J7SZQUCW.js";
4
4
  import {
5
5
  withRecipe
6
- } from "./chunk-SQ4FSNYL.js";
6
+ } from "./chunk-LUF52EIY.js";
7
7
  import {
8
8
  bumpDep,
9
9
  readPackageJson,
@@ -243,4 +243,4 @@ async function upgradeSvelte4to5(site, opts = {}) {
243
243
  export {
244
244
  upgradeSvelte4to5
245
245
  };
246
- //# sourceMappingURL=chunk-ECDJDHKT.js.map
246
+ //# sourceMappingURL=chunk-JHJNNJLG.js.map
@@ -2,6 +2,7 @@ import {
2
2
  AI_AGENTS,
3
3
  CLASSICAL_AGENTS,
4
4
  USER_AGENT,
5
+ anchorsTruncated,
5
6
  buildJourney,
6
7
  canonicalizeUrl,
7
8
  crawlSite,
@@ -16,7 +17,7 @@ import {
16
17
  sleep,
17
18
  summarizeAccessibility,
18
19
  usablePages
19
- } from "./chunk-LAJ3FF54.js";
20
+ } from "./chunk-5UI4FERC.js";
20
21
  import {
21
22
  checkGoal
22
23
  } from "./chunk-T7ZUUOZ2.js";
@@ -26,8 +27,12 @@ import {
26
27
 
27
28
  // src/prospect/consistency.ts
28
29
  var PHONE_IN_TEXT = /(?<!\d)(?:\+?1[\s.-]?)?\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}(?!\d)/g;
29
- var COPYRIGHT_YEAR = /(?:©|&copy;|copyright)([^\d<>]{0,30}?)(?:(\d{4})\s*[-–—]\s*)?(\d{4})/gi;
30
- var SENTENCE_BREAK = /[.!?]\s/;
30
+ var NAME_GAP = "(?:[\\s.,&'\u2019-]*[A-Z][A-Za-z.'\u2019&-]{0,19}){0,6}[\\s.,&'\u2019-]*";
31
+ var COPYRIGHT_YEAR = new RegExp(
32
+ `(?:\xA9|&[Cc]opy;|&COPY;|[Cc]opyright|COPYRIGHT)(${NAME_GAP})(?:(\\d{4})\\s*[-\u2013\u2014]\\s*)?(\\d{4})`,
33
+ "g"
34
+ );
35
+ var SENTENCE_BREAK = /[.!?]\s+[A-Z]/;
31
36
  function normalizePhone(raw) {
32
37
  const digits = raw.replace(/\D/g, "");
33
38
  const national = digits.length === 11 && digits.startsWith("1") ? digits.slice(1) : digits;
@@ -66,9 +71,12 @@ function checkConsistency(pages) {
66
71
  record(emails, address.toLowerCase(), address, page.url, true);
67
72
  continue;
68
73
  }
69
- hrefs.add(href);
74
+ const abs = resolveNavigable(href, page.url);
75
+ if (!abs) continue;
76
+ const key = canonicalizeUrl(abs);
77
+ if (key) hrefs.add(key);
70
78
  }
71
- linkSets.push({ url: page.url, hrefs });
79
+ linkSets.push({ url: page.url, hrefs, truncated: anchorsTruncated(extract) });
72
80
  for (const match of extract.text.matchAll(PHONE_IN_TEXT)) {
73
81
  const raw = match[0];
74
82
  if (!raw) continue;
@@ -81,24 +89,26 @@ function checkConsistency(pages) {
81
89
  if (year >= 1990 && year <= 2100) years.add(year);
82
90
  }
83
91
  }
92
+ const templateSets = linkSets.filter((p) => !p.truncated);
84
93
  const counts = /* @__PURE__ */ new Map();
85
- for (const { hrefs } of linkSets) {
94
+ for (const { hrefs } of templateSets) {
86
95
  for (const href of hrefs) counts.set(href, (counts.get(href) ?? 0) + 1);
87
96
  }
88
- const threshold = Math.ceil(linkSets.length * 0.6);
97
+ const threshold = Math.ceil(templateSets.length * 0.6);
89
98
  const sharedNav = new Set(
90
99
  [...counts.entries()].filter(([, n]) => n >= threshold).map(([href]) => href)
91
100
  );
92
- const canJudgeTemplate = linkSets.length >= 3 && sharedNav.size > 0;
101
+ const canJudgeTemplate = templateSets.length >= 3 && sharedNav.size > 0;
93
102
  const sortedYears = [...years].sort((a, b) => a - b);
94
103
  return {
95
104
  phones: [...phones.values()],
96
105
  emails: [...emails.values()],
97
106
  copyrightYears: sortedYears,
98
107
  newestCopyrightYear: sortedYears.at(-1) ?? null,
99
- pagesOffTemplate: canJudgeTemplate ? linkSets.filter((p) => ![...sharedNav].some((h) => p.hrefs.has(h))).map((p) => p.url) : [],
108
+ pagesOffTemplate: canJudgeTemplate ? templateSets.filter((p) => ![...sharedNav].some((h) => p.hrefs.has(h))).map((p) => p.url) : [],
100
109
  sharedNavLinks: sharedNav.size,
101
- pagesExamined: linkSets.length
110
+ pagesExamined: linkSets.length,
111
+ pagesWithTruncatedAnchors: linkSets.filter((p) => p.truncated).map((p) => p.url)
102
112
  };
103
113
  }
104
114
 
@@ -734,7 +744,7 @@ function ownHost(url) {
734
744
  return null;
735
745
  }
736
746
  }
737
- function analyzeAnswerSpace(answers, prospectUrl) {
747
+ function analyzeAnswerSpace(answers, prospectUrl, categoryAttempted) {
738
748
  const category = answers.filter((a) => a.kind === "category");
739
749
  const host = ownHost(prospectUrl);
740
750
  const counts = /* @__PURE__ */ new Map();
@@ -774,7 +784,7 @@ function analyzeAnswerSpace(answers, prospectUrl) {
774
784
  const own = ownIndex === -1 ? void 0 : topSources[ownIndex];
775
785
  return {
776
786
  answersWithCitations,
777
- queriesAsked: category.length,
787
+ queriesAsked: categoryAttempted,
778
788
  citationsTotal,
779
789
  distinctDomains: topSources.length,
780
790
  topSources,
@@ -1045,7 +1055,7 @@ async function runVisibilityProbes(input, engines, opts = {}) {
1045
1055
  // a pure read of citations already recorded, so computing it here keeps the
1046
1056
  // ask loop to one job and lets the same function be re-run over a stored
1047
1057
  // report if we ever backfill.
1048
- answerSpace: analyzeAnswerSpace(answers, input.url)
1058
+ answerSpace: analyzeAnswerSpace(answers, input.url, categoryAttempted)
1049
1059
  };
1050
1060
  }
1051
1061
  function perplexityEngine(apiKey, fetchImpl = fetch) {
@@ -1455,12 +1465,15 @@ ${a.fullAnswer}
1455
1465
  function normalize(s) {
1456
1466
  return s.replace(/\s+/g, " ").trim().toLowerCase();
1457
1467
  }
1458
- function distinctive(terms) {
1468
+ function distinctive(terms, claim) {
1469
+ const namesInClaim = new Set(
1470
+ scanWords(claim).filter((w) => w.capitalised && !w.opensSentence).map((w) => w.lower)
1471
+ );
1459
1472
  return terms.map((t) => t.trim()).filter((t) => {
1460
1473
  if (t.length < 5) return false;
1461
1474
  const multiWord = /\s/.test(t);
1462
1475
  const hasNumber = /\d/.test(t);
1463
- const properNoun = /^[A-Z]/.test(t);
1476
+ const properNoun = namesInClaim.has(t.toLowerCase());
1464
1477
  return multiWord || hasNumber || properNoun;
1465
1478
  });
1466
1479
  }
@@ -1477,7 +1490,7 @@ function backstopAbsent(assertions, siteText, searchTermsByClaim, siteFullyRead)
1477
1490
  if (a.verdict !== "absent") return a;
1478
1491
  const flat = siteText.replace(/\s+/g, " ");
1479
1492
  let scattered = null;
1480
- for (const term of distinctive(searchTermsByClaim.get(a.claim) ?? [])) {
1493
+ for (const term of distinctive(searchTermsByClaim.get(a.claim) ?? [], a.claim)) {
1481
1494
  const found = findTerm(haystack, term);
1482
1495
  if (found === null) continue;
1483
1496
  if (found === "exact") {
@@ -1538,21 +1551,53 @@ var WEAK_WORDS = /* @__PURE__ */ new Set([
1538
1551
  "based"
1539
1552
  ]);
1540
1553
  function quoteSupportsClaim(claim, quote) {
1541
- const words = (t) => {
1554
+ const tokens = (t) => {
1542
1555
  const out = /* @__PURE__ */ new Set();
1543
- for (const w of t.split(/[^A-Za-z0-9]+/)) {
1544
- if (w === "") continue;
1545
- const lower = w.toLowerCase();
1546
- if (WEAK_WORDS.has(lower)) continue;
1547
- const properNoun = /^[A-Z]/.test(w) && w.length >= 3;
1548
- if (properNoun || lower.length >= 4) out.add(lower);
1556
+ const names = /* @__PURE__ */ new Set();
1557
+ const run = [];
1558
+ const flushRun = () => {
1559
+ if (run.length > 0) names.add(run.join(" "));
1560
+ run.length = 0;
1561
+ };
1562
+ for (const w of scanWords(t)) {
1563
+ if (WEAK_WORDS.has(w.lower)) {
1564
+ flushRun();
1565
+ continue;
1566
+ }
1567
+ const properNoun = w.capitalised && !w.opensSentence && w.raw.length >= 2;
1568
+ if (properNoun) run.push(w.lower);
1569
+ else flushRun();
1570
+ if (properNoun || w.lower.length >= 4) out.add(w.lower);
1549
1571
  }
1550
- return out;
1572
+ flushRun();
1573
+ return { words: out, names };
1551
1574
  };
1552
- const inQuote = words(quote);
1575
+ const c = tokens(claim);
1576
+ const q = tokens(quote);
1553
1577
  let shared = 0;
1554
- for (const w of words(claim)) if (inQuote.has(w)) shared += 1;
1555
- return shared >= 2;
1578
+ for (const w of c.words) if (q.words.has(w)) shared += 1;
1579
+ if (shared >= 2) return true;
1580
+ for (const n of c.names) if (q.names.has(n)) return true;
1581
+ return false;
1582
+ }
1583
+ function scanWords(text) {
1584
+ const out = [];
1585
+ let opensSentence = true;
1586
+ let index = 0;
1587
+ for (const m of text.matchAll(/[A-Za-z0-9]+/g)) {
1588
+ const raw = m[0];
1589
+ const gap = text.slice(index, m.index);
1590
+ if (out.length > 0 && /[.!?\n\r]/.test(gap)) opensSentence = true;
1591
+ index = m.index + raw.length;
1592
+ out.push({
1593
+ raw,
1594
+ lower: raw.toLowerCase(),
1595
+ capitalised: /^[A-Z]/.test(raw),
1596
+ opensSentence
1597
+ });
1598
+ opensSentence = false;
1599
+ }
1600
+ return out;
1556
1601
  }
1557
1602
  function verifyQuotes(raw, answerText, siteText, answerOf, prospectDomain, searchTermsByClaim = /* @__PURE__ */ new Map()) {
1558
1603
  const answers = normalize(answerText);
@@ -1564,7 +1609,7 @@ function verifyQuotes(raw, answerText, siteText, answerOf, prospectDomain, searc
1564
1609
  let verdict = a.verdict;
1565
1610
  let siteQuote = a.siteQuote;
1566
1611
  if (a.verdict === "contradicted") {
1567
- for (const term of distinctive(searchTermsByClaim.get(a.claim) ?? [])) {
1612
+ for (const term of distinctive(searchTermsByClaim.get(a.claim) ?? [], a.claim)) {
1568
1613
  if (findTerm(site, term) !== "exact") continue;
1569
1614
  const at = site.indexOf(normalize(term));
1570
1615
  verdict = "confirmed";
@@ -1970,7 +2015,7 @@ function claudeCodeEngine(run = defaultClaudeCodeRun) {
1970
2015
  function defaultLighthouseDeps() {
1971
2016
  return {
1972
2017
  async audit(site) {
1973
- const { lighthouseAudit } = await import("./lighthouse-52WSPZ5D.js");
2018
+ const { lighthouseAudit } = await import("./lighthouse-RFMFREEX.js");
1974
2019
  return lighthouseAudit({ site });
1975
2020
  }
1976
2021
  };
@@ -2442,7 +2487,12 @@ async function checkBasics(crawl, deps) {
2442
2487
  // Only meaningful on an https site, and only over the images the extract
2443
2488
  // records — stylesheets and scripts are not captured, so the report must
2444
2489
  // not claim to have checked them.
2445
- measured: isHttps && usable.pages.length > 0,
2490
+ //
2491
+ // `imageSrcsMeasured`, not `pages.length > 0`: the loop above reads
2492
+ // `extract.imageSrcs ?? []`, and that field is absent on every report
2493
+ // stored before it existed. Counting those as measured published "we
2494
+ // checked and found none" from a measurement never taken.
2495
+ measured: isHttps && usable.imageSrcsMeasured,
2446
2496
  imageUrls: [...insecureImages].slice(0, 12),
2447
2497
  imagesSeen
2448
2498
  },
@@ -3014,4 +3064,4 @@ export {
3014
3064
  mergeFixes,
3015
3065
  runProspectAudit
3016
3066
  };
3017
- //# sourceMappingURL=chunk-QFSL2L2B.js.map
3067
+ //# sourceMappingURL=chunk-L5F3WYIY.js.map