infra-kit 0.1.105 → 0.1.107

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/.eslintcache +1 -1
  2. package/.omc/state/agent-replay-afc6290b-40d3-4bef-b3b6-14484c034ab9.jsonl +14 -0
  3. package/.omc/state/agent-replay-e947a3c6-989d-4a60-91dd-6b0ddd827b2d.jsonl +3 -0
  4. package/.omc/state/idle-notif-cooldown.json +1 -1
  5. package/.omc/state/sessions/afc6290b-40d3-4bef-b3b6-14484c034ab9/last-tool-error-state.json +7 -0
  6. package/.omc/state/sessions/afc6290b-40d3-4bef-b3b6-14484c034ab9/mission-state.json +89 -0
  7. package/.omc/state/sessions/afc6290b-40d3-4bef-b3b6-14484c034ab9/pre-tool-advisory-throttle.json +34 -0
  8. package/.omc/state/sessions/afc6290b-40d3-4bef-b3b6-14484c034ab9/ralph-state.json +13 -0
  9. package/.omc/state/sessions/afc6290b-40d3-4bef-b3b6-14484c034ab9/skill-active-state.json +15 -0
  10. package/.omc/state/sessions/afc6290b-40d3-4bef-b3b6-14484c034ab9/subagent-tracking-state.json +35 -0
  11. package/.omc/state/sessions/afc6290b-40d3-4bef-b3b6-14484c034ab9/ultrawork-state.json +11 -0
  12. package/.omc/state/sessions/e947a3c6-989d-4a60-91dd-6b0ddd827b2d/last-tool-error-state.json +7 -0
  13. package/.omc/state/sessions/e947a3c6-989d-4a60-91dd-6b0ddd827b2d/pre-tool-advisory-throttle.json +10 -0
  14. package/.omc/state/sessions/e947a3c6-989d-4a60-91dd-6b0ddd827b2d/subagent-tracking-state.json +26 -0
  15. package/dist/cli.js +61 -54
  16. package/dist/cli.js.map +4 -4
  17. package/dist/mcp.js +2 -2
  18. package/dist/mcp.js.map +2 -2
  19. package/package.json +1 -1
  20. package/src/commands/.omc/state/sessions/afc6290b-40d3-4bef-b3b6-14484c034ab9/pre-tool-advisory-throttle.json +14 -0
  21. package/src/commands/doctor/__tests__/agent-files.test.ts +110 -0
  22. package/src/commands/doctor/doctor.ts +66 -1
  23. package/src/commands/init/__tests__/agent-files.test.ts +147 -0
  24. package/src/commands/init/agent-files.ts +199 -0
  25. package/src/commands/init/index.ts +7 -0
  26. package/src/commands/init/init.ts +34 -25
  27. package/src/entry/cli.ts +1 -1
  28. package/src/integrations/cmux/open-workspace-with-layout.ts +1 -1
  29. package/src/lib/managed-block/__tests__/managed-block.test.ts +121 -0
  30. package/src/lib/managed-block/index.ts +8 -0
  31. package/src/lib/managed-block/managed-block.ts +145 -0
  32. package/tsconfig.tsbuildinfo +1 -1
package/dist/mcp.js CHANGED
@@ -28,7 +28,7 @@ git push origin ${d} && git switch dev
28
28
  `);a.info(`\u2705 ${n.length-c.length}/${n.length} merges completed successfully.`)}else a.info(`\u2705 All merges completed successfully!
29
29
  `);m.print();let p={successfulMerges:n.length-c.length,failedMerges:c.length,failedBranches:c,totalBranches:n.length};return{content:h(JSON.stringify(p,null,2)),structuredContent:p}},on=async e=>{try{return await W`git switch ${e}`,await W`git pull origin ${e}`,await W`git merge origin/dev --no-edit`,await W`git push origin ${e}`,await W`git switch dev`,a.info(`Successfully merged dev into ${e}`),!0}catch(r){let t=new g(r,{operation:`merge dev into ${e}`,remediation:"resolve conflicts manually or rerun after 'git fetch origin'"});return a.error({error:r,branch:e,msg:t.message}),await W`git reset --merge HEAD~1`,!1}},Qr=v({name:"gh-merge-dev",description:"Merge origin/dev into every open regular (non-hotfix) release branch and push the result. Mutates local git state and the remote release branches. When invoked via MCP, pass all=true \u2014 the branch picker is unreachable without a TTY, and the confirmation prompt is auto-skipped for MCP calls, so the caller is responsible for gating. Irreversible once pushed.",inputSchema:{all:Te.boolean().optional().describe("Target every open regular release branch. Must be true for MCP calls (the interactive picker is unavailable without a TTY).")},outputSchema:{successfulMerges:Te.number().describe("Number of successful merges"),failedMerges:Te.number().describe("Number of failed merges"),failedBranches:Te.array(Te.string()).describe("List of branches that failed to merge"),totalBranches:Te.number().describe("Total number of branches processed")},handler:co});import ln from"@inquirer/confirm";import pn from"@inquirer/select";import mn from"node:process";import{z as Ze}from"zod";import{$ as j}from"zx";var lo=(e,r)=>{if(!(typeof e!="string"||e.length===0))return e.slice(-r).trim()},et=e=>{if(e===null||typeof e!="object")return{message:String(e)};let r=e,t={};e instanceof Error?(t.name=e.name,t.message=e.message):typeof r.message=="string"&&(t.message=r.message);let o=r.exitCode;(typeof o=="number"||o===null)&&(t.exitCode=o);let s=lo(r.stderr,500);s&&(t.stderr=s);let n=lo(r.stdout,200);return n&&(t.stdout=n),t};import{$ as tt}from"zx";import{$ as po}from"zx";var ze=async e=>{try{let r=(await po`cmux list-workspaces`.quiet()).stdout,t=sn(r,e);if(!t)return;await po`cmux close-workspace --workspace ${t}`.quiet()}catch(r){a.debug({error:r,title:e},"cmux: skipped closing workspace")}},sn=(e,r)=>{for(let t of e.split(`
30
30
  `)){let o=t.match(/^[* ]\s*(workspace:\d+)\s+(.+?)(?:\s+\[selected\])?\s*$/);if(!o)continue;let s=o[1];if((o[2]?.trim()??"")===r)return s}};import{$ as nn}from"zx";var rt=async()=>{try{let e=(await nn`cmux list-workspaces`.quiet()).stdout,r=new Set;for(let t of e.split(`
31
- `)){let o=t.match(/^[* ]\s*workspace:\d+\s+(.+?)(?:\s+\[selected\])?\s*$/);if(!o)continue;let s=o[1]?.trim();s&&r.add(s)}return r}catch(e){return a.debug({error:e},"cmux: skipped listing workspace titles"),new Set}};import{$ as He}from"zx";var Xe=async e=>{let{cwd:r,title:t}=e,o=(await He`cmux workspace create --cwd ${r}`).stdout,s=cn(o),n=(await He`cmux list-pane-surfaces --workspace ${s}`).stdout,i=an(n);await He`cmux new-split right --workspace ${s} --surface ${i}`,await He`cmux new-split down --workspace ${s} --surface ${i}`,t&&await He`cmux workspace rename --workspace ${s} ${t}`},an=e=>{let r=e.match(/surface:\d+/);if(!r)throw new Error("cmux: could not locate initial surface in list-pane-surfaces output");return r[0]},cn=e=>{let r=e.match(/workspace:\d+/);if(!r)throw new Error("cmux: could not locate workspace ref in workspace create output");return r[0]};var G=e=>{let{repoName:r,branch:t}=e,o=y(t),s=o?C(o):t;return`${r} ${s}`};var Ye=async e=>{let{branches:r,worktreeDir:t,repoName:o,pruneFolder:s=!1}=e,n=await Promise.allSettled(r.map(async l=>{let c=`${t}/${l}`,p=G({repoName:o,branch:l});return await ze(p),await tt`git worktree remove ${c}`,l})),i=[];for(let[l,c]of n.entries())if(c.status==="fulfilled")i.push(c.value);else{let p=r[l],d=new g(c.reason,{operation:`remove worktree for ${p}`,remediation:"check 'git worktree list' for the path; uncommitted changes block removal"});a.error({error:c.reason,msg:d.message})}return s&&i.length===r.length&&(await tt`git worktree prune`,await tt`rm -rf ${t}`,a.info(`\u{1F5D1}\uFE0F Removed worktree folder: ${t}`),a.info("")),i};var Ee=async(e,r,t)=>{try{return await t()}catch(o){throw a.error({err:et(o)},`\u274C Failed to ${e}`),new g(o,{operation:e,remediation:r})}},uo=async e=>{let r=await j`gh pr list --head ${e} --state all --json number,state,title --limit 1`;return JSON.parse(r.stdout)[0]??null},dn=async e=>{let r=or(e),t=await j`gh pr list --head dev --base main --state merged --json number,state,title --limit 20`;return JSON.parse(t.stdout).find(n=>n.title===r)??null},mo=async()=>{let e=await j`gh pr list --head dev --base main --state open --json number,state,title --limit 5`;return JSON.parse(e.stdout)[0]??null},un=async e=>{let r=K(e),t=await uo(r);if(!t)throw a.error(`\u274C No PR found for branch ${r}.`),new g(void 0,{operation:`deliver release ${r}`,remediation:`confirm a PR exists ('gh pr list --head ${r} --state all')`});return{selectedReleaseBranch:r,releasePrTitle:t.title}},fn=async()=>{let e=await b(),r=e.map(i=>i.branch),t=new Map(e.map(i=>[i.branch,k(i.title)]));m.setInteractive();let o=await E(),s=await pn({message:"\u{1F33F} Select release branch",choices:I({branches:r,descriptions:o,types:t})}),n=e.find(i=>i.branch===s);if(!n)throw a.error(`\u274C Release branch ${s} not found in open PRs.`),new g(void 0,{operation:`deliver release ${s}`,remediation:`confirm an open PR exists for ${s} ('gh pr list')`});return{selectedReleaseBranch:s,releasePrTitle:n.title}},gn=async e=>{if(!(await _("release")).includes(e))return;let[t,o]=await Promise.all([P(),F()]),s=`${t}${J}`;if((await Ye({branches:[e],worktreeDir:s,repoName:o})).length===0)throw new g(void 0,{operation:`remove worktree for ${e} before merge`,remediation:`run manually: git worktree remove ${s}/${e} (use --force if uncommitted changes)`})},hn=async e=>{let{selectedReleaseBranch:r,releaseType:t}=e,o=t==="hotfix"?"main":"dev",s=await uo(r);if(!s)throw new g(void 0,{operation:`look up release PR for ${r}`,remediation:"verify the PR exists in GitHub"});if(s.state==="MERGED"){a.info(`\u2713 Release PR ${r} already merged \u2014 skipping`);return}if(s.state==="CLOSED")throw new g(void 0,{operation:`merge release PR ${r} into ${o}`,remediation:"the PR is closed without merge; reopen it or create a new release"});await Ee(`merge release PR ${r} into ${o}`,`check 'gh pr view ${r}' for mergeability and required reviews`,async()=>{await j`gh pr merge ${r} --squash --admin --delete-branch`})},wn=async e=>{let r=C(e),t=or(e),o=await mo();if(o){let n=o.number;return o.title!==t&&(a.info(`Adopting open dev \u2192 main PR #${n} ("${o.title}") and retitling for ${r}`),await Ee(`retitle dev \u2192 main PR #${n} to "${t}"`,`update manually: gh pr edit ${n} --title "${t}"`,async()=>{await j`gh pr edit ${n} --title ${t}`})),n}await Ee(`create RC PR (dev \u2192 main) for ${r}`,"run 'gh pr create --base main --head dev' manually to surface the underlying error (e.g. no commits between dev and main)",async()=>{await j`gh pr create --base main --head dev --title ${t} --body ""`});let s=await mo();if(!s)throw new g(void 0,{operation:`look up RC PR for ${r}`,remediation:"verify the RC PR was created ('gh pr list --head dev --base main')"});return s.number},vn=async e=>{let r=C(e);if(await dn(e)){a.info(`\u2713 RC PR for ${r} already merged into main \u2014 skipping`);return}let o=await wn(e);await Ee(`merge RC PR #${o} (dev \u2192 main) for ${r}`,`check 'gh pr view ${o}' for mergeability and required reviews`,async()=>{await j`gh pr merge ${o} --squash --admin`})},Rn=async()=>{j.quiet=!1,await Ee("dispatch deploy-all workflow on main","check 'gh workflow list' and that you have permission to dispatch deploy-all.yml",async()=>{await j`gh workflow run deploy-all.yml --ref main -f environment=prod`}),j.quiet=!0},yn=async()=>{await Ee("sync main back into dev","run manually: git switch main && git pull && git switch dev && git pull && git merge main --no-edit && git push",async()=>{await j`git switch main && git pull && git switch dev && git pull && git merge main --no-edit && git push`})},kn=async e=>{let r=await ue();if(!r){a.info("\u{1F514} Jira is not configured, skipping Jira release delivery");return}try{let t=M(e);await Xr({versionName:t},r)}catch(t){a.error({err:et(t)},"Failed to deliver Jira release (non-blocking)")}},fo=async e=>{let{version:r,confirmedCommand:t}=e;m.start("release-deliver");let{selectedReleaseBranch:o,releasePrTitle:s}=r?await un(r):await fn(),n=y(o);if(!n)throw new g(void 0,{operation:`deliver release ${o}`,remediation:'pass a version (e.g. "1.2.5") or a release name (e.g. "checkout-redesign")'});let i=C(n);m.addOption("--version",i),a.info(`Delivering ${n.kind==="name"?"named release":"version"} ${o}`);let l=k(s),c=t?!0:await ln({message:`Are you sure you want to deliver version ${o} to production?`});t||m.setInteractive(),c||(a.info("Operation cancelled. Exiting..."),mn.exit(0)),m.addOption("--yes",!0),j.quiet=!0,await gn(o),await hn({selectedReleaseBranch:o,releaseType:l}),l!=="hotfix"&&await vn(n),await Rn(),await yn(),j.quiet=!1,await kn(n),a.info(`Successfully delivered ${o} to production!`),m.print();let p={releaseBranch:o,version:i,type:l,success:!0};return{content:h(JSON.stringify(p,null,2)),structuredContent:p}},ot=v({name:"gh-release-deliver",description:'Deliver a release to production. For hotfixes: squash-merges the release branch to main and dispatches the deploy-all workflow. For regular releases: squash-merges to dev, opens an RC PR, merges dev into main, dispatches the deploy-all workflow, then syncs main back to dev. Also releases the matching Jira fix version if Jira is configured. Dispatches the deploy workflow fire-and-forget \u2014 the tool returns once the workflow is accepted by GitHub, not when the deployment finishes. PR-merge steps are idempotent: re-running after a partial failure skips PRs that are already merged. Irreversible production operation: the confirmation prompt is auto-skipped for MCP calls, so the caller is responsible for gating. "version" is required when invoked via MCP (the picker is unreachable without a TTY).',inputSchema:{version:Ze.string().describe('Accepts a release version (e.g. "1.2.5") OR a release name (e.g. "checkout-redesign") to deliver to production. Required for MCP calls.')},outputSchema:{releaseBranch:Ze.string().describe("The release branch that was delivered"),version:Ze.string().describe("The version that was delivered"),type:Ze.enum(["regular","hotfix"]).describe("Release type"),success:Ze.boolean().describe("Whether the delivery was successful")},handler:fo});import go from"@inquirer/select";import{z as ee}from"zod";import{$ as st}from"zx";var ho=async e=>{let{version:r,env:t,skipTerraform:o}=e;m.start("release-deploy-all");let s="";if(r)s=r==="dev"?"dev":K(r);else{m.setInteractive();let p=await b(),d=p.map(w=>w.branch),u=new Map(p.map(w=>[w.branch,k(w.title)])),f=await E();s=await go({message:"\u{1F33F} Select release branch",choices:[{name:"dev",value:"dev"},...I({branches:d,descriptions:f,types:u})]})}let n=Ge(s);m.addOption("--version",n);let{environments:i}=await T(),l="";if(t?l=t:(m.setInteractive(),l=await go({message:"\u{1F9EA} Select environment",choices:i.map(p=>({name:p,value:p}))})),m.addOption("--env",l),!i.includes(l))throw new g(void 0,{operation:"launch deploy-all workflow",remediation:`pass one of: ${i.join(", ")}`,stderrExcerpt:`invalid environment: ${l}`});let c=o??!1;c&&m.addOption("--skip-terraform",!0);try{st.quiet=!0,await st`gh workflow run deploy-all.yml --ref ${s} -f environment=${l} ${c?["-f","skip_terraform_deploy=true"]:[]}`,st.quiet=!1,a.info(`Successfully launched deploy-all workflow_dispatch for release branch: ${s} and environment: ${l}`),m.print();let d={releaseBranch:s,version:n,environment:l,skipTerraformDeploy:c,success:!0};return{content:h(JSON.stringify(d,null,2)),structuredContent:d}}catch(p){throw a.error({error:p},"\u274C Error launching workflow"),new g(p,{operation:"launch deploy-all workflow",remediation:"check 'gh workflow list' and that deploy-all.yml exists on the target ref"})}},nt=v({name:"gh-release-deploy-all",description:'Dispatch the deploy-all.yml GitHub Actions workflow to deploy every service from a release branch to the given environment. Fire-and-forget \u2014 returns once GitHub accepts the workflow_dispatch, NOT when the deployment finishes; watch the workflow run for completion status. Use gh-release-deploy-selected for a subset of services. Pass version="dev" to deploy from the dev branch instead of a release branch. Both "version" and "env" are required when invoked via MCP (interactive pickers are unavailable without a TTY).',inputSchema:{version:ee.string().describe('Accepts a release version (e.g. "1.2.5") OR a release name (e.g. "checkout-redesign") \u2014 resolves to the release/vX.Y.Z or release/n/<name> branch. Pass "dev" to deploy from the dev branch instead. Required for MCP calls.'),env:ee.string().describe('Target environment name \u2014 must match an env configured for the project (e.g. "dev", "renana", "oriana"). Required for MCP calls.'),skipTerraform:ee.boolean().optional().describe("Skip the terraform deployment stage.")},outputSchema:{releaseBranch:ee.string().describe("The release branch that was deployed"),version:ee.string().describe("The version that was deployed"),environment:ee.string().describe("The environment deployed to"),skipTerraformDeploy:ee.boolean().describe("Whether terraform deployment was skipped"),success:ee.boolean().describe("Whether the deployment was successful")},handler:ho});import bn from"@inquirer/checkbox";import wo from"@inquirer/select";import xn from"node:fs/promises";import{resolve as Cn}from"node:path";import Pn from"yaml";import{z as L}from"zod";import{$ as it}from"zx";var vo=async e=>{let{version:r,env:t,services:o,skipTerraform:s}=e;m.start("release-deploy-selected");let n="";if(r)n=r==="dev"?"dev":K(r);else{m.setInteractive();let w=await b(),$=w.map(Re=>Re.branch),S=new Map(w.map(Re=>[Re.branch,k(Re.title)])),A=await E();n=await wo({message:"\u{1F33F} Select release branch",choices:[{name:"dev",value:"dev"},...I({branches:$,descriptions:A,types:S})]})}let i=Ge(n);m.addOption("--version",i);let{environments:l}=await T(),c="";if(t?c=t:(m.setInteractive(),c=await wo({message:"\u{1F9EA} Select environment",choices:l.map(w=>({name:w,value:w}))})),m.addOption("--env",c),!l.includes(c))throw new g(void 0,{operation:"launch deploy-selected workflow",remediation:`pass one of: ${l.join(", ")}`,stderrExcerpt:`invalid environment: ${c}`});let p=await Tn();if(p.length===0)throw new g(void 0,{operation:"launch deploy-selected workflow",remediation:"check .github/workflows/deploy-selected-services.yml for boolean service inputs",stderrExcerpt:"no services found in workflow file"});let d=[];if(o&&o.length>0?d=o:(m.setInteractive(),d=await bn({message:"\u{1F680} Select services to deploy (space to select, enter to confirm)",choices:p.map(w=>({name:w,value:w}))})),m.addOption("--services",d),d.length===0)throw new g(void 0,{operation:"launch deploy-selected workflow",remediation:`pass at least one service from: ${p.join(", ")}`,stderrExcerpt:"no services selected"});let u=d.filter(w=>!p.includes(w));if(u.length>0)throw new g(void 0,{operation:"launch deploy-selected workflow",remediation:`pass services from: ${p.join(", ")}`,stderrExcerpt:`invalid services: ${u.join(", ")}`});let f=s??!1;f&&m.addOption("--skip-terraform",!0);try{it.quiet=!0;let w=d.flatMap(A=>["-f",`${A}=true`]);await it`gh workflow run deploy-selected-services.yml --ref ${n} -f environment=${c} ${w} ${f?["-f","skip_terraform_deploy=true"]:[]}`,it.quiet=!1,a.info(`Successfully launched deploy-selected-services workflow_dispatch for release branch: ${n}, environment: ${c}, services: ${d.join(", ")}`),m.print();let S={releaseBranch:n,version:i,environment:c,services:d,skipTerraformDeploy:f,success:!0};return{content:h(JSON.stringify(S,null,2)),structuredContent:S}}catch(w){throw a.error({error:w},"\u274C Error launching workflow"),new g(w,{operation:"launch deploy-selected workflow",remediation:"check 'gh workflow list' and that deploy-selected-services.yml exists on the target ref"})}},Tn=async()=>{let e=await P(),r=Cn(e,".github/workflows/deploy-selected-services.yml"),t=await xn.readFile(r,"utf-8"),s=Pn.parse(t).on.workflow_dispatch.inputs,n=[];for(let[i,l]of Object.entries(s))l.type==="boolean"&&i!=="skip_terraform_deploy"&&n.push(i);return n},at=v({name:"gh-release-deploy-selected",description:'Dispatch the deploy-selected-services.yml GitHub Actions workflow to deploy a chosen subset of services from a release branch to the given environment. Fire-and-forget \u2014 returns once GitHub accepts the workflow_dispatch, NOT when the deployment finishes; watch the workflow run for completion status. Service names are validated against the boolean inputs declared in the workflow. Use gh-release-deploy-all for every service. "version", "env", and "services" are all required when invoked via MCP (interactive pickers are unavailable without a TTY).',inputSchema:{version:L.string().describe('Accepts a release version (e.g. "1.2.5") OR a release name (e.g. "checkout-redesign") \u2014 resolves to the release/vX.Y.Z or release/n/<name> branch. Pass "dev" to deploy from the dev branch instead. Required for MCP calls.'),env:L.string().describe('Target environment name \u2014 must match an env configured for the project (e.g. "dev", "renana", "oriana"). Required for MCP calls.'),services:L.array(L.string()).describe('Service names to deploy. Each must match a boolean input declared in .github/workflows/deploy-selected-services.yml (e.g. "client-be", "client-fe"). Required for MCP calls.'),skipTerraform:L.boolean().optional().describe("Skip the terraform deployment stage.")},outputSchema:{releaseBranch:L.string().describe("The release branch that was deployed"),version:L.string().describe("The version that was deployed"),environment:L.string().describe("The environment deployed to"),services:L.array(L.string()).describe("The services that were deployed"),skipTerraformDeploy:L.boolean().describe("Whether terraform deployment was skipped"),success:L.boolean().describe("Whether the deployment was successful")},handler:vo});import{z as $e}from"zod";var Ro=async()=>{let r=(await b()).flatMap(i=>{let l=y(i.branch);return l?[{version:C(l),jiraKey:M(l),type:k(i.title)}]:[]}),t=await E(),o=Math.max(...r.map(i=>i.version.length)),s=r.map(i=>{let l=Pe(i.version,i.type,o),c=t.get(i.jiraKey);return c?`${l} ${c}`:l});a.info(`All release branches:
31
+ `)){let o=t.match(/^[* ]\s*workspace:\d+\s+(.+?)(?:\s+\[selected\])?\s*$/);if(!o)continue;let s=o[1]?.trim();s&&r.add(s)}return r}catch(e){return a.debug({error:e},"cmux: skipped listing workspace titles"),new Set}};import{$ as He}from"zx";var Xe=async e=>{let{cwd:r,title:t}=e,o=(await He`cmux workspace create --cwd ${r}`).stdout,s=cn(o),n=(await He`cmux list-pane-surfaces --workspace ${s}`).stdout,i=an(n);await He`cmux new-split right --workspace ${s} --surface ${i}`,await He`cmux new-split down --workspace ${s} --surface ${i}`,t&&await He`cmux workspace rename --workspace ${s} --title ${t}`},an=e=>{let r=e.match(/surface:\d+/);if(!r)throw new Error("cmux: could not locate initial surface in list-pane-surfaces output");return r[0]},cn=e=>{let r=e.match(/workspace:\d+/);if(!r)throw new Error("cmux: could not locate workspace ref in workspace create output");return r[0]};var G=e=>{let{repoName:r,branch:t}=e,o=y(t),s=o?C(o):t;return`${r} ${s}`};var Ye=async e=>{let{branches:r,worktreeDir:t,repoName:o,pruneFolder:s=!1}=e,n=await Promise.allSettled(r.map(async l=>{let c=`${t}/${l}`,p=G({repoName:o,branch:l});return await ze(p),await tt`git worktree remove ${c}`,l})),i=[];for(let[l,c]of n.entries())if(c.status==="fulfilled")i.push(c.value);else{let p=r[l],d=new g(c.reason,{operation:`remove worktree for ${p}`,remediation:"check 'git worktree list' for the path; uncommitted changes block removal"});a.error({error:c.reason,msg:d.message})}return s&&i.length===r.length&&(await tt`git worktree prune`,await tt`rm -rf ${t}`,a.info(`\u{1F5D1}\uFE0F Removed worktree folder: ${t}`),a.info("")),i};var Ee=async(e,r,t)=>{try{return await t()}catch(o){throw a.error({err:et(o)},`\u274C Failed to ${e}`),new g(o,{operation:e,remediation:r})}},uo=async e=>{let r=await j`gh pr list --head ${e} --state all --json number,state,title --limit 1`;return JSON.parse(r.stdout)[0]??null},dn=async e=>{let r=or(e),t=await j`gh pr list --head dev --base main --state merged --json number,state,title --limit 20`;return JSON.parse(t.stdout).find(n=>n.title===r)??null},mo=async()=>{let e=await j`gh pr list --head dev --base main --state open --json number,state,title --limit 5`;return JSON.parse(e.stdout)[0]??null},un=async e=>{let r=K(e),t=await uo(r);if(!t)throw a.error(`\u274C No PR found for branch ${r}.`),new g(void 0,{operation:`deliver release ${r}`,remediation:`confirm a PR exists ('gh pr list --head ${r} --state all')`});return{selectedReleaseBranch:r,releasePrTitle:t.title}},fn=async()=>{let e=await b(),r=e.map(i=>i.branch),t=new Map(e.map(i=>[i.branch,k(i.title)]));m.setInteractive();let o=await E(),s=await pn({message:"\u{1F33F} Select release branch",choices:I({branches:r,descriptions:o,types:t})}),n=e.find(i=>i.branch===s);if(!n)throw a.error(`\u274C Release branch ${s} not found in open PRs.`),new g(void 0,{operation:`deliver release ${s}`,remediation:`confirm an open PR exists for ${s} ('gh pr list')`});return{selectedReleaseBranch:s,releasePrTitle:n.title}},gn=async e=>{if(!(await _("release")).includes(e))return;let[t,o]=await Promise.all([P(),F()]),s=`${t}${J}`;if((await Ye({branches:[e],worktreeDir:s,repoName:o})).length===0)throw new g(void 0,{operation:`remove worktree for ${e} before merge`,remediation:`run manually: git worktree remove ${s}/${e} (use --force if uncommitted changes)`})},hn=async e=>{let{selectedReleaseBranch:r,releaseType:t}=e,o=t==="hotfix"?"main":"dev",s=await uo(r);if(!s)throw new g(void 0,{operation:`look up release PR for ${r}`,remediation:"verify the PR exists in GitHub"});if(s.state==="MERGED"){a.info(`\u2713 Release PR ${r} already merged \u2014 skipping`);return}if(s.state==="CLOSED")throw new g(void 0,{operation:`merge release PR ${r} into ${o}`,remediation:"the PR is closed without merge; reopen it or create a new release"});await Ee(`merge release PR ${r} into ${o}`,`check 'gh pr view ${r}' for mergeability and required reviews`,async()=>{await j`gh pr merge ${r} --squash --admin --delete-branch`})},wn=async e=>{let r=C(e),t=or(e),o=await mo();if(o){let n=o.number;return o.title!==t&&(a.info(`Adopting open dev \u2192 main PR #${n} ("${o.title}") and retitling for ${r}`),await Ee(`retitle dev \u2192 main PR #${n} to "${t}"`,`update manually: gh pr edit ${n} --title "${t}"`,async()=>{await j`gh pr edit ${n} --title ${t}`})),n}await Ee(`create RC PR (dev \u2192 main) for ${r}`,"run 'gh pr create --base main --head dev' manually to surface the underlying error (e.g. no commits between dev and main)",async()=>{await j`gh pr create --base main --head dev --title ${t} --body ""`});let s=await mo();if(!s)throw new g(void 0,{operation:`look up RC PR for ${r}`,remediation:"verify the RC PR was created ('gh pr list --head dev --base main')"});return s.number},vn=async e=>{let r=C(e);if(await dn(e)){a.info(`\u2713 RC PR for ${r} already merged into main \u2014 skipping`);return}let o=await wn(e);await Ee(`merge RC PR #${o} (dev \u2192 main) for ${r}`,`check 'gh pr view ${o}' for mergeability and required reviews`,async()=>{await j`gh pr merge ${o} --squash --admin`})},Rn=async()=>{j.quiet=!1,await Ee("dispatch deploy-all workflow on main","check 'gh workflow list' and that you have permission to dispatch deploy-all.yml",async()=>{await j`gh workflow run deploy-all.yml --ref main -f environment=prod`}),j.quiet=!0},yn=async()=>{await Ee("sync main back into dev","run manually: git switch main && git pull && git switch dev && git pull && git merge main --no-edit && git push",async()=>{await j`git switch main && git pull && git switch dev && git pull && git merge main --no-edit && git push`})},kn=async e=>{let r=await ue();if(!r){a.info("\u{1F514} Jira is not configured, skipping Jira release delivery");return}try{let t=M(e);await Xr({versionName:t},r)}catch(t){a.error({err:et(t)},"Failed to deliver Jira release (non-blocking)")}},fo=async e=>{let{version:r,confirmedCommand:t}=e;m.start("release-deliver");let{selectedReleaseBranch:o,releasePrTitle:s}=r?await un(r):await fn(),n=y(o);if(!n)throw new g(void 0,{operation:`deliver release ${o}`,remediation:'pass a version (e.g. "1.2.5") or a release name (e.g. "checkout-redesign")'});let i=C(n);m.addOption("--version",i),a.info(`Delivering ${n.kind==="name"?"named release":"version"} ${o}`);let l=k(s),c=t?!0:await ln({message:`Are you sure you want to deliver version ${o} to production?`});t||m.setInteractive(),c||(a.info("Operation cancelled. Exiting..."),mn.exit(0)),m.addOption("--yes",!0),j.quiet=!0,await gn(o),await hn({selectedReleaseBranch:o,releaseType:l}),l!=="hotfix"&&await vn(n),await Rn(),await yn(),j.quiet=!1,await kn(n),a.info(`Successfully delivered ${o} to production!`),m.print();let p={releaseBranch:o,version:i,type:l,success:!0};return{content:h(JSON.stringify(p,null,2)),structuredContent:p}},ot=v({name:"gh-release-deliver",description:'Deliver a release to production. For hotfixes: squash-merges the release branch to main and dispatches the deploy-all workflow. For regular releases: squash-merges to dev, opens an RC PR, merges dev into main, dispatches the deploy-all workflow, then syncs main back to dev. Also releases the matching Jira fix version if Jira is configured. Dispatches the deploy workflow fire-and-forget \u2014 the tool returns once the workflow is accepted by GitHub, not when the deployment finishes. PR-merge steps are idempotent: re-running after a partial failure skips PRs that are already merged. Irreversible production operation: the confirmation prompt is auto-skipped for MCP calls, so the caller is responsible for gating. "version" is required when invoked via MCP (the picker is unreachable without a TTY).',inputSchema:{version:Ze.string().describe('Accepts a release version (e.g. "1.2.5") OR a release name (e.g. "checkout-redesign") to deliver to production. Required for MCP calls.')},outputSchema:{releaseBranch:Ze.string().describe("The release branch that was delivered"),version:Ze.string().describe("The version that was delivered"),type:Ze.enum(["regular","hotfix"]).describe("Release type"),success:Ze.boolean().describe("Whether the delivery was successful")},handler:fo});import go from"@inquirer/select";import{z as ee}from"zod";import{$ as st}from"zx";var ho=async e=>{let{version:r,env:t,skipTerraform:o}=e;m.start("release-deploy-all");let s="";if(r)s=r==="dev"?"dev":K(r);else{m.setInteractive();let p=await b(),d=p.map(w=>w.branch),u=new Map(p.map(w=>[w.branch,k(w.title)])),f=await E();s=await go({message:"\u{1F33F} Select release branch",choices:[{name:"dev",value:"dev"},...I({branches:d,descriptions:f,types:u})]})}let n=Ge(s);m.addOption("--version",n);let{environments:i}=await T(),l="";if(t?l=t:(m.setInteractive(),l=await go({message:"\u{1F9EA} Select environment",choices:i.map(p=>({name:p,value:p}))})),m.addOption("--env",l),!i.includes(l))throw new g(void 0,{operation:"launch deploy-all workflow",remediation:`pass one of: ${i.join(", ")}`,stderrExcerpt:`invalid environment: ${l}`});let c=o??!1;c&&m.addOption("--skip-terraform",!0);try{st.quiet=!0,await st`gh workflow run deploy-all.yml --ref ${s} -f environment=${l} ${c?["-f","skip_terraform_deploy=true"]:[]}`,st.quiet=!1,a.info(`Successfully launched deploy-all workflow_dispatch for release branch: ${s} and environment: ${l}`),m.print();let d={releaseBranch:s,version:n,environment:l,skipTerraformDeploy:c,success:!0};return{content:h(JSON.stringify(d,null,2)),structuredContent:d}}catch(p){throw a.error({error:p},"\u274C Error launching workflow"),new g(p,{operation:"launch deploy-all workflow",remediation:"check 'gh workflow list' and that deploy-all.yml exists on the target ref"})}},nt=v({name:"gh-release-deploy-all",description:'Dispatch the deploy-all.yml GitHub Actions workflow to deploy every service from a release branch to the given environment. Fire-and-forget \u2014 returns once GitHub accepts the workflow_dispatch, NOT when the deployment finishes; watch the workflow run for completion status. Use gh-release-deploy-selected for a subset of services. Pass version="dev" to deploy from the dev branch instead of a release branch. Both "version" and "env" are required when invoked via MCP (interactive pickers are unavailable without a TTY).',inputSchema:{version:ee.string().describe('Accepts a release version (e.g. "1.2.5") OR a release name (e.g. "checkout-redesign") \u2014 resolves to the release/vX.Y.Z or release/n/<name> branch. Pass "dev" to deploy from the dev branch instead. Required for MCP calls.'),env:ee.string().describe('Target environment name \u2014 must match an env configured for the project (e.g. "dev", "renana", "oriana"). Required for MCP calls.'),skipTerraform:ee.boolean().optional().describe("Skip the terraform deployment stage.")},outputSchema:{releaseBranch:ee.string().describe("The release branch that was deployed"),version:ee.string().describe("The version that was deployed"),environment:ee.string().describe("The environment deployed to"),skipTerraformDeploy:ee.boolean().describe("Whether terraform deployment was skipped"),success:ee.boolean().describe("Whether the deployment was successful")},handler:ho});import bn from"@inquirer/checkbox";import wo from"@inquirer/select";import xn from"node:fs/promises";import{resolve as Cn}from"node:path";import Pn from"yaml";import{z as L}from"zod";import{$ as it}from"zx";var vo=async e=>{let{version:r,env:t,services:o,skipTerraform:s}=e;m.start("release-deploy-selected");let n="";if(r)n=r==="dev"?"dev":K(r);else{m.setInteractive();let w=await b(),$=w.map(Re=>Re.branch),S=new Map(w.map(Re=>[Re.branch,k(Re.title)])),A=await E();n=await wo({message:"\u{1F33F} Select release branch",choices:[{name:"dev",value:"dev"},...I({branches:$,descriptions:A,types:S})]})}let i=Ge(n);m.addOption("--version",i);let{environments:l}=await T(),c="";if(t?c=t:(m.setInteractive(),c=await wo({message:"\u{1F9EA} Select environment",choices:l.map(w=>({name:w,value:w}))})),m.addOption("--env",c),!l.includes(c))throw new g(void 0,{operation:"launch deploy-selected workflow",remediation:`pass one of: ${l.join(", ")}`,stderrExcerpt:`invalid environment: ${c}`});let p=await Tn();if(p.length===0)throw new g(void 0,{operation:"launch deploy-selected workflow",remediation:"check .github/workflows/deploy-selected-services.yml for boolean service inputs",stderrExcerpt:"no services found in workflow file"});let d=[];if(o&&o.length>0?d=o:(m.setInteractive(),d=await bn({message:"\u{1F680} Select services to deploy (space to select, enter to confirm)",choices:p.map(w=>({name:w,value:w}))})),m.addOption("--services",d),d.length===0)throw new g(void 0,{operation:"launch deploy-selected workflow",remediation:`pass at least one service from: ${p.join(", ")}`,stderrExcerpt:"no services selected"});let u=d.filter(w=>!p.includes(w));if(u.length>0)throw new g(void 0,{operation:"launch deploy-selected workflow",remediation:`pass services from: ${p.join(", ")}`,stderrExcerpt:`invalid services: ${u.join(", ")}`});let f=s??!1;f&&m.addOption("--skip-terraform",!0);try{it.quiet=!0;let w=d.flatMap(A=>["-f",`${A}=true`]);await it`gh workflow run deploy-selected-services.yml --ref ${n} -f environment=${c} ${w} ${f?["-f","skip_terraform_deploy=true"]:[]}`,it.quiet=!1,a.info(`Successfully launched deploy-selected-services workflow_dispatch for release branch: ${n}, environment: ${c}, services: ${d.join(", ")}`),m.print();let S={releaseBranch:n,version:i,environment:c,services:d,skipTerraformDeploy:f,success:!0};return{content:h(JSON.stringify(S,null,2)),structuredContent:S}}catch(w){throw a.error({error:w},"\u274C Error launching workflow"),new g(w,{operation:"launch deploy-selected workflow",remediation:"check 'gh workflow list' and that deploy-selected-services.yml exists on the target ref"})}},Tn=async()=>{let e=await P(),r=Cn(e,".github/workflows/deploy-selected-services.yml"),t=await xn.readFile(r,"utf-8"),s=Pn.parse(t).on.workflow_dispatch.inputs,n=[];for(let[i,l]of Object.entries(s))l.type==="boolean"&&i!=="skip_terraform_deploy"&&n.push(i);return n},at=v({name:"gh-release-deploy-selected",description:'Dispatch the deploy-selected-services.yml GitHub Actions workflow to deploy a chosen subset of services from a release branch to the given environment. Fire-and-forget \u2014 returns once GitHub accepts the workflow_dispatch, NOT when the deployment finishes; watch the workflow run for completion status. Service names are validated against the boolean inputs declared in the workflow. Use gh-release-deploy-all for every service. "version", "env", and "services" are all required when invoked via MCP (interactive pickers are unavailable without a TTY).',inputSchema:{version:L.string().describe('Accepts a release version (e.g. "1.2.5") OR a release name (e.g. "checkout-redesign") \u2014 resolves to the release/vX.Y.Z or release/n/<name> branch. Pass "dev" to deploy from the dev branch instead. Required for MCP calls.'),env:L.string().describe('Target environment name \u2014 must match an env configured for the project (e.g. "dev", "renana", "oriana"). Required for MCP calls.'),services:L.array(L.string()).describe('Service names to deploy. Each must match a boolean input declared in .github/workflows/deploy-selected-services.yml (e.g. "client-be", "client-fe"). Required for MCP calls.'),skipTerraform:L.boolean().optional().describe("Skip the terraform deployment stage.")},outputSchema:{releaseBranch:L.string().describe("The release branch that was deployed"),version:L.string().describe("The version that was deployed"),environment:L.string().describe("The environment deployed to"),services:L.array(L.string()).describe("The services that were deployed"),skipTerraformDeploy:L.boolean().describe("Whether terraform deployment was skipped"),success:L.boolean().describe("Whether the deployment was successful")},handler:vo});import{z as $e}from"zod";var Ro=async()=>{let r=(await b()).flatMap(i=>{let l=y(i.branch);return l?[{version:C(l),jiraKey:M(l),type:k(i.title)}]:[]}),t=await E(),o=Math.max(...r.map(i=>i.version.length)),s=r.map(i=>{let l=Pe(i.version,i.type,o),c=t.get(i.jiraKey);return c?`${l} ${c}`:l});a.info(`All release branches:
32
32
  `),a.info(`
33
33
  ${s.join(`
34
34
  `)}
@@ -46,7 +46,7 @@ ${r}`:`${e}
46
46
  from: "${d}"
47
47
  to: "${u}"
48
48
  `});o||m.setInteractive(),f||(a.info("Operation cancelled. Exiting..."),Un.exit(0)),m.addOption("--yes",!0),await lr({versionId:p.id,description:u},s);let w=So(s,p),$=Gn(w,u);await fr({branch:n,body:$}),a.info(`\u2705 Updated description for ${c}`),a.info(`\u{1F517} Jira Version: ${w}`),a.info(`\u{1F517} PR branch: ${n}
49
- `),m.print();let S={version:l,branch:n,jiraVersionUrl:w,previousDescription:d,newDescription:u,changed:!0};return{content:h(JSON.stringify(S,null,2)),structuredContent:S}},vt=v({name:"release-desc-edit",description:"Edit a release's description in Jira and in the matching GitHub release PR body. Accepts a release version or a release name: targets the Jira fix version named `v<version>` (versioned) or `<name>` (named) and the open PR on branch `release/v<version>` or `release/n/<name>`. The PR body is rewritten canonically to `<jiraVersionUrl>\\n\\n<description>` \u2014 any prior manual edits to the body are overwritten. Both `version` and `description` are required for MCP calls (the picker/prompt are unreachable without a TTY). Empty `description` clears the description on both sides. Confirmation is auto-skipped for MCP, so the caller is responsible for gating.",inputSchema:{version:te.string().describe('Accepts a release version (e.g. "1.2.5") OR a release name (e.g. "checkout-redesign").'),description:te.string().describe("New description. Empty string clears the description.")},outputSchema:{version:te.string().describe("Release version"),branch:te.string().describe('Release branch name (e.g. "release/v1.2.5" or "release/n/checkout-redesign")'),jiraVersionUrl:te.string().describe("Jira fix version URL"),previousDescription:te.string().describe("The description before the update"),newDescription:te.string().describe("The description after the update"),changed:te.boolean().describe("Whether the description actually changed")},handler:Io});import{z as Zn}from"zod";var No={name:"infra-kit",type:"module",version:"0.1.105",description:"infra-kit",main:"dist/index.js",module:"dist/index.js",types:"dist/entry/index.d.ts",exports:{".":{types:"./dist/entry/index.d.ts",import:"./dist/index.js"}},bin:{"infra-kit":"dist/cli.js"},engines:{node:">=24.x"},scripts:{inspector:"npx @modelcontextprotocol/inspector node ./dist/mcp.js --debug",build:"pnpm run clean-artifacts && node ./scripts/build.js",check:"node ./dist/cli.js check","clean-artifacts":"rm -rf dist","clean-cache":"rm -rf node_modules/.cache .eslintcache tsconfig.tsbuildinfo .turbo .swc","prettier-fix":"pnpm exec prettier **/* --write --no-error-on-unmatched-pattern --log-level silent --ignore-path ../../../.prettierignore","prettier-check":"pnpm exec prettier **/* --check --no-error-on-unmatched-pattern --log-level silent --ignore-path ../../../.prettierignore","eslint-check":"pnpm exec eslint --cache --quiet --report-unused-disable-directives ./src","eslint-fix":"pnpm exec eslint --cache --quiet --report-unused-disable-directives ./src --fix","ts-check":"tsc --noEmit",test:"pnpm exec vitest run --reporter=minimal","test-watch":"pnpm exec vitest --watch --silent passed-only","test-ui":"pnpm exec vitest --ui --silent passed-only","test-report":"pnpm exec vitest run --coverage --silent passed-only",qa:"pnpm run prettier-check && pnpm run eslint-check && pnpm run ts-check && pnpm run test && echo \u2705 Success",fix:"pnpm run prettier-fix && pnpm run eslint-fix && pnpm run qa"},dependencies:{"@inquirer/checkbox":"^5.2.1","@inquirer/confirm":"^6.1.1","@inquirer/select":"^5.2.1","@modelcontextprotocol/sdk":"^1.29.0",commander:"^15.0.0",pino:"^10.3.1","pino-pretty":"^13.1.3",yaml:"^2.9.0",zod:"^4.4.3",zx:"^8.8.5"},devDependencies:{"@wl/eslint-config":"workspace:*","@wl/vitest-config":"workspace:*",esbuild:"^0.28.1",typescript:"^6.0.3"}};var Ao=async()=>{let e=No.version;a.info(e);let r={version:e};return{content:h(JSON.stringify(r,null,2)),structuredContent:r}},Rt=v({name:"version",description:"Print the installed infra-kit CLI version",inputSchema:{},outputSchema:{version:Zn.string().describe("Installed infra-kit CLI version (from package.json)")},handler:Ao});import ei from"@inquirer/checkbox";import kt from"@inquirer/confirm";import ri from"@inquirer/select";import ti from"node:process";import{z as oe}from"zod";import{$ as we}from"zx";import Oo from"node:fs/promises";import vr from"node:path";var Qe=async e=>{let{workspacePath:r,folderPaths:t}=e,o=vr.dirname(r),s;try{s=await Oo.readFile(r,"utf-8")}catch(d){throw new Error(`Cursor workspace file not found at ${r}: ${d.message}`)}let n;try{n=JSON.parse(s)}catch(d){throw new Error(`Failed to parse ${r} as JSON. Comments (JSONC) are not supported. ${d.message}`)}let i=n.folders??[],l=new Set(i.map(d=>vr.resolve(o,d.path))),c=[],p=[];for(let d of t){let u=vr.resolve(d);if(l.has(u)){p.push(d);continue}let f=vr.relative(o,u);i.push({path:f}),l.add(u),c.push(d)}return n.folders=i,await Oo.writeFile(r,`${JSON.stringify(n,null,2)}
49
+ `),m.print();let S={version:l,branch:n,jiraVersionUrl:w,previousDescription:d,newDescription:u,changed:!0};return{content:h(JSON.stringify(S,null,2)),structuredContent:S}},vt=v({name:"release-desc-edit",description:"Edit a release's description in Jira and in the matching GitHub release PR body. Accepts a release version or a release name: targets the Jira fix version named `v<version>` (versioned) or `<name>` (named) and the open PR on branch `release/v<version>` or `release/n/<name>`. The PR body is rewritten canonically to `<jiraVersionUrl>\\n\\n<description>` \u2014 any prior manual edits to the body are overwritten. Both `version` and `description` are required for MCP calls (the picker/prompt are unreachable without a TTY). Empty `description` clears the description on both sides. Confirmation is auto-skipped for MCP, so the caller is responsible for gating.",inputSchema:{version:te.string().describe('Accepts a release version (e.g. "1.2.5") OR a release name (e.g. "checkout-redesign").'),description:te.string().describe("New description. Empty string clears the description.")},outputSchema:{version:te.string().describe("Release version"),branch:te.string().describe('Release branch name (e.g. "release/v1.2.5" or "release/n/checkout-redesign")'),jiraVersionUrl:te.string().describe("Jira fix version URL"),previousDescription:te.string().describe("The description before the update"),newDescription:te.string().describe("The description after the update"),changed:te.boolean().describe("Whether the description actually changed")},handler:Io});import{z as Zn}from"zod";var No={name:"infra-kit",type:"module",version:"0.1.107",description:"infra-kit",main:"dist/index.js",module:"dist/index.js",types:"dist/entry/index.d.ts",exports:{".":{types:"./dist/entry/index.d.ts",import:"./dist/index.js"}},bin:{"infra-kit":"dist/cli.js"},engines:{node:">=24.x"},scripts:{inspector:"npx @modelcontextprotocol/inspector node ./dist/mcp.js --debug",build:"pnpm run clean-artifacts && node ./scripts/build.js",check:"node ./dist/cli.js check","clean-artifacts":"rm -rf dist","clean-cache":"rm -rf node_modules/.cache .eslintcache tsconfig.tsbuildinfo .turbo .swc","prettier-fix":"pnpm exec prettier **/* --write --no-error-on-unmatched-pattern --log-level silent --ignore-path ../../../.prettierignore","prettier-check":"pnpm exec prettier **/* --check --no-error-on-unmatched-pattern --log-level silent --ignore-path ../../../.prettierignore","eslint-check":"pnpm exec eslint --cache --quiet --report-unused-disable-directives ./src","eslint-fix":"pnpm exec eslint --cache --quiet --report-unused-disable-directives ./src --fix","ts-check":"tsc --noEmit",test:"pnpm exec vitest run --reporter=minimal","test-watch":"pnpm exec vitest --watch --silent passed-only","test-ui":"pnpm exec vitest --ui --silent passed-only","test-report":"pnpm exec vitest run --coverage --silent passed-only",qa:"pnpm run prettier-check && pnpm run eslint-check && pnpm run ts-check && pnpm run test && echo \u2705 Success",fix:"pnpm run prettier-fix && pnpm run eslint-fix && pnpm run qa"},dependencies:{"@inquirer/checkbox":"^5.2.1","@inquirer/confirm":"^6.1.1","@inquirer/select":"^5.2.1","@modelcontextprotocol/sdk":"^1.29.0",commander:"^15.0.0",pino:"^10.3.1","pino-pretty":"^13.1.3",yaml:"^2.9.0",zod:"^4.4.3",zx:"^8.8.5"},devDependencies:{"@wl/eslint-config":"workspace:*","@wl/vitest-config":"workspace:*",esbuild:"^0.28.1",typescript:"^6.0.3"}};var Ao=async()=>{let e=No.version;a.info(e);let r={version:e};return{content:h(JSON.stringify(r,null,2)),structuredContent:r}},Rt=v({name:"version",description:"Print the installed infra-kit CLI version",inputSchema:{},outputSchema:{version:Zn.string().describe("Installed infra-kit CLI version (from package.json)")},handler:Ao});import ei from"@inquirer/checkbox";import kt from"@inquirer/confirm";import ri from"@inquirer/select";import ti from"node:process";import{z as oe}from"zod";import{$ as we}from"zx";import Oo from"node:fs/promises";import vr from"node:path";var Qe=async e=>{let{workspacePath:r,folderPaths:t}=e,o=vr.dirname(r),s;try{s=await Oo.readFile(r,"utf-8")}catch(d){throw new Error(`Cursor workspace file not found at ${r}: ${d.message}`)}let n;try{n=JSON.parse(s)}catch(d){throw new Error(`Failed to parse ${r} as JSON. Comments (JSONC) are not supported. ${d.message}`)}let i=n.folders??[],l=new Set(i.map(d=>vr.resolve(o,d.path))),c=[],p=[];for(let d of t){let u=vr.resolve(d);if(l.has(u)){p.push(d);continue}let f=vr.relative(o,u);i.push({path:f}),l.add(u),c.push(d)}return n.folders=i,await Oo.writeFile(r,`${JSON.stringify(n,null,2)}
50
50
  `,"utf-8"),{added:c,skipped:p}};import Qn from"node:fs/promises";import yr from"node:path";import _o from"node:fs/promises";import Rr from"node:path";var he=async e=>{let{workspacePath:r,folderPaths:t}=e,o=Rr.dirname(r),s;try{s=await _o.readFile(r,"utf-8")}catch(f){throw new Error(`Cursor workspace file not found at ${r}: ${f.message}`)}let n;try{n=JSON.parse(s)}catch(f){throw new Error(`Failed to parse ${r} as JSON. Comments (JSONC) are not supported. ${f.message}`)}let i=n.folders??[],l=new Set(t.map(f=>Rr.resolve(f))),c=new Set,p=i.filter(f=>{let w=Rr.resolve(o,f.path);return l.has(w)?(c.add(w),!1):!0});n.folders=p,await _o.writeFile(r,`${JSON.stringify(n,null,2)}
51
51
  `,"utf-8");let d=[],u=[];for(let f of t){let w=Rr.resolve(f);c.has(w)?d.push(f):u.push(f)}return{removed:d,notFound:u}};var yt=async e=>{let{workspacePath:r,worktreeDir:t,currentBranches:o}=e,s=yr.dirname(r),n=yr.resolve(`${t}/release`),i=await Qn.readFile(r,"utf-8"),c=JSON.parse(i).folders??[],p=new Set(o.map($=>yr.resolve(`${t}/${$}`))),d=[];for(let $ of c){let S=yr.resolve(s,$.path);(S===n||S.startsWith(`${n}/`))&&!p.has(S)&&d.push(S)}let u=[];d.length>0&&(u=(await he({workspacePath:r,folderPaths:d})).removed);let f=o.map($=>`${t}/${$}`),{added:w}=f.length>0?await Qe({workspacePath:r,folderPaths:f}):{added:[]};return{added:w,removed:u}};import Fo from"node:path";var z=(e,r)=>Fo.isAbsolute(e)?e:Fo.resolve(r,e);var oi="feature",si="release",Mo=["workspace","windows","none"],Do=async e=>{let{confirmedCommand:r,all:t,versions:o,cursor:s,githubDesktop:n,cmux:i}=e;m.start("worktrees-add");try{let l=await _("release"),c=await P(),p=`${c}${J}`;await jo(`${p}/${si}`),await jo(`${p}/${oi}`);let d=[];if(o)d=o.split(",").map(O=>q(V(O.trim())));else{let O=await b(),H=O.map(ne=>ne.branch);if(H.length===0)return a.info("\u2139\uFE0F No open release branches found"),m.print(),{content:h(JSON.stringify({createdWorktrees:[],count:0},null,2)),structuredContent:{createdWorktrees:[],count:0}};if(t)d=H;else{m.setInteractive();let ne=new Map(O.map(tr=>[tr.branch,k(tr.title)])),rr=await E();d=await ei({required:!0,message:"\u{1F33F} Select release branches",choices:I({branches:H,descriptions:rr,types:ne})})}}t?m.addOption("--all",!0):m.addOption("--versions",fe(d));let u=r?!0:await kt({message:"Are you sure you want to proceed with these worktree changes?"});r||m.setInteractive(),u||(a.info("Operation cancelled. Exiting..."),ti.exit(0)),r||m.addOption("--yes",!0);let f=await T(),w=f.ide?.provider==="cursor"?f.ide.config:void 0,$=s??w?.mode??await ri({message:"Cursor mode for created worktrees?",default:"workspace",choices:[{name:"Add to workspace file",value:"workspace",description:"Append each worktree as a folder in ide.config.workspaceConfigPath, then open the workspace"},{name:"Open separate windows",value:"windows",description:"Open each created worktree in its own Cursor window"},{name:"Skip",value:"none",description:"Do not open Cursor"}]});typeof s>"u"&&!w?.mode&&m.setInteractive(),m.addOption("--cursor",$);let S=n??f.worktrees?.openInGithubDesktop??await kt({message:"Open created worktrees in GitHub Desktop?"});typeof n>"u"&&f.worktrees?.openInGithubDesktop===void 0&&m.setInteractive(),S?m.addOption("--github-desktop",!0):m.addOption("--no-github-desktop",!0);let A=i??f.worktrees?.openInCmux??await kt({message:"Open created worktrees in cmux?"});typeof i>"u"&&f.worktrees?.openInCmux===void 0&&m.setInteractive(),A?m.addOption("--cmux",!0):m.addOption("--no-cmux",!0);let{branchesToCreate:Re}=ni({selectedReleaseBranches:d,currentWorktrees:l}),se=await ii(Re,p);if(ai(se),$==="workspace")if(!w?.workspaceConfigPath)a.warn("\u26A0\uFE0F Skipping Cursor: ide.config.workspaceConfigPath is not set in infra-kit config");else{let O=z(w.workspaceConfigPath,c),H=se.map(Ko=>`${p}/${Ko}`),{added:ne,skipped:rr}=await Qe({workspacePath:O,folderPaths:H}),tr=rr.length>0?` (${rr.length} already present)`:"";a.info(`\u2705 Added ${ne.length} folder(s) to ${O}${tr}`),await we`cursor ${O}`}else if($==="windows")for(let O of se)await we`cursor ${p}/${O}`;if(S)for(let O of se)await we`github ${p}/${O}`,await we`sleep 5`;if(A){let O=await F();for(let H of se){let ne=G({repoName:O,branch:H});await Xe({cwd:`${p}/${H}`,title:ne})}}m.print();let St={createdWorktrees:se,count:se.length};return{content:h(JSON.stringify(St,null,2)),structuredContent:St}}catch(l){throw a.error({error:l},"\u274C Error managing worktrees"),new g(l,{operation:"create worktrees",remediation:"verify branches don't already exist as worktrees: 'git worktree list'"})}},jo=async e=>{await we`mkdir -p ${e}`},ni=e=>{let{selectedReleaseBranches:r,currentWorktrees:t}=e,o=t.filter(n=>ae(n));return{branchesToCreate:r.filter(n=>!o.includes(n))}},ii=async(e,r)=>{let t=await Promise.allSettled(e.map(async s=>{let n=`${r}/${s}`;return await we`git worktree add ${n} ${s}`,await we({cwd:n})`pnpm install`,s})),o=[];for(let[s,n]of t.entries())if(n.status==="fulfilled")o.push(n.value);else{let i=e[s],l=new g(n.reason,{operation:`git worktree add for ${i}`,remediation:"check the branch name and that the parent dir is writable"});a.error({error:n.reason,msg:l.message})}return o},ai=e=>{if(e.length>0){a.info("\u2705 Created git worktrees:");for(let r of e)a.info(r);a.info("")}else a.info("\u2139\uFE0F No new git worktrees to create")},bt=v({name:"worktrees-add",description:'Create local git worktrees for release branches under the worktrees directory and run "pnpm install" in each. Mutates the local filesystem. When invoked via MCP, pass either "versions" (comma-separated) or all=true \u2014 the branch picker and "open in Cursor / GitHub Desktop / cmux" follow-up prompts are unreachable without a TTY, and the CLI confirmation is auto-skipped for MCP calls.',inputSchema:{all:oe.boolean().optional().describe('Add worktrees for every open release branch. Either "all" or "versions" must be provided for MCP calls (the interactive picker is unavailable without a TTY). Ignored if "versions" is provided.'),versions:oe.string().optional().describe('Comma-separated release versions or names to target (e.g. "1.2.5, 1.2.6" or "checkout-redesign, 1.2.5"). Either "versions" or all=true must be provided for MCP calls. Overrides "all" when set.'),cursor:oe.enum(Mo).optional().describe('Cursor open mode for created worktrees. "workspace" appends each worktree as a folder to "ide.config.workspaceConfigPath" in infra-kit config and opens the workspace. "windows" opens each worktree in its own Cursor window. "none" skips Cursor. Resolution order: this flag \u2192 "ide.config.mode" from infra-kit config \u2192 interactive prompt (CLI) / "none" (MCP, no TTY).'),githubDesktop:oe.boolean().optional().describe('Open each created worktree in GitHub Desktop. Resolution order: this flag \u2192 "worktrees.openInGithubDesktop" from infra-kit config \u2192 interactive prompt (CLI) / false (MCP, no TTY).'),cmux:oe.boolean().optional().describe('Open each created worktree in a new cmux workspace with a 3-pane layout (left-top, left-bottom, full-height right), all rooted at the worktree directory. Resolution order: this flag \u2192 "worktrees.openInCmux" from infra-kit config \u2192 interactive prompt (CLI) / false (MCP, no TTY).')},outputSchema:{createdWorktrees:oe.array(oe.string()).describe("List of created git worktree branches"),count:oe.number().describe("Number of git worktrees created")},handler:Do});import{z as Se}from"zod";var Lo=async()=>{let e=await _("release");if(e.length===0)return a.info("\u2139\uFE0F No active worktrees found"),{content:h(JSON.stringify({worktrees:[],count:0},null,2)),structuredContent:{worktrees:[],count:0}};let[r,t]=await Promise.all([b(),E()]),o=new Map(r.map(c=>[c.branch,k(c.title)])),s=e.flatMap(c=>{let p=y(c);if(!p)return[];let d=C(p),u=o.get(c)||"regular",f=t.get(M(p))||null;return[{version:d,type:u,description:f}]}),n=Math.max(...s.map(c=>c.version.length)),i=s.map(c=>{let p=Pe(c.version,c.type,n);return c.description?`${p} ${c.description}`:p});a.info("\u{1F33F} Active worktrees:"),a.info(`
52
52
  ${i.join(`