devin-bugs 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +33 -21
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  // @bun
3
- var y=Object.defineProperty;var f=(z)=>z;function u(z,Q){this[z]=f.bind(null,Q)}var h=(z,Q)=>{for(var X in Q)y(z,X,{get:Q[X],enumerable:!0,configurable:!0,set:u.bind(Q,X)})};var x=(z,Q)=>()=>(z&&(Q=z(z=0)),Q);import{homedir as _}from"node:os";import{join as K}from"node:path";var q="https://app.devin.ai/api",P="https://app.devin.ai",o,d,L,Ez,A=300;var I=x(()=>{o=K(_(),".config","devin-bugs"),d=K(_(),".cache","devin-bugs"),L=K(o,"token.json"),Ez=K(d,"browser-profile")});var b={};h(b,{getToken:()=>F,forceReauth:()=>S,clearAuth:()=>Wz});import{existsSync as D,mkdirSync as c,readFileSync as s,writeFileSync as t,unlinkSync as a}from"node:fs";import{dirname as r}from"node:path";import{createServer as e}from"node:http";import{execFile as zz}from"node:child_process";function w(z){let Q=z.replace(/-/g,"+").replace(/_/g,"/");return Buffer.from(Q,"base64").toString("utf-8")}function Qz(z){let Q=z.split(".");if(Q.length!==3)throw Error("Invalid JWT format");let X=JSON.parse(w(Q[1]));if(typeof X.exp!=="number")throw Error("JWT missing exp claim");return X.exp*1000}function Xz(z){if(!D(z))c(z,{recursive:!0})}function Yz(){try{if(!D(L))return null;let z=s(L,"utf-8"),Q=JSON.parse(z);if(!Q.accessToken||!Q.expiresAt)return null;return Q}catch{return null}}function N(z,Q){Xz(r(L));let X=Qz(z),Z={accessToken:z,obtainedAt:Date.now(),expiresAt:X,...Q&&Object.keys(Q).length>0?{auth0Cache:Q}:{}};return t(L,JSON.stringify(Z,null,2)),Z}async function Zz(z){if(!z.auth0Cache)return null;let Q=null,X=null,Z=null;for(let[Y,J]of Object.entries(z.auth0Cache)){if(!Y.startsWith("@@auth0spajs@@"))continue;let G=Y.split("::"),W=G[1],V=G[2];try{let B=JSON.parse(J);if(B.body?.refresh_token){Q=B.body.refresh_token,X=W??null,Z=V??null;break}}catch{continue}}if(!Q||!X)return null;let $=null;try{let Y=JSON.parse(w(z.accessToken.split(".")[1]));if(typeof Y.iss==="string")$=new URL(Y.iss).hostname}catch{}if(!$)return console.error("\x1B[33m▸ Could not determine Auth0 domain for token refresh.\x1B[0m"),null;try{let Y=`https://${$}/oauth/token`;console.error(`\x1B[33m▸ Refreshing token via ${Y}\x1B[0m`);let J=await fetch(Y,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({grant_type:"refresh_token",client_id:X,refresh_token:Q,...Z?{audience:Z}:{}})});if(!J.ok){let W=await J.text().catch(()=>"");return console.error(`\x1B[33m▸ Refresh failed (${J.status}): ${W.slice(0,100)}\x1B[0m`),null}let G=await J.json();if(!G.access_token)return null;if(G.refresh_token&&z.auth0Cache)for(let[W,V]of Object.entries(z.auth0Cache)){if(!W.startsWith("@@auth0spajs@@"))continue;try{let B=JSON.parse(V);if(B.body?.refresh_token){B.body.refresh_token=G.refresh_token,B.body.access_token=G.access_token,z.auth0Cache[W]=JSON.stringify(B);break}}catch{continue}}return console.error(`\x1B[32m✓ Token refreshed!\x1B[0m
4
- `),G.access_token}catch(Y){return console.error(`\x1B[33m▸ Refresh error: ${Y instanceof Error?Y.message:Y}\x1B[0m`),null}}function E(){try{if(D(L))a(L)}catch{}}function $z(z){return z.expiresAt-Date.now()>A*1000}function Jz(z){let Q=process.platform==="darwin"?{cmd:"open",args:[z]}:process.platform==="win32"?{cmd:"cmd",args:["/c","start","",z]}:{cmd:"xdg-open",args:[z]};zz(Q.cmd,Q.args,(X)=>{if(X)console.error("\x1B[33m▸ Could not open browser automatically.\x1B[0m"),console.error(` Open this URL manually: ${z}
5
- `)})}function Mz(z){return`<!DOCTYPE html>
3
+ var p=Object.defineProperty;var i=(z)=>z;function l(z,Q){this[z]=i.bind(null,Q)}var d=(z,Q)=>{for(var X in Q)p(z,X,{get:Q[X],enumerable:!0,configurable:!0,set:l.bind(Q,X)})};var j=(z,Q)=>()=>(z&&(Q=z(z=0)),Q);import{homedir as P}from"node:os";import{join as L}from"node:path";var _="https://app.devin.ai/api",w="https://app.devin.ai",e,zz,V,mz,R=300;var D=j(()=>{e=L(P(),".config","devin-bugs"),zz=L(P(),".cache","devin-bugs"),V=L(e,"token.json"),mz=L(zz,"browser-profile")});var v={};d(v,{getToken:()=>U,forceReauth:()=>H,clearAuth:()=>Fz});import{existsSync as N,mkdirSync as Qz,readFileSync as Xz,writeFileSync as Yz,unlinkSync as Zz}from"node:fs";import{dirname as $z}from"node:path";import{createServer as Jz}from"node:http";import{execFile as Wz}from"node:child_process";function S(z){let Q=z.replace(/-/g,"+").replace(/_/g,"/");return Buffer.from(Q,"base64").toString("utf-8")}function Gz(z){let Q=z.split(".");if(Q.length!==3)throw Error("Invalid JWT format");let X=JSON.parse(S(Q[1]));if(typeof X.exp!=="number")throw Error("JWT missing exp claim");return X.exp*1000}function Mz(z){if(!N(z))Qz(z,{recursive:!0})}function Bz(){try{if(!N(V))return null;let z=Xz(V,"utf-8"),Q=JSON.parse(z);if(!Q.accessToken||!Q.expiresAt)return null;return Q}catch{return null}}function I(z,Q){Mz($z(V));let X=Gz(z),Z={accessToken:z,obtainedAt:Date.now(),expiresAt:X,...Q&&Object.keys(Q).length>0?{auth0Cache:Q}:{}};return Yz(V,JSON.stringify(Z,null,2)),Z}async function Kz(z){if(!z.auth0Cache)return null;let Q=null,X=null,Z=null;for(let[$,W]of Object.entries(z.auth0Cache)){if(!$.startsWith("@@auth0spajs@@"))continue;let M=$.split("::"),G=M[1],B=M[2];try{let K=JSON.parse(W);if(K.body?.refresh_token){Q=K.body.refresh_token,X=G??null,Z=B??null;break}}catch{continue}}if(!Q||!X)return null;let J=null;try{let $=JSON.parse(S(z.accessToken.split(".")[1]));if(typeof $.iss==="string")J=new URL($.iss).hostname}catch{}if(!J)return console.error("\x1B[33m▸ Could not determine Auth0 domain for token refresh.\x1B[0m"),null;try{let $=`https://${J}/oauth/token`;console.error(`\x1B[33m▸ Refreshing token via ${$}\x1B[0m`);let W=await fetch($,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({grant_type:"refresh_token",client_id:X,refresh_token:Q,...Z?{audience:Z}:{}})});if(!W.ok){let G=await W.text().catch(()=>"");return console.error(`\x1B[33m▸ Refresh failed (${W.status}): ${G.slice(0,100)}\x1B[0m`),null}let M=await W.json();if(!M.access_token)return null;if(M.refresh_token&&z.auth0Cache)for(let[G,B]of Object.entries(z.auth0Cache)){if(!G.startsWith("@@auth0spajs@@"))continue;try{let K=JSON.parse(B);if(K.body?.refresh_token){K.body.refresh_token=M.refresh_token,K.body.access_token=M.access_token,z.auth0Cache[G]=JSON.stringify(K);break}}catch{continue}}return console.error(`\x1B[32m✓ Token refreshed!\x1B[0m
4
+ `),M.access_token}catch($){return console.error(`\x1B[33m▸ Refresh error: ${$ instanceof Error?$.message:$}\x1B[0m`),null}}function C(){try{if(N(V))Zz(V)}catch{}}function Vz(z){return z.expiresAt-Date.now()>R*1000}function Lz(z){let Q=process.platform==="darwin"?{cmd:"open",args:[z]}:process.platform==="win32"?{cmd:"cmd",args:["/c","start","",z]}:{cmd:"xdg-open",args:[z]};Wz(Q.cmd,Q.args,(X)=>{if(X)console.error("\x1B[33m▸ Could not open browser automatically.\x1B[0m"),console.error(` Open this URL manually: ${z}
5
+ `)})}function Uz(z){return`<!DOCTYPE html>
6
6
  <html lang="en">
7
7
  <head>
8
8
  <meta charset="utf-8">
@@ -67,7 +67,7 @@ var y=Object.defineProperty;var f=(z)=>z;function u(z,Q){this[z]=f.bind(null,Q)}
67
67
  <div class="step">
68
68
  <div class="step-num">1</div>
69
69
  <div class="step-text">
70
- <a href="${P}" target="_blank" rel="noopener">
70
+ <a href="${w}" target="_blank" rel="noopener">
71
71
  Open app.devin.ai</a> and log in with GitHub
72
72
  </div>
73
73
  </div>
@@ -115,32 +115,44 @@ var y=Object.defineProperty;var f=(z)=>z;function u(z,Q){this[z]=f.bind(null,Q)}
115
115
  poll();
116
116
  </script>
117
117
  </body>
118
- </html>`}function C(){return new Promise((z,Q)=>{let X=null,Z=e(($,Y)=>{if(Y.setHeader("Access-Control-Allow-Origin","*"),Y.setHeader("Access-Control-Allow-Methods","POST, GET, OPTIONS"),Y.setHeader("Access-Control-Allow-Headers","Content-Type"),$.method==="OPTIONS"){Y.writeHead(204),Y.end();return}if($.method==="GET"&&$.url==="/status"){Y.writeHead(200,{"Content-Type":"application/json"}),Y.end(JSON.stringify({received:X!==null}));return}if($.method==="POST"&&$.url==="/callback"){let J="";$.on("data",(G)=>J+=G.toString()),$.on("end",()=>{try{let G=JSON.parse(J);if(typeof G.token==="string"&&G.token.length>20){X=G.token;let W=G.auth0Cache;if(W&&Object.keys(W).length>0)console.error(`\x1B[36m▸ Captured ${Object.keys(W).length} Auth0 cache entries\x1B[0m`);Y.writeHead(200,{"Content-Type":"application/json"}),Y.end(JSON.stringify({ok:!0})),setTimeout(()=>{Z.close(),z({token:X,auth0Cache:W,server:Z})},500);return}}catch{}Y.writeHead(400,{"Content-Type":"application/json"}),Y.end(JSON.stringify({error:"Invalid token"}))});return}if($.method==="GET"&&($.url==="/"||$.url==="/login")){let J=Z.address().port;Y.writeHead(200,{"Content-Type":"text/html"}),Y.end(Mz(J));return}Y.writeHead(404),Y.end("Not found")});Z.listen(0,"127.0.0.1",()=>{let Y=Z.address().port;console.error("\x1B[33m▸ Opening browser for Devin login...\x1B[0m"),console.error(` Local server: http://localhost:${Y}
119
- `),Jz(`http://localhost:${Y}`),setTimeout(()=>{if(!X)Z.close(),Q(Error("Login timed out after 5 minutes."))},300000)}),Z.on("error",Q)})}async function F(z){let Q=process.env.DEVIN_TOKEN;if(Q&&Q.length>0)return Q;if(!z?.noCache){let $=Yz();if($&&$z($))return $.accessToken;if($?.auth0Cache){let Y=await Zz($);if(Y)return N(Y,$.auth0Cache),Y}}let{token:X,auth0Cache:Z}=await C();return console.error(`\x1B[32m✓ Authentication successful!\x1B[0m
120
- `),N(X,Z),X}async function S(){E();let{token:z,auth0Cache:Q}=await C();return console.error(`\x1B[32m✓ Authentication successful!\x1B[0m
121
- `),N(z,Q),z}function Wz(){E(),console.error("Cleared cached token.")}var H=x(()=>{I()});import{parseArgs as jz}from"util";var p=/(?:https?:\/\/)?github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)/,l=/^([^/#]+)\/([^/#]+)#(\d+)$/,i=/^([^/#]+)\/([^/#]+)\/pull\/(\d+)$/,n=/(?:https?:\/\/)?app\.devin\.ai\/review\/([^/]+)\/([^/]+)\/pull\/(\d+)/;function R(z){let Q=z.match(p)??z.match(n)??z.match(l)??z.match(i);if(!Q)throw Error(`Invalid PR reference: ${z}
122
- Expected: owner/repo#123 or https://github.com/owner/repo/pull/123`);let[,X,Z,$]=Q;return{owner:X,repo:Z,number:parseInt($,10),prPath:`github.com/${X}/${Z}/pull/${$}`}}H();I();class U extends Error{constructor(){super("Authentication expired. Re-authenticating...");this.name="AuthExpiredError"}}class O extends Error{status;body;constructor(z,Q){super(`Devin API error ${z}: ${Q}`);this.status=z;this.body=Q;this.name="ApiError"}}async function Gz(z,Q){let X=`${q}/${z}`,Z=await fetch(X,{headers:{Authorization:`Bearer ${Q}`,Accept:"application/json"}});if(!Z.ok){if(Z.status===401||Z.status===403)throw new U;let $=await Z.text().catch(()=>"");throw new O(Z.status,$)}return Z.json()}async function j(z,Q){return Gz(`pr-review/digest?pr_path=${encodeURIComponent(z)}`,Q)}function Bz(z){if(!z)return null;let Q=z.match(/<!--\s*devin-review-comment\s*(\{.+\})\s*-->/);if(!Q?.[1])return null;try{let X=JSON.parse(Q[1]);return{id:String(X.id??""),file_path:String(X.file_path??""),start_line:typeof X.start_line==="number"?X.start_line:0,end_line:typeof X.end_line==="number"?X.end_line:0,side:X.side==="LEFT"?"LEFT":"RIGHT"}}catch{return null}}function Vz(z){if(z.startsWith("\uD83D\uDD34"))return"severe";if(z.startsWith("\uD83D\uDFE1"))return"warning";if(z.startsWith("\uD83D\uDFE2"))return"info";return"info"}function Lz(z){return z.match(/\*\*(.+?)\*\*/)?.[1]?.trim()??z.split(`
123
- `)[0]?.slice(0,120).trim()??""}function Kz(z){return z.split(`
118
+ </html>`}function E(){return new Promise((z,Q)=>{let X=null,Z=Jz((J,$)=>{if($.setHeader("Access-Control-Allow-Origin","*"),$.setHeader("Access-Control-Allow-Methods","POST, GET, OPTIONS"),$.setHeader("Access-Control-Allow-Headers","Content-Type"),J.method==="OPTIONS"){$.writeHead(204),$.end();return}if(J.method==="GET"&&J.url==="/status"){$.writeHead(200,{"Content-Type":"application/json"}),$.end(JSON.stringify({received:X!==null}));return}if(J.method==="POST"&&J.url==="/callback"){let W="";J.on("data",(M)=>W+=M.toString()),J.on("end",()=>{try{let M=JSON.parse(W);if(typeof M.token==="string"&&M.token.length>20){X=M.token;let G=M.auth0Cache;if(G&&Object.keys(G).length>0)console.error(`\x1B[36m▸ Captured ${Object.keys(G).length} Auth0 cache entries\x1B[0m`);$.writeHead(200,{"Content-Type":"application/json"}),$.end(JSON.stringify({ok:!0})),setTimeout(()=>{Z.close(),z({token:X,auth0Cache:G,server:Z})},500);return}}catch{}$.writeHead(400,{"Content-Type":"application/json"}),$.end(JSON.stringify({error:"Invalid token"}))});return}if(J.method==="GET"&&(J.url==="/"||J.url==="/login")){let W=Z.address().port;$.writeHead(200,{"Content-Type":"text/html"}),$.end(Uz(W));return}$.writeHead(404),$.end("Not found")});Z.listen(0,"127.0.0.1",()=>{let $=Z.address().port;console.error("\x1B[33m▸ Opening browser for Devin login...\x1B[0m"),console.error(` Local server: http://localhost:${$}
119
+ `),Lz(`http://localhost:${$}`),setTimeout(()=>{if(!X)Z.close(),Q(Error("Login timed out after 5 minutes."))},300000)}),Z.on("error",Q)})}async function U(z){let Q=process.env.DEVIN_TOKEN;if(Q&&Q.length>0)return Q;if(!z?.noCache){let J=Bz();if(J&&Vz(J))return J.accessToken;if(J?.auth0Cache){let $=await Kz(J);if($)return I($,J.auth0Cache),$}}let{token:X,auth0Cache:Z}=await E();return console.error(`\x1B[32m✓ Authentication successful!\x1B[0m
120
+ `),I(X,Z),X}async function H(){C();let{token:z,auth0Cache:Q}=await E();return console.error(`\x1B[32m✓ Authentication successful!\x1B[0m
121
+ `),I(z,Q),z}function Fz(){C(),console.error("Cleared cached token.")}var x=j(()=>{D()});import{parseArgs as Rz}from"util";import{execFileSync as n}from"node:child_process";var o=/(?:https?:\/\/)?github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)/,c=/^([^/#]+)\/([^/#]+)#(\d+)$/,s=/^([^/#]+)\/([^/#]+)\/pull\/(\d+)$/,t=/^#?(\d+)$/,a=/(?:https?:\/\/)?app\.devin\.ai\/review\/([^/]+)\/([^/]+)\/pull\/(\d+)/;function r(){try{let z=n("git",["remote","get-url","origin"],{encoding:"utf-8",timeout:3000,stdio:["pipe","pipe","pipe"]}).trim(),Q=z.match(/github\.com\/([^/]+)\/([^/.]+)/);if(Q)return{owner:Q[1],repo:Q[2]};let X=z.match(/github\.com:([^/]+)\/([^/.]+)/);if(X)return{owner:X[1],repo:X[2]}}catch{}return null}function A(z){let Q=z.match(o)??z.match(a)??z.match(c)??z.match(s);if(Q){let[,Z,J,$]=Q;return{owner:Z,repo:J,number:parseInt($,10),prPath:`github.com/${Z}/${J}/pull/${$}`}}let X=z.match(t);if(X){let Z=r();if(Z){let J=X[1];return{owner:Z.owner,repo:Z.repo,number:parseInt(J,10),prPath:`github.com/${Z.owner}/${Z.repo}/pull/${J}`}}throw Error(`Cannot resolve PR #${X[1]} — not in a GitHub repo.
122
+ `+`Use the full form: owner/repo#${X[1]}`)}throw Error(`Invalid PR reference: ${z}
123
+ Expected: owner/repo#123, #123 (in a git repo), or https://github.com/owner/repo/pull/123`)}x();D();class F extends Error{constructor(){super("Authentication expired. Re-authenticating...");this.name="AuthExpiredError"}}class O extends Error{status;body;constructor(z,Q){super(`Devin API error ${z}: ${Q}`);this.status=z;this.body=Q;this.name="ApiError"}}async function T(z,Q){let X=`${_}/${z}`,Z=await fetch(X,{headers:{Authorization:`Bearer ${Q}`,Accept:"application/json"}});if(!Z.ok){if(Z.status===401||Z.status===403)throw new F;let J=await Z.text().catch(()=>"");throw new O(Z.status,J)}return Z.json()}async function q(z,Q){return T(`pr-review/digest?pr_path=${encodeURIComponent(z)}`,Q)}async function k(z,Q){return T(`pr-review/jobs?pr_path=${encodeURIComponent(z)}`,Q)}function b(z){if(z.jobs.length===0)return{status:"no_review",message:"No Devin review has been triggered for this PR."};let Q=z.jobs.sort((Z,J)=>new Date(J.created_at).getTime()-new Date(Z.created_at).getTime())[0],X=["lifeguard","groups","copy_detection","display_info"];if(Q.status==="completed"){if(Q.versions[Q.versions.length-1]?.metadata.is_finished)return{status:"completed",message:"Devin review complete.",stages:{completed:X,total:X}}}if(Q.status==="running"||Q.status==="completed"&&Q.versions.length>0){let Z=Q.versions[Q.versions.length-1],J=Z?.metadata.completed??[],$={lifeguard:"Bug detection",groups:"File grouping",copy_detection:"Copy detection",display_info:"Finalizing"};if(!Z?.metadata.is_finished){let W=X.find((G)=>!J.includes(G));return{status:"running",message:`Devin review in progress: ${W?$[W]??W:"Processing"} (${J.length}/${X.length} stages)`,stages:{completed:J,total:X}}}return{status:"completed",message:"Devin review complete.",stages:{completed:J,total:X}}}if(Q.status==="failed")return{status:"failed",message:"Devin review failed."};return{status:"running",message:"Devin review pending...",stages:{completed:[],total:X}}}function Oz(z){if(!z)return null;let Q=z.match(/<!--\s*devin-review-comment\s*(\{.+\})\s*-->/);if(!Q?.[1])return null;try{let X=JSON.parse(Q[1]);return{id:String(X.id??""),file_path:String(X.file_path??""),start_line:typeof X.start_line==="number"?X.start_line:0,end_line:typeof X.end_line==="number"?X.end_line:0,side:X.side==="LEFT"?"LEFT":"RIGHT"}}catch{return null}}function Dz(z){if(z.startsWith("\uD83D\uDD34"))return"severe";if(z.startsWith("\uD83D\uDFE1"))return"warning";if(z.startsWith("\uD83D\uDFE2"))return"info";return"info"}function Iz(z){return z.match(/\*\*(.+?)\*\*/)?.[1]?.trim()??z.split(`
124
+ `)[0]?.slice(0,120).trim()??""}function Nz(z){return z.split(`
124
125
  `).slice(1).join(`
125
- `).trim()}function Fz(z){return z.match(/(?:recommendation|suggested fix|fix):\s*(.+?)(?:\n\n|\n#+|\n🔴|\n🟡|$)/is)?.[1]?.trim()??""}function Uz(z,Q){if(z.startsWith("BUG_"))return"lifeguard-bug";if(z.startsWith("ANALYSIS_")||z.startsWith("INFO_"))return"lifeguard-analysis";let X=Q.toLowerCase();if(X.includes("potential bug")||X.includes("\uD83D\uDD34")||X.includes("bug:")||X.includes("race condition")||X.includes("vulnerability")||X.includes("double-charge")||X.includes("sql injection"))return"lifeguard-bug";return"lifeguard-analysis"}function Oz(z,Q){let X=Bz(Q.hidden_header),Z=Q.body??"";if(!Z&&!X)return null;let $=X?.id??String(Q.devin_review_id??""),Y=Uz($,Z);return{filePath:X?.file_path??"",startLine:X?.start_line??null,endLine:X?.end_line??null,side:X?.side??"RIGHT",title:Lz(Z),description:Kz(Z),severity:Vz(Z),recommendation:Fz(Z),needsInvestigation:Z.toLowerCase().includes("needs investigation"),type:Y,isResolved:z.is_resolved,isOutdated:z.is_outdated,htmlUrl:Q.html_url??null}}function Iz(z){return z.devin_review_id!=null||z.hidden_header?.includes("devin-review-comment")===!0||z.author?.login==="devin-ai-integration"||z.author?.login==="devin-ai-integration[bot]"||z.author?.login==="devin-ai[bot]"}function v(z,Q){let X=[];for(let Z of z.review_threads){if(!Q?.includeResolved&&Z.is_resolved)continue;if(!Q?.includeOutdated&&Z.is_outdated)continue;for(let $ of Z.comments){if(!Iz($))continue;let Y=Oz(Z,$);if(Y){X.push(Y);break}}}if(!Q?.includeAnalysis)return X.filter((Z)=>Z.type==="lifeguard-bug");return X}var M={reset:"\x1B[0m",bold:"\x1B[1m",dim:"\x1B[2m",red:"\x1B[31m",green:"\x1B[32m",yellow:"\x1B[33m",cyan:"\x1B[36m",white:"\x1B[37m",bgRed:"\x1B[41m",bgYellow:"\x1B[43m",bgBlue:"\x1B[44m"};function Nz(z){let Q=z.toUpperCase();switch(z.toLowerCase()){case"severe":case"critical":return`${M.bgRed}${M.white}${M.bold} ${Q} ${M.reset}`;case"warning":return`${M.bgYellow}${M.bold} ${Q} ${M.reset}`;default:return`${M.bgBlue}${M.white} ${Q} ${M.reset}`}}function Dz(z){if(z==="lifeguard-bug")return`${M.red}${M.bold}BUG${M.reset}`;return`${M.cyan}${M.bold}INFO${M.reset}`}function Sz(z){if(!z.filePath)return"";let Q=`${M.cyan}${z.filePath}${M.reset}`;if(z.startLine==null)return Q;let X=z.endLine!=null&&z.endLine!==z.startLine?`${M.dim}:${z.startLine}-${z.endLine}${M.reset}`:`${M.dim}:${z.startLine}${M.reset}`;return`${Q}${X}`}function Hz(z,Q,X){let Z=" ".repeat(Q),$=z.split(/\s+/),Y=[],J="";for(let G of $)if(J.length+G.length+1>X-Q)Y.push(Z+J),J=G;else J=J?`${J} ${G}`:G;if(J)Y.push(Z+J);return Y.join(`
126
- `)}function T(z,Q){let X=[],Z=z.filter((J)=>J.type==="lifeguard-bug").length,$=z.filter((J)=>J.type==="lifeguard-analysis").length,Y=[];if(Z>0)Y.push(`${M.red}${M.bold}${Z} bug${Z===1?"":"s"}${M.reset}`);if($>0)Y.push(`${M.cyan}${$} suggestion${$===1?"":"s"}${M.reset}`);if(Y.length===0)return X.push(`
127
- ${M.green}${M.bold}No unresolved bugs${M.reset} in ${M.dim}${Q.owner}/${Q.repo}#${Q.number}${M.reset}
126
+ `).trim()}function Hz(z){return z.match(/(?:recommendation|suggested fix|fix):\s*(.+?)(?:\n\n|\n#+|\n🔴|\n🟡|$)/is)?.[1]?.trim()??""}function xz(z,Q){if(z.startsWith("BUG_"))return"lifeguard-bug";if(z.startsWith("ANALYSIS_")||z.startsWith("INFO_"))return"lifeguard-analysis";let X=Q.toLowerCase();if(X.includes("potential bug")||X.includes("\uD83D\uDD34")||X.includes("bug:")||X.includes("race condition")||X.includes("vulnerability")||X.includes("double-charge")||X.includes("sql injection"))return"lifeguard-bug";return"lifeguard-analysis"}function qz(z,Q){let X=Oz(Q.hidden_header),Z=Q.body??"";if(!Z&&!X)return null;let J=X?.id??String(Q.devin_review_id??""),$=xz(J,Z);return{filePath:X?.file_path??"",startLine:X?.start_line??null,endLine:X?.end_line??null,side:X?.side??"RIGHT",title:Iz(Z),description:Nz(Z),severity:Dz(Z),recommendation:Hz(Z),needsInvestigation:Z.toLowerCase().includes("needs investigation"),type:$,isResolved:z.is_resolved,isOutdated:z.is_outdated,htmlUrl:Q.html_url??null}}function jz(z){return z.devin_review_id!=null||z.hidden_header?.includes("devin-review-comment")===!0||z.author?.login==="devin-ai-integration"||z.author?.login==="devin-ai-integration[bot]"||z.author?.login==="devin-ai[bot]"}function y(z,Q){let X=[];for(let Z of z.review_threads){if(!Q?.includeResolved&&Z.is_resolved)continue;if(!Q?.includeOutdated&&Z.is_outdated)continue;for(let J of Z.comments){if(!jz(J))continue;let $=qz(Z,J);if($){X.push($);break}}}if(!Q?.includeAnalysis)return X.filter((Z)=>Z.type==="lifeguard-bug");return X}var Y={reset:"\x1B[0m",bold:"\x1B[1m",dim:"\x1B[2m",red:"\x1B[31m",green:"\x1B[32m",yellow:"\x1B[33m",cyan:"\x1B[36m",white:"\x1B[37m",bgRed:"\x1B[41m",bgYellow:"\x1B[43m",bgBlue:"\x1B[44m"};function Az(z){let Q=z.toUpperCase();switch(z.toLowerCase()){case"severe":case"critical":return`${Y.bgRed}${Y.white}${Y.bold} ${Q} ${Y.reset}`;case"warning":return`${Y.bgYellow}${Y.bold} ${Q} ${Y.reset}`;default:return`${Y.bgBlue}${Y.white} ${Q} ${Y.reset}`}}function Pz(z){if(z==="lifeguard-bug")return`${Y.red}${Y.bold}BUG${Y.reset}`;return`${Y.cyan}${Y.bold}INFO${Y.reset}`}function _z(z){if(!z.filePath)return"";let Q=`${Y.cyan}${z.filePath}${Y.reset}`;if(z.startLine==null)return Q;let X=z.endLine!=null&&z.endLine!==z.startLine?`${Y.dim}:${z.startLine}-${z.endLine}${Y.reset}`:`${Y.dim}:${z.startLine}${Y.reset}`;return`${Q}${X}`}function wz(z,Q,X){let Z=" ".repeat(Q),J=z.split(/\s+/),$=[],W="";for(let M of J)if(W.length+M.length+1>X-Q)$.push(Z+W),W=M;else W=W?`${W} ${M}`:M;if(W)$.push(Z+W);return $.join(`
127
+ `)}function m(z,Q){let X=[],Z=z.filter((W)=>W.type==="lifeguard-bug").length,J=z.filter((W)=>W.type==="lifeguard-analysis").length,$=[];if(Z>0)$.push(`${Y.red}${Y.bold}${Z} bug${Z===1?"":"s"}${Y.reset}`);if(J>0)$.push(`${Y.cyan}${J} suggestion${J===1?"":"s"}${Y.reset}`);if($.length===0)return X.push(`
128
+ ${Y.green}${Y.bold}No unresolved bugs${Y.reset} in ${Y.dim}${Q.owner}/${Q.repo}#${Q.number}${Y.reset}
128
129
  `),X.join(`
129
130
  `);X.push(`
130
- ${Y.join(", ")} in ${M.dim}${Q.owner}/${Q.repo}#${Q.number}${M.reset}
131
- `);for(let J of z){let G=Dz(J.type),W=Sz(J),V=Nz(J.severity);if(X.push(` ${G} ${W} ${V}`),J.title)X.push(` ${M.bold}${M.white}${J.title}${M.reset}`);if(J.description&&J.description!==J.title){let B=J.description.replace(/<details>[\s\S]*?<\/details>/g,"").replace(/<!--[\s\S]*?-->/g,"").replace(/^\[.*?\]\(.*?\)$/gm,"").replace(/<a[\s\S]*?<\/a>/g,"").replace(/<picture>[\s\S]*?<\/picture>/g,"").replace(/<img[^>]*>/g,"").replace(/^---\s*$/gm,"").replace(/^\*Was this helpful\?.*$/gm,"").replace(/^#+\s*.+$/gm,"").replace(/\*\*(.+?)\*\*/g,"$1").replace(/`([^`]+)`/g,"$1").trim().split(`
131
+ ${$.join(", ")} in ${Y.dim}${Q.owner}/${Q.repo}#${Q.number}${Y.reset}
132
+ `);for(let W of z){let M=Pz(W.type),G=_z(W),B=Az(W.severity);if(X.push(` ${M} ${G} ${B}`),W.title)X.push(` ${Y.bold}${Y.white}${W.title}${Y.reset}`);if(W.description&&W.description!==W.title){let K=W.description.replace(/<details>[\s\S]*?<\/details>/g,"").replace(/<!--[\s\S]*?-->/g,"").replace(/^\[.*?\]\(.*?\)$/gm,"").replace(/<a[\s\S]*?<\/a>/g,"").replace(/<picture>[\s\S]*?<\/picture>/g,"").replace(/<img[^>]*>/g,"").replace(/^---\s*$/gm,"").replace(/^\*Was this helpful\?.*$/gm,"").replace(/^#+\s*.+$/gm,"").replace(/\*\*(.+?)\*\*/g,"$1").replace(/`([^`]+)`/g,"$1").trim().split(`
132
133
 
133
134
  `)[0].split(`
134
- `).filter((m)=>m.trim()).join(" ").trim();if(B)X.push(Hz(`${M.dim}${B}${M.reset}`,2,100))}if(J.recommendation)X.push(` ${M.green}→ ${J.recommendation}${M.reset}`);X.push("")}return X.join(`
135
- `)}function k(z){return JSON.stringify(z,null,2)}var xz=`
135
+ `).filter((h)=>h.trim()).join(" ").trim();if(K)X.push(wz(`${Y.dim}${K}${Y.reset}`,2,100))}if(W.recommendation)X.push(` ${Y.green}→ ${W.recommendation}${Y.reset}`);X.push("")}return X.join(`
136
+ `)}function f(z,Q){let X=[],Z=`${Y.dim}${Q.owner}/${Q.repo}#${Q.number}${Y.reset}`,J={lifeguard:"Bug detection",groups:"File grouping",copy_detection:"Copy detection",display_info:"Finalizing"};switch(z.status){case"no_review":X.push(`
137
+ ${Y.yellow}No Devin review${Y.reset} for ${Z}`),X.push(` ${Y.dim}Devin hasn't been triggered on this PR yet.${Y.reset}
138
+ `);break;case"running":{let $=z.stages;if(X.push(`
139
+ ${Y.yellow}${Y.bold}Devin review in progress${Y.reset} for ${Z}
140
+ `),$){for(let W of $.total){let M=$.completed.includes(W),G=J[W]??W,B=M?`${Y.green}✓${Y.reset}`:`${Y.yellow}○${Y.reset}`;X.push(` ${B} ${M?Y.dim:Y.white}${G}${Y.reset}`)}X.push("")}X.push(` ${Y.dim}Bugs will appear once the review completes.${Y.reset}
141
+ `);break}case"completed":X.push(`
142
+ ${Y.green}${Y.bold}No unresolved bugs${Y.reset} in ${Z}`),X.push(` ${Y.dim}Devin review complete — all clear.${Y.reset}
143
+ `);break;case"failed":X.push(`
144
+ ${Y.red}Devin review failed${Y.reset} for ${Z}`),X.push(` ${Y.dim}The review job encountered an error.${Y.reset}
145
+ `);break}return X.join(`
146
+ `)}function g(z){return JSON.stringify(z,null,2)}var Sz=`
136
147
  \x1B[1mdevin-bugs\x1B[0m \u2014 Extract unresolved bugs from Devin AI code reviews
137
148
 
138
149
  \x1B[1mUsage:\x1B[0m
139
150
  devin-bugs <pr> [options]
140
151
 
141
152
  \x1B[1mArguments:\x1B[0m
142
- pr GitHub PR URL or shorthand
153
+ pr GitHub PR URL, shorthand, or just a number (in a git repo)
143
154
  Examples: owner/repo#123
155
+ 49 (infers repo from git remote)
144
156
  https://github.com/owner/repo/pull/123
145
157
  https://app.devin.ai/review/owner/repo/pull/123
146
158
 
@@ -162,6 +174,6 @@ Expected: owner/repo#123 or https://github.com/owner/repo/pull/123`);let[,X,Z,$]
162
174
  devin-bugs owner/repo#46 --json
163
175
  devin-bugs owner/repo#46 --all --raw
164
176
  DEVIN_TOKEN=xxx devin-bugs owner/repo#46
165
- `;function g(){console.log(xz)}function Rz(){console.log("devin-bugs 0.3.1")}async function _z(){let z;try{z=jz({allowPositionals:!0,options:{json:{type:"boolean",default:!1},all:{type:"boolean",default:!1},raw:{type:"boolean",default:!1},"no-cache":{type:"boolean",default:!1},login:{type:"boolean",default:!1},logout:{type:"boolean",default:!1},help:{type:"boolean",short:"h",default:!1},version:{type:"boolean",short:"v",default:!1}}})}catch(W){console.error(`\x1B[31mError:\x1B[0m ${W.message}`),process.exit(1)}let{values:Q,positionals:X}=z;if(Q.help){g();return}if(Q.version){Rz();return}if(Q.logout){let{clearAuth:W}=await Promise.resolve().then(() => (H(),b));W();return}if(Q.login){let W=await F({noCache:Q["no-cache"]});console.error("\x1B[32m\u2713 Authenticated successfully.\x1B[0m"),console.error(` Token cached for future use.
166
- `);try{let V=JSON.parse(Buffer.from(W.split(".")[1],"base64url").toString()),B=new Date(V.exp*1000);console.error(` Expires: ${B.toLocaleString()}`)}catch{}return}if(X.length===0)console.error(`\x1B[31mError:\x1B[0m Missing PR argument.
167
- `),g(),process.exit(1);let Z=X[0],$;try{$=R(Z)}catch(W){console.error(`\x1B[31mError:\x1B[0m ${W.message}`),process.exit(1)}let Y;try{Y=await F({noCache:Q["no-cache"]})}catch(W){console.error(`\x1B[31mAuth error:\x1B[0m ${W.message}`),process.exit(1)}let J;try{J=await j($.prPath,Y)}catch(W){if(W instanceof U){console.error("\x1B[33m\u25B8 Token expired, re-authenticating...\x1B[0m");try{Y=await S(),J=await j($.prPath,Y)}catch(V){console.error(`\x1B[31mError:\x1B[0m ${V.message}`),process.exit(1)}}else if(W instanceof O){if(W.status===404)console.error(`\x1B[31mError:\x1B[0m PR not found or no Devin review exists for ${$.owner}/${$.repo}#${$.number}`);else console.error(`\x1B[31mAPI error ${W.status}:\x1B[0m ${W.body}`);process.exit(1)}else throw W}if(Q.raw){console.log(JSON.stringify(J,null,2));return}let G=v(J,{includeAnalysis:Q.all});if(Q.json)console.log(k(G));else console.log(T(G,$))}_z().catch((z)=>{console.error(`\x1B[31mFatal error:\x1B[0m ${z.message??z}`),process.exit(1)});
177
+ `;function u(){console.log(Sz)}function Cz(){console.log("devin-bugs 0.4.0")}async function Ez(){let z;try{z=Rz({allowPositionals:!0,options:{json:{type:"boolean",default:!1},all:{type:"boolean",default:!1},raw:{type:"boolean",default:!1},"no-cache":{type:"boolean",default:!1},login:{type:"boolean",default:!1},logout:{type:"boolean",default:!1},help:{type:"boolean",short:"h",default:!1},version:{type:"boolean",short:"v",default:!1}}})}catch(G){console.error(`\x1B[31mError:\x1B[0m ${G.message}`),process.exit(1)}let{values:Q,positionals:X}=z;if(Q.help){u();return}if(Q.version){Cz();return}if(Q.logout){let{clearAuth:G}=await Promise.resolve().then(() => (x(),v));G();return}if(Q.login){let G=await U({noCache:Q["no-cache"]});console.error("\x1B[32m\u2713 Authenticated successfully.\x1B[0m"),console.error(` Token cached for future use.
178
+ `);try{let B=JSON.parse(Buffer.from(G.split(".")[1],"base64url").toString()),K=new Date(B.exp*1000);console.error(` Expires: ${K.toLocaleString()}`)}catch{}return}if(X.length===0)console.error(`\x1B[31mError:\x1B[0m Missing PR argument.
179
+ `),u(),process.exit(1);let Z=X[0],J;try{J=A(Z)}catch(G){console.error(`\x1B[31mError:\x1B[0m ${G.message}`),process.exit(1)}let $;try{$=await U({noCache:Q["no-cache"]})}catch(G){console.error(`\x1B[31mAuth error:\x1B[0m ${G.message}`),process.exit(1)}let W;try{W=await q(J.prPath,$)}catch(G){if(G instanceof F){console.error("\x1B[33m\u25B8 Token expired, re-authenticating...\x1B[0m");try{$=await H(),W=await q(J.prPath,$)}catch(B){console.error(`\x1B[31mError:\x1B[0m ${B.message}`),process.exit(1)}}else if(G instanceof O){if(G.status===404)console.error(`\x1B[31mError:\x1B[0m PR not found or no Devin review exists for ${J.owner}/${J.repo}#${J.number}`);else console.error(`\x1B[31mAPI error ${G.status}:\x1B[0m ${G.body}`);process.exit(1)}else throw G}if(Q.raw){console.log(JSON.stringify(W,null,2));return}let M=y(W,{includeAnalysis:Q.all});if(M.length===0)try{let G=await k(J.prPath,$),B=b(G);if(Q.json)console.log(JSON.stringify({bugs:[],status:B},null,2));else console.log(f(B,J));return}catch{}if(Q.json)console.log(g(M));else console.log(m(M,J))}Ez().catch((z)=>{console.error(`\x1B[31mFatal error:\x1B[0m ${z.message??z}`),process.exit(1)});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devin-bugs",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "CLI to extract unresolved bugs from Devin AI code reviews",
5
5
  "type": "module",
6
6
  "bin": {