shippie
Version:
an extensible code review agent
141 lines (107 loc) • 51.4 kB
JavaScript
#!/usr/bin/env node
"use strict";var So=Object.create;var X=Object.defineProperty;var $o=Object.getOwnPropertyDescriptor;var xo=Object.getOwnPropertyNames;var Io=Object.getPrototypeOf,ko=Object.prototype.hasOwnProperty;var g=(e,t)=>()=>(e&&(t=e(e=0)),t);var we=(e,t)=>{for(var o in t)X(e,o,{get:t[o],enumerable:!0})},Ao=(e,t,o,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of xo(t))!ko.call(e,i)&&i!==o&&X(e,i,{get:()=>t[i],enumerable:!(r=$o(t,i))||r.enumerable});return e};var v=(e,t,o)=>(o=e!=null?So(Io(e)):{},Ao(t||!e||!e.__esModule?X(o,"default",{value:e,enumerable:!0}):o,e));var Re,n,b=g(()=>{"use strict";Re=require("tslog"),n=new Re.Logger({prettyLogTemplate:"{{logLevelName}} "})});var x=g(()=>{"use strict"});var Ee,D,Se=g(()=>{"use strict";Ee=require("tinyglobby"),D=async e=>{let t=await(0,Ee.glob)(e,{onlyFiles:!0});if(t.length===0)throw new Error(`No template file found for pattern: ${e}`);return t[0]}});var $e={};we($e,{configure:()=>Oo});var _,P,F,ee,Oo,_o,Fo,Lo,No,Mo,xe=g(()=>{"use strict";_=require("child_process"),P=v(require("fs")),F=v(require("path")),ee=require("@inquirer/prompts");x();b();Se();Oo=async e=>{e.platform==="github"&&await Lo(),e.platform==="gitlab"&&await No(),e.platform==="azdev"&&await Mo()},_o=async()=>{let e=await(0,ee.password)({message:"Please input your OpenAI API key (leave blank to use GitHub Models):",mask:"*"});if(!e){n.info("No API key provided, using GitHub Models.");return}return e},Fo=async()=>await(0,ee.password)({message:"Please input your OpenAI API key:"}),Lo=async()=>{let e=await _o(),o=await D(`**/templates/${e?"github-pr.yml":"github-pr-models.yml"}`),r=F.default.join(process.cwd(),".github","workflows");P.default.mkdirSync(r,{recursive:!0});let i=F.default.join(r,"shippie.yml");if(P.default.writeFileSync(i,P.default.readFileSync(o,"utf8"),"utf8"),n.info(`Created GitHub Actions workflow at: ${i}`),e)try{(0,_.execSync)("gh auth status || gh auth login",{stdio:"inherit"}),(0,_.execSync)(`gh secret set OPENAI_API_KEY --body=${String(e)}`),n.info("Successfully added the OPENAI_API_KEY secret to your GitHub repository.")}catch{n.error("It seems that the GitHub CLI is not installed or there was an error during authentication. Don't forget to add the OPENAI_API_KEY to the repo settings/Environment/Actions/Repository Secrets manually.")}},No=async()=>{let e=await D("**/templates/gitlab-pr.yml"),t=process.cwd(),o=F.default.join(t,".gitlab-ci.yml");P.default.writeFileSync(o,P.default.readFileSync(e,"utf8"),"utf8"),n.info(`Created GitLab CI at: ${o}`);let r=await Fo();if(!r){n.error("No API key provided. Please manually add the OPENAI_API_KEY secret to your GitLab CI/CD environment variables for your repository.");return}try{(0,_.execSync)("glab auth login",{stdio:"inherit"}),(0,_.execSync)(`glab variable set OPENAI_API_KEY ${String(r)}`),n.info(`Successfully added the OPENAI_API_KEY secret to your GitLab repository.
Please make sure you have set up your Gitlab access token before using this tool. Refer to the README (Gitlab CI section) for information on how to do this.`)}catch{n.error("It seems that the GitLab CLI is not installed or there was an error during authentication. Don't forget to add the OPENAI_API_KEY and the GITLAB_TOKEN to the repo's CI/CD Variables manually. Refer to the README (Gitlab CI section)for information on how to set up your access token.")}},Mo=async()=>{let e=await D("**/templates/azdev-pr.yml"),t=process.cwd(),o=F.default.join(t,"shippie.yaml");P.default.writeFileSync(o,P.default.readFileSync(e,"utf8"),"utf8"),n.info(`Created Azure DevOps Pipeline at: ${o}`),n.info("Please manually add the OPENAI_API_KEY and API_TOKEN secrets as encrypted variables in the UI.")}});var ke,Ie=g(()=>{ke={name:"shippie",version:"0.20.0",description:"an extensible code review agent",bin:{shippie:"./dist/index.js"},main:"./dist/index.js",types:"./dist/index.d.ts",repository:{type:"git",url:"https://github.com/mattzcarey/shippie"},scripts:{start:"bun run ./src/index.ts",review:"bun run ./src/index.ts review",configure:"bun run ./src/index.ts configure","test:e2e":"bun test src/specs/scenarios.test.ts --concurrent","test:unit":"bun test review configure common src/specs/runner src/specs/utils",build:"tsup","publish:build":"bun run build && bun publish --access public",check:"biome check .","check:types":"tsc --noEmit","check:fix":"biome check . --fix","check:fix:unsafe":"biome check . --unsafe --fix"},keywords:["code-review","shippie","review","model-context-protocol","mcp","chatgpt","gpt","openai","anthropic","sonnet 4","claude code","grok","perplexity","gpt-4.1","huggingface","ai","genai","sonnet"],author:"Matt Carey",license:"MIT",dependencies:{"@actions/github":"^5.1.1","@ai-sdk/anthropic":"^1.2.10","@ai-sdk/azure":"^1.3.22","@ai-sdk/google":"^1.2.11","@ai-sdk/openai":"^1.3.12","@inquirer/prompts":"^3.0.4",ai:"^4.3.15",dotenv:"^16.3.1","gray-matter":"^4.0.3",octokit:"^3.1.0",picocolors:"^1.1.1",picomatch:"^4.0.2",tinyglobby:"^0.2.10",tslog:"^4.8.2",ulid:"^3.0.0",yargs:"^17.7.2",zod:"^3.24.1"},devDependencies:{"@biomejs/biome":"^1.9.4","@types/bun":"^1.1.16","@types/picomatch":"^4.0.0","@types/yargs":"^17.0.33",autoevals:"^0.0.127",tsup:"^8.4.0",typescript:"^5.1.6"},files:["dist/*"]}});var Ae,H,Oe=g(()=>{"use strict";Ae=require("ulid");Ie();H=class{url="https://telemetry.shippie.dev/events";run_id;repo_id;args;provider;constructor(t,o){this.args=t,this.run_id=(0,Ae.ulid)(),this.provider=o,this.repo_id=this.provider.getRepoId()}startReview(){let t={event_type:"review_started",repo_id:this.repo_id,run_id:this.run_id,args:this.args,system:{platform:process.platform,arch:process.arch,node_version:process.version,shippie_version:ke.version}};fetch(this.url,{method:"POST",body:JSON.stringify(t)})}}});var B,_e,Fe,te=g(()=>{"use strict";b();B=()=>{let e=["GITHUB_SHA","BASE_SHA","GITHUB_TOKEN"],t=[];for(let o of e)process.env[o]||t.push(o);if(t.length>0)throw n.error(`Missing environment variables: ${t.join(", ")}`),new Error("One or more GitHub environment variables are not set");return{githubSha:process.env.GITHUB_SHA??"",baseSha:process.env.BASE_SHA??"",githubToken:process.env.GITHUB_TOKEN??""}},_e=()=>{let e=["CI_MERGE_REQUEST_DIFF_BASE_SHA","CI_PROJECT_ID","CI_MERGE_REQUEST_IID","CI_COMMIT_SHA","GITLAB_TOKEN","GITLAB_HOST"].filter(t=>!process.env[t]);if(e.length>0)throw n.error(`Missing environment variables: ${e.join(", ")}`),new Error("One or more GitLab environment variables are not set. Did you set up your Gitlab access token? Refer to the README (Gitlab CI section) on how to set it up.");return{mergeRequestBaseSha:process.env.CI_MERGE_REQUEST_DIFF_BASE_SHA??"",gitlabSha:process.env.CI_COMMIT_SHA??"",gitlabToken:process.env.GITLAB_TOKEN??"",projectId:process.env.CI_PROJECT_ID??"",mergeRequestIIdString:process.env.CI_MERGE_REQUEST_IID??"",gitlabHost:process.env.GITLAB_HOST??"https://gitlab.com"}},Fe=()=>{let e=["SYSTEM_PULLREQUEST_SOURCECOMMITID","BASE_SHA","API_TOKEN"],t=[];for(let o of e)process.env[o]||t.push(o);if(t.length>0)throw n.error(`Missing environment variables: ${t.join(", ")}`),new Error("One or more Azure DevOps environment variables are not set");return{azdevSha:process.env.SYSTEM_PULLREQUEST_SOURCECOMMITID??"",baseSha:process.env.BASE_SHA??"",azdevToken:process.env.API_TOKEN??""}}});var Le,Go,z,C,I=g(()=>{"use strict";Le=require("child_process"),Go=require("path");te();x();b();z=e=>{let t="--diff-filter=AMRT -U0";if(e==="github"){let{githubSha:o,baseSha:r}=B();return`git diff ${t} ${r} ${o}`}if(e==="gitlab"){let{gitlabSha:o,mergeRequestBaseSha:r}=_e();return`git diff ${t} ${r} ${o}`}if(e==="azdev"){let{azdevSha:o,baseSha:r}=Fe();return`git diff ${t} ${r} ${o}`}if(e==="local")return`git diff ${t} --cached`;throw new Error("Invalid CI platform")},C=()=>new Promise((e,t)=>{(0,Le.exec)("git rev-parse --show-toplevel",(o,r)=>{o?t(new Error(`Failed to find git root. Error: ${o.message}`)):e(r.trim())})})});var Ne,Me,Ue,oe,Do,Ge,De=g(()=>{"use strict";Ne=require("child_process"),Me=require("fs/promises"),Ue=require("path"),oe=require("process");b();I();Do=(e,t)=>{let o=e.split(`
`),r=new Map,i=null,s=/^diff --git a\/(.+) b\/(.+)$/,a=/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;for(let l of o){let c=l.match(s);if(c){i=(0,Ue.join)(t,c[2]),r.has(i)||r.set(i,[]);continue}if(!i)continue;let u=l.match(a);if(u){let m=u[2]?Number.parseInt(u[2],10):1,d=Number.parseInt(u[3],10),f=u[4]?Number.parseInt(u[4],10):1;if(f>0){let p=d+f-1,h=r.get(i);h&&h.push({start:d,end:p})}if(f===0&&m>0){let p=r.get(i);p&&p.push({start:d,end:d,isPureDeletion:!0})}}}return r},Ge=async e=>{try{let t=await C(),o=z(e);n.debug("Running combined diff command:",o);let r=await new Promise((u,m)=>{(0,Ne.exec)(o,{cwd:t,maxBuffer:1024*1024*10},(d,f,p)=>{if(d)return m(new Error(`Failed to execute git diff. Error: ${d.message}`));p?.toLowerCase().includes("error")?n.error("Git diff command stderr error:",p):p&&n.debug("Git diff command stderr info:",p),u(f)})});r.trim()||(n.warn("No changes found between refs. Ensure changes are staged (if local) or refs are correct (if CI)."),(0,oe.exit)(0));let i=Do(r,t);n.debug("Parsed changed lines map:",i);let s=Array.from(i.keys());s.length===0&&(n.warn("Parsed diff but found no file changes. Check diff command output."),(0,oe.exit)(0));let a=s.map(async u=>{try{let m=await(0,Me.readFile)(u,"utf8"),d=i.get(u)||[];return{fileName:u,fileContent:m,changedLines:d}}catch(m){return n.error(`Failed to read file content for ${u}: ${m}`),null}}),c=(await Promise.all(a)).filter(u=>u!==null);return c.length===0&&s.length>0&&n.warn("All changed files failed to be read. Check file paths and permissions."),n.debug("Final ReviewFile objects:",c),c}catch(t){throw n.error(`Error in getFilesWithChanges: ${t.message}`),new Error(`Failed to get files with changes: ${t.message}`)}}});var He,Be,ze,Ve,je,jo,Ye,Ke=g(()=>{"use strict";He=require("@ai-sdk/anthropic"),Be=require("@ai-sdk/azure"),ze=require("@ai-sdk/google"),Ve=require("@ai-sdk/openai"),je={azure:Be.createAzure,openai:Ve.createOpenAI,google:ze.createGoogleGenerativeAI,anthropic:He.createAnthropic},jo=(e,t)=>{let o=je[e];if(!o)throw new Error(`Unsupported provider: ${e}. The supported providers are: ${Object.keys(je).join(", ")}`);return e==="azure"&&process.env.AZURE_API_VERSION&&(t.apiVersion=process.env.AZURE_API_VERSION),t?o(t):o()},Ye=(e,t)=>{let o=e.split(":");if(o.length!==2)throw new Error('Invalid model string format. Expected "provider:modelName", e.g., "openai:gpt-4o"');let[r,i]=o;return jo(r,t??{})(i)}});var w,V,Y=g(()=>{"use strict";w={SUMMARY_TITLE:"## General Summary \u{1F3F4}\u200D\u2620\uFE0F",SEPARATOR:`
---
`,SIGN_OFF:"### Review powered by [Shippie \u{1F6A2}](https://github.com/mattzcarey/shippie) - The open source, extensible review agent.",CTA:`<details>
<summary>\u{1F680} Good review?</summary>
---
**Help us improve!** Your feedback and support make Shippie better for everyone.
\u2B50 **Quick win?** [Star the repo](https://github.com/mattzcarey/shippie) if you find it useful
\u{1F4A1} **Have ideas?** [Open a discussion](https://github.com/mattzcarey/shippie/discussions)
\u{1F6E0}\uFE0F **Wanna chat about agents?** [Send me a DM](https://x.com/mattzcarey)
---
*Sponsor the project* to preview features and influence the roadmap
\u{1F449} [YOUR COMPANY HERE](https://sustain.dev/sponsor/shippie) \u{1F448}
</details>`,TOOL_CALLS_TITLE:"\u{1F6E0}\uFE0F Tool Calls",TOKEN_USAGE_TITLE:"\u{1F4CA} Token Usage"},V=e=>`${w.SUMMARY_TITLE}
${e}${w.SEPARATOR}${w.SIGN_OFF}
${w.CTA}`});var qe,Ho,Bo,K,Je,q=g(()=>{"use strict";Y();qe=(e,t)=>t.reduce((r,i)=>({promptTokens:r.promptTokens+i.usage.promptTokens,completionTokens:r.completionTokens+i.usage.completionTokens,totalTokens:r.totalTokens+i.usage.totalTokens}),e),Ho=(e,t)=>({promptTokens:e.promptTokens+t.promptTokens,completionTokens:e.completionTokens+t.completionTokens,totalTokens:e.totalTokens+t.totalTokens}),Bo=e=>{let t=e.match(/Total for PR\s*\|\s*(\d+(?:,\d+)*)\s*\|\s*(\d+(?:,\d+)*)\s*\|\s*(\d+(?:,\d+)*)/);if(!t)return null;try{let o=Number.parseInt(t[1].replace(/,/g,""),10),r=Number.parseInt(t[2].replace(/,/g,""),10),i=Number.parseInt(t[3].replace(/,/g,""),10);return Number.isNaN(o)||Number.isNaN(r)||Number.isNaN(i)?null:{promptTokens:o,completionTokens:r,totalTokens:i}}catch{return null}},K=(e,t,o)=>{let r=o?Bo(o):null,i=r?Ho(e,r):e;return`
<details>
<summary>${w.TOOL_CALLS_TITLE}</summary>
${t.map(s=>`
#### \`${s.name}\`
\`\`\`json
${JSON.stringify(s.args,null,4)}
\`\`\`
<details>
<summary>Result:</summary>
\`\`\`json
${JSON.stringify(s.result,null,6)}
\`\`\`
</details>
`).join(`
`)}
</details>
<details>
<summary>${w.TOKEN_USAGE_TITLE}</summary>
| Usage | Prompt Tokens | Completion Tokens | Total Tokens |
|-------|--------------|------------------|-------------|
| Current Run | ${e.promptTokens.toLocaleString()} | ${e.completionTokens.toLocaleString()} | ${e.totalTokens.toLocaleString()} |
| Total for PR | ${i.promptTokens.toLocaleString()} | ${i.completionTokens.toLocaleString()} | ${i.totalTokens.toLocaleString()} |
</details>`},Je=(e,t,o)=>{let r=[];for(let i of t)for(let s of i.toolCalls){let a=i.toolResults.find(l=>l.toolCallId===s.toolCallId);s&&a&&r.push({args:s.args,name:s.toolName,result:a.result,retry:o})}return e.concat(r)}});var Qe,R,Ze,zo,We,Xe,et=g(()=>{"use strict";Qe=require("crypto"),R=require("@actions/github");te();Y();q();x();b();Ze=()=>{let{githubToken:e}=B();if(!e)throw new Error("GITHUB_TOKEN is not set");return e},zo=()=>{try{let e=Ze(),{payload:t,issue:o}=R.context;if(!t.pull_request){n.warn("Not a pull request context. Cannot get Octokit details.");return}let r=(0,R.getOctokit)(e),{owner:i,repo:s,number:a}=o;return{octokit:r,owner:i,repo:s,pull_number:a}}catch(e){n.error(`Failed to get Octokit details: ${e}`);return}},We=()=>{try{let e=Ze();if(!e){n.error("GitHub token not found. Cannot initialize Octokit.");return}return(0,R.getOctokit)(e)}catch(e){n.error(`Failed to get Octokit instance: ${e}`);return}},Xe=async()=>({postReviewComment:async t=>{let{filePath:o,comment:r,startLine:i,endLine:s}=t,a=zo();if(!a){n.error("Could not get Octokit repository details for posting review comment.");return}let{octokit:l,owner:c,repo:u,pull_number:m}=a;try{let f=(await l.rest.pulls.get({owner:c,repo:u,pull_number:m})).data.head.sha;if(i===s||!i){let{data:h}=await l.rest.pulls.createReviewComment({owner:c,repo:u,pull_number:m,commit_id:f,body:r,path:o,line:s});return h.html_url}let{data:p}=await l.rest.pulls.createReviewComment({owner:c,repo:u,pull_number:m,commit_id:f,body:r,path:o,line:s,start_line:i,start_side:"RIGHT",side:"RIGHT"});return p.html_url}catch(d){n.error(`Failed to post review comment on GitHub: ${JSON.stringify(d)}`);return}},postThreadComment:async t=>{let{comment:o}=t,r=We();if(!r)return;let{payload:i,issue:s}=R.context;if(!i.pull_request){n.warn("Not a pull request. Skipping commenting on PR thread...");return}let{owner:a,repo:l,number:c}=s,u=V(o);try{let{data:m}=await r.rest.issues.listComments({owner:a,repo:l,issue_number:c}),d=m.find(p=>p.body?.includes(w.SIGN_OFF));if(d){let{data:p}=await r.rest.issues.updateComment({owner:a,repo:l,comment_id:d.id,body:u});return p.html_url}let{data:f}=await r.rest.issues.createComment({owner:a,repo:l,issue_number:c,body:u});return f.html_url}catch(m){n.error(`Failed to post thread comment on GitHub: ${JSON.stringify(m)}`);return}},getPlatformOption:()=>"github",submitUsage:async(t,o)=>{let r=We();if(!r)return;let{payload:i,issue:s}=R.context;if(!i.pull_request){n.warn("Not a pull request. Skipping usage data submission...");return}let{owner:a,repo:l,number:c}=s;try{let{data:u}=await r.rest.issues.listComments({owner:a,repo:l,issue_number:c}),m=u.find(d=>d.body?.includes(w.SIGN_OFF));if(m){let d=m.body||"",f=K(t,o,d),p=d;if(p.includes(`<summary>${w.TOKEN_USAGE_TITLE}</summary>`))p=p.replace(new RegExp(`<details>\\s*<summary>${w.TOKEN_USAGE_TITLE.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}<\\/summary>[\\s\\S]*?<\\/details>`),f);else{let h=p.lastIndexOf(w.SEPARATOR+w.SIGN_OFF);h!==-1?p=`${p.substring(0,h)}
${f}${p.substring(h)}`:p=`${p}
${f}`}await r.rest.issues.updateComment({owner:a,repo:l,comment_id:m.id,body:p}),n.info("Usage data added to thread comment.")}else n.warn("No existing thread comment found to append usage data to.")}catch(u){n.error(`Failed to update thread comment with usage data: ${JSON.stringify(u)}`)}},getRepoId:()=>{try{let{owner:t,repo:o}=R.context.repo,r=`${t}/${o}`;return(0,Qe.createHash)("sha256").update(r).digest("hex").substring(0,32)}catch(t){return n.error(`Failed to get repo ID: ${t}`),"github_repo_anonymous"}}})});var tt,y,J,S,ot,rt,re,nt,it=g(()=>{"use strict";tt=require("crypto"),y=require("fs/promises"),J=require("os"),S=require("path");Y();q();I();x();b();ot=".shippie",rt=(0,S.join)((0,J.homedir)(),ot),re=(0,S.join)(rt,"repo_ids.json"),nt=async()=>{let e=await C(),o=`local_${new Date().toISOString().replace(/:/g,"-")}.md`,r=(0,S.join)(e,ot),i=(0,S.join)(r,"review"),s=(0,S.join)(i,o),a=(0,S.join)(i,".gitignore"),l=async()=>{try{await(0,y.mkdir)(i,{recursive:!0});try{await(0,y.access)(a)}catch{await(0,y.writeFile)(a,"*"),n.debug(`Created .gitignore file at ${a}`)}}catch(m){if(m?.code!=="EEXIST")throw n.error(`Failed to create review directory at ${i}: ${m}`),m;try{await(0,y.access)(a)}catch{await(0,y.writeFile)(a,"*"),n.debug(`Created .gitignore file at ${a}`)}}},c=async m=>{await l();try{await(0,y.appendFile)(s,m+J.EOL)}catch(d){throw n.error(`Failed to append to local review file ${s}: ${d}`),d}},u=async(m,d)=>{try{await(0,y.mkdir)(rt,{recursive:!0});try{let f=await(0,y.readFile)(re,"utf-8"),p=JSON.parse(f);if(p[m]===d)return;p[m]=d,await(0,y.writeFile)(re,JSON.stringify(p,null,2))}catch{let p={[m]:d};await(0,y.writeFile)(re,JSON.stringify(p,null,2))}}catch(f){n.error(`Failed to update repo ID mapping: ${f}`)}};return{postReviewComment:async m=>(await c(`${m.comment}
`),`Suggestion added to local review file: ${s}`),postThreadComment:async m=>{let d=V(m.comment);return await c(`${d}
`),`General comment added to local review file: ${s}`},getPlatformOption:()=>"local",submitUsage:async(m,d)=>{try{let f=`${K(m,d)}
`;await c(f),n.info(`Usage data added to local review file: ${s}`)}catch(f){n.error(`Failed to add usage data to local review file: ${f}`)}},getRepoId:()=>{try{let m=e,d=(0,tt.createHash)("sha256").update(m).digest("hex").substring(0,32);return u(m,d).catch(f=>n.error(`Failed to update repo ID mapping: ${f}`)),d}catch(m){return n.error(`Failed to get repo ID: ${m}`),"local_repo_anonymous"}}}}});var st,at=g(()=>{"use strict";x();b();et();it();st=async e=>{switch(e){case"github":return n.info("Using GitHub platform provider."),await Xe();case"gitlab":throw n.info("Using GitLab platform provider."),new Error("GitLab CI provider is not implemented yet.");case"azdev":throw n.info("Using Azure DevOps platform provider."),new Error("Azure DevOps platform provider is not implemented yet.");case"local":return n.info("Using Local platform provider."),await nt();default:throw new Error(`Unsupported CI environment specified: ${e}`)}}});var ne,ie,se,lt,k,ae=g(()=>{"use strict";ne=require("fs/promises"),ie=require("path"),se=require("ai"),lt=require("ai/mcp-stdio");I();b();k=class{clients={};config=null;async loadConfig(){try{let t=await C(),o={mcpServers:{}};try{let r=(0,ie.join)(t,".shippie","mcp.json"),i=await(0,ne.readFile)(r,"utf-8"),s=JSON.parse(i);o.mcpServers={...o.mcpServers,...s.mcpServers},n.info("Loaded MCP config from .shippie/mcp.json")}catch(r){n.debug(`No .shippie/mcp.json found or error reading it: ${r}`)}try{let r=(0,ie.join)(t,".cursor","mcp.json"),i=await(0,ne.readFile)(r,"utf-8"),s=JSON.parse(i);o.mcpServers={...o.mcpServers,...s.mcpServers},n.info("Loaded MCP config from .cursor/mcp.json")}catch(r){n.debug(`No .cursor/mcp.json found or error reading it: ${r}`)}if(Object.keys(o.mcpServers).length===0){n.warn("No MCP configuration found in .shippie/mcp.json or .cursor/mcp.json"),this.config=null;return}this.config=o}catch(t){n.error(`Failed to load MCP config: ${t}`),this.config=null}}async startClients(){if(!this.config){n.warn("Cannot start clients: MCP config not loaded");return}for(let[t,o]of Object.entries(this.config.mcpServers))try{if(o.command){let r=new lt.Experimental_StdioMCPTransport({command:o.command,args:o.args});this.clients[t]=await(0,se.experimental_createMCPClient)({transport:r}),n.info(`Started MCP client for ${t}`)}else o.url?this.clients[t]=await(0,se.experimental_createMCPClient)({transport:{type:"sse",url:o.url,headers:o.headers}}):(n.error(`Invalid MCP server configuration for ${t}`),this.clients[t]=null)}catch(r){n.error(`Failed to create MCP client for ${t}: ${r}`),this.clients[t]=null}}async getTools(){let t={};for(let[o,r]of Object.entries(this.clients))if(r)try{t[o]=await r.tools()}catch(i){n.error(`Failed to get tools from ${o}: ${i}`),t[o]={}}return t}async closeClients(){for(let[t,o]of Object.entries(this.clients))if(o)try{await o.close()}catch(r){n.error(`Failed to close MCP client ${t}: ${r}`)}this.clients={}}}});var ct,mt,ut,L,Vo,pt,dt=g(()=>{"use strict";ct=require("child_process"),mt=require("util"),ut=require("ai"),L=require("zod"),Vo=(0,mt.promisify)(ct.exec),pt=(0,ut.tool)({description:"Execute a bash command and get its output. Use this tool to run commands such as git commands to step through changes made to get to the current state of the codebase or to run tests or build scripts.",parameters:L.z.object({command:L.z.string().describe("The bash command to execute"),cwd:L.z.string().describe("The working directory for the command. Default is the current directory.").default("."),timeout:L.z.number().describe("Timeout in milliseconds before the command is killed").default(1e4)}),execute:async({command:e,cwd:t,timeout:o})=>{try{let r=["rm -rf","mkfs","dd",":(){","wget","curl"];for(let l of r)if(e.includes(l))return`Error: Potentially dangerous command detected: ${l}`;let{stdout:i,stderr:s}=await Vo(e,{cwd:t,timeout:o,maxBuffer:1024*1024}),a="";return i&&(a+=`STDOUT:
${i}
`),s&&(a+=`STDERR:
${s}
`),a||(a="Command executed successfully with no output."),a}catch(r){return r instanceof Error?"code"in r&&"killed"in r&&r.killed?`Command timed out after ${o}ms`:`Error executing command: ${r.message}`:"Unknown error executing command"}}})});var ft,E,Yo,gt,ht=g(()=>{"use strict";ft=require("ai"),E=require("zod"),Yo=1e4,gt=(0,ft.tool)({description:"Make HTTP requests to external APIs and websites. Returns the response body as text.",parameters:E.z.object({url:E.z.string().url().describe("The URL to fetch data from"),method:E.z.enum(["GET","POST","PUT","DELETE","PATCH","HEAD"]).default("GET").describe("HTTP method to use"),headers:E.z.record(E.z.string()).optional().describe("HTTP headers to include in the request"),body:E.z.string().optional().describe("Request body (for POST, PUT, PATCH)"),timeout:E.z.number().default(Yo).describe("Request timeout in milliseconds")}),execute:async({url:e,method:t,headers:o,body:r,timeout:i})=>{try{let a=new URL(e).hostname;if(a==="localhost"||a==="127.0.0.1"||a.startsWith("192.168.")||a.startsWith("10.")||a.startsWith("172.16."))return"Error: Requests to internal networks are not allowed for security reasons";let l=new AbortController,c=setTimeout(()=>l.abort(),i),u={method:t,headers:o,signal:l.signal};r&&["POST","PUT","PATCH"].includes(t)&&(u.body=r);let m=await fetch(e,u);return clearTimeout(c),await m.text()}catch(s){return s instanceof Error?s.name==="AbortError"?`Request timed out after ${i}ms`:`Error: ${s.message}`:"Unknown error during fetch"}}})});var le,bt,yt,T,wt,vt=g(()=>{"use strict";le=v(require("path")),bt=require("ai"),yt=require("tinyglobby"),T=require("zod"),wt=(0,bt.tool)({description:"Find files matching glob patterns. Useful for locating files with specific extensions or naming patterns.",parameters:T.z.object({patterns:T.z.array(T.z.string()).describe("One or more glob patterns to match files"),cwd:T.z.string().describe("The directory to search in. Default is the current directory.").default("."),excludePatterns:T.z.array(T.z.string()).describe("Glob patterns to exclude from results").optional(),includeHidden:T.z.boolean().describe("Whether to include hidden files (starting with .)").default(!1),onlyFiles:T.z.boolean().describe("Whether to only include files (not directories)").default(!0),maxResults:T.z.number().describe("Maximum number of results to return").default(100)}),execute:async({patterns:e,cwd:t,excludePatterns:o,includeHidden:r,onlyFiles:i,maxResults:s})=>{try{let a={cwd:t,dot:r,onlyFiles:i,ignore:o||[]},l=[];for(let p of e){let h=await(0,yt.glob)(p,a);l.push(...h)}let c=[...new Set(l)],u=c.slice(0,s),m=c.length>s;if(u.length===0)return`No files found matching the patterns: ${e.join(", ")}`;let d={};for(let p of u){let h=le.default.dirname(p);d[h]||(d[h]=[]),d[h].push(le.default.basename(p))}let f=[];for(let[p,h]of Object.entries(d)){p==="."?f.push(`./ (${h.length} files):`):f.push(`${p}/ (${h.length} files):`);for(let Z of h)f.push(` ${Z}`);f.push("")}return m&&f.push(`... and ${c.length-s} more files (limited to ${s} results)`),`Found ${c.length} files matching the patterns: ${e.join(", ")}
${f.join(`
`)}`}catch(a){return`Error finding files: ${a instanceof Error?a.message:String(a)}`}}})});var Tt,Pt,Ct,Rt,$,Et,St=g(()=>{"use strict";Tt=v(require("fs/promises")),Pt=v(require("path")),Ct=require("ai"),Rt=require("tinyglobby"),$=require("zod"),Et=(0,Ct.tool)({description:"Search for a pattern in files. Returns matching lines with line numbers and file paths.",parameters:$.z.object({pattern:$.z.string().describe("The pattern to search for (supports JavaScript regex)"),path:$.z.string().describe("Directory or file path to search in. Default is the current directory.").default("."),glob:$.z.string().describe('Glob pattern for filtering files (e.g., "**/*.ts" for TypeScript files)').default("**/*.*"),ignoreCase:$.z.boolean().describe("Whether to ignore case when matching").default(!1),maxResults:$.z.number().describe("Maximum number of results to return").default(30)}),execute:async({pattern:e,path:t,glob:o,ignoreCase:r,maxResults:i})=>{try{let s=await(0,Rt.glob)(o,{cwd:t,onlyFiles:!0,dot:!1}),a=new RegExp(e,r?"i":""),l=[],c=0;for(let u of s){if(c>=i)break;try{let m=Pt.default.resolve(t,u),f=(await Tt.default.readFile(m,"utf-8")).split(`
`);for(let p=0;p<f.length&&!(c>=i);p++){let h=f[p];a.test(h)&&(l.push(`${u}:${p+1}: ${h.trim()}`),c++)}}catch{}}return l.length===0?`No matches found for pattern "${e}" in ${s.length} files.`:`Found ${l.length} matches for pattern "${e}":
${l.join(`
`)}`}catch(s){return`Error searching files: ${s instanceof Error?s.message:String(s)}`}}})});var ce,me,$t,N,xt,It,kt=g(()=>{"use strict";ce=v(require("fs/promises")),me=v(require("path")),$t=require("ai"),N=require("zod"),xt=(0,$t.tool)({description:"List files and directories at a specified path. Helpful for exploring the repository structure.",parameters:N.z.object({path:N.z.string().describe("The absolute path to list contents from.").default("."),recursive:N.z.boolean().describe("Whether to list contents recursively").default(!1),includeHidden:N.z.boolean().describe("Whether to include hidden files (starting with .)").default(!1)}),execute:async({path:e,recursive:t,includeHidden:o})=>{try{let r=await ce.default.readdir(e,{withFileTypes:!0}),i=o?r:r.filter(a=>!a.name.startsWith(".")),s=[];for(let a of i){let l=me.default.join(e,a.name);if(a.isDirectory()){if(s.push(`${a.name}/`),t){let u=(await It(l,t,o)).split(`
`).filter(m=>m.trim()).map(m=>` ${m}`).join(`
`);u&&s.push(u)}}else s.push(`${a.name}`)}return s.join(`
`)}catch(r){return`Error listing directory: ${r instanceof Error?r.message:String(r)}`}}}),It=async(e,t,o)=>{try{let r=await ce.default.readdir(e,{withFileTypes:!0}),i=o?r:r.filter(a=>!a.name.startsWith(".")),s=[];for(let a of i){let l=me.default.join(e,a.name);if(a.isDirectory()){if(s.push(`${a.name}/`),t){let u=(await It(l,t,o)).split(`
`).filter(m=>m.trim()).map(m=>` ${m}`).join(`
`);u&&s.push(u)}}else s.push(`${a.name}`)}return s.join(`
`)}catch(r){return`Error listing directory: ${r instanceof Error?r.message:String(r)}`}}});var At,Ot,ue=g(()=>{"use strict";At={".js":"JavaScript",".ts":"TypeScript",".py":"Python",".sh":"Shell",".go":"Go",".rs":"Rust",".tsx":"TypeScript",".jsx":"JavaScript",".dart":"Dart",".php":"PHP",".cpp":"C++",".h":"C++",".c":"C",".cxx":"C++",".hpp":"C++",".hxx":"C++",".cs":"C#",".rb":"Ruby",".kt":"Kotlin",".kts":"Kotlin",".java":"Java",".vue":"Vue",".tf":"Terraform",".hcl":"Terraform",".swift":"Swift",".md":"Markdown",".mdx":"Markdown",".mdc":"Markdown (Cursor rules)",".txt":"Text",".json":"JSON",".yaml":"YAML",".yml":"YAML",".toml":"TOML",".html":"HTML"},Ot=new Set(["**/*.d.ts","dist/**","node_modules/**","**/package-lock.json","**/*.lock"])});var _t,W,pe=g(()=>{"use strict";_t=require("path");ue();W=(e,t="Unknown Language")=>{let o=(0,_t.extname)(e);return At[o]||t}});var Ft,Lt,M,Nt,Mt=g(()=>{"use strict";Ft=require("fs"),Lt=require("ai"),M=require("zod");pe();Nt=(0,Lt.tool)({description:"Read the current state of a file or part of a file. You should use this tool to gather specific context. You should use this in conjunction with the read_diff tool to get the full picture of the changes. You should read several lines before and after the changes. You may need to go back and read more lines.",parameters:M.z.object({path:M.z.string().describe("The absolute path to the file to read"),startLine:M.z.number().optional().describe("The line number to start reading from."),endLine:M.z.number().optional().describe("The line number to end reading at.")}),execute:async({path:e,startLine:t,endLine:o})=>{try{let i=(await Ft.promises.readFile(e,"utf-8")).split(`
`),s=200,a=t?t-1:0,l=o?o-1:a+s,u=i.slice(a,l+1).join(`
`),m=`Here is the file excerpt you requested. NOTE that unless an EOF is shown, the file is not complete. File path: ${e}
Lines Selected: ${a+1} to ${l+1}:
`,d=W(e,"");return`${m}\`\`\`${d.toLowerCase()}
${u}\`\`\``}catch(r){return r instanceof Error?r.message.includes("ENOENT")||r.message.includes("no such file")?`Error: File not found at path '${e}'. The file does not exist or the path is incorrect. Use the 'ls' tool to explore the directory structure and find the correct path.`:r.message.includes("EACCES")?`Error: Permission denied when trying to read file '${e}'. You don't have read access to this file.`:r.message.includes("EISDIR")?`Error: '${e}' is a directory, not a file. Use the 'ls' tool to list directory contents instead.`:`Error reading file '${e}': ${r.message}`:`Unknown error occurred while reading file '${e}'`}}})});var Ut,de,Gt,Dt=g(()=>{"use strict";Ut=require("ai"),de=require("zod"),Gt=(0,Ut.tool)({description:"Use the tool to think about something. It will not obtain new information or make any changes to the repository, but just log the thought. Use it when complex reasoning or brainstorming is needed. For example, if you explore the repo and discover a potential bug, call this tool to brainstorm all the unexpected behaviour the code might exhibit, and assess which suggested changes are needed. Alternatively, if you receive some test results, call this tool to brainstorm ways to fix the failing tests. Finally, if you are unsure if a change in the code base warrants a suggestion from you, call this tool to understand the impact of the change.",parameters:de.z.object({thought:de.z.string().describe("Your thoughts")}),execute:async({thought:e})=>e})});var jt,Ht,fe,Bt,zt=g(()=>{"use strict";jt=require("child_process"),Ht=require("ai"),fe=require("zod");I();b();Bt=e=>(0,Ht.tool)({description:"Generate a diff for a file. This tool shows changes made to a file which should be reviewed. Use in conjunction with read_file to read the current state of a file.",parameters:fe.z.object({path:fe.z.string().describe("The absolute path to the file to generate a diff for")}),execute:async({path:t})=>{try{let o=e.getPlatformOption(),i=`${z(o)} -- "${t}"`;return await new Promise((s,a)=>{(0,jt.exec)(i,{maxBuffer:1024*1024*10},(l,c,u)=>{if(l&&l.code!==0&&l.code!==1)return n.error(`Git diff error: ${l.message}`),a(l);u&&n.warn(`Git diff stderr: ${u}`),s(c||"No changes detected")})})}catch(o){return n.error(`Failed to generate diff: ${o}`),`Error generating diff: ${o instanceof Error?o.message:String(o)}`}}})});var G,U,Ko,Vt,Yt=g(()=>{"use strict";G=require("ai"),U=require("zod");b();ae();ge();Ko=(0,G.tool)({description:"Submit a report to the main agent. This is how you finish your work.",parameters:U.z.object({report:U.z.string().describe("The report to submit to the main agent")})}),Vt=(e,t)=>(0,G.tool)({description:"Spawn a sub-agent with a specific goal that runs autonomously with access to all available tools. The sub-agent will work towards the goal and return a structured report with findings and recommendations. Use this subagent to run token heavy tasks which can be run async from the main agent.",parameters:U.z.object({goal:U.z.string().describe("The specific goal or task for the sub-agent to accomplish. Include as much context as possible to help the sub-agent understand the goal.")}),execute:async({goal:o})=>{try{n.info(`Spawning sub-agent with goal: ${o}`);let r=e,i=new k;await i.loadConfig(),await i.startClients();let s=await Q({model:r,mcpClientManager:i}),a={submit_report:Ko,...s};n.debug("Sub-agent tools available:",Object.keys(a));let l=`You are an autonomous sub-agent with the following goal: ${o}
You have access to various tools to help you accomplish this goal. Work systematically towards the goal, using the available tools as needed.
CRITICAL REQUIREMENT: You MUST end your work by providing a comprehensive final report that includes:
## Summary
Brief overview of what was accomplished
## Findings
Detailed findings, discoveries, or analysis results
## Recommendations
Any suggestions, improvements, or next steps (if applicable)
## Conclusion
Final assessment and key takeaways
Submit the report to the main agent using the 'submit_report' tool.`,c=await(0,G.generateText)({model:r,prompt:l,tools:a,maxSteps:t});return await i.closeClients(),c.toolCalls.length>0?c.toolCalls[0].args.report:(n.error("Sub-agent completed execution but produced no report output"),n.error("Sub-agent result finishReason:",c.finishReason),n.debug("Sub-agent result steps:",c.steps),n.info("Sub-agent result text:",c.text),c.text?(n.info("Sub-agent result text:",c.text),c.text):"Sub-agent completed execution but produced no report output")}catch(r){return n.error("Error in sub-agent execution:",r),r instanceof Error?`Error executing sub-agent: ${r.message}`:"Unknown error occurred while executing sub-agent"}}})});var Kt,he,qt,Jt=g(()=>{"use strict";Kt=require("ai"),he=require("zod");b();qt=e=>(0,Kt.tool)({description:"Posts the final review report as a general comment on the PR. Call this tool when the review is complete.",parameters:he.z.object({report:he.z.string().describe("The final review report formatted in markdown. It should contain a brief but specific overview of the changes, and potential edge cases or issues that the reviewer should be aware of.")}),execute:async({report:t})=>{try{let o={comment:t},r=await e.postThreadComment(o);return n.info(`Report posted via tool. Result: ${r??"No URL"}`),r?`Report posted successfully: ${r}`:"Report posted, but no URL returned."}catch(o){return n.error(`Failed to post report via tool: ${o}`),`Error posting report: ${o}`}}})});var Wt,A,Qt,Zt=g(()=>{"use strict";Wt=require("ai"),A=require("zod");b();Qt=e=>(0,Wt.tool)({description:"Posts a specific suggestion or comment on a particular file line or range. You should ONLY do this on files with actionable problems. If a file is fine you CANNOT use this tool otherwise the user will not trust you again. If there are multiple changes to make in line numbers which are close to each other, you should make all the changes in ONE comment. In that case the line numbers will encompass all the lines that need to be changed.",parameters:A.z.object({filePath:A.z.string().describe("The absolute path to the file you are suggesting changes to."),comment:A.z.string().describe("The review comment for the actionable change or changes. It should be in the format of: {{ a short description of why the user MUST make the change }} ```suggestion\n{{ direct replacement for the line or lines }}\n```"),startLine:A.z.number().optional().describe("The line number to start the comment at."),endLine:A.z.number().optional().describe("The line number to end the comment at.")}),execute:async({filePath:t,comment:o,startLine:r,endLine:i})=>{let s=`### Suggestion for \`${t}\`
${o}`;try{let a=await e.postReviewComment({filePath:t,comment:s,startLine:r,endLine:i});return n.info(`Suggestion for ${t} posted via tool. Result: ${a??"No URL"}`),a?`Suggestion posted successfully: ${a}`:"Suggestion posted, but no URL returned."}catch(a){return n.error(`Failed to post suggestion for ${t} via tool: ${a}`),`Error posting suggestion: ${a instanceof Error?a.message:String(a)}`}}})});var qo,Q,ge=g(()=>{"use strict";dt();ht();vt();St();kt();Mt();Dt();zt();Yt();Jt();Zt();qo=()=>({read_file:Nt,fetch:gt,glob:wt,grep:Et,ls:xt,bash:pt,thinking:Gt}),Q=async(e={})=>{let t={...qo()};if(e.platformProvider&&(t.read_diff=Bt(e.platformProvider),t.suggest_change=Qt(e.platformProvider),t.submit_summary=qt(e.platformProvider)),e.model&&e.includeSubAgent&&e.maxSteps&&(t.spawn_subagent=Vt(e.model,e.maxSteps)),e.mcpClientManager){let o={};for(let[r,i]of Object.entries(await e.mcpClientManager.getTools()))for(let[s,a]of Object.entries(i))o[`${r}-${s}`]=a;Object.assign(t,o)}return t}});var Xt,eo,to=g(()=>{"use strict";Xt=require("ai");b();eo=async(e,t,o,r,i)=>(0,Xt.generateText)({model:t,prompt:e,tools:r,maxSteps:o,onStepFinish:s=>{n.debug("Step finished:",s),s.toolCalls.some(l=>l.toolName==="submit_summary")&&i&&(n.debug("Detected submit_summary tool usage in step, triggering callback."),i())}})});var oo,ro=g(()=>{"use strict";q();ae();ge();b();to();oo=async(e,t,o,r,i=3)=>{n.info(`Running agentic review (max retries: ${i})...`);let s=new k;await s.loadConfig(),await s.startClients();let a=await Q({platformProvider:o,model:t,mcpClientManager:s,includeSubAgent:!0,maxSteps:r});n.debug("Tools:",Object.keys(a));let l=null,c=e,u="",m=!1,d={promptTokens:0,completionTokens:0,totalTokens:0},f=[];for(let p=1;p<=i;p++){if(n.info(`Attempt ${p}/${i}...`),m=!1,l=await eo(c,t,r,a,()=>{m=!0}),d=qe(d,l.steps),f=Je(f,l.steps,p),m){n.info(`Agent submitted summary on attempt ${p} (detected via callback).`);break}if(n.warn(`Agent did not submit summary on attempt ${p}.`),p<i){let h=l.toolResults.map(ye=>`Tool Result (${ye.toolName}): ${JSON.stringify(ye.result)}`).join(`
`),Z=l.text?`
Final Text: ${l.text}`:"";u+=`
--- Attempt ${p} Context ---
${h}${Z}
--- End Attempt ${p} Context ---`,c=`${e}${u}
Please continue the task based on previous attempts and ensure you call submit_summary.`,n.info(`Preparing for attempt ${p+1}.`)}}if(!l)throw new Error("Agent did not produce any result.");return m?await o.submitUsage(d,f):n.error(`Agent failed to submit summary after ${i} attempts. Proceeding anyway.`),await s.closeClients(),l.text}});var no,be,O,io,so,Jo,Wo,Qo,Zo,Xo,er,ao,lo,co,mo=g(()=>{"use strict";no=require("crypto"),be=require("fs/promises"),O=require("path"),io=v(require("gray-matter")),so=require("tinyglobby");b();Jo=[".cursor/rules/*.{mdc,md}",".shippie/rules/*.{mdc,md}",".windsurfrules/*.{mdc,md}",".windsurf/rules/*.{mdc,md}","clinerules/*.{mdc,md}","AGENTS.md","AGENT.md","CLAUDE.md"],Wo=["todo.md",".same/todos.md","CONTRIBUTING.md"],Qo=(e,t=3)=>e.split(`
`).filter(r=>r.trim()).slice(0,t).join(" ").slice(0,200),Zo=e=>{if(e){if(Array.isArray(e))return e.filter(t=>typeof t=="string");if(typeof e=="string")return e.split(",").map(t=>t.trim()).filter(Boolean)}},Xo=e=>(0,no.createHash)("sha256").update(e.trim()).digest("hex").slice(0,16),er=e=>{let t=new Set,o=new Map,r=[];for(let i of e){let s=Xo(i.content);if(t.has(s)){n.debug(`Skipping duplicate rule content from ${i.path}`);continue}let a=i.description.toLowerCase().trim(),l=o.get(a);if(l){let c=i.path.split("/").length,u=l.path.split("/").length;if(c>u){let m=r.findIndex(d=>d.path===l.path);m!==-1&&(r[m]=i,o.set(a,i),n.debug(`Replacing rule from ${l.path} with more specific rule from ${i.path}`))}else{n.debug(`Skipping rule from ${i.path}, already have similar rule from ${l.path}`);continue}}else o.set(a,i),r.push(i);t.add(s)}return r},ao=async e=>{let t=[];for(let o of Jo)try{let r=await(0,so.glob)(o,{cwd:e,absolute:!1});for(let i of r)try{let s=(0,O.join)(e,i),a=await(0,be.readFile)(s,"utf-8"),l=(0,O.relative)(e,s),c=(0,io.default)(a),u={description:c.data.description,globs:Zo(c.data.globs),alwaysApply:c.data.alwaysApply},m=i.endsWith(".mdc")?"mdc":"md",d=u.description||Qo(c.content||a);t.push({path:l,type:m,frontmatter:Object.keys(c.data).length>0?u:void 0,description:d,content:c.content||a})}catch(s){n.debug(`Failed to read rules file ${i}:`,s)}}catch(r){n.debug(`Failed to glob pattern ${o}:`,r)}return er(t)},lo=async e=>{let t=[];for(let o of Wo)try{let r=(0,O.join)(e,o),i=await(0,be.readFile)(r,"utf-8"),s=(0,O.relative)(e,r);t.push({path:s,content:i.trim()})}catch{n.debug(`Important file ${o} not found or couldn't be read`)}return t},co=(e,t)=>{if(e.length===0&&t.length===0)return"";let o=`
// Project Context
`;if(e.length>0){let r=e.filter(s=>!s.frontmatter?.alwaysApply),i=e.filter(s=>s.frontmatter?.alwaysApply);if(r.length>0){o+=`See these rules files for more info:
`;for(let s of r)o+=`- ${s.path}: ${s.description}
`,s.frontmatter?.globs?.length&&(o+=` Applies to: ${s.frontmatter.globs.join(", ")}
`);o+=`
`}if(i.length>0){o+=`Always-apply rules (full content):
`;for(let s of i)o+=`
## ${s.path}
${s.content}
`}}if(t.length>0){o+=`Important project documentation:
`;for(let r of t)o+=`
## ${r.path}
${r.content}
`}return o}});var uo,tr,or,po,fo,go=g(()=>{"use strict";uo=require("path"),tr=(e,t)=>{let o={name:"root",children:{}};for(let r of e){let s=(0,uo.relative)(t,r.fileName).split("/"),a=o;for(let[l,c]of s.entries())c&&(a.children[c]||(a.children[c]={name:c,children:{}}),a=a.children[c],l===s.length-1&&(a.isEndOfPath=!0,a.changedLines=r.changedLines,a.fullPath=r.fileName))}return o},or=e=>!e||e.length===0?"":(e.sort((t,o)=>t.start-o.start),e.map(t=>t.isPureDeletion?`${t.start} (deletion)`:t.start===t.end?`${t.start}`:`${t.start}-${t.end}`).join(", ")),po=(e,t="",o=!0)=>{let r="";if(e.name!=="root"){let s=`${t}${o?"\u2514\u2500\u2500 ":"\u251C\u2500\u2500 "}${e.name}`;if(e.isEndOfPath){let a=or(e.changedLines);a&&(s+=`: ${a}`)}r+=`${s}
`}let i=Object.keys(e.children).sort();for(let[s,a]of i.entries()){let l=e.children[a],c=s===i.length-1,u=e.name==="root"?"":`${t}${o?" ":"\u2502 "}`;r+=po(l,u,c)}return r},fo=(e,t)=>{let o=tr(e,t||"");return`Files changed for this review (paths relative to root, includes line ranges):
${po(o,"",!0).trim()}
---
`}});var ho,bo=g(()=>{"use strict";ho="You are an expert {ProgrammingLanguage} developer agent. Your task is to review a pull request. Keep going until the user's query is completely resolved before ending your turn. Only terminate when you are sure the review is complete.\nUse tools to investigate the file content, codebase structure, or the impact of changes and to gather information. You MUST plan before each action or tool call, and reflect on the outcomes of previous steps. Act as a human reviewer.\n\n// Goal\nReview the changed code in the provided files and produce a concise summary describing the intent of the overall changes in the pull request. You MUST use the tools provided to you to complete your task.\n\n// Understanding File Changes\n- Line numbers followed by \"(deletion)\" indicate places where content was removed without any replacement. These are pure deletions in the file.\n- Regular line numbers or ranges show where content was added or modified. The line numbers are referenced from the new file version.\n\n// Rules for code review\n- **Functionality:** Ensure changes do not break existing functionality. Use tools to investigate if needed.\n- **Testing:** Verify that changes are adequately tested. Suggest new tests using `new_file` if coverage is lacking.\n- **Best Practices:** Ensure changes follow clean code principles, are DRY (Don't Repeat Yourself), and are concise. Follow SOLID principles where applicable.\n- **Risk Assessment:** Evaluate changed code using a risk score from 1 (low risk) to 5 (high risk). Flag API keys or secrets present in plain text immediately as highest risk (5).\n- **Readability & Performance:** Comment on improving readability and performance where applicable.\n- **Focus:** Only review lines of code which have been changed (added '+' or removed '-'). Ignore context lines. Do not praise or complement anything. Only focus on the negative aspects.\n- **Brevity:** Keep feedback brief, concise, and accurate. If multiple similar issues exist, comment only on the most critical. Feedback should be in {ReviewLanguage}.\n- **Confidence:** Be aware of unfamiliar libraries/techniques. Only comment if confident there's a problem. Do not comment on breaking functions down unless it's a huge problem.\n- **Examples:** Include brief, correct code snippets for suggested changes using `suggest_change`. Use ordered lists for multiple suggestions. Use the same programming language as the file under review.\n\n// Workflow\n1. **Gather context on the project:** Try to understand what type of project you are reviewing. Use tools like `ls`, `grep` and `glob` to gather context on the project. Find any rules files such as `.cursor/rules/*` or `CLAUDE.md` to understand the coding style, and project best practices.\n2. **Analyze code changes:** See the changed files. Use the `read_file` and `read_diff` along with `ls`, `grep` and `glob` tools to gather context around the changed lines to understand their impact or intent. Pay attention to surrounding functions, classes, and imports.\n3. **Assess Impact & Intent:** Determine what the changes aim to achieve and evaluate potential side effects. Use the `bash` tool to run tests or linters if necessary to verify correctness and style.\n4. (Optional) **Run the application:** If you think it's a good idea, you can use the `bash` tool to run the application to see what it does and if it is working as expected. Note: you may have to install the dependencies first. Use the project tooling where possible.\n5. **Identify Issues:** Based on the rules below, identify specific problems or areas for improvement in the changed code.\n6. **Deliver Feedback:** Use the `suggest_change` tool to provide specific feedback on code changes with problems. Feedback should be provide direct and concise and only on critical NEGATIVE changes.\n7. **Summarize Intent:** Synthesize your understanding into a brief summary of the pull request's purpose.\n8. **Final Output:** Finish your task by calling `submit_summary` with the summary text described in step 7.\n\nREMEMBER: you must call `submit_summary` with your summary text. Return only a simple success message if you have called `submit_summary`. Otherwise, return a simple error message describing why you did not call `submit_summary`."});var yo,wo=g(()=>{"use strict";I();mo();go();bo();pe();yo=async(e,t,o)=>{let r=await C(),i=e.length>0?W(e[0].fileName):"default",s=ho.replace("{ProgrammingLanguage}",i).replace("{ReviewLanguage}",t),a=fo(e,r),[l,c]=await Promise.all([ao(r),lo(r)]),u=co(l,c),m=o?`
// Custom Instructions
${o}
`:"";return`${s}${m}${u}
${a}`}});var vo,To,Po=g(()=>{"use strict";vo=v(require("picomatch"));ue();To=(e,t)=>{let o=t??Array.from(Ot);if(o.length===0)return e;let r=(0,vo.default)(o);return e.filter(i=>!r(i.fileName))}});var Co={};we(Co,{review:()=>rr});var rr,Ro=g(()=>{"use strict";Oe();De();Ke();at();b();ro();wo();Po();rr=async e=>{n.debug("Review started."),n.debug(`Model used: ${e.modelString}`),n.debug(`Review language: ${e.reviewLanguage}`),n.debug(`Platform: ${e.platform}`),n.debug(`Max steps: ${e.maxSteps}`),n.debug(`Telemetry: ${e.telemetry}`),n.debug(`Ignored file globs: ${e.ignore}`),n.debug(`Custom instructions: ${e.customInstructions}`);let t=e.baseUrl?.trim(),o=t?{baseURL:t}:{};n.debug(`Model Options: ${JSON.stringify(o)}`);let r=await st(e.platform);n.debug("Platform provider:",r),e.telemetry&&(n.info("Shippie collects anonymous usage data to help improve the product. You can opt out by setting --telemetry=false when running Shippie."),new H(e,r).startReview());let i=await Ge(e.platform);n.debug(`Found ${i.length} changed files.`);let s=To(i,e.ignore);if(n.debug(`Filtered ${s.length} files to review.`),s.length===0){n.info("No file to review, finishing review now.");return}n.debug(`Files to review after filtering: ${s.map(c=>c.fileName)}`);let a=Ye(e.modelString,o),l=await yo(s,e.reviewLanguage,e.customInstructions);n.debug("Prompt:",l);try{let c=await oo(l,a,r,e.maxSteps);n.debug("Review response:",c),n.info("Review completed successfully.")}catch(c){n.error("Review failed with error:",c),process.exit(1)}}});var Eo=v(require("dotenv"));var ve=v(require("dotenv")),Te=v(require("yargs")),Pe=require("yargs/helpers");ve.default.config();var Ce=async()=>(0,Te.default)((0,Pe.hideBin)(process.argv)).command("configure","Configure the tool",e=>e.option("platform",{description:"Specifies for which platform ('github', 'gitlab' or 'azdev') the project should be configured for. Defaults to 'github'.",choices:["github","gitlab","azdev"],type:"string",default:"github"})).command("review","Review code changes",e=>e.option("modelString",{description:'The model to use for generating the review. Defaults to "openai:gpt-4o-mini".',type:"string",default:"openai:gpt-4.1-mini"}).option("reviewLanguage",{description:"Specifies the target natural language for translation",type:"string",default:"English"}).option("platform",{description:"Platform type",choices:["github","gitlab","azdev","local"],