@lovek4d/kickflip 0.7.2 → 0.7.4

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.
package/board/domain.mjs CHANGED
@@ -1,10 +1,10 @@
1
- var Ht=Object.defineProperty;var i=(e,t)=>Ht(e,"name",{value:t,configurable:!0});import{routeRecord as E}from"../hub/routes.mjs";import Vt from"node:fs";import{ticketBadge as ft,ticketIn as Xt}from"./tickets.mjs";import{prLinks as Yt,reviewLinks as zt,reviewTime as Jt,reviewersOf as Zt}from"./review.mjs";import{remoteFromInput as en}from"../hub/repos.mjs";import{reviewsOf as tn,reviewNeeds as nn,reviewActions as sn}from"./auto-review.mjs";import{cardPRs as on,hasPR as et,leastReady as rn,prKeyOf as an,prURL as ln}from"../shared/card-prs.mjs";import gt from"node:vm";import{checkWords as dn,parseCheck as cn}from"../shared/owner-check.mjs";import{buildScriptOf as un}from"./build-script.mjs";import{HUB as Fe,hubLineOf as kt}from"./hub-lines.mjs";const tt=gt.createContext({});for(const e of["ui-enums.js","ui-order.js"])gt.runInContext(Vt.readFileSync(new URL(`./public/${e}`,import.meta.url),"utf8"),tt);const{BUTTON:oe,ACTION_KIND:q,BUILDER_KIND:ce,PR_STATE:H,PHASES:mt}=tt.BoardUI,{laneOrder:pn}=tt.BoardUI,fn=["item-message","mark-ready","grill-more","prototype-request","visual-request","reask-request"],wt="question-reopen",ko=Object.freeze({todo:"Todo",progress:"In Progress",review:"In Review",done:"Done"}),Se="move",gn=10*6e4,kn=5*6e4,ht="build-refused",qe=Object.freeze({ask:"builder-ask",answer:"builder-answer"}),mn=864e5,ue=i(e=>e.lane==="done","closedLane"),wn=i(e=>`file:${e}`,"fileFold"),yt=7*mn,bt=i((e,t)=>({id:e,url:t?.workspace?`https://linear.app/${t.workspace}/issue/${e}`:null}),"linearRef"),hn=i(e=>String(e).replace(/^S(\d+)$/,"INT-$1"),"internalRef"),nt=i((e,t,n)=>Xt(n,t.ticket)?t.ticket:e.internal===!0?hn(t.id):t.id,"refOf"),yn=i((e,t,n)=>ft(e,t,n)||(t?{id:String(t),url:null,source:null}:null),"ticketOf");function bn(e,t,n,s,r,a,d,c,u){return!a&&(e.lane==="done"||t?.state==="MERGED"||t?.state==="CLOSED")?"done":(n||s)&&!c?"progress":r?"review":t?.state==="OPEN"?t.checks?.state==="fail"&&(!t.isDraft||d)?"progress":"review":t||u?"progress":"todo"}i(bn,"laneOf");const Ke="Lander",St=i(e=>e.kind!==ce.install&&e.label!==Ke,"changesCode");function Sn(e,t){return e?.state==="MERGED"||t?H.merged:e?.state==="CLOSED"?H.closed:e?.state==="OPEN"?e.isDraft?H.draft:H.open:null}i(Sn,"prStateOf");const vt=i(e=>e.pr?.status===H.closed,"unlanded"),vn=i(e=>e.some(t=>t.kind==="work")&&e.every(t=>t.lane==="done"),"finishedOf");function st(e){const t=xt(e);return t?.done&&t.handed_back?t.handed_back:null}i(st,"handedBackAt");const xt=i(e=>e.filter(t=>t?.started&&V(t)&&t.kind!==ce.install).sort((t,n)=>String(t.started).localeCompare(String(n.started))).at(-1),"newestBuild");function xn(e,t,n){return Object.fromEntries(e.map(s=>{const r=E(t?.reviews,s.github),a=r==="APPROVED"?"approved":r==="CHANGES_REQUESTED"?"changes":Object.hasOwn(n,s.name)?"sent":"unsent";return[s.name,{state:a,since:a==="sent"?E(n,s.name):null,github:s.github||null}]}))}i(xn,"reviewOf");function On(e,t,n,s,r,a,{held:d,needsApproval:c}){return n?.kind===ce.install?"builder":s?"checking":n?"builder":d&&e==="review"?"merge":r&&e==="review"?"owner":e==="review"&&t?.state==="OPEN"&&t.isDraft?a?"ci":"ready":e==="review"?Ot(t,c)?"merge":"reviewers":null}i(On,"waitsOnOf");const Ot=i((e,t)=>e?.reviewDecision==="APPROVED"||!t&&!["REVIEW_REQUIRED","CHANGES_REQUESTED"].includes(e?.reviewDecision),"reviewed"),De=/(^|\/)prototype-[\w-]+\.html/,$n={reviewers:"Waiting for PR reviewers",merge:"Approved, merges once CI and the merge state allow",ci:"Marked ready for review once CI passes"},Ge={waiting:"Approved, Merge shows once CI and the merge state allow",ready:"Approved and green: tap Merge",merging:"Merging"},ot={auto:"Ready, merges once CI and the merge state allow",waiting:"Ready, Merge shows once CI and the merge state allow",ready:"Ready and green: tap Merge"},An=i(e=>`Verified: tap Merge to land it on ${e}`,"heldNext"),En="Fix the failing CI",$t="Tap Done once it is settled",Bn=["status","agent","ask-link","command",Se,qe.answer],At=i(e=>(e.who==="claude"||e.via==="github")&&!Bn.includes(e.kind)&&(e.kind!==Fe||kt(e).fault),"countsUnread"),_n=["lander-handoff","land-waiting"];function Rn({slug:e,itemFile:t,it:n,order:s,prs:r,repo:a,remote:d,base:c,builder:u,reachedPhase:g,agents:m,worktree:p,eta:w,thread:x,events:_,working:O,said:N,askedAt:D,topicItems:B,prSlack:T,boardReviewers:P,now:y,bumpedAt:b,holdAsk:l,closedAt:A,mergeAt:h,autoMerge:te,skipDraft:ae,tickets:G}){const X=r.map(f=>({repo:f.repo,number:f.number,url:ln(f),...f.gh||f.kept||{},status:Sn(f.gh,f.merged_at)})),F=rn(r.map(f=>f.gh)),L=r[F]?.gh||null,Y=X[F]?.status??null,z=n.lane||null,Ne=!!A&&String(A)>String(n.lane_at||"");Ne&&(n={...n,lane:"done",closed_at:A,reopened_at:void 0});const U=[n.closed_at,...r.flatMap(f=>[f.merged_at,f.gh?.mergedAt])].filter(Boolean).map(String).sort().pop()||null,j=String(U||""),Te=X.length>0&&X.every(f=>f.status===H.closed),K=(n.lane==="done"||n.reopened_at||Y===H.merged)&&!Te&&([...N.filter(f=>f.kind==="item-message").map(f=>f.ts),...D].map(String).filter(f=>f>j).sort().pop()||n.reopened_at)||void 0,ge=n.owner_step&&!Ne?String(n.owner_step):null,Ve=(n.lane==="done"||Y===H.merged)&&!Te,Le=!!K&&!!n.next&&String(n.next_at||"")>String(n.reopened_at||U||"");K&&!Le&&(n={...n,next:void 0});const Ae=st(m),Xe=!!K&&N.some(f=>String(f.ts)>j&&(f.state==="waiting"||f.state==="working")),Q=`${e}/${n.id}`,J=Zt(t,P),Ee=Object.fromEntries(Object.entries(zt(t,n,Q,B,Yt(T,r))).map(([f,W])=>[f,Jt(W.ts)])),ke=Object.values(Ee).filter(Boolean).sort()[0]||null,me=[...x.map(f=>f.ts),..._.map(f=>f.ts)].sort().pop()||null,Be=[...x.filter(f=>At({...f,who:f.who||"claude"})).map(f=>f.ts),..._.map(f=>f.reply?.at)].filter(Boolean).sort(),Z=!et(n)&&n.pushed?.branch?{branch:n.pushed.branch,at:n.pushed.at||null,host:d?.host||null}:null,ne=!et(n)&&n.merge_ready?.branch?{branch:n.merge_ready.branch,at:n.merge_ready.at||null,base:c}:null,le=!!n.rebase_run&&L?.state==="OPEN",_e=(n.rebased||[]).at(-1),Re=m.filter(f=>f.started&&!f.done&&V(f)),Pe=!!Ae&&St(xt(m));let R=bn(n,L,Re.some(St),Pe,Z||ne,!!K,ae,le,Re.length>0||!!Ae);K&&R==="todo"&&(R="progress");const Ye=R==="review"&&!!ke&&y-Date.parse(ke)>yt,Me=!!ge&&Ve&&R==="done";Me&&(R="review");let C=On(R,L,u,!!Ae&&R!=="done",Z,ae,{held:ne,needsApproval:J.length>0});const ze=C==="builder"&&L?.state==="OPEN"&&!L.isDraft,ie=R==="progress"&&!C&&L?.state==="OPEN"&&(!L.isDraft||ae),de=Me||!!K&&R==="progress"&&!C&&!ie&&!Xe&&!Le;de&&(C="owner");const we=l&&String(l.ts)>String(n.held_at||""),Ie=R==="todo"&&(we?!!l.data.hold:!!n.held),je=Ie&&(we?l.data.until:n.pick_up_at)||null,Ue=R!=="done",Je=!!b&&!(j&&String(b)<=j),o=x.filter(f=>(f.who||"claude")==="claude"&&!["status","command",Se].includes(f.kind)&&!_n.includes(f.line)).map(f=>String(f.ts)).sort().pop()||"",k=[h,ne&&n.merge_ready.tapped].filter(Boolean).map(String).sort().pop(),S=C==="merge"&&!!k&&k>o,$=x.filter(f=>(f.kind==="status"||f.kind===Fe)&&String(f.ts)>String(k)).sort((f,W)=>String(f.ts).localeCompare(String(W.ts))).at(-1),v=$?.line==="land-waiting"?`${Ge.merging} \xB7 ${$.text[0].toLowerCase()}${$.text.slice(1)}`:Ge.merging,M=x.filter(f=>!["status",Se].includes(f.kind)).sort((f,W)=>String(f.ts).localeCompare(String(W.ts))).at(-1),I=R==="todo"&&M?.line===ht?M.text:null,he=K||m.filter(V).map(f=>f.started).filter(Boolean).map(String).sort().pop()||null,se=L?.reviewDecision==="APPROVED",Ze=Z&&C==="owner"?`merge ${Z.branch} into ${c}`:ne&&C==="merge"?S?v:An(c):C==="merge"&&!te?S?v:(se?Ge:ot).waiting:C==="merge"&&!se?ot.auto:$n[C],We=Y===H.draft?`Review draft PR #${X[F].number}`:null,ye=!le&&_e?.to&&_e.to===L?.headRefOid?`Rebased onto ${_e.base||c}, pushed${L.mergeable==="MERGEABLE"?" \xB7 PR is clean again":""}`:null,be=le&&C==="checking"?m.flatMap(f=>f.calls).sort((f,W)=>String(f.at).localeCompare(String(W.at))).at(-1)?.text??null:null;return{key:Q,kind:"work",order:s,file:e,fileTitle:t.title||e,id:n.id,ref:nt(t,n,G),title:n.title,storedLane:z,created:n.created||null,reviewSince:n.review_since||ke||n.merge_ready?.at||n.pushed?.at||n.created||null,progressSince:he,closedAt:R==="done"?U:null,internal:t.internal===!0,repo:t.internal===!0&&!t.repo?null:a,lane:R,waitsOn:C,builder:C==="checking"?null:u,reachedPhase:g,agents:m,worktree:p??null,eta:w,working:O,reopened:!!K,awaitsDone:de,ownerStep:de?ge:null,ownerCheck:de&&ge&&n.owner_check?{words:dn(cn(n.owner_check.check)),broken:n.owner_check.broken||null}:null,stagedAt:R==="todo"&&n.phase==="staging"&&n.staged_at||null,roundAt:n.round_at||null,deps:n.deps||[],blockedUntil:n.blocked_until||null,blockReason:n.block_reason||null,held:Ie,heldUntil:je,risky:!!n.risky,bumpable:Ue,bumpedAt:Ue&&Je?b:null,holdable:R!=="done",hasNotes:!!(n.plan||String(n.notes||"").trim()),finishable:R!=="done",merging:S,refusal:I,manualSteps:n.manual_steps||[],fromAsk:n.from_ask||null,unfiled:!!n.unfiled,filedHeld:!!n.filed_held,next:ze||R==="done"||C==="checking"?null:ie?En:ye||Ze||n.next||We||null,rebaseFailed:be,branch:n.branch||L?.headRefName||Z?.branch||null,pushed:R==="done"||Me?null:Z,mergeReady:R==="review"?ne:null,stale:R!=="done"&&(Ye||!!u?.stale),stalled:n.stalled?.reason||null,landStopped:R==="review"&&ne&&n.merge_ready.stopped&&String(n.merge_ready.stopped)>String(n.merge_ready.tapped||"")?{words:Rt("land"),at:n.merge_ready.stopped}:null,ticket:G?.source==="linear"&&!n.ticket&&n.linear?.length?null:yn(G,n.ticket,{repo:a}),ticketSource:G?.source||null,linear:(n.linear||[]).map(f=>G?.source==="linear"&&ft(G,f,{repo:a})||bt(f,G)),pr:X[F]||null,prs:X,reviewers:J.map(f=>f.name),review:xn(J,L||r[F]?.kept,Ee),inReviewSince:ke,threadCount:x.length+_.length,lastTs:me,othersAt:Be,grilling:Mt("grill",_,m,D),prototyping:Mt("prototype",_,m,D),storedPhase:n.phase||null,phase:null,updatedAt:L?.mergedAt||L?.updatedAt||r[F]?.merged_at||u?.at||me||null,ask:null,fold:null,action:null,install:R==="done"?null:In(n.install,L),waitFacts:Mn(n,{agents:m,thread:x,events:_,refusedAt:I?M.ts:null})}}i(Rn,"workItem");function Mn(e,{agents:t,thread:n,events:s,refusedAt:r}){const a=new Set(n.filter(g=>g.kind===qe.answer).map(g=>g.ask)),d=n.filter(g=>g.kind===qe.ask&&!a.has(g.ask)).sort((g,m)=>String(g.ts).localeCompare(String(m.ts)))[0],c=i(g=>s.filter(m=>at(m,g)).pop()?.ts||null,"requestAt"),u=n.filter(g=>(g.who||"claude")==="claude"&&!["status","agent","command",Se].includes(g.kind)&&g.line!==ht).map(g=>g.ts);return{handedBackAt:st(t),next:e.next||null,ask:d?{at:d.ts,text:d.text}:null,grillAt:c("grill"),prototypeAt:c("prototype"),refusedAt:r,unblockedAt:e.unblocked_at||null,unverified:t.some(g=>g.calls.some(m=>!g.verified||String(m.at)>String(g.verified.at))),writes:[...u,...t.flatMap(g=>[g.verified?.at,g.started])].filter(Boolean).map(String).sort()}}i(Mn,"waitFactsOf");function In(e,t){if(!e?.url)return null;const n=e.sha?String(e.sha).toLowerCase():null,s=t?.headRefOid?String(t.headRefOid).toLowerCase():null;return{url:e.url,label:e.label||null,builtAt:e.built_at||null,sha:n,older:!!(n&&s&&!s.startsWith(n))}}i(In,"installOf");function qn(e,{needsApproval:t=!0}={}){if(e?.state!=="OPEN")return[`PR is ${String(e?.state||"unknown").toLowerCase()}`];const{state:n,pending:s,failing:r}=e.checks||{};return[e.isDraft&&"it is a draft",!Ot(e,t)&&`review: ${String(e.reviewDecision||"none").toLowerCase().replace(/_/g," ")}`,n==="fail"&&`failing: ${r?.length?r.join(", "):"a check"}`,n==="pending"&&`pending: ${s?re(s,"check"):"checks"}`,e.mergeStateStatus!=="CLEAN"&&`merge state: ${String(e.mergeStateStatus||"unknown").toLowerCase()}`].filter(Boolean)}i(qn,"mergeBlockers");const Dn=i((e,t)=>!qn(e,{needsApproval:t}).length,"mergeable");function Cn(e,t,n){if(ue(e))return null;const s=`#selectedCard=${encodeURIComponent(e.key)}`;if(e.stalled)return{kind:q.stalled,url:s,label:`stalled: ${e.stalled}`};if(Nn(e)){const r=t.get(e.questions.first);return De.test(r?.url||"")?{kind:q.pick,url:`questions.html#${r.id}`,label:"Prototypes ready: pick a take"}:{kind:q.questions,url:`questions.html#${e.questions.first}`,label:`Answer ${re(e.questions.open,"question")}`}}return e.waitsOn==="ready"?{kind:q.ready,url:e.pr.url,label:`Review draft PR #${e.pr.number}`}:e.kind==="ask"&&e.waitsOn==="owner"?{kind:q.done,url:s,label:e.next||$t}:e.waitsOn==="merge"&&e.manualSteps.length&&!e.mergeReady?{kind:q.steps,url:s,label:"Run the manual steps, then merge"}:e.waitsOn==="merge"&&(!n||e.mergeReady)&&!e.merging&&e.prs.every(r=>["MERGED","CLOSED"].includes(r.state)||Dn(r,e.reviewers.length>0))?{kind:q.merge,url:s,label:"Merge"}:null}i(Cn,"actionOf");const Nn=i(e=>!!(e.questions&&e.questions.state!=="resolved"&&e.questions.open>0),"asking"),rt=new Set([q.pick,q.questions]);function Et(e){return e.questions?oe.openQuestions:null}i(Et,"actionButtonOf");const Tn=i(e=>e.kind==="work"&&e.lane==="todo"&&!e.action&&!e.unpicked,"buildableOf"),Ln=i((e,t)=>/^item:/.test(e.topic||"")&&De.test(e.url||"")&&!(e.options||[]).some(n=>n.id===t),"picksNoTake");function Pn(e,t){const n=e.filter(a=>a.topic===`item:${t}`),r=[...n.filter(a=>De.test(a.url||"")).flatMap(a=>a.answers.map(d=>({q:a,a:d,again:Ln(a,d.choice)}))),...n.filter(a=>a.request).map(a=>({q:a,a:a.request,again:!0}))].sort((a,d)=>String(a.a.ts).localeCompare(String(d.a.ts))).at(-1);return r?.again?{id:r.q.id,note:r.a.note.replace(/\s+/g," ").trim()}:null}i(Pn,"noTakePicked");const jn="Prototype again",Un=Object.freeze({[q.pick]:oe.openQuestions,[q.questions]:oe.openQuestions,[q.ready]:oe.github,[q.steps]:oe.github,[q.merge]:oe.merge,[q.done]:oe.done,[q.stalled]:oe.reply}),Wn="Merges once unblocked",Fn=i(e=>`Blocked ${[e.blockedBy.length&&`by ${e.blockedBy.map(t=>t.ref).join(", ")}`,e.blockedUntil&&`until ${e.blockedUntil}`].filter(Boolean).join(" and ")}`,"mergeBlockedWords");function Kn(e,t,n){if(e.action=Cn(e,t,n),e.mergeBlocked=e.action?.kind===q.merge&&e.blocked?Fn(e):null,e.mergeBlocked&&Object.assign(e,{action:null,next:Wn}),e.action?.kind===q.questions&&e.questions.fromAsks){Object.assign(e,{action:null,waitsOn:"owner",next:e.action.label,actionButton:Et(e),resolvedBy:null});return}e.awaitsDone&&=!e.action,e.awaitsDone||Object.assign(e,{ownerStep:null,ownerCheck:null}),e.actionButton=Et(e),e.resolvedBy=Un[e.action?.kind]||null,e.action?.kind===q.merge&&!e.mergeReady&&(e.next=(e.pr?.reviewDecision==="APPROVED"?Ge:ot).ready),e.awaitsDone&&Object.assign(e,{lane:"review",next:e.ownerStep||$t,bumpable:!1,bumpedAt:null});const s=e.lane==="todo"||e.waitsOn==="ready"||e.lane==="progress"&&e.waitsOn==="owner";if(!e.action||!s||e.stalled)return;const r=e.lane==="todo"&&rt.has(e.action.kind)?"todo":"review";Object.assign(e,{lane:r,waitsOn:e.waitsOn==="ready"?"ready":"owner",next:e.action.label})}i(Kn,"placeOwnerWait");const Bt=i((e,t)=>e?e!=="todo":t.some(V),"started"),_t="none set yet";function mo(e,t){const n=t.filter(s=>s?.started);return e.next||et(e)||e.pushed||e.held||e.lane==="done"||st(n)?!1:!n.some(s=>!s.done)&&Bt(e.lane,n)}i(mo,"owesNext");function Gn(e,t=new Date){const n=new Date(e),s=n.toLocaleTimeString("en-GB",{hour:"2-digit",minute:"2-digit"});return n.toDateString()===t.toDateString()?s:`${n.getMonth()+1}/${n.getDate()} ${s}`}i(Gn,"askedLabel");function Qn(e,t,n,s,r,a,d,c){const u=t.find(h=>h.kind==="ask"&&h.eventId===e.id),g=u.files,m=c||(u.text||"").trim().split(`
2
- `)[0]||g.map(h=>h.name).join(", ")||"Ask",p=u.state==="replied"||u.state==="answered",w=r?.state==="progress"&&!(String(a||"")>String(r.at||"")),x=!w&&(!!r||!!a),_=p&&!x&&!w&&d.open>0,O=p&&!x&&!w&&!_&&d.answered>0,N=x||p&&!w&&!_&&!O,D=p&&!s&&!r?.moved&&[...t.filter(h=>h.who==="claude"&&h.kind!=="ask-link"&&h.kind!=="status"&&h.kind!=="command").map(h=>h.ts),...t.map(h=>h.reply?.at)].filter(Boolean).sort().pop()||null,B=N||w||_||O,T=t.some(h=>h.who==="love"&&h.kind!=="ask"&&(h.state==="waiting"||h.state==="working")),P=t.filter(h=>h.who==="love"&&h.kind==="item-message").flatMap(Ce).filter(([,h])=>h==="waiting"||h==="working").map(([h])=>h),y=B?"answered":u.state,b=B?null:n||null,l=[...t.filter(At).map(h=>h.ts),...t.map(h=>h.reply?.at)].filter(Boolean).sort(),A=[...t.map(h=>h.ts),...l,...w?[r.at]:[]].filter(Boolean).sort().pop();return{key:`ask:${e.id}`,kind:"ask",order:0,file:null,fileTitle:null,id:e.id,ref:B?Gn(e.ts):"New",isNew:!B,title:m,storedLane:null,internal:!1,repo:null,lane:w?"progress":N?"done":"todo",waitsOn:w&&!T?"owner":null,builder:null,agents:[],eta:null,working:t.some(h=>Ce(h).some(([,te])=>te==="working")),deps:[],held:!1,risky:!1,bumpable:!B,bumpedAt:b,manualSteps:[],fromAsk:null,from:null,next:w&&r.next||null,hasNotes:!1,branch:null,finishable:B&&!N,linear:[],pr:null,prs:[],reviewers:[],review:{},inReviewSince:null,reviewSince:null,progressSince:w&&r.at||null,closedAt:null,threadCount:t.length,lastTs:A,othersAt:B?l:[],grilling:!1,prototyping:!1,created:e.ts,updatedAt:B?A:e.ts,ask:{eventId:e.id,state:y,askedAt:e.ts,bumpedAt:b,edited:!!u.edited,canEdit:!B,text:u.text,files:g,answerAt:D,from:u.from||null,owed:P},fold:null,action:null}}i(Qn,"askItem");const pe="visual",ve="helper",Hn=new Set(["grill","prototype",pe]),ee=i(e=>Hn.has(e.kind),"plans"),V=i(e=>!ee(e),"builds"),Vn={grill:"grilling",prototype:"prototyping"},Xn=i((e,t)=>`${e} ${t} by orchestrator`,"orchestratorDid"),Rt=i(e=>Xn(e,"stopped"),"stoppedWords"),Yn={grill:"grill-more",prototype:"prototype-request"},at=i((e,t)=>e.kind===Yn[t]&&!e.data?.question,"isRequest");function Mt(e,t,n,s){if(n.some(a=>a.kind===e&&a.started&&!a.done))return!0;const r=t.filter(a=>at(a,e)).pop();return!!(r&&!r.reply&&!s.some(a=>String(a)>String(r.ts))&&!n.some(a=>a.kind===e&&a.done&&String(a.started)>=String(r.ts)))}i(Mt,"planningOf");const wo=30*6e4;function ho(e,t){const n=[];for(const s of e.items){if(ue(s)||(s.agents||[]).some(a=>a.kind==="prototype"&&a.started&&!a.done))continue;const r=s.prototyping&&t.filter(a=>a.data?.key===s.key&&at(a,"prototype")).pop();r&&n.push({key:s.key,request:r.id,since:r.ts});for(const a of e.owner.filter(d=>d.topic===`item:${s.key}`&&d.state==="requested"))n.push({key:s.key,request:a.request.id,question:a.id,since:a.request.ts})}return n}i(ho,"prototypeWaits");function lt(e={},t=!1){return Object.entries(e).filter(([n,s])=>s&&typeof s=="object"&&(t||!n.startsWith("past-"))).map(([n,s])=>({name:n,kind:It(s.kind)||null,model:s.model||(s.kind==="opus"||s.kind==="sonnet"?s.kind:null),label:s.label||null,started:s.started||null,eta:s.done?null:s.eta||null,done:s.done||null,log:s.session?.log||null,pid:s.session?.pid??null,calls:Array.isArray(s.calls)?s.calls:[],verified:s.verified||null,stopped:s.stopped||null,handed_back:s.handed_back||null,...s.pause?{pause:s.pause}:{},...s.kind===ve?{writes:!!s.writes,requested:s.requested||null}:{},...s.waits?{waits:s.waits}:{}})).sort((n,s)=>String(n.started||"").localeCompare(String(s.started||""))||n.name.localeCompare(s.name))}i(lt,"agentsOf");function it(e={}){return Object.values(e).filter(t=>t?.worktree?.path).sort((t,n)=>String(t.started||"").localeCompare(String(n.started||""))).at(-1)?.worktree.path??null}i(it,"worktreeOf");function zn(e,t=[]){const n=e.filter(V).map(s=>String(s.started||"")).filter(Boolean).sort().pop();return n===void 0?null:t.filter(s=>s.kind==="status"&&s.phase&&!s.planner&&String(s.ts)>=n).sort((s,r)=>String(s.ts).localeCompare(String(r.ts))).pop()?.phase||null}i(zn,"reachedPhaseOf");function Jn(e,t=[],n,s=null,r=null,a=!1){const d=e.filter(b=>b.started&&!b.done&&b.kind!==pe),c=d.every(ee),u=c?d:d.filter(V),g=u.map(b=>String(b.started||"")).sort()[0];if(g===void 0)return null;const m=t.filter(b=>b.kind==="status"&&String(b.ts)>=g&&(c||!b.planner)).sort((b,l)=>String(b.ts).localeCompare(String(l.ts))).pop(),p=u.filter(b=>b.kind!==ve).at(-1),w=u.some(b=>b.kind===ce.install)?ce.install:p?It(p.kind):"claude",x=c?null:u.find(b=>b.stopped&&String(b.stopped.at)>=String(b.started))?.stopped,_=m?.ts||g,O=x?x.at:s!==null&&s>Date.parse(_)?new Date(s).toISOString():_,N=m?m.phase||m.planner?m.text.replace(": "," \xB7 "):m.text||null:c?Vn[w]:null,D=n-Date.parse(O),B=c?null:u.find(b=>b.pause)?.pause??null,T=!c&&!x&&(r==="held"||!!B?.paused),P=c||T?null:x?!1:r,y=T?!1:P===null?D>=gn:!P;return{progress:N,phase:m?.phase||null,at:_,kind:w,planner:c,silentSince:O,stale:y,alive:P,held:T,quiet:!!B||P===!0&&!a&&D>=kn,stopped:x?Rt(x.run):null,...B?{pause:{cause:B.cause,words:B.words,paused:!!B.paused}}:{}}}i(Jn,"builderOf");const yo={blocked:["nothing blocks it","nothing blocks it"],held:["you unhold it","unheld"],usage:["the usage pause lifts","the usage pause lifted"],bumped:["a slot frees","a slot freed"]},Zn=15*6e4,es=i(e=>(e.agents||[]).find(t=>t.name==="main"&&t.started&&!t.done&&V(t))??null,"mainBuild"),ts=i((e,t,n,{now:s,usageHeld:r})=>e.blocked||e.held||r(n??"claude")||!!(t.until&&s<Date.parse(t.until)),"stillPaused");function bo(e,{now:t=Date.now(),usageHeld:n}){const s={ask:[],drop:[],stop:[],resume:[]},r=[];for(const a of e){if(a.kind!=="work"||a.lane!=="progress")continue;const d=es(a);if(!d)continue;const{pause:c}=d;if(!c){a.builder?.phase!=="landing"&&n(d.kind??"claude")&&s.ask.push(a.key);continue}const u=ts(a,c,d.kind,{now:t,usageHeld:n});if(c.paused){u||r.push({key:a.key,at:c.paused});continue}!u&&c.cause!=="bumped"?s.drop.push(a.key):t-Date.parse(c.at)>=Zn&&s.stop.push(a.key)}return s.resume=r.sort((a,d)=>String(a.at).localeCompare(String(d.at))).map(a=>a.key),s}i(bo,"pauseWork");const dt={opus:"Claude Opus",sonnet:"Claude Sonnet",codex:"Codex"},ns=i(({model:e,kind:t})=>dt[e]||dt[t]||(t==="claude"?dt.opus:null),"modelName"),It=i(e=>e==="opus"||e==="sonnet"?"claude":e,"providerOf");function xe(e){const t=ns(e),{kind:n}=e,s=e.label?.replace(/\bbuilder\b/,"implementer").replace(/\bBuilder\b/,"Implementer"),r=i(d=>d[0].toLowerCase()+d.slice(1),"lower");if(t&&ee(e))return`${t} ${n}`;if(t)return`${t} ${r(s?.replace(/^(?:claude\s+)?(?:opus|sonnet|codex)\b\s*/i,"")||"implementer")}`;const a=n?n[0].toUpperCase()+n.slice(1):"";return a?s?s.toLowerCase().includes(n.toLowerCase())?s:`${a} ${r(s)}`:a:s||"Implementer"}i(xe,"agentName");const qt=i((e,t)=>!!e.resolved&&(!e.resolved_at||String(e.resolved_at)>=String(t)),"resolvedSince");function ss(e,t,n=[],s=[],r=[]){const a=t.filter(O=>O.data?.id===e.id),d=a[a.length-1]||null,c=r.filter(O=>O.data?.question===e.id).at(-1),u=i((O,...N)=>!!O&&N.every(D=>String(O.ts)>String(D||"")),"after"),g=!!(e.resolved&&e.resolved_at&&d&&String(d.ts)>e.resolved_at),m=n.filter(O=>O.data?.question===e.id&&u(O,d?.ts,c?.ts)).at(-1),p=u(c,d?.ts,m?.ts,e.resolved_at),w=e.resolved&&!g&&!p,x=w?s.filter(O=>O.data?.question===e.id&&String(O.ts)>String(e.resolved_at||"")).at(-1):null,_=p?"open":w?"resolved":m?"requested":d?d.reply?"replied":"answered":"open";return{...e,state:_,reopened:p,request:m?{id:m.id,ts:m.ts,note:m.data.note||"",choice:m.data.choice||null}:null,reask:x?{id:x.id,ts:x.ts}:null,answers:a.map(O=>({ts:O.ts,choice:O.data.choice||null,note:O.data.note||"",files:O.data.files||[],reply:O.reply,replies:O.replies,acted:qt(e,O.ts)}))}}i(ss,"ownerQuestion");const fe=i(e=>e.round??(De.test(e.url||"")?1:0),"roundOf");function os(e,t){const n=i((s,r)=>s.confirm_of||(s.options||[]).some(a=>a.id===r.choice),"settles");return e.filter(s=>fe(s)===t&&s.answers.length).map(s=>[s,s.answers.at(-1)]).filter(([s,r])=>n(s,r)).map(([,s])=>s).sort((s,r)=>String(s.ts).localeCompare(String(r.ts))).at(-1)}i(os,"roundSettledBy");function rs(e){const t=e.find(r=>r.request&&!e.some(a=>fe(a)>fe(r)));if(t)return{n:fe(t)+1,state:"making"};const n=Math.max(0,...e.map(fe));if(!n)return null;const s=os(e,n);return s?{n,state:"closed",take:s.choice}:e.some(r=>fe(r)===n&&r.state!=="resolved")?{n,state:"back"}:null}i(rs,"roundState");function as(e){const t=e.filter(n=>De.test(n.url||"")).map(n=>({n:fe(n),q:n.id,url:n.url,confirm:!!n.confirm_of,state:n.state})).sort((n,s)=>n.n-s.n);return t.length?{open:t.some(n=>n.state==="open"),rounds:t}:null}i(as,"prototypesOf");const Oe=i((e,t,n)=>{const s=e.get(t);s?s.push(n):e.set(t,[n])},"add"),$e=i((e,t)=>{const n=new Map;for(const s of e)Oe(n,t(s),s);return n},"groupBy");function ls(e,t,n=new Set){const s=new Set(e.map(d=>d.id)),r=[...e];t.some(d=>!s.has(d.topic))&&r.push({id:"",title:"Other"});const a=$e(t,d=>s.has(d.topic)?d.topic:"");return r.map(d=>{const c=a.get(d.id)||[],u=i(p=>c.filter(w=>w.state===p).length,"count"),g=c.every(p=>p.state==="resolved")||n.has(d.id)&&!u("open"),m=c.length&&g?"resolved":"open";return{...d,state:m,questions:c.map(p=>p.id),open:u("open"),answered:u("answered"),replied:u("replied"),resolved:u("resolved"),round:rs(c)}}).filter(d=>d.questions.length)}i(ls,"ownerTopics");const Qe={implement:"Implementation requested",prototype:"Prototype requested",grill:"Grill requested",visual:"Visual requested",reask:"Re-ask requested",hold:"Held",resume:"Resumed",done:"Marked done",staged:"Marked as staging",readied:"Marked as ready",related:"Related to",unrelated:"Unrelated from",followup:"Follow-up","followup-of":"Follow-up of"},Dt={"grill-more":"grill","prototype-request":"prototype","visual-request":"visual","reask-request":"reask"},Ct={"mark-ready":"Mark ready",...Object.fromEntries(Object.entries(Dt).map(([e,t])=>[e,Qe[t]]))},is=["grill-more","prototype-request","visual-request"],ds="answers submitted",Nt={reply:"Reply",build:"Implement"},cs=i(e=>Nt[e.data.intent],"intentLabel"),us=i((e,t)=>{if(e.kind!=="prototype-request"||!t.has(e.data.question))return e.data.text||(e.data.files?.length?"":E(Ct,e.kind))||"";const n=t.get(e.data.question),s=(n.options||[]).find(r=>r.id===e.data.choice);return[Lt(t,n),s&&`${s.id}. ${s.label}`,e.data.note].filter(Boolean).join(`
3
- \u2192 `)},"actionText"),Tt=i((e,t)=>[...e.keys()].indexOf(t)+1,"numberOf"),Lt=i((e,t)=>`Question ${Tt(e,t.id)} \xB7 ${t.question}`,"numbered"),ps="question reopened",He=i(e=>e.data?.shared_by?.login?{by:e.data.shared_by.name||e.data.shared_by.login}:{},"byOf"),fs=i((e,t)=>{const n=t.get(e.card);return{...e,text:`${Qe[e.command]} ${n?`${n.ref}: ${n.title}`:e.card}`,link:`#selectedCard=${encodeURIComponent(e.card)}`}},"linkLine"),Pt=i(e=>({kind:"command",command:e,request:!0,mark:Qe[e]}),"commandLine"),gs=i((e,t)=>{if(e.kind==="item-message")return Nt[e.data.intent]?{status:cs(e)}:{};const n={request:!0,command:Dt[e.kind]};return is.includes(e.kind)?(e.data.question?t.has(e.data.question):String(e.data.text||"").trim()||e.data.files?.length)?{status:Ct[e.kind]}:n:e.kind==="reask-request"?n:{}},"requestOf"),ct=i(e=>[...e.filter(t=>t.who==="claude"&&!["status","agent","command",Se,qe.answer].includes(t.kind)).map(t=>t.ts),...e.filter(t=>t.kind==="ask").map(t=>t.reply?.at)].filter(Boolean).map(String).sort().pop()||"","claudeAtOf");function jt(e,t={}){const n=new Map,s=new Map;for(const p of t.items||[])Oe(n,p.topic,p),s.set(p.id,p.topic);const r=new Map,a=new Map,d=new Map,c=new Map,u=new Map,g=new Map,m=new Map;e.forEach((p,w)=>{m.set(p,w),p.data?.key&&fn.includes(p.kind)&&Oe(r,p.data.key,p),p.kind==="owner-answer"&&s.has(p.data?.id)&&Oe(a,s.get(p.data.id),p),p.kind===wt&&s.has(p.data?.question)&&Oe(d,s.get(p.data.question),p),p.kind==="ask"&&c.set(p.id,p)});for(const p of e.filter(w=>w.kind==="ask-edited").sort((w,x)=>String(w.ts).localeCompare(String(x.ts)))){u.set(p.data?.id,p.data?.text);for(const w of p.data?.files||[])Oe(g,p.data?.id,w)}return{byKey:r,answers:a,reopens:d,asks:c,edits:u,editFiles:g,questions:n,order:m}}i(jt,"threadIndex");const ut=i((e,t)=>{const n=t.filter(s=>s?.length);return n.length>1?n.flat().sort((s,r)=>e.order.get(s)-e.order.get(r)):[...n[0]||[]]},"inLogOrder"),ks=/; its log is \S*\/\.claude\/kickflip\/logs\/([^/\s]+\.log)\.$/,ms=i(e=>{const t=!e.log&&typeof e.text=="string"?ks.exec(e.text):null;return t?{...e,text:`${e.text.slice(0,t.index)}.`,log:t[1]}:e},"withLog");function ws(e,t){const n=t.run?e.find(s=>s.started===t.run):e.filter(s=>ee(s)===!!t.planner&&s.started&&String(s.started)<=String(t.ts)&&(!s.done||String(t.ts)<=String(s.done))).pop();return n?{agent:xe(n),run:pt(e,n),running:!n.done}:{}}i(ws,"agentOfLine");function pt(e,t){return t.label!==Ke?t.started:e.filter(n=>V(n)&&n.label!==Ke&&n.started&&String(n.started)<String(t.started)).pop()?.started??t.started}i(pt,"sectionOf");function Ut(e,{lines:t=[],events:n=[],owner:s={},working:r={},fromAsk:a=null,links:d=[],agents:c={},moved:u=[],cards:g=new Map,index:m=jt(n,s)}){const p=e.startsWith("ask:")?[e.slice(4)]:[a,...u].filter(Boolean),w=[...new Set([e,...p.map(l=>`ask:${l}`)])],x=new Map((m.questions.get(`item:${e}`)||[]).map(l=>[l.id,l])),_=m.answers.get(`item:${e}`)||[],O=new Set([...x.values()].map(l=>l.round_of).filter(Boolean)),N=lt(c,!0).filter(V).map(l=>String(l.started||"")).filter(Boolean),D=new Set(_.filter(l=>qt(x.get(l.data.id),l.ts)||O.has(l.data.id)||N.some(A=>A>String(l.ts))).map(l=>l.id)),B=lt(c,!0),T=[...B.flatMap(l=>[...l.started?[{ts:l.started,who:"claude",kind:"agent",text:`${xe(l)} started`,agent:xe(l),run:pt(B,l),finished:!1,eta:l.done?null:l.eta,...l.log?{log:l.log}:{},...ee(l)?{planner:!0}:{}}]:[],...l.done?[{ts:l.done,who:"claude",kind:"agent",text:`${xe(l)} finished`,agent:xe(l),run:pt(B,l),finished:!0,eta:null,...ee(l)?{planner:!0}:{}}]:[],...l.calls.length?[{ts:l.done||l.calls.at(-1).at,who:"claude",kind:"calls",text:xe(l),calls:l.calls.map(A=>A.text),verified:l.verified}]:[]]),...t.map(ms).map(l=>l.card?fs(l,g):l).map(l=>({ts:l.ts,who:l.who||"claude",...l.log?{log:l.log}:{},...l.kind==="status"?{kind:"status",...l.planner?{planner:!0}:{},...ws(B,l)}:{},...l.kind===pe||l.kind===Se?{kind:l.kind}:{},...l.kind===Fe?{kind:Fe,line:l.line,...kt(l)}:{},...l.command?Pt(l.command):{},...l.card?{card:l.card}:{},...Object.values(qe).includes(l.kind)?{kind:l.kind,ask:l.ask}:{},text:l.text||"",html:l.html||null,link:l.link||null,via:l.via||null,...l.by?{by:l.by}:{}})),...ut(m,w.map(l=>m.byKey.get(l))).map(l=>({ts:l.ts,who:"love",via:"site",kind:l.kind,text:us(l,x),files:l.data.files||[],eventId:l.id,reply:l.reply,replies:l.replies,...gs(l,x),...He(l)})),..._.map(l=>{const A=[l.data.label||l.data.choice,l.data.note].filter(Boolean).join(" \xB7 ");return{ts:l.ts,who:"love",via:"questions",kind:l.kind,text:`${Lt(x,x.get(l.data.id))}
4
- \u2192 ${A}`,files:l.data.files||[],eventId:l.id,reply:l.reply,replies:l.replies,status:ds,...He(l)}}),...(m.reopens.get(`item:${e}`)||[]).map(l=>({ts:l.ts,who:"love",kind:l.kind,request:!0,mark:ps,text:`Question ${Tt(x,l.data.question)} was reopened`,...He(l)})),...ut(m,[...new Set(p)].map(l=>m.asks.has(l)?[m.asks.get(l)]:[])).flatMap(l=>[{ts:l.ts,who:"love",via:"ask",kind:l.kind,text:l.data?.text||"",files:l.data?.files||[],eventId:l.id,reply:l.reply,replies:l.replies,...u.includes(l.id)?{moved:!0}:{},...He(l),...typeof l.data?.from?.title=="string"?{from:l.data.from.title}:{}},...l.data?.quick===!0?[{ts:l.ts,who:"love",...Pt("implement"),text:Qe.implement}]:[],...d.map(A=>({ts:l.ts,who:"claude",kind:"ask-link",text:A.folded?`Moved to ${A.ref}: ${A.title}`:`${A.became?"Became":"Added to"} ${A.ref}`,link:`#selectedCard=${encodeURIComponent(A.key)}`,html:null,via:null}))])].sort((l,A)=>String(l.ts).localeCompare(String(A.ts))),P=ct(T),{edits:y,editFiles:b}=m;for(const l of T){if(!l.eventId)continue;(l.kind==="ask"||l.kind==="item-message")&&typeof y.get(l.eventId)=="string"&&Object.assign(l,{text:y.get(l.eventId),edited:!0,files:[...l.files,...b.get(l.eventId)||[]]});const A=l.kind==="ask"&&(d.length>0||!!l.moved);l.state=l.reply?"replied":A||D.has(l.eventId)||P>String(l.ts)?"answered":E(r,l.eventId)?"working":"waiting",l.canDelete=l.kind==="item-message"&&(l.state==="working"||l.state==="waiting"),l.canEdit=l.canDelete}return hs(T)}i(Ut,"threadOf");const Wt=["waiting","working","answered","replied"];function hs(e){const t=new Map;for(const n of e)n.kind==="owner-answer"&&t.set(n.ts,[...t.get(n.ts)||[],n]);return e.flatMap(n=>{if(n.kind!=="owner-answer")return[n];const s=t.get(n.ts);return s.length===1?[n]:s[0]!==n?[]:[{...n,text:s.map(r=>r.text).join(`
1
+ var Vt=Object.defineProperty;var d=(e,t)=>Vt(e,"name",{value:t,configurable:!0});import{routeRecord as R}from"../hub/routes.mjs";import Xt from"node:fs";import{ticketBadge as pt,ticketIn as Yt}from"./tickets.mjs";import{prLinks as zt,reviewLinks as Jt,reviewTime as Zt,reviewersOf as en}from"./review.mjs";import{remoteFromInput as tn}from"../hub/repos.mjs";import{reviewsOf as nn,reviewNeeds as sn,reviewActions as on}from"./auto-review.mjs";import{cardPRs as rn,hasPR as et,leastReady as an,prKeyOf as ln,prURL as dn}from"../shared/card-prs.mjs";import ft from"node:vm";import{checkWords as cn,parseCheck as un}from"../shared/owner-check.mjs";import{buildScriptOf as pn}from"./build-script.mjs";import{HUB as Qe,hubLineOf as gt}from"./hub-lines.mjs";const fn="card-merged",tt=ft.createContext({});for(const e of["ui-enums.js","ui-order.js"])ft.runInContext(Xt.readFileSync(new URL(`./public/${e}`,import.meta.url),"utf8"),tt);const{BUTTON:ce,ACTION_KIND:q,BUILDER_KIND:ue,PR_STATE:V,PHASES:kt}=tt.BoardUI,{laneOrder:gn}=tt.BoardUI,kn=["item-message","mark-ready","grill-more","prototype-request","visual-request","reask-request"],mt="question-reopen",bo=Object.freeze({todo:"Todo",progress:"In Progress",review:"In Review",done:"Done"}),Ee="move",mn=10*6e4,wn=5*6e4,wt="build-refused",Ne=Object.freeze({ask:"builder-ask",answer:"builder-answer"}),hn=864e5,pe=d(e=>e.lane==="done","closedLane"),yn=d(e=>`file:${e}`,"fileFold"),ht=7*hn,yt=d((e,t)=>({id:e,url:t?.workspace?`https://linear.app/${t.workspace}/issue/${e}`:null}),"linearRef"),bn=d(e=>String(e).replace(/^S(\d+)$/,"INT-$1"),"internalRef"),nt=d((e,t,n)=>Yt(n,t.ticket)?t.ticket:e.internal===!0?bn(t.id):t.id,"refOf"),Sn=d((e,t,n)=>pt(e,t,n)||(t?{id:String(t),url:null,source:null}:null),"ticketOf");function vn(e,t,n,s,r,a,c,l,u){return!a&&(e.lane==="done"||t?.state==="MERGED"||t?.state==="CLOSED")?"done":(n||s)&&!l?"progress":r?"review":t?.state==="OPEN"?t.checks?.state==="fail"&&(!t.isDraft||c)?"progress":"review":t||u?"progress":"todo"}d(vn,"laneOf");const He="Lander",bt=d(e=>e.kind!==ue.install&&e.label!==He,"changesCode");function xn(e,t){return e?.state==="MERGED"||t?V.merged:e?.state==="CLOSED"?V.closed:e?.state==="OPEN"?e.isDraft?V.draft:V.open:null}d(xn,"prStateOf");const St=d(e=>e.pr?.status===V.closed,"unlanded"),On=d(e=>e.some(t=>t.kind==="work")&&e.every(t=>t.lane==="done"),"finishedOf");function st(e){const t=vt(e);return t?.done&&t.handed_back?t.handed_back:null}d(st,"handedBackAt");const vt=d(e=>e.filter(t=>t?.started&&Q(t)&&t.kind!==ue.install).sort((t,n)=>String(t.started).localeCompare(String(n.started))).at(-1),"newestBuild");function En(e,t,n){return Object.fromEntries(e.map(s=>{const r=R(t?.reviews,s.github),a=r==="APPROVED"?"approved":r==="CHANGES_REQUESTED"?"changes":Object.hasOwn(n,s.name)?"sent":"unsent";return[s.name,{state:a,since:a==="sent"?R(n,s.name):null,github:s.github||null}]}))}d(En,"reviewOf");function $n(e,t,n,s,r,a,{held:c,needsApproval:l}){return n?.kind===ue.install?"builder":s?"checking":n?"builder":c&&e==="review"?"merge":r&&e==="review"?"owner":e==="review"&&t?.state==="OPEN"&&t.isDraft?a?"ci":"ready":e==="review"?xt(t,l)?"merge":"reviewers":null}d($n,"waitsOnOf");const xt=d((e,t)=>e?.reviewDecision==="APPROVED"||!t&&!["REVIEW_REQUIRED","CHANGES_REQUESTED"].includes(e?.reviewDecision),"reviewed"),De=/(^|\/)prototype-[\w-]+\.html/,An={reviewers:"Waiting for PR reviewers",merge:"Approved, merges once CI and the merge state allow",ci:"Marked ready for review once CI passes"},Ve={waiting:"Approved, Merge shows once CI and the merge state allow",ready:"Approved and green: tap Merge",merging:"Merging"},ot={auto:"Ready, merges once CI and the merge state allow",waiting:"Ready, Merge shows once CI and the merge state allow",ready:"Ready and green: tap Merge"},Bn=d(e=>`Verified: tap Merge to land it on ${e}`,"heldNext"),_n="Fix the failing CI",Rn="Your reply waits on the orchestrator",Mn="Tap Done once it is settled",In=["status","agent","ask-link","command",Ee,Ne.answer],qn=d(e=>(e.who==="claude"||e.via==="github")&&!In.includes(e.kind)&&(e.kind!==Qe||gt(e).fault),"countsUnread"),Cn=["lander-handoff","land-waiting"];function Nn({slug:e,itemFile:t,it:n,order:s,prs:r,repo:a,remote:c,base:l,builder:u,reachedPhase:m,agents:f,worktree:p,eta:w,thread:v,events:_,working:x,said:P,askedAt:C,topicItems:L,prSlack:T,boardReviewers:W,now:h,bumpedAt:b,holdAsk:i,closedAt:S,mergeAt:oe,autoMerge:ke,skipDraft:me,tickets:K}){const F=r.map(k=>({repo:k.repo,number:k.number,url:dn(k),...k.gh||k.kept||{},status:xn(k.gh,k.merged_at)})),ee=an(r.map(k=>k.gh)),I=r[ee]?.gh||null,G=F[ee]?.status??null,ze=n.lane||null,U=!!S&&String(S)>String(n.lane_at||"");U&&(n={...n,lane:"done",closed_at:S,reopened_at:void 0});const N=[n.closed_at,...r.flatMap(k=>[k.merged_at,k.gh?.mergedAt])].filter(Boolean).map(String).sort().pop()||null,we=String(N||""),he=F.length>0&&F.every(k=>k.status===V.closed),ye=(n.lane==="done"||n.reopened_at||G===V.merged)&&!he&&([...P.filter(k=>k.kind==="item-message").map(k=>k.ts),...C].map(String).filter(k=>k>we).sort().pop()||n.reopened_at)||void 0,Le=n.answered_at&&!n.phase&&!n.closed_at&&!ye?String(n.answered_at):void 0,j=ye||Le,Re=n.owner_step&&!U?String(n.owner_step):null,Je=(n.lane==="done"||G===V.merged)&&!he,H=!!j&&!!n.next&&String(n.next_at||"")>String(n.reopened_at||N||"");j&&!H&&(n={...n,next:void 0});const X=st(f),Te=j&&P.filter(k=>String(k.ts)>we&&(k.state==="waiting"||k.state==="working")).map(k=>String(k.ts)).sort()[0]||null,be=!!Te,Se=`${e}/${n.id}`,ve=en(t,W),Pe=Object.fromEntries(Object.entries(Jt(t,n,Se,L,zt(T,r))).map(([k,de])=>[k,Zt(de.ts)])),te=Object.values(Pe).filter(Boolean).sort()[0]||null,Ue=[...v.map(k=>k.ts),..._.map(k=>k.ts)].sort().pop()||null,je=[...v.filter(k=>qn({...k,who:k.who||"claude"})).map(k=>k.ts),..._.map(k=>k.reply?.at)].filter(Boolean).sort(),Y=!et(n)&&n.pushed?.branch?{branch:n.pushed.branch,at:n.pushed.at||null,host:c?.host||null}:null,z=!et(n)&&n.merge_ready?.branch?{branch:n.merge_ready.branch,at:n.merge_ready.at||null,base:l}:null,Me=!!n.rebase_run&&I?.state==="OPEN",Ie=(n.rebased||[]).at(-1),re=f.filter(k=>k.started&&!k.done&&Q(k)),Ze=!!X&&bt(vt(f));let A=vn(n,I,re.some(bt),Ze,Y||z,!!j,me,Me,re.length>0||!!X);j&&A==="todo"&&(A="progress");const We=A==="review"&&!!te&&h-Date.parse(te)>ht,ae=!!Re&&Je&&A==="done";ae&&(A="review");let D=$n(A,I,u,!!X&&A!=="done",Y,me,{held:z,needsApproval:ve.length>0});const Fe=D==="builder"&&I?.state==="OPEN"&&!I.isDraft,qe=A==="progress"&&!D&&I?.state==="OPEN"&&(!I.isDraft||me),xe=ae||!!j&&A==="progress"&&!D&&!qe&&!be&&!H;xe&&(D="owner");const o=!!j&&f.some(k=>Q(k)&&String(k.started||"")>String(j)),g=A==="progress"&&!D&&(qe||!!j&&!o);g&&(A="review");const E=i&&String(i.ts)>String(n.held_at||""),$=A==="todo"&&(E?!!i.data.hold:!!n.held),y=$&&(E?i.data.until:n.pick_up_at)||null,M=A!=="done",B=!!b&&!(we&&String(b)<=we),Ke=v.filter(k=>(k.who||"claude")==="claude"&&!["status","command",Ee].includes(k.kind)&&!Cn.includes(k.line)).map(k=>String(k.ts)).sort().pop()||"",le=[oe,z&&n.merge_ready.tapped].filter(Boolean).map(String).sort().pop(),Ce=D==="merge"&&!!le&&le>Ke,Oe=v.filter(k=>(k.kind==="status"||k.kind===Qe)&&String(k.ts)>String(le)).sort((k,de)=>String(k.ts).localeCompare(String(de.ts))).at(-1),ne=Oe?.line==="land-waiting"?`${Ve.merging} \xB7 ${Oe.text[0].toLowerCase()}${Oe.text.slice(1)}`:Ve.merging,J=v.filter(k=>!["status",Ee].includes(k.kind)).sort((k,de)=>String(k.ts).localeCompare(String(de.ts))).at(-1),O=A==="todo"&&J?.line===wt?J.text:null,ie=j||f.filter(Q).map(k=>k.started).filter(Boolean).map(String).sort().pop()||null,Ge=I?.reviewDecision==="APPROVED",Kt=Y&&D==="owner"?`merge ${Y.branch} into ${l}`:z&&D==="merge"?Ce?ne:Bn(l):D==="merge"&&!ke?Ce?ne:(Ge?Ve:ot).waiting:D==="merge"&&!Ge?ot.auto:An[D],Gt=G===V.draft?`Review draft PR #${F[ee].number}`:null,Qt=!Me&&Ie?.to&&Ie.to===I?.headRefOid?`Rebased onto ${Ie.base||l}, pushed${I.mergeable==="MERGEABLE"?" \xB7 PR is clean again":""}`:null,Ht=Me&&D==="checking"?f.flatMap(k=>k.calls).sort((k,de)=>String(k.at).localeCompare(String(de.at))).at(-1)?.text??null:null;return{key:Se,kind:"work",order:s,file:e,fileTitle:t.title||e,id:n.id,ref:nt(t,n,K),title:n.title,storedLane:ze,created:n.created||null,reviewSince:g&&j||n.review_since||te||n.merge_ready?.at||n.pushed?.at||n.created||null,progressSince:ie,closedAt:A==="done"?N:null,internal:t.internal===!0,repo:t.internal===!0&&!t.repo?null:a,lane:A,waitsOn:D,builder:D==="checking"?null:u,reachedPhase:m,agents:f,worktree:p??null,eta:w,working:x,reopened:!!ye,answered:!!Le,awaitsDone:xe,ownerStep:xe?Re:null,ownerCheck:xe&&Re&&n.owner_check?{words:cn(un(n.owner_check.check)),broken:n.owner_check.broken||null}:null,stagedAt:A==="todo"&&n.phase==="staging"&&n.staged_at||null,roundAt:n.round_at||null,deps:n.deps||[],blockedUntil:n.blocked_until||null,blockReason:n.block_reason||null,held:$,heldUntil:y,risky:!!n.risky,bumpable:M,bumpedAt:M&&B?b:null,holdable:A!=="done",hasNotes:!!(n.plan||String(n.notes||"").trim()),finishable:A!=="done",merging:Ce,refusal:O,manualSteps:n.manual_steps||[],fromAsk:n.from_ask||null,unfiled:!!n.unfiled,filedHeld:!!n.filed_held,next:Fe||A==="done"||D==="checking"?null:g&&be?Rn:qe?_n:Qt||Kt||n.next||Gt||null,rebaseFailed:Ht,branch:n.branch||I?.headRefName||Y?.branch||null,pushed:A==="done"||ae?null:Y,mergeReady:A==="review"?z:null,stale:A!=="done"&&(We||!!u?.stale),stalled:n.stalled?.reason||null,landStopped:A==="review"&&z&&n.merge_ready.stopped&&String(n.merge_ready.stopped)>String(n.merge_ready.tapped||"")?{words:At("land"),at:n.merge_ready.stopped}:null,ticket:K?.source==="linear"&&!n.ticket&&n.linear?.length?null:Sn(K,n.ticket,{repo:a}),ticketSource:K?.source||null,linear:(n.linear||[]).map(k=>K?.source==="linear"&&pt(K,k,{repo:a})||yt(k,K)),pr:F[ee]||null,prs:F,reviewers:ve.map(k=>k.name),review:En(ve,I||r[ee]?.kept,Pe),inReviewSince:te,threadCount:v.length+_.length,lastTs:Ue,othersAt:je,grilling:Bt("grill",_,f,C),prototyping:Bt("prototype",_,f,C),storedPhase:n.phase||null,phase:null,recheckAt:n.recheck_at||null,updatedAt:I?.mergedAt||I?.updatedAt||r[ee]?.merged_at||u?.at||Ue||null,ask:null,fold:null,action:null,install:A==="done"?null:Ln(n.install,I),waitFacts:Dn(n,{agents:f,thread:v,events:_,refusedAt:O?J.ts:null,replyAt:g?Te:null})}}d(Nn,"workItem");function Dn(e,{agents:t,thread:n,events:s,refusedAt:r,replyAt:a}){const c=new Set(n.filter(f=>f.kind===Ne.answer).map(f=>f.ask)),l=n.filter(f=>f.kind===Ne.ask&&!c.has(f.ask)).sort((f,p)=>String(f.ts).localeCompare(String(p.ts)))[0],u=d(f=>s.filter(p=>at(p,f)).pop()?.ts||null,"requestAt"),m=n.filter(f=>(f.who||"claude")==="claude"&&!["status","agent","command",Ee].includes(f.kind)&&f.line!==wt).map(f=>f.ts);return{handedBackAt:st(t),next:e.next||null,ask:l?{at:l.ts,text:l.text}:null,grillAt:u("grill"),prototypeAt:u("prototype"),refusedAt:r,replyAt:a,unblockedAt:e.unblocked_at||null,unverified:t.some(f=>f.calls.some(p=>!f.verified||String(p.at)>String(f.verified.at))),writes:[...m,...t.flatMap(f=>[f.verified?.at,f.started])].filter(Boolean).map(String).sort()}}d(Dn,"waitFactsOf");function Ln(e,t){if(!e?.url)return null;const n=e.sha?String(e.sha).toLowerCase():null,s=t?.headRefOid?String(t.headRefOid).toLowerCase():null;return{url:e.url,label:e.label||null,builtAt:e.built_at||null,sha:n,older:!!(n&&s&&!s.startsWith(n))}}d(Ln,"installOf");function Tn(e,{needsApproval:t=!0}={}){if(e?.state!=="OPEN")return[`PR is ${String(e?.state||"unknown").toLowerCase()}`];const{state:n,pending:s,failing:r}=e.checks||{};return[e.isDraft&&"it is a draft",!xt(e,t)&&`review: ${String(e.reviewDecision||"none").toLowerCase().replace(/_/g," ")}`,n==="fail"&&`failing: ${r?.length?r.join(", "):"a check"}`,n==="pending"&&`pending: ${s?se(s,"check"):"checks"}`,e.mergeStateStatus!=="CLEAN"&&`merge state: ${String(e.mergeStateStatus||"unknown").toLowerCase()}`].filter(Boolean)}d(Tn,"mergeBlockers");const Pn=d((e,t)=>!Tn(e,{needsApproval:t}).length,"mergeable");function Un(e,t,n){if(pe(e))return null;const s=`#selectedCard=${encodeURIComponent(e.key)}`;if(e.stalled)return{kind:q.stalled,url:s,label:`stalled: ${e.stalled}`};if(jn(e)){const r=t.get(e.questions.first);return De.test(r?.url||"")?{kind:q.pick,url:`questions.html#${r.id}`,label:"Prototypes ready: pick a take"}:{kind:q.questions,url:`questions.html#${e.questions.first}`,label:`Answer ${se(e.questions.open,"question")}`}}return e.waitsOn==="ready"?{kind:q.ready,url:e.pr.url,label:`Review draft PR #${e.pr.number}`}:e.waitsOn==="merge"&&e.manualSteps.length&&!e.mergeReady?{kind:q.steps,url:s,label:"Run the manual steps, then merge"}:e.waitsOn==="merge"&&(!n||e.mergeReady)&&!e.merging&&e.prs.every(r=>["MERGED","CLOSED"].includes(r.state)||Pn(r,e.reviewers.length>0))?{kind:q.merge,url:s,label:"Merge"}:null}d(Un,"actionOf");const jn=d(e=>!!(e.questions&&e.questions.state!=="resolved"&&e.questions.open>0),"asking"),rt=new Set([q.pick,q.questions]);function Ot(e){return e.questions?ce.openQuestions:null}d(Ot,"actionButtonOf");const Wn=d(e=>e.kind==="work"&&e.lane==="todo"&&!e.action&&!e.unpicked,"buildableOf"),Fn=d((e,t)=>/^item:/.test(e.topic||"")&&De.test(e.url||"")&&!(e.options||[]).some(n=>n.id===t),"picksNoTake");function Kn(e,t){const n=e.filter(a=>a.topic===`item:${t}`),r=[...n.filter(a=>De.test(a.url||"")).flatMap(a=>a.answers.map(c=>({q:a,a:c,again:Fn(a,c.choice)}))),...n.filter(a=>a.request).map(a=>({q:a,a:a.request,again:!0}))].sort((a,c)=>String(a.a.ts).localeCompare(String(c.a.ts))).at(-1);return r?.again?{id:r.q.id,note:r.a.note.replace(/\s+/g," ").trim()}:null}d(Kn,"noTakePicked");const Gn="Prototype again",Qn=Object.freeze({[q.pick]:ce.openQuestions,[q.questions]:ce.openQuestions,[q.ready]:ce.github,[q.steps]:ce.github,[q.merge]:ce.merge,[q.stalled]:ce.reply}),Hn="Merges once unblocked",Vn=d(e=>`Blocked ${[e.blockedBy.length&&`by ${e.blockedBy.map(t=>t.ref).join(", ")}`,e.blockedUntil&&`until ${e.blockedUntil}`].filter(Boolean).join(" and ")}`,"mergeBlockedWords");function Xn(e,t,n){if(e.action=Un(e,t,n),e.mergeBlocked=e.action?.kind===q.merge&&e.blocked?Vn(e):null,e.mergeBlocked&&Object.assign(e,{action:null,next:Hn}),e.action?.kind===q.questions&&e.questions.fromAsks){Object.assign(e,{action:null,waitsOn:"owner",next:e.action.label,actionButton:Ot(e),resolvedBy:null});return}e.awaitsDone&&=!e.action,e.awaitsDone||Object.assign(e,{ownerStep:null,ownerCheck:null}),e.actionButton=Ot(e),e.resolvedBy=Qn[e.action?.kind]||null,e.action?.kind===q.merge&&!e.mergeReady&&(e.next=(e.pr?.reviewDecision==="APPROVED"?Ve:ot).ready),e.awaitsDone&&Object.assign(e,{lane:"review",next:e.ownerStep||Mn,bumpable:!1,bumpedAt:null});const s=e.lane==="todo"||e.waitsOn==="ready"||e.lane==="progress"&&e.waitsOn==="owner";if(!e.action||!s||e.stalled)return;const r=e.lane==="todo"&&rt.has(e.action.kind)?"todo":"review";Object.assign(e,{lane:r,waitsOn:e.waitsOn==="ready"?"ready":"owner",next:e.action.label})}d(Xn,"placeOwnerWait");const Et=d((e,t)=>e?e!=="todo":t.some(Q),"started"),$t="none set yet";function So(e,t){const n=t.filter(s=>s?.started);return e.next||et(e)||e.pushed||e.held||e.lane==="done"||st(n)?!1:!n.some(s=>!s.done)&&Et(e.lane,n)}d(So,"owesNext");const fe="visual",$e="helper",Yn=new Set(["grill","prototype",fe]),Z=d(e=>Yn.has(e.kind),"plans"),Q=d(e=>!Z(e),"builds"),zn={grill:"grilling",prototype:"prototyping"},Jn=d((e,t)=>`${e} ${t} by orchestrator`,"orchestratorDid"),At=d(e=>Jn(e,"stopped"),"stoppedWords"),Zn={grill:"grill-more",prototype:"prototype-request"},at=d((e,t)=>e.kind===Zn[t]&&!e.data?.question,"isRequest");function Bt(e,t,n,s){if(n.some(a=>a.kind===e&&a.started&&!a.done))return!0;const r=t.filter(a=>at(a,e)).pop();return!!(r&&!r.reply&&!s.some(a=>String(a)>String(r.ts))&&!n.some(a=>a.kind===e&&a.done&&String(a.started)>=String(r.ts)))}d(Bt,"planningOf");const vo=30*6e4;function xo(e,t){const n=[];for(const s of e.items){if(pe(s)||(s.agents||[]).some(a=>a.kind==="prototype"&&a.started&&!a.done))continue;const r=s.prototyping&&t.filter(a=>a.data?.key===s.key&&at(a,"prototype")).pop();r&&n.push({key:s.key,request:r.id,since:r.ts});for(const a of e.owner.filter(c=>c.topic===`item:${s.key}`&&c.state==="requested"))n.push({key:s.key,request:a.request.id,question:a.id,since:a.request.ts})}return n}d(xo,"prototypeWaits");function lt(e={},t=!1){return Object.entries(e).filter(([n,s])=>s&&typeof s=="object"&&(t||!n.startsWith("past-"))).map(([n,s])=>({name:n,kind:_t(s.kind)||null,model:s.model||(s.kind==="opus"||s.kind==="sonnet"?s.kind:null),label:s.label||null,started:s.started||null,eta:s.done?null:s.eta||null,done:s.done||null,log:s.session?.log||null,pid:s.session?.pid??null,calls:Array.isArray(s.calls)?s.calls:[],verified:s.verified||null,stopped:s.stopped||null,handed_back:s.handed_back||null,...s.pause?{pause:s.pause}:{},...s.kind===$e?{writes:!!s.writes,requested:s.requested||null}:{},...s.waits?{waits:s.waits}:{}})).sort((n,s)=>String(n.started||"").localeCompare(String(s.started||""))||n.name.localeCompare(s.name))}d(lt,"agentsOf");function it(e={}){return Object.values(e).filter(t=>t?.worktree?.path).sort((t,n)=>String(t.started||"").localeCompare(String(n.started||""))).at(-1)?.worktree.path??null}d(it,"worktreeOf");function es(e,t=[]){const n=e.filter(Q).map(s=>String(s.started||"")).filter(Boolean).sort().pop();return n===void 0?null:t.filter(s=>s.kind==="status"&&s.phase&&!s.planner&&String(s.ts)>=n).sort((s,r)=>String(s.ts).localeCompare(String(r.ts))).pop()?.phase||null}d(es,"reachedPhaseOf");function ts(e,t=[],n,s=null,r=null,a=!1){const c=e.filter(b=>b.started&&!b.done&&b.kind!==fe),l=c.every(Z),u=l?c:c.filter(Q),m=u.map(b=>String(b.started||"")).sort()[0];if(m===void 0)return null;const f=t.filter(b=>b.kind==="status"&&String(b.ts)>=m&&(l||!b.planner)).sort((b,i)=>String(b.ts).localeCompare(String(i.ts))).pop(),p=u.filter(b=>b.kind!==$e).at(-1),w=u.some(b=>b.kind===ue.install)?ue.install:p?_t(p.kind):"claude",v=l?null:u.find(b=>b.stopped&&String(b.stopped.at)>=String(b.started))?.stopped,_=f?.ts||m,x=v?v.at:s!==null&&s>Date.parse(_)?new Date(s).toISOString():_,P=f?f.phase||f.planner?f.text.replace(": "," \xB7 "):f.text||null:l?zn[w]:null,C=n-Date.parse(x),L=l?null:u.find(b=>b.pause)?.pause??null,T=!l&&!v&&(r==="held"||!!L?.paused),W=l||T?null:v?!1:r,h=T?!1:W===null?C>=mn:!W;return{progress:P,phase:f?.phase||null,at:_,kind:w,planner:l,silentSince:x,stale:h,alive:W,held:T,quiet:!!L||W===!0&&!a&&C>=wn,stopped:v?At(v.run):null,...L?{pause:{cause:L.cause,words:L.words,paused:!!L.paused}}:{}}}d(ts,"builderOf");const Oo={blocked:["nothing blocks it","nothing blocks it"],held:["you unhold it","unheld"],usage:["the usage pause lifts","the usage pause lifted"],bumped:["a slot frees","a slot freed"]},ns=15*6e4,ss=d(e=>(e.agents||[]).find(t=>t.name==="main"&&t.started&&!t.done&&Q(t))??null,"mainBuild"),os=d((e,t,n,{now:s,usageHeld:r})=>e.blocked||e.held||r(n??"claude")||!!(t.until&&s<Date.parse(t.until)),"stillPaused");function Eo(e,{now:t=Date.now(),usageHeld:n}){const s={ask:[],drop:[],stop:[],resume:[]},r=[];for(const a of e){if(a.kind!=="work"||a.lane!=="progress")continue;const c=ss(a);if(!c)continue;const{pause:l}=c;if(!l){a.builder?.phase!=="landing"&&n(c.kind??"claude")&&s.ask.push(a.key);continue}const u=os(a,l,c.kind,{now:t,usageHeld:n});if(l.paused){u||r.push({key:a.key,at:l.paused});continue}!u&&l.cause!=="bumped"?s.drop.push(a.key):t-Date.parse(l.at)>=ns&&s.stop.push(a.key)}return s.resume=r.sort((a,c)=>String(a.at).localeCompare(String(c.at))).map(a=>a.key),s}d(Eo,"pauseWork");const dt={opus:"Claude Opus",sonnet:"Claude Sonnet",codex:"Codex"},rs=d(({model:e,kind:t})=>dt[e]||dt[t]||(t==="claude"?dt.opus:null),"modelName"),_t=d(e=>e==="opus"||e==="sonnet"?"claude":e,"providerOf");function Ae(e){const t=rs(e),{kind:n}=e,s=e.label?.replace(/\bbuilder\b/,"implementer").replace(/\bBuilder\b/,"Implementer"),r=d(c=>c[0].toLowerCase()+c.slice(1),"lower");if(t&&Z(e))return`${t} ${n}`;if(t)return`${t} ${r(s?.replace(/^(?:claude\s+)?(?:opus|sonnet|codex)\b\s*/i,"")||"implementer")}`;const a=n?n[0].toUpperCase()+n.slice(1):"";return a?s?s.toLowerCase().includes(n.toLowerCase())?s:`${a} ${r(s)}`:a:s||"Implementer"}d(Ae,"agentName");const Rt=d((e,t)=>!!e.resolved&&(!e.resolved_at||String(e.resolved_at)>=String(t)),"resolvedSince");function as(e,t,n=[],s=[],r=[]){const a=t.filter(x=>x.data?.id===e.id),c=a[a.length-1]||null,l=r.filter(x=>x.data?.question===e.id).at(-1),u=d((x,...P)=>!!x&&P.every(C=>String(x.ts)>String(C||"")),"after"),m=!!(e.resolved&&e.resolved_at&&c&&String(c.ts)>e.resolved_at),f=n.filter(x=>x.data?.question===e.id&&u(x,c?.ts,l?.ts)).at(-1),p=u(l,c?.ts,f?.ts,e.resolved_at),w=e.resolved&&!m&&!p,v=w?s.filter(x=>x.data?.question===e.id&&String(x.ts)>String(e.resolved_at||"")).at(-1):null,_=p?"open":w?"resolved":f?"requested":c?c.reply?"replied":"answered":"open";return{...e,state:_,reopened:p,request:f?{id:f.id,ts:f.ts,note:f.data.note||"",choice:f.data.choice||null}:null,reask:v?{id:v.id,ts:v.ts}:null,answers:a.map(x=>({ts:x.ts,choice:x.data.choice||null,note:x.data.note||"",files:x.data.files||[],reply:x.reply,replies:x.replies,acted:Rt(e,x.ts)}))}}d(as,"ownerQuestion");const ge=d(e=>e.round??(De.test(e.url||"")?1:0),"roundOf");function ls(e,t){const n=d((s,r)=>s.confirm_of||(s.options||[]).some(a=>a.id===r.choice),"settles");return e.filter(s=>ge(s)===t&&s.answers.length).map(s=>[s,s.answers.at(-1)]).filter(([s,r])=>n(s,r)).map(([,s])=>s).sort((s,r)=>String(s.ts).localeCompare(String(r.ts))).at(-1)}d(ls,"roundSettledBy");function is(e){const t=e.find(r=>r.request&&!e.some(a=>ge(a)>ge(r)));if(t)return{n:ge(t)+1,state:"making"};const n=Math.max(0,...e.map(ge));if(!n)return null;const s=ls(e,n);return s?{n,state:"closed",take:s.choice}:e.some(r=>ge(r)===n&&r.state!=="resolved")?{n,state:"back"}:null}d(is,"roundState");function ds(e){const t=e.filter(n=>De.test(n.url||"")).map(n=>({n:ge(n),q:n.id,url:n.url,confirm:!!n.confirm_of,state:n.state})).sort((n,s)=>n.n-s.n);return t.length?{open:t.some(n=>n.state==="open"),rounds:t}:null}d(ds,"prototypesOf");const Be=d((e,t,n)=>{const s=e.get(t);s?s.push(n):e.set(t,[n])},"add"),_e=d((e,t)=>{const n=new Map;for(const s of e)Be(n,t(s),s);return n},"groupBy");function cs(e,t,n=new Set){const s=new Set(e.map(c=>c.id)),r=[...e];t.some(c=>!s.has(c.topic))&&r.push({id:"",title:"Other"});const a=_e(t,c=>s.has(c.topic)?c.topic:"");return r.map(c=>{const l=a.get(c.id)||[],u=d(p=>l.filter(w=>w.state===p).length,"count"),m=l.every(p=>p.state==="resolved")||n.has(c.id)&&!u("open"),f=l.length&&m?"resolved":"open";return{...c,state:f,questions:l.map(p=>p.id),open:u("open"),answered:u("answered"),replied:u("replied"),resolved:u("resolved"),round:is(l)}}).filter(c=>c.questions.length)}d(cs,"ownerTopics");const Xe={implement:"Implementation requested",prototype:"Prototype requested",grill:"Grill requested",visual:"Visual requested",reask:"Re-ask requested",hold:"Held",resume:"Resumed",done:"Marked done",staged:"Marked as staging",readied:"Marked as ready",related:"Related to",unrelated:"Unrelated from",followup:"Follow-up","followup-of":"Follow-up of"},Mt={"grill-more":"grill","prototype-request":"prototype","visual-request":"visual","reask-request":"reask"},It={"mark-ready":"Mark ready",...Object.fromEntries(Object.entries(Mt).map(([e,t])=>[e,Xe[t]]))},us=["grill-more","prototype-request","visual-request"],ps="answers submitted",qt={reply:"Reply",build:"Implement"},fs=d(e=>qt[e.data.intent],"intentLabel"),gs=d((e,t)=>{if(e.kind!=="prototype-request"||!t.has(e.data.question))return e.data.text||(e.data.files?.length?"":R(It,e.kind))||"";const n=t.get(e.data.question),s=(n.options||[]).find(r=>r.id===e.data.choice);return[Nt(t,n),s&&`${s.id}. ${s.label}`,e.data.note].filter(Boolean).join(`
2
+ \u2192 `)},"actionText"),Ct=d((e,t)=>[...e.keys()].indexOf(t)+1,"numberOf"),Nt=d((e,t)=>`Question ${Ct(e,t.id)} \xB7 ${t.question}`,"numbered"),ks="question reopened",Ye=d(e=>e.data?.shared_by?.login?{by:e.data.shared_by.name||e.data.shared_by.login}:{},"byOf"),ms=d((e,t)=>{const n=t.get(e.card);return{...e,text:`${Xe[e.command]} ${n?`${n.ref}: ${n.title}`:e.card}`,link:`#selectedCard=${encodeURIComponent(e.card)}`}},"linkLine"),Dt=d(e=>({kind:"command",command:e,request:!0,mark:Xe[e]}),"commandLine"),ws=d((e,t)=>{if(e.kind==="item-message")return qt[e.data.intent]?{status:fs(e)}:{};const n={request:!0,command:Mt[e.kind]};return us.includes(e.kind)?(e.data.question?t.has(e.data.question):String(e.data.text||"").trim()||e.data.files?.length)?{status:It[e.kind]}:n:e.kind==="reask-request"?n:{}},"requestOf"),Lt=d(e=>[...e.filter(t=>t.who==="claude"&&!["status","agent","command",Ee,Ne.answer].includes(t.kind)).map(t=>t.ts),...e.filter(t=>t.kind==="ask").map(t=>t.reply?.at)].filter(Boolean).map(String).sort().pop()||"","claudeAtOf");function Tt(e,t={}){const n=new Map,s=new Map;for(const p of t.items||[])Be(n,p.topic,p),s.set(p.id,p.topic);const r=new Map,a=new Map,c=new Map,l=new Map,u=new Map,m=new Map,f=new Map;e.forEach((p,w)=>{f.set(p,w),p.data?.key&&kn.includes(p.kind)&&Be(r,p.data.key,p),p.kind==="owner-answer"&&s.has(p.data?.id)&&Be(a,s.get(p.data.id),p),p.kind===mt&&s.has(p.data?.question)&&Be(c,s.get(p.data.question),p),p.kind==="ask"&&l.set(p.id,p)});for(const p of e.filter(w=>w.kind==="ask-edited").sort((w,v)=>String(w.ts).localeCompare(String(v.ts)))){u.set(p.data?.id,p.data?.text);for(const w of p.data?.files||[])Be(m,p.data?.id,w)}return{byKey:r,answers:a,reopens:c,asks:l,edits:u,editFiles:m,questions:n,order:f}}d(Tt,"threadIndex");const ct=d((e,t)=>{const n=t.filter(s=>s?.length);return n.length>1?n.flat().sort((s,r)=>e.order.get(s)-e.order.get(r)):[...n[0]||[]]},"inLogOrder"),hs=/; its log is \S*\/\.claude\/kickflip\/logs\/([^/\s]+\.log)\.$/,ys=d(e=>{const t=!e.log&&typeof e.text=="string"?hs.exec(e.text):null;return t?{...e,text:`${e.text.slice(0,t.index)}.`,log:t[1]}:e},"withLog");function bs(e,t){const n=t.run?e.find(s=>s.started===t.run):e.filter(s=>Z(s)===!!t.planner&&s.started&&String(s.started)<=String(t.ts)&&(!s.done||String(t.ts)<=String(s.done))).pop();return n?{agent:Ae(n),run:ut(e,n),running:!n.done}:{}}d(bs,"agentOfLine");function ut(e,t){return t.label!==He?t.started:e.filter(n=>Q(n)&&n.label!==He&&n.started&&String(n.started)<String(t.started)).pop()?.started??t.started}d(ut,"sectionOf");function Ss(e,{lines:t=[],events:n=[],owner:s={},working:r={},fromAsk:a=null,links:c=[],agents:l={},moved:u=[],cards:m=new Map,index:f=Tt(n,s)}){const p=e.startsWith("ask:")?[e.slice(4)]:[a,...u].filter(Boolean),w=[...new Set([e,...p.map(i=>`ask:${i}`)])],v=new Map((f.questions.get(`item:${e}`)||[]).map(i=>[i.id,i])),_=f.answers.get(`item:${e}`)||[],x=new Set([...v.values()].map(i=>i.round_of).filter(Boolean)),P=lt(l,!0).filter(Q).map(i=>String(i.started||"")).filter(Boolean),C=new Set(_.filter(i=>Rt(v.get(i.data.id),i.ts)||x.has(i.data.id)||P.some(S=>S>String(i.ts))).map(i=>i.id)),L=lt(l,!0),T=[...L.flatMap(i=>[...i.started?[{ts:i.started,who:"claude",kind:"agent",text:`${Ae(i)} started`,agent:Ae(i),run:ut(L,i),finished:!1,eta:i.done?null:i.eta,...i.log?{log:i.log}:{},...Z(i)?{planner:!0}:{}}]:[],...i.done?[{ts:i.done,who:"claude",kind:"agent",text:`${Ae(i)} finished`,agent:Ae(i),run:ut(L,i),finished:!0,eta:null,...Z(i)?{planner:!0}:{}}]:[],...i.calls.length?[{ts:i.done||i.calls.at(-1).at,who:"claude",kind:"calls",text:Ae(i),calls:i.calls.map(S=>S.text),verified:i.verified}]:[]]),...t.map(ys).map(i=>i.card?ms(i,m):i).map(i=>({ts:i.ts,who:i.who||"claude",...i.log?{log:i.log}:{},...i.kind==="status"?{kind:"status",...i.planner?{planner:!0}:{},...bs(L,i)}:{},...i.kind===fe||i.kind===Ee?{kind:i.kind}:{},...i.kind===Qe?{kind:Qe,line:i.line,...gt(i)}:{},...i.command?Dt(i.command):{},...i.card?{card:i.card}:{},...Object.values(Ne).includes(i.kind)?{kind:i.kind,ask:i.ask}:{},text:i.text||"",html:i.html||null,link:i.link||null,via:i.via||null,...i.by?{by:i.by}:{}})),...ct(f,w.map(i=>f.byKey.get(i))).map(i=>({ts:i.ts,who:"love",via:"site",kind:i.kind,text:gs(i,v),files:i.data.files||[],eventId:i.id,reply:i.reply,replies:i.replies,...ws(i,v),...Ye(i)})),..._.map(i=>{const S=[i.data.label||i.data.choice,i.data.note].filter(Boolean).join(" \xB7 ");return{ts:i.ts,who:"love",via:"questions",kind:i.kind,text:`${Nt(v,v.get(i.data.id))}
3
+ \u2192 ${S}`,files:i.data.files||[],eventId:i.id,reply:i.reply,replies:i.replies,status:ps,...Ye(i)}}),...(f.reopens.get(`item:${e}`)||[]).map(i=>({ts:i.ts,who:"love",kind:i.kind,request:!0,mark:ks,text:`Question ${Ct(v,i.data.question)} was reopened`,...Ye(i)})),...ct(f,[...new Set(p)].map(i=>f.asks.has(i)?[f.asks.get(i)]:[])).flatMap(i=>[{ts:i.ts,who:"love",via:"ask",kind:i.kind,text:i.data?.text||"",files:i.data?.files||[],eventId:i.id,reply:i.reply,replies:i.replies,...u.includes(i.id)?{moved:!0}:{},...Ye(i),...typeof i.data?.from?.title=="string"?{from:i.data.from.title}:{}},...i.data?.quick===!0?[{ts:i.ts,who:"love",...Dt("implement"),text:Xe.implement}]:[],...c.map(S=>({ts:i.ts,who:"claude",kind:"ask-link",text:S.folded?`Moved to ${S.ref}: ${S.title}`:`${S.became?"Became":"Added to"} ${S.ref}`,link:`#selectedCard=${encodeURIComponent(S.key)}`,html:null,via:null}))])].sort((i,S)=>String(i.ts).localeCompare(String(S.ts))),W=Lt(T),{edits:h,editFiles:b}=f;for(const i of T){if(!i.eventId)continue;(i.kind==="ask"||i.kind==="item-message")&&typeof h.get(i.eventId)=="string"&&Object.assign(i,{text:h.get(i.eventId),edited:!0,files:[...i.files,...b.get(i.eventId)||[]]});const S=i.kind==="ask"&&(c.length>0||!!i.moved);i.state=i.reply?"replied":S||C.has(i.eventId)||W>String(i.ts)?"answered":R(r,i.eventId)?"working":"waiting",i.canDelete=i.kind==="item-message"&&(i.state==="working"||i.state==="waiting"),i.canEdit=i.canDelete}return vs(T)}d(Ss,"threadOf");const Pt=["waiting","working","answered","replied"];function vs(e){const t=new Map;for(const n of e)n.kind==="owner-answer"&&t.set(n.ts,[...t.get(n.ts)||[],n]);return e.flatMap(n=>{if(n.kind!=="owner-answer")return[n];const s=t.get(n.ts);return s.length===1?[n]:s[0]!==n?[]:[{...n,text:s.map(r=>r.text).join(`
5
4
 
6
- `),files:s.flatMap(r=>r.files),state:s.map(r=>r.state).sort((r,a)=>Wt.indexOf(r)-Wt.indexOf(a))[0],reply:void 0,answers:s.map(r=>({eventId:r.eventId,state:r.state,reply:r.reply,replies:r.replies,text:r.text}))}]})}i(hs,"foldSubmits");const Ce=i(e=>(e.answers||[e]).filter(t=>t.eventId).map(t=>[t.eventId,t.state]),"stateEntries");function ys(e,t,n,s,r,a){const d=i((p,w)=>{const x=a.filter(O=>O.kind==="wave-triage"&&O.data?.[p]===w).pop(),_=r.filter(O=>O.topic===`${p}:${w}`&&(O.state==="open"||O.state==="answered")).length;return{running:!!(x&&!x.reply),questions:_}},"triageOf"),c=new Map(s.map(p=>[p.key,p])),u=i(p=>p.filter(w=>w.lane==="done"&&!vt(w)).length,"merged"),g=n.map(p=>({l:p,cards:p.keys.map(w=>c.get(w)).filter(Boolean)})).map(p=>({...p,open:!p.l.finished_at&&p.cards.some(w=>w.lane!=="done")})).sort((p,w)=>Number(p.open)-Number(w.open));for(const{l:p,cards:w}of g)for(const x of w)x.wave={slug:p.slug,title:p.title};const m=g.filter(p=>p.open).map(({l:p,cards:w})=>({kind:"wave",slug:p.slug,title:p.title,goal:p.goal,keys:w.map(x=>x.key),next:bs(w),merged:u(w),total:w.length,triage:d("wave",p.slug)}));return e.fromWaves?[...m,...t.filter(p=>!p.finished).map(p=>({w:p,cards:s.filter(w=>w.file===p.slug&&w.kind!=="ask")})).filter(({cards:p})=>p.length).map(({w:p,cards:w})=>({kind:"file",slug:p.slug,title:p.title,goal:null,keys:w.map(x=>x.key),next:null,merged:u(w),total:w.length,triage:d("file",p.slug)}))]:m}i(ys,"waveBandsOf");function bs(e){const t=e.filter(a=>a.lane==="todo"),n=t.find(a=>a.ready&&a.buildable)||t[0];if(!n)return null;const s=n.ready&&n.buildable,r=s?"ready":n.held?"held":n.blockedBy.length?`waits on ${n.blockedBy.map(a=>a.ref).join(", ")}`:n.blockedUntil?`blocked until ${n.blockedUntil}`:n.action?"needs you":n.phase==="new"?"not ready":n.phase;return{key:n.key,ref:n.ref,ready:s,word:r}}i(bs,"nextOf");const Ss=i(e=>e.items||e.phases.flat(),"waveItems");function So(e){const{owner:t={},topicItems:n={},events:s=[],threads:r={},prs:a={},working:d={}}=e,c=e.now??Date.now(),u=jt(s,t),g=new Set(s.filter(o=>o.kind==="ask-withdrawn"&&!o.data?.key).map(o=>o.data?.id)),m=new Map(s.filter(o=>o.kind==="ask"&&!g.has(o.id)).map(o=>[o.id,o])),p=i((o,k)=>new Map(s.filter(S=>S.kind===o).sort((S,$)=>String(S.ts).localeCompare(String($.ts))).map(S=>[S.data?.[k],S.ts])),"latest"),w=p("ask-bump","id"),x=p("card-unbump","key"),_=new Map([...p("card-bump","key")].filter(([o,k])=>!(String(x.get(o)||"")>String(k)))),O=p("card-done","key"),N=p("card-merge","key"),D=e.autoMerge===!0,B=e.tickets,T=new Map(s.filter(o=>o.kind==="card-hold").sort((o,k)=>String(o.ts).localeCompare(String(k.ts))).map(o=>[o.data?.key,o])),P=new Map,y=new Map(e.files.flatMap(({slug:o,itemFile:k})=>k.items.map(S=>[`${o}/${S.id}`,{ref:nt(k,S,B),title:S.title}]))),b=e.askLinks||{},l=new Map;for(const[o,k]of Object.entries(b))m.has(o)&&l.set(k,[...l.get(k)||[],o]);const A=[],h=[],te=new Map,ae=new Map,G=new Map,X=new Map,F=e.github?null:en(e.remote);for(const{slug:o,itemFile:k,agents:S={}}of e.files){const $=e.github||k.repo||null;A.push({slug:o,title:k.title||o,status:k.status||null,internal:!!k.internal,finished:!1,reviewers:(k.reviewers||[]).map(v=>v.name)}),k.items.forEach((v,M)=>{const I=`${o}/${v.id}`,he=(l.get(I)||[]).filter(f=>f!==v.from_ask),se=[v.from_ask,...he].filter(f=>m.has(f)),Ze=new Set(se.map(f=>`ask:${f}`)),We=[...E(r,I)||[],...se.flatMap(f=>E(r,`ask:${f}`)||[])];v.from_ask&&P.set(v.from_ask,[...P.get(v.from_ask)||[],{key:I,ref:nt(k,v,B),became:!0}]);const ye=Ut(I,{lines:We,events:s,owner:t,working:d,fromAsk:v.from_ask||null,moved:he,cards:y,index:u});for(const f of ye)for(const[W,Qt]of Ce(f))te.set(W,Qt);ae.set(I,ct(ye)),h.push(Rn({slug:o,itemFile:k,it:v,order:M,repo:$,remote:F,base:e.branch||"main",prs:on(v,$).map(f=>({...f,gh:E(a,an(f))||null})),...(f=>({agents:f,builder:Jn(f,E(r,I),c,e.moved?.[it(E(S,v.id))]??null,E(e.alive,I)??null,!!e.commandRunning?.[it(E(S,v.id))]),reachedPhase:zn(f,E(r,I)),eta:f.map(W=>W.eta).filter(Boolean).sort().pop()||null}))(lt(E(S,v.id))),worktree:it(E(S,v.id))??v.worktree?.path??null,thread:We,events:[...ut(u,[I,...Ze].map(f=>u.byKey.get(f))),...se.map(f=>m.get(f))],working:ye.some(f=>Ce(f).some(([,W])=>W==="working")),said:ye.filter(f=>f.who==="love"&&(f.kind==="item-message"||f.kind==="owner-answer")),askedAt:(u.questions.get(`item:${I}`)||[]).filter(f=>f.asked_at).map(f=>f.asked_at),topicItems:n,prSlack:e.prSlack||{},boardReviewers:e.reviewers||[],now:c,bumpedAt:_.get(I),holdAsk:T.get(I),closedAt:O.get(I),mergeAt:N.get(I),autoMerge:D,skipDraft:e.skipDraft===!0,tickets:B}));const be=h[h.length-1];m.has(v.from_ask)&&(be.created=m.get(v.from_ask).ts),X.set(I,{parent:v.parent?Kt(o,v.parent):null,related:(v.related||[]).map(f=>Kt(o,f))}),be.lane==="done"&&G.set(I,be.closedAt||String(be.updatedAt||""))})}const L=new Map(h.map(o=>[o.key,o])),Y=new Map,z=e.try||{},Ne=e.autoBuild===!0,U=["web","app"].includes(z.kind)?z.kind:e.files.some(({itemFile:o})=>o.items.some(k=>k.install?.url))?"app":"web";for(const o of h){if(o.kind!=="work"||o.lane==="done")continue;const k=E(z.cards,o.key)||{},S=k.head||o.pr?.headRefOid,$=U==="web"?k.sha:o.install?.sha,v=o.builder?.kind===ce.install?"running":k.state||(U==="app"&&o.install?"ready":null);o.try={kind:U,state:v,stale:!!($&&S&&!String(S).toLowerCase().startsWith(String($).toLowerCase())),url:U==="web"?null:o.install?.url||null,port:U==="web"&&v==="ready"&&k.https||null,sha:$||null,changes:k.ahead>0,script:k.script===!0,autoBuild:Ne}}const j=z.main,Te=j?.script&&j.sha?{slug:j.slug,branch:j.branch||"main",sha:j.sha.slice(0,7),at:j.at,kind:U,state:j.state||null,url:U==="web"?null:e.main?.install?.url||null,port:U==="web"&&j.state==="ready"&&j.https||null,pull:z.pull?{running:z.pull.running===!0,error:z.pull.error||null}:null,script:un(j.target)}:null,K=e.askProgress||{},ge=p("ask-done","id"),Ve=$e(s.filter(o=>o.kind==="owner-answer"),o=>o.data?.id),Le=$e(s.filter(o=>o.kind==="prototype-request"),o=>o.data?.question),Ae=$e(s.filter(o=>o.kind==="reask-request"),o=>o.data?.question),Xe=$e(s.filter(o=>o.kind===wt),o=>o.data?.question),Q=(t.items||[]).map(o=>ss(o,Ve.get(o.id)||[],Le.get(o.id)||[],Ae.get(o.id)||[],Xe.get(o.id)||[])),J=new Map(Q.map(o=>[o.id,o])),Ee=$e(Q,o=>o.topic),ke=i(o=>{const k=Ee.get(o)||[];return{open:k.filter(S=>S.state==="open").length,answered:k.filter(S=>S.state==="answered").length}},"pendingOn");for(const o of m.values()){const k=`ask:${o.id}`,S=L.get(E(b,o.id)),$=[...P.get(o.id)||[]],v=S&&!$.some(I=>I.key===S.key);if(v&&$.push({key:S.key,ref:S.ref,title:S.title,became:!1,folded:!0}),$.length&&!v){Y.set(k,$[0].key);continue}if(!v&&E(b,o.id)){Y.set(k,E(b,o.id));continue}const M=Ut(k,{lines:E(r,k)||[],events:s,working:d,links:$,index:u});for(const I of M)for(const[he,se]of Ce(I))te.set(he,se);ae.set(k,ct(M)),h.push(Qn(o,M,w.get(o.id),$.length>0||!!E(b,o.id),E(K,o.id),ge.get(o.id),ke(`item:${k}`),E(e.askTitles||{},o.id)))}for(const o of A){const k=h.filter(S=>S.file===o.slug);if(o.finished=vn(k),o.finished)for(const S of k)S.lane==="done"&&(S.fold=wn(o.slug))}for(const o of h)o.wave=null;const me=new Set((t.topics||[]).map(o=>o.id)),Be=e.files.flatMap(({slug:o,itemFile:k})=>(k.waves||[]).map(S=>({...S,keys:Ss(S).map($=>`${o}/${$}`)}))),Z=i(()=>ls([...t.topics||[],...h.filter(o=>!me.has(`item:${o.key}`)).map(o=>({id:`item:${o.key}`,card:o.key,title:`${o.ref}: ${o.title}`,...o.kind==="ask"?{askedAt:o.ask.askedAt}:{}})),...Be.filter(o=>!me.has(`wave:${o.slug}`)).map(o=>({id:`wave:${o.slug}`,title:o.title,summary:"Wave triage."})),...(e.fromWaves?A:[]).filter(o=>!me.has(`file:${o.slug}`)).map(o=>({id:`file:${o.slug}`,title:o.title,summary:"Wave triage."}))],Q,new Set(h.filter(ue).map(o=>`item:${o.key}`))),"topicsOf"),ne=new Map(Z().map(o=>[o.id,o]));for(const o of h){const k=ne.get(`item:${o.key}`);if(!k){o.questions=null,o.prototypes=null;continue}const S=k.questions.filter(M=>J.get(M)?.state!=="resolved"),$=k.questions.find(M=>J.get(M)?.state==="open")||S[0]||k.questions[0],v=k.questions.map(M=>J.get(M)).filter(M=>M?.state==="open");o.questions={topic:k.id,state:k.state,open:k.open,answered:k.answered,replied:k.replied,resolved:k.resolved,first:$,askedAt:v.map(M=>M.asked_at).filter(Boolean).sort(),fromAsks:v.length>0&&v.every(M=>M.ask)},o.prototypes=as(k.questions.map(M=>J.get(M)))}const le=new Map(h.map(o=>[o.key,o])),_e=new Map(Be.map(o=>[`wave:${o.slug}`,o.keys]));for(const o of h){const k=o.kind==="work"&&o.lane!=="done",S=new Set(o.deps.flatMap(v=>_e.get(v)||[String(v).includes("/")?v:`${o.file}/${v}`]));o.blockedBy=k?[...S].map(v=>le.get(v)).filter(v=>v&&v.lane!=="done"&&!v.reopened).map(Ft):[],o.blockedUntil=k?o.blockedUntil??null:null,o.blockReason=k?o.blockReason??null:null,o.blocked=o.blockedBy.length>0||!!o.blockedUntil,Kn(o,J,D),o.lane==="todo"&&o.kind==="work"&&!o.held&&Bt(o.storedLane,o.agents)&&(o.lane="progress"),o.unpicked=o.kind==="work"&&o.lane==="todo"&&!!Pn(Q,o.key),o.kind==="work"&&(o.phase=Qs(o)),o.unpicked&&!o.action&&(o.next=jn),o.buildable=Tn(o);const $=o.kind==="work"&&o.lane==="todo";o.unsorted=$&&(!!o.fromAsk||o.unfiled)&&!o.filedHeld&&o.phase==="new",o.ready=$&&o.phase==="ready"&&!o.blocked&&!o.action,o.staging=$&&o.phase==="staging",o.kind==="work"&&(o.hostHeld=!!e.hostBusy&&o.ready&&!!o.bumpedAt&&!o.held)}Os(h,le,X);const Re=new Map((t.items||[]).map(o=>[o.id,o.topic])),Pe=i(o=>o.kind==="owner-answer"?Re.has(o.data?.id)?/^item:(.+)$/.exec(Re.get(o.data.id))?.[1]||null:"":o.kind==="ask"?`ask:${o.id}`:o.data?.key||(o.kind.startsWith("ask-")&&o.data?.id?`ask:${o.data.id}`:null),"ownCardOf"),R=i(o=>{const k=Pe(o);return Y.get(k)||k},"cardOf"),Ye=i(o=>{const k=o.startsWith("ask:")?o.slice(4):null,S=k&&E(K,k)?.state==="done"?E(K,k).at:null;return[O.get(o),k&&ge.get(k),S,G.get(o)].filter(Boolean).sort().pop()||""},"closedAt"),Me=i(o=>{const k=R(o);return k===null?!o.reply:!le.has(k)||!(Ye(k)<String(E(d,o.id)))?!1:te.has(o.id)?te.get(o.id)==="working":!o.reply&&!Y.has(Pe(o))&&!(String(ae.get(k)||"")>String(o.ts))},"unanswered"),C=s.filter(o=>E(d,o.id)&&Me(o)).map(o=>({id:o.id,kind:o.kind,key:R(o),data:o.data,since:E(d,o.id)}));for(const o of h)o.working=C.some(k=>k.key===o.key),o.kind==="work"&&o.lane==="progress"&&!o.builder&&!o.next&&o.waitsOn!=="checking"&&!o.working&&(o.next=_t),o.sortAt=$s[o.lane](o)||(o.lane==="review"?null:o.updatedAt)||null;for(const o of h){if(o.kind!=="work")continue;const k=(Ee.get(`item:${o.key}`)||[]).filter($=>$.state==="answered").map($=>$.answers.at(-1).ts).sort().pop(),S=C.filter($=>$.key===o.key).map($=>String($.since)).sort()[0];o.orchestrator=!o.stalled&&(["progress","todo"].includes(o.lane)||o.waitsOn==="checking"||o.lane==="review"&&(o.stale||o.waitFacts.unblockedAt))?zs(o,o.waitFacts,{answeredAt:k,workingSince:S}):null,delete o.waitFacts}js(h,s,d),Us(h,e.screening||{}),h.sort(pn);const ze=ys(e,A,Be,h,Q,s),ie=Z(),de=tn({on:e.autoReview,incoming:e.incoming,records:e.reviewRecords,events:s,now:c,refs:new Map(h.filter(o=>o.kind==="work").map(o=>[o.key,o.ref]))}),we=[...Es(h,ie,Q),...nn(de)],Ie=new Map(we.filter(o=>o.questions).map((o,k)=>[o.topic||`item:${o.key}`,k])),je=i(o=>Ie.get(o.id)??Ie.size,"rankOf");ie.sort((o,k)=>je(o)-je(k));const Ue=new Set(we.filter(o=>o.questions&&o.progress).map(o=>`item:${o.key}`));for(const o of ie)Ue.has(o.id)&&(o.progress=!0);const Je=h.filter(o=>o.ask?.answerAt).sort((o,k)=>k.ask.answerAt.localeCompare(o.ask.answerAt)).map(o=>({key:o.key,id:o.id,text:o.title,at:o.ask.answerAt}));return vs({kind:U,main:Te,remote:F&&{host:F.host,name:`${F.host}/${F.path}`},itemFiles:A,waveBands:ze,items:h,moved:Object.fromEntries(Y),answers:Je,owner:Q,ownerTopics:ie,reviews:de,needs:we,working:C.length,workingOn:C,checks:As(e,c)},e)}i(So,"buildBoard");function vs(e,{listen:t,prompts:n}={}){const s=[..._s(n),...e.needs];return{...e,needs:s,needsSummary:Rs(s),checks:{...e.checks,watcher:{listening:"running",asleep:"asleep",waking:"waking"}[t]||"stopped"}}}i(vs,"withHub");const Ft=i(e=>({key:e.key,ref:e.ref}),"linkOf"),Kt=i((e,t)=>String(t).includes("/")?String(t):`${e}/${t}`,"linkKey"),xs=i((e,t)=>e.ref.localeCompare(t.ref,void 0,{numeric:!0}),"byRef");function Os(e,t,n){const s=new Map,r=new Map,a=new Map,d=i((u,g,m)=>u.set(g,[...u.get(g)||[],m]),"add");for(const[u,g]of n){g.parent&&d(s,g.parent,u);for(const m of g.related)m!==u&&(d(r,u,m),d(r,m,u))}for(const u of e)for(const g of u.blockedBy||[])d(a,g.key,u.key);const c=i((u=[])=>[...new Set(u)].map(g=>t.get(g)).filter(Boolean).map(Ft).sort(xs),"cards");for(const u of e){const g=n.get(u.key)?.parent;u.parent=g?{key:g,ref:t.get(g)?.ref||g.slice(g.indexOf("/")+1)}:null,u.children=c(s.get(u.key)),u.related=c(r.get(u.key)),u.waitsOnIt=c(a.get(u.key))}}i(Os,"linkCards");const $s={todo:i(e=>e.created,"todo"),progress:i(e=>e.progressSince,"progress"),review:i(e=>e.reviewSince,"review"),done:i(e=>e.kind==="ask"?e.ask.answerAt:e.closedAt,"done")},re=i((e,t)=>`${e} ${t}${e===1?"":"s"}`,"plural");function As({checked:e,watcher:t,replyAt:n,threads:s={},working:r={},files:a=[]},d){const c=Number(e?.every)||30,g=[n,t?.since,...Object.values(r),...Object.values(s).flat().filter(p=>(p.who||"claude")==="claude").map(p=>p.ts),...a.flatMap(p=>Object.values(p.agents||{}).flatMap(w=>Object.values(w||{})).map(w=>w?.done||w?.started))].filter(p=>typeof p=="string"&&!Number.isNaN(Date.parse(p))).reduce((p,w)=>p&&Date.parse(p)>Date.parse(w)?p:w,null),m=!!e?.idle;return{at:e?.at||null,every:m?null:c,idle:m,late:!m&&!!e?.at&&d-Date.parse(e.at)>2*c*6e4,claudeAt:g}}i(As,"checksOf");function Es(e,t,n){const s=new Set(e.map(c=>c.questions?.topic).filter(Boolean)),r=i(c=>c.find(u=>n.find(g=>g.id===u)?.state==="open")||c[0],"firstOpen"),a=t.filter(c=>c.state!=="resolved"&&c.open>0&&!s.has(c.id)).map(c=>{const u=`questions.html#${r(c.questions)}`,g=/^(?:wave|file):(.+)$/.exec(c.id)?.[1]||null;return{kind:"Question",title:c.title,text:`${c.title} \xB7 ${re(c.open,"question")}`,url:u,topic:c.id,band:g,questions:{open:c.open,url:u},ready:null,since:n.filter(m=>m.topic===c.id&&m.state==="open").map(m=>m.asked_at).filter(Boolean).sort()[0]||null}}),d=e.map(c=>{const u=c.questions&&c.questions.state!=="resolved"&&c.questions.open>0?{open:c.questions.open,url:`questions.html#${c.questions.first}`}:null,g=c.waitsOn==="ready"?c.pr.url:null,m=!u&&!g&&c.action?{label:c.action.label,url:c.action.url}:null;if(!u&&!g&&!m)return null;const p=`${c.ref}: ${c.title}`,w=[p,u&&re(u.open,"question"),g&&"Ready on GitHub",m?.label].filter(Boolean).join(" \xB7 ");return{kind:u?"Question":g?"Tap Ready":"Action needed",title:p,text:w,url:`#selectedCard=${encodeURIComponent(c.key)}`,key:c.key,questions:u,ready:g,action:m,since:c.created||null,progress:c.lane==="progress"}}).filter(Boolean);return[...a,...d].sort(Bs)}i(Es,"needsOf");const Bs=i((e,t)=>!!t.progress-!!e.progress||(e.since&&t.since?e.since.localeCompare(t.since):!!t.since-!!e.since),"oldestFirst");function _s(e=[]){return e.map(t=>{const n=t.questions?.length?`Question: ${t.questions.map(s=>s.question).join(" \xB7 ")}`:`${t.tool}: ${t.detail||t.action}`;return{kind:"Prompt",title:n,text:n,url:"",prompt:t}})}i(_s,"promptLines");function Rs(e){const t=e.filter(a=>a.prompt).length,n=e.reduce((a,d)=>a+(d.questions?.open||0),0),s=e.filter(a=>a.ready).length,r=e.filter(a=>a.review).length;return[t&&re(t,"prompt"),n&&re(n,"question"),s&&`${re(s,"draft")} waiting on Ready`,r&&`${re(r,"review")} held`].filter(Boolean).join(" \xB7 ")}i(Rs,"needsSummary");function vo(e){const t=i((...n)=>e.items.filter(s=>n.includes(s.lane)).length,"count");return{items:e.needs.map(({kind:n,text:s,url:r})=>({kind:n,text:s,url:r})),working:e.working,summary:e.needsSummary,lanes:{review:t("review"),progress:t("progress"),todo:t("todo")},building:e.items.some(n=>(n.agents||[]).some(s=>!s.done))}}i(vo,"needsAnswer");const Ms=i(e=>e.buildable&&e.phase==="ready"&&!e.held&&!e.blocked,"pullableCard"),Is=10;function qs(e,{cap:t=Is,pulled:n=[],hold:s=[],taken:r=0}={}){const a=e.filter(y=>!ue(y)),d=i((y,b)=>y.started&&!y.done&&V(y)&&!(y.kind===ve&&!y.writes)&&!y.pause?.paused&&!(y.waits&&b.some(l=>l.name===y.waits&&!l.done)),"counts"),c=a.filter(y=>!y.stalled).reduce((y,b)=>y+(b.agents||[]).filter((l,A,h)=>d(l,h)).length,0),u=a.filter(y=>Ms(y)&&!y.working).map(y=>y.key),g=a.filter(y=>n.includes(y.key)&&(u.includes(y.key)||y.buildable&&y.working)).map(y=>y.key),m=a.flatMap(y=>(y.agents||[]).filter(b=>b.kind===ve&&!b.started&&!b.done).map(b=>({key:y.key,name:b.name,writes:b.writes}))),p=Math.max(0,t-c-g.length-r),w=m.filter(y=>y.writes),x=[...m.filter(y=>!y.writes),...w.slice(0,p)].map(({key:y,name:b})=>({key:y,name:b})),_=Math.max(0,p-w.length),O=new Set(a.filter(y=>y.bumpedAt).map(y=>y.key)),N=u.filter(y=>!g.includes(y)&&!s.includes(y)),D=N.filter(y=>O.has(y)),B=[...D,...N.filter(y=>!O.has(y)).slice(0,Math.max(0,_-D.length))],T=D.slice(_),P=Ds(a,d).slice(0,T.length).map((y,b)=>({key:y,by:T[b]}));return{idle:!c&&!a.some(y=>y.waitsOn==="checking"),pullable:u,waiting:g,pulls:B,helpers:x,bumpOuts:P,pastCap:T,free:p}}i(qs,"pullOf");function Ds(e,t){const n=i(s=>{const r=s.agents||[],a=r.filter(u=>t(u,r)),[d]=a;return s.lane==="progress"&&!s.bumpedAt&&!s.stalled&&s.waitsOn!=="checking"&&s.builder?.phase!=="landing"&&!(s.prs||[]).length&&!s.pushed&&!s.mergeReady&&a.length===1&&d.kind!==ve&&d.pid&&d.label!==Ke&&!d.pause&&!r.some(u=>u.kind===ve&&u.started&&!u.done)?String(d.started):null},"newest");return e.map(s=>({key:s.key,at:n(s)})).filter(s=>s.at).sort((s,r)=>r.at.localeCompare(s.at)).map(s=>s.key)}i(Ds,"bumpOutsOf");function xo(e,t,n){return e.filter(s=>s.kind==="work"&&!ue(s)&&s.waitsOn!=="checking"&&!(s.agents||[]).some(r=>r.started&&!r.done)).flatMap(s=>{const r=(s.prs||[]).find(d=>d.state==="OPEN"&&d.mergeable==="CONFLICTING"&&d.repo===s.repo&&d.headRefOid),a=r&&`${r.headRefOid}@${n}`;return r&&E(t,s.key)!==a?[{key:s.key,pr:r.number,branch:r.headRefName,conflict:a}]:[]})}i(xo,"rebasesDue");const Cs=3;function Oo(e,{cap:t=Cs}={}){const n=e.filter(d=>!ue(d)),s=n.filter(d=>(d.agents||[]).some(c=>ee(c)&&c.started&&!c.done)).length,r=n.filter(d=>d.buildable&&!["ready","staging"].includes(d.phase)&&!d.unsorted&&!d.blocked&&!d.held&&!d.working&&!d.grilling&&!d.prototyping&&!d.questions&&!(d.agents||[]).some(c=>ee(c)&&c.kind!==pe)),a=r.filter(d=>d.bumpedAt);return[...a,...r.filter(d=>!d.bumpedAt).slice(0,Math.max(0,t-s-a.length))].map(d=>d.key)}i(Oo,"arrivalsOf");function $o(e){return e.filter(t=>t.kind==="work"&&t.lane==="todo"&&t.roundAt&&["grilling","prototyping"].includes(t.phase)&&!(t.agents||[]).some(n=>ee(n)&&n.kind!==pe&&n.started&&(String(n.started)>=String(t.roundAt)||!n.done))).map(t=>({key:t.key,kind:t.phase==="grilling"?"grill":"prototype"}))}i($o,"roundsDue");const Ns=i(e=>`${pe}-${e.id}`,"visualAgent");function Ao(e,t){const n=[];for(const s of e){const r=s.agents||[];if(r.some(c=>c.kind===pe&&c.started&&!c.done))continue;const a=new Set(r.map(c=>c.name)),d=t.find(c=>c.kind==="visual-request"&&c.data?.key===s.key&&!a.has(Ns(c)));d&&n.push({key:s.key,request:d})}return n}i(Ao,"visualsDue");const Ts=i(e=>e.kind==="ask"?`ask:${e.id}`:e.kind==="card-added"?e.data?.key??null:e.kind==="item-message"&&String(e.data?.key||"").startsWith("ask:")?e.data.key:null,"intakeKeyOf");function Ls(e,t,n){const s=e.get(n);return s?t.kind==="ask"?s.isNew:t.kind==="card-added"?s.kind==="work"&&s.unfiled:(s.ask?.owed||[]).includes(t.id):!1}i(Ls,"unsorted");const Ps={ask:"Sort the new task: file it with item add --from-ask, fold it with item relate-ask, or answer it with reply","item-message":"Answer the owner's message in the ask's thread: reply to it","card-added":"Link and file the card an agent added: item notes, then item set --relate and --phase"};function js(e,t,n){const s=new Map(e.map(a=>[a.key,a])),r=new Set;for(const a of t){const d=Ts(a);!d||r.has(d)||!Ls(s,a,d)||(r.add(d),s.get(d).orchestrator={kind:"intake",since:String(a.ts),picked:E(n,a.id)||null,next:Ps[a.kind]})}}i(js,"intakeWaits");function Us(e,t){for(const n of e){const s=E(t,n.key);s&&(n.orchestrator={kind:"screen",since:s.since,picked:null,next:`Screen the grill's questions ${s.ids.join(", ")}: question settle <id> --note for each the board already answers, then question publish ${n.key}; left 15 minutes unscreened, they reach the owner as they are`})}}i(Us,"screenWaits");function Ws(e){return e.filter(t=>t.lane==="todo"||t.lane==="review"&&t.stale).map(t=>JSON.stringify([t.key,t.title,t.deps||[]])).sort().join(`
7
- `)}i(Ws,"todoOf");function Eo(e){const t=e.filter(s=>s.kind==="work"&&s.lane!=="done"&&s.questions?.open>0),n=t.map(s=>JSON.stringify([s.key,s.questions.open,s.questions.askedAt||[]])).sort().join(`
5
+ `),files:s.flatMap(r=>r.files),state:s.map(r=>r.state).sort((r,a)=>Pt.indexOf(r)-Pt.indexOf(a))[0],reply:void 0,answers:s.map(r=>({eventId:r.eventId,state:r.state,reply:r.reply,replies:r.replies,text:r.text}))}]})}d(vs,"foldSubmits");const Ut=d(e=>(e.answers||[e]).filter(t=>t.eventId).map(t=>[t.eventId,t.state]),"stateEntries");function xs(e,t,n,s,r,a){const c=d((p,w)=>{const v=a.filter(x=>x.kind==="wave-triage"&&x.data?.[p]===w).pop(),_=r.filter(x=>x.topic===`${p}:${w}`&&(x.state==="open"||x.state==="answered")).length;return{running:!!(v&&!v.reply),questions:_}},"triageOf"),l=new Map(s.map(p=>[p.key,p])),u=d(p=>p.filter(w=>w.lane==="done"&&!St(w)).length,"merged"),m=n.map(p=>({l:p,cards:p.keys.map(w=>l.get(w)).filter(Boolean)})).map(p=>({...p,open:!p.l.finished_at&&p.cards.some(w=>w.lane!=="done")})).sort((p,w)=>Number(p.open)-Number(w.open));for(const{l:p,cards:w}of m)for(const v of w)v.wave={slug:p.slug,title:p.title};const f=m.filter(p=>p.open).map(({l:p,cards:w})=>({kind:"wave",slug:p.slug,title:p.title,goal:p.goal,keys:w.map(v=>v.key),next:Os(w),merged:u(w),total:w.length,triage:c("wave",p.slug)}));return e.fromWaves?[...f,...t.filter(p=>!p.finished).map(p=>({w:p,cards:s.filter(w=>w.file===p.slug&&w.kind!=="ask")})).filter(({cards:p})=>p.length).map(({w:p,cards:w})=>({kind:"file",slug:p.slug,title:p.title,goal:null,keys:w.map(v=>v.key),next:null,merged:u(w),total:w.length,triage:c("file",p.slug)}))]:f}d(xs,"waveBandsOf");function Os(e){const t=e.filter(a=>a.lane==="todo"),n=t.find(a=>a.ready&&a.buildable)||t[0];if(!n)return null;const s=n.ready&&n.buildable,r=s?"ready":n.held?"held":n.blockedBy.length?`waits on ${n.blockedBy.map(a=>a.ref).join(", ")}`:n.blockedUntil?`blocked until ${n.blockedUntil}`:n.action?"needs you":n.phase==="new"?"not ready":n.phase;return{key:n.key,ref:n.ref,ready:s,word:r}}d(Os,"nextOf");const Es=d(e=>e.items||e.phases.flat(),"waveItems");function $o(e){const{owner:t={},topicItems:n={},events:s=[],threads:r={},prs:a={},working:c={}}=e,l=e.now??Date.now(),u=Tt(s,t),m=new Set(s.filter(o=>o.kind==="ask-withdrawn").map(o=>o.data?.id)),f=new Map(s.filter(o=>o.kind==="ask"&&!m.has(o.id)).map(o=>[o.id,o])),p=d((o,g)=>new Map(s.filter(E=>E.kind===o).sort((E,$)=>String(E.ts).localeCompare(String($.ts))).map(E=>[E.data?.[g],E.ts])),"latest"),w=p("card-unbump","key"),v=new Map([...p("card-bump","key")].filter(([o,g])=>!(String(w.get(o)||"")>String(g)))),_=p("card-done","key"),x=p("card-merge","key"),P=e.autoMerge===!0,C=e.tickets,L=new Map(s.filter(o=>o.kind==="card-hold").sort((o,g)=>String(o.ts).localeCompare(String(g.ts))).map(o=>[o.data?.key,o])),T=new Map,W=new Map(e.files.flatMap(({slug:o,itemFile:g})=>g.items.map(E=>[`${o}/${E.id}`,{ref:nt(g,E,C),title:E.title}]))),h=e.askLinks||{},b=new Map;for(const[o,g]of Object.entries(h))f.has(o)&&b.set(g,[...b.get(g)||[],o]);const i=[],S=[],oe=new Map,ke=new Map,me=new Map,K=new Map,F=e.github?null:tn(e.remote);for(const{slug:o,itemFile:g,agents:E={}}of e.files){const $=e.github||g.repo||null;i.push({slug:o,title:g.title||o,status:g.status||null,internal:!!g.internal,finished:!1,reviewers:(g.reviewers||[]).map(y=>y.name)}),g.items.forEach((y,M)=>{const B=`${o}/${y.id}`,Ke=(b.get(B)||[]).filter(O=>O!==y.from_ask),le=[y.from_ask,...Ke].filter(O=>f.has(O)),Ce=new Set(le.map(O=>`ask:${O}`)),Oe=[...R(r,B)||[],...le.flatMap(O=>R(r,`ask:${O}`)||[])];y.from_ask&&T.set(y.from_ask,[...T.get(y.from_ask)||[],{key:B,ref:nt(g,y,C),became:!0}]);const ne=Ss(B,{lines:Oe,events:s,owner:t,working:c,fromAsk:y.from_ask||null,moved:Ke,cards:W,index:u});for(const O of ne)for(const[ie,Ge]of Ut(O))oe.set(ie,Ge);ke.set(B,Lt(ne)),S.push(Nn({slug:o,itemFile:g,it:y,order:M,repo:$,remote:F,base:e.branch||"main",prs:rn(y,$).map(O=>({...O,gh:R(a,ln(O))||null})),...(O=>({agents:O,builder:ts(O,R(r,B),l,e.moved?.[it(R(E,y.id))]??null,R(e.alive,B)??null,!!e.commandRunning?.[it(R(E,y.id))]),reachedPhase:es(O,R(r,B)),eta:O.map(ie=>ie.eta).filter(Boolean).sort().pop()||null}))(lt(R(E,y.id))),worktree:it(R(E,y.id))??y.worktree?.path??null,thread:Oe,events:[...ct(u,[B,...Ce].map(O=>u.byKey.get(O))),...le.map(O=>f.get(O))],working:ne.some(O=>Ut(O).some(([,ie])=>ie==="working")),said:ne.filter(O=>O.who==="love"&&(O.kind==="item-message"||O.kind==="owner-answer")),askedAt:(u.questions.get(`item:${B}`)||[]).filter(O=>O.asked_at).map(O=>O.asked_at),topicItems:n,prSlack:e.prSlack||{},boardReviewers:e.reviewers||[],now:l,bumpedAt:v.get(B),holdAsk:L.get(B),closedAt:_.get(B),mergeAt:x.get(B),autoMerge:P,skipDraft:e.skipDraft===!0,tickets:C}));const J=S[S.length-1];f.has(y.from_ask)&&(J.created=f.get(y.from_ask).ts),J.askMsg=ne.find(O=>O.kind==="ask"&&O.eventId===y.from_ask)||null,K.set(B,{parent:y.parent?Wt(o,y.parent):null,related:(y.related||[]).map(O=>Wt(o,O))}),J.lane==="done"&&me.set(B,J.closedAt||String(J.updatedAt||""))})}const ee=new Map(S.map(o=>[o.key,o])),I=new Map,G=e.try||{},ze=e.autoBuild===!0,U=["web","app"].includes(G.kind)?G.kind:e.files.some(({itemFile:o})=>o.items.some(g=>g.install?.url))?"app":"web";for(const o of S){if(o.kind!=="work"||o.lane==="done")continue;const g=R(G.cards,o.key)||{},E=g.head||o.pr?.headRefOid,$=U==="web"?g.sha:o.install?.sha,y=o.builder?.kind===ue.install?"running":g.state||(U==="app"&&o.install?"ready":null);o.try={kind:U,state:y,stale:!!($&&E&&!String(E).toLowerCase().startsWith(String($).toLowerCase())),url:U==="web"?null:o.install?.url||null,port:U==="web"&&y==="ready"&&g.https||null,sha:$||null,changes:g.ahead>0,script:g.script===!0,autoBuild:ze}}const N=G.main,we=N?.script&&N.sha?{slug:N.slug,branch:N.branch||"main",sha:N.sha.slice(0,7),at:N.at,kind:U,state:N.state||null,url:U==="web"?null:e.main?.install?.url||null,port:U==="web"&&(N.state==="ready"?N.https:N.serving?.https)||null,serving:U==="web"&&N.serving?{sha:N.serving.sha.slice(0,7),at:N.serving.at}:null,pull:G.pull?{running:G.pull.running===!0,error:G.pull.error||null}:null,script:pn(N.target)}:null,he=[],ye=[],Le=_e(s.filter(o=>o.kind==="owner-answer"),o=>o.data?.id),j=_e(s.filter(o=>o.kind==="prototype-request"),o=>o.data?.question),Re=_e(s.filter(o=>o.kind==="reask-request"),o=>o.data?.question),Je=_e(s.filter(o=>o.kind===mt),o=>o.data?.question),H=(t.items||[]).map(o=>as(o,Le.get(o.id)||[],j.get(o.id)||[],Re.get(o.id)||[],Je.get(o.id)||[])),X=new Map(H.map(o=>[o.id,o])),Te=_e(H,o=>o.topic);for(const o of f.values()){const g=`ask:${o.id}`,E=R(r,g)||[],$=T.get(o.id)?.[0].key||R(h,o.id);if(!$){E.some(B=>B.line==="ask-moved")||ye.push(o.id);continue}I.set(g,$);const y=E.filter(B=>B.line===fn).at(-1),M=ee.get($);if(y&&M){const B=y.title||String(u.edits.get(o.id)??o.data?.text??"").trim().split(`
6
+ `)[0]||"Ask";he.push({key:M.key,ref:M.ref,text:B,at:String(y.ts)})}}for(const o of i){const g=S.filter(E=>E.file===o.slug);if(o.finished=On(g),o.finished)for(const E of g)E.lane==="done"&&(E.fold=yn(o.slug))}for(const o of S)o.wave=null;const be=new Set((t.topics||[]).map(o=>o.id)),Se=e.files.flatMap(({slug:o,itemFile:g})=>(g.waves||[]).map(E=>({...E,keys:Es(E).map($=>`${o}/${$}`)}))),ve=d(()=>cs([...t.topics||[],...S.filter(o=>!be.has(`item:${o.key}`)).map(o=>({id:`item:${o.key}`,card:o.key,title:`${o.ref}: ${o.title}`})),...Se.filter(o=>!be.has(`wave:${o.slug}`)).map(o=>({id:`wave:${o.slug}`,title:o.title,summary:"Wave triage."})),...(e.fromWaves?i:[]).filter(o=>!be.has(`file:${o.slug}`)).map(o=>({id:`file:${o.slug}`,title:o.title,summary:"Wave triage."}))],H,new Set(S.filter(pe).map(o=>`item:${o.key}`))),"topicsOf"),Pe=new Map(ve().map(o=>[o.id,o]));for(const o of S){const g=Pe.get(`item:${o.key}`);if(!g){o.questions=null,o.prototypes=null;continue}const E=g.questions.filter(M=>X.get(M)?.state!=="resolved"),$=g.questions.find(M=>X.get(M)?.state==="open")||E[0]||g.questions[0],y=g.questions.map(M=>X.get(M)).filter(M=>M?.state==="open");o.questions={topic:g.id,state:g.state,open:g.open,answered:g.answered,replied:g.replied,resolved:g.resolved,first:$,askedAt:y.map(M=>M.asked_at).filter(Boolean).sort(),fromAsks:y.length>0&&y.every(M=>M.ask)},o.prototypes=ds(g.questions.map(M=>X.get(M)))}const te=new Map(S.map(o=>[o.key,o])),Ue=new Map(Se.map(o=>[`wave:${o.slug}`,o.keys]));for(const o of S){const g=o.kind==="work"&&o.lane!=="done",E=new Set(o.deps.flatMap(y=>Ue.get(y)||[String(y).includes("/")?y:`${o.file}/${y}`]));o.blockedBy=g?[...E].map(y=>te.get(y)).filter(y=>y&&y.lane!=="done"&&!y.reopened).map(jt):[],o.blockedUntil=g?o.blockedUntil??null:null,o.blockReason=g?o.blockReason??null:null,o.blocked=o.blockedBy.length>0||!!o.blockedUntil,Xn(o,X,P),o.lane==="todo"&&o.kind==="work"&&!o.held&&Et(o.storedLane,o.agents)&&(o.lane="progress"),o.unpicked=o.kind==="work"&&o.lane==="todo"&&!!Kn(H,o.key),o.kind==="work"&&(o.phase=Ys(o)),o.unpicked&&!o.action&&(o.next=Gn),o.buildable=Wn(o);const $=o.kind==="work"&&o.lane==="todo";if(o.unsorted=$&&(!!o.fromAsk||o.unfiled)&&!o.filedHeld&&o.phase==="new",o.kind==="work"){const y=o.askMsg;delete o.askMsg,o.unsorted&&y&&(o.isNew=!0,o.ask={eventId:y.eventId,state:y.state,askedAt:y.ts,edited:!!y.edited,canEdit:!0,text:y.text,files:y.files,from:y.from||null})}o.ready=$&&o.phase==="ready"&&!o.blocked&&!o.recheckAt&&!o.action,o.staging=$&&o.phase==="staging",o.kind==="work"&&(o.hostHeld=!!e.hostBusy&&o.ready&&!!o.bumpedAt&&!o.held)}Bs(S,te,K);const je=new Map((t.items||[]).map(o=>[o.id,o.topic])),Y=d(o=>o.kind==="owner-answer"?je.has(o.data?.id)?/^item:(.+)$/.exec(je.get(o.data.id))?.[1]||null:"":o.kind==="ask"?`ask:${o.id}`:o.data?.key||(o.kind.startsWith("ask-")&&o.data?.id?`ask:${o.data.id}`:null),"ownCardOf"),z=d(o=>{const g=Y(o);return I.get(g)||g},"cardOf"),Me=d(o=>[_.get(o),me.get(o)].filter(Boolean).sort().pop()||"","closedAt"),Ie=d(o=>{const g=z(o);return g===null?!o.reply:!te.has(g)||!(Me(g)<String(R(c,o.id)))?!1:oe.has(o.id)?oe.get(o.id)==="working":!o.reply&&!I.has(Y(o))&&!(String(ke.get(g)||"")>String(o.ts))},"unanswered"),re=s.filter(o=>R(c,o.id)&&Ie(o)).map(o=>({id:o.id,kind:o.kind,key:z(o),data:o.data,since:R(c,o.id)}));for(const o of S)o.working=re.some(g=>g.key===o.key),o.kind==="work"&&o.lane==="progress"&&!o.builder&&!o.next&&o.waitsOn!=="checking"&&!o.working&&(o.next=$t),o.sortAt=_s[o.lane](o)||(o.lane==="review"?null:o.updatedAt)||null;for(const o of S){if(o.kind!=="work")continue;const g=(Te.get(`item:${o.key}`)||[]).filter($=>$.state==="answered").map($=>$.answers.at(-1).ts).sort().pop(),E=re.filter($=>$.key===o.key).map($=>String($.since)).sort()[0];o.orchestrator=!o.stalled&&(["progress","todo"].includes(o.lane)||o.waitsOn==="checking"||o.lane==="review"&&(o.stale||o.waitFacts.unblockedAt||o.waitFacts.replyAt))?no(o,o.waitFacts,{answeredAt:g,workingSince:E}):null,delete o.waitFacts}Ks(S,s,c,I),Gs(S,e.screening||{}),S.sort(gn);const Ze=xs(e,i,Se,S,H,s),A=ve(),We=nn({on:e.autoReview,incoming:e.incoming,records:e.reviewRecords,events:s,now:l,refs:new Map(S.filter(o=>o.kind==="work").map(o=>[o.key,o.ref]))}),ae=[...Ms(S,A,H),...sn(We)],D=new Map(ae.filter(o=>o.questions).map((o,g)=>[o.topic||`item:${o.key}`,g])),Fe=d(o=>D.get(o.id)??D.size,"rankOf");A.sort((o,g)=>Fe(o)-Fe(g));const qe=new Set(ae.filter(o=>o.questions&&o.progress).map(o=>`item:${o.key}`));for(const o of A)qe.has(o.id)&&(o.progress=!0);const xe=S.flatMap(o=>o.answered&&ke.get(o.key)?[{key:o.key,id:o.id,text:o.title,at:ke.get(o.key)}]:[]).sort((o,g)=>g.at.localeCompare(o.at));return he.sort((o,g)=>g.at.localeCompare(o.at)),$s({kind:U,main:we,remote:F&&{host:F.host,name:`${F.host}/${F.path}`},itemFiles:i,waveBands:Ze,items:S,moved:Object.fromEntries(I),answers:xe,merges:he,unfiled:ye,owner:H,ownerTopics:A,reviews:We,needs:ae,working:re.length,workingOn:re,checks:Rs(e,l)},e)}d($o,"buildBoard");function $s(e,{listen:t,prompts:n}={}){const s=[...qs(n),...e.needs];return{...e,needs:s,needsSummary:Cs(s),checks:{...e.checks,watcher:{listening:"running",asleep:"asleep",waking:"waking"}[t]||"stopped"}}}d($s,"withHub");const jt=d(e=>({key:e.key,ref:e.ref}),"linkOf"),Wt=d((e,t)=>String(t).includes("/")?String(t):`${e}/${t}`,"linkKey"),As=d((e,t)=>e.ref.localeCompare(t.ref,void 0,{numeric:!0}),"byRef");function Bs(e,t,n){const s=new Map,r=new Map,a=new Map,c=d((u,m,f)=>u.set(m,[...u.get(m)||[],f]),"add");for(const[u,m]of n){m.parent&&c(s,m.parent,u);for(const f of m.related)f!==u&&(c(r,u,f),c(r,f,u))}for(const u of e)for(const m of u.blockedBy||[])c(a,m.key,u.key);const l=d((u=[])=>[...new Set(u)].map(m=>t.get(m)).filter(Boolean).map(jt).sort(As),"cards");for(const u of e){const m=n.get(u.key)?.parent;u.parent=m?{key:m,ref:t.get(m)?.ref||m.slice(m.indexOf("/")+1)}:null,u.children=l(s.get(u.key)),u.related=l(r.get(u.key)),u.waitsOnIt=l(a.get(u.key))}}d(Bs,"linkCards");const _s={todo:d(e=>e.created,"todo"),progress:d(e=>e.progressSince,"progress"),review:d(e=>e.reviewSince,"review"),done:d(e=>e.closedAt,"done")},se=d((e,t)=>`${e} ${t}${e===1?"":"s"}`,"plural");function Rs({checked:e,watcher:t,replyAt:n,threads:s={},working:r={},files:a=[]},c){const l=Number(e?.every)||30,m=[n,t?.since,...Object.values(r),...Object.values(s).flat().filter(p=>(p.who||"claude")==="claude").map(p=>p.ts),...a.flatMap(p=>Object.values(p.agents||{}).flatMap(w=>Object.values(w||{})).map(w=>w?.done||w?.started))].filter(p=>typeof p=="string"&&!Number.isNaN(Date.parse(p))).reduce((p,w)=>p&&Date.parse(p)>Date.parse(w)?p:w,null),f=!!e?.idle;return{at:e?.at||null,every:f?null:l,idle:f,late:!f&&!!e?.at&&c-Date.parse(e.at)>2*l*6e4,claudeAt:m}}d(Rs,"checksOf");function Ms(e,t,n){const s=new Set(e.map(l=>l.questions?.topic).filter(Boolean)),r=d(l=>l.find(u=>n.find(m=>m.id===u)?.state==="open")||l[0],"firstOpen"),a=t.filter(l=>l.state!=="resolved"&&l.open>0&&!s.has(l.id)).map(l=>{const u=`questions.html#${r(l.questions)}`,m=/^(?:wave|file):(.+)$/.exec(l.id)?.[1]||null;return{kind:"Question",title:l.title,text:`${l.title} \xB7 ${se(l.open,"question")}`,url:u,topic:l.id,band:m,questions:{open:l.open,url:u},ready:null,since:n.filter(f=>f.topic===l.id&&f.state==="open").map(f=>f.asked_at).filter(Boolean).sort()[0]||null}}),c=e.map(l=>{const u=l.questions&&l.questions.state!=="resolved"&&l.questions.open>0?{open:l.questions.open,url:`questions.html#${l.questions.first}`}:null,m=l.waitsOn==="ready"?l.pr.url:null,f=!u&&!m&&l.action?{label:l.action.label,url:l.action.url}:null;if(!u&&!m&&!f)return null;const p=`${l.ref}: ${l.title}`,w=[p,u&&se(u.open,"question"),m&&"Ready on GitHub",f?.label].filter(Boolean).join(" \xB7 ");return{kind:u?"Question":m?"Tap Ready":"Action needed",title:p,text:w,url:`#selectedCard=${encodeURIComponent(l.key)}`,key:l.key,questions:u,ready:m,action:f,since:l.created||null,progress:l.lane==="progress"}}).filter(Boolean);return[...a,...c].sort(Is)}d(Ms,"needsOf");const Is=d((e,t)=>!!t.progress-!!e.progress||(e.since&&t.since?e.since.localeCompare(t.since):!!t.since-!!e.since),"oldestFirst");function qs(e=[]){return e.map(t=>{const n=t.questions?.length?`Question: ${t.questions.map(s=>s.question).join(" \xB7 ")}`:`${t.tool}: ${t.detail||t.action}`;return{kind:"Prompt",title:n,text:n,url:"",prompt:t}})}d(qs,"promptLines");function Cs(e){const t=e.filter(a=>a.prompt).length,n=e.reduce((a,c)=>a+(c.questions?.open||0),0),s=e.filter(a=>a.ready).length,r=e.filter(a=>a.review).length;return[t&&se(t,"prompt"),n&&se(n,"question"),s&&`${se(s,"draft")} waiting on Ready`,r&&`${se(r,"review")} held`].filter(Boolean).join(" \xB7 ")}d(Cs,"needsSummary");function Ao(e){const t=d((...n)=>e.items.filter(s=>n.includes(s.lane)).length,"count");return{items:e.needs.map(({kind:n,text:s,url:r})=>({kind:n,text:s,url:r})),working:e.working,summary:e.needsSummary,lanes:{review:t("review"),progress:t("progress"),todo:t("todo")},building:e.items.some(n=>(n.agents||[]).some(s=>!s.done))}}d(Ao,"needsAnswer");const Ns=d(e=>e.buildable&&e.phase==="ready"&&!e.held&&!e.blocked&&!e.recheckAt,"pullableCard"),Ds=10;function Ls(e,{cap:t=Ds,pulled:n=[],hold:s=[],taken:r=0}={}){const a=e.filter(h=>!pe(h)),c=d((h,b)=>h.started&&!h.done&&Q(h)&&!(h.kind===$e&&!h.writes)&&!h.pause?.paused&&!(h.waits&&b.some(i=>i.name===h.waits&&!i.done)),"counts"),l=a.filter(h=>!h.stalled).reduce((h,b)=>h+(b.agents||[]).filter((i,S,oe)=>c(i,oe)).length,0),u=a.filter(h=>Ns(h)&&!h.working).map(h=>h.key),m=a.filter(h=>n.includes(h.key)&&(u.includes(h.key)||h.buildable&&h.working)).map(h=>h.key),f=a.flatMap(h=>(h.agents||[]).filter(b=>b.kind===$e&&!b.started&&!b.done).map(b=>({key:h.key,name:b.name,writes:b.writes}))),p=Math.max(0,t-l-m.length-r),w=f.filter(h=>h.writes),v=[...f.filter(h=>!h.writes),...w.slice(0,p)].map(({key:h,name:b})=>({key:h,name:b})),_=Math.max(0,p-w.length),x=new Set(a.filter(h=>h.bumpedAt).map(h=>h.key)),P=u.filter(h=>!m.includes(h)&&!s.includes(h)),C=P.filter(h=>x.has(h)),L=[...C,...P.filter(h=>!x.has(h)).slice(0,Math.max(0,_-C.length))],T=C.slice(_),W=Ts(a,c).slice(0,T.length).map((h,b)=>({key:h,by:T[b]}));return{idle:!l&&!a.some(h=>h.waitsOn==="checking"),pullable:u,waiting:m,pulls:L,helpers:v,bumpOuts:W,pastCap:T,free:p}}d(Ls,"pullOf");function Ts(e,t){const n=d(s=>{const r=s.agents||[],a=r.filter(u=>t(u,r)),[c]=a;return s.lane==="progress"&&!s.bumpedAt&&!s.stalled&&s.waitsOn!=="checking"&&s.builder?.phase!=="landing"&&!(s.prs||[]).length&&!s.pushed&&!s.mergeReady&&a.length===1&&c.kind!==$e&&c.pid&&c.label!==He&&!c.pause&&!r.some(u=>u.kind===$e&&u.started&&!u.done)?String(c.started):null},"newest");return e.map(s=>({key:s.key,at:n(s)})).filter(s=>s.at).sort((s,r)=>r.at.localeCompare(s.at)).map(s=>s.key)}d(Ts,"bumpOutsOf");function Bo(e,t,n){return e.filter(s=>s.kind==="work"&&!pe(s)&&s.waitsOn!=="checking"&&!(s.agents||[]).some(r=>r.started&&!r.done)).flatMap(s=>{const r=(s.prs||[]).find(c=>c.state==="OPEN"&&c.mergeable==="CONFLICTING"&&c.repo===s.repo&&c.headRefOid),a=r&&`${r.headRefOid}@${n}`;return r&&R(t,s.key)!==a?[{key:s.key,pr:r.number,branch:r.headRefName,conflict:a}]:[]})}d(Bo,"rebasesDue");const Ps=3;function _o(e,{cap:t=Ps}={}){const n=e.filter(l=>!pe(l)),s=n.filter(l=>(l.agents||[]).some(u=>Z(u)&&u.started&&!u.done)).length,r=n.filter(l=>l.buildable&&!["ready","staging"].includes(l.phase)&&!l.unsorted&&!l.held&&!l.working&&!l.grilling&&!l.prototyping&&!l.questions&&!(l.agents||[]).some(u=>Z(u)&&u.kind!==fe)),a=[...r.filter(l=>!l.blocked),...r.filter(l=>l.blocked)],c=a.filter(l=>l.bumpedAt);return[...c,...a.filter(l=>!l.bumpedAt).slice(0,Math.max(0,t-s-c.length))].map(l=>l.key)}d(_o,"arrivalsOf");function Ro(e){return e.filter(t=>t.kind==="work"&&t.lane==="todo"&&t.roundAt&&["grilling","prototyping"].includes(t.phase)&&!(t.agents||[]).some(n=>Z(n)&&n.kind!==fe&&n.started&&(String(n.started)>=String(t.roundAt)||!n.done))).map(t=>({key:t.key,kind:t.phase==="grilling"?"grill":"prototype"}))}d(Ro,"roundsDue");const Us=d(e=>`${fe}-${e.id}`,"visualAgent");function Mo(e,t){const n=[];for(const s of e){const r=s.agents||[];if(r.some(l=>l.kind===fe&&l.started&&!l.done))continue;const a=new Set(r.map(l=>l.name)),c=t.find(l=>l.kind==="visual-request"&&l.data?.key===s.key&&!a.has(Us(l)));c&&n.push({key:s.key,request:c})}return n}d(Mo,"visualsDue");const js=d((e,t)=>e.kind==="ask"?t.get(`ask:${e.id}`)??null:e.kind==="card-added"?e.data?.key??null:null,"intakeKeyOf");function Ws(e,t,n){const s=e.get(n);return s?t.kind==="ask"?!!s.isNew:s.unfiled:!1}d(Ws,"unsorted");const Fs={ask:d((e,t)=>`Sort the new task: item notes, then item set ${e} --phase grilling or prototyping (ready, with --size, for a Quick build); merge it into a card with item relate-ask ${t.id} <card> --summary; or answer it with reply ${e}`,"ask"),"card-added":d(()=>"Link and file the card an agent added: item notes, then item set --relate and --phase","card-added")};function Ks(e,t,n,s){const r=new Map(e.map(c=>[c.key,c])),a=new Set;for(const c of t){const l=js(c,s);!l||a.has(l)||!Ws(r,c,l)||(a.add(l),r.get(l).orchestrator={kind:"intake",since:String(c.ts),picked:R(n,c.id)||null,next:Fs[c.kind](l,c)})}}d(Ks,"intakeWaits");function Gs(e,t){for(const n of e){const s=R(t,n.key);s&&(n.orchestrator={kind:"screen",since:s.since,picked:null,next:`Screen the grill's questions ${s.ids.join(", ")}: question settle <id> --note for each the board already answers, then question publish ${n.key}; left 15 minutes unscreened, they reach the owner as they are`})}}d(Gs,"screenWaits");function Qs(e){return e.filter(t=>t.lane==="todo"||t.lane==="review"&&t.stale).map(t=>JSON.stringify([t.key,t.title,t.deps||[]])).sort().join(`
7
+ `)}d(Qs,"todoOf");function Io(e){const t=e.filter(s=>s.kind==="work"&&s.lane!=="done"&&s.questions?.open>0),n=t.map(s=>JSON.stringify([s.key,s.questions.open,s.questions.askedAt||[]])).sort().join(`
8
8
  `);return{waiting:t.map(s=>s.key),seen:`${n}
9
9
  --
10
- ${Ws(e)}`}}i(Eo,"triageOf");const Fs=100,Ks=["key","kind","id","ref","title","lane","phase","file","fileTitle","fold","internal","ticket","ticketSource","linear","pr","prs","review","reviewers","risky","sortAt","threadCount","othersAt","hasNotes","questions","prototypes","next","ask","action","actionButton","builder","working","waitsOn","reachedPhase","grilling","refusal","install","parent","children","related","waitsOnIt","blockedBy","ready","finishable","reopened","manualSteps","branch","worktree"],Bo=i(e=>Object.fromEntries(Ks.filter(t=>t in e).map(t=>[t,e[t]])),"slimDone");function _o(e,t=Fs){const n=new Set((e.waveBands||[]).flatMap(r=>r.keys)),s=new Set(e.items.filter(r=>r.lane!=="done").flatMap(r=>[...(r.deps||[]).map(a=>String(a).includes("/")?String(a):`${r.file}/${a}`),r.parent?.key,...(r.children||[]).map(a=>a.key),...(r.related||[]).map(a=>a.key)]));return e.items.filter(r=>r.lane==="done"&&r.kind==="work").sort((r,a)=>String(a.closedAt||"").localeCompare(String(r.closedAt||""))).slice(t).filter(r=>r.storedLane==="done"&&!r.reopened&&!n.has(r.key)&&!s.has(r.key)&&(!r.questions||r.questions.state==="resolved"))}i(_o,"archivedOf");const Gs={ready:"draft",ci:"draft",reviewers:"reviewers",merge:"merge"};function Qs(e){return e.lane==="todo"?e.unpicked?"prototyping":mt.todo.includes(e.storedPhase)?e.storedPhase:e.storedLane&&e.storedLane!=="todo"?"ready":"new":e.lane==="progress"?e.waitsOn==="checking"?"checking":e.builder&&!e.builder.planner&&e.builder.kind!==ce.install&&e.builder.phase||e.reachedPhase||"reading":e.lane==="review"?e.stale?"stale":e.waitsOn==="owner"&&e.pushed?"merge":Gs[e.waitsOn]??null:e.storedLane==="done"&&mt.done.includes(e.storedPhase)?e.storedPhase:e.prs.some(t=>t.status===H.merged)?"merged":"closed"}i(Qs,"phaseOf");function Ro(e){return e.items.filter(t=>t.kind==="work").flatMap(t=>{const n=t.lane;return n==="done"||t.storedLane==="done"&&!t.reopened?[]:n===t.storedLane&&(n==="todo"||t.phase===t.storedPhase)?[]:[{key:t.key,to:n,phase:t.phase,why:Hs(t,n),from:{lane:t.storedLane,phase:t.storedPhase}}]})}i(Ro,"movesOf");function Hs(e,t){return t==="todo"?e.held?"it is held":e.blocked?"it is blocked":"nothing builds it":e.reopened&&t==="progress"?"a message or question reopened it":t==="progress"?e.waitsOn==="checking"?"its build handed back for a check":e.builder?"a build started":"Claude acts next":e.next||"it waits on review"}i(Hs,"moveWhy");const Vs=i(e=>e.startsWith("ask:")?`ask-${e.slice(4)}`:`item-${e}`,"pushTag");function Mo(e){const t=i(a=>e.owner.filter(d=>d.topic===a&&d.state==="open").map(({id:d,question:c})=>({id:d,question:c})),"open"),n=[q.ready,q.merge],s=e.items.filter(a=>a.action).map(a=>{const d=rt.has(a.action.kind)?t(a.questions.topic):[];return{tag:Vs(a.key),kind:a.action.kind,title:`${a.ref}: ${a.action.label}`,body:d[0]?.question||a.title,url:d.length?a.action.url:`#selectedCard=${encodeURIComponent(a.key)}`,now:!n.includes(a.action.kind),questions:d}}),r=e.needs.filter(a=>a.topic).map(a=>{const d=t(a.topic);return{tag:`topic-${a.topic}`,kind:q.questions,title:a.text,body:d[0]?.question||a.title,url:a.url,now:!0,questions:d}});return[...s,...r,...sn(e.reviews)]}i(Mo,"ownerActions");function Io(e,{pulled:t=[]}={}){return!e.items.filter(r=>!ue(r)).some(r=>r.waitsOn==="checking"||r.grilling||r.prototyping||(r.agents||[]).some(a=>!a.done))&&!e.working&&!qs(e.items,{pulled:t}).waiting.length}i(Io,"strictlyIdle");const Xs="item stall it with the reason when it can't go on",Gt="restart it once from its worktree with a thread line; if the restarted run goes stale too, item stall it with the reason",Ys='Judge the staged card against the whole board: item set --phase ready --text "<what you checked>", or --phase grilling or prototyping for another round; where its notes answer a split question Split as proposed, file the split instead';function zs(e,t,{answeredAt:n,workingSince:s}){const r=i(g=>e.agents.some(m=>m.kind===g&&m.started&&!m.done),"running"),a=[e.progressSince,...e.agents.map(g=>g.done)].filter(Boolean).map(String).sort().pop()||null,d=t.ask?{kind:"ask",since:t.ask.at,next:`Answer the builder's ask: ${t.ask.text}`}:e.staging?{kind:"staging",since:e.stagedAt,next:Ys}:n?{kind:"answer",since:n,next:"Act on the owner's answer"}:rt.has(e.action?.kind)?null:e.builder?.stopped?{kind:"stale",since:e.builder.silentSince,next:`${e.builder.stopped}: ${Gt}, or drop the card`}:e.builder?.stale?{kind:"stale",since:e.builder.silentSince,next:`Find out why the builder went silent, then finish its run or ${Gt}`}:e.lane==="review"&&e.stale?{kind:"stale",since:new Date(Date.parse(e.inReviewSince)+yt).toISOString(),daily:!0,next:`Find out why the review went stale and fix what you can: rebase, CI, comments; if only a reviewer owes it, a thread line naming who; or ${Xs}`}:e.waitsOn==="checking"?t.unverified?{kind:"calls",since:t.handedBackAt,next:t.next||"Verify the builder's judgment calls"}:{kind:"checking",since:t.handedBackAt,next:t.next||"Land the checked build"}:e.grilling&&t.grillAt&&!r("grill")?{kind:"grill",since:t.grillAt,next:"Start the grill asked for"}:e.prototyping&&t.prototypeAt&&!r("prototype")?{kind:"prototype",since:t.prototypeAt,next:"Draw the prototype asked for"}:e.refusal&&t.refusedAt?{kind:"refused",since:t.refusedAt,next:e.refusal}:e.next===_t&&a?{kind:"unowned",since:a,next:"Start a builder on it, or set its Next"}:t.unblockedAt&&e.lane!=="todo"&&!e.builder&&!e.blocked&&!t.writes.some(g=>g>t.unblockedAt)?{kind:"unblocked",since:t.unblockedAt,next:"Its block ended: act on what it waited for, or set its Next"}:null;if(!d?.since)return null;const c=String(d.since),u=[t.writes.find(g=>g>c),s].filter(Boolean).sort()[0]||null;return{kind:d.kind,since:c,picked:u,next:d.next,...d.daily?{daily:!0}:{}}}i(zs,"orchestratorWaitOf");const qo=i((e,t=null)=>({key:e.id,ref:e.data.date,title:`Overnight triage ${e.data.date}`,kind:"triage",since:e.ts,picked:t,next:`Run the overnight triage and reply: board.mjs reply ${e.id}`}),"triageWaitOf");function Do(e){const t=e.items.filter(n=>n.orchestrator).map(n=>({key:n.key,ref:n.ref,title:n.title,...n.orchestrator,bumpedAt:n.bumpedAt||null}));return[...t.filter(n=>n.bumpedAt).sort((n,s)=>n.bumpedAt.localeCompare(s.bumpedAt)),...t.filter(n=>!n.bumpedAt)]}i(Do,"orchestratorWaits");const Co=30*6e4,Js=15*6e4,Zs=60*6e4,eo=1440*6e4;function No(e,t,n){const s=n.toISOString(),r=[],a=[];return{waits:t.map(c=>{const u=e.find(p=>p.key===c.key&&p.kind===c.kind&&p.since===c.since);if(!u)return c.picked||r.push(c.key),{...c,bumped:s};const g=c.picked?[c.picked,u.bumped].filter(Boolean).sort().pop():u.bumped||c.since,m=n-Date.parse(g)>=(c.picked?c.daily?eo:Zs:Js);return m&&a.push({key:c.key,ref:c.ref,kind:c.kind,since:c.since,next:c.next}),{...c,bumped:m?s:u.bumped||c.since,...u.pushed?{pushed:!0}:{}}}),added:r,again:a}}i(No,"waitsStep");function To(e,t){const n=i(s=>{const r=Math.floor((t-Date.parse(s))/6e4),a=Math.floor(r/60),d=r%60;return a?`${a}h${d?` ${d}m`:""}`:`${d}m`},"age");return`still waiting on you: ${e.map(s=>`${s.ref} ${s.kind} for ${n(s.since)}: ${s.next}`).join("; ")}`}i(To,"waitsAgainLine");const to=15;function Lo(e,{idle:t,now:n}){const s=n.toISOString(),r=e.last||s;return t?e.since?e.pushed||n-Date.parse(e.since)<to*6e4?{state:{...e,last:r},push:!1}:{state:{last:s,since:e.since,pushed:!0},push:!0}:{state:{last:r,since:s},push:!1}:{state:{last:r},push:!1}}i(Lo,"idleStep");function Po(e,t){const n=i(u=>String(u.closedAt||"")>t,"after"),s=e.filter(u=>u.kind==="work"&&u.lane==="done"&&!vt(u)&&n(u)),r=new Map;for(const u of e)u.wave&&r.set(u.wave.slug,[...r.get(u.wave.slug)||[],u]);const a=[...r.values()].filter(u=>u.every(g=>g.lane==="done")&&u.some(n)).map(u=>u[0].wave.title),d=s.slice(0,3).map(u=>u.ref).join(", ")+(s.length>3?` and ${s.length-3} more`:"");return[s.length&&`${s.length} ${s.length===1?"card":"cards"} landed: ${d}`,...a.map(u=>`${u} finished`)].filter(Boolean).join(" \xB7 ")||"Nothing closed since the last idle notification."}i(Po,"idleSummary");export{q as ACTION_KIND,Ct as ACTION_LABELS,ds as ANSWERS_SUBMITTED,qe as BUILDER_ASK,ce as BUILDER_KIND,kn as BUILDER_QUIET_AFTER,gn as BUILDER_STALE_AFTER,ht as BUILD_REFUSED,oe as BUTTON,Qe as COMMANDS,eo as DAILY_REWAKE_AFTER,Ks as DONE_FIELDS,Fs as DONE_KEPT,ve as HELPER,to as IDLE_MINUTES,Nt as INTENT_LABELS,fn as ITEM_EVENTS,Ke as LANDER,ko as LANES,Is as MAX_IMPLEMENTERS,Cs as MAX_PLANNERS,Se as MOVE,_t as NO_NEXT,Co as ORCHESTRATOR_PUSH_AFTER,yo as PAUSE_CLEARS,Zn as PAUSE_HARD_STOP,mt as PHASES,Zs as PICKED_REWAKE_AFTER,Vn as PLANNER_WORD,jn as PROTOTYPE_AGAIN,wo as PROTOTYPE_WAIT,H as PR_STATE,wt as QUESTION_REOPEN,Js as REWAKE_AFTER,pe as VISUAL,_o as archivedOf,Oo as arrivalsOf,So as buildBoard,V as builds,wn as fileFold,st as handedBackAt,Lo as idleStep,Po as idleSummary,qn as mergeBlockers,ns as modelName,Ro as movesOf,vo as needsAnswer,Pn as noTakePicked,Xn as orchestratorDid,Do as orchestratorWaits,mo as owesNext,Mo as ownerActions,ss as ownerQuestion,bo as pauseWork,Ln as picksNoTake,ee as plans,ho as prototypeWaits,It as providerOf,qs as pullOf,Vs as pushTag,xo as rebasesDue,nt as refOf,fe as roundOf,os as roundSettledBy,$o as roundsDue,Bo as slimDone,Ce as stateEntries,Rt as stoppedWords,Io as strictlyIdle,jt as threadIndex,Ut as threadOf,Ws as todoOf,Eo as triageOf,qo as triageWaitOf,vt as unlanded,Ns as visualAgent,Ao as visualsDue,To as waitsAgainLine,No as waitsStep,Ss as waveItems,vs as withHub};
10
+ ${Qs(e)}`}}d(Io,"triageOf");const Hs=100,Vs=["key","kind","id","ref","title","lane","phase","file","fileTitle","fold","internal","ticket","ticketSource","linear","pr","prs","review","reviewers","risky","sortAt","threadCount","othersAt","hasNotes","questions","prototypes","next","ask","action","actionButton","builder","working","waitsOn","reachedPhase","grilling","refusal","install","parent","children","related","waitsOnIt","blockedBy","ready","finishable","reopened","manualSteps","branch","worktree"],qo=d(e=>Object.fromEntries(Vs.filter(t=>t in e).map(t=>[t,e[t]])),"slimDone");function Co(e,t=Hs){const n=new Set((e.waveBands||[]).flatMap(r=>r.keys)),s=new Set(e.items.filter(r=>r.lane!=="done").flatMap(r=>[...(r.deps||[]).map(a=>String(a).includes("/")?String(a):`${r.file}/${a}`),r.parent?.key,...(r.children||[]).map(a=>a.key),...(r.related||[]).map(a=>a.key)]));return e.items.filter(r=>r.lane==="done"&&r.kind==="work").sort((r,a)=>String(a.closedAt||"").localeCompare(String(r.closedAt||""))).slice(t).filter(r=>r.storedLane==="done"&&!r.reopened&&!n.has(r.key)&&!s.has(r.key)&&(!r.questions||r.questions.state==="resolved"))}d(Co,"archivedOf");const Xs={ready:"draft",ci:"draft",reviewers:"reviewers",merge:"merge"};function Ys(e){return e.lane==="todo"?e.unpicked?"prototyping":kt.todo.includes(e.storedPhase)?e.storedPhase:e.storedLane&&e.storedLane!=="todo"?"ready":"new":e.lane==="progress"?e.waitsOn==="checking"?"checking":e.builder&&!e.builder.planner&&e.builder.kind!==ue.install&&e.builder.phase||e.reachedPhase||"reading":e.lane==="review"?e.stale?"stale":e.waitsOn==="owner"&&e.pushed?"merge":Xs[e.waitsOn]??null:e.storedLane==="done"&&kt.done.includes(e.storedPhase)?e.storedPhase:e.prs.some(t=>t.status===V.merged)?"merged":"closed"}d(Ys,"phaseOf");function No(e){return e.items.filter(t=>t.kind==="work").flatMap(t=>{const n=t.lane;return n==="done"||t.storedLane==="done"&&!t.reopened?[]:n===t.storedLane&&(n==="todo"||t.phase===t.storedPhase)?[]:[{key:t.key,to:n,phase:t.phase,why:zs(t,n),from:{lane:t.storedLane,phase:t.storedPhase}}]})}d(No,"movesOf");function zs(e,t){return t==="todo"?e.held?"it is held":e.blocked?"it is blocked":"nothing builds it":e.reopened&&e.storedLane==="done"?"a message or question reopened it":e.answered&&t==="review"?"answered, it waits for your Done":t==="progress"?e.waitsOn==="checking"?"its build handed back for a check":e.builder?"a build started":"Claude acts next":e.next||"it waits on review"}d(zs,"moveWhy");const Js=d(e=>e.startsWith("ask:")?`ask-${e.slice(4)}`:`item-${e}`,"pushTag");function Do(e){const t=d(a=>e.owner.filter(c=>c.topic===a&&c.state==="open").map(({id:c,question:l})=>({id:c,question:l})),"open"),n=[q.ready,q.merge],s=e.items.filter(a=>a.action).map(a=>{const c=rt.has(a.action.kind)?t(a.questions.topic):[];return{tag:Js(a.key),kind:a.action.kind,title:`${a.ref}: ${a.action.label}`,body:c[0]?.question||a.title,url:c.length?a.action.url:`#selectedCard=${encodeURIComponent(a.key)}`,now:!n.includes(a.action.kind),questions:c}}),r=e.needs.filter(a=>a.topic).map(a=>{const c=t(a.topic);return{tag:`topic-${a.topic}`,kind:q.questions,title:a.text,body:c[0]?.question||a.title,url:a.url,now:!0,questions:c}});return[...s,...r,...on(e.reviews)]}d(Do,"ownerActions");function Lo(e,{pulled:t=[]}={}){return!e.items.filter(r=>!pe(r)).some(r=>r.waitsOn==="checking"||r.grilling||r.prototyping||(r.agents||[]).some(a=>!a.done))&&!e.working&&!Ls(e.items,{pulled:t}).waiting.length}d(Lo,"strictlyIdle");const Zs="item stall it with the reason when it can't go on",Ft="restart it once from its worktree with a thread line; if the restarted run goes stale too, item stall it with the reason",eo='Judge the staged card against the whole board: item set --phase ready --text "<what you checked>", or --phase grilling or prototyping for another round; where its notes answer a split question Split as proposed, file the split instead',to='Its block ended: re-read its notes and answered questions against what landed; answer a stale one again with question settle <id> --note, or send the card back with item set --phase grilling when the change is too big; then item set --phase ready --text "<what you re-checked>"';function no(e,t,{answeredAt:n,workingSince:s}){const r=d(m=>e.agents.some(f=>f.kind===m&&f.started&&!f.done),"running"),a=[e.progressSince,...e.agents.map(m=>m.done)].filter(Boolean).map(String).sort().pop()||null,c=t.ask?{kind:"ask",since:t.ask.at,next:`Answer the builder's ask: ${t.ask.text}`}:e.staging?{kind:"staging",since:e.stagedAt,next:eo}:n?{kind:"answer",since:n,next:"Act on the owner's answer"}:t.replyAt?{kind:"reply",since:t.replyAt,next:"Act on the owner's reply"}:rt.has(e.action?.kind)?null:e.builder?.stopped?{kind:"stale",since:e.builder.silentSince,next:`${e.builder.stopped}: ${Ft}, or drop the card`}:e.builder?.stale?{kind:"stale",since:e.builder.silentSince,next:`Find out why the builder went silent, then finish its run or ${Ft}`}:e.lane==="review"&&e.stale?{kind:"stale",since:new Date(Date.parse(e.inReviewSince)+ht).toISOString(),daily:!0,next:`Find out why the review went stale and fix what you can: rebase, CI, comments; if only a reviewer owes it, a thread line naming who; or ${Zs}`}:e.waitsOn==="checking"?t.unverified?{kind:"calls",since:t.handedBackAt,next:t.next||"Verify the builder's judgment calls"}:{kind:"checking",since:t.handedBackAt,next:t.next||"Land the checked build"}:e.grilling&&t.grillAt&&!r("grill")?{kind:"grill",since:t.grillAt,next:"Start the grill asked for"}:e.prototyping&&t.prototypeAt&&!r("prototype")?{kind:"prototype",since:t.prototypeAt,next:"Draw the prototype asked for"}:e.recheckAt&&e.lane==="todo"&&e.phase==="ready"&&!e.blocked?{kind:"recheck",since:e.recheckAt,next:to}:e.refusal&&t.refusedAt?{kind:"refused",since:t.refusedAt,next:e.refusal}:e.next===$t&&a?{kind:"unowned",since:a,next:"Start a builder on it, or set its Next"}:t.unblockedAt&&e.lane!=="todo"&&!e.builder&&!e.blocked&&!t.writes.some(m=>m>t.unblockedAt)?{kind:"unblocked",since:t.unblockedAt,next:"Its block ended: act on what it waited for, or set its Next"}:null;if(!c?.since)return null;const l=String(c.since),u=[t.writes.find(m=>m>l),s].filter(Boolean).sort()[0]||null;return{kind:c.kind,since:l,picked:u,next:c.next,...c.daily?{daily:!0}:{}}}d(no,"orchestratorWaitOf");const To=d((e,t=null)=>({key:e.id,ref:e.data.date,title:`Overnight triage ${e.data.date}`,kind:"triage",since:e.ts,picked:t,next:`Run the overnight triage and reply: board.mjs reply ${e.id}`}),"triageWaitOf");function Po(e){const t=e.items.filter(n=>n.orchestrator).map(n=>({key:n.key,ref:n.ref,title:n.title,...n.orchestrator,bumpedAt:n.bumpedAt||null}));return[...t.filter(n=>n.bumpedAt).sort((n,s)=>n.bumpedAt.localeCompare(s.bumpedAt)),...t.filter(n=>!n.bumpedAt)]}d(Po,"orchestratorWaits");const Uo=30*6e4,so=15*6e4,oo=60*6e4,ro=1440*6e4;function jo(e,t,n){const s=n.toISOString(),r=[],a=[];return{waits:t.map(l=>{const u=e.find(p=>p.key===l.key&&p.kind===l.kind&&p.since===l.since);if(!u)return l.picked||r.push(l.key),{...l,bumped:s};const m=l.picked?[l.picked,u.bumped].filter(Boolean).sort().pop():u.bumped||l.since,f=n-Date.parse(m)>=(l.picked?l.daily?ro:oo:so);return f&&a.push({key:l.key,ref:l.ref,kind:l.kind,since:l.since,next:l.next}),{...l,bumped:f?s:u.bumped||l.since,...u.pushed?{pushed:!0}:{}}}),added:r,again:a}}d(jo,"waitsStep");function Wo(e,t){const n=d(s=>{const r=Math.floor((t-Date.parse(s))/6e4),a=Math.floor(r/60),c=r%60;return a?`${a}h${c?` ${c}m`:""}`:`${c}m`},"age");return`still waiting on you: ${e.map(s=>`${s.ref} ${s.kind} for ${n(s.since)}: ${s.next}`).join("; ")}`}d(Wo,"waitsAgainLine");const ao=15;function Fo(e,{idle:t,now:n}){const s=n.toISOString(),r=e.last||s;return t?e.since?e.pushed||n-Date.parse(e.since)<ao*6e4?{state:{...e,last:r},push:!1}:{state:{last:s,since:e.since,pushed:!0},push:!0}:{state:{last:r,since:s},push:!1}:{state:{last:r},push:!1}}d(Fo,"idleStep");function Ko(e,t){const n=d(u=>String(u.closedAt||"")>t,"after"),s=e.filter(u=>u.kind==="work"&&u.lane==="done"&&!St(u)&&n(u)),r=new Map;for(const u of e)u.wave&&r.set(u.wave.slug,[...r.get(u.wave.slug)||[],u]);const a=[...r.values()].filter(u=>u.every(m=>m.lane==="done")&&u.some(n)).map(u=>u[0].wave.title),c=s.slice(0,3).map(u=>u.ref).join(", ")+(s.length>3?` and ${s.length-3} more`:"");return[s.length&&`${s.length} ${s.length===1?"card":"cards"} landed: ${c}`,...a.map(u=>`${u} finished`)].filter(Boolean).join(" \xB7 ")||"Nothing closed since the last idle notification."}d(Ko,"idleSummary");export{q as ACTION_KIND,It as ACTION_LABELS,ps as ANSWERS_SUBMITTED,Ne as BUILDER_ASK,ue as BUILDER_KIND,wn as BUILDER_QUIET_AFTER,mn as BUILDER_STALE_AFTER,wt as BUILD_REFUSED,ce as BUTTON,Xe as COMMANDS,ro as DAILY_REWAKE_AFTER,Vs as DONE_FIELDS,Hs as DONE_KEPT,$e as HELPER,ao as IDLE_MINUTES,qt as INTENT_LABELS,kn as ITEM_EVENTS,He as LANDER,bo as LANES,Ds as MAX_IMPLEMENTERS,Ps as MAX_PLANNERS,Ee as MOVE,$t as NO_NEXT,Uo as ORCHESTRATOR_PUSH_AFTER,Oo as PAUSE_CLEARS,ns as PAUSE_HARD_STOP,kt as PHASES,oo as PICKED_REWAKE_AFTER,zn as PLANNER_WORD,Gn as PROTOTYPE_AGAIN,vo as PROTOTYPE_WAIT,V as PR_STATE,mt as QUESTION_REOPEN,so as REWAKE_AFTER,fe as VISUAL,Co as archivedOf,_o as arrivalsOf,$o as buildBoard,Q as builds,yn as fileFold,st as handedBackAt,Fo as idleStep,Ko as idleSummary,Tn as mergeBlockers,rs as modelName,No as movesOf,Ao as needsAnswer,Kn as noTakePicked,Jn as orchestratorDid,Po as orchestratorWaits,So as owesNext,Do as ownerActions,as as ownerQuestion,Eo as pauseWork,Fn as picksNoTake,Z as plans,xo as prototypeWaits,_t as providerOf,Ls as pullOf,Js as pushTag,Bo as rebasesDue,nt as refOf,ge as roundOf,ls as roundSettledBy,Ro as roundsDue,qo as slimDone,Ut as stateEntries,At as stoppedWords,Lo as strictlyIdle,Tt as threadIndex,Ss as threadOf,Qs as todoOf,Io as triageOf,To as triageWaitOf,St as unlanded,Us as visualAgent,Mo as visualsDue,Wo as waitsAgainLine,jo as waitsStep,Es as waveItems,$s as withHub};
package/board/finish.mjs CHANGED
@@ -1,4 +1,4 @@
1
- var I=Object.defineProperty;var c=(t,a)=>I(t,"name",{value:a,configurable:!0});import P from"node:fs";import v from"node:path";import{spawnSync as q}from"node:child_process";import*as r from"./store.mjs";import{builderRepo as E,git as w,joiningLand as F,landInTurn as N,passTests as j,syncBase as A,worktreesDir as L}from"./worktree.mjs";import{readPermissions as S}from"./permissions.mjs";import{landByBuilder as T}from"./headless.mjs";import{boardAt as C}from"../hub/boards.mjs";import{boardRepo as D}from"../hub/board-repo.mjs";const b=c(t=>{throw new r.BoardError(t)},"fail"),W=c(t=>typeof t=="string"&&t.trim()?t.trim():b("--text must be non-empty text"),"said");async function H(t,a,{text:o,closing:n,ownerStep:e,check:s,repo:i,onWait:g,merge:d=!1}){r.messageText(o);const f=a.split("/")[1],u=await r.recordedWorktree(t,a),{repo:$,base:p,remote:l}=E(t,{repo:i??u?.repo}),m={path:u?.path??v.join(L(),f),branch:u?.branch??f,repo:v.resolve($),base:p},M=await r.cardName(t,a),h=await r.readBoardSettings(t),R=l?D(C(t).route).github:null;if(l&&(!R||h.prs))return _(t,a,{...m,ref:M,text:W(o)});const B=await F(M,()=>V(t,a,{tree:m,ref:M,base:p,remote:l,repo:$,settings:h,text:o,closing:n,ownerStep:e,check:s,onWait:g}),{merge:d});return B.joined?await r.isFinished(t,a)?{said:"landed and done",lines:[]}:H(t,a,{text:o,closing:n,ownerStep:e,check:s,repo:i,onWait:g,merge:d}):B}c(H,"finishCard");async function V(t,a,{tree:o,ref:n,base:e,remote:s,repo:i,settings:g,text:d,closing:f,ownerStep:u,check:$,onWait:p}){const l=g.auto_merge||!!(await r.heldMerge(t,a))?.tapped,m=l&&await r.blockWords(t,a);m&&b(`${n} is blocked ${m}, so it can't land; it lands once nothing blocks it`),await r.checkClose(t,a,d,u,$),await r.checkCallsVerified(t,a),s&&(A(o.repo,e),w(o.repo,"branch","--unset-upstream",o.branch)),l&&await r.landingLine(t,a);const M={site:t,key:a,first:await r.landFirst(t,a)};if(await N({...o,ref:n},{hold:!l,onWait:p,queue:M,sync:c(()=>s&&A(o.repo,e),"sync")}),!l){const R=await r.setItem(t,a,{mergeReady:o.branch,ownerStep:u,check:$});return await r.appendMessage(t,a,{text:d}),{said:"verified; waits for your Merge",lines:R.lines}}const h=await r.setItem(t,a,{done:!0,landed:!0,...f?{closing:f}:{text:d},ownerStep:u,check:$});return{said:u===void 0?"landed and done":`landed; waits on the owner step "${u.trim()}"`,lines:[...h.lines,...await x(t,a,i,e)]}}c(V,"landCard");async function Z(t,a,{launch:o=T}={}){(await r.heldMerge(t,a))?.tapped||b(`${a} isn't waiting on a Merge tap`);try{const{base:e}=E(t,{repo:(await r.recordedWorktree(t,a))?.repo}),s=c(i=>r.appendStatus(t,a,"land-waiting",{card:i}),"onWait");return await H(t,a,{text:`Landed on ${e} on your Merge.`,closing:["landed-on-merge",{base:e}],onWait:s,merge:!0})}catch(e){if(!(e instanceof r.BoardError))throw e;const s=e.message.split(`
2
- `)[0];if(/^rebasing .+ conflicts in |^tests failed in /.test(s)){await r.appendStatus(t,a,"lander-handoff",{why:s});try{await o(t,{key:a,model:await r.builderModel(t,a)},{why:s})}catch(i){await r.appendStatus(t,a,"lander-failed",{error:i.message})}}else await r.appendStatus(t,a,"land-failed",{why:s});throw e}}c(Z,"landHeld");async function x(t,a,o,n){if(!(await S(o)).pushMain)return[];const e=w(o,"push","--quiet","origin",n);if(e.ok)return[`pushed ${n} to origin`];const s=`${n} didn't reach origin: ${e.err}; push it with git -C ${o} push origin ${n}`;return await r.appendStatus(t,a,"push-failed",{base:n,error:e.err}),[`landed, but ${s}`]}c(x,"pushBase");async function _(t,a,{path:o,branch:n,base:e,ref:s,text:i}){await r.checkCallsVerified(t,a),P.existsSync(o)||b(`no worktree at ${o}`),w(o,"status","--porcelain").out&&b(`${o} has uncommitted changes; commit them there first`),Number(w(o,"rev-list","--count",`refs/remotes/origin/${e}..HEAD`).out)===0&&b(`${n} has no commits beyond ${e}; nothing to send for review`),await j(o,{outcome:"nothing was pushed"});const{github:g}=D(C(t).route),d=g?await r.cardPR(t,a):null,f=d?w(o,"rev-parse",`refs/remotes/origin/${n}`).out:null,u=d?w(o,"push","--quiet",`--force-with-lease=refs/heads/${n}:${f}`,"origin",`HEAD:refs/heads/${n}`):w(o,"push","--quiet","origin",`HEAD:refs/heads/${n}`);if(u.ok||b(`could not push ${n} to origin: ${u.err}`),d){const h=c(B=>Number(w(o,"rev-list","--count",`refs/remotes/origin/${e}..${B}`).out),"ahead");await r.recordRebase(t,a,{from:f,to:w(o,"rev-parse","HEAD").out,replayed:h("HEAD")<=h(f)});const R=await r.setItem(t,a,{pr:d});return await r.appendMessage(t,a,{text:i}),{said:`pushed ${n} to PR #${d}`,lines:R.lines}}if(!g){const h=await r.setItem(t,a,{pushed:n});return await r.appendMessage(t,a,{text:i}),{said:`pushed ${n} for its merge`,lines:h.lines}}const{title:$}=await r.briefFacts(t,a),p=q("gh",["pr","create","--draft","--repo",g,"--base",e,"--head",n,"--title",`${s}: ${$}`,"--body",i],{cwd:o,encoding:"utf8"}),l=`${p.stdout||""}
3
- ${p.stderr||""}`,m=(p.status===0||/already exists/.test(l))&&/\/pull\/(\d+)/.exec(l)?.[1];m||b(`could not open a draft PR for ${n}: ${(p.stderr||p.error?.message||`gh exited ${p.status}`).trim().split(`
4
- `)[0]}`);const M=await r.setItem(t,a,{pr:Number(m)});return await r.appendMessage(t,a,{text:i}),{said:`draft PR #${m} opened`,lines:M.lines}}c(_,"sendForReview");export{H as finishCard,Z as landHeld};
1
+ var I=Object.defineProperty;var c=(t,a)=>I(t,"name",{value:a,configurable:!0});import P from"node:fs";import v from"node:path";import{ghSync as N,limitWords as q}from"../shared/gh.mjs";import*as o from"./store.mjs";import{builderRepo as E,git as w,joiningLand as F,landInTurn as S,passTests as W,syncBase as A,worktreesDir as j}from"./worktree.mjs";import{readPermissions as L}from"./permissions.mjs";import{landByBuilder as T}from"./headless.mjs";import{boardAt as C}from"../hub/boards.mjs";import{boardRepo as D}from"../hub/board-repo.mjs";const b=c(t=>{throw new o.BoardError(t)},"fail"),V=c(t=>typeof t=="string"&&t.trim()?t.trim():b("--text must be non-empty text"),"said");async function H(t,a,{text:r,closing:n,ownerStep:e,check:i,repo:s,onWait:g,merge:d=!1}){o.messageText(r);const f=a.split("/")[1],u=await o.recordedWorktree(t,a),{repo:$,base:l,remote:p}=E(t,{repo:s??u?.repo}),m={path:u?.path??v.join(j(),f),branch:u?.branch??f,repo:v.resolve($),base:l},M=await o.cardName(t,a),h=await o.readBoardSettings(t),R=p?D(C(t).route).github:null;if(p&&(!R||h.prs))return z(t,a,{...m,ref:M,text:V(r)});const B=await F(M,()=>x(t,a,{tree:m,ref:M,base:l,remote:p,repo:$,settings:h,text:r,closing:n,ownerStep:e,check:i,onWait:g}),{merge:d});return B.joined?await o.isFinished(t,a)?{said:"landed and done",lines:[]}:H(t,a,{text:r,closing:n,ownerStep:e,check:i,repo:s,onWait:g,merge:d}):B}c(H,"finishCard");async function x(t,a,{tree:r,ref:n,base:e,remote:i,repo:s,settings:g,text:d,closing:f,ownerStep:u,check:$,onWait:l}){const p=g.auto_merge||!!(await o.heldMerge(t,a))?.tapped,m=p&&await o.blockWords(t,a);m&&b(`${n} is blocked ${m}, so it can't land; it lands once nothing blocks it`),await o.checkClose(t,a,d,u,$),await o.checkCallsVerified(t,a),i&&(A(r.repo,e),w(r.repo,"branch","--unset-upstream",r.branch)),p&&await o.landingLine(t,a);const M={site:t,key:a,first:await o.landFirst(t,a)};if(await S({...r,ref:n},{hold:!p,onWait:l,queue:M,sync:c(()=>i&&A(r.repo,e),"sync")}),!p){const R=await o.setItem(t,a,{mergeReady:r.branch,ownerStep:u,check:$});return await o.appendMessage(t,a,{text:d}),{said:"verified; waits for your Merge",lines:R.lines}}const h=await o.setItem(t,a,{done:!0,landed:!0,...f?{closing:f}:{text:d},ownerStep:u,check:$});return{said:u===void 0?"landed and done":`landed; waits on the owner step "${u.trim()}"`,lines:[...h.lines,...await _(t,a,s,e)]}}c(x,"landCard");async function y(t,a,{launch:r=T}={}){(await o.heldMerge(t,a))?.tapped||b(`${a} isn't waiting on a Merge tap`);try{const{base:e}=E(t,{repo:(await o.recordedWorktree(t,a))?.repo}),i=c(s=>o.appendStatus(t,a,"land-waiting",{card:s}),"onWait");return await H(t,a,{text:`Landed on ${e} on your Merge.`,closing:["landed-on-merge",{base:e}],onWait:i,merge:!0})}catch(e){if(!(e instanceof o.BoardError))throw e;const i=e.message.split(`
2
+ `)[0];if(/^rebasing .+ conflicts in |^tests failed in /.test(i)){await o.appendStatus(t,a,"lander-handoff",{why:i});try{await r(t,{key:a,model:await o.builderModel(t,a)},{why:i})}catch(s){await o.appendStatus(t,a,"lander-failed",{error:s.message})}}else await o.appendStatus(t,a,"land-failed",{why:i});throw e}}c(y,"landHeld");async function _(t,a,r,n){if(!(await L(r)).pushMain)return[];const e=w(r,"push","--quiet","origin",n);if(e.ok)return[`pushed ${n} to origin`];const i=`${n} didn't reach origin: ${e.err}; push it with git -C ${r} push origin ${n}`;return await o.appendStatus(t,a,"push-failed",{base:n,error:e.err}),[`landed, but ${i}`]}c(_,"pushBase");async function z(t,a,{path:r,branch:n,base:e,ref:i,text:s}){await o.checkCallsVerified(t,a),P.existsSync(r)||b(`no worktree at ${r}`),w(r,"status","--porcelain").out&&b(`${r} has uncommitted changes; commit them there first`),Number(w(r,"rev-list","--count",`refs/remotes/origin/${e}..HEAD`).out)===0&&b(`${n} has no commits beyond ${e}; nothing to send for review`),await W(r,{outcome:"nothing was pushed"});const{github:g}=D(C(t).route),d=g?await o.cardPR(t,a):null,f=d?w(r,"rev-parse",`refs/remotes/origin/${n}`).out:null,u=d?w(r,"push","--quiet",`--force-with-lease=refs/heads/${n}:${f}`,"origin",`HEAD:refs/heads/${n}`):w(r,"push","--quiet","origin",`HEAD:refs/heads/${n}`);if(u.ok||b(`could not push ${n} to origin: ${u.err}`),d){const h=c(B=>Number(w(r,"rev-list","--count",`refs/remotes/origin/${e}..${B}`).out),"ahead");await o.recordRebase(t,a,{from:f,to:w(r,"rev-parse","HEAD").out,replayed:h("HEAD")<=h(f)});const R=await o.setItem(t,a,{pr:d});return await o.appendMessage(t,a,{text:s}),{said:`pushed ${n} to PR #${d}`,lines:R.lines}}if(!g){const h=await o.setItem(t,a,{pushed:n});return await o.appendMessage(t,a,{text:s}),{said:`pushed ${n} for its merge`,lines:h.lines}}const{title:$}=await o.briefFacts(t,a),l=N(["pr","create","--draft","--repo",g,"--base",e,"--head",n,"--title",`${i}: ${$}`,"--body",s],{cwd:r}),p=`${l.stdout||""}
3
+ ${l.stderr||""}`,m=(l.status===0||/already exists/.test(p))&&/\/pull\/(\d+)/.exec(p)?.[1];l.limited&&await o.appendStatus(t,a,"gh-limited",{what:"No PR opened",words:q(l.limited)}),m||b(`could not open a draft PR for ${n}: ${(l.stderr||`gh exited ${l.status}`).trim().split(`
4
+ `)[0]}`);const M=await o.setItem(t,a,{pr:Number(m)});return await o.appendMessage(t,a,{text:s}),{said:`draft PR #${m} opened`,lines:M.lines}}c(z,"sendForReview");export{H as finishCard,y as landHeld};
@@ -1,8 +1,8 @@
1
- var z=Object.defineProperty;var l=(t,e)=>z(t,"name",{value:e,configurable:!0});import Q from"node:os";import{BRIEF_LINE_LIMIT as X}from"./hub-lines.mjs";import T from"node:path";import{fileURLToPath as J}from"node:url";import{launchAgent as P}from"./agents.mjs";import{processStarts as W,ps as Z}from"../hub/live.mjs";import{boardAt as C}from"../hub/boards.mjs";import{readHubRules as ee}from"../hub/rules.mjs";import{builderRepo as M}from"./worktree.mjs";import{appendStatus as te,BoardError as oe,briefFacts as L,readBoardRules as re,recordedSession as ne,recordSession as O}from"./store.mjs";import{HELPER as se,plans as ae,VISUAL as ie}from"./domain.mjs";const he=J(new URL("./bin/board.mjs",import.meta.url)),$=l(t=>`node ${he} --site ${t}`,"cli"),E=l(t=>`git -c core.commentChar=';' rebase ${t}`,"rebase"),de="a draft .md of your own outside the board's data folder (such as in /tmp)",le=l((t,e,r)=>`\`${t} item notes ${e} --heading "${r}" --file <draft.md>\``,"notesCommand"),I=l((t,e="Rules from the hub and this board:")=>t.length?[e,...t.map(r=>`- ${r}`),""]:[],"ruleLines"),q=l(t=>t.length?["Related cards: read their notes for context; don't build their scope.",...t.map(e=>`- ${e.ref} "${e.title}" (${e.relation}, ${e.lane}): ${e.notes?`notes ${e.notes}`:"no notes"}, thread ${e.thread}`),""]:[],"linkLines"),S=l(t=>`The card's thread, which holds the owner's messages (who: love): ${t}. Read it live.`,"threadLine"),F=l(({route:t,key:e,worktree:r,base:h})=>[`- With no unverified calls, done finishes the card itself: it rebases onto ${h}, reruns the full test suite, fast-forwards ${h} and closes the card (on a board with pull requests it pushes the branch and opens a draft PR instead; with Auto-merge off it stops before the fast-forward, and the card waits for the owner's Merge).`,`- On a conflict it undoes its rebase and names the files: run \`${E(h)}\` in ${r}, resolve each conflict keeping both sides' intent, continue with \`git -c core.commentChar=';' rebase --continue\`, rerun the tests, and run done again.`,"- On failing tests, fix them, commit, and run done again.","- While another card is landing, done waits its turn and then lands; leave it running.","- Once a reviewer has the card's PR link, done refuses without `--updated \"<what changed>\"`, which goes in that link's Slack thread, or `--updated none` for a change only to comments, names, docs or tests.",`- Finishing never goes to the orchestrator. If done refuses for a reason you can't fix, record it with \`${$(t)} builder call ${e} "<why>"\` and run done again: with the call it hands the card back.`],"finishSteps"),j=l(({route:t,key:e})=>`\`${$(t)} builder done ${e} --text "<what you checked>"\``,"finishCommand"),ce=l((t,e)=>[`5. To run a separate part of the work in parallel, ask the board for a helper: \`${t} agent launch ${e} --name <short-name> --task "<the whole task, with the files and what to hand back>"\`, adding --writes when it changes code or runs tests (it gets its own worktree off your branch, and waits for a free implementer slot). Then \`${t} agent wait ${e} --name <short-name>\`, which prints its hand-back; it returns within 10 minutes, so run it again while it says still running, and do the task yourself if it says the helper exited. Merge a writing helper's work with \`${t} agent merge ${e} --name <short-name>\` before you test and finish.`],"helperSteps");function ue({route:t,key:e,ref:r,title:h,notes:n,thread:d,worktree:a,base:u,rules:p=[],links:c=[]}){const o=$(t);return[`You are the headless builder for card ${r} (${e}, "${h}") on the kickflip board /${t}. The board launched you; nobody watches this session, so never wait on a person. Work only in ${a}. Never restart the hub or a board server, and never push.`,"",`1. Run \`${o} builder start ${e} --kind build\`. If it refuses, stop.`,`2. Report progress with \`${o} builder progress ${e} --phase reading|building|testing|checking|landing "<plain, 80 characters or fewer>"\`, at least every 10 minutes. The board adds the phase word; don't start the text with it. The phases:`," - reading: the notes and the code, until the first test is written."," - building: the tests and the code, the ADR and docs, and the commit."," - testing: the full suite run before you finish."," - checking: your own review of the diff before you hand back; the Orchestrator's review of your calls continues on the same 4th bar."," - landing: set by the finish step itself; you never report it.",`3. Decide, record the call (step 4) and carry on: the orchestrator reviews your calls after the build. Ask it only when you are blocked, meaning every way forward would likely be thrown away: the spec contradicts itself or the code, a rule needs the owner's go before code is written, or the answer is a fact only the orchestrator has. Then run \`${o} builder ask ${e} "<question>"\`, do any work the answer can't change, and wait with \`${o} builder asks ${e} --wait\`, which returns within 10 minutes, so run it again while it says still waiting. If no answer comes in 30 minutes, record the question as a call, "Question: <question>", and hand back.`,`4. Record each judgment call the notes leave open with \`${o} builder call ${e} "<call>"\` instead of guessing silently or asking. A problem you find outside your card is a call "Found: <what>" for the orchestrator to file at review; ask about it at once only when it stalls other cards' builds or lands.`,...ce(o,e),"",n?`Spec: the card's notes, ${n}, all sections, where later sections override earlier ones.`:"The card has no notes file: build from its title, and record what you had to decide as calls.",`${S(d)} Read it with the notes: a message of the owner's is an instruction, but where it contradicts the notes, the notes win; that contradiction counts as blocked (step 3), so ask with \`${o} builder ask ${e} "<question>"\` and wait as step 3 says. Read the thread again in checking, before you finish, for anything the owner wrote during the build.`,"",...q(c),...I(p),"Write tests first, and run the full suite once, in the foreground, at the end. Where the repo keeps ADRs, write one named with a timestamp: docs/adr/YYYY-MM-DD-HHMM-<slug>.md.",`Commit with \`git -c core.commentChar=';' commit -F <file>\`, the subject "${r}: <what the card now does>".`,"",`To finish, run ${j({route:t,key:e})}.`,"- If you recorded a judgment call, it hands the card back for the orchestrator to verify your calls; stop there: the orchestrator's verification finishes the card.",...F({route:t,key:e,worktree:a,base:u}),"","Never push."].join(`
2
- `)}l(ue,"builderBrief");function Y({route:t,key:e,ref:r,title:h,thread:n,worktree:d,base:a,answers:u=[],resumed:p=!1,why:c}){const o=$(t);return[p?`The board resumes you, the builder of card ${r} (${e}, "${h}") on the kickflip board /${t}, to land your build: ${c??"the orchestrator verified your judgment calls"}, and it waits only to land. Nobody watches this session, so never wait on a person. Work only in ${d}, never restart the hub or a board server, and never push.`:`You are the headless lander for card ${r} (${e}, "${h}") on the kickflip board /${t}. Its build is done and the orchestrator verified its judgment calls; it waits only to land. Nobody watches this session, so never wait on a person. Work only in ${d}, never restart the hub or a board server, and never push.`,"",`1. Run \`${o} builder start ${e} --kind build --label Lander --phase landing --progress "taking up the verified build"\`. If it refuses, stop.`,`2. Report progress with \`${o} builder progress ${e} --phase landing "<plain, 80 characters or fewer>"\`. The board adds the phase word; don't start the text with it.`,...u.length?["3. The owner answered the builder's deferred asks. Where an answer changes the build, change it, with its tests, and commit:",...u.map(i=>`- ${i.ask} "${i.question}": ${i.answer}`)]:[],"",S(n),"",`To finish, run ${j({route:t,key:e})}.`,...F({route:t,key:e,worktree:d,base:a}),"","Never push."].join(`
3
- `)}l(Y,"landerBrief");function _({route:t,key:e,ref:r,title:h,thread:n,worktree:d,base:a,pr:u,resumed:p=!1}){const c=$(t);return[...p?[`The board resumes you, the builder of card ${r}, for this run: the lines below are your brief for it.`]:[],`You are the headless rebase run for card ${r} (${e}, "${h}") on the kickflip board /${t}. Its PR #${u} conflicts with ${a}; rebase its branch onto ${a} so the PR is clean again. Nobody watches this session, so never wait on a person. Work only in ${d}, never restart the hub or a board server, and never push.`,"",`1. Run \`${c} builder start ${e} --kind build --label Rebase --phase building --progress "Rebase run started \xB7 fetching ${a}"\`. If it refuses, stop.`,`2. Run \`git fetch origin\`, then \`${E(`origin/${a}`)}\`. Resolve each conflict keeping both sides' intent, \`git add\` it and continue with \`git -c core.commentChar=';' rebase --continue\`. Report each step with \`${c} builder progress ${e} --phase building "<line>"\`, the board adds the phase word, in these words: "Rebasing onto ${a} \xB7 resolving <n> files", then "Rebasing onto ${a} \xB7 suite running".`,"3. Run the full test suite once, in the foreground. Fix what the rebase broke, and commit each fix with `git -c core.commentChar=';' commit -F <file>`. A push with commits of its own is no longer rebase-only, so a merge after an approval won't count it as trivial.",`4. If a conflict or a failing test is past what you can resolve with confidence, run \`git -c core.commentChar=';' rebase --abort\` if a rebase is under way, record it with \`${c} builder call ${e} "Rebase failed in <the files or tests> \xB7 <why>"\`, and run the finish step: with the call it hands the card to the orchestrator.`,"",S(n),"",`To finish, run ${j({route:t,key:e})}, the text saying what you resolved. It reruns the suite and pushes the branch to PR #${u} with --force-with-lease; the PR stays as it is.`,"- If it refuses because someone pushed to the branch since your fetch, fetch and rebase again (step 2), then run done again.","- On failing tests, fix them, commit, and run done again.","","Never push."].join(`
4
- `)}l(_,"rebaseBrief");function pe({route:t,key:e,ref:r,title:h,notes:n,thread:d,kind:a,repo:u,rules:p=[],links:c=[],text:o,files:i=[],question:s,note:f,choice:w,ticketQuestion:b=!1}){const m=$(t),g=a==="grill";return[`You are the headless ${g?"grill":"prototype"} planner for card ${r} (${e}, "${h}") on the kickflip board /${t}. The board launched you; nobody watches this session, so never wait on a person: the owner answers on the board, and a later planner or builder picks up from their answers. Read and run in ${u}; write no output code ${g?"there":`in ${u} itself`}, never restart the hub or a board server, and never push.`,"",`1. Run \`${m} builder start ${e} --kind ${a}\`. If it refuses, stop.`,`2. Gather the context yourself: ${n?`the card's notes, ${n}, all sections, where later sections override earlier ones; `:"the card has no notes file yet; "}its thread, ${d}, the repo's docs/adr and CONTEXT.md, and the cards its notes and thread link.`,`3. Report progress with \`${m} builder progress ${e} "<plain, 80 characters or fewer>"\` at least every 10 minutes.`,g?`4. Find every decision the card leaves open: read the code it touches and answer what the code answers yourself. Post each decision left as a question for the owner with \`${m} question add --file <json>\` on topic item:${e}, one per decision, your recommendation first. Once you finish, the orchestrator screens them before the owner sees them, settling the ones the board already answers and passing the owner the rest. Invoke the grilling skill with the Skill tool and run it as one round: its questions go to the board with question add, never into the chat, and you never wait for an answer; after the round, finish. (With no Skill tool, follow this step as written.) When the code shows the card has separable parts, each a build of its own, propose splitting it into a wave of smaller cards as one question ahead of the prototype question: its options "Split as proposed" and "Keep as one card", your recommendation first. Slice it as ~/.claude/skills/to-tickets/SKILL.md step 3, "Draft vertical slices", says (read that step; you can't invoke the skill, and skip its quiz and publish steps). The first card is the thinnest end-to-end path. The question's context lists each proposed card with its title, its scope, the files it touches and the earlier proposed cards it waits on, or none: a card waits on an earlier one only when it builds on that card or shares its seam (the same function, component, CLI command or doc paragraph); sharing only a file waits on nothing. Write the same breakdown in your notes section. On Split the orchestrator files the cards when the card stages; never file them yourself.`+(b?' This card is a task, with no ticket yet: just before the prototype question, ask "Create a ticket for this task?", whose options are yes and no, with "ticket_gate": true in its `question add --file` entry; the board makes the ticket from the card\'s title and notes the moment the owner answers yes. Recommend yes when the task lands code, no when it lands nothing (a publish, a setting, a one-off run).':"")+' End every grill with one more question, "Prototype round first?", whose options are yes and no, with "prototype_gate": true in its `question add --file` entry: once the owner has answered every question the board moves the card to prototyping on yes, or to staging on no, so nothing else moves it. Recommend yes when the card changes the hub\'s or a board\'s UI, no otherwise. Ask it even when it is your only question.':`4. Invoke the prototype skill with the Skill tool and build the round as take files, one per option: record them with \`${m} prototype add <topic> --card ${e} --take <K>:<label> \u2026\`, write the take files it prints, built from the repo's existing components, helpers and scripts fed mock data (the prototype template loads the board's scripts, so a take reuses the board's components; a hub-page take reuses them by path or copy), each take showing the states it needs inside itself, then post one question whose url opens prototype-<topic>.html with \`${m} question add --file <json>\` on topic item:${e}, never asking in the chat. Build in situ only where one of the board's rules asks for it: then, run \`${m} prototype base\`, which makes it or moves it to main and prints its path, whether its build script is there and the listener a page loads. When the build script is missing, write one there that serves the app and prints "port <n>" (board/build-script.mjs). Add one page to the worktree, at a path no other round uses, built from the repo's real components, helpers and scripts fed mock data, fully interactive; it reads the take from ?variant= and the state from ?state=, and loads the listener so \`KickflipPrototype.on((take, state) => \u2026)\` re-renders it in place when the viewer switches. Record it with \`${m} prototype add <topic> --card ${e} --served <path> --take <K>:<label> \u2026 --state <key>:<label> \u2026\`, one take per option and one state for each state that matters to the question, then post one question whose url opens prototype-<topic>.html with \`${m} question add --file <json>\` on topic item:${e}, never asking in the chat. (With no Skill tool, follow this step as written.)`,...o?[` The owner asked for this ${g?"grill":"prototype"}, writing: "${o}"`]:[],...i.length?[` The owner attached: ${i.map(v=>v.path).join(", ")}.`]:[],...s?[` The owner asked for prototypes of question ${s}${f?`, noting "${f}"`:""}${w?`, leaning to option ${w}`:""}: draw that question's next round, its question naming round_of ${s}.`]:[],`5. Record what you learned in the card's notes, naming each answered question's id: write it in ${de}, then run ${le(m,e,`${g?"Grill":"Prototype"} (<MM/DD>)`)}, which appends it as a section. The notes file is read-only; never write into it. Anything you leave open becomes a question with question add, never only a line in the notes, so the card can't go ready past it. A question's text is one line of at most ${X} characters (question add refuses a longer one); put the detail in its context and the options' details, which take markdown.`,...g?[`6. Where the owner asked for prototypes, or a rule here wants a UI prototype first, end the card \`${m} item set ${e} --phase prototyping\` instead and skip the prototype question. Otherwise the prototype question is what moves the card on: never set it ready yourself, and never leave a Next that asks the owner to mark it ready.`]:[],"",...q(c),...I(p,"Rules from the hub and this board, written for the orchestrator: where one tells you to push, land, restart the hub or a board server, or do the orchestrator's work, it isn't yours; you never push or restart:"),`To finish, run \`${m} builder done ${e} --text "<what you asked>"\`.`,"","Never push."].join(`
5
- `)}l(pe,"plannerBrief");function fe({route:t,key:e,ref:r,title:h,notes:n,thread:d,repo:a,agent:u,rules:p=[],links:c=[],text:o,files:i=[]}){const s=$(t);return[`You are the headless visual agent for card ${r} (${e}, "${h}") on the kickflip board /${t}. The owner asked, in the card's thread, for a visual: one HTML page that answers in a visual way, such as a diagram, a flow or a comparison. It is not a prototype or a grill: no question, no takes. The board launched you; nobody watches this session, so never wait on a person. Read and run in ${a}; write no code there, never restart the hub or a board server, and never push.`,"",`1. Run \`${s} builder start ${e} --kind visual --agent ${u}\`. If it refuses, stop.`,`2. Gather what the page needs: ${n?`the card's notes, ${n}; `:""}its thread, ${d}, and the code or docs it is about.`,o?` The owner wrote: "${o}"`:" The owner typed nothing: draw what the thread was last discussing.",...i.length?[` The owner attached: ${i.map(f=>f.path).join(", ")}.`]:[],"3. Write one HTML file outside the repo, such as in a temporary folder. Keep its own CSS and script inline; it may load a library such as Mermaid or D3 from a CDN. Make it read well at phone width (390px) and on a desktop, in the dark and the light theme (prefers-color-scheme).",`4. Place it with \`${s} visual add ${e} --file <page.html> --title "<what it shows>"\`, which copies it to the board and links it in the thread.`,"","Write nothing else: no notes, questions, card state, Next or progress lines.","",...q(c),...I(p,"Rules from the hub and this board, written for the orchestrator: where one tells you to push, land, restart the hub or a board server, or do the orchestrator's work, it isn't yours; you never push or restart:"),`To finish, run \`${s} builder done ${e} --agent ${u}\`.`,"","Never push."].join(`
6
- `)}l(fe,"visualBrief");function we({route:t,key:e,ref:r,title:h,notes:n,thread:d,agent:a,task:u,writes:p,cwd:c,rules:o=[],links:i=[]}){const s=$(t);return[`You are the headless helper ${a} on card ${r} (${e}, "${h}") on the kickflip board /${t}. Another agent working on the card asked the board for you to do one task beside it, and waits on your hand-back. Nobody watches this session, so never wait on a person. Never restart the hub or a board server, and never push.`,p?`Work only in ${c}, your own worktree, branched from the card's: commit your work there with \`git -c core.commentChar=';' commit -F <file>\`, the subject "${r}: <what it does>". The card's builder merges your branch, so leave nothing uncommitted.`:`Read and run in ${c}; change no files there and commit nothing.`,"","The task:",u,"",`1. Run \`${s} builder start ${e} --kind helper --agent ${a}\`. If it refuses, stop.`,`2. Report progress with \`${s} builder progress ${e} --agent ${a} --phase reading|building|testing|checking "<plain, 80 characters or fewer>"\` at least every 10 minutes; the board adds your name and the phase word.`,"3. Do only the task. Don't ask the orchestrator or the owner, record no judgment calls and post no questions: put any decision you had to make in your hand-back.",n?` The card's notes, for context: ${n}.`:"",` ${S(d)}`,"",...q(i),...I(o,"Rules from the hub and this board, written for the orchestrator: where one tells you to push, land, restart the hub or a board server, or do the orchestrator's work, it isn't yours; you never push or restart:"),`To finish, run \`${s} builder done ${e} --agent ${a} --text "<your hand-back: what you did or found, ${p?"what you committed, ":""}and any decision you made>"\`.`,"","Never push."].filter((f,w,b)=>f!==""||b[w-1]!=="").join(`
7
- `)}l(we,"helperBrief");const me=6e4;function be(t,{starts:e=W}={}){const r=t.filter(n=>n.pid&&n.at).map(n=>n.pid),h=r.length&&e(r)||new Map;return t.filter(n=>{const d=h.get(n.pid);return d!==void 0&&d<=Date.parse(n.at)&&d>=Date.parse(n.at)-me})}l(be,"launchedAgents");function Be(t,{starts:e=W,kill:r=l((n,d)=>process.kill(-n,d),"kill"),killAfter:h=5e3}={}){const n=be(t,{starts:e});for(const{pid:d}of n){try{r(d,"SIGTERM")}catch{continue}setTimeout(()=>{try{r(d,"SIGKILL")}catch{}},h).unref()}return n.length}l(Be,"stopAgents");async function Ne(t,{grace:e=5e3}={}){const r=Z(["-A","-o","pid=,ppid=,pgid=,args="]);if(r===null)throw new oe("ps gave no answer, so nothing was stopped; run it again");const h=r.split(`
8
- `).map(o=>/^\s*(\d+)\s+(\d+)\s+(\d+)\s+(.*)$/.exec(o)).filter(Boolean).map(([,o,i,s,f])=>({pid:Number(o),ppid:Number(i),pgid:Number(s),args:f})),n=new Set(h.filter(o=>t.includes(o.pgid)).map(o=>o.pid));for(let o=!0;o;){o=!1;for(const i of h)!n.has(i.pid)&&n.has(i.ppid)&&(n.add(i.pid),o=!0)}const d=h.find(o=>o.pid===process.pid)?.pgid,a=h.filter(o=>n.has(o.pid)&&o.pgid>1&&o.pgid!==d),u=[...new Set(a.map(o=>o.pgid))],p=l(o=>{for(const i of u)try{process.kill(-i,o)}catch{}},"signal");p("SIGTERM");const c=Date.now()+e;for(;a.some(o=>B(o.pid))&&Date.now()<c;)await new Promise(o=>setTimeout(o,50));return a.some(o=>B(o.pid))&&p("SIGKILL"),a.map(({pid:o,args:i})=>({pid:o,args:i}))}l(Ne,"stopRuns");const B=l(t=>{try{return process.kill(t,0),!0}catch(e){return e.code==="EPERM"}},"pidAlive");async function Ae(t,e,{launch:r,handBack:h,alive:n=B,held:d=l(()=>!1,"held"),stopped:a=new Set,paused:u=new Set,limitedUntil:p=l(()=>null,"limitedUntil"),atLimit:c=l(async()=>{},"atLimit")}){const o={};for(const[i,s]of Object.entries(t)){if(!e.has(i))continue;if(a.has(i)){o[i]={...s,handedBack:!0};continue}if(u.has(i)||s.pid!==null&&n(s.pid)||s.handedBack){o[i]=s;continue}const f={key:i,...s.kind?{kind:s.kind}:{},...s.model?{model:s.model}:{}},w=s.pid!==null?p(s):null;if(w){await c(f,w),o[i]={...s,pid:null};continue}if(d(s.kind??"claude")){o[i]=s;continue}if(s.pid!==null&&s.relaunched){await h(f),o[i]={...s,handedBack:!0};continue}const{key:b,...m}=f;o[i]={...m,...await r(f),relaunched:s.pid!==null||!!s.relaunched}}return o}l(Ae,"reviveBuilders");const H=l((t,e)=>T.join(Q.homedir(),".claude","kickflip","logs",`${e}-${t}-${new Date().toISOString().replace(/[:.]/g,"-")}.log`),"logFile");async function ge(t,{key:e,kind:r="claude",model:h,agent:n="main",text:d,files:a,question:u,note:p,choice:c,task:o,writes:i,worktree:s},{lander:f=!1}={}){const w=await L(t,e),b=C(t)?.route??T.basename(t),m=[...ee(),...await re(t)].map(V=>V.text),{base:g,repo:v}=M(t),k=ae({kind:r}),R=r===se,N=!k&&!R&&!f&&w.rebasePR!==null,A=r===ie,D=k?v:R?s??w.worktree??v:w.worktree,y={...w,route:b,base:g,repo:v,rules:m,kind:r,agent:n,text:d,files:a,question:u,note:p,choice:c,task:o,writes:i,cwd:D,pr:w.rebasePR},G=A?"Visual agent":R?`Helper ${n}`:k?"Planner":f?"Lander":N?"Rebase run":"Builder",U=A?fe(y):R?we(y):k?pe(y):f?Y(y):N?_(y):ue(y),x=await P({kind:k||R?"claude":r,model:h,cwd:D,site:t,prompt:U,log:H(e.split("/")[1],G.split(" ")[0].toLowerCase())});return await O(t,e,x.sessionId,x.pid,n,T.basename(x.log)),x}l(ge,"launchBuilder");const De=l(({ref:t,worktree:e,cleared:r})=>`The board paused your build of card ${t}, and the pause has cleared: ${r}. Carry on where you stopped, in ${e}, following your brief as before. Your builder record is still open, so don't run builder start again.`,"resumeBrief");async function K(t,{key:e,kind:r="claude",model:h,session:n,brief:d,fresh:a=!1,freshLaunch:u={}}){if(r!=="claude"||!n||a)return{...await ge(t,{key:e,kind:r,model:h},u),resumed:!1};const p=await L(t,e),c=await P({kind:r,model:h,cwd:n.worktree??p.worktree,site:t,prompt:d(p),resume:n.id,log:H(e.split("/")[1],"builder")});return await O(t,e,c.sessionId??n.id,c.pid,"main",T.basename(c.log)),{...c,resumed:!0}}l(K,"resumeBuilder");async function Pe(t,{key:e,kind:r="claude",model:h},{why:n,fresh:d=!1}={}){const a=await ne(t,e),u=await L(t,e),p=u.rebasePR!==null,c=a&&(!p||a.worktree===u.worktree)?a:null,o=C(t)?.route??T.basename(t),{base:i}=M(t),s=l(b=>(p?_:Y)({...b,route:o,base:i,pr:b.rebasePR,why:n,resumed:!0}),"brief"),f={lander:!p};let w;try{w=await K(t,{key:e,kind:r,model:h,session:c,brief:s,fresh:d,freshLaunch:f})}catch(b){if(!c)throw b;w=await K(t,{key:e,kind:r,model:h,session:null,brief:s,freshLaunch:f})}return w.resumed&&await te(t,e,"builder-resumed-to-land",{session:w.sessionId??c.id}),w}l(Pe,"landByBuilder");export{he as BOARD_CLI,ue as builderBrief,we as helperBrief,Pe as landByBuilder,Y as landerBrief,ge as launchBuilder,be as launchedAgents,B as pidAlive,pe as plannerBrief,_ as rebaseBrief,De as resumeBrief,K as resumeBuilder,Ae as reviveBuilders,Be as stopAgents,Ne as stopRuns,fe as visualBrief};
1
+ var Q=Object.defineProperty;var l=(t,e)=>Q(t,"name",{value:e,configurable:!0});import X from"node:os";import{BRIEF_LINE_LIMIT as J}from"./hub-lines.mjs";import T from"node:path";import{fileURLToPath as Z}from"node:url";import{launchAgent as P}from"./agents.mjs";import{processStarts as W,ps as C}from"../hub/live.mjs";import{boardAt as M}from"../hub/boards.mjs";import{readHubRules as ee}from"../hub/rules.mjs";import{builderRepo as O}from"./worktree.mjs";import{appendStatus as te,BoardError as oe,briefFacts as L,readBoardRules as re,recordedSession as ne,recordSession as E}from"./store.mjs";import{HELPER as se,plans as ae,VISUAL as ie}from"./domain.mjs";const he=Z(new URL("./bin/board.mjs",import.meta.url)),$=l(t=>`node ${he} --site ${t}`,"cli"),F=l(t=>`git -c core.commentChar=';' rebase ${t}`,"rebase"),de="a draft .md of your own outside the board's data folder (such as in /tmp)",le=l((t,e,r)=>`\`${t} item notes ${e} --heading "${r}" --file <draft.md>\``,"notesCommand"),I=l((t,e="Rules from the hub and this board:")=>t.length?[e,...t.map(r=>`- ${r}`),""]:[],"ruleLines"),q=l(t=>t.length?["Related cards: read their notes for context; don't build their scope.",...t.map(e=>`- ${e.ref} "${e.title}" (${e.relation}, ${e.lane}): ${e.notes?`notes ${e.notes}`:"no notes"}, thread ${e.thread}`),""]:[],"linkLines"),S=l(t=>`The card's thread, which holds the owner's messages (who: love): ${t}. Read it live.`,"threadLine"),Y=l(({route:t,key:e,worktree:r,base:i})=>[`- With no unverified calls, done finishes the card itself: it rebases onto ${i}, reruns the full test suite, fast-forwards ${i} and closes the card (on a board with pull requests it pushes the branch and opens a draft PR instead; with Auto-merge off it stops before the fast-forward, and the card waits for the owner's Merge).`,`- On a conflict it undoes its rebase and names the files: run \`${F(i)}\` in ${r}, resolve each conflict keeping both sides' intent, continue with \`git -c core.commentChar=';' rebase --continue\`, rerun the tests, and run done again.`,"- On failing tests, fix them, commit, and run done again.","- While another card is landing, done waits its turn and then lands; leave it running.","- Once a reviewer has the card's PR link, done refuses without `--updated \"<what changed>\"`, which goes in that link's Slack thread, or `--updated none` for a change only to comments, names, docs or tests.",`- Finishing never goes to the orchestrator. If done refuses for a reason you can't fix, record it with \`${$(t)} builder call ${e} "<why>"\` and run done again: with the call it hands the card back.`],"finishSteps"),j=l(({route:t,key:e})=>`\`${$(t)} builder done ${e} --text "<what you checked>"\``,"finishCommand"),ce=l((t,e)=>[`5. To run a separate part of the work in parallel, ask the board for a helper: \`${t} agent launch ${e} --name <short-name> --task "<the whole task, with the files and what to hand back>"\`, adding --writes when it changes code or runs tests (it gets its own worktree off your branch, and waits for a free implementer slot). Then \`${t} agent wait ${e} --name <short-name>\`, which prints its hand-back; it returns within 10 minutes, so run it again while it says still running, and do the task yourself if it says the helper exited. Merge a writing helper's work with \`${t} agent merge ${e} --name <short-name>\` before you test and finish.`],"helperSteps");function ue({route:t,key:e,ref:r,title:i,notes:n,thread:a,worktree:h,base:u,rules:p=[],links:c=[]}){const d=$(t);return[`You are the headless builder for card ${r} (${e}, "${i}") on the kickflip board /${t}. The board launched you; nobody watches this session, so never wait on a person. Work only in ${h}. Never restart the hub or a board server, and never push.`,"",`1. Run \`${d} builder start ${e} --kind build\`. If it refuses, stop.`,`2. Report progress with \`${d} builder progress ${e} --phase reading|building|testing|checking|landing "<plain, 80 characters or fewer>"\`, at least every 10 minutes. The board adds the phase word; don't start the text with it. The phases:`," - reading: the notes and the code, until the first test is written."," - building: the tests and the code, the ADR and docs, and the commit."," - testing: the full suite run before you finish."," - checking: your own review of the diff before you hand back; the Orchestrator's review of your calls continues on the same 4th bar."," - landing: set by the finish step itself; you never report it.",`3. Decide, record the call (step 4) and carry on: the orchestrator reviews your calls after the build. Ask it only when you are blocked, meaning every way forward would likely be thrown away: the spec contradicts itself or the code, a rule needs the owner's go before code is written, or the answer is a fact only the orchestrator has. Then run \`${d} builder ask ${e} "<question>"\`, do any work the answer can't change, and wait with \`${d} builder asks ${e} --wait\`, which returns within 10 minutes, so run it again while it says still waiting. If no answer comes in 30 minutes, record the question as a call, "Question: <question>", and hand back.`,`4. Record each judgment call the notes leave open with \`${d} builder call ${e} "<call>"\` instead of guessing silently or asking. A problem you find outside your card is a call "Found: <what>" for the orchestrator to file at review; ask about it at once only when it stalls other cards' builds or lands.`,...ce(d,e),"",n?`Spec: the card's notes, ${n}, all sections, where later sections override earlier ones.`:"The card has no notes file: build from its title, and record what you had to decide as calls.",`${S(a)} Read it with the notes: a message of the owner's is an instruction, but where it contradicts the notes, the notes win; that contradiction counts as blocked (step 3), so ask with \`${d} builder ask ${e} "<question>"\` and wait as step 3 says. Read the thread again in checking, before you finish, for anything the owner wrote during the build.`,"",...q(c),...I(p),"Write tests first, and run the full suite once, in the foreground, at the end. Where the repo keeps ADRs, write one named with a timestamp: docs/adr/YYYY-MM-DD-HHMM-<slug>.md.",`Commit with \`git -c core.commentChar=';' commit -F <file>\`, the subject "${r}: <what the card now does>".`,"",`To finish, run ${j({route:t,key:e})}.`,"- If you recorded a judgment call, it hands the card back for the orchestrator to verify your calls; stop there: the orchestrator's verification finishes the card.",...Y({route:t,key:e,worktree:h,base:u}),"","Never push."].join(`
2
+ `)}l(ue,"builderBrief");function _({route:t,key:e,ref:r,title:i,thread:n,worktree:a,base:h,answers:u=[],resumed:p=!1,why:c}){const d=$(t);return[p?`The board resumes you, the builder of card ${r} (${e}, "${i}") on the kickflip board /${t}, to land your build: ${c??"the orchestrator verified your judgment calls"}, and it waits only to land. Nobody watches this session, so never wait on a person. Work only in ${a}, never restart the hub or a board server, and never push.`:`You are the headless lander for card ${r} (${e}, "${i}") on the kickflip board /${t}. Its build is done and the orchestrator verified its judgment calls; it waits only to land. Nobody watches this session, so never wait on a person. Work only in ${a}, never restart the hub or a board server, and never push.`,"",`1. Run \`${d} builder start ${e} --kind build --label Lander --phase landing --progress "taking up the verified build"\`. If it refuses, stop.`,`2. Report progress with \`${d} builder progress ${e} --phase landing "<plain, 80 characters or fewer>"\`. The board adds the phase word; don't start the text with it.`,...u.length?["3. The owner answered the builder's deferred asks. Where an answer changes the build, change it, with its tests, and commit:",...u.map(o=>`- ${o.ask} "${o.question}": ${o.answer}`)]:[],"",S(n),"",`To finish, run ${j({route:t,key:e})}.`,...Y({route:t,key:e,worktree:a,base:h}),"","Never push."].join(`
3
+ `)}l(_,"landerBrief");function H({route:t,key:e,ref:r,title:i,thread:n,worktree:a,base:h,pr:u,resumed:p=!1}){const c=$(t);return[...p?[`The board resumes you, the builder of card ${r}, for this run: the lines below are your brief for it.`]:[],`You are the headless rebase run for card ${r} (${e}, "${i}") on the kickflip board /${t}. Its PR #${u} conflicts with ${h}; rebase its branch onto ${h} so the PR is clean again. Nobody watches this session, so never wait on a person. Work only in ${a}, never restart the hub or a board server, and never push.`,"",`1. Run \`${c} builder start ${e} --kind build --label Rebase --phase building --progress "Rebase run started \xB7 fetching ${h}"\`. If it refuses, stop.`,`2. Run \`git fetch origin\`, then \`${F(`origin/${h}`)}\`. Resolve each conflict keeping both sides' intent, \`git add\` it and continue with \`git -c core.commentChar=';' rebase --continue\`. Report each step with \`${c} builder progress ${e} --phase building "<line>"\`, the board adds the phase word, in these words: "Rebasing onto ${h} \xB7 resolving <n> files", then "Rebasing onto ${h} \xB7 suite running".`,"3. Run the full test suite once, in the foreground. Fix what the rebase broke, and commit each fix with `git -c core.commentChar=';' commit -F <file>`. A push with commits of its own is no longer rebase-only, so a merge after an approval won't count it as trivial.",`4. If a conflict or a failing test is past what you can resolve with confidence, run \`git -c core.commentChar=';' rebase --abort\` if a rebase is under way, record it with \`${c} builder call ${e} "Rebase failed in <the files or tests> \xB7 <why>"\`, and run the finish step: with the call it hands the card to the orchestrator.`,"",S(n),"",`To finish, run ${j({route:t,key:e})}, the text saying what you resolved. It reruns the suite and pushes the branch to PR #${u} with --force-with-lease; the PR stays as it is.`,"- If it refuses because someone pushed to the branch since your fetch, fetch and rebase again (step 2), then run done again.","- On failing tests, fix them, commit, and run done again.","","Never push."].join(`
4
+ `)}l(H,"rebaseBrief");function pe({route:t,key:e,ref:r,title:i,notes:n,thread:a,kind:h,repo:u,rules:p=[],links:c=[],text:d,files:o=[],question:s,note:w,choice:f,ticketQuestion:b=!1}){const m=$(t),g=h==="grill";return[`You are the headless ${g?"grill":"prototype"} planner for card ${r} (${e}, "${i}") on the kickflip board /${t}. The board launched you; nobody watches this session, so never wait on a person: the owner answers on the board, and a later planner or builder picks up from their answers. Read and run in ${u}; write no output code ${g?"there":`in ${u} itself`}, never restart the hub or a board server, and never push.`,"",`1. Run \`${m} builder start ${e} --kind ${h}\`. If it refuses, stop.`,`2. Gather the context yourself: ${n?`the card's notes, ${n}, all sections, where later sections override earlier ones; `:"the card has no notes file yet; "}its thread, ${a}, the repo's docs/adr and CONTEXT.md, and the cards its notes and thread link.`,`3. Report progress with \`${m} builder progress ${e} "<plain, 80 characters or fewer>"\` at least every 10 minutes.`,g?`4. Find every decision the card leaves open: read the code it touches and answer what the code answers yourself. Post each decision left as a question for the owner with \`${m} question add --file <json>\` on topic item:${e}, one per decision, your recommendation first. Once you finish, the orchestrator screens them before the owner sees them, settling the ones the board already answers and passing the owner the rest. Invoke the grilling skill with the Skill tool and run it as one round: its questions go to the board with question add, never into the chat, and you never wait for an answer; after the round, finish. (With no Skill tool, follow this step as written.) When the code shows the card has separable parts, each a build of its own, propose splitting it into a wave of smaller cards as one question ahead of the prototype question: its options "Split as proposed" and "Keep as one card", your recommendation first. Slice it as ~/.claude/skills/to-tickets/SKILL.md step 3, "Draft vertical slices", says (read that step; you can't invoke the skill, and skip its quiz and publish steps). The first card is the thinnest end-to-end path. The question's context lists each proposed card with its title, its scope, the files it touches and the earlier proposed cards it waits on, or none: a card waits on an earlier one only when it builds on that card or shares its seam (the same function, component, CLI command or doc paragraph); sharing only a file waits on nothing. Write the same breakdown in your notes section. On Split the orchestrator files the cards when the card stages; never file them yourself.`+(b?' This card is a task, with no ticket yet: just before the prototype question, ask "Create a ticket for this task?", whose options are yes and no, with "ticket_gate": true in its `question add --file` entry; the board makes the ticket from the card\'s title and notes the moment the owner answers yes. Recommend yes when the task lands code, no when it lands nothing (a publish, a setting, a one-off run).':"")+' End every grill with one more question, "Prototype round first?", whose options are yes and no, with "prototype_gate": true in its `question add --file` entry: once the owner has answered every question the board moves the card to prototyping on yes, or to staging on no, so nothing else moves it. Recommend yes when the card changes the hub\'s or a board\'s UI, no otherwise. Ask it even when it is your only question.':`4. Invoke the prototype skill with the Skill tool and build the round as take files, one per option: record them with \`${m} prototype add <topic> --card ${e} --take <K>:<label> \u2026\`, write the take files it prints, built from the repo's existing components, helpers and scripts fed mock data (the prototype template loads the board's scripts, so a take reuses the board's components; a hub-page take reuses them by path or copy), each take showing the states it needs inside itself, then post one question whose url opens prototype-<topic>.html with \`${m} question add --file <json>\` on topic item:${e}, never asking in the chat. Build in situ only where one of the board's rules asks for it: then, run \`${m} prototype base\`, which makes it or moves it to main and prints its path, whether its build script is there and the listener a page loads. When the build script is missing, write one there that serves the app and prints "port <n>" (board/build-script.mjs). Add one page to the worktree, at a path no other round uses, built from the repo's real components, helpers and scripts fed mock data, fully interactive; it reads the take from ?variant= and the state from ?state=, and loads the listener so \`KickflipPrototype.on((take, state) => \u2026)\` re-renders it in place when the viewer switches. Record it with \`${m} prototype add <topic> --card ${e} --served <path> --take <K>:<label> \u2026 --state <key>:<label> \u2026\`, one take per option and one state for each state that matters to the question, then post one question whose url opens prototype-<topic>.html with \`${m} question add --file <json>\` on topic item:${e}, never asking in the chat. (With no Skill tool, follow this step as written.)`,...d?[` The owner asked for this ${g?"grill":"prototype"}, writing: "${d}"`]:[],...o.length?[` The owner attached: ${o.map(v=>v.path).join(", ")}.`]:[],...s?[` The owner asked for prototypes of question ${s}${w?`, noting "${w}"`:""}${f?`, leaning to option ${f}`:""}: draw that question's next round, its question naming round_of ${s}.`]:[],`5. Record what you learned in the card's notes, naming each answered question's id: write it in ${de}, then run ${le(m,e,`${g?"Grill":"Prototype"} (<MM/DD>)`)}, which appends it as a section. The notes file is read-only; never write into it. Anything you leave open becomes a question with question add, never only a line in the notes, so the card can't go ready past it. A question's text is one line of at most ${J} characters (question add refuses a longer one); put the detail in its context and the options' details, which take markdown.`,...g?[`6. Where the owner asked for prototypes, or a rule here wants a UI prototype first, end the card \`${m} item set ${e} --phase prototyping\` instead and skip the prototype question. Otherwise the prototype question is what moves the card on: never set it ready yourself, and never leave a Next that asks the owner to mark it ready.`]:[],"",...q(c),...I(p,"Rules from the hub and this board, written for the orchestrator: where one tells you to push, land, restart the hub or a board server, or do the orchestrator's work, it isn't yours; you never push or restart:"),`To finish, run \`${m} builder done ${e} --text "<what you asked>"\`.`,"","Never push."].join(`
5
+ `)}l(pe,"plannerBrief");function fe({route:t,key:e,ref:r,title:i,notes:n,thread:a,repo:h,agent:u,rules:p=[],links:c=[],text:d,files:o=[]}){const s=$(t);return[`You are the headless visual agent for card ${r} (${e}, "${i}") on the kickflip board /${t}. The owner asked, in the card's thread, for a visual: one HTML page that answers in a visual way, such as a diagram, a flow or a comparison. It is not a prototype or a grill: no question, no takes. The board launched you; nobody watches this session, so never wait on a person. Read and run in ${h}; write no code there, never restart the hub or a board server, and never push.`,"",`1. Run \`${s} builder start ${e} --kind visual --agent ${u}\`. If it refuses, stop.`,`2. Gather what the page needs: ${n?`the card's notes, ${n}; `:""}its thread, ${a}, and the code or docs it is about.`,d?` The owner wrote: "${d}"`:" The owner typed nothing: draw what the thread was last discussing.",...o.length?[` The owner attached: ${o.map(w=>w.path).join(", ")}.`]:[],"3. Write one HTML file outside the repo, such as in a temporary folder. Keep its own CSS and script inline; it may load a library such as Mermaid or D3 from a CDN. Make it read well at phone width (390px) and on a desktop, in the dark and the light theme (prefers-color-scheme).",`4. Place it with \`${s} visual add ${e} --file <page.html> --title "<what it shows>"\`, which copies it to the board and links it in the thread.`,"","Write nothing else: no notes, questions, card state, Next or progress lines.","",...q(c),...I(p,"Rules from the hub and this board, written for the orchestrator: where one tells you to push, land, restart the hub or a board server, or do the orchestrator's work, it isn't yours; you never push or restart:"),`To finish, run \`${s} builder done ${e} --agent ${u}\`.`,"","Never push."].join(`
6
+ `)}l(fe,"visualBrief");function we({route:t,key:e,ref:r,title:i,notes:n,thread:a,agent:h,task:u,writes:p,cwd:c,rules:d=[],links:o=[]}){const s=$(t);return[`You are the headless helper ${h} on card ${r} (${e}, "${i}") on the kickflip board /${t}. Another agent working on the card asked the board for you to do one task beside it, and waits on your hand-back. Nobody watches this session, so never wait on a person. Never restart the hub or a board server, and never push.`,p?`Work only in ${c}, your own worktree, branched from the card's: commit your work there with \`git -c core.commentChar=';' commit -F <file>\`, the subject "${r}: <what it does>". The card's builder merges your branch, so leave nothing uncommitted.`:`Read and run in ${c}; change no files there and commit nothing.`,"","The task:",u,"",`1. Run \`${s} builder start ${e} --kind helper --agent ${h}\`. If it refuses, stop.`,`2. Report progress with \`${s} builder progress ${e} --agent ${h} --phase reading|building|testing|checking "<plain, 80 characters or fewer>"\` at least every 10 minutes; the board adds your name and the phase word.`,"3. Do only the task. Don't ask the orchestrator or the owner, record no judgment calls and post no questions: put any decision you had to make in your hand-back.",n?` The card's notes, for context: ${n}.`:"",` ${S(a)}`,"",...q(o),...I(d,"Rules from the hub and this board, written for the orchestrator: where one tells you to push, land, restart the hub or a board server, or do the orchestrator's work, it isn't yours; you never push or restart:"),`To finish, run \`${s} builder done ${e} --agent ${h} --text "<your hand-back: what you did or found, ${p?"what you committed, ":""}and any decision you made>"\`.`,"","Never push."].filter((w,f,b)=>w!==""||b[f-1]!=="").join(`
7
+ `)}l(we,"helperBrief");const me=6e4;function be(t,{starts:e=W}={}){const r=t.filter(n=>n.pid&&n.at).map(n=>n.pid),i=r.length&&e(r)||new Map;return t.filter(n=>{const a=i.get(n.pid);return a!==void 0&&a<=Date.parse(n.at)&&a>=Date.parse(n.at)-me})}l(be,"launchedAgents");function Be(t,{starts:e=W,kill:r=l((n,a)=>process.kill(-n,a),"kill"),killAfter:i=5e3}={}){const n=be(t,{starts:e});for(const{pid:a}of n){try{r(a,"SIGTERM")}catch{continue}setTimeout(()=>{try{r(a,"SIGKILL")}catch{}},i).unref()}return n.length}l(Be,"stopAgents");async function Ae(t,{grace:e=5e3}={}){const r=["-A","-o","pid=,ppid=,pgid=,args="],i=C(r)??C(r);if(i===null)throw new oe("ps gave no answer, so nothing was stopped; run it again");const n=i.split(`
8
+ `).map(o=>/^\s*(\d+)\s+(\d+)\s+(\d+)\s+(.*)$/.exec(o)).filter(Boolean).map(([,o,s,w,f])=>({pid:Number(o),ppid:Number(s),pgid:Number(w),args:f})),a=new Set(n.filter(o=>t.includes(o.pgid)).map(o=>o.pid));for(let o=!0;o;){o=!1;for(const s of n)!a.has(s.pid)&&a.has(s.ppid)&&(a.add(s.pid),o=!0)}const h=n.find(o=>o.pid===process.pid)?.pgid,u=n.filter(o=>a.has(o.pid)&&o.pgid>1&&o.pgid!==h),p=[...new Set(u.map(o=>o.pgid))],c=l(o=>{for(const s of p)try{process.kill(-s,o)}catch{}},"signal");c("SIGTERM");const d=Date.now()+e;for(;u.some(o=>B(o.pid))&&Date.now()<d;)await new Promise(o=>setTimeout(o,50));return u.some(o=>B(o.pid))&&c("SIGKILL"),u.map(({pid:o,args:s})=>({pid:o,args:s}))}l(Ae,"stopRuns");const B=l(t=>{try{return process.kill(t,0),!0}catch(e){return e.code==="EPERM"}},"pidAlive");async function Ne(t,e,{launch:r,handBack:i,alive:n=B,held:a=l(()=>!1,"held"),stopped:h=new Set,paused:u=new Set,limitedUntil:p=l(()=>null,"limitedUntil"),atLimit:c=l(async()=>{},"atLimit")}){const d={};for(const[o,s]of Object.entries(t)){if(!e.has(o))continue;if(h.has(o)){d[o]={...s,handedBack:!0};continue}if(u.has(o)||s.pid!==null&&n(s.pid)||s.handedBack){d[o]=s;continue}const w={key:o,...s.kind?{kind:s.kind}:{},...s.model?{model:s.model}:{}},f=s.pid!==null?p(s):null;if(f){await c(w,f),d[o]={...s,pid:null};continue}if(a(s.kind??"claude")){d[o]=s;continue}if(s.pid!==null&&s.relaunched){await i(w),d[o]={...s,handedBack:!0};continue}const{key:b,...m}=w;d[o]={...m,...await r(w),relaunched:s.pid!==null||!!s.relaunched}}return d}l(Ne,"reviveBuilders");const K=l((t,e)=>T.join(X.homedir(),".claude","kickflip","logs",`${e}-${t}-${new Date().toISOString().replace(/[:.]/g,"-")}.log`),"logFile");async function ge(t,{key:e,kind:r="claude",model:i,agent:n="main",text:a,files:h,question:u,note:p,choice:c,task:d,writes:o,worktree:s},{lander:w=!1}={}){const f=await L(t,e),b=M(t)?.route??T.basename(t),m=[...ee(),...await re(t)].map(z=>z.text),{base:g,repo:v}=O(t),k=ae({kind:r}),R=r===se,A=!k&&!R&&!w&&f.rebasePR!==null,N=r===ie,D=k?v:R?s??f.worktree??v:f.worktree,y={...f,route:b,base:g,repo:v,rules:m,kind:r,agent:n,text:a,files:h,question:u,note:p,choice:c,task:d,writes:o,cwd:D,pr:f.rebasePR},U=N?"Visual agent":R?`Helper ${n}`:k?"Planner":w?"Lander":A?"Rebase run":"Builder",V=N?fe(y):R?we(y):k?pe(y):w?_(y):A?H(y):ue(y),x=await P({kind:k||R?"claude":r,model:i,cwd:D,site:t,prompt:V,log:K(e.split("/")[1],U.split(" ")[0].toLowerCase())});return await E(t,e,x.sessionId,x.pid,n,T.basename(x.log)),x}l(ge,"launchBuilder");const De=l(({ref:t,worktree:e,cleared:r})=>`The board paused your build of card ${t}, and the pause has cleared: ${r}. Carry on where you stopped, in ${e}, following your brief as before. Your builder record is still open, so don't run builder start again.`,"resumeBrief");async function G(t,{key:e,kind:r="claude",model:i,session:n,brief:a,fresh:h=!1,freshLaunch:u={}}){if(r!=="claude"||!n||h)return{...await ge(t,{key:e,kind:r,model:i},u),resumed:!1};const p=await L(t,e),c=await P({kind:r,model:i,cwd:n.worktree??p.worktree,site:t,prompt:a(p),resume:n.id,log:K(e.split("/")[1],"builder")});return await E(t,e,c.sessionId??n.id,c.pid,"main",T.basename(c.log)),{...c,resumed:!0}}l(G,"resumeBuilder");async function Pe(t,{key:e,kind:r="claude",model:i},{why:n,fresh:a=!1}={}){const h=await ne(t,e),u=await L(t,e),p=u.rebasePR!==null,c=h&&(!p||h.worktree===u.worktree)?h:null,d=M(t)?.route??T.basename(t),{base:o}=O(t),s=l(b=>(p?H:_)({...b,route:d,base:o,pr:b.rebasePR,why:n,resumed:!0}),"brief"),w={lander:!p};let f;try{f=await G(t,{key:e,kind:r,model:i,session:c,brief:s,fresh:a,freshLaunch:w})}catch(b){if(!c)throw b;f=await G(t,{key:e,kind:r,model:i,session:null,brief:s,freshLaunch:w})}return f.resumed&&await te(t,e,"builder-resumed-to-land",{session:f.sessionId??c.id}),f}l(Pe,"landByBuilder");export{he as BOARD_CLI,ue as builderBrief,we as helperBrief,Pe as landByBuilder,_ as landerBrief,ge as launchBuilder,be as launchedAgents,B as pidAlive,pe as plannerBrief,H as rebaseBrief,De as resumeBrief,G as resumeBuilder,Ne as reviveBuilders,Be as stopAgents,Ae as stopRuns,fe as visualBrief};