From 5e40d2ada1d65600a25f501ff031e5b7c9009715 Mon Sep 17 00:00:00 2001 From: Jesse Raber Date: Thu, 8 Oct 2026 20:44:06 -0400 Subject: [PATCH] 0.2.0: portfolio + install lessons (R-023..R-035, R-037..R-040) Gate G0/G8b/G8c/G11/G12, fresh-clone rule, connector blob-SHA compare, branch-rename ban, install channels + Copilot split (W-015 open), install procedure + Channel column, two new templates, check_versions --extra-root, loaded line names path, visuals. Host-install log rows from today ride along. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01WGPiXpFCxzJMTCP4fagEwf --- .claude-plugin/plugin.json | 2 +- .codex-plugin/plugin.json | 2 +- .github/social-preview.png | Bin 0 -> 35227 bytes CHANGELOG.md | 11 +++++++ HOST_INSTALL_LOG.md | 4 +++ README.md | 11 +++++++ packaging/INSTALL-microsoft-copilot.md | 2 +- packaging/INSTALL-universal.md | 2 +- packaging/RELEASE_NOTES_v0.2.0.md | 12 +++++++ packaging/check_versions.py | 31 +++++++++++++----- packaging/verify_release.py | 2 +- skills/github-release/SKILL.md | 4 +-- .../github-release/references/host-matrix.md | 15 ++++++++- skills/github-release/references/publish.md | 4 +++ .../github-release/references/release-gate.md | 12 +++++-- .../github-release/references/repo-setup.md | 2 ++ .../references/verify-and-record.md | 8 +++++ .../github-release/scripts/check_versions.py | 31 +++++++++++++----- .../github-release/scripts/verify_release.py | 2 +- .../templates/HOST_INSTALL_CHECK_PROMPT.md | 17 ++++++++++ .../templates/HOST_INSTALL_LOG.md | 4 +-- .../templates/NEXT_AGENT_RELEASE_PROMPT.md | 19 +++++++++++ tests/test_helpers.py | 15 +++++++++ 23 files changed, 181 insertions(+), 31 deletions(-) create mode 100644 .github/social-preview.png create mode 100644 packaging/RELEASE_NOTES_v0.2.0.md create mode 100644 skills/github-release/templates/HOST_INSTALL_CHECK_PROMPT.md create mode 100644 skills/github-release/templates/NEXT_AGENT_RELEASE_PROMPT.md diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index e911226..da8614f 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "github-release", - "version": "0.1.0", + "version": "0.2.0", "description": "Create GitHub repositories for Agent Skills and release them right the first time: version gate, deterministic host packages, draft-first publishing, published-asset verification and a per-host install log.", "author": { "name": "Jesse Raber", diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 9f6bef7..ba17619 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "github-release", - "version": "0.1.0", + "version": "0.2.0", "description": "Create GitHub repositories for Agent Skills and release them right the first time: version gate, deterministic host packages, draft-first publishing, published-asset verification and a per-host install log.", "skills": "./skills/", "author": { diff --git a/.github/social-preview.png b/.github/social-preview.png new file mode 100644 index 0000000000000000000000000000000000000000..6f9ec115b9fd1719c4977139e01e632c4ba5594f GIT binary patch literal 35227 zcmeFZ`8U+z|2Ix4l~5!hv=TxQLP(1x`@RlE_GPk+eTz!ABq7F9_I2#*7(=q}24kHG zSqEbo>lp6K`~AJo`QGQ;f5832=cUujam>thU9ao;d_EqJ=i?Hlsjf(Wf&Bs%6&1a* z(hF@Wsx#o*Q$goWftzZ~vmGj`Fbm}u^15Eht5cq)%nRI&8+6pa6~5=C|Gf2BYckxI zXjJ*dz9^-tvRNN#f-EdTHdh@P7mXLeh^XDGm#(WewR}Tul5aaDojsTMc5;C-6Fuv? zJL#4{glzW6>^}LH`!Xi@%PqMmEQ|$wBo$S6SMTJ1zxyfmmFo1#k6yf|xps2Dap6wm z$(>5;2KCvK`{z^VADrBuUQr67KKUm7>8Xn+_e*D(UZ329?)}eO{Lfna&%OYm@INu~ zf6EuIp=%d@Cb=(7+HH3dE6ru4=EwiO$xt)k{>(OTuHbq9DXP~THK+f-oU%s65u1@= zIr~*XE$PGAks<<-xIkP07h!&xKei(|`K?G|?2U6R|GB*Py~m8e*VqypTQxLisv#vQ z`J3x(YY)77Mny%X2gQAopk9Y41<}vVtW4(VgmO*QDZHq-U^mo{YY2bNAJ+-qaBa;w zprK*TT#%2i)_ZHDMa7^uRW9)8u%(6|p{nCBL7GoC!sMt8+r?S7X{aWNOc}E5S@m=> ztWGRfbcS>52Z#{qEcK|WUPOD16joHI?w>Z*w+3G$Q@*>gu{z&kbr3DBFWXGJy1GPX z=D~pOUpils`c}l~IKW>a`a15Hzloxvq9uz16pF8LQJQVN86C;SckXuz(vy)})@OY^ zhC<7*HFYKEW~R0@y~ml!H9}G;|^v!hdcQA<`Jyma~{S}5gPG*o~`8?(HzosogTdk z3;*tLJpPGqq^7Ll1J~Q?w(-S=iHUJ~Jus>pOkxf`GO|{Kma$xrV{CR`gW-?V)HiGF zpp;yrMLl(R$sgD2wUbBjtK;KYQMz`$=kyT=HENs&LilkbOnmp#Trb@{NOWr*uN}Rni{sV(9n)(CPEo~M;M&||WMsM_bl?{#F;oUm`QwC4 z4qp<@z|SsImE1U)Mbc4Ctpk|oPdTQ)7w-Q!Rt69J({6%D=RxYuZ7Sqxwv5U7O;6bNGJGbp%f|7$^c5g zDCz77O;GN2=>Utt?@MzF(vKh4)Yf|Y_q|=3L*wN9(H2(L`tbpGGczaEm1|Khn5)v#Nd0NSPZzl!Ikx{y z5<`&?OjK7`S#92)Tz}elS8(Ofj0bL;Yay zqdF$UC77*mZw&ZugBg)HAFOPhQ6cpP=z(BKkq&fOQ%v)ST-hHg7rrjzF}W^6{dAg? zSl+B4>$?Lz^ zb598aUfzYn!}{dp$$#I!6C|8dr9G$DV=O(_aAKZnf?Xgz1RlFhS;q1#)>#B~)Dd*P zDcyPh9tIb48Z5MnZLEVGQYeRqhm)p|l#{HfV`O9(5Fl?BaD?*R1xbl1_S?xWo1V!U z8hWH-(7jSfXn!mvC2l`@qr{mr@Z>^|Qy(221*CFusrUA(tBct1+VJuw>hcQ;2?_D@ z3*Eop-QL~Z-tM)2L*^l$q^#`pK@{8|Bb`J}Y<&t_9&$dJj8X;|31R4bu3kZWPG~3+ z2@eSgL7_r!v~O*0ZqaRmFW0TjwMmpO*~8x6etdOk=-v*04}Up=U9dOr0kICpI{N)VGRI&hlVjVBq3fI|^tJOp?&)jgHnBEp_Ot z;*>5n4HMC#?`bjq5*ZPp0{^VvlO%qg&S~<_{R;Vz*Lk|ebH*Zxsywl^NMszoa&U0) zheIhy`xE7!_>U0@U7ek(Ixg~;XlQ9eogqjP5u2lu^M#uFD2dg>)6>n)xGMzOXihgnnZ;(RfW2Gl zE;douox%iEz5C)V1Y|;db-c=ZW8Y?>$GpUb7=Y313pcHKS6W!u-K$=`BMf#&1~&6e z$ls1wF3yLJx!_6TKGK{S4xG>9P4!+511asOWHV<~+GFgg4J1DBt*NQ$cxBXLe*>hA zVb%B%G+V8;KqgN&zouZnTSwzgPUhiZ7ZFWq5V|dj`3%2A)6~`Fux!c9t9G5Jm+?vK zLLo%@r^jj#$kGBwQPCQYVFxRD7#sBzS%<=$tow3c*NDVW(WRzxJrBJ&WZ6O@MONdy z@uS1knrFBm{Lm4?NzNbGb zFv;m9l$!;tCGw)pjwp-*2COVBY-ZUnZY4f?b66mZwpC&`%KG{9Kwf^kO;+U9r4KxU z<5#Y-59Ll6pE^x-pE>3AdCI{~RW)`YG^mgDzPd)=10R1X+x~iQ3_T$bBTd$0mvwB< z-bSApDYE=-Y*e{Y=m;_B81#?3Fg7-(mL?9v?xTpPLCHM>11uD6cF^PtO?*Tay9GvZQSsFMi|qa8(UnY0 zOvk3^rcoGmWOwU$*~=Fd2e6)3fpX><->+WL4V)s%NjP7(_&!*ekg`Uv&&{3@!VM)F zqY;pO0G+aJHH+*!|Ahro_El1(^hMp76%{ysMLicUJTB5{+wn>GJF{{)8xB1nt=auC zPQ3D-no8m34$bKBaHYz^7oWnt*?cil#%bLgFpVP9(#e`BVQ*io@pg0dt>e^%rRiS; zehFvXqjwodiARFWT9JM9TjPwFk3z|Q4FFz(yT14nAmyj?@;F)C75il zQDxlO-mUsdJeM_Xz9;FZ&O>i?ochz1bOAYYJbnam;k=!03db;uqJn|CdRcM2&`^e6 zd(e@8CS4MDIG9q7~M$CW5q%79-o*9L^;MpM^}e+LPu}x zWiaBzw5G2LrNacmUZi9AS%o3uZzFC-u_F#Pq#@d&aZ7KnGl*I>9nRU?AO>pwq*7In2vEzuTQ3=KIQ1jg=}sef7l469mUt8{a*KF< zm7StCl$Mc_AFXoA!NJ|!+{kNit5$Y91xAoZ@P3tlYGz5-&M3BCI>~5b8StGh&!`ns zbbt(Ixb5?~i&PJ!N*P0jYGP!ad_nYCl9C$lOL;DPiSC5gI=p!$4zN*lHSP!MxN*9^ z{)wAG=D|>rj?JHUQwqf$Jv~!ZkYk#?a*g>UnT>PMv6;Ap)MLLUrY-&F^op%dMN|EK zj^95w7AiDfNU)X%`D!3ciaP#PRzN^N9YRl6*PZg|7iS~YaI^Rtl>uX*96l#ouEA@` zU>>0)=HlYAzXQ(#iC3y5V`eSLcRt;CFzgk4t$VA+{HvX~vy2!FlRgm}^RBK&qb%qwEPN65T3 zzvnln`*lS@Q2_@m0%g;iiVT>wHZ~q_PM!=7KIQA{DFFsUflV?qqYn;Y7k3=Z zeG~Fkf!<++G+24WQa0R3MYY8QJ7p@}XdeBB#mV(*hOD)Ojoa2Nkx46|aXQ>R&Y4J85MEQS9O0p&;8762&tLDBt0?ASW ziqj>#X3NP~;*UZU@~`k)$9e_{o7R(dkZu|2mbx}Lq~|f1`oKf%$kC=RhUX1;kAF2t z5D=9e66xA*doH&=Lfp(sFc^hG!3zpPa})tl1r?j^(a*F18DuYI4-?8LHYds+>H9Vk zkuW(kGhL@@YSih2a96jvebNt!Sv=uZ;@ z%nR^!EK(kae=_+nQHD1hHH?fBYYSe#<&bdl-rklFBN_m>@WUncs^FgE;@|gm@p3*x z=Ll*+Y6q%hfD@p@)ntLZp|y>?SeS8EVc|-afejR2s?zmQ?ug_TC>NOGBLWwPm5rf$ zCR$@^J&%vTi;DO64dzxgo3o~BrJW8$eLb898sY*pHT^+NM((cJnRO2j>yhmX5AC9M z=nU>Xe7F?K9e6O^mUfCT!Y1O?4>(fAKCDsvq)5i*`o8z@a2>QPT3HEIC_exC)}{!& z?sPpMxJKm#?Ex&Eot}*EDB?$F` z!;(#2L3F1;Prw#u3R4N!6w)4lW#on9Y`dQbG2(7lgSf>`!PoD}=?>8j;}y>akEmw> z5{ryfP2vA34^XHCK0xkWd;4ZlURQY(h{~=z%U}Z=rP0)sl=Lr(QLDZX8dX1)92>he zFHKB{xA)aRR#cFRJZ1Yc+IUi0bxOT~oIwm&A3$>Zy;Ztd$M6saKw66mthX9~Wc|Yu zZSve*rmz5+i)@z_70Lw+GGNBC_bXiDx~^QgVr!J7%kMN&5vI=a;3`-bRmGw6B^enR zTn;QOEF37Fps-PG=EW>?A+#s4LA@X|JKKMk!dT~=>+seFVo<7QfOrw*PbvkgB9?h6 zZwLV;nn@LH5}<+05?sR|>fMbS%+qi_jxC0dMf7~W9zD&Ot6Xw_vR|Eo+RKE1)`gaVtGByz z(6R+Yj7F*yCePA$)xgEY17Ti_E86pt5w}^rKu71fiX)TL0zobRZHV7Csb;|1?SJ_2 z=lk~wAE?mHn_6V(7MQ7^$s_52neRBb=m&akwnWvD3|}lFCPmtF<hbM!OP-=!t3 zIT6>|!_Bz@&hhHuGF6~uqz0}nEu{HMNk~Wl=N~_U>uc|y4 z>(b|;(e`On0-&x{IWZ4ZseM@P2IcO=Jh%*34$81ZETNpcD-9tA1M|wy;QtmEM^Pqkcb#L3>q)<9x&7;W z`(&HDhAKrTE;olpUX%T^zG|NbsPS->pqRMFTYD^n7dZ+`-X&yE?jaQxGN=ABz^fg7 z=dxHM2T9{CEm_Kjwy`Ds@~4m2s+K_}Ul1~V0!sY!?KY5px9?$EPZCE|u5{BYH8qT7 zQ4m$&$3M$M;3WqX` zIP*E(Ec~|Sg%`&%kVvH6gk7jO!+C$Jc@^Q_(RwR#`dxGTF{yFu|7K9)2~@zLd=0Rb z$tfwz*bEAzra#yGwV_#C7nf25q(Rzl_s*|tfOprfZ)w1m6-=Vyi?n-MPJ$6$6?J=S zxl7m>vLkTr*?0<4xGxDJgSMKOn25gq(7ms>M??0Z9$flg)#YncuS;K~P|*cn;_!q- zSB_Ukr#pSajbmd^ zh?P#)69Lzg1G?R?CY#P^_3__s12YWt=?fVkS~NWL9Z46Sw+krzZ)r%(g+HYWeh0LT zwW(V$NP)@Q1pKjKF~N92*wnGVruNgB2D2*Lr^gL-SLtkj;$JPAj$o@p+aJUw+6r z@S0GSvau0HHy=0%WZZ2>m(l(vpiL9~*IlgW439P*Vfju|wOS0IMf5`e9bPQeuSP)j z&Ro%#M{`AXiv)z7{e)G-GF>3C-|iV~pW6g@a%iXmgq;v&6Mhu(Nh8ol!9u%?>m^`# zfI`66Bk0 zmv`^p^=~}Ba;<+M_U;5&b-;Jg6dyaXht%%K)k}YmXXyHR;OM7V7P<=;0H=3{s9Y>O zXji)PEiC#A208_B^3Iy{yN$s)pviMT@%oyOV4$hF!6sp`_w{31pXkpdV?IGa@?5oC z0ND~!-7a+d6XViUL1>;yQ~lQbN=WDz-S|ag*ojoZOy>LxfHn}}mrG}6b$B>XB0&R^ z%ftFbKq<+<{<#EHsZV~Kq>YUcSR9Gu4uWMGfb|@;z#V(t3N8YGU6z3B_QQ&3m)Lw9 z|1-{tFk48@m)_>-Kc*2WPqsV2GWQ)@4D>>LXJ!yNIX1fS6#a7WCZ|Ig(4^&+k<~g? zKgaO|^FZIjKg!M0H*z&KH7Q_U8tmlj(Sop+E#++tzTpU9P;}*qsPXGO^Xr?Y+npRh zm9rovU3+#b=EKM3;}2$rww4eBWn_li@w)yUVY%ZDo76r~ZeTC-bPvW0gqP~{UCZ>J zqd$Mu<4y$f)^c6vuVzKX=#+P)Q5R5$Jl(J`flmT_e3Ah(L%&?(cU#8O0;Rncl_s}2YHQDKs}w(Y+aJ_vRKYu3J$U;d6RP2jV7K%MdQi1*}6FJoZ5Sv6@uz7rn3)+hE9k4SbTyo`}L$h_t zS_@x~&)mzD_~`I219tmYdybJ|5Z!?>s8`Qf1Dk+ZaR)tg#mb`DFT+8_y6XUXW z*dljyNJ!b8-&vA-`}b>%@6H-Fj|M0jKnrDQcsqPjb)#tvFVVbdVJsk-Y;^EuGq8{1 zYvM}HtX!v@)s0`rTkT(m#%>3VEFg< z&zuhn^KtB2_B$9mRcneM7^g^i^ye`OLZm04*s7?H(INKt_5I%-9`dW>_}wZxtI@y9kP}<=L3;xlzuhjO4RkGo1D-U;y#$XZjH6r3lj~seor`h>|-@9js~o zgIR9883rBfaaR%+E9$dgVva2C{?4NJRjWtPYVZ#)J(dbF{KL&nO>epvV|)r9@5E@; z1&GzZ+epu1d~R_^Yv3VV96D^-k|_Kn2cz%qV_gR1H)jZpD|L$9h!_*|YBf38o$#92 zbUH;ko-iV&jZQ;#TTQ2DNm0`OnJ6Fojc|Z6zyic|ap=)AO2ejVc@F3!QD%1w zbPO{mX8Y-b1q%w^6udje`KQE#y`XC(fZPP*pWMa8zY-*st+;%HHt{0Xs z1iBz;etQi&`?b3EyBncIr0yn*PRMCmMnYZqb{z{h8em@;{h=kE^^sL+7=Jxz(&%tw z#GPRPtR0bJF`&;Vwyk%0ce$VR+wJqb z`8B>^t@#-l@Eog`tpOiLnG6@r0`xbqDGeh`6|^mE$qr;Im#3smfnAi|W@z;gsPl-H zWj$$!?Ky1Mw(W5`M(r=?QD$XL4dfk47NhMaxvoBA7xO!wl8ENSNsucIx^dMd-{QE~ zYE4t)Qi?G83Q^Y)l;(4*6P5l8MPpKN#mt}z*`%fhcpa}r%lQ>68|IdVN7-_&t*m5H zHod*Q<4|w2P9Dg6&{qQKptxZtn+x*$-Y{JuxhElFcV+T$@4GUP*QG$mc!5Be&DhNr zln2!kQBV)W2|0wJEC6d@sz4C-c~rm*&&{=k7=S(7U|jd^-{)7zs?8n5_T0BK#0JWz zH+ls;pbYKLVuzE^<)1!^A5jv9n?vabGW;OO(o$*(XbW_#4ie2$i9T{ zDYM~oJ3Bnw*#iJb47fx)PE|NlxaBlJqYbjamRK*Qn4`CbDv z+_VgwW=B)uHfaHaU!Mhs1mnCNdMBX04L)KAlOO34gw54dUx9XbQc4O$Q`6Acm_Jrh zsw3HcgtWQ6UBqfwZHFltnV#-R5-Z&?X~V+Ml6>c$0T@0$nU;)xy|D>3Sk!8WQbxK- z0Q_Kv3MVrZf&N5S@P67CPEnt!1A3c2NR8XVmm#xd!Yl^pQlL4zy)=bsdAZQG)tw6~ zSXT?AoWNN?wUVU+r;kEt6BCWZKvx=@KU}Bhg>FFIM!Js>mFEF>2P%K>B1@HDSA((a z+x4dqUpk#h8d=yuq&VI2A%Xj(_s-W=QeN)6J;%z;CgZx4fL|G{RBT=q5!23tV0hG| zV2*gCLA3bgBEk9)Qvn$f(1e0Hr9S~-1A#yi{447-GiSJ%fpF#nIvBia*UWpMMg{Bf zMZZlNDDx{32}0abex&sQOM_ws)~n0?1up>=k~>pwA9+*S=EFu%K!E z{rit}qvK=k?=UHE&8xvBSO*sUfIb`FH8+ylYSIAIF4fe%TrL3*TYS}XUrvSM(zjrk z*!Et??WoB&+xf!|uw}nt>&Y5#2SODmXip5nPLis`MycKS{X{sNyiwoBD!iwPRU0Z` z-D^42kWC`vnwk}b%<8T)GjpVUU0NAb1cax&=Q z_Ur*TI|iybu1;^Sn>7{Si?Hx7VPRn?Z-Ty`-wZG`fDZpx$^&FcNp4NeC=Q3~P@bP% z<-B(78m`P6>>seA%XPH?>R~WqGBB%?kUc4t5IeguH{p69^UrZgFN%BI1CR;`Ztj%x z*Xw{fh736W;1SYocg<5E;E|_bw>5aepds9tDe)e(siiDN|8h-j?|Tn)D373_#3O%S zLMdXdy!CcD^tkEw?`JY~0fjm_J7B<=Jm$(}8{nfTd$|=am;`rbCNo|ybY*imVWfT) zu6a-2yqPBD*nllLFv(IXEA6o+?#bea2cUL)@dBOR!9Pr2i#OR|y0yoo&f`hJ`e1}q zq~2C%p@jQthv}Wdwf2Ai{yB~i23*bs-$>{R+}~z&im#|U!BmQ;OiKZn0rV}VcgY^~ z-Q#2?&sfHE&EfrzLzX&)1qC7GEoim?UIaAWQ=nVs0a+TJ{hZPjm*?+d z$R+4IriyVT&fyXF1O>$)`&L#~grjlWy3HQjt}0HDc^t|!#m)kx`IzbNE0!x)dc8JS zq|64DLFd$*LmU_us))r=@Zx10XGJ_Njl2Pvuc~fMzzhQaz%I_Yr47BmLk5E|{sTdm zI<*PO=qK+7Gf2D#4};71up^YwjWqhzsl4vC+m`+zqX3MMXUGe}G60}XOdM3m$Y?Ar zVCUmbmk9(Q@{hNvM_vde^qH4_vwg;_p9AWz(<)CzJK-VRjTi_74zKT?%Gt~VVKRI$vvZC5e%N@D$VrZoB%Tq zU<4ld2#zvueb2W*dbUnyb|Zg*fYCUIf9olzs92dZa&zaL#V(&vS+?`?COj9n7DLq~-k*`L0es4FXmplWeW_G%!WyEO4g3sC z3udZnYNF1iw2YkIOT%M6hgU79U#X~=CyJ39o1Xg=IzUdge8%$h7YR*~{Yu{{k8`Tj3k#n6T{_E~>N9`FIWKV>0C?c2}mLg{PRAV_PHBu5h~b7@isbWMwXl&47g=PDZPw(f8xgT$m>g zZbEUAF2>jFPq%Hw-xuv&vUDGTy>-T`r#zku%Y2&w8>;Z`sD zxHgClf}Kbd_nBUEn%4PQ0WlxAp8P>0X85qn-Mzc=Ohmi~Vfc7J`9yOrEh#CHsz!tY zhQ!D&wfy(j?RmuKD3gOZ2`Fmo{d@cs=w$tN`<^mE!cj!ONR=0GofF8qh}r)XD2c0w zkxR5JnVFcPv>%!4z?d{#drGxHzteONba*5!Hbx5X8t`RsfI880`L)&q#a z0Y}@r)FBGVPqK!C-mn`QfGuogas71)Q152I>nj~-Be|um+kaAnuB=+cFOXs}Hhr9r z-+A>lP)7FtX#k~n7L#T-#qT%xVFR>hU12aW&nQQXFq-0W^t&Z`zJYZ>$`lLR+M;Wq z90lV2kiYAks}9#^HOM>MezV&Ki_P%!YdkxG?!nqLPb zfV@=w1{2RBR~R0x@xY=~{|YE`@0EhK-N_!yfL4OA8@cf~*PxFJa$CCEcA)T6PR>oM z`1tr1-a9}+HLY`>8(I?88W_mR(m$pd+NWdfGp||v`}Y#A>?QCr+`JjJjHM%O>>#u< zKE=j%H#Ny)g~#p*&l|7&aAEd!Cmiez0j>x8{;I%vEGFJ#p;~T)QgL7-uv&T_xJf{O zIgV6x{5e1SiGeG3u1{d07h<^8RcunV_E#Xqy0p|q+H;kyA6<+P==FQk`k^D5P(1*; z2!dt@!=Tqt?J$vT6YUH}E^(f2n0NpZcI-DDA+vh-&7nkQUJW}*Tx3gG{O#KbUD%_; ziB>g}wrEb#H;1>4kEBcu4Qq}Ye*w#mr2l;3Wq}N!m6U<-=i@3YGQt!%y?6l^FPf;b zo2oa`p{1ooMW})DwYL!q%FSdsG*JG1-WtYO1#Gaam=}WKL$H)~jafBKW|I>u!F&%~ z-0{-&OMlI!8}Z<3o|@_amojj7oe$Oo8j7y2uA$)%%K94x1qC04RupnK7MM!D=`bY~kns^athy_uPqDFAQ~)06*AO=;wT{|bma0ecU4MFj{= zA*AmU)v@2c0rgq&W!sN}fp|Tdvs6Dg|F>a%k>+>Y&# za$mdxq$bUGB{`uvlzRw^@~79={now z&tEw?IjTH5-5P2r8Nf|J$J9h(J=S}RMKq9NwOUnV(yeb0@kWsMG1a{ossi8Ho@)3b zr|cg9;Dav3ObG36kI7xgSfh|3U!Ja%7U~>;}i3ZulP#uRxM?(!`vpWOtGAUQDO&uGf zdn5G*}ShIqL7_!=%LBdJ}&7-fs!3Z54qDI`$1hb19M5BxgT z2N)<3{7ao@J(}2+vj(wd@Qfsi+2g>qz0!GU^pbg?xpuai}%QY&` zW-GWrr8&E>fUi}_v^eNZsTeNMH$!tJq^0#K@=}d*G5rlxzVi(MK?-C8ciPIz3XS~w z&W7E1#n^CxLASA()5*f+HBb&DY)3U8rlh5z-8<3?41@}V*8et9Xc^bcYyFn6X;_cN zQM<*GX<#E1Q&gj-S~jk5%sX7&3lwOG2>T)q^{1lBetzn(cwW$Ws8CPkAq9H?yRGo$ z+pDbL8}0;LvvHXs$LJY>;cD~1Ig)1=TE7c;LF41Oq<#MV3!A0TUIb>%2U`9X#SmY| z!35=>e>7 zW*g86k5!PCx($RmN5P~rg2pTT{rp%ko@<#YGQQcJdH@VoJ54=z_3!}vDkqS&DpaG9;>+emt9F>4pNUt!3M{x zD@*$A^?%%20Hc5vnZ17f`UTvh2QE=Ow>mpLtp$9>>u)?*zJW%Q!~0=x zN3pGq9WC{?wl;%>Y^PMPITP9uf+nA^g+`ci1^PVQI*)A)X-{VZlkUZVtoK?~bMx~f z*-l6vwWJZo3&2Lp7u&J9xydEl-QYqwi0evFPm#*&;j;#uEFX4KHRa`-%*24HH6HC{uJ6^VL((`@OJ*IR@4)Iqh70?dy$pW!l{XEN& zFe#JE=5pw@Dc>sFfimkNV*~;Las_Bsk#|;vBPiobLBuD=vRr>C4Bxo zkil()UhA7edg|*3YkgV8cA-JB)JfU;9U&Zt9|3VAOD4^B+H2pmSqP++;V(j%&;lEK zDPQ#N8pdFQm?CYL)|ckF+soO49(nvAV;t$XYTR7|5b?$9nIn1db^P(gXrWa$E0d^a z&(1QiGhH4~*zgMe2HJy2q%Vl;IuF88`{+i2KIPrU?=$psAH?iL{0}e<0Rj1l;C=Mmi>i8PJX-GJPteoIJ9AQbyjD( z;NanikW$v*g1MhvV`I}Y_vmph1lemstS`3WR=*kbTyAfDc9Z)_zUkB?k4ECbA5sqg zM0(P)TvZa-_57e`J}jtCnq6Fcyp?6}eT>+{Au%7Yo+?5v5_cM}pxE^Yhohrq6K%TF z?|!pM^9}g>^FpvD>n zp#q>LJB?St4@YO?1T|9>`wlB2a{1YIH*-4&g&fX`bgmitf3|0ZDM+Rib&+_}xJ2B& zte{4HW+-0OY2>c0`;Ec6P?q&24f@=67?m z;+KFwU0o`dXbdE(`E;@c_hv*@CO*2%^1e94e6K4kbj0Ii6u(@5h4MEMN+9o(Jy%9+ zkAvO=?AC{VpkrUWq*8z5-1agyZL8IU;dnBk!DnvsR|xG4qRq<6`c;-Tw>iGbX=0oE zfzpPb>n@iJ5s$8RX*N&lDm=0@$;!;UGv|&v*oKQaI1dW7O-@aj2W(d=L_NE5d{Vx; ztW+YxFQ`?wEMu#o=&IFm&~|3#MA`HqMjakX*4E-G>rE``%=KMs!~DH{t8( z$Wc$J36K%GD=Ep#Tdis~zcf9TtCg`P<0}TmJ=2_=PLo=lFM@}iKW|Tvl9W^nTxPSf zcbO;U-411J;r#+Y@fY94BfUUmyiX8&e$C3$ z@y3hFFsY2>-jOVWv<6No_bW8hq`X-2&Z^pHw4SEn;sODiVN)DHp}nWwFY8a{l<^tu z>kDk}pANq9=Y=A1vXIj+&^uom;w=!}+d=MC{hZxb_;Ny!-`jxFVz$j4&I+8gA)Oy zBU=u!ao`vH($M+q96DCmR)ei978Qci_yQQb}li zk?mB@dsQCaqmPNaE?Q21j2}ig8z2I0h@W#{hWVuR@<@5+Pk&$O|78iJp>QHv#&z$jtM#i_ zo8w|~Q|LhGOlBL6{8yzrmaUQW7o2aL`)y7DTFa5f+_zog)I`0VD*Lzmq{`TMcY(VTxd!4Jlji5MF(}NPCXRaErVa$+*6~;G z-i3JLk2Mo12=m>AXt}}?)QZ9M=)moKJp1qup*mGmk$0M(t}Iy(_WHPCb>b+Xq{I$m z-Ki$P7iizn(=pcIQD#s$fKFfk^9O_7q~ZSUV19VmEh+`z za<6d0;(+h)VUZ6}clytUWoIux5>`x#c_Ict8H-sNi+akFU-xxfpd(LP$IlF31RPsA zQ8K}E`uF|?^dX;emiAoHH;0DAu}L2TG<<;%U0z zze`47)8Rx@16`(PtbjsfvBLq4;@VOwx9$-mrkn+t8nwTJS_i<;HU4Cxf;lyH zC9LS3ckB4ak`kUy!cl34qAsR(lmBpEpRYzf1b|ZhY)0(4>l3 z8CQc_KuysI@AtHfT=#gAA*L0D_EwQKbWQZ^{5csJ`86SznK_H5hx_{<%#orv^sdv( zUs}UacAxhW(|8(K@_Q`{3g}pS`vNG##b{%Xjo%-?y=r~FxxRi)#C!oRs~om zQ5cmzSM2&~e*E^<;B~F>2Qzb}PgEKTmzo-J{n-XG%SThXIGSD&7qh*zY;&oa?NihE)Tiqn0&LlV)2rH2tRO z=pD*l4-9TC^rgtS0ciOO4o@ETCfyI6_NQk*$t)kuN8GLclUeMUVP{i&N0i*Wa;rEs z)jo*{^TkT$q3r(?D8SYR;-(!j#e!L4dTO23X_+ZjzZFP;x$#mBk1xc&(uDV;LRz6dUY-&(a39haAe#~j6L8@ z%(s`WI?K<`AO59|ofD?FQJiBFCGIq_voxV0saXO}6UkEYY7EuX>a%4YLcC9}`Z%HHQvp`1oM{jU24ap~16tx1|JtcfpB+0E6$!Og?_K`*Z*I;Xd04<|=4bTK zJsgGdM|yyEyo2z$X9pp)Sc~G9<|x;lmBdg+ZhgboFfN&{U;jH`MqG_B-~RsrbGv8y z*TFk^J>GG&%Bz*3Si_UVn*I6=cUCs0(8$e-5e2~D;JPaIa$IUw-SJjq(cK4YLXRnK z1HuP6p-erGp3Lbr$b8#zbiHu^3c0`UZvA%a3Mf<%Nx&W6wD-SZAJKL?I#}RFhutBw z(EO^&RcbC6W`nd&#g~_Ftw`Xmi@4Xsj}>bwe|mQ`S<5KrMf5RE9zRT~vX%USfd&A5Ig_I${V*Rp9}itHTy&mIT1%r)FBUMMD%o?Fs! zPswS-9^25YQJ@bFI<4nAZHul6-9wj3+V8HKHdCU~17-K-T=&6AX|;#95Mm&u!o`pD z3iOKr1r?aD_+q;?%P7Eaay0Xi4rF$|Y;zy0kznC@gRz6B7xP8yB9TRkyy^_+?FxT@ z!<0{44h?mOhQI&&7ug;8J2)7KMsrZZV$_jBMEo*Et^gdPoS$UG(K9&rBm6_Ndy;^1 zTY#Gb2c$WrVP9-9^MvAtC%YQlUfH9!H$YrpP6~VQVp}#vi|w+)WvbOM32F}*%qasV z{30J5K5zTN$#{JfN&*7VBJwN9?>|aB-+k?PtA}fPw{NqOwfTLu5>yvKDDeVW;c8Ke zbd><}Uv@EjaPC*n!@+Z6r03y7Ef7lJWL`(dQYF?#*tq8Ja$MK2fkA|B%7E1$r{O{) zow2#JFF4YDl6!L_Z8OdViT|0PsdHOUS~aL54#j%3ohLLH}|3z&C}BxIBI=yuHLRL_{NIP_@n5=2QHgI??Y(hZLcsf0Re5< z={b@`?CXH%bZZnx=Ms=haJu`|^(Fm0>}F97K6(QS@i)!^)+poi?_C_MfXh`LvzRj6 z3~Wo46CD>65!#gw3txctlou^`Ge&8J^SYVeQe1I^sn1Tg9kT((^}jQbm6bid%28x@ zofDiOo;w%s^BE0um=5kk8`cZ-+QR zrc`&a>_)AwuBHXPv2IgOQ8Q7Y)Bn$o$zyQW9eXfv0m!G|Ecpe{7)Fu%I8_X=$B!PC z{NKl|()@pp`w_MNs`M){-l3J&Z30Nh_p*^MJmQkJb(*dq*B?le4{OWAml_)z+YxaA zp$=mr^#EzIi|d-2*1M6#TPj}M^4=L+;3PQ}Ihp3qYJ*b+>{;DkTYu?ft8cBZ_q*=@ zIzGG)_zMuW#7I*`%k44GyU_cuHdsvg<4$yGdgxcDg5E`unJV4DCEL+^nPn}@!v#<; z0YzZGKkTFWEqToOwB=Nvnoe?@g3X_5g|7;+9WTC+lT*8`2hLKcCQ7MQ63Qs#U3>H|=C)}6TF0=N`Cq^d z7ONqXv7*{_f4stuj7c)T63Fc@QY<9_0gtlKsinnCM?E^w{nz_(LwLELW%R&Hao?!o zFN$#<(0jVz{>P8po6=io8LtCH!BJb=LS6j}IP?_}Q4sj_Ddj*O9He-omh>uX&{8pa z(dUu7wxOXHVWj6QJ^O{={wgQrn`VXN$1e9}WFR?~hBtY6bL2xpFJ6E2c*nRqPQbvT zxMdRQDIraMV`P*E&V4_9x(f2SClU9&(k(-WSHtvipn6VGvW%Ng$IKbwUcVO5IlUG0 zMAoS;B%~QIn~R0@!+BtvjZ|4!q=UaO!b0IaLo4xvJirSVcm7=Gb5~k=qN5{EeX1@+ z3br&*o}kO`zy3D>bQ3eah7*~v47pPX2>CoSuX%&}MrT~t#s+9Tpx|)0Kth+ylPBPW zKGK8u>o5i!QGVT*I%WODXZ*pGk)5eR{S)>CK8By(_$w-OT9}9HK**bTE0~jMJp(F! z9h2p$Nwv8#tPXFb)04t?K&bHXsY?$`0zv}#V;R6l9cd!t^uwJsVBb5cxENY6ayj_M z=-AlyDi#ezAKCv0lGHkOZ1wf&9-3%(Ft>x}!yWj@b6%y`$JZCIr^R~(B^CBnzS!QJ zG;j4wG+{49tXFCtTDIji_-rr7^~q8UiN8T3#nv#kza$^oDCxP=y*X!2C=EN23{BVf zyNc`cy|Ud0|M3Bd-K5%aB;#JeN$&aX)G@btOd%pAaDIv|Ard!!R^4J4{{+u%l66A%Wuzh`E%vcx31jU0E|Ril z8T-Cx8T)R`eWu_2opaB*=iGD8x&PdI?hmJ9Yi4}EpY3^`_xttCKvlcro86nAul|5^ zr5YmS{cU_W>UU?G%9r?Xg{Lr=9Y}L|TP3fMUiiA4swHEUN{{11Yc9;SBZ-n7!mqNm zn=2j{%mn>~Tr!V1Z3cH_vs!{wMils2jV@KNl=&-XokqMhlK7QaXY7SVrd6#3*F#-k%rNuSvq{ zJ1azL6gx}u;e*?yp?HI}9ZpF}fx{I4MJeND4{pfRdhM+SG%2z-6{I_L%~_@fp50!1 zevh<^pfqoA-)sHvtMTG;bsoJuqKR>sZ47EoJf6#UPBeg z`pV++HKo*TX+D9L?arSfo9%z)oX5>0#xWqomYCk^NDc@O$sQgaW@?_bI`H2H({QgnVf#0>cNPFERDL%dyU%)h21xr5akXYys{gJZ6Pxp9k**0? zXcaTxy{oPDfC|OY-Q9E1=OB3qr!IKT&+}(HnD$=aJvSF9ahWDHK7D3qu!yo-YW z1Vpk438?M1ID=Ku_IN2aHa6{PK7;NutD%CO<`o%}Hy(ca%hK5-81#-k0KhjS@ z&vbSkvO?gDek?EA!L+uu<>TCAJI!bh*&$qo&{ah4B|~;HM6OHH0>ugK&wURrRk$p4 zmo=9;S+>WEc~9IGBu<%X zDRYMx+w@i&Rcx_D{BCJ6EPwjR@g_Thp8Vf$@SifBljDm_kVe_#LW#}4e%*96Le%=b zaHl}jeik@+id!ABsE|;gq!e9Q-*$C&fOmrDkIL8~3D-_ofn&v;1+R$p58)_$=YLK! zAP|{9*-W)A2wc1qN*qN zsdyPqru%@k%KA;dces=iz(d?CnP9ifwsH4{nT&_*e`lc$(zerq>71@;{Q3Kgg$ZAHYLQ|o^DZcKd?(aK{3&E_Xj!V z+(pV@`4_TUwl~I}jQ@y2I9WgDvO$?(s}3W64uqx^^_slF5_k)?_TC^N65d$kvPYTXUchv15arBS8 z`9tpI@tbO2^)9fwt-LN3y!1cZ9ufMLT=<={%Apr|usUl3KB(5;t-|saBi^2E zeFDi^vy~#VOMo_tHZwqA65`_GlxMDe5i2h%Ken?E?puu(ejXkP_q~=NV%8n^`S zXKxSuio)RK1RZ!gu!rFFw;1swBK$0ULm*Xv2P0ZGfUiI?8s6^1bGkEq9PRW>CH7`p zw=?+#Zm8JAi5C(_T}|4F8*-w$x=y-^ap=s2Qbh&rs`D-5U;qSpzb;yAr{=nYJKQJr z!^5OPhjktEo)!u!`A^U3&u;*y=jWO+`3|M4Z%8dz#eOI44qvJuk@Z$`WXSgeZdvdA z=o0Ge5^6DuL=`>n7#ca0=8yznN6pR}b}p(9=qOG&YHH9af}t;VukRi%TJ9L#fqg?L zOAj|{U-Y8D--*=bpE(H{3_%l+=ooQ#a*>ryYA6mSQ;!3TXoEHAf>J0Co zvbOZnWv(m2NSyJy;G3j$5Qp^_7@re%@hjsoqqjT4dN&-NOBft*a!rICUMg8DtG{^3 z{Vlw-+qAV7L{TzNzE+?Oz~lpti(4@EJxhKi?rmafigRCZDum{|@Y%h3@o$vIRJFnC zWG8OG$XGbb&Z;Gc#DdAr{|S#_3G2NP-Q%u&fPOfO!(R7Zb_j6_Dn zB+7z#H>crehOiYoc#tiQzW`!~vo$eI@e+u(wkY&i-z2rF8`D7+&Dr@l*1)x8j={X~ z{iCHn2J~+r!6a-;?=?BY>(tHQd-Nii$UCs^IH^fL_Bkx-D+M4)xigQk^D~8K@G%pD zE-7B_+uZeQ0A1(@4hb$%{+xq(;X9J(H*fvlz7;ucLt_1Ka3BFi6Riw06fhQ^K&P%%T)R||jQw_E!a&1QFnc(ScK=zwWz58l6cyDKSFDxQF zbwriq?nc(+D5)rQ+2tI5CES$990Xy; zOlq^P*<`k^n{gyFtPbg)>dRl^r)C#yEU!LLs&HD|`5i8uh4VB)2wfr*`SpuI&IX6c z5Lute+PU5ppDn2L9w0^aYEqdisUZs}9uw>Zs()}g97emq>Pzk?PJfGM7T^Yd(cH7N zr(G?ZtU&8|SeEAP?WJWYPC`bO_W)NC;CMnNZd?1rtb-@h*2kD~m48ylkBue1O+W!^ z@2len*pm_xkYML!u(fWfdX@%XyN9Tm91-RJ3P)#jPfg%U-(7XGY{lFucZQXA> zFFMX=E99Ba#`hXW|DB#rjTM4{C$Cfuy2tA^FA_#1wTYk*8;|$i4>90)js$=IG~*a` zjww-agqaoURHCyjEJD@*!ua?RM4usCQsOQnvnP79MxSHt!bQF2#=r|XfyZZyn5>HN zQOTy!qDqo|_J_+@MeI5Z!J0P=Ooo4>%|`ac3w1EsM|E&MYxS`KtA#?Al&Ry!5xbA< z!E=_`QW zn1=83v)1-Dmk8jAh3#t79El|ET5_nN#UE!$miZ}j8SLz)(XdjmS{U3dx=7ZOpghyZ zl~gp)0(L!JCuWwM7B8vGDWArt;n8B(*rK(*!75BQ;@!LwQdh}+k{z_U#IDd=L}mQ! ztc2~{1x+2AJ_Sg>l-A%d6 zGkWxAHGvX$tVFaSAh@Gm`?U#CFRe%6<;Jh^Y37CAl9GYgO;(ZUT>bQiZ>9T(P%C%q zxqp_E4V5}1lK?`-&)ODp!Z_}7@@Mlo687kEvI_70Heg8f_9o^3A5PVlS@p$3k z0pXyDZr5{h=Ye@RYG!fQsA3x&3cD`W`asC3s_O3TEwQ_|a5YUWi}`H2VGH2h2Era7 zD+$#Tq)hFu+4w1$#x$LBd6#pN^U?`(fXB+zAy!%}Yz<~uf!P(@6|uVq1sLyVw5;OK z7q-}@mrvE6nq$604Zqz#`w7AWK($6fl&gh&JKgKywLcFVL zpFK~|7S-(q+%kTMusm$U%pAG4EsGPhWb`h5&XGO+mnFy*KQvrIOtfB~X^Ruz+205E zu!a#9nP7||{`_ei8iXkwycrdinW;1^?)>vTy8!-E$Fxb+i}BC?&PvS>L-f9Yg@ga` zmb0y4EE+xN>*H-4zgr2K$~A3I&wW4}9{kXp4=JPpbHq|@Z8^s2!+FWW))F53k)ZbW z_U)Ay;4VK6HWeWD>K8w^$A-8p3^X)1^KjPt{Tw8}1=hBNc{!Pfzkgq!wrq1>3V*=J zgCEwVe^bWMzE23<#5lFZN^UO&ahRFsWVYSlK@n>#x$$Apms53~}oTAvMV-A;)tqLs;YjKJ-<(%jrD( zRt5kUf6DeZtLC^F_+K00y(8*{G~oUrz%C7MV2BlHW!Dl#zYiE>a+c95NK%T3dEe54 zjL7Ljjs1DOu>n}Lff75$vpYzG?fIHp{g*ST%IfNpAd&KcMNHXw=HqvAZN{;YDvxxZ zuON1UQ@=F_H5cLEdRPbsJ#9u)DAzF5``;kqQB7j4XkqOIYR}`qzp3t7+i%&`eXlaqUL;w>}}uc7FXei1|w9RfbCcl6~)nhR*0Y#7$ z=FLh^zXpD#ZRuWqy+Y-(yTT?hwx5~a8tztO%N1Mz9jAV1P)(f88g#`JpAq>RpcFoC zeGl-lA>g@pZu`YhuazLGV?%@-W~FoHo;g)SdytoM8Qe4p5$tIR8Q`}RGTzul=$jS4iZKwO`kpP8RnG<-rw zK@I+!4SZm=9h$00Y@FA?uq<(8(9#|5gbLdKTprF<_^jAG0|b3C^mpz^UR8+)ZTQ1M zn?g9o720+vFX1Fy@e%FrYwPRnv649|H4o3UkuD)#&5>Pab$tl9eoFV1J1do)jsIP) zShHK4I94?5?QoFUCA4q)UFgRC1?G}xk#@H~QTT5_un1hYnuD#z3S_Isp+TC<-Nvn@ zuwk=p0+QGc9=n#7CQNbpxnQQFY1vbby8#JaE@1WIW)^Ci0UeWST(+r!VkkRuU`kr; zY|-u@JG1aOMaM9#uD+X*8XDvmlV64mrn$B+p?moa4jN!@8z{C~`p2xtjPrcE zKxKdI@<_GRh)0eAXag|J`Iph6cE1Q|ug}oYg+2(CG%on|Q-%Q>^6I{`HqYdiH(#=H{53po3! zn2q|>7-XLPNIn5%h#04^rFyi;aZS(p&W9B>`AK8lzq1ThyW+i6mmUj!DUXZM(bxYz z`6F;T{3B9Y-STLe+jmQ_D&oGemihVl z0cRW23iM|!^yK9)=X4wr^??k}&%2}w^1y+Xt)+no6Gu1=p-ZfDb~5vee#4r>>@Cnf zI;xh|bbl#Dn_ug=++LF|t6$w3N%6QeAKujN=IXlr;7gjm5sS&W`qzyO4Ud;_O)=uL z(;0My<{`Gi?BWp>!dIzeWv3-gkNbU$a8X>B%>1{&El`)Kpm7E=iiNZ}Z1fk>pVmEO zZFW9yS56nnU5}fCF^Kb5S^}_8l1!=owk{+WJ|v{?@s%9S&mQYeOiUaDQXIgc3=6h; zt<Y`aUlEW0&JskL{U;@43eFMIQkFtzbZ-;KNObIEYGD+Vl+j~2=e!@-9K=fs+w z#mSn(gVfH$sl!i>5=CAb^3&A>>n`GXQ!#K;0`cet{y@Gc+HSCr_JkeQH5yJq$ePud z4n@Ikf=OjeZs$4hWukA^ge2hoD&$h)qtOgxm7gkq&|EE0mYRKN-hfyRGeAGNCvPf~u^6 zlBK>Z%LJJ(X;Na#>u=Lk6SA;RoOXBf9pyZ!{M6HujtR$TAC_etGCHZM+P_s|k>!iz z2lM6FxH%YL1AtT5%`UIQq*XK0Yr@AZT^Wt3^P~3oEog>(;`?~zWUdS?n^fo5sy*J( zGLXQ!&wqJ4;EYY<%2&EtGgQK@1N2S5xZLNN3ORKdd zE{guPPud;1K1YSY7QYJgzHldn(6c@>EoFO~hin8TzDQz`gVSbj)*S(gv{ymIeZ$NP zRMTiLPh9%ft=vozYswv^)Xuf6#D(zvJKl*7hGueL5#isr-UU!F`~=P@&eoLO5FfdB zeFKl~qpGN0TB^C>%-P~42^B4xT0sZOz6+@AQT4;Ta%g@~ZBpIR+o%|N*p^n0BtMG1lT|6CiZ!2Qx@~mb_3dLulJ&zW^pqwf!cexA7ba^>`X&VE8@y~(kyK3 z`e>2)uFO%$Be=NO_v%!6vKpABu8vJlZ{j-NelqH&-~t$ zWpasY7Gzqi5B>BG#SN-ge^(g*$CgX(H2YmRwqa4CCv z_b7=7ox<9|Y#XW}-pyiVm7#xjCt2$AY?qp<=6(di$-%{ib_I%D$@{SX^cUH^o?p72 zI_hI$ITq>2X&m!1iQs&a>vJ4o1x)bPLfVvarzkFpiXz*q$)|gZs^}u?6+^FZ5oR@x z6ba$^7Zwj6#sZKbv(>@Xm54{Dg6W*?{CO7Ok?(w$Fjolqle2QJT{5*t$q*J8T)8h` z5&Bj!jTJq(?NOipEStzC_Dk1L?PXr09KNju6C7r|KL$*EY#2K*?Wlme^b|5&UqV>!FhXv#Fb%uF^_9v6cND%PBb|zkYTbOsk8!fHIrP!;j-du{Po9S z;FsSq_<8<)_1-xiHXrDFdsWtVdc=CR{1yMfBA1It*9k>{4qR(EKKcPZ$=O;85g zd)P>BFJW*dZI2Le2FdfLzrS0(+u!E6d^N3`fGn5-_VJ~;(w5SQ6F%Svz z9D0X`3@JAj_Scj*6MSHJdQN^@rL)b9;1%b!L2cu!AQp5P#$C?cuCb zwR2El-5godazrGQqE8+2Dg~on+k-ig#dci-E1kfgGS<^e-y8Y>>uCkD-F!kMzTiCbV}U<-uhY z_~l#a;rdx+d)2lWTqc%4cGdi7JIR_ZDX5>N(*0{(PLY)y@>gxNLKr+U1L`bH6l-KFQ;32e2c=g3sYTrO#J-f41_sxRrY$6#!2+T&!7x zmsXa!{M{-M@_*2Pe$&JUO9v6&xLgCr{`Io($-7}bM>Z)jMr zgA>*BOJ`Ds{tfS-i*o$lQx6aSaru9LsZox(&i8rQY% z>%MFecIrKnZhyJYPa}S-Jd0hHjMpr_a=WO=CL7k(dwZ0StcyJ~I;GwGbL^SD< z2js$Ey?3+f3{>qEXtqqfm^RM79FZk)i@0Os$-!rB^yK=F2BR7lCL=<)z$G3URk^8| z8A&1YnPE?+60(ts<=Vol3B02K*aZoTalEi4!Gwu;;d4R)abzeIMIfjd1q)|GoQSL; zj@O4u3n7IO1(2fetjIy6PQQ4X@D&(KmD-#cb8=oowWJs zpFw3;Uo(7=U2r&9qr(z= ztlVdECYQnds_NO^Ov~-P%_E@cSRO`^0Hx4*0@~_4S~d#~3K}7dCDWjb-=CLCQ9<{> zUIvi*5=~7y4zaD~98}asW8&d!MIb!U+6~>lfpvhy$Q{Wc&})GM4y3w1K8FBkLP9=$ zexn~gi=O*(B-e}7{I5Klao4Kyxb-8I50wllQMvO~xstf8>h5$6^UkRZ_W1bz$B%7# zuuUyD+v2X|HH5=5)@H}*RY~mPSbp=ni_fUqENm^1QcFmdR{R`Lr1qDqGY(3iA_Zqd zNHBlvJM;ax#zj+gn&(Go97sZqDh~@Y*}51UPsrw9d(S#q)|&mTSZMCUZ7?<+E!3Sq z`u_cU9$giw_^%xdbm`CD#M-GZ{IFTBCg*DM$z=R~{O)CJ>BV!23D}kv;Y?k?2%S;k z4hQm>_0FG*!2IY(t^5h+(STua<<$2IovbswF`*n1<)k=O+ed#rSVXPH;KIK;YJo9? zlW-xS3zj-7^Zaa3Jaw@G#Z`m=RLj)`qFaE`DpT)Ii*fut7i9y9Gea8fBZvYwSiqs2 zf>}w098;$Av*U=|r?*}mGyRx87nK(8{`D7ih3_#0`Sk28YU8gLtQm+zay)|kk0$VW zFbO?}rPeAB9t4&x96{iCMMamV9~VqcGLrg2`O-oY#q{8y59dT2Z5MjRvkcyfQqf{G z>I?iWX#n+Kd2kRLAa=b(6Zm@ZQ1<*Yo(%b(nd}<~t<1W*x|FG~<d8)jE? z9Rva*?Zu-~@K|%NKKeceQvMn4z1H6mTWv=Ir+kB-iqc&DOn&9_a3S?2cm8|%Xc2|W z%Q5#|7_~2*GF32B^JRMO@#IV~^L^`gJaP1-;e7rEX+q@f`Ua$qpEH#i`yoUraNbE^ zF*)zuxL)8Bj;wcQM`+u{$@& z{u62fQ;c*s|EuHVe|4OMVetQT$4L@AriC-bVJ85x_nvwcTWDr67wcoMOt%pCoA0a_L!YV|u#+`18F z$j5)<#=Fp(%|U*f_ISyxsB=9j8SXalCn9P+J|XILD#i=mjsX&q-W5(9s^YL z3xKJ3IX`rzR7MW6k4bfP-Q9y=f~#^{^)uwtS*cZ$tI7>*_vti z0wH61it&lJ>!^dnObyKJ)YMS6_x{I^>fki7WR3)D%KP^lti3r2w|#C12_@HQgH~s- z*oK3p=SOdEdw>69zR20BJ1xMudKVfR_~D_lG8FT>kWb{SQ%e@HHyYl6Y(|S)TVe;2 z&aAg=`@lZO!C@lm>I$W!iHW3^_bod+=&Neyp^JraIJl@0)-Q*>i@v_BtnwCrxp=y> zG%p|k%$1X1pq5uO!Yw5}&uV(|(e^@Zp>$<28?QAFiK8-q{v6Ah8Svnyr0hT!G-m!- zX{S9cE31rG+B8(@l9O+vn^2yc_^wP>4xHmmHBnVVLnDgwpY%ulWQ5Gcz_OK%}ZChFd3wffg>^cu>X`XQ9>NZ)X<=CDahZu6IE$UE9*b9k;QDId@i% zKi{TAn+NqSxawPDoqu-|eY~*sH7*!I&BTqBd7F93I}cLZ4$0sH28_!{l}#gn6mFf) zwm_r{zSyoeC}J0IECfNnGNg}&A+fVtY(89JYxllDo{uhuvhcSa;d=h%E_ zYNpwx2aUnzY+b1#rU`vTYjei!b+d718{Mky&p>ZY=EB% z=>h1Z32$I(Lk<*{WIx#nQmZ4(?au`9n7J|`cX2cQ6s~1t^v91k+R}%}pX)*d@*__L z(}2|TbF|!I2m>uef#Waw$_Jz$*>Qu-?V}Zzt_lkN7pQy=582s?B4f%bRdMtj;FJXi zOB4S#H4V!x=sLhaF+TO1k!3Hc<1;K|CwCc{J$b)<)|v&&8SF8B7bqx2)!QmPw?9n$ zR0WF`Q!!2}wTm>R?*;}QAN49)+!5G(n(+|q);@*}6S zGUQkS+6Z%+Ux!|!Fhq=$`C}O0=KDPtM1gZy$`?!Oxfw0h_e>k<~a>oeSaN< z3*gSotOQ-u2dBOEZE3=H@`g9_1=Ta+|9uf+9NXBS46oW4lPNc=Yrttc`*h=YAYg^C zbxGo-7FR8>P*l(&j6jV|JyGT_xwEJC3LkVl@ZKYf1kn#4h-J)KZicxwy?8K{2LzN* z(;=@=!A__4Kvf(CUf}{utpq}s6=@#|f$kDOqS$Gl{~%mhsb3UC8u;AdTT7xBpc$Kc zsOp0b2Mxesxwyo{2!Vr2xAWhyQ{doyC|%5A8bg=kmUM+bXChNwV|A3EmB~(~na~US zZiF06uVX|Xx`XApcvv04OF7q7Qd5Jv3rjB}BO{nZ2eYe^-lpX#NOv9_t?I-aa$b~@ zx+n$s3@LCTf#VF8L>g9M469y_y=j$d9H%a!RJOxR6XpswTor1Y_>r;#o7t@&Z*~H| zq+JsqD`RIyM@MIr>pktYH>z;%O%fFE{pLlw>@-&F#7$kLO=I1VtYc9#^*os89mdY_ zq}Mrf1==A#Yt;|0nwr+u6wo*{Zu+Zv)pKLf4(MIg=4n5EfZ?jM*oabw{uDIeK#qz$ zY;bgPJk8knfxW-jxxnXbSXda`d#)>2xZ{u6_Ea_izyXX~p)5jMs8LjAW1T`qcs4lJ z)Dpz|tK~ps+h|<14^WVRH88AIWE&CNpbwsyH!vx{TQxmBeTJEthvfr+pQ`r{16Zhg zuOnLW_qi_BAvpE9&E>V>q;vCQpTu`+8kd7(lH{M%4y#)bgp4&wC8T8ybOJe zC~|yao`r=)Cs_eF3m^WDC}~=N{e(#r6?4lDqp~bBQ0g)5xL9QKZ-%~baOg09qS^hB z)pKi+`^F7=im$My%Bz_}G2A%lOd71bL>oeaLtd|-VCRVwC&EKjj&)~el|79+QfpX& zq`q|WW>Tu4hi;Mb;r7?b;+qIW?imOX!NCOeX>}i;Whw8f%1R2E(k~I;2FN2iS0@#Z z$^e@a$=Ffs-Kgq!;i-j%f})YIP6OQ4)8u(>?l|zayII%?S_r1SdhOp$gH4O=0?-?r zJFk})YH)r6h{EvcE+{CL^V#0dIvEy;&OFOcD&guT_%!WqZJ~M%`644tl(?K&Zn>q; ze0HWrI4xCQe|NL?T}0EI5iTjICgGZnlsq(GHy!^mLZMIp=GQNKV6=T;7mEc0H9dv2 zG_lR-l7WB8qe>4@KT-FV1GpzDN<6#U`J z+`rr!Q&Q^n*X+@wB_5meArx+h-t_TOEWjr3V zd=3t)e>LaXPP1O)6?UHV-vstx#Qn=~2!l_0e2kcNd$R$7nBb=6wDw~a7?uRmsmC99 z7Pmd7)Dx-+U{ZvCd$7h=sAQqwf>Oc& zj;T08tJGt&seaM6VFMc9Lc((+-8XJyxg@c$Fy$cAvOU(gz(c!D+Ne7WFpf3O%`+)K z?oG!wNAlwbD-`Q>+X1|@JW?bVO4^w<&P#Y)<`)GeR4$jwLU&eXRGD-0H|RgeZFo4* zaAL3oq>bO$$xO3{MUUnkJ=>A=%10Z9h2`c;ZAe_0o(6x&4XQd=b{`=}@!hFPFJ6$l zx`rktCH)IS;>#cm@n3{ThYWhAZqCca?#DZ`pG+lhONd?P0+cb2;h4Vi2J^Z!4H74Q z>(-4#7dBm8T^MkcLqlOIUH#tV~vH9bDK4Ul2qD3@nHw-fz5z ztsl&2a60*0LK;{>@a**gv|Ajv@6*4j_TJAqSVj3=0P`oREV-@RJKy!ym%< zvQN95AmPcy#WgTeJx|rkNm`NoGhevgV=}!L{LDjZ6OKCI2+wzl{I$3F8faRa{u=dlJz79AV z>~>3m5Ja{`PWy1EhsYN0ajh-P(E)k@!)0nWgW9_wZ4JGNhnFoIR0!b(Z?z$5+G@2)4Ky>W6iD`o#*8TnN9zAn^GBn}b93&>tp`nm{5QlWbDFU|J TUxhA`F3AIV)qB~prhfkoZW0OP literal 0 HcmV?d00001 diff --git a/CHANGELOG.md b/CHANGELOG.md index e2e2ab5..2aff8fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## 0.2.0 (draft, not released) + +Portfolio and install lessons (R-023..R-035, R-038..R-040; evidence in the project's 2026-10-08 scan and install records): + +- Gate: G0 release-lane-free (one release in flight; expected-main-SHA refusal); fresh clone outside synced folders; G8b executable-bit drift; G8c tag-kind consistency; G11 out-of-repo touch points (`check_versions.py --extra-root`, e.g. a wiki clone); G12 lightweight visual check before public pushes. +- Publish: connector/API pushes can alter bytes — compare pushed blob SHAs with `git hash-object` before the PR; never rename a branch with an open PR (replacement-PR procedure); `--force-with-lease` scope; delete the merged release branch with a recovery SHA after publishing. +- Host matrix: install channels per host (they drift independently; read installed files, never catalog caches); Microsoft Copilot split into Agent Builder vs chat attachment with the ZIP-root question documented open (W-015); Opal/Manus/Copilot sandboxes are review-only — no git work there. +- Record: install procedure (backup with manifest, file-for-file compare incl. zero extra files); `Channel` column in HOST_INSTALL_LOG; new `templates/HOST_INSTALL_CHECK_PROMPT.md` and `templates/NEXT_AGENT_RELEASE_PROMPT.md`. +- Loaded line now names the SKILL.md path, so checks can spot shadow copies. +- Repo setup: auto-delete head branches and social-preview/Website fields as defaults; `safe.directory` note for tool-owned clones. + ## 0.1.0 (draft, not released) - First version: modes Setup, Prepare, Gate, Publish, Verify, Record. diff --git a/HOST_INSTALL_LOG.md b/HOST_INSTALL_LOG.md index 40df2d0..8d54a07 100644 --- a/HOST_INSTALL_LOG.md +++ b/HOST_INSTALL_LOG.md @@ -5,3 +5,7 @@ Evidence: `observed` (agent saw it), `user-reported`, or a path to a screenshot | Date (TZ) | Version | Asset (sha256 first 12) | Host / account | Action | Result | Displayed | Evidence | By | |---|---|---|---|---|---|---|---|---| +| 2026-10-08 13:xx-14:22 EDT | 0.1.0 candidate | github-release-0.1.0-UNIVERSAL-skill.zip (7958b5b64ea5) | Codex desktop / local user skills | Install, fresh-session load and routing smoke test | loaded-smoke-pass; install accepted and 22/22 files matched; two earlier subprocess attempts hit an execution-helper setup error, but the next Codex turn loaded v0.1.0 and correctly routed standalone Codex to Universal | `Loaded GitHub Release v0.1.0` | observed; current chat T003 plus failed subprocess sessions 01a11cb2-4292-78d3-8de0-ea32a8175ed4 and 01a11cb2-e502-7e31-a498-d11fdef07400 | Codex session 185e6066-cbb8-4abd-9d1a-394bd0b4fcb5 | +| 2026-10-08 14:xx-14:28 EDT | 0.1.0 candidate | github-release-0.1.0-claude-code-plugin.zip (2d0bf67303c3) | Claude Code 2.1.223 / local user plugin | Install, enable, validate and routing smoke test | loaded-smoke-pass; install accepted, enabled, 26/26 cached files matched, marketplace validation passed, and authenticated routing smoke passed after an earlier expired-OAuth attempt | `Loaded GitHub Release v0.1.0`; selected `-claude-code-plugin` | observed; local CLI output, session T005 | Codex session 185e6066-cbb8-4abd-9d1a-394bd0b4fcb5 | +| 2026-10-08 ~16:2x EDT | 0.1.0 (published) | github-release-0.1.0-claude-code-plugin.zip (f7d0f6bfed75) | Claude Code / local user plugin via marketplace | Reinstall from published release asset (hash-verified vs SHA256SUMS), routing smoke test | loaded-smoke-pass; supersedes candidate-hash row above | `Loaded GitHub Release v0.1.0`; selected `-claude-code-plugin` | observed; CLI output, Claude session 2e2fbf7d | Claude session 2e2fbf7d-1c2d-47a7-82fd-16374ceac5f0 | +| 2026-10-08 ~16:2x EDT | 0.1.0 (published) | github-release-0.1.0-UNIVERSAL-skill.zip (cfffc4ea2d18) | Codex desktop / local user skills | Reinstall from published release asset (hash-verified vs SHA256SUMS) | accepted; 22/22 installed files match archive; behavioral smoke not run (needs a Codex session); supersedes candidate-hash row above | none shown | observed; file-hash comparison, Claude session 2e2fbf7d | Claude session 2e2fbf7d-1c2d-47a7-82fd-16374ceac5f0 | diff --git a/README.md b/README.md index f16df41..8aa0d72 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,17 @@ An Agent Skill that teaches AI agents to set up skill repositories and release t | Gemini Apps | `…-gemini-apps-only.zip` | | Opal | `…-opal-only.zip` | +## The flow it enforces + +```mermaid +flowchart LR + P[Prepare\ncandidate + checks] --> G[Gate\nG0-G12, read-only] + G -->|owner approves tag| T[Tag pushed\nworkflow builds DRAFT] + T --> V[Verify draft\nbytes == candidate] + V -->|owner approves publish| R[Published\nimmutable] + R --> H[Host installs\nlogged per channel] +``` + ## What it enforces - Creating a repository, pushing, tagging, publishing, changing settings and installing into hosts are separate owner approvals. diff --git a/packaging/INSTALL-microsoft-copilot.md b/packaging/INSTALL-microsoft-copilot.md index 2f55cbc..c0497cc 100644 --- a/packaging/INSTALL-microsoft-copilot.md +++ b/packaging/INSTALL-microsoft-copilot.md @@ -6,4 +6,4 @@ This package has no PowerShell files. The Python helpers need only the standard A successful upload proves only package acceptance. Test with a read-only request such as "Is this repository ready to tag?". -Expected loaded version: **GitHub Release v0.1.0**. +Expected loaded version: **GitHub Release v0.2.0**. diff --git a/packaging/INSTALL-universal.md b/packaging/INSTALL-universal.md index ebf9d27..464251c 100644 --- a/packaging/INSTALL-universal.md +++ b/packaging/INSTALL-universal.md @@ -25,7 +25,7 @@ Point the system prompt or context loader at `github-release/SKILL.md`. > Is my skill repository ready to tag v1.0.0? -A loaded skill states **Mode: Gate** first and reports **Loaded GitHub Release v0.1.0**. +A loaded skill states **Mode: Gate** first and reports **Loaded GitHub Release v0.2.0 (SKILL.md at )**. ## Requirements diff --git a/packaging/RELEASE_NOTES_v0.2.0.md b/packaging/RELEASE_NOTES_v0.2.0.md new file mode 100644 index 0000000..09acf99 --- /dev/null +++ b/packaging/RELEASE_NOTES_v0.2.0.md @@ -0,0 +1,12 @@ +# github-release 0.2.0 + +Hardening release from real multi-agent release history: a release-lane gate (one release in flight, expected-SHA refusal), connector byte-drift checks, fresh-clone and sandbox rules, per-host install channels with an install-check prompt template, an install procedure with backups and zero-extra-file verification, out-of-repo version checks (`--extra-root`), and a loaded line that names its own path. See CHANGELOG.md for the full list. + +Verify downloads with `sha256sum -c SHA256SUMS.txt`. + +## Publication boundary + +Installed host copies are updated separately; this release does not change any host by itself. + +--- +Built by [Jesse Raber](https://jesseraber.net) · [YouTube](https://www.youtube.com/@Jesse_Raber) diff --git a/packaging/check_versions.py b/packaging/check_versions.py index d87442c..e1d45d7 100644 --- a/packaging/check_versions.py +++ b/packaging/check_versions.py @@ -22,7 +22,7 @@ import re import sys -__version__ = '0.1.0' +__version__ = '0.2.0' SEMVER = re.compile(r'^\d+\.\d+\.\d+$') TEXT_SUFFIXES = {'.md', '.py', '.json', '.yml', '.yaml', '.txt', '.ps1', '.toml', '.cfg'} SKIP_DIRS = {'.git', '__pycache__', 'node_modules', 'dist'} @@ -75,17 +75,15 @@ def is_history(rel, patterns): return any(fnmatch.fnmatch(rel, pat) for pat in patterns) -def check_previous(repo, cfg, previous, results): - pat = re.compile(r'(?)**. State the mode in the first line. The path lets install checks detect shadow copies and stale channels. ## Choose the mode diff --git a/skills/github-release/references/host-matrix.md b/skills/github-release/references/host-matrix.md index a886c8d..005fff7 100644 --- a/skills/github-release/references/host-matrix.md +++ b/skills/github-release/references/host-matrix.md @@ -18,21 +18,34 @@ Reference: `multi-agent-folder-cleanup` v1.6.1. On 2026-10-08 the author rebuilt | `-opal-only` | Opal import | `SKILL.md` with only `name` + `description` frontmatter; top-level `references/*.md`; LF endings; **no folder entries** | everything else | observed: confirmed by import (repo CHANGELOG 1.5.0); per-file size rejection above ~48–79 KB observed 2026-10-03, approximate | 2026-10-06 | | `-project-rules-optional` | add-on, any host | only if the skill ships optional add-on material | — | repo | 2026-10-06 | +## Install channels per host + +A host can have several install channels that drift to different versions independently (observed on Codex three times: standalone dir vs Personal Plugin at different versions; the standalone dir vanished twice between turns). Rules: record the **Channel** in every install-log row; a version check reads the **installed files** (`SKILL.md` metadata, helper `--version`), never a catalog cache — if a cache is cited, record its mtime. + +| Host | Channels | +|---|---| +| Claude | app skill upload (Settings → Capabilities → Skills); Claude Code plugin (`/plugin install` or marketplace) | +| Codex / ChatGPT | standalone skills dir (`~/.codex/skills/`); Personal Plugin (manual "Upload new version" in the ChatGPT UI — no API); desktop marketplace (cache `~/.codex/cache/remote_plugin_catalog/*.json` is stale evidence) | +| Antigravity | `~/.gemini/config/skills/` from the repository | +| Gemini Apps, Copilot, Grok, Opal | single upload channel each (UI) | + ## Host notes - **Claude app**: upload the Universal ZIP under Settings → Capabilities → Skills (repo-documented path). - **Claude Code**: plugin ZIP or the repository marketplace (`.claude-plugin/marketplace.json`). - **Codex / ChatGPT**: plugin via Plugin Creator; standalone skill via the Universal ZIP. -- **Microsoft Copilot**: the sandbox has no direct network; packaged scripts must not need it (repo). +- **Microsoft Copilot**: two distinct surfaces — **Agent Builder** (Configure → Skills, takes the agent ZIP) and **chat attachment** (observed 2026-10-07 rejecting the ZIP but accepting Markdown files attached directly). The documented Agent Builder layout is root-level `SKILL.md`, while this package nests it under `/`; whether Agent Builder requires the root layout is an **open question** (pending test, ai-prompting W-015) — do not change the package until that test runs. The sandbox has no direct network; packaged scripts must not need it (repo). - **Grok**: sandbox is POSIX; OneDrive/SharePoint hydration cannot be checked there (repo). - **Gemini Apps**: never rename or obfuscate code to pass the scan; omit and document. - **Opal**: references must be flat for the skill to work there; keep essential instructions in `SKILL.md` and top-level `references/`. - **Antigravity**: installs from the GitHub repository (repo-documented; untested here). - **Local models (Ollama, LM Studio, llama.cpp front-ends)**: point the system prompt or context loader at `SKILL.md`. - **Manus**: not in v1; no package rules recorded. +- **Sandboxed review hosts (Opal, Manus, Copilot)**: review-only for releases — never do git work there. Observed: an Opal workspace reset wiped a clone before it could push (2026-10-07). ## Open questions - Does Gemini accept `.yml` templates? (github-release omits them as a precaution.) - Is the Opal size limit per file or per package, and what is the exact threshold? - Which hosts load an `AGENTS.md` from a project folder? Not a packaging rule; tracked in the project. +- Does Copilot Agent Builder require root-level `SKILL.md` in the ZIP? (See Microsoft Copilot host note; pending W-015 test.) diff --git a/skills/github-release/references/publish.md b/skills/github-release/references/publish.md index 84cefc8..32dce6e 100644 --- a/skills/github-release/references/publish.md +++ b/skills/github-release/references/publish.md @@ -32,6 +32,8 @@ Observed 2026-10-08 (multi-agent-folder-cleanup v1.6.2 pilot): a cloud agent ses When a step is refused from the cloud, do not retry through another route; hand the owner the exact command and wait. The refusal changes where the step runs, never the approval for it. +Prefer `git push` from a real clone. If a connector/API push is the only route, it can **alter file bytes** (observed 2026-10-06: a connector decoded `\u` escapes in a pushed Python file into literal characters; caught only by hash comparison). After any non-git push, before opening the PR, compare every pushed blob with the local file: `gh api repos///contents/?ref= --jq .sha` must equal `git hash-object ` locally. + - Typical flow: feature PR (CI green) → merge → release PR with version bump (CI green) → merge → annotated tag on the merge commit. ```bash @@ -77,10 +79,12 @@ With immutable releases enabled in repository settings, assets and the tag are l - Download the published assets again into the project's local release folder and run `verify_release.py --expect-published --expect-immutable` against the published metadata (`gh api repos///releases/tags/v1.2.0`). - Published releases do not update any host. Each host install is a separate action and a separate install-log row ([verify-and-record.md](verify-and-record.md)). +- Delete the merged release branch (approval), recording its tip SHA for recovery; list any other remote branches whose tips are ancestors of `main` for a separate owner decision. Repositories with "automatically delete head branches" enabled (repo-setup default) skip the first step. - Update the project's records: tracker, quick context, index. ## Never +- Renaming a branch that has an open PR — GitHub closes the PR and it cannot be reopened (observed 2026-10-08). A renumbered candidate gets a new branch and a replacement PR; close the old PR with a comment linking forward. `--force-with-lease` only on your own un-reviewed branch, never on `main`. - `gh release upload --clobber` or deleting and re-uploading a published asset. - `--generate-notes` as a substitute for the reviewed notes file. - Publishing from a session that cannot show the verify result. diff --git a/skills/github-release/references/release-gate.md b/skills/github-release/references/release-gate.md index 99d3776..1322068 100644 --- a/skills/github-release/references/release-gate.md +++ b/skills/github-release/references/release-gate.md @@ -26,6 +26,8 @@ Typical touch points: ## 2. Build the local candidate +Build from a **fresh clone of the canonical repository, outside any synced folder** (`%USERPROFILE%\dev\` or a temp dir, never inside OneDrive/SharePoint), cloned with `core.autocrlf=false`. Observed failures from clones inside synced project folders: `.git/index.lock` conflicts, read-only dirs that cannot be removed, stale copies that no longer match the remote (2026-10-06, folder-cleanup and cabinet-load-optimizer records). A project folder's copy of the repo is documentation, not a build source. + ```bash python packaging/build_packages.py ../candidate-1.2.0 --status "local candidate, not published" ``` @@ -40,7 +42,8 @@ Run in order. Stop at the first fail. | # | Check | How | |---|---|---| -| G1 | Working tree clean; candidate built from the exact commit to be tagged | `git status --porcelain` empty; record `git rev-parse HEAD` | +| G0 | Release lane is free: no other open release PR; no `v-*` branch by another writer; the expected `main` SHA (from the handoff prompt, when one exists) still equals the remote | `gh pr list --state open`; `git ls-remote --heads origin`; `git ls-remote origin main`. One release in flight per repo — observed collision 2026-10-08: two agents built "v1.6.2" from different bases; the loser's PR had to be renumbered | +| G1 | Working tree clean; candidate built from the exact commit to be tagged, in a fresh clone outside synced folders (section 2) | `git status --porcelain` empty; record `git rev-parse HEAD` | | G2 | Versions agree; no unintended stale versions | `check_versions.py` exit 0 | | G3 | Release notes file exists for this version | `check_versions.py` | | G4 | Workflow is draft-first and never clobbers | `check_versions.py --workflow .github/workflows/release.yml` | @@ -49,13 +52,18 @@ Run in order. Stop at the first fail. | G6 | Skill validators pass (frontmatter, name matches folder, description <= 1024 chars; OpenAI skill/plugin validators pinned by commit) | CI or local run | | G7 | Candidate built, every archive byte-verified, packaged scripts smoke-tested | builder output; no warnings left unexplained | | G8 | Line endings: `.md`/`.py` are LF in the repository (`.gitattributes`); no CRLF in the Opal package | builder asserts Opal; `git ls-files --eol` | +| G8b | No unintended executable bits (mode drift between OSes is real: 3 files went 100755 on Linux while Windows kept 100644, folder-cleanup v1.6.1) | `git ls-files -s \| grep ^100755` lists only intended scripts | +| G8c | Tag kind matches the repository's convention — mixed lightweight/annotated tags break `v^{}` peeling and citation | `git cat-file -t v` (expect `tag` for annotated) | | G9 | Each host package matches host-matrix.md rules for this release | compare package listing with Table A | | G10 | Tag does not exist yet locally or remotely | `git ls-remote --tags origin vX.Y.Z` empty | +| G11 | Out-of-repo version touch points: the wiki (`.wiki.git`), marketplace listings and host store descriptions name the new version or are explicitly listed as not-run | `check_versions.py --previous X.Y.Z --extra-root ../wiki-clone`; anything unreachable gets a `not-run` line, never a silent pass | +| G12 | Visual check before a public push: README renders (tables, any diagram), links and image alt text are current, and the social-preview image is not contradicted by this release's content. Update assets only when content changed | open the rendered README on the branch; repository settings → social preview (owner) | ## 4. Windows lessons (observed) - Clone with `core.autocrlf=false`; `git am` failed with `autocrlf=true`. The template `.gitattributes` forces LF for all text so Windows and Linux builds match. -- A local candidate is only comparable to the CI build if both use the same builder version; the builder pins ZIP metadata (`create_system`) so the OS does not change the bytes. Different zlib versions could still change compressed bytes; if a candidate differs only that way, compare member contents and record it. +- A local candidate is only comparable to the CI build if both use the same builder version; the builder pins ZIP metadata (`create_system`) so the OS does not change the bytes. Different zlib versions could still change compressed bytes; if a candidate differs only that way, compare member contents and record it. When the owner's machine and CI run different OSes and bytes still differ, the comparable candidate is the **CI artifact of the gated commit** (download it from the CI run) rather than the laptop build. +- If `git status` reports "dubious ownership" (clones inside tool-owned dirs such as `~/.codex/...`), use `git -c safe.directory=` for that clone only; never set `safe.directory=*`. - Child-process stdout is cp1252 on Windows; helpers must write UTF-8 safely. - Do not write shared text files with Windows PowerShell 5.1 (BOM/ANSI). Preserve a BOM only where a file already needs one. - Do not retype large files through chat tools; copy or apply patches. diff --git a/skills/github-release/references/repo-setup.md b/skills/github-release/references/repo-setup.md index e44523a..ca4eb19 100644 --- a/skills/github-release/references/repo-setup.md +++ b/skills/github-release/references/repo-setup.md @@ -29,6 +29,8 @@ Use in **Setup**. Draft everything locally first; creating the GitHub repository - `.gitattributes`: LF for `.md`, `.py`, `.json`, `.yml`; leave `.ps1` as the repository already has it. - Actions pinned by full commit SHA; external validators downloaded at pinned commits. - Branch protection on `main`: PR required, CI required. +- "Automatically delete head branches" ON (settings change, approval) — stale merged branches otherwise accumulate (9 observed on one repo before a manual tidy). +- Social-preview image (Settings → General, 1280×640) and repository Website field set before wide sharing; both are owner settings actions. - Releases: enable **immutable releases** in repository settings (owner approval; it is a settings change). - CI on Ubuntu, and on Windows when helpers are meant to run on Windows. - README opens with a "which ZIP do I download?" table generated from host-matrix.md. diff --git a/skills/github-release/references/verify-and-record.md b/skills/github-release/references/verify-and-record.md index 9647aab..bde4055 100644 --- a/skills/github-release/references/verify-and-record.md +++ b/skills/github-release/references/verify-and-record.md @@ -17,6 +17,13 @@ Exit 0 only when every requested check ran and passed. Quote its output in the r Spot-check content too: open one packaged `SKILL.md` per host package and confirm the version line. +## Install procedure (replacing an existing host copy) + +1. Back up the currently installed copy with a SHA-256 manifest and `sha256sum -c` the backup before touching anything. +2. Verify the new archive against the release `SHA256SUMS.txt`. +3. Replace, then compare the installed tree to the archive **file for file, including the count of extra files (must be 0)** — a hash-match of present files does not catch leftovers from the old version. +4. Confirm the loaded report line and its path in a fresh host session (`templates/HOST_INSTALL_CHECK_PROMPT.md` generates the read-only check to paste into the host); the path in the line is what exposes shadow copies in plugin caches and stale channels. + ## Record host installs Each skill repository keeps `HOST_INSTALL_LOG.md` (template in `templates/`). Append one row per attempt; never edit old rows except to add a `superseded` row that points forward. @@ -26,6 +33,7 @@ Each skill repository keeps `HOST_INSTALL_LOG.md` (template in `templates/`). Ap | Date | ISO date and time with timezone, when known | | Version / Asset | exact archive name; first 12 of its SHA-256 | | Host / account | e.g. Gemini Apps, personal; Copilot, tenant name | +| Channel | which install channel on that host (see host-matrix.md "Install channels per host") | | Action | upload, update, remove, smoke-test | | Result | `accepted`, `rejected`, `accepted-not-loaded`, `loaded-smoke-pass`, `loaded-smoke-fail`, `superseded` | | Displayed | the name/version the host shows, or `none shown` | diff --git a/skills/github-release/scripts/check_versions.py b/skills/github-release/scripts/check_versions.py index d87442c..e1d45d7 100644 --- a/skills/github-release/scripts/check_versions.py +++ b/skills/github-release/scripts/check_versions.py @@ -22,7 +22,7 @@ import re import sys -__version__ = '0.1.0' +__version__ = '0.2.0' SEMVER = re.compile(r'^\d+\.\d+\.\d+$') TEXT_SUFFIXES = {'.md', '.py', '.json', '.yml', '.yaml', '.txt', '.ps1', '.toml', '.cfg'} SKIP_DIRS = {'.git', '__pycache__', 'node_modules', 'dist'} @@ -75,17 +75,15 @@ def is_history(rel, patterns): return any(fnmatch.fnmatch(rel, pat) for pat in patterns) -def check_previous(repo, cfg, previous, results): - pat = re.compile(r'(? fields, paste into the host, import the report as evidence + +Read-only: the host session must change nothing. Import its report into the install log as `Evidence: user-reported` (or `observed` if you watched it run). A failed check is a log row too. + +```text +Read-only install check for the skill, v. Run these tests and report each as PASS/FAIL with what you saw. Change no files. + +T1 Arrival line: invoke the skill; quote its loaded report line exactly, including the SKILL.md path it names. +T2 Path check: does that path match the expected install location ? Name any OTHER copies of this skill you can see (plugin caches, second channels, older dirs). +T3 Version: quote the `metadata.version` line from the loaded SKILL.md file itself (not from a catalog or cache). +T4 Helpers: run each packaged helper with `--version` and quote the output (skip on hosts without a shell; report SKIPPED). +T5 File count: count files in the installed skill folder; expected for this package. +T6 Behavior smoke: answer from the skill only — . +Report: one line per test, then the loaded line verbatim. +``` + +Expected answers come from the release's host matrix and package listing; fill them before sending, and never mark a test PASS that the host did not actually run. diff --git a/skills/github-release/templates/HOST_INSTALL_LOG.md b/skills/github-release/templates/HOST_INSTALL_LOG.md index a5ccda6..64ebfbd 100644 --- a/skills/github-release/templates/HOST_INSTALL_LOG.md +++ b/skills/github-release/templates/HOST_INSTALL_LOG.md @@ -3,5 +3,5 @@ Append-only. One row per attempt. Never edit an old row; add a `superseded` row instead. Evidence: `observed` (agent saw it), `user-reported`, or a path to a screenshot / error text. -| Date (TZ) | Version | Asset (sha256 first 12) | Host / account | Action | Result | Displayed | Evidence | By | -|---|---|---|---|---|---|---|---|---| +| Date (TZ) | Version | Asset (sha256 first 12) | Host / account | Channel | Action | Result | Displayed | Evidence | By | +|---|---|---|---|---|---|---|---|---|---| diff --git a/skills/github-release/templates/NEXT_AGENT_RELEASE_PROMPT.md b/skills/github-release/templates/NEXT_AGENT_RELEASE_PROMPT.md new file mode 100644 index 0000000..826eb8b --- /dev/null +++ b/skills/github-release/templates/NEXT_AGENT_RELEASE_PROMPT.md @@ -0,0 +1,19 @@ +# Release handoff prompt — fill every field before sending + +Copy this into the next agent's chat to hand off a release. The receiving agent runs gate G0 against these values FIRST and refuses on any mismatch — a handoff without expected hashes has produced stale-base release PRs (observed); with them, drift was caught before any work (observed 2026-10-08). + +```text +Release task: build and gate v in /. One release lane: refuse if any other release PR or v*-branch is open. + +Expected state — verify before any work, stop on mismatch: +- main SHA: +- open release PRs: none (or: # is mine to continue) +- tag v: must not exist +- tracker/register file: sha256 + +Mode: Prepare + Gate only. Tag, draft, publish and settings are separate owner approvals (publish.md). +Candidate goes to: . +Stop points: after the gate report; before anything that changes GitHub. +``` + +Keep the filled copy with the session records; the receiving agent quotes which expected values it verified. diff --git a/tests/test_helpers.py b/tests/test_helpers.py index d18b38d..8319057 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -133,6 +133,21 @@ def test_release_list_json_rejected(self): self.assertEqual(r.returncode, 1) self.assertIn('ONE release object', r.stdout) + def test_extra_root_previous_scan(self): + with tempfile.TemporaryDirectory() as td: + wiki = Path(td) / 'wiki' + wiki.mkdir() + (wiki / 'Home.md').write_text('Current release: v0.0.1\n', encoding='utf-8') + v = json.loads((REPO / '.claude-plugin/plugin.json').read_text())['version'] + r = run(SKILL / 'scripts/check_versions.py', '--repo', REPO, '--version', v, + '--previous', '0.0.1', '--extra-root', wiki) + self.assertNotEqual(r.returncode, 0) + self.assertIn('wiki/Home.md', r.stdout) + r = run(SKILL / 'scripts/check_versions.py', '--repo', REPO, '--version', v, + '--previous', '0.0.1', '--extra-root', Path(td) / 'missing') + self.assertNotEqual(r.returncode, 0) + self.assertIn('extra root not found', r.stdout) + def test_helper_versions(self): for s in ('check_versions.py', 'verify_release.py'): r = run(SKILL / 'scripts' / s, '--version')