From 7646f29d366a0973c044582075de99945d50cd09 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 1 Jul 2026 01:11:17 +0000 Subject: [PATCH] chore: publish from main --- .../java-modernization-studio/.gitignore | 4 + .../java-modernization-studio/README.md | 92 + .../assets/preview.png | Bin 0 -> 282178 bytes .../java-modernization-studio/autopilot.mjs | 162 ++ .../java-modernization-studio/canvas.json | 29 + .../java-modernization-studio/catalog.mjs | 175 ++ .../java-modernization-studio/doctor.mjs | 412 ++++ .../java-modernization-studio/extension.mjs | 122 ++ .../java-modernization-studio/package.json | 23 + .../java-modernization-studio/prompts.mjs | 155 ++ .../java-modernization-studio/renderer.mjs | 1038 ++++++++++ extensions/java-modernization-studio/scan.mjs | 571 ++++++ .../java-modernization-studio/server.mjs | 334 ++++ .../test/cockpit.test.mjs | 1662 +++++++++++++++++ 14 files changed, 4779 insertions(+) create mode 100644 extensions/java-modernization-studio/.gitignore create mode 100644 extensions/java-modernization-studio/README.md create mode 100644 extensions/java-modernization-studio/assets/preview.png create mode 100644 extensions/java-modernization-studio/autopilot.mjs create mode 100644 extensions/java-modernization-studio/canvas.json create mode 100644 extensions/java-modernization-studio/catalog.mjs create mode 100644 extensions/java-modernization-studio/doctor.mjs create mode 100644 extensions/java-modernization-studio/extension.mjs create mode 100644 extensions/java-modernization-studio/package.json create mode 100644 extensions/java-modernization-studio/prompts.mjs create mode 100644 extensions/java-modernization-studio/renderer.mjs create mode 100644 extensions/java-modernization-studio/scan.mjs create mode 100644 extensions/java-modernization-studio/server.mjs create mode 100644 extensions/java-modernization-studio/test/cockpit.test.mjs diff --git a/extensions/java-modernization-studio/.gitignore b/extensions/java-modernization-studio/.gitignore new file mode 100644 index 00000000..46c7eb98 --- /dev/null +++ b/extensions/java-modernization-studio/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +*.log +.DS_Store +artifacts/ diff --git a/extensions/java-modernization-studio/README.md b/extensions/java-modernization-studio/README.md new file mode 100644 index 00000000..8677964a --- /dev/null +++ b/extensions/java-modernization-studio/README.md @@ -0,0 +1,92 @@ +# Java Modernization Studio + +An interactive GitHub Copilot **canvas** that turns the [GitHub Copilot App Modernization for Java](https://learn.microsoft.com/en-us/azure/developer/java/migration/migrate-github-copilot-app-modernization-for-java) CLI workflow into a visible, steerable dashboard — assess a legacy Java app, drive a prioritized remediation plan, run validation gates, and dispatch Microsoft predefined tasks, all grounded in the repo's real artifacts. + +The Copilot App Modernization for Java tooling stays the engine. This canvas is the cockpit on top of it: it reads what the workflow produces and turns each step into an agent-driving button. + +## What it does + +- **Overview** — at-a-glance modernization status (phase, % complete, finding counts) scanned from the repo. +- **Readiness (Environment Doctor)** — checks JDK, Maven (or `./mvnw`), Git, Docker, and Azure CLI on PATH and flags what's missing before you start. +- **Assessment** — renders structured findings from `.appmod/assessment.json` (stack summary, severity-ordered findings, strengths), each with a one-click action. +- **Plan & Progress** — renders `plan.md` / `progress.md` as live checklists (`- [ ]` / `- [x]`). +- **Validation** — runs the workflow's quality gates (CVE validation, test generation) before and after changes. +- **Tasks** — dispatches Microsoft predefined modernization tasks (managed identity for DB, secrets → Key Vault, message-broker → Service Bus, S3 → Blob, cache → Redis, Entra ID auth, and more) relevant to the detected stack. +- **Summary** — surfaces `summary.md` when the run is complete. +- **Autopilot** — an optional phase-ordered, hands-free loop that advances assessment → remediation → validation and updates the dashboard as the agent makes progress. + +Buttons don't execute logic in the canvas — they dispatch a grounded prompt to your Copilot agent (action kinds: `run_task`, `generate_plan`, `run_cve`, `generate_tests`, `fix_finding`). The agent does the work; the canvas reflects the result. + +## Prerequisites + +- **GitHub Copilot app** (the canvas host). +- **GitHub Copilot App Modernization for Java** tooling — the underlying workflow this canvas drives. +- **JDK 17+** and **Maven** (or a `./mvnw` wrapper) for the Java project you're modernizing. +- *Optional:* **Azure CLI** (`az`) for cloud-readiness and Azure migration tasks; **Docker** for container checks. + +## Install + +This is an in-repo canvas extension. Copy the `java-modernization-studio/` folder into one of: + +- `~/.copilot/extensions/` — **user scope** (just you), or +- `.github/extensions/` — **project scope** (shared with your repo's team). + +Then reload extensions (or restart the app) so Copilot discovers it. No build step is required — the Copilot CLI resolves `@github/copilot-sdk` automatically. + +## Usage + +Point the canvas at a Java repository and let the agent drive it: + +```text +Open the Java Modernization Studio canvas for /path/to/my-java-app and run a readiness check. +``` + +The canvas resolves the target repo from its `repoPath` input, falling back to the session's working directory. From there: + +1. **Readiness first** — resolve any missing JDK/Maven/Azure CLI the Doctor flags. +2. **Assess** — generate `.appmod/assessment.json` + a prioritized `plan.md` / `progress.md`. +3. **Remediate** — work findings in severity order (P0 first), using task buttons and "Help me fix this". +4. **Validate** — run the CVE and test-generation gates. +5. **Ship** — when the work is genuinely complete, write `summary.md`. + +### Suggested agent instructions + +```text +When a user modernizes a Java project with the Java Modernization Studio canvas: +1) Open the canvas pointed at the repo (repoPath) and run the Environment Doctor first. +2) Run an assessment; write findings to .appmod/assessment.json and a prioritized plan.md / progress.md. + Start plan.md, progress.md, and summary.md with the exact first line so the + canvas recognizes them as modernization artifacts. +3) Work findings in severity order (P0 first); run the validation gates (CVE scan, test generation) + before and after code changes. +4) Keep plan.md / progress.md updated as - [ ] / - [x] checklists. Only write summary.md when the + work is truly complete. +``` + +## How it stays grounded + +The canvas renders **real repo state**, never invented status: + +- Structured findings come from `.appmod/assessment.json`. +- Plan/progress/summary come from root `plan.md` / `progress.md` / `summary.md`. +- To avoid mistaking an unrelated repo's `plan.md` for modernization output, root markdown is trusted **only** when `.appmod/` exists **or** the file's first line is the provenance marker ``. +- Stack detection (build tool, Java version, framework, container) is parsed from `pom.xml` / Gradle / Dockerfile and drives which tasks are shown. + +## Agent-callable actions + +| Action | Description | +|---|---| +| `get_state` | Return the current modernization snapshot scanned from the repo (assessment, plan/progress, gates, tasks). | +| `refresh` | Re-scan the repo and push a fresh snapshot to the open canvas. | + +## Development + +The grounding/parsing logic is pure and unit-tested independently of the canvas runtime: + +```bash +node --test test/cockpit.test.mjs +``` + +## License + +MIT diff --git a/extensions/java-modernization-studio/assets/preview.png b/extensions/java-modernization-studio/assets/preview.png new file mode 100644 index 0000000000000000000000000000000000000000..7aac2e9262625af423cd54cd6ff2fe026f90e42c GIT binary patch literal 282178 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

&tw(EDBrF`xy8*#i;7 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

    >2@Vh#6uy5d1_r->-us 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�-5;ml;6-@*g)?* z8u;1Q38PXR&W&XuQtx$PEo`#(#$H@mBC|1t^lEkmBMZ;!h|TH26IHRsYG&*Xc#`Q{ zy?`nLq#zS!{R9p^z(ncN*>u*tqoP}w)CdF(O`+`WV%BN(R9@*iDQ)l#KdmpeSZ>pA^rQn`+Bk9jkDlqLLtGlCMQXOiYdx zp%KBVNMTX->+S88e=F6~)8ph|lX}d=8fpZ};1&SyM45n`kW!@gZ6rn;t0RgwPIgfO zJ#F4~&2O{{L0r&R@s%zaOmpd0PGtV~C9UX(MkY%bauuP>=@$3-CzFviEEFI3XEGk2b;67=oTO%>+g{QjS}ez0!5^l{}_ryeRERqmyn8YA}z6O==@s`zr5jy?)1&V4IUdbkQxEymbdU`WP2 zJv*bJt*trQjPj=B%NJo^C1Q;JKDoiZ0d;C}gJ;w%1UloB3j<)v_n%i>7-%jyA(Es= zo*(4|;19k^?W*bAZiX;(sNKr5+ZwEYPe^fPqHX%|W2k4Qr*(XKK3cfw>6b4Lsm-nE zs!JI3EyZU>ZF-h=s#yiKS!~gsc$qST>3zzewj~y1I)8sG-7X%n%>Ia)002q9@}b)_64cmzqKvVsCr5V9 zj@eu8;kMkhu75;u{Uo9Xh$a2IlA>VoMTG~bD%1<-9DYav!!6_n$jHcG*&F%io`0&n zaH#|1DFu=hTj&3rx(5$NHI3gm+=P!=FmLKUNo|eZ0owmE z-^|#?L8~%qEe#^L&(IftR5&?4&5qWD*Y<|MSJ-xCy=a9>uC24PT*9(9rupiGwBo~- z-D$w;#zwQ?ZtIyqoYPW^*q@_Z${)rD)NKrijup{neSO_b4fwInt7@lTb|r1Mq~PTP zK8GYHz)ugi)mZKA-zj4ES?IPl)#W&T;tkT6KHZ(YJDG9Z7}UpDcX4xZ*q&LNch0G) zt1FXODzCL?Wi4;IHQxRGYY;)LsHgA!QaZ)guHECyl7 z(FJN@kb^2N0x6IN!(n-Qbn#21c(JCN#MjAkc359uP+y;v)R>Z{yW8W-shrDvljlYb z#4CkuZ61?+@vWTPd-*SMn{mXvPV1|yum8DnK2bfaE0H|BI9k9?%bfHkRjDV6=7eob z8CLgWpuFu;DCSS<$+0Z_Q0Zw|Kk4XD7Rf!^J>aF(*n!}zREG5Y`9SM%&xF?cwwBN| zFHQN2RUckmDA%nmcoGFR7JYO2OzELp6t_0hWVjI&T@cT6eEzFmnUF1ae|^u3>3V@Q z@$UMhdXfC)%Z_jE7K?kycCKP~4u)G1zUn2qCHP9M9Ua$#vKkj_ECL{ihZGaP)^MX( zI+jtt*3Pl5OL5V1?jV<^U5fKAn8|5xZoW>nK$Wi9!Orf(KL_|A3}g>dz-+vphnZo# zt9OYuvqZf@UgOlX%mcp=GbVUg|pOI5D8bkr}<6;3F>{#vCB6eoJCp zWoo)sYO=3)DJ@jl%wY}KGb^lw?B;JvQd58W2FJ8@w59O77mEe27aC}4YO;S+fJFNM zmm$af1zA3mw(SxIZ6G7Ip3rNyJsMgfe~fT!7*-9IOzF+>YMwH7@vZp@ZQCDtp}(2^ zKjpsi^-ZuhznJ-$tZ<^2zC6FUa$EboyDu~KJE{(@tA!qFf>(YZ7J7hI?=)x0g=Y8k zOlzLM)ra8Nea%5z7k72vh>&XzzriilMOzKtNe>Fb6HA$LbElFD$6cM?($pUZ<_-%R zvysK^tCDli@Z2x_Z}?}%9S&?urMekw@Dn*rw3kDso{6+1Y}cF0BXBrbVVjy*lh;2F zcRvt6b(T=ZNgpVceYBf>oF|O6fBh^rDwk0}?r$tWGktRB>)u|G`ILTGcTRtXcWm>{ z${zKGC*9+@qGrN139LK4zU#HsR?{q*jt&PW@9cP1hE!_m21u@k=L^@3wP&Tf4>-3k zm|TY2<}4*A4Ue)mO%-O^?2I;R}h*lHl*Wb1YHn zZ=LCo!i`Qp-w^KTxuFpzS<&BJR~TciaDGhHgoo@*sUOf~ge#h+urpN}OnG^>O(iM< z$As=`);XXH2(^yjfQm2>2F>l~qM zR&xO_Yg!&{;p1yp=cfqH)H^c)4*Y12JcQexk%A&i+{VT~l-PU{$tc@(tNj-c!t^%l zNYaVR{L#FScs~Wn8g)4*)`pK?Dr1vgW8QUcp%|kn<{<^aDPdF;dk-H;CGV~=RobN? z26dP{5rtUp9SS3lYiDqD90bhRx7$1E4O}~Yh5Y!i@}?e7yUyd>q@|3%+sI0!KH;>U z3ZdHweS;0PjBsvbEjU>ca(nVax?pQ@ZRyqGVRJr1!7Q>p^m2Lx&y(wC#@dd1OTaJi zkT_)0D&ys*DHl~Z=x5wxZ?vSA>Uo?_&Rd6=xNAzc0y%Ul1v#C?r-yXq7DH!2&85zd zJC&EXb#eJHj3;2gvaIoWxO1vJ&LuWPzqQxz*~V^)DpaOf`vp>D*xss@Vf*!bVJ}L+ zUh*?h3yfRTlhQ4n*`lmQJ9B;!S8(AvNO0oBIWn^b9IxMKUmSlr9FRqd(yjrxmmqL4|#pCw3?|$Junc_L;2?B29I6?+lxu3p-4GL zhJfAf32T2%so@8fi?TKSmy~~$7-7vpUMvu?e5lpU4fS39k%(V4YisKr(Z>%H(AJ2! zv=5Y12X(Z<_>tLsuAEew#F|mvQ%NR`*FA2O6uz5P&BR@;LA60G!Qrmm?Vr1yuUs02 zyZgT_R+{N3va#uRTXn22%64tMp9i;Ry0z(m4_&!K4MX1_EzPFlp>y<)YK?M zWn)+Y$z+66>2)I09PLz!!R!p(wZnHRF%h%)Sy;aBg$&Q~>;l>czpEuwZ!!X@@O{G4 z3%-0Qeauta*ZWhKFEoW_J<4lX8O3}r^mM*eoVr<-awovg6CDJWuV?EzZrM|9iQ**tbq(JYVTq zzua^mMEo8t(uJ(wbjqFd*8RvZ+ft-L1KHB0ooiv^fF^13P3w^|1$V;f# z8+#AcAfpk{sm7!M2;K>TV`=PSlD3a<#A8BU+0DIMqwXaeGo}`-L&CwQ8zSdQuTL&b z$mE>L7%Pc8obB~%7eDzogl;XL>e!nkyd9)Ty^-yuL1Nv95R6f_r6{eG+^OzXvn|Ut zn3sz*STgpbWIJ;v%R$cViOLm9PzgIE`gLo~PpSzy(THBB>dtb65U`^>eL7ToU4brG z=Xr%DTc~{%Jw$Fl+dP}obai`igon$Zc{%JqdfvD1eQpb?MYCn^mCg`v3nGZ?)#+}m zo|mt%vtg&))b0LapT_a@Z|E-%xBi5ZSkKxo1{K&7#6yc+o!;5m*(p4ZL6pZ1tXZG7 z)%8oV0tb?LigiloX0)Os1ryorukRMJOR_)cycS&S$Zo6oRgt65sQ=Qz&eQhb_fkng zdTk=djp91iJx7+M7YTN{InnPJcr+WCoeBYwoPWRg28Kk&`l#Pk*=Fin>J!o+yK`&v zFSf)t>wR21>{d>}Gk&715t?_!BzHxg68~=eGd7v9q@FHeKNZ1~DdxsRm3GBwI=Q)S z%=@_0~U9(6|X84q_8D|XonpNqy1*s+QK)ctvW0%i&H|7xdos{>132xQvX}6kXoUN{{JhB&bHv_x}DU_3ATs$TRgl1SeTG$GEw!A#%qGpq@!|4+bk+j;* zq{sC}?3L2G;J)m`;ekqG+@060&Wb~4tGfMqKdL6m=oDQ&wC*}l*WQuaD+(exoJG@< z-S+4P4g||6K2a+zUb85G<*hi|J!6$7dK!ef>Ac7~iLNQfYyZBFJH`i}@u$MzPe zjqh+tyF)kB9niYF#yq~+9vLSrjcZj3hmr8&5^9S>4719929Vj5+r2jb1G?i2#lT=^ zMY8I@rBWncHu^pbyC7bl=j5k#+Hnd!ty276_QBXMT2 z{n48JWj%}iOE>rG%ZF8HRd(BlJEe2=LW+gzyhzeu?@@eH5tLz0h@D;`XfNN{;=FQ1 z^8&hL`g2`L>0r&eea)=deOV3-PSh-2UW7`yL)%MOQKnQLvT)opFwo?6(*awdy1h8l z7hv0~sip+cC{2}@iW!r|`WwCIZkM$qYK}LTvRda>wkG}ftR|z<8xdgOiz8eFkBBnn z;Zayn0A2d!>#OANtXFDRc`6g+H;~9Hv3aSPV81c2FKBZB$oVP`72@bxmO?@aiO?PT zyva&`iRcvx^dpl>sPpd3361h%Mj+@7JTGCLf4~U8VLqR7r&m|;isv9KNBd8FR~6QM z1LjDAgHk?B7be^kOXhF-mJ~R;?U8SjZ|xCi>(ys3cs|vF6x6yXJ6h;OI*`buNRUp8yUyIFpqCzs{_q&_s7Yek|1(O~O$Bfa!Bl6Frma?##Rg8&%@MCJ)dYGD{rR zxvfzXSGEer0<{eBkf(@wOMB~ktEpI%oLz2MKuRX36)?Y}v(V?5!` zAZT1+6kEs1#VS)pC1xJLaGdPhRO!D>l`WVyV?eQh7vzGnonVC%wm_WEdlRydy z37hTqMDj>XW*nV+IuXB9lstW$V6}Zt=x5ae9$wzzTRkSP3UjebfD>S%!u{v!`9uZx zC|z8thd%DuB584Tw)gZED=VUW*J5#~#J}2N#3QhPe3UbknWC?le)iY7vb!6rNmv;j zrS3>P=J(W*68+{RPXn#2y$W)5V^*W_f;Z8bX;u7SS!6`cYmPQXRu?s51d>v@=S6>91JVy5$A zbUz%UNqEYz_gsr1Hz6{TR@4om& zB~q0$Q)q)%Lzd}_Wym5*+*P4^ZkX&<0@}9TnR`J2ZMuj)8U2gXHHAeAri-Dy(Uyvq zrCk*mm?tpkochqV3tZ5J8_cr7#Hp%7$ZvQi>{3PeHZ)6QS00@|V!d@c-#v>As)KN# zRV1ENsC#;ATLjY;f=hG0-Z>+X)ce|Xr{+$##)g@nJOji^Q}P%e#Wd@AY#B98Ni(6Y zW7*Yu9P(O^K7o^3jOROP>sAIp0t^Ndyt(e3DqV5jndm<%E4Uq|T-Yo{5!BNswQq#zNDrFxl-G|Y{GGE6wFBKi2) zptq;zafn81N)|!N_eFQfwx&EG2O@-O2mHA*QR_+9J4#PydRe{=Cl4=KYeFp-a|rFR z+u7N9^p$d)(ZzYIS$B80$zTev%O*x+1zsG#yKyJluCKR0nkP38rPFTR2?82XW~_@O zXzm9$1~G2|Ry&*@t>47%C4Am^fQUt9R_L@A+dDT8`SDf{K)JxCadhwc_KMOC@rWk3 z!Fth4?K7op!c?t&r$D27ZC%|Tlgm$?ByIsxWfwByL-p0$-p{OxO{w?c^QnhmbHkF& z9c%elm>j5OSboy{4&+yrC&Omxq4~$dCFogJ6VO6Uh06??XsA40RG~~GuEe!HnX;j( zddAUAUu|rJssW+QMe&tnPzP%ZxtRQNX71{#bUafLja&MM&(w!J78c9U$28m*=*=v2 zB{qF$r5@OGUn1mfk`I)3N^(Zpk&&CG;@PesqD!TPIv*44x+WDRtW7MKnm@|3c)rrF zAg`D?<7`)s!C+F$U3g%d8z+w!GxsCX*+72)-R8XzMe^h%HV8nsyc!{(Q}EqL8IKd(N)A+}Y;GmRq;)xoPD|vxQESw4 zg!NhJ))#6tRe-$H<#^qCr|IKarUsLI5<9x_T17^07r&@(sohXZ!y;&(rIDrX1W!>W zeKyRog`=jn_U7>D%=7a2SxlN}B9GC?vDrQzgQkbT3Cn@}V`FlY>=4suB<<nLtAI#%r&1zaBCXOb-3*F~C?VY;-Q6`3(lB&)_fRvyz|7pmImh!o&+qc|Y&_-U69iXQ!wEh3*!uyc)fsbatjc@&?H+i~Ib(j(>iy z{r&S_|GqB$^Uwe3@XKF)`}EN1igOKB+&SWhX1t)c#?~uPdG>$T{{6C_emRAete4>` z#Xn<;yw_s{&1!REIdQv8%0N=whn8oxy$i6b_w!OET`1(t{u758)>-&o+f?-OHqJ43 zTI?&mT=6tbW^&0hjVJex$EPUPo&NV4&L|K+MC)@Aa!Qg1J$XDfmNzV8XDE*Q1mdxk z*xtJTzdLcVN`CrTEK7>q!0{wkQ>5C_qV4MLH^o`y&`26~FZ$2xIX{7CrdTcY;z)AP zeGN<~rvupu%=j?yB}4vW{{Os;BJLW(O0%j7aQ)Ojx=*Wey}9A&;#yHv)!hSwb$7kN zS!Lo8VKn+tQd;He;Q0E#gDC~8Q<rF1HuQ=lS6q;f$PiRbOV8}8mtMk_mXF0F zAwDtg$3IeWjMqPSs2ZkGMTJK{9d3T#Bfz*EN$!ZG8>ZZBo{|leph`AJv1SNVme!tsPmfpGfr|e9kZK70)-M zCP{XqhX3c{Z%y}NU^vYAsld4so%iQ)%;|D^QBq{gJe z;L51V%1ZsuMh_*)@$vERy^v{o78j8a6BUt=l%&AJgm+yVQCTeo0^ZA5FfR9r%&@IXsEn z+t&IuJp1W?#sE%i!YZEh%}*a>U7XX$n<*!!In0vrVOgCP`tgrgh(aZ9;W{HFo!Za5vAY!caoPs>`(l~8v4)g{y)rY&KvN{ zNiPh-0F&iEIy~nZ_Xt6YvMV<@9s6H2EhI-eM&mK4znCddK0JYe(c5Rl0G)q1xPEYJmEs>%vy9y) z>O@M)|4JDS7ud;~@FBp9QEvYVu)sDS9>M-y&qpL=0s$^Ray;-kfek?YfC zE&krgbq{30%|DnQJkVtuIcc?Crgu20mLr9CzWhBYjzd^g!8v1`JrQ~Z<-YkR_=9H7 zSD||Z@IQU$e_DNd%;&iDK1p8m-ym8;TNRy048&cv4nj+%k8VLO;)9i6Gvv_M&w!Et@FmPqPp6R>|@bpn?~z zhrIGcmL$lbUtE35xcB~f+d^;Y?(;cZiN}~)E{&mdPg&&};rro)n+ZnUGLz*j~F_{P9+LC`Luub zuSFI5_DLC%ld~AqgGau|0<^+P@@*bN%<@E;!Q}4^^zQM-80Tr&Q689B7TETY%-WKg zBGGDsZVGF?1mt>z>oh%2p7*gTzTo)%)&0JbQZAC++tOnjDK>q!@bl#xwBASAfWrn= z9Lda=Y385Scm89AEL8sS<@ccg=Uex`Y({sstN(dQ1L=PmFoxg! z{)6@x%Uot2{&V?_KYH+cVn9ax|KN42#mt#Oy*tZ{fY|4R54f=S_(z}y!$0#)O*=ck z2`DJ@1K(3OI!j8vzrKzmxeCt5oHKpvZ!92AGczp3=({^B!re;v&hL+yb3rez%f>8P z~;z(#le@ggN5dp=J`jJZg*B84{85*J5PIO@>@W`_a)z>eeaHqCEAg*5m6 zRF&^I{H?m~12!2+Xcm={d_+ZPs7lf)#c28?FtaH6UwG0dG>XE$j|_+xe>A%+8GpEn z`+Ld@yzwj=006liE@=FzNP0M@}k%uR!TvfUrT*{nhi~8HQr)zrM0VrDf``HWkz+5QCf@3~9Uscd90YgvIl9c2X;K2S2OApV{&Ns7o{{8o< za1Sf_o?3Un75=?wq2b~x__m+&{QNc+GnNnSP24RiJKz0Y%YEU|bh<|^&*&YV9K?c$ zK1Mk(BtFeaAxy8h>uF5 z!+!pJ?YHKVy!R)C&EB75Nnk%~{sD2JSY0RUguci4{$5el>(_3?di^*$x&@a0-pcU} z>07;52L1eoQR%;>!dK$gwNkQfgt$1W50$NLxH(e(l^~iAugJK4AqHYeGZz1g5(;(t zTee8vTUg&_!IH)+UJiZfU(F2Uio*ZPV(jc6#kLR>o*d`$NQ{MtMT7$?Y4|uo{z}R6 z@-oQvWGo)|zReTM{pb9q5-TY6%s4$z zOZ)NW$3p_^4()GW0k>9AA%sH6m0~22=+@2Xbjy?XZ6n8uVCtj)8op85FO~O_{R6|l zzf#_^=}K7QIbmtHlxj^kHN_m6V%d$DElLf6jFS2Y##%*N&VPbcEH*4J)a1LbL`Rg^%l zJa>`b%b61dToWQj>KN(Zjpx^Y4j+fPaBjYO^XA7@oS*r>w~?f2WPGxk>Z^_QZAwy# zG#qzb_g5ugm7YF*W96Ckf7}@sM~9XD7=StjJ2m;S+)L?KvD~!Qm-@U|_V-thQggK{ zx}dy6nuik^`sLlx?)apT$K@Ss|CkGW`e*?eV zDMm)^J=eQ~i$guGH8)&%gGJE(y$CosT47CdsU-s$KzUnVM8HPU_cuX4s!> ztgOBL{n659FJD?bfBx=WNij$B0`nU*ih!fxc&iSv&BM)&h`gsj);5yEb#O+XDGGmTpYfXSj>_Me*RArdZICq-UFu>?`Q!F&Rr7cw2wExaNBAi! z!QQ^fqar0GrDc?vY_N@}CZAJsf3iW`#jWS)BF%+xoQPBGM747oZ=Ey4m#Z9b>p-<=UPt`c2 z>)AKeI<1Ec4i3t9$vds}p07l}=u-tfx<_U%8ng{P)soZ>kB*?&MZBcw%I}?aFdDJD z%EgUduJF0?^UlTAZd(p0a;i5D;<`PBK;K+$ZFvU1-aDzNKfYjiUE-B_Z3Z5&h-~jK z(uF6Bn(C5YQH$uR!2WEuovyN-vI)uIyNts|$hvHgFdeiAXPc4M)_U9`ApATzm z0}Wn5-Tc{XH~9X3$q=b*UWs0n-E^JH^uaPyFr@%5#H;nj)2C0_VTtkaR?_&Zv+;b^ zpz0ATVA~?LD*3G2E3Btw4cmJu1#Atg?a)oQcq{qB71ny~yFp&a9fR7fdB9v)TPGWN z@x^|)F6!e$+cl;qPhP2cUolfuR9u!NFzsIJ(~B&4M2Pa{qT&Hl_U|jNyU+r!;_QCLqu;9K@D~Tlwq5an0TpW zNeARXMWjqrda1a+bWN8f5OKE9PWN=t`brF$^{of>@CtS6eSdw80)>AVOuN$4(z;I( zvtCFuGn4M(YWw*rFfvzHWPIiJ@$E{h(J~#!v(BQ~dQ=4YwQSmwf`3!ugd^Ap~ zE+ObnRU3x{R#x&j=y8I|)imOXCInJMJn>YV3ph?Qi|8wZ)FN)=MAi)(Ek{m{j*op$ zOUrI&)!Ad;6Z`9Vo$S?bCzrNo-)4&!NZ`cS-VCo5Ek8AGFJMged>Lx^cK1MzrtU0@ z(&q6CHsq$cQLLzGUY~O=PB7#=7_)xtWV@Ex^K!=V+&~z;bPEP->0qDzJKLf9{D({H zDPF=NEZdvw?MexlZwNi98$=XX8ey$T8C<~V;nxM>(a;!j; zAx`WZE|=my<=G9`L&5X2jXn_<=d&jgim|6*$%oq&UA9vdu5S)*e~V$QhrbVSE|`5V zT`@oDqoCgczM@&Zw2bUjLA~@VBQMj$K3DB%{ctvmqiq#YbSny~qVr?)~{KD^G z4-hO@t8HE?6(efMlDTyK|2m@X5d{SWCyy01qYXdZB$+F<$MV~aY=FZBuQ{@lS8>PU z(mjZePR8_bKwrEaNPRyfnj!$f0QPSP{p0qI4mbE*pI z7AIm4+Y>vudDC{fa&xZIIuIAu%?(RNk7LkyD;nV7QSe&$^&P&#zEoBH&N`8&l@AjA zDflf0>Q&%oN(uL!wv^%$L7!ca7<-1Shey>q6%_6v9hz5T3_JmM+O0$7`#e2hp86#; zNIXDI!y)%K->)ar@i9~Xc@K)^l1h{2KjJVdJwy#wPvCuxG`$#g!XrTMZF6`cH`ZEu z-vX^zJ?|3A+*K1}M1W6Nhm6k;Vv_IbiZS4Nu>?W#^l;3%AMFy)b4SnT#@UX!ZB?Y$ zK30ww%~VdBD#HhF7+xy`BptES&SiV(K^_-_fB-zC9$xD4BLA_&l64U^dN{*g!COC{ z)hV#{q}$rQy{%2(v+ED7A5=ySKIh1J=j~mjQ#b!?qj{)fpyp~e_AsGOIxxK_>%w{r zKK337STA$l(@97ursZ3)zQb82gYTI+_WZDx%#it@&PRT2tPNS z8jCPx(bEOoU)7*oK+Ah02KC;}=YZ}kq$>hFkLcj2dHo9O)F0-TlYzx}^?xEuw&yVi;;qJkW70?+ zyDvqgFS12p%w{s%F(dMlyV;ZG<_+k@;Wr=86ZQV$Ts+A51eHT+DGYi#bSK}sjp3{4 z0s2Jz`~m`R5Sc?!nxH z=6PVE>B=#5T}+4e-imxKb+L+%1Lu+i9a=8x4;+Z^28Dh*Mg7u+A+p^icx=cS~4-M^|4{u4a<(xZo$SSlA@x}iybLjajPPTtWBWf zY{<4Lsk94GC#dbqN_m;!%E}na*LFME6KhvFhP$~&vz2wUCSH+|!Vbd<@{1b}27W{&-+q`NS&R|R->SDc zD2FlEy{Tw3s-9y9roiwwwks|mlL&3A>*#n`@F(_n_wl53da!#YL&6=J>D-~r5hU>Z?)&V8S%BnEa|5v!PJnJ}vC5Y+A1G@NcSuJ;bS z)*oQx{KKZ+ZnjPy8to_*!=AKEvShmaBFB9-Ps<;Vz>!mSuO^)-x@p*PuUrf*2EdJ1 zM~5QP!0Lwr=0j5SMtbP4>+3}5`4}UXqXBY}jG?hI87E`k+?>8xA%$#)2gj2gB&NRd zMvz^;bo*N4{uz&MVx7nVHDEJo@F<{)y!HfqH{!BsVkVtESMwkowFOVu&j->&iS7?b zJmFLwg2G}=dnJ6f=*7PFn9_0B%~foED{jcjR=BOz(ViGT*-jEF<~CzK zvR$}8OC<4Pqz{wLAba|2opw9Zpv?K~o;F!9-1B&%6n##_Yd50$A&gd;ii5yE+E!$< zTDQ`MNkcOZb-b5ZOl~ufk-41W_~sz;EV^4F2oLgAz|d#&;wYe-G;$ZyHVoUMdKbL& zu*~uTjbE% z7#rPs4NgLs>ON|Gn8Ce4G_1{y5%k!iuAcoHdERH2BaU!Rd((Quw0EpPesFXP5MKE0 zi)hRa z4@?}}nn4RaJl_dy`4$3yC5g1JHK`-7^cwO0;) z;C-dJ$E1}HQhzp%-}~bQdlaz9R?m2+AFQIxh#?rk6s0p;9zhoiU%F+FM`RPB> z0BK%8U!dFFTEXFuee--O%tE7E&<}@9t$ieAF?m$^rUW+c#o-_xoIG@UW3X|Xfs9CC zj(e}HP%?xyx#`!-`t$y2QyOYm^e(2|nnNL)Ii|v5Zl|k*b`ZDcc`AA=!9FN7blAR2 z`Oz*xQdJW!B9kmR>PaQd8(C7XnIn~I>*;n(LMZ)Xip`1&UOKkj%*=0Vn+B^ea_HZn zWP`F}nb3o&1|5xAcMhfHb{RLk$FyQr*fAazvE1|@kLc-FlB(whs2_YIWg8%z%>TV{My^}tv5xcpb&_%L#PHha%c7k4$Wt9 zXg+H_jx2`>D=_+EmowhD6VWhJ&=kb2_G>FeLk>gdv557c>bWO%dA{c%)H3Bdq%}`w zE4o@BYh5s8zG>`6ynl=iQuao7yK+BzB20Jei=hDzddjeFhAzsd>a4ZZrJ#*P#; z9Y?gZnPZN(CR;7^Zw3ZcReCObr0LPtu{+^?Z0O!BPjvkzK0bxzy=J}&`{}Xg5Y!o2 zjG;rH_Plqs!?2l<;fnTg(OdM#zl+5+eBMj?;|UK%9xGYB9?X6h*d+*!kC$Hd)==#? zZVCRfjV?Y)fmm7^rbdnoif#PVh~T4gN~w5bwBCBOEpV zl~&xU?J0w>dlLh`;ATgPBB;Y|j`l=2S5JK7XgUTS<$YK~WMJ7NKI^d&NR^e&2%h7# zts4xp&4J!$Qfu_&i(_ME?w-7qITXS!TNY^ZB;iVzG?#>zW5blZ7O5n%lFmt{D=S5_ zb<(oQybql8y%PxoC{=!-;MKye6>&o&hco$fa>P;j089;@LP=n>b=_YknCiir`abU1-)HQX8e=O?$BBI z9WnwMUR5H|rJY{+Us7dX=6zEh77vk%l)Crl7d7C17jYVyTuHPL-bx$%R%*}z}(Q7|gM=5(m+1B8hQboEHrb;`F!gKmd| zc|-3^4l-okh~jHn?eoVm#q(RI>A&Umx;>gdDgO59GkCjg2e73|X<0K(dq}&L(I~{{ z+cYLYNY))Og*+)=Sb|y>!i;FeLlC&PvA!Nu1NZ ztA*ThH9iH{NLG+~7DaE?8_Q=b+ojNCmt;FI<$Isgn6g$8)g8@>>ZtQ7$!4} zjXD+?N0o=bY=Rs-U&Q?yZ*tk1SnPm9?Ou7`OnCbCbukp740Q_dK0{QuOJ|!QBZGo& zcbJ0$uVryd+ZwrU5^|&*v&?u~V@z+HXC@-xTrNxFJ}T&KR9vO$6VQFe0d-n!1YOHx z*~wN&#H>y4HEb;PgolU!4SySU+?REM%{7|#R@qLwCVk0$GhGzQ%ygBE+P29_?U|3K zyPljKdBrA^uqh?ah3j+`01$23kP+E^8%_t81u8%fxAScZ>^U$r7x(JZfpR)+{b6UY>3+%^&G0?5NdR zLuv_$-{hmG&yYDXOJIXJ%~sn(OK*__AV+fv5BKU-L-)PS0Gb;6_JYYn?@AGw zUk*Qw&pS`s4@1=d#-)H4CG2~?>lQvy;%mGt8~h@{*7iWMm|CQ=+Qa%}Z+5ocr82I@ zdfe`;`YHNG9VA!gXpK0-2GExyyCkBKB<)TtvnRkli4tqAupFjsfD|C!sRDv+*myZi zw9yXz8Hglb#OV0YH-;W>EG`iQ)fM1Lv$<5W-*U7X<$b&{#b>?V8<&sA7;!tOQ0Hh& z&+ZZh$o3sq8cJ8sR;CW5HFDd{DLt9hTN33Lh)<@g)wsp?@O$*3prf=Nhd3g_S-_5BGap-Mr+xpA{A+uM}1v~ClQ zchUqQ6CT-%lMzT2c+M+;uSLA7Q3mjN^+A`^tldn5PEm0ySI(F#V;6yCF87<9nicA|ufiK)x?+Uvi*O1}l*8;=EQ8+kR# zo!GS#q>n|j>lOKD5kvBfV91-&$k6D!l&mU5w&P8~m~xkdv2I7a4EeYZ%7_Ma0eLKp zD5AHylZ73+vzeV-;^79sx$)U|>CWot^4HCNPOwT30bX9s5A}z2h_!^hTDKh#4`J19 zc!eSXxsV0s-*VN`fv32_XVz=5PEaq|WZOSeE6lk$6<>#~-`gI^^}i+!`tdF`cUDxk zJc~HIHdzA`J#TnK0g$-q2G`D+++hg+P>PSJLu-5#e{mqZ(YTk>7R4c43hHK%mt#hX zGgeH*)bfl3=AB!`+-5C%p~H@|DZo)0=6#f<2e24F`7uCd%GUaxI0%amqjI! z{MP#{x^Vi~=HbJKWCol&l`$RUv@>=dS)Vj`yvAyyIZKdo2gXbUPHTPR+lU@}C=1bK zgQ)_Dx##ifR-@og$@u-NpGO`v-lWYrq;GbiLl3bOt0W=I=PHqVvu>~nyaKp3KdVux zI-HpYn`paY0uoS@y)VLi%}@Burt9sYf5Y$f(z4R}Zo5z9L7I?Fua7a}kLoetjkvEH zxNRTz3B>&A6@fDf#THd1bAymeFlC<7!PGZo9phFWFCSkA$<GtSWqwKFYSft;J~Y&8kvx|KfK02( zJ-E|R3?s*_)kZH~sSODU3FVI$@vRkWWLK4m_Rbu{kq|Gzwx+!>61SpOk@a33;`GoQ zxv;38TRtZe6;-EdyAn#Ms;6j_&ZJKDESIdx&0Dw7yX#EcYS`MhznGVMw#W1HSwq*I zz0F{C?rk#j-Y5Rb^FA>2;^Lx@*9nNX#5|uKpZCS~I>{`)(mA>HnLw+kjAn#^j_xR3 zMV!*(;LFCwhGX3q7!$P!M!+3-{#eoD4U!lmPkGSZ5+uVPfaqJkqwB%`El8s1>inqn zamtC;-nvjKO|BS5yo)7>q4^`Rv2rAAEbpXFQ3= ztWVhSv=aoQ>}yw%U==f^&XL4%B4N?|_Inp@^9iZbBLJ!qv)S-qfV9SX(fV#Q(}>lZ zo120|w|Kud!COq~8)+ZNC*}q5lTeAX5fknt0h4zTkCg~%#gF@{=OJgK zWV*9E*!Wm0^RXY&wP*z=5DQVlxUP|14pljIH>NQOhY z>=0Xt&DmG5X%rtLH)mO<4!>*C8qtdBy?i<5l3o<3@+Ic=N~GoRdOu$Pk!f(Efd&jy zbpDx848CpOG~H-^vg zzWZ6*PWO=E*<_h5W8jL6v~+F1jf(PL0bK6Xn<}SUkd#@+?IhycdY5tGo%PLmY@)Ui``P1u;LRNe z$V-SHcc$|XGb+2Q%zw*eMc?NL_Ik*~S7TP0?A#gI}TIiJVK}3Z`7io+zCJH+u>nM)x)~&{%Lr_vg>O2`5vXyn2rw!(A_5|&jri|uLh^inXZ?2a)C~b=ATy0 z@994Nv;O`2EA|b@G6b&NI<|bjdfxp0U=}cPys>>TsIVK#eFd!Po4zLqGTGHvI?sNL z4Gw@hM2h#Z^J1Tf#NHWwTk8vwz#24~p*WyAc!Kku=zBN4>p08J4FI+`cA%Uq_riTb zm;wbb2-nRS`P%ildbnoPs1r=oQp~xqPcJWVG$`X^hCr`vNuUoFSzNm?; zH2t((hC(6l& zq6r62O(zLJ+$P_b@g%2n%==_lV2{?Tm@BhlzD$t|x(at2B4BLgXjlNoz<0Qso%Kk` zVkRBO!%KB7=e|gt21pP_O-oQpJeY94ilq}$;E}M<-r6(blGdVxFWXA7)^X-|hDgu*INj%>Bc?AtcqaYF&`{~y_ z5s?cI7~3gJp^8H=t9#z)iy57U)YMcJac^v)+x+?7xqQ3yO$mv!Q@XL`xWvd^2DB&n z^skF=7|ctkuR2D75Q6E#>yiuY&VqGyfg(4q&%6`8r|S@unDcx291Q@mI!EpymU@Z{ zb*lS?o!Ph$3UY?ttuE=^7S?~equ1#>oyU`^=e>_MfC)@1?)_Z1W9VaOXsA7qWG=l0 z(zyL%+J+#)C?6=#XKdX*lB2dHmzI`B&x!1$BVu59Y-VcSG4h4UVPyC^wwLWd8=RXm z%T`^jEFd#94YxEgiD&hQl(LE#17USTQ0NO3XZZTZt{J(H56zF>3c z*sUf%siMfxkZEZ>3{RFl>n|Ayr`rbZChrkfoZ^It3db%+DsfWB!*O+NE2bu6yZFN@dZSL>Ph{Am~JHy&q zye_7T~!EOD7eige6m4=cs$@2>9wa1kk4W5jx5D<{6c4|z&m`{(`U9LrvLy~xS~*<}-hiyvQ(R5^8CPf>(_e0Wf$TrMS&of2rp6Br4c#6oZIvNQ zmT+Id`72=jle}}hBYVIkJceHL6ChMK0=Mkmf(!mFrEbRpz&dT9xT3ZYL`Fk{JWVI+ zVqp|;gC{qhUBB+KH4^d%(ckJWu?q~*@hBrsZ(&=7j8v8V@YZ3}>MKx|-JQ=W!tdk{ z{M$BD6@RA|{AAgON&OuY4f2&I6D$1C_8)7RUr2AFuCS5NT? zZURU3zeK#WM=hXAYpUqxG7Iw~P0-wM^k0%CH~uXE4c-PNOF<3)mpGzqbUY#)5=J3q ze^J5zN7nP}?(FDb{9(o?PbDvIXu-{dldSQ|BAiZv_-5%SPzG}$zZtR2xqwBQ`qQuB z;sc$0$6XwlP@*$0AbATu;bcxcaJ+&u7y9q5OtiEqs7mA2`HG-|4C7pUKSp{QC3#pc zKdn)v6+mtBaQ&>#egMn6bwaEBLMV*gQmE{M|5Y#yEwD&` z?*vUNwL5PJSWg1Y%uL~P5PCWuy({mMsYJYxv|YX3Vd=SVk_3gyfKjjI|Ie1dVHOCS z;kEv=#2Onm({O<+7cTYe!!v(>@MPQzH27XVKHhi#=C6LZ0JGakoB^!mf&!|zCml-u zQx45fuK@MH?Eiaq{W!!Jo5`BJ!N>|giDxKEmlDCDB3poyk!cjlI)4Nt>#tz5{xw=Z zp3iNLD^baDK99tB{ts?0-BVQ$&|ydJbHL|-8!TW==zp(?VC!@JfB{1A1ge#PHpAu9 z{$FDki@hWL@f!>HYa0K*NSOG4o(}S!cx82!RwOx%IUv0wWPP11r<;+Hu>;T~Az7K` zCMITi!wRGe&)mN`QhKlS3>63Aln!#iW)h_ZM6>RBFItfKll zRT-2|T1BfgMYiRzu6bHw&l{BLR2*kCuuAUQ8QJhTGgEL`{*z@gM zCCkT09AKLanyiAMP%KYqNN}N!l-5;+=;C-Ww`L4(0R1;_A0G+#bA~uU$l_2jB=%9@ z;JD#nqk^0k;aacJ@p8pHJr}Y+0dVM2djmY0jbV)dpVZ~aYQLo-zOlTVzR+ms?Q?aaG1F&%Q`=g=Cc5pTfZ={fpXS?JOT3zLEZLyyy2cNA zej8O)Rq}H3%9w?P2dkFsJIHM8^B-MCpFv}*a~dCI;#pz08qAi0)~c!+<6cS@7Bk-P zDcl_shzELbX1&v`(Vzsj#=Gz1H>88jAmsEE!WhK5i*9^aolk61Qu;fJ)wue>UZIq@ zs7&U7>)&GJb5wLZwThZ-q_4YD%GYb^`gId87&X|e%Cz3d{fdMwnHu?=L>y%IKRH;R zKXO?ruD#Wri}xZ#sWBh)%idz5thR};$e|Ap58a!n&Hnn4DzAXVarJ3Y#L;F6CFcp0c0CMH^)U*}e>`QWqxDxbJxQ+!uF-?-DCKvE>gW<JPB{mK*pV zY<>B#^osDcQr!{Q3G{)V5!=bh78@cY60^0|#q@KYXO>3u*c)Yp`1oH#3q2;d>20=2 z`!pFiO;NPM51*#aiPg1_JgIwKrL;{dx5m!NsGHQ(KhRF_?W$V1B?wcf3m!$evTEv% zrOT$@HdkfBv;Woql7`=AF0YmIE#^b!HEFd{a3FMZ6;C(5#iaOTRGdo(zql0@!!t;( zE$?dNky3+=KR4thv$`5~>zqlzz%BE`cy4^uildlAc4qcAYhV9j$Q!WKc`abX>jXP6 z)TVgjd@gnzPK%2&tGUWcX)7MsdYzSMp{nh@Y;igpqIHXgxm#O z`C;Mv>+uN6!faN(+APp~5I^U^JuT`&&FDh?*;?cJkX>Dth2D-pO0YD^TA2a8E-$tM zNr(wvz&Z;I*@Pb!W%VK8fMAR|Ab$9;nSrp(%C{j{{_N~*%6q_sn}LB=K~b6g$&)Xj zwSst67NB7FnXeWmChBi<#C)2pG&j;TF)2KK)lu9wert1in}nFEfK>%DQ|o1KXJ8q_mklc!bhtynnx_>HcNS&fdnC%rUS*asnnqDp?quQu3RoQ5emD%X6>&{a`~=!k z#Q=g&cQ?1uIr;tjIofh{`>s1vbUFqs;_PV|9KF50WywZ5G(sBRja1tmr|k9F7!Ykh zVgW_I1zK`b5WhW_il(M3KAogLtoMz+^F7Fx%_Ax%Cfhp_Z60Mh|9zJGY{FsEhA&S$ z;)2P+YjQ;eWQXmEopW=Sn_$CKgv#z6zcn#2*^QdmbGZV6Bt~;B4Q6;Iyx$R6s8LdU^ zM8?X_^zUdcBpfZP#@O^VQ{c6~N! zy>gmZbao1vZ#vvb&t~P>6`C$%@!uf|AR!@%2$7YMrElvHHnijtbkBGtvZ|B1bG^Uv zHjbUqY0W!vw)6*%i!+Ow44_xC5BbVOm~Oxh5udeYOt6I!sg6_CwGyh4u3C>;$EomT zg_mtj6>1@)#7TL~7AR@s+h^=XIV$X+=X56Znp`OLyxT%5uPqhRjB;&jg zxoR&~DmSjIZNxug!HR|_$g^M!SOP`tnyRGqrf)fBA}=<0I24{S+b$=2cE&b&ug1%3 zK*IPxbIDL_N#P+R><5I#Xj2g57rD^>?`=MAbF4;);p2HkNv3W#p>1*F60w}F2Hbal zb8NSsp6g&YMjX9VuVp<(Mk*mwsnoM35K^cA0Y9PI4!g50e;N4Bj_0*|2eTEJa?9G8 ztH;*UW>1=YJGRhw+1v-+>!-1(CSbnAAa^A!6vNgIa#-Y|2cJDxfPGIHAh&K5Eo%am zQ^!c)J}cxpF~XI^c2LWadJ+kda7|~r<$RdfV}q5UHSYt&!QpzU6Krp&H0k)xbE(18 zf)S{Fy=5dXZV=OuGi2#Zo8e)SXL?bMv zYzx?OVG?;DVZOb2rDIrDebjE~FulX{hFa28^7SR@8H{-RX)t)Zv{Wh}F?>c&m~3QMyau z!zHWs8DSK~*}d_#ip9>-*Wdkn9QAFueP!w>KO1po*u#iD7pa+kigkwJLJrEbjC=)j zh|#FyhOM$iq|xC(1jiP-TX2i|Y>N98?OjJ+A2eD8bsBE(zP*~waL{&C+!fb(Iy}`Z zC70~6sU)B1^s<;m&M<>f*w>=|Ww#E;yXlvG66h^FW1}dk<oVj0Rn>Q%TMD78b;>cx_Kn}wEjlOjk{AJFDz9^hO7vWBy;aD^%NK!Gp%k#^ znTu+uy9mGK<-)N@>d^s9&i)ZOMkPvY!1D7`WU#tOc#)X#R};(WF}9+5s|>_RjKd2T zD@F(C1xK*|`t@<{7oJ|3`(cl!VjoeSEZzEiTQIxWG4P{=%d@ZZN(t5SVo&ot)HR|8 zKJ_IkYo+P4hdplKvTNzU@(@OB7dQ0^N{TwNzq^pur|4j@cl6#IQm=xu3FzA%iK*iy z@UD`{&$w(8?CMsR_*zdD^Q__(sE`6%DU3tANO+Z8S_pDYNDJ$bcU|qR%SfeFiU0M= zDu<&ntdGcKoh}33_)jNPNX_dnN_VzS6WPB9KeFYr?Cbh`x_?wmd*3NxImM;QRQpD5 zMQ^MaJVpXjF-KJIvd6jg^&7S5Orq7A1!3~_7j#~TqvEFY>Tgus;|4CdT(gr$mgLvN ztk(%USh|$8;60C7*gpk%1E}&4GO^$kK(J>oQC`mAw_{`EE8>A|mTJG7+cnR*b=bMT zPiE+)jiDOBvZjwP*GF$?YBk4;58j7#$83BY$zOe45qDM{2Z5oilS>!rSv+00oq<0z zjdwvc|KJHJaMu5sfKe5qmj)-g_aJI%cn87=blV9|9dIMNi?3s*?cuW zBg)godfs8UvW#)!q7F#U!=g*+$^2{9HD)|w3&ctr?S}>L0;kGz-5-`=mWAcLv8&(U zoQaNM)iE52-WYBC#-*0$M+-S9bdk~5X}L*!e&Nz?G)AKIiwOVBtD zTQ~HWu4~qbZ&}Jj?{#ddPgh~S9KUHio9h5s91##(3bi5Y{cb&yqr<~OMjG+()2(Te z-EtXE7DKOf+|AmOIQIOrEj_s2iRnfl+~>4b$-ntQ)U3c9dH`dflN>V_`+!aAJoNcq zBNv8_?!uUZiAjfax~|K_Fs3cT+|YZt4sFAIZ#{@d#iFzy)>}}}k>aIAq0B!mQU(iqx={7VpoZE&+)0&9>#sYjTW(hJ_ zt>buZ-w)$(KS!&qs8;R`sz&#`H(i7# z@hS;Pu3a4Wz-D8$^U&Hw*4rIih<;i zkOrk2>F#ckZWtW{6%}czO}ZJ4Y;>w9u@TaYu2CBxqZzRK;`{l1fB*me^nQ9@k9o1z zuD#^*tkFeu3LTgRYuFHbwvBInq<;gy7(#Jn>LM)%Yc!X|Y z>tR*qOFuIXXhomg0fip)wYvx#lcMl1zyOmv=}7)$K5KFQ-CT*B4rRsJ6*3dk)>{RK z@`pr`quRKMu=)9jY>1n82$iZteDV=C4m7!y3`T(;we#=r;~)m1YGM^*ZWCM%oH)l~ z1b!E6Si5C8*p@1=d1w+g#Z~a=3K;J?)27$h^^p<1gKS^;6)7P4xm>(4x$t`e!;cML z8drP24SZmbsuN|Ld>5Ig-TJHK)}QuGwk7Ie;AxsME8WTdl_Sl?1VX!jW+tUT7r!d` zU;pQ!&fagkfAtK9jbCPVNZn4U9>JN`I#Scpj=+yP{{S5?`zi(|&6BL09d;JpXPp^_U3k|4bFhvwrE3a&ulS8^*gKs>fT7u1T{3MmWDr^_C;6SfLVA?HxL)Um>4a>K#Q z&yIMCQyC6RcS3?xvAY1$;q%wiyDoL<$RtO8eVu9v*zF#aP9rqqhYfyG+|>-=)#-)T zMWP0+yk_o}ZML5^1P#D)LS?R$#N$kk%Zc&yNtu0OkKkN_$NsFL_8Gb5G+B1z6m(S_ z6KoZ`zs`-$v)Yvnr^T+?X^>26@X;QuQ@!i5hYC*ZmT!ieV+egEQF+Mp`D9iTc4y0g zeL5mK>tIX#?_Ptj>+SpF&P-U-)SCcec-Q|kRAF*i*JR{z6_B^Mzc$G(Rk;GF&kOrm z`vOWPLyu>)#x3mJ`D&d;B>=7bq=n0vN8*k6z5ppT=wk&H;vU!S&s#u!6Ry=hwCr{@a-LkWpt7UX#%E#(;wzVr=wf7P;A=Z5v5O4fz1$ zfh~*48dpE&mnNoUW_6o?%3jMkA3p@{&v{^&H7+hrtjy@JD3%5>-cQe z(FoHa=FabubUDI93$a?5LgHL4m9pM@HWau50#t@J&iZq;!Q1h}p~IYnzJe0}xga^J zR6pFBiz+_)bS0uBNOWLpxZ~^5>Ktj!{_8LPPj}YF8+5Lk==TiF*`KoyL+MjN6!N6YXcUBdO(CiWDQc?v9Nn!77`TJ2rN+>*1dLzv2A z_ko$Hu^d`pG8N*q`#d%dUQjt#qaenZf)$~Yn0F--ZOT>Ft!tn+GlTt*&tpp!6t*z_ zujWYH;>UBl083%}HcN*5X3vw>4~em@P5$n~r3!C!2Okvl%0x@!#w&&omV@P0#c7wB zvL79?)Vj>%$u$cJ+LWR9F_XqyIlvX+9Dil^BX zE>UB_gB5^0%Jw$oW%nN9=PTrt_m9TCm#y3X_H0$iZCX3XZ<*~6@pL4c^kVBaMc9BD z8(hLpv}s^t#zJ$<$!VQbiOFe>U@kqGUb@$Cn&U+o#Wmi49#r)OX}!Ib3B9r8L85QHTJ;Jm@hzs!wsfL0ohHY*vyS9~IDM=FFZB^UB}ld;7fGYAa3Qi=~yx(Q({b;HJ# z-rPics~c6#Fz77@3%Ox<6bjEa{e?0lb*ClBls6YYj?a!h=EmiUn@eRwjqf*lZ+RAQ zU|sjyT!G%r2Z$-=bjpZpWqP9;y!$?d3=2DSO>?tFJ^GX*50m_Fh&e=;CB{` zh-W@)_G=Ak*Lg46omq6&>OY;FnZ)mXFkK6sjLWFodpdv66|MipcJxMeupfFw!?wy8 zaXR-=4T|>ZnR3sZqA^(@kS{%mGkP?GxyG_JrRyZX!vJ-AcHO?>!x4bG_q^kZes=w* zu(W!hEk3go)}T$pK8^xPlj8Rm1FyIk%!3Ck(vzjZt8qgdJWI%I$bPH4ukz~tO#Mub z**SNb;QBBUm&Dt`v#(ro_i%GmqseHQdp!}lfe<(-_+cYSNUW*xB{R1l9^gv|EC{Tc zYsD{;-7vnQhN6f}Hk>Ac!b$*nF>s{Ug2bG9-GrZxZat5s(T|TCdi)3Id`0Q+r!;Lp zycauoNZ$kdH5T(yF|wMTsatpW?h!U#It9COkBNbUALqhMGgie}YnI79#3DJLA*BmS zIrw_)jMHihS^V)b=v;cWS2#~Y3m7i#zt*F;etLp01I5gSxeTclHoqw}Anh?7s?H@9 zqzk)+_+@8Z+@sWw6ASrCY5wP5yxpYOX!d@*tcSkj8ww2Xr}7Hb5pR)c6&u3 zWrPQ8Rp`U!-CJFHLfJI_z&l$j#VaxvyH}t>?Oq|XMM9TIB!nuOhp9&olWKZ`9hrY~J6=u~vqvyWnn zPwX1$G`MZ+>laB>x1v)L`T_H^mk^nb-u9Kr!;VQ1IWYFOt$wNwk09TWK0VEoauIE~ zaUhM`aj;5|l=(TU>q_mhj_$D20fqf_hYY+st!Y6Zk%cs}L20%T$!Dk%(M?fbE@g6M zsi#^LiJ{p*DXs^KMEvc{S`WBfDe>#aRV$$DJs2UjKYA-f#vmS#u+4uk>V4`$XD!)Z zK-3KUPdD4VLWnNhmV*~|2c7)RGm~VNv(*}=Vl3m$WQv#APt1TU;~9vJJXmsiu{V2z z$_{ze#C`nOadXe`KD*h$L!3J`T*S<_(fa^~T=0&c0nySHge^{oLT1zcR=WlrYl^a? z8x00l2pHt4suL_;Ug`GVD?PgCT60-v86$IWZuKeUUAn!$db}teFOiojw?X-?^$U2- z#n59bYIr{xswXCq_QlrY=G6n5!b3fH;kK@`KVqDH^$h7)VmVLlX2!nzY@M~a(#H|t z$dn5Qi?qR|6j(k?Yz11Sk7@M^OzYPTe-4khFKq!#&joB>Nk`VN*U*xk4Q%Z1MSF;}Pl%>) zPoC<$6+vlC;mx}N386atkAO@!jOgM)x?<0pwVDwJ0Pf63ZpBNl?%`)erRwaRKt2b` zUb|cjwZmS&2mib~Bbs)Z%!p=BQp#^-h-;dpfOCVwRKb5F&(&T^WxZKe>-}>^h#z{+P1RhM&YRbqS}cI>L^Dbd+&O< z?r!taic7_IL4x}ljgm#>u(;n7NaUc6i$H*YO%2t${GUkfDZdSS2OP97#=h=(g8nTP z4mFq4KMTK-VBM#c`}X|U%;XA;*WVBywE}j22H?6%7dkd}ihK)@jLl^H zDQI>B=5p8e0GT2lU@F&i?BwO_DwHx>-Vez+_=F`3#n?L8(zT!TOF(l3ShpJ1O>y;> z!3&Y`vk>f=a2H14sTk!dye<2?V05&H2=qzSphk`gDc=jTpI68-UE)3LYqa6cpg|a( z;6{fwz|sqB`t9ZJv4{T6ts#$%6!!-v)l*24_3?GHwO6j30!T+0I59juIC$q7*NmkQ zgk=cHp6W{K>#Cr4aFnzVdm{y~shB-D{w@fz`Is-g*LClPv!IRh8d=glOKX~!NcWCi zQY<21sDtxNm5R#b0FrqS5xz&h_WDyf@ai--AV8hox?PzL%(CxDJ*O*g`j)6z-Z!sa zKj%|X{=UVhd1Oa)hV=m-6Lslbde1t|r-e<(>kUdGq4R2cn%fd;uxtU^R9An=sMq!V zbTO^yaBSrArZe~F#__ne<@DnmqWMuVMQ*At^k z+U#M7k(CtVPd=W{<>LeGB$Lhzo*KNS3V8JMC+9?8hkYLBnyu;D`j`Q+;DQG4C*8fh zK|A}0dxa7mH?m#4!|Kqhdc^n2DXWcgqE@Z`zufYbFtl&L@?E1bI#BTF)34(gidT%E z#hwK^>ZP_X=j9e!H0e_h2yLn{5s`g4&kZ-@NPhM(|EF4B-}2V_pR;+*L3MS6_W)n0 z=O5i}s);g8y#WZDpuGQsGZm{!%Z&_Fm5&OSx(uBloSN8n?T~kU-gIr{VG7PG%m`)U zZd`0f+f*-3O7fsnkV$}c=BhVmL)UH`v`>|*-Jr_6mVSaqp&0 z)tAh!&<1^{t2lSw^_4H=d}n6>S8n`-i zkEq#O7;p1cDnu*WD2xn7 zvoXeWeRG>I_Nj9l%`55a?L2#e$ZzsIYy_8dgOBmN@2rW)+zfwH0hjV&G5d7_MlZ$e ze|A?}pxPj^yvfhq!>S1A(`2KR0#jbnmLbJej#Eg5)zZAi-Y@cFz@4G5?CrKb;&U== zKS&VwPPab^VV>=)*kVwlcG7nTf2)p^On?9)l(Hd&v2F#7?UU(DiM9}fz7T^%rlkHV z$OphU?j06M;sNUp#Q;MVhrQbLK&G2aa^DZEOgn~S8rCcdWtJ z7R~IWr8Xya@;E``9+Tj%i7vJDK1W3k^jTUG{LG$8&_BBE#lghRqnQZCnTMSZnfAU( zEJm=mpYH8~gsdEWUMaI`&MmrZ)=t){{`1erPag^j9Yh508`{dtM-BsAuWQlKq@9kl zvrdy{?Q7cHGy;C?7=H9S#jU_XuYHwhs0)_II&H~?INih!Hl#*W$ zrex)!d13|FlfW^@I=Y~c$|_L%LK<52)YN1rY}|d~n^PJ7S!$|@Xk%km)V;zdt6Bif zPV|p$ojDiVnn=ugW^4Xq<7lIq;fK49oLp2`{ZY`UWbnN;`BpKO_-W4OD0F8OokDydv*W|ikfJXNjjF~?GngQR7p zPuhe$T*4r$!BbiTLn~S{W9=OqS4*L3?3eqGO+?tjsQ+gBk8U~4sztzBKA8d(?Lca( zeW=eaoeRogslJbS!jmm&(3mpB1BwLDSW=3Uoz&&rJN;YC^z0bc)`QcGRM?z(YP0Veoi-bC6p|_}tls#;!Q~WKYU0CerT*(Y*`L); zDI7RG$obsM%gu(4Ei^NAGm5fk>5bdHkT1z|C2xgIX7B-*PJ?!@Si`@UPWVN&>AW5X z)6S#Md2=v*osh_ockNjd5N?<2=7Lia0=A5j^5|lV%UxiVo4g zOvZs9*m@Xh!(L*NY7%&S%;Klj_`jWT8qgVUEG^+oR}~aH!}80M2cOwNHm$4NGg%l) zd;O2ghc4^bh{)q`x)oKoxR=u7>D8)J7s0$SH|-wrhC~iBq4)B(KKZEKcur|mPN|n~ z%Dy`l8Xtbl6bI7sDpgo0xpV4tvfZ?eFQ1ik{d77+PAhhL@Igyg(#N9o?o0$*-wR?e zU9ufrp|?_f{pOtR<6jBcQo13rZcaat6KVa|M`_LH5ZPt=Z`75fI|#%c=_<>58LfmZ zV~uD((7{Mj{lKpP!$RIZQ_`6*~0s$}~ii?udeW3?iontr!Zw zPrZbloTfLe#73rbbeNRfX{ynOi={AwB#SauWfQOjOnXiC3(cGp`s@DvSy+A5<`8DF z1{bO_+9hY+uSoGoA~wv4_TM8r&h(8$;V3O5)6W!s<_RYspEwA&Z=--Pv|_d56o93t(XdtFD!LG3ZtSY~r|)t@v|e-3aX zOLdc6==@W4Rj#+cZ z8)|C=TP{7Jx761X6jOA2dbi*obVldjPlb}$kqNthdCnGA*G^_V_bw5n!gxmQKON%c=ht5< zNLY8n3A+zscq}wMVn}O0g58lI%bv;fhf=f610hu@dv{e}UKJl2x<7yZ_U%4KWj6CD zb^meK!td(|N``Y!v4CX8F|ywLX_s%qXu#LLG=U)eyXL|r#VULe_mpq6&l~_Xww)gA zYDv8A#UEv0`+(J{!9#oGaJc>wyDGg`+=u}2m{{(euD7)|HSgAD88{Mp_xB&m!k~!_ zz=#W;h-5Z`a8YVB9{896ASi6$yfC)>>sNgN#Txp#ww8e`o^o&?C{l4Ql`HYgyMlJ% zr{jzC}qb|>e>%grpH{B1T!AE zV*6|nt-H^kNx3iC?@r`K7G?l3a?!!Q+i#>FRph4GjK2^CO7*sjQ}gdXl((J3!rB2+ z-sku?ihootw3y zDhVh#5`7aE2SaIHZC>Rgv|jbovu|Zw5AxY8D6{k?nQ%21@9&bKmqJW6fhFv`-*%@k z#kaGc!B;2Kv-H{*+AlA@;-3-k(Q@n%C8a#kPToOHyr|EHV#YdL@ft4x&Rc2kG|3gdVX8kq!Li^`;XT)kB z`f!e~_<{1HbBp-r%vI!-9^ZZ4;14)MI{cZTMG@G@?5IYy?+vB+?0p5oaH@V!hQ$ve zQci;G&>$z*kR!}EYKXl3us>b3rO|VDY_sVH$cTTXk(K_NC3epzqOyX=%tyb#{V}aX zNCAM`32jrKjlj7H%k2Bn5ltN2H=OXMS)&a)kQsfm+Mt;~GG}hH7M2nmuOkbZ;YNr3 zgv7nhKC|}hQ)GYGx-omco?Me`jBnuPaTk+j+fyNCfnQB>e?({j0V^sLi<>x0D2(Lc z8DmbGs&luN?)V|6&|#sdG#MAk96NRS?4(=GbTArLm9{s`i^xE5R?d7gS$#|94F!$g z_Uy8=qm(3L%{jW_KuLZ&JJYOQeWweq)Hlj>d!#}S9c28Iz@t^3^3+Q-2!E$--n3t; zsV4c4nrsW_AcC(!+qN;}fVW}}brg95^_B_Dmb5U%5+o^Nxn_{{jz>}sjV(zHhUc)( zlHK=Y)FV}8l-u8PI}S2G5f4B1Li!0fpUP z<;dj($v;f2Z{VCBzp2u7hLAUsHtC`EA#m3|AdAv}JxCKNZr3ekzL@h-(`(}&u=B;? zyYPUAas*+c+^1?(Y0Ifav@2?(^c=I=b^DnCELf??Hgm6@j~DRI5D0P}3t3-wMfX1d z_ai_z#zYbX+4$WT@E7HAj!t+GpnhtxoS952I%NzCJ>M??$d8z34(RQW4UBihispWs zJfQaC#pZB|nFthH34AL>u4-^&Cn8qVL5gD$d9mFt!zD@+)?pB zuegc(OgfIN^XQjYSIvCuu@7C(dAafD2H^T(y0bz~9a)6uE_pMzP53VsV49Jb$Z@Qv zn3R*Dj9!R6lp6aH8NBt>V)DK>jgZG^*)^tMSu z?lcu~ZTSAxJXNGGQ8V+)4Wx!gPI&;T2M17Yoq7pSjDmxm2Q6t~Rjc*y30YMuuA7O* zte$O>6Ix;HMzW$4gh}{_7D?9^C=!5LY%I%?yl>oliF#swjO;>x@>v@ZH_c>;} zTyKaq2^(m)>+K+))+8$^cEt9T<)bXiHG_;ayljZ_>;R0%60C~Bt zSf3BsE--GeZ3KUPx%(Di-VWFDf)6UJ?#=l(XIanwEF1&*e4{9oFl7w1y|>pLIe|f$ z1sE5{wLk;rX2;yb-HM)`O#%)c2=$~>ZvmAxTB0Lmi^n3v}Ls6LjEr7FjGHxji=Ksg{bOP7=#IiREk17T$Z$~PqtA! z+KBa+7AR$-m(C74n6}nS0bewUkNvt}`unktsb24rk*j~~HK^BrnQcQ{_5NPSt|*xG z9{=i_EP?wywOKYJic1xGvMnu<8$L>b3%|n;oiIOsLhT%;@N=WKF8JgBQ9nk$bu4-R z{=JWx+D4#9+hMf^9d1CBBym1nrX@kzI81_@X~8Bv4|(XAtDXB!u1EJ7h@It#-~vsB zJW8ipDIf_?WA@F4lK6I#c(E?nTCi_p&F%~{W~j){+ipE8AowRMzzTGl@nJI~vp1d? zC3W77$+bzNjPDZMNvd+)*vSsrv534UurXYguBkjcQWR!8o&~ulxu3T@KvvaOrdbsa zSPt{{UZ867gLtaVyXk{H1=hPuBUWms%G%n44O%opZU*q7vS7;0uYe|ShnCuHX{92p zH!Xh{P<0tNq;AQ{lc zCB?TNjfI9f`OlD!b_q?}2@dge((5{5ie(7ke6l;uduf>1N6%PR;*nWxE%6S}zMcmB z1#F1xi%$7$W%eE8DQm%ut*<2WWyDMgT+Bkdvg%}Q7`Th7kmVl(wemy&suTMil+iUEMxV!nxJp>6#69RM>kk?p4+d_9#{U{7PD7RJi-&?2X3B7Pmu5xlI- z74Gv#4U~b9h}lF|g?nQF+JY;cL4zbYB5 z{F{mEto$>?}2F!eOsM~vvrJix^ab&{t{o+_|NYIVOjndHo-KMXPr`ZV&U5HVV4 z(+v2I7Zet9o19m9v0}dcSe*>n0Q$)SA@;|H>$i0M>WTjVF*d}lrei?WrF;SisyyZz z%PervT8c#=r`O?3(yCrS_I-G>|9SyOAt0~a>5cYKWzwUz7)lta)gzwlZ{Wo}J%5+h z2Bomrc(i*Zza%v}99p&wg^-e#BSXHa!4Cj-7#e!_LR_Dy68~epTls*R&?NIwqCd9!A}CF9;T}; zXq?*IXR6iSxE30q3JIz^yX9$(T z%^0zP+Yb2ycM!f6Q2@8&GO2bB-Kmhy6sz%d?neMbX>wofmFYAe&Gnm`-F^w&f&t$# zL+Ezzae5oMdPLK`sgGB!Qg1$XWTH_Jim=21Gcao71^ag@Ar;6p0cSu+wDLVXBXMYG z2&)8uD<4zC`Z)LgTpy>OM#LpuSsr?@itFBK28HkhH~SD3d?%0bGpqb|5HR9tw|d=wvE~9AUnOP9rd;1qN_?C z+5$z|eIakVxz%VO?co2r-GxPKnYi@E1$9z+T3@W5bhXj1;%KPtM@i-{48ckWw>Q^& z`EgG%FpQ_u86|RY&Mbci`1!%=YmsX{j#H+FC@{*b3M8@4aLaR}A=wA6>Q+#LDVVZQ zO0+$$qIC+@E?!Du!>&{GFa_rjS>I4{cpx&w@=jNRK^7uDuRBim0U-_0XMbxccy)b1B)ty^xL2 zDH+a+3%$wg1A+n24A>pHXp& zIzja2Ka+h@=kEV9+!3%)YX(a`sG~}*D&9b z_Hr~So}L(do~K9dz6}6qOX)ejXnS4Dp1RPL!upfFwV@WLC1AV<01@q7RG7p7eTkPJ zC@D|9&BTPqIWp$+H>%RRmoB|B`1p1$YN$&uk=g-sdGPPIKT6x}xDqc9Weqjz4`HXG zUG|^SE1QSfAS~PhXT3CM@HXp_PXfJL4acT4JSnI=4T#PY{6e+=5s_G;sxoPj$gLG0 zELS=>#LIA|H@1!XiEbXdYOTV33TPYDD4iAUi$Z=CO>*D0nVGMD9&6TaUg?H%SIf!d$t+ogS(Oqlwj1&Q^<|lMT1?!QVo=R$`~8Msrzg!S<@E8}Y-g z+e1mG(%69CK)^;qTa3ktpwIZHCm^%ofq{Y0nmmA%=5e!GCx=S@I^hU*TmEa^`Pl(J zl4F@E?$PFc8j*eBEKERz8Z})iP#SIcZ2V*;jg#jb5R@X|JylcpR(%1yOI7hd^@ijV z)V%3sca7WD`B!yNjO#PVP$Mv5XrUuU+Ct>-zLpPe`o~00r-rh7Xa$n_KIdf`h;)~9 zG^__5fd0Dm{GPiB{Eh^?%7yOjvb1#dEIog$Kt^J7K*2r-HXg522dU7 z0o^Tope<*EP}VNHsPF$UsDJ-~-`+q5`6fYhWwj9}6t7ZX`@OmbFMe*u4X{&Gd{Tu% zzW2Q?{_{-V>N^(mV3NpSd>4f&%;}>0c+9!(2y-TlYL%Z5g z^eitumYgXh(?h(K9sfF!f2OAN;Xu26BqUnsqn=ui1;?ZBKVqhuQ0! zyT+U~G^8y(R=}=nc%BDd6>^>A2xKE3RPmoD!m@Z2lp^XX@We$x86i>|Zz4Uspsd>{ z;Rz^uGu8nQHgl^vJ*9_dA&`m{UR6L(oXiS9JJRR#GoATlclfY95!-@_lQq^p&h;L{ zC;;3EuTd);;@S-J9eP6qaLQe*1E{u*Z_rrCYL21~<^xJTfNo~MQ4h0++JCJehuo80 zUuI3d`O@~ai@pK<^Z`1@Y?4s9FK==fvEAGeun}1`pwiCcyLJIjbKguih%TdIIu_bM zsU_1{wI{+zkEk3ED-*u;-*4B$e%psx`Hw2ECn4I7RScD=jfQ&JPF9l43Mk}3z&mH@ zys>v+;CIY4=dt?`HKg0*I57{XICE9ae|Ks#Vuuptd2SmjXST_ zc%-NdtOK#!&(BnYVi>|_MeR7V2?6-Q+9V(Awad|WC_++rglt5(TnHZ%xjlrPZ2yY| ztY(a&?Pm(_DYrRjlaQ)MpRG#J3`&(g(G%0tOmol4{(hINzkV&lCpV^i#XDY_0U{?P z&8?RO!Jp6m&VLH*X)2V+lRZP8U?g={^vZv>_QsSVQ1D_^oesW#opQaA<9+abJZAUf zsVX2N+;s5Vbg4*zXgdy#^FA<4w@Og{G`P8Tp1T>qZq#{&bO_a2_sBeDH;H~gd8e9} zyU?Ey8PRE+oqvyxJxIW{d5k;7CW9N1%0*m9e=N2wVc3b-jPQq%;m3|5b^xH-Y~yZkBOC{lPtRHvZZ3}NK5 z4j_@@u}yyTs6L*aC87~n4P6%_Jq==ilRx$#9xG`5Np_T>LiS{7!=ZuG((BIvW3G5G z0_f9?7@fAV$&!;;7l$;BH4SEMyMh71BO=%=+r{=CRq%LUUsU#rBXSGz6c#5kXTM4t zvV{TU(JTxMuNr?P@PA%X`hwgC{j1uKE)vnZ>(`P4tqneX*Su*Y&B3?$e)mS2T;y-2 z$D&ej#4pV}ZkEzVjVxn#Rc)=L>;3v3Q_@IV^TUB-fP$*|3}{z9`>;&qhh~5mMt>jc z&CcYAxJTGtlbo98?IT)>mIC^4(qSJngtG(?!CUJ}TfqP%J_p$sEm;$oZelzgy0XT4O56JLku zw{0tOzWr@jMn?*mm^xWBH#XWW0OVc|kj!ME9~A9sx_`i&zuaZl?+oWCq0L4L^wX2s zc_S|g$LH0N=HG=!u;^5|Z=U$#k|8(SkvH4IQR^;l-Low4g0*60ObB*|GiwGB#}9*+ zumTp!Us5Dw6rl*$|q62Xn3dr0A-6BPc_cGJ3NcK-(y4 z`+(54fCATiM0@+cEXA=wp+HHjG8BkZr09G>e`iHR$&bPX&PCdON^E#Uji~OF-2Vdp zid?>(HOshEh~5m9C1vP zagT%tt`zRzk4O6I!o>_O8fpktL4%sQI`r&Mg6u>B#VaAlPgqeH!R>%%4S-$t9eV)6 zvR1>js-@>SU>lrZz(#cX6Bm|F6pyhz&+SkuScKg-Le?-1evEo^?Dk%E_X_I@c2x~n z5#L?`tQxH}x?7VIJugVdG`kwJb>YW!8E}Ltt+^UOE=?RVLxBypk@2y$ZJR2lNZwXa z>+QCyiETJ2uJ7Kwz&w|v$m-E}c70<+zU;)I_re{V?Nl z9mbnd9bq2$`~eP5-#C8q=5uoLNLN;{fiwkS#wdIDwZv3(qqdIi!yLXGho_gct6NA^ zd6%?5P2Spxr5>4T46YI&Wd$E^l5d&{JB=`KaqR)i4&!~^gX%Yg8aqYXYdpGCRRpp! z0@e@e{@vfHnwpGagKZiv7ul2khE-d7q33dw<`EB*vI5R){VL3aoQ(%;`8Fq8Ji;Yc<^qKb-cnj~%`9 z{d$l@C%M}#s~=Z=UX6^8*L&pyzE5vCv?lccn+B{>b{NyUdYdl26&_V?OV3DrIm&u)6rUz5}Wm@oQ4&H0}ldA?2VL_mGx~u$SS;km?Qg#|IVdiq0_OZP^EX2HA_P6yz;LCD+Zj%PS^#45nze-E~ z|8D*RIL!Rd6Tltz@y+!TR$bDmxXbQX%dh2@U(A76*5{P=b|tDk!py&5*s)-mkGCj@uW}qa?i8_^~<61aE%*!UfRNC)6qTsVz7K_+%(X zaWyV~tOK+i1r3fLaik8m&O9L`ftRx|brr^zC&_QGQjwz7eyyv_v^q<8gLCDTsf;*rEtA7-as-?xh`Qmz^5y_$~YPF29l@ zi@fOq^QE*jXlfetS+%zB4Zy@KD(au;xPQp*%tt->&ztqMFU|mRZr+yWtKO7H)^Ca& zp{7_@dW|eu2^mOd$p9_S-RGr@w9++RnB<7VN}@sF#~`O251vYc0>?{6AjoP+v)pLoF9Jk2&;_IY}# zdWTKW*Lbbk=ua%u{z|ViCpUalr0e{D@3Gc#6BQXnQRzoVLYscRt7O^H#e=Uh0jpDA zE~CO#&h3X=9^}zk{mlwUZ!pWFAVx{OA&UJww*cRrJ6}knRBWuQ!mPqt@0+hf#V46M zru-R)%(f&Ib~>M8Lp8GEAHv6D#qw9+foe$;^nwb|FNDHRi*-CRy^-KJuy zceM`X5He1&^5JXGc|Gv%*IutH2z2Y?r|vS{*v~jAy~n=2KKM6|&ykN8teKR!YmfZE`%kOINBappQ2d z3`9j|#?u+YQ8JpjlQ0v5yj;foSDOR+jadFRu%TZ+)Z!*H^~NgB3>oAjA5aWakV^^V za>(xft8F4~qGU46-F$#H1+LD2pVx(@K!Udv(HY&X(-?eMR?6XUILpmcDJA9tv&6pGh59(ABs%a_`KG}^DQRFv8{Aenu!m*d zT^6*-J3Bi|K1{4l^1XBCpKdhoms+Urzm9^y=jIp#aijL-rrz@@^#*NlIUWaUJqe!?)Po8T`X|eBA5~ zO85W!4Q$rAbH>kVZY-Fen^sQD2AOAgg=B-x-l@=`bPcAh@6FJqr^D)9<{sEZelm=X z`n$7JXAM?ka^b+0+2l0?4t;(3fBu`V`@I)^HSeR-y13$;iKm1@uJL*GlDJ1}SMuiA zUE4qmt9^?w7b}yAfg5IBg@3NSm|>vewSBH`1j680LUC7}QPfk!tdWX>n~iO{`p^^D z$gkdp`Pep;2H95F&9L0`+u~mh|BnOa#T!KJ5`cL0$ZT5=Opzy|JU0DaEAbt;qdlx8 z0RP?edSToa(H~niz#&|)Txz~(_$2XO*UVGl37|5Odfmo9G%#MT=wmzDC_Iu@UR&N= zRpkLhsApY`Ol1An=kLP#sW1I%UgJ&j=aBPSS(QX7(sO^(Wo+VJe(>1Yew}RfepqXg zpHDqlrA?(!e?})l?MvF~$@j)C-Qi(=B+G5neA8t4YX$}^ILrLBU*QI(ieo@T`~Usl zZEoDO3Y(B7V1ItkndfJrU0m&m$^@W1wxz-uWLsk-~w{=8WTTc3KnFzK{s7GWXE?zNhj z$r+|ClDgcp6OKzs0^c@JY8>JmRov%9?x!o7L7d6&n3*_N;X_vF9EeD;-v9N>?N}$$b0+YRjulD7xahDO@LeX=#)J|7&IxQ zmCH;N?oU4T3OScZLiwFbb#pM5eb11NQyb_;p?UxA?%%z;a`1=rAJRs}$t?^VkqMx#S3u6r*M%ExlP5vD}m}?FPbqH~}tu z1I;Z^BFBFG(c$Ojdrj;>4O{Xcs zs+odHjT|jztDhdu5u)P~ePmQivy8u{rc~QoY-D@Kkq8kSHLVo`?e^;7!dADpF$U|e zV|tQn194f^@pc}*C$}=~KDQn>rHxxs-3P%#`mFIk_wJY!WD3u?p?twUgGnluzh{Id zf27Xb`s_z{{s!m0Mg9$djQHZ#BZ2L6v%Y_-`jjYVrUuwNm(qrG4G3=3za`jakkZyF*JH{F9{xigE0_LA>!ot|3cVuAuF-o?I!`5_F+YxvsXqV}Av+s5#>zJzY z+MhN^^MM;~tipkmAm}o#nJ(rABohH4yg>N8+YG`WB92Zew&^+ZFB>C{&%l^ytF-mc z1*XVEsnv{7Cf-JQQC>DSz$N2K9+m3o|JhmX zm_I=C)StN4C#PiEC4Y7CjoG>A%s8REjZsSK8Fza95jLU%X0hF$Xk2!PLXIkt*Jmx# z&_5Y$R>FNm)|q>n%r3}dIZS@GFhwGsz_a-GwTHIsPzE~HeOIEkf8V)X=FXhAm{yMu zMt25o^PX;L;*N1O)DnN|*w5c7P~kw2iX@iHU8G%RaW`=6mw!B{H6w4>z(Vva*S;no ztQu7bto+#5jy@H7{U)zbWnAC)xJ>zR4Pe z!1wIJ{%%A|F*;0@*3XG%S=00g*Sqes%nVY7bIDYLWx?7VCqY>q+v*ahTQ^rC9roQ% zkBKPoLx*WPVY<}6)nKjt=uhkNlcyiW-5ccKxVAl?STqk*-jOJTlB6mdHv|O^vO9=3 zdDId<5c(uk!~1bV1_tbznAy>*7tX{%>jYYthTG^0m4-CJG`|y!CEEi+tcy5iypx=j8p#?G#~)5r$s(q;>*u zeLu-g@pm6xubZBC*SMX~z1viy&wj_if5$B5_R~#r&Yp0abwAo8$f7xQ+$j4!XJb+% zI~EjXT`*lmc^FaJ8; z=lnv;J!U*iojZ0OqM_$H`x8s6tfcf6RJKkL#LQXUFAhGCr=@OK!GfV_eLd&p5Q#wEg}={gZqbF)gv!N@>eIXiD+XV3Am+seZfAm#&rh46+p1v*Iba2-BH zIs~gxcyJ$G`)qgTh{&;-msb37Nf!a((wn8AmaZ&0Ki8xs#pFXfqSHKYr8lM(uGPw? zI{nQjQ^38}!VHUbWtop`rV|GC1elZ^gi)`Y#V3L z%;-p+yS|_k;U4zDh1zL-$fmnPwFHKiE>LwK#h%#?u>FruN6%#Et?3ZJFs8abET`Upwzyrv=qekX#$Fn>IN{qk$0wR zX}xw5XhpS1{g_ov(zq0?QDY(_u1m_&SCD%zJxp81#XMOb5v-f|`B#}MM|btd*%aFO z`trpltj+o4J3Th5D|kBFK32~%YQgES_c3hGz8uu)Ia;SYEgO!aaBS3Z9dQohMI9VO zeL5%?eZS^7azit+euMbWbgO*G!_Cx^`=oCgu4o*3JjqMdu{Fq5;Z{A{Y(gm;*gLzO z&4;Gx9u8`=rM_=gW#;iqJIkCq@L=Z&^UVhYfr(;GUX9d^^-uWunR$44o(*~oZ2qh% z_pEb@bot%Mu(3P^ir1IUx6=(~OGkBdFfOJJ3=9MST9imx_TAlGxj>EOTNm}M0Zz6U zb4j^>_7UuOEWemIVV!41{O$Ave?-IkxP#<#p?S`U>6Jj{T|xz#v|0|KtSHSS1QYsR zvJK;vc*jhg%b6cCkPcjG4<`5(&!!tUEL;mv5stsZH)&~R%OyQCqomL!kgd*M8C%0D zJN!TyMl~rhiTR_4$75qs5`Ghs_))|7^k=Sed3co}O3)-XZ%+szeoBql$o!?jjSWj+ za1PccLMM}xqw6jDw~{wDinOb|c9C&zzrnrq!YQZKoH*t%RON!)t5+HSy5BOWW*<|p zvSr-NGb9}8_4`#*ex|ZynEZK>savIK%GVo##uWX}4$$Zla@Dr@io1pcfkmqu_KHcT z`!TGMe2_QQd!x^~gj_pArypq%UK#{}|E=mpsTB5(erfqB*wpR2cgQGWY#f^G`}wKP zc7Ffbxo^~nS8X$j&GU*iAAbOLWz&PpnE|RO{bap+-{90s8l24~KgOTi$&JZR#aU>6 zRCoN?x9#xR$5hM7z)jsiv=X0@<-sjVvIXGP3sVyWQp+ox?PO) z%)BIxCo*#cB&RbU3?Fq9iA^GqtEc_-hC-px8q^*Udr!YI6t0Yz@5mdKe#}~{@OH)9 zHrI3gTnTDAe4d&b*T5Epn{X_QOFh|pYGeQoSUl!Z6-kj1k@^(ED z>j0h!pbn)izG_FPVIiMSikC;)lRct(N0|nv<@NnFa*m>e<1*Bh zPC6;$C?);WfXkV8Cq{xknV(H3eL94aIzf}3V#iVVBfT_KZ^{|8y%(_04%XVQB%`_) zCrK^)qQ&$4AFWl`Ge1n!H?hQT!8xx@!+W7|_T_J9Yk&T(D6D-44Tzy|mQ5Zr(9 zQg56OFyHmXta6(4JrHtp2%-s6d>t4Pg6yXQyl^S}y7;V%Z~mna0H=5s{Vpk=C#I4I z4n-%`0bI2E=%}$hwO6N{{ox~Fa5i|PfXfAq z%>TZ;wm;`A^5P^E4{9>S6yFFTJ-7n0TnQBHFS@0hxM_u-jatJt&OC`i8)`_k0lRR+J~Cj={?U|RHizs>UAKl?|vb80~hw zm|6BcYbEf-veS6v8fsE>3~1qtu;7=y!2H5$i-^ z!LTD7&*M2d{-CZbPuNjf=Vi{VSiX4KN8ew`I~^&`u0%jG#;yowh+n#!3MWOl^8jwSK|rg-Gu?6?-$$Mg$5WB;67$4WKBDZ z{5xy_5=D^Xgs@AHn{8K;R zgzLtCM|@T|{_XHa#Ipl_fN>ubj5*~$+4Jj)h|5yIjgnps@G;jWT&~6SM`8^3NJS zIOvH!xp6(z!B9aT4ZVACP%2-Yl`ILC??+77%LOjyeHpB z5=*&7Fe@#9HLT37(tLDyvdV04x1A=We&Oz-a@*bG>e?6-3bhyrh*b7XI&>}u09l+@ zbLf))H-FMK9zi55v_Da|w*Pr$n6vP`K+s!5tBFX=SpE5Bc#L+l@B80S%FSOraFhCa zkBKb&oE83)96~W_Tbre5L68>6;6ma;Dq6z$g`!XjF}Z$^h##kC~ejMXheRz4*n|6?>?Ugfg_=@Xpjj++5JB!jrvv@;*miI0F) z(}bdvrQHNNVDRG7;PcmKFNE#mMJ^4Ned1GnA>kh&^OEZci-4fu$k;L$!TF!s->wBR zg@FD>Q29&T#d76O)Z29E+!?7ha6$Cp=RL0Z3%w2QvfWKgEmFGMN#04*zD%^E{C&pt z#MNg(&WF>WcOX3dS2|AAkMrl{_dXjL8cL^L!Mpa!?+T#M`A>r}mk@N&PI<>Q7tjhm zmE(~^UcrCtFl!2(++W#;rE9R!n%_d#M~rvY0X>z2gWe~ z;3C2L5R>^!5x*hnvrmp&>B;wIvlT%~iYZwRMO$pkv6%f3>be>`=xbr`{vR^-zi{)Ci-FBv?ymE6CYYs! zUrX}Qvam7er0ohZek(063~8RFb9V&}yUKWECa6 zvWozDnfwJJa6yb4{o8q$8}8?Kg#;X}Am&bxgRMnW3MWP~^WCPVctAs3bg?G+sCrLv zP5oJn^_QFZG3iRI>FOS*2t7Odrv@}Mv-ZX3&+FYhA(eg*_?Y)TFZd0i((hffQV`on z+(;tmA3_5qhQ8TZ4vwaWi{iB~x6r)Y#bvT9M_BHBDu$Yj|M{8oJ4s#|r{Hr&cOo^s?oc$vm;~s>2C{ZH| zmk-Enqq^Tm4ucc{uCy~rwFE{CyA133Rr4YCfPg*Cj!}>+TeC&$!=?8WBNewa>zbRJ)4$*}A9`-;eHX>n@>XmYobdHHJm2@n3*PrB<<;2U(Ccw7zPfPBRavMUf zmKpD{A0s@2{b89oeW~t-&Fu*mV=N`46IG4?#gZUcD^Z9OvhX@XhuKQk#W9trmV1p6 zE(?vOx7aw=8j|ME*D$wJ(s+|Ez%y|nKh17os6`^YUUUlP7iNPhtYEu|UB<=rz3l1P z_qERUrhG1-#BeoQULe(y*#>3~@RaC;B}`6Q;jEd6HxTdqiqM*j4>m+cv03|#AZ%%V;ie5{F(^m?(tG#F|6 zN0ybdv4wdCLIxTR#v_i72Zo`aG1%iSZ{b*PPZmhvh?*%gBP;9EbB3Ug$@LyL{3y1; zf47bmsz2ceXFL8hH7GbDgF^xbdGpG@N&M54KS}&WxY}HThd^qHwM$&6GH!nmg$MNQ zrWTkzn(1S3uezU{i!$zOO*8?6&_Jt9UA0UnXx@z zzFfn?o_gL-~N*W~R1Az0TAh84{Ji3Uqqzq<)^KV}V@;?o%Xk|p)h|Jf^)Ac26@fC#^bV)rv zyi4B8#uf1T=6(}Bq@daD?(Y8HGd$r_kKXfZTA~>-dj_oxL!4oGxywt7WcSRMCZ9p* z=9gJK7QCgeT*GJtN53%5|4)4WBI}~>e`gjX^z6j@pZ-ZS_faDL`3n5=4fld!?ay~R zT7TyMqPehL5P1Fhrk?a)L*1gF^=7=GZ#B0g!<8bxdWiHV zm_V(a?a8KU#*a69{tPi!;vY`3Kb`wlf#SMdi71asGh1Dy`11{Dzu3cLV+S7<@&{bP z=^%5Z2;}GI@8}0*=fJ&(zs-j1wRG>wZS@r3Z7c+PTo;eJaWVW~Tj9x(k3~|hgD;L5 z6mgXq@VG^}BreR>)YXyen1UNn(k$E5Tlic*XMlr)lAsQ|?das-@LEPe`nB92qWZrrmo$y>^n1Y& z+s0b+b#PFkv2*yPnu~TZZtXqlf{XXa$va*pJ46lO;B<(%QU#J8?8c_eMGOW>-})zk ziGwpN^IE3yLBccr;8qin~iM6y5Fz{$LHfC5nScP zo*LP>tasT;9m7T9&-BSm82%NJ<=1jD!tRe)`FZrZglAGew4QR|;NX8bArf!R+3Bz+ zYk`a-zG^(1ZSGSDy%4P?{P=&Ke5-XO^S=+u1;6~))Z_S=-T4Gn(zWoLNn#>L#<^N0*1-92Pon%`VmNe9yPkRv{fIzV#fe1##z_;ORneq3P7p<1{&x;O|B?k z@e!~^RKOO6djq=}BKulSTHevY!N%V4zbbxdYwh>1w`q})6lvkv;7LXBB&0>GK}hIg zfZue#O|oQ(y?E~bDibL7sJsuR9-^5i4$k)fs&wZ|&SSt1BKd9s^JO!FQj(E@oj5rC zSJ63NGRga*w-O!A_191$KA4(IK3rRf&TG2&Pj|!hlUbh_sx3do!8v}cSl+pC?_b?f z9sEL<{E-as13Gw*!{sL?$1i5Vr_RT>q2WYMR`HJ&VK}^y4e-wRd;h=HU$I&Km!Aj; z3GwhK#OUfIrz9s|EEO0x?_BY9apJpoLmvn^?flA+%`k{zR?kqMVt?<*epTiyoM{i#Bxy)S_t*O>=pFlqBH1S*5i#H#bu*g`Tsn-`eP5l)HEi z_+dRBM1zYgXI7^JPpZuF0c4GJ8{N=TJgBjm%S6id@4Gj;ZDQ6UqN58hajDHfZwWF>P+lGs%sOX{$Nvlu}E)^A3xvT8@sI{Q;nTNQzcukoR90J$M z@ZfXh)av`u-o8Cvr|7K(Hzwfa<-OR};NW~!4|$+oy^AzQ9Gj}7qL`zV7Q)T5g9X@a z6?V|wA6Z`jX>jkLDr82Dl^!+Dr}O1`xe zZ~%&dp5C}JC@E=O%u1`frCKRdzH*?uTb7MnWNxBJnnjKA+_0pb#-Bj3l`rSwtr{Y~sz_2S(VBdckh{4kv=xrA*KPe{JfqyL#2tU6coUs^l7w4Z z$2Ij$#yXzkO3_I(aeKo)>sw zU~q6An)L9As+C`fP5VM)QK!?~m!R);`Ub6so6lAxcPn`_Rep5<0KJ;__p9vwsj z=TfV#trb2nSHli^Ck*wX1sSXUSNP=KFvCd;!-G6J__<-~9pD-Ms{% za)*eW5k7u?H+T1SMO77*ckkb4y?^)q-MhXkS692EqM{%M=O1t}%?eaBl@pU+R#op3 zN2!g!SS>DF*``bL-iDH04>6JlGuX$po3U#Xg?iua2HHcJjVHE*B{%ALVz8Uq4 z3;$IQU&6G_B1ik$a7*lHmf`iWur{p z!cLu?lJheZ zi431U5OA70a+vlfaH}!u#_ro`X-!1tbX)OS10~-e=-JkDbmVesCL<%W-PrHAI=WZ7 z)v#b9XIY4>0t^AWyG+kLyBIK$DRoZX&&^=}vqbGc0rFT4z2WHB-)Z;xQ1S;We*W5( zxnKcBa_+Qw_NlQM7meT|tc$*{!r{G(85(L&70Qeau{Y8j|q+`+1-a?fPdt ztE}hGV%he~HcWmbS;Oz~9BqJ8J|cV$M}cmuyIZ;qfHL8>1hm`2*KOd8um3+ieLL}nSUyZ=i zLU5>NnD50Bc^&j3FsOEShv71M8u7cTr>p52{+~YS{2L5{ed)u8tGv5g{fvXaWyUqZzFi*K(ko;@DUmnU)2J-Xq5m53k)M@Gi6*_YW2@(`= zbTQne*|QkQhaRYO-laQ>7eRP8HMYGO=@XtOTHM?`ZP_SsKiHUCpU-L<`TFYAujzcW zT3O69sG+5@36_#FozENK&AkU`fG9N^K47(H7;HD55FzTSG8#N{jhvm)HZ|BPS)i{X6Eaw4;_wJp%fDHE*Fv9opRTgy?QlQpeT%YJR(YWm+Up+1}C_53$D#`)J7 z1&{oHGLZduX#c+p(f=PCnNI#R#|0JuP>+4~7!Wnxxs#BXn24u+$tUK=j~~@XE}KhA zPv#VyWeGoBrekAcd;DC+%iJ|sogiHk79Ah|a|(KwoFpbSwfK_HD;b&3YJ?QfM73^} zVGuXsFy4Q_%~|{44j;z%Mv%1hnU@3=Rf&hBwRK4uI=Wc1L8j(;PSI#uoz3nUZUBv` ztu?r}YKe9_Sy-j?&h04k*o3PmC?W)2y3h9G+(fL;%^aCUWNu{x97N$6CW=eUSPA@c*H@g@s!&z073 z!i2cpl+S+us58w=`=Pbt(XY7d)Kc9!oen<^lT%v1@3>f3vaT1&RxFHJPF7piQ|ZOb zlf`Dh@|B6dY(*JaKuX>ne2)P^52*XX?~0K#RI3m?uyN*aS9}{6x```G%3DnD*f&Bx z+c7iq08^Hg<%?8+rc2s6I+CBLh>($yO(M9wy1pD$e_Bpmg_ODYTw4fArnz1fLVo^E z0uCv7lchzH_Si3kW%@ZDn>y^r||d z4#MfVRQPr0I-9oQ{tBzNAwh@8vKTwN7N$RR8cX|afc0D?4vYwtm|jB`MtH#>ZpUQ_Q_YuIAR(9C>U=xcb^aafnsY zR27@8Y$LA`0*{Dh_?G--GP30|BbjdXDr6$;(ihnU7Y-9mO~N5L%Ja_^mxNPKTz6{r zI1aXT^NbJV#%eySNjU!+h!7#=i0e{^l&k2fv1;UcPU6YLFs80rm#%3J`RP=@0FqOV zVFNp&gAG{dpyr{EO*Tww`Ywkafes7O-mee}L*mZ>6`%zZi8G48ALOvWlC31&U4iI zJV%yXKazTARo}y+Q10FQ??WmtJ z8TvbBe*c!^mLa;TSxopzj*|1P`|BqLD=Zvpj2e>qsgv`ARjrvc&cnui6xglnLLZSS zv7fJee`&rUyet!Bq?9ph}Ft$l&0=GuRO6Hh~^&3qS-sUIwP-o5?6Ig7T ztp6>KV!Kw9deXuxxa66Px;&G%^5B@+!6igYvheFrQ=P}>!!Txv%YD?k)G;{y=21;? zCDELOaKE{@Crdr&>$Ax903xqJ=4DkAAymH7Rw}rRu4Q%KdYG~HL)1OnU6ptbQl<{~ z@F?y{=GWc7S)U-R73)&useXkz0SzIY=9R-U{0Jp*QPHRY&5eyNC1En{P@sWi~$1r&(B?141`mW)=6KTjfoe z2SokD3V|9&;$q<@;@G>hj+^~UVBl2kd-S2MJ634A+~9Pgs;L!bBJDg)tO%rY;WO~p zrq?+{wl_8kjVjdVowo(5t=gf|OiS!KIyrW&Lpd_0$1-=XH#iHgY#ig_g+1VR)9GVW zFCMoMUifX{i!o`csh9{RIY6e#pz|$Pmew#T=Q9{lfEqNg-s%3as&5|KEvQ{-s+Ff6 zQii^g`ohs|YzDV1<7DOrxcY28qICYwt8Lcqz4j$%&Ko37o6{v-7Ce?!_~LMlf`#83 ztdH8;*d`|tuHVeH!DZp}C{fv@!8IV~V_34$!rA1OmuhPuPYmCf_BP%0?@hRtNr~79 zPpk5s4K4K6h8?of@ zgr85&QXiUk-aRKDPi~oi<{qIuDAKQcQAC7?38Xfh(58Rj5&-5CV%~(W|~+ zYchMOlgvmXQaIGb>MbFh^^s>B?BPL1c7~lpH#Y8UoIFaHYl+`=7)CC*Q$s6DUnG~X z8&WvjxQUBo))*n1&}+K9kPwyUs|GM-G||j#p;w9)2&%fwTAL+gi5~DXPw(Ei5u}cr zlzk&(NNHPCkElagsk~M(q8Up>711KkzrI*a$!Re0(^+bQ@G9}_Im$gohNO(5w5WSi zx+{ja%Aj0trpCPq@;029&%?b|duHU)mMIyBpQqrWJ=5!-=P!fZ*OA4VwQ8O385FuS zvOO|nS&a7M73o9V+gpboc6PCy%vg?L&T3Ne(f2InW8w__ zF`#r~0czZSnb&fL{SvHx`5BEO6?Q974$VO$Lt-dzR{T09zBj28a!|IPhGZrnU|eEA z7JA^DnB)Qd;DPQIG~@8(!!iiC*Ije8_=Bf*5YZEfch9J5rJ)P7Vq|owt_NN22^ZthRK}C`?D>~k{o|JGCQEIDckM1`89A_5PY`A( zjTFYe+mSuZ<}Nwca~&yE*Tk@JjwYOzBjs3mt4M5kC(Aq$C(*=9-FG7FhFFvXZ1;T} zR(P$3UJ~v&QX)W zxEn~#=k%PNeP;Sp<_U=aiEC2m-RZsq3hYLR3%e6N z7)5*R>^$S9wb56f6Ua%-3^p-Mj0!1@5L#7dqQQSis<=)ijnT`hvvV>EF zyiO0JIkek;#+wZm?(QH&da8@xt%pKWsCkMLTIP3aN1;C-Pc9i6g6k$FN@k`3hgSR* z)%~B<>z1gQW#Mc^rF)#zR7q~wvuC9uy_yfN z260IexI>6=7F^;2XtRdGIKF>N17sA^QJW=EPj-hnjGKf`Ad|{@k~KoogRHo)(c=E= z;t&Co#aaU1+ks0vo2yV)RlMpf&)vADlW&QL6Y-i_qr{mt90yelvPs`-kap>68k+V68)?GF3!?uXa8-17G zBWArr+;6$9W1^zw)0eRh+b5>EXjZP;1PW=4N6wyxo1yUek~?tRc)cH9@8r=E2_K|# z{q-wiy*PPwH(m&N{$d`!T_O-uVR+yCmUtyI$pP2M5gaRwd@}Dz&2P?~0qv9i3NwE? zPi>0C(Q0E8=_@$bNOL+=V-2OJ@hs89w`S_qg$lXTrDPTPGeg=wV}2W2Ip6zcvMwVJRNR(mp0^* zBS~UuYDElu&1gQB%8lE?5me(aVJIl(mi1kb+u{2I${x>c;fv?+3U&54s)m5o(2l+FVcg;jSt6<@$cj`YAQ)iJS9Lz;}RvmweNh*k-mGIiF z$>Wx#S#N@mS-cdkImUg(^e!ReSwKz7=5Blg8CHlbsRN_$eJ*=w0SvL^=BQJB-B9(b zY1{3P2Y*zrbwV;RiX7S7!%P8yZIOK*L177nSE+>?Zjv zb}05MMQhH_@$rIZDv|Q3{KS?A<~b-Mh}~D4!J+i>>TLnz=0dTS)oFUa7GAg^Otb7c`Ic6$)4v_-W$cWaJhO zyP~ELu{PEjAH#cMC(h%^Q3KN(X$a2=g+LNWG|&1v9;tAW_3kdrfaR*L1PE%|kG8fS zhJx&R_>+~+=RbqeWH<~9?zU3$7Q#u?7Q1v-GhVoF#isNG=ycoRm^YPjGrr+WP040vX)AHV=*FFIb6LzlZ*W90CDF3SY~fA@#NrM`xb4`qRsN)t z)uH^VQXs17*q;IJO~|M0hB%PskGt7#(Ob}R59Z(VNYI}aZ*Z0EGo~PAWN2lJrO4?#`>iDJw zujR2#vqSG)1B&e-DfqPk)+e=G-QvTV@^g&m2=lq(lnX3C_RuW(ifFH1k{Jc;N^Gqi zs|iM8JV#8xQDUqjpTmZt1BxH zOGrCg1eRcjK7zDGf_57V!1~P8m^5W&&A+FbTsGVT9?Qs_C8Zp-ke7;%l0 zk$buhy&A7J(A9j*@bkcY>}j!+br!~+j5U(ld!9;?7drk(WV@*(2r0~=4Hakh%MIad z^Cu-@q(8l5Fc)WZy0QH`D=Hj<5nBwLsdjb8R%u+bD=J?oakPLS5__d1PMZYxttJ;$ z+nSb_*35MxlOyep*G3@EN^P|f+geR#@HHP(qv|_lC$Y*cddF`;eG;?T?=4tX)gI}? zfoubzP9_MM*der0a&5fweH;8v#IT7Mc1F8olI$19<#=Q-&Gkehs5Ey&)uxcJy^Ek; zoSG)VI&ucM_aJamFMcBo*R14_GJIe`Jp?z&Csi39YHluM=r~|sNHBxEm*8jAci;1b z`%%$Au^nRPHRWVQ6fg5N>9XGoEhpu(Jnf3^Lox24_LcG;#c?J@t0<)FQ4cup;}I`t zoF8J-Kqy~0jSnj*X)7~)@N(Z|4G!SaqBx&ECF3qH^BU&)3w2iag6-bL)KFd? z75hvR3NugIn)Y5vAz~n;ImxuXl$s+xQ=a@jFE`f=5m+1MS8k*xGup&8kBlCPPMX56 zwxAMm<)3(N{wFLlmoBF#Sd7Mg%g7qK%9QY-G1uXROqRx>Z_7Qf)wA}z$TfDiex5#=*jERA=`WCi4ECdkOvu{uA3yoYecn1%T? z$P)$b-@HY0nz32c;OZ|5$8-)sD+1x|r7E(7@bMB~sDCR*Q zDy0Ep!3ouLv!2JmQg`?F8TAjT&B%)Mnxs)dI-ATFVvvaZ+6ve=dUdORnx_CO(^G!F z35j~h#-^J>CvA?MsqVzZ2;X5drS7+&sLZGcpy8#&yA4zQYZU|bvdOPheB#=amL zgyYSIm&gNs_#xlYZtgc-=X5zMGVB&dF6EYMS zG%Ci?9MbPqc;6;z;qYa6F)y5sE*P(VcMZpxmv=H{`NX9sH8{BY+cNe`n&780F(_;xr6zGByuj#OH%HRW#|SkQ*BlRT4& z3x@dD8c#1-URGu^K?cK&`>&*N&NT5cP@4{_nF~%XqPCwyf9L%ZGaE26!FKB;Q6NBDLFgnOvPN?ZEJU5~B$ z{XCDOt=_|h%e+ql@cs~1?;jfnA0Mk1=_p@)R0Z8|>G@odxS+v-b?!Y8i32+c>Ewsw zEz)bZA|cVfVl6$>QjL~`*_mrid>fmIB!O-aye`T7(ZI>7+?2I!dGQEzVQioR*W6ll^O1ih!d7wfzNBR4O(qrBs<}pQ1bxUi~ zb+&1ko+vI83<>m=KZ@KH_srSo~ubqxlGyqk{C#)>AL*Jam6J73@{B;%JgaHN-YKGy@t zI&xF6{PC+_f1S~>nN!LVJp!lq>L!zYnpT{`!VF-U%N=E5>gnk<3}3U26I%4Ar>A1M z-g0$6sKT6RA7}Omks~N>;PNUD)ZVG^_F-Gx8ube>YJN;sN#~<{1S9xKh^>(_ zN3fLYy_PX|I;1JFSJp9&C*-2SG9K7uJ18sDj6e zXDuuDNP8)|`-=peO7xNjDSuW-6KpXG43OcGJa}%g~Z1kO*>yEI)Pp-)N{16v)p^xmh$CXxBOK|5mzLI z<#&DAY)}InEiWhPzW-CY*DfPoZ+!@MNgSL48?g)r@cHMU$}QKps6^n&bdBj`h>o#m zR3i+8dWW8f91oYMST&wiZBAvew?qO0n(vRWv+NijpPIhxqPe|hi>TVi%hZ9Ti>)%zT0dhO%LK~j}@tZ|Th%CK%q+$!(OPRT=LcSFdUMXdSZ zFo(&c9%t!3O@R@@YP_*~dsP0~pRhouCiwDTb@dUr;QR-p>>Vm1eS?|^RJ<@iPt&O z8m(d-h4qKDi{+c#NVF}g31hu?uEH&*w8Q7P z`L$H)rAphASa$3Znx||!NUOTfn9Jn24BbIqx1mGG=sJ;U_8v~)yeX7U$Uo`s>FQRk z!Kg3U9sdQ5S>F6S;!dz`v>|nHb;^6TVZurY1c|P%4f;-+XiJcB2#^i++WJ}Pe5NnE zZ)=RgJ9a;oS^a=~Uba9nzI{A8`WkRu=0`BZ)!;}1)Mi0Oz5RXhl_RolWAiP-&B@o( zkELcOY2?D6D3>fYH*<8`!kF8H6`jWH6rD)ONh&vHxtT?ZRZ8ob%Oy$A>C(Ac>Q zzT^;nuR1T2UdpVp0H1eWRpT!=DImyfVRhc)?wTZTcg-3)0Ne}+PP5Fv`wsB-taiB< zG?GhM8w7JgR0;G?DK*Icye?&Lk#jml$?JJlhmE3|D$sFqGeJ5-Pz*q6gdq#tx+1UPFM9}!#)yx=Cx>*Ak=n)C?&oFrt3uB9&Z=VM z*|DZpwxu6#3F*q7~8%J9FmD*@&gVvi8r`|3S!~0pf@f9zy43U{(BllMbyb5M3 z`b|3j5BA6TtwBW8$a~W& zU$M@^S3?LxMk82daeCLYoiRC?_W0pV=ZjreH=@fuYcn_QW=veu*RjGA3~VplN7L-H;vvVw@PW(&M-;| zoNlbsRSb90Hyh7Q(+ySOi{+z6Eh4{Q&MMbV9a4sr*IW-8;_1@RjU;vgEMCf=hvNFV zOY=t@HiuVc*Ve@+R*@ezlX^i|vGxRL0aW<%(A1P-uWnZ~%M>~y;O|+B+$Ho%WxTsu z1h2O&L*~m(XuALSfjgRV*UMrdoCw*KL#>oZT>E)I#mbKC)@ZYPi1lzYeK{hb`+jv> zgh^bJs1>ukJQm%g+$8$K(PYb@mmRhuR_N+ovmuZ5Iys@xRNkkR=zxF>%JzhxZ1Pi~ zGfL^T@U!fI39-JkT=TdTt^J#wf(KyZkE`FWsRyjHkrwI4ZtJ}H=kM=^cm60F=g)_2 z){kGZoET%`&aNvhJ)fdyzB*F-iF>_5VJ(SVtu?nRE?*;WW+ejc);&h|t&O|LTG1BNk~48^aOqdFd(kSe=-&YBC?r*54bNBGe$3 z%S;_8eYEV?Y`!0e0i|%aDEiT&D!^<#}STs0P23XdA0?C{)gSYgk>vButSIN+2ymkt7Df&sw8}( zKsrE#?5wbZOLl2#@r^$2A{om3ETcZAhF=BN-eDy_nTyO+`=`GAv&cD|!FE3d`xqgk zF~!cBDVDbc{^CO8^os>5Z|PV~#FRL{L?pzy=RM<Ia-)r~Xd)ol=}R&IJFv403ZCWQbGTO2 zdh7+2FJ_a4yhpZ2$iTEFv1_OKD;5#@C<_rUvHR?pvdG9m(Kih6Xg0jfsALno*Si0b zu1b-@(NC_4NN^_SJv0MgQmCtP8Lr|2~LfkB*Lp zh90s!dNc~mD6B4Yl+H6XCG!dM@Sx(RmHDhkCA#!MwF>MO=dI7qCdbDguO)g0-N5=F ze7Ygx`x)$K44G}?ERr*tkD9ug#u;j{wG}yBM1}_hZ>+3tF)g{fyIs|IuPiw%7ojT0QNn-3d%QV}@Uz`ugyx_bJy^o>rv*e0SuhPcCJIl$7xRIzB@*Sar#W zYHwBiv`Ed&m@b8bdgPh%WBxUd)d6hZ?7cjLYNZXd3$!2aj()eE2w)Kub!Las3~e$4 zW*g#kdk}{3rUsB`gELA=S*y=hBX4K1r7FpnLUIZk!+iUD*p;OeMZ1xzkPYjIhzLdf z3N;!iz&*2UOjN@0zWDn#-9t>tQ=aPPnSM~vUK^}F&Fa?IK!@5C;YqxqprFVED0mKm z6_vk{uK`tdh+HBRhl7hW^4T5Wz^fd)(Wq->}9`f3+>&@JGjN$JTTa&$7drlR8>iU zH}*;HW&2u-T2YTXK3+S%9}xv4r#sqR4@f|7=*sg+4{0S(r&^9LX8?pQ~v$(&BHGTe^1!&_D;gBPU0AHV9%uJe#pYJN-{9gtZ`$W3J{N&(CfrqF#q*$b#yjDV`8qTC4oDmxYF^vGD|K zZhn3Vt8|BG#=g|6*T`p8WT)e(^pk(y8KB{KAFA5>*uv*z>gh}IKDIG>muynda0A-0k392?HV0~7(keF)%!Klr%q^HJCJYs->*M1sT+-GaQKCUH zL84rQ8Vjm#vS}7DlWmY5cFv?iIrY4!;6}Imnx7s5P~~xt7v1twgV`p_8Ffx)^2s5E z@zD=sfMYK;Y%*PO_OY+Cz0b&){K#|l`-jLMp>!TDZuuIyFTf%Y|1ZqO`uY$NSguY~ zZK;?&efk4P-so-2e)9{;R!Y%A0^$e5N6Han0F*ascAAeHd`!t{#IBa%_&oPhcLLw; z-d@RgOC0axB`HabTBQv=PEJl3*Hhc6Djx(6-}ekL$TgDl*k_Z1?E01J%7JiLXv!n7 zn)na|ASb@@a1C)bV?=tP{`m}HoRR=v-ZALXwM;P?#$hE%4S#>7Cb*{ne=2rDWUb5AVI5^R7`*>E&s3#Pj7@G?#HQ z!Gr8bZctEEnynq&dm46w?Beo3ZMwS@mV=O#7L+`OabEn@4hY<6&6+EB$FyznNUx@7 ztZi2)6b-U45$WU!s z+D*E!lWhQw=P~NZj}(8N^^*2|1G!HmAcj;}VLyYV`g12rKMQfpQCE5I-f;TGDdlF2 z`;^@&+5=v4)xpm%K%WD~xb^eapvFCR2EP3<9R8lITbDmy>qqo0ZQR~2SJMgZsV1$s zy+W9MSS|`79}_}_P2T81P;nQWy7+|1n?q0MN6O9fQK;rq46?fRSpCU(0XdWRebHp9 z*WPX?tR#YQQ0UJZ&t|R?Y$hFa&*ZSkuwt!LK;zxDbiKy-7GFX<0o%Qtt9BC}8pvL; zQ=lVC%cYz4-n=$Hr>UoJgn;n;(VB8*^=9-}DLaeO7+k#*9j>=^Y^>&is3r~aO_icpo_^@l9WeRU0?(^rF!|7AUZ%1SV2iDal#I4kt%tJkz25s9~1pc6TmsKUgl23Oyl6uL*B4YBE<;9v-#*yT;mMg$O& z+}_qXOm53>TN}<5^;wm^v?PYLU3@RA9y~!n5EFm6rmSVbJ$HyE&NO-H#XmIHWd3p* zs@?qV^(Oej@d=vfKYVsF>HSgP%;y{%Qwet2wD@#h9U(7~Gv#NidE`p$5ghBwG@z@J zuO!KSLUfSLijF`W|sIY6*XavREQDOqIt?&O#9Iw z88|pDiKyc3>$FJO-&a5ztgd`#8C-a%6>XuAGSFnGKi|owUFf-j{CZ5C@No8C_cgCB zW2^P23I+zzguMW@Cgm*c&u`!jlX=@ki=EK&wH)tKHHS%>w#?%%&JGFI=9WtDS;`;kJoHqFMv!!xKY;4u9n+TS{cI$V6>^od|259d!i z5`sHWU^Xjy_)T8R#Zp!Y-Bu#de1c<|a0&VR`S~UFboTW7Zr8#Z58471di{qB3=y0C zN?wN>0-RB_mnR--U{GtfWp7uqGsJ2DLhXNQ0h5mVWsrS6QIc*sql4BD#+UUb#K*p%HO?HJcKoQ43=72CelOxyir`CsASAaV<<_ zQYjjmz}vg3DXI;U3e`eAK05)h=O%<5?A^~Mp&o)B#7%?Y0fzw^(h+i`HgB@u@i36$ zwCLgH(so4pG5QOjYvy-I9c+njYoz!d3%RWg(Qad0$g1j$jd_lzPC3r_m_Hp{pd;*e z?SoF@Z>1Xu-Yvz(;-P-p6+SeF9=>Z*A;l=s(5}GC14;?PjgvGHnMKyooiwzbI?>4Q zHK(s1J0EkDE&~?ZLV??8VD%m!VZ(MJ$a-JxBj2p*`F0P$8AM;7Qtw9KR)y22hnAQl z?;Wo+^{x>5K7XDo)|j$>tYhKhV#U-TZeGw-xOa}jut_r7EJ?tKj>l}^y|2&7@>;Q8 zq1; zz6m0M#_Z#kE2mGOT-*euYB;YIHO&~i1U|bo-L_yVi_Pe!0t<+^W~MWPPe?koS&-C)!7*l+?8qMlWZ`9`3u7JT!d&OGk2`gms^M?J`g z0e9tilF??kVDNA-b2qwclc4dqu4#L!-(h9`NrvKB<&S9Nhqep)h5FT2>?WE9 zUl~5hVpO`Rovp;d=WmP5gIA55cIF;^5Brg8C30@k*?F53I=|n)g;N&w$?GkLSQxhu z?1yNVDaZ3*M@#4PCO1>7Qk0T%>fB^N5@eZOboVZ0MX)@B@A`obES`_Yp008(1-ft7 zjKK_M9h$ppyM0=eJPFv#@_YEo)+U7}=Zx9C9@z z;DQ$vbA6pyP;SeYRnO$NoZ3{aGdulHg?gHT=uYc9DAuI(by+3F zR2tSFF27U;P_yv`WsN@O(;Bee%juCi+!A+0JHM|`nwpwo2-0jJSjaa$Om2jX*I8_d zHI|MyuDx7E3>}eGZyzG8jcHzPPaTSf`flNCPFI-aZ)mKJ?e4IoO)-0>KxQ~rwxsOz zfkFlz!6MyV)_5?ejR&(W2;$Ac-&>yo(G&}~(kgb3yuR8&jZAd3k6o%@kFlVFOLI^~ zuSk?`E33HN=Y+JeVY2V>7+7msvtiq=0aCDCqNwr#yP~OPjObc*lmG-ozvG)+Ab*}O zUe(L z;eK>K-Xy>C<<)e$U@5w3pA$u_@6y1;Ba7*+Ja&#aS@O0O%38|(eXQ$hF=X|T zI^aX?l?{piAl(p!IEIN#m#$>vKmW^F=rh^yWS!l~lCgJEcc(nekopdT1^ob|KP3;% zw3iL#(lxL5jP8t{_O*FWWRTejNj)t;{_d}qn%lWVVEq8^!kgQLteb$v}tV zQ<q;!ILT($!>{SywM zD(dsEAdm(i2hcd!vdoxIIks>pC_VxfRE8hpS1x|jpFp7CBZ$=zguJuJ40_2P_0Dz* zveBk|w#F#~WOY95f@|mZuzeF4D(occ?l7E*TgzpZwT<1od)!8Q&HdG0r?JeOe&rHS za2VnLSud0=_lgXi?l$n;x0ILBi|$H1xL5XEWk_zYr?E2V#xcu2zlSI|B?P0QTtYSH?{48$$L!sg z128~A<8p(^vy=|IvcmiL*eO0d_PLu2%RI?zGFqVwqW#t3{^DWiY%462t5`8X%gq{2 zbeTQ6TZbEuab|N<46vrdTh_;cF(!b)E}((cNq$KlpNP>P44uAP_fY&A7^OE z&`t63{T;Y`Hx+z;_?d=A6fAp*Wav2_%J3}0`P`%Egbj8nCOjM-R8+09)oo0ShXCN6 zMQmrK;RJtMBrKT0U+_WI)+nGavhsD9NFn={R+vJ%DRSeJHzp@Ie@glOuqw z8YS~OG&u>G-P@A%Tf`njy1Rf%PO0^Nv@X&8*A5f@v#CG>ZhUHhZ#dJ1i6U@wtbS`I zwM8>uBZk)>uziQc{IvWRUmnO{^)sJ0Z4 zUX^%BHJ;&=zG+ymk*BdUkSVWUy#cuM@|E5P9^!TNwPetFK9VmR{SCTPozk=e0j;g5*Sea; zMjK}(q~yahEf35RMq#Akxn)Wen>G+!cQ)%#8QDg~dxYq_ecNsxbneu;_^oMX^$HDx1SIQ#a7T zH@U>RIty1FE)fy=&Su&a))$-mnqU6lu>LMl%tb~#+9teQ*2jHwoI_OUTDP8AQPo09 zr3H{u?~O0i&Eosy(mU6RzwizyV@MSCrR!_IlvhLb2)aOxn@u7UznHt#7IHp(sF6Z9 z`f(>YOThNqcN>9H+T-#3idP`#D6G%)xuTP23i8HW)THMv>|f1XIyuQ&xdyp1!vck1 z3*XzRZw9O`jwdD&E~A)e%f}QM8(xz<37ySs#gi$@b@v#&RLth3-N%A>4b-yBEkJHsCBD<3c`wei&f4|kURO7jptj0(V0W1_Dg&CY&0D9zhJLHaj1EZn`5fsw(pqxZsHyfLV|=~M#*9w z$*eat;_f$hZsf0#(b!<8_n_`mHkQ!`Ro6i>=ED+y#yQC!>)&Tz7k~cQcahJolPQ`_ zbExR>fXB=L$i&;=AFd4-MPvOz$yq%c%BZ@gjqvuGdZ^*KFzXJ{?T)H9pQ@~Nq4QZ7 z&wm<5yVjR^vV16}Q|I04HrO{|hxHFR7A)ICRoeZhk}%I1FH#6PbM?kW5LzvkZHj#l zTcTGU1q&#Z>~}0!$;TMgJB8jSM=`6_l)zxwt}Nzt9e~bHchVp2^|HXQ8kaoqrmJXr zs8N0?6f!^YD*cs}QwVJ3Z?3z^9 ztGfiYLmygox^fWSzo=VlU5VR+KjzZizX%Mqw^R8GVMadqpr&^5GJEE^8qwQHMKrIg z?r{)NHu0w<1O%MK9wI9B?(*<-P6(&xKEGWtyJfr{FlJ16A;l1L_z8Tj_G5MM(I=H4 z-vXb;J^#j{a}Vx)U=_b5UZpv4u=*wC#B1@MH1=|?l;gIpmBvkDQJb3|ud9ei)ZZ1* zdUviS{g#(tPvWIA!>_M?sPH-1WqzbMT%YnVi~RijB|O!P+i;jhwJ{?*JM7i2o#?yu zk?ODwtP1#Q)<?|6%*_Pa`m&T67llZUT9AR*Q;jz z;wU=5MU2y__Io zCa5^SE7Q-`;coPv``$4Qs1L@XCd)INPU&x!yTP=!snS;0>h3(0v%Cz%c(A#3xO%y0 zn~3SJQURF`-)m&EnC^K<@1sD%&53b|VC_oyS+U~wlFoDJMp)}-0&ds5udw*!^Hf!mPyR-@a zl-7$Z{8bCkza-|RMlrT~sJb85+_cdF?4H zJlkOuW>GG=yEsyp0jk_4BIL44U+i06uTWyyx457-? zRkErSxu0T&c`tiBI1=D^r~>qSGVAlB`!-fXj$Nnf#bUaT&-ve%h<63TUm8fjAwSa} z>-HzBv`dG9qR)@~=KLV;FU&?F63vAxI4mJSGrxkvF;ILdL+o;eyt1hfkqLz`v9lZC zhbvPFG`T0ECyBX0B}PYxgyAX1b`f@r0{Z}Y-#`t7Rjc4LtQosW$oEmBC(;6XIDfe% zp(FV|Jt(CNr!;L3bXjvcJ-LRkI3as5El}aqLO}Hzvn$<83#s)$;2@N-t^pyhT=uA2 z5E_LS8mn%d^uIsH)kq%(q}I5V5RIo_9{ViUx;6?vdSr>Guu&M%(iy`N3C}01u+4>K z68nw@s;+V_4iLOL#Bv`$qTXm$jGF8O3(OfMrpDWf#YU{GM&6#=p-J^XBk~?SebedA z640S4Y#yo|OwUuPw5ISe(Ruail#+nccxMX~Y3|?g2KC0O7KP}y293|2Sl>=RZCx4+ zqS)=SMo#*MPk9}z096|Or}fFeBWD#!=Zn!*qRqTx_iZE~WXOWGN;B2F7TCt*mL8|9>2!f&U|1`K-oA(tjw#92J*m@Ax zIaGXYOLSN?QtJnnV=FR}Q8PyF`M~=y|I0K*$(xgv^{21jjPGAxST&nrlM1eO80l>Y zHloI$*xnfpIG;Xt->h@T@7LqYw;35kH7y=1lH&x-C%<>7phu@l55;;>(Vxe)9_f}KJN+h?(PVOu{nU`jM0$_yH3s$Fm)00@WTOsbqtulZ38s(f$| zPvY0b4~K$ns$+$%>@wZi>@AC1VVj@^qyZAb!$D;#*z!`2vUpt3t8}1kCx3(EVJOul zVlqUDRXO~_4s|hE9H?@G=!RTgW^R1ncEZtd&PGHplWjGdu8G5-rUWv+QQAX8TkF1M zVx{q{-9$kO%t%{tV#+pnn|}?&ys}v+DKLue*?f#oXj)#I$hN68(2L1G)5U z(5=C8-N#BYj6kqWp%8R_EI_?v-#<<~mAOz05%it=?lvMTTkE|07JLbX6Ye>06T6WM zgKn!wIXgsNvTXZF;?s>b{M1#=LfNGt78ZsmBa^~BPYvgI9fOJ?4_3RL2B)`I zJsrS^fSi?C0TQ7--U?q;bfqHxdXZ1PO<`RR&8f?qg!K7nz7P+O9IeXEx1P<@4mBfh zm-{zL1;tOV%s@9bUssKM7IlOY&kt_$m+!NJ=|1*kBCJh+kPUn%^KA{{Th%E58n*98 z8ag5t)Yxt6aXitpw|O1Ueso#&o;Yq9goEQ0+Sv6V#MN0&Zh4}X4JWq4UQKeL@u`(wPy)3J5n$rNq5SW-lJY_-hg6Li2B z7>k8QRNU)sxe>t>e#K$Epp4f1@H3bN^7ZD95H67hnp|Te&#UMj1YkJ?CJ*Ok95+N4 zFz>*$TA&X_CV%dZ=kNHs)ei_V&{(Wnak6Oe$U48R3Uqd>`If_x_11H&z|J9;Ip)}M zurAsTH(skwIObUx&lgOLL^#d;nBRl)5&lR>g7q9>UkGQIe4Nc1DJJucJ6rDF@%mdE z*l5g$dA{diWquTVQ(|BbXq@mfFz}UV$XbFa^fR5Rw1D*+p%C>=5lc*2b)YMpkdeUM-1K?gTYiJM23goNntyh6y#Fw{YcS? zz>uDsWCZ)KOT7W_{rzMuRDk6q=HrcSUms0kutm}ppD7pJuvu^uZ&S~il-`2Aw6xT| zsOVfPelT{iTJEe^&UlAOMXeg7Iqh%gX1G3@A*dp<jo25#n+)Aafok6ZwS(-?GvPYwUs~4^ zUn|`=Aexrb6}da+aiyhBZE%8So_0iY?0=tjj^gUot7q_+5Nd&+u1R3JYS^3G1?9&x zc)=as-rgX{)5!~cmQOjU;>A{syQopw*MC1c3ffsg{U)p_U#g|sr2jklJP$W zYx@7RPWP|I`M)0>_J2(?S}L`E|K2IZn*@Yh1KXL=zlHt*s%JhYPD#Obl1be{E)|Mk zk~@DyZLD|DyBebJesp}Dc2CkczmQP@E5gs;G?;F;HdJ75&}6ri45(^JmxS`USk&t@ z>iNlr@(o)Kw0lo&I+7a+L*#GYrs<4kF)Z~S(}U;N)YRNG9tZU9Z_P0mVpw<92g9gg zfavLq-cY9G^slRVM0V>au?Y@v{lOnUde&)ckEQB43(`BU20LW*9p=&kF^=)36v5q7 zA;)FQ1VF~;FLKxiOlQ>UenL#K0rl0ZxiLp46dcAy3QJGNZ*Z;`8um8f2P|*qsGEs_C-_CeEi z?}LW*No$`=>Hr|UtUcrb2q+7UD_t!uywkF3&Sg$#{JA&;hFLc^%Gfuxn43ief{AL# zT#mizBMZ2X$8P^m}G`Iqr zp*2T-ZO^#~$8)^Y8MW|TpyYq|l25uVnN!c9ZV`4k& z{G$$6z6TYuEdW3>l%rQW=NFXfbwHy7WU$t)Q5MEwuk1dHk@Beni3lzLEr$Z)bQ9q4 z16{bLq2}tQzV7aB>-4I(=?;xWP$QO-j@{!5Ch%_>zf>^z1#_#n`^aR zTfQ&vKxVd8(JWtQDj5PopDpC-4yecyp24b6fWZ0e5$l_|xXw3|pJ5s4*<`S2(>BK%6jhaBKi^7J}wI1AgHh)ZPQ%?I75kpWCR2MqdA@g%{<;Ln_`{MaLVtRkehiELR zXE-9VAvuRue5spupCX$t|9(`*79hI{@r2~e`V*2Fe;B)eanft1DihkABr?T@!nyk8r7rSbfqemUQ z8o2oG;(AWB%74ic7)f?3XxHD4*y$IUoPtL+%USE`>BR1zubuW2F*Go!&}Y?#-r6}% z*qY$H-M3|hE)i(>^P2=Lk&U=n*@W?ztn96Jq8Fp=5?pMUoDD@*3I@my>~f2l6UhM0LpnJ)R-4qXoKgu z*Q5vzpbqQb+jEv#QhY=glV$-kRBuzZMtB=7HqX=>PF8Q)ny0XJjcUeNq3b3Sm6gHr z>YVLuRVrUW$*!8UJohf}+`Mj>6R$KETxH&gO%cb)rZl+yI^P*{ME0X{jRvcmOQY^b~;15S}6~M?cE)= zpLAwCGLV!^%eGjAZlo4B+;Lck!;Txp?VY_PzX4jeKba>>&`#2bYN|n8RiPgH|ACmi zHkh+~(qs$;k^rG1dZk@;?sBlvxxK1lbr`AwtGbadAzc zvuBo-kRswHGu2n&`ux&{d)=ln!SCm)@dOIt@;AuBEqTT6nt%^w9DYTEMRn5nMf-B> zD4-%)-BMrCBg<;onwE)2ilsMqpLV(TiepeGe)6emmi+}EVmP>z!w&2=Vc(7wCjjfJ z(-#@joiLQEYe)p;()XO{ySm~3Xmp|(^Nly&Y*D{MtMEVM&o4e|M;_h&fKR7Y4CL22 zk8D1e$7$-M2psWu!2f(g?lTSZed}nX1rTt(?|tnpm2sYWejTKvzn{Cr)G43jEO2gV zYY?3l=v6!R^N0#JIPMI978!OJ&(m#lou0^1e$9n3cM1sYSQyOVwaVYINPVAd_yA8R#h|BTxTOJJAX$XGFM;&py@ zppG)n)a)_`le3sFs$xSPux;KMuL^AJ>VnxJ#g}_xdRpvrY)VGzhnR`nk3ik=W;SJ$ zPtghN)2EfNBd#S4B*Ma8`J$%#UVs}l4wj@f@kJn-(KQLD=|z83Q<9e#rgVOsnTld0 zZ0xJNw#Nmhqe0?F!enkV1Bxh!$LHx3Oi zD^`xUb`A$(MLbJ;b5lktkZ+Y^)dh00*$fE)w|{oL)?X&wI9tK`cVz!;WB@iEUDM>u zWZkao>+AOkqXYi^B4Q)J8UIRAI||(UU(}1~KMi-*cK;)nDP+!9dNDCEK0ZYvFr5?? z75ySQf&V8=_hy-bB>rJoNJymQ{wn+iM`AWumH%_p>|PBt0*a5n{*8xc{!R4%tV`8m zbAY9(=>mY-^(KkaUA?MWWIJXJ&(mTy?g22|u1uhXPbcJh76;< z!o=9vS91%weJUL#{DQ{7oW8XqCeuK;rGDA6KMZwq7U@?1LnnWs|gNv#t@G^{oXxaxvn^XcyHo) zwVc}Wv>?V~WzuOf;MB#*y-E+)+nnzo@`{aJWYXFb3vsjPiPah!(KhA*nmo-fvJ2{cx%#UhIf@Ciw=qGFx)f&SEYXii8>EHLZ^0E)(B z?>2rCia+tX6|e9(fW310ue_>LvR`EIT{l&6~{QJ>Rmmmr0@` zlkjU}EhjJAR@cBvF$uS_bUf%!vMPR}u(jA7zbY>&$q7)qlM2ug0APisph)#6M}8iO zy?gr>fM%8d@T~9x>lWn{vE7c0+~YxjpsKc-%Iio8MpSuDWver-bS>eU?wnv1{y-Gw zYq5Je?Byu8g&AVEcw}|ps4)b##slH2X;d@#tQ@bT*55(hXt6xg*s#g0@OgU!77 z0^cc62en0%;eaYX&>v1yT+=#6Z3w%ciG%-hIQJ&LSw8draLy(T+glxziUw;XTHu_L z%vYq}Jga7DsU<86a&z7-56y%9ow$c4iNF>N0)Pi2Hw!p%Ub(D$zQ-TVTt%L4WheJ( z=jdfRgD%%Z0}>Y~YE?CFj#)rFUM-kty1Te|*(^N3W`d#lUk{+ln~o(UT}GZ6e?Z&- zN9)4}?aj_yWnY`My|_D~wF=J^O6o##zGp(vGpSN}GcQuPl;z|A?C?WqXiN%#YVq49 zbTrCS^I1RlhA6KLK+toU@_&4#le`vpAhUCo=SywJc(jUz^ejinZ`{a&G2Gf1D74VB zAxII;h7Ar5!q^R)oZ+i{Yr_G+M!~rNcpU%`jJrFiaMzCi+kbd$p#Ok#$zGXq1pPS4 z2k!Oz0X8x+GNe$KJz#24Fs-%ouMT&vDOiZM; z9)tk&wYBF6t@8QvM_rP9ZP$N&F?bTwyh|5V@7_fM)$Pb`BCxxxQEX4cp&uF=8p-HA z)S#>GBdhbOhMQ!6+wpWIz=0U_cXHElr$ooo{WD9PoQDrV3j&zZ9{~vkOsURRf2t0o zr`C%Wic|tToUn_#xR^t1TJ{2u2`MT>QD1i+H41x}aI^ zS1kaS?VS1X_?xw6K6~t8{yL(@91wH5(dYm?D0JSd9#wyvX3y^cEKQ+;f`YpLT6h5M z>0h21LM-Ao+AR6V!Yz`%8a{ z9{%Z(5&gfUM@BR-f^AGn`p0$dKcmd!O-03T1HAS@iJ|`f$G)I}f5pw$^%8Q{G65y4 zw+Zm>7%xcOiC6e-YwHoP5Op2BB7Np>Cw^vME0}XNjpf((KEwH0rX9F+fh#RUijYW8 z85k~D7)bs$UacYi-fH1`vA$mD%u(>{3}Vl^uHL{L9YAIM`*5TT4E)$%w?LFJM33Yk*0y@@uZ3^4FSDJB!7eVcHm>C4=H_Uk&&t@GFb z5Da9d61a{viHe-CYRu)ZU?qhY;lFJbwItP0$o62gGnReo8+aZVa5yvsRujZtva{=c z4}M=t4fwO7$}J-#|6`|$W-7*4Rc(k3Yiu-hwe@}_UGe+LlKQ$-ft}_6#qAkwX?)Ol zNU1xk&mD~GYErJhm6UurPyQ7laJ0+gVgHaH|ZP3z9*OdwU&H}{e9Ay)(U+?E=K@=27N_r{!Z9sxetpwDI zvFiff^~>m6fvo>nX8K7c+cSJb|KaHPTKMS;HPjffNq^~%+QH7jW%f;wo2ootfZk!{ z^n03If3v8w$*&QZ=DGtI)IWWT2X;c8JN^0sx7#iED}aZjIGqD^mo6SqFozUDFXvKK&4{I%?0;X9mq_GEigj4dH@(cn_)c| zh!+$dUy_c_i9I4tg(2BAeod{DmnIDjF+n$27FK|{FmNU~-d`Dgs7^wkPaQ0%)#nGOv`n_kCLkpT9x8rS;NQ#(?*b5~cU zXTHOq&V?E(gvu%eDJnGIhW&C>!b9m_^&ljp6#gL2b|Q?>?k|*)rL3In?Me+Uj!Y;| z0yjPvdHTlkh1_w6d=44E4deA7^!%^MnMkbTslKKgw4f-vIQyLl$L7YX zu;TRcd!@`^Y;-J1JQU&IpFw8~J>;Ml+lO`c6T7$OAmJZ{#SOaBH7UZCAk}EIT`-2)n;qKyCGLm=0<6y@T zVE34BqIj=}Qb#yrUX)$OjneGG$J;Zaf;~`@@>_T9yyZ?F{f^;pH(Yf(-)GJm7Y9X^ zm8VaAAtVyln3C#@#!ti&M|=Rr)bG8CrNayrgeW88KV>Nz+`w(6KGJoDnpMk&QAQTa z{tgdIj~vCUwz()rXhLAlKgN@}D%jVb7e^mBv8xZsf*{w7RK5e@q(_dnE-b&V=!d?i z6@hW5e><}GeWyq9+q=~xEBDlZmYYk12rggKFwsBK1#e1gc5!lO1J8oe6+536v6Jid zHWCOBk1QF8xQca5bo*`wF~n;+{{AdI%AjPeqWf@kU`>T}Ym)e;(a-+J`I?Lh}u18V8G^y(BrLBH1ksrL#(wrQ*%*H{}OxVMuh+Qgky#UU=KlKen zjkBQMfA6krNAAk5$9Hz&D{Gd;95;2!8t5Y1{~5BcWi^-=_fF)2GigQU$xF)h;0RKa zl^bVcYI+*!l{8&~U|?u#^SN=cx!t8_>0AuIPfn|5gOXCG z=H4EzAPIRP5)~LY?O#C@C}V{qxd@-!MOj!#fS>(yFsp}Ce8Y`*&VmH!F}U#J05ktB ziuBsv#v!ru3(13L4e-yQ%6O;#^5u(z4@sj9FI~HOHM8^~QDB?3gG>K7?#gXjj`P1x z1Wovt;BEFD2c7p>p^svAROsm;)a)1j{WbWTDnaXO=KuPMna<@vaMgbgRsa9TPBG&E zjZ}5>?%Q-}>EOVR6#XcK&eeRMF3F2*#3puk3)uNi$MrrI40}ht`_*vZPldDRYvjA= zO;7q!TSwt-*LQ2m7Wwa7yX*#|`X36k;@g=eGXSdR_aXD(j-0f^W7FX6peuL#>8-w{ z@o^P={VKOm0e?vSZwJ!T^8}t#rI%AzEuDMv{^G}LEPvgFhpWt57G4S}tNgRoR$hXx z9O%hs-VqV8ckH`K|8%jy{T02O*YMkR1yxm5 z6O&nIA^96#Y;0`zh!M434*kaR_<#K+wux<~g6DszFdNR2!{Ko9r$$c4KWmj}#(_(+ zYB>7aOKHcbXW}K?SNXrighBuPaITuk-QvGPMSJxsjoBT>mlhTlavH(GfLGAcPAAi` zg5EW+w4MIh{tZZFG(+~O7)fb`ypNB7;XGl4KQQ3rwcgVa#Ezt)L#X+m?cLj5JQak_ z(kt8+g#2ttR{=U17veg&3^D|KuYT(``s1#uj8P`|X`u2n4XU4Ct5J(?Zd@v#pX_FB zOjJ3!b>C}O07x`~?EMP_1f7=5=LrKQYdrJi0O5=ySAdVPElT+|u4XqVAaQSsW8l z9%M9PLM}a%CQ<~%au)z^m}b24>J}+6agb1@^&CUHcYi6p$So{VrZq5#tmPU+z-!p_ z!ceKns0iCZy!`Lhl#c2!{rK7vbSqdW6kz^0_TDO>s;!M0J_;%wA}C$brKIE*q@=qx zu%){@w@QnENVkA=cY}17bR#L<&4z#So^#%F-v7J*uD*4{g|JMJwFy7H_6r7}`0?pMMMXt_%3y(U|K`Q)>?pc`=XqD> z#2HBF?Mb}CCQBA@BEkbkIh%X<@y?0i5@EoEWWQu@B#YPH(1qz0F04i8%)Emj5Q)_7bG=_8LJ-G0)-qDQ zW}kqfb?rudqnwQgwLpW;=kXTz$rDe&fFC_&tzPR^&QWS#YX7uZ4)t2vSl!rQ$YP3Q zi=mMwIH{B5sM?#YaoajwrjwsNT4&QG@yG})D=U+fadZTv@A^u)0P8PY+!$6JA@~iI zxAz2VQmL_%0zrp0#QyjkPk!*nkJpxT-f}=udhF5#roqRLJ}>nQ45%NOGY?o41QQ5VVPkh6? zYfkr-?zz@0qf@nme;%i3oE$AC7DedE*;lcox)X&p%jtSmItur#(;Z%n{MBtjtf;g z2wL~Ui!_cWlz1CVI-|!d#u?y@O$TQ=5x5qdrE51ZuMo)s_kp%!i z6rw$xR}>WcKFJNMj&;CC4L)?zI-f$n$8D|wu^9h&2=z`8vKUWv<0J1(PT_MC0U6h$ z9FO1}O9M%Dv9Dj8oQESBbjWF+?L9oXANYDOL6BNLd6v(@<8*sUKD~2iwgPG*G|q9I zKuLabIn+WGv_nt*l^7>v%jPVigZE)&7mbqEE3jGpbH#;abmix&6moiRo30L4Y~6@4 zOI?i2%w%;2kZ(!6(QEDKe-q$EOlqaOvfedp0K+25C;#kw5Ru6YZB#N@oAWqZ*qf4u z5TOOKRc;NPw6sf8aq+wxoJa5tE9@T}AkIey2VvFpN&_uo@D0&7ey2RA7u&&42=P<^ zqHCRGO8+jv^L&^N1&8+Xz#H+1@UwqQByg8OTuVD$z8vVCAue(DvFT3~REH2TmV4a5 z$-T^1zG0IeG{|v20g2UNK4AaINe=oxmMbfPFwGvSgp&#yUl-dn@gS;x2TCoiFP*nn z;q*uF+4UM)gWvt{)ey{X^ZJQ7An9eOU##tsQ)Ge7%>Cp-CWy4Q?&*2INaGV^@a+V7b4`dRz}Ce*&2@`o2GjrH0lG1Zb!8}8(dpA_zV(- zm|LCLZB0Gbona~YnsxJCj}kbo0~fL_qlPM(q97z5ui32N}_miZts=g>T%$nx9NC((00&Sl^Mrs)oVTMFxlH1YKa`t z@23%suq9;?;*K}SSi_~Glka(t)|h&!RVqCU@-voE*rw=rZ1sg&^wQRu_9bTzy(Cxbd=Go@ zfMI0vgA33odm$d{LT=BKUQAtbG2vQBa6TDf|G*$C;Kx0lC;BE8>;RFc%~z-7uj`fJ zev<((%yT8~V^O|~-TukqpBG>wpNWh^3F!rE#V*daTZR+EvEZ>W-Q*=8=>UJ!m zw|x6bLH_qM;WMY6QT5 zW%7M-@r#TPmU`IOPU;Qz5ky@1@Wn;Fa1ry-?97`oh31bv$I5vAr<_T0%Zd`6`IA+L z=a&txAE+(93qNm(U{XU+XEmK4GKrEW@YsM*s|PM;_ura|$MF>9c|(<)!s^t<)%XG! z#K$TRb{noXZF5G~mvuKGC8`Y`aM$g&w-@XE2~}DZrlCP#)-m*!o6SifZ~@f=w%KcL ztITUW;RAQ4EffyQX%gzMa%PnAf_baHyVez-jNFa|?AHp{uZoZj)*@L3`BWSR*1qJB zFH`(?rcqHj7;V0%PfOk0edDnD^Yhy8_4TYi;diCV6T7Hh*Fjk^FPh7;iqhRdpEl>u z{G%M_w^ItLi~z>J(FbD67LqmVA9HnQk-R|XGX~3d%u!-^h9lOeipcWwd$8okFwewx zM>3g&1_uYbax5l&=?Edx=a2HQgjQO0N}JB$R9Varv1Q;EFdVnWE#_TzLqadxyE;=K zXCo;&9FEz>a>El2h1w0Y<*2%Zf}9p=0kh{9sPL7kvh@~EHG=NV=t`~O>T<#24C$N1 z_&nv3`pOZH^{vCJ))^t(^P}U2FUG4RVoG{P|Ez$4^kvH-ZpT#%B5;H0R%5p-cU`ffTpVm&40lIawx_KkEV10;Fs z%wsk$gU*+}8uixIFF5?(58oW`=+M;E+8D%eJ&}r~vb(-T!V)h!DNtwS)kVd zZ=8IRP{FY!1~HG7aH8uH4*Yuxu_F(tK_H)UfmKBePH(55=e0@j-(Q2DtO?sIJ_~KoT@gvq5Vtlyn-EqrGv; z$dqgh?XJH5jW$MX3(PA+lXLJc%Dqp^Q$(Piut9HVX z*w{v!UZplt{j}>=Y(f5@#hTY{^BDM+p6);>7N#paZ0zr+73OwS8%f2mPZkv7f`IJN zw-~WU?tU3kzhER1PA+Y_y7Rlc4@SvrNFmPoL!4LxEPW?*w9Tz~^-Nxj72f;4F(U0kwbRMnAMFz`t>LeZ9GsU zU7BMK?o(vah5?tJH4_ONzkFV@A*1Qmr_J%C78X{Dv`tBp5BV!`{sk)ac(RM34}&9G zccTdm_|Wq@oG$*NmZIj}2neS}!)MICo+`V#ihv_@mGvB=D-UNi9S!rx-rBZi< z5$}AJ9wFZVm2-e6$C8q#Pw$C^n){QVj_NZN4xMHk#=UXi*B$NcfeCSCM${NEvxT2; zJY5aub22TG$=ZIXQly5zKw}~o$HziTTj^Ftt1%W%-8tHCKH{@IRmM;vn+_o2KfMD- zPza(~lxsyU?76wQ`3kfm%z#Er%Je#nmqV+9_&hW2rxfysJF4-cYi; zHw?lp^jGbP7-}SO!_MlQy5p!jD=a49WE{E|ERy@c{`|?B!w-BK?dO_#QgXzD$jOa) zK~rq}+hH+>1ZkW-&LQ403Nzl4ARM=1F<({{Gj3^&m zWm2!&o1`gEXXLgR;|66kSWW>zPuk><2Bl?^Y)PSrH%57gR9Z* zq*~_~Bu*-oAa$_Mref+dkPG)InZ;a8(Rqy{>J$lwNIJVm^}n(wBYLY#Y?xAN$mMOMkN&eJTM|$0FJsaX}afAa@G^Y zf`{{rqP0&6v~Y^Dzv44Scyy-?%q^)MZrP0NGeRprhgF5~m0yg#XN%&_;VXW*xuYnP z{7P;kfG~BJHHp9Q)+PNKZ`isX!_%-uBm8;vv|8-1x1{2u1Jn8kPak;C9a^?4-kOd{ zp{$S6_UqyC<*F#w0$ zC`idxrR7XvsF3&Y*+>{(WPD(_P*F9d0Y79uR7e8FSjS# zH^Nn{o4n}~h@osfukd0RoxFm`^4l+5Zt|KM8iBYw4zO4i&!b`yW%o6m*`gm)sO^;) zXOhO8oEOCZ26^|gF^F+wvpZFMLqFj&O_#q-@5bxU<#Tx~EQ=E~dL?{UZiZLAns~GVgvV zIthuBYk)gh&s?*m!T5;Zhmdm?-+mQ48v8W~jvpd^Xob2Z`ND|W%{YL2O_iD@B_t%U zcTO{g(?^7agxtt5ZB5{sN;g;=M}t z)*|Cltfv!~GMQ_+DsChz`V;wQm=xZUwM`hb6S_MET z2<0i<8qbK3^nVh>zobrJS7D_yl_NgP?9AQ0MaM(-zt};9n5AXDq*s8o*~15xTsk90 z7G1MTOLMq{gd00MN3(<2xZ|MHf7RLU?J+gjAbJA&Sxy6DBK%a214v?~my0R%P!ceObp>QlFqB~4zu zx9z>X{J^u#+D)f~=`$QXG#}u7lV*|+10DGJjPV(onN)l;Y>DSqN~@k+Jcc;^UD462 zzB+CSfxCF?{Bg@o-)v4qMI~in$q|L(JB~)+s2(;RM=#p-*_VZsgya)G3)OrQrtSFS zz55m{;XV8KWPWJBXq1$lS@eX!k!qjaPEUP8z%?i`dPEKnPgdQ28ueeLa!(@r)ib~) zEN!#+`9$5J=BM?e|E!0&MrxlQ<0$s@#wGKq%c&FJ;k$odW}ojVrny8#q#MkN>LWF0 z3PPpBsqr7#b0KlJMo;km^Pi;2d^QMt+>pne4`umTLQ+X3Anq#~T-DVqRYv&thk>7w zaV4uya5JI>PFto$>RHasE&urUjr@miFmFC-AJeUm`a<8xStQ72`>#K5bG?U~LCY`w zOP}s1S!3jzfPYn9;fr_QaXK;IFC=3h75^m_3k60zPjImb=RXWwU|{((PKq>@fPKSz zkMtrglh&urn*xUcY4+bs>4o)cQM2Z0krfvlEH$c=cdHj=rj36;w7Z{@&8!YV^dY8v zWfe~;hnI|eQRIhj9Cnz+oT1@dTy5Hka{X5bq(kIHci>gJRQ*U_4G+ahAUlfa2 zZ&2aiL=DY*P&K=wgC~^G07$WBMXFa?zHx+v?VV*>7P{LM|7Kl2-!W11M7|tlQ>MBv zMeqni0*8Gi(^s2HsMP8(sFKwP;%N%*Xf*RTsU(Qlf8D!G+Z~JMg<{KhVE(V$6wP_R zlmD+@3k;-hy}R*$U+m2PgU9^8U-@fr{@=LV4aEP&`BF;o&&-WeQjj!#r(h~+s$rhO%^@be{%*8uUa0n)L5iJWZx^5Hhr016NTg@2bXG03DGn zOs_bvr-J_w71z4!#mQ;lCz4Jsv80SoLk&Q#}zG0QkM)eP!5hZew9!Wn~ls z5qk(cxQOu!FasY3G@@aJeE#IUbVL%a7dgHSjLyhA@HYJ>#ldI#m?kP&9vF)M za$4Pgvzdi36ciNu(^y z?3VW)ALu}(iJ`>A5&_I^+WH72AKiz@eiEYd?u(lAuw6?zO~C#uuKfUSy5c21bz7dK zEIp@y_!qlC9BQr4>fdqnh5^%ET=gWBJH9JB<1b9*CyFcn;m{ESZ7H9jU2hVHPsJlH zEV~7gsl-xVUJw_9j)m#5crlv(-hx}=(Vch!wKzI9kUj}5h(I^Bh3(_;&~vn4+`(3^ zs~69yu^;`lhh(B*hPnRE2=b?5ja}eU&-~=ixSw~l?$o`z9cb3(-`YYAgba$b|GC#% zJOY+vU$m4LxXlWtrF!K0@>I-39Z7Hh^Ry#_+B-UgT$h63Nu_)%197s3)^7GjEqM8A3p-6e?Buy z8J{8UlfjpH4H+2^(%SC{Q{GLHH)RBo&!uAuTrXuf9(r8Z8$l~9!+#%wz=x@7x8TH^ z>oWOLBWcfXAB&m@OY9=nsE^kB&8O<)TxULh{8(mKQdL^|IPykROiWBd0?7A+j_DdQ zRvekw)i_3JGCpMKQ8oD#m*D*Q>O{Up4)9%u(Mdc)M~{98Yn`|4iRW5WRj`%Rz%r4z z8x1?7$@jm@3Cm#huBnhFaB?|Zy>n>zImN4qFaUOK*MJ9$1(kRN!@c3Uv)%rjke5-@99>Ob39%IF2&tbsv6CFSLP5p-wjNR6DO`@5j^l$@6+PZiLP z);fHIv!BOq^Jnf(MJ~HNya1zMjAWYt2jmKiv9_u}$G2pNCiVPW?VKfW@_<%I8HEe) z%$+TOtcp7l^>o(lS~Fi`m@$eGm_U(9a3mp1L_~j8Ci#l&JRF|h`C@LXm8|EQgx5Zj zQ8NvA&-)3YHJluOrYGg^ktKd9B7lx}aW3=XKV^twCN4qhMtC&wd)h9A!AoXH^0<)GLGi1yJ+cl;RTD~%E8gZtb)p2%!;a&bg> z1smgl?&S+18dryJ&No~I-e-hYTFgwRD^U7uP2`&#st}Ig$B(B7^W7WH&d!Fx69BY9 zm4pp4M39R>Km`?Z?S2?A9r=`bp9j03l-!=k?yK9R2jf$uW&RMjt8Nz>D0y*zm!>=jB6ICyre%_jiu zU1CA~^!OJ^0X3#yd*GsJcofV0pL7d*KsJ5T_;HIx@&vWc@j`0LIcdB=Q*pBO&2!_cP@PgcT@YfU z%ufNFr&*JVuuxE5WP~lbproY=-kz0M;+x}y%9W{EIa@6#QNV=`)Mz5VjwA?roVjdY zHdo4SEvWTQSMfXk?o0B%p&X7D!LW&$VI4Vse#I_gXhi)dWs%lLNyUzKt5w>hEf`G@ zPgVvx_wSY?|L6mxG+;{g?0W1EUefc;vDIGnNO32Wa~6b$SkoW`Z$k+LBJDr!F4kY& z1jqUEN^aXoFy1yRKEXFyxuO1*79+J1XV&11f{ubAV7osA)B}b-;8elpk40SLd}8kd zJE+WuHVK;y0N@Kn1Q!c{h*E6fh>TcJn zZZql&2415ASecR>fDr!JAfa@)SWn`28BA(ASL5b-Atl-{Hg6w8?nd~yaO&d?i~ZD; zVYFHV?#p<=y}s1)KOZkWUc}3#*k@SF|9PMtMW_QQkOhwd-LQiTa5x5uR&GraY!~Yw zOzapbO<1`3j9lQgBgh!a6b%9j0fM*X_zcnYUhBIJ9%tbJ^cuzQfLR3|X)0n>`j(i- z=HmQ(r8}ZdX5%NI=q5B5n_B}ErvoPJ%~t|HeRAJl)N}0H(&b7K@?s1)?Ms?ze}^uK z%nZaFKfl{iCtaXm+(y{G9_^j+0Dw|Y3euxvVs69YLlY3`vpH5UQUjf(|9m&=>vk8& z<`(2lt+2_3K7Ia-fq@YTZ$v};Ia$58}F=)=E~nLzyAT63IC`u{MACHre@PN_{CeK{J`Gm(dKuZjy}2qB*2vs+Su(e)9wl|;~K z$#`#g-P`+y4RNQ$NZ3BBRc!hk0rwugYK4g-=(9j;EHA+G zP6j>zLN}4!&NGc#lPWRC^1Iw?tY6(J@Jnw%7az*E+3oD?M7hXb|E$>J`JC`VQ!Yio zeIL-vRIY5d0$$fchf%lm>E%=;!N=cpi9@ReLSLafThBZ{Rw^xhF0zQya9iUVp^>^x8P+%w~sIS;zw=1XXK7wHg zn|7>-zs^HNlcyp?lde9@jne6>gFFIa0JUqOF?a2eg z@b&TUk-;nKdPa;mUW-sUP8AM^(^(^U5e@^g9Xs!bAF)My%1JKj`Ii!M6p87#Q~l6N zhLPpY&)k#MJFR5o^!+@zHwUMuxZV2;&2JEw*gu2((X5Wkp=ThU$9}Bs1XM%}h*hxj z)`ASP9_WnA?qi$Q?ue`?(`+K|!}r2(9PdYn5QpsYyi;%$rCNT=*e`=?I{TeZR|&|% zBtp-{8%KwSzI$!#hc7SynyO7|{-D!Dc#!n?-S|@y7NuyI z!ZFKUAWDOrYs$IU{E=*rLI#_UL7@<8m|ReHK^T0rJwWdRWl#|lqqw#k$?gd|BX`u* z_S*x5{6A-*4OwGLN+LWwhnwpMtMX4{wC%z6#3hxV4c-bai_!g~qc;??4_$E%1?*Qe zw$8LR^LHkLBJ9%uXOgO?2bR@HIH)~Vwrm;^4mTu8SYpEB#WwoesR0gp^|FN#U68o;!PjV`y12~b_1;FH}kju-p*R6X_!`bl#O zd;URR4=5W(Oa>T9qtSMfftX_(xaLLu`1y$ZbiQ6MX}fx?S{eMSL@^*M0v|Y=PKfXB z0HrPItQ_g+P=PF|)QTbudYo1bq2 zxL7Fc7JFit&SnP(J32DKA{N-yL%*>r=rP8U0OAzVWU8#^q@H6a%FyiSt+K@P^sa5! z8*MqeA(tF(a2U(Saq6-bZ|~td+-guPof+0~?*RgKXR<*e^L3jjC@4cH8eT+f3!Zl> zHg!SlM7RJu^&Fp-qNhf{^7jM~1lBv8&6ln+86oNWdS7`R#L4`@*YcJ03lq6GwXLW`3%M)^0mpv zqjFE`%Q|{LS^={dugg`VtUWg)-h^w_0@WZj4=1F?WNv%G#d9v|cy^9$ZTFy6v|nG7 zz$>-JAKACsZX{d;Q>*{vy){P`?VODr&@zE5^u=waPQYoaJXixZ^);n4?$(M!|xPj z7Y-^+QPJ;)s7R#iH|(1dqG-6DAWRSV8T$pjQl-8?WxO-(rF5O!_{zHnmXa06;BYnbEg>V~>Q9bwhWBM&vb; zzF^2K7Yb#Qrn#KrPifbspRy$ejO7|+=+rrWl_j%dbaQuG9gLA5i20@Znw|9v1ST>= zPKKN6SR1@ERpyXK(s{g|(zrJPA1V~|@N@oS6l_`d_NXj#%1g!i?00gTNJ%*D8V7|C z=Rkbe_2Xi1#5n#LPCJ2Gfug!x9&zt@j_MImKd#0{{6TlP#sXbE2KpY=Lvj15s5R;DYHEEgF^~o~8ZZ?9RrMeH~6~|(m2%aiBu@FBd^VoOapSns- z@PBI0#pk_N#Z>R2gQj#N>Uy};`T|)ScUkm1o&%qXQ}n%I!5G~fq%;rKPOeNFb=8P% zpBDXi`Raf@)gls(ENib_Vw@AJt7`0yHl7CvyMn#t_3%whviebrkzx@s9Xt|kZd$^A z$#kWf2aNv!)eTOBO}(=qr08Ua=XGN({9JK$vm!FnScE@2mA%1pBklHd_lI*%&9CB4kLi9 zwFn3yxo1^@Yb|fcH`>bC^W5Se(FK4+b+*##ott^hC9pES!yn>FqF4ot=?y$qfQNIR zle7KfZhzmG;H>Dx$;r)qI=V4p@7}doWcW@=DXhqiMrhJZq0(|njaf$})sr{r;uX%; z&D>Dkpb387_-y87L-)l`Kz>nu&Kj{-K^Xo>Jy%N&q+fQ77)j!MT80*RQzO(< zgi`wB13J+1k{eq5$=Ikh= zzYxYJw0+Qg2D$khRIznipsIW%eE>DwQWbR@L&{+!A<^x`_e>GuDz%3|AV$+KEgtin z|4NtWaRdk4F&j-St??Wk<&BS%of8uPx1i4Bb3G6Isb?0vX6NzDqZI7B>|&!Ppa=0! zUBEqx#{%bhH>f9X_^mpajb8O>a(FW-)>liS0uSPGvz(we`IhUz5+hlNgQVk4;^aFA zLopyR<|({g2W<&JvU7)YmCB*Qv7T{0r+;ncf z=2hWCuhG{dw>!;Sb|F&4(F32})6hy)PlcLhnLV7Y|CkPS?%x=ywj57S-=(4tR@L!% ztUG`RK`0lPMmIImzvnnv_U9EZI?{1b!pm-4#$#Qug0^qes-IssOtV&7vGA)n5Oha; zyh{1AN@ouX>_YKjLB5tt;OFiG8oj%)`Zcg#6TgqZD7+ z`$~td(lGJnJPD3pL>OzDrR1;k{k4l$4(u6X9;<>fW|@w?pZ z?UQ4vsi~T!hEul-!WRTBJG>5SIH<~S#YSZD6jkkdPt7>p)3Z;$1a;oaY9g{P#)_@L zf~h*P&!?mO{6yUy>5YGh4q9KK2$p7XqWZig!b zIoo;BtkJoFx9tITJGlh~ZCryBR{5k>%a;$U&*}T#y?&A^m9aIq7#N#gwZt04OR_9w zXR;U2SY-8Ly2hBB5NFQmB`ZbknUwbHa)*%Wxyf&i;}0{q@WO}hD^C6xid9b?K1aK- zex1GRLra>+%QVQs$+|_4l*ab)tKjHg`<5c(`Syr>q}+f=YYUl>XSMyRd7G4a!3`ZF zpL^eLMF2qu{`~Nn9H&6FtmE?nJZc0938=9 zMuTrePjGO)VIkjuV{LPLrS(iIIZgxUM*)wZ*9`gTm`+zb6<<&ND_O%)Vu&&pG_U%0 zQ5dvOBqb&3^iO2~i4u?rwQYgL1PHro>lc*Cgi}MB)C!b%xf;ujjxwpmJ5Dq_mO%2l z)7JJRz6%P*X-9r0SuUOt(lWNRxR^Li`Kn0WfqiC~!{fSFAX$8O5Q6x29Sxjv=!i5z zf?f_~<4i=zZcSX**^PkJZIsf)R|}k1*N4K>E!0_fCYfh?MB2V9(D9ME%?Da1!=Fuj z9EZGG3-(LC?1i~Mn=btH0pYxQu=P9V7j_4mLS40wPm-9V>vo2O-ucA*_|nkHyVM=i zD!sS~%+51yEUoI>_6oXREy#ZM4UG>829oqAT75M%QU0X2(w@KJEPor(vB^sg;?#fL zx~F!S?Myo@gpue0KSc15e_QpQ-%~`sjeIc()Yi35w<(FqoEKG@A;b)GaYEipIihcH zDJ}qdo2yztb;N}95OXSee?KCI4K@~cbePff(?EQRJ>wW+e>I?!R6N_CP!vGK0u)S= zyn8NNpnI)XJzpu_z|kTf}zojXe>> za5vvf{1ijrU=$Ne(91aAK+kKFycE=3|Iris`THj~xxST)VN?{!Vllq)yl5oaU&V%m zM8m0$Lf)GmHmQ)e>=bBKeo1P(aamO0HZjRCa{~LrXLzM{4SUaVdO+AGV$uS&xnld7 zwUfSs_bp8gw2EXK6Cs6#7{MR(t_m_z1X^|_F2Iqk4f=A#-`@cws2A3&Y;lz zEt`ofRUqcIoaFI>?9p*@1*qp*NUXbUUODc0%OX^>LP>!fK27(gtvljuch1GZL0SCb zE)W9T?%}F~#b`0*rZd2R;j4#Hi8c24Y-;&Ny#%0?xLXnWB5P{<3Umw1)BR73p6>zD z2e3K6Z-li6OFBcF$Zl*$!xo-05lv`X$jI~s;bnc!$7_C^uQ0dxSP$E%R;ByvCAnsq zv)6j)PSg)#eS2utULdT|H>sONG$l{`KnBITw9exmvVrw%y%V+2_H>4W1U0!^kt#Y3 z1s}-#KwIsl71ArsEZ6}6;jawl1rSlN+|LtY=}@9y#NR$w;jtGWD5Rj~ zSG+Lqb=z&WLOpj_OQgBukC_m;(L~T+<;9U*+@&8TO#@ETx-t-*jKpne2fl^1G=*Y6Gwt%sb2a9Sj3})P?t@^ zQ2Y-cirjDZ40sWWd)RQolk|c6o2S7w_Ny}CY+Ts}0hR24xR?ngnJVjzlhg_7VD63b zBE=DV0VTx&duPY{B7adai!>u}Bw!6u&Nf6{odb<{;~>qlAs4{W?H%U+H|=a=goR^{ zPgNV(*J=I$QSo5Z!+u~YW^L_u0Jy-ZZ$+0ARyaIq zJ2gj7eJRx>4|6y^8_{52Wdv~I?c~0a-435P#*Svgd5Y-YF|63C6D1q|twf!G5x84J z63NskKgY_Y>%w2ZqM&_!p1{qTlxxykNZM0=)hA)^IZ`#yGn)7G&qGjnjsU>4@c=mq zPFQtkdm*Ge_wZzidB$_Rr|QkNfS1X9*IIENv?$$^ORjmT1(=H?nKW;vZ2#aJp)3Z+>i)$wSmijnI4Y$yCl)P}ut+~1 zD>uQ>-{o;-QO<=1-Zv#6NE+44@3J~yr%6~&!0U2*W)J?@Sm}0!uxyQl&r`(>{a)E8 zRbV+j@<&m-^o^V;CgA!a|F{PBdHaAWnVZC7N)$4vJT!^>nW4NQjcGpmP3ZCaAt8eO z^`)j28D;TzEGos{uD=Z>`j3EwE0!!=9kL6A{Ht9_IyT+KQ!f)7_T0F zU}Ow4^ljrBXn5{(v3FF-9Ai=`ldkBsZ5Ody&ATmpkk`2=;6~@T#tNw~q#X(1phWpfuXghqVB&th7DS0u zyOVjsScI7E9p5E6pCmjbi<%v;IQ`q9Kv7Eh(yTX*BTF(DydxAu@k}NIa==IKTG1v< zgiK_HaMJf}#2rf?$KX(b1E`{&O7Ef!5}U`0i8Q^@XM1P7nP8L)nK=w6t1KoE&|-z= z2s(MDm%P+C?HiZP0hdR(49prsbBhuh=B2Rb{DB)+ivUgS^|nqZ%YsnKR7@EkSS2hO;JBuA`w6P`T_B&;ho|5!fHlz;{^~Z9u`h3|I{i$MMd?c<|zlrt#IloNErm$V{)~ zxX7=R(gy6zQ5WgrVF-)v1(bt%1t&*Kf)n}OxpfXvC}=U>Szb;THSwb@waCY}sbWov z@&ih0>mHjXb!OOnclXA02=OMR4ZpMLx-JD=$}2|*l+4anGx;|r1w+XXL=ybB#&e>6 zKVo5F$&yRX#+bVl9-M#G1B8!JKe#<-`3nu!nKet9LF3wjH=I)w0Aw-9Ytb5|%Nw)TWj%`dKXx^dXg zDJFi3U>E-3hlyl0E%!oBZQ0?9zlF1fuSmK9XA!yDm1ALee<$Rys$fuzeZ|?`YyEOb ze$aT8G$G2VFF`P%9aS zFu0)U{$eUPnoKv{LV$xKNO%T%3sKtM&$0&o7)04J)c5J2=2XU~P04dn7btxceLkl2 z_XRqjRshe-kIjWBiGgkk@zC>*MTtl|WT$DDffRnG)IV~y_OKUB4}yvl4~OD}&&?(! zc|%f0a*Z6qV?C{{iN^~D@-{Po@z70+Sdzk*HPnjG9Y0h)m(6cCc*1pc2g^S0PZ-NR zr}Q%#C>l}tVHZ@Et+}hx_A6c1hO4*Mo^uj*4Yd`RtUmPB>>h@vD1DS|16 z?IoG9_^XS~y(ZP0w%wNT-RUxQQp}xU1OnhmnTs(*qm4VnJHog*TtX;$mvl1E&T3a# zopbnzK0TRfT6o%%(x1R};dF?+DsOoTv6ukl%=5>hGGifGA+5S!*^-WDGaNb}9O?2D zws*fWeFtBa|1RT6E%ebVaA#OED=KR2{AGI7T%F0Dx35@Me{4ZH&CbH!QE6`I$1zK0 zrIPU9OWwO~WoA^~$!uxfTFWyYyLdE2mx?|l8HZCe$IUicb(}0&s4?QA?KicnZbda= z5=BQ*!$a&3w%y0QmLo-xkNY98vlt6A%4yXQ50{w>b(Er;{jj~@MN{b3ckH>1CBqC~ z$lxqyQu)EuYNsC6+v0Uz|Gy-mG@U+=u#0r+SYc1ubf+u5Is+Pn_-q#`=7DRkSn5YV zQTFSbttT099e(Hxo6xO{97IX=9#{gy8Q3jfs|5A@#3nu>yV z84axT3$z+ln$Y!;W3hKH*J!MqD^K7dX_XxZW3$6Y&Y*2&36U6#lD zg*#B3p0O!=(SD$u7to)?2`|pi8JB#S;IsNkc$bCgRZpXNMxtgxhu0!seNynvVe~;K z@$GFv40P{uYEDOGLQs0R>g@mxV%Di8?tzXa+IkQaBemzTwGD=^KxTqP7~fRnQET&n zzJ6G}R7$OsfYODz^jK{HGiFwk80?8@q4~VInf)tm2{mr)zhs3g7bjH0k;wM;&+~u0jQ{N)4um4k|wNi0&*B}+gEe#bYT+f)- zu1#fpsc9Fx1lV2K4|U-}w?oj4!i7)#0R z652H9G_}P3HuVYb1;lZEgV#rJI!;PDs#$fiwZ+-EGob(n87eAj^e0_=+bhq%vb=aP z)H?$LFfq)2uoUG*sV?bhkxr(7O$9V*JIlye>V=842jk1qGbVn7#g}?|0Wr!cM#6_3 zdB^1vdV;EOjt48>_49XZNq9HWVgaX}_`UdBx9P^OXMG(yrGNsx+kw{cSv) z6Ds=J*I2|RkB-Rv-0%sG_B9QYEyvvwBa&5Y^A6kz{myBrGT&mth;YaVgDYqX8W@CB zUd-tj{0YL*1B}Uz{5G22(pdtA>UMo@7p#UV^i*gqCZ!G}$tOEpw*|=yjeVYRdbdC} z!IN@xdvle-Z8@!y<-8*XABb^iMC@LH7TfVWsL8L7kWuM0+F~XA{$iLiJZU!b#DmIj zwzepztUx2TSW*@X!S%*&=UU4Fg($L%>JIE7fObCn0+o*K9vB}_|KgVPjv_H4F%U~X zn^s&m0;wrayQb7N$3!lHFGr$Ch^?Xl^UiT0b>IV@mY#?WWm_zjV#lb@Vstav*N8q% z>E}qNCZH-mU`J4Q-~NnT-Hh)%o_D`vGGip6Knc~_d%PN9i`i|)lY2vnwaK38$hGSC}rY7~OXG3koqTEh$mtv#|gc0Wc64U1RTb z^AQ4uHXhRSAuBK4AkAsb*FWISB}~eQ&>2IKD$PZjj6d97r~B(mg6Rg?KY{zmQf3yG zrVHc1#N?M2(U&Y9b=B7Mx~3l5%60I)Mj)R}Q|Y+V+Sb0aU^vWlC!hTO0b$u&2W-~w zu8fabT;`nFFl;<%rDdJx#V7VA(_t$6rQ;2b%~dv?TF;|#tGh3h^I5e8d@V5ortn}S zXeRr%-nPkKUqgl6uxr9z|FB5ez`$U&ysXUO!~F*@UYg9;Bd%`Iv9Yny&^knK@4@a` zKBTq@swvC6*BD%io1jfxGz*LLh280Pjn~x~(-Ck@cy)t` zg+;hOwkV~Vt6ro@jHmZY52!uy!Cgi6-07yJV5J&C_pw_1g@>Q`A%YruDpUU&8Y*U% zWvSR`wTGMPk1)=a@}d}33Q$qNwH~Fe>1e}C$ZR?5L079?=O#v zs@QdBSz<)bXV%OCZaju$=ot(CDo(%E=x?mJ5U4kpaZqg>+;GKe&ZbXK*r>%EwY0x; zurr~D&xya9DV}5b^UJI`9dvkIL(^>Ug<~N%y030+ZGG*fkqF(UBCU3UDVsn`RP@5* zNiHq?rRFXw)^IrK!^MDQQwEC#81N13G?#(t7rO5vQ9>W54u9;6YQGa;)6M_>>*W-* zbGC1h2EVUJq%ZgqJl-|S0=ITFTmoPWCf*U)rGbl2t&iN${J4r!T6UtEJ33w=y8%DJ z_n(fG@P~H*&E6mARvr>qAO6%a|7Y9`Swoxtnp}U5qbMQ4-&oGwpoeQ}nfj{+1%==r zXU0D37Ye@$00Lp8yRMJFFLrhdi+&1DkPz{4{s}%Q&C(e*xYLJs6Y=lM$dG;UDH$%q zZ&}=FFMR_mV7msCIsewQQ#%w_wKh?v{sNzoKSzh9xeextqW}MX2oQjyJruF{=Nud1pH9K zTMxqh&7i@f*w|;`7oweQ0taquEj_fmD|!Ta|ZrvLv( zPyXDUng6Hd`LXlL^$m?l2!~?y3(Ae7<4`nhYwW+zW56sb2?-axPrj5t{WY?Kam2InLpO%8ic9e(2QyUxe+)#Y!kK!6)EkpPgNPp|a7 zQ#gT+gzZ$o#bG3(U704~crN$PVBEAaRDbi!MKvY+U%t-NdPm55Bo@7f=ic(zIKg_U zPHW&G>-5#Gl=Wx}EGqdA8ZEi%*Rrsj@+di^Au$J?bYe|GnDsVBJ^e1~mkS`6_WU3dLWe|BaJQbsS$T zp_hF$7PqtPz*NX{cD$l8n|A9-(feb#mn z@n=RCC>Kt}Ds~{;3bnl&b(JYo>m+~uD5BGL^KN@mne=dyOIXwt$C6m~)BR)@=j39Y zujT6o9%rFgL`tw{Fj@PreS=#K>%CnmM1ip6t7(BOuXJ4CjiMMa9}~t|ZGC((T~*o{ zkx9K_VP+@s#cN4%_i8-vz@cI$(Yz0Q=Oj#&Itf)%9H6LMSEg1Mkm&nM(a!+}HIwU=riV zY3Bz`$=7niHX$jk64}4wX%lF*YLc>mv57rLmgcb)XX4HvYzF3Vyjj$^{q<(yUn*MS z4i2;Rc#TZpaw*YIBC*I!LV<9AshYXXxj=;#4kXL#IRB!2%wC+EZTB{J+$a{F>GlXg zj7F8sV8>SWqFu0HHQWM*lc@L>MehlDm#Ys1${?M>10vjtZ6>;0;F^cy&?IS9>rK_2 z{EE>S6n2#5Lqy$R+Rdus|4-PtYVZSgPC`8O?T4TA(24e7# z2^Bt%BYSq=x*n7YLk8ryr}n@rC?exQe`>=Xi$OFz_4^EP?lSw|LeW(S5HXkkBD3$D z&mHHE=@{p%YTAzsujYuxJot;71&a{`s{C9$u1MB~F;(CuuPF0o3l3BV|5VU{$ zQx9XTVc*Tl71lMq?((&$LLw?5-yN^b?n50HLgVQ&Bh{;_9-DgI4XXds&Ev+i9XBiM=*s=kmVV(y^vJ{D3%Os%i%60_Buv0S zCP%8zP8N55>>z=Ilk?$j4VHGns+YTZAL?;c;pNhjho^Oe;$`<}FLsyW+;n1O0Gkp@ zF5Dk$(SEb4>(TmncX&5!)lWgB9HK0R#qqG&8BksxK3-Nqpl-}ITYzZX4s7@0x#H$@ zK0F-CSmVakJke@SGpcT^)M_8z+9^z^_^E2Ehm+P#!c?3NMQ(uf`U<^!bDQdX5gu{} ziGCyCcT)J|4Ay?gl77|`g$I^ZxVb%^@z^}PH0R zv203|1^2D(o&=24hanc-d`b~GbZ}x0RYs@%w z;x+;A$NhOn05YORs{uUB&Fm5az<;$Af=N5aPtg{5tdbSTFG`fMPk>AI^550VqAkd3 zTB2Nhcyc4AY&v3u2%PMQ5a7t?mHHG--c?YK25}Ft955EIP?zAC9#>h-u&X_Xgmy9s zASsr-gQSio3k>6>F#F}^PpD=yNmjMn;T31iDdjR_(dTFqRgJ_3dNteZ2+(2HuD##K z1oJcho-CySSy5b%_gqDGq5*aQ9&2w;mjFL7U@(h5`KY_2>il>=Y0gY+tZZ2=J+~y^ z#8sv}@-QuID5rSkhed6rU9i?0iAS)=u`w`YtrMLI3|<;cF3PqUi)~eSWF{4p{c9~^ z!O8YiE98^C*m`I2mhq0{J$(%AXT8?usR}1u)ZQ2SM*%a(3NHQm3D^@`VlOAiWft2D^x z-M%~*@9R`W*opmC6jGs4nljG5SsIx+-5UzhDF`#d#lv$4t_Y=2J|H_&8tWN2FOa$7 zt(*P9-;sRnj9SfBhK5TeYNRqc9c}~yUG0J$fQcQzSFEgbyqsi83>5A)LIDxe=b>sB zr?Kv8Z;fHBT?R7w; z=zQPI_HcgB)?Z3Xr>Cb=XS2?G=X|edhecHJ$uUEApV++HJT_RPf?x+CJ_&U)eGL7X z={dVIkze|~-D}2a=K~m+gpyC2XKlBfFRE}$?Czso$Z&bubs7p8rnvR6&Mf4xT5G=F z1Ouyne&dTGU@N%2frUd`>`wc0DYY53AFv0{_rZhhN+CH&YBh(9M?@&WJ&>bQbOOv% z?Z4$#<&ed2%84}ZvwO78e4*xPNPw(ltXNh*&U*rbn#`;I`MUeJV};{|iflz=OGDF; z(1yo9iTY1lJCx*qu=_+LBo4iUBK4sT!0g6_EC&o*{ypb?MygaA>c)O^2<9Ab+%yH? z`?bz<@dt*NsGvw*`~5+cRx#z~wkQAYsb;s={csKEXuAt(^M$g|SXDA>Sc+Yyh*@cG znVaVuZ-h7XVz`fle9?!3A=Z2!q=WNsPiGYGBm>O!W}tHp7`39&5s&9*T@mIcEj8Hs z3~@gA0Eq)jhq9jeeN$X^FRT?42`UMk+&}$hfH+Rl1+i>6flL=n428C$Am)yUwqM(< z)Vtc7^XaV%R_m8nS3|J_FvS&iFwk?Y5^_yv!~wsg?e+&F`l>3UzS^{Rw*poB$Eb21 zmD0@p$^6u>UAy}?VZftvHWdF6SSBTL+9c9>a}S7rz2sW%dL6a}Uj-0FYm(iO6=LrV zWo9eDFazLm*1e}@>k^iM@qU4B6cPv=jG{%1p>C6xC`Ne zeKZ5PTks60b?o6MB0g6!xlJ?<$9)g~$_mX+_ls&d!P&S<>BAgfQd;>~ zpgux21Pz@?+}SC287>OFIz)XNZH)Nt-M5n1XwkmdjXYlAx|Go*<8b*^aa8S5(GufO zu_!YBF^9ip`&bA8t}h~94}2`F{S->)^pv@)gOO70#W)O1ln1HQ;|suSG7$c$xz!F> zLiS?z^1Jn(kR*E3p}&$!v8Ji(U`0lfMXI6DZ10N0dZX3u^-~R>qqz#BdLGDbS_b#P zF+T1*LtvvmwUI%m85ILR%w4*5R#+wL=gOk*PtMoPMy>wdJz%<=S%pCA^KFdxMJz_7Yj>^iuI;|U2F`DG{#Kx zJ@`F?$N}ysrhXWF0hB*m1z~+Q9Xq2xNNfl7tcUIWDKgCW*DF0eGWBq2$AL|Md!dHX ziF6E!KcxC2yQQi!rLk!BE_~5yYqa~XAy@GB?_H$Xo|}^MzPrQ8T1ZFb-k&ifwt&*_ z{&d162aA6|5`dsPaj5$3Ti>?ehWEwSg%yv-tL=sW;a_bqcR-@0t$lT4?Od}oiR-3V z)=UFo4MHvZ%W%2QA{2Kk>F}$_(d=cz-unw}swFk(aW7?MZvfSA*3f-9W87xx zV1ooh3HK)vnVc0KV-g><)@pH5*M^nJwZwy?r#l80O`{hy<`x&`|QW zdx9lE?7!ACy@N_VVF~49y&Z2fHg*N|f}zwd7T^tS=rE$3P9D#9kT)t7YfIj`siMMC znH8Nr=h|x3zJrs%9I^25NJ~x1*E665LE;ahZjaLW(938AlQ&c9I$9$>(=d1RQW&3B zw$eKtl%1;lTF4lQbka`~(Fs->W{c&)SzZSiLf)#@-%fF?zX#n=G-8qCr!VLD1<7Z< zgq$iZU=ed!E;nejrwgT4f%j;R(evA3Bg!6~|6CQ~C!;ymZ+NnRienP0-VYcw3tmE? zcIVTd-5k$<3u1V2mXOUl;!_k1`De+Y*TV^+$9t3B+FHORfP8+?k?K-S#9JOa{=le=q&ol<-Em=6r~`Umj>#C zcb7?Kl7E%vu-phY$(Ji(H;2XbrTywgAMaDt_A4-u{V(-hi`4y-tWw7}Fe1UEs^^E0(9n#BXN%+vawQC_wd%$-_p!Z1 zm2xEp80eg)qi^Z@_k>)wEy>z#A8wiLzF55WSe^|TGGrD`^vk4Fu<(;G40^uZceK>3 zKbR)T?63RHRw>t{e7Q9_XR{_VPH}mSgQK8oTA#)=uO`|ZO=N`rZuvs!FT!a(wE^iR z&6T20Wj{gNkmG*+zK2q1yDmm1#;!r9Rr6KBh25BPHk*jsZdF3?2Ef-jBHJ+L_E$%3cbd*;l#v!z;EpG0hvg)CjP7kD9k8ISEC z>q!1I&nu~bR!S#SW(fBOun5L3Zm3Jsh>uB5BF0AHgqJJJ+BR&(TGeghd5{suvN*{d z?;RR{aOI>oc2~S)r$?c{M(@8WyR~~p%(EtsB{yP}zlCrcm7W#p>ul!~d=> zID>oFJ77Tn~sgis@A~K zuz8*zTWK&Wpi46e?UoGd{^Rz_Sqs9zeY;ImDe-~tFx=9iWiUM-j5)!{mmQ%v)o26} zeMX-Ni#cs}Jy<8U6hmFTYDu62vvQeIgkQHX?*;kKzZ<_A8GhEr-vA2G`}Y@G3>s_( zwc-=|RTNvK2;4`8MVTFE70AL%jcTC71zRLs&c4&v(KWB7kHHiN#+{@pjt52cuw zF>W_;h5VqTJ~d8{@s7nRE122I>OJGB6;#gT)_pD&(_idQ7Cz0i9hdZXU7Gcq*>klh>FO2wMo=g-8}mrd7RbCr?Y*7g83*X+%pD5u?A{Mi>7avu_nSJW6;`7#Yr zrlq!ZCQJRtp6IBXP9Mi2eQ;502A2gJnMCHzIg0p;XJLs_Q;>4OorX+i%Sr?MrHBXt z`^EkLaRD^clOR%p7B3d7-NJ1+0mbvAIo;k(MsXy9EC!*hszMRrXuOJ_6=9=>B*u@= zCjnEJ7V(;b#f#kTTQ%dxd-fevm%|M)RK63h_QiC* zSr7g-$!K9|j{`bIF4OODHF3d=!0zuPkN#SKa4Is1lIPbWPwJ-~8uRgQ_>Y1z?!f8t zxQf<5mbIbEsD4-~7e6j7zB>|18d%f-p(<6b$YLG%p)2UEo5bb~7aFjbU&N@G(JwlL zl}{1v1Mrbs#x$g-@zlziPLhS!A5H3MFi$Kj0}8OEpy^AU%mv+e=f*Ldz45#0^o4c)QF zG+t#rg}MHTH;+qstDpL;sR8Qw?Wi{*$Ew^ul=-xY6%x7}mcF-!XtciEdQQE`dUL5Rpzm7!3yzTIOd>+1=TMzjgM z$bT9@V9l;iPpAWJ-Ixi5e5$#}o*6T*pF0tf-X6TIs&snL=K%Hrs_RT{#zzMJ__T(Q zPmI)@8ig{Mlr!`UpQWtOu|tPu4g!LL@YJ#!l%onhjO7kw*ijveGWBwfUcNqn!}e+R;Jmj_Cus8umV#IfFABi)7!jCRp}E6QXz&&6=d z`G=^jl0{8{072R$?(53_sXNNKGy}J7 zJ_9(W1mlx!Wwd;_#C`z|zNTvn@rvIU#@`G>GAyBWqsIUjg2Bb-q0+3l)AYiBAP-pa z2zxx5iK>ikj8(na0cFzzKN1GRq6PXteS8+iZ zhm+l-!7yD!iGwC0>T6UDWquQ@dD^KqZiUZh%)?`%>ka@dMsyOD>`f=8(AA0vnYUQW3GooaA39pV@%}mlv}}0ylRhmtujM?pqWUii zACKZ6Yp$ePIld@qC;wcicG_Xbh>doz#fppcruahn+e0((9}dL8sSP-kei!5d52n2x zf@l`ov=r3^hfWWoh2yMx-?Z!BE-n=Zmq>Riu4NcP?SHz=Hu~-L1OnYsY~h4xJUmvy z7!*h^Ivg_!y}Yu#B-X~?3kLc{4K8ks#OF2@4_UVW!Nd0#lbl&5W<6Ec1+d+Y{6)O7 zWf%yE3TUa`f>{#8RJ`z($sQ+>J(d>Rc_K@+9_0%=j~L^6W*uvB!N*cw_>O2A(8g?o zG7Io)vc63`jm?#+2pv_pOm+7kOJ3SnEdu(^AlkQ*D6fnQ9wZd_I-r4=wML$;*H6Wg z#r+)n`m%ZaZD%ySwP^M&wH`XW$>TJz47*fAQPEkaEI8-{_i{2i7Vh>_ z!a7>j)oO^EtPCh=;pWou{)5+ve{n77hf1lNp=^*GAA?PV=Z>9AN-!{6R zX)0a}FJesP-FL?^0khn*vNFKke=}XF@N6o8vqW}mcXv=0sx8nFzgWRk$VfrhVPt{jms!eG!IxJHpD<(b_e*2Zwhx03Pc* ze?w775NG%`dp`j;7RT3Gi!FaML!7&QR7sPEYv(mtgG=CF)0hCLrAtIN8XowJO-!ZkNMB9v!gM$Q>Yog!6Fffdti5# z0<_IyK3x5oZ1=*E4r|mxe4|hv$E~vTye6qMs6^!Vk9**UAH=<*i68c!IuLHY?&LevA2+j>5<`_P_iVj$%Vqh!HFu#n4BZ%dp(HZ?C7s&jzn!%yQ4P6v_`YB-=6J&zy_P_dot_jrZUeCZitPjC+4e^@T(j#Ks!UPSaKX35cIJ9c=lA z6iP~aj;iB1CH~2xYV^Do`&*{phJo&VE9x0k5?z(U%k>5 zmW0Yw)}Eox)`HtjIgX;PRw-kGR!wE`=AN_cc??U%?(~EoN9O#e; zDD!@V)KHDWD~U;fV~m4g&~C1{`9PW(+~+wkJlTXsG4J?`QP;Z8a!%>(v3JCMlx!?( zzi>i(_S`ldsIgQmBS{~o{{B_9%MpC@DDZ|%?;Q(i=(0Ne(z(r&;uAP@cs}zqIAhfA zDA+Jxiu#aAtyP&7HkJSCUb8*4a-Uo#Y5KF09Z~@PDmW!dy)r`VgH`gK>D|UOlnYa&{8_U+Dv45e`I=r_Ex=L+f$(bhqQ*XWIaec=}mEz|1jO9@a z^C|Rjt+itvy2y@T^?inVhHbB?bio?;OK?hsq#t+De{-zkJhozM%r57?Jwh5Z!nUHF zP^=fqQLAYAUreK}gtb#v7fHZ1wH+R=T5~#l7uS)7wpvG-_LGU3Kx#=YS{Kvt@#Sa= zTScSv%IIEyd25YQ2pO4q3phiShSQ&Y&U3l6RnFLrMAB42+~-cFmn&c3Gms(6#5ASh ze1^V!6R1x7NXP3%2VVz-bTBPGQ{eqQTe;u(6vjx)X(_&YLcc9gJaSE@fr{>{+lM@a0g z_hU4a!*h6<&W7d}cZ@6>73KRTgw5>cOAB3*fH{)L^b_01yb=u$WjP6}q!D!NJFJuc zNmh5h9##B9J$N{?DNa`axXeEfF*7quoz>AU_rn4SV4v=k#lCb~A3a|N34}k-{i2=Q z6 zLHd*8enw!3R5#ye8=lxP(%CtWn2-f|Q5D#r51HN;Zi)-rqP+dDtU@O4{fb5;EIuXe z`jit{C8*D1akk2~o*?Iw0>N8girO7Asu?4Q5bw`jnmEecdqigK2Y;x)%q|Eh&C$lR z4Jvn>o)?25{!lsF1kGvtqo|34K(bCqUW>hem8NbUx?^ME=@Nzj*38fHX%EEC zB0#FN!`}#sqG9!eTc9G;`y|{5Is7sMgmHjlpf3f*1|kk$Gj5FbK-T@Jsth+~t-=R`$BwY6D$Ew={MXn>}pcMwaT zFTQge8%k-f(H$_FFSl*4b&Ye#LNPa=D@+1LzP(I3FO24`#DWQo{AJ{N6hi1FSkG&)$K z(mU;i|3pATA)MNgRbXLGgi2Z2=!38G86V2>baczMunEgRP1$cfAb~CKi5}QQmj2t9 zIYva#qt?gml1&_7#lTpxxv5!#U+)F=9O~$%vg0ZgQ|R$FG`}?4Ul!OIqTv(K!tFC? zvb!#8KBfScj=*xBz1q4WYLmIL=$fQl{dwMI`#qQ1e5lZ;E8pYE$?vah3zfOg7cS{3 zw$HoS+L_5kT~o!l3@W|C^D}%BPFDP>wf0NXDf`$EZ7T3?g8VppiK=vAa|zj>lusK9 zwN=3@Cm1KW3JuIZqAi9(>x)?8S*ppYes1TJVa83`fM!LdEd0V{H9D~HlKK+EVC{g`nbDKpo%ZTHfQ&;9_d)5oV_f9_k50rdknUB&rSxe9LG~&gI{oZ67uv$1mz)67C(LW{$5kp&3c*qI zdMq+t_+3(AXrqb32ApUzNBKy}2W~%X#1rnNpO85A&%|17cCrObBD$hbD56n?m`Is! zH=Nn+jQ^}UUcq631rlO%zCQjQ)XfeeGo841v26kr|5HRJi54_X6OE*_!D18_Eb*uV zkZRjvHJ%VNV;sxr?-4blPst%D7aAXB`tKLg6W)JNQBBKHZ2 zdv+53zendpC>Z!8lm@x$$gPgkRz1QePTBN>gL5h}+JZ`*;Z3X3!{6%vF=1qeqgXjh zwSDm#87^+AoWCjTwyrLReh0cODMQkU_%1~}4xEV*eTo_0G@^*`fN4KjCqRMe#K06^V2^~{W5 z2h@oW$tpKWM1kOACOC6>i_sa_n%=Ju?Aw7}L$3{8dL1}*jz0+VtncEqfx79G+o(!PXjQ37 zYsvZLGYR;-)ZbV!I1vdk@~5Rby+uV{_hwp$)AI5q32MShX58cvm8UJ$+4~S+-d64j z(>h&X<{}ndUi_;Sjbt7kHPX}7s(gHR!9{{^;)~DxE&3}-?%!_EOWND{puoe;7V}7~ z+Xfz_G_yO03u6SFcAD=vP<_C$|CCxlygpxGh#pM_bJcw7(MhT;pZT~3gk+62|EYQB z>-`S67w!+v1B78-W!SVnsR$$Z)nCMdP5# z_$}r4f^dM^{WrRc^L+xl7UgTPPl-%`JSx%6I9;d5>v}gGZ{8FLr0=FMOJwsq5;cUR zaKBL+U%QjD9itS+wuPgGj>Pn70~lfmUG>!KVUgO8J~02rn`;c$n;!nNj2i;@L^pe^ z5+a@y7pNC(PdTg6>MzVVfHZn=*KavOZj3bgPpOl`_q*+$Vq#+1XMkU5=)!&3D;y#c z{pp=Z)3|TjIWBXkxVV31hz?h${|vBz5s4NP&PT(Jd9aV)hXJnc^z>vGH|D|-PK(v1 zV3QW49R5;nvEd}Na!+kXTH%I+7GP09#@d)C5(pz19hR17FL{al8Sqv>Ah)Tu9Wu%M z)~oV8vZh8QJvVd}%{Q4?LfO!w$Z5^Ugpfe%+sJW%5b(b{3YviRG&i|TDGHRi_-VNi zVLAWO3WH$1+JeC7KY+p~WT9|$t^~AHgM^W{B9973}>K&gL7giZCY2z$^HWtXKt;an(!W$vn^6<}5vr=d4_#L=M3&9W_ z@nIBA#M#rECL;KyU$6W;o@Z23$8EYwjlY#*t+c|*Umoc3_nQOqEPDpziHElGQrQpW z$+$iym8zC2wTi`yxT{cs=_+s``=_nJtU4`jT0FJix8ok@6i6W)AphvKeSKpHZ=5wl zqyPQ?c)^PeM}3MEQtu9{6GJ{s1wVu}O8aoc>=c73U63~pF>Ec?P$dO+2V zD<&oeaUKr+>i0B(AsIWbVyh1c?&aj-QYrq4RC%VgGV!#-cw+OiIHGEO=>G|kT`Z2} zQa^UyH0Ee@*;j1P3D5x`w@>Fl%0vKntG~hyaC9MOX5e$R0wp+%O+~Wek)TV!m$9{X z$^#%9O)|cAPY_p51qMh!r~>r#aD^;0U(_C=GC|A6^GSm!(BD5qN#EL9HPQ|b zAThILd(jLQoN5H*(N*=fhvKR7AA`FXfO#PTa3f|^n3Dws^F3i6yZ!n$U>QC1H;`%b zqEG{YE=C2g3J0P2ipN%kU*@mb0Z(&c-76MU1>q9{KDXPngdppyj$FKmNqX1=NYH0L z3a2e}JAyj)N%~!+A_p-1#-Rs$X0*D9diw%~k-0hZpI=Jntvt5(!b2)yJrZ7Y!w}sb znJ>LUbjat0ln{@IOraS+s?S?5cN}!~2(j4Qn&MPSbyd=;^g=&>MA)GL7Q9`3SFQ8R zWG~TKIUL@R!x0Y>xif~O`+O$RBfW0fPwDI6^iX=NtE= z2A%Ib4l?{JThAwtXP&T^LP{64k#2)EB_a367^7GePr<>$9@?s%Q+eN;_cEyu)NvO? zlTvS`&T?6;6-qqO!cE{U8F1Z^A3yF=RrOr{*!8#&`r6^1cn8=^*sj#f&1si-&HyqI z1*P3aaz@7r(^C3U3Uhe70l5E^4gNC_=y{sRN~Je+{Vp~I>4*VN}C%?ET#6iZVS z=#L2n=1nu-+bbQ8^Ll^-6VvkvQ~wgC4Vk+!_QMqB`%5WH-bSXh%J8%*VD12M?8eZ- z-r@0c0)p}G(XHwqp+q+yCc^?v$sg@OO~8+Mo0$;*yzwhFp3Gz_Z)-VR1DqRV>2lev zgB7O1ZW%Xul`oBzsJ{-ZVbv-APjvXlEx}&>s;e3WaaYfjN)7g$wB!pZwT*kJ8 zzjN>AsxoKS&@t)JR9k_y(uw1rEjh)w0{}HB_6qKP^B0Q{wuBtCz`M)gy}rNE=VTsk zCOmn@b9lFK-d*9bhhHUP!U4%V--#zOo{u<;=4>ApB@JiEJxw!x%#|yDi%==x#JE)w zYGJ+93z=l(brcdFT_k6FF=p~brkYl**}w~NPb4m2ijVyo@TnmXo=!ruGoPya0J*g( zL+utxlN6i5n=LnOJ6H4z%hU`}P^UcfFC||>w4Sz#He$Lb{8&^iq>Mpg+;mi_-Kn-_ zUU9TALAKIjo>`_%9fw50s3GAGW16!E-}L_PVJsZa;OO`-W*CrF|6t47 zh)g?paUq6wx^q+Jilg8&uQ~$+`%&i}5eikMPyhnv_nJ>Hf=jVgsIZav3&_FS%Z?Iw zlBY}MxzQ?$PZR4$Ve-0Zt?ru4|F}LsTOC~?#(_`7wob>hcYxXRbKX`(9Ij5#pT`ER zXZiGIpW|sjpV`|gQqB#Hj>NEwBK3B*IvgX$`kw}8f zCeN+=Ro&sg9+_$T8rlqhJL7a6y@#XEJu7PhdRLc#fPjq__jdpUaH?HgSAP=uW9W=+ z%{P`rE_&566t^8DXcFtT^U3j|+Yy=i?q&($$`x00+J1L%zC72+MGpOZp*WF#wlHrO z7CG|}cJ!uoPeLN+0ElT5&HFJw!ilf=_^WG&GNiE*bD1)fyWTti8wrSDb&mSG>&HK+ zu=@~S%E95=mfhF@a}#N)^77abX5YDXk62h@%-gD~0c6Jv z-sbc=0;Y3=r=6!b#c&(H6!ExY5S{DxQTdmnIGJiDNunWU@jDADrZ?xquc7eINchn^ zi*S}}Ib{qYY$gK@)*mg-1Y~T!WCO)D3kl43P+i!VHhEqQ?B5nwrfp{uk#&#>dztPo z5yKSgC7UO#)=q~Ub6AUm9|3-!nKj_a0V5Axy+bPBu7-SLIedjCj+VYO1ia;bxG0&& zAVb>k(`iU<3K;x6++OF3mX`YYEW@FiS|+mdZ$Qlc_tONQ=Vj{5i>KEr!8_k2gozkfRhNjv$FIL_`Wjii-o6#&PL!GkbanI zO!bL1?Ja@>($_f$1Wwnq-nH@vMAKW8)T;9VvD>nWH%w)(@Ux~zRVfkQU7Oc+!;{(m z&hyP-2up|;FAa^`!TNhtnr+f7*q7&0|JiUN;a6R*Ab%s^&;tsY)44!!i^P(<#&tg& z|6%NIEG<}+H%>}HKHUTd(qM3$K-PG~<#B?+5K^S7pxU#gOyhkVAuv&9+CGW z*5qc)6zQ~TmV1Ezv_omtUTZxRknqLT-IW9ly-f=Uu8q&lj2K^TynjBu2!~_SiCyq` z|8MQiUOtmNiD0_0uIcvbj!B!z5`}>uAQ`%Q56=T3UnnS-mKXCj7mohs!*$UwK|?NM zQ(7gPr^y|WVu4|&wCW8T-)^>#u8!%>03h|dNfXG#i_tO5^l6G5Os%i!qR!~Ul)T!U z(ogrHV$ivYv62EqY5cy*ZNOI3eTsFuyK}-EPUuGXaYH?G07S?K;5G6dt-GZ#Nr&z_ z#!~ko`?@iu{TPJa43M68&ZRWXJZdI^&MDZaj7_FzB{-|CX*7--z~d+?Ka+mk1yZ*f z%(+Pq)Xj7lW)(<}4H@$6AJfv_G$J!xU|?XfX(Zo~BDh?LRb7vUZS`G6s;k+&q`wZA?(~9f7AyA zN_h3x^R0VCyLQjSZO_CrWwBmM<7m!-~blzQXYYGSDn z+YkOm4(6qr@?F3<`^@jTL+$m#<+x9zXf)dDw3F~XJfPvEWY(TsHfWyS0LNu#UQ3FR zPfA>pn=f&_!+k?SKE%8HXDP$Fh`9KBemN<6$-ye7J2F0ac}EI>Q89ZTUjydo=(o|X zdwSith4zZe>^IuqCy(s0nS6trqHW7Td^F}0V5u~2doKfnRU2j^lu+kKRpqj~YkqUGTG0*|;;>5HyiTkb zj;6o_2DR?)7iRMdAfSktW*!hxf%F4Ewj@P;+V2TH|D62iqV zO52B#KLXNo$<)(Fawn0t3L>_)a_a0(#kfuj77BR#hKSJu?gH(ePDDL;Don8}HB)&l zGpNgXfdrO@v(-k!0m5|MWVy`NZ$R7cEJSYCD zplUnQ+t5Lzn)5A2o2>Z5R4%2Q{`*V4hisjhcLtxgnjFR-WE1i&Lb6sBvH0z(A3B4l zI&~KHSD*;smKGs*K#pOeR~jslSMaDj2_)8%>?W2QX$y}y@M1JT59bbaGwZWWtPgLJ zz{RO!OV^R}+;&!HFYJZRfy)=%yZRkhnGyNz{%Y=h_~^x|AF2^HvfuGqyIcz96&NB6 zZRIV|h$eY`;dl3)qzK`EcNfTJ8=*`FJp-=Tij2q}VwpgQX|k1STc4Si-LYRdIU(?t}M{XOY#HDV)-8$SMAFmLCdSxoKDjDslG$B5? zd%uJLcrM9S@Iq#JKYY;T;>!NwmAtLCny1#Pn>xNguihXf=Yk>g0!KscRcye5f!A=> zD;KM!_W5)VMnD(P0HKt$?spWjZVb@~@*gpeh{uu`9$nXVr7wkj+=(~-NnRECkUb%Km|AV_-H^az z*Bn3mp)hiIO~50dJRu(Cz8Z(q6q@5hT_b$n3{B>iKMV{Yo+RC8-lhf= zT-vVM6MhyLp?JFr!N<@?4}VS7Uicr&RX?!5hl=Lt%AD3Cno_I-3q}ru43r~G&uh1` z5s^Z*!p&-_q-q*FnP}KDg+WWA%5eP@ z4mskdV^obOPrrHA#NY(SAS1#3KsE24T`DpISQg*uc-b5~v&_i>WD#W;FYD=ydIw

    jBoHh}ekSlvBe`|}%Tkq+gK|>95Te)*W9iR;mm0YwsUETMI{=`%8 zFV%7#(}n#Wu&&)F;&ElCqWbD+VE(pdC!v9!$Rd7M5i8UepoZ3^-&9U_E6f!0@k$g|1({D+cJ zw$aCY6Pc_-@*6KyGw!Ml&|*Jh3}&S^e+Mu(6^o!mrK)c791`Q5lNC_I0B=;YtOw}t zPaFJM*4A9CxFmn{3r8RHE(XCn-oh!p6oxa$(TtC#)AZJwc~S>UW=eu1B*%cV4Vs8p z#(LqN70=qgJXbzmuv(s^pGsEWzJ!S)juWKbr~D>-5eGoOpt$K zS>{F1bCjoY+axy;XDf#2_6Kh98~z_13b~$<2P41fw7o)GMdy=8vvL6m8RDz*#|txNr% zuQ4RKn{Ar^R^zdly46jo^D1RJnyd+#STiiXbHa0C1+M~%&|O;t7{<&A*n}OSlUXJ| zFlRJR^apjUZwyxwibL? zU5DAyeAkYT2PbN|)dx3DT*|H3?*?Iiq{l_D<)~ovqzzo4;gADY zudNO$V|2>R2-A5w$uGZ}GcNp^iA(MTB z?-f4<0M>V7#GL`f(V=7N)<-a!#1;H@&8suQrIiNFv1-z)X&foo*plZzNB! z!WSD^^-KwDiJ_%b)T9O-0sZ@ zsFQA|vw1CA+}0@aU`d77*^3kvDU37+*sazAs+g=&9_Co~`?Si`8z*@+B5CR6lu!7U zuz!zoZ*K%h?_^Yi-0+!%&U7EiO!h)6pZntdLWUpXWeyn^%36^AMP@LQ=sP9T)&)#h zcRb0)kN^N-ul-tREwGjcrnQ_; zd8mLjUy6;4xU)1~cVOhW8_-?Uj@h9{YwVDyp;GY313EmG%a(CTkPM3!;Ouca=LvoS z#rcBusaD9^7jiB;Q=PLO3fJ?g=r^@qDS!<9mu|L51RC9Vc60^611xZ4m^C3IA_)$t zHt551|1bYl*RKO4wD2iAD)78}emi(1C9%0PQWg0-;M{?^8k*AaFMw^kQX|0k2micA z?&(YcAizl#Dmkz+0}*FYHR->(;-d5}ekFmGKb}<2A`1JP_aqeIq`@Dly`%9dzz1262mUfoof~u2{U7X-=xH6pa~g&ZgM1ZAmt)pyux{stfTwm!_CXFUkL4 zULMXi%mMmkG!47OQt^zQf(fa1lf&_-h@P0j4^Mym3rTQ`0aLn{qN03yiZZx9v^3LB zj@XjaO)1aj$E|B()Vt^8BOvwpH|&cRizL`2;Jnkt`~YM=5F2A1+uGi3;FPj-_mJaR zw;`F9rgs?{0{za@`TaDJa%hoJN~=xs6FtBh`*pss4s{(Fg&*e@z@cI&X+*xb<~)@n zS*jN?vi&pJ5698?)&o-wc$D3vu7AXk2!74q&NTU-{gB0*j}Mo=Xdj*t71CgP|C?s~ zQtj=4OGRZQhXat4g&Dc5hqJyLY_>NN(ckTnZPoWJ9q_qxj=b=*n_l3;7$YE}uF%{r zH~!HyBme4EtbTNk+{Ob7x`Gfj%D`bM<5SqXBZXJ)8(X$jxf!5Bu+%IAB;!z+_}WLP ziIhe&6=6|!IUIvKz{^8GRDU3ZM{IDe7^j$4A zK^AqF({209ICc6e#od2i9hZ^!uT2MaT;AIxJ_Z87G}bFji=##9cn{h2vBPYU!oRC) z&jA=tl4%arJHLG|yFY=Ewh31U<}Ah!13d-Kvg@p+i_s^4F-e)&mk4)0X|~72jcvo* zGk*=0+8fbs@=!KTwKYRNv!0ffvLC*AHdx)okx1qo=H|=Fd6_QP*W9WGoPNP8#JAhK zLabV^=ylMO4PgG;e?(n^zMU3<88E&*eH=VAkNMrwht)2Nh z)a(DpbrL69$RNs+Wd;)>OGaiiGpFqP8nRD?jL1?LHAs$iFxhj4u|!5kb&`@$mZ7qS zL1OG#V=Tk)xjWzQ=eqg>KJ&x;a9{JoT-W+yWNz7z60{3wp>H8$7w#6)&~ z6HZqL$C+u`09Z-t3{$qG!1r-z{TC9^QB?u}DT8lZg1&unc|#Qap$u3){{TbYxYMoN z$p9-X90A=(`$zB6Q|)aKoW*X6roX^|=Y#n1Vzcv`!X>>&PHD?H*?WHWguz!W%UxEN zM7-ODTv8nWzl6fxzi~?GxjYz=!4ZEE&er(@Lf|3nsx`(SSoOC9r_oam(_NqZKA9=( zpXn`WGcyL}#(Y+vev(sCIT<<2y7%eXOND5V%Qj}cbb=0N=f(s| zppQV2O%;Bn1gV($#(meJ|c$`fo$WrzCp6VYa5 zWtZ{X3$A6+dUW2`A$~m|md85P#1@pc+)(ui$Q9(#?|e?ASll~L4-ALUn|76%uwHn- zqr)>X+HH#x^sPaIy6Bgmt=)e+`SIxJ5*4X$kc#1nfcgga#g~Ir1jwA*AWM|TdF`;X z{xk31Ryt`|iZrPkxXc~KcncJ6YcWR3g1myfKHslx1%Zw@^TT4DqGTrOh*XTLs2~kY zZJfTEn*|jj>baT?dCvt*b8+#t7-BEjly>&{Y6pKsdwDdL3Gzr4HM3hd8pCb}g|_QI z?|rq0q5OvKenweUwY2IejGIfb480~|(iB_hb4g`XM_mhLEiIAQAj%CPPpaZwzw>{3 zR<4<_M1-F1%wfMXyiV_<=bSkwLTi)diF!=+Iq>$t?2WZh3~RLeqBPGb+ls4pRaSzU zjUpuyMx^COUG<6ZNUPbMWniQHn6<;7ybB22Z?0I%vQq}~Y-|_4WiTno8TN%}q(GI) zr%Hwf{T(6Or+LFh)&GQee>f09BQ$XN&IZ@31A8)Rk8dt>l4~|+hHV9vj;m4Y+1bL( zQVKFcWJ56j)1j5Y^~&5LNk_f20QY3I{H-;Zb&#B!`_fmY2j_x{rEtrFIt(4GY?Xkp z^<@2?4vbZfxo%Y0Z6&e32{1Rt{nXzw0F>PNk(L**E$l6DjsrV$ z$SV0o;%f+ej`CF*5Z360sNu9bTXiuZ(uCg$$!4`5Zg>C^_{S@G^)z^3R(%8tQjdR%ZR0f5J~%c_66F(f=bE8=V2)wg-L2-S>;fF1Ft?j=4zuU=i%e#a z4np;$s_D}CQ~GlFZdF;*xyjwkeaaCB8rztJle9wwzeNt?z2DgPQDIbz!H;zOY)6Y) zBMy)btIG`>9@e(k+6E>{Vndd;Ud=m&Ex-YwU8wD?E9};X)YZNMBf^(J*cc&(ZoxrK`gd1~kOToK9ua6#2>$U!M^4l!)kcezFlb zkOmGvK}o@}huSIOyFZU#n^DBtXVa=z-Z#I;fx%OG=oCzj%CfD6zW!h27q*g_&TY3B zkM_liG=j`|xV8HCsXHvC8E<{JPxFH&5nj3o1Y=`1h^jrY8_cMlY4QMenENsJuk-QY z7Na0Gjg7K?PbzzhCm}J!MIlwZTxsXiGk#9~nR)9WA*f`I@CRd?7k|;EyIf1(?fbDB z9$#LF?a9I|>4hg3&E~XxFbJLlmJvY`Nku2<_|C#Wg|`3o=9D(lh|W@OlZr>_;gjn^ z1oZY7WyU`~<$Hs2rETD*q?aaIRuoFjq&ZD4Yy(rIDkXBwR_wjm$DW}}07`1J4&h8M zNqyVsMv2aysn#jI_(MxUNB0cJpn>~Imfl_!(5g&?wr{V7VodfjhL5S@U7d?8hCI&P zHYwPi28}8HiJ%jjKYX_YsUnc2pw|;dYwC}P|HgO~HG;)rgOv`0oF8B&ka7&2`V{l$ zU-!X&c$(w!Qm%PefTE~xrdy{f!ZLxM9F>%-^qg$%Z9?;J&T(bSri#b(rok^IV_Wpp z^HY|3U{{c3On8iPNRVb=NN)I1ql^`Bf_ki$7oCutWB*4<^S7x(_7@s9@7276j$y-By7wq0MIQuSzM<9pE@r;fwpTmRMD9c_lk4)88*7`j zrIXCxWzffS9yJWCj=B#j?+~-oW7lWSM=r+wCSlfrbt1sHr)x&rGavj0Wn%(jqu4vK z7?E(<=!k?h5-!mge|rS{OXqY%N9ljc(>a7}D0)KQ*@1w*h06YAP>3F7dh5VF5@`9 z3Sq)lz<{aP{a)j0-Sc&C;+OP_!=<91+Go<76X;0D7C~IiVHPJ4WLG&NS0GAr__VB# zH8e}Nh1)vR`JYuvNGU+{0}v(JDYgJdud>FxFj(K@t?e-h3~^(VhzA|U7AnMq2i`;P zH@}qYN`ahCRhK=UeSpQRUaj*X!y~>0^E3211vy#<7R7M?Oz0nYmhO{iuc%j;@GNp*5{VY5-7 z!Cg!&A}okQLu$NeKo>xnlhZiu2P*b)i4D6DH$>bWF&nP7u68Vsx4ZjF&x3~QXVqXJ zv~H->aNVATT{uN)4y4UZm;AKq0CD<7Q>Yl;Zdch<4`lXcq>yy%Qdvi8gF#$Yzo`xQ znn9BtD+{T+W{j?VL<|}<=fFIVpAR>b4>nn2Ngap;!3dE2hl>-YlDasFRlP0vnp z*0QCHx+EErdsZ*@Xkex@Fm}(*&)dAPy6ukSwl>d0#Ko7B;nX-u(H;VC$yLyq+CC!L zwlGc<*C2J%M&B@*+mP*l(CnFnP2gckDF~w|fmf~Dy$FyrQE-sn(;Zpd0$0MI4eTO3 zV#O^H_wu$VMKCqXXO|}@646F$w6pN&v4z#O=1N^VyN5MJ2aU9*f=x-d!N%5B1v$Jq zUJ5+4ot%amWBI}$x3rPH2;gM=poWcqFtgzCiy85OmEcb?;9AJv?au#Rl>ffbpZ+2) z{JB_oDB+hmwm(X^K(nPp9^uRMyA*$sTQno8UA60rJ>!3 F{{hcd#hCyA literal 0 HcmV?d00001 diff --git a/extensions/java-modernization-studio/autopilot.mjs b/extensions/java-modernization-studio/autopilot.mjs new file mode 100644 index 00000000..60287a6e --- /dev/null +++ b/extensions/java-modernization-studio/autopilot.mjs @@ -0,0 +1,162 @@ +// autopilot.mjs — Sequencing engine for "Run on autopilot". +// +// Given the live plan, Autopilot drives the agent through the checklist one +// step at a time: pick the next eligible step (respecting phase ordering), +// hand it to the agent, wait for the turn to finish, re-scan, and repeat — +// streaming progress to the canvas after every step. +// +// All I/O is injected (`snapshot`, `runTurn`, `buildStepPrompt`, `onProgress`) +// so the loop is unit-testable without a live session or HTTP server. The pure +// helpers below are the same selection logic the renderer uses for "Continue +// here", which keeps the visible recommendation and the automated run in sync. + +// How long to wait for a single step's turn to go idle. Generous: one +// modernization step can involve edits, a build, and tests. The wait does not +// abort in-flight agent work; it only bounds how long Autopilot blocks before +// treating the step as failed. +export const AUTOPILOT_TURN_TIMEOUT_MS = 30 * 60 * 1000; + +export const AUTOPILOT_MAX_STEPS = 25; + +/** The checklist Autopilot follows: progress.md when present, else plan.md. */ +export function currentSteps(state) { + if (!state) return []; + if (state.progress && state.progress.steps && state.progress.steps.length) return state.progress.steps; + if (state.plan && state.plan.steps) return state.plan.steps; + return []; +} + +/** Stable identity for a step across re-scans (phase + title). */ +export function stepKey(step) { + if (!step) return ""; + return (step.section || "") + "::" + (step.title || ""); +} + +/** + * The next step Autopilot should run: the first not-done step in the active + * phase, falling back to the first not-done step overall. Mirrors the renderer's + * "Continue here" so the automated run never jumps ahead of the safe next step. + * @returns {object|null} + */ +export function selectNextStep(state) { + const steps = currentSteps(state); + if (!steps.length) return null; + const ord = (state && state.ordering) || { activeRank: null }; + const inPhase = steps.find( + (x) => x.status !== "done" && (ord.activeRank == null || x.rank === ord.activeRank) + ); + return inPhase || steps.find((x) => x.status !== "done") || null; +} + +/** Whether the given step is now checked off in a freshly scanned state. */ +export function isStepDone(state, step) { + const k = stepKey(step); + const match = currentSteps(state).find((s) => stepKey(s) === k); + return !!(match && match.status === "done"); +} + +/** Construct the mutable, serializable run record broadcast to the canvas. */ +export function makeRun({ scope, maxSteps, startRank } = {}) { + return { + running: true, + cancelled: false, + scope: scope === "all" ? "all" : "phase", + maxSteps: maxSteps || AUTOPILOT_MAX_STEPS, + startRank: startRank == null ? null : startRank, + status: "running", + current: null, + completed: [], + startedAt: Date.now(), + finishedAt: null, + }; +} + +/** + * Drive the run to completion. Mutates `run` in place and calls + * `deps.onProgress(run, state|null)` whenever something changes so the caller + * can broadcast. Resolves with the final `run`. + * + * @param {object} run from makeRun() + * @param {{ + * snapshot: () => Promise, + * runTurn: (prompt: string) => Promise, + * buildStepPrompt: (step: object) => string, + * onProgress: (run: object, state: object|null) => void, + * log?: Function, + * }} deps + */ +export async function runAutopilot(run, deps) { + const onProgress = deps.onProgress || (() => {}); + let lastKey = null; + try { + while (true) { + if (run.cancelled) { + run.status = "cancelled"; + break; + } + if (run.completed.length >= run.maxSteps) { + run.status = "capped"; + break; + } + const state = await deps.snapshot(); + const step = selectNextStep(state); + if (!step) { + run.status = "completed"; + break; + } + // Phase scope: stop once the active phase advances past where we began. + if (run.scope === "phase" && run.startRank != null && step.rank != null && step.rank > run.startRank) { + run.status = "phase_done"; + break; + } + const key = stepKey(step); + // Selecting the same step twice running means the previous attempt did + // not check it off — the agent is stuck or waiting on a decision. Stop + // and hand control back rather than loop on it. + if (key === lastKey) { + run.status = "stuck"; + run.stuck = step.title; + break; + } + lastKey = key; + + run.current = { title: step.title, section: step.section || null }; + onProgress(run, state); + if (deps.log) deps.log("Autopilot → " + step.title, { ephemeral: true }); + + let stepError = null; + try { + await deps.runTurn(deps.buildStepPrompt(step)); + } catch (e) { + stepError = (e && e.message) || String(e); + } + + const after = await deps.snapshot(); + const done = isStepDone(after, step); + run.completed.push({ + title: step.title, + section: step.section || null, + done, + error: stepError, + at: Date.now(), + }); + run.current = null; + onProgress(run, after); + + if (stepError) { + run.status = "error"; + run.error = stepError; + break; + } + } + } catch (e) { + run.status = "error"; + run.error = (e && e.message) || String(e); + } finally { + if (run.status === "running") run.status = "completed"; + run.running = false; + run.finishedAt = Date.now(); + onProgress(run, null); + } + return run; +} diff --git a/extensions/java-modernization-studio/canvas.json b/extensions/java-modernization-studio/canvas.json new file mode 100644 index 00000000..ec31d1c7 --- /dev/null +++ b/extensions/java-modernization-studio/canvas.json @@ -0,0 +1,29 @@ +{ + "id": "appmod-cockpit", + "name": "Java Modernization Studio", + "description": "Drive the GitHub Copilot App Modernization for Java workflow from an interactive canvas: environment readiness, repo assessment, prioritized plan and progress, validation gates, and one-click predefined-task runs grounded in the repo's real artifacts.", + "version": "1.0.0", + "author": { + "name": "Ayan Gupta", + "url": "https://github.com/ayangupt" + }, + "keywords": [ + "app-modernization", + "assessment-dashboard", + "azure-migration", + "java-modernization", + "legacy-java", + "modernization-cockpit", + "validation-gates" + ], + "screenshots": { + "icon": { + "path": "assets/preview.png", + "type": "image/png" + }, + "gallery": { + "path": "assets/preview.png", + "type": "image/png" + } + } +} \ No newline at end of file diff --git a/extensions/java-modernization-studio/catalog.mjs b/extensions/java-modernization-studio/catalog.mjs new file mode 100644 index 00000000..da2fa6b3 --- /dev/null +++ b/extensions/java-modernization-studio/catalog.mjs @@ -0,0 +1,175 @@ +// catalog.mjs — Static catalog of GitHub Copilot App Modernization for Java +// predefined tasks and supported Java upgrade paths. Sourced from Microsoft Learn: +// /azure/developer/java/migration/migrate-github-copilot-app-modernization-for-java-predefined-tasks +// /azure/developer/github-copilot-app-modernization/tools +// +// `detect` lists dependency-signature keys (see scan.mjs DEP_SIGNATURES). When a +// repo's build files match one of those keys, the task is flagged "relevant". + +export const PREDEFINED_TASKS = [ + { + id: "rabbitmq-to-servicebus", + name: "RabbitMQ to Azure Service Bus", + category: "Messaging", + summary: + "Convert RabbitMQ usage (Spring AMQP, Spring JMS, or Jakarta EE over AMQP) to Azure Service Bus, preserving messaging semantics with secure auth by default.", + detect: ["rabbitmq"], + }, + { + id: "activemq-to-servicebus", + name: "ActiveMQ to Azure Service Bus", + category: "Messaging", + summary: + "Convert ActiveMQ producers, consumers, connection factories, and queue/topic interactions to Azure Service Bus equivalents.", + detect: ["activemq"], + }, + { + id: "aws-sqs-to-servicebus", + name: "AWS SQS to Azure Service Bus", + category: "Messaging", + summary: + "Translate AWS SQS queue operations and message handling to Azure Service Bus, preserving at-least-once delivery, batching, and visibility-timeout behaviors.", + detect: ["awsSqs"], + }, + { + id: "managed-identity-db", + name: "Managed Identities for Database migration", + category: "Identity & Secrets", + summary: + "Prepare your codebase for Managed Identity authentication when moving from a local database to Azure SQL, MySQL, PostgreSQL, or Cosmos DB.", + detect: ["jdbc"], + }, + { + id: "managed-identity-credentials", + name: "Managed Identities for Credential Migration", + category: "Identity & Secrets", + summary: + "Replace connection strings / shared access signatures for messaging services (Event Hubs, Service Bus) with Azure Managed Identity authentication.", + detect: ["rabbitmq", "activemq", "jms"], + }, + { + id: "secrets-to-keyvault", + name: "Secrets & Certificate Management to Azure Key Vault", + category: "Identity & Secrets", + summary: + "Move hardcoded secrets and local TLS/mTLS certificates (Java KeyStores) to Azure Key Vault, retrieving them at runtime via the JCA provider.", + detect: ["keystore"], + }, + { + id: "crypto-to-keyvault", + name: "Cryptography operations to Azure Key Vault", + category: "Identity & Secrets", + summary: + "Migrate local signing, verification, encryption, and decryption to Azure Key Vault so keys never leave the vault.", + detect: ["crypto"], + }, + { + id: "aws-secrets-to-keyvault", + name: "AWS Secret Manager to Azure Key Vault", + category: "Identity & Secrets", + summary: + "Reconfigure secret creation, retrieval, update, and deletion from AWS Secret Manager to Azure Key Vault.", + detect: ["awsSecrets"], + }, + { + id: "entra-id-auth", + name: "User authentication to Microsoft Entra ID", + category: "Identity & Secrets", + summary: + "Transition LDAP-based user authentication to Microsoft Entra ID authentication.", + detect: ["ldap"], + }, + { + id: "aws-s3-to-blob", + name: "AWS S3 to Azure Storage Blob", + category: "Storage", + summary: + "Convert code that interacts with AWS S3 into Azure Storage Blob logic while maintaining the same semantics.", + detect: ["awsS3"], + }, + { + id: "file-io-to-fileshare", + name: "Local file I/O to Azure Storage File share mounts", + category: "Storage", + summary: + "Convert local file reads/writes to unified mount-path access so an Azure Storage File share can persist data across replicas.", + detect: [], + }, + { + id: "logging-to-console", + name: "Logging to local file → console (Azure Monitor)", + category: "Observability", + summary: + "Convert file-based logging to console-based logging so Azure hosting integrates it with Azure Monitor automatically.", + detect: ["filelog"], + }, + { + id: "javamail-to-acs", + name: "Java Mail to Azure Communication Services", + category: "Email", + summary: + "Convert SMTP-based mail sending to Azure Communication Services, which works in Azure hosting environments that block port 25.", + detect: ["javamail"], + }, + { + id: "databases-to-azure", + name: "On-prem databases to Azure database offerings", + category: "Database", + summary: + "Migrate Oracle, IBM Db2, Informix, or Sybase ASE to Azure Database for PostgreSQL or Azure SQL with passwordless Entra ID auth, reconciling SQL dialect differences.", + detect: ["oracle", "db2", "sybase", "informix"], + }, + { + id: "cache-to-redis", + name: "Cache solutions to Azure Managed Redis", + category: "Cache", + summary: + "Migrate in-memory or distributed caches (Infinispan, SwarmCache, Memcached, etc.) to Azure Managed Redis with passwordless Entra ID auth.", + detect: ["cache"], + }, + { + id: "ant-eclipse-to-maven", + name: "Ant / Eclipse project to Maven", + category: "Build", + summary: + "Convert Ant or Eclipse IDE projects to Maven so the project builds consistently from any environment.", + detect: ["ant"], + }, +]; + +// Java runtime upgrade ladder targeted by App Modernization (8 → 11 → 17 → 21 → 25), +// with particular focus on Spring Boot apps. +export const JAVA_UPGRADE_TARGETS = [11, 17, 21, 25]; + +// Day-to-day Java utilities you can invoke in Copilot Chat with the `#` prefix. +export const APPMOD_TOOLS = { + cve: { + id: "appmod-validate-cves-for-java", + label: "Validate CVEs", + summary: + "Scan the project for known Java CVEs and validate that critical vulnerabilities are addressed.", + }, + tests: { + id: "appmod-generate-tests-for-java", + label: "Generate unit tests", + summary: "Use AI code understanding to generate unit tests for the Java code.", + }, +}; + +// The five validation gates App Modernization runs after code transformation. +export const VALIDATION_GATES = [ + { key: "build", label: "Build" }, + { key: "tests", label: "Unit Tests" }, + { key: "cve", label: "CVE Check" }, + { key: "consistency", label: "Consistency" }, + { key: "completeness", label: "Completeness" }, +]; + +/** Return the catalog with a `relevant` flag set per task based on detected deps. */ +export function catalogWithRelevance(detectedKeys) { + const set = new Set(detectedKeys || []); + return PREDEFINED_TASKS.map((t) => ({ + ...t, + relevant: t.detect.some((d) => set.has(d)), + })); +} diff --git a/extensions/java-modernization-studio/doctor.mjs b/extensions/java-modernization-studio/doctor.mjs new file mode 100644 index 00000000..d121dc86 --- /dev/null +++ b/extensions/java-modernization-studio/doctor.mjs @@ -0,0 +1,412 @@ +// doctor.mjs — Environment & workflow readiness checks for the App Modernization +// Cockpit. Probes the local CLI toolchain (JDK, Maven/Gradle, git, Docker, Azure +// CLI / azd, Node) and combines those results with repo facts from scan.mjs so a +// user can confirm their machine is ready *before* they start migrating — and get +// clear, actionable remediation when something is missing. +// +// Split into a pure builder (`buildDoctorReport`) and an impure runner +// (`runDoctor`) so the decision logic is unit-testable without shelling out. + +import { execFile } from "node:child_process"; +import { platform } from "node:os"; +import { readdirSync } from "node:fs"; +import { readFile } from "node:fs/promises"; +import { join } from "node:path"; + +// One probe per tool. Many CLIs print their version to stderr (notably `java`), +// so probes capture both streams. +export const TOOL_PROBES = [ + { key: "java", cmd: "java", args: ["-version"] }, + { key: "mvn", cmd: "mvn", args: ["-v"] }, + { key: "gradle", cmd: "gradle", args: ["-v"] }, + { key: "git", cmd: "git", args: ["--version"] }, + { key: "docker", cmd: "docker", args: ["--version"] }, + { key: "az", cmd: "az", args: ["--version"] }, + { key: "azd", cmd: "azd", args: ["version"] }, + { key: "node", cmd: "node", args: ["--version"] }, +]; + +/** Friendly OS label for remediation hints. */ +export function osLabel() { + const p = platform(); + if (p === "darwin") return "macOS"; + if (p === "win32") return "Windows"; + if (p === "linux") return "Linux"; + return p; +} + +/** Extract a short version string from a tool's --version output. Pure. */ +export function parseToolVersion(key, text) { + if (!text) return null; + const t = String(text); + if (key === "java") { + const m = t.match(/version\s+"?([\d._]+)"?/i); + if (m) return m[1]; + } + if (key === "node") { + const m = t.match(/v?(\d+\.\d+\.\d+)/); + if (m) return m[1]; + } + const g = t.match(/(\d+\.\d+(?:\.\d+)?)/); + if (g) return g[1]; + const first = t.split(/\r?\n/)[0].trim(); + return first ? first.slice(0, 40) : null; +} + +function defaultExec(cmd, args) { + return makeExec(augmentedEnv(process.env, platform()))(cmd, args); +} + +/** + * Build a child-process env whose PATH also covers the usual places dev tools + * live. A GUI-launched app (the Copilot app) inherits a minimal launchd PATH + * (/usr/bin:/bin:/usr/sbin:/sbin), so tools installed via Homebrew, SDKMAN, + * asdf, or a keg-only JDK aren't found even though the user's terminal sees + * them fine. Prepending these locations makes the probes match reality without + * touching the user's actual environment. Pure given an injected directory + * lister (so it's unit-testable without hitting the filesystem). + */ +export function augmentedEnv(env, plat, lister) { + const base = Object.assign({}, env); + if (plat === "win32") return base; // Windows PATH semantics differ; leave as-is. + const ls = lister || ((d) => { try { return readdirSync(d); } catch (e) { return []; } }); + const extra = ["/opt/homebrew/bin", "/opt/homebrew/sbin", "/usr/local/bin", "/usr/local/sbin"]; + if (base.HOME) { + extra.push(base.HOME + "/.sdkman/candidates/java/current/bin"); + extra.push(base.HOME + "/.asdf/shims"); + } + if (base.JAVA_HOME) extra.push(base.JAVA_HOME + "/bin"); + // Keg-only Homebrew JDKs aren't symlinked into .../bin, so enumerate them. + for (const root of ["/opt/homebrew/opt", "/usr/local/opt"]) { + for (const name of ls(root)) { + if (/^openjdk(@\d+)?$/.test(name)) extra.push(root + "/" + name + "/libexec/openjdk.jdk/Contents/Home/bin"); + } + } + const seen = {}; + const prefix = extra.filter((p) => { if (seen[p]) return false; seen[p] = 1; return true; }); + base.PATH = prefix.join(":") + (base.PATH ? ":" + base.PATH : ""); + return base; +} + +/** Build an exec function bound to a specific environment. */ +export function makeExec(env) { + return (cmd, args) => + new Promise((resolve) => { + execFile(cmd, args, { timeout: 3500, windowsHide: true, env }, (error, stdout, stderr) => { + resolve({ error: error || null, stdout: String(stdout || ""), stderr: String(stderr || "") }); + }); + }); +} + +/** + * Expand a shell-style JAVA_HOME value from an rc assignment into a concrete path, + * WITHOUT executing anything. Handles surrounding quotes, an inline comment on + * unquoted values, and `~` / `$HOME` / `${HOME}` expansion. Returns null when the + * value still contains something only a shell could resolve (command substitution, + * other variables) — we'd rather report nothing than a bogus path. + */ +export function expandJavaHomeValue(raw, env) { + if (typeof raw !== "string") return null; + let v = raw.trim(); + if (!v) return null; + if (v[0] !== '"' && v[0] !== "'") { + const c = v.indexOf(" #"); + if (c >= 0) v = v.slice(0, c).trim(); + } + if (v.length >= 2 && ((v[0] === '"' && v[v.length - 1] === '"') || (v[0] === "'" && v[v.length - 1] === "'"))) { + v = v.slice(1, -1); + } + const home = (env && env.HOME) || ""; + if (v === "~") v = home; + else if (v.startsWith("~/")) v = home + v.slice(1); + v = v.replace(/\$\{HOME\}/g, home).replace(/\$HOME(?=\/|$)/g, home); + if (!v || /[`]|\$\(|\$\w/.test(v)) return null; // unresolved shell expansion + return v; +} + +/** + * Statically parse a shell rc file's text for the last `JAVA_HOME=` assignment. + * Pure and side-effect-free. Returns the expanded path or null. + */ +export function parseJavaHomeFromRc(text, env) { + if (typeof text !== "string" || !text) return null; + const re = /^[ \t]*(?:export[ \t]+)?JAVA_HOME[ \t]*=[ \t]*(.+?)[ \t]*$/gm; + let m; + let last = null; + while ((m = re.exec(text)) !== null) { + const val = expandJavaHomeValue(m[1], env); + if (val) last = val; // later assignments win, mirroring shell evaluation order + } + return last; +} + +/** + * Best-effort discovery of the JAVA_HOME the user's interactive shell is + * configured with, so the readiness report reflects the JDK their terminal + * actually uses (e.g. a pinned LTS) rather than whichever JDK happens to sort + * first on the augmented PATH. + * + * IMPORTANT: this only *reads and statically parses* the shell rc files — it never + * sources or executes them. That keeps the readiness probe honest with the UI + * promise ("only reads version numbers; nothing is installed or changed") and + * avoids running arbitrary user startup code. A JAVA_HOME defined via command + * substitution (e.g. `$(/usr/libexec/java_home)`) can't be resolved statically, so + * we simply fall back to PATH-based probing in that case. The rc reader is + * injectable for tests. + */ +export async function discoverJavaHome(env, plat, readRc) { + const e = env || {}; + if (e.JAVA_HOME) return e.JAVA_HOME; // already explicit in this process + if (plat === "win32") return null; // not a $JAVA_HOME/rc world + const home = e.HOME || ""; + if (!home) return null; + const shell = e.SHELL || (plat === "darwin" ? "/bin/zsh" : "/bin/bash"); + const names = /zsh/.test(shell) + ? [".zshenv", ".zprofile", ".zshrc"] + : /bash/.test(shell) + ? [".bash_profile", ".bashrc", ".profile"] + : [".profile"]; + const read = + readRc || + (async (p) => { + try { + return await readFile(p, "utf8"); + } catch { + return null; + } + }); + let found = null; + for (const name of names) { + let text = null; + try { + text = await read(join(home, name)); + } catch { + text = null; + } + const val = parseJavaHomeFromRc(text, e); + if (val) found = val; // later rc files (e.g. .zshrc) win + } + return found || null; +} + +/** Probe a single tool. Returns { key, found, version }. */ +export async function probeOne(def, exec) { + const run = exec || defaultExec; + let r; + try { + r = await run(def.cmd, def.args); + } catch (e) { + r = { error: e, stdout: "", stderr: "" }; + } + const text = ((r.stdout || "") + "\n" + (r.stderr || "")).trim(); + // ENOENT means the binary isn't on PATH at all. + if (r.error && r.error.code === "ENOENT") return { key: def.key, found: false, version: null }; + if (r.error && !text) return { key: def.key, found: false, version: null }; + const version = parseToolVersion(def.key, text); + if (r.error) { + // Non-zero exit. Genuine `--version` calls exit 0, so only trust this if a + // real version number came back. This rejects the macOS `java` stub, which + // exits 1 with "Unable to locate a Java Runtime" when no JDK is installed. + if (!(version && /\d/.test(version))) return { key: def.key, found: false, version: null }; + } + return { key: def.key, found: true, version }; +} + +/** Probe every tool and build the readiness report. Impure (spawns processes). */ +export async function runDoctor(scan, opts = {}) { + let exec = opts.exec; + if (!exec) { + const env = augmentedEnv(process.env, platform()); + // Prefer the JDK the user's shell is configured with so the report matches + // their terminal (and so a dependency-pulled JDK doesn't shadow their pin). + // Static rc parse — no shell sourcing. + const jh = await discoverJavaHome(process.env, platform()); + if (jh && !env.JAVA_HOME) { + env.JAVA_HOME = jh; + env.PATH = jh + "/bin:" + env.PATH; + } + exec = makeExec(env); + } + const probesArr = await Promise.all(TOOL_PROBES.map((d) => probeOne(d, exec))); + const probes = {}; + for (const p of probesArr) probes[p.key] = p; + return buildDoctorReport({ probes, scan: scan || {} }); +} + +function envAction(tool, detail, fix) { + return { kind: "fix_env", payload: { tool, detail, fix }, label: "Help me set up " + tool }; +} + +/** + * Turn probe results + scan facts into grouped readiness checks. Pure. + * @returns {{ overall:"ready"|"caution"|"blocked", generatedAt:string, groups:Array }} + */ +export function buildDoctorReport({ probes, scan }) { + const get = (k) => (probes && probes[k]) || { found: false, version: null }; + const a = (scan && scan.assessment) || {}; + const buildTool = a.buildTool || null; + + const buildRun = []; + + // --- JDK ----------------------------------------------------------------- + const java = get("java"); + buildRun.push( + java.found + ? { id: "jdk", label: "Java Development Kit", status: "ok", detail: "Java " + (java.version || "detected") } + : { + id: "jdk", + label: "Java Development Kit", + status: "fail", + detail: "java not found on PATH", + fix: "Install a JDK 17 or newer (Microsoft Build of OpenJDK or Eclipse Temurin) and make sure `java` is on your PATH.", + action: envAction("a JDK", "java not found on PATH", "Install JDK 17+ (Microsoft OpenJDK / Temurin) and add it to PATH."), + } + ); + + // --- Build tool ---------------------------------------------------------- + if (buildTool === "Maven") { + const mvn = get("mvn"); + const ok = mvn.found || a.hasMavenWrapper; + buildRun.push( + ok + ? { + id: "build", + label: "Maven", + status: "ok", + detail: mvn.found ? "Maven " + (mvn.version || "detected") : "Using the project Maven wrapper (./mvnw)", + } + : { + id: "build", + label: "Maven", + status: "fail", + detail: "mvn not found and no ./mvnw wrapper in the repo", + fix: "Install Apache Maven, or add the Maven wrapper (`mvn -N wrapper:wrapper`) so the build runs anywhere.", + action: envAction("Maven", "mvn not found and no ./mvnw wrapper", "Install Apache Maven or add the ./mvnw wrapper."), + } + ); + } else if (buildTool === "Gradle") { + const gr = get("gradle"); + const ok = gr.found || a.hasGradleWrapper; + buildRun.push( + ok + ? { + id: "build", + label: "Gradle", + status: "ok", + detail: gr.found ? "Gradle " + (gr.version || "detected") : "Using the project Gradle wrapper (./gradlew)", + } + : { + id: "build", + label: "Gradle", + status: "fail", + detail: "gradle not found and no ./gradlew wrapper in the repo", + fix: "Install Gradle, or add the Gradle wrapper (`gradle wrapper`) so the build runs anywhere.", + action: envAction("Gradle", "gradle not found and no ./gradlew wrapper", "Install Gradle or add the ./gradlew wrapper."), + } + ); + } else { + buildRun.push({ + id: "build", + label: "Build tool", + status: "info", + detail: "No Maven or Gradle build file detected yet.", + }); + } + + // --- git ----------------------------------------------------------------- + const git = get("git"); + buildRun.push( + git.found + ? { id: "git", label: "Git", status: "ok", detail: git.version ? "git " + git.version : "installed" } + : { + id: "git", + label: "Git", + status: "fail", + detail: "git not found on PATH", + fix: "Install Git — the modernization workflow uses a branch and a pull request.", + action: envAction("Git", "git not found on PATH", "Install Git and ensure it is on PATH."), + } + ); + + // --- containerize / deploy (optional) ------------------------------------ + const deploy = []; + const docker = get("docker"); + if (docker.found) { + deploy.push({ id: "docker", label: "Docker", status: "ok", detail: docker.version ? "Docker " + docker.version : "installed" }); + } else if (a.hasDockerfile) { + deploy.push({ + id: "docker", + label: "Docker", + status: "warn", + detail: "A Dockerfile is present but Docker isn't installed.", + fix: "Install Docker Desktop (or the Docker engine) to build and run the container image locally.", + action: envAction("Docker", "Dockerfile present but Docker not installed", "Install Docker Desktop / engine."), + }); + } else { + deploy.push({ id: "docker", label: "Docker", status: "info", detail: "Not installed — only needed to build container images." }); + } + const az = get("az"); + deploy.push( + az.found + ? { id: "az", label: "Azure CLI", status: "ok", detail: az.version ? "az " + az.version : "installed" } + : { id: "az", label: "Azure CLI", status: "info", detail: "Optional — needed to provision Azure resources and set up passwordless auth." } + ); + const azd = get("azd"); + deploy.push( + azd.found + ? { id: "azd", label: "Azure Developer CLI", status: "ok", detail: azd.version ? "azd " + azd.version : "installed" } + : { id: "azd", label: "Azure Developer CLI", status: "info", detail: "Optional — `azd` deploys the app to Azure once it's modernized." } + ); + + // --- workflow readiness (from scan, no probes) --------------------------- + const flow = []; + const g = scan && scan.git; + if (!g) { + flow.push({ id: "branch", label: "Source control", status: "info", detail: "Git branch state unavailable." }); + } else if (g.isMigrationBranch) { + flow.push({ id: "branch", label: "Working branch", status: "ok", detail: "On modernization branch '" + g.branch + "'." }); + } else { + flow.push({ + id: "branch", + label: "Working branch", + status: "info", + detail: "On '" + (g.branch || "unknown") + "' — consider a dedicated modernization branch before making changes.", + }); + } + const hasAssessment = !!( + (scan && scan.report && scan.report.findings && scan.report.findings.length) || + (scan && scan.plan && scan.plan.exists) || + (scan && scan.progress && scan.progress.exists) + ); + flow.push( + hasAssessment + ? { id: "assessed", label: "Assessment", status: "ok", detail: "Assessment artifacts found — you're underway." } + : { + id: "assessed", + label: "Assessment", + status: "warn", + detail: "No assessment yet. Start here to map your modernization work.", + action: { kind: "start_assessment", payload: {}, label: "Run assessment" }, + } + ); + const nSkills = (scan && scan.skills && scan.skills.length) || 0; + flow.push({ + id: "skills", + label: "Custom skills", + status: "info", + detail: nSkills ? nSkills + " custom skill" + (nSkills > 1 ? "s" : "") + " discovered." : "No custom skills yet (optional).", + }); + + const groups = [ + { id: "buildRun", name: "Build & run", checks: buildRun }, + { id: "deploy", name: "Containerize & deploy to Azure (optional)", checks: deploy }, + { id: "flow", name: "Workflow readiness", checks: flow }, + ]; + + let overall = "ready"; + const all = groups.reduce((acc, grp) => acc.concat(grp.checks), []); + if (all.some((c) => c.status === "fail")) overall = "blocked"; + else if (all.some((c) => c.status === "warn")) overall = "caution"; + + return { overall, generatedAt: new Date().toISOString(), groups }; +} diff --git a/extensions/java-modernization-studio/extension.mjs b/extensions/java-modernization-studio/extension.mjs new file mode 100644 index 00000000..0f05774c --- /dev/null +++ b/extensions/java-modernization-studio/extension.mjs @@ -0,0 +1,122 @@ +// extension.mjs — Java Modernization Studio (user-scoped canvas). Wiring only. +// +// Declares the canvas and bridges the SDK to the testable modules: +// scan.mjs repo + markdown parsing -> grounded state +// catalog.mjs Microsoft predefined task catalog +// prompts.mjs action -> crafted agent prompt +// server.mjs per-instance loopback HTTP server + action dispatch +// renderer.mjs cockpit UI +// +// Button clicks POST /action; agent actions are forwarded to session.send. When a +// turn finishes (session.idle) every open cockpit re-scans and pushes fresh state. + +import { joinSession, createCanvas, CanvasError } from "@github/copilot-sdk/extension"; +import { basename } from "node:path"; +import { scanRepo } from "./scan.mjs"; +import { createInstanceServer, pushState, resolveRepoPath } from "./server.mjs"; +import { AUTOPILOT_TURN_TIMEOUT_MS } from "./autopilot.mjs"; + +const instances = new Map(); // instanceId -> rec { server, url, repoPath, sseClients } +let sessionRef = null; +let lastWorkingDir = null; + +const session = await joinSession({ + canvases: [ + createCanvas({ + id: "appmod-cockpit", + displayName: "Java Modernization Studio", + description: + "Drive the GitHub Copilot App Modernization for Java workflow: assessment, plan/progress, validation gates, and task/skill runs grounded in the repo's real artifacts.", + inputSchema: { + type: "object", + additionalProperties: false, + properties: { + repoPath: { type: "string", description: "Absolute path to the Java repo to inspect." }, + initialTab: { type: "string", enum: ["overview", "readiness", "assessment", "plan", "validation", "tasks", "summary"] }, + }, + }, + actions: [ + { + name: "get_state", + description: + "Return the current modernization state snapshot scanned from the repo (assessment, plan/progress, gates, skills, tasks).", + handler: async (ctx) => { + const rec = instances.get(ctx.instanceId); + const repoPath = rec ? rec.repoPath : resolveRepoPath(ctx, lastWorkingDir); + return await scanRepo(repoPath); + }, + }, + { + name: "refresh", + description: "Re-scan the repo and push a fresh snapshot to the open cockpit.", + handler: async (ctx) => { + const rec = instances.get(ctx.instanceId); + if (!rec) throw new CanvasError("not_open", "Cockpit instance is not open."); + await pushState(rec, logFn); + return { ok: true, repoPath: rec.repoPath }; + }, + }, + ], + open: async (ctx) => { + const repoPath = resolveRepoPath(ctx, lastWorkingDir); + if (repoPath) lastWorkingDir = repoPath; // never clobber a good target with null + const initialTab = ctx.input && ctx.input.initialTab ? ctx.input.initialTab : "overview"; + let rec = instances.get(ctx.instanceId); + if (!rec) { + rec = await createInstanceServer({ + instanceId: ctx.instanceId, + repoPath, + initialTab, + sendPrompt: (prompt) => sessionRef.send({ prompt }), + runTurn: (prompt) => sessionRef.sendAndWait({ prompt }, AUTOPILOT_TURN_TIMEOUT_MS), + log: logFn, + }); + instances.set(ctx.instanceId, rec); + } else if (rec.repoPath !== repoPath) { + rec.repoPath = repoPath; // refresh target on re-open + rec.doctor = null; // env readiness depends on repo facts — recompute for the new target + } + return { + title: "Java Modernization Studio", + status: repoPath ? basename(repoPath) : "no repo", + url: rec.url, + }; + }, + onClose: async (ctx) => { + const rec = instances.get(ctx.instanceId); + if (rec) { + instances.delete(ctx.instanceId); + for (const res of rec.sseClients) { + try { + res.end(); + } catch { + /* ignore */ + } + } + await new Promise((resolve) => rec.server.close(() => resolve())); + } + }, + }), + ], +}); + +sessionRef = session; +function logFn(message, opts) { + try { + return sessionRef.log(message, opts); + } catch { + /* logging is best-effort */ + } +} + +if (session.workspacePath && !lastWorkingDir) lastWorkingDir = session.workspacePath; + +// A finished turn may have changed plan.md/progress.md/summary.md or the working +// tree — re-scan and push fresh state to every open cockpit. +session.on("session.idle", async () => { + for (const rec of instances.values()) { + await pushState(rec, logFn); + } +}); + +await session.log("Java Modernization Studio ready", { ephemeral: true }); diff --git a/extensions/java-modernization-studio/package.json b/extensions/java-modernization-studio/package.json new file mode 100644 index 00000000..4924f078 --- /dev/null +++ b/extensions/java-modernization-studio/package.json @@ -0,0 +1,23 @@ +{ + "name": "java-modernization-studio", + "version": "1.0.0", + "type": "module", + "main": "extension.mjs", + "author": { + "name": "Ayan Gupta", + "url": "https://github.com/ayangupt" + }, + "dependencies": { + "@github/copilot-sdk": "1.0.4" + }, + "description": "Drive the GitHub Copilot App Modernization for Java workflow from an interactive canvas: environment readiness, repo assessment, prioritized plan and progress, validation gates, and one-click predefined-task runs grounded in the repo's real artifacts.", + "keywords": [ + "java-modernization", + "app-modernization", + "azure-migration", + "legacy-java", + "assessment-dashboard", + "validation-gates", + "modernization-cockpit" + ] +} diff --git a/extensions/java-modernization-studio/prompts.mjs b/extensions/java-modernization-studio/prompts.mjs new file mode 100644 index 00000000..ffd5a4f5 --- /dev/null +++ b/extensions/java-modernization-studio/prompts.mjs @@ -0,0 +1,155 @@ +// prompts.mjs — Maps each cockpit button action to a crafted agent instruction. +// Pure (no I/O, no session) so it can be unit-tested in isolation. + +import { PREDEFINED_TASKS } from "./catalog.mjs"; + +export const APPMOD_PREAMBLE = + "You are running the GitHub Copilot App Modernization for Java workflow. " + + "Keep plan.md and progress.md up to date as a checklist (use - [ ] / - [x]) and, when the work is complete, write summary.md. " + + "Start each of these files (plan.md, progress.md, summary.md) with the exact line as the very first line so the cockpit recognizes them as modernization artifacts."; + +export const ACTION_LABELS = { + start_assessment: "Assessment", + generate_plan: "Generate plan", + run_task: "Run task", + run_skill: "Run skill", + run_cve: "CVE scan", + generate_tests: "Generate tests", + run_build_tests: "Build & tests", + run_consistency: "Consistency check", + run_completeness: "Completeness check", + create_skill: "Create skill", + open_pr: "Open PR", + fix_finding: "Resolve finding", + work_step: "Work on step", + fix_env: "Environment setup", +}; + +/** + * Build the agent prompt for a cockpit action. + * @returns {string|null} prompt text, or null for unknown actions / unknown task ids. + */ +export function buildPrompt(kind, payload, repoPath) { + const p = payload || {}; + switch (kind) { + case "start_assessment": + return ( + APPMOD_PREAMBLE + + "\n\nRun an App Modernization assessment of this Java project at " + repoPath + ". " + + "Report the current Java runtime, build tool, framework versions, outdated/vulnerable dependencies, " + + "and cloud-readiness issues for Azure. Summarize findings and write a prioritized plan.md of remediation steps. Do not change code yet." + + "\n\nAlso write the findings as structured JSON to .appmod/assessment.json so the cockpit can render them. Use this shape:\n" + + "{\n" + + ' "generatedAt": "",\n' + + ' "headline": "",\n' + + ' "summary": "<2-3 sentence plain-language verdict>",\n' + + ' "stack": { "buildTool": "", "java": "", "framework": "", "database": "", "container": "" },\n' + + ' "findings": [\n' + + ' { "id": "kebab-id", "severity": "P0|P1|P2|P3", "title": "", "detail": "", "files": ["path"],\n' + + ' "action": { "kind": "run_task|generate_plan|run_cve|generate_tests|fix_finding", "payload": {}, "label": "" } }\n' + + " ],\n" + + ' "strengths": [""]\n' + + "}\n" + + "Order findings by severity (P0 first). For action.kind, use run_task with payload {\"taskId\":\"...\"} when a Microsoft predefined task fits, " + + "generate_plan/run_cve/generate_tests when those fit, otherwise use fix_finding (the cockpit turns it into a 'Help me fix this' button). Omit action only if there is genuinely nothing to do." + ); + case "generate_plan": + return ( + APPMOD_PREAMBLE + + "\n\nCreate a step-by-step modernization plan to upgrade this project to Java " + (p.targetJava || 21) + ". " + + "Cover build-file changes, removed/replaced APIs, dependency upgrades, and required test updates. " + + "Write the plan as a checklist in plan.md and create an empty progress.md mirroring those steps. Do not change code yet." + ); + case "run_task": { + const t = PREDEFINED_TASKS.find((x) => x.id === p.taskId); + if (!t) return null; + return ( + APPMOD_PREAMBLE + + "\n\nApply the App Modernization predefined task \"" + t.name + "\": " + t.summary + "\n" + + "Steps: (1) record the plan in plan.md and progress.md; (2) check out a migration branch; " + + "(3) make the code changes for this task; (4) validate by building, running unit tests, and a CVE check; " + + "(5) write a summary.md of what changed. Pause for my confirmation before committing." + ); + } + case "run_skill": + if (!p.folder) return null; + return ( + APPMOD_PREAMBLE + + "\n\nRun the custom modernization skill in .github/skills/" + p.folder + "/SKILL.md. " + + "Follow its instructions and referenced resources exactly, updating plan.md and progress.md as you go, " + + "and write summary.md when finished." + ); + case "run_cve": + return "Check this Java project for known CVE issues using #appmod-validate-cves-for-java and report which dependencies need upgrading, then update progress.md."; + case "generate_tests": + return "Generate unit tests for this Java project using #appmod-generate-tests-for-java, then run them and record the result in progress.md."; + case "run_build_tests": + return "Build this project and run its unit tests. Report failures with root-cause analysis and update the Build and Unit Tests entries in progress.md."; + case "run_consistency": + return "Run a consistency check comparing the modernized code against the original behavior (APIs, SQL, wire format). List any behavioral differences and update progress.md."; + case "run_completeness": + return "Run a completeness check: verify every step in plan.md has been implemented. List anything missing and update progress.md."; + case "create_skill": + return ( + "Help me create a custom App Modernization skill. Create .github/skills//SKILL.md with Skill Name, " + + "Description, and Content sections following the Agent Skills specification, plus any resource files it references. Ask me what migration this skill should capture." + ); + case "open_pr": + return "Summarize the modernization changes on the current branch and open a pull request with a clear title and a body that lists the plan steps completed and validation results."; + case "work_step": { + const title = (p.title || "").trim(); + if (!title) return null; + const phase = p.section ? " (phase: " + p.section + ")" : ""; + return ( + APPMOD_PREAMBLE + + "\n\nWork on this single step from the modernization checklist" + phase + ":\n\"" + title + "\"\n\n" + + "Do just this step: tell me what you'll change, make the code/config changes, then check it off in progress.md " + + "(change its - [ ] to - [x]) and add a one-line note of what changed. If the step needs a decision from me or is " + + "ambiguous, ask before proceeding. Pause for my confirmation before committing." + ); + } + case "auto_step": { + const title = (p.title || "").trim(); + if (!title) return null; + const phase = p.section ? " (phase: " + p.section + ")" : ""; + return ( + APPMOD_PREAMBLE + + "\n\nYou are running in AUTOPILOT, working the modernization checklist on your own. " + + "Do exactly ONE step now" + phase + ":\n\"" + title + "\"\n\n" + + "For this single step: briefly state what you will change, make the necessary code/config changes, " + + "then check it off in progress.md (change its - [ ] to - [x]) and add a one-line note of what changed. " + + "Do NOT commit, and do NOT start any other step. " + + "If this step genuinely needs a decision from me, or is blocked or ambiguous, do NOT guess: leave it unchecked, " + + "add a short note in progress.md describing exactly what you need, and stop so I can take over." + ); + } + case "fix_finding": { + const title = (p.title || "").trim(); + if (!title) return null; + const detail = (p.detail || "").trim(); + const files = Array.isArray(p.files) && p.files.length ? "\nAffected files: " + p.files.join(", ") + "." : ""; + const sev = p.severity ? " (" + p.severity + ")" : ""; + return ( + APPMOD_PREAMBLE + + "\n\nHelp me resolve this App Modernization assessment finding" + sev + ":\n" + + title + (detail ? " — " + detail : "") + "." + files + + "\n\nExplain the fix, make the change, and update the matching checklist items in plan.md and progress.md. " + + "Pause for my confirmation before committing." + ); + } + case "fix_env": { + const tool = (p.tool || "").trim(); + if (!tool) return null; + const detail = p.detail ? "\nWhat the readiness check found: " + p.detail + "." : ""; + const fix = p.fix ? "\nSuggested fix: " + p.fix : ""; + return ( + "I'm preparing my local environment for the GitHub Copilot App Modernization for Java workflow.\n" + + "The readiness check flagged a missing or misconfigured tool: " + tool + "." + detail + fix + + "\n\nWalk me through installing and configuring " + tool + " on my machine, including the exact commands for my OS " + + "and how to verify it's on PATH at the right version for this project. This is environment setup only — do not modify my application code." + ); + } + default: + return null; + } +} diff --git a/extensions/java-modernization-studio/renderer.mjs b/extensions/java-modernization-studio/renderer.mjs new file mode 100644 index 00000000..56fd94e4 --- /dev/null +++ b/extensions/java-modernization-studio/renderer.mjs @@ -0,0 +1,1038 @@ +// renderer.mjs — Single-page cockpit UI served by each instance's loopback server. +// The client script is authored as a real function (clientMain) and serialized to +// a string at render time, so its own template literals / ${} are NOT evaluated by +// this module's template literal. Only CSS and a boot blob are interpolated. + +const CSS = ` +:root { color-scheme: light dark; } +* { box-sizing: border-box; } +body { + margin: 0; + background: var(--background-color-default, #ffffff); + color: var(--text-color-default, #1f2328); + font-family: var(--font-sans, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif); + font-size: var(--text-body-medium, 14px); + line-height: var(--leading-body-medium, 20px); +} +a { color: var(--true-color-blue, #0969da); } +.muted { color: var(--text-color-muted, #656d76); } +header.top { + position: sticky; top: 0; z-index: 5; + background: var(--background-color-default, #fff); + border-bottom: 1px solid var(--border-color-default, #d0d7de); + padding: 14px 18px 0; +} +.title-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; } +h1 { + font-size: var(--text-title-medium, 18px); + font-weight: var(--font-weight-semibold, 600); + margin: 0; letter-spacing: .2px; +} +.spacer { flex: 1; } +.repo { font-family: var(--font-mono, monospace); font-size: 12px; } +.chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 10px 0 12px; } +.chip { + display: inline-flex; align-items: center; gap: 5px; + border: 1px solid var(--border-color-default, #d0d7de); + border-radius: 999px; padding: 3px 10px; font-size: 12px; + background: var(--background-color-muted, #f6f8fa); +} +.chip b { font-weight: 600; } +.badge { border-radius: 999px; padding: 2px 9px; font-size: 11px; font-weight: 600; border: 1px solid transparent; white-space: nowrap; } +.b-green { color: #fff; background: var(--true-color-green, #1a7f37); } +.b-amber { color: #fff; background: var(--true-color-orange, #9a6700); } +.b-red { color: #fff; background: var(--true-color-red, #cf222e); } +.b-gray { color: var(--text-color-default,#1f2328); background: var(--background-color-muted,#eaeef2); border-color: var(--border-color-default,#d0d7de); } +.b-blue { color: #fff; background: var(--true-color-blue, #0969da); } +nav.tabs { display: flex; gap: 2px; margin-top: 6px; flex-wrap: wrap; } +nav.tabs button { + background: none; border: none; border-bottom: 2px solid transparent; + padding: 8px 12px; font-size: 13px; cursor: pointer; color: var(--text-color-muted,#656d76); + font-family: inherit; +} +nav.tabs button.active { color: var(--text-color-default,#1f2328); border-bottom-color: var(--true-color-blue,#0969da); font-weight: 600; } +nav.tabs button:hover { color: var(--text-color-default,#1f2328); border-bottom-color: var(--border-color-default,#d0d7de); } +nav.tabs button.active:hover { border-bottom-color: var(--true-color-blue,#0969da); } +nav.tabs button:focus-visible { outline: 2px solid var(--color-focus-outline, var(--true-color-blue,#0969da)); outline-offset: -2px; border-radius: 4px; } +nav.tabs button .count { font-size: 11px; color: var(--text-color-muted,#656d76); } +main { padding: 16px 18px 40px; max-width: 980px; } +.card { + border: 1px solid var(--border-color-default, #d0d7de); + border-radius: 10px; padding: 14px 16px; margin-bottom: 12px; + background: var(--background-color-default, #fff); +} +.card h2 { font-size: 14px; margin: 0 0 10px; font-weight: 600; display: flex; align-items: center; gap: 8px; } +.card.relevant { border-color: var(--true-color-blue, #0969da); box-shadow: 0 0 0 1px var(--true-color-blue-muted, rgba(9,105,218,.25)); } +.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px,1fr)); gap: 10px; } +.kv { display: flex; flex-direction: column; gap: 2px; } +.kv .k { font-size: 11px; text-transform: uppercase; letter-spacing: .4px; color: var(--text-color-muted,#656d76); } +.kv .v { font-size: 14px; font-weight: 600; } +button.btn { + font-family: inherit; font-size: 12.5px; cursor: pointer; + border: 1px solid var(--border-color-default, #d0d7de); + background: var(--background-color-muted, #f6f8fa); + color: var(--text-color-default, #1f2328); + border-radius: 7px; padding: 6px 12px; +} +button.btn:not(.primary):not(:disabled):hover { + background: var(--background-color-muted, #eaeef2); + border-color: var(--border-color-muted, #afb8c1); + box-shadow: inset 0 0 0 999px rgba(140,149,159,.14); +} +button.btn.primary { background: var(--true-color-blue, #0969da); color: #fff; border-color: transparent; } +button.btn.primary:not(:disabled):hover { filter: brightness(1.08); box-shadow: 0 1px 2px rgba(31,35,40,.18); } +button.btn:focus-visible { outline: 2px solid var(--color-focus-outline, var(--true-color-blue, #0969da)); outline-offset: 1px; } +button.btn:disabled { opacity: .55; cursor: default; } +.actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 4px; } +.progress { height: 8px; border-radius: 999px; background: var(--background-color-muted,#eaeef2); overflow: hidden; } +.progress > span { display: block; height: 100%; background: var(--true-color-green,#1a7f37); } +ul.steps { list-style: none; margin: 0; padding: 0; } +ul.steps li { display: flex; align-items: flex-start; gap: 9px; padding: 6px 0; border-bottom: 1px solid var(--border-color-muted, #eaeef2); } +ul.steps li:last-child { border-bottom: none; } +.dot { width: 16px; height: 16px; border-radius: 50%; flex: 0 0 auto; margin-top: 2px; border: 2px solid; } +.dot.done { background: var(--true-color-green,#1a7f37); border-color: var(--true-color-green,#1a7f37); } +.dot.in_progress { background: var(--true-color-orange,#9a6700); border-color: var(--true-color-orange,#9a6700); } +.dot.failed { background: var(--true-color-red,#cf222e); border-color: var(--true-color-red,#cf222e); } +.dot.pending { background: transparent; border-color: var(--border-color-default,#8c959f); } +.task { display: flex; align-items: flex-start; gap: 10px; padding: 10px 0; border-bottom: 1px solid var(--border-color-muted,#eaeef2); } +.task:last-child { border-bottom: none; } +.task .body { flex: 1; } +.task .name { font-weight: 600; font-size: 13px; } +.task .sum { font-size: 12.5px; color: var(--text-color-muted,#656d76); margin-top: 2px; } +.cat { font-size: 11px; text-transform: uppercase; letter-spacing: .5px; color: var(--text-color-muted,#656d76); margin: 14px 0 4px; font-weight: 600; } +.empty { text-align: center; padding: 26px 16px; color: var(--text-color-muted,#656d76); } +.empty .big { font-size: 15px; color: var(--text-color-default,#1f2328); margin-bottom: 6px; font-weight: 600; } +.toast { + position: fixed; bottom: 16px; left: 50%; transform: translateX(-50%); + background: var(--true-color-blue,#0969da); color: #fff; padding: 9px 16px; + border-radius: 8px; font-size: 13px; opacity: 0; transition: opacity .2s; pointer-events: none; z-index: 20; + max-width: 90%; +} +.toast.show { opacity: 1; } +.banner { background: var(--true-color-blue-muted, #ddf4ff); border: 1px solid var(--true-color-blue,#0969da); color: var(--text-color-default,#1f2328); padding: 8px 12px; border-radius: 8px; font-size: 12.5px; margin-bottom: 12px; display: flex; gap: 8px; align-items: center; } +.md { font-size: 13px; line-height: 1.55; } +.md h1,.md h2,.md h3 { font-size: 15px; margin: 14px 0 6px; } +.md code { font-family: var(--font-mono,monospace); background: var(--background-color-muted,#f6f8fa); padding: 1px 4px; border-radius: 4px; font-size: 12px; } +.md ul { padding-left: 20px; } +.spin { width:13px; height:13px; border:2px solid currentColor; border-right-color:transparent; border-radius:50%; display:inline-block; animation: sp .7s linear infinite; } +@keyframes sp { to { transform: rotate(360deg); } } + +/* journey stepper */ +.stepper { display: flex; align-items: center; flex-wrap: wrap; gap: 2px; } +.stepper .step { display: flex; align-items: center; gap: 7px; } +.stepper .ix { width: 22px; height: 22px; border-radius: 50%; border: 2px solid var(--border-color-default,#8c959f); color: var(--text-color-muted,#656d76); display: inline-flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 600; flex: 0 0 auto; } +.stepper .lbl { font-size: 12.5px; color: var(--text-color-muted,#656d76); } +.stepper .step.done .ix { background: var(--true-color-green,#1a7f37); border-color: var(--true-color-green,#1a7f37); color: #fff; } +.stepper .step.done .lbl { color: var(--text-color-default,#1f2328); } +.stepper .step.current .ix { border-color: var(--true-color-blue,#0969da); color: var(--true-color-blue,#0969da); } +.stepper .step.current .lbl { color: var(--text-color-default,#1f2328); font-weight: 600; } +.stepper .bar { width: 24px; height: 2px; background: var(--border-color-default,#d0d7de); margin: 0 5px; flex: 0 0 auto; } +.stepper .bar.done { background: var(--true-color-green,#1a7f37); } + +/* hero next-step */ +.hero { border: 1px solid var(--true-color-blue,#0969da); background: var(--true-color-blue-muted,#ddf4ff); border-radius: 10px; padding: 14px 16px; margin-bottom: 12px; } +.hero .eyebrow { font-size: 11px; text-transform: uppercase; letter-spacing: .5px; font-weight: 700; color: var(--true-color-blue,#0969da); } +.hero .htitle { font-size: 16px; font-weight: 700; margin: 3px 0 4px; } +.hero .hbody { font-size: 13px; color: var(--text-color-default,#1f2328); margin: 0 0 10px; } + +/* findings */ +.sevrow { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; } +.finding { border: 1px solid var(--border-color-default,#d0d7de); border-left-width: 4px; border-radius: 8px; padding: 11px 13px; margin-bottom: 9px; } +.finding.sev-P0 { border-left-color: var(--true-color-red,#cf222e); } +.finding.sev-P1 { border-left-color: var(--true-color-orange,#9a6700); } +.finding.sev-P2 { border-left-color: var(--true-color-blue,#0969da); } +.finding.sev-P3 { border-left-color: var(--border-color-default,#8c959f); } +.finding .ftitle { font-weight: 600; font-size: 13.5px; display: flex; align-items: center; gap: 8px; } +.finding .fdetail { font-size: 12.5px; color: var(--text-color-default,#1f2328); margin: 6px 0 0; } +.finding .ffiles { font-size: 11.5px; color: var(--text-color-muted,#656d76); margin-top: 7px; display: flex; flex-wrap: wrap; gap: 4px; } +.finding .ffiles code { font-family: var(--font-mono,monospace); background: var(--background-color-muted,#f6f8fa); padding: 1px 5px; border-radius: 4px; word-break: break-all; } + +/* plan & progress steps */ +.legend { display: flex; gap: 16px; margin-top: 11px; font-size: 11.5px; color: var(--text-color-muted,#656d76); } +.legend > span { display: inline-flex; align-items: center; } +.legend .dot { width: 12px; height: 12px; margin: 0 5px 0 0; } +ul.steps li { align-items: center; } +ul.steps li .stext { flex: 1; } +ul.steps li .srhs { flex: 0 0 auto; margin-left: 10px; } +.smuted { font-size: 11.5px; color: var(--text-color-muted,#656d76); white-space: nowrap; } +ul.steps li.locked .stext { opacity: .55; } +ul.steps li.locked .dot { opacity: .5; } +.card.locked { border-style: dashed; } +.card.locked > h2 { color: var(--text-color-muted,#656d76); } +.finding.locked { opacity: .72; } +.finding.locked .ftitle { opacity: .8; } +/* environment doctor / readiness checks */ +ul.checks { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 9px; } +ul.checks li { display: flex; gap: 10px; align-items: flex-start; } +ul.checks .ci { width: 18px; height: 18px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; font-size: 11px; font-weight: 700; flex: 0 0 auto; margin-top: 1px; } +.chk.ok .ci { background: rgba(26,127,55,.15); color: var(--true-color-green,#1a7f37); } +.chk.warn .ci { background: rgba(154,103,0,.18); color: #9a6700; } +.chk.fail .ci { background: rgba(207,34,46,.15); color: var(--true-color-red,#cf222e); } +.chk.info .ci { background: var(--background-color-muted,#eaeef2); color: var(--text-color-muted,#656d76); } +ul.checks .cbody { display: flex; flex-direction: column; gap: 1px; flex: 1 1 auto; min-width: 0; } +ul.checks .clabel { font-weight: 600; } +ul.checks .cdetail { font-size: 12px; color: var(--text-color-muted,#656d76); } +ul.checks .cfix { font-size: 12px; color: #9a6700; margin-top: 2px; } +ul.checks .crhs { flex: 0 0 auto; } +.chk.fail .clabel { color: var(--true-color-red,#cf222e); } +.banner-red { border-color: var(--true-color-red,#cf222e); background: rgba(207,34,46,.08); } +nav.tabs button .count.c-red { color: var(--true-color-red,#cf222e); font-weight: 700; } +nav.tabs button .count.c-amber { color: #9a6700; font-weight: 700; } +.lockwhy { margin-right: 8px; } +/* recommended-approach guide */ +.card.guide { background: var(--background-color-muted, #f6f8fa); } +.flow { display: flex; flex-wrap: wrap; align-items: center; gap: 4px; } +.flow .fstep { font-size: 12px; padding: 3px 9px; border-radius: 999px; border: 1px solid var(--border-color-default,#d0d7de); background: var(--background-color-default,#fff); color: var(--text-color-muted,#656d76); } +.flow .fstep.on { border-color: var(--true-color-blue,#0969da); color: var(--true-color-blue,#0969da); font-weight: 600; } +.flow .fstep.past { color: var(--true-color-green,#1a7f37); border-color: var(--true-color-green,#1a7f37); } +.flow .farrow { color: var(--text-color-muted,#8c959f); font-size: 12px; } +/* autopilot */ +.banner-auto { border-color: var(--true-color-blue,#0969da); background: var(--true-color-blue-muted,#ddf4ff); align-items: center; } +.card.autopilot { border-color: var(--true-color-blue,#0969da); } +.badge.b-blue { background: var(--true-color-blue-muted,#ddf4ff); color: var(--true-color-blue,#0969da); } +.apc { font-size: 11.5px; color: var(--text-color-muted,#656d76); margin-left: 6px; } +ul.aplog { list-style: none; margin: 8px 0 12px; padding: 8px 12px; border: 1px solid var(--border-color-default,#d0d7de); border-radius: 8px; background: var(--background-color-muted,#f6f8fa); display: flex; flex-direction: column; gap: 4px; font-size: 12.5px; } +ul.aplog li { display: block; } +.apok { color: var(--true-color-green,#1a7f37); font-weight: 700; } +.apno { color: var(--true-color-red,#cf222e); font-weight: 700; } +`; + +function clientMain() { + const boot = window.__APPMOD__ || {}; + // The host embeds a per-instance token in the iframe URL; echo it back on every + // request so the loopback server accepts us. Empty in unit harnesses (no guard). + const apiToken = boot.token || ""; + const tokenQuery = apiToken ? "?token=" + encodeURIComponent(apiToken) : ""; + let state = null; + let activeTab = boot.initialTab || "overview"; + let pending = null; + let autopilotRunning = false; + + const $ = (sel) => document.querySelector(sel); + const esc = (s) => + String(s == null ? "" : s).replace(/[&<>"']/g, (c) => + ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c]) + ); + + function toast(msg) { + const t = $("#toast"); + t.textContent = msg; + t.classList.add("show"); + clearTimeout(toast._t); + toast._t = setTimeout(() => t.classList.remove("show"), 2600); + } + + function statusBadge(status) { + const map = { + completed: ["b-green", "Completed"], + in_progress: ["b-amber", "In progress"], + not_started: ["b-gray", "Not started"], + done: ["b-green", "Done"], + pending: ["b-gray", "Pending"], + failed: ["b-red", "Failed"], + not_run: ["b-gray", "Not run"], + pass: ["b-green", "Pass"], + }; + const [cls, label] = map[status] || ["b-gray", status || "—"]; + return '' + esc(label) + ""; + } + + function btn(label, kind, payload, opts) { + opts = opts || {}; + const cls = "btn" + (opts.primary ? " primary" : ""); + // While Autopilot runs, freeze manual actions so a stray click can't inject + // a competing prompt into the same session — except Stop and tab navigation. + const frozen = autopilotRunning && kind !== "autopilot_stop" && kind !== "autopilot_dismiss" && String(kind).indexOf("goto:") !== 0; + const dis = pending || frozen ? " disabled" : ""; + return ( + '" + ); + } + + // ---- tab renderers ------------------------------------------------------- + + function overviewTab(s) { + let html = ""; + + // Environment readiness nudge — surfaces a blocker before the user starts. + const d = s.doctor; + if (d && d.overall === "blocked") { + html += '"; + } else if (d && d.overall === "caution") { + html += '"; + } + + // Journey stepper — always shows where you are in the workflow. + html += '

    Your modernization journey

    ' + journeyHtml(s) + "
    "; + + // One adaptive "do this next" hero so the next click is never ambiguous. + const hero = heroNext(s); + html += + '
    ' + esc(hero.eyebrow) + "
    " + + '
    ' + esc(hero.title) + "
    " + + '
    ' + hero.body + "
    " + + '
    ' + hero.actions + "
    "; + + // Autopilot nudge — once there's a checklist with work left, offer to run it + // hands-free. Hidden while a run is already active (the strip covers that). + const planSteps = (s.progress && s.progress.steps.length ? s.progress.steps : (s.plan ? s.plan.steps : [])) || []; + const pendingSteps = planSteps.filter((x) => x.status !== "done").length; + const envBlocked = d && d.overall === "blocked"; + if (pendingSteps > 0 && !(s.autopilot && s.autopilot.running)) { + const cta = envBlocked + ? btn("Open Readiness →", "goto:readiness") + : btn("▶ Run on autopilot", "autopilot_start", { scope: "phase" }, { primary: true }) + btn("See in Plan →", "goto:plan"); + html += + '

    ⚡ Run on autopilot beta

    ' + + '

    Instead of clicking each step, let Copilot work the ' + pendingSteps + + " remaining step(s) in order and update this dashboard live. It pauses at the end of the phase and does not commit.

    " + + '
    ' + cta + "
    "; + } + + // Findings snapshot (only once an assessment has been run). + const rep = s.report; + if (rep && rep.findings && rep.findings.length) { + const by = sevCounts(rep.findings); + html += '

    Assessment findings ' + btn("Open Assessment →", "goto:assessment") + "

    "; + if (rep.summary) html += '

    ' + esc(rep.summary) + "

    "; + html += '
    ' + sevChip("P0", by.P0) + sevChip("P1", by.P1) + sevChip("P2", by.P2) + sevChip("P3", by.P3) + "
    "; + } + + // Stack facts. + const a = s.assessment || {}; + const jv = a.javaVersion ? "Java " + a.javaVersion : "Java (unknown)"; + html += + '

    Stack

    ' + + kv("Build tool", a.buildTool || "—") + + kv("Java version", esc(jv)) + + kv("Spring Boot", a.springBoot ? "Yes" + (a.springVersion ? " " + esc(a.springVersion) : "") : "No") + + kv("Container", a.hasDockerfile ? "Dockerfile ✓" : "None") + + kv("Git branch", s.git && s.git.branch ? esc(s.git.branch) : "—") + + kv("Working tree", s.git ? (s.git.dirty ? s.git.changedFiles + " changed" : "clean") : "—") + + "
    "; + + const rel = (s.tasks || []).filter((t) => t.relevant); + if (rel.length) { + html += '

    Detected in this repo ' + rel.length + "

    "; + rel.forEach((t) => (html += taskRow(t))); + html += "
    "; + } + return html; + } + + function assessmentTab(s) { + const rep = s.report; + if (!rep || !(rep.findings && rep.findings.length)) { + return emptyState( + "No assessment yet", + "Run an assessment to scan this repo for its Java runtime, dependencies, vulnerabilities, and Azure cloud-readiness gaps. Findings land here as a prioritized, clickable checklist — nothing in your code changes.", + btn("Run assessment", "start_assessment", {}, { primary: true }) + ); + } + const by = sevCounts(rep.findings); + let html = '

    Assessment results'; + if (rep.generatedAt) html += ' ' + esc(fmtDate(rep.generatedAt)) + ""; + html += '' + btn("Re-run", "start_assessment") + "

    "; + if (rep.headline) html += '

    ' + esc(rep.headline) + "

    "; + if (rep.summary) html += '

    ' + esc(rep.summary) + "

    "; + if (rep.stack) { + const st = rep.stack; + html += '
    ' + + (st.buildTool ? kv("Build", esc(st.buildTool)) : "") + + (st.java ? kv("Java", esc(st.java)) : "") + + (st.framework ? kv("Framework", esc(st.framework)) : "") + + (st.database ? kv("Database", esc(st.database)) : "") + + (st.container ? kv("Container", esc(st.container)) : "") + + "
    "; + } + html += '
    ' + sevChip("P0", by.P0) + sevChip("P1", by.P1) + sevChip("P2", by.P2) + sevChip("P3", by.P3) + "
    "; + + ["P0", "P1", "P2", "P3"].forEach((sev) => { + const items = rep.findings.filter((x) => x.severity === sev); + if (!items.length) return; + html += '
    ' + esc(sevName(sev)) + "
    "; + items.forEach((x) => (html += findingCard(x, s.ordering))); + }); + + if (rep.strengths && rep.strengths.length) { + html += '

    Already done well ' + rep.strengths.length + "

      "; + rep.strengths.forEach((x) => (html += '
    • ' + esc(x) + "
    • ")); + html += "
    "; + } + return html; + } + + // ---- assessment helpers -------------------------------------------------- + + function journeyHtml(s) { + const labels = ["Assess", "Remediate", "Validate", "Ship"]; + const hasPlan = !!((s.report && s.report.findings) || (s.plan && s.plan.exists) || (s.progress && s.progress.exists)); + const gates = s.gates || {}; + const anyGate = Object.keys(gates).some((k) => gates[k] && gates[k] !== "not_run"); + const done = s.status === "completed"; + let cur; + if (done) cur = 4; + else if (anyGate) cur = 2; + else if (hasPlan) cur = 1; + else cur = 0; + let h = '
    '; + labels.forEach((label, i) => { + if (i > 0) h += ''; + const cls = i < cur ? "done" : i === cur ? "current" : ""; + const mark = i < cur ? "✓" : String(i + 1); + h += '
    ' + mark + '' + esc(label) + "
    "; + }); + return h + "
    "; + } + + function heroNext(s) { + const rep = s.report; + const findings = rep && rep.findings ? rep.findings : []; + const hasReport = findings.length > 0; + const p0 = findings.filter((f) => f.severity === "P0"); + const p1 = findings.filter((f) => f.severity === "P1"); + const gates = s.gates || {}; + const anyGate = Object.keys(gates).some((k) => gates[k] && gates[k] !== "not_run"); + const hasPlan = !!((s.plan && s.plan.exists) || (s.progress && s.progress.exists)); + + if (s.status === "completed") { + return { + eyebrow: "Final step", + title: "Ship your changes", + body: "Validations are complete. Open a pull request to hand off the modernized service.", + actions: btn("Open a pull request", "open_pr", {}, { primary: true }) + btn("View summary", "goto:summary"), + }; + } + if (!hasReport && !hasPlan) { + return { + eyebrow: "Start here", + title: "Assess the project", + body: "Scan the repo for its Java runtime, dependencies, known CVEs, and Azure cloud-readiness gaps. Nothing changes in your code — you get a prioritized list of findings to work through.", + actions: btn("Run assessment", "start_assessment", {}, { primary: true }), + }; + } + if (p0.length) { + return { + eyebrow: "Blocker — fix this first", + title: p0[0].title, + body: esc(p0[0].detail || "A P0 issue is blocking modernization. Resolve it before moving on."), + actions: heroAction(p0[0]) + btn("See all findings", "goto:assessment"), + }; + } + if (hasReport && !anyGate) { + if (p1.length) { + return { + eyebrow: "Recommended next", + title: "Start remediation: " + p1[0].title, + body: esc(p1[0].detail || "Work through the high-priority findings first."), + actions: heroAction(p1[0]) + btn("See all findings", "goto:assessment"), + }; + } + return { + eyebrow: "Recommended next", + title: "Work through the findings", + body: "Open the Assessment tab and resolve findings by priority. Each one has a button that hands the fix to the agent.", + actions: btn("Open Assessment", "goto:assessment", {}, { primary: true }), + }; + } + return { + eyebrow: "Recommended next", + title: "Validate your changes", + body: "Build the project, run unit tests, and re-check CVEs to confirm the migration holds.", + actions: btn("Build & run tests", "run_build_tests", {}, { primary: true }) + btn("Open Validation", "goto:validation"), + }; + } + + function heroAction(f) { + const act = f.action; + if (act && act.kind && act.kind !== "fix_finding") { + return btn(act.label || "Run this step", act.kind, act.payload || {}, { primary: true }); + } + return btn((act && act.label) || "Help me fix this", "fix_finding", findingCtx(f), { primary: true }); + } + + function findingCard(x, ord) { + const locked = isFindingLocked(x, ord); + let h = '
    '; + h += '
    ' + esc(x.severity || "—") + "" + esc(x.title) + "
    "; + if (x.detail) h += '
    ' + esc(x.detail) + "
    "; + if (x.files && x.files.length) { + h += '
    ' + x.files.map((p) => "" + esc(p) + "").join("") + "
    "; + } + h += '
    ' + findingActions(x, ord) + "
    "; + return h + "
    "; + } + + function sevRank(sev) { + const m = { P0: 1, P1: 2, P2: 3, P3: 4 }; + return m[sev] != null ? m[sev] : null; + } + function isFindingLocked(x, ord) { + if (!ord || ord.activeRank == null) return false; + const r = sevRank(x.severity); + return r != null && r > ord.activeRank; + } + + function findingActions(x, ord) { + const act = x.action; + if (isFindingLocked(x, ord)) { + const why = '🔒 fix ' + esc(shortPhase(ord.activePhase)) + " issues first"; + if (act && act.kind && act.kind !== "fix_finding") { + return why + btn(act.label || "Run this step", act.kind, act.payload || {}) + btn("Help me fix this", "fix_finding", findingCtx(x)); + } + return why + btn((act && act.label) || "Help me fix this", "fix_finding", findingCtx(x)); + } + if (act && act.kind && act.kind !== "fix_finding") { + return btn(act.label || "Run this step", act.kind, act.payload || {}, { primary: true }) + btn("Help me fix this", "fix_finding", findingCtx(x)); + } + return btn((act && act.label) || "Help me fix this", "fix_finding", findingCtx(x), { primary: true }); + } + + function findingCtx(x) { + return { title: x.title, detail: x.detail, files: x.files, severity: x.severity }; + } + function sevCounts(f) { + const c = { P0: 0, P1: 0, P2: 0, P3: 0 }; + (f || []).forEach((x) => { if (c[x.severity] != null) c[x.severity]++; }); + return c; + } + function sevBadgeClass(sev) { + return { P0: "b-red", P1: "b-amber", P2: "b-blue", P3: "b-gray" }[sev] || "b-gray"; + } + function sevChip(sev, n) { + return '' + esc(sev) + " · " + (n || 0) + ""; + } + function sevName(sev) { + return { P0: "P0 · Blockers", P1: "P1 · High priority", P2: "P2 · Medium priority", P3: "P3 · Low / platform" }[sev] || sev; + } + function fmtDate(iso) { + try { + const d = new Date(iso); + if (isNaN(d.getTime())) return iso; + return d.toLocaleDateString(undefined, { month: "short", day: "numeric" }) + " " + d.toLocaleTimeString(undefined, { hour: "2-digit", minute: "2-digit" }); + } catch (e) { + return iso; + } + } + + // ---- autopilot ----------------------------------------------------------- + + // Live status strip for an Autopilot run, shown above the active tab body. + function autopilotStrip(s) { + const a = s && s.autopilot; + if (!a) return ""; + if (a.running) { + const scopeLabel = a.scope === "all" ? "all phases" : "this phase"; + const now = a.current + ? "Working: " + esc(a.current.title) + (a.current.section ? ' ' + esc(a.current.section) + "" : "") + : "Selecting the next step…"; + return ( + '" + autopilotLog(a) + ); + } + const labels = { + completed: "finished — every eligible step is done", + phase_done: "finished this phase", + stuck: "paused — a step needs your input", + cancelled: "stopped", + capped: "reached its step limit", + error: "stopped on an error", + }; + const label = labels[a.status] || "finished"; + const bad = a.status === "error" || a.status === "stuck"; + let note = "Review the changes (nothing was committed), then continue."; + if (a.stuck) note = "Stuck on: " + esc(a.stuck) + ". Open it in Plan & Progress to finish it yourself, then resume."; + if (a.error) note = esc(a.error); + const more = + btn("▶ Continue (next phase)", "autopilot_start", { scope: "phase" }, { primary: true }) + + btn("Dismiss", "autopilot_dismiss", {}); + return ( + '" + autopilotLog(a) + ); + } + + function autopilotLog(a) { + if (!a || !a.completed || !a.completed.length) return ""; + let h = '
      '; + a.completed.forEach((c) => { + h += + "
    • " + (c.done ? '' : '') + + " " + esc(c.title) + + (c.section ? ' ' + esc(c.section) + "" : "") + + (c.error ? ' ' + esc(c.error) + "" : "") + + "
    • "; + }); + return h + "
    "; + } + + // Autopilot launch card on the Plan tab. + function autopilotCard(s) { + const running = !!(s.autopilot && s.autopilot.running); + const blocked = s.doctor && s.doctor.overall === "blocked"; + let body = + '

    Let Copilot work the checklist for you. Autopilot runs each eligible step in order, ' + + "updates progress.md, and streams results here live. It pauses at the end of the phase, stops if a step needs a decision, " + + "and does not commit — so you stay in control and can review the diff.

    "; + let actions; + if (running) { + actions = btn("■ Stop autopilot", "autopilot_stop", {}); + } else if (blocked) { + actions = 'Environment is not ready. ' + btn("Open Readiness →", "goto:readiness"); + } else { + actions = + btn("▶ Run this phase", "autopilot_start", { scope: "phase" }, { primary: true }) + + btn("Run all phases", "autopilot_start", { scope: "all" }); + } + return '

    ⚡ Autopilot beta

    ' + body + '
    ' + actions + "
    "; + } + + function planTab(s) { + const usingProgress = !!(s.progress && s.progress.steps.length); + const steps = (usingProgress ? s.progress.steps : (s.plan ? s.plan.steps : [])) || []; + if (!steps.length) { + return emptyState( + "No plan yet", + "Run an assessment or generate an upgrade plan. App Modernization writes plan.md and a progress.md checklist, and your steps land here — each unchecked one becomes a button that hands that exact step to the agent.", + btn("Run assessment", "start_assessment", {}, { primary: true }) + btn("Generate upgrade plan", "generate_plan", { targetJava: 21 }) + ); + } + const src = usingProgress ? "progress.md" : "plan.md"; + const total = steps.length; + const done = steps.filter((x) => x.status === "done").length; + const ord = s.ordering || { activeRank: null, activePhase: null }; + + // Intro + overall progress + legend. + let html = '

    Plan & Progress ' + esc(src) + "" + statusBadge(s.status) + "

    "; + html += '

    Your live modernization checklist. Each unchecked step has a Work on this button that hands just that step to the agent in this session — it checks the box here when the step is done.

    '; + html += '
    '; + html += '

    ' + done + " of " + total + " steps done · " + (s.percent || 0) + "% complete

    "; + html += '
    DoneIn progressTo do🔒 Do later
    '; + html += "
    "; + + // Recommended-order guide so users don't work steps out of sequence. + html += approachGuide(ord); + + // Autopilot: hand the whole phase to the agent instead of clicking each step. + html += autopilotCard(s); + + // "Continue here" — the recommended next step: first not-done step in the + // active phase (falls back to the first not-done step overall). + const next = steps.find((x) => x.status !== "done" && (ord.activeRank == null || x.rank === ord.activeRank)) || steps.find((x) => x.status !== "done"); + if (next) { + html += + '
    Continue here · recommended next step
    ' + + '
    ' + esc(next.title) + "
    " + + (next.section ? '
    Phase: ' + esc(next.section) + "
    " : "") + + '
    ' + stepAction(next) + (next.section && /validation|gates/i.test(next.section) ? btn("Open Validation", "goto:validation") : "") + "
    "; + } else { + html += ''; + } + + // Steps grouped by their phase/section. + const groups = []; + const idx = {}; + steps.forEach((st) => { + const key = st.section || "Steps"; + if (idx[key] == null) { idx[key] = groups.length; groups.push({ name: key, items: [], rank: st.rank }); } + groups[idx[key]].items.push(st); + }); + + groups.forEach((g) => { + const gdone = g.items.filter((x) => x.status === "done").length; + const isValidation = /validation|gates/i.test(g.name); + const gLocked = ord.activeRank != null && g.rank != null && g.rank > ord.activeRank; + html += '

    ' + (gLocked ? "🔒 " : "") + esc(g.name) + ' ' + gdone + "/" + g.items.length + ""; + if (isValidation) html += '' + btn("Open Validation →", "goto:validation"); + else if (gLocked) html += 'after ' + esc(shortPhase(ord.activePhase)) + ""; + html += '

      '; + g.items.forEach((st) => { + const locked = ord.activeRank != null && st.rank != null && st.rank > ord.activeRank; + html += + '' + + '' + esc(st.title) + "" + + '' + stepRowAction(st, isValidation, locked, ord) + ""; + }); + html += "
    "; + }); + + return html; + } + + function approachGuide(ord) { + const order = ["Assessment", "P0 Build", "P1 Security", "P2 Runtime", "P3 Observability", "Validate"]; + const activeIdx = ord && ord.activeRank != null ? ord.activeRank : -1; + const flow = order + .map((name, i) => '' + esc(name) + "") + .join(''); + let body = "Work top-down and finish each phase before the next — e.g. don't upgrade the Java runtime (P2) while the build is still broken (P0). Steps in later phases are marked 🔒 until the current phase is done; Continue here always points to the safe next step."; + if (ord && ord.activePhase) body += " You're in " + esc(ord.activePhase) + "."; + return '

    Recommended approach

    ' + flow + "

    " + body + "

    "; + } + function shortPhase(name) { + if (!name) return "the current phase"; + const m = String(name).match(/\bP[0-3]\b/i); + return m ? m[0].toUpperCase() : name; + } + + function stepAction(st) { + const label = st.status === "in_progress" ? "Resume this step" : "Work on this step"; + return btn(label, "work_step", { title: st.title, section: st.section }, { primary: true }); + } + function stepRowAction(st, isValidation, locked, ord) { + if (st.status === "done") return '✓ Done'; + if (isValidation) return 'run from Validation →'; + if (locked) { + return '🔒 after ' + esc(shortPhase(ord && ord.activePhase)) + "" + btn("Do anyway", "work_step", { title: st.title, section: st.section }); + } + const label = st.status === "in_progress" ? "Resume" : "Work on this"; + return btn(label, "work_step", { title: st.title, section: st.section }); + } + + function validationTab(s) { + const gates = s.gates || {}; + const labels = { build: "Build", tests: "Unit Tests", cve: "CVE Check", consistency: "Consistency", completeness: "Completeness" }; + let html = '

    Validation gates

    '; + Object.keys(labels).forEach((k) => { + html += '
    ' + esc(labels[k]) + "" + statusBadge(gates[k] || "not_run") + "
    "; + }); + html += "
    "; + html += + '

    Run validations

    ' + + btn("Build & unit tests", "run_build_tests", {}, { primary: true }) + + btn("Scan CVEs", "run_cve") + + btn("Generate unit tests", "generate_tests") + + btn("Consistency check", "run_consistency") + + btn("Completeness check", "run_completeness") + + "

    CVE scan uses #appmod-validate-cves-for-java; test generation uses #appmod-generate-tests-for-java.

    "; + return html; + } + + function doctorTab(s) { + const d = s.doctor; + if (!d) { + return emptyState( + "Environment readiness", + "Check that your machine has the tools the App Modernization workflow needs — a JDK, your build tool, git, and (optionally) Docker and the Azure CLI. This only reads version numbers; nothing is installed or changed.", + btn("Check my environment", "recheck_env", {}, { primary: true }) + ); + } + const tone = d.overall === "ready" ? "b-green" : d.overall === "caution" ? "b-amber" : "b-red"; + const head = + d.overall === "ready" ? "Your environment is ready" : + d.overall === "caution" ? "Almost ready — a couple of things to check" : + "Not ready yet — resolve the blockers below"; + const sub = + d.overall === "ready" ? "All required tools are installed. You're clear to start modernizing." : + d.overall === "caution" ? "The required tools are present; some optional or recommended items need attention." : + "One or more required tools are missing. Fix these before building the project or running tasks."; + + const notAssessed = d.groups.some((g) => g.checks.some((c) => c.id === "assessed" && c.status !== "ok")); + let heroActions = btn("Re-check environment", "recheck_env", {}, { primary: true }); + if (notAssessed) heroActions += btn("Run assessment", "start_assessment", {}); + + let html = + '
    Environment readiness
    ' + + '
    ' + esc(head) + ' ' + esc(d.overall) + "
    " + + '
    ' + esc(sub) + "
    " + + '
    ' + heroActions + "
    "; + + d.groups.forEach((g) => { + html += '

    ' + esc(g.name) + "

      "; + g.checks.forEach((c) => { + html += + '
    • ' + + '' + checkIcon(c.status) + "" + + '' + esc(c.label) + "" + + '' + esc(c.detail || "") + "" + + (c.fix ? 'Fix: ' + esc(c.fix) + "" : "") + + "" + + '' + checkAction(c) + "
    • "; + }); + html += "
    "; + }); + + html += '

    Probed locally on your machine — version numbers only. Nothing was installed or modified.

    '; + return html; + } + + function checkIcon(status) { + if (status === "ok") return "✓"; + if (status === "warn") return "!"; + if (status === "fail") return "✕"; + return "•"; + } + function checkAction(c) { + if (!c.action) return ""; + const opts = c.status === "fail" ? { primary: true } : {}; + return btn(c.action.label || "Fix", c.action.kind, c.action.payload || {}, opts); + } + + function tasksTab(s) { + let html = ""; + // Custom skills + html += '

    Custom skills .github/skills

    '; + if (!s.skills || !s.skills.length) { + html += '

    No custom skills found. Capture an org-specific migration as a reusable skill.

    ' + + '
    ' + btn("Create a custom skill", "create_skill") + "
    "; + } else { + s.skills.forEach((sk) => { + html += + '
    ' + esc(sk.name) + "
    " + + '
    ' + esc(sk.description || sk.folder) + "
    " + + '
    ' + btn("Run", "run_skill", { folder: sk.folder }, { primary: true }) + "
    "; + }); + } + html += "
    "; + + // Predefined catalog grouped by category, relevant first + const tasks = (s.tasks || []).slice().sort((a, b) => (b.relevant ? 1 : 0) - (a.relevant ? 1 : 0)); + const cats = {}; + tasks.forEach((t) => { + (cats[t.category] = cats[t.category] || []).push(t); + }); + html += '

    Microsoft predefined tasks

    '; + Object.keys(cats).forEach((cat) => { + html += '
    ' + esc(cat) + "
    "; + cats[cat].forEach((t) => (html += taskRow(t))); + }); + html += "
    "; + return html; + } + + function summaryTab(s) { + let html = ""; + if (s.summary && s.summary.exists && s.summary.markdown) { + html += '

    summary.md

    ' + miniMd(s.summary.markdown) + "
    "; + } else { + html += emptyState("No summary yet", "App Modernization writes summary.md after validations pass.", ""); + } + const g = s.git || {}; + html += + '

    Branch & PR

    ' + + kv("Branch", g.branch ? esc(g.branch) : "—") + + kv("Migration branch", g.isMigrationBranch ? "Yes" : "No") + + kv("Changed files", g.changedFiles != null ? String(g.changedFiles) : "—") + + "
    " + + btn("Open a pull request", "open_pr", {}, { primary: true }) + + btn("Refresh", "refresh") + + "
    "; + return html; + } + + // ---- helpers ------------------------------------------------------------- + + function nextTarget(v) { + const ladder = [11, 17, 21, 25]; + for (const t of ladder) if (t > v) return t; + return 25; + } + function kv(k, v) { + return '
    ' + esc(k) + '' + v + "
    "; + } + function taskRow(t) { + return ( + '
    ' + esc(t.name) + + (t.relevant ? ' relevant' : "") + + '
    ' + esc(t.summary) + "
    " + + "
    " + btn("Run", "run_task", { taskId: t.id }) + "
    " + ); + } + function emptyState(big, sub, action) { + return '
    ' + esc(big) + "
    " + sub + "
    " + (action ? '
    ' + action + "
    " : "") + "
    "; + } + function miniMd(md) { + const lines = esc(md).split(/\r?\n/); + let out = ""; + let inList = false; + for (let line of lines) { + if (/^\s*[-*]\s+/.test(line)) { + if (!inList) { out += "
      "; inList = true; } + out += "
    • " + line.replace(/^\s*[-*]\s+/, "") + "
    • "; + continue; + } + if (inList) { out += "
    "; inList = false; } + const h = line.match(/^(#{1,3})\s+(.*)/); + if (h) { out += "" + h[2] + ""; continue; } + if (line.trim() === "") { out += "
    "; continue; } + out += "

    " + line + "

    "; + } + if (inList) out += ""; + return out.replace(/`([^`]+)`/g, "$1"); + } + + // ---- shell --------------------------------------------------------------- + + function render() { + if (!state) { + $("#app").innerHTML = '
    Loading…
    '; + return; + } + if (!state.ok) { + $("#app").innerHTML = emptyState("Repo not available", esc(state.error || "Could not read the repository."), btn("Retry", "refresh", {}, { primary: true })); + renderHeader(); + return; + } + renderHeader(); + autopilotRunning = !!(state.autopilot && state.autopilot.running); + const tabs = { overview: overviewTab, readiness: doctorTab, assessment: assessmentTab, plan: planTab, validation: validationTab, tasks: tasksTab, summary: summaryTab }; + let body = ""; + try { + body = autopilotStrip(state); + body += pending ? '" : ""; + body += (tabs[activeTab] || overviewTab)(state); + } catch (e) { + // A tab renderer threw — show the error instead of silently leaving the + // previous tab's content on screen (which looks like a dead click). + body = '"; + } + $("#app").innerHTML = body; + } + + function renderHeader() { + const s = state || {}; + const a = s.assessment || {}; + $("#status").innerHTML = s.status ? statusBadge(s.status) : ""; + $("#repo").textContent = s.repoPath ? s.repoPath.split(/[\\/]/).slice(-2).join("/") : ""; + const chips = $("#chips"); + if (!s.ok) { chips.innerHTML = ""; } + else { + chips.innerHTML = [ + a.buildTool ? '' + esc(a.buildTool) + "" : "", + a.javaVersion ? 'Java ' + esc(a.javaVersion) + "" : "", + a.springBoot ? 'Spring Boot' : "", + a.hasDockerfile ? 'Docker' : "", + s.git && s.git.branch ? '' + esc(s.git.branch) + "" : "", + ].join(""); + } + const counts = { + assessment: s.report && s.report.findings ? s.report.findings.length : 0, + plan: s.progress && s.progress.steps.length ? s.progress.steps.length : (s.plan ? s.plan.steps.length : 0), + tasks: (s.tasks ? s.tasks.length : 0) + (s.skills ? s.skills.length : 0), + }; + document.querySelectorAll("nav.tabs button").forEach((b) => { + b.classList.toggle("active", b.dataset.tab === activeTab); + const c = b.querySelector(".count"); + if (!c) return; + if (b.dataset.tab === "readiness") { + const ov = s.doctor && s.doctor.overall; + c.textContent = ov === "blocked" ? "⚠" : ov === "caution" ? "!" : ""; + c.className = "count " + (ov === "blocked" ? "c-red" : ov === "caution" ? "c-amber" : ""); + } else { + c.textContent = counts[b.dataset.tab] ? "(" + counts[b.dataset.tab] + ")" : ""; + } + }); + } + + async function loadState() { + try { + const r = await fetch("/state" + tokenQuery); + state = await r.json(); + render(); + } catch (e) { + toast("Failed to load state"); + } + } + + async function doAction(kind, payload) { + if (kind === "refresh") { toast("Refreshing…"); await loadState(); return; } + if (kind.indexOf("goto:") === 0) { activeTab = kind.slice(5); render(); window.scrollTo(0, 0); return; } + // Autopilot controls stream their results over SSE, so they must not set the + // blocking "pending" banner that freezes the whole view for a one-shot send. + const isAutopilot = kind.indexOf("autopilot_") === 0; + try { + const r = await fetch("/action" + tokenQuery, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ kind, payload }), + }); + const res = await r.json(); + if (res.ok) { + if (res.state) { + // Local, synchronous actions (refresh, recheck_env) hand back a + // fresh snapshot inline. Apply it now and clear any spinner instead + // of waiting on an SSE broadcast that can be missed if the provider + // restarts or the connection drops — which would spin forever. + state = res.state; + pending = null; + } else if (!isAutopilot) { + pending = res.label || kind; + } + toast(res.message || "Sent to the agent"); + render(); + } else { + toast(res.error || "Action failed"); + } + } catch (e) { + toast("Action failed"); + } + } + + // event delegation for all action buttons + tabs + document.addEventListener("click", (e) => { + const tabBtn = e.target.closest("nav.tabs button"); + if (tabBtn) { activeTab = tabBtn.dataset.tab; render(); return; } + const b = e.target.closest("[data-kind]"); + if (b && !b.disabled) { + let payload = {}; + try { payload = JSON.parse(b.dataset.payload || "{}"); } catch {} + doAction(b.dataset.kind, payload); + } + }); + + // live updates: server pushes a fresh snapshot when the agent finishes a turn + try { + const ev = new EventSource("/events" + tokenQuery); + // Resync whenever the stream (re)connects. If the provider restarted while an + // action was in flight, its "done" broadcast was lost; re-fetching state here + // clears any stuck spinner and shows the real result. + ev.onopen = () => { loadState(); }; + ev.onmessage = (m) => { + try { + const data = JSON.parse(m.data); + if (data && data.type === "state") { state = data.state; pending = null; render(); } + else if (data && data.type === "autopilot" && state) { state.autopilot = data.autopilot; render(); } + } catch {} + }; + } catch {} + + render(); + loadState(); +} + +export function renderHtml({ instanceId, initialTab, token } = {}) { + // Escape `<` so a stray "" in any field can't break out of the inline + // " + + "" + ); +} diff --git a/extensions/java-modernization-studio/scan.mjs b/extensions/java-modernization-studio/scan.mjs new file mode 100644 index 00000000..60f8b1a4 --- /dev/null +++ b/extensions/java-modernization-studio/scan.mjs @@ -0,0 +1,571 @@ +// scan.mjs — Grounds the cockpit in the real repository. Reads App Modernization +// artifacts (plan.md / progress.md / summary.md), discovers custom skills under +// .github/skills/, extracts assessment facts from Maven/Gradle build files, and +// reports git branch state. Everything degrades gracefully when files are absent. + +import { readFile, readdir, stat, open } from "node:fs/promises"; +import { join } from "node:path"; +import { execFile } from "node:child_process"; +import { catalogWithRelevance, VALIDATION_GATES } from "./catalog.mjs"; + +// Dependency signatures: substring(s) searched in build files -> catalog `detect` key. +const DEP_SIGNATURES = { + rabbitmq: ["rabbitmq", "spring-rabbit", "amqp-client", "spring-amqp", "starter-amqp", "amqp"], + activemq: ["activemq"], + jms: ["javax.jms", "jakarta.jms", "spring-jms"], + awsS3: ["aws-java-sdk-s3", "s3", "software.amazon.awssdk"], + awsSqs: ["aws-java-sdk-sqs", "sqs"], + awsSecrets: ["secretsmanager"], + javamail: ["javax.mail", "jakarta.mail", "com.sun.mail", "spring-boot-starter-mail"], + ldap: ["spring-ldap", "spring-security-ldap", "unboundid-ldapsdk", "ldaptive"], + cache: ["infinispan", "swarmcache", "memcached", "spymemcached", "ehcache"], + oracle: ["ojdbc", "oracle"], + db2: ["db2jcc", "com.ibm.db2"], + sybase: ["jconn", "sybase"], + informix: ["informix"], + jdbc: ["jdbc", "hikari", "mysql", "postgresql", "mssql-jdbc", "sqlserver"], + keystore: ["keystore", ".jks", "javax.net.ssl"], + crypto: ["javax.crypto", "java.security", "bouncycastle", "bcprov"], + filelog: ["fileappender", "rollingfileappender", "logback", "log4j"], +}; + +// Cap how much of any single file we read. Build files and modernization docs are +// tiny; an unbounded read on a pathological repo (a giant generated XML, a vendored +// blob) would waste memory for no parsing benefit. Truncating is safe — every +// parser here scans for early markers/coordinates. +const MAX_READ_BYTES = 2 * 1024 * 1024; + +function execGit(args, cwd) { + return new Promise((resolve) => { + execFile("git", args, { cwd, timeout: 4000 }, (err, stdout) => { + if (err) resolve(null); + else resolve(String(stdout).trim()); + }); + }); +} + +async function readText(path) { + try { + const st = await stat(path); + if (!st.isFile()) return null; + if (st.size > MAX_READ_BYTES) { + const fh = await open(path, "r"); + try { + const buf = Buffer.alloc(MAX_READ_BYTES); + const { bytesRead } = await fh.read(buf, 0, MAX_READ_BYTES, 0); + return buf.toString("utf8", 0, bytesRead); + } finally { + await fh.close(); + } + } + return await readFile(path, "utf8"); + } catch { + return null; + } +} + +async function exists(path) { + try { + await stat(path); + return true; + } catch { + return false; + } +} + +async function isDirectory(path) { + try { + return (await stat(path)).isDirectory(); + } catch { + return false; + } +} + +// Explicit provenance marker the cockpit writes at the top of artifacts it owns, +// so a root plan/progress/summary self-identifies as modernization state even +// when there is no .appmod/ directory yet. +const APPMOD_MARKER_RE = / marker. This stops an unrelated summary.md from + // flipping a foreign repo to "completed", or a stray plan.md from injecting + // fake steps into the progress/ordering/autopilot machinery. + const hasAppmodArtifact = + reportRaw != null || apPlan != null || apProgress != null || apSummary != null; + const trusted = (root) => hasAppmodArtifact || hasMarker(root); + const planMd = apPlan || (trusted(rootPlan) ? rootPlan : null); + const progressMd = apProgress || (trusted(rootProgress) ? rootProgress : null); + const summaryMd = apSummary || (trusted(rootSummary) ? rootSummary : null); + + const assessment = await buildAssessment(repoPath); + const skills = await discoverSkills(repoPath); + + const planSteps = parseSteps(planMd); + const progressSteps = parseSteps(progressMd); + // Tag each step with its canonical phase rank for ordering guidance. + for (const arr of [planSteps, progressSteps]) { + for (const st of arr) st.rank = phaseRankFromName(st.section); + } + // Progress is the source of truth for status; fall back to plan steps. + const steps = progressSteps.length ? progressSteps : planSteps; + const percent = percentDone(steps); + const ordering = computeOrdering(steps); + // Prefer an explicit "Validation"/"gates" section for gate status; fall back + // to scanning all steps when no such section exists. + const gateSteps = + sectionSteps(progressMd, /validation|gates/i) || + sectionSteps(summaryMd, /validation|gates/i) || + steps; + const gates = deriveGates(gateSteps, summaryMd); + + let status = "not_started"; + if (summaryMd) status = "completed"; + else if (planMd || progressMd) status = "in_progress"; + + let git = null; + if (includeGit) { + const branch = await execGit(["rev-parse", "--abbrev-ref", "HEAD"], repoPath); + const dirty = await execGit(["status", "--porcelain"], repoPath); + git = { + branch, + isMigrationBranch: !!branch && /moderniz|migrat|upgrade|appmod/i.test(branch), + dirty: dirty === null ? null : dirty.length > 0, + changedFiles: dirty ? dirty.split(/\r?\n/).filter(Boolean).length : 0, + }; + } + + return { + ok: true, + repoPath, + scannedAt: new Date().toISOString(), + status, + percent, + assessment, + tasks: catalogWithRelevance(assessment.detectedKeys), + skills, + git, + plan: { exists: !!planMd, steps: planSteps }, + progress: { exists: !!progressMd, steps: progressSteps }, + summary: { exists: !!summaryMd, markdown: summaryMd || "" }, + report, + ordering, + gates, + }; +} diff --git a/extensions/java-modernization-studio/server.mjs b/extensions/java-modernization-studio/server.mjs new file mode 100644 index 00000000..af2e2275 --- /dev/null +++ b/extensions/java-modernization-studio/server.mjs @@ -0,0 +1,334 @@ +// server.mjs — Per-instance loopback HTTP server + action dispatch for the cockpit. +// Decoupled from the SDK: agent messaging is injected as `sendPrompt`, so this +// module can be exercised by tests without a live session. + +import { createServer } from "node:http"; +import { randomBytes } from "node:crypto"; +import { scanRepo } from "./scan.mjs"; +import { renderHtml } from "./renderer.mjs"; +import { buildPrompt, ACTION_LABELS } from "./prompts.mjs"; +import { runDoctor } from "./doctor.mjs"; +import { makeRun, runAutopilot, AUTOPILOT_MAX_STEPS } from "./autopilot.mjs"; + +/** Resolve which repo path an open/action context refers to, by precedence. */ +export function resolveRepoPath(ctx, lastWorkingDir) { + const fromInput = + ctx && ctx.input && typeof ctx.input.repoPath === "string" ? ctx.input.repoPath : null; + const fromSession = + ctx && ctx.session && ctx.session.workingDirectory ? ctx.session.workingDirectory : null; + // No process.cwd() fallback: that resolves to the extension's own directory, + // not the user's repo, and would make the cockpit scan unrelated files. When + // nothing resolves we return null and the UI shows a "repo not available" + // state instead of plausible-but-wrong data. + return fromInput || fromSession || lastWorkingDir || null; +} + +/** + * Scan the repo and attach the environment readiness report. Tool probing is + * expensive (spawns several processes), so the doctor result is cached on the + * instance record and only recomputed on first build or an explicit recheck — + * not on every per-turn refresh. Probing only runs when `rec.runDoctor` is wired + * (real instances), so unit tests stay fast and deterministic. + */ +export async function buildState(rec, { recheckEnv = false } = {}) { + const state = await scanRepo(rec.repoPath); + if (state && state.ok && typeof rec.runDoctor === "function") { + if (recheckEnv || rec.doctor == null) { + try { + rec.doctor = await rec.runDoctor(state); + } catch { + rec.doctor = rec.doctor || null; + } + } + state.doctor = rec.doctor; + } else if (state && state.ok) { + state.doctor = rec.doctor || null; + } + if (state && state.ok) state.autopilot = rec.autopilot || null; + return state; +} + +/** Write an SSE payload to every connected client of an instance. */ +export function broadcast(rec, payload) { + const data = "data: " + JSON.stringify(payload) + "\n\n"; + for (const res of rec.sseClients) { + try { + res.write(data); + } catch { + // Client disconnected uncleanly: drop it so we don't keep throwing on + // every future broadcast (dead clients would otherwise leak in the Set). + rec.sseClients.delete(res); + } + } +} + +/** Re-scan the repo and push the fresh snapshot to connected clients. */ +export async function pushState(rec, log, opts) { + let state; + try { + state = await buildState(rec, opts); + } catch (e) { + if (log) log("appmod-cockpit scan failed: " + e.message, { level: "error" }); + return null; + } + broadcast(rec, { type: "state", state }); + return state; +} + +/** Clamp a requested step budget into a sane range. */ +function clampSteps(n) { + const v = Number(n); + if (!Number.isFinite(v)) return AUTOPILOT_MAX_STEPS; + return Math.max(1, Math.min(50, Math.round(v))); +} + +/** + * Kick off an Autopilot run for this instance. Returns immediately; the run loop + * streams progress to connected clients over SSE. The run promise is parked on + * the record so it is not garbage-collected mid-flight. + * @param {object} rec instance record + * @param {object} payload { scope?: "phase"|"all", maxSteps?, force? } + * @param {{ runTurn:(p:string)=>Promise, log?:Function }} deps + */ +export async function startAutopilot(rec, payload, deps) { + if (rec.autopilot && rec.autopilot.running) { + return { ok: false, error: "Autopilot is already running." }; + } + if (!deps || typeof deps.runTurn !== "function") { + return { ok: false, error: "This session can't drive Autopilot." }; + } + const p = payload || {}; + const start = await buildState(rec); + if (!start || !start.ok) { + return { ok: false, error: "Can't read the repo to start Autopilot." }; + } + if (start.doctor && start.doctor.overall === "blocked" && !p.force) { + return { ok: false, error: "Your environment isn't ready. Open Readiness and install the missing tools first." }; + } + const scope = p.scope === "all" ? "all" : "phase"; + const startRank = start.ordering ? start.ordering.activeRank : null; + const run = makeRun({ scope, maxSteps: clampSteps(p.maxSteps), startRank }); + rec.autopilot = run; + broadcast(rec, { type: "autopilot", autopilot: run }); + + const runDeps = { + snapshot: () => buildState(rec), + runTurn: deps.runTurn, + buildStepPrompt: (step) => buildPrompt("auto_step", { title: step.title, section: step.section }, rec.repoPath), + onProgress: (r, state) => { + if (state) broadcast(rec, { type: "state", state }); + else broadcast(rec, { type: "autopilot", autopilot: r }); + }, + log: deps.log, + }; + rec.autopilotPromise = runAutopilot(run, runDeps) + .then(() => pushState(rec, deps.log)) + .catch((e) => { + if (deps.log) deps.log("Autopilot run failed: " + e.message, { level: "error" }); + }); + return { ok: true, message: "Autopilot started", autopilot: run }; +} + +/** + * Handle a cockpit action. + * @param {object} rec instance record ({ repoPath, sseClients, ... }) + * @param {string} kind action kind + * @param {object} payload action payload + * @param {{ sendPrompt:(p:string)=>Promise, runTurn?:Function, log?:Function }} deps + */ +export async function dispatchAction(rec, kind, payload, deps) { + if (kind === "refresh") { + const state = await pushState(rec, deps && deps.log); + return { ok: true, message: "Refreshed", state }; + } + if (kind === "recheck_env") { + const state = await pushState(rec, deps && deps.log, { recheckEnv: true }); + return { ok: true, message: "Re-checked environment", state }; + } + if (kind === "autopilot_start") { + return await startAutopilot(rec, payload, deps); + } + if (kind === "autopilot_stop") { + if (rec.autopilot && rec.autopilot.running) { + rec.autopilot.cancelled = true; + broadcast(rec, { type: "autopilot", autopilot: rec.autopilot }); + return { ok: true, message: "Stopping after the current step finishes…" }; + } + return { ok: false, error: "Autopilot isn't running." }; + } + if (kind === "autopilot_dismiss") { + if (rec.autopilot && !rec.autopilot.running) { + rec.autopilot = null; + await pushState(rec, deps && deps.log); + } + return { ok: true, message: "Dismissed" }; + } + const prompt = buildPrompt(kind, payload, rec.repoPath); + if (!prompt) return { ok: false, error: "Unknown action: " + kind }; + if (!deps || typeof deps.sendPrompt !== "function") { + return { ok: false, error: "Session not ready" }; + } + const label = ACTION_LABELS[kind] || kind; + try { + await deps.sendPrompt(prompt); + if (deps.log) deps.log("Java Modernization Studio → " + label, { ephemeral: true }); + return { ok: true, label, message: "Sent to the agent: " + label, prompt }; + } catch (e) { + return { ok: false, error: e.message }; + } +} + +// Cap the request body the /action endpoint will buffer. Actions carry tiny JSON +// payloads; anything large is a bug or abuse, so we reject rather than buffer it. +const MAX_BODY_BYTES = 256 * 1024; + +function readBody(req, max = MAX_BODY_BYTES) { + return new Promise((resolve) => { + let data = ""; + let size = 0; + let done = false; + req.on("data", (c) => { + if (done) return; + size += c.length; + if (size > max) { + done = true; + resolve({ body: "", tooLarge: true }); + if (typeof req.destroy === "function") req.destroy(); + return; + } + data += c; + }); + req.on("end", () => { + if (!done) { + done = true; + resolve({ body: data, tooLarge: false }); + } + }); + req.on("error", () => { + if (!done) { + done = true; + resolve({ body: "", tooLarge: false }); + } + }); + }); +} + +/** + * Build the request handler for an instance. Exposed for tests so routes can be + * exercised without binding a socket. + */ +export function makeHandler(rec, { instanceId, initialTab, sendPrompt, runTurn, log }) { + return async function handler(req, res) { + const url = new URL(req.url, "http://127.0.0.1"); + try { + // Per-instance secret: real instances (createInstanceServer) mint a token + // that the host embeds in the iframe URL. Reject any loopback request that + // doesn't present it, so other local processes can't read repo state or + // dispatch agent actions just by guessing the random port. When no token is + // set (direct makeHandler unit tests) the guard is a no-op. + if (rec.token) { + const provided = url.searchParams.get("token"); + if (provided !== rec.token) { + res.statusCode = 403; + res.setHeader("Content-Type", "application/json"); + res.end(JSON.stringify({ ok: false, error: "forbidden" })); + return; + } + } + if (req.method === "GET" && url.pathname === "/") { + res.setHeader("Content-Type", "text/html; charset=utf-8"); + res.end(renderHtml({ instanceId, initialTab, token: rec.token })); + return; + } + if (req.method === "GET" && url.pathname === "/state") { + const state = await buildState(rec); + res.setHeader("Content-Type", "application/json"); + res.end(JSON.stringify(state)); + return; + } + if (req.method === "GET" && url.pathname === "/events") { + res.writeHead(200, { + "Content-Type": "text/event-stream", + "Cache-Control": "no-cache", + Connection: "keep-alive", + }); + res.write("retry: 3000\n\n"); + rec.sseClients.add(res); + req.on("close", () => rec.sseClients.delete(res)); + return; + } + if (req.method === "POST" && url.pathname === "/action") { + const { body, tooLarge } = await readBody(req); + if (tooLarge) { + res.statusCode = 413; + res.setHeader("Content-Type", "application/json"); + res.end(JSON.stringify({ ok: false, error: "request body too large" })); + return; + } + let parsed; + try { + parsed = JSON.parse(body || "{}"); + } catch { + res.statusCode = 400; + res.setHeader("Content-Type", "application/json"); + res.end(JSON.stringify({ ok: false, error: "invalid JSON body" })); + return; + } + if (!parsed || typeof parsed.kind !== "string" || !parsed.kind) { + res.statusCode = 400; + res.setHeader("Content-Type", "application/json"); + res.end(JSON.stringify({ ok: false, error: "missing or invalid 'kind'" })); + return; + } + const result = await dispatchAction(rec, parsed.kind, parsed.payload, { sendPrompt, runTurn, log }); + res.setHeader("Content-Type", "application/json"); + res.end(JSON.stringify(result)); + return; + } + res.statusCode = 404; + res.end("not found"); + } catch (e) { + if (!res.headersSent) { + res.statusCode = 500; + res.setHeader("Content-Type", "application/json"); + } + res.end(JSON.stringify({ ok: false, error: e.message })); + } + }; +} + +/** + * Start a loopback server for one canvas instance. + * @returns {Promise} rec with { server, url, repoPath, sseClients } + */ +export async function createInstanceServer({ instanceId, repoPath, initialTab, sendPrompt, runTurn, log, runDoctor: runDoctorImpl }) { + const rec = { server: null, url: "", repoPath, sseClients: new Set(), doctor: null, autopilot: null, token: randomBytes(16).toString("hex") }; + // Wire the environment doctor (injectable for tests); real instances probe the + // local toolchain, cached and only recomputed on explicit recheck. + rec.runDoctor = typeof runDoctorImpl === "function" ? runDoctorImpl : (scan) => runDoctor(scan, {}); + const handler = makeHandler(rec, { instanceId, initialTab, sendPrompt, runTurn, log }); + const server = createServer((req, res) => { + // The handler is async: if it rejects, surface a 500 and log it rather than + // letting it become an unhandled rejection that can crash the extension. + handler(req, res).catch((err) => { + if (log) log("Java Modernization Studio server error: " + (err && err.message ? err.message : err), { level: "error" }); + try { + if (!res.headersSent) { + res.statusCode = 500; + res.setHeader("Content-Type", "application/json"); + res.end(JSON.stringify({ ok: false, error: "internal error" })); + } else { + res.end(); + } + } catch { + /* response already torn down */ + } + }); + }); + await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); + const addr = server.address(); + const port = typeof addr === "object" && addr ? addr.port : 0; + rec.server = server; + rec.url = "http://127.0.0.1:" + port + "/?token=" + rec.token; + return rec; +} diff --git a/extensions/java-modernization-studio/test/cockpit.test.mjs b/extensions/java-modernization-studio/test/cockpit.test.mjs new file mode 100644 index 00000000..c85207d4 --- /dev/null +++ b/extensions/java-modernization-studio/test/cockpit.test.mjs @@ -0,0 +1,1662 @@ +// cockpit.test.mjs — Test suite for the Java Modernization Studio canvas. +// Run with: node --test test/cockpit.test.mjs +// +// Covers the testable modules (scan, catalog, prompts, renderer, server). It does +// NOT import extension.mjs, which calls joinSession() at module load and needs a +// live CLI session. + +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { mkdtemp, writeFile, mkdir, rm } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join, dirname } from "node:path"; +import { execFileSync } from "node:child_process"; +import { EventEmitter } from "node:events"; + +import { scanRepo } from "../scan.mjs"; +import { PREDEFINED_TASKS, catalogWithRelevance, VALIDATION_GATES } from "../catalog.mjs"; +import { buildPrompt, ACTION_LABELS } from "../prompts.mjs"; +import { renderHtml } from "../renderer.mjs"; +import { + buildDoctorReport, + parseToolVersion, + probeOne, + runDoctor, + augmentedEnv, + discoverJavaHome, + parseJavaHomeFromRc, + TOOL_PROBES, +} from "../doctor.mjs"; +import { + resolveRepoPath, + broadcast, + pushState, + dispatchAction, + makeHandler, + createInstanceServer, +} from "../server.mjs"; +import { + selectNextStep, + stepKey, + isStepDone, + makeRun, + runAutopilot, +} from "../autopilot.mjs"; + +// ---- helpers ---------------------------------------------------------------- + +// Minimal provenance: a .appmod/ artifact tells the scanner this repo is actively +// running the modernization workflow, so root plan/progress/summary are trusted as +// workflow state (vs. unrelated files that merely share those names). +const PROV = { ".appmod/assessment.json": "{}" }; + +async function makeRepo(files = {}) { + const dir = await mkdtemp(join(tmpdir(), "appmod-test-")); + for (const [rel, content] of Object.entries(files)) { + const full = join(dir, rel); + await mkdir(dirname(full), { recursive: true }); + await writeFile(full, content); + } + return dir; +} + +async function withRepo(files, fn) { + const dir = await makeRepo(files); + try { + return await fn(dir); + } finally { + await rm(dir, { recursive: true, force: true }); + } +} + +function git(dir, ...args) { + execFileSync("git", args, { cwd: dir, stdio: "pipe" }); +} + +function fakeRes() { + return { + statusCode: 200, + headers: {}, + body: "", + ended: false, + setHeader(k, v) { + this.headers[String(k).toLowerCase()] = v; + }, + writeHead(code, hdrs) { + this.statusCode = code; + if (hdrs) for (const k of Object.keys(hdrs)) this.headers[k.toLowerCase()] = hdrs[k]; + }, + write(s) { + this.body += s; + }, + end(s) { + if (s !== undefined) this.body += s; + this.ended = true; + }, + }; +} + +// Drive makeHandler with a fake POST request stream. +async function invokePost(handler, path, payloadObj) { + const req = new EventEmitter(); + req.method = "POST"; + req.url = path; + const res = fakeRes(); + const pending = handler(req, res); + req.emit("data", JSON.stringify(payloadObj)); + req.emit("end"); + await pending; + return res; +} + +async function invokeGet(handler, path) { + const req = new EventEmitter(); + req.method = "GET"; + req.url = path; + const res = fakeRes(); + await handler(req, res); + return { req, res }; +} + +// =========================================================================== +// scan.mjs +// =========================================================================== + +test("scan: nonexistent path returns ok:false", async () => { + const s = await scanRepo("/no/such/path/at/all/xyz"); + assert.equal(s.ok, false); +}); + +test("scan: empty repo is not_started with all gates not_run", async () => { + await withRepo({}, async (dir) => { + const s = await scanRepo(dir, { includeGit: false }); + assert.equal(s.ok, true); + assert.equal(s.status, "not_started"); + assert.equal(s.percent, 0); + assert.equal(s.plan.exists, false); + assert.equal(s.progress.exists, false); + assert.equal(s.summary.exists, false); + for (const g of VALIDATION_GATES) assert.equal(s.gates[g.key], "not_run"); + }); +}); + +test("scan: Maven Java version from properties", async () => { + await withRepo( + { "pom.xml": "8" }, + async (dir) => { + const s = await scanRepo(dir, { includeGit: false }); + assert.equal(s.assessment.buildTool, "Maven"); + assert.equal(s.assessment.javaVersion, "8"); + } + ); +}); + +test("scan: Maven compiler release strips 1.x and detects", async () => { + await withRepo( + { "pom.xml": "17" }, + async (dir) => { + const s = await scanRepo(dir, { includeGit: false }); + assert.equal(s.assessment.javaVersion, "17"); + } + ); +}); + +test("scan: Gradle build tool + sourceCompatibility", async () => { + await withRepo( + { "build.gradle": "sourceCompatibility = '11'\napply plugin: 'java'" }, + async (dir) => { + const s = await scanRepo(dir, { includeGit: false }); + assert.equal(s.assessment.buildTool, "Gradle"); + assert.equal(s.assessment.javaVersion, "11"); + } + ); +}); + +test("scan: nested module pom is discovered", async () => { + await withRepo( + { "service/pom.xml": "8" }, + async (dir) => { + const s = await scanRepo(dir, { includeGit: false }); + assert.equal(s.assessment.buildTool, "Maven"); + assert.equal(s.assessment.javaVersion, "8"); + } + ); +}); + +test("scan: Spring Boot detection", async () => { + await withRepo( + { "pom.xml": "spring-boot-starter-web" }, + async (dir) => { + const s = await scanRepo(dir, { includeGit: false }); + assert.equal(s.assessment.springBoot, true); + } + ); +}); + +test("scan: Dockerfile detected at root and one level down", async () => { + await withRepo({ Dockerfile: "FROM tomcat:9" }, async (dir) => { + const s = await scanRepo(dir, { includeGit: false }); + assert.equal(s.assessment.hasDockerfile, true); + }); + await withRepo({ "svc/Dockerfile": "FROM tomcat:9" }, async (dir) => { + const s = await scanRepo(dir, { includeGit: false }); + assert.equal(s.assessment.hasDockerfile, true); + }); +}); + +test("scan: dependency relevance flags the right catalog tasks", async () => { + const cases = [ + ["spring-boot-starter-amqp", "rabbitmq-to-servicebus"], + ["activemq-client", "activemq-to-servicebus"], + ["aws-java-sdk-s3", "aws-s3-to-blob"], + ["ojdbc8", "databases-to-azure"], + ["spring-ldap-core", "entra-id-auth"], + ]; + for (const [dep, taskId] of cases) { + await withRepo( + { "pom.xml": `${dep}` }, + async (dir) => { + const s = await scanRepo(dir, { includeGit: false }); + const task = s.tasks.find((t) => t.id === taskId); + assert.ok(task, `task ${taskId} exists`); + assert.equal(task.relevant, true, `${dep} -> ${taskId} relevant`); + } + ); + } +}); + +test("scan: checklist statuses (done / in_progress / pending)", async () => { + await withRepo( + { ...PROV, "progress.md": "- [x] Done step\n- [/] Working step\n- [ ] Todo step\n" }, + async (dir) => { + const s = await scanRepo(dir, { includeGit: false }); + const byTitle = Object.fromEntries(s.progress.steps.map((x) => [x.title, x.status])); + assert.equal(byTitle["Done step"], "done"); + assert.equal(byTitle["Working step"], "in_progress"); + assert.equal(byTitle["Todo step"], "pending"); + } + ); +}); + +test("scan: emoji status markers override", async () => { + await withRepo( + { ...PROV, "progress.md": "- [ ] Build ✅\n- [ ] Tests ❌\n" }, + async (dir) => { + const s = await scanRepo(dir, { includeGit: false }); + const byTitle = Object.fromEntries( + s.progress.steps.map((x) => [x.title.replace(/[^A-Za-z ].*/, "").trim(), x.status]) + ); + assert.equal(byTitle["Build"], "done"); + assert.equal(byTitle["Tests"], "failed"); + } + ); +}); + +test("scan: numbered-list fallback when no checkboxes", async () => { + await withRepo({ ...PROV, "plan.md": "# Plan\n1. First thing\n2. Second thing\n" }, async (dir) => { + const s = await scanRepo(dir, { includeGit: false }); + assert.equal(s.plan.steps.length, 2); + assert.equal(s.plan.steps[0].title, "First thing"); + }); +}); + +test("scan: heading fallback when no checkboxes or numbers", async () => { + await withRepo({ ...PROV, "plan.md": "# Title\n## Phase one\n## Phase two\n" }, async (dir) => { + const s = await scanRepo(dir, { includeGit: false }); + const titles = s.plan.steps.map((x) => x.title); + assert.deepEqual(titles, ["Phase one", "Phase two"]); + }); +}); + +test("scan: percent counts in_progress as half", async () => { + await withRepo( + { ...PROV, "progress.md": "- [x] a\n- [/] b\n- [ ] c\n- [ ] d\n" }, + async (dir) => { + const s = await scanRepo(dir, { includeGit: false }); + // (1 + 0.5) / 4 = 37.5 -> 38 + assert.equal(s.percent, 38); + } + ); +}); + +test("scan: gates derived from matching step titles", async () => { + await withRepo( + { ...PROV, "progress.md": "- [x] Unit Tests pass\n- [/] Build app\n- [ ] CVE scan\n" }, + async (dir) => { + const s = await scanRepo(dir, { includeGit: false }); + assert.equal(s.gates.tests, "done"); + assert.equal(s.gates.build, "in_progress"); + assert.equal(s.gates.cve, "pending"); // matched but not run + assert.equal(s.gates.consistency, "not_run"); // unmatched + } + ); +}); + +test("scan: gates scope to an explicit Validation section, not assessment text", async () => { + // Assessment rows mention "build" and "vulnerable" (done); they must NOT + // satisfy the Build/CVE gates, which live in the Validation section (pending). + const md = [ + "## Assessment", + "- [x] Inventory runtime, build tool, dependencies", + "- [x] Identify vulnerable dependencies", + "", + "## Validation gates", + "- [ ] Build — mvn clean package succeeds", + "- [ ] CVE Check — no critical CVEs", + "", + "## Next", + "- [x] something else with the word build in it", + ].join("\n"); + await withRepo({ ...PROV, "progress.md": md }, async (dir) => { + const s = await scanRepo(dir, { includeGit: false }); + assert.equal(s.gates.build, "pending"); + assert.equal(s.gates.cve, "pending"); + }); +}); + +test("scan: status is completed when summary.md present", async () => { + await withRepo({ ...PROV, "summary.md": "# done" }, async (dir) => { + const s = await scanRepo(dir, { includeGit: false }); + assert.equal(s.status, "completed"); + }); +}); + +test("scan: status is in_progress when only plan present", async () => { + await withRepo({ ...PROV, "plan.md": "- [ ] a" }, async (dir) => { + const s = await scanRepo(dir, { includeGit: false }); + assert.equal(s.status, "in_progress"); + }); +}); + +test("scan: reads .appmod/assessment.json into report", async () => { + const report = { + generatedAt: "2026-01-01T00:00:00Z", + headline: "h", + findings: [{ id: "x", severity: "P0", title: "t", detail: "d", files: ["a"], action: { kind: "fix_finding" } }], + }; + await withRepo({ ".appmod/assessment.json": JSON.stringify(report) }, async (dir) => { + const s = await scanRepo(dir, { includeGit: false }); + assert.ok(s.report, "report should be present"); + assert.equal(s.report.findings.length, 1); + assert.equal(s.report.findings[0].severity, "P0"); + }); +}); + +test("scan: report is null when assessment.json absent or malformed", async () => { + await withRepo({ "pom.xml": "" }, async (dir) => { + const s = await scanRepo(dir, { includeGit: false }); + assert.equal(s.report, null); + }); + await withRepo({ ".appmod/assessment.json": "{ not valid json" }, async (dir) => { + const s = await scanRepo(dir, { includeGit: false }); + assert.equal(s.report, null, "malformed JSON should not throw, just yield null"); + }); +}); + +// ---- provenance gate (cross-repo safety) ------------------------------------ + +test("scan: an unrelated summary.md without provenance does NOT mark completed", async () => { + await withRepo({ "summary.md": "# My library\nUsage and changelog notes." }, async (dir) => { + const s = await scanRepo(dir, { includeGit: false }); + assert.notEqual(s.status, "completed", "a stray summary.md must not flip status to completed"); + assert.equal(s.status, "not_started"); + assert.equal(s.summary.exists, false, "untrusted summary is treated as absent"); + }); +}); + +test("scan: an unrelated plan.md without provenance is not treated as workflow steps", async () => { + await withRepo({ "plan.md": "# Roadmap\n- [ ] ship feature x\n- [ ] write docs\n" }, async (dir) => { + const s = await scanRepo(dir, { includeGit: false }); + assert.equal(s.plan.steps.length, 0, "stray checkboxes must not become modernization steps"); + assert.equal(s.status, "not_started"); + }); +}); + +test("scan: assessment.json provenance makes a root progress.md trusted", async () => { + await withRepo( + { ".appmod/assessment.json": "{}", "progress.md": "## P0\n- [x] a\n- [ ] b\n" }, + async (dir) => { + const s = await scanRepo(dir, { includeGit: false }); + assert.equal(s.progress.steps.length, 2); + assert.equal(s.status, "in_progress"); + } + ); +}); + +test("scan: an explicit appmod marker makes a root plan trusted without .appmod/", async () => { + await withRepo( + { "plan.md": "\n## P0 Build\n- [ ] Fix build\n" }, + async (dir) => { + const s = await scanRepo(dir, { includeGit: false }); + assert.equal(s.plan.steps.length, 1, "marked file is trusted"); + assert.equal(s.status, "in_progress"); + } + ); +}); + +test("scan: .appmod/ namespaced docs are trusted and take precedence over root", async () => { + await withRepo( + { + ".appmod/plan.md": "## P0\n- [ ] from appmod\n", + "plan.md": "## P0\n- [ ] from root\n- [ ] root two\n", + }, + async (dir) => { + const s = await scanRepo(dir, { includeGit: false }); + assert.equal(s.plan.steps.length, 1, "uses .appmod/plan.md, not the root copy"); + assert.equal(s.plan.steps[0].title, "from appmod"); + } + ); +}); + +test("scan: a non-directory path is reported unavailable (not scanned)", async () => { + await withRepo({ "pom.xml": "" }, async (dir) => { + const s = await scanRepo(join(dir, "pom.xml"), { includeGit: false }); + assert.equal(s.ok, false, "a file path is not a repo"); + }); +}); + +test("scan: steps carry their section heading", async () => { + const md = "# Plan\n## P0 — Build\n- [x] a\n- [ ] b\n## P1 — Secrets\n- [ ] c\n"; + await withRepo({ ...PROV, "progress.md": md }, async (dir) => { + const s = await scanRepo(dir, { includeGit: false }); + const byTitle = Object.fromEntries(s.progress.steps.map((x) => [x.title, x.section])); + assert.equal(byTitle["a"], "P0 — Build"); + assert.equal(byTitle["b"], "P0 — Build"); + assert.equal(byTitle["c"], "P1 — Secrets"); + }); +}); + +test("scan: ordering computes the active phase, ranks, and per-phase counts", async () => { + const md = + "# Plan\n## Assessment\n- [x] a1\n- [x] a2\n" + + "## P0 — Build consistency\n- [x] b1\n- [ ] b2\n" + + "## P1 — Secrets & identity\n- [ ] c1\n- [ ] c2\n" + + "## Validation gates\n- [ ] v1\n"; + await withRepo({ ...PROV, "progress.md": md }, async (dir) => { + const s = await scanRepo(dir, { includeGit: false }); + assert.equal(s.ordering.activeRank, 1, "P0 is the active rank"); + assert.match(s.ordering.activePhase, /P0/); + // Per-phase roll-up. + const byName = Object.fromEntries(s.ordering.phases.map((p) => [p.name, p])); + assert.equal(byName["Assessment"].done, 2); + assert.equal(byName["Assessment"].total, 2); + assert.equal(byName["P0 — Build consistency"].done, 1); + assert.equal(byName["P0 — Build consistency"].total, 2); + assert.equal(byName["P1 — Secrets & identity"].done, 0); + // Each step carries a phase rank; P1 steps sit ahead of the active P0. + const byTitle = Object.fromEntries(s.progress.steps.map((x) => [x.title, x.rank])); + assert.equal(byTitle["a1"], 0); + assert.equal(byTitle["b2"], 1); + assert.equal(byTitle["c1"], 2); + assert.ok(byTitle["c1"] > s.ordering.activeRank, "P1 step is ahead of sequence (locked)"); + }); +}); + +test("scan: ordering activeRank is null once every ranked step is done", async () => { + const md = "# Plan\n## Assessment\n- [x] a\n## P0 — Build\n- [x] b\n"; + await withRepo({ ...PROV, "progress.md": md }, async (dir) => { + const s = await scanRepo(dir, { includeGit: false }); + assert.equal(s.ordering.activeRank, null); + assert.equal(s.ordering.activePhase, null); + }); +}); + + +test("scan: discovers custom skills from .github/skills", async () => { + await withRepo( + { + ".github/skills/my-skill/SKILL.md": "name: My Skill\ndescription: Does a thing\n", + ".github/skills/other/SKILL.md": "# Other Skill\n", + }, + async (dir) => { + const s = await scanRepo(dir, { includeGit: false }); + assert.equal(s.skills.length, 2); + const mine = s.skills.find((x) => x.folder === "my-skill"); + assert.equal(mine.name, "My Skill"); + assert.equal(mine.description, "Does a thing"); + } + ); +}); + +test("scan: git branch + migration detection + dirty flag", async () => { + await withRepo({ "pom.xml": "", "README.md": "x" }, async (dir) => { + git(dir, "init", "-b", "modernize-java21"); + git(dir, "config", "user.email", "t@example.com"); + git(dir, "config", "user.name", "Test"); + git(dir, "add", "."); + git(dir, "commit", "-m", "init", "--no-gpg-sign"); + + let s = await scanRepo(dir); + assert.equal(s.git.branch, "modernize-java21"); + assert.equal(s.git.isMigrationBranch, true); + assert.equal(s.git.dirty, false); + + await writeFile(join(dir, "new.txt"), "hi"); + s = await scanRepo(dir); + assert.equal(s.git.dirty, true); + assert.equal(s.git.changedFiles, 1); + }); +}); + +// =========================================================================== +// doctor.mjs (environment readiness) +// =========================================================================== + +function fakeExec(map) { + // map: cmd -> { stdout?, stderr?, error? } (missing cmd => ENOENT) + return async (cmd) => { + const r = map[cmd]; + if (!r) return { error: Object.assign(new Error("not found"), { code: "ENOENT" }), stdout: "", stderr: "" }; + return { error: r.error || null, stdout: r.stdout || "", stderr: r.stderr || "" }; + }; +} + +test("doctor: parseToolVersion handles java (stderr), node, and generic output", () => { + assert.equal(parseToolVersion("java", 'openjdk version "17.0.2" 2022-01-18'), "17.0.2"); + assert.equal(parseToolVersion("node", "v22.3.0"), "22.3.0"); + assert.equal(parseToolVersion("git", "git version 2.39.5"), "2.39.5"); + assert.equal(parseToolVersion("x", ""), null); +}); + +test("doctor: probeOne reports not-found on ENOENT and found otherwise", async () => { + const exec = fakeExec({ java: { stderr: 'openjdk version "21.0.1"' } }); + const found = await probeOne({ key: "java", cmd: "java", args: ["-version"] }, exec); + assert.deepEqual(found, { key: "java", found: true, version: "21.0.1" }); + const missing = await probeOne({ key: "mvn", cmd: "mvn", args: ["-v"] }, exec); + assert.equal(missing.found, false); +}); + +test("doctor: probeOne rejects the macOS java stub (non-zero exit, no version)", async () => { + const stub = async () => ({ + error: Object.assign(new Error("exit 1"), { code: 1 }), + stdout: "", + stderr: "The operation couldn't be completed. Unable to locate a Java Runtime.", + }); + const r = await probeOne({ key: "java", cmd: "java", args: ["-version"] }, stub); + assert.equal(r.found, false, "stub with no real version is treated as not installed"); +}); + +test("doctor: missing JDK + missing Maven (no wrapper) blocks readiness", () => { + const rep = buildDoctorReport({ + probes: { git: { found: true, version: "2.39" } }, + scan: { ok: true, assessment: { buildTool: "Maven", hasMavenWrapper: false }, git: { branch: "main" }, plan: {}, progress: {} }, + }); + assert.equal(rep.overall, "blocked"); + const checks = rep.groups.flatMap((g) => g.checks); + const jdk = checks.find((c) => c.id === "jdk"); + assert.equal(jdk.status, "fail"); + assert.ok(jdk.action && jdk.action.kind === "fix_env", "JDK failure offers a fix_env action"); + const build = checks.find((c) => c.id === "build"); + assert.equal(build.status, "fail"); +}); + +test("doctor: a Maven wrapper satisfies the build-tool check without mvn installed", () => { + const rep = buildDoctorReport({ + probes: { java: { found: true, version: "17" }, git: { found: true } }, + scan: { ok: true, assessment: { buildTool: "Maven", hasMavenWrapper: true }, git: { branch: "x" }, progress: { exists: true } }, + }); + const build = rep.groups.flatMap((g) => g.checks).find((c) => c.id === "build"); + assert.equal(build.status, "ok"); + assert.match(build.detail, /wrapper/i); +}); + +test("doctor: a Dockerfile without Docker is a caution (warn), not a blocker", () => { + const rep = buildDoctorReport({ + probes: { java: { found: true }, mvn: { found: true }, git: { found: true } }, + scan: { ok: true, assessment: { buildTool: "Maven", hasDockerfile: true }, git: { branch: "x" }, progress: { exists: true } }, + }); + const docker = rep.groups.flatMap((g) => g.checks).find((c) => c.id === "docker"); + assert.equal(docker.status, "warn"); + assert.equal(rep.overall, "caution"); +}); + +test("doctor: no assessment yet surfaces a start_assessment action", () => { + const rep = buildDoctorReport({ + probes: { java: { found: true }, mvn: { found: true }, git: { found: true } }, + scan: { ok: true, assessment: { buildTool: "Maven" }, git: { branch: "x" }, plan: { exists: false }, progress: { exists: false } }, + }); + const assessed = rep.groups.flatMap((g) => g.checks).find((c) => c.id === "assessed"); + assert.equal(assessed.status, "warn"); + assert.equal(assessed.action.kind, "start_assessment"); +}); + +test("doctor: everything present is ready", () => { + const rep = buildDoctorReport({ + probes: { + java: { found: true, version: "21" }, mvn: { found: true }, git: { found: true }, + docker: { found: true }, az: { found: true }, azd: { found: true }, node: { found: true }, + }, + scan: { ok: true, assessment: { buildTool: "Maven" }, git: { branch: "modernize", isMigrationBranch: true }, progress: { exists: true }, skills: [{ folder: "a" }] }, + }); + assert.equal(rep.overall, "ready"); +}); + +test("doctor: runDoctor probes every tool via the injected exec", async () => { + const exec = fakeExec({ + java: { stderr: 'openjdk version "17.0.9"' }, + git: { stdout: "git version 2.40.0" }, + }); + const rep = await runDoctor( + { ok: true, assessment: { buildTool: "Maven" }, git: { branch: "x" }, progress: { exists: true } }, + { exec } + ); + assert.ok(rep.groups.length >= 3); + assert.equal(TOOL_PROBES.length, 8); +}); + +test("doctor: augmentedEnv covers Homebrew, keg JDKs, SDKMAN, and JAVA_HOME (macOS)", () => { + // GUI apps inherit a minimal PATH; the doctor must still find tools the user's + // terminal sees. Inject a directory lister so the test doesn't hit the filesystem. + const lister = (d) => (d === "/opt/homebrew/opt" ? ["openjdk@21", "openjdk", "maven", "node@20"] : []); + const env = augmentedEnv({ PATH: "/usr/bin:/bin", HOME: "/Users/x", JAVA_HOME: "/jh" }, "darwin", lister); + const parts = env.PATH.split(":"); + assert.ok(parts.includes("/opt/homebrew/bin"), "Homebrew bin"); + assert.ok(parts.includes("/jh/bin"), "JAVA_HOME/bin"); + assert.ok(parts.includes("/Users/x/.sdkman/candidates/java/current/bin"), "SDKMAN"); + assert.ok(parts.includes("/opt/homebrew/opt/openjdk@21/libexec/openjdk.jdk/Contents/Home/bin"), "versioned keg"); + assert.ok(parts.includes("/opt/homebrew/opt/openjdk/libexec/openjdk.jdk/Contents/Home/bin"), "unversioned keg"); + assert.ok(!parts.some((p) => /\/maven\/libexec\/openjdk/.test(p)), "non-JDK kegs are skipped"); + assert.ok(env.PATH.endsWith("/usr/bin:/bin"), "original PATH preserved at the tail"); +}); + +test("doctor: augmentedEnv is a no-op on Windows and tolerates a bare env", () => { + assert.equal(augmentedEnv({ PATH: "C:\\Windows" }, "win32", () => []).PATH, "C:\\Windows"); + // No HOME/JAVA_HOME/PATH provided — must not throw and still prepend brew bins. + const env = augmentedEnv({}, "darwin", () => []); + assert.ok(env.PATH.split(":").includes("/opt/homebrew/bin")); +}); + +test("doctor: discoverJavaHome respects an explicit JAVA_HOME and skips rc files", async () => { + let read = false; + const readRc = async () => { read = true; return ""; }; + const jh = await discoverJavaHome({ JAVA_HOME: "/explicit/jdk" }, "darwin", readRc); + assert.equal(jh, "/explicit/jdk"); + assert.equal(read, false, "must not read rc files when JAVA_HOME is already set"); +}); + +test("doctor: discoverJavaHome statically parses a literal JAVA_HOME from the rc (no shell)", async () => { + const reads = []; + const readRc = async (p) => { + reads.push(p); + if (/\.zshrc$/.test(p)) { + return 'export PATH="$HOME/bin:$PATH"\nexport JAVA_HOME="$HOME/.sdkman/candidates/java/current"\n'; + } + return null; + }; + const jh = await discoverJavaHome({ HOME: "/Users/x", SHELL: "/bin/zsh" }, "darwin", readRc); + assert.equal(jh, "/Users/x/.sdkman/candidates/java/current", "expands $HOME, no execution"); + assert.ok(reads.some((p) => /\.zshrc$/.test(p)), "reads the zsh rc files"); +}); + +test("doctor: discoverJavaHome returns null on Windows, no match, unresolved subst, or read error", async () => { + assert.equal(await discoverJavaHome({ HOME: "/h" }, "win32", async () => "export JAVA_HOME=/x"), null); + assert.equal( + await discoverJavaHome({ HOME: "/h", SHELL: "/bin/zsh" }, "darwin", async () => "# nothing here"), + null + ); + // Command substitution can't be resolved statically -> null (falls back to PATH probing). + assert.equal( + await discoverJavaHome({ HOME: "/h", SHELL: "/bin/zsh" }, "darwin", async () => + "export JAVA_HOME=$(/usr/libexec/java_home -v 21)" + ), + null + ); + assert.equal( + await discoverJavaHome({ HOME: "/h", SHELL: "/bin/zsh" }, "darwin", async () => { throw new Error("boom"); }), + null + ); +}); + +test("doctor: parseJavaHomeFromRc handles quotes, comments, ~ and last-wins", () => { + assert.equal(parseJavaHomeFromRc("export JAVA_HOME=/opt/jdk17 # pinned", {}), "/opt/jdk17"); + assert.equal(parseJavaHomeFromRc("JAVA_HOME='/opt/jdk21'", {}), "/opt/jdk21"); + assert.equal(parseJavaHomeFromRc("export JAVA_HOME=~/jdks/21", { HOME: "/Users/x" }), "/Users/x/jdks/21"); + assert.equal( + parseJavaHomeFromRc("export JAVA_HOME=/a\nexport JAVA_HOME=/b", {}), + "/b", + "later assignment wins" + ); + assert.equal(parseJavaHomeFromRc("export NOTJAVA=/x", {}), null); +}); + +test("prompts: fix_env requires a tool and embeds the readiness detail", () => { + const p = buildPrompt("fix_env", { tool: "a JDK", detail: "java not found", fix: "Install Temurin" }, "/r"); + assert.match(p, /a JDK/); + assert.match(p, /java not found/); + assert.match(p, /do not modify my application code/i); + assert.equal(buildPrompt("fix_env", {}, "/r"), null); +}); + +test("scan: detects Maven/Gradle wrappers for the doctor", async () => { + await withRepo({ "pom.xml": "", "mvnw": "#!/bin/sh\n" }, async (dir) => { + const s = await scanRepo(dir, { includeGit: false }); + assert.equal(s.assessment.hasMavenWrapper, true); + assert.equal(s.assessment.hasGradleWrapper, false); + }); +}); + +test("scan: detects Windows wrapper scripts (mvnw.cmd / gradlew.bat)", async () => { + await withRepo({ "pom.xml": "", "mvnw.cmd": "@echo off\n" }, async (dir) => { + const s = await scanRepo(dir, { includeGit: false }); + assert.equal(s.assessment.hasMavenWrapper, true, "mvnw.cmd counts as a wrapper"); + }); + await withRepo({ "build.gradle": "plugins {}", "gradlew.bat": "@echo off\n" }, async (dir) => { + const s = await scanRepo(dir, { includeGit: false }); + assert.equal(s.assessment.hasGradleWrapper, true, "gradlew.bat counts as a wrapper"); + }); +}); + +test("scan: Dockerfile detection is case-insensitive and accepts Containerfile", async () => { + await withRepo({ "dockerfile": "FROM tomcat:9" }, async (dir) => { + const s = await scanRepo(dir, { includeGit: false }); + assert.equal(s.assessment.hasDockerfile, true, "lowercase dockerfile"); + }); + await withRepo({ "Containerfile": "FROM tomcat:9" }, async (dir) => { + const s = await scanRepo(dir, { includeGit: false }); + assert.equal(s.assessment.hasDockerfile, true, "Podman Containerfile"); + }); +}); + +test("scan: multi-module repo unions Java version + deps across modules (not just the first)", async () => { + await withRepo( + { + "pom.xml": "pomapiworker", + "api/pom.xml": "17", + "worker/pom.xml": + "com.rabbitmqamqp-client", + }, + async (dir) => { + const s = await scanRepo(dir, { includeGit: false }); + assert.equal(s.assessment.buildTool, "Maven"); + assert.equal(s.assessment.javaVersion, "17", "Java version comes from a module, not the aggregator"); + assert.ok( + s.assessment.detectedKeys.includes("rabbitmq"), + "a dependency declared only in a second module is still detected" + ); + } + ); +}); + +test("scan: dependency matching no longer fires on English words containing a token", async () => { + // "important"/"constant" contain "ant"; a plain substring match used to flag Ant. + await withRepo( + { + "pom.xml": + "An important and constant service", + }, + async (dir) => { + const s = await scanRepo(dir, { includeGit: false }); + assert.ok(!s.assessment.detectedKeys.includes("ant"), "no false Ant detection"); + } + ); + // A real Ant build is still flagged, via the build tool. + await withRepo({ "build.xml": "" }, async (dir) => { + const s = await scanRepo(dir, { includeGit: false }); + assert.equal(s.assessment.buildTool, "Ant"); + assert.ok(s.assessment.detectedKeys.includes("ant"), "genuine Ant build is detected"); + }); +}); + +test("scan: a real coordinate token is still matched with boundary-aware logic", async () => { + await withRepo( + { + "pom.xml": + "aws-java-sdk-s3", + }, + async (dir) => { + const s = await scanRepo(dir, { includeGit: false }); + assert.ok(s.assessment.detectedKeys.includes("awsS3")); + } + ); +}); + +test("scan: assessment.json is normalized (severity aliases, files string, bad shapes)", async () => { + const raw = { + findings: [ + { id: "a", severity: "critical", title: "x", files: "only/one.txt" }, + { id: "b", severity: "high", title: "y" }, + { id: "c", severity: "weird", title: "z", files: ["k.txt", 5] }, + "not-an-object", + { severity: "low" }, + ], + strengths: "single strength", + }; + await withRepo({ ".appmod/assessment.json": JSON.stringify(raw) }, async (dir) => { + const s = await scanRepo(dir, { includeGit: false }); + const f = s.report.findings; + assert.equal(f.length, 4, "non-object findings are dropped, valid ones kept"); + assert.equal(f[0].severity, "P0", "critical -> P0"); + assert.deepEqual(f[0].files, ["only/one.txt"], "string files -> array"); + assert.equal(f[1].severity, "P1", "high -> P1"); + assert.equal(f[2].severity, "P3", "unknown severity -> P3 (kept, not dropped)"); + assert.deepEqual(f[2].files, ["k.txt"], "non-string file entries filtered out"); + assert.equal(f[3].title, "Untitled finding", "missing title gets a fallback"); + assert.deepEqual(s.report.strengths, ["single strength"], "string strengths -> array"); + }); +}); + +test("scan: a non-object assessment.json yields a null report (no crash)", async () => { + await withRepo({ ".appmod/assessment.json": "42" }, async (dir) => { + const s = await scanRepo(dir, { includeGit: false }); + assert.equal(s.report, null); + }); + await withRepo({ ".appmod/assessment.json": "[1,2,3]" }, async (dir) => { + const s = await scanRepo(dir, { includeGit: false }); + assert.equal(s.report, null, "a top-level array is not a valid report"); + }); +}); + + + +test("catalog: has 16 well-formed tasks", () => { + assert.equal(PREDEFINED_TASKS.length, 16); + for (const t of PREDEFINED_TASKS) { + assert.ok(t.id && t.name && t.category && t.summary, "task fields present: " + t.id); + assert.ok(Array.isArray(t.detect)); + } + const ids = new Set(PREDEFINED_TASKS.map((t) => t.id)); + assert.equal(ids.size, PREDEFINED_TASKS.length, "ids are unique"); +}); + +test("catalog: catalogWithRelevance flags only matching detect keys", () => { + const withRel = catalogWithRelevance(["rabbitmq"]); + assert.equal(withRel.find((t) => t.id === "rabbitmq-to-servicebus").relevant, true); + assert.equal(withRel.find((t) => t.id === "aws-s3-to-blob").relevant, false); +}); + +// =========================================================================== +// prompts.mjs +// =========================================================================== + +test("prompts: every labelled action builds a non-empty prompt", () => { + for (const kind of Object.keys(ACTION_LABELS)) { + const payload = + kind === "run_task" + ? { taskId: PREDEFINED_TASKS[0].id } + : kind === "run_skill" + ? { folder: "x" } + : kind === "fix_finding" + ? { title: "Some finding" } + : kind === "work_step" + ? { title: "Some step" } + : kind === "fix_env" + ? { tool: "a JDK" } + : {}; + const p = buildPrompt(kind, payload, "/repo"); + assert.ok(typeof p === "string" && p.length > 0, "prompt for " + kind); + } +}); + +test("prompts: run_task unknown id returns null", () => { + assert.equal(buildPrompt("run_task", { taskId: "nope" }, "/repo"), null); +}); + +test("prompts: run_task known id embeds the task name", () => { + const p = buildPrompt("run_task", { taskId: "aws-s3-to-blob" }, "/repo"); + assert.match(p, /AWS S3 to Azure Storage Blob/); +}); + +test("prompts: assessment embeds repo path; generate_plan embeds target", () => { + assert.match(buildPrompt("start_assessment", {}, "/my/repo"), /\/my\/repo/); + assert.match(buildPrompt("generate_plan", { targetJava: 21 }, "/r"), /Java 21/); +}); + +test("prompts: appmod tools referenced by hash command", () => { + assert.match(buildPrompt("run_cve", {}, "/r"), /#appmod-validate-cves-for-java/); + assert.match(buildPrompt("generate_tests", {}, "/r"), /#appmod-generate-tests-for-java/); +}); + +test("prompts: unknown kind returns null", () => { + assert.equal(buildPrompt("bogus", {}, "/r"), null); +}); + +test("prompts: fix_finding embeds the finding title/detail and needs a title", () => { + const p = buildPrompt("fix_finding", { title: "TLS off", detail: "encrypt=false", files: ["db.properties"], severity: "P1" }, "/r"); + assert.match(p, /TLS off/); + assert.match(p, /encrypt=false/); + assert.match(p, /db\.properties/); + assert.equal(buildPrompt("fix_finding", {}, "/r"), null, "no title -> null"); +}); + +test("prompts: start_assessment instructs writing structured assessment.json", () => { + const p = buildPrompt("start_assessment", {}, "/my/repo"); + assert.match(p, /\.appmod\/assessment\.json/); + assert.match(p, /findings/); + assert.match(p, /P0\|P1\|P2\|P3/); +}); + +test("prompts: fix_finding has a label in ACTION_LABELS", () => { + assert.ok(ACTION_LABELS.fix_finding, "fix_finding should be labelled"); +}); + +test("prompts: work_step embeds the step title + phase and requires a title", () => { + const p = buildPrompt("work_step", { title: "Add ingress TLS", section: "P1 — Secrets & identity" }, "/r"); + assert.match(p, /Add ingress TLS/); + assert.match(p, /P1 — Secrets & identity/); + assert.match(p, /progress\.md/); + assert.equal(buildPrompt("work_step", {}, "/r"), null, "no title -> null"); +}); + +// =========================================================================== +// renderer.mjs +// =========================================================================== + +test("renderer: returns full HTML doc with app root and boot blob", () => { + const html = renderHtml({ instanceId: "abc", initialTab: "plan" }); + assert.match(html, /^/); + assert.match(html, /id="app"/); + assert.match(html, /Java Modernization Studio/); + assert.match(html, /"initialTab":"plan"/); + assert.match(html, /"instanceId":"abc"/); +}); + +test("renderer: defaults initialTab to overview", () => { + const html = renderHtml({ instanceId: "x" }); + assert.match(html, /"initialTab":"overview"/); +}); + +test("renderer: button hover is contrast-safe (no inverted emphasis background)", () => { + const html = renderHtml({ instanceId: "css" }); + const css = html.slice(html.indexOf("")); + // Regression: a `:hover` must never swap a dark-text button onto the dark + // `--background-color-emphasis` surface (that made hovered text unreadable). + assert.ok(!/:hover[^}]*background-color-emphasis/.test(css), "no emphasis background on hover"); + // The default (non-primary) button keeps a readable surface on hover. + assert.match(css, /button\.btn:not\(\.primary\):not\(:disabled\):hover/); + // Keyboard users get a visible focus ring. + assert.match(css, /button\.btn:focus-visible/); +}); + +test("renderer: boot blob escapes < so it cannot break out of ")[0]; + const slots = {}; + const make = (id) => ({ + _html: "", id, + set innerHTML(v) { this._html = v; }, + get innerHTML() { return this._html; }, + addEventListener() {}, querySelectorAll() { return []; }, querySelector() { return null; }, + classList: { add() {}, remove() {}, toggle() {} }, setAttribute() {}, style: {}, dataset: {}, textContent: "", + }); + const win = { __APPMOD__: { instanceId: "exec", initialTab }, addEventListener() {}, location: {}, scrollTo() {} }; + const ES = class { constructor() {} addEventListener() {} close() {} }; + const doc = { + getElementById: (id) => { if (!slots[id]) slots[id] = make(id); return slots[id]; }, + querySelectorAll: () => [], + querySelector: (s) => { if (typeof s === "string" && s[0] === "#") { const id = s.slice(1); if (!slots[id]) slots[id] = make(id); return slots[id]; } return null; }, + addEventListener() {}, createElement: () => make("x"), body: make("body"), + }; + const fetchShim = async () => ({ ok: true, json: async () => state }); + // Inject our shims as the only free identifiers the client uses, so the body + // runs against them rather than this module's lexical scope. + const fn = new Function("window", "document", "EventSource", "fetch", "setTimeout", "clearTimeout", "console", script); + fn(win, doc, ES, fetchShim, setTimeout, clearTimeout, console); + // The client renders once synchronously (Loading), then loadState() fetches + // and re-renders async — let those microtasks settle before reading #app. + await new Promise((r) => setTimeout(r, 0)); + await new Promise((r) => setTimeout(r, 0)); + return { app: () => (slots.app ? slots.app._html : "") }; +} + +// A realistic snapshot with a multi-phase plan, an assessment report, gates, an +// environment report, and no autopilot run — exercises the common render paths. +function sampleState(extra = {}) { + const steps = [ + { title: "Fix the build", section: "P0 Build", status: "in_progress", rank: 1 }, + { title: "Patch CVEs", section: "P1 Security", status: "pending", rank: 2 }, + { title: "Upgrade Java", section: "P2 Runtime", status: "pending", rank: 3 }, + ]; + return { + ok: true, repoPath: "/demo/app", scannedAt: new Date().toISOString(), + status: "in_progress", percent: 10, + assessment: { buildTool: "Maven", javaVersion: "8", springBoot: true, hasDockerfile: true, detectedKeys: [] }, + tasks: [], skills: [], + git: { branch: "modernize/java21", isMigrationBranch: true, dirty: true, changedFiles: 3 }, + plan: { exists: true, steps }, progress: { exists: true, steps }, + summary: { exists: false, markdown: "" }, + report: { + generatedAt: new Date().toISOString(), headline: "Java 8 Spring app", summary: "Outdated runtime and deps.", + stack: { buildTool: "Maven", java: "8", framework: "Spring Boot", database: "MSSQL", container: "Docker" }, + findings: [ + { id: "java8", severity: "P0", title: "Java 8 is EOL", detail: "Upgrade the runtime.", files: ["pom.xml"], action: { kind: "generate_plan", payload: { targetJava: 21 }, label: "Plan upgrade" } }, + { id: "cve", severity: "P1", title: "Vulnerable dependency", detail: "Bump it.", files: [], action: { kind: "fix_finding", payload: {}, label: "Fix" } }, + ], + strengths: ["Has a Dockerfile"], + }, + ordering: { activeRank: 1, activePhase: "P0 Build", phases: [{ name: "P0 Build", rank: 1, done: 0, total: 1 }] }, + gates: { build: "not_run", tests: "not_run", cve: "not_run", consistency: "not_run", completeness: "not_run" }, + doctor: { overall: "ready", generatedAt: new Date().toISOString(), groups: [ + { id: "build", name: "Build & run", checks: [{ id: "jdk", label: "JDK", status: "ok", detail: "Java 21" }] }, + ] }, + autopilot: null, + ...extra, + }; +} + +test("renderer: every tab renders without throwing against a realistic state", async () => { + const tabs = ["overview", "readiness", "assessment", "plan", "validation", "tasks", "summary"]; + for (const tab of tabs) { + const r = await renderClientTab(sampleState(), tab); + const out = r.app(); + assert.ok(out && out.length > 40, "tab '" + tab + "' produced no body"); + assert.ok(!/hit an error/.test(out), "tab '" + tab + "' fell back to the error banner"); + } +}); + +test("renderer: Plan tab actually renders its checklist when executed (regression)", async () => { + const r = await renderClientTab(sampleState(), "plan"); + const out = r.app(); + assert.match(out, /Plan & Progress/); + assert.match(out, /Fix the build/); + assert.match(out, /data-kind="work_step"/); + assert.match(out, /Autopilot/); + assert.ok(!/hit an error/.test(out)); +}); + +test("renderer: a running autopilot shows the live strip and freezes manual actions", async () => { + const state = sampleState({ + autopilot: { running: true, scope: "phase", status: "running", current: { title: "Fix the build", section: "P0 Build" }, completed: [{ title: "Earlier", section: "P0 Build", done: true }], maxSteps: 25 }, + }); + const r = await renderClientTab(state, "plan"); + const out = r.app(); + assert.match(out, /Autopilot running/); + assert.match(out, /autopilot_stop/); + assert.match(out, /Earlier/); // step log + assert.match(out, /data-kind="work_step"[^>]*disabled/); // manual buttons frozen +}); + +// Drive the live client: capture its click handler + EventSource so a test can +// fire a real button click and simulate an SSE (re)connect, then read #app. +// fetch routes GET /state -> current snapshot, POST /action -> a scripted result. +function driveClient(initialState, opts = {}) { + const initialTab = opts.initialTab || "readiness"; + const html = renderHtml({ instanceId: "drive", initialTab }); + const script = html.split("")[0]; + const slots = {}; + const make = (id) => ({ + _html: "", id, + set innerHTML(v) { this._html = v; }, get innerHTML() { return this._html; }, + addEventListener() {}, querySelectorAll() { return []; }, querySelector() { return null; }, + classList: { add() {}, remove() {}, toggle() {} }, setAttribute() {}, style: {}, dataset: {}, textContent: "", + }); + let clickHandler = null; + const es = { onopen: null, onmessage: null, onerror: null, addEventListener() {}, close() {} }; + const win = { __APPMOD__: { instanceId: "drive", initialTab }, addEventListener() {}, location: {}, scrollTo() {} }; + const ES = function () { return es; }; // `new ES()` yields our singleton + const doc = { + getElementById: (id) => { if (!slots[id]) slots[id] = make(id); return slots[id]; }, + querySelectorAll: () => [], + querySelector: (s) => { if (typeof s === "string" && s[0] === "#") { const id = s.slice(1); if (!slots[id]) slots[id] = make(id); return slots[id]; } return null; }, + addEventListener: (type, fn) => { if (type === "click") clickHandler = fn; }, + createElement: () => make("x"), body: make("body"), + }; + let getState = initialState; + let getStateCalls = 0; + const fetchShim = async (url, init) => { + if (init && init.method === "POST") return { ok: true, json: async () => (opts.actionResponse || { ok: true, message: "ok" }) }; + getStateCalls++; + return { ok: true, json: async () => getState }; + }; + new Function("window", "document", "EventSource", "fetch", "setTimeout", "clearTimeout", "console", script)(win, doc, ES, fetchShim, setTimeout, clearTimeout, console); + const settle = async () => { await new Promise((r) => setTimeout(r, 0)); await new Promise((r) => setTimeout(r, 0)); }; + return { + async ready() { await settle(); return this; }, + app: () => (slots.app ? slots.app._html : ""), + getStateCalls: () => getStateCalls, + setGetState(s) { getState = s; }, + async clickKind(kind, payload) { + const btnEl = { disabled: false, dataset: { kind, payload: JSON.stringify(payload || {}) } }; + const ev = { target: { closest: (sel) => (sel.indexOf("nav.tabs") === 0 ? null : btnEl) } }; + clickHandler(ev); + await settle(); + }, + async reconnect() { if (es.onopen) es.onopen(); await settle(); }, + }; +} + +test("renderer: recheck_env applies the inline result and never strands the spinner", async () => { + // Backend returns a fresh, now-ready snapshot inline (res.state) — the client + // must apply it directly, not fall back to the agent-style 'pending' banner + // that only clears on an SSE broadcast (which can be lost on a provider restart). + const blocked = sampleState({ doctor: { overall: "blocked", generatedAt: "t", groups: [{ id: "build", name: "Build", checks: [{ id: "jdk", label: "JDK", status: "fail", detail: "java not found" }] }] } }); + const ready = sampleState({ doctor: { overall: "ready", generatedAt: "t2", groups: [{ id: "build", name: "Build", checks: [{ id: "jdk", label: "JDK", status: "ok", detail: "Java 21" }] }] } }); + const c = await driveClient(blocked, { initialTab: "readiness", actionResponse: { ok: true, message: "Re-checked environment", state: ready } }).ready(); + assert.match(c.app(), /Not ready yet/); // starts blocked + await c.clickKind("recheck_env", {}); + assert.match(c.app(), /Your environment is ready/); // inline state applied + assert.ok(!/Watch the chat/.test(c.app()), "must not show the agent 'pending' spinner banner"); + assert.equal(c.getStateCalls(), 1, "recheck must not need an extra /state fetch (uses inline state)"); +}); + +test("renderer: an SSE reconnect resyncs state (recovers a missed broadcast)", async () => { + const blocked = sampleState({ doctor: { overall: "blocked", generatedAt: "t", groups: [{ id: "build", name: "Build", checks: [{ id: "jdk", label: "JDK", status: "fail", detail: "x" }] }] } }); + const ready = sampleState({ doctor: { overall: "ready", generatedAt: "t2", groups: [{ id: "build", name: "Build", checks: [{ id: "jdk", label: "JDK", status: "ok", detail: "Java 21" }] }] } }); + const c = await driveClient(blocked, { initialTab: "readiness" }).ready(); + const before = c.getStateCalls(); + assert.match(c.app(), /Not ready yet/); + // Provider finished the recheck after a restart that dropped the broadcast; + // backend state is now ready. The stream reconnects -> client must re-fetch. + c.setGetState(ready); + await c.reconnect(); + assert.equal(c.getStateCalls(), before + 1, "onopen must re-fetch /state"); + assert.match(c.app(), /Your environment is ready/); +}); + +// =========================================================================== +// server.mjs +// =========================================================================== + +test("server: resolveRepoPath precedence input > session > last, else null", () => { + assert.equal( + resolveRepoPath({ input: { repoPath: "/in" }, session: { workingDirectory: "/s" } }, "/last"), + "/in" + ); + assert.equal(resolveRepoPath({ session: { workingDirectory: "/s" } }, "/last"), "/s"); + assert.equal(resolveRepoPath({}, "/last"), "/last"); + // No process.cwd() fallback — unresolved means null (UI shows "repo not available"). + assert.equal(resolveRepoPath({}, null), null); + assert.equal(resolveRepoPath({}, undefined), null); +}); + +test("server: broadcast writes SSE frame to every client", () => { + const chunks = []; + const rec = { sseClients: new Set([{ write: (s) => chunks.push(s) }]) }; + broadcast(rec, { type: "state", state: { ok: true } }); + assert.equal(chunks.length, 1); + assert.match(chunks[0], /^data: /); + assert.match(chunks[0], /"type":"state"/); +}); + +test("server: pushState scans repo and broadcasts snapshot", async () => { + await withRepo({ "pom.xml": "" }, async (dir) => { + const chunks = []; + const rec = { repoPath: dir, sseClients: new Set([{ write: (s) => chunks.push(s) }]) }; + const state = await pushState(rec); + assert.equal(state.ok, true); + assert.equal(chunks.length, 1); + assert.match(chunks[0], /"buildTool":"Maven"/); + }); +}); + +test("server: dispatch refresh re-scans without sending a prompt", async () => { + await withRepo({ "pom.xml": "" }, async (dir) => { + let sent = 0; + const rec = { repoPath: dir, sseClients: new Set() }; + const res = await dispatchAction(rec, "refresh", {}, { sendPrompt: async () => sent++ }); + assert.equal(res.ok, true); + assert.equal(sent, 0); + }); +}); + +test("server: dispatch agent action forwards crafted prompt to sendPrompt", async () => { + const rec = { repoPath: "/repo", sseClients: new Set() }; + const sentPrompts = []; + const res = await dispatchAction(rec, "run_cve", {}, { sendPrompt: async (p) => sentPrompts.push(p) }); + assert.equal(res.ok, true); + assert.equal(res.label, "CVE scan"); + assert.equal(sentPrompts.length, 1); + assert.match(sentPrompts[0], /#appmod-validate-cves-for-java/); +}); + +test("server: dispatch unknown action returns error, no send", async () => { + const rec = { repoPath: "/repo", sseClients: new Set() }; + let sent = 0; + const res = await dispatchAction(rec, "bogus", {}, { sendPrompt: async () => sent++ }); + assert.equal(res.ok, false); + assert.match(res.error, /Unknown action/); + assert.equal(sent, 0); +}); + +test("server: dispatch without sendPrompt reports session not ready", async () => { + const rec = { repoPath: "/repo", sseClients: new Set() }; + const res = await dispatchAction(rec, "run_cve", {}, {}); + assert.equal(res.ok, false); + assert.match(res.error, /not ready/); +}); + +test("server: handler GET / serves HTML", async () => { + const rec = { repoPath: "/repo", sseClients: new Set() }; + const handler = makeHandler(rec, { instanceId: "i", initialTab: "overview", sendPrompt: async () => {} }); + const { res } = await invokeGet(handler, "/"); + assert.match(res.headers["content-type"], /text\/html/); + assert.match(res.body, /Java Modernization Studio/); +}); + +test("server: handler GET /state serves scanned JSON", async () => { + await withRepo({ "pom.xml": "" }, async (dir) => { + const rec = { repoPath: dir, sseClients: new Set() }; + const handler = makeHandler(rec, { instanceId: "i", sendPrompt: async () => {} }); + const { res } = await invokeGet(handler, "/state"); + const json = JSON.parse(res.body); + assert.equal(json.ok, true); + assert.equal(json.repoPath, dir); + }); +}); + +test("server: handler GET /events registers and cleans up SSE client", async () => { + const rec = { repoPath: "/repo", sseClients: new Set() }; + const handler = makeHandler(rec, { instanceId: "i", sendPrompt: async () => {} }); + const { req } = await invokeGet(handler, "/events"); + assert.equal(rec.sseClients.size, 1); + req.emit("close"); + assert.equal(rec.sseClients.size, 0); +}); + +test("server: handler POST /action runs dispatch", async () => { + const rec = { repoPath: "/repo", sseClients: new Set() }; + const sent = []; + const handler = makeHandler(rec, { instanceId: "i", sendPrompt: async (p) => sent.push(p) }); + const res = await invokePost(handler, "/action", { kind: "run_cve" }); + const json = JSON.parse(res.body); + assert.equal(json.ok, true); + assert.equal(sent.length, 1); +}); + +test("server: token guard forbids requests without the per-instance token", async () => { + const rec = { repoPath: "/repo", sseClients: new Set(), token: "s3cret" }; + const sent = []; + const handler = makeHandler(rec, { instanceId: "i", sendPrompt: async (p) => sent.push(p) }); + for (const path of ["/", "/state", "/events"]) { + const { res } = await invokeGet(handler, path); + assert.equal(res.statusCode, 403, path + " without a token must be forbidden"); + } + const post = await invokePost(handler, "/action", { kind: "run_cve" }); + assert.equal(post.statusCode, 403, "/action without a token must be forbidden"); + assert.equal(sent.length, 0, "no action is dispatched for an unauthenticated request"); + assert.equal(rec.sseClients.size, 0, "no SSE client is registered for an unauthenticated request"); +}); + +test("server: token guard allows requests carrying the correct token", async () => { + const rec = { repoPath: "/repo", sseClients: new Set(), token: "s3cret" }; + const sent = []; + const handler = makeHandler(rec, { instanceId: "i", sendPrompt: async (p) => sent.push(p) }); + const { req } = await invokeGet(handler, "/events?token=s3cret"); + assert.equal(rec.sseClients.size, 1, "a valid token registers the SSE client"); + req.emit("close"); + const post = await invokePost(handler, "/action?token=s3cret", { kind: "run_cve" }); + const json = JSON.parse(post.body); + assert.equal(json.ok, true); + assert.equal(sent.length, 1); +}); + +test("server: POST /action rejects an oversized request body (413)", async () => { + const rec = { repoPath: "/repo", sseClients: new Set() }; + const sent = []; + const handler = makeHandler(rec, { instanceId: "i", sendPrompt: async (p) => sent.push(p) }); + const req = new EventEmitter(); + req.method = "POST"; + req.url = "/action"; + const res = fakeRes(); + const pending = handler(req, res); + req.emit("data", "x".repeat(300 * 1024)); // > 256 KB cap + req.emit("end"); + await pending; + assert.equal(res.statusCode, 413); + const json = JSON.parse(res.body); + assert.equal(json.ok, false); + assert.match(json.error, /too large/i); + assert.equal(sent.length, 0, "no action is dispatched for a rejected body"); +}); + +test("server: handler unknown route 404s", async () => { + const rec = { repoPath: "/repo", sseClients: new Set() }; + const handler = makeHandler(rec, { instanceId: "i", sendPrompt: async () => {} }); + const { res } = await invokeGet(handler, "/nope"); + assert.equal(res.statusCode, 404); +}); + +test("server: broadcast drops a client whose write fails (no leak, no repeat throw)", () => { + const live = []; + const dead = { + write() { + throw new Error("EPIPE: client gone"); + }, + }; + const rec = { sseClients: new Set([dead, { write: (s) => live.push(s) }]) }; + broadcast(rec, { type: "state", state: { ok: true } }); + assert.ok(!rec.sseClients.has(dead), "a client whose write throws is removed from the Set"); + assert.equal(rec.sseClients.size, 1, "the healthy client is retained"); + assert.equal(live.length, 1, "the healthy client still receives the frame"); + // A later broadcast must not keep throwing now that the dead client is gone. + assert.doesNotThrow(() => broadcast(rec, { type: "state", state: { ok: true } })); + assert.equal(live.length, 2); +}); + +test("server: POST /action rejects malformed JSON with 400 and dispatches nothing", async () => { + const rec = { repoPath: "/repo", sseClients: new Set() }; + let sent = 0; + const handler = makeHandler(rec, { instanceId: "i", sendPrompt: async () => sent++ }); + const req = new EventEmitter(); + req.method = "POST"; + req.url = "/action"; + const res = fakeRes(); + const pending = handler(req, res); + req.emit("data", "{ not valid json "); + req.emit("end"); + await pending; + assert.equal(res.statusCode, 400); + assert.match(res.headers["content-type"], /application\/json/); + const json = JSON.parse(res.body); + assert.equal(json.ok, false); + assert.match(json.error, /invalid JSON/i); + assert.equal(sent, 0, "a malformed body dispatches no action"); +}); + +test("server: POST /action rejects a missing/invalid kind with 400", async () => { + const rec = { repoPath: "/repo", sseClients: new Set() }; + let sent = 0; + const handler = makeHandler(rec, { instanceId: "i", sendPrompt: async () => sent++ }); + const res = await invokePost(handler, "/action", { payload: {} }); // no kind + assert.equal(res.statusCode, 400); + assert.match(res.headers["content-type"], /application\/json/); + const json = JSON.parse(res.body); + assert.equal(json.ok, false); + assert.match(json.error, /kind/i); + assert.equal(sent, 0, "a kindless request dispatches no action"); +}); + +test("server: a handler exception returns 500 with a JSON content-type", async () => { + // A throwing repoPath getter makes buildState (GET /state) reject, exercising + // the handler's outer catch — which must still label the error body as JSON. + const rec = { + sseClients: new Set(), + get repoPath() { + throw new Error("boom"); + }, + }; + const handler = makeHandler(rec, { instanceId: "i", sendPrompt: async () => {} }); + const { res } = await invokeGet(handler, "/state"); + assert.equal(res.statusCode, 500); + assert.match(res.headers["content-type"], /application\/json/); + const json = JSON.parse(res.body); + assert.equal(json.ok, false); +}); + +test("server: recheck_env runs the doctor, caches it, and broadcasts (no agent prompt)", async () => { + await withRepo({ "pom.xml": "" }, async (dir) => { + let runs = 0; + const chunks = []; + const rec = { + repoPath: dir, + sseClients: new Set([{ write: (s) => chunks.push(s) }]), + doctor: null, + runDoctor: async () => ({ overall: "blocked", groups: [], runs: ++runs }), + }; + let sent = 0; + const res = await dispatchAction(rec, "recheck_env", {}, { sendPrompt: async () => sent++ }); + assert.equal(res.ok, true); + assert.equal(sent, 0, "recheck_env does not message the agent"); + assert.equal(rec.doctor.overall, "blocked"); + assert.equal(runs, 1); + assert.match(chunks[0], /"overall":"blocked"/); + }); +}); + +test("server: createInstanceServer binds a loopback socket end-to-end", async () => { + await withRepo({ "pom.xml": "" }, async (dir) => { + const sent = []; + const rec = await createInstanceServer({ + instanceId: "live", + repoPath: dir, + initialTab: "overview", + sendPrompt: async (p) => sent.push(p), + runDoctor: async () => ({ overall: "ready", groups: [] }), + }); + try { + assert.match(rec.url, /^http:\/\/127\.0\.0\.1:\d+\/\?token=[a-f0-9]+$/); + const u = new URL(rec.url); + const base = u.origin; + const q = "?token=" + u.searchParams.get("token"); + + const home = await fetch(rec.url); + assert.equal(home.status, 200); + assert.match(await home.text(), /Java Modernization Studio/); + + const state = await (await fetch(base + "/state" + q)).json(); + assert.equal(state.ok, true); + assert.equal(state.assessment.buildTool, "Maven"); + assert.equal(state.doctor.overall, "ready", "doctor report is attached to state"); + + const action = await fetch(base + "/action" + q, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ kind: "generate_tests" }), + }); + const aj = await action.json(); + assert.equal(aj.ok, true); + assert.equal(sent.length, 1); + + // A request that omits the per-instance token must be refused, even though + // it reached the right loopback port. + const unauth = await fetch(base + "/state"); + assert.equal(unauth.status, 403, "missing token is rejected at the socket"); + } finally { + await new Promise((r) => rec.server.close(() => r())); + } + }); +}); + +// ---- autopilot -------------------------------------------------------------- + +// Build an evolving plan model: snapshot() reflects current statuses and a fresh +// activeRank; markNextDone() simulates the agent checking a step off. +function planSim(steps) { + const model = steps.map((s) => ({ section: null, rank: null, status: "pending", ...s })); + const activeRank = () => { + const notDone = model.filter((s) => s.status !== "done" && s.rank != null); + return notDone.length ? Math.min(...notDone.map((s) => s.rank)) : null; + }; + return { + model, + snapshot: async () => ({ + ok: true, + progress: { steps: model.map((s) => ({ ...s })) }, + plan: { steps: model.map((s) => ({ ...s })) }, + ordering: { activeRank: activeRank() }, + }), + markNextDone() { + const notDone = model.find((s) => s.status !== "done"); + if (notDone) notDone.status = "done"; + }, + }; +} + +test("autopilot: selectNextStep follows phase order then falls back", () => { + const s = { + progress: { steps: [ + { title: "A", status: "done", rank: 1 }, + { title: "B", status: "pending", rank: 1 }, + { title: "C", status: "pending", rank: 2 }, + ] }, + ordering: { activeRank: 1 }, + }; + assert.equal(selectNextStep(s).title, "B", "picks the active-phase pending step"); + const s2 = { progress: { steps: [{ title: "X", status: "done", rank: 1 }] }, ordering: { activeRank: null } }; + assert.equal(selectNextStep(s2), null, "null when nothing is pending"); + assert.equal(selectNextStep({}), null, "null when there are no steps"); +}); + +test("autopilot: stepKey is stable and isStepDone reads status", () => { + const step = { title: "Upgrade", section: "P0 Build" }; + assert.equal(stepKey(step), "P0 Build::Upgrade"); + const state = { progress: { steps: [{ title: "Upgrade", section: "P0 Build", status: "done" }] } }; + assert.equal(isStepDone(state, step), true); + assert.equal(isStepDone({ progress: { steps: [] } }, step), false); +}); + +test("autopilot: auto_step prompt is autonomous and never commits", () => { + assert.equal(buildPrompt("auto_step", {}, "/repo"), null, "no title -> no prompt"); + const p = buildPrompt("auto_step", { title: "Bump Spring", section: "P1 Security" }, "/repo"); + assert.match(p, /AUTOPILOT/); + assert.match(p, /Bump Spring/); + assert.match(p, /Do NOT commit/); + assert.match(p, /one step/i); +}); + +test("autopilot: runs every step to completion (happy path)", async () => { + const sim = planSim([ + { title: "A", section: "P0 Build", rank: 1 }, + { title: "B", section: "P0 Build", rank: 1 }, + ]); + const run = makeRun({ scope: "all", maxSteps: 10, startRank: 1 }); + const prompts = []; + await runAutopilot(run, { + snapshot: sim.snapshot, + runTurn: async (p) => { prompts.push(p); sim.markNextDone(); }, + buildStepPrompt: (step) => step.title, + onProgress: () => {}, + }); + assert.equal(run.status, "completed"); + assert.equal(run.completed.length, 2); + assert.ok(run.completed.every((c) => c.done), "each step ended up checked off"); + assert.deepEqual(prompts, ["A", "B"]); + assert.equal(run.running, false); +}); + +test("autopilot: phase scope stops at the phase boundary", async () => { + const sim = planSim([ + { title: "A", section: "P0 Build", rank: 1 }, + { title: "B", section: "P1 Security", rank: 2 }, + ]); + const run = makeRun({ scope: "phase", maxSteps: 10, startRank: 1 }); + await runAutopilot(run, { + snapshot: sim.snapshot, + runTurn: async () => sim.markNextDone(), + buildStepPrompt: (step) => step.title, + onProgress: () => {}, + }); + assert.equal(run.status, "phase_done"); + assert.equal(run.completed.length, 1, "only the P0 step ran; it paused before P1"); + assert.equal(run.completed[0].title, "A"); +}); + +test("autopilot: stops (stuck) when a step does not get checked off", async () => { + const sim = planSim([{ title: "Tricky", section: "P0 Build", rank: 1 }]); + const run = makeRun({ scope: "all", maxSteps: 10, startRank: 1 }); + await runAutopilot(run, { + snapshot: sim.snapshot, + runTurn: async () => { /* agent fails to complete the step */ }, + buildStepPrompt: (step) => step.title, + onProgress: () => {}, + }); + assert.equal(run.status, "stuck"); + assert.equal(run.stuck, "Tricky"); + assert.equal(run.completed.length, 1); + assert.equal(run.completed[0].done, false); +}); + +test("autopilot: honors cancellation between steps", async () => { + const sim = planSim([ + { title: "A", section: "P0 Build", rank: 1 }, + { title: "B", section: "P0 Build", rank: 1 }, + ]); + const run = makeRun({ scope: "all", maxSteps: 10, startRank: 1 }); + await runAutopilot(run, { + snapshot: sim.snapshot, + runTurn: async () => sim.markNextDone(), + buildStepPrompt: (step) => step.title, + onProgress: (r) => { if (r.current) r.cancelled = true; }, + }); + assert.equal(run.status, "cancelled"); + assert.equal(run.completed.length, 1, "the in-flight step finished, then it stopped"); +}); + +test("server: autopilot_start refuses without a runTurn driver", async () => { + const rec = { repoPath: "/nope", sseClients: new Set(), doctor: null, autopilot: null }; + const res = await dispatchAction(rec, "autopilot_start", {}, { sendPrompt: async () => {}, log: () => {} }); + assert.equal(res.ok, false); + assert.match(res.error, /drive/i); + assert.equal(rec.autopilot, null); +}); + +test("server: autopilot_start is blocked when the environment is not ready", async () => { + await withRepo({ ...PROV, "pom.xml": "", "progress.md": "## P0 Build\n- [ ] Fix build\n" }, async (dir) => { + const rec = { + repoPath: dir, + sseClients: new Set(), + doctor: null, + autopilot: null, + runDoctor: async () => ({ overall: "blocked", groups: [] }), + }; + const res = await dispatchAction(rec, "autopilot_start", {}, { runTurn: async () => {}, log: () => {} }); + assert.equal(res.ok, false); + assert.match(res.error, /environment/i); + assert.equal(rec.autopilot, null, "no run starts while blocked"); + }); +}); + +test("server: autopilot_start drives the loop and broadcasts progress", async () => { + await withRepo({ ...PROV, "pom.xml": "", "progress.md": "## P0 Build\n- [ ] Fix the build\n" }, async (dir) => { + const chunks = []; + const rec = { + repoPath: dir, + sseClients: new Set([{ write: (s) => chunks.push(s) }]), + doctor: null, + autopilot: null, + runDoctor: async () => ({ overall: "ready", groups: [] }), + }; + let turns = 0; + const res = await dispatchAction(rec, "autopilot_start", { scope: "phase" }, { runTurn: async () => { turns++; }, log: () => {} }); + assert.equal(res.ok, true); + assert.ok(rec.autopilot, "a run record is attached"); + await rec.autopilotPromise; // let the loop settle + assert.equal(rec.autopilot.running, false); + assert.ok(turns >= 1, "the agent was driven at least once"); + // The step never gets checked off (runTurn is a stub), so it pauses as stuck. + assert.equal(rec.autopilot.status, "stuck"); + assert.ok(chunks.some((c) => /autopilot|"type":"state"/.test(c)), "progress was broadcast"); + }); +}); + +test("server: autopilot_stop and autopilot_dismiss behave by run state", async () => { + const rec = { repoPath: "/x", sseClients: new Set(), doctor: null, autopilot: null }; + const stopIdle = await dispatchAction(rec, "autopilot_stop", {}, {}); + assert.equal(stopIdle.ok, false, "nothing to stop when idle"); + + rec.autopilot = { running: true, cancelled: false, completed: [] }; + const stopRunning = await dispatchAction(rec, "autopilot_stop", {}, {}); + assert.equal(stopRunning.ok, true); + assert.equal(rec.autopilot.cancelled, true, "stop flips the cancel flag"); + + rec.autopilot = { running: false, status: "completed", completed: [] }; + await withRepo({ "pom.xml": "" }, async (dir) => { + rec.repoPath = dir; + const res = await dispatchAction(rec, "autopilot_dismiss", {}, {}); + assert.equal(res.ok, true); + assert.equal(rec.autopilot, null, "dismiss clears a finished run"); + }); +}); + +test("renderer: ships the Autopilot controls (start/stop, card, live strip)", () => { + const html = renderHtml({ instanceId: "x" }); + assert.match(html, /autopilot_start/); + assert.match(html, /autopilot_stop/); + assert.match(html, /Autopilot/); + assert.match(html, /autopilotStrip/); +});