chief-clancy 0.7.2 → 0.7.3
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.
|
@@ -42,8 +42,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
42
42
|
`;su(s,d,"utf8");return}let a=o!=null?` | pr:${o}`:"",u=i?` | parent:${i}`:"",l=`${c} | ${t} | ${r} | ${n}${a}${u}
|
|
43
43
|
`;su(s,l,"utf8")}function au(e){let t=cu(e,".clancy","progress.txt"),r;try{r=Fm(t,"utf8")}catch{return[]}let n=[];for(let o of r.split(`
|
|
44
44
|
`)){let i=o.trim();if(!i)continue;let s=i.split(" | ");if(s.length<4)continue;let c=s[0];if(s[1]==="BRIEF"||s[1]==="APPROVE_BRIEF"){n.push({timestamp:c,key:s[2],summary:s.slice(3).join(" | "),status:s[1]});continue}let a=s[1],u,l,d,m=[];for(let h=2;h<s.length;h++){let y=s[h],_=y.match(/^pr:(\d+)$/),z=y.match(/^parent:(.+)$/);_?l=parseInt(_[1],10):z?d=z[1]:h>=3&&!u&&y===y.toUpperCase()&&y.length>1?u=y:m.push(y)}u&&(u==="APPROVE"&&(u="APPROVE_PLAN"),n.push({timestamp:c,key:a,summary:m.join(" | "),status:u,...l!=null&&{prNumber:l},...d!=null&&{parent:d}}))}return n}function uu(e,t){let r=au(e),n=t.toLowerCase(),o=0;for(let i of r)i.key.toLowerCase()===n&&i.status==="REWORK"&&o++;return o}function U(e,t){let r=au(e),n=new Map;for(let o of r)n.set(o.key,o);return[...n.values()].filter(o=>o.status===t)}function Ym(e){return e.startsWith("epic/")||e.startsWith("milestone/")}function dr(e,t,r,n){let o=[],i=r?Ym(r):!1;switch(e.provider){case"github":o.push(i?`Part of ${t.key}`:`Closes ${t.key}`);break;case"jira":o.push(`**Jira:** [${t.key}](${e.env.JIRA_BASE_URL}/browse/${t.key})`);break;case"linear":o.push(`**Linear:** ${t.key}`);break}return o.push(""),t.description&&(o.push("## Description"),o.push(""),o.push(t.description),o.push("")),n&&(o.push("## \u26A0 Verification Warning"),o.push(""),o.push(n),o.push(""),o.push("This PR may need manual fixes before merging."),o.push("")),o.push("---"),o.push("*Created by [Clancy](https://github.com/Pushedskydiver/clancy)*"),o.push(""),o.push("---"),o.push("<details>"),o.push("<summary><strong>Rework instructions</strong> (click to expand)</summary>"),o.push(""),o.push("To request changes:"),o.push("- **Code comments** \u2014 leave inline comments on specific lines. These are always picked up automatically."),o.push("- **General feedback** \u2014 reply with a comment starting with `Rework:` followed by what needs fixing. Comments without the `Rework:` prefix are treated as discussion."),o.push(""),o.push("Example: `Rework: The form validation doesn't handle empty passwords`"),o.push(""),o.push("</details>"),o.join(`
|
|
45
|
-
`)}function lu(e,t,r){let
|
|
46
|
-
`)}import{execFileSync as Gm}from"node:child_process";function pu(e){let t=e.trim().replace(/\.git$/,""),r=t.match(/^git@([^:]+):(.+)$/),n=t.match(/^(?:https?|ssh):\/\/(?:[^@]+@)?([^/:]+)(?::\d+)?\/(.+)$/),o=r?.[1]??n?.[1],i=r?.[2]??n?.[2];if(!(!o||!i))return{hostname:o,path:i}}function qm(e){let t=pu(e);if(!t)return{host:"unknown",url:e};let{hostname:r,path:n}=t;switch(Hm(r)){case"github":{let i=n.split("/");return i.length>=2?{host:"github",owner:i[0],repo:i[1],hostname:r}:{host:"unknown",url:e}}case"gitlab":return{host:"gitlab",projectPath:n,hostname:r};case"bitbucket":{let i=n.match(/^scm\/([^/]+)\/(.+)$/);if(i)return{host:"bitbucket-server",projectKey:i[1],repoSlug:i[2],hostname:r};let s=n.split("/");return s.length>=2?{host:"bitbucket",workspace:s[0],repoSlug:s[1],hostname:r}:{host:"unknown",url:e}}case"azure":return{host:"azure",url:e};default:return{host:"unknown",url:e}}}function Hm(e){let t=e.toLowerCase();return t==="github.com"||t.includes("github")?"github":t==="gitlab.com"||t.includes("gitlab")?"gitlab":t==="bitbucket.org"||t.includes("bitbucket")?"bitbucket":t.includes("dev.azure")||t.includes("visualstudio")?"azure":"unknown"}function ye(e){let t;try{t=Gm("git",["remote","get-url","origin"],{encoding:"utf8"}).trim()}catch{return{host:"none"}}return t?e?Vm(t,e):qm(t):{host:"none"}}function Vm(e,t){let r=pu(e);if(!r)return{host:"unknown",url:e};let{hostname:n,path:o}=r;switch(t.toLowerCase()){case"github":{let i=o.split("/");return i.length>=2?{host:"github",owner:i[0],repo:i[1],hostname:n}:{host:"unknown",url:e}}case"gitlab":return{host:"gitlab",projectPath:o,hostname:n};case"bitbucket":{let i=o.split("/");return i.length>=2?{host:"bitbucket",workspace:i[0],repoSlug:i[1],hostname:n}:{host:"unknown",url:e}}case"bitbucket-server":{let i=o.match(/^scm\/([^/]+)\/(.+)$/);if(i)return{host:"bitbucket-server",projectKey:i[1],repoSlug:i[2],hostname:n};let s=o.split("/");return s.length>=2?{host:"bitbucket-server",projectKey:s[0],repoSlug:s[1],hostname:n}:{host:"unknown",url:e}}default:return{host:"unknown",url:e}}}function _t(e,t){if(t)return t.replace(/\/$/,"");switch(e.host){case"github":return e.hostname==="github.com"?"https://api.github.com":`https://${e.hostname}/api/v3`;case"gitlab":return`https://${e.hostname}/api/v4`;case"bitbucket":return"https://api.bitbucket.org/2.0";case"bitbucket-server":return`https://${e.hostname}/rest/api/1.0`;default:return}}var Wm=E({id:_e(),links:E({html:$(E({href:$(b())}))}),participants:D(E({state:$(b()),role:b()}))}),fu=E({values:D(Wm)}),Xm=E({content:E({raw:b()}),inline:$(E({path:$(b())})),created_on:b(),user:$(E({nickname:$(b())}))}),Qs=E({values:D(Xm)}),Qm=E({id:_e(),links:E({self:$(D(E({href:$(b())})))}),reviewers:D(E({status:b()}))}),du=E({values:D(Qm)}),eh=E({text:b(),anchor:$(E({path:$(b())})),createdDate:_e(),author:$(E({slug:$(b())}))}),th=E({action:b(),comment:$(eh)}),ec=E({values:D(th)});async function Se(e,t,r,n,o){let i=new AbortController,s=setTimeout(()=>i.abort(),3e4);try{let c=await fetch(e,{method:"POST",headers:{...t,"Content-Type":"application/json"},body:JSON.stringify(r),signal:i.signal});if(!c.ok){let l=await c.text().catch(()=>""),d=o?.(c.status,l)??!1;return{ok:!1,error:`HTTP ${c.status}${l?`: ${l.slice(0,200)}`:""}`,alreadyExists:d}}let a=await c.json(),u=n(a);return!u.url&&!u.number?{ok:!1,error:"PR created but response missing URL and number"}:{ok:!0,url:u.url,number:u.number}}catch(c){return{ok:!1,error:c instanceof Error?c.message:String(c)}}finally{clearTimeout(s)}}function Ue(e,t){return`Basic ${Buffer.from(`${e}:${t}`).toString("base64")}`}function te(e){return e.trim().toLowerCase().startsWith("rework:")}function Pe(e){return e.trim().replace(/^rework:\s*/i,"").trim()}async function mu(e,t,r,n,o,i,s,c){return Se(`https://api.bitbucket.org/2.0/repositories/${r}/${n}/pullrequests`,{Authorization:Ue(e,t)},{title:s,description:c,source:{branch:{name:o}},destination:{branch:{name:i}},close_source_branch:!0},a=>{let u=a;return{url:u.links?.html?.href??"",number:u.id??0}},(a,u)=>a===409&&u.includes("already exists"))}async function hu(e,t,r,n,o,i,s,c){return Se(`${t}/projects/${r}/repos/${n}/pull-requests`,{Authorization:`Bearer ${e}`},{title:s,description:c,fromRef:{id:`refs/heads/${o}`,repository:{slug:n,project:{key:r}}},toRef:{id:`refs/heads/${i}`,repository:{slug:n,project:{key:r}}}},a=>{let u=a;return{url:u.links?.self?.[0]?.href??"",number:u.id??0}},(a,u)=>a===409&&(u.includes("already exists")||u.includes("Only one pull request")))}async function gu(e,t,r,n,o,i){try{return(await fetch(`https://api.bitbucket.org/2.0/repositories/${r}/${n}/pullrequests/${o}/comments`,{method:"POST",headers:{Authorization:Ue(e,t),"Content-Type":"application/json"},body:JSON.stringify({content:{raw:i}})})).ok}catch{return!1}}async function _u(e,t,r,n,o,i){try{return(await fetch(`${t}/projects/${r}/repos/${n}/pull-requests/${o}/comments`,{method:"POST",headers:{Authorization:`Bearer ${e}`,"Content-Type":"application/json"},body:JSON.stringify({text:i})})).ok}catch{return!1}}async function yu(e,t,r,n,o,i){try{let s=`https://api.bitbucket.org/2.0/repositories/${r}/${n}/pullrequests?q=source.branch.name="${o}"&state=OPEN`,c=await fetch(s,{headers:{Authorization:Ue(e,t)}});if(!c.ok)return;let a=fu.parse(await c.json());if(a.values.length===0)return;let u=a.values[0],l=u.links.html?.href??"",d=`https://api.bitbucket.org/2.0/repositories/${r}/${n}/pullrequests/${u.id}/comments?pagelen=100`,m=await fetch(d,{headers:{Authorization:Ue(e,t)}});if(!m.ok)return;let h=Qs.parse(await m.json()),y=i?h.values.filter(H=>H.created_on>i):h.values,_=y.some(H=>H.inline!=null),z=y.some(H=>H.inline==null&&te(H.content.raw));return{changesRequested:_||z,prNumber:u.id,prUrl:l}}catch{return}}async function bu(e,t,r,n,o,i){try{let s=`https://api.bitbucket.org/2.0/repositories/${r}/${n}/pullrequests/${o}/comments?pagelen=100`,c=await fetch(s,{headers:{Authorization:Ue(e,t)}});if(!c.ok)return[];let a=Qs.parse(await c.json()),u=i?a.values.filter(d=>d.created_on>i):a.values,l=[];for(let d of u)if(d.inline!=null){let m=d.inline.path?`[${d.inline.path}] `:"";l.push(`${m}${d.content.raw}`)}else te(d.content.raw)&&l.push(Pe(d.content.raw));return l}catch{return[]}}async function $u(e,t,r,n,o,i){try{let s=`${t}/projects/${r}/repos/${n}/pull-requests?state=OPEN&at=refs/heads/${o}`,c=await fetch(s,{headers:{Authorization:`Bearer ${e}`}});if(!c.ok)return;let a=du.parse(await c.json());if(a.values.length===0)return;let u=a.values[0],l=u.links.self?.[0]?.href??"",d=`${t}/projects/${r}/repos/${n}/pull-requests/${u.id}/activities?limit=100`,m=await fetch(d,{headers:{Authorization:`Bearer ${e}`}});if(!m.ok)return;let h=ec.parse(await m.json()),y=i?Date.parse(i):void 0,_=h.values.filter(R=>R.action==="COMMENTED"&&R.comment&&(y==null||R.comment.createdDate>y)),z=_.some(R=>R.comment.anchor!=null),F=_.some(R=>R.comment.anchor==null&&te(R.comment.text));return{changesRequested:z||F,prNumber:u.id,prUrl:l}}catch{return}}async function xu(e,t,r,n,o,i){try{let s=`${t}/projects/${r}/repos/${n}/pull-requests/${o}/activities?limit=100`,c=await fetch(s,{headers:{Authorization:`Bearer ${e}`}});if(!c.ok)return[];let a=ec.parse(await c.json()),u=i?Date.parse(i):void 0,l=[];for(let d of a.values){if(d.action!=="COMMENTED"||!d.comment||u!=null&&d.comment.createdDate<=u)continue;let m=d.comment;if(m.anchor!=null){let h=m.anchor.path?`[${m.anchor.path}] `:"";l.push(`${h}${m.text}`)}else te(m.text)&&l.push(Pe(m.text))}return l}catch{return[]}}var rh=p.object({number:p.number(),title:p.string(),body:p.optional(p.nullable(p.string())),pull_request:p.optional(p.unknown()),milestone:p.optional(p.nullable(p.object({title:p.string()}))),labels:p.optional(p.array(p.object({name:p.optional(p.string())})))}),wu=p.array(rh),nh=p.object({id:p.number(),body:p.optional(p.nullable(p.string())),created_at:p.string(),user:p.optional(p.object({login:p.string()}))}),tc=p.array(nh),oh=p.object({number:p.number(),html_url:p.string(),state:p.string()}),vu=p.array(oh),ih=p.object({state:p.string(),user:p.object({login:p.string()}),submitted_at:p.string()}),ku=p.array(ih),sh=p.object({body:p.optional(p.nullable(p.string())),path:p.optional(p.string()),created_at:p.optional(p.string()),user:p.optional(p.object({login:p.string()}))}),rc=p.array(sh);var q="https://api.github.com";async function mr(e,t,r,n){try{let o=new AbortController,i=setTimeout(()=>o.abort(),1e4),s=await fetch(e,{headers:t,signal:o.signal});if(clearTimeout(i),s.ok)return{ok:!0};let c=r[s.status];return c?{ok:!1,error:c}:{ok:!1,error:`\u2717 HTTP ${s.status}`}}catch{return{ok:!1,error:n}}}function Q(e){return{Authorization:`Bearer ${e}`,Accept:"application/vnd.github+json","X-GitHub-Api-Version":"2022-11-28"}}function be(e){return{Authorization:`Basic ${e}`,Accept:"application/json"}}async function zu(e,t,r,n,o=q,i,s){try{let c=Q(e),a=await fetch(`${o}/repos/${t}/pulls?head=${n}:${r}&state=open`,{headers:c});if(!a.ok)return;let u=vu.parse(await a.json());if(u.length===0)return;let l=u[0],d=i?`&since=${i}`:"",[m,h]=await Promise.all([fetch(`${o}/repos/${t}/pulls/${l.number}/comments?per_page=100${d}`,{headers:c}),fetch(`${o}/repos/${t}/issues/${l.number}/comments?per_page=100${d}`,{headers:c})]);if(!m.ok||!h.ok)return;let y=rc.parse(await m.json()),_=tc.parse(await h.json()),z=s?y.filter(re=>re.user?.login!==s):y,F=s?_.filter(re=>re.user?.login!==s):_,H=z.length>0,R=F.some(re=>re.body&&te(re.body)),O=H||R,J;if(!O)try{let re=await fetch(`${o}/repos/${t}/pulls/${l.number}/reviews?per_page=100`,{headers:c});if(re.ok){let Er=ku.parse(await re.json()),ac=new Map;for(let ie of Er)ie.state==="PENDING"||ie.state==="DISMISSED"||ac.set(ie.user.login,ie.state);let uc=[...ac.entries()].filter(([,ie])=>ie==="CHANGES_REQUESTED");uc.length>0&&(O=!0,J=uc.map(([ie])=>ie))}}catch{}return{changesRequested:O,prNumber:l.number,prUrl:l.html_url,reviewers:J}}catch{return}}async function Su(e,t,r,n=q,o,i){try{let s=Q(e),c=o?`&since=${o}`:"",[a,u]=await Promise.all([fetch(`${n}/repos/${t}/pulls/${r}/comments?per_page=100${c}`,{headers:s}),fetch(`${n}/repos/${t}/issues/${r}/comments?per_page=100${c}`,{headers:s})]);if(!a.ok||!u.ok)return[];let l=rc.parse(await a.json()),d=tc.parse(await u.json()),m=i?l.filter(_=>_.user?.login!==i):l,h=i?d.filter(_=>_.user?.login!==i):d,y=[];for(let _ of m){if(!_.body)continue;let z=_.path?`[${_.path}] `:"";y.push(`${z}${_.body}`)}for(let _ of h)_.body&&te(_.body)&&y.push(Pe(_.body));return y}catch{return[]}}async function Pu(e,t,r,n,o=q){try{return(await fetch(`${o}/repos/${t}/issues/${r}/comments`,{method:"POST",headers:{...Q(e),"Content-Type":"application/json"},body:JSON.stringify({body:n})})).ok}catch{return!1}}async function Eu(e,t,r,n,o=q){try{return(await fetch(`${o}/repos/${t}/pulls/${r}/requested_reviewers`,{method:"POST",headers:{...Q(e),"Content-Type":"application/json"},body:JSON.stringify({reviewers:n})})).ok}catch{return!1}}async function Iu(e,t,r,n,o,i,s=q){return Se(`${s}/repos/${t}/pulls`,Q(e),{title:o,head:r,base:n,body:i},c=>{let a=c;return{url:a.html_url??"",number:a.number??0}},(c,a)=>c===422&&a.includes("already exists"))}var ch=E({iid:_e(),web_url:b(),detailed_merge_status:$(b())}),Tu=D(ch),ah=E({body:b(),resolvable:De(),resolved:$(De()),system:De(),type:$(cr(b())),created_at:$(b()),position:$(E({new_path:$(b())})),author:$(E({username:b()}))}),uh=E({id:$(b()),notes:D(ah)}),nc=D(uh);async function Au(e,t,r,n,o,i,s){let c=encodeURIComponent(r);return Se(`${t}/projects/${c}/merge_requests`,{"PRIVATE-TOKEN":e},{source_branch:n,target_branch:o,title:i,description:s,remove_source_branch:!0},a=>{let u=a;return{url:u.web_url??"",number:u.iid??0}},(a,u)=>a===409&&u.includes("already exists"))}async function Ru(e,t,r,n,o){try{let i=encodeURIComponent(r);return(await fetch(`${t}/projects/${i}/merge_requests/${n}/notes`,{method:"POST",headers:{"PRIVATE-TOKEN":e,"Content-Type":"application/json"},body:JSON.stringify({body:o})})).ok}catch{return!1}}async function Cu(e,t,r,n,o){let i=encodeURIComponent(r),s=0;for(let c of o)try{(await fetch(`${t}/projects/${i}/merge_requests/${n}/discussions/${c}`,{method:"PUT",headers:{"PRIVATE-TOKEN":e,"Content-Type":"application/json"},body:JSON.stringify({resolved:!0})})).ok&&s++}catch{}return s}async function Zu(e,t,r,n,o){try{let i=encodeURIComponent(r),s=`${t}/projects/${i}/merge_requests?source_branch=${n}&state=opened`,c=await fetch(s,{headers:{"PRIVATE-TOKEN":e}});if(!c.ok)return;let a=Tu.parse(await c.json());if(a.length===0)return;let u=a[0],l=`${t}/projects/${i}/merge_requests/${u.iid}/discussions?per_page=100`,d=await fetch(l,{headers:{"PRIVATE-TOKEN":e}});if(!d.ok)return;let m=nc.parse(await d.json()),h=!1;for(let y of m){for(let _ of y.notes)if(!_.system&&!(o&&_.created_at&&_.created_at<=o)){if(_.type==="DiffNote"&&_.resolvable!==!1&&_.resolved!==!0){h=!0;break}if(te(_.body)){h=!0;break}}if(h)break}return{changesRequested:h,prNumber:u.iid,prUrl:u.web_url}}catch{return}}async function Nu(e,t,r,n,o){try{let i=encodeURIComponent(r),s=`${t}/projects/${i}/merge_requests/${n}/discussions?per_page=100`,c=await fetch(s,{headers:{"PRIVATE-TOKEN":e}});if(!c.ok)return{comments:[],discussionIds:[]};let a=nc.parse(await c.json()),u=[],l=[];for(let d of a){let m=!1;for(let h of d.notes)if(!h.system&&!(o&&h.created_at&&h.created_at<=o))if(h.type==="DiffNote"&&h.resolvable!==!1&&h.resolved!==!0){let y=h.position?.new_path?`[${h.position.new_path}] `:"";u.push(`${y}${h.body}`),m=!0}else te(h.body)&&(u.push(Pe(h.body)),m=!0);m&&d.id&&l.push(d.id)}return{comments:u,discussionIds:l}}catch{return{comments:[],discussionIds:[]}}}function yt(e,t){let r=L(e);switch(t.host){case"github":if(r.GITHUB_TOKEN)return{token:r.GITHUB_TOKEN};break;case"gitlab":if(r.GITLAB_TOKEN)return{token:r.GITLAB_TOKEN};break;case"bitbucket":if(r.BITBUCKET_USER&&r.BITBUCKET_TOKEN)return{token:r.BITBUCKET_TOKEN,username:r.BITBUCKET_USER};break;case"bitbucket-server":if(r.BITBUCKET_TOKEN)return{token:r.BITBUCKET_TOKEN};break}}async function bt(e,t,r,n,o,i){let s=yt(e,t);if(!s)return;let c=_t(t,L(e).CLANCY_GIT_API_URL);if(c)switch(t.host){case"github":return Iu(s.token,`${t.owner}/${t.repo}`,r,n,o,i,c);case"gitlab":return Au(s.token,c,t.projectPath,r,n,o,i);case"bitbucket":return mu(s.username,s.token,t.workspace,t.repoSlug,r,n,o,i);case"bitbucket-server":return hu(s.token,c,t.projectKey,t.repoSlug,r,n,o,i);default:return}}function hr(e,t,r){let n=encodeURIComponent(t),o=encodeURIComponent(r);if(e.host==="github")return`https://${e.hostname}/${e.owner}/${e.repo}/compare/${o}...${n}`;if(e.host==="gitlab")return`https://${e.hostname}/${e.projectPath}/-/merge_requests/new?merge_request[source_branch]=${n}&merge_request[target_branch]=${o}`;if(e.host==="bitbucket")return`https://${e.hostname}/${e.workspace}/${e.repoSlug}/pull-requests/new?source=${n}&dest=${o}`;if(e.host==="bitbucket-server")return`https://${e.hostname}/projects/${e.projectKey}/repos/${e.repoSlug}/pull-requests?create&sourceBranch=refs/heads/${n}&targetBranch=refs/heads/${o}`}function oc(e,t){let r=pc(e),n=Ke(e);if(r)return vt(e)?!0:(console.log(S(`\u26A0 Epic branch ${e} exists on remote but could not be fetched.`)),!1);if(n)return console.log(Te(`\u2717 Epic branch ${e} exists locally but not on remote.`)),console.log(S(" This may contain work from a previous Clancy version that squash-merged locally.")),console.log(x(" To preserve this work, push it manually:")),console.log(x(` git push -u origin ${e}`)),console.log(x(" Then re-run /clancy:once to continue.")),!1;try{if(Ou("git",["fetch","origin",t],{encoding:"utf8",stdio:["pipe","pipe","pipe"],timeout:15e3}),Ou("git",["checkout","-b",e,`origin/${t}`],{encoding:"utf8"}),Ye(e))console.log(N(` \u2713 Created epic branch ${e}`));else return console.log(S(`\u26A0 Created ${e} locally but could not push to origin.`)),!1;return!0}catch(o){return console.log(Te(`\u2717 Could not create epic branch: ${o instanceof Error?o.message:String(o)}`)),!1}}async function ic(e,t,r,n,o,i=!1,s,c){if(!Ye(r)){console.log(S(`\u26A0 Could not push ${r} to origin.`)),console.log(x(" The branch is still available locally. Push manually:")),console.log(x(` git push -u origin ${r}`)),i||C(process.cwd(),t.key,t.title,"PUSH_FAILED",void 0,s),Z(n);let y=Ee(Date.now()-o);return console.log(""),console.log(S(`\u26A0 ${t.key} implemented but push failed`)+x(` (${y})`)),!1}console.log(N(` \u2713 Pushed ${r}`));let u;try{let y=ph(process.cwd(),".clancy","verify-attempt.txt"),_=lh(y,"utf8").trim(),z=parseInt(_,10);z>0&&(u=`Verification checks did not fully pass (${z} attempt(s)). Review carefully.`)}catch{}let l=L(e).CLANCY_GIT_PLATFORM,d=ye(l),m=`feat(${t.key}): ${t.title}`,h=dr(e,{key:t.key,title:t.title,description:t.description,provider:e.provider},n,u);if(d.host!=="none"&&d.host!=="unknown"&&d.host!=="azure"){let y=await bt(e,d,r,n,m,h);if(y?.ok)console.log(N(` \u2713 PR created: ${y.url}`)),i||C(process.cwd(),t.key,t.title,"PR_CREATED",y.number,s);else if(y&&!y.ok&&y.alreadyExists)console.log(S(` \u26A0 A PR/MR already exists for ${r}. Branch pushed.`)),i||C(process.cwd(),t.key,t.title,"PUSHED",void 0,s);else if(y&&!y.ok){console.log(S(` \u26A0 PR/MR creation failed: ${y.error}`));let _=hr(d,r,n);console.log(_?x(` Create one manually: ${_}`):x(" Branch pushed \u2014 create a PR/MR manually.")),i||C(process.cwd(),t.key,t.title,"PUSHED",void 0,s)}else{let _=hr(d,r,n);console.log(_?x(` Create a PR: ${_}`):x(" Branch pushed to remote. Create a PR/MR manually.")),i||C(process.cwd(),t.key,t.title,"PUSHED",void 0,s)}}else d.host==="none"?(console.log(S(`\u26A0 No git remote configured. Branch available locally: ${r}`)),i||C(process.cwd(),t.key,t.title,"LOCAL",void 0,s)):(console.log(x(" Branch pushed to remote. Create a PR/MR manually.")),i||C(process.cwd(),t.key,t.title,"PUSHED",void 0,s));if(e.provider!=="github"&&c){let y=e.env.CLANCY_STATUS_REVIEW??e.env.CLANCY_STATUS_DONE;y&&await c.transitionTicket(t,y)}return Z(n),!0}async function ju(e,t,r,n,o,i){console.log(""),console.log(N(`\u{1F389} All children of ${t} are done!`)),console.log(x(` Creating epic PR: ${n} \u2192 ${o}`));let s=U(process.cwd(),"PR_CREATED"),c=U(process.cwd(),"DONE"),a=U(process.cwd(),"REWORK"),u=U(process.cwd(),"PUSHED"),l=[...s,...c,...a,...u].filter(F=>F.parent===t),d=L(e).CLANCY_GIT_PLATFORM,m=ye(d),h=`feat(${t}): ${r}`,y=lu(t,r,l);if(m.host==="none"||m.host==="unknown"||m.host==="azure")return console.log(S("\u26A0 Cannot create epic PR \u2014 no supported git remote detected.")),console.log(x(` Push manually: git push origin ${n}`)),console.log(x(` Then create a PR targeting ${o}`)),!1;let _=await bt(e,m,n,o,h,y);if(_?.ok){if(console.log(N(` \u2713 Epic PR created: ${_.url}`)),C(process.cwd(),t,r,"EPIC_PR_CREATED",_.number),e.provider!=="github"&&i){let F=e.env.CLANCY_STATUS_REVIEW??e.env.CLANCY_STATUS_DONE;F&&await i.transitionTicket({key:t,title:r,description:"",parentInfo:"none",blockers:"None"},F)}return!0}if(_&&!_.ok&&_.alreadyExists)return console.log(S(` \u26A0 An epic PR already exists for ${n}.`)),C(process.cwd(),t,r,"EPIC_PR_CREATED"),!0;console.log(S(`\u26A0 Epic PR creation failed: ${_?.error??"unknown error"}`)),console.log(x(" Create it manually:")),console.log(x(` Branch: ${n} \u2192 ${o}`));let z=hr(m,n,o);return z&&console.log(x(` ${z}`)),!1}async function Lu(e){let t=e.board,r=e.ticket,n=e.ticketBranch,o=e.targetBranch,i=e.baseBranch,s=e.hasParent,c=e.isRework??!1;e.originalBranch=lc();let a=!1;if(s&&!c){let l=await t.fetchChildrenStatus(r.parentInfo,r.linearIssueId);l&&l.total===1&&(a=!0)}e.skipEpicBranch=a;let u=s&&!a?o:i;if(e.effectiveTarget=u,c){if(s&&!a){if(!oc(o,i))return e.originalBranch&&Z(e.originalBranch),!1}else Tr(u,i);vt(n)?Z(n):(Z(u),Z(n,!0))}else if(s&&!a){if(!oc(o,i))return e.originalBranch&&Z(e.originalBranch),!1;Z(o),Z(n,!0)}else Tr(i,i),Z(i),Z(n,!0);try{hc(e.cwd,{pid:process.pid,ticketKey:r.key,ticketTitle:r.title,ticketBranch:n,targetBranch:u,parentKey:r.parentInfo,description:(r.description??"").slice(0,2e3),startedAt:new Date().toISOString()}),e.lockOwner=!0}catch{console.log(x(" (warning: could not write lock file \u2014 crash recovery disabled)"))}return!0}function fh(e){return e.includes("hooks.slack.com")}function dh(e){return JSON.stringify({text:e})}function mh(e){return JSON.stringify({type:"message",attachments:[{contentType:"application/vnd.microsoft.card.adaptive",content:{$schema:"http://adaptivecards.io/schemas/adaptive-card.json",type:"AdaptiveCard",version:"1.4",body:[{type:"TextBlock",text:e,wrap:!0}]}}]})}async function Mu(e,t){let r=fh(e)?dh(t):mh(t);try{let n=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:r});n.ok||console.warn(`\u26A0 Notification failed: HTTP ${n.status}`)}catch{console.warn("\u26A0 Notification failed: could not reach webhook")}}async function Bu(e){let t=e.config,r=e.ticket,n=Ee(Date.now()-e.startTime);console.log(""),console.log(N(`\u{1F3C1} ${r.key} complete`)+x(` (${n})`)),console.log(x(` "Bake 'em away, toys."`));let o=t.env.CLANCY_NOTIFY_WEBHOOK;return o&&await Mu(o,`\u2713 Clancy completed [${r.key}] ${r.title}`),!0}import{appendFileSync as hh,mkdirSync as gh}from"node:fs";import{dirname as _h,join as yh}from"node:path";function Du(e,t,r,n=6600){let o=yh(e,".clancy","costs.log");gh(_h(o),{recursive:!0});let i=new Date(r).getTime(),s=Date.now(),c=Number.isNaN(i)?0:Math.max(0,s-i),a=Math.round(c/6e4),u=Math.round(a*n),d=`${new Date().toISOString()} | ${t} | ${a}min | ~${u} tokens (estimated)
|
|
45
|
+
`)}function lu(e,t,r,n){let o=[];o.push(`## ${e} \u2014 ${t}`),o.push(""),o.push("### Children"),o.push("");for(let i of r){let s=i.prNumber?` (#${i.prNumber})`:"";o.push(`- ${i.key} \u2014 ${i.summary}${s}`)}if(n==="github"){o.push(""),o.push("### Closes"),o.push("");let i=r.map(s=>s.key).filter(s=>s.startsWith("#"));e.startsWith("#")&&i.unshift(e),i.length>0&&o.push(i.map(s=>`Closes ${s}`).join(", "))}return o.push(""),o.push("---"),o.push("*Created by [Clancy](https://github.com/Pushedskydiver/clancy)*"),o.join(`
|
|
46
|
+
`)}import{execFileSync as Gm}from"node:child_process";function pu(e){let t=e.trim().replace(/\.git$/,""),r=t.match(/^git@([^:]+):(.+)$/),n=t.match(/^(?:https?|ssh):\/\/(?:[^@]+@)?([^/:]+)(?::\d+)?\/(.+)$/),o=r?.[1]??n?.[1],i=r?.[2]??n?.[2];if(!(!o||!i))return{hostname:o,path:i}}function qm(e){let t=pu(e);if(!t)return{host:"unknown",url:e};let{hostname:r,path:n}=t;switch(Hm(r)){case"github":{let i=n.split("/");return i.length>=2?{host:"github",owner:i[0],repo:i[1],hostname:r}:{host:"unknown",url:e}}case"gitlab":return{host:"gitlab",projectPath:n,hostname:r};case"bitbucket":{let i=n.match(/^scm\/([^/]+)\/(.+)$/);if(i)return{host:"bitbucket-server",projectKey:i[1],repoSlug:i[2],hostname:r};let s=n.split("/");return s.length>=2?{host:"bitbucket",workspace:s[0],repoSlug:s[1],hostname:r}:{host:"unknown",url:e}}case"azure":return{host:"azure",url:e};default:return{host:"unknown",url:e}}}function Hm(e){let t=e.toLowerCase();return t==="github.com"||t.includes("github")?"github":t==="gitlab.com"||t.includes("gitlab")?"gitlab":t==="bitbucket.org"||t.includes("bitbucket")?"bitbucket":t.includes("dev.azure")||t.includes("visualstudio")?"azure":"unknown"}function ye(e){let t;try{t=Gm("git",["remote","get-url","origin"],{encoding:"utf8"}).trim()}catch{return{host:"none"}}return t?e?Vm(t,e):qm(t):{host:"none"}}function Vm(e,t){let r=pu(e);if(!r)return{host:"unknown",url:e};let{hostname:n,path:o}=r;switch(t.toLowerCase()){case"github":{let i=o.split("/");return i.length>=2?{host:"github",owner:i[0],repo:i[1],hostname:n}:{host:"unknown",url:e}}case"gitlab":return{host:"gitlab",projectPath:o,hostname:n};case"bitbucket":{let i=o.split("/");return i.length>=2?{host:"bitbucket",workspace:i[0],repoSlug:i[1],hostname:n}:{host:"unknown",url:e}}case"bitbucket-server":{let i=o.match(/^scm\/([^/]+)\/(.+)$/);if(i)return{host:"bitbucket-server",projectKey:i[1],repoSlug:i[2],hostname:n};let s=o.split("/");return s.length>=2?{host:"bitbucket-server",projectKey:s[0],repoSlug:s[1],hostname:n}:{host:"unknown",url:e}}default:return{host:"unknown",url:e}}}function _t(e,t){if(t)return t.replace(/\/$/,"");switch(e.host){case"github":return e.hostname==="github.com"?"https://api.github.com":`https://${e.hostname}/api/v3`;case"gitlab":return`https://${e.hostname}/api/v4`;case"bitbucket":return"https://api.bitbucket.org/2.0";case"bitbucket-server":return`https://${e.hostname}/rest/api/1.0`;default:return}}var Wm=E({id:_e(),links:E({html:$(E({href:$(b())}))}),participants:D(E({state:$(b()),role:b()}))}),fu=E({values:D(Wm)}),Xm=E({content:E({raw:b()}),inline:$(E({path:$(b())})),created_on:b(),user:$(E({nickname:$(b())}))}),Qs=E({values:D(Xm)}),Qm=E({id:_e(),links:E({self:$(D(E({href:$(b())})))}),reviewers:D(E({status:b()}))}),du=E({values:D(Qm)}),eh=E({text:b(),anchor:$(E({path:$(b())})),createdDate:_e(),author:$(E({slug:$(b())}))}),th=E({action:b(),comment:$(eh)}),ec=E({values:D(th)});async function Se(e,t,r,n,o){let i=new AbortController,s=setTimeout(()=>i.abort(),3e4);try{let c=await fetch(e,{method:"POST",headers:{...t,"Content-Type":"application/json"},body:JSON.stringify(r),signal:i.signal});if(!c.ok){let l=await c.text().catch(()=>""),d=o?.(c.status,l)??!1;return{ok:!1,error:`HTTP ${c.status}${l?`: ${l.slice(0,200)}`:""}`,alreadyExists:d}}let a=await c.json(),u=n(a);return!u.url&&!u.number?{ok:!1,error:"PR created but response missing URL and number"}:{ok:!0,url:u.url,number:u.number}}catch(c){return{ok:!1,error:c instanceof Error?c.message:String(c)}}finally{clearTimeout(s)}}function Ue(e,t){return`Basic ${Buffer.from(`${e}:${t}`).toString("base64")}`}function te(e){return e.trim().toLowerCase().startsWith("rework:")}function Pe(e){return e.trim().replace(/^rework:\s*/i,"").trim()}async function mu(e,t,r,n,o,i,s,c){return Se(`https://api.bitbucket.org/2.0/repositories/${r}/${n}/pullrequests`,{Authorization:Ue(e,t)},{title:s,description:c,source:{branch:{name:o}},destination:{branch:{name:i}},close_source_branch:!0},a=>{let u=a;return{url:u.links?.html?.href??"",number:u.id??0}},(a,u)=>a===409&&u.includes("already exists"))}async function hu(e,t,r,n,o,i,s,c){return Se(`${t}/projects/${r}/repos/${n}/pull-requests`,{Authorization:`Bearer ${e}`},{title:s,description:c,fromRef:{id:`refs/heads/${o}`,repository:{slug:n,project:{key:r}}},toRef:{id:`refs/heads/${i}`,repository:{slug:n,project:{key:r}}}},a=>{let u=a;return{url:u.links?.self?.[0]?.href??"",number:u.id??0}},(a,u)=>a===409&&(u.includes("already exists")||u.includes("Only one pull request")))}async function gu(e,t,r,n,o,i){try{return(await fetch(`https://api.bitbucket.org/2.0/repositories/${r}/${n}/pullrequests/${o}/comments`,{method:"POST",headers:{Authorization:Ue(e,t),"Content-Type":"application/json"},body:JSON.stringify({content:{raw:i}})})).ok}catch{return!1}}async function _u(e,t,r,n,o,i){try{return(await fetch(`${t}/projects/${r}/repos/${n}/pull-requests/${o}/comments`,{method:"POST",headers:{Authorization:`Bearer ${e}`,"Content-Type":"application/json"},body:JSON.stringify({text:i})})).ok}catch{return!1}}async function yu(e,t,r,n,o,i){try{let s=`https://api.bitbucket.org/2.0/repositories/${r}/${n}/pullrequests?q=source.branch.name="${o}"&state=OPEN`,c=await fetch(s,{headers:{Authorization:Ue(e,t)}});if(!c.ok)return;let a=fu.parse(await c.json());if(a.values.length===0)return;let u=a.values[0],l=u.links.html?.href??"",d=`https://api.bitbucket.org/2.0/repositories/${r}/${n}/pullrequests/${u.id}/comments?pagelen=100`,m=await fetch(d,{headers:{Authorization:Ue(e,t)}});if(!m.ok)return;let h=Qs.parse(await m.json()),y=i?h.values.filter(H=>H.created_on>i):h.values,_=y.some(H=>H.inline!=null),z=y.some(H=>H.inline==null&&te(H.content.raw));return{changesRequested:_||z,prNumber:u.id,prUrl:l}}catch{return}}async function bu(e,t,r,n,o,i){try{let s=`https://api.bitbucket.org/2.0/repositories/${r}/${n}/pullrequests/${o}/comments?pagelen=100`,c=await fetch(s,{headers:{Authorization:Ue(e,t)}});if(!c.ok)return[];let a=Qs.parse(await c.json()),u=i?a.values.filter(d=>d.created_on>i):a.values,l=[];for(let d of u)if(d.inline!=null){let m=d.inline.path?`[${d.inline.path}] `:"";l.push(`${m}${d.content.raw}`)}else te(d.content.raw)&&l.push(Pe(d.content.raw));return l}catch{return[]}}async function $u(e,t,r,n,o,i){try{let s=`${t}/projects/${r}/repos/${n}/pull-requests?state=OPEN&at=refs/heads/${o}`,c=await fetch(s,{headers:{Authorization:`Bearer ${e}`}});if(!c.ok)return;let a=du.parse(await c.json());if(a.values.length===0)return;let u=a.values[0],l=u.links.self?.[0]?.href??"",d=`${t}/projects/${r}/repos/${n}/pull-requests/${u.id}/activities?limit=100`,m=await fetch(d,{headers:{Authorization:`Bearer ${e}`}});if(!m.ok)return;let h=ec.parse(await m.json()),y=i?Date.parse(i):void 0,_=h.values.filter(R=>R.action==="COMMENTED"&&R.comment&&(y==null||R.comment.createdDate>y)),z=_.some(R=>R.comment.anchor!=null),F=_.some(R=>R.comment.anchor==null&&te(R.comment.text));return{changesRequested:z||F,prNumber:u.id,prUrl:l}}catch{return}}async function xu(e,t,r,n,o,i){try{let s=`${t}/projects/${r}/repos/${n}/pull-requests/${o}/activities?limit=100`,c=await fetch(s,{headers:{Authorization:`Bearer ${e}`}});if(!c.ok)return[];let a=ec.parse(await c.json()),u=i?Date.parse(i):void 0,l=[];for(let d of a.values){if(d.action!=="COMMENTED"||!d.comment||u!=null&&d.comment.createdDate<=u)continue;let m=d.comment;if(m.anchor!=null){let h=m.anchor.path?`[${m.anchor.path}] `:"";l.push(`${h}${m.text}`)}else te(m.text)&&l.push(Pe(m.text))}return l}catch{return[]}}var rh=p.object({number:p.number(),title:p.string(),body:p.optional(p.nullable(p.string())),pull_request:p.optional(p.unknown()),milestone:p.optional(p.nullable(p.object({title:p.string()}))),labels:p.optional(p.array(p.object({name:p.optional(p.string())})))}),wu=p.array(rh),nh=p.object({id:p.number(),body:p.optional(p.nullable(p.string())),created_at:p.string(),user:p.optional(p.object({login:p.string()}))}),tc=p.array(nh),oh=p.object({number:p.number(),html_url:p.string(),state:p.string()}),vu=p.array(oh),ih=p.object({state:p.string(),user:p.object({login:p.string()}),submitted_at:p.string()}),ku=p.array(ih),sh=p.object({body:p.optional(p.nullable(p.string())),path:p.optional(p.string()),created_at:p.optional(p.string()),user:p.optional(p.object({login:p.string()}))}),rc=p.array(sh);var q="https://api.github.com";async function mr(e,t,r,n){try{let o=new AbortController,i=setTimeout(()=>o.abort(),1e4),s=await fetch(e,{headers:t,signal:o.signal});if(clearTimeout(i),s.ok)return{ok:!0};let c=r[s.status];return c?{ok:!1,error:c}:{ok:!1,error:`\u2717 HTTP ${s.status}`}}catch{return{ok:!1,error:n}}}function Q(e){return{Authorization:`Bearer ${e}`,Accept:"application/vnd.github+json","X-GitHub-Api-Version":"2022-11-28"}}function be(e){return{Authorization:`Basic ${e}`,Accept:"application/json"}}async function zu(e,t,r,n,o=q,i,s){try{let c=Q(e),a=await fetch(`${o}/repos/${t}/pulls?head=${n}:${r}&state=open`,{headers:c});if(!a.ok)return;let u=vu.parse(await a.json());if(u.length===0)return;let l=u[0],d=i?`&since=${i}`:"",[m,h]=await Promise.all([fetch(`${o}/repos/${t}/pulls/${l.number}/comments?per_page=100${d}`,{headers:c}),fetch(`${o}/repos/${t}/issues/${l.number}/comments?per_page=100${d}`,{headers:c})]);if(!m.ok||!h.ok)return;let y=rc.parse(await m.json()),_=tc.parse(await h.json()),z=s?y.filter(re=>re.user?.login!==s):y,F=s?_.filter(re=>re.user?.login!==s):_,H=z.length>0,R=F.some(re=>re.body&&te(re.body)),O=H||R,J;if(!O)try{let re=await fetch(`${o}/repos/${t}/pulls/${l.number}/reviews?per_page=100`,{headers:c});if(re.ok){let Er=ku.parse(await re.json()),ac=new Map;for(let ie of Er)ie.state==="PENDING"||ie.state==="DISMISSED"||ac.set(ie.user.login,ie.state);let uc=[...ac.entries()].filter(([,ie])=>ie==="CHANGES_REQUESTED");uc.length>0&&(O=!0,J=uc.map(([ie])=>ie))}}catch{}return{changesRequested:O,prNumber:l.number,prUrl:l.html_url,reviewers:J}}catch{return}}async function Su(e,t,r,n=q,o,i){try{let s=Q(e),c=o?`&since=${o}`:"",[a,u]=await Promise.all([fetch(`${n}/repos/${t}/pulls/${r}/comments?per_page=100${c}`,{headers:s}),fetch(`${n}/repos/${t}/issues/${r}/comments?per_page=100${c}`,{headers:s})]);if(!a.ok||!u.ok)return[];let l=rc.parse(await a.json()),d=tc.parse(await u.json()),m=i?l.filter(_=>_.user?.login!==i):l,h=i?d.filter(_=>_.user?.login!==i):d,y=[];for(let _ of m){if(!_.body)continue;let z=_.path?`[${_.path}] `:"";y.push(`${z}${_.body}`)}for(let _ of h)_.body&&te(_.body)&&y.push(Pe(_.body));return y}catch{return[]}}async function Pu(e,t,r,n,o=q){try{return(await fetch(`${o}/repos/${t}/issues/${r}/comments`,{method:"POST",headers:{...Q(e),"Content-Type":"application/json"},body:JSON.stringify({body:n})})).ok}catch{return!1}}async function Eu(e,t,r,n,o=q){try{return(await fetch(`${o}/repos/${t}/pulls/${r}/requested_reviewers`,{method:"POST",headers:{...Q(e),"Content-Type":"application/json"},body:JSON.stringify({reviewers:n})})).ok}catch{return!1}}async function Iu(e,t,r,n,o,i,s=q){return Se(`${s}/repos/${t}/pulls`,Q(e),{title:o,head:r,base:n,body:i},c=>{let a=c;return{url:a.html_url??"",number:a.number??0}},(c,a)=>c===422&&a.includes("already exists"))}var ch=E({iid:_e(),web_url:b(),detailed_merge_status:$(b())}),Tu=D(ch),ah=E({body:b(),resolvable:De(),resolved:$(De()),system:De(),type:$(cr(b())),created_at:$(b()),position:$(E({new_path:$(b())})),author:$(E({username:b()}))}),uh=E({id:$(b()),notes:D(ah)}),nc=D(uh);async function Au(e,t,r,n,o,i,s){let c=encodeURIComponent(r);return Se(`${t}/projects/${c}/merge_requests`,{"PRIVATE-TOKEN":e},{source_branch:n,target_branch:o,title:i,description:s,remove_source_branch:!0},a=>{let u=a;return{url:u.web_url??"",number:u.iid??0}},(a,u)=>a===409&&u.includes("already exists"))}async function Ru(e,t,r,n,o){try{let i=encodeURIComponent(r);return(await fetch(`${t}/projects/${i}/merge_requests/${n}/notes`,{method:"POST",headers:{"PRIVATE-TOKEN":e,"Content-Type":"application/json"},body:JSON.stringify({body:o})})).ok}catch{return!1}}async function Cu(e,t,r,n,o){let i=encodeURIComponent(r),s=0;for(let c of o)try{(await fetch(`${t}/projects/${i}/merge_requests/${n}/discussions/${c}`,{method:"PUT",headers:{"PRIVATE-TOKEN":e,"Content-Type":"application/json"},body:JSON.stringify({resolved:!0})})).ok&&s++}catch{}return s}async function Zu(e,t,r,n,o){try{let i=encodeURIComponent(r),s=`${t}/projects/${i}/merge_requests?source_branch=${n}&state=opened`,c=await fetch(s,{headers:{"PRIVATE-TOKEN":e}});if(!c.ok)return;let a=Tu.parse(await c.json());if(a.length===0)return;let u=a[0],l=`${t}/projects/${i}/merge_requests/${u.iid}/discussions?per_page=100`,d=await fetch(l,{headers:{"PRIVATE-TOKEN":e}});if(!d.ok)return;let m=nc.parse(await d.json()),h=!1;for(let y of m){for(let _ of y.notes)if(!_.system&&!(o&&_.created_at&&_.created_at<=o)){if(_.type==="DiffNote"&&_.resolvable!==!1&&_.resolved!==!0){h=!0;break}if(te(_.body)){h=!0;break}}if(h)break}return{changesRequested:h,prNumber:u.iid,prUrl:u.web_url}}catch{return}}async function Nu(e,t,r,n,o){try{let i=encodeURIComponent(r),s=`${t}/projects/${i}/merge_requests/${n}/discussions?per_page=100`,c=await fetch(s,{headers:{"PRIVATE-TOKEN":e}});if(!c.ok)return{comments:[],discussionIds:[]};let a=nc.parse(await c.json()),u=[],l=[];for(let d of a){let m=!1;for(let h of d.notes)if(!h.system&&!(o&&h.created_at&&h.created_at<=o))if(h.type==="DiffNote"&&h.resolvable!==!1&&h.resolved!==!0){let y=h.position?.new_path?`[${h.position.new_path}] `:"";u.push(`${y}${h.body}`),m=!0}else te(h.body)&&(u.push(Pe(h.body)),m=!0);m&&d.id&&l.push(d.id)}return{comments:u,discussionIds:l}}catch{return{comments:[],discussionIds:[]}}}function yt(e,t){let r=L(e);switch(t.host){case"github":if(r.GITHUB_TOKEN)return{token:r.GITHUB_TOKEN};break;case"gitlab":if(r.GITLAB_TOKEN)return{token:r.GITLAB_TOKEN};break;case"bitbucket":if(r.BITBUCKET_USER&&r.BITBUCKET_TOKEN)return{token:r.BITBUCKET_TOKEN,username:r.BITBUCKET_USER};break;case"bitbucket-server":if(r.BITBUCKET_TOKEN)return{token:r.BITBUCKET_TOKEN};break}}async function bt(e,t,r,n,o,i){let s=yt(e,t);if(!s)return;let c=_t(t,L(e).CLANCY_GIT_API_URL);if(c)switch(t.host){case"github":return Iu(s.token,`${t.owner}/${t.repo}`,r,n,o,i,c);case"gitlab":return Au(s.token,c,t.projectPath,r,n,o,i);case"bitbucket":return mu(s.username,s.token,t.workspace,t.repoSlug,r,n,o,i);case"bitbucket-server":return hu(s.token,c,t.projectKey,t.repoSlug,r,n,o,i);default:return}}function hr(e,t,r){let n=encodeURIComponent(t),o=encodeURIComponent(r);if(e.host==="github")return`https://${e.hostname}/${e.owner}/${e.repo}/compare/${o}...${n}`;if(e.host==="gitlab")return`https://${e.hostname}/${e.projectPath}/-/merge_requests/new?merge_request[source_branch]=${n}&merge_request[target_branch]=${o}`;if(e.host==="bitbucket")return`https://${e.hostname}/${e.workspace}/${e.repoSlug}/pull-requests/new?source=${n}&dest=${o}`;if(e.host==="bitbucket-server")return`https://${e.hostname}/projects/${e.projectKey}/repos/${e.repoSlug}/pull-requests?create&sourceBranch=refs/heads/${n}&targetBranch=refs/heads/${o}`}function oc(e,t){let r=pc(e),n=Ke(e);if(r)return vt(e)?!0:(console.log(S(`\u26A0 Epic branch ${e} exists on remote but could not be fetched.`)),!1);if(n)return console.log(Te(`\u2717 Epic branch ${e} exists locally but not on remote.`)),console.log(S(" This may contain work from a previous Clancy version that squash-merged locally.")),console.log(x(" To preserve this work, push it manually:")),console.log(x(` git push -u origin ${e}`)),console.log(x(" Then re-run /clancy:once to continue.")),!1;try{if(Ou("git",["fetch","origin",t],{encoding:"utf8",stdio:["pipe","pipe","pipe"],timeout:15e3}),Ou("git",["checkout","-b",e,`origin/${t}`],{encoding:"utf8"}),Ye(e))console.log(N(` \u2713 Created epic branch ${e}`));else return console.log(S(`\u26A0 Created ${e} locally but could not push to origin.`)),!1;return!0}catch(o){return console.log(Te(`\u2717 Could not create epic branch: ${o instanceof Error?o.message:String(o)}`)),!1}}async function ic(e,t,r,n,o,i=!1,s,c){if(!Ye(r)){console.log(S(`\u26A0 Could not push ${r} to origin.`)),console.log(x(" The branch is still available locally. Push manually:")),console.log(x(` git push -u origin ${r}`)),i||C(process.cwd(),t.key,t.title,"PUSH_FAILED",void 0,s),Z(n);let y=Ee(Date.now()-o);return console.log(""),console.log(S(`\u26A0 ${t.key} implemented but push failed`)+x(` (${y})`)),!1}console.log(N(` \u2713 Pushed ${r}`));let u;try{let y=ph(process.cwd(),".clancy","verify-attempt.txt"),_=lh(y,"utf8").trim(),z=parseInt(_,10);z>0&&(u=`Verification checks did not fully pass (${z} attempt(s)). Review carefully.`)}catch{}let l=L(e).CLANCY_GIT_PLATFORM,d=ye(l),m=`feat(${t.key}): ${t.title}`,h=dr(e,{key:t.key,title:t.title,description:t.description,provider:e.provider},n,u);if(d.host!=="none"&&d.host!=="unknown"&&d.host!=="azure"){let y=await bt(e,d,r,n,m,h);if(y?.ok)console.log(N(` \u2713 PR created: ${y.url}`)),i||C(process.cwd(),t.key,t.title,"PR_CREATED",y.number,s);else if(y&&!y.ok&&y.alreadyExists)console.log(S(` \u26A0 A PR/MR already exists for ${r}. Branch pushed.`)),i||C(process.cwd(),t.key,t.title,"PUSHED",void 0,s);else if(y&&!y.ok){console.log(S(` \u26A0 PR/MR creation failed: ${y.error}`));let _=hr(d,r,n);console.log(_?x(` Create one manually: ${_}`):x(" Branch pushed \u2014 create a PR/MR manually.")),i||C(process.cwd(),t.key,t.title,"PUSHED",void 0,s)}else{let _=hr(d,r,n);console.log(_?x(` Create a PR: ${_}`):x(" Branch pushed to remote. Create a PR/MR manually.")),i||C(process.cwd(),t.key,t.title,"PUSHED",void 0,s)}}else d.host==="none"?(console.log(S(`\u26A0 No git remote configured. Branch available locally: ${r}`)),i||C(process.cwd(),t.key,t.title,"LOCAL",void 0,s)):(console.log(x(" Branch pushed to remote. Create a PR/MR manually.")),i||C(process.cwd(),t.key,t.title,"PUSHED",void 0,s));if(e.provider!=="github"&&c){let y=e.env.CLANCY_STATUS_REVIEW??e.env.CLANCY_STATUS_DONE;y&&await c.transitionTicket(t,y)}return Z(n),!0}async function ju(e,t,r,n,o,i){console.log(""),console.log(N(`\u{1F389} All children of ${t} are done!`)),console.log(x(` Creating epic PR: ${n} \u2192 ${o}`));let s=U(process.cwd(),"PR_CREATED"),c=U(process.cwd(),"DONE"),a=U(process.cwd(),"REWORK"),u=U(process.cwd(),"PUSHED"),l=[...s,...c,...a,...u].filter(F=>F.parent===t),d=L(e).CLANCY_GIT_PLATFORM,m=ye(d),h=`feat(${t}): ${r}`,y=lu(t,r,l,e.provider);if(m.host==="none"||m.host==="unknown"||m.host==="azure")return console.log(S("\u26A0 Cannot create epic PR \u2014 no supported git remote detected.")),console.log(x(` Push manually: git push origin ${n}`)),console.log(x(` Then create a PR targeting ${o}`)),!1;let _=await bt(e,m,n,o,h,y);if(_?.ok){if(console.log(N(` \u2713 Epic PR created: ${_.url}`)),C(process.cwd(),t,r,"EPIC_PR_CREATED",_.number),e.provider!=="github"&&i){let F=e.env.CLANCY_STATUS_REVIEW??e.env.CLANCY_STATUS_DONE;F&&await i.transitionTicket({key:t,title:r,description:"",parentInfo:"none",blockers:"None"},F)}return!0}if(_&&!_.ok&&_.alreadyExists)return console.log(S(` \u26A0 An epic PR already exists for ${n}.`)),C(process.cwd(),t,r,"EPIC_PR_CREATED"),!0;console.log(S(`\u26A0 Epic PR creation failed: ${_?.error??"unknown error"}`)),console.log(x(" Create it manually:")),console.log(x(` Branch: ${n} \u2192 ${o}`));let z=hr(m,n,o);return z&&console.log(x(` ${z}`)),!1}async function Lu(e){let t=e.board,r=e.ticket,n=e.ticketBranch,o=e.targetBranch,i=e.baseBranch,s=e.hasParent,c=e.isRework??!1;e.originalBranch=lc();let a=!1;if(s&&!c){let l=await t.fetchChildrenStatus(r.parentInfo,r.linearIssueId);l&&l.total===1&&(a=!0)}e.skipEpicBranch=a;let u=s&&!a?o:i;if(e.effectiveTarget=u,c){if(s&&!a){if(!oc(o,i))return e.originalBranch&&Z(e.originalBranch),!1}else Tr(u,i);vt(n)?Z(n):(Z(u),Z(n,!0))}else if(s&&!a){if(!oc(o,i))return e.originalBranch&&Z(e.originalBranch),!1;Z(o),Z(n,!0)}else Tr(i,i),Z(i),Z(n,!0);try{hc(e.cwd,{pid:process.pid,ticketKey:r.key,ticketTitle:r.title,ticketBranch:n,targetBranch:u,parentKey:r.parentInfo,description:(r.description??"").slice(0,2e3),startedAt:new Date().toISOString()}),e.lockOwner=!0}catch{console.log(x(" (warning: could not write lock file \u2014 crash recovery disabled)"))}return!0}function fh(e){return e.includes("hooks.slack.com")}function dh(e){return JSON.stringify({text:e})}function mh(e){return JSON.stringify({type:"message",attachments:[{contentType:"application/vnd.microsoft.card.adaptive",content:{$schema:"http://adaptivecards.io/schemas/adaptive-card.json",type:"AdaptiveCard",version:"1.4",body:[{type:"TextBlock",text:e,wrap:!0}]}}]})}async function Mu(e,t){let r=fh(e)?dh(t):mh(t);try{let n=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:r});n.ok||console.warn(`\u26A0 Notification failed: HTTP ${n.status}`)}catch{console.warn("\u26A0 Notification failed: could not reach webhook")}}async function Bu(e){let t=e.config,r=e.ticket,n=Ee(Date.now()-e.startTime);console.log(""),console.log(N(`\u{1F3C1} ${r.key} complete`)+x(` (${n})`)),console.log(x(` "Bake 'em away, toys."`));let o=t.env.CLANCY_NOTIFY_WEBHOOK;return o&&await Mu(o,`\u2713 Clancy completed [${r.key}] ${r.title}`),!0}import{appendFileSync as hh,mkdirSync as gh}from"node:fs";import{dirname as _h,join as yh}from"node:path";function Du(e,t,r,n=6600){let o=yh(e,".clancy","costs.log");gh(_h(o),{recursive:!0});let i=new Date(r).getTime(),s=Date.now(),c=Number.isNaN(i)?0:Math.max(0,s-i),a=Math.round(c/6e4),u=Math.round(a*n),d=`${new Date().toISOString()} | ${t} | ${a}min | ~${u} tokens (estimated)
|
|
47
47
|
`;hh(o,d,"utf8")}function Uu(e){let t=e.config,r=e.ticket;try{let n=zt(e.cwd);if(n){let o=Number(t.env.CLANCY_TOKEN_RATE??"6600");Du(e.cwd,r.key,n.startedAt,Number.isFinite(o)&&o>0?o:6600)}}catch{}return!0}var bh=p.object({login:p.string()}),$h=/^[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$/,gr;function _r(e){return $h.test(e)}async function Ju(e,t){return mr(`${q}/repos/${t}`,Q(e),{401:"\u2717 GitHub auth failed \u2014 check GITHUB_TOKEN",403:"\u2717 GitHub permission denied",404:`\u2717 GitHub repo "${t}" not found`},"\u2717 Could not reach GitHub \u2014 check network")}async function Fe(e,t){if(gr)return gr;try{let r=await fetch(`${t??q}/user`,{headers:Q(e)});if(!r.ok){let i=r.status===401||r.status===403?' Fine-grained PATs need "Account permissions \u2192 read" (or classic PATs need read:user scope).':"";return console.warn(`\u26A0 GitHub /user returned HTTP ${r.status} \u2014 falling back to @me.${i}`),"@me"}let n=await r.json(),o=bh.safeParse(n);if(o.success)return gr=o.data.login,gr;console.warn("\u26A0 Unexpected GitHub /user response \u2014 falling back to @me")}catch(r){console.warn(`\u26A0 GitHub /user request failed: ${r instanceof Error?r.message:String(r)} \u2014 falling back to @me`)}return"@me"}async function yr(e,t,r,n,o,i=5){let s,c=new URLSearchParams({state:"open",assignee:n??"@me",per_page:"10"});r&&c.set("labels",r);try{s=await fetch(`${q}/repos/${t}/issues?${c}`,{headers:Q(e)})}catch(d){return console.warn(`\u26A0 GitHub API request failed: ${d instanceof Error?d.message:String(d)}`),[]}if(!s.ok)return console.warn(`\u26A0 GitHub API returned HTTP ${s.status}`),[];let a;try{a=await s.json()}catch{return console.warn("\u26A0 GitHub API returned invalid JSON"),[]}let u=wu.safeParse(a);if(!u.success)return console.warn(`\u26A0 Unexpected GitHub response shape: ${u.error.message}`),[];let l=u.data.filter(d=>!d.pull_request);return o&&(l=l.filter(d=>!d.labels?.some(m=>m.name==="clancy:hitl"))),l.slice(0,i).map(d=>({key:`#${d.number}`,title:d.title,description:d.body??"",provider:"github",milestone:d.milestone?.title}))}async function br(e,t,r,n){if(!_r(t))return!1;let o=/Blocked by #(\d+)/gi,i=new Set,s;for(;(s=o.exec(n))!==null;){let c=parseInt(s[1],10);!Number.isNaN(c)&&c!==r&&i.add(c)}if(!i.size)return!1;try{for(let c of i){let a=await fetch(`${q}/repos/${t}/issues/${c}`,{headers:Q(e)});if(!a.ok)continue;if((await a.json()).state!=="closed")return!0}return!1}catch{return!1}}async function Ku(e,t,r){if(_r(t))try{let n=await Fu(e,t,`Epic: #${r}`);return n&&n.total>0?n:await Fu(e,t,`Parent: #${r}`)}catch{return}}async function Fu(e,t,r){let n=Q(e),o=`"${r}" repo:${t} is:issue`,i=new URLSearchParams({q:o,per_page:"1"}),s=await fetch(`${q}/search/issues?${i}`,{headers:n});if(!s.ok)return;let a=(await s.json()).total_count??0;if(a===0)return{total:0,incomplete:0};let u=`"${r}" repo:${t} is:issue is:open`,l=new URLSearchParams({q:u,per_page:"1"}),d=await fetch(`${q}/search/issues?${l}`,{headers:n});if(!d.ok)return{total:a,incomplete:a};let h=(await d.json()).total_count??0;return{total:a,incomplete:h}}function $r(e,t){return e==="github"?`feature/issue-${t.replace("#","")}`:`feature/${t.toLowerCase()}`}function xr(e,t,r){return r?e==="github"?`milestone/${r.toLowerCase().replace(/\s+/g,"-").replace(/[^a-z0-9-]/g,"")}`:`epic/${r.toLowerCase()}`:t}async function Yu(e){let t=U(process.cwd(),"PR_CREATED"),r=U(process.cwd(),"REWORK"),n=U(process.cwd(),"PUSHED"),o=U(process.cwd(),"PUSH_FAILED"),i=[...t,...r,...n,...o];if(i.length===0)return;let s=L(e).CLANCY_GIT_PLATFORM,c=ye(s);if(c.host==="none"||c.host==="unknown"||c.host==="azure")return;let a=yt(e,c);if(!a)return;let u=_t(c,L(e).CLANCY_GIT_API_URL);if(!u)return;let l;if(c.host==="github")try{l=await Fe(a.token,u)}catch{}let d=i.slice(0,5);for(let m of d){let h=$r(e.provider,m.key),y;if(m.timestamp){let z=new Date(m.timestamp.replace(" ","T")+"Z");y=Number.isNaN(z.getTime())?void 0:z.toISOString()}let _;switch(c.host){case"github":_=await zu(a.token,`${c.owner}/${c.repo}`,h,c.owner,u,y,l);break;case"gitlab":_=await Zu(a.token,u,c.projectPath,h,y);break;case"bitbucket":_=await yu(a.username,a.token,c.workspace,c.repoSlug,h,y);break;case"bitbucket-server":_=await $u(a.token,u,c.projectKey,c.repoSlug,h,y);break}if(_?.changesRequested){let z=[],F;switch(c.host){case"github":z=await Su(a.token,`${c.owner}/${c.repo}`,_.prNumber,u,y,l);break;case"gitlab":{let R=await Nu(a.token,u,c.projectPath,_.prNumber,y);z=R.comments,F=R.discussionIds;break}case"bitbucket":z=await bu(a.username,a.token,c.workspace,c.repoSlug,_.prNumber,y);break;case"bitbucket-server":z=await xu(a.token,u,c.projectKey,c.repoSlug,_.prNumber,y);break}return{ticket:{key:m.key,title:m.summary,description:m.summary,parentInfo:m.parent??"none",blockers:"None"},feedback:z,prNumber:_.prNumber,discussionIds:F,reviewers:_.reviewers??[]}}}}function xh(e){if(e.length===0)return"[clancy] Rework pushed addressing reviewer feedback.";let t=e.length,r=e.slice(0,3).map(o=>`- ${o.slice(0,80)}`).join(`
|
|
48
48
|
`),n=e.length>3?`
|
|
49
49
|
- ...`:"";return`[clancy] Rework pushed addressing ${t} feedback item${t!==1?"s":""}.
|
package/package.json
CHANGED