@lovek4d/kickflip 0.6.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/CONTEXT.md +15 -11
  2. package/SKILL.md +7 -7
  3. package/bin/doctor.mjs +5 -4
  4. package/bin/install.mjs +3 -3
  5. package/board/bin/board.mjs +26 -12
  6. package/board/domain.mjs +7 -7
  7. package/board/headless.mjs +9 -9
  8. package/board/public/app-cards.js +23 -44
  9. package/board/public/app-lanes.js +4 -7
  10. package/board/public/app-threads.js +12 -8
  11. package/board/public/prototype-switcher.js +7 -2
  12. package/board/public/shell.js +4 -3
  13. package/board/public/sidebar.css +5 -0
  14. package/board/public/sidebar.js +3 -0
  15. package/board/public/style.css +11 -19
  16. package/board/public/ui-cards.js +21 -15
  17. package/board/public/ui-enums.js +2 -2
  18. package/board/public/ui-header.js +4 -6
  19. package/board/public/ui-hub.js +7 -4
  20. package/board/public/ui-order.js +1 -1
  21. package/board/public/ui-page-header.js +1 -1
  22. package/board/public/ui-sidebar.js +11 -5
  23. package/board/public/ui-snapshot.js +1 -1
  24. package/board/public/ui.js +3 -2
  25. package/board/public/view-hub.js +3 -1
  26. package/board/public/view-questions.js +1 -1
  27. package/board/server.mjs +20 -19
  28. package/board/stop.mjs +1 -1
  29. package/board/store.mjs +27 -25
  30. package/board/transcript.mjs +2 -2
  31. package/hooks-plugin/hooks/board-data-gate.mjs +9 -6
  32. package/hub/front-door.mjs +1 -1
  33. package/hub/hub.mjs +1 -1
  34. package/hub/sessions.mjs +2 -2
  35. package/hub/try.mjs +7 -7
  36. package/hub/usage.mjs +3 -3
  37. package/package.json +1 -1
  38. package/shared/card-prs.mjs +1 -1
  39. package/shared/machine-gate.mjs +2 -2
  40. package/shared/tailscale.mjs +1 -1
  41. package/shared/usage-gate.mjs +1 -1
package/board/domain.mjs CHANGED
@@ -1,10 +1,10 @@
1
- var Ut=Object.defineProperty;var l=(e,t)=>Ut(e,"name",{value:t,configurable:!0});import{routeRecord as A}from"../hub/routes.mjs";import Wt from"node:fs";import{ticketBadge as dt,ticketIn as Ft}from"./tickets.mjs";import{prLinks as Kt,reviewLinks as Gt,reviewTime as Qt,reviewersOf as Vt}from"./review.mjs";import{remoteFromInput as Ht}from"../hub/repos.mjs";import{reviewsOf as Xt,reviewNeeds as Yt,reviewActions as zt}from"./auto-review.mjs";import{cardPRs as Jt,hasPR as Ze,leastReady as Zt,prKeyOf as en,prURL as tn}from"../shared/card-prs.mjs";import ct from"node:vm";import{checkWords as nn,parseCheck as on}from"../shared/owner-check.mjs";import{buildScriptOf as sn}from"./build-script.mjs";const et=ct.createContext({});for(const e of["ui-enums.js","ui-order.js"])ct.runInContext(Wt.readFileSync(new URL(`./public/${e}`,import.meta.url),"utf8"),et);const{BUTTON:ce,ACTION_KIND:q,BUILDER_KIND:Re,PR_STATE:ee}=et.BoardUI,{laneOrder:rn}=et.BoardUI,an=["item-message","mark-ready","grill-more","prototype-request","visual-request","reask-request"],ut="question-reopen",Yo=Object.freeze({todo:"Todo",progress:"In Progress",review:"In Review",done:"Done"}),be="move",ln=10*6e4,dn=5*6e4,pt="build-refused",_e=Object.freeze({ask:"builder-ask",answer:"builder-answer"}),cn=864e5,ue=l(e=>e.lane==="done"||e.lane==="asks","closedLane"),un=l(e=>`wave:${e}`,"waveFold"),ft=7*cn,gt=l((e,t)=>({id:e,url:t?.workspace?`https://linear.app/${t.workspace}/issue/${e}`:null}),"linearRef"),pn=l((e,t)=>String(e).replace(/^S(\d+)$/,`${t||"INT"}-$1`),"internalRef"),We=l((e,t,n)=>Ft(n,t.ticket)?t.ticket:e.internal===!0?pn(t.id,n?.prefix):t.id,"refOf"),fn=l((e,t,n,s)=>dt(e||(t?{source:"internal",prefix:"INT"}:null),n,s)||(t?{id:String(t),url:null,source:null}:null),"ticketOf");function gn(e,t,n,s,r,a,u,d){return!a&&(e.lane==="done"||t?.state==="MERGED"||t?.state==="CLOSED")?"done":(n||s)&&!d?"progress":r?"review":t?.state==="OPEN"?t.checks?.state==="fail"&&(!t.isDraft||u)?"progress":"review":t?"progress":"todo"}l(gn,"laneOf");function kn(e,t){return e?.state==="MERGED"||t?ee.merged:e?.state==="CLOSED"?ee.closed:e?.state==="OPEN"?e.isDraft?ee.draft:ee.open:null}l(kn,"prStateOf");const kt=l(e=>e.pr?.status===ee.closed,"unlanded"),mn=l(e=>e.some(t=>t.kind==="work")&&e.every(t=>t.lane==="done"),"finishedOf");function tt(e){const t=e.filter(n=>n?.started&&ne(n)&&n.kind!==Re.install).sort((n,s)=>String(n.started).localeCompare(String(s.started))).at(-1);return t?.done&&t.handed_back?t.handed_back:null}l(tt,"handedBackAt");function wn(e,t,n){return Object.fromEntries(e.map(s=>{const r=A(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"?A(n,s.name):null,github:s.github||null}]}))}l(wn,"reviewOf");function hn(e,t,n,s,r,a,{held:u,needsApproval:d}){return n?.kind===Re.install?"builder":s?"checking":n?"builder":u&&e==="review"?"merge":r&&e==="review"?"owner":e==="review"&&t?.state==="OPEN"&&t.isDraft?a?"ci":"ready":e==="review"||e==="stale"?mt(t,d)?"merge":"reviewers":null}l(hn,"waitsOnOf");const mt=l((e,t)=>e?.reviewDecision==="APPROVED"||!t&&!["REVIEW_REQUIRED","CHANGES_REQUESTED"].includes(e?.reviewDecision),"reviewed"),Me=/(^|\/)prototype-[\w-]+\.html/,yn={reviewers:"Waiting for PR reviewers",merge:"Approved, merges once CI and the merge state allow",ci:"Marked ready for review once CI passes"},Fe={waiting:"Approved, Merge shows once CI and the merge state allow",ready:"Approved and green: tap Merge",merging:"Merging"},nt={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=l(e=>`Verified: tap Merge to land it on ${e}`,"heldNext"),Sn="Fix the failing CI",wt="Tap Done once it is settled",vn=["status","agent","ask-link","command",be,_e.answer],ht=l(e=>(e.who==="claude"||e.via==="github")&&!vn.includes(e.kind),"countsUnread"),xn=/^(?:Not merged yet: |Lander launched headless )/;function On({slug:e,wave:t,it:n,order:s,prs:r,repo:a,remote:u,base:d,builder:p,reachedPhase:f,agents:g,worktree:c,eta:h,thread:m,events:x,working:S,said:L,askedAt:_,topicItems:P,prSlack:T,boardReviewers:F,now:E,bumpedAt:C,holdAsk:i,closedAt:y,mergeAt:B,autoMerge:ge,skipDraft:re,tickets:U}){const H=r.map(w=>({repo:w.repo,number:w.number,url:tn(w),...w.gh||{},status:kn(w.gh,w.merged_at)})),K=Zt(r.map(w=>w.gh)),D=r[K]?.gh||null,X=H[K]?.status??null,Y=n.lane||null,qe=!!y&&String(y)>String(n.lane_at||"");qe&&(n={...n,lane:"done",closed_at:y,reopened_at:void 0});const W=[n.closed_at,...r.flatMap(w=>[w.merged_at,w.gh?.mergedAt])].filter(Boolean).map(String).sort().pop()||null,j=String(W||""),De=H.length>0&&H.every(w=>w.status===ee.closed),G=(n.lane==="done"||n.reopened_at||X===ee.merged)&&!De&&([...L.filter(w=>w.kind==="item-message").map(w=>w.ts),..._].map(String).filter(w=>w>j).sort().pop()||n.reopened_at)||void 0,ke=n.owner_step&&!qe?String(n.owner_step):null,He=(n.lane==="done"||X===ee.merged)&&!De;G&&(n={...n,next:void 0});const Ne=tt(g),Xe=!!G&&L.some(w=>String(w.ts)>j&&(w.state==="waiting"||w.state==="working")),me=`${e}/${n.id}`,Q=Vt(t,F),oe=Object.fromEntries(Object.entries(Gt(t,n,me,P,Kt(T,r))).map(([w,V])=>[w,Qt(V.ts)])),ae=Object.values(oe).filter(Boolean).sort()[0]||null,Ce=[...m.map(w=>w.ts),...x.map(w=>w.ts)].sort().pop()||null,Oe=[...m.filter(w=>ht({...w,who:w.who||"claude"})).map(w=>w.ts),...x.map(w=>w.reply?.at)].filter(Boolean).sort(),z=!Ze(n)&&n.pushed?.branch?{branch:n.pushed.branch,at:n.pushed.at||null,host:u?.host||null}:null,J=!Ze(n)&&n.merge_ready?.branch?{branch:n.merge_ready.branch,at:n.merge_ready.at||null,base:d}:null,$e=!!n.rebase_run&&D?.state==="OPEN",le=(n.rebased||[]).at(-1);let R=gn(n,D,g.some(w=>w.started&&!w.done&&ne(w)),!!Ne,z||J,!!G,re,$e);G&&R==="todo"&&(R="progress"),R==="review"&&ae&&E-Date.parse(ae)>ft&&(R="stale");const we=!!ke&&He&&R==="done";we&&(R="review");let N=hn(R,D,p,!!Ne&&R!=="done",z,re,{held:J,needsApproval:Q.length>0});const Ye=N==="builder"&&D?.state==="OPEN"&&!D.isDraft,Te=R==="progress"&&!N&&D?.state==="OPEN"&&(!D.isDraft||re),Z=we||!!G&&R==="progress"&&!N&&!Te&&!Xe;Z&&(N="owner");const Le=i&&String(i.ts)>String(n.held_at||""),ie=R==="todo"&&(Le?!!i.data.hold:!!n.held),Pe=ie&&(Le?i.data.until:n.pick_up_at)||null,he=R==="todo"||R==="progress",Ee=g.filter(ne).map(w=>String(w.started||"")).filter(Boolean).sort()[0]||"",je=C&&!(Ee&&String(C)<Ee),ze=m.filter(w=>(w.who||"claude")==="claude"&&!["status","command",be].includes(w.kind)&&!xn.test(w.text||"")).map(w=>String(w.ts)).sort().pop()||"",Ae=[B,J&&n.merge_ready.tapped].filter(Boolean).map(String).sort().pop(),o=N==="merge"&&!!Ae&&Ae>ze,k=m.filter(w=>w.kind==="status"&&String(w.ts)>String(Ae)).sort((w,V)=>String(w.ts).localeCompare(String(V.ts))).at(-1),b=k?.text.startsWith("Waiting for ")?`${Fe.merging} \xB7 ${k.text[0].toLowerCase()}${k.text.slice(1)}`:Fe.merging,v=m.filter(w=>!["status",be].includes(w.kind)).sort((w,V)=>String(w.ts).localeCompare(String(V.ts))).at(-1),O=R==="todo"&&v?.kind===pt?v.text:null,M=G||g.filter(ne).map(w=>w.started).filter(Boolean).map(String).sort().pop()||null,I=D?.reviewDecision==="APPROVED",ye=z&&N==="owner"?`merge ${z.branch} into ${d}`:J&&N==="merge"?o?b:bn(d):N==="merge"&&!ge?o?b:(I?Fe:nt).waiting:N==="merge"&&!I?nt.auto:yn[N],de=X===ee.draft?`Review draft PR #${H[K].number}`:null,Je=!$e&&le?.to&&le.to===D?.headRefOid?`Rebased onto ${le.base||d}, pushed${D.mergeable==="MERGEABLE"?" \xB7 PR is clean again":""}`:null,Ue=$e&&N==="checking"?g.flatMap(w=>w.calls).sort((w,V)=>String(w.at).localeCompare(String(V.at))).at(-1)?.text??null:null;return{key:me,kind:"work",order:s,wave:e,waveTitle:t.title||e,id:n.id,ref:We(t,n,U),title:n.title,storedLane:Y,created:n.created||null,reviewSince:n.review_since||ae||n.merge_ready?.at||n.pushed?.at||n.created||null,progressSince:M,closedAt:R==="done"?W:null,internal:t.internal===!0,repo:t.internal===!0&&!t.repo?null:a,lane:R,waitsOn:N,builder:N==="checking"?null:p,reachedPhase:f,agents:g,worktree:c??null,eta:h,working:S,reopened:!!G,awaitsDone:Z,ownerStep:Z?ke:null,ownerCheck:Z&&ke&&n.owner_check?{words:nn(on(n.owner_check.check)),broken:n.owner_check.broken||null}:null,stagedAt:R==="todo"&&n.state==="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:Pe,risky:!!n.risky,bumpable:he,bumpedAt:he&&je?C:null,holdable:R!=="done",hasNotes:!!(n.plan||String(n.notes||"").trim()),finishable:R!=="done",merging:o,refusal:O,manualSteps:n.manual_steps||[],fromAsk:n.from_ask||null,unfiled:!!n.unfiled,next:Ye||R==="done"||N==="checking"?null:Te?Sn:Je||ye||n.next||de||null,rebaseFailed:Ue,branch:n.branch||D?.headRefName||z?.branch||null,pushed:R==="done"||we?null:z,mergeReady:R==="review"?J:null,landStopped:R==="review"&&J&&n.merge_ready.stopped&&String(n.merge_ready.stopped)>String(n.merge_ready.tapped||"")?{words:vt("land"),at:n.merge_ready.stopped}:null,ticket:U?.source==="linear"&&!n.ticket&&n.linear?.length?null:fn(U,n.ticket,n.ticket||(t.internal?We(t,n,U):n.linear?.[0]),{repo:a,key:me}),ticketSource:U?.source||null,linear:(n.linear||[]).map(w=>U?.source==="linear"&&dt(U,w,{key:me,repo:a})||gt(w,U)),pr:H[K]||null,prs:H,reviewers:Q.map(w=>w.name),review:wn(Q,D,oe),inReviewSince:ae,threadCount:m.length+x.length,lastTs:Ce,othersAt:Oe,grilling:xt("grill",x,g,_),prototyping:xt("prototype",x,g,_),state:n.state||null,updatedAt:D?.mergedAt||D?.updatedAt||r[K]?.merged_at||p?.at||Ce||null,ask:null,fold:null,action:null,install:R==="done"?null:En(n.install,D),waitFacts:$n(n,{agents:g,thread:m,events:x,refusedAt:O?v.ts:null})}}l(On,"workItem");function $n(e,{agents:t,thread:n,events:s,refusedAt:r}){const a=new Set(n.filter(f=>f.kind===_e.answer).map(f=>f.ask)),u=n.filter(f=>f.kind===_e.ask&&!a.has(f.ask)).sort((f,g)=>String(f.ts).localeCompare(String(g.ts)))[0],d=l(f=>s.filter(g=>st(g,f)).pop()?.ts||null,"requestAt"),p=n.filter(f=>(f.who||"claude")==="claude"&&!["status","agent","command",be,pt].includes(f.kind)).map(f=>f.ts);return{handedBackAt:tt(t),next:e.next||null,ask:u?{at:u.ts,text:u.text}:null,grillAt:d("grill"),prototypeAt:d("prototype"),refusedAt:r,unverified:t.some(f=>f.calls.some(g=>!f.verified||String(g.at)>String(f.verified.at))),writes:[...p,...t.flatMap(f=>[f.verified?.at,f.started])].filter(Boolean).map(String).sort()}}l($n,"waitFactsOf");function En(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))}}l(En,"installOf");function An(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",!mt(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)}l(An,"mergeBlockers");const Bn=l((e,t)=>!An(e,{needsApproval:t}).length,"mergeable");function Rn(e,t,n){if(ue(e))return null;const s=`#item=${encodeURIComponent(e.key)}`;if(_n(e)){const r=t.get(e.questions.first);return Me.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.kind==="ask"&&e.waitsOn==="owner"?{kind:q.done,url:s,label:e.next||wt}: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)||Bn(r,e.reviewers.length>0))?{kind:q.merge,url:s,label:"Merge"}:null}l(Rn,"actionOf");const _n=l(e=>!!(e.questions&&e.questions.state!=="resolved"&&e.questions.open>0),"asking"),ot=new Set([q.pick,q.questions]);function yt(e){return e.questions?ce.openQuestions:null}l(yt,"actionButtonOf");const Mn=l(e=>e.kind==="work"&&e.lane==="todo"&&!e.action&&!e.unpicked,"buildableOf"),In=l((e,t)=>/^item:/.test(e.topic||"")&&Me.test(e.url||"")&&!(e.options||[]).some(n=>n.id===t),"picksNoTake");function qn(e,t){const n=e.filter(a=>a.topic===`item:${t}`),r=[...n.filter(a=>Me.test(a.url||"")).flatMap(a=>a.answers.map(u=>({q:a,a:u,again:In(a,u.choice)}))),...n.filter(a=>a.request).map(a=>({q:a,a:a.request,again:!0}))].sort((a,u)=>String(a.a.ts).localeCompare(String(u.a.ts))).at(-1);return r?.again?{id:r.q.id,note:r.a.note.replace(/\s+/g," ").trim()}:null}l(qn,"noTakePicked");const Dn="Prototype again",Nn=Object.freeze({[q.pick]:ce.openQuestions,[q.questions]:ce.openQuestions,[q.ready]:ce.github,[q.steps]:ce.github,[q.merge]:ce.merge,[q.done]:ce.done}),Cn="Merges once unblocked",Tn=l(e=>`Blocked ${[e.blockedBy.length&&`by ${e.blockedBy.map(t=>t.ref).join(", ")}`,e.blockedUntil&&`until ${e.blockedUntil}`].filter(Boolean).join(" and ")}`,"mergeBlockedWords");function Ln(e,t,n){if(e.action=Rn(e,t,n),e.mergeBlocked=e.action?.kind===q.merge&&e.blocked?Tn(e):null,e.mergeBlocked&&Object.assign(e,{action:null,next:Cn}),e.action?.kind===q.questions&&e.questions.fromAsks){Object.assign(e,{action:null,waitsOn:"owner",next:e.action.label,actionButton:yt(e),resolvedBy:null});return}e.awaitsDone&&=!e.action,e.awaitsDone||Object.assign(e,{ownerStep:null,ownerCheck:null}),e.actionButton=yt(e),e.resolvedBy=Nn[e.action?.kind]||null,e.action?.kind===q.merge&&!e.mergeReady&&(e.next=(e.pr?.reviewDecision==="APPROVED"?Fe:nt).ready),e.awaitsDone&&Object.assign(e,{lane:"review",next:e.ownerStep||wt,bumpable:!1,bumpedAt:null});const s=e.lane==="todo"||e.waitsOn==="ready"||e.lane==="progress"&&e.waitsOn==="owner";if(!e.action||!s)return;const r=e.lane==="todo"&&ot.has(e.action.kind)?"todo":"review";Object.assign(e,{lane:r,waitsOn:e.waitsOn==="ready"?"ready":"owner",next:e.action.label}),r==="review"&&Object.assign(e,{bumpable:!1,bumpedAt:null})}l(Ln,"placeOwnerWait");const bt=l((e,t)=>e?e!=="todo":t.some(ne),"started"),St="none set yet";function zo(e,t){const n=t.filter(s=>s?.started);return e.next||Ze(e)||e.pushed||e.held||e.lane==="done"||tt(n)?!1:!n.some(s=>!s.done)&&bt(e.lane,n)}l(zo,"owesNext");function Pn(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}`}l(Pn,"askedLabel");function jn(e,t,n,s,r,a,u){const d=t.find(y=>y.kind==="ask"&&y.eventId===e.id),p=d.files,f=(d.text||"").trim().split(`
2
- `)[0]||p.map(y=>y.name).join(", ")||"Ask",g=d.state==="replied"||d.state==="answered",c=r?.state==="progress"&&!(String(a||"")>String(r.at||"")),h=!c&&(!!r||!!a),m=g&&!h&&!c&&u.open>0,x=g&&!h&&!c&&!m&&u.answered>0,S=h||g&&!c&&!m&&!x,L=g&&!s&&!r?.moved&&[...t.filter(y=>y.who==="claude"&&y.kind!=="ask-link"&&y.kind!=="status"&&y.kind!=="command").map(y=>y.ts),...t.map(y=>y.reply?.at)].filter(Boolean).sort().pop()||null,_=S||c||m||x,P=t.some(y=>y.who==="love"&&y.kind!=="ask"&&(y.state==="waiting"||y.state==="working")),T=t.filter(y=>y.who==="love"&&y.kind==="item-message").flatMap(Ie).filter(([,y])=>y==="waiting"||y==="working").map(([y])=>y),F=_?"answered":d.state,E=_?null:n||null,C=[...t.filter(ht).map(y=>y.ts),...t.map(y=>y.reply?.at)].filter(Boolean).sort(),i=[...t.map(y=>y.ts),...C,...c?[r.at]:[]].filter(Boolean).sort().pop();return{key:`ask:${e.id}`,kind:"ask",order:0,wave:null,waveTitle:null,id:e.id,ref:_?Pn(e.ts):"New",isNew:!_,title:f,storedLane:null,internal:!1,repo:null,lane:c?"progress":S?"asks":"todo",waitsOn:c&&!P?"owner":null,builder:null,agents:[],eta:null,working:t.some(y=>Ie(y).some(([,B])=>B==="working")),deps:[],held:!1,risky:!1,bumpable:!_,bumpedAt:E,manualSteps:[],fromAsk:null,from:null,next:c&&r.next||null,hasNotes:!1,branch:null,finishable:_&&!S,linear:[],pr:null,prs:[],reviewers:[],review:{},inReviewSince:null,reviewSince:null,progressSince:c&&r.at||null,closedAt:null,threadCount:t.length,lastTs:i,othersAt:_?C:[],grilling:!1,prototyping:!1,created:e.ts,updatedAt:_?i:e.ts,ask:{eventId:e.id,state:F,askedAt:e.ts,bumpedAt:E,edited:!!d.edited,canEdit:!_,text:d.text,files:p,answerAt:L,from:d.from||null,owed:T},fold:null,action:null}}l(jn,"askItem");const pe="visual",Ke="helper",Un=new Set(["grill","prototype",pe]),te=l(e=>Un.has(e.kind),"plans"),ne=l(e=>!te(e),"builds"),Wn={grill:"grilling",prototype:"prototyping"},Fn=l((e,t)=>`${e} ${t} by orchestrator`,"orchestratorDid"),vt=l(e=>Fn(e,"stopped"),"stoppedWords"),Kn={grill:"grill-more",prototype:"prototype-request"},st=l((e,t)=>e.kind===Kn[t]&&!e.data?.question,"isRequest");function xt(e,t,n,s){if(n.some(a=>a.kind===e&&a.started&&!a.done))return!0;const r=t.filter(a=>st(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)))}l(xt,"planningOf");const Jo=30*6e4;function Zo(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&&st(a,"prototype")).pop();r&&n.push({key:s.key,request:r.id,since:r.ts});for(const a of e.owner.filter(u=>u.topic===`item:${s.key}`&&u.state==="requested"))n.push({key:s.key,request:a.request.id,question:a.id,since:a.request.ts})}return n}l(Zo,"prototypeWaits");function Ge(e={}){return Object.entries(e).filter(([,t])=>t&&typeof t=="object").map(([t,n])=>({name:t,kind:Ot(n.kind)||null,model:n.model||(n.kind==="opus"||n.kind==="sonnet"?n.kind:null),label:n.label||null,started:n.started||null,eta:n.done?null:n.eta||null,done:n.done||null,log:n.session?.log||null,calls:Array.isArray(n.calls)?n.calls:[],verified:n.verified||null,stopped:n.stopped||null,handed_back:n.handed_back||null,...n.kind===Ke?{writes:!!n.writes,requested:n.requested||null}:{},...n.waits?{waits:n.waits}:{}})).sort((t,n)=>String(t.started||"").localeCompare(String(n.started||""))||t.name.localeCompare(n.name))}l(Ge,"agentsOf");function rt(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}l(rt,"worktreeOf");function Gn(e,t=[]){const n=e.filter(ne).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}l(Gn,"reachedPhaseOf");function Qn(e,t=[],n,s=null,r=null,a=!1){const u=e.filter(E=>E.started&&!E.done&&E.kind!==pe),d=u.every(te),p=d?u:u.filter(ne),f=p.map(E=>String(E.started||"")).sort()[0];if(f===void 0)return null;const g=t.filter(E=>E.kind==="status"&&String(E.ts)>=f&&(d||!E.planner)).sort((E,C)=>String(E.ts).localeCompare(String(C.ts))).pop(),c=p.filter(E=>E.kind!==Ke).at(-1),h=p.some(E=>E.kind===Re.install)?Re.install:c?Ot(c.kind):"claude",m=d?null:p.find(E=>E.stopped&&String(E.stopped.at)>=String(E.started))?.stopped,x=g?.ts||f,S=m?m.at:s!==null&&s>Date.parse(x)?new Date(s).toISOString():x,L=g?g.phase||g.planner?g.text.replace(": "," \xB7 "):g.text||null:d?Wn[h]:null,_=n-Date.parse(S),P=!d&&!m&&r==="held",T=d||P?null:m?!1:r,F=P?!1:T===null?_>=ln:!T;return{progress:L,phase:g?.phase||null,at:x,kind:h,planner:d,silentSince:S,stalled:F,alive:T,held:P,quiet:T===!0&&!a&&_>=dn,stopped:m?vt(m.run):null}}l(Qn,"builderOf");const at={opus:"Claude Opus",sonnet:"Claude Sonnet",codex:"Codex"},Vn=l(({model:e,kind:t})=>at[e]||at[t]||(t==="claude"?at.opus:null),"modelName"),Ot=l(e=>e==="opus"||e==="sonnet"?"claude":e,"providerOf");function Se(e){const t=Vn(e),{kind:n}=e,s=e.label?.replace(/\bbuilder\b/,"implementer").replace(/\bBuilder\b/,"Implementer"),r=l(u=>u[0].toLowerCase()+u.slice(1),"lower");if(t&&te(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"}l(Se,"agentName");const $t=l((e,t)=>!!e.resolved&&(!e.resolved_at||String(e.resolved_at)>=String(t)),"resolvedSince");function Hn(e,t,n=[],s=[],r=[]){const a=t.filter(S=>S.data?.id===e.id),u=a[a.length-1]||null,d=r.filter(S=>S.data?.question===e.id).at(-1),p=l((S,...L)=>!!S&&L.every(_=>String(S.ts)>String(_||"")),"after"),f=!!(e.resolved&&e.resolved_at&&u&&String(u.ts)>e.resolved_at),g=n.filter(S=>S.data?.question===e.id&&p(S,u?.ts,d?.ts)).at(-1),c=p(d,u?.ts,g?.ts,e.resolved_at),h=e.resolved&&!f&&!c,m=h?s.filter(S=>S.data?.question===e.id&&String(S.ts)>String(e.resolved_at||"")).at(-1):null,x=c?"open":h?"resolved":g?"requested":u?u.reply?"replied":"answered":"open";return{...e,state:x,reopened:c,request:g?{id:g.id,ts:g.ts,note:g.data.note||"",choice:g.data.choice||null}:null,reask:m?{id:m.id,ts:m.ts}:null,answers:a.map(S=>({ts:S.ts,choice:S.data.choice||null,note:S.data.note||"",files:S.data.files||[],reply:S.reply,acted:$t(e,S.ts)}))}}l(Hn,"ownerQuestion");const fe=l(e=>e.round??(Me.test(e.url||"")?1:0),"roundOf");function Xn(e,t){const n=l((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)}l(Xn,"roundSettledBy");function Yn(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=Xn(e,n);return s?{n,state:"closed",take:s.choice}:e.some(r=>fe(r)===n&&r.state!=="resolved")?{n,state:"back"}:null}l(Yn,"roundState");function zn(e){const t=e.filter(n=>Me.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}l(zn,"prototypesOf");const ve=l((e,t,n)=>{const s=e.get(t);s?s.push(n):e.set(t,[n])},"add"),xe=l((e,t)=>{const n=new Map;for(const s of e)ve(n,t(s),s);return n},"groupBy");function Jn(e,t,n=new Set){const s=new Set(e.map(u=>u.id)),r=[...e];t.some(u=>!s.has(u.topic))&&r.push({id:"",title:"Other"});const a=xe(t,u=>s.has(u.topic)?u.topic:"");return r.map(u=>{const d=a.get(u.id)||[],p=l(c=>d.filter(h=>h.state===c).length,"count"),f=d.every(c=>c.state==="resolved")||n.has(u.id)&&!p("open"),g=d.length&&f?"resolved":"open";return{...u,state:g,questions:d.map(c=>c.id),open:p("open"),answered:p("answered"),replied:p("replied"),resolved:p("resolved"),round:Yn(d)}}).filter(u=>u.questions.length)}l(Jn,"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",related:"Related to",unrelated:"Unrelated from",followup:"Follow-up","followup-of":"Follow-up of"},Et={"grill-more":"grill","prototype-request":"prototype","visual-request":"visual","reask-request":"reask"},At={"mark-ready":"Mark ready",...Object.fromEntries(Object.entries(Et).map(([e,t])=>[e,Qe[t]]))},Zn=["grill-more","prototype-request","visual-request"],eo="answers submitted",Bt={reply:"Reply",build:"Implement"},to=l(e=>Bt[e.data.intent],"intentLabel"),no=l((e,t)=>{if(e.kind!=="prototype-request"||!t.has(e.data.question))return e.data.text||(e.data.files?.length?"":A(At,e.kind))||"";const n=t.get(e.data.question),s=(n.options||[]).find(r=>r.id===e.data.choice);return[_t(t,n),s&&`${s.id}. ${s.label}`,e.data.note].filter(Boolean).join(`
3
- \u2192 `)},"actionText"),Rt=l((e,t)=>[...e.keys()].indexOf(t)+1,"numberOf"),_t=l((e,t)=>`Question ${Rt(e,t.id)} \xB7 ${t.question}`,"numbered"),oo="question reopened",Ve=l(e=>e.data?.shared_by?.login?{by:e.data.shared_by.name||e.data.shared_by.login}:{},"byOf"),so=l((e,t)=>{const n=t.get(e.card);return{...e,text:`${Qe[e.command]} ${n?`${n.ref}: ${n.title}`:e.card}`,link:`#item=${encodeURIComponent(e.card)}`}},"linkLine"),Mt=l(e=>({kind:"command",command:e,request:!0,mark:Qe[e]}),"commandLine"),ro=l((e,t)=>{if(e.kind==="item-message")return Bt[e.data.intent]?{status:to(e)}:{};const n={request:!0,command:Et[e.kind]};return Zn.includes(e.kind)?(e.data.question?t.has(e.data.question):String(e.data.text||"").trim()||e.data.files?.length)?{status:At[e.kind]}:n:e.kind==="reask-request"?n:{}},"requestOf"),lt=l(e=>[...e.filter(t=>t.who==="claude"&&!["status","agent","command",be,_e.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 It(e,t={}){const n=new Map,s=new Map;for(const c of t.items||[])ve(n,c.topic,c),s.set(c.id,c.topic);const r=new Map,a=new Map,u=new Map,d=new Map,p=new Map,f=new Map,g=new Map;e.forEach((c,h)=>{g.set(c,h),c.data?.key&&an.includes(c.kind)&&ve(r,c.data.key,c),c.kind==="owner-answer"&&s.has(c.data?.id)&&ve(a,s.get(c.data.id),c),c.kind===ut&&s.has(c.data?.question)&&ve(u,s.get(c.data.question),c),c.kind==="ask"&&d.set(c.id,c)});for(const c of e.filter(h=>h.kind==="ask-edited").sort((h,m)=>String(h.ts).localeCompare(String(m.ts)))){p.set(c.data?.id,c.data?.text);for(const h of c.data?.files||[])ve(f,c.data?.id,h)}return{byKey:r,answers:a,reopens:u,asks:d,edits:p,editFiles:f,questions:n,order:g}}l(It,"threadIndex");const it=l((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"),ao=/; its log is \S*\/\.claude\/kickflip\/logs\/([^/\s]+\.log)\.$/,lo=l(e=>{const t=!e.log&&typeof e.text=="string"?ao.exec(e.text):null;return t?{...e,text:`${e.text.slice(0,t.index)}.`,log:t[1]}:e},"withLog");function io(e,t){const n=t.run?e.find(s=>s.started===t.run):e.filter(s=>te(s)===!!t.planner&&s.started&&String(s.started)<=String(t.ts)&&(!s.done||String(t.ts)<=String(s.done))).pop();return n?{agent:Se(n),run:n.started,running:!n.done}:{}}l(io,"agentOfLine");function qt(e,{lines:t=[],events:n=[],owner:s={},working:r={},fromAsk:a=null,links:u=[],agents:d={},moved:p=[],cards:f=new Map,index:g=It(n,s)}){const c=e.startsWith("ask:")?[e.slice(4)]:[a,...p].filter(Boolean),h=[...new Set([e,...c.map(i=>`ask:${i}`)])],m=new Map((g.questions.get(`item:${e}`)||[]).map(i=>[i.id,i])),x=g.answers.get(`item:${e}`)||[],S=new Set([...m.values()].map(i=>i.round_of).filter(Boolean)),L=Ge(d).filter(ne).map(i=>String(i.started||"")).filter(Boolean),_=new Set(x.filter(i=>$t(m.get(i.data.id),i.ts)||S.has(i.data.id)||L.some(y=>y>String(i.ts))).map(i=>i.id)),P=Ge(d),T=[...Ge(d).flatMap(i=>[...i.started?[{ts:i.started,who:"claude",kind:"agent",text:`${Se(i)} started`,agent:Se(i),run:i.started,finished:!1,eta:i.done?null:i.eta,...i.log?{log:i.log}:{},...te(i)?{planner:!0}:{}}]:[],...i.done?[{ts:i.done,who:"claude",kind:"agent",text:`${Se(i)} finished`,agent:Se(i),run:i.started,finished:!0,eta:null,...te(i)?{planner:!0}:{}}]:[],...i.calls.length?[{ts:i.done||i.calls.at(-1).at,who:"claude",kind:"calls",text:Se(i),calls:i.calls.map(y=>y.text),verified:i.verified}]:[]]),...t.map(lo).map(i=>i.card?so(i,f):i).map(i=>({ts:i.ts,who:i.who||"claude",...i.log?{log:i.log}:{},...i.kind==="status"?{kind:"status",...i.planner?{planner:!0}:{},...io(P,i)}:{},...i.kind===pe||i.kind===be?{kind:i.kind}:{},...i.command?Mt(i.command):{},...i.card?{card:i.card}:{},...Object.values(_e).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}:{}})),...it(g,h.map(i=>g.byKey.get(i))).map(i=>({ts:i.ts,who:"love",via:"site",kind:i.kind,text:no(i,m),files:i.data.files||[],eventId:i.id,reply:i.reply,...ro(i,m),...Ve(i)})),...x.map(i=>{const y=[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:`${_t(m,m.get(i.data.id))}
4
- \u2192 ${y}`,files:i.data.files||[],eventId:i.id,reply:i.reply,status:eo,...Ve(i)}}),...(g.reopens.get(`item:${e}`)||[]).map(i=>({ts:i.ts,who:"love",kind:i.kind,request:!0,mark:oo,text:`Question ${Rt(m,i.data.question)} was reopened`,...Ve(i)})),...it(g,[...new Set(c)].map(i=>g.asks.has(i)?[g.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,...p.includes(i.id)?{moved:!0}:{},...Ve(i),...typeof i.data?.from?.title=="string"?{from:i.data.from.title}:{}},...i.data?.quick===!0?[{ts:i.ts,who:"love",...Mt("implement"),text:Qe.implement}]:[],...u.map(y=>({ts:i.ts,who:"claude",kind:"ask-link",text:y.folded?`Moved to ${y.ref}: ${y.title}`:`${y.became?"Became":"Added to"} ${y.ref}`,link:`#item=${encodeURIComponent(y.key)}`,html:null,via:null}))])].sort((i,y)=>String(i.ts).localeCompare(String(y.ts))),F=lt(T),{edits:E,editFiles:C}=g;for(const i of T){if(!i.eventId)continue;(i.kind==="ask"||i.kind==="item-message")&&typeof E.get(i.eventId)=="string"&&Object.assign(i,{text:E.get(i.eventId),edited:!0,files:[...i.files,...C.get(i.eventId)||[]]});const y=i.kind==="ask"&&(u.length>0||!!i.moved);i.state=i.reply?"replied":y||_.has(i.eventId)||F>String(i.ts)?"answered":A(r,i.eventId)?"working":"waiting",i.canDelete=i.kind==="item-message"&&(i.state==="working"||i.state==="waiting"),i.canEdit=i.canDelete}return co(T)}l(qt,"threadOf");const Dt=["waiting","working","answered","replied"];function co(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 Gt=Object.defineProperty;var l=(e,t)=>Gt(e,"name",{value:t,configurable:!0});import{routeRecord as A}from"../hub/routes.mjs";import Qt from"node:fs";import{ticketBadge as ct,ticketIn as Vt}from"./tickets.mjs";import{prLinks as Ht,reviewLinks as Xt,reviewTime as Yt,reviewersOf as zt}from"./review.mjs";import{remoteFromInput as Jt}from"../hub/repos.mjs";import{reviewsOf as Zt,reviewNeeds as en,reviewActions as tn}from"./auto-review.mjs";import{cardPRs as nn,hasPR as et,leastReady as on,prKeyOf as sn,prURL as rn}from"../shared/card-prs.mjs";import ut from"node:vm";import{checkWords as an,parseCheck as ln}from"../shared/owner-check.mjs";import{buildScriptOf as dn}from"./build-script.mjs";const tt=ut.createContext({});for(const e of["ui-enums.js","ui-order.js"])ut.runInContext(Qt.readFileSync(new URL(`./public/${e}`,import.meta.url),"utf8"),tt);const{BUTTON:oe,ACTION_KIND:q,BUILDER_KIND:be,PR_STATE:Z}=tt.BoardUI,{laneOrder:cn}=tt.BoardUI,un=["item-message","mark-ready","grill-more","prototype-request","visual-request","reask-request"],pt="question-reopen",ts=Object.freeze({todo:"Todo",progress:"In Progress",review:"In Review",done:"Done"}),Se="move",ft=10*6e4,pn=5*6e4,gt="build-refused",Ie=Object.freeze({ask:"builder-ask",answer:"builder-answer"}),fn=864e5,pe=l(e=>e.lane==="done"||e.lane==="asks","closedLane"),gn=l(e=>`wave:${e}`,"waveFold"),kt=7*fn,mt=l((e,t)=>({id:e,url:t?.workspace?`https://linear.app/${t.workspace}/issue/${e}`:null}),"linearRef"),kn=l((e,t)=>String(e).replace(/^S(\d+)$/,`${t||"INT"}-$1`),"internalRef"),Ke=l((e,t,n)=>Vt(n,t.ticket)?t.ticket:e.internal===!0?kn(t.id,n?.prefix):t.id,"refOf"),mn=l((e,t,n,s)=>ct(e||(t?{source:"internal",prefix:"INT"}:null),n,s)||(t?{id:String(t),url:null,source:null}:null),"ticketOf");function wn(e,t,n,s,r,a,u,d,p){return!a&&(e.lane==="done"||t?.state==="MERGED"||t?.state==="CLOSED")?"done":(n||s)&&!d?"progress":r?"review":t?.state==="OPEN"?t.checks?.state==="fail"&&(!t.isDraft||u)?"progress":"review":t||p?"progress":"todo"}l(wn,"laneOf");const hn="Lander",wt=l(e=>e.kind!==be.install&&e.label!==hn,"changesCode");function yn(e,t){return e?.state==="MERGED"||t?Z.merged:e?.state==="CLOSED"?Z.closed:e?.state==="OPEN"?e.isDraft?Z.draft:Z.open:null}l(yn,"prStateOf");const ht=l(e=>e.pr?.status===Z.closed,"unlanded"),bn=l(e=>e.some(t=>t.kind==="work")&&e.every(t=>t.lane==="done"),"finishedOf");function nt(e){const t=yt(e);return t?.done&&t.handed_back?t.handed_back:null}l(nt,"handedBackAt");const yt=l(e=>e.filter(t=>t?.started&&te(t)&&t.kind!==be.install).sort((t,n)=>String(t.started).localeCompare(String(n.started))).at(-1),"newestBuild");function Sn(e,t,n){return Object.fromEntries(e.map(s=>{const r=A(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"?A(n,s.name):null,github:s.github||null}]}))}l(Sn,"reviewOf");function vn(e,t,n,s,r,a,{held:u,needsApproval:d}){return n?.kind===be.install?"builder":s?"checking":n?"builder":u&&e==="review"?"merge":r&&e==="review"?"owner":e==="review"&&t?.state==="OPEN"&&t.isDraft?a?"ci":"ready":e==="review"?bt(t,d)?"merge":"reviewers":null}l(vn,"waitsOnOf");const bt=l((e,t)=>e?.reviewDecision==="APPROVED"||!t&&!["REVIEW_REQUIRED","CHANGES_REQUESTED"].includes(e?.reviewDecision),"reviewed"),qe=/(^|\/)prototype-[\w-]+\.html/,xn={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"},On=l(e=>`Verified: tap Merge to land it on ${e}`,"heldNext"),$n="Fix the failing CI",St="Tap Done once it is settled",En=["status","agent","ask-link","command",Se,Ie.answer],vt=l(e=>(e.who==="claude"||e.via==="github")&&!En.includes(e.kind),"countsUnread"),An=/^(?:Not merged yet: |Lander launched headless )/;function Bn({slug:e,wave:t,it:n,order:s,prs:r,repo:a,remote:u,base:d,builder:p,reachedPhase:f,agents:g,worktree:c,eta:h,thread:m,events:x,working:S,said:L,askedAt:_,topicItems:P,prSlack:T,boardReviewers:K,now:E,bumpedAt:C,holdAsk:i,closedAt:y,mergeAt:B,autoMerge:ke,skipDraft:re,tickets:U}){const V=r.map(w=>({repo:w.repo,number:w.number,url:rn(w),...w.gh||w.kept||{},status:yn(w.gh,w.merged_at)})),W=on(r.map(w=>w.gh)),N=r[W]?.gh||null,H=V[W]?.status??null,X=n.lane||null,Ne=!!y&&String(y)>String(n.lane_at||"");Ne&&(n={...n,lane:"done",closed_at:y,reopened_at:void 0});const F=[n.closed_at,...r.flatMap(w=>[w.merged_at,w.gh?.mergedAt])].filter(Boolean).map(String).sort().pop()||null,j=String(F||""),Ce=V.length>0&&V.every(w=>w.status===Z.closed),G=(n.lane==="done"||n.reopened_at||H===Z.merged)&&!Ce&&([...L.filter(w=>w.kind==="item-message").map(w=>w.ts),..._].map(String).filter(w=>w>j).sort().pop()||n.reopened_at)||void 0,me=n.owner_step&&!Ne?String(n.owner_step):null,Ye=(n.lane==="done"||H===Z.merged)&&!Ce;G&&(n={...n,next:void 0});const $e=nt(g),ze=!!G&&L.some(w=>String(w.ts)>j&&(w.state==="waiting"||w.state==="working")),we=`${e}/${n.id}`,Q=zt(t,K),ne=Object.fromEntries(Object.entries(Xt(t,n,we,P,Ht(T,r))).map(([w,J])=>[w,Yt(J.ts)])),ae=Object.values(ne).filter(Boolean).sort()[0]||null,Te=[...m.map(w=>w.ts),...x.map(w=>w.ts)].sort().pop()||null,Ee=[...m.filter(w=>vt({...w,who:w.who||"claude"})).map(w=>w.ts),...x.map(w=>w.reply?.at)].filter(Boolean).sort(),Y=!et(n)&&n.pushed?.branch?{branch:n.pushed.branch,at:n.pushed.at||null,host:u?.host||null}:null,z=!et(n)&&n.merge_ready?.branch?{branch:n.merge_ready.branch,at:n.merge_ready.at||null,base:d}:null,Ae=!!n.rebase_run&&N?.state==="OPEN",le=(n.rebased||[]).at(-1),Be=g.filter(w=>w.started&&!w.done&&te(w)),Le=!!$e&&wt(yt(g));let R=wn(n,N,Be.some(wt),Le,Y||z,!!G,re,Ae,Be.length>0||!!$e);G&&R==="todo"&&(R="progress");const Je=R==="review"&&!!ae&&E-Date.parse(ae)>kt,Re=!!me&&Ye&&R==="done";Re&&(R="review");let D=vn(R,N,p,!!$e&&R!=="done",Y,re,{held:z,needsApproval:Q.length>0});const Ze=D==="builder"&&N?.state==="OPEN"&&!N.isDraft,ie=R==="progress"&&!D&&N?.state==="OPEN"&&(!N.isDraft||re),de=Re||!!G&&R==="progress"&&!D&&!ie&&!ze;de&&(D="owner");const he=i&&String(i.ts)>String(n.held_at||""),_e=R==="todo"&&(he?!!i.data.hold:!!n.held),Pe=_e&&(he?i.data.until:n.pick_up_at)||null,je=R==="todo"||R==="progress",Ue=g.filter(te).map(w=>String(w.started||"")).filter(Boolean).sort()[0]||"",o=C&&!(Ue&&String(C)<Ue),k=m.filter(w=>(w.who||"claude")==="claude"&&!["status","command",Se].includes(w.kind)&&!An.test(w.text||"")).map(w=>String(w.ts)).sort().pop()||"",b=[B,z&&n.merge_ready.tapped].filter(Boolean).map(String).sort().pop(),v=D==="merge"&&!!b&&b>k,O=m.filter(w=>w.kind==="status"&&String(w.ts)>String(b)).sort((w,J)=>String(w.ts).localeCompare(String(J.ts))).at(-1),M=O?.text.startsWith("Waiting for ")?`${Ge.merging} \xB7 ${O.text[0].toLowerCase()}${O.text.slice(1)}`:Ge.merging,I=m.filter(w=>!["status",Se].includes(w.kind)).sort((w,J)=>String(w.ts).localeCompare(String(J.ts))).at(-1),ce=R==="todo"&&I?.kind===gt?I.text:null,ue=G||g.filter(te).map(w=>w.started).filter(Boolean).map(String).sort().pop()||null,We=N?.reviewDecision==="APPROVED",Fe=Y&&D==="owner"?`merge ${Y.branch} into ${d}`:z&&D==="merge"?v?M:On(d):D==="merge"&&!ke?v?M:(We?Ge:ot).waiting:D==="merge"&&!We?ot.auto:xn[D],ye=H===Z.draft?`Review draft PR #${V[W].number}`:null,Me=!Ae&&le?.to&&le.to===N?.headRefOid?`Rebased onto ${le.base||d}, pushed${N.mergeable==="MERGEABLE"?" \xB7 PR is clean again":""}`:null,$=Ae&&D==="checking"?g.flatMap(w=>w.calls).sort((w,J)=>String(w.at).localeCompare(String(J.at))).at(-1)?.text??null:null;return{key:we,kind:"work",order:s,wave:e,waveTitle:t.title||e,id:n.id,ref:Ke(t,n,U),title:n.title,storedLane:X,created:n.created||null,reviewSince:n.review_since||ae||n.merge_ready?.at||n.pushed?.at||n.created||null,progressSince:ue,closedAt:R==="done"?F:null,internal:t.internal===!0,repo:t.internal===!0&&!t.repo?null:a,lane:R,waitsOn:D,builder:D==="checking"?null:p,reachedPhase:f,agents:g,worktree:c??null,eta:h,working:S,reopened:!!G,awaitsDone:de,ownerStep:de?me:null,ownerCheck:de&&me&&n.owner_check?{words:an(ln(n.owner_check.check)),broken:n.owner_check.broken||null}:null,stagedAt:R==="todo"&&n.state==="staging"&&n.staged_at||null,roundAt:n.round_at||null,deps:n.deps||[],blockedUntil:n.blocked_until||null,blockReason:n.block_reason||null,held:_e,heldUntil:Pe,risky:!!n.risky,bumpable:je,bumpedAt:je&&o?C:null,holdable:R!=="done",hasNotes:!!(n.plan||String(n.notes||"").trim()),finishable:R!=="done",merging:v,refusal:ce,manualSteps:n.manual_steps||[],fromAsk:n.from_ask||null,unfiled:!!n.unfiled,next:Ze||R==="done"||D==="checking"?null:ie?$n:Me||Fe||n.next||ye||null,rebaseFailed:$,branch:n.branch||N?.headRefName||Y?.branch||null,pushed:R==="done"||Re?null:Y,mergeReady:R==="review"?z:null,stale:R!=="done"&&(Je||!!p?.stale),stalled:n.stalled?.reason||null,landStopped:R==="review"&&z&&n.merge_ready.stopped&&String(n.merge_ready.stopped)>String(n.merge_ready.tapped||"")?{words:Et("land"),at:n.merge_ready.stopped}:null,ticket:U?.source==="linear"&&!n.ticket&&n.linear?.length?null:mn(U,n.ticket,n.ticket||(t.internal?Ke(t,n,U):n.linear?.[0]),{repo:a,key:we}),ticketSource:U?.source||null,linear:(n.linear||[]).map(w=>U?.source==="linear"&&ct(U,w,{key:we,repo:a})||mt(w,U)),pr:V[W]||null,prs:V,reviewers:Q.map(w=>w.name),review:Sn(Q,N||r[W]?.kept,ne),inReviewSince:ae,threadCount:m.length+x.length,lastTs:Te,othersAt:Ee,grilling:At("grill",x,g,_),prototyping:At("prototype",x,g,_),state:n.state||null,updatedAt:N?.mergedAt||N?.updatedAt||r[W]?.merged_at||p?.at||Te||null,ask:null,fold:null,action:null,install:R==="done"?null:_n(n.install,N),waitFacts:Rn(n,{agents:g,thread:m,events:x,refusedAt:ce?I.ts:null})}}l(Bn,"workItem");function Rn(e,{agents:t,thread:n,events:s,refusedAt:r}){const a=new Set(n.filter(f=>f.kind===Ie.answer).map(f=>f.ask)),u=n.filter(f=>f.kind===Ie.ask&&!a.has(f.ask)).sort((f,g)=>String(f.ts).localeCompare(String(g.ts)))[0],d=l(f=>s.filter(g=>rt(g,f)).pop()?.ts||null,"requestAt"),p=n.filter(f=>(f.who||"claude")==="claude"&&!["status","agent","command",Se,gt].includes(f.kind)).map(f=>f.ts);return{handedBackAt:nt(t),next:e.next||null,ask:u?{at:u.ts,text:u.text}:null,grillAt:d("grill"),prototypeAt:d("prototype"),refusedAt:r,unverified:t.some(f=>f.calls.some(g=>!f.verified||String(g.at)>String(f.verified.at))),writes:[...p,...t.flatMap(f=>[f.verified?.at,f.started])].filter(Boolean).map(String).sort()}}l(Rn,"waitFactsOf");function _n(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))}}l(_n,"installOf");function Mn(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",!bt(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)}l(Mn,"mergeBlockers");const In=l((e,t)=>!Mn(e,{needsApproval:t}).length,"mergeable");function qn(e,t,n){if(pe(e))return null;const s=`#item=${encodeURIComponent(e.key)}`;if(e.stalled)return{kind:q.stalled,url:s,label:`stalled: ${e.stalled}`};if(Dn(e)){const r=t.get(e.questions.first);return qe.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.kind==="ask"&&e.waitsOn==="owner"?{kind:q.done,url:s,label:e.next||St}: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)||In(r,e.reviewers.length>0))?{kind:q.merge,url:s,label:"Merge"}:null}l(qn,"actionOf");const Dn=l(e=>!!(e.questions&&e.questions.state!=="resolved"&&e.questions.open>0),"asking"),st=new Set([q.pick,q.questions]);function xt(e){return e.questions?oe.openQuestions:null}l(xt,"actionButtonOf");const Nn=l(e=>e.kind==="work"&&e.lane==="todo"&&!e.action&&!e.unpicked,"buildableOf"),Cn=l((e,t)=>/^item:/.test(e.topic||"")&&qe.test(e.url||"")&&!(e.options||[]).some(n=>n.id===t),"picksNoTake");function Tn(e,t){const n=e.filter(a=>a.topic===`item:${t}`),r=[...n.filter(a=>qe.test(a.url||"")).flatMap(a=>a.answers.map(u=>({q:a,a:u,again:Cn(a,u.choice)}))),...n.filter(a=>a.request).map(a=>({q:a,a:a.request,again:!0}))].sort((a,u)=>String(a.a.ts).localeCompare(String(u.a.ts))).at(-1);return r?.again?{id:r.q.id,note:r.a.note.replace(/\s+/g," ").trim()}:null}l(Tn,"noTakePicked");const Ln="Prototype again",Pn=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}),jn="Merges once unblocked",Un=l(e=>`Blocked ${[e.blockedBy.length&&`by ${e.blockedBy.map(t=>t.ref).join(", ")}`,e.blockedUntil&&`until ${e.blockedUntil}`].filter(Boolean).join(" and ")}`,"mergeBlockedWords");function Wn(e,t,n){if(e.action=qn(e,t,n),e.mergeBlocked=e.action?.kind===q.merge&&e.blocked?Un(e):null,e.mergeBlocked&&Object.assign(e,{action:null,next:jn}),e.action?.kind===q.questions&&e.questions.fromAsks){Object.assign(e,{action:null,waitsOn:"owner",next:e.action.label,actionButton:xt(e),resolvedBy:null});return}e.awaitsDone&&=!e.action,e.awaitsDone||Object.assign(e,{ownerStep:null,ownerCheck:null}),e.actionButton=xt(e),e.resolvedBy=Pn[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||St,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"&&st.has(e.action.kind)?"todo":"review";Object.assign(e,{lane:r,waitsOn:e.waitsOn==="ready"?"ready":"owner",next:e.action.label}),r==="review"&&Object.assign(e,{bumpable:!1,bumpedAt:null})}l(Wn,"placeOwnerWait");const Ot=l((e,t)=>e?e!=="todo":t.some(te),"started"),$t="none set yet";function ns(e,t){const n=t.filter(s=>s?.started);return e.next||et(e)||e.pushed||e.held||e.lane==="done"||nt(n)?!1:!n.some(s=>!s.done)&&Ot(e.lane,n)}l(ns,"owesNext");function Fn(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}`}l(Fn,"askedLabel");function Kn(e,t,n,s,r,a,u){const d=t.find(y=>y.kind==="ask"&&y.eventId===e.id),p=d.files,f=(d.text||"").trim().split(`
2
+ `)[0]||p.map(y=>y.name).join(", ")||"Ask",g=d.state==="replied"||d.state==="answered",c=r?.state==="progress"&&!(String(a||"")>String(r.at||"")),h=!c&&(!!r||!!a),m=g&&!h&&!c&&u.open>0,x=g&&!h&&!c&&!m&&u.answered>0,S=h||g&&!c&&!m&&!x,L=g&&!s&&!r?.moved&&[...t.filter(y=>y.who==="claude"&&y.kind!=="ask-link"&&y.kind!=="status"&&y.kind!=="command").map(y=>y.ts),...t.map(y=>y.reply?.at)].filter(Boolean).sort().pop()||null,_=S||c||m||x,P=t.some(y=>y.who==="love"&&y.kind!=="ask"&&(y.state==="waiting"||y.state==="working")),T=t.filter(y=>y.who==="love"&&y.kind==="item-message").flatMap(De).filter(([,y])=>y==="waiting"||y==="working").map(([y])=>y),K=_?"answered":d.state,E=_?null:n||null,C=[...t.filter(vt).map(y=>y.ts),...t.map(y=>y.reply?.at)].filter(Boolean).sort(),i=[...t.map(y=>y.ts),...C,...c?[r.at]:[]].filter(Boolean).sort().pop();return{key:`ask:${e.id}`,kind:"ask",order:0,wave:null,waveTitle:null,id:e.id,ref:_?Fn(e.ts):"New",isNew:!_,title:f,storedLane:null,internal:!1,repo:null,lane:c?"progress":S?"asks":"todo",waitsOn:c&&!P?"owner":null,builder:null,agents:[],eta:null,working:t.some(y=>De(y).some(([,B])=>B==="working")),deps:[],held:!1,risky:!1,bumpable:!_,bumpedAt:E,manualSteps:[],fromAsk:null,from:null,next:c&&r.next||null,hasNotes:!1,branch:null,finishable:_&&!S,linear:[],pr:null,prs:[],reviewers:[],review:{},inReviewSince:null,reviewSince:null,progressSince:c&&r.at||null,closedAt:null,threadCount:t.length,lastTs:i,othersAt:_?C:[],grilling:!1,prototyping:!1,created:e.ts,updatedAt:_?i:e.ts,ask:{eventId:e.id,state:K,askedAt:e.ts,bumpedAt:E,edited:!!d.edited,canEdit:!_,text:d.text,files:p,answerAt:L,from:d.from||null,owed:T},fold:null,action:null}}l(Kn,"askItem");const fe="visual",Qe="helper",Gn=new Set(["grill","prototype",fe]),ee=l(e=>Gn.has(e.kind),"plans"),te=l(e=>!ee(e),"builds"),Qn={grill:"grilling",prototype:"prototyping"},Vn=l((e,t)=>`${e} ${t} by orchestrator`,"orchestratorDid"),Et=l(e=>Vn(e,"stopped"),"stoppedWords"),Hn={grill:"grill-more",prototype:"prototype-request"},rt=l((e,t)=>e.kind===Hn[t]&&!e.data?.question,"isRequest");function At(e,t,n,s){if(n.some(a=>a.kind===e&&a.started&&!a.done))return!0;const r=t.filter(a=>rt(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)))}l(At,"planningOf");const os=30*6e4;function ss(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&&rt(a,"prototype")).pop();r&&n.push({key:s.key,request:r.id,since:r.ts});for(const a of e.owner.filter(u=>u.topic===`item:${s.key}`&&u.state==="requested"))n.push({key:s.key,request:a.request.id,question:a.id,since:a.request.ts})}return n}l(ss,"prototypeWaits");function Ve(e={}){return Object.entries(e).filter(([,t])=>t&&typeof t=="object").map(([t,n])=>({name:t,kind:Bt(n.kind)||null,model:n.model||(n.kind==="opus"||n.kind==="sonnet"?n.kind:null),label:n.label||null,started:n.started||null,eta:n.done?null:n.eta||null,done:n.done||null,log:n.session?.log||null,calls:Array.isArray(n.calls)?n.calls:[],verified:n.verified||null,stopped:n.stopped||null,handed_back:n.handed_back||null,...n.kind===Qe?{writes:!!n.writes,requested:n.requested||null}:{},...n.waits?{waits:n.waits}:{}})).sort((t,n)=>String(t.started||"").localeCompare(String(n.started||""))||t.name.localeCompare(n.name))}l(Ve,"agentsOf");function at(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}l(at,"worktreeOf");function Xn(e,t=[]){const n=e.filter(te).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}l(Xn,"reachedPhaseOf");function Yn(e,t=[],n,s=null,r=null,a=!1){const u=e.filter(E=>E.started&&!E.done&&E.kind!==fe),d=u.every(ee),p=d?u:u.filter(te),f=p.map(E=>String(E.started||"")).sort()[0];if(f===void 0)return null;const g=t.filter(E=>E.kind==="status"&&String(E.ts)>=f&&(d||!E.planner)).sort((E,C)=>String(E.ts).localeCompare(String(C.ts))).pop(),c=p.filter(E=>E.kind!==Qe).at(-1),h=p.some(E=>E.kind===be.install)?be.install:c?Bt(c.kind):"claude",m=d?null:p.find(E=>E.stopped&&String(E.stopped.at)>=String(E.started))?.stopped,x=g?.ts||f,S=m?m.at:s!==null&&s>Date.parse(x)?new Date(s).toISOString():x,L=g?g.phase||g.planner?g.text.replace(": "," \xB7 "):g.text||null:d?Qn[h]:null,_=n-Date.parse(S),P=!d&&!m&&r==="held",T=d||P?null:m?!1:r,K=P?!1:T===null?_>=ft:!T;return{progress:L,phase:g?.phase||null,at:x,kind:h,planner:d,silentSince:S,stale:K,alive:T,held:P,quiet:T===!0&&!a&&_>=pn,stopped:m?Et(m.run):null}}l(Yn,"builderOf");const lt={opus:"Claude Opus",sonnet:"Claude Sonnet",codex:"Codex"},zn=l(({model:e,kind:t})=>lt[e]||lt[t]||(t==="claude"?lt.opus:null),"modelName"),Bt=l(e=>e==="opus"||e==="sonnet"?"claude":e,"providerOf");function ve(e){const t=zn(e),{kind:n}=e,s=e.label?.replace(/\bbuilder\b/,"implementer").replace(/\bBuilder\b/,"Implementer"),r=l(u=>u[0].toLowerCase()+u.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"}l(ve,"agentName");const Rt=l((e,t)=>!!e.resolved&&(!e.resolved_at||String(e.resolved_at)>=String(t)),"resolvedSince");function Jn(e,t,n=[],s=[],r=[]){const a=t.filter(S=>S.data?.id===e.id),u=a[a.length-1]||null,d=r.filter(S=>S.data?.question===e.id).at(-1),p=l((S,...L)=>!!S&&L.every(_=>String(S.ts)>String(_||"")),"after"),f=!!(e.resolved&&e.resolved_at&&u&&String(u.ts)>e.resolved_at),g=n.filter(S=>S.data?.question===e.id&&p(S,u?.ts,d?.ts)).at(-1),c=p(d,u?.ts,g?.ts,e.resolved_at),h=e.resolved&&!f&&!c,m=h?s.filter(S=>S.data?.question===e.id&&String(S.ts)>String(e.resolved_at||"")).at(-1):null,x=c?"open":h?"resolved":g?"requested":u?u.reply?"replied":"answered":"open";return{...e,state:x,reopened:c,request:g?{id:g.id,ts:g.ts,note:g.data.note||"",choice:g.data.choice||null}:null,reask:m?{id:m.id,ts:m.ts}:null,answers:a.map(S=>({ts:S.ts,choice:S.data.choice||null,note:S.data.note||"",files:S.data.files||[],reply:S.reply,acted:Rt(e,S.ts)}))}}l(Jn,"ownerQuestion");const ge=l(e=>e.round??(qe.test(e.url||"")?1:0),"roundOf");function Zn(e,t){const n=l((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)}l(Zn,"roundSettledBy");function eo(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=Zn(e,n);return s?{n,state:"closed",take:s.choice}:e.some(r=>ge(r)===n&&r.state!=="resolved")?{n,state:"back"}:null}l(eo,"roundState");function to(e){const t=e.filter(n=>qe.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}l(to,"prototypesOf");const xe=l((e,t,n)=>{const s=e.get(t);s?s.push(n):e.set(t,[n])},"add"),Oe=l((e,t)=>{const n=new Map;for(const s of e)xe(n,t(s),s);return n},"groupBy");function no(e,t,n=new Set){const s=new Set(e.map(u=>u.id)),r=[...e];t.some(u=>!s.has(u.topic))&&r.push({id:"",title:"Other"});const a=Oe(t,u=>s.has(u.topic)?u.topic:"");return r.map(u=>{const d=a.get(u.id)||[],p=l(c=>d.filter(h=>h.state===c).length,"count"),f=d.every(c=>c.state==="resolved")||n.has(u.id)&&!p("open"),g=d.length&&f?"resolved":"open";return{...u,state:g,questions:d.map(c=>c.id),open:p("open"),answered:p("answered"),replied:p("replied"),resolved:p("resolved"),round:eo(d)}}).filter(u=>u.questions.length)}l(no,"ownerTopics");const He={implement:"Implementation requested",prototype:"Prototype requested",grill:"Grill requested",visual:"Visual requested",reask:"Re-ask requested",hold:"Held",resume:"Resumed",done:"Marked done",related:"Related to",unrelated:"Unrelated from",followup:"Follow-up","followup-of":"Follow-up of"},_t={"grill-more":"grill","prototype-request":"prototype","visual-request":"visual","reask-request":"reask"},Mt={"mark-ready":"Mark ready",...Object.fromEntries(Object.entries(_t).map(([e,t])=>[e,He[t]]))},oo=["grill-more","prototype-request","visual-request"],so="answers submitted",It={reply:"Reply",build:"Implement"},ro=l(e=>It[e.data.intent],"intentLabel"),ao=l((e,t)=>{if(e.kind!=="prototype-request"||!t.has(e.data.question))return e.data.text||(e.data.files?.length?"":A(Mt,e.kind))||"";const n=t.get(e.data.question),s=(n.options||[]).find(r=>r.id===e.data.choice);return[Dt(t,n),s&&`${s.id}. ${s.label}`,e.data.note].filter(Boolean).join(`
3
+ \u2192 `)},"actionText"),qt=l((e,t)=>[...e.keys()].indexOf(t)+1,"numberOf"),Dt=l((e,t)=>`Question ${qt(e,t.id)} \xB7 ${t.question}`,"numbered"),lo="question reopened",Xe=l(e=>e.data?.shared_by?.login?{by:e.data.shared_by.name||e.data.shared_by.login}:{},"byOf"),io=l((e,t)=>{const n=t.get(e.card);return{...e,text:`${He[e.command]} ${n?`${n.ref}: ${n.title}`:e.card}`,link:`#item=${encodeURIComponent(e.card)}`}},"linkLine"),Nt=l(e=>({kind:"command",command:e,request:!0,mark:He[e]}),"commandLine"),co=l((e,t)=>{if(e.kind==="item-message")return It[e.data.intent]?{status:ro(e)}:{};const n={request:!0,command:_t[e.kind]};return oo.includes(e.kind)?(e.data.question?t.has(e.data.question):String(e.data.text||"").trim()||e.data.files?.length)?{status:Mt[e.kind]}:n:e.kind==="reask-request"?n:{}},"requestOf"),it=l(e=>[...e.filter(t=>t.who==="claude"&&!["status","agent","command",Se,Ie.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 Ct(e,t={}){const n=new Map,s=new Map;for(const c of t.items||[])xe(n,c.topic,c),s.set(c.id,c.topic);const r=new Map,a=new Map,u=new Map,d=new Map,p=new Map,f=new Map,g=new Map;e.forEach((c,h)=>{g.set(c,h),c.data?.key&&un.includes(c.kind)&&xe(r,c.data.key,c),c.kind==="owner-answer"&&s.has(c.data?.id)&&xe(a,s.get(c.data.id),c),c.kind===pt&&s.has(c.data?.question)&&xe(u,s.get(c.data.question),c),c.kind==="ask"&&d.set(c.id,c)});for(const c of e.filter(h=>h.kind==="ask-edited").sort((h,m)=>String(h.ts).localeCompare(String(m.ts)))){p.set(c.data?.id,c.data?.text);for(const h of c.data?.files||[])xe(f,c.data?.id,h)}return{byKey:r,answers:a,reopens:u,asks:d,edits:p,editFiles:f,questions:n,order:g}}l(Ct,"threadIndex");const dt=l((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"),uo=/; its log is \S*\/\.claude\/kickflip\/logs\/([^/\s]+\.log)\.$/,po=l(e=>{const t=!e.log&&typeof e.text=="string"?uo.exec(e.text):null;return t?{...e,text:`${e.text.slice(0,t.index)}.`,log:t[1]}:e},"withLog");function fo(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:ve(n),run:n.started,running:!n.done}:{}}l(fo,"agentOfLine");function Tt(e,{lines:t=[],events:n=[],owner:s={},working:r={},fromAsk:a=null,links:u=[],agents:d={},moved:p=[],cards:f=new Map,index:g=Ct(n,s)}){const c=e.startsWith("ask:")?[e.slice(4)]:[a,...p].filter(Boolean),h=[...new Set([e,...c.map(i=>`ask:${i}`)])],m=new Map((g.questions.get(`item:${e}`)||[]).map(i=>[i.id,i])),x=g.answers.get(`item:${e}`)||[],S=new Set([...m.values()].map(i=>i.round_of).filter(Boolean)),L=Ve(d).filter(te).map(i=>String(i.started||"")).filter(Boolean),_=new Set(x.filter(i=>Rt(m.get(i.data.id),i.ts)||S.has(i.data.id)||L.some(y=>y>String(i.ts))).map(i=>i.id)),P=Ve(d),T=[...Ve(d).flatMap(i=>[...i.started?[{ts:i.started,who:"claude",kind:"agent",text:`${ve(i)} started`,agent:ve(i),run:i.started,finished:!1,eta:i.done?null:i.eta,...i.log?{log:i.log}:{},...ee(i)?{planner:!0}:{}}]:[],...i.done?[{ts:i.done,who:"claude",kind:"agent",text:`${ve(i)} finished`,agent:ve(i),run:i.started,finished:!0,eta:null,...ee(i)?{planner:!0}:{}}]:[],...i.calls.length?[{ts:i.done||i.calls.at(-1).at,who:"claude",kind:"calls",text:ve(i),calls:i.calls.map(y=>y.text),verified:i.verified}]:[]]),...t.map(po).map(i=>i.card?io(i,f):i).map(i=>({ts:i.ts,who:i.who||"claude",...i.log?{log:i.log}:{},...i.kind==="status"?{kind:"status",...i.planner?{planner:!0}:{},...fo(P,i)}:{},...i.kind===fe||i.kind===Se?{kind:i.kind}:{},...i.command?Nt(i.command):{},...i.card?{card:i.card}:{},...Object.values(Ie).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}:{}})),...dt(g,h.map(i=>g.byKey.get(i))).map(i=>({ts:i.ts,who:"love",via:"site",kind:i.kind,text:ao(i,m),files:i.data.files||[],eventId:i.id,reply:i.reply,...co(i,m),...Xe(i)})),...x.map(i=>{const y=[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:`${Dt(m,m.get(i.data.id))}
4
+ \u2192 ${y}`,files:i.data.files||[],eventId:i.id,reply:i.reply,status:so,...Xe(i)}}),...(g.reopens.get(`item:${e}`)||[]).map(i=>({ts:i.ts,who:"love",kind:i.kind,request:!0,mark:lo,text:`Question ${qt(m,i.data.question)} was reopened`,...Xe(i)})),...dt(g,[...new Set(c)].map(i=>g.asks.has(i)?[g.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,...p.includes(i.id)?{moved:!0}:{},...Xe(i),...typeof i.data?.from?.title=="string"?{from:i.data.from.title}:{}},...i.data?.quick===!0?[{ts:i.ts,who:"love",...Nt("implement"),text:He.implement}]:[],...u.map(y=>({ts:i.ts,who:"claude",kind:"ask-link",text:y.folded?`Moved to ${y.ref}: ${y.title}`:`${y.became?"Became":"Added to"} ${y.ref}`,link:`#item=${encodeURIComponent(y.key)}`,html:null,via:null}))])].sort((i,y)=>String(i.ts).localeCompare(String(y.ts))),K=it(T),{edits:E,editFiles:C}=g;for(const i of T){if(!i.eventId)continue;(i.kind==="ask"||i.kind==="item-message")&&typeof E.get(i.eventId)=="string"&&Object.assign(i,{text:E.get(i.eventId),edited:!0,files:[...i.files,...C.get(i.eventId)||[]]});const y=i.kind==="ask"&&(u.length>0||!!i.moved);i.state=i.reply?"replied":y||_.has(i.eventId)||K>String(i.ts)?"answered":A(r,i.eventId)?"working":"waiting",i.canDelete=i.kind==="item-message"&&(i.state==="working"||i.state==="waiting"),i.canEdit=i.canDelete}return go(T)}l(Tt,"threadOf");const Lt=["waiting","working","answered","replied"];function go(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
5
 
6
- `),files:s.flatMap(r=>r.files),state:s.map(r=>r.state).sort((r,a)=>Dt.indexOf(r)-Dt.indexOf(a))[0],reply:void 0,answers:s.map(r=>({eventId:r.eventId,state:r.state,reply:r.reply,text:r.text}))}]})}l(co,"foldSubmits");const Ie=l(e=>(e.answers||[e]).filter(t=>t.eventId).map(t=>[t.eventId,t.state]),"stateEntries");function uo(e,t,n,s,r,a){const u=l((c,h)=>{const m=a.filter(S=>S.kind==="wave-triage"&&S.data?.[c]===h).pop(),x=r.filter(S=>S.topic===`${c}:${h}`&&(S.state==="open"||S.state==="answered")).length;return{running:!!(m&&!m.reply),questions:x}},"triageOf"),d=new Map(s.map(c=>[c.key,c])),p=l(c=>c.filter(h=>h.lane==="done"&&!kt(h)).length,"merged"),f=n.map(c=>({l:c,cards:c.keys.map(h=>d.get(h)).filter(Boolean)})).map(c=>({...c,open:!c.l.finished_at&&c.cards.some(h=>h.lane!=="done")})).sort((c,h)=>Number(c.open)-Number(h.open));for(const{l:c,cards:h}of f)for(const m of h)m.lineup={slug:c.slug,title:c.title};const g=f.filter(c=>c.open).map(({l:c,cards:h})=>({kind:"lineup",slug:c.slug,title:c.title,goal:c.goal,keys:h.map(m=>m.key),phases:c.phases.map(m=>m.filter(x=>d.has(x))).filter(m=>m.length),next:po(h),merged:p(h),total:h.length,triage:u("lineup",c.slug)}));return e.fromWaves?[...g,...t.filter(c=>!c.finished).map(c=>({w:c,cards:s.filter(h=>h.wave===c.slug&&h.kind!=="ask")})).filter(({cards:c})=>c.length).map(({w:c,cards:h})=>({kind:"wave",slug:c.slug,title:c.title,goal:null,keys:h.map(m=>m.key),phases:null,next:null,merged:p(h),total:h.length,triage:u("wave",c.slug)}))]:g}l(uo,"waveBandsOf");function po(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.state||"not ready";return{key:n.key,ref:n.ref,ready:s,word:r}}l(po,"nextOf");function es(e,t,n,s){const r=e.waveBands.find(c=>c.kind==="lineup"&&c.slug===t);if(!r)return null;const a=new Map(e.items.map(c=>[c.key,c.lane])),u=r.keys.filter(c=>a.get(c)==="todo"),d=u.indexOf(n),p=d+s;if(d<0||p<0||p>=u.length)return null;[u[d],u[p]]=[u[p],u[d]];let f=0;const g=r.keys.map(c=>a.get(c)==="todo"?u[f++]:c);return f=0,r.phases.map(c=>g.slice(f,f+=c.length))}l(es,"lineupMoved");const fo=l(e=>e.phases||e.items.map(t=>[t]),"lineupPhases");function ts(e){const{owner:t={},topicItems:n={},events:s=[],threads:r={},prs:a={},working:u={}}=e,d=e.now??Date.now(),p=It(s,t),f=new Set(s.filter(o=>o.kind==="ask-withdrawn"&&!o.data?.key).map(o=>o.data?.id)),g=new Map(s.filter(o=>o.kind==="ask"&&!f.has(o.id)).map(o=>[o.id,o])),c=l((o,k)=>new Map(s.filter(b=>b.kind===o).sort((b,v)=>String(b.ts).localeCompare(String(v.ts))).map(b=>[b.data?.[k],b.ts])),"latest"),h=c("ask-bump","id"),m=c("card-unbump","key"),x=new Map([...c("card-bump","key")].filter(([o,k])=>!(String(m.get(o)||"")>String(k)))),S=c("card-done","key"),L=c("card-merge","key"),_=e.autoMerge===!0,P=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])),F=new Map,E=new Map(e.waves.flatMap(({slug:o,wave:k})=>k.items.map(b=>[`${o}/${b.id}`,{ref:We(k,b,P),title:b.title}]))),C=e.askLinks||{},i=new Map;for(const[o,k]of Object.entries(C))g.has(o)&&i.set(k,[...i.get(k)||[],o]);const y=[],B=[],ge=new Map,re=new Map,U=new Map,H=new Map,K=e.github?null:Ht(e.remote);for(const{slug:o,wave:k,agents:b={}}of e.waves){const v=e.github||k.repo||null;y.push({slug:o,title:k.title||o,status:k.status||null,internal:!!k.internal,finished:!1,reviewers:(k.reviewers||[]).map(O=>O.name)}),k.items.forEach((O,M)=>{const I=`${o}/${O.id}`,ye=(i.get(I)||[]).filter($=>$!==O.from_ask),de=[O.from_ask,...ye].filter($=>g.has($)),Je=new Set(de.map($=>`ask:${$}`)),Ue=[...A(r,I)||[],...de.flatMap($=>A(r,`ask:${$}`)||[])];O.from_ask&&F.set(O.from_ask,[...F.get(O.from_ask)||[],{key:I,ref:We(k,O,P),became:!0}]);const w=qt(I,{lines:Ue,events:s,owner:t,working:u,fromAsk:O.from_ask||null,moved:ye,cards:E,index:p});for(const $ of w)for(const[Be,jt]of Ie($))ge.set(Be,jt);re.set(I,lt(w)),B.push(On({slug:o,wave:k,it:O,order:M,repo:v,remote:K,base:e.branch||"main",prs:Jt(O,v).map($=>({...$,gh:A(a,en($))||null})),...($=>({agents:$,builder:Qn($,A(r,I),d,e.moved?.[rt(A(b,O.id))]??null,A(e.alive,I)??null,!!e.commandRunning?.[rt(A(b,O.id))]),reachedPhase:Gn($,A(r,I)),eta:$.map(Be=>Be.eta).filter(Boolean).sort().pop()||null}))(Ge(A(b,O.id))),worktree:rt(A(b,O.id))??O.worktree?.path??null,thread:Ue,events:[...it(p,[I,...Je].map($=>p.byKey.get($))),...de.map($=>g.get($))],working:w.some($=>Ie($).some(([,Be])=>Be==="working")),said:w.filter($=>$.who==="love"&&($.kind==="item-message"||$.kind==="owner-answer")),askedAt:(p.questions.get(`item:${I}`)||[]).filter($=>$.asked_at).map($=>$.asked_at),topicItems:n,prSlack:e.prSlack||{},boardReviewers:e.reviewers||[],now:d,bumpedAt:x.get(I),holdAsk:T.get(I),closedAt:S.get(I),mergeAt:L.get(I),autoMerge:_,skipDraft:e.skipDraft===!0,tickets:P}));const V=B[B.length-1];H.set(I,{parent:O.parent?Ct(o,O.parent):null,related:(O.related||[]).map($=>Ct(o,$))}),V.lane==="done"&&U.set(I,V.closedAt||String(V.updatedAt||""))})}const D=new Map(B.map(o=>[o.key,o])),X=new Map,Y=e.try||{},qe=e.autoBuild===!0,W=["web","app"].includes(Y.kind)?Y.kind:e.waves.some(({wave:o})=>o.items.some(k=>k.install?.url))?"app":"web";for(const o of B){if(o.kind!=="work"||o.lane==="done")continue;const k=A(Y.cards,o.key)||{},b=k.head||o.pr?.headRefOid,v=W==="web"?k.sha:o.install?.sha,O=o.builder?.kind===Re.install?"running":k.state||(W==="app"&&o.install?"ready":null);o.try={kind:W,state:O,stale:!!(v&&b&&!String(b).toLowerCase().startsWith(String(v).toLowerCase())),url:W==="web"?null:o.install?.url||null,port:W==="web"&&O==="ready"&&k.https||null,sha:v||null,changes:k.ahead>0,script:k.script===!0,autoBuild:qe}}const j=Y.main,De=j?.script&&j.sha?{slug:j.slug,branch:j.branch||"main",sha:j.sha.slice(0,7),at:j.at,kind:W,state:j.state||null,url:W==="web"?null:e.main?.install?.url||null,port:W==="web"&&j.state==="ready"&&j.https||null,pull:Y.pull?{running:Y.pull.running===!0,error:Y.pull.error||null}:null,script:sn(j.target)}:null,G=e.askProgress||{},ke=c("ask-done","id"),He=xe(s.filter(o=>o.kind==="owner-answer"),o=>o.data?.id),Ne=xe(s.filter(o=>o.kind==="prototype-request"),o=>o.data?.question),Xe=xe(s.filter(o=>o.kind==="reask-request"),o=>o.data?.question),me=xe(s.filter(o=>o.kind===ut),o=>o.data?.question),Q=(t.items||[]).map(o=>Hn(o,He.get(o.id)||[],Ne.get(o.id)||[],Xe.get(o.id)||[],me.get(o.id)||[])),oe=new Map(Q.map(o=>[o.id,o])),ae=xe(Q,o=>o.topic),Ce=l(o=>{const k=ae.get(o)||[];return{open:k.filter(b=>b.state==="open").length,answered:k.filter(b=>b.state==="answered").length}},"pendingOn");for(const o of g.values()){const k=`ask:${o.id}`,b=D.get(A(C,o.id)),v=[...F.get(o.id)||[]],O=b&&!v.some(I=>I.key===b.key);if(O&&v.push({key:b.key,ref:b.ref,title:b.title,became:!1,folded:!0}),v.length&&!O){X.set(k,v[0].key);continue}if(!O&&A(C,o.id)){X.set(k,A(C,o.id));continue}const M=qt(k,{lines:A(r,k)||[],events:s,working:u,links:v,index:p});for(const I of M)for(const[ye,de]of Ie(I))ge.set(ye,de);re.set(k,lt(M)),B.push(jn(o,M,h.get(o.id),v.length>0||!!A(C,o.id),A(G,o.id),ke.get(o.id),Ce(`item:${k}`)))}for(const o of y){const k=B.filter(b=>b.wave===o.slug);if(o.finished=mn(k),o.finished)for(const b of k)b.lane==="done"&&(b.fold=un(o.slug))}for(const o of B)o.lineup=null;const Oe=new Set((t.topics||[]).map(o=>o.id)),z=e.waves.flatMap(({slug:o,wave:k})=>(k.lineups||[]).map(b=>{const v=fo(b).map(O=>O.map(M=>`${o}/${M}`));return{...b,phases:v,keys:v.flat()}})),J=l(()=>Jn([...t.topics||[],...B.filter(o=>!Oe.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}:{}})),...z.filter(o=>!Oe.has(`lineup:${o.slug}`)).map(o=>({id:`lineup:${o.slug}`,title:o.title,summary:"Wave triage."})),...(e.fromWaves?y:[]).filter(o=>!Oe.has(`wave:${o.slug}`)).map(o=>({id:`wave:${o.slug}`,title:o.title,summary:"Wave triage."}))],Q,new Set(B.filter(ue).map(o=>`item:${o.key}`))),"topicsOf"),$e=new Map(J().map(o=>[o.id,o]));for(const o of B){const k=$e.get(`item:${o.key}`);if(!k){o.questions=null,o.prototypes=null;continue}const b=k.questions.filter(M=>oe.get(M)?.state!=="resolved"),v=k.questions.find(M=>oe.get(M)?.state==="open")||b[0]||k.questions[0],O=k.questions.map(M=>oe.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:v,askedAt:O.map(M=>M.asked_at).filter(Boolean).sort(),fromAsks:O.length>0&&O.every(M=>M.ask)},o.prototypes=zn(k.questions.map(M=>oe.get(M)))}const le=new Map(B.map(o=>[o.key,o]));for(const o of B){const k=o.kind==="work"&&o.lane!=="done";o.blockedBy=k?o.deps.map(v=>le.get(String(v).includes("/")?v:`${o.wave}/${v}`)).filter(v=>v&&v.lane!=="done"&&!v.reopened).map(Nt):[],o.blockedUntil=k?o.blockedUntil??null:null,o.blockReason=k?o.blockReason??null:null,o.blocked=o.blockedBy.length>0||!!o.blockedUntil,Ln(o,oe,_),o.lane==="todo"&&o.kind==="work"&&!o.held&&bt(o.storedLane,o.agents)&&(o.lane="progress"),o.unpicked=o.kind==="work"&&o.lane==="todo"&&!!qn(Q,o.key),o.unpicked&&(o.state="prototyping"),o.unpicked&&!o.action&&(o.next=Dn),o.buildable=Mn(o);const b=o.kind==="work"&&o.lane==="todo";o.unsorted=b&&(!!o.fromAsk||o.unfiled)&&!o.state,o.ready=b&&o.state==="ready"&&!o.blocked&&!o.action,o.staging=b&&o.state==="staging"}mo(B,le,H);const R=new Map((t.items||[]).map(o=>[o.id,o.topic])),we=l(o=>o.kind==="owner-answer"?R.has(o.data?.id)?/^item:(.+)$/.exec(R.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"),N=l(o=>{const k=we(o);return X.get(k)||k},"cardOf"),Ye=l(o=>{const k=o.startsWith("ask:")?o.slice(4):null,b=k&&A(G,k)?.state==="done"?A(G,k).at:null;return[S.get(o),k&&ke.get(k),b,U.get(o)].filter(Boolean).sort().pop()||""},"closedAt"),Te=l(o=>{const k=N(o);return k===null?!o.reply:!le.has(k)||!(Ye(k)<String(A(u,o.id)))?!1:ge.has(o.id)?ge.get(o.id)==="working":!o.reply&&!X.has(we(o))&&!(String(re.get(k)||"")>String(o.ts))},"unanswered"),Z=s.filter(o=>A(u,o.id)&&Te(o)).map(o=>({id:o.id,kind:o.kind,key:N(o),data:o.data,since:A(u,o.id)}));for(const o of B)o.working=Z.some(k=>k.key===o.key),o.kind==="work"&&o.lane==="progress"&&!o.builder&&!o.next&&o.waitsOn!=="checking"&&!o.working&&(o.next=St),o.sortAt=wo[o.lane](o)||(["review","stale"].includes(o.lane)?null:o.updatedAt)||null;for(const o of B){if(o.kind!=="work")continue;const k=(ae.get(`item:${o.key}`)||[]).filter(v=>v.state==="answered").map(v=>v.answers.at(-1).ts).sort().pop(),b=Z.filter(v=>v.key===o.key).map(v=>String(v.since)).sort()[0];o.orchestrator=["progress","todo","stale"].includes(o.lane)||o.waitsOn==="checking"||o.lane==="review"&&o.builder?.stalled?No(o,o.waitFacts,{answeredAt:k,workingSince:b}):null,delete o.waitFacts}Bo(B,s),B.sort(rn);const Le=uo(e,y,z,B,Q,s),ie=J(),Pe=Xt({on:e.autoReview,incoming:e.incoming,records:e.reviewRecords,events:s,now:d,refs:new Map(B.filter(o=>o.kind==="work").map(o=>[o.key,o.ref]))}),he=[...yo(B,ie,Q),...Yt(Pe)],Ee=new Map(he.filter(o=>o.questions).map((o,k)=>[o.topic||`item:${o.key}`,k])),je=l(o=>Ee.get(o.id)??Ee.size,"rankOf");ie.sort((o,k)=>je(o)-je(k));const ze=new Set(he.filter(o=>o.questions&&o.progress).map(o=>`item:${o.key}`));for(const o of ie)ze.has(o.id)&&(o.progress=!0);const Ae=B.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 go({kind:W,main:De,remote:K&&{host:K.host,name:`${K.host}/${K.path}`},waves:y,waveBands:Le,items:B,moved:Object.fromEntries(X),answers:Ae,owner:Q,ownerTopics:ie,reviews:Pe,needs:he,working:Z.length,workingOn:Z,checks:ho(e,d)},e)}l(ts,"buildBoard");function go(e,{listen:t,prompts:n}={}){const s=[...So(n),...e.needs];return{...e,needs:s,needsSummary:vo(s),checks:{...e.checks,watcher:{listening:"running",asleep:"asleep",waking:"waking"}[t]||"stopped"}}}l(go,"withHub");const Nt=l(e=>({key:e.key,ref:e.ref}),"linkOf"),Ct=l((e,t)=>String(t).includes("/")?String(t):`${e}/${t}`,"linkKey"),ko=l((e,t)=>e.ref.localeCompare(t.ref,void 0,{numeric:!0}),"byRef");function mo(e,t,n){const s=new Map,r=new Map,a=new Map,u=l((p,f,g)=>p.set(f,[...p.get(f)||[],g]),"add");for(const[p,f]of n){f.parent&&u(s,f.parent,p);for(const g of f.related)g!==p&&(u(r,p,g),u(r,g,p))}for(const p of e)for(const f of p.blockedBy||[])u(a,f.key,p.key);const d=l((p=[])=>[...new Set(p)].map(f=>t.get(f)).filter(Boolean).map(Nt).sort(ko),"cards");for(const p of e){const f=n.get(p.key)?.parent;p.parent=f?{key:f,ref:t.get(f)?.ref||f.slice(f.indexOf("/")+1)}:null,p.children=d(s.get(p.key)),p.related=d(r.get(p.key)),p.waitsOnIt=d(a.get(p.key))}}l(mo,"linkCards");const wo={todo:l(e=>e.created,"todo"),progress:l(e=>e.progressSince,"progress"),review:l(e=>e.reviewSince,"review"),stale:l(e=>e.reviewSince,"stale"),done:l(e=>e.closedAt,"done"),asks:l(e=>e.ask.answerAt,"asks")},se=l((e,t)=>`${e} ${t}${e===1?"":"s"}`,"plural");function ho({checked:e,watcher:t,replyAt:n,threads:s={},working:r={},waves:a=[]},u){const d=Number(e?.every)||30,f=[n,t?.since,...Object.values(r),...Object.values(s).flat().filter(c=>(c.who||"claude")==="claude").map(c=>c.ts),...a.flatMap(c=>Object.values(c.agents||{}).flatMap(h=>Object.values(h||{})).map(h=>h?.done||h?.started))].filter(c=>typeof c=="string"&&!Number.isNaN(Date.parse(c))).reduce((c,h)=>c&&Date.parse(c)>Date.parse(h)?c:h,null),g=!!e?.idle;return{at:e?.at||null,every:g?null:d,idle:g,late:!g&&!!e?.at&&u-Date.parse(e.at)>2*d*6e4,claudeAt:f}}l(ho,"checksOf");function yo(e,t,n){const s=new Set(e.map(d=>d.questions?.topic).filter(Boolean)),r=l(d=>d.find(p=>n.find(f=>f.id===p)?.state==="open")||d[0],"firstOpen"),a=t.filter(d=>d.state!=="resolved"&&d.open>0&&!s.has(d.id)).map(d=>{const p=`questions.html#${r(d.questions)}`,f=/^(?:lineup|wave):(.+)$/.exec(d.id)?.[1]||null;return{kind:"Question",title:d.title,text:`${d.title} \xB7 ${se(d.open,"question")}`,url:p,topic:d.id,band:f,questions:{open:d.open,url:p},ready:null,since:n.filter(g=>g.topic===d.id&&g.state==="open").map(g=>g.asked_at).filter(Boolean).sort()[0]||null}}),u=e.map(d=>{const p=d.questions&&d.questions.state!=="resolved"&&d.questions.open>0?{open:d.questions.open,url:`questions.html#${d.questions.first}`}:null,f=d.waitsOn==="ready"?d.pr.url:null,g=!p&&!f&&d.action?{label:d.action.label,url:d.action.url}:null;if(!p&&!f&&!g)return null;const c=`${d.ref}: ${d.title}`,h=[c,p&&se(p.open,"question"),f&&"Ready on GitHub",g?.label].filter(Boolean).join(" \xB7 ");return{kind:p?"Question":f?"Tap Ready":"Action needed",title:c,text:h,url:`#item=${encodeURIComponent(d.key)}`,key:d.key,questions:p,ready:f,action:g,since:d.created||null,progress:d.lane==="progress"}}).filter(Boolean);return[...a,...u].sort(bo)}l(yo,"needsOf");const bo=l((e,t)=>!!t.progress-!!e.progress||(e.since&&t.since?e.since.localeCompare(t.since):!!t.since-!!e.since),"oldestFirst");function So(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}})}l(So,"promptLines");function vo(e){const t=e.filter(a=>a.prompt).length,n=e.reduce((a,u)=>a+(u.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 ")}l(vo,"needsSummary");function ns(e){const t=l((...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","stale"),progress:t("progress"),todo:t("todo")},building:e.items.some(n=>(n.agents||[]).some(s=>!s.done))}}l(ns,"needsAnswer");const xo=l(e=>e.buildable&&e.state==="ready"&&!e.held&&!e.blocked,"pullableCard"),Oo=10;function $o(e,{cap:t=Oo,pulled:n=[]}={}){const s=e.filter(m=>!ue(m)),r=l((m,x)=>m.started&&!m.done&&ne(m)&&!(m.kind===Ke&&!m.writes)&&!(m.waits&&x.some(S=>S.name===m.waits&&!S.done)),"counts"),a=s.reduce((m,x)=>m+(x.agents||[]).filter((S,L,_)=>r(S,_)).length,0),u=s.filter(m=>xo(m)&&!m.working).map(m=>m.key),d=s.filter(m=>n.includes(m.key)&&(u.includes(m.key)||m.buildable&&m.working)).map(m=>m.key),p=s.flatMap(m=>(m.agents||[]).filter(x=>x.kind===Ke&&!x.started&&!x.done).map(x=>({key:m.key,name:x.name,writes:x.writes}))),f=Math.max(0,t-a-d.length),g=p.filter(m=>m.writes),c=[...p.filter(m=>!m.writes),...g.slice(0,f)].map(({key:m,name:x})=>({key:m,name:x})),h=u.filter(m=>!d.includes(m)).slice(0,Math.max(0,f-g.length));return{idle:!a&&!s.some(m=>m.waitsOn==="checking"),pullable:u,waiting:d,pulls:h,helpers:c}}l($o,"pullOf");function os(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(u=>u.state==="OPEN"&&u.mergeable==="CONFLICTING"&&u.repo===s.repo&&u.headRefOid),a=r&&`${r.headRefOid}@${n}`;return r&&A(t,s.key)!==a?[{key:s.key,pr:r.number,branch:r.headRefName,conflict:a}]:[]})}l(os,"rebasesDue");const Eo=3;function ss(e,{cap:t=Eo}={}){const n=e.filter(a=>!ue(a)),s=n.filter(a=>(a.agents||[]).some(u=>te(u)&&u.started&&!u.done)).length;return n.filter(a=>a.buildable&&!["ready","staging"].includes(a.state)&&!a.unsorted&&!a.blocked&&!a.held&&!a.working&&!a.grilling&&!a.prototyping&&!a.questions&&!(a.agents||[]).some(u=>te(u)&&u.kind!==pe)).slice(0,Math.max(0,t-s)).map(a=>a.key)}l(ss,"arrivalsOf");function rs(e){return e.filter(t=>t.kind==="work"&&t.lane==="todo"&&t.roundAt&&["grilling","prototyping"].includes(t.state)&&!(t.agents||[]).some(n=>te(n)&&n.kind!==pe&&n.started&&(String(n.started)>=String(t.roundAt)||!n.done))).map(t=>({key:t.key,kind:t.state==="grilling"?"grill":"prototype"}))}l(rs,"roundsDue");const Ao=l(e=>`${pe}-${e.id}`,"visualAgent");function as(e,t){const n=[];for(const s of e){const r=s.agents||[];if(r.some(d=>d.kind===pe&&d.started&&!d.done))continue;const a=new Set(r.map(d=>d.name)),u=t.find(d=>d.kind==="visual-request"&&d.data?.key===s.key&&!a.has(Ao(d)));u&&n.push({key:s.key,request:u})}return n}l(as,"visualsDue");const Tt=l(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 Lt(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}l(Lt,"unsorted");function Pt(e,t){const n=new Map(e.map(r=>[r.key,r])),s=new Set(t.filter(r=>r.kind==="intake-handoff").map(r=>r.data?.event));return t.flatMap(r=>{const a=Tt(r);return a&&!s.has(r.id)&&Lt(n,r,a)?[{event:r.id,key:a,kind:r.kind}]:[]})}l(Pt,"intakeOpen");function ls(e,t,n){return Object.values(n).some(s=>!s.ended)?null:Pt(e,t).find(s=>!A(n,s.event))??null}l(ls,"intakeDue");function is(e,t,n){return Pt(e,t).filter(s=>A(n,s.event)?.ended)}l(is,"intakeStranded");function Bo(e,t){const n=new Map(e.map(r=>[r.key,r])),s=new Map(t.map(r=>[r.id,r]));for(const r of t.filter(a=>a.kind==="intake-handoff")){const a=s.get(r.data?.event),u=a&&Tt(a);!u||!Lt(n,a,u)||(n.get(u).orchestrator={kind:"intake",since:String(r.ts),picked:null,next:`Sort what intake triage handed you: ${r.data.text}`})}}l(Bo,"intakeWaits");function Ro(e){return e.filter(t=>t.lane==="todo"||t.lane==="stale").map(t=>JSON.stringify([t.key,t.title,t.deps||[]])).sort().join(`
7
- `)}l(Ro,"todoOf");function ds(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(`
6
+ `),files:s.flatMap(r=>r.files),state:s.map(r=>r.state).sort((r,a)=>Lt.indexOf(r)-Lt.indexOf(a))[0],reply:void 0,answers:s.map(r=>({eventId:r.eventId,state:r.state,reply:r.reply,text:r.text}))}]})}l(go,"foldSubmits");const De=l(e=>(e.answers||[e]).filter(t=>t.eventId).map(t=>[t.eventId,t.state]),"stateEntries");function ko(e,t,n,s,r,a){const u=l((c,h)=>{const m=a.filter(S=>S.kind==="wave-triage"&&S.data?.[c]===h).pop(),x=r.filter(S=>S.topic===`${c}:${h}`&&(S.state==="open"||S.state==="answered")).length;return{running:!!(m&&!m.reply),questions:x}},"triageOf"),d=new Map(s.map(c=>[c.key,c])),p=l(c=>c.filter(h=>h.lane==="done"&&!ht(h)).length,"merged"),f=n.map(c=>({l:c,cards:c.keys.map(h=>d.get(h)).filter(Boolean)})).map(c=>({...c,open:!c.l.finished_at&&c.cards.some(h=>h.lane!=="done")})).sort((c,h)=>Number(c.open)-Number(h.open));for(const{l:c,cards:h}of f)for(const m of h)m.lineup={slug:c.slug,title:c.title};const g=f.filter(c=>c.open).map(({l:c,cards:h})=>({kind:"lineup",slug:c.slug,title:c.title,goal:c.goal,keys:h.map(m=>m.key),phases:c.phases.map(m=>m.filter(x=>d.has(x))).filter(m=>m.length),next:mo(h),merged:p(h),total:h.length,triage:u("lineup",c.slug)}));return e.fromWaves?[...g,...t.filter(c=>!c.finished).map(c=>({w:c,cards:s.filter(h=>h.wave===c.slug&&h.kind!=="ask")})).filter(({cards:c})=>c.length).map(({w:c,cards:h})=>({kind:"wave",slug:c.slug,title:c.title,goal:null,keys:h.map(m=>m.key),phases:null,next:null,merged:p(h),total:h.length,triage:u("wave",c.slug)}))]:g}l(ko,"waveBandsOf");function mo(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.state||"not ready";return{key:n.key,ref:n.ref,ready:s,word:r}}l(mo,"nextOf");function rs(e,t,n,s){const r=e.waveBands.find(c=>c.kind==="lineup"&&c.slug===t);if(!r)return null;const a=new Map(e.items.map(c=>[c.key,c.lane])),u=r.keys.filter(c=>a.get(c)==="todo"),d=u.indexOf(n),p=d+s;if(d<0||p<0||p>=u.length)return null;[u[d],u[p]]=[u[p],u[d]];let f=0;const g=r.keys.map(c=>a.get(c)==="todo"?u[f++]:c);return f=0,r.phases.map(c=>g.slice(f,f+=c.length))}l(rs,"lineupMoved");const wo=l(e=>e.phases||e.items.map(t=>[t]),"lineupPhases");function as(e){const{owner:t={},topicItems:n={},events:s=[],threads:r={},prs:a={},working:u={}}=e,d=e.now??Date.now(),p=Ct(s,t),f=new Set(s.filter(o=>o.kind==="ask-withdrawn"&&!o.data?.key).map(o=>o.data?.id)),g=new Map(s.filter(o=>o.kind==="ask"&&!f.has(o.id)).map(o=>[o.id,o])),c=l((o,k)=>new Map(s.filter(b=>b.kind===o).sort((b,v)=>String(b.ts).localeCompare(String(v.ts))).map(b=>[b.data?.[k],b.ts])),"latest"),h=c("ask-bump","id"),m=c("card-unbump","key"),x=new Map([...c("card-bump","key")].filter(([o,k])=>!(String(m.get(o)||"")>String(k)))),S=c("card-done","key"),L=c("card-merge","key"),_=e.autoMerge===!0,P=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])),K=new Map,E=new Map(e.waves.flatMap(({slug:o,wave:k})=>k.items.map(b=>[`${o}/${b.id}`,{ref:Ke(k,b,P),title:b.title}]))),C=e.askLinks||{},i=new Map;for(const[o,k]of Object.entries(C))g.has(o)&&i.set(k,[...i.get(k)||[],o]);const y=[],B=[],ke=new Map,re=new Map,U=new Map,V=new Map,W=e.github?null:Jt(e.remote);for(const{slug:o,wave:k,agents:b={}}of e.waves){const v=e.github||k.repo||null;y.push({slug:o,title:k.title||o,status:k.status||null,internal:!!k.internal,finished:!1,reviewers:(k.reviewers||[]).map(O=>O.name)}),k.items.forEach((O,M)=>{const I=`${o}/${O.id}`,ce=(i.get(I)||[]).filter($=>$!==O.from_ask),ue=[O.from_ask,...ce].filter($=>g.has($)),We=new Set(ue.map($=>`ask:${$}`)),Fe=[...A(r,I)||[],...ue.flatMap($=>A(r,`ask:${$}`)||[])];O.from_ask&&K.set(O.from_ask,[...K.get(O.from_ask)||[],{key:I,ref:Ke(k,O,P),became:!0}]);const ye=Tt(I,{lines:Fe,events:s,owner:t,working:u,fromAsk:O.from_ask||null,moved:ce,cards:E,index:p});for(const $ of ye)for(const[w,J]of De($))ke.set(w,J);re.set(I,it(ye)),B.push(Bn({slug:o,wave:k,it:O,order:M,repo:v,remote:W,base:e.branch||"main",prs:nn(O,v).map($=>({...$,gh:A(a,sn($))||null})),...($=>({agents:$,builder:Yn($,A(r,I),d,e.moved?.[at(A(b,O.id))]??null,A(e.alive,I)??null,!!e.commandRunning?.[at(A(b,O.id))]),reachedPhase:Xn($,A(r,I)),eta:$.map(w=>w.eta).filter(Boolean).sort().pop()||null}))(Ve(A(b,O.id))),worktree:at(A(b,O.id))??O.worktree?.path??null,thread:Fe,events:[...dt(p,[I,...We].map($=>p.byKey.get($))),...ue.map($=>g.get($))],working:ye.some($=>De($).some(([,w])=>w==="working")),said:ye.filter($=>$.who==="love"&&($.kind==="item-message"||$.kind==="owner-answer")),askedAt:(p.questions.get(`item:${I}`)||[]).filter($=>$.asked_at).map($=>$.asked_at),topicItems:n,prSlack:e.prSlack||{},boardReviewers:e.reviewers||[],now:d,bumpedAt:x.get(I),holdAsk:T.get(I),closedAt:S.get(I),mergeAt:L.get(I),autoMerge:_,skipDraft:e.skipDraft===!0,tickets:P}));const Me=B[B.length-1];V.set(I,{parent:O.parent?jt(o,O.parent):null,related:(O.related||[]).map($=>jt(o,$))}),Me.lane==="done"&&U.set(I,Me.closedAt||String(Me.updatedAt||""))})}const N=new Map(B.map(o=>[o.key,o])),H=new Map,X=e.try||{},Ne=e.autoBuild===!0,F=["web","app"].includes(X.kind)?X.kind:e.waves.some(({wave:o})=>o.items.some(k=>k.install?.url))?"app":"web";for(const o of B){if(o.kind!=="work"||o.lane==="done")continue;const k=A(X.cards,o.key)||{},b=k.head||o.pr?.headRefOid,v=F==="web"?k.sha:o.install?.sha,O=o.builder?.kind===be.install?"running":k.state||(F==="app"&&o.install?"ready":null);o.try={kind:F,state:O,stale:!!(v&&b&&!String(b).toLowerCase().startsWith(String(v).toLowerCase())),url:F==="web"?null:o.install?.url||null,port:F==="web"&&O==="ready"&&k.https||null,sha:v||null,changes:k.ahead>0,script:k.script===!0,autoBuild:Ne}}const j=X.main,Ce=j?.script&&j.sha?{slug:j.slug,branch:j.branch||"main",sha:j.sha.slice(0,7),at:j.at,kind:F,state:j.state||null,url:F==="web"?null:e.main?.install?.url||null,port:F==="web"&&j.state==="ready"&&j.https||null,pull:X.pull?{running:X.pull.running===!0,error:X.pull.error||null}:null,script:dn(j.target)}:null,G=e.askProgress||{},me=c("ask-done","id"),Ye=Oe(s.filter(o=>o.kind==="owner-answer"),o=>o.data?.id),$e=Oe(s.filter(o=>o.kind==="prototype-request"),o=>o.data?.question),ze=Oe(s.filter(o=>o.kind==="reask-request"),o=>o.data?.question),we=Oe(s.filter(o=>o.kind===pt),o=>o.data?.question),Q=(t.items||[]).map(o=>Jn(o,Ye.get(o.id)||[],$e.get(o.id)||[],ze.get(o.id)||[],we.get(o.id)||[])),ne=new Map(Q.map(o=>[o.id,o])),ae=Oe(Q,o=>o.topic),Te=l(o=>{const k=ae.get(o)||[];return{open:k.filter(b=>b.state==="open").length,answered:k.filter(b=>b.state==="answered").length}},"pendingOn");for(const o of g.values()){const k=`ask:${o.id}`,b=N.get(A(C,o.id)),v=[...K.get(o.id)||[]],O=b&&!v.some(I=>I.key===b.key);if(O&&v.push({key:b.key,ref:b.ref,title:b.title,became:!1,folded:!0}),v.length&&!O){H.set(k,v[0].key);continue}if(!O&&A(C,o.id)){H.set(k,A(C,o.id));continue}const M=Tt(k,{lines:A(r,k)||[],events:s,working:u,links:v,index:p});for(const I of M)for(const[ce,ue]of De(I))ke.set(ce,ue);re.set(k,it(M)),B.push(Kn(o,M,h.get(o.id),v.length>0||!!A(C,o.id),A(G,o.id),me.get(o.id),Te(`item:${k}`)))}for(const o of y){const k=B.filter(b=>b.wave===o.slug);if(o.finished=bn(k),o.finished)for(const b of k)b.lane==="done"&&(b.fold=gn(o.slug))}for(const o of B)o.lineup=null;const Ee=new Set((t.topics||[]).map(o=>o.id)),Y=e.waves.flatMap(({slug:o,wave:k})=>(k.lineups||[]).map(b=>{const v=wo(b).map(O=>O.map(M=>`${o}/${M}`));return{...b,phases:v,keys:v.flat()}})),z=l(()=>no([...t.topics||[],...B.filter(o=>!Ee.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}:{}})),...Y.filter(o=>!Ee.has(`lineup:${o.slug}`)).map(o=>({id:`lineup:${o.slug}`,title:o.title,summary:"Wave triage."})),...(e.fromWaves?y:[]).filter(o=>!Ee.has(`wave:${o.slug}`)).map(o=>({id:`wave:${o.slug}`,title:o.title,summary:"Wave triage."}))],Q,new Set(B.filter(pe).map(o=>`item:${o.key}`))),"topicsOf"),Ae=new Map(z().map(o=>[o.id,o]));for(const o of B){const k=Ae.get(`item:${o.key}`);if(!k){o.questions=null,o.prototypes=null;continue}const b=k.questions.filter(M=>ne.get(M)?.state!=="resolved"),v=k.questions.find(M=>ne.get(M)?.state==="open")||b[0]||k.questions[0],O=k.questions.map(M=>ne.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:v,askedAt:O.map(M=>M.asked_at).filter(Boolean).sort(),fromAsks:O.length>0&&O.every(M=>M.ask)},o.prototypes=to(k.questions.map(M=>ne.get(M)))}const le=new Map(B.map(o=>[o.key,o]));for(const o of B){const k=o.kind==="work"&&o.lane!=="done";o.blockedBy=k?o.deps.map(v=>le.get(String(v).includes("/")?v:`${o.wave}/${v}`)).filter(v=>v&&v.lane!=="done"&&!v.reopened).map(Pt):[],o.blockedUntil=k?o.blockedUntil??null:null,o.blockReason=k?o.blockReason??null:null,o.blocked=o.blockedBy.length>0||!!o.blockedUntil,Wn(o,ne,_),o.lane==="todo"&&o.kind==="work"&&!o.held&&Ot(o.storedLane,o.agents)&&(o.lane="progress"),o.unpicked=o.kind==="work"&&o.lane==="todo"&&!!Tn(Q,o.key),o.unpicked&&(o.state="prototyping"),o.unpicked&&!o.action&&(o.next=Ln),o.buildable=Nn(o);const b=o.kind==="work"&&o.lane==="todo";o.unsorted=b&&(!!o.fromAsk||o.unfiled)&&!o.state,o.ready=b&&o.state==="ready"&&!o.blocked&&!o.action,o.staging=b&&o.state==="staging"}bo(B,le,V);const Be=new Map((t.items||[]).map(o=>[o.id,o.topic])),Le=l(o=>o.kind==="owner-answer"?Be.has(o.data?.id)?/^item:(.+)$/.exec(Be.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=l(o=>{const k=Le(o);return H.get(k)||k},"cardOf"),Je=l(o=>{const k=o.startsWith("ask:")?o.slice(4):null,b=k&&A(G,k)?.state==="done"?A(G,k).at:null;return[S.get(o),k&&me.get(k),b,U.get(o)].filter(Boolean).sort().pop()||""},"closedAt"),Re=l(o=>{const k=R(o);return k===null?!o.reply:!le.has(k)||!(Je(k)<String(A(u,o.id)))?!1:ke.has(o.id)?ke.get(o.id)==="working":!o.reply&&!H.has(Le(o))&&!(String(re.get(k)||"")>String(o.ts))},"unanswered"),D=s.filter(o=>A(u,o.id)&&Re(o)).map(o=>({id:o.id,kind:o.kind,key:R(o),data:o.data,since:A(u,o.id)}));for(const o of B)o.working=D.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=So[o.lane](o)||(o.lane==="review"?null:o.updatedAt)||null;for(const o of B){if(o.kind!=="work")continue;const k=(ae.get(`item:${o.key}`)||[]).filter(v=>v.state==="answered").map(v=>v.answers.at(-1).ts).sort().pop(),b=D.filter(v=>v.key===o.key).map(v=>String(v.since)).sort()[0];o.orchestrator=!o.stalled&&(["progress","todo"].includes(o.lane)||o.waitsOn==="checking"||o.lane==="review"&&o.stale)?jo(o,o.waitFacts,{answeredAt:k,workingSince:b}):null,delete o.waitFacts}qo(B,s),B.sort(cn);const Ze=ko(e,y,Y,B,Q,s),ie=z(),de=Zt({on:e.autoReview,incoming:e.incoming,records:e.reviewRecords,events:s,now:d,refs:new Map(B.filter(o=>o.kind==="work").map(o=>[o.key,o.ref]))}),he=[...xo(B,ie,Q),...en(de)],_e=new Map(he.filter(o=>o.questions).map((o,k)=>[o.topic||`item:${o.key}`,k])),Pe=l(o=>_e.get(o.id)??_e.size,"rankOf");ie.sort((o,k)=>Pe(o)-Pe(k));const je=new Set(he.filter(o=>o.questions&&o.progress).map(o=>`item:${o.key}`));for(const o of ie)je.has(o.id)&&(o.progress=!0);const Ue=B.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 ho({kind:F,main:Ce,remote:W&&{host:W.host,name:`${W.host}/${W.path}`},waves:y,waveBands:Ze,items:B,moved:Object.fromEntries(H),answers:Ue,owner:Q,ownerTopics:ie,reviews:de,needs:he,working:D.length,workingOn:D,checks:vo(e,d)},e)}l(as,"buildBoard");function ho(e,{listen:t,prompts:n}={}){const s=[...$o(n),...e.needs];return{...e,needs:s,needsSummary:Eo(s),checks:{...e.checks,watcher:{listening:"running",asleep:"asleep",waking:"waking"}[t]||"stopped"}}}l(ho,"withHub");const Pt=l(e=>({key:e.key,ref:e.ref}),"linkOf"),jt=l((e,t)=>String(t).includes("/")?String(t):`${e}/${t}`,"linkKey"),yo=l((e,t)=>e.ref.localeCompare(t.ref,void 0,{numeric:!0}),"byRef");function bo(e,t,n){const s=new Map,r=new Map,a=new Map,u=l((p,f,g)=>p.set(f,[...p.get(f)||[],g]),"add");for(const[p,f]of n){f.parent&&u(s,f.parent,p);for(const g of f.related)g!==p&&(u(r,p,g),u(r,g,p))}for(const p of e)for(const f of p.blockedBy||[])u(a,f.key,p.key);const d=l((p=[])=>[...new Set(p)].map(f=>t.get(f)).filter(Boolean).map(Pt).sort(yo),"cards");for(const p of e){const f=n.get(p.key)?.parent;p.parent=f?{key:f,ref:t.get(f)?.ref||f.slice(f.indexOf("/")+1)}:null,p.children=d(s.get(p.key)),p.related=d(r.get(p.key)),p.waitsOnIt=d(a.get(p.key))}}l(bo,"linkCards");const So={todo:l(e=>e.created,"todo"),progress:l(e=>e.progressSince,"progress"),review:l(e=>e.reviewSince,"review"),done:l(e=>e.closedAt,"done"),asks:l(e=>e.ask.answerAt,"asks")},se=l((e,t)=>`${e} ${t}${e===1?"":"s"}`,"plural");function vo({checked:e,watcher:t,replyAt:n,threads:s={},working:r={},waves:a=[]},u){const d=Number(e?.every)||30,f=[n,t?.since,...Object.values(r),...Object.values(s).flat().filter(c=>(c.who||"claude")==="claude").map(c=>c.ts),...a.flatMap(c=>Object.values(c.agents||{}).flatMap(h=>Object.values(h||{})).map(h=>h?.done||h?.started))].filter(c=>typeof c=="string"&&!Number.isNaN(Date.parse(c))).reduce((c,h)=>c&&Date.parse(c)>Date.parse(h)?c:h,null),g=!!e?.idle;return{at:e?.at||null,every:g?null:d,idle:g,late:!g&&!!e?.at&&u-Date.parse(e.at)>2*d*6e4,claudeAt:f}}l(vo,"checksOf");function xo(e,t,n){const s=new Set(e.map(d=>d.questions?.topic).filter(Boolean)),r=l(d=>d.find(p=>n.find(f=>f.id===p)?.state==="open")||d[0],"firstOpen"),a=t.filter(d=>d.state!=="resolved"&&d.open>0&&!s.has(d.id)).map(d=>{const p=`questions.html#${r(d.questions)}`,f=/^(?:lineup|wave):(.+)$/.exec(d.id)?.[1]||null;return{kind:"Question",title:d.title,text:`${d.title} \xB7 ${se(d.open,"question")}`,url:p,topic:d.id,band:f,questions:{open:d.open,url:p},ready:null,since:n.filter(g=>g.topic===d.id&&g.state==="open").map(g=>g.asked_at).filter(Boolean).sort()[0]||null}}),u=e.map(d=>{const p=d.questions&&d.questions.state!=="resolved"&&d.questions.open>0?{open:d.questions.open,url:`questions.html#${d.questions.first}`}:null,f=d.waitsOn==="ready"?d.pr.url:null,g=!p&&!f&&d.action?{label:d.action.label,url:d.action.url}:null;if(!p&&!f&&!g)return null;const c=`${d.ref}: ${d.title}`,h=[c,p&&se(p.open,"question"),f&&"Ready on GitHub",g?.label].filter(Boolean).join(" \xB7 ");return{kind:p?"Question":f?"Tap Ready":"Action needed",title:c,text:h,url:`#item=${encodeURIComponent(d.key)}`,key:d.key,questions:p,ready:f,action:g,since:d.created||null,progress:d.lane==="progress"}}).filter(Boolean);return[...a,...u].sort(Oo)}l(xo,"needsOf");const Oo=l((e,t)=>!!t.progress-!!e.progress||(e.since&&t.since?e.since.localeCompare(t.since):!!t.since-!!e.since),"oldestFirst");function $o(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}})}l($o,"promptLines");function Eo(e){const t=e.filter(a=>a.prompt).length,n=e.reduce((a,u)=>a+(u.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 ")}l(Eo,"needsSummary");function ls(e){const t=l((...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))}}l(ls,"needsAnswer");const Ao=l(e=>e.buildable&&e.state==="ready"&&!e.held&&!e.blocked,"pullableCard"),Bo=10;function Ro(e,{cap:t=Bo,pulled:n=[]}={}){const s=e.filter(m=>!pe(m)),r=l((m,x)=>m.started&&!m.done&&te(m)&&!(m.kind===Qe&&!m.writes)&&!(m.waits&&x.some(S=>S.name===m.waits&&!S.done)),"counts"),a=s.filter(m=>!m.stalled).reduce((m,x)=>m+(x.agents||[]).filter((S,L,_)=>r(S,_)).length,0),u=s.filter(m=>Ao(m)&&!m.working).map(m=>m.key),d=s.filter(m=>n.includes(m.key)&&(u.includes(m.key)||m.buildable&&m.working)).map(m=>m.key),p=s.flatMap(m=>(m.agents||[]).filter(x=>x.kind===Qe&&!x.started&&!x.done).map(x=>({key:m.key,name:x.name,writes:x.writes}))),f=Math.max(0,t-a-d.length),g=p.filter(m=>m.writes),c=[...p.filter(m=>!m.writes),...g.slice(0,f)].map(({key:m,name:x})=>({key:m,name:x})),h=u.filter(m=>!d.includes(m)).slice(0,Math.max(0,f-g.length));return{idle:!a&&!s.some(m=>m.waitsOn==="checking"),pullable:u,waiting:d,pulls:h,helpers:c}}l(Ro,"pullOf");function is(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(u=>u.state==="OPEN"&&u.mergeable==="CONFLICTING"&&u.repo===s.repo&&u.headRefOid),a=r&&`${r.headRefOid}@${n}`;return r&&A(t,s.key)!==a?[{key:s.key,pr:r.number,branch:r.headRefName,conflict:a}]:[]})}l(is,"rebasesDue");const _o=3;function ds(e,{cap:t=_o}={}){const n=e.filter(a=>!pe(a)),s=n.filter(a=>(a.agents||[]).some(u=>ee(u)&&u.started&&!u.done)).length;return n.filter(a=>a.buildable&&!["ready","staging"].includes(a.state)&&!a.unsorted&&!a.blocked&&!a.held&&!a.working&&!a.grilling&&!a.prototyping&&!a.questions&&!(a.agents||[]).some(u=>ee(u)&&u.kind!==fe)).slice(0,Math.max(0,t-s)).map(a=>a.key)}l(ds,"arrivalsOf");function cs(e){return e.filter(t=>t.kind==="work"&&t.lane==="todo"&&t.roundAt&&["grilling","prototyping"].includes(t.state)&&!(t.agents||[]).some(n=>ee(n)&&n.kind!==fe&&n.started&&(String(n.started)>=String(t.roundAt)||!n.done))).map(t=>({key:t.key,kind:t.state==="grilling"?"grill":"prototype"}))}l(cs,"roundsDue");const Mo=l(e=>`${fe}-${e.id}`,"visualAgent");function us(e,t){const n=[];for(const s of e){const r=s.agents||[];if(r.some(d=>d.kind===fe&&d.started&&!d.done))continue;const a=new Set(r.map(d=>d.name)),u=t.find(d=>d.kind==="visual-request"&&d.data?.key===s.key&&!a.has(Mo(d)));u&&n.push({key:s.key,request:u})}return n}l(us,"visualsDue");const Ut=l(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 Wt(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}l(Wt,"unsorted");function Ft(e,t){const n=new Map(e.map(r=>[r.key,r])),s=new Set(t.filter(r=>r.kind==="intake-handoff").map(r=>r.data?.event));return t.flatMap(r=>{const a=Ut(r);return a&&!s.has(r.id)&&Wt(n,r,a)?[{event:r.id,key:a,kind:r.kind}]:[]})}l(Ft,"intakeOpen");function ps(e,t,n){return Object.values(n).some(s=>!s.ended)?null:Ft(e,t).find(s=>!A(n,s.event))??null}l(ps,"intakeDue");const Io=ft;function fs(e,t){return Object.entries(e).filter(([,n])=>!n.ended&&t-Date.parse(n.at)>=Io).map(([n])=>n)}l(fs,"intakeHung");function gs(e,t,n){return Ft(e,t).filter(s=>A(n,s.event)?.ended)}l(gs,"intakeStranded");function qo(e,t){const n=new Map(e.map(r=>[r.key,r])),s=new Map(t.map(r=>[r.id,r]));for(const r of t.filter(a=>a.kind==="intake-handoff")){const a=s.get(r.data?.event),u=a&&Ut(a);!u||!Wt(n,a,u)||(n.get(u).orchestrator={kind:"intake",since:String(r.ts),picked:null,next:`Sort what intake triage handed you: ${r.data.text}`})}}l(qo,"intakeWaits");function Do(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
+ `)}l(Do,"todoOf");function ks(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
- ${Ro(e)}`}}l(ds,"triageOf");const _o=100,Mo=["key","kind","id","ref","title","lane","wave","waveTitle","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"],cs=l(e=>Object.fromEntries(Mo.filter(t=>t in e).map(t=>[t,e[t]])),"slimDone");function us(e,t=_o){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.wave}/${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"))}l(us,"archivedOf");function ps(e){return e.items.filter(t=>t.kind==="work").flatMap(t=>{const n=t.lane==="stale"?"review":t.lane;return n===t.storedLane||n==="done"||t.storedLane==="done"&&!t.reopened?[]:[{key:t.key,to:n,why:Io(t,n)}]})}l(ps,"movesOf");function Io(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"}l(Io,"moveWhy");const qo=l(e=>e.startsWith("ask:")?`ask-${e.slice(4)}`:`item-${e}`,"pushTag");function fs(e){const t=l(a=>e.owner.filter(u=>u.topic===a&&u.state==="open").map(({id:u,question:d})=>({id:u,question:d})),"open"),n=[q.ready,q.merge],s=e.items.filter(a=>a.action).map(a=>{const u=ot.has(a.action.kind)?t(a.questions.topic):[];return{tag:qo(a.key),kind:a.action.kind,title:`${a.ref}: ${a.action.label}`,body:u[0]?.question||a.title,url:u.length?a.action.url:`#item=${encodeURIComponent(a.key)}`,now:!n.includes(a.action.kind),questions:u}}),r=e.needs.filter(a=>a.topic).map(a=>{const u=t(a.topic);return{tag:`topic-${a.topic}`,kind:q.questions,title:a.text,body:u[0]?.question||a.title,url:a.url,now:!0,questions:u}});return[...s,...r,...zt(e.reviews)]}l(fs,"ownerActions");function gs(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&&!$o(e.items,{pulled:t}).waiting.length}l(gs,"strictlyIdle");const Do="Judge the staged card: item set --state ready, or --state grilling or prototyping for another round";function No(e,t,{answeredAt:n,workingSince:s}){const r=l(f=>e.agents.some(g=>g.kind===f&&g.started&&!g.done),"running"),a=[e.progressSince,...e.agents.map(f=>f.done)].filter(Boolean).map(String).sort().pop()||null,u=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:Do}:n?{kind:"answer",since:n,next:"Act on the owner's answer"}:ot.has(e.action?.kind)?null:e.builder?.stopped?{kind:"stalled",since:e.builder.silentSince,next:`${e.builder.stopped}: start it again when it should run, or drop the card`}:e.builder?.stalled?{kind:"stalled",since:e.builder.silentSince,next:"Find out why the builder went silent or stopped, then restart or finish its run"}:e.lane==="stale"&&e.inReviewSince?{kind:"stale",since:new Date(Date.parse(e.inReviewSince)+ft).toISOString(),next:"Find out why the review went stale and fix what you can: rebase, CI, comments, a silent reviewer"}: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===St&&a?{kind:"unowned",since:a,next:"Start a builder on it, or set its Next"}:null;if(!u?.since)return null;const d=String(u.since),p=[t.writes.find(f=>f>d),s].filter(Boolean).sort()[0]||null;return{kind:u.kind,since:d,picked:p,next:u.next}}l(No,"orchestratorWaitOf");function ks(e){return e.items.filter(t=>t.orchestrator).map(t=>({key:t.key,ref:t.ref,title:t.title,...t.orchestrator}))}l(ks,"orchestratorWaits");const ms=30*6e4,Co=15*6e4,To=60*6e4;function ws(e,t,n){const s=n.toISOString(),r=[],a=[];return{waits:t.map(d=>{const p=e.find(c=>c.key===d.key&&c.kind===d.kind&&c.since===d.since);if(!p)return d.picked||r.push(d.key),{...d,bumped:s};const f=d.picked?[d.picked,p.bumped].filter(Boolean).sort().pop():p.bumped||d.since,g=n-Date.parse(f)>=(d.picked?To:Co);return g&&a.push({key:d.key,ref:d.ref,kind:d.kind,since:d.since,next:d.next}),{...d,bumped:g?s:p.bumped||d.since,...p.pushed?{pushed:!0}:{}}}),added:r,again:a}}l(ws,"waitsStep");function hs(e,t){const n=l(s=>{const r=Math.floor((t-Date.parse(s))/6e4),a=Math.floor(r/60),u=r%60;return a?`${a}h${u?` ${u}m`:""}`:`${u}m`},"age");return`still waiting on you: ${e.map(s=>`${s.ref} ${s.kind} for ${n(s.since)}: ${s.next}`).join("; ")}`}l(hs,"waitsAgainLine");const Lo=15;function ys(e,{idle:t,now:n}){const s=n.toISOString(),r=e.last||s;return t?e.since?e.pushed||n-Date.parse(e.since)<Lo*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}}l(ys,"idleStep");function bs(e,t){const n=l(p=>String(p.closedAt||"")>t,"after"),s=e.filter(p=>p.kind==="work"&&p.lane==="done"&&!kt(p)&&n(p)),r=new Map;for(const p of e)p.lineup&&r.set(p.lineup.slug,[...r.get(p.lineup.slug)||[],p]);const a=[...r.values()].filter(p=>p.every(f=>f.lane==="done")&&p.some(n)).map(p=>p[0].lineup.title),u=s.slice(0,3).map(p=>p.ref).join(", ")+(s.length>3?` and ${s.length-3} more`:"");return[s.length&&`${s.length} ${s.length===1?"card":"cards"} landed: ${u}`,...a.map(p=>`${p} finished`)].filter(Boolean).join(" \xB7 ")||"Nothing closed since the last idle notification."}l(bs,"idleSummary");export{q as ACTION_KIND,At as ACTION_LABELS,eo as ANSWERS_SUBMITTED,_e as BUILDER_ASK,Re as BUILDER_KIND,dn as BUILDER_QUIET_AFTER,ln as BUILDER_STALLED_AFTER,pt as BUILD_REFUSED,ce as BUTTON,Qe as COMMANDS,Mo as DONE_FIELDS,_o as DONE_KEPT,Ke as HELPER,Lo as IDLE_MINUTES,Bt as INTENT_LABELS,an as ITEM_EVENTS,Yo as LANES,Oo as MAX_IMPLEMENTERS,Eo as MAX_PLANNERS,be as MOVE,St as NO_NEXT,ms as ORCHESTRATOR_PUSH_AFTER,To as PICKED_REWAKE_AFTER,Wn as PLANNER_WORD,Dn as PROTOTYPE_AGAIN,Jo as PROTOTYPE_WAIT,ee as PR_STATE,ut as QUESTION_REOPEN,Co as REWAKE_AFTER,pe as VISUAL,us as archivedOf,ss as arrivalsOf,ts as buildBoard,ne as builds,tt as handedBackAt,ys as idleStep,bs as idleSummary,ls as intakeDue,Tt as intakeKeyOf,is as intakeStranded,es as lineupMoved,fo as lineupPhases,An as mergeBlockers,Vn as modelName,ps as movesOf,ns as needsAnswer,qn as noTakePicked,Fn as orchestratorDid,ks as orchestratorWaits,zo as owesNext,fs as ownerActions,Hn as ownerQuestion,In as picksNoTake,te as plans,Zo as prototypeWaits,Ot as providerOf,$o as pullOf,qo as pushTag,os as rebasesDue,We as refOf,fe as roundOf,Xn as roundSettledBy,rs as roundsDue,cs as slimDone,Ie as stateEntries,vt as stoppedWords,gs as strictlyIdle,It as threadIndex,qt as threadOf,Ro as todoOf,ds as triageOf,kt as unlanded,Ao as visualAgent,as as visualsDue,hs as waitsAgainLine,ws as waitsStep,un as waveFold,go as withHub};
10
+ ${Do(e)}`}}l(ks,"triageOf");const No=100,Co=["key","kind","id","ref","title","lane","wave","waveTitle","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"],ms=l(e=>Object.fromEntries(Co.filter(t=>t in e).map(t=>[t,e[t]])),"slimDone");function ws(e,t=No){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.wave}/${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"))}l(ws,"archivedOf");function hs(e){return e.items.filter(t=>t.kind==="work").flatMap(t=>{const n=t.lane;return n===t.storedLane||n==="done"||t.storedLane==="done"&&!t.reopened?[]:[{key:t.key,to:n,why:To(t,n)}]})}l(hs,"movesOf");function To(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"}l(To,"moveWhy");const Lo=l(e=>e.startsWith("ask:")?`ask-${e.slice(4)}`:`item-${e}`,"pushTag");function ys(e){const t=l(a=>e.owner.filter(u=>u.topic===a&&u.state==="open").map(({id:u,question:d})=>({id:u,question:d})),"open"),n=[q.ready,q.merge],s=e.items.filter(a=>a.action).map(a=>{const u=st.has(a.action.kind)?t(a.questions.topic):[];return{tag:Lo(a.key),kind:a.action.kind,title:`${a.ref}: ${a.action.label}`,body:u[0]?.question||a.title,url:u.length?a.action.url:`#item=${encodeURIComponent(a.key)}`,now:!n.includes(a.action.kind),questions:u}}),r=e.needs.filter(a=>a.topic).map(a=>{const u=t(a.topic);return{tag:`topic-${a.topic}`,kind:q.questions,title:a.text,body:u[0]?.question||a.title,url:a.url,now:!0,questions:u}});return[...s,...r,...tn(e.reviews)]}l(ys,"ownerActions");function bs(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&&!Ro(e.items,{pulled:t}).waiting.length}l(bs,"strictlyIdle");const Kt="item stall it with the reason when it can't go on",Po="Judge the staged card: item set --state ready, or --state grilling or prototyping for another round";function jo(e,t,{answeredAt:n,workingSince:s}){const r=l(f=>e.agents.some(g=>g.kind===f&&g.started&&!g.done),"running"),a=[e.progressSince,...e.agents.map(f=>f.done)].filter(Boolean).map(String).sort().pop()||null,u=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:Po}:n?{kind:"answer",since:n,next:"Act on the owner's answer"}:st.has(e.action?.kind)?null:e.builder?.stopped?{kind:"stale",since:e.builder.silentSince,next:`${e.builder.stopped}: start it again when it should run, or drop the card`}:e.builder?.stale?{kind:"stale",since:e.builder.silentSince,next:`Find out why the builder went silent or stopped, then restart or finish its run, or ${Kt}`}:e.lane==="review"&&e.stale?{kind:"stale",since:new Date(Date.parse(e.inReviewSince)+kt).toISOString(),next:`Find out why the review went stale and fix what you can: rebase, CI, comments, a silent reviewer, or ${Kt}`}: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"}:null;if(!u?.since)return null;const d=String(u.since),p=[t.writes.find(f=>f>d),s].filter(Boolean).sort()[0]||null;return{kind:u.kind,since:d,picked:p,next:u.next}}l(jo,"orchestratorWaitOf");function Ss(e){return e.items.filter(t=>t.orchestrator).map(t=>({key:t.key,ref:t.ref,title:t.title,...t.orchestrator}))}l(Ss,"orchestratorWaits");const vs=30*6e4,Uo=15*6e4,Wo=60*6e4;function xs(e,t,n){const s=n.toISOString(),r=[],a=[];return{waits:t.map(d=>{const p=e.find(c=>c.key===d.key&&c.kind===d.kind&&c.since===d.since);if(!p)return d.picked||r.push(d.key),{...d,bumped:s};const f=d.picked?[d.picked,p.bumped].filter(Boolean).sort().pop():p.bumped||d.since,g=n-Date.parse(f)>=(d.picked?Wo:Uo);return g&&a.push({key:d.key,ref:d.ref,kind:d.kind,since:d.since,next:d.next}),{...d,bumped:g?s:p.bumped||d.since,...p.pushed?{pushed:!0}:{}}}),added:r,again:a}}l(xs,"waitsStep");function Os(e,t){const n=l(s=>{const r=Math.floor((t-Date.parse(s))/6e4),a=Math.floor(r/60),u=r%60;return a?`${a}h${u?` ${u}m`:""}`:`${u}m`},"age");return`still waiting on you: ${e.map(s=>`${s.ref} ${s.kind} for ${n(s.since)}: ${s.next}`).join("; ")}`}l(Os,"waitsAgainLine");const Fo=15;function $s(e,{idle:t,now:n}){const s=n.toISOString(),r=e.last||s;return t?e.since?e.pushed||n-Date.parse(e.since)<Fo*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}}l($s,"idleStep");function Es(e,t){const n=l(p=>String(p.closedAt||"")>t,"after"),s=e.filter(p=>p.kind==="work"&&p.lane==="done"&&!ht(p)&&n(p)),r=new Map;for(const p of e)p.lineup&&r.set(p.lineup.slug,[...r.get(p.lineup.slug)||[],p]);const a=[...r.values()].filter(p=>p.every(f=>f.lane==="done")&&p.some(n)).map(p=>p[0].lineup.title),u=s.slice(0,3).map(p=>p.ref).join(", ")+(s.length>3?` and ${s.length-3} more`:"");return[s.length&&`${s.length} ${s.length===1?"card":"cards"} landed: ${u}`,...a.map(p=>`${p} finished`)].filter(Boolean).join(" \xB7 ")||"Nothing closed since the last idle notification."}l(Es,"idleSummary");export{q as ACTION_KIND,Mt as ACTION_LABELS,so as ANSWERS_SUBMITTED,Ie as BUILDER_ASK,be as BUILDER_KIND,pn as BUILDER_QUIET_AFTER,ft as BUILDER_STALE_AFTER,gt as BUILD_REFUSED,oe as BUTTON,He as COMMANDS,Co as DONE_FIELDS,No as DONE_KEPT,Qe as HELPER,Fo as IDLE_MINUTES,Io as INTAKE_TIMEOUT_AFTER,It as INTENT_LABELS,un as ITEM_EVENTS,hn as LANDER,ts as LANES,Bo as MAX_IMPLEMENTERS,_o as MAX_PLANNERS,Se as MOVE,$t as NO_NEXT,vs as ORCHESTRATOR_PUSH_AFTER,Wo as PICKED_REWAKE_AFTER,Qn as PLANNER_WORD,Ln as PROTOTYPE_AGAIN,os as PROTOTYPE_WAIT,Z as PR_STATE,pt as QUESTION_REOPEN,Uo as REWAKE_AFTER,fe as VISUAL,ws as archivedOf,ds as arrivalsOf,as as buildBoard,te as builds,nt as handedBackAt,$s as idleStep,Es as idleSummary,ps as intakeDue,fs as intakeHung,Ut as intakeKeyOf,gs as intakeStranded,rs as lineupMoved,wo as lineupPhases,Mn as mergeBlockers,zn as modelName,hs as movesOf,ls as needsAnswer,Tn as noTakePicked,Vn as orchestratorDid,Ss as orchestratorWaits,ns as owesNext,ys as ownerActions,Jn as ownerQuestion,Cn as picksNoTake,ee as plans,ss as prototypeWaits,Bt as providerOf,Ro as pullOf,Lo as pushTag,is as rebasesDue,Ke as refOf,ge as roundOf,Zn as roundSettledBy,cs as roundsDue,ms as slimDone,De as stateEntries,Et as stoppedWords,bs as strictlyIdle,Ct as threadIndex,Tt as threadOf,Do as todoOf,ks as triageOf,ht as unlanded,Mo as visualAgent,us as visualsDue,Os as waitsAgainLine,xs as waitsStep,gn as waveFold,ho as withHub};
@@ -1,9 +1,9 @@
1
- var G=Object.defineProperty;var u=(t,e)=>G(t,"name",{value:e,configurable:!0});import K from"node:os";import v from"node:path";import{fileURLToPath as _}from"node:url";import{launchAgent as L}from"./agents.mjs";import{processStarts as B,ps as V}from"../hub/live.mjs";import{boardAt as P}from"../hub/boards.mjs";import{readHubRules as C}from"../hub/rules.mjs";import{builderRepo as D}from"./worktree.mjs";import{BoardError as z,appendThread as X,briefFacts as J,readBoardRules as M,recordSession as Q}from"./store.mjs";import{HELPER as Z,modelName as ee,plans as te,VISUAL as oe}from"./domain.mjs";const re=_(new URL("./bin/board.mjs",import.meta.url)),b=u(t=>`node ${re} --site ${t}`,"cli"),Y=u(t=>`git -c core.commentChar=';' rebase ${t}`,"rebase"),R=u((t,e="Rules from the hub and this board:")=>t.length?[e,...t.map(a=>`- ${a}`),""]:[],"ruleLines"),T=u(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||"no notes"}`),""]:[],"linkLines"),O=u(({route:t,key:e,worktree:a,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 \`${Y(h)}\` in ${a}, 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 \`${b(t)} builder call ${e} "<why>"\` and run done again: with the call it hands the card back.`],"finishSteps"),N=u(({route:t,key:e})=>`\`${b(t)} builder done ${e} --text "<what you checked>"\``,"finishCommand"),ne=u((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 ae({route:t,key:e,ref:a,title:h,notes:n,worktree:r,base:s,rules:l=[],links:d=[]}){const i=b(t);return[`You are the headless builder for card ${a} (${e}, "${h}") on the kickflip board /${t}. The board launched you; nobody watches this session, so never wait on a person. Work only in ${r}. Never restart the hub or a board server, and never push.`,"",`1. Run \`${i} builder start ${e} --kind build\`. If it refuses, stop.`,`2. Report progress with \`${i} 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. To ask the orchestrator something, run \`${i} builder ask ${e} "<question>"\` and carry on; read its replies with \`${i} builder asks ${e}\`. Never block on a reply.`,`4. Record each judgment call the notes leave open with \`${i} builder call ${e} "<call>"\` instead of guessing silently.`,...ne(i,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.","",...T(d),...R(l),"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 "${a}: <what the card now does>".`,"",`To finish, run ${N({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.",...O({route:t,key:e,worktree:r,base:s}),"","Never push."].join(`
2
- `)}u(ae,"builderBrief");function se({route:t,key:e,ref:a,title:h,worktree:n,base:r,answers:s=[]}){const l=b(t);return[`You are the headless lander for card ${a} (${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 ${n}, never restart the hub or a board server, and never push.`,"",`1. Run \`${l} builder start ${e} --kind build --label Lander --phase landing --progress "taking up the verified build"\`. If it refuses, stop.`,`2. Report progress with \`${l} builder progress ${e} --phase landing "<plain, 80 characters or fewer>"\`. The board adds the phase word; don't start the text with it.`,...s.length?["3. The owner answered the builder's deferred asks. Where an answer changes the build, change it, with its tests, and commit:",...s.map(d=>`- ${d.ask} "${d.question}": ${d.answer}`)]:[],"",`To finish, run ${N({route:t,key:e})}.`,...O({route:t,key:e,worktree:n,base:r}),"","Never push."].join(`
3
- `)}u(se,"landerBrief");function ie({route:t,key:e,ref:a,title:h,worktree:n,base:r,pr:s}){const l=b(t);return[`You are the headless rebase run for card ${a} (${e}, "${h}") on the kickflip board /${t}. Its PR #${s} conflicts with ${r}; rebase its branch onto ${r} so the PR is clean again. Nobody watches this session, so never wait on a person. Work only in ${n}, never restart the hub or a board server, and never push.`,"",`1. Run \`${l} builder start ${e} --kind build --label Rebase --phase building --progress "Rebase run started \xB7 fetching ${r}"\`. If it refuses, stop.`,`2. Run \`git fetch origin\`, then \`${Y(`origin/${r}`)}\`. Resolve each conflict keeping both sides' intent, \`git add\` it and continue with \`git -c core.commentChar=';' rebase --continue\`. Report each step with \`${l} builder progress ${e} --phase building "<line>"\`, the board adds the phase word, in these words: "Rebasing onto ${r} \xB7 resolving <n> files", then "Rebasing onto ${r} \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 \`${l} 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.`,"",`To finish, run ${N({route:t,key:e})}, the text saying what you resolved. It reruns the suite and pushes the branch to PR #${s} 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
- `)}u(ie,"rebaseBrief");function he({route:t,key:e,ref:a,title:h,notes:n,kind:r,repo:s,rules:l=[],links:d=[],text:i,files:o=[],question:c,note:w,choice:m}){const p=b(t),g=r==="grill";return[`You are the headless ${g?"grill":"prototype"} planner for card ${a} (${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 ${s}; write no output code ${g?"there":`in ${s} itself`}, never restart the hub or a board server, and never push.`,"",`1. Run \`${p} builder start ${e} --kind ${r}\`. 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, in the board site's data/threads folder, the repo's docs/adr and CONTEXT.md, and the cards its notes and thread link.`,`3. Report progress with \`${p} 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 \`${p} question add --file <json>\` on topic item:${e}, one per decision, your recommendation first. 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.) 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: the board moves the card to prototyping on yes, or to staging on no once the owner has answered the rest, 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. Build the round in situ, in the board's base prototype worktree: run \`${p} 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 \`${p} 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 \`${p} question add --file <json>\` on topic item:${e}, never asking in the chat. Only a repo whose app can't be served as a web page keeps take files: \`${p} prototype add <topic> --card ${e} --take <K>:<label> \u2026\`, one take file per option built from the board's real components fed fake data. Invoke the prototype skill with the Skill tool for this step. (With no Skill tool, follow this step as written.)`,...i?[` The owner asked for this ${g?"grill":"prototype"}, writing: "${i}"`]:[],...o.length?[` The owner attached: ${o.map(f=>f.path).join(", ")}.`]:[],...c?[` The owner asked for prototypes of question ${c}${w?`, noting "${w}"`:""}${m?`, leaning to option ${m}`:""}: draw that question's next round, its question naming round_of ${c}.`]:[],`5. Record what you learned in the card's notes with \`${p} item set ${e} --notes-file <file.md>\`, naming each answered question's id.`,...g?[`6. Where the owner asked for prototypes, or a rule here wants a UI prototype first, end the card \`${p} item set ${e} --state 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.`]:[],"",...T(d),...R(l,"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 \`${p} builder done ${e} --text "<what you asked>"\`.`,"","Never push."].join(`
5
- `)}u(he,"plannerBrief");function de({route:t,key:e,ref:a,title:h,notes:n,repo:r,agent:s,rules:l=[],links:d=[],text:i,files:o=[]}){const c=b(t);return[`You are the headless visual agent for card ${a} (${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 ${r}; write no code there, never restart the hub or a board server, and never push.`,"",`1. Run \`${c} builder start ${e} --kind visual --agent ${s}\`. If it refuses, stop.`,`2. Gather what the page needs: ${n?`the card's notes, ${n}; `:""}its thread, in the board site's data/threads folder, and the code or docs it is about.`,i?` The owner wrote: "${i}"`:" 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 \`${c} 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.","",...T(d),...R(l,"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 \`${c} builder done ${e} --agent ${s}\`.`,"","Never push."].join(`
6
- `)}u(de,"visualBrief");function le({route:t,key:e,ref:a,title:h,notes:n,agent:r,task:s,writes:l,cwd:d,rules:i=[],links:o=[]}){const c=b(t);return[`You are the headless helper ${r} on card ${a} (${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.`,l?`Work only in ${d}, your own worktree, branched from the card's: commit your work there with \`git -c core.commentChar=';' commit -F <file>\`, the subject "${a}: <what it does>". The card's builder merges your branch, so leave nothing uncommitted.`:`Read and run in ${d}; change no files there and commit nothing.`,"","The task:",s,"",`1. Run \`${c} builder start ${e} --kind helper --agent ${r}\`. If it refuses, stop.`,`2. Report progress with \`${c} builder progress ${e} --agent ${r} --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}.`:"","",...T(o),...R(i,"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 \`${c} builder done ${e} --agent ${r} --text "<your hand-back: what you did or found, ${l?"what you committed, ":""}and any decision you made>"\`.`,"","Never push."].filter((w,m,p)=>w!==""||p[m-1]!=="").join(`
7
- `)}u(le,"helperBrief");function ce({route:t,repo:e,rules:a=[],waves:h=[],cards:n=[],event:r,key:s,kind:l,quick:d=!1,thread:i=[]}){const o=b(t),c=i.map(f=>`- ${f.who}: ${f.text}${f.files?.length?` (attached: ${f.files.map(I=>I.path).join(", ")})`:""}`),w=d?"--state staging --size <small|large>":"--state <grilling|prototyping>",m=d?" The owner chose Implement: put the plan in the notes; the orchestrator judges it from staging before it goes ready.":" --state prototyping when it changes the hub's or a board's UI, --state grilling for anything else; the board starts the grill or prototype itself.",p=u(f=>[` 1. Write its notes file in the wave's folder, a .md named for the change: open with the owner's words verbatim, under "## Asked (owner, <date>)", then a short "## What it is" in plain words and a "## Related" line naming each linked card and how they overlap. Append that "## Related" line to each linked card's notes file too.`,` 2. ${f}`,m],"fileIt"),g={ask:[`Your job: sort new task ${r}, on card ${s}, one of these ways:`,`- Answer it, when it is a question or asks for nothing to build: \`${o} reply ${r} --file <reply.html>\`.`,`- Fold it into the open card it is about: \`${o} item relate-ask ${r} <key> --summary "<one line>"\`, then append the owner's words verbatim to that card's notes file under "## Asked (owner, <date>)".`,`- File it as a new card: \`${o} item add <wave> --title "<the change, in plain words>"\` prints "<label> added as <key>"; use that key.`,...p(`\`${o} item set <key> --from-ask ${r} --notes-file <file> --relate <keys> --text "<one line on what you filed>" ${w}\` (link every card it overlaps, depends on or would duplicate; --deps for one it truly waits on).`)],"item-message":[`Your job: the owner's message ${r} in the thread of new task ${s}, whose last line is theirs. Answer it with \`${o} reply ${r} --file <reply.html>\`, or, if it changes what the task asks, sort the task as the message now has it:`,`- fold it: \`${o} item relate-ask ${s.slice(4)} <key> --summary "<one line>"\`; or file it: \`${o} item add <wave> --title "<the change>"\`, then`,...p(`\`${o} item set <key> --from-ask ${s.slice(4)} --notes-file <file> --relate <keys> --text "<one line>" ${w}\`,`)," then reply to the message saying what you did."],"card-added":[`Your job: card ${s}, which an agent added unfiled (card-added ${r}). Read its notes and the cards its title looks related to, tidy its notes (what it is, why, where it came from), and add a "## Related" line to it and each linked card's notes. Then file it in one call:`,`\`${o} item set ${s} --relate <keys> --text "<one line>" --state <grilling|prototyping>\`: prototyping when it changes the hub's or a board's UI, grilling for anything else.`]};return[`You are an intake triage run on the kickflip board /${t}. The board launched you to sort one thing that reached it unsorted; nobody watches this session, so never wait on a person. Work in ${e}, reading only: you write through the board's CLI, \`${o} <command>\`, and notes files, nothing else. Never set a card ready, never build, never push, and never restart the hub or a board server.`,"",...g[l],"",`If you can't tell how to sort it (which card it belongs to, or what it asks), don't guess: \`${o} intake handoff ${r} --text "<what you are unsure of>"\` hands it to the orchestrator, and stop.`,"Each command writes its own line in the thread, which is your report; add one with `"+o+" thread "+s+' --text "<line>"` only for what those lines leave unsaid. Every line stands alone: name cards by their label.',"",`The thread of ${s}:`,...c.length?c:["- (no messages)"],"",`The board's open cards, as label key "title", lane, state: notes file. Read the notes of any card whose title looks related before you choose.`,...n.length?n.map(f=>`- ${f.ref} ${f.key} "${f.title}", ${f.lane}, ${f.state||"no state"}: ${f.notes||"no notes"}`):["- (none)"],"","Waves a new card can go in, as slug: folder:",...h.map(f=>`- ${f.slug}: ${f.dir}`),"",...R(a,"Rules from the hub and this board, written for the orchestrator: where one tells you to push, land, build or restart, it isn't yours:"),"Sort that one thing, then exit. Never push."].join(`
8
- `)}u(ce,"intakeBrief");async function Te(t,e){const a=P(t)?.route??v.basename(t),h=[...C(),...await M(t)].map(s=>s.text),{repo:n}=D(t),r=await L({kind:"claude",model:"sonnet",cwd:n,site:t,prompt:ce({...e,route:a,repo:n,rules:h}),log:E(e.event,"intake")});return await X(t,e.key,{text:`${ee({kind:"claude",model:"sonnet"})} intake triage`,kind:"status",log:v.basename(r.log)}),r}u(Te,"launchIntake");const ue=6e4;function pe(t,{starts:e=B}={}){const a=t.filter(n=>n.pid&&n.at).map(n=>n.pid),h=a.length&&e(a)||new Map;return t.filter(n=>{const r=h.get(n.pid);return r!==void 0&&r<=Date.parse(n.at)&&r>=Date.parse(n.at)-ue})}u(pe,"launchedAgents");function Ie(t,{starts:e=B,kill:a=u((n,r)=>process.kill(-n,r),"kill"),killAfter:h=5e3}={}){const n=pe(t,{starts:e});for(const{pid:r}of n){try{a(r,"SIGTERM")}catch{continue}setTimeout(()=>{try{a(r,"SIGKILL")}catch{}},h).unref()}return n.length}u(Ie,"stopAgents");async function je(t,{grace:e=5e3}={}){const a=V(["-A","-o","pid=,ppid=,pgid=,args="]);if(a===null)throw new z("ps gave no answer, so nothing was stopped; run it again");const h=a.split(`
9
- `).map(o=>/^\s*(\d+)\s+(\d+)\s+(\d+)\s+(.*)$/.exec(o)).filter(Boolean).map(([,o,c,w,m])=>({pid:Number(o),ppid:Number(c),pgid:Number(w),args:m})),n=new Set(h.filter(o=>t.includes(o.pgid)).map(o=>o.pid));for(let o=!0;o;){o=!1;for(const c of h)!n.has(c.pid)&&n.has(c.ppid)&&(n.add(c.pid),o=!0)}const r=h.find(o=>o.pid===process.pid)?.pgid,s=h.filter(o=>n.has(o.pid)&&o.pgid>1&&o.pgid!==r),l=[...new Set(s.map(o=>o.pgid))],d=u(o=>{for(const c of l)try{process.kill(-c,o)}catch{}},"signal");d("SIGTERM");const i=Date.now()+e;for(;s.some(o=>S(o.pid))&&Date.now()<i;)await new Promise(o=>setTimeout(o,50));return s.some(o=>S(o.pid))&&d("SIGKILL"),s.map(({pid:o,args:c})=>({pid:o,args:c}))}u(je,"stopRuns");const S=u(t=>{try{return process.kill(t,0),!0}catch(e){return e.code==="EPERM"}},"pidAlive");async function Ne(t,e,{launch:a,handBack:h,alive:n=S,held:r=u(()=>!1,"held"),stopped:s=new Set}){const l={};for(const[d,i]of Object.entries(t)){if(!e.has(d))continue;if(s.has(d)){l[d]={...i,handedBack:!0};continue}if(i.pid!==null&&n(i.pid)||r(i.kind??"claude")||i.handedBack){l[d]=i;continue}const o={key:d,...i.kind?{kind:i.kind}:{},...i.model?{model:i.model}:{}};if(i.pid!==null&&i.relaunched){await h(o),l[d]={...i,handedBack:!0};continue}const{key:c,...w}=o;l[d]={...w,...await a(o),relaunched:i.pid!==null}}return l}u(Ne,"reviveBuilders");const E=u((t,e)=>v.join(K.homedir(),".claude","kickflip","logs",`${e}-${t}-${new Date().toISOString().replace(/[:.]/g,"-")}.log`),"logFile");async function Se(t,{key:e,kind:a="claude",model:h,agent:n="main",text:r,files:s,question:l,note:d,choice:i,task:o,writes:c,worktree:w},{lander:m=!1}={}){const p=await J(t,e),g=P(t)?.route??v.basename(t),f=[...C(),...await M(t)].map(U=>U.text),{base:I,repo:j}=D(t),y=te({kind:a}),k=a===Z,A=!y&&!k&&!m&&p.rebasePR!==null,W=a===oe,q=y?j:k?w??p.worktree??j:p.worktree,$={...p,route:g,base:I,repo:j,rules:f,kind:a,agent:n,text:r,files:s,question:l,note:d,choice:i,task:o,writes:c,cwd:q,pr:p.rebasePR},F=W?"Visual agent":k?`Helper ${n}`:y?"Planner":m?"Lander":A?"Rebase run":"Builder",H=W?de($):k?le($):y?he($):m?se($):A?ie($):ae($),x=await L({kind:y||k?"claude":a,model:h,cwd:q,site:t,prompt:H,log:E(e.split("/")[1],F.split(" ")[0].toLowerCase())});return await Q(t,e,x.sessionId,x.pid,n,v.basename(x.log)),x}u(Se,"launchBuilder");export{re as BOARD_CLI,ae as builderBrief,le as helperBrief,ce as intakeBrief,se as landerBrief,Se as launchBuilder,Te as launchIntake,pe as launchedAgents,S as pidAlive,he as plannerBrief,ie as rebaseBrief,Ne as reviveBuilders,Ie as stopAgents,je as stopRuns,de as visualBrief};
1
+ var _=Object.defineProperty;var l=(t,e)=>_(t,"name",{value:e,configurable:!0});import V from"node:os";import x from"node:path";import{fileURLToPath as z}from"node:url";import{launchAgent as B}from"./agents.mjs";import{processStarts as L,ps as X}from"../hub/live.mjs";import{boardAt as P}from"../hub/boards.mjs";import{readHubRules as C}from"../hub/rules.mjs";import{builderRepo as M}from"./worktree.mjs";import{BoardError as J,appendThread as Q,briefFacts as Z,readBoardRules as Y,recordSession as ee}from"./store.mjs";import{HELPER as te,modelName as oe,plans as re,VISUAL as ne}from"./domain.mjs";const ae=z(new URL("./bin/board.mjs",import.meta.url)),b=l(t=>`node ${ae} --site ${t}`,"cli"),O=l(t=>`git -c core.commentChar=';' rebase ${t}`,"rebase"),E="a draft .md of your own outside the board's data folder (such as in /tmp)",j=l((t,e,a)=>`\`${t} item notes ${e} --heading "${a}" --file <draft.md>\``,"notesCommand"),T=l((t,e="Rules from the hub and this board:")=>t.length?[e,...t.map(a=>`- ${a}`),""]:[],"ruleLines"),N=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||"no notes"}`),""]:[],"linkLines"),F=l(({route:t,key:e,worktree:a,base:d})=>[`- With no unverified calls, done finishes the card itself: it rebases onto ${d}, reruns the full test suite, fast-forwards ${d} 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 \`${O(d)}\` in ${a}, 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 \`${b(t)} builder call ${e} "<why>"\` and run done again: with the call it hands the card back.`],"finishSteps"),S=l(({route:t,key:e})=>`\`${b(t)} builder done ${e} --text "<what you checked>"\``,"finishCommand"),se=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 ie({route:t,key:e,ref:a,title:d,notes:n,worktree:r,base:s,rules:u=[],links:p=[]}){const f=b(t);return[`You are the headless builder for card ${a} (${e}, "${d}") on the kickflip board /${t}. The board launched you; nobody watches this session, so never wait on a person. Work only in ${r}. Never restart the hub or a board server, and never push.`,"",`1. Run \`${f} builder start ${e} --kind build\`. If it refuses, stop.`,`2. Report progress with \`${f} 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. To ask the orchestrator something, run \`${f} builder ask ${e} "<question>"\` and carry on; read its replies with \`${f} builder asks ${e}\`. Never block on a reply.`,`4. Record each judgment call the notes leave open with \`${f} builder call ${e} "<call>"\` instead of guessing silently.`,...se(f,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.","",...N(p),...T(u),"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 "${a}: <what the card now does>".`,"",`To finish, run ${S({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:r,base:s}),"","Never push."].join(`
2
+ `)}l(ie,"builderBrief");function he({route:t,key:e,ref:a,title:d,worktree:n,base:r,answers:s=[]}){const u=b(t);return[`You are the headless lander for card ${a} (${e}, "${d}") 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 ${n}, never restart the hub or a board server, and never push.`,"",`1. Run \`${u} builder start ${e} --kind build --label Lander --phase landing --progress "taking up the verified build"\`. If it refuses, stop.`,`2. Report progress with \`${u} builder progress ${e} --phase landing "<plain, 80 characters or fewer>"\`. The board adds the phase word; don't start the text with it.`,...s.length?["3. The owner answered the builder's deferred asks. Where an answer changes the build, change it, with its tests, and commit:",...s.map(p=>`- ${p.ask} "${p.question}": ${p.answer}`)]:[],"",`To finish, run ${S({route:t,key:e})}.`,...F({route:t,key:e,worktree:n,base:r}),"","Never push."].join(`
3
+ `)}l(he,"landerBrief");function de({route:t,key:e,ref:a,title:d,worktree:n,base:r,pr:s}){const u=b(t);return[`You are the headless rebase run for card ${a} (${e}, "${d}") on the kickflip board /${t}. Its PR #${s} conflicts with ${r}; rebase its branch onto ${r} so the PR is clean again. Nobody watches this session, so never wait on a person. Work only in ${n}, never restart the hub or a board server, and never push.`,"",`1. Run \`${u} builder start ${e} --kind build --label Rebase --phase building --progress "Rebase run started \xB7 fetching ${r}"\`. If it refuses, stop.`,`2. Run \`git fetch origin\`, then \`${O(`origin/${r}`)}\`. Resolve each conflict keeping both sides' intent, \`git add\` it and continue with \`git -c core.commentChar=';' rebase --continue\`. Report each step with \`${u} builder progress ${e} --phase building "<line>"\`, the board adds the phase word, in these words: "Rebasing onto ${r} \xB7 resolving <n> files", then "Rebasing onto ${r} \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 \`${u} 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.`,"",`To finish, run ${S({route:t,key:e})}, the text saying what you resolved. It reruns the suite and pushes the branch to PR #${s} 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(de,"rebaseBrief");function le({route:t,key:e,ref:a,title:d,notes:n,kind:r,repo:s,rules:u=[],links:p=[],text:f,files:o=[],question:i,note:h,choice:m}){const c=b(t),g=r==="grill";return[`You are the headless ${g?"grill":"prototype"} planner for card ${a} (${e}, "${d}") 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 ${s}; write no output code ${g?"there":`in ${s} itself`}, never restart the hub or a board server, and never push.`,"",`1. Run \`${c} builder start ${e} --kind ${r}\`. 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, in the board site's data/threads folder, the repo's docs/adr and CONTEXT.md, and the cards its notes and thread link.`,`3. Report progress with \`${c} 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 \`${c} question add --file <json>\` on topic item:${e}, one per decision, your recommendation first. 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.) 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: the board moves the card to prototyping on yes, or to staging on no once the owner has answered the rest, 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. Build the round in situ, in the board's base prototype worktree: run \`${c} 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 \`${c} 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 \`${c} question add --file <json>\` on topic item:${e}, never asking in the chat. Only a repo whose app can't be served as a web page keeps take files: \`${c} prototype add <topic> --card ${e} --take <K>:<label> \u2026\`, one take file per option built from the board's real components fed fake data. Invoke the prototype skill with the Skill tool for this step. (With no Skill tool, follow this step as written.)`,...f?[` The owner asked for this ${g?"grill":"prototype"}, writing: "${f}"`]:[],...o.length?[` The owner attached: ${o.map($=>$.path).join(", ")}.`]:[],...i?[` The owner asked for prototypes of question ${i}${h?`, noting "${h}"`:""}${m?`, leaning to option ${m}`:""}: draw that question's next round, its question naming round_of ${i}.`]:[],`5. Record what you learned in the card's notes, naming each answered question's id: write it in ${E}, then run ${j(c,e,`${g?"Grill":"Prototype"} (<MM/DD>)`)}, which appends it as a section. The notes file is read-only; never write into it.`,...g?[`6. Where the owner asked for prototypes, or a rule here wants a UI prototype first, end the card \`${c} item set ${e} --state 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.`]:[],"",...N(p),...T(u,"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 \`${c} builder done ${e} --text "<what you asked>"\`.`,"","Never push."].join(`
5
+ `)}l(le,"plannerBrief");function ce({route:t,key:e,ref:a,title:d,notes:n,repo:r,agent:s,rules:u=[],links:p=[],text:f,files:o=[]}){const i=b(t);return[`You are the headless visual agent for card ${a} (${e}, "${d}") 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 ${r}; write no code there, never restart the hub or a board server, and never push.`,"",`1. Run \`${i} builder start ${e} --kind visual --agent ${s}\`. If it refuses, stop.`,`2. Gather what the page needs: ${n?`the card's notes, ${n}; `:""}its thread, in the board site's data/threads folder, and the code or docs it is about.`,f?` The owner wrote: "${f}"`:" The owner typed nothing: draw what the thread was last discussing.",...o.length?[` The owner attached: ${o.map(h=>h.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 \`${i} 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.","",...N(p),...T(u,"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 \`${i} builder done ${e} --agent ${s}\`.`,"","Never push."].join(`
6
+ `)}l(ce,"visualBrief");function ue({route:t,key:e,ref:a,title:d,notes:n,agent:r,task:s,writes:u,cwd:p,rules:f=[],links:o=[]}){const i=b(t);return[`You are the headless helper ${r} on card ${a} (${e}, "${d}") 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.`,u?`Work only in ${p}, your own worktree, branched from the card's: commit your work there with \`git -c core.commentChar=';' commit -F <file>\`, the subject "${a}: <what it does>". The card's builder merges your branch, so leave nothing uncommitted.`:`Read and run in ${p}; change no files there and commit nothing.`,"","The task:",s,"",`1. Run \`${i} builder start ${e} --kind helper --agent ${r}\`. If it refuses, stop.`,`2. Report progress with \`${i} builder progress ${e} --agent ${r} --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}.`:"","",...N(o),...T(f,"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 \`${i} builder done ${e} --agent ${r} --text "<your hand-back: what you did or found, ${u?"what you committed, ":""}and any decision you made>"\`.`,"","Never push."].filter((h,m,c)=>h!==""||c[m-1]!=="").join(`
7
+ `)}l(ue,"helperBrief");function pe({route:t,repo:e,rules:a=[],waves:d=[],cards:n=[],event:r,key:s,kind:u,quick:p=!1,thread:f=[]}){const o=b(t),i=f.map(w=>`- ${w.who}: ${w.text}${w.files?.length?` (attached: ${w.files.map(k=>k.path).join(", ")})`:""}`),h=p?"--state staging --size <small|large>":"--state <grilling|prototyping>",m=p?" The owner chose Implement: put the plan in the notes; the orchestrator judges it from staging before it goes ready.":" --state prototyping when it changes the hub's or a board's UI, --state grilling for anything else; the board starts the grill or prototype itself.",c=l(w=>[` 1. Write its notes in ${E}: open with the owner's words verbatim, under "## Asked (owner, <date>)", then a short "## What it is" in plain words and a "## Related" line naming each linked card and how they overlap. Add that line to each linked card's notes with ${j(o,"<linked key>","Related")} on a draft of its own.`,` 2. ${w}`,m],"fileIt"),g=l(w=>`\`${o} item add <wave> --title "${w}" --notes-from <draft.md>\``,"add"),$={ask:[`Your job: sort new task ${r}, on card ${s}, one of these ways:`,`- Answer it, when it is a question or asks for nothing to build: \`${o} reply ${r} --file <reply.html>\`.`,`- Fold it into the open card it is about: \`${o} item relate-ask ${r} <key> --summary "<one line>"\`, then add the owner's words verbatim to that card's notes with ${j(o,"<key>","Asked (owner, <date>)")}.`,`- File it as a new card, its notes drafted first (step 1): ${g("<the change, in plain words>")} prints "<label> added as <key>"; use that key.`,...c(`\`${o} item set <key> --from-ask ${r} --relate <keys> --text "<one line on what you filed>" ${h}\` (link every card it overlaps, depends on or would duplicate; --deps for one it truly waits on).`)],"item-message":[`Your job: the owner's message ${r} in the thread of new task ${s}, whose last line is theirs. Answer it with \`${o} reply ${r} --file <reply.html>\`, or, if it changes what the task asks, sort the task as the message now has it:`,`- fold it: \`${o} item relate-ask ${s.slice(4)} <key> --summary "<one line>"\`; or file it: ${g("<the change>")}, with its notes drafted as in step 1, then`,...c(`\`${o} item set <key> --from-ask ${s.slice(4)} --relate <keys> --text "<one line>" ${h}\`,`)," then reply to the message saying what you did."],"card-added":[`Your job: card ${s}, which an agent added unfiled (card-added ${r}). Read its notes and the cards its title looks related to, add what it is, why and where it came from to its notes with ${j(o,s,"What it is")}, and a "Related" section naming each linked card to it and to each linked card's notes the same way, each from a draft of its own. Then file it in one call:`,`\`${o} item set ${s} --relate <keys> --text "<one line>" --state <grilling|prototyping>\`: prototyping when it changes the hub's or a board's UI, grilling for anything else.`]};return[`You are an intake triage run on the kickflip board /${t}. The board launched you to sort one thing that reached it unsorted; nobody watches this session, so never wait on a person. Work in ${e}, reading only: you write only through the board's CLI, \`${o} <command>\`, notes included. Never set a card ready, never build, never push, and never restart the hub or a board server.`,"",...$[u],"",`If you can't tell how to sort it (which card it belongs to, or what it asks), don't guess: \`${o} intake handoff ${r} --text "<what you are unsure of>"\` hands it to the orchestrator, and stop.`,"Each command writes its own line in the thread, which is your report; add one with `"+o+" thread "+s+' --text "<line>"` only for what those lines leave unsaid. Every line stands alone: name cards by their label.',"",`The thread of ${s}:`,...i.length?i:["- (no messages)"],"",`The board's open cards, as label key "title", lane, state: notes file. Read the notes of any card whose title looks related before you choose.`,...n.length?n.map(w=>`- ${w.ref} ${w.key} "${w.title}", ${w.lane}, ${w.state||"no state"}: ${w.notes||"no notes"}`):["- (none)"],"","Waves a new card can go in, as slug: folder:",...d.map(w=>`- ${w.slug}: ${w.dir}`),"",...T(a,"Rules from the hub and this board, written for the orchestrator: where one tells you to push, land, build or restart, it isn't yours:"),"Sort that one thing, then exit. Never push."].join(`
8
+ `)}l(pe,"intakeBrief");async function je(t,e){const a=P(t)?.route??x.basename(t),d=[...C(),...await Y(t)].map(s=>s.text),{repo:n}=M(t),r=await B({kind:"claude",model:"sonnet",cwd:n,site:t,prompt:pe({...e,route:a,repo:n,rules:d}),log:H(e.event,"intake")});return await Q(t,e.key,{text:`${oe({kind:"claude",model:"sonnet"})} intake triage`,kind:"status",log:x.basename(r.log)}),r}l(je,"launchIntake");const fe=6e4;function we(t,{starts:e=L}={}){const a=t.filter(n=>n.pid&&n.at).map(n=>n.pid),d=a.length&&e(a)||new Map;return t.filter(n=>{const r=d.get(n.pid);return r!==void 0&&r<=Date.parse(n.at)&&r>=Date.parse(n.at)-fe})}l(we,"launchedAgents");function Ne(t,{starts:e=L,kill:a=l((n,r)=>process.kill(-n,r),"kill"),killAfter:d=5e3}={}){const n=we(t,{starts:e});for(const{pid:r}of n){try{a(r,"SIGTERM")}catch{continue}setTimeout(()=>{try{a(r,"SIGKILL")}catch{}},d).unref()}return n.length}l(Ne,"stopAgents");async function Se(t,{grace:e=5e3}={}){const a=X(["-A","-o","pid=,ppid=,pgid=,args="]);if(a===null)throw new J("ps gave no answer, so nothing was stopped; run it again");const d=a.split(`
9
+ `).map(o=>/^\s*(\d+)\s+(\d+)\s+(\d+)\s+(.*)$/.exec(o)).filter(Boolean).map(([,o,i,h,m])=>({pid:Number(o),ppid:Number(i),pgid:Number(h),args:m})),n=new Set(d.filter(o=>t.includes(o.pgid)).map(o=>o.pid));for(let o=!0;o;){o=!1;for(const i of d)!n.has(i.pid)&&n.has(i.ppid)&&(n.add(i.pid),o=!0)}const r=d.find(o=>o.pid===process.pid)?.pgid,s=d.filter(o=>n.has(o.pid)&&o.pgid>1&&o.pgid!==r),u=[...new Set(s.map(o=>o.pgid))],p=l(o=>{for(const i of u)try{process.kill(-i,o)}catch{}},"signal");p("SIGTERM");const f=Date.now()+e;for(;s.some(o=>A(o.pid))&&Date.now()<f;)await new Promise(o=>setTimeout(o,50));return s.some(o=>A(o.pid))&&p("SIGKILL"),s.map(({pid:o,args:i})=>({pid:o,args:i}))}l(Se,"stopRuns");const A=l(t=>{try{return process.kill(t,0),!0}catch(e){return e.code==="EPERM"}},"pidAlive");async function Ae(t,e,{launch:a,handBack:d,alive:n=A,held:r=l(()=>!1,"held"),stopped:s=new Set,limitedUntil:u=l(()=>null,"limitedUntil"),atLimit:p=l(async()=>{},"atLimit"),now:f=Date.now()}){const o={};for(const[i,h]of Object.entries(t)){if(!e.has(i))continue;if(s.has(i)){o[i]={...h,handedBack:!0};continue}if(h.pid!==null&&n(h.pid)||h.handedBack){o[i]=h;continue}const m={key:i,...h.kind?{kind:h.kind}:{},...h.model?{model:h.model}:{}},c=h.pid!==null?u(h):null;if(c){await p(m,c),o[i]={...h,pid:null,until:c};continue}if(r(h.kind??"claude")||h.until&&f<Date.parse(h.until)){o[i]=h;continue}if(h.pid!==null&&h.relaunched){await d(m),o[i]={...h,handedBack:!0};continue}const{key:g,...$}=m;o[i]={...$,...await a(m),relaunched:h.pid!==null||!!h.relaunched}}return o}l(Ae,"reviveBuilders");const H=l((t,e)=>x.join(V.homedir(),".claude","kickflip","logs",`${e}-${t}-${new Date().toISOString().replace(/[:.]/g,"-")}.log`),"logFile");async function We(t,{key:e,kind:a="claude",model:d,agent:n="main",text:r,files:s,question:u,note:p,choice:f,task:o,writes:i,worktree:h},{lander:m=!1}={}){const c=await Z(t,e),g=P(t)?.route??x.basename(t),$=[...C(),...await Y(t)].map(K=>K.text),{base:w,repo:k}=M(t),v=re({kind:a}),R=a===te,W=!v&&!R&&!m&&c.rebasePR!==null,q=a===ne,D=v?k:R?h??c.worktree??k:c.worktree,y={...c,route:g,base:w,repo:k,rules:$,kind:a,agent:n,text:r,files:s,question:u,note:p,choice:f,task:o,writes:i,cwd:D,pr:c.rebasePR},U=q?"Visual agent":R?`Helper ${n}`:v?"Planner":m?"Lander":W?"Rebase run":"Builder",G=q?ce(y):R?ue(y):v?le(y):m?he(y):W?de(y):ie(y),I=await B({kind:v||R?"claude":a,model:d,cwd:D,site:t,prompt:G,log:H(e.split("/")[1],U.split(" ")[0].toLowerCase())});return await ee(t,e,I.sessionId,I.pid,n,x.basename(I.log)),I}l(We,"launchBuilder");export{ae as BOARD_CLI,ie as builderBrief,ue as helperBrief,pe as intakeBrief,he as landerBrief,We as launchBuilder,je as launchIntake,we as launchedAgents,A as pidAlive,le as plannerBrief,de as rebaseBrief,Ae as reviveBuilders,Ne as stopAgents,Se as stopRuns,ce as visualBrief};