@lovek4d/kickflip 0.7.0 → 0.7.2

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 (68) hide show
  1. package/CONTEXT.md +53 -49
  2. package/SKILL.md +17 -13
  3. package/bin/event-lines.mjs +1 -1
  4. package/bin/kickflip.mjs +3 -3
  5. package/board/agents.mjs +2 -2
  6. package/board/arch-review.mjs +4 -4
  7. package/board/bin/board.mjs +113 -105
  8. package/board/domain.mjs +7 -7
  9. package/board/finish.mjs +4 -4
  10. package/board/headless.mjs +8 -8
  11. package/board/hub-lines.mjs +1 -0
  12. package/board/onboarding.mjs +3 -3
  13. package/board/public/app-arch.js +1 -1
  14. package/board/public/app-cards.js +6 -7
  15. package/board/public/app-header.js +1 -1
  16. package/board/public/app-lanes.js +39 -62
  17. package/board/public/app-needs.js +16 -11
  18. package/board/public/app-reader.js +9 -5
  19. package/board/public/app.js +3 -3
  20. package/board/public/attach.js +18 -2
  21. package/board/public/dialogs.js +11 -13
  22. package/board/public/hub-kit.js +1 -1
  23. package/board/public/hub.css +0 -1
  24. package/board/public/linear-key.js +2 -2
  25. package/board/public/new-task.js +1 -1
  26. package/board/public/prototype-kit.js +2 -5
  27. package/board/public/router.js +1 -1
  28. package/board/public/sidebar.css +3 -3
  29. package/board/public/sidebar.js +2 -2
  30. package/board/public/style.css +43 -43
  31. package/board/public/ui-cards.js +25 -20
  32. package/board/public/ui-hub.js +1 -5
  33. package/board/public/ui-links.js +5 -5
  34. package/board/public/ui-order.js +6 -7
  35. package/board/public/ui-questions.js +17 -13
  36. package/board/public/ui-sidebar.js +1 -1
  37. package/board/public/ui-threads.js +29 -21
  38. package/board/public/ui.js +23 -6
  39. package/board/public/view-hub-settings.js +11 -19
  40. package/board/public/view-hub.js +25 -11
  41. package/board/public/view-notes.js +1 -1
  42. package/board/public/view-questions.js +18 -5
  43. package/board/public/view-settings.js +7 -7
  44. package/board/render-check.mjs +10 -10
  45. package/board/review.mjs +1 -1
  46. package/board/server.mjs +18 -19
  47. package/board/stop.mjs +1 -1
  48. package/board/store.mjs +26 -26
  49. package/board/tickets.mjs +3 -3
  50. package/hooks-plugin/hooks/board-data-gate.mjs +6 -6
  51. package/hub/awake.mjs +1 -1
  52. package/hub/board-repo.mjs +1 -1
  53. package/hub/boards.mjs +4 -4
  54. package/hub/github.mjs +3 -3
  55. package/hub/hub.mjs +11 -11
  56. package/hub/incoming.mjs +2 -2
  57. package/hub/linear.mjs +1 -1
  58. package/hub/locks.mjs +1 -1
  59. package/hub/new-board.mjs +2 -2
  60. package/hub/owner-checks.mjs +1 -1
  61. package/hub/readiness.mjs +1 -1
  62. package/hub/review-send.mjs +1 -1
  63. package/hub/slack.mjs +1 -1
  64. package/hub/try.mjs +6 -7
  65. package/hub/views.mjs +1 -1
  66. package/package.json +1 -1
  67. package/shared/usage-gate.mjs +1 -1
  68. package/starter/server.mjs +3 -3
package/board/server.mjs CHANGED
@@ -1,24 +1,23 @@
1
1
  #!/usr/bin/env node
2
- var fa=Object.defineProperty;var c=(t,e)=>fa(t,"name",{value:e,configurable:!0});async function ce(t,e){let a;try{a=await y.open(p.join(t,"data","try",`${e}.log`),"r");const{size:n}=await a.stat(),i=Math.max(0,n-64*1024),s=Buffer.alloc(n-i),{bytesRead:l}=await a.read(s,0,s.length,i),u=s.subarray(0,l).toString("utf8").split(/\r?\n/);return i&&u.shift(),u.at(-1)===""&&u.pop(),u.slice(-40)}catch(n){if(n.code==="ENOENT")return[];throw n}finally{await a?.close()}}c(ce,"tryLogTail");import{topicItems as ha}from"./review.mjs";import{boardWorktree as $t,builderRepo as wa,helperWorktree as pa,moveQueuedLand as ma,prWorktree as ga}from"./worktree.mjs";import{transcriptMovedAt as ya,transcriptCommandRunning as ka,readRunTranscript as ba}from"./transcript.mjs";import{fromHost as va}from"./on-host.mjs";import{readSync as Sa,writeSync as Oa,stamp as ja,patchSince as Pa}from"./sync.mjs";import{archOf as $a,readReviews as Aa,repoName as Ta,startReview as Ea}from"./arch-review.mjs";import Ra from"node:http";import y from"node:fs/promises";import et from"node:os";import p from"node:path";import{execFile as le,spawn as Ia}from"node:child_process";import{fileURLToPath as _a}from"node:url";import ue from"node:vm";import Na from"node:zlib";import xa from"node:crypto";import{promisify as Da}from"node:util";import{archivedOf as de,buildBoard as At,movesOf as Ca,visualsDue as Ha,withHub as Ba,DONE_KEPT as fe,ORCHESTRATOR_PUSH_AFTER as La,orchestratorWaits as Fa,triageWaitOf as Ma,waitsStep as Ua,PROTOTYPE_WAIT as Wa,prototypeWaits as Ja,idleStep as Ga,idleSummary as Ka,ITEM_EVENTS as za,QUESTION_REOPEN as Tt,lineupMoved as Va,slimDone as he,needsAnswer as Qa,ownerActions as Ya,picksNoTake as Xa,pullOf as we,arrivalsOf as Za,rebasesDue as qa,refOf as Et,roundsDue as tn,strictlyIdle as en,threadOf as an,triageOf as nn}from"./domain.mjs";import{localDay as rn,quietAt as sn,quietMiddleOf as on,quietOf as pe}from"../hub/schedule.mjs";import{cardPRs as cn,prKeyOf as me}from"../shared/card-prs.mjs";import{dropQueued as ln}from"../hub/push.mjs";import{closeSync as un,createReadStream as dn,existsSync as ge,mkdirSync as fn,openSync as hn,readdirSync as wn,statSync as ye,watch as ke}from"node:fs";import{PROTOTYPE_TEMPLATE as pn,esc as mn,prototypePage as gn,versionOf as Rt}from"./prototype-page.mjs";import{BOARD_CLI as yn,launchBuilder as K,pidAlive as be,reviveBuilders as kn,stopAgents as bn}from"./headless.mjs";import{BoardError as R,publishUnscreened as vn,readScreening as Sn,startHelper as On,startVisual as jn,openAgents as Pn,isLanding as $n,implementAsIs as An,stageImplement as Tn,stageReply as En,planAgain as Rn,heldMerge as In,opensPRs as _n,tapMerge as Nn,addTopic as xn,appendThread as x,archiveCards as Dn,archiveCount as Cn,archiveRank as Hn,readArchive as ve,boardConfig as Bn,changeLineup as Ln,abandonLaunch as Fn,cutOffBuilds as Mn,abandonPlanner as yt,landable as Un,relaunchLander as Wn,migrateFollowups as Jn,migratePhases as Gn,migrateStatuses as Kn,moveCard as zn,stageImplementAsks as Vn,startPlanner as Qn,stripDeadFields as Yn,pickUp as Xn,startRebase as Zn,startTappedBuild as It,sendPush as qn,recordOwnerTap as ti,takeHoldStopped as ei,clearStall as ai,mergeCard as ni,readPrototypes as ii,finishHandshake as ri,itemSources as kt,readAskLinks as _t,readAskProgress as si,readAskTitles as oi,readBoardRules as ci,readBoardSettings as li,readHandshake as ui,readHidden as di,readLineupNotes as fi,readMarks as hi,readNotes as wi,readWorking as bt,readUpload as pi,addReviewers as mi,removeReviewers as gi,markRead as Se,resolveNoteAnswers as yi,settleAnswers as ki,splitSharedNotes as bi,unrecorded as vi,saveBoardRules as Si,saveBoardSettings as Oi,saveBoardShare as ji,saveUpload as Pi,setBoardPause as $i,setHidden as Oe,settingsOf as C,threadFile as Ai,wavesDir as Ti}from"./store.mjs";import{changed as Ei,readHubRules as Ri}from"../hub/rules.mjs";import{boardAt as Y,listenState as Ii,linksOf as _i}from"../hub/boards.mjs";import{pendingFor as Ni,shown as xi}from"../hub/prompts.mjs";import{routeRecord as F}from"../hub/routes.mjs";import{CHOICES as Di,FOREVER as je,durationEnd as Pe}from"../hub/duration.mjs";import{VIEW_PAGES as Ci,shellHtml as Hi}from"../hub/shell.mjs";import{crossSite as Bi,isJSON as Li}from"../starter/cross-site.mjs";import{shareEntries as Fi}from"../hub/share.mjs";import{readUsageGate as J,launchHeld as z,pauseReasonFor as Nt,boardPausedUntil as Mi,MACHINE_HELD_LINE as Ui}from"../shared/usage-gate.mjs";import{runLimit as Wi}from"../hub/usage.mjs";import{audit as Ji,boardAudit as Gi}from"../shared/audit.mjs";import{reviewWaits as Ki}from"../shared/skills.mjs";import{boardRepo as xt,mainRefOf as zi}from"../hub/board-repo.mjs";import{listBuildTargets as Vi}from"./build-script.mjs";import{permissionsView as Qi,setPermission as Yi}from"./permissions.mjs";import{bumpOut as Xi}from"./stop.mjs";import{SlackError as Zi,SLACK_NEEDS_TOKEN as $e,savedSlack as Ae,slackStatus as Te}from"../hub/slack.mjs";import{collaborators as qi,githubUser as tr}from"../hub/pr-report.mjs";const at=p.dirname(_a(import.meta.url));async function Ee(){const t=await y.stat(p.join(at,"..","hub","icon-180.png")).catch(()=>null);return`/_hub/icon-180.png?v=${t?Math.floor(t.mtimeMs).toString(36):"0"}`}c(Ee,"touchIcon");async function er(t){if(!t.includes("<!--header "))return t;const e=ue.createContext({});return ue.runInContext(await y.readFile(p.join(at,"public","ui-page-header.js"),"utf8"),e),t.replace(/<!--header (\{.*?\})-->/g,(a,n)=>e.BoardUI.header(JSON.parse(n)))}c(er,"pageHeaders");const o=p.resolve(process.argv[2]||process.cwd()),ar=[p.join(o,"public"),p.join(at,"public"),p.join(at,"..","starter","public"),p.join(at,"..","shared")],S=p.join(o,"data"),E=c(t=>Ji(Gi(o),t),"logCall"),H=p.join(S,"events.jsonl"),vt=p.join(S,"replies"),Re=Number(process.env.PORT||4600),G=(process.env.PREFIX||"").replace(/\/+$/,""),Ie={".html":"text/html; charset=utf-8",".css":"text/css; charset=utf-8",".js":"text/javascript; charset=utf-8",".mjs":"text/javascript; charset=utf-8",".json":"application/json; charset=utf-8",".svg":"image/svg+xml",".png":"image/png",".ico":"image/x-icon",".txt":"text/plain; charset=utf-8",".md":"text/markdown; charset=utf-8",".plist":"text/xml; charset=utf-8",".ipa":"application/octet-stream",".apk":"application/vnd.android.package-archive"},nr=Da(Na.gzip),ir=/^(text\/|application\/json|image\/svg\+xml)/;async function _e(t,e,a,n){!["GET","HEAD"].includes(t.req?.method)&&!t.req.boardRead&&_++;const i={...a};let s=Buffer.isBuffer(n)?n:Buffer.from(n??"");if(ir.test(i["content-type"]||"")&&(i.vary="accept-encoding",s.length>=1024&&/\bgzip\b/.test(t.req?.headers["accept-encoding"]||"")))try{s=await nr(s),i["content-encoding"]="gzip"}catch{}i["content-length"]=s.length,t.writeHead(e,i),t.end(t.req?.method==="HEAD"?void 0:s)}c(_e,"send");const f=c((t,e,a,n={})=>_e(t,e,{"content-type":Ie[".json"],"cache-control":"no-store",...n},JSON.stringify(a)),"json"),Ne=/\b(src|href)="([\w.][\w./-]*\.(?:js|css))"/g;async function rr(t,e){const a=e.endsWith("/")?e:p.posix.dirname(e),n=[...new Set([...t.matchAll(Ne)].map(s=>s[2]))],i=new Map(await Promise.all(n.map(async s=>{const l=await Je(p.posix.join(a,s)),u=l?await y.stat(l).catch(()=>null):null;return[s,u?Rt(u):null]})));return t.replace(Ne,(s,l,u)=>i.get(u)?`${l}="${u}?v=${i.get(u)}"`:s)}c(rr,"versionAssets");const xe=c((t,e)=>String(t||"").split(",").map(a=>a.trim().replace(/^W\//,"")).includes(e.replace(/^W\//,"")),"sameTag"),j=c(async(t,e)=>{try{return JSON.parse(await y.readFile(t,"utf8"))}catch{return e}},"readJSON"),M=c(async t=>{try{return(await y.readFile(t,"utf8")).split(`
3
- `).filter(Boolean).map(e=>JSON.parse(e))}catch{return[]}},"readLines"),nt=c(t=>Ai(o,t),"threadFile"),Dt=c(async()=>{const t=await di(o);return(await M(H)).filter(e=>!t.has(e.id))},"shownEvents");async function B(t,e=64*1024){if(!Li(t.headers))throw Object.assign(new Error("Send the body as application/json."),{status:403});let a="";for await(const n of t)if(a+=n,a.length>e)throw Object.assign(new Error("too large"),{status:413});return a?JSON.parse(a):{}}c(B,"readBody");const sr=new Set(["owner-answer","owner-answers","prototype-request","reask-request",Tt,"grill-more","visual-request","item-message","ask","ask-edited","ask-withdrawn","ask-bump","ask-done","card-build","card-hold","card-done","card-bump","card-unbump","lineup-move","plan-waves"]),or="This board is shared with you to look at only.",cr="Merge and Board settings stay the owner\u2019s.";async function lr(t,e){const a=t.headers["x-kickflip-shared"];return a?Fi(await L()).find(i=>i.login===a)?.level!=="full"||!["/_claude/events","/api/upload"].includes(e)||t.method!=="POST"?{refused:or}:{by:{login:a,name:t.headers["tailscale-user-name"]||null}}:null}c(lr,"sharedWriter");const U=c(async(t,e)=>(await Ct([[t,e]]))[0],"appendEvent");async function Ct(t){const e=new Date().toISOString(),a=t.map(([n,i])=>({id:"e"+Date.now().toString(36)+Math.random().toString(36).slice(2,7),ts:e,kind:n,data:i}));return await y.mkdir(vt,{recursive:!0}),await y.appendFile(H,a.map(n=>JSON.stringify(n)+`
4
- `).join("")),_++,ze=Ve(H),a.map(n=>n.id)}c(Ct,"appendEvents");async function De(t){const e=await vi(o,t),a=new Map(((await j(p.join(S,"owner.json"),{items:[]})).items||[]).map(l=>[l.id,l]));if(!e.length)return[];const n=c(l=>a.has(String(l.id))&&Xa(a.get(String(l.id)),l.choice),"again"),i=await Ct(e.map(l=>["owner-answer",n(l)?{...l,prototype_again:!0}:l]));await yi(o,e);const{prototype:s}=await ki(o,e);_++;for(const l of s)await tt(l,"prototype");return i}c(De,"recordAnswers");async function ur(t){if(!Array.isArray(t)||!t.length)return"Send at least one answer.";const e=new Set(((await j(p.join(S,"owner.json"),{items:[]})).items||[]).map(n=>n.id)),a=t.filter(n=>!e.has(String(n?.id)));return a.length?`${a.map(n=>String(n?.id)).join(", ")} ${a.length===1?"is":"are"} no longer asked here. Reload to see what is, then send again.`:null}c(ur,"answersRefused");async function dr(t){const e=(await N()).owner.find(a=>a.id===t);return e?["open","requested"].includes(e.state)?`${t} has no answer sent, so there is nothing to reopen.`:null:`${t} is no longer asked here. Reload to see what is.`}c(dr,"reopenRefused");async function fr(t){const e=(await N()).owner.find(a=>a.id===t);return e?e.state!=="resolved"?`${t} isn\u2019t resolved, so there is nothing to ask again.`:null:`${t} is no longer asked here. Reload to see what is.`}c(fr,"reaskRefused");async function it(t){for(const e of["html","json"]){const a=p.join(vt,`${t.id}.${e}`);try{const[n,i]=await Promise.all([y.readFile(a,"utf8"),y.stat(a)]);return{...t,reply:{[e]:e==="json"?JSON.parse(n):n,at:i.mtime.toISOString()}}}catch{}}return{...t,reply:null}}c(it,"withReply");async function hr(t,e){const a=await bt(o);let n=await Dt();return t&&(n=n.filter(i=>i.kind===t)),Promise.all(n.reverse().slice(0,e).map(async i=>{const s=await it(i);return s.reply||!F(a,i.id)?s:{...s,working:F(a,i.id)}}))}c(hr,"listEvents");const Ht=new Map,wr="number,title,url,state,isDraft,reviewDecision,mergeStateStatus,mergeable,statusCheckRollup,mergedAt,headRefName,headRefOid,updatedAt,latestReviews";function pr(t,e){return new Promise(a=>{le("gh",["pr","view",String(e),"--repo",t,"--json",wr],{timeout:2e4},(n,i)=>{if(n)return a(null);try{a(JSON.parse(i))}catch{a(null)}})})}c(pr,"ghPR");function mr(t,e){return new Promise(a=>{le("gh",["api",`repos/${t}/pulls/${e}/comments?per_page=100`],{timeout:2e4},(n,i)=>{if(n)return a(0);try{const s=JSON.parse(i),l=new Set(s.map(u=>u.in_reply_to_id).filter(Boolean));a(s.filter(u=>!u.in_reply_to_id&&/codex/i.test(u.user?.login||"")&&!l.has(u.id)).length)}catch{a(0)}})})}c(mr,"ghCodexOpen");function gr(t){const e=t||[];let a=0,n=0;for(const i of e){const s=(i.conclusion||i.state||"").toUpperCase(),l=(i.status||"").toUpperCase();["FAILURE","ERROR","CANCELLED","TIMED_OUT","ACTION_REQUIRED","STARTUP_FAILURE"].includes(s)?a++:(l&&l!=="COMPLETED"||s==="PENDING"||s==="EXPECTED")&&n++}return{total:e.length,fail:a,pending:n,state:a?"fail":n?"pending":e.length?"pass":"none"}}c(gr,"checksOf");async function yr(t,e){const a=`${t}#${e}`;let n=Ht.get(a);return n||(n={at:0,value:null,loading:null},Ht.set(a,n)),Date.now()-n.at>45e3&&!n.loading&&(n.loading=Promise.all([pr(t,e),mr(t,e)]).then(([i,s])=>{if(i){const{statusCheckRollup:l,latestReviews:u,...m}=i,k=Object.fromEntries((u||[]).map($=>[$.author?.login,$.state])),b={...m,checks:gr(l),reviews:k,codexOpen:s};JSON.stringify(b)!==JSON.stringify(n.value)&&_++,n.value=b}n.at=Date.now(),n.loading=null})),!n.value&&n.loading&&await n.loading,n.value}c(yr,"prState");const L=c(()=>Bn(o),"config");async function kr(){const t=await y.readdir(vt).catch(()=>[]),e=await Promise.all(t.map(n=>y.stat(p.join(vt,n)).then(i=>i.mtimeMs,()=>0))),a=Math.max(0,...e);return a?new Date(a).toISOString():null}c(kr,"latestReply");async function Ce(t){let e=[];try{e=(await y.readdir(t)).filter(i=>i.endsWith(".json"))}catch{}const a=await Promise.all(e.map(i=>j(p.join(t,i),null))),n=Object.create(null);return e.forEach((i,s)=>{if(!a[s])return;const[l,u="main"]=p.basename(i,".json").split("@");(n[l]||=Object.create(null))[u]=a[s]}),n}c(Ce,"readAgents");async function Bt(){const t=Y(o);return{cfg:await L(),github:t?.github,local:t?.local,hub:t?.hub,remote:t?.remote,branch:t?.branch,topicItems:ha(),hostBusy:J({dir:o}).machine.state==="busy"}}c(Bt,"outsideData");async function He(){return(await Promise.all((await kt(o)).map(async t=>{const e=await j(t.file,null);return!e||!Array.isArray(e.items)?null:{slug:t.slug,wave:e,agents:await Ce(p.join(t.dir,"agents"))}}))).filter(Boolean)}c(He,"liveWaves");async function br(t){const e=await He();for(const a of t){const n=e.find(i=>i.slug===a.wave);n?n.wave.items.push(a.item):e.push({slug:a.wave,wave:{items:[a.item]},agents:{}})}return e}c(br,"withArchived");async function Be({cfg:t,github:e,local:a,hub:n,remote:i,branch:s},l){const u=t.items==="waves";l??=await He();const m=Object.create(null);return await Promise.all(l.flatMap(({slug:k,wave:b})=>b.items.map(async $=>{m[`${k}/${$.id}`]=await M(nt(`${k}/${$.id}`))}))),{github:e,remote:i,branch:s,reviewers:_n({github:e,local:a,hub:n},C(t,{hub:n}))?t.reviewers||[]:[],fromWaves:u,autoMerge:C(t).auto_merge,skipDraft:C(t).skip_draft,autoBuild:C(t).auto_build,tickets:C(t).tickets,waves:l,threads:m,working:await bt(o),autoReview:C(t).auto_review,incoming:await j(p.join(S,"incoming.json"),null),reviewRecords:await j(p.join(S,"reviews.json"),{})}}c(Be,"itemSnapshot");async function Le(t,e=[]){const a=await Be(t,e.length?await br(e):void 0),{waves:n,threads:i,working:s}=a,l=Object.create(null),u=new Set(e.map(d=>d.key));await Promise.all(n.flatMap(({slug:d,wave:h})=>{const P=a.github||h.repo;return h.items.flatMap(w=>cn(w,P).map(async v=>{l[me(v)]=u.has(`${d}/${w.id}`)?Ht.get(me(v))?.value??null:await yr(v.repo,v.number)}))}));const m=new Set(["ask","owner-answer","grill-more","visual-request","item-message","wave-triage","overnight-triage"]),k=await Promise.all((await Dt()).map(d=>m.has(d.kind)||F(s,d.id)?it(d):{...d,reply:null}));await Promise.all(k.filter(d=>d.kind==="ask").map(async d=>{const h=await M(nt(`ask:${d.id}`));h.length&&(i[`ask:${d.id}`]=h)}));const b=Object.create(null),$=Object.create(null);for(const{agents:d}of n)for(const h of Object.values(d||{}).flatMap(P=>Object.values(P))){const P=h.worktree?.path,w=P&&!h.done?ya(P):null;w!==null&&(b[P]=w),P&&!h.done&&ka(P)&&($[P]=!0)}const g=Object.create(null),A=await j(p.join(S,"headless-builders.json"),{}),r=await j(p.join(S,"landers.json"),{});for(const[d,h]of[...Object.entries(r),...Object.entries(A)])h?.pid===null&&F(A,d)===h&&(g[d]="held"),Number.isInteger(h?.pid)&&(be(h.pid)?g[d]=!0:h.handedBack?g[d]=!1:delete g[d]);return{...a,moved:b,commandRunning:$,alive:g,try:await j(p.join(S,"try.json"),null),main:await j(p.join(S,"main.json"),null),prs:l,events:k,askLinks:await _t(o),askProgress:await si(o),askTitles:await oi(o),checked:await j(p.join(S,"checked.json"),null),watcher:await j(p.join(S,"watcher.json"),null),replyAt:await kr(),owner:(d=>({...d,items:[...d.items||[],...e.flatMap(h=>h.questions||[])]}))(await j(p.join(S,"owner.json"),{items:[]})),topicItems:t.topicItems,prSlack:await j(p.join(S,"pr-slack.json"),{}),hostBusy:t.hostBusy,screening:await Sn(o)}}c(Le,"snapshot");async function vr(t){if(!t.length)return[];const e=new Map(At(await Le(await Bt(),t)).items.map(a=>[a.key,a]));return t.map(a=>{const n=e.get(a.key);return he({...n,sortAt:a.closed_at,worktree:n.worktree&&!ge(n.worktree)?null:n.worktree})})}c(vr,"archivedCards");const X=G.slice(1)||p.basename(o);let _=0,rt=null;async function N(){const t=await Bt(),e=JSON.stringify([_,t]);if(rt?.key!==e){const a=Le(t).then(At);rt={key:e,built:a},a.catch(()=>{rt?.built===a&&(rt=null)})}return rt.built}c(N,"sharedBoard");let Lt=!1;async function Fe(){if(!Lt){Lt=!0;try{const t=await M(H),e=new Set(t.filter(a=>a.kind==="prototype-overdue").map(a=>a.data?.request));for(const a of Ja(await N(),t))e.has(a.request)||Date.now()-Date.parse(a.since)<Wa||await U("prototype-overdue",a)}catch(t){console.error("Prototype check failed:",t.message)}finally{Lt=!1}}}c(Fe,"checkPrototypeWaits");async function Sr(t){const e=await ui(o);if(!e?.ping?.id||e.done_at)return null;const a=t?.find(i=>i.link==="github"),n={events:e.ack?.id===e.ping.id,...a||!t?{github:!!a?.ok}:{}};return n.events&&n.github!==!1?(await ri(o),null):n}c(Sr,"setupOf");const Me=Di.map(([t,e])=>({value:t,label:e,ms:t===je?null:Pe(new Date(0),t)-new Date(0)})),Or=Number(process.env.KICKFLIP_PEOPLE_WAIT_MS)||12e3,Ue=c((t,e)=>Promise.race([t,new Promise((a,n)=>setTimeout(()=>n(new Error(`${e} did not answer in time`)),Or).unref())]),"withinWait");async function jr(){const t={slack:null,github:[]},e=Ae(),a=Y(o)?.github,[n,i]=await Promise.allSettled([!e||Te().refused?Promise.resolve(null):Ue(e.people(),"Slack"),a?Ue(qi(a),"GitHub"):Promise.resolve([])]);if(!e||Te().refused)t.slackProblem=$e;else if(n.status==="fulfilled")t.slack=n.value;else{const s=n.reason;t.slackProblem=s.message==="Slack: missing_scope"?"Slack needs the users:read scope; update the app on Hub settings \u203A Slack.":s.message}return i.status==="fulfilled"?t.github=i.value:t.githubProblem=`GitHub: ${i.reason.message}`,t}c(jr,"reviewerPeople");async function Pr(t){const e=Ae();if(!e)throw new R($e);return Promise.all(t.map(async a=>{const n=await tr(a.github.trim());if(!n)throw new R(`GitHub has no user ${a.github.trim()}.`);return{name:a.name.trim(),slack_user:a.slack_user.trim(),dm:await e.openDM(a.slack_user.trim()),github:n}}))}c(Pr,"withDMs");async function st(){const t=new Date().toISOString(),{title:e,reviewers:a}=await L(),n=Y(o),i=n?Ii(n):"deaf",s=Ba(await N(),{listen:i,prompts:n?Ni(n.route).map(xi):[]});s.items=s.items.map(u=>u.worktree&&!ge(u.worktree)?{...u,worktree:null}:u);const l=n?_i(n,{listen:i}):null;return{generatedAt:t,title:e,reviewers:(a||[]).map(u=>u?.name).filter(u=>typeof u=="string"),reviewerGithub:Object.fromEntries((a||[]).filter(u=>typeof u?.name=="string").map(u=>[u.name,u.github])),...s,links:l,setup:await Sr(l),holdChoices:Me,arch:n?$a(await Aa(o),s.items,s.owner,{repo:Ta(xt(n.route)),waits:Ki()}):null}}c(st,"board");async function $r(t){["GET","HEAD"].includes(t.method)||_++;const e={...await st(),doneArchived:await Cn(o)};e.owner=e.owner.filter(i=>i.state!=="resolved");const a=await hi(o);e.items=e.items.map(i=>({...i.lane==="done"?he(i):i,readAt:a[i.key]||null}));const n=new Set(e.items.map(i=>i.key));return e.ownerTopics=e.ownerTopics.map(({title:i,summary:s,askedAt:l,...u})=>n.has(u.card)?u:{...u,title:i,summary:s,...l?{askedAt:l}:{}}),e}c($r,"buildPageBoard");const Ft=p.join(S,"sync.json");let Z=await Sa(Ft),Mt=Z.n,Ut=Promise.resolve(),St=null;function Ar(){return Ut=Ut.then(async()=>{const t=Z;t.n!==Mt&&(await y.mkdir(S,{recursive:!0}),await Oa(Ft,t),Mt=t.n)}),Ut}c(Ar,"saveSync");async function Tr(t){const e=await $r(t);for((!St||e.generatedAt>=St.generatedAt)&&({sync:Z,board:St}=ja(Z,e));Mt<Z.n;)await Ar();return{sync:Z,board:St}}c(Tr,"pageBoard");async function W(t,e){if(new URL(t.url,"http://site").searchParams.get("board")==="0")return;const a=await va(t),{sync:n,board:i}=await Tr(t);return t.boardRead=!0,{...(/^\d+$/.test(e)?Pa(n,i,Number(e)):null)||i,onHost:a}}c(W,"answerBoard");let We=Promise.resolve();function Er(){const t=We.then(async()=>{const e=Y(o);if(!e)throw Object.assign(new R("This board isn\u2019t in the hub\u2019s manifest."),{status:404});const{arch:a}=await st();if(a.last?.open)return{key:a.last.key,ref:a.last.ref,existing:!0};const{key:n,data:i}=await Ea(o,e),s=await U("arch-review",i);return{key:n,id:s,existing:!1}});return We=t.catch(()=>{}),t}c(Er,"runReview");async function Rr(t){const[e,a]=t.split("/"),n=(await kt(o)).find(u=>u.slug===e),i=n?await j(n.file,null):null,s=!i?.items?.some(u=>u.id===a)&&(await ve(o)).find(u=>u.key===t),l=Array.isArray(i?.items)&&i.items.find(u=>u.id===a)||s?.item||null;return{item:l,questions:s?.questions||[],agents:l&&n?F(await Ce(p.join(n.dir,"agents")),a)||{}:{}}}c(Rr,"itemOf");async function Ir(t){const e=F(await _t(o),t),{tickets:a}=C(await L()),n=[];for(const i of await kt(o)){const s=await j(i.file,null);for(const l of Array.isArray(s?.items)?s.items:[]){const u=`${i.slug}/${l.id}`;l.from_ask===t?n.unshift({key:u,ref:Et(s,l,a),became:!0}):u===e&&n.push({key:u,ref:Et(s,l,a),became:!1})}}return n}c(Ir,"askLinksOf");async function _r(t){const{item:e,agents:a,questions:n}=t.startsWith("ask:")?{item:null,agents:{},questions:[]}:await Rr(t),i=await j(p.join(S,"owner.json"),{items:[]}),s={...i,items:[...i.items||[],...n]},l=new Set(s.items.filter(r=>r.topic===`item:${t}`).map(r=>r.id)),u=t.startsWith("ask:"),m=u?t.slice(4):e?.from_ask||null,k=u?[]:Object.entries(await _t(o)).filter(([,r])=>r===t).map(([r])=>r),b=[m,...k].filter(Boolean),$=new Set([t,...b.map(r=>`ask:${r}`)]),g=(await Dt()).filter(r=>$.has(r.data?.key)&&za.includes(r.kind)||r.kind==="owner-answer"&&l.has(r.data?.id)||r.kind===Tt&&l.has(r.data?.question)||r.kind==="ask"&&b.includes(r.id)||r.kind==="ask-edited"),A=(await Promise.all([...$].map(r=>M(nt(r))))).flat();return an(t,{lines:A,events:await Promise.all(g.map(it)),owner:s,working:await bt(o),fromAsk:m,links:u?await Ir(m):[],agents:a,moved:k})}c(_r,"thread");async function Je(t){for(const e of ar){let a=p.resolve(e,"."+t);if(a!==e&&!a.startsWith(e+p.sep))return null;if((await y.stat(a).catch(()=>null))?.isDirectory()&&(a=p.join(a,"index.html")),await y.stat(a).then(i=>i.isFile(),()=>!1))return a}return null}c(Je,"staticFile");async function Nr(t){const e=/^\/prototype-([a-z0-9-]+)\.html$/.exec(t)?.[1];return e&&(await ii(o)).find(a=>a.topic===e)||null}c(Nr,"recordedPrototype");async function xr(t,e){try{const a=decodeURIComponent(String(t.headers["x-filename"]||""));return f(e,200,await Pi(o,t,{name:a,type:t.headers["content-type"],length:Number(t.headers["content-length"])||0}))}catch(a){if(!(a instanceof R)&&!(a instanceof URIError))throw a;return t.resume(),f(e,a.status||400,{error:a.message})}}c(xr,"upload");const Dr=/^(text\/html|image\/svg\+xml|application\/(xhtml\+)?xml|text\/xml)$/;async function Cr(t,e,a){const n=await pi(o,a);if(!n){e.writeHead(404).end();return}if(e.writeHead(200,{"content-type":n.type.startsWith("text/")?`${n.type}; charset=utf-8`:n.type,"content-length":n.size,"content-disposition":`inline; filename*=UTF-8''${encodeURIComponent(n.name)}`,"cache-control":"private, max-age=86400","x-content-type-options":"nosniff",...Dr.test(n.type)?{"content-security-policy":"sandbox"}:{}}),t.method==="HEAD"){e.end();return}dn(n.path).pipe(e)}c(Cr,"sendUpload");const Wt=new Set;function Hr(t,e){e.writeHead(200,{"content-type":"text/event-stream; charset=utf-8","cache-control":"no-store",connection:"keep-alive"}),e.write(`retry: 3000
2
+ var ka=Object.defineProperty;var c=(e,t)=>ka(e,"name",{value:t,configurable:!0});async function ct(e,t){let a;try{a=await b.open(p.join(e,"data","try",`${t}.log`),"r");const{size:n}=await a.stat(),i=Math.max(0,n-64*1024),s=Buffer.alloc(n-i),{bytesRead:l}=await a.read(s,0,s.length,i),u=s.subarray(0,l).toString("utf8").split(/\r?\n/);return i&&u.shift(),u.at(-1)===""&&u.pop(),u.slice(-40)}catch(n){if(n.code==="ENOENT")return[];throw n}finally{await a?.close()}}c(ct,"tryLogTail");import{topicItems as ba}from"./review.mjs";import{boardWorktree as Ee,builderRepo as Sa,helperWorktree as va,moveQueuedLand as Oa,prWorktree as ja}from"./worktree.mjs";import{transcriptMovedAt as Pa,transcriptCommandRunning as Aa,readRunTranscript as Ea}from"./transcript.mjs";import{fromHost as Ta}from"./on-host.mjs";import{readSync as $a,writeSync as Ra,stamp as _a,patchSince as Ia}from"./sync.mjs";import{archOf as Na,readReviews as xa,repoName as Da,startReview as Ca}from"./arch-review.mjs";import Ba from"node:http";import b from"node:fs/promises";import ae from"node:os";import p from"node:path";import{execFile as ut,spawn as Ha}from"node:child_process";import{fileURLToPath as Fa}from"node:url";import dt from"node:vm";import La from"node:zlib";import Ua from"node:crypto";import{promisify as Wa}from"node:util";import{PAUSE_CLEARS as Ma,pauseWork as Ja,plans as Ga,providerOf as Ka,archivedOf as ft,buildBoard as Te,movesOf as za,visualsDue as Va,withHub as Qa,DONE_KEPT as ht,ORCHESTRATOR_PUSH_AFTER as Ya,orchestratorWaits as Xa,triageWaitOf as Za,waitsStep as qa,PROTOTYPE_WAIT as en,prototypeWaits as tn,idleStep as an,idleSummary as nn,ITEM_EVENTS as rn,QUESTION_REOPEN as $e,slimDone as pt,needsAnswer as sn,ownerActions as on,picksNoTake as ln,pullOf as wt,arrivalsOf as cn,rebasesDue as un,refOf as Re,roundsDue as dn,strictlyIdle as fn,threadOf as hn,triageOf as pn}from"./domain.mjs";import{localDay as wn,quietAt as mn,quietMiddleOf as gn,quietOf as mt}from"../hub/schedule.mjs";import{cardPRs as yn,prKeyOf as gt}from"../shared/card-prs.mjs";import{dropQueued as kn}from"../hub/push.mjs";import{closeSync as bn,createReadStream as Sn,existsSync as yt,mkdirSync as vn,openSync as On,readdirSync as jn,statSync as kt,watch as bt}from"node:fs";import{PROTOTYPE_TEMPLATE as Pn,esc as An,prototypePage as En,versionOf as _e}from"./prototype-page.mjs";import{BOARD_CLI as Tn,landByBuilder as St,launchBuilder as ne,launchedAgents as $n,pidAlive as vt,resumeBrief as Rn,resumeBuilder as _n,reviveBuilders as In,stopAgents as Nn,stopRuns as xn}from"./headless.mjs";import{replyFilesOf as Dn,dropPause as Cn,markPaused as Ot,openRuns as Bn,requestPause as jt,resumePaused as Hn,BoardError as $,publishUnscreened as Fn,readScreening as Ln,startHelper as Un,startVisual as Wn,openAgents as Pt,isLanding as Mn,implementAsIs as Jn,stageImplement as Gn,stageReply as Kn,planAgain as zn,heldMerge as Vn,opensPRs as Qn,tapMerge as Yn,addTopic as Xn,appendStatus as C,archiveCards as Zn,archiveCount as qn,archiveRank as ei,readArchive as At,boardConfig as ti,abandonLaunch as ai,cutOffBuilds as ni,abandonPlanner as ke,landable as ii,relaunchLander as ri,migrateFollowups as si,migratePhases as oi,migrateStatuses as li,moveCard as ci,stageImplementAsks as ui,startPlanner as di,stripDeadFields as fi,pickUp as hi,startRebase as pi,startTappedBuild as Ie,sendPush as wi,recordOwnerTap as mi,takeHoldStopped as gi,clearStall as yi,mergeCard as ki,readPrototypes as bi,finishHandshake as Si,itemSources as be,readAskLinks as Ne,readAskProgress as vi,readAskTitles as Oi,readBoardRules as ji,readBoardSettings as Pi,readHandshake as Ai,readHidden as Ei,readWaveNotes as Ti,readMarks as $i,readNotes as Ri,readWorking as Se,readUpload as _i,addReviewers as Ii,removeReviewers as Ni,markRead as Et,resolveNoteAnswers as xi,settleAnswers as Di,splitSharedNotes as Ci,unrecorded as Bi,saveBoardRules as Hi,saveBoardSettings as Fi,saveBoardShare as Li,saveUpload as Ui,setBoardPause as Wi,setHidden as Tt,settingsOf as B,threadFile as Mi,wavesDir as Ji}from"./store.mjs";import{changed as Gi,readHubRules as Ki}from"../hub/rules.mjs";import{boardAt as X,listenState as zi,linksOf as Vi}from"../hub/boards.mjs";import{pendingFor as Qi,shown as Yi}from"../hub/prompts.mjs";import{routeRecord as M}from"../hub/routes.mjs";import{CHOICES as Xi,FOREVER as $t,durationEnd as Rt}from"../hub/duration.mjs";import{VIEW_PAGES as Zi,shellHtml as qi}from"../hub/shell.mjs";import{crossSite as er,isJSON as tr}from"../starter/cross-site.mjs";import{shareEntries as ar}from"../hub/share.mjs";import{readUsageGate as K,launchHeld as V,pauseReasonFor as xe,pausedWords as nr,pausesRunning as _t,boardPausedUntil as ir}from"../shared/usage-gate.mjs";import{runLimit as rr}from"../hub/usage.mjs";import{audit as sr,boardAudit as or}from"../shared/audit.mjs";import{reviewWaits as lr}from"../shared/skills.mjs";import{boardRepo as De,mainRefOf as cr}from"../hub/board-repo.mjs";import{listBuildTargets as ur}from"./build-script.mjs";import{permissionsView as dr,setPermission as fr}from"./permissions.mjs";import{bumpOut as hr}from"./stop.mjs";import{SlackError as pr,SLACK_NEEDS_TOKEN as It,savedSlack as Nt,slackStatus as xt}from"../hub/slack.mjs";import{collaborators as wr,githubUser as mr}from"../hub/pr-report.mjs";const ie=p.dirname(Fa(import.meta.url));async function Dt(){const e=await b.stat(p.join(ie,"..","hub","icon-180.png")).catch(()=>null);return`/_hub/icon-180.png?v=${e?Math.floor(e.mtimeMs).toString(36):"0"}`}c(Dt,"touchIcon");async function gr(e){if(!e.includes("<!--header "))return e;const t=dt.createContext({});return dt.runInContext(await b.readFile(p.join(ie,"public","ui-page-header.js"),"utf8"),t),e.replace(/<!--header (\{.*?\})-->/g,(a,n)=>t.BoardUI.header(JSON.parse(n)))}c(gr,"pageHeaders");const o=p.resolve(process.argv[2]||process.cwd()),yr=[p.join(o,"public"),p.join(ie,"public"),p.join(ie,"..","starter","public"),p.join(ie,"..","shared")],v=p.join(o,"data"),j=c(e=>sr(or(o),e),"logCall"),F=p.join(v,"events.jsonl"),re=p.join(v,"replies"),Ct=Number(process.env.PORT||4600),z=(process.env.PREFIX||"").replace(/\/+$/,""),Bt={".html":"text/html; charset=utf-8",".css":"text/css; charset=utf-8",".js":"text/javascript; charset=utf-8",".mjs":"text/javascript; charset=utf-8",".json":"application/json; charset=utf-8",".svg":"image/svg+xml",".png":"image/png",".ico":"image/x-icon",".txt":"text/plain; charset=utf-8",".md":"text/markdown; charset=utf-8",".plist":"text/xml; charset=utf-8",".ipa":"application/octet-stream",".apk":"application/vnd.android.package-archive"},kr=Wa(La.gzip),br=/^(text\/|application\/json|image\/svg\+xml)/;async function Ht(e,t,a,n){!["GET","HEAD"].includes(e.req?.method)&&!e.req.boardRead&&R++;const i={...a};let s=Buffer.isBuffer(n)?n:Buffer.from(n??"");if(br.test(i["content-type"]||"")&&(i.vary="accept-encoding",s.length>=1024&&/\bgzip\b/.test(e.req?.headers["accept-encoding"]||"")))try{s=await kr(s),i["content-encoding"]="gzip"}catch{}i["content-length"]=s.length,e.writeHead(t,i),e.end(e.req?.method==="HEAD"?void 0:s)}c(Ht,"send");const h=c((e,t,a,n={})=>Ht(e,t,{"content-type":Bt[".json"],"cache-control":"no-store",...n},JSON.stringify(a)),"json"),Ft=/\b(src|href)="([\w.][\w./-]*\.(?:js|css))"/g;async function Sr(e,t){const a=t.endsWith("/")?t:p.posix.dirname(t),n=[...new Set([...e.matchAll(Ft)].map(s=>s[2]))],i=new Map(await Promise.all(n.map(async s=>{const l=await Yt(p.posix.join(a,s)),u=l?await b.stat(l).catch(()=>null):null;return[s,u?_e(u):null]})));return e.replace(Ft,(s,l,u)=>i.get(u)?`${l}="${u}?v=${i.get(u)}"`:s)}c(Sr,"versionAssets");const Lt=c((e,t)=>String(e||"").split(",").map(a=>a.trim().replace(/^W\//,"")).includes(t.replace(/^W\//,"")),"sameTag"),O=c(async(e,t)=>{try{return JSON.parse(await b.readFile(e,"utf8"))}catch{return t}},"readJSON"),J=c(async e=>{try{return(await b.readFile(e,"utf8")).split(`
3
+ `).filter(Boolean).map(t=>JSON.parse(t))}catch{return[]}},"readLines"),Ce=c(e=>Mi(o,e),"threadFile"),Be=c(async()=>{const e=await Ei(o);return(await J(F)).filter(t=>!e.has(t.id))},"shownEvents");async function L(e,t=64*1024){if(!tr(e.headers))throw Object.assign(new Error("Send the body as application/json."),{status:403});let a="";for await(const n of e)if(a+=n,a.length>t)throw Object.assign(new Error("too large"),{status:413});return a?JSON.parse(a):{}}c(L,"readBody");const vr=new Set(["owner-answer","owner-answers","prototype-request","reask-request",$e,"grill-more","visual-request","item-message","ask","ask-edited","ask-withdrawn","ask-bump","ask-done","card-build","card-hold","card-done","card-bump","card-unbump","plan-waves"]),Or="This board is shared with you to look at only.",jr="Merge and Board settings stay the owner\u2019s.";async function Pr(e,t){const a=e.headers["x-kickflip-shared"];return a?ar(await W()).find(i=>i.login===a)?.level!=="full"||!["/_claude/events","/api/upload"].includes(t)||e.method!=="POST"?{refused:Or}:{by:{login:a,name:e.headers["tailscale-user-name"]||null}}:null}c(Pr,"sharedWriter");const U=c(async(e,t)=>(await He([[e,t]]))[0],"appendEvent");async function He(e){const t=new Date().toISOString(),a=e.map(([n,i])=>({id:"e"+Date.now().toString(36)+Math.random().toString(36).slice(2,7),ts:t,kind:n,data:i}));return await b.mkdir(re,{recursive:!0}),await b.appendFile(F,a.map(n=>JSON.stringify(n)+`
4
+ `).join("")),R++,qt=ea(F),a.map(n=>n.id)}c(He,"appendEvents");async function Ut(e){const t=await Bi(o,e),a=new Map(((await O(p.join(v,"owner.json"),{items:[]})).items||[]).map(l=>[l.id,l]));if(!t.length)return[];const n=c(l=>a.has(String(l.id))&&ln(a.get(String(l.id)),l.choice),"again"),i=await He(t.map(l=>["owner-answer",n(l)?{...l,prototype_again:!0}:l]));await xi(o,t);const{prototype:s}=await Di(o,t);R++;for(const l of s)await te(l,"prototype");return i}c(Ut,"recordAnswers");async function Ar(e){if(!Array.isArray(e)||!e.length)return"Send at least one answer.";const t=new Set(((await O(p.join(v,"owner.json"),{items:[]})).items||[]).map(n=>n.id)),a=e.filter(n=>!t.has(String(n?.id)));return a.length?`${a.map(n=>String(n?.id)).join(", ")} ${a.length===1?"is":"are"} no longer asked here. Reload to see what is, then send again.`:null}c(Ar,"answersRefused");async function Er(e){const t=(await x()).owner.find(a=>a.id===e);return t?["open","requested"].includes(t.state)?`${e} has no answer sent, so there is nothing to reopen.`:null:`${e} is no longer asked here. Reload to see what is.`}c(Er,"reopenRefused");async function Tr(e){const t=(await x()).owner.find(a=>a.id===e);return t?t.state!=="resolved"?`${e} isn\u2019t resolved, so there is nothing to ask again.`:null:`${e} is no longer asked here. Reload to see what is.`}c(Tr,"reaskRefused");async function se(e){const a=(await Promise.all((await Dn(re,e.id)).map(async({name:n,ext:i})=>{const s=p.join(re,n);try{const[l,u]=await Promise.all([b.readFile(s,"utf8"),b.stat(s)]);return[{[i]:i==="json"?JSON.parse(l):l,at:u.mtime.toISOString()}]}catch{return[]}}))).flat();return{...e,reply:a.at(-1)||null,replies:a}}c(se,"withReply");async function $r(e,t){const a=await Se(o);let n=await Be();return e&&(n=n.filter(i=>i.kind===e)),Promise.all(n.reverse().slice(0,t).map(async i=>{const s=await se(i);return s.reply||!M(a,i.id)?s:{...s,working:M(a,i.id)}}))}c($r,"listEvents");const Fe=new Map,Rr="number,title,url,state,isDraft,reviewDecision,mergeStateStatus,mergeable,statusCheckRollup,mergedAt,headRefName,headRefOid,updatedAt,latestReviews";function _r(e,t){return new Promise(a=>{ut("gh",["pr","view",String(t),"--repo",e,"--json",Rr],{timeout:2e4},(n,i)=>{if(n)return a(null);try{a(JSON.parse(i))}catch{a(null)}})})}c(_r,"ghPR");function Ir(e,t){return new Promise(a=>{ut("gh",["api",`repos/${e}/pulls/${t}/comments?per_page=100`],{timeout:2e4},(n,i)=>{if(n)return a(0);try{const s=JSON.parse(i),l=new Set(s.map(u=>u.in_reply_to_id).filter(Boolean));a(s.filter(u=>!u.in_reply_to_id&&/codex/i.test(u.user?.login||"")&&!l.has(u.id)).length)}catch{a(0)}})})}c(Ir,"ghCodexOpen");function Nr(e){const t=e||[];let a=0,n=0;for(const i of t){const s=(i.conclusion||i.state||"").toUpperCase(),l=(i.status||"").toUpperCase();["FAILURE","ERROR","CANCELLED","TIMED_OUT","ACTION_REQUIRED","STARTUP_FAILURE"].includes(s)?a++:(l&&l!=="COMPLETED"||s==="PENDING"||s==="EXPECTED")&&n++}return{total:t.length,fail:a,pending:n,state:a?"fail":n?"pending":t.length?"pass":"none"}}c(Nr,"checksOf");async function xr(e,t){const a=`${e}#${t}`;let n=Fe.get(a);return n||(n={at:0,value:null,loading:null},Fe.set(a,n)),Date.now()-n.at>45e3&&!n.loading&&(n.loading=Promise.all([_r(e,t),Ir(e,t)]).then(([i,s])=>{if(i){const{statusCheckRollup:l,latestReviews:u,...k}=i,w=Object.fromEntries((u||[]).map(A=>[A.author?.login,A.state])),y={...k,checks:Nr(l),reviews:w,codexOpen:s};JSON.stringify(y)!==JSON.stringify(n.value)&&R++,n.value=y}n.at=Date.now(),n.loading=null})),!n.value&&n.loading&&await n.loading,n.value}c(xr,"prState");const W=c(()=>ti(o),"config");async function Dr(){const e=await b.readdir(re).catch(()=>[]),t=await Promise.all(e.map(n=>b.stat(p.join(re,n)).then(i=>i.mtimeMs,()=>0))),a=Math.max(0,...t);return a?new Date(a).toISOString():null}c(Dr,"latestReply");async function Wt(e){let t=[];try{t=(await b.readdir(e)).filter(i=>i.endsWith(".json"))}catch{}const a=await Promise.all(t.map(i=>O(p.join(e,i),null))),n=Object.create(null);return t.forEach((i,s)=>{if(!a[s])return;const[l,u="main"]=p.basename(i,".json").split("@");(n[l]||=Object.create(null))[u]=a[s]}),n}c(Wt,"readAgents");async function Le(){const e=X(o);return{cfg:await W(),github:e?.github,local:e?.local,hub:e?.hub,remote:e?.remote,branch:e?.branch,topicItems:ba(),hostBusy:K({dir:o}).machine.state==="busy"}}c(Le,"outsideData");async function Mt(){return(await Promise.all((await be(o)).map(async e=>{const t=await O(e.file,null);return!t||!Array.isArray(t.items)?null:{slug:e.slug,itemFile:t,agents:await Wt(p.join(e.dir,"agents"))}}))).filter(Boolean)}c(Mt,"liveFiles");async function Cr(e){const t=await Mt();for(const a of e){const n=t.find(i=>i.slug===a.file);n?n.itemFile.items.push(a.item):t.push({slug:a.file,itemFile:{items:[a.item]},agents:{}})}return t}c(Cr,"withArchived");async function Jt({cfg:e,github:t,local:a,hub:n,remote:i,branch:s},l){const u=e.items==="waves";l??=await Mt();const k=Object.create(null);return await Promise.all(l.flatMap(({slug:w,itemFile:y})=>y.items.map(async A=>{k[`${w}/${A.id}`]=await J(Ce(`${w}/${A.id}`))}))),{github:t,remote:i,branch:s,reviewers:Qn({github:t,local:a,hub:n},B(e,{hub:n}))?e.reviewers||[]:[],fromWaves:u,autoMerge:B(e).auto_merge,skipDraft:B(e).skip_draft,autoBuild:B(e).auto_build,tickets:B(e).tickets,files:l,threads:k,working:await Se(o),autoReview:B(e).auto_review,incoming:await O(p.join(v,"incoming.json"),null),reviewRecords:await O(p.join(v,"reviews.json"),{})}}c(Jt,"itemSnapshot");async function Gt(e,t=[]){const a=await Jt(e,t.length?await Cr(t):void 0),{files:n,threads:i,working:s}=a,l=Object.create(null),u=new Set(t.map(d=>d.key));await Promise.all(n.flatMap(({slug:d,itemFile:f})=>{const P=a.github||f.repo;return f.items.flatMap(m=>yn(m,P).map(async S=>{l[gt(S)]=u.has(`${d}/${m.id}`)?Fe.get(gt(S))?.value??null:await xr(S.repo,S.number)}))}));const k=new Set(["ask","owner-answer","grill-more","visual-request","item-message","wave-triage","overnight-triage"]),w=await Promise.all((await Be()).map(d=>k.has(d.kind)||M(s,d.id)?se(d):{...d,reply:null}));await Promise.all(w.filter(d=>d.kind==="ask").map(async d=>{const f=await J(Ce(`ask:${d.id}`));f.length&&(i[`ask:${d.id}`]=f)}));const y=Object.create(null),A=Object.create(null);for(const{agents:d}of n)for(const f of Object.values(d||{}).flatMap(P=>Object.values(P))){const P=f.worktree?.path,m=P&&!f.done?Pa(P):null;m!==null&&(y[P]=m),P&&!f.done&&Aa(P)&&(A[P]=!0)}const _=Object.create(null),g=await O(p.join(v,"headless-builders.json"),{}),r=await O(p.join(v,"landers.json"),{});for(const[d,f]of[...Object.entries(r),...Object.entries(g)])f?.pid===null&&M(g,d)===f&&(_[d]="held"),Number.isInteger(f?.pid)&&(vt(f.pid)?_[d]=!0:f.handedBack?_[d]=!1:delete _[d]);return{...a,moved:y,commandRunning:A,alive:_,try:await O(p.join(v,"try.json"),null),main:await O(p.join(v,"main.json"),null),prs:l,events:w,askLinks:await Ne(o),askProgress:await vi(o),askTitles:await Oi(o),checked:await O(p.join(v,"checked.json"),null),watcher:await O(p.join(v,"watcher.json"),null),replyAt:await Dr(),owner:(d=>({...d,items:[...d.items||[],...t.flatMap(f=>f.questions||[])]}))(await O(p.join(v,"owner.json"),{items:[]})),topicItems:e.topicItems,prSlack:await O(p.join(v,"pr-slack.json"),{}),hostBusy:e.hostBusy,screening:await Ln(o)}}c(Gt,"snapshot");async function Br(e){if(!e.length)return[];const t=new Map(Te(await Gt(await Le(),e)).items.map(a=>[a.key,a]));return e.map(a=>{const n=t.get(a.key);return pt({...n,sortAt:a.closed_at,worktree:n.worktree&&!yt(n.worktree)?null:n.worktree})})}c(Br,"archivedCards");const Z=z.slice(1)||p.basename(o);let R=0,oe=null;async function x(){const e=await Le(),t=JSON.stringify([R,e]);if(oe?.key!==t){const a=Gt(e).then(Te);oe={key:t,built:a},a.catch(()=>{oe?.built===a&&(oe=null)})}return oe.built}c(x,"sharedBoard");let Ue=!1;async function Kt(){if(!Ue){Ue=!0;try{const e=await J(F),t=new Set(e.filter(a=>a.kind==="prototype-overdue").map(a=>a.data?.request));for(const a of tn(await x(),e))t.has(a.request)||Date.now()-Date.parse(a.since)<en||await U("prototype-overdue",a)}catch(e){console.error("Prototype check failed:",e.message)}finally{Ue=!1}}}c(Kt,"checkPrototypeWaits");async function Hr(e){const t=await Ai(o);if(!t?.ping?.id||t.done_at)return null;const a=e?.find(i=>i.link==="github"),n={events:t.ack?.id===t.ping.id,...a||!e?{github:!!a?.ok}:{}};return n.events&&n.github!==!1?(await Si(o),null):n}c(Hr,"setupOf");const zt=Xi.map(([e,t])=>({value:e,label:t,ms:e===$t?null:Rt(new Date(0),e)-new Date(0)})),Fr=Number(process.env.KICKFLIP_PEOPLE_WAIT_MS)||12e3,Vt=c((e,t)=>Promise.race([e,new Promise((a,n)=>setTimeout(()=>n(new Error(`${t} did not answer in time`)),Fr).unref())]),"withinWait");async function Lr(){const e={slack:null,github:[]},t=Nt(),a=X(o)?.github,[n,i]=await Promise.allSettled([!t||xt().refused?Promise.resolve(null):Vt(t.people(),"Slack"),a?Vt(wr(a),"GitHub"):Promise.resolve([])]);if(!t||xt().refused)e.slackProblem=It;else if(n.status==="fulfilled")e.slack=n.value;else{const s=n.reason;e.slackProblem=s.message==="Slack: missing_scope"?"Slack needs the users:read scope; update the app on Hub \u203A Integrations \u203A Slack.":s.message}return i.status==="fulfilled"?e.github=i.value:e.githubProblem=`GitHub: ${i.reason.message}`,e}c(Lr,"reviewerPeople");async function Ur(e){const t=Nt();if(!t)throw new $(It);return Promise.all(e.map(async a=>{const n=await mr(a.github.trim());if(!n)throw new $(`GitHub has no user ${a.github.trim()}.`);return{name:a.name.trim(),slack_user:a.slack_user.trim(),dm:await t.openDM(a.slack_user.trim()),github:n}}))}c(Ur,"withDMs");async function le(){const e=new Date().toISOString(),{title:t,reviewers:a}=await W(),n=X(o),i=n?zi(n):"deaf",s=Qa(await x(),{listen:i,prompts:n?Qi(n.route).map(Yi):[]});s.items=s.items.map(u=>u.worktree&&!yt(u.worktree)?{...u,worktree:null}:u);const l=n?Vi(n,{listen:i}):null;return{generatedAt:e,title:t,reviewers:(a||[]).map(u=>u?.name).filter(u=>typeof u=="string"),reviewerGithub:Object.fromEntries((a||[]).filter(u=>typeof u?.name=="string").map(u=>[u.name,u.github])),...s,links:l,setup:await Hr(l),holdChoices:zt,arch:n?Na(await xa(o),s.items,s.owner,{repo:Da(De(n.route)),waits:lr()}):null}}c(le,"board");async function Wr(e){["GET","HEAD"].includes(e.method)||R++;const t={...await le(),doneArchived:await qn(o)};t.owner=t.owner.filter(i=>i.state!=="resolved");const a=await $i(o);t.items=t.items.map(i=>({...i.lane==="done"?pt(i):i,readAt:a[i.key]||null}));const n=new Set(t.items.map(i=>i.key));return t.ownerTopics=t.ownerTopics.map(({title:i,summary:s,askedAt:l,...u})=>n.has(u.card)?u:{...u,title:i,summary:s,...l?{askedAt:l}:{}}),t}c(Wr,"buildPageBoard");const We=p.join(v,"sync.json");let q=await $a(We),Me=q.n,Je=Promise.resolve(),ve=null;function Mr(){return Je=Je.then(async()=>{const e=q;e.n!==Me&&(await b.mkdir(v,{recursive:!0}),await Ra(We,e),Me=e.n)}),Je}c(Mr,"saveSync");async function Jr(e){const t=await Wr(e);for((!ve||t.generatedAt>=ve.generatedAt)&&({sync:q,board:ve}=_a(q,t));Me<q.n;)await Mr();return{sync:q,board:ve}}c(Jr,"pageBoard");async function G(e,t){if(new URL(e.url,"http://site").searchParams.get("board")==="0")return;const a=await Ta(e),{sync:n,board:i}=await Jr(e);return e.boardRead=!0,{...(/^\d+$/.test(t)?Ia(n,i,Number(t)):null)||i,onHost:a}}c(G,"answerBoard");let Qt=Promise.resolve();function Gr(){const e=Qt.then(async()=>{const t=X(o);if(!t)throw Object.assign(new $("This board isn\u2019t in the hub\u2019s manifest."),{status:404});const{arch:a}=await le();if(a.last?.open)return{key:a.last.key,ref:a.last.ref,existing:!0};const{key:n,data:i}=await Ca(o,t),s=await U("arch-review",i);return{key:n,id:s,existing:!1}});return Qt=e.catch(()=>{}),e}c(Gr,"runReview");async function Kr(e){const[t,a]=e.split("/"),n=(await be(o)).find(u=>u.slug===t),i=n?await O(n.file,null):null,s=!i?.items?.some(u=>u.id===a)&&(await At(o)).find(u=>u.key===e),l=Array.isArray(i?.items)&&i.items.find(u=>u.id===a)||s?.item||null;return{item:l,questions:s?.questions||[],agents:l&&n?M(await Wt(p.join(n.dir,"agents")),a)||{}:{}}}c(Kr,"itemOf");async function zr(e){const t=M(await Ne(o),e),{tickets:a}=B(await W()),n=[];for(const i of await be(o)){const s=await O(i.file,null);for(const l of Array.isArray(s?.items)?s.items:[]){const u=`${i.slug}/${l.id}`;l.from_ask===e?n.unshift({key:u,ref:Re(s,l,a),became:!0}):u===t&&n.push({key:u,ref:Re(s,l,a),became:!1})}}return n}c(zr,"askLinksOf");async function Vr(e){const{item:t,agents:a,questions:n}=e.startsWith("ask:")?{item:null,agents:{},questions:[]}:await Kr(e),i=await O(p.join(v,"owner.json"),{items:[]}),s={...i,items:[...i.items||[],...n]},l=new Set(s.items.filter(r=>r.topic===`item:${e}`).map(r=>r.id)),u=e.startsWith("ask:"),k=u?e.slice(4):t?.from_ask||null,w=u?[]:Object.entries(await Ne(o)).filter(([,r])=>r===e).map(([r])=>r),y=[k,...w].filter(Boolean),A=new Set([e,...y.map(r=>`ask:${r}`)]),_=(await Be()).filter(r=>A.has(r.data?.key)&&rn.includes(r.kind)||r.kind==="owner-answer"&&l.has(r.data?.id)||r.kind===$e&&l.has(r.data?.question)||r.kind==="ask"&&y.includes(r.id)||r.kind==="ask-edited"),g=(await Promise.all([...A].map(r=>J(Ce(r))))).flat();return hn(e,{lines:g,events:await Promise.all(_.map(se)),owner:s,working:await Se(o),fromAsk:k,links:u?await zr(k):[],agents:a,moved:w})}c(Vr,"thread");async function Yt(e){for(const t of yr){let a=p.resolve(t,"."+e);if(a!==t&&!a.startsWith(t+p.sep))return null;if((await b.stat(a).catch(()=>null))?.isDirectory()&&(a=p.join(a,"index.html")),await b.stat(a).then(i=>i.isFile(),()=>!1))return a}return null}c(Yt,"staticFile");async function Qr(e){const t=/^\/prototype-([a-z0-9-]+)\.html$/.exec(e)?.[1];return t&&(await bi(o)).find(a=>a.topic===t)||null}c(Qr,"recordedPrototype");async function Yr(e,t){try{const a=decodeURIComponent(String(e.headers["x-filename"]||""));return h(t,200,await Ui(o,e,{name:a,type:e.headers["content-type"],length:Number(e.headers["content-length"])||0}))}catch(a){if(!(a instanceof $)&&!(a instanceof URIError))throw a;return e.resume(),h(t,a.status||400,{error:a.message})}}c(Yr,"upload");const Xr=/^(text\/html|image\/svg\+xml|application\/(xhtml\+)?xml|text\/xml)$/;async function Zr(e,t,a){const n=await _i(o,a);if(!n){t.writeHead(404).end();return}if(t.writeHead(200,{"content-type":n.type.startsWith("text/")?`${n.type}; charset=utf-8`:n.type,"content-length":n.size,"content-disposition":`inline; filename*=UTF-8''${encodeURIComponent(n.name)}`,"cache-control":"private, max-age=86400","x-content-type-options":"nosniff",...Xr.test(n.type)?{"content-security-policy":"sandbox"}:{}}),e.method==="HEAD"){t.end();return}Sn(n.path).pipe(t)}c(Zr,"sendUpload");const Ge=new Set;function qr(e,t){t.writeHead(200,{"content-type":"text/event-stream; charset=utf-8","cache-control":"no-store",connection:"keep-alive"}),t.write(`retry: 3000
5
5
 
6
- `),Wt.add(e),t.on("close",()=>Wt.delete(e))}c(Hr,"stream");const Ge=c(t=>{for(const e of Wt)e.write(t)},"broadcast");setInterval(()=>Ge(`: ping
6
+ `),Ge.add(t),e.on("close",()=>Ge.delete(t))}c(qr,"stream");const Xt=c(e=>{for(const t of Ge)t.write(e)},"broadcast");setInterval(()=>Xt(`: ping
7
7
 
8
- `),25e3).unref();const Br=100,Lr=500;let Ot=null,Ke=0;function Jt({read:t=!1}={}){t||_++;const e=Date.now();Ot||(Ke=e),clearTimeout(Ot),Ot=setTimeout(()=>{Ot=null,Ge(`event: change
8
+ `),25e3).unref();const es=100,ts=500;let Oe=null,Zt=0;function Ke({read:e=!1}={}){e||R++;const t=Date.now();Oe||(Zt=t),clearTimeout(Oe),Oe=setTimeout(()=>{Oe=null,Xt(`event: change
9
9
  data: ${JSON.stringify({at:new Date().toISOString()})}
10
10
 
11
- `),ca(),la(),ua(),ee(),ne(),ct(),re()},Math.max(0,Math.min(Br,Ke+Lr-e)))}c(Jt,"changed");const Fr=new Set(["node_modules","dd","DerivedData"]),Gt=c(t=>String(t).endsWith(".tmp")||String(t).split(/[\\/]/).some(e=>e.startsWith(".")||Fr.has(e)),"ignored");let ze=null;const Ve=c(t=>{try{const e=ye(t);return`${e.size}:${e.mtimeMs}`}catch{return null}},"statOf");function Qe(t,e){if(!e)return Jt();if(Gt(e))return;const a=p.join(t,String(e));a!==Ft&&Jt({read:a===H&&Ve(H)===ze})}c(Qe,"heard");const Kt=c(t=>{try{return ye(t).isDirectory()}catch{return!1}},"isFolder");function Mr(t){let e=c(()=>{},"stop"),a=!1,n=null;const i=c(()=>{n||(e(),a=!0,n=setTimeout(()=>{n=null,s()},2e3).unref())},"retry"),s=c(()=>{try{e=(process.platform==="linux"?Wr:Ur)(t,i)}catch{i();return}a&&Jt()},"start");s()}c(Mr,"watchTree");function Ur(t,e){const a=ke(t,{recursive:!0},(n,i)=>{Qe(t,i),Kt(t)||e()});return a.on("error",e),()=>a.close()}c(Ur,"watchRecursive");function Wr(t,e){const a=new Map,n=c(s=>{for(const[l,u]of a)(l===s||l.startsWith(s+p.sep))&&(u.close(),a.delete(l))},"unwatch"),i=c(s=>{if(a.has(s))return;const l=ke(s,(u,m)=>{if(Qe(s,m),s===t&&!Kt(t))return e();if(!m||Gt(m))return;const k=p.join(s,String(m));if(!Kt(k))n(k);else try{i(k)}catch{n(k)}});a.set(s,l),l.on("error",()=>s===t?e():n(s));for(const u of wn(s,{withFileTypes:!0}))if(!(!u.isDirectory()||Gt(u.name)))try{i(p.join(s,u.name))}catch{n(p.join(s,u.name))}},"watchFolder");try{i(t)}catch(s){throw n(t),s}return()=>n(t)}c(Wr,"watchEachFolder");async function Jr(){const t=(await L()).items==="waves"?[Ti()]:(await kt(o)).map(a=>a.dir),e=[...new Set([S,...t].map(a=>p.resolve(a)))];for(const a of e.filter(n=>!e.some(i=>i!==n&&n.startsWith(i+p.sep))))Mr(a)}c(Jr,"watchBoard");const Gr=Ra.createServer(async(t,e)=>{try{if(t.method!=="GET"&&t.method!=="HEAD"&&_++,t.method!=="GET"&&t.method!=="HEAD"&&Bi(t.headers)){e.writeHead(403,{"content-type":"text/plain; charset=utf-8","cache-control":"no-store"}),e.end("Requests must come from the app\u2019s own pages.");return}const a=new URL(t.url,"http://site");let n=a.pathname;G&&(n===G||n.startsWith(G+"/"))&&(n=n.slice(G.length)||"/");const i=t.method!=="GET"&&t.method!=="HEAD"?await lr(t,n):null;if(i?.refused)return f(e,403,{error:i.refused});if(n==="/_claude/health")return f(e,200,{ok:!0});if(n==="/api/board"){const r=await W(t,String(a.searchParams.get("since")||"")),d=`W/"${r.version}"`;if(xe(t.headers["if-none-match"],d)){e.writeHead(304,{etag:d,"cache-control":"no-store",vary:"accept-encoding"}).end();return}return f(e,200,r,{etag:d})}if(n==="/api/reviewer-people")return f(e,200,await jr());if(n==="/api/questions"){const r=await N(),d=new Set(r.ownerTopics.find(h=>h.id===String(a.searchParams.get("topic")??""))?.questions||[]);return f(e,200,{questions:r.owner.filter(h=>d.has(h.id)&&h.state==="resolved")})}if(n==="/api/done"){const r=String(a.searchParams.get("before")||""),d=(await ve(o)).filter(h=>!r||Hn(h)<r);return f(e,200,{cards:await vr(d.slice(0,fe)),left:Math.max(0,d.length-fe)})}if(n==="/api/links")return f(e,200,{links:(await st()).links});if(n==="/api/stream"&&t.method==="GET")return Hr(t,e);if(n==="/_claude/needs")return f(e,200,Qa(await st()));if(n==="/api/working"){const r=await N(),d=new Map(r.items.filter(h=>h.kind==="work").map(h=>[h.key,h.ref]));return f(e,200,{working:r.workingOn.map(h=>({id:h.id,kind:h.kind,card:d.get(h.key)||null,since:h.since}))})}if(n==="/api/thread")return f(e,200,await _r(String(a.searchParams.get("key")||"")));if(n==="/api/notes"&&a.searchParams.has("wave"))try{return f(e,200,await fi(o,String(a.searchParams.get("wave")),C(await L()).tickets))}catch(r){if(r instanceof R)return f(e,404,{error:r.message});throw r}if(n==="/api/notes"||n==="/api/plan"){const r=String(a.searchParams.get("item")||"");try{const{wave:d,item:h,markdown:P}=await wi(o,r);return f(e,200,{key:r,ref:Et(d,h,C(await L()).tickets),title:h.title,markdown:P})}catch(d){if(d instanceof R)return f(e,404,{error:d.message});throw d}}if(n==="/_claude/events"&&t.method==="POST"){const r=await B(t,524288),d=String(r.kind||"message").slice(0,64);delete r.kind;const h=String(r.since||"");if(delete r.since,i&&!sr.has(d))return f(e,403,{error:cr});for(const w of[r,...Array.isArray(r.answers)?r.answers.filter(v=>v&&typeof v=="object"):[]])delete w.shared_by,i&&(w.shared_by=i.by);if(d==="card-hold"&&r.hold&&r.for!==void 0){const w=Pe(new Date,r.for);if(!w)return f(e,400,{error:"That hold ends in the past or more than 30 days on. Pick another time."});delete r.for,w!==je&&(r.until=w.toISOString())}if(d==="card-build"){const w=String(r.key||""),v=await It(o,w,T=>$t(o,w.split("/")[1],T));return f(e,200,{id:await jt(v),board:await W(t,h)})}if(d==="item-message"){const w=String(r.key||""),v=(await N()).items.find(I=>I.key===w&&I.kind==="work"),T=!String(r.text||"").trim()&&!(Array.isArray(r.files)&&r.files.length);if(v?.stalled&&await ai(o,w),T&&(v?.ready||v?.lane==="todo"))return await An(o,w,i?i.by.name||i.by.login:void 0),_++,f(e,200,{id:null,board:await W(t,h)});v?.lane==="todo"&&r.intent==="build"?await Tn(o,w,r):v?.lane==="todo"&&v.phase==="ready"&&await En(o,w,r),_++}if(d==="lineup-move"){const w=String(r.lineup||""),v=Va(await N(),w,String(r.key||""),r.by===-1?-1:r.by===1?1:0);if(!v)return f(e,400,{error:`That card can't move that way in the wave "${w}".`});try{await Ln(o,w,{steps:v.map(T=>T.join(","))})}catch(T){if(T instanceof R)return f(e,400,{error:T.message});throw T}return f(e,200,{id:null,board:await W(t,h)})}if(d==="owner-answer")return f(e,200,{id:(await De([r]))[0]??null,board:await W(t,h)});if(d==="owner-answers"){const w=await ur(r.answers);return w?f(e,400,{error:w}):f(e,200,{ids:await De(r.answers),board:await W(t,h)})}if(d==="card-merge"&&await In(o,String(r.key||"")).catch(()=>null)){const w=String(r.key);try{await Nn(o,w)}catch(v){if(v instanceof R)return f(e,400,{error:v.message});throw v}return es(w),f(e,200,{id:null,board:await W(t,h)})}if(d==="card-merge"){try{await ni(o,String(r.key||""),{tapped:!0})}catch(w){if(w instanceof R)return f(e,400,{error:w.message});throw w}return f(e,200,{id:null,board:await W(t,h)})}if(d==="card-hold"||d==="card-done"){const w=String(r.key||"");let v,T;try{T=d==="card-hold"&&!r.hold?[]:await Pn(o,w),d==="card-done"&&await $n(o,w)&&(T=[]),v=await ti(o,d,r)}catch(I){return f(e,I instanceof R?400:500,{error:`Couldn\u2019t write ${w} to its wave file: ${I.message}`})}if(!v)return f(e,400,{error:`${w} is already in Done, so its wave file stays as it is.`});T.length&&(bn(T),await x(o,w,{text:"Stopped its builder."})),d==="card-hold"&&!r.hold&&await ta(w)}if(d==="reviewers"){const w=c(O=>O===void 0?[]:Array.isArray(O)?O:null,"list"),v=w(r.add),T=w(r.remove),I=c(O=>typeof O=="string"&&O.trim(),"text");if(!v||!T||!v.length&&!T.length)return f(e,400,{error:"Send the people to add and the names to remove, each as a list."});if(!T.every(I)||!v.every(O=>I(O?.name)&&I(O.slack_user)&&I(O.github)))return f(e,400,{error:"Each person to add needs a name, a Slack member and a GitHub login."});try{T.length&&await gi(o,T.map(O=>O.trim())),v.length&&await mi(o,await Pr(v))}catch(O){if(O instanceof R||O instanceof Zi)return f(e,400,{error:O.message});throw O}const pt=await Ct([...T.length?[["reviewers-removed",{remove:T}]]:[],...v.length?[["reviewers-added",{add:v.map(O=>O.name)}]]:[]]);return f(e,200,{id:pt.at(-1),board:await W(t,h)})}if(d==="reask-request"){const w=await fr(String(r.question||""));if(w)return f(e,400,{error:w})}if(d===Tt){const w=await dr(String(r.question||""));if(w)return f(e,400,{error:w})}const P=await U(d,r);if((d==="card-bump"||d==="card-unbump")&&ma(o,String(r.key||""),d==="card-bump"?new Date().toISOString():null),(d==="grill-more"||d==="prototype-request")&&r.key){const w={...r.text?{text:String(r.text)}:{},...Array.isArray(r.files)&&r.files.length?{files:r.files}:{},...r.question?{question:String(r.question),note:r.note?String(r.note):void 0,choice:r.choice??void 0}:{}},v=d==="grill-more"?"grill":"prototype";await Rn(o,String(r.key),v),await tt(String(r.key),v,w)}return d==="visual-request"&&r.key&&await Ze((await N()).items,await M(H)),d==="ask-withdrawn"&&await Oe(o,String(r.id||""),!0),f(e,200,{id:P,board:await W(t,h)})}if(n==="/api/rules"&&t.method==="GET")return f(e,200,{board:await ci(o),hub:Ri()});if(n==="/api/rules"&&t.method==="POST"){const r=await B(t);let d;try{d=await Si(o,r.rules,r.base??[])}catch(P){if(P instanceof R)return f(e,400,{error:P.message});throw P}const h=Ei(d.changes)?await U("rules",{scope:"board",...d.changes}):null;return f(e,200,{rules:d.rules,id:h})}if(n==="/api/settings"&&t.method==="GET")return f(e,200,{settings:await li(o),paused_until:Mi(o),pause_choices:Me});if(n==="/api/build-targets"&&t.method==="GET"){const r=Y(o);if(!r)return f(e,404,{error:"This board isn\u2019t in the hub\u2019s manifest."});const d=xt(r.route);return f(e,200,{targets:await Vi(d.repo,zi(d))})}if(n==="/api/pause"&&t.method==="POST"){const r=await B(t);if(!r||typeof r!="object"||Array.isArray(r)||Object.keys(r).join()!=="pause"||typeof r.pause!="string")return f(e,400,{error:"Send only {pause: a duration, a date and time, or off}."});let d;try{d=await $i(o,r.pause)}catch(h){if(h instanceof R)return f(e,400,{error:h.message});throw h}return d&&(Qt=!0),ct(),f(e,200,{paused_until:d})}if(n==="/api/permissions"){const r=Y(o);if(!r)return f(e,404,{error:"This board isn\u2019t in the hub\u2019s manifest."});const d=xt(r.route).repo,h=t.method==="POST"?await B(t):null;try{return h&&await Yi(d,o,h.id,h.on),f(e,200,await Qi(d,o))}catch(P){if(P instanceof R)return f(e,400,{error:P.message});throw P}}if(n==="/api/try/main/log"&&t.method==="GET")return f(e,200,{lines:await ce(o,"main")});if(n==="/api/try/prototypes"&&t.method==="GET"){const r=(await j(p.join(o,"data","try.json"),{}))?.prototypes||{};return f(e,200,{state:r.state||null,https:r.state==="ready"&&r.https||null,since:r.state==="ready"&&r.since||null,lines:r.state==="failed"?await ce(o,"prototypes"):[]})}if(n==="/api/log"&&t.method==="GET"){const r=a.searchParams.get("file")||"";if(!/^[\w.-]+\.log$/.test(r)||r.startsWith("."))return f(e,400,{error:"file takes a log file\u2019s name."});let d;try{d=await y.open(p.join(et.homedir(),".claude","kickflip","logs",r),"r");const{size:h}=await d.stat(),P=Math.max(0,h-256*1024),w=Buffer.alloc(h-P),{bytesRead:v}=await d.read(w,0,w.length,P),T=w.subarray(0,v).toString("utf8");return f(e,200,{text:P?T.slice(T.indexOf(`
12
- `)+1):T,truncated:P>0,size:h})}catch(h){if(h.code==="ENOENT")return f(e,404,{error:"No such log."});throw h}finally{await d?.close()}}if(n==="/api/transcript"&&t.method==="GET"){const r=a.searchParams.get("file")||"";if(!/^[\w.-]+\.log$/.test(r)||r.startsWith("."))return f(e,400,{error:"file takes a log file\u2019s name."});if(!/^\d+$/.test(a.searchParams.get("from")||""))return f(e,400,{error:"from takes a byte offset."});try{return f(e,200,await ba(p.join(et.homedir(),".claude","kickflip","logs",r),Number(a.searchParams.get("from"))))}catch(d){if(d.code==="ENOENT")return f(e,404,{error:"No such log."});throw d}}if(n==="/api/try/main/pull"&&t.method==="POST"){const r=await B(t);if(!r||typeof r!="object"||Array.isArray(r)||Object.keys(r).length)return f(e,400,{error:"Send an empty object."});try{const d=await fetch(`http://127.0.0.1:${process.env.KICKFLIP_HUB_PORT||4500}/_hub/pull`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({route:G.slice(1)||p.basename(o)}),signal:AbortSignal.timeout(3e4)});return f(e,d.status,await d.json())}catch(d){return f(e,502,{error:`Could not pull: ${d.message}`})}}if(n==="/api/try"&&t.method==="POST"){const r=await B(t);if(!r||typeof r!="object"||Array.isArray(r)||Object.keys(r).some(d=>d!=="key")||typeof r.key!="string")return f(e,400,{error:"Send only the card key."});try{const d=await fetch(`http://127.0.0.1:${process.env.KICKFLIP_HUB_PORT||4500}/_hub/build`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({route:G.slice(1)||p.basename(o),key:r.key}),signal:AbortSignal.timeout(1e4)});return f(e,d.status,await d.json())}catch(d){return f(e,502,{error:`Could not start the build: ${d.message}`})}}if(n==="/api/share"&&t.method==="POST")try{return f(e,200,await ji(o,await B(t)))}catch(r){if(r instanceof R)return f(e,400,{error:r.message});throw r}if(n==="/api/settings"&&t.method==="POST"){const r=await B(t);let d;try{d=await Oi(o,r)}catch(w){if(w instanceof R)return f(e,400,{error:w.message});throw w}const h=d.changed.filter(w=>w!=="idle_notification"),P=h.length?await U("settings",Object.fromEntries(h.map(w=>[w,d.settings[w]??null]))):null;return d.changed.includes("auto_merge")&&d.settings.auto_merge&&await U("check-github",{reason:"auto-merge"}),f(e,200,{settings:d.settings,id:P})}if(n==="/api/arch-review"&&t.method==="POST"){await B(t);try{return f(e,200,await Er())}catch(r){if(r instanceof R)return f(e,r.status||400,{error:r.message});throw r}}if(n==="/api/hide"&&t.method==="POST"){const r=await B(t);try{await Oe(o,String(r.id||""),r.hidden!==!1)}catch(d){if(d instanceof R)return f(e,400,{error:d.message});throw d}return f(e,200,{ok:!0})}if(n==="/api/read"&&t.method==="POST"){const{marks:r}=await B(t);return r&&typeof r=="object"&&!Array.isArray(r)&&Object.values(r).every(h=>typeof h=="string"&&!Number.isNaN(Date.parse(h)))?(await Se(o,(await N()).items.map(h=>h.key),r),f(e,200,{ok:!0})):f(e,400,{error:"Send {marks: {card key: ISO time}}."})}if(n==="/_claude/events"&&t.method==="GET")return f(e,200,await hr(a.searchParams.get("kind"),Math.min(500,Number(a.searchParams.get("limit")||100))));if(n==="/api/upload"&&t.method==="POST")return await xr(t,e);if(t.method!=="GET"&&t.method!=="HEAD"){e.writeHead(405).end();return}if(n.startsWith("/uploads/"))return await Cr(t,e,decodeURIComponent(n.slice(9)));if(n==="/plan.html"){e.writeHead(302,{location:`notes.html${a.search}`,"cache-control":"no-store"}).end();return}if(n==="/index.html"){e.writeHead(302,{location:`./${a.search}`,"cache-control":"no-store"}).end();return}const s=Ci.has(n.slice(1)),l=s?null:await Nr(n),u=s?null:l?pn:n==="/prototype.html"?null:await Je(decodeURIComponent(n));if(!s&&!u){e.writeHead(404).end();return}const m=s?".html":p.extname(u).toLowerCase(),k=s?null:await y.stat(u);let b=s?Buffer.from(Hi({touchIcon:await Ee()})):l?Buffer.from(await gn(o,l)):await y.readFile(u);!s&&m===".html"&&(b=(await er(b.toString("utf8"))).replaceAll("{{title}}",mn((await L()).title)).replaceAll("{{touchIcon}}",await Ee())),!s&&m===".html"&&(b=Buffer.from(await rr(b,n)));const $=m===".html"?`W/"${xa.createHash("sha1").update(b).digest("base64url").slice(0,20)}"`:`W/"${Rt(k)}"`,g=m!==".html"&&a.searchParams.get("v")===Rt(k),A={"content-type":F(Ie,m)||"application/octet-stream","cache-control":g?"public, max-age=31536000, immutable":"no-cache",etag:$};if(xe(t.headers["if-none-match"],$)){e.writeHead(304,{etag:$,"cache-control":A["cache-control"],vary:"accept-encoding"}).end();return}return await _e(e,200,A,b)}catch(a){e.headersSent||(a.status===403?e.writeHead(403,{"content-type":"text/plain; charset=utf-8","cache-control":"no-store"}).end(a.message):a instanceof R?f(e,a.status||400,{error:a.message}):e.writeHead(a.status||(a.code==="ENOENT"?404:400)).end())}});let zt=!1;async function Ye(){if(!zt){zt=!0;try{const t=Date.now(),e=(await N()).items.filter(n=>n.heldUntil&&n.lane!=="done"&&Date.parse(n.heldUntil)<=t),a=e.length?await M(H):[];for(const{key:n,heldUntil:i}of e)await Xn(o,n,i,{announce:!a.some(s=>s.kind==="card-unblocked"&&s.data?.key===n&&s.data?.until===i)}),await ta(n),E({call:"pick-up",key:n,result:"resumed",reason:"Its hold ended.",inputs:{heldUntil:i}})}catch(t){console.error("Pick-up check failed:",t.message)}finally{zt=!1}}}c(Ye,"checkPickUps");let ot=null,Vt=!1,D=!1,Qt=!1;function ct(){return ot?(Vt=!0,ot):(ot=(async()=>{do{Vt=!1;try{await Kr()}catch(t){console.error("Auto check failed:",t.message)}}while(Vt)})().finally(()=>{ot=null}),ot)}c(ct,"checkAuto");async function Kr(){const t=C(await L()),e=await M(H),{items:a,reviews:n}=await N(),i=await rs(e),s=await oa(Zt),l=t.max_implementers,u={free:we(a,{cap:l,pulled:s,hold:i}).free},m=u.free,k=J({dir:o});Qt&&k.state!=="paused"&&await U("usage-resumed",{window:"manual"}),Qt=k.manual==="board",D=z(k,"claude"),D||await Zr(),D||await Qr(tn(a)),D||await as(a,ea(a),u),D||await ns(a,u);const{idle:b,pullable:$,waiting:g,pulls:A,helpers:r,bumpOuts:d,pastCap:h}=we(a,{cap:l,pulled:s,hold:i,taken:m-u.free}),P=b&&!D;D||await Ze(a,e),D||await Yr(r),t.auto_pull&&!D&&await Vr(Za(a,{cap:t.max_planners}).map(O=>a.find(mt=>mt.key===O))),await is(t,e,a,P);const w=$.filter(O=>i.includes(O));w.length&&E({call:"pull",result:"held",reason:"An overnight triage waits for the session's reply and names these cards, so the pull leaves them until it is answered or quiet hours end.",inputs:{cards:w}});const v=c(O=>z(O,"claude")?[]:O.near&&!O.near.going?A.slice(0,1):A,"pullNow"),T=c(async O=>{for(const gt of d.filter(Q=>Q.by===O)){const Q={call:"bump-out",key:gt.key,reason:`${O} is bumped and every implementer slot is full; this was the newest unbumped build.`,inputs:{by:O,cap:l}};try{E({...Q,result:"bumped out",inputs:{...Q.inputs,lines:await Xi(o,gt.key,O)}})}catch(Pt){if(!(Pt instanceof R))throw Pt;E({...Q,result:"refused",reason:`${Q.reason} The bump-out refused: ${Pt.message}`})}finally{_++}}const mt=await It(o,O,gt=>$t(o,O.split("/")[1],gt),"hub"),da=h.includes(O)?"It is bumped, so it starts past the cap.":"An implementer slot was free and it was the top ready card in Todo.";return E({call:"pull",key:O,result:zr(mt),reason:da,inputs:{cap:l,pullable:$,waiting:g}}),jt(mt)},"startPull");if(await os(Zt,t.auto_pull,g,D?[]:v(J({dir:o})),T))return;const{due:I,repo:pt}=n;!P||!I||(E({call:"pr-review",result:"asked",reason:`The board is idle and PR #${I.number} touches its work.`,inputs:{repo:pt,pr:I.number,head:I.head,touches:I.touches,asked:!!I.asked}}),await U("pr-review",{pr:I.number,head:I.head,repo:pt,title:I.title,author:I.author,url:I.url,touches:I.touches,asked:!!I.asked}))}c(Kr,"runAuto");function V(t="claude"){const e=z(J({dir:o}),t);return e&&(D=!0),e}c(V,"launchHeldNow");const zr=c(t=>t?t.bumped?"bumped":"started":"refused","startResult");async function jt(t){if(_++,!t||t.bumped)return null;const e={kind:t.kind,model:t.model,relaunched:!1};try{const a=J({dir:o});if(z(a,t.kind))E({call:"launch-held",key:t.key,result:"held",reason:Nt(a,t.kind),inputs:{kind:t.kind,model:t.model??null,gate:a}}),a.machine.state==="busy"&&await x(o,t.key,{text:Ui}),await Yt(t.key,{...e,pid:null});else{const{pid:n,log:i}=await K(o,t);await Yt(t.key,{...e,pid:n,log:i})}}catch(a){await Fn(o,t.key,`the launch failed (${a.message}). Tap Implement to try again.`)}return null}c(jt,"handOff");async function Vr(t){for(const{key:e,phase:a}of t){if(V())return;const n=a==="prototyping"?"prototype":"grill";E({call:"arrival-planner",key:e,result:n,reason:a==="prototyping"?"It landed in Todo filed prototyping.":"It landed in Todo, not filed prototyping.",inputs:{phase:a}}),await x(o,e,{text:`Landed in Todo, so the board started its ${n}.`}),await tt(e,n),_++}}c(Vr,"launchArrivals");async function Qr(t){for(const{key:e,kind:a}of t){if(V())return;E({call:"round-planner",key:e,result:a,reason:"It was sent back from staging or ready for another round.",inputs:{kind:a}}),await x(o,e,{text:`Back to ${a==="grill"?"grilling":"prototyping"}, so the board started its ${a}.`}),await tt(e,a),_++}}c(Qr,"launchRounds");async function Yr(t){for(const{key:e,name:a}of t){if(V())return;_++;try{const n=await On(o,e,a,i=>pa(e.split("/")[1],a,i));if(!n)continue;E({call:"helper",key:e,result:"launching",reason:"A helper was asked for and pullOf let it launch.",inputs:{name:a,model:n.model,worktree:n.worktree}});try{await K(o,n)}catch(i){await yt(o,e,`Couldn't launch helper ${a} (${i.message}).`,a)}}catch(n){if(!(n instanceof R))throw n;await yt(o,e,`Couldn't start helper ${a}: ${n.message}`,a)}}}c(Yr,"launchHelpers");let Xe=Promise.resolve();function Ze(t,e){const a=Xe.then(()=>Xr(t,e));return Xe=a.catch(()=>{}),a}c(Ze,"launchVisuals");async function Xr(t,e){for(const{key:a,request:n}of Ha(t,e)){if(V())return;try{const i=await jn(o,a,n);if(!i)continue;const{text:s,files:l}=n.data,u={...s?{text:String(s)}:{},...Array.isArray(l)&&l.length?{files:l}:{}};try{await K(o,{...i,...u})}catch(m){await yt(o,a,`Couldn't launch the visual agent (${m.message}).`,i.agent)}}catch(i){if(!(i instanceof R))throw i;await x(o,a,{text:`Couldn't start the visual agent: ${i.message}`})}}}c(Xr,"drawVisuals");const q=p.join(S,"queued-planners.json");async function qe(t){await y.mkdir(S,{recursive:!0}),await y.writeFile(q+".tmp",JSON.stringify(t)+`
13
- `),await y.rename(q+".tmp",q)}c(qe,"saveQueuedPlanners");async function tt(t,e,a={}){const n=J({dir:o});if(z(n,"claude")){D=!0,await qe({...await j(q,{}),[t]:{kind:e,ask:a}}),E({call:"planner-queued",key:t,result:e,reason:Nt(n,"claude"),inputs:{kind:e,gate:n}}),await x(o,t,{text:`The ${e} waits and starts when the hold lifts. ${Nt(n,"claude")}`});return}try{const i=await Qn(o,t,e);if(!i)return;try{await K(o,{...i,...a})}catch(s){await yt(o,t,`Couldn't launch the ${e} (${s.message}).`)}}catch(i){if(!(i instanceof R))throw i;await x(o,t,{text:`Couldn't start the ${e}: ${i.message}`})}}c(tt,"launchPlanner");async function ta(t){const e=await ei(o,t);e&&(E({call:"resume",key:t,result:e.kind,reason:"Its hold stopped this run.",inputs:e}),e.kind==="grill"||e.kind==="prototype"?await tt(t,e.kind):e.label==="Builder"&&await jt(await It(o,t,a=>$t(o,t.split("/")[1],a),"hub")))}c(ta,"resumeRun");async function Zr(){for(const[t,{kind:e,ask:a}]of Object.entries(await j(q,{}))){if(V())return;const{[t]:n,...i}=await j(q,{});await qe(i),E({call:"planner-queued",key:t,result:"launching",reason:"The hold it waited on has lifted.",inputs:{kind:e}}),await tt(t,e,a),_++}}c(Zr,"launchQueuedPlanners");async function Yt(t,e){const a=p.join(S,"headless-builders.json"),n=await j(a,{});e?n[t]=e:delete n[t],await y.mkdir(S,{recursive:!0}),await y.writeFile(a+".tmp",JSON.stringify(n)+`
14
- `),await y.rename(a+".tmp",a)}c(Yt,"trackLaunch");async function qr(){try{const t=await j(p.join(S,"headless-builders.json"),{});for(const e of(await Mn(o)).filter(a=>!t[a.key]))E({call:"launch-cut-off",key:e.key,result:"launching",reason:"A restart cut off the launch after its build started.",inputs:{kind:e.kind,model:e.model??null}}),await jt(e)}catch(t){console.error("Cut-off launch check failed:",t.message)}}c(qr,"launchCutOffBuilds");const ea=c(t=>new Set(t.filter(e=>e.builder?.stopped).map(e=>e.key)),"stoppedKeys");let Xt=!1;async function ts(){if(!Xt){Xt=!0;try{const t=p.join(S,"headless-builders.json"),e=await j(t,{});if(!Object.keys(e).length)return;const{items:a}=At(await Be(await Bt())),n=new Set(a.filter(m=>m.lane!=="done"&&m.builder&&!m.builder.planner).map(m=>m.key)),i=c(async m=>{E({call:"relaunch",key:m.key,result:"handed back",reason:"Its builder exited twice with its record open.",inputs:e[m.key]}),await x(o,m.key,{text:"Its builder exited twice with the build unfinished; it reads stopped until the orchestrator takes it up."})},"handBack"),s=c(async m=>{const k=e[m.key],b={call:"relaunch",key:m.key,inputs:k,reason:k.until?"Its run stopped at the usage limit, which has reset.":k.pid===null?"Its launch was held by a pause, a busy host or a start near the line, which has lifted.":"Its builder exited with its record open, for the first time."};try{const{pid:$,log:g}=await K(o,m);return E({...b,result:k.pid===null?"launched":"relaunched"}),{pid:$,log:g}}catch($){return E({...b,result:"failed",reason:`${b.reason} The launch failed: ${$.message}`}),await x(o,m.key,{text:`Couldn't relaunch the builder headless (${$.message}).`}),{pid:null}}},"launch"),l=c(async(m,k)=>{E({call:"relaunch",key:m.key,result:"held",reason:`Its run stopped at the usage limit, which resets at ${k}.`,inputs:e[m.key]}),await x(o,m.key,{text:`Its builder stopped at the usage limit; it starts again at the reset, ${k}.`})},"atLimit"),u=await kn(e,n,{launch:s,handBack:i,held:c(m=>z(J({dir:o}),m),"held"),stopped:ea(a),limitedUntil:c(m=>m.log?Wi(m.log):null,"limitedUntil"),atLimit:l});JSON.stringify(u)!==JSON.stringify(e)&&(await y.writeFile(t+".tmp",JSON.stringify(u)+`
15
- `),await y.rename(t+".tmp",t))}catch(t){console.error("Headless builder check failed:",t.message)}finally{Xt=!1}}}c(ts,"reviveHeadless");function es(t){const e=p.join(et.homedir(),".claude","kickflip","logs");fn(e,{recursive:!0});const a=hn(p.join(e,`merge-${t.split("/")[1]}-${new Date().toISOString().replace(/[:.]/g,"-")}.log`),"a"),n=process.env.KICKFLIP_MERGE_CMD?JSON.parse(process.env.KICKFLIP_MERGE_CMD):[process.execPath,yn],i=Ia(n[0],[...n.slice(1),"--site",X,"merge",t],{detached:!0,stdio:["ignore",a,a]});un(a),i.on("error",s=>x(o,t,{text:`Not merged: the land couldn't start (${s.message}). Tap Merge again.`}).catch(()=>{})),i.unref()}c(es,"runHeldMerge");async function as(t,e,a){const n=t.some(g=>g.waitsOn==="checking"),i=Object.fromEntries(t.map(g=>[g.key,g.waitsOn==="checking"?"checking":g.lane!=="done"&&g.agents?.some(A=>A.started&&!A.done)?"building":null])),s=p.join(S,"landers.json"),l=Object.fromEntries(Object.entries(await j(s,{})).map(([g,A])=>[g,typeof A=="string"?{verified:A,pid:null}:A])),u=new Set,m=new Set;for(const[g,A]of Object.entries(l))if(!(!A.pid||be(A.pid)||e.has(g))&&i[g]!=="checking"){if(i[g]!=="building"){m.add(g);continue}await Wn(o,g),u.add(g),E({call:"lander",key:g,result:"handed back again",reason:"Its lander exited with its builder still open.",inputs:A})}const k=Object.fromEntries(Object.entries(l).filter(([g])=>!u.has(g)&&!m.has(g))),b=c(async()=>{await y.writeFile(s+".tmp",JSON.stringify(k)+`
16
- `),await y.rename(s+".tmp",s)},"save");if(m.size&&await b(),!n&&!u.size)return;const $=aa(t,(await Un(o)).filter(g=>k[g.key]?.verified!==g.verified));if($.length){for(const g of $){if(V(g.kind??"claude"))break;if(!na(t,a,g.key))continue;const A={call:"lander",key:g.key,reason:"Its build was verified and waits to land.",inputs:{verified:g.verified,before:k[g.key]??null}};try{k[g.key]={verified:g.verified,pid:(await K(o,g,{lander:!0})).pid},E({...A,result:"launched"}),await Yt(g.key,null)}catch(r){E({...A,result:"failed",reason:`${A.reason} The launch failed: ${r.message}`}),k[g.key]={verified:g.verified,pid:null},await x(o,g.key,{text:`Couldn't launch the lander headless (${r.message}); land it with board.mjs land ${g.key}.`})}}await b()}}c(as,"launchLanders");const lt=c((t,e)=>t.find(a=>a.key===e)?.bumpedAt||"","bumpOf"),aa=c((t,e)=>[...e.filter(a=>lt(t,a.key)).sort((a,n)=>lt(t,a.key).localeCompare(lt(t,n.key))),...e.filter(a=>!lt(t,a.key))],"byBump");function na(t,e,a){return!lt(t,a)&&e.free<=0?!1:(e.free--,!0)}c(na,"takeSlot");async function ns(t,e){const a=p.join(S,"rebases.json"),n=await j(a,{}),{base:i}=wa(o),s=aa(t,qa(t,Object.fromEntries(Object.entries(n).map(([l,u])=>[l,u.conflict])),i));if(s.length)try{for(const l of s){if(V())break;if(!na(t,e,l.key))continue;const u=l.key.split("/")[1],m={call:"rebase-run",key:l.key,reason:`Its PR #${l.pr} conflicts with ${i}.`,inputs:{pr:l.pr,conflict:l.conflict,before:n[l.key]??null}};n[l.key]={conflict:l.conflict,pid:null};let k;try{k=await Zn(o,l.key,{pr:l.pr,conflict:l.conflict,base:i},()=>ga(o,u,l.branch))}catch(b){if(!(b instanceof R))throw b;E({...m,result:"refused",reason:`${m.reason} The start refused: ${b.message}`}),await x(o,l.key,{text:`PR #${l.pr} conflicts with ${i}, but the rebase run couldn't start: ${b.message}`});continue}finally{_++}try{n[l.key].pid=(await K(o,k)).pid,E({...m,result:"launched"})}catch(b){E({...m,result:"failed",reason:`${m.reason} The launch failed: ${b.message}`}),await x(o,l.key,{text:`Couldn't launch the rebase run headless (${b.message}); it reads stopped until the orchestrator takes it up.`})}}}finally{await y.writeFile(a+".tmp",JSON.stringify(n)+`
17
- `),await y.rename(a+".tmp",a)}}c(ns,"launchRebases");async function ia(){const t=on(new Date,pe(await j(p.join(et.homedir(),".claude","kickflip","settings.json"),{})));return t&&rn(t)}c(ia,"triageNight");async function is(t,e,a,n){const i=p.join(S,"triaged.json"),s=await j(i,{}),{waiting:l,seen:u}=nn(a),m=c(async()=>{await y.mkdir(S,{recursive:!0}),await y.writeFile(i+".tmp",JSON.stringify({seen:u})+`
18
- `),await y.rename(i+".tmp",i)},"save"),k=e.filter(A=>A.kind==="overnight-triage").at(-1);if(k&&!(await it(k)).reply){s.seen!==u&&await m();return}if(!t.auto_triage||!n||l.length<2||u===s.seen)return;const b=await ia();if(!b||k?.data?.date===b)return;const $=`overnight-triage-${b}`;((await j(p.join(S,"owner.json"),{topics:[]})).topics||[]).some(A=>A.id===$)||await xn(o,{id:$,title:`Overnight triage ${b}`}),E({call:"triage",result:"sent",reason:`Auto triage is on, ${l.length} cards wait on answers, and tonight's triage hadn't gone out.`,inputs:{date:b,cards:l}}),await U("overnight-triage",{date:b,topic:$,cards:l}),await m()}c(is,"checkTriage");async function ra(t){const e=t.filter(a=>a.kind==="overnight-triage").at(-1);return e&&!(await it(e)).reply?e:null}c(ra,"unansweredTriage");async function rs(t){const e=await ra(t),a=pe(await j(p.join(et.homedir(),".claude","kickflip","settings.json"),{}));return e&&sn(new Date,a)?e.data.cards||[]:[]}c(rs,"triageHold");async function ss(){if(!C(await L()).auto_triage)return!1;const t=await ia();return!!t&&(await M(H)).filter(e=>e.kind==="overnight-triage").at(-1)?.data?.date!==t}c(ss,"triageDue");const Zt={file:"pulled.json",line:"Pulled in: an implementer slot was free."},sa=c(t=>t.keys||(t.key?[t.key]:[]),"markedOf"),oa=c(async({file:t})=>sa(await j(p.join(S,t),{})),"sentOf");async function os({file:t,line:e},a,n,i,s){const l=p.join(S,t),u=a?[...n,...i]:[];for(const m of a?i:[])await y.mkdir(p.dirname(nt(m)),{recursive:!0}),await y.appendFile(nt(m),JSON.stringify({ts:new Date().toISOString(),who:"claude",text:e})+`
19
- `),await s(m);return JSON.stringify(sa(await j(l,{})))!==JSON.stringify(u)&&(await y.mkdir(S,{recursive:!0}),await y.writeFile(l+".tmp",JSON.stringify({keys:u,at:new Date().toISOString()})+`
20
- `),await y.rename(l+".tmp",l)),u.length>0}c(os,"sendPulls");async function qt(t,{key:e,reason:a,inputs:n}){const i={call:"push",key:e,reason:a,inputs:{tag:t.tag,title:t.title,...n}};try{await qn(o,t),E({...i,result:"sent"})}catch(s){if(!(s instanceof R))throw s;const l=s.message.split(`
21
- `).at(-2)||s.message;E({...i,result:"not sent",reason:`${a} Not sent: ${l}`}),console.error(`Push ${t.tag} not sent:`,l)}}c(qt,"sendOwnPush");let ut=null,te=!1;function ee(){return ut?(te=!0,ut):(ut=(async()=>{do{te=!1;try{await cs()}catch(t){console.error("Action push check failed:",t.message)}}while(te)})().finally(()=>{ut=null}),ut)}c(ee,"checkActions");async function cs(){const t=p.join(S,"action-pushes.json"),e=await j(t,null),a=Ya(await N()),n=Object.fromEntries(a.map(i=>[i.tag,{kind:i.kind,ids:i.questions.map(s=>s.id)}]));if(e){for(const s of a){const l=F(e,s.tag),u=s.questions.filter(b=>!l?.ids?.includes(b.id));if(l?.kind===s.kind&&!u.length)continue;const m=u.length?`questions.html#${encodeURIComponent(u[0].id)}`:s.url,k=l?l.kind!==s.kind?`What waits on the owner changed from ${l.kind} to ${s.kind}.`:"A new question waits on the owner.":"Something new waits on the owner.";await qt({kind:"action",title:s.title,body:u[0]?.question||s.body,url:`/${X}/${m}`,tag:s.tag,now:s.now},{key:s.tag.startsWith("item-")?s.tag.slice(5):void 0,reason:k,inputs:{was:l??null,kind:s.kind,fresh:u.map(b=>b.id)}})}const i=Object.keys(e).filter(s=>!F(n,s));i.length&&await ln(i,X)}JSON.stringify(n)!==JSON.stringify(e)&&(await y.mkdir(S,{recursive:!0}),await y.writeFile(t+".tmp",JSON.stringify(n)+`
22
- `),await y.rename(t+".tmp",t))}c(cs,"pushActions");let dt=null,ae=!1;function ne(){return dt?(ae=!0,dt):(dt=(async()=>{do{ae=!1;try{await ls()}catch(t){console.error("Orchestrator wait check failed:",t.message)}}while(ae)})().finally(()=>{dt=null}),dt)}c(ne,"checkWaits");async function ls(){const t=p.join(S,"orchestrator-waits.json"),e=await j(t,null),a=e?.waits||[],n=await ra(await M(H)),i=[...Fa(await N()),...n?[Ma(n,F(await bt(o),n.id))]:[]],s=Ua(a,i,new Date),l=[];for(const u of s.waits)u.kind!=="triage"&&!u.picked&&!u.pushed&&Date.now()-Date.parse(u.since)>=La?(await qt({kind:"reminder",title:`${u.ref} waits on the orchestrator`,body:`${u.title}: ${u.next}`,url:`/${X}/#selectedCard=${encodeURIComponent(u.key)}`,tag:`wait-${u.key}`,now:!0},{key:u.key,reason:`It has waited on the orchestrator since ${u.since} and nobody picked it up.`,inputs:{kind:u.kind,since:u.since}}),l.push({...u,pushed:!0})):l.push(u);(!e||JSON.stringify(l)!==JSON.stringify(a))&&(await y.mkdir(S,{recursive:!0}),await y.writeFile(t+".tmp",JSON.stringify({waits:l})+`
23
- `),await y.rename(t+".tmp",t)),(s.added.length||s.again.length)&&await U("orchestrator-waits",{added:s.added,again:s.again})}c(ls,"stepWaits");let ft=null,ie=!1;function re(){return ft?(ie=!0,ft):(ft=(async()=>{do{ie=!1;try{await us()}catch(t){console.error("Idle check failed:",t.message)}}while(ie)})().finally(()=>{ft=null}),ft)}c(re,"checkIdle");async function us(){const t=p.join(S,"idle.json"),e=await L();if(!C(e).idle_notification)return y.rm(t,{force:!0});const a=await j(t,{}),n=await N(),i=en(n,{pulled:await oa(Zt)}),{state:s,push:l}=Ga(a,{idle:i,now:new Date});l&&await qt({kind:"reminder",title:`${e.title||X} has gone idle`,body:Ka(n.items,a.last),url:`/${X}/`,tag:"idle",now:!0},{reason:"The board has been strictly idle long enough, with idle notifications on.",inputs:{previous:a}}),JSON.stringify(s)!==JSON.stringify(a)&&(await y.mkdir(S,{recursive:!0}),await y.writeFile(t+".tmp",JSON.stringify(s)+`
24
- `),await y.rename(t+".tmp",t))}c(us,"stepIdle");let ht=null,se=!1;function ca(){return ht?(se=!0,ht):(ht=(async()=>{do{se=!1;try{const t=Ca(await N());for(const e of t)await zn(o,e.key,e.to,e.why,e.phase);t.length&&_++}catch(t){console.error("Lane settle failed:",t.message)}}while(se)})().finally(()=>{ht=null}),ht)}c(ca,"settleLanes");let wt=null,oe=!1;function la(){return wt?(oe=!0,wt):(wt=(async()=>{do{oe=!1;try{await ds()}catch(t){console.error("Done archive failed:",t.message)}}while(oe)})().finally(()=>{wt=null}),wt)}c(la,"checkArchive");async function ua(){try{await Se(o,(await N()).items.map(t=>t.key))}catch(t){console.error("Read marks failed:",t.message)}}c(ua,"markNewCards");async function ds(){if(!de(await N()).length)return;const t=de(await st()),e=t.length?await Dn(o,t.map(a=>({key:a.key,closed_at:a.sortAt,prs:a.prs}))):[];e.length&&console.log(`Done archive: ${e.length} card${e.length===1?"":"s"} moved`)}c(ds,"archiveDone");try{for(const t of await Jn(o))console.log(t)}catch(t){console.error("follow-up migration:",t.message)}try{for(const t of await Kn(o))console.log(t)}catch(t){console.error("status migration:",t.message)}try{for(const t of await Gn(o))console.log(t)}catch(t){console.error("phase migration:",t.message)}try{for(const t of await Vn(o))console.log(t)}catch(t){console.error("staging migration:",t.message)}try{for(const t of await bi(o))console.log(t)}catch(t){console.error("notes split:",t.message)}try{await Yn(o)}catch(t){console.error("dead-field strip:",t.message)}await qr(),await ca(),await la(),await ua(),await Jr(),setInterval(()=>{_++,Fe(),ts(),vn(o).then(t=>{t.length&&_++},t=>console.error("Screen publish failed:",t.message)),Ye(),D&&!z(J({dir:o}),"claude")?ct():ss().then(t=>{t&&ct()},t=>console.error("Triage check failed:",t.message)),ee(),ne(),re()},6e4).unref(),await Fe(),await Ye(),await ct(),ee(),ne(),re(),Gr.listen(Re,"127.0.0.1",()=>console.log(`site on http://127.0.0.1:${Re}/ (prefix ${G||"none"})`));
11
+ `),wa(),ma(),ga(),at(),it(),ue(),st()},Math.max(0,Math.min(es,Zt+ts-t)))}c(Ke,"changed");const as=new Set(["node_modules","dd","DerivedData"]),ze=c(e=>String(e).endsWith(".tmp")||String(e).split(/[\\/]/).some(t=>t.startsWith(".")||as.has(t)),"ignored");let qt=null;const ea=c(e=>{try{const t=kt(e);return`${t.size}:${t.mtimeMs}`}catch{return null}},"statOf");function ta(e,t){if(!t)return Ke();if(ze(t))return;const a=p.join(e,String(t));a!==We&&Ke({read:a===F&&ea(F)===qt})}c(ta,"heard");const Ve=c(e=>{try{return kt(e).isDirectory()}catch{return!1}},"isFolder");function ns(e){let t=c(()=>{},"stop"),a=!1,n=null;const i=c(()=>{n||(t(),a=!0,n=setTimeout(()=>{n=null,s()},2e3).unref())},"retry"),s=c(()=>{try{t=(process.platform==="linux"?rs:is)(e,i)}catch{i();return}a&&Ke()},"start");s()}c(ns,"watchTree");function is(e,t){const a=bt(e,{recursive:!0},(n,i)=>{ta(e,i),Ve(e)||t()});return a.on("error",t),()=>a.close()}c(is,"watchRecursive");function rs(e,t){const a=new Map,n=c(s=>{for(const[l,u]of a)(l===s||l.startsWith(s+p.sep))&&(u.close(),a.delete(l))},"unwatch"),i=c(s=>{if(a.has(s))return;const l=bt(s,(u,k)=>{if(ta(s,k),s===e&&!Ve(e))return t();if(!k||ze(k))return;const w=p.join(s,String(k));if(!Ve(w))n(w);else try{i(w)}catch{n(w)}});a.set(s,l),l.on("error",()=>s===e?t():n(s));for(const u of jn(s,{withFileTypes:!0}))if(!(!u.isDirectory()||ze(u.name)))try{i(p.join(s,u.name))}catch{n(p.join(s,u.name))}},"watchFolder");try{i(e)}catch(s){throw n(e),s}return()=>n(e)}c(rs,"watchEachFolder");async function ss(){const e=(await W()).items==="waves"?[Ji()]:(await be(o)).map(a=>a.dir),t=[...new Set([v,...e].map(a=>p.resolve(a)))];for(const a of t.filter(n=>!t.some(i=>i!==n&&n.startsWith(i+p.sep))))ns(a)}c(ss,"watchBoard");const os=Ba.createServer(async(e,t)=>{try{if(e.method!=="GET"&&e.method!=="HEAD"&&R++,e.method!=="GET"&&e.method!=="HEAD"&&er(e.headers)){t.writeHead(403,{"content-type":"text/plain; charset=utf-8","cache-control":"no-store"}),t.end("Requests must come from the app\u2019s own pages.");return}const a=new URL(e.url,"http://site");let n=a.pathname;z&&(n===z||n.startsWith(z+"/"))&&(n=n.slice(z.length)||"/");const i=e.method!=="GET"&&e.method!=="HEAD"?await Pr(e,n):null;if(i?.refused)return h(t,403,{error:i.refused});if(n==="/_claude/health")return h(t,200,{ok:!0});if(n==="/api/board"){const r=await G(e,String(a.searchParams.get("since")||"")),d=`W/"${r.version}"`;if(Lt(e.headers["if-none-match"],d)){t.writeHead(304,{etag:d,"cache-control":"no-store",vary:"accept-encoding"}).end();return}return h(t,200,r,{etag:d})}if(n==="/api/reviewer-people")return h(t,200,await Lr());if(n==="/api/questions"){const r=await x(),d=new Set(r.ownerTopics.find(f=>f.id===String(a.searchParams.get("topic")??""))?.questions||[]);return h(t,200,{questions:r.owner.filter(f=>d.has(f.id)&&f.state==="resolved")})}if(n==="/api/done"){const r=String(a.searchParams.get("before")||""),d=(await At(o)).filter(f=>!r||ei(f)<r);return h(t,200,{cards:await Br(d.slice(0,ht)),left:Math.max(0,d.length-ht)})}if(n==="/api/links")return h(t,200,{links:(await le()).links});if(n==="/api/stream"&&e.method==="GET")return qr(e,t);if(n==="/_claude/needs")return h(t,200,sn(await le()));if(n==="/api/working"){const r=await x(),d=new Map(r.items.filter(f=>f.kind==="work").map(f=>[f.key,f.ref]));return h(t,200,{working:r.workingOn.map(f=>({id:f.id,kind:f.kind,card:d.get(f.key)||null,since:f.since}))})}if(n==="/api/thread")return h(t,200,await Vr(String(a.searchParams.get("key")||"")));if(n==="/api/notes"&&a.searchParams.has("wave"))try{return h(t,200,await Ti(o,String(a.searchParams.get("wave")),B(await W()).tickets))}catch(r){if(r instanceof $)return h(t,404,{error:r.message});throw r}if(n==="/api/notes"||n==="/api/plan"){const r=String(a.searchParams.get("item")||"");try{const{itemFile:d,item:f,markdown:P}=await Ri(o,r);return h(t,200,{key:r,ref:Re(d,f,B(await W()).tickets),title:f.title,markdown:P})}catch(d){if(d instanceof $)return h(t,404,{error:d.message});throw d}}if(n==="/_claude/events"&&e.method==="POST"){const r=await L(e,524288),d=String(r.kind||"message").slice(0,64);delete r.kind;const f=String(r.since||"");if(delete r.since,i&&!vr.has(d))return h(t,403,{error:jr});for(const m of[r,...Array.isArray(r.answers)?r.answers.filter(S=>S&&typeof S=="object"):[]])delete m.shared_by,i&&(m.shared_by=i.by);if(d==="card-hold"&&r.hold&&r.for!==void 0){const m=Rt(new Date,r.for);if(!m)return h(t,400,{error:"That hold ends in the past or more than 30 days on. Pick another time."});delete r.for,m!==$t&&(r.until=m.toISOString())}if(d==="card-build"){const m=String(r.key||""),S=await Ie(o,m,E=>Ee(o,m.split("/")[1],E));return h(t,200,{id:await je(S),board:await G(e,f)})}if(d==="item-message"){const m=String(r.key||""),S=(await x()).items.find(H=>H.key===m&&H.kind==="work"),E=!String(r.text||"").trim()&&!(Array.isArray(r.files)&&r.files.length);if(S?.stalled&&await yi(o,m),E&&(S?.ready||S?.lane==="todo"))return await Jn(o,m,i?i.by.name||i.by.login:void 0),R++,h(t,200,{id:null,board:await G(e,f)});S?.lane==="todo"&&r.intent==="build"?await Gn(o,m,r):S?.lane==="todo"&&S.phase==="ready"&&await Kn(o,m,r),R++}if(d==="owner-answer")return h(t,200,{id:(await Ut([r]))[0]??null,board:await G(e,f)});if(d==="owner-answers"){const m=await Ar(r.answers);return m?h(t,400,{error:m}):h(t,200,{ids:await Ut(r.answers),board:await G(e,f)})}if(d==="card-merge"&&await Vn(o,String(r.key||"")).catch(()=>null)){const m=String(r.key);try{await Yn(o,m)}catch(S){if(S instanceof $)return h(t,400,{error:S.message});throw S}return ks(m),h(t,200,{id:null,board:await G(e,f)})}if(d==="card-merge"){try{await ki(o,String(r.key||""),{tapped:!0})}catch(m){if(m instanceof $)return h(t,400,{error:m.message});throw m}return h(t,200,{id:null,board:await G(e,f)})}if(d==="card-hold"||d==="card-done"){const m=String(r.key||"");let S,E;try{E=d==="card-hold"&&!r.hold?[]:await Pt(o,m),d==="card-done"&&await Mn(o,m)&&(E=[]),S=await mi(o,d,r)}catch(I){return h(t,I instanceof $?400:500,{error:`Couldn\u2019t write ${m} to its item file: ${I.message}`})}if(!S)return h(t,400,{error:`${m} is already in Done, so its item file stays as it is.`});const H=await Pt(o,m);E=E.filter(I=>!H.some(T=>T.pid===I.pid)),E.length&&(Nn(E),await C(o,m,"builder-stopped")),d==="card-hold"&&!r.hold&&await sa(m)}if(d==="reviewers"){const m=c(T=>T===void 0?[]:Array.isArray(T)?T:null,"list"),S=m(r.add),E=m(r.remove),H=c(T=>typeof T=="string"&&T.trim(),"text");if(!S||!E||!S.length&&!E.length)return h(t,400,{error:"Send the people to add and the names to remove, each as a list."});if(!E.every(H)||!S.every(T=>H(T?.name)&&H(T.slack_user)&&H(T.github)))return h(t,400,{error:"Each person to add needs a name, a Slack member and a GitHub login."});try{E.length&&await Ni(o,E.map(T=>T.trim())),S.length&&await Ii(o,await Ur(S))}catch(T){if(T instanceof $||T instanceof pr)return h(t,400,{error:T.message});throw T}const I=await He([...E.length?[["reviewers-removed",{remove:E}]]:[],...S.length?[["reviewers-added",{add:S.map(T=>T.name)}]]:[]]);return h(t,200,{id:I.at(-1),board:await G(e,f)})}if(d==="reask-request"){const m=await Tr(String(r.question||""));if(m)return h(t,400,{error:m})}if(d===$e){const m=await Er(String(r.question||""));if(m)return h(t,400,{error:m})}const P=await U(d,r);if((d==="card-bump"||d==="card-unbump")&&Oa(o,String(r.key||""),d==="card-bump"?new Date().toISOString():null),(d==="grill-more"||d==="prototype-request")&&r.key){const m={...r.text?{text:String(r.text)}:{},...Array.isArray(r.files)&&r.files.length?{files:r.files}:{},...r.question?{question:String(r.question),note:r.note?String(r.note):void 0,choice:r.choice??void 0}:{}},S=d==="grill-more"?"grill":"prototype";await zn(o,String(r.key),S),await te(String(r.key),S,m)}return d==="visual-request"&&r.key&&await ia((await x()).items,await J(F)),d==="ask-withdrawn"&&await Tt(o,String(r.id||""),!0),h(t,200,{id:P,board:await G(e,f)})}if(n==="/api/rules"&&e.method==="GET")return h(t,200,{board:await ji(o),hub:Ki()});if(n==="/api/rules"&&e.method==="POST"){const r=await L(e);let d;try{d=await Hi(o,r.rules,r.base??[])}catch(P){if(P instanceof $)return h(t,400,{error:P.message});throw P}const f=Gi(d.changes)?await U("rules",{scope:"board",...d.changes}):null;return h(t,200,{rules:d.rules,id:f})}if(n==="/api/settings"&&e.method==="GET")return h(t,200,{settings:await Pi(o),paused_until:ir(o),pause_choices:zt});if(n==="/api/build-targets"&&e.method==="GET"){const r=X(o);if(!r)return h(t,404,{error:"This board isn\u2019t in the hub\u2019s manifest."});const d=De(r.route);return h(t,200,{targets:await ur(d.repo,cr(d))})}if(n==="/api/pause"&&e.method==="POST"){const r=await L(e);if(!r||typeof r!="object"||Array.isArray(r)||Object.keys(r).join()!=="pause"||typeof r.pause!="string")return h(t,400,{error:"Send only {pause: a duration, a date and time, or off}."});let d;try{d=await Wi(o,r.pause)}catch(f){if(f instanceof $)return h(t,400,{error:f.message});throw f}return d&&(Xe=!0),ue(),h(t,200,{paused_until:d})}if(n==="/api/permissions"){const r=X(o);if(!r)return h(t,404,{error:"This board isn\u2019t in the hub\u2019s manifest."});const d=De(r.route).repo,f=e.method==="POST"?await L(e):null;try{return f&&await fr(d,o,f.id,f.on),h(t,200,await dr(d,o))}catch(P){if(P instanceof $)return h(t,400,{error:P.message});throw P}}if(n==="/api/try/main/log"&&e.method==="GET")return h(t,200,{lines:await ct(o,"main")});if(n==="/api/try/prototypes"&&e.method==="GET"){const r=(await O(p.join(o,"data","try.json"),{}))?.prototypes||{};return h(t,200,{state:r.state||null,https:r.state==="ready"&&r.https||null,since:r.state==="ready"&&r.since||null,lines:r.state==="failed"?await ct(o,"prototypes"):[]})}if(n==="/api/log"&&e.method==="GET"){const r=a.searchParams.get("file")||"";if(!/^[\w.-]+\.log$/.test(r)||r.startsWith("."))return h(t,400,{error:"file takes a log file\u2019s name."});let d;try{d=await b.open(p.join(ae.homedir(),".claude","kickflip","logs",r),"r");const{size:f}=await d.stat(),P=Math.max(0,f-256*1024),m=Buffer.alloc(f-P),{bytesRead:S}=await d.read(m,0,m.length,P),E=m.subarray(0,S).toString("utf8");return h(t,200,{text:P?E.slice(E.indexOf(`
12
+ `)+1):E,truncated:P>0,size:f})}catch(f){if(f.code==="ENOENT")return h(t,404,{error:"No such log."});throw f}finally{await d?.close()}}if(n==="/api/transcript"&&e.method==="GET"){const r=a.searchParams.get("file")||"";if(!/^[\w.-]+\.log$/.test(r)||r.startsWith("."))return h(t,400,{error:"file takes a log file\u2019s name."});if(!/^\d+$/.test(a.searchParams.get("from")||""))return h(t,400,{error:"from takes a byte offset."});try{return h(t,200,await Ea(p.join(ae.homedir(),".claude","kickflip","logs",r),Number(a.searchParams.get("from"))))}catch(d){if(d.code==="ENOENT")return h(t,404,{error:"No such log."});throw d}}if(n==="/api/try/main/pull"&&e.method==="POST"){const r=await L(e);if(!r||typeof r!="object"||Array.isArray(r)||Object.keys(r).length)return h(t,400,{error:"Send an empty object."});try{const d=await fetch(`http://127.0.0.1:${process.env.KICKFLIP_HUB_PORT||4500}/_hub/pull`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({route:z.slice(1)||p.basename(o)}),signal:AbortSignal.timeout(3e4)});return h(t,d.status,await d.json())}catch(d){return h(t,502,{error:`Could not pull: ${d.message}`})}}if(n==="/api/try"&&e.method==="POST"){const r=await L(e);if(!r||typeof r!="object"||Array.isArray(r)||Object.keys(r).some(d=>d!=="key")||typeof r.key!="string")return h(t,400,{error:"Send only the card key."});try{const d=await fetch(`http://127.0.0.1:${process.env.KICKFLIP_HUB_PORT||4500}/_hub/build`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({route:z.slice(1)||p.basename(o),key:r.key}),signal:AbortSignal.timeout(1e4)});return h(t,d.status,await d.json())}catch(d){return h(t,502,{error:`Could not start the build: ${d.message}`})}}if(n==="/api/share"&&e.method==="POST")try{return h(t,200,await Li(o,await L(e)))}catch(r){if(r instanceof $)return h(t,400,{error:r.message});throw r}if(n==="/api/settings"&&e.method==="POST"){const r=await L(e);let d;try{d=await Fi(o,r)}catch(m){if(m instanceof $)return h(t,400,{error:m.message});throw m}const f=d.changed.filter(m=>m!=="idle_notification"),P=f.length?await U("settings",Object.fromEntries(f.map(m=>[m,d.settings[m]??null]))):null;return d.changed.includes("auto_merge")&&d.settings.auto_merge&&await U("check-github",{reason:"auto-merge"}),h(t,200,{settings:d.settings,id:P})}if(n==="/api/arch-review"&&e.method==="POST"){await L(e);try{return h(t,200,await Gr())}catch(r){if(r instanceof $)return h(t,r.status||400,{error:r.message});throw r}}if(n==="/api/hide"&&e.method==="POST"){const r=await L(e);try{await Tt(o,String(r.id||""),r.hidden!==!1)}catch(d){if(d instanceof $)return h(t,400,{error:d.message});throw d}return h(t,200,{ok:!0})}if(n==="/api/read"&&e.method==="POST"){const{marks:r}=await L(e);return r&&typeof r=="object"&&!Array.isArray(r)&&Object.values(r).every(f=>typeof f=="string"&&!Number.isNaN(Date.parse(f)))?(await Et(o,(await x()).items.map(f=>f.key),r),h(t,200,{ok:!0})):h(t,400,{error:"Send {marks: {card key: ISO time}}."})}if(n==="/_claude/events"&&e.method==="GET")return h(t,200,await $r(a.searchParams.get("kind"),Math.min(500,Number(a.searchParams.get("limit")||100))));if(n==="/api/upload"&&e.method==="POST")return await Yr(e,t);if(e.method!=="GET"&&e.method!=="HEAD"){t.writeHead(405).end();return}if(n.startsWith("/uploads/"))return await Zr(e,t,decodeURIComponent(n.slice(9)));if(n==="/plan.html"){t.writeHead(302,{location:`notes.html${a.search}`,"cache-control":"no-store"}).end();return}if(n==="/index.html"){t.writeHead(302,{location:`./${a.search}`,"cache-control":"no-store"}).end();return}const s=Zi.has(n.slice(1)),l=s?null:await Qr(n),u=s?null:l?Pn:n==="/prototype.html"?null:await Yt(decodeURIComponent(n));if(!s&&!u){t.writeHead(404).end();return}const k=s?".html":p.extname(u).toLowerCase(),w=s?null:await b.stat(u);let y=s?Buffer.from(qi({touchIcon:await Dt()})):l?Buffer.from(await En(o,l)):await b.readFile(u);!s&&k===".html"&&(y=(await gr(y.toString("utf8"))).replaceAll("{{title}}",An((await W()).title)).replaceAll("{{touchIcon}}",await Dt())),!s&&k===".html"&&(y=Buffer.from(await Sr(y,n)));const A=k===".html"?`W/"${Ua.createHash("sha1").update(y).digest("base64url").slice(0,20)}"`:`W/"${_e(w)}"`,_=k!==".html"&&a.searchParams.get("v")===_e(w),g={"content-type":M(Bt,k)||"application/octet-stream","cache-control":_?"public, max-age=31536000, immutable":"no-cache",etag:A};if(Lt(e.headers["if-none-match"],A)){t.writeHead(304,{etag:A,"cache-control":g["cache-control"],vary:"accept-encoding"}).end();return}return await Ht(t,200,g,y)}catch(a){t.headersSent||(a.status===403?t.writeHead(403,{"content-type":"text/plain; charset=utf-8","cache-control":"no-store"}).end(a.message):a instanceof $?h(t,a.status||400,{error:a.message}):t.writeHead(a.status||(a.code==="ENOENT"?404:400)).end())}});let Qe=!1;async function aa(){if(!Qe){Qe=!0;try{const e=Date.now(),t=(await x()).items.filter(n=>n.heldUntil&&n.lane!=="done"&&Date.parse(n.heldUntil)<=e),a=t.length?await J(F):[];for(const{key:n,heldUntil:i}of t)await hi(o,n,i,{announce:!a.some(s=>s.kind==="card-unblocked"&&s.data?.key===n&&s.data?.until===i)}),await sa(n),j({call:"pick-up",key:n,result:"resumed",reason:"Its hold ended.",inputs:{heldUntil:i}})}catch(e){console.error("Pick-up check failed:",e.message)}finally{Qe=!1}}}c(aa,"checkPickUps");let ce=null,Ye=!1,D=!1,Xe=!1;function ue(){return ce?(Ye=!0,ce):(ce=(async()=>{do{Ye=!1;try{await ls()}catch(e){console.error("Auto check failed:",e.message)}}while(Ye)})().finally(()=>{ce=null}),ce)}c(ue,"checkAuto");async function ls(){const e=B(await W()),t=await J(F),{items:a,reviews:n}=await x(),i=await Os(t),s=await pa(qe),l=e.max_implementers,u={free:wt(a,{cap:l,pulled:s,hold:i}).free},k=u.free,w=K({dir:o});Xe&&w.state!=="paused"&&await U("usage-resumed",{window:"manual"}),Xe=w.manual==="board",D=V(w,"claude"),D||await ms(),D||await ds(dn(a)),D||await bs(a,la(a),u),D||await Ss(a,u);const y=await ps(a,w);if(!D)for(const N of y.slice(0,u.free))u.free--,await ws(N);const{idle:A,pullable:_,waiting:g,pulls:r,helpers:d,bumpOuts:f,pastCap:P}=wt(a,{cap:l,pulled:s,hold:i,taken:k-u.free}),m=A&&!D;D||await ia(a,t),D||await fs(d),e.auto_pull&&!D&&await us(cn(a,{cap:e.max_planners}).map(N=>a.find(ge=>ge.key===N))),await vs(e,t,a,m);const S=_.filter(N=>i.includes(N));S.length&&j({call:"pull",result:"held",reason:"An overnight triage waits for the session's reply and names these cards, so the pull leaves them until it is answered or quiet hours end.",inputs:{cards:S}});const E=c(N=>V(N,"claude")?[]:N.near&&!N.near.going?r.slice(0,1):r,"pullNow"),H=c(async N=>{for(const ye of f.filter(Y=>Y.by===N)){const Y={call:"bump-out",key:ye.key,reason:`${N} is bumped and every implementer slot is full; this was the newest unbumped build.`,inputs:{by:N,cap:l}};try{j({...Y,result:"pausing",inputs:{...Y.inputs,lines:await hr(o,ye.key,N)}})}catch(Ae){if(!(Ae instanceof $))throw Ae;j({...Y,result:"refused",reason:`${Y.reason} The bump-out refused: ${Ae.message}`})}finally{R++}}const ge=await Ie(o,N,ye=>Ee(o,N.split("/")[1],ye),"hub"),ya=P.includes(N)?"It is bumped, so it starts past the cap.":"An implementer slot was free and it was the top ready card in Todo.";return j({call:"pull",key:N,result:cs(ge),reason:ya,inputs:{cap:l,pullable:_,waiting:g}}),je(ge)},"startPull");if(await Ps(qe,e.auto_pull,g,D?[]:E(K({dir:o})),H))return;const{due:I,repo:T}=n;!m||!I||(j({call:"pr-review",result:"asked",reason:`The board is idle and PR #${I.number} touches its work.`,inputs:{repo:T,pr:I.number,head:I.head,touches:I.touches,asked:!!I.asked}}),await U("pr-review",{pr:I.number,head:I.head,repo:T,title:I.title,author:I.author,url:I.url,touches:I.touches,asked:!!I.asked}))}c(ls,"runAuto");function Q(e="claude"){const t=V(K({dir:o}),e);return t&&(D=!0),t}c(Q,"launchHeldNow");const cs=c(e=>e?e.bumped?"bumped":"started":"refused","startResult");async function je(e){if(R++,!e||e.bumped)return null;const t={kind:e.kind,model:e.model,relaunched:!1};try{const a=K({dir:o});if(V(a,e.kind))j({call:"launch-held",key:e.key,result:"held",reason:xe(a,e.kind),inputs:{kind:e.kind,model:e.model??null,gate:a}}),a.machine.state==="busy"&&await C(o,e.key,"host-busy"),await Pe(e.key,{...t,pid:null});else{const{pid:n,log:i}=await ne(o,e);await Pe(e.key,{...t,pid:n,log:i})}}catch(a){await ai(o,e.key,`the launch failed: ${a.message}`)}return null}c(je,"handOff");async function us(e){for(const{key:t,phase:a}of e){if(Q())return;const n=a==="prototyping"?"prototype":"grill";j({call:"arrival-planner",key:t,result:n,reason:a==="prototyping"?"It landed in Todo filed prototyping.":"It landed in Todo, not filed prototyping.",inputs:{phase:a}}),await te(t,n),R++}}c(us,"launchArrivals");async function ds(e){for(const{key:t,kind:a}of e){if(Q())return;j({call:"round-planner",key:t,result:a,reason:"It was sent back from staging or ready for another round.",inputs:{kind:a}}),await te(t,a),R++}}c(ds,"launchRounds");async function fs(e){for(const{key:t,name:a}of e){if(Q())return;R++;try{const n=await Un(o,t,a,i=>va(t.split("/")[1],a,i));if(!n)continue;j({call:"helper",key:t,result:"launching",reason:"A helper was asked for and pullOf let it launch.",inputs:{name:a,model:n.model,worktree:n.worktree}});try{await ne(o,n)}catch(i){await ke(o,t,["helper-failed",{name:a,error:i.message}],a)}}catch(n){if(!(n instanceof $))throw n;await ke(o,t,["helper-failed",{name:a,error:n.message}],a)}}}c(fs,"launchHelpers");let na=Promise.resolve();function ia(e,t){const a=na.then(()=>hs(e,t));return na=a.catch(()=>{}),a}c(ia,"launchVisuals");async function hs(e,t){for(const{key:a,request:n}of Va(e,t)){if(Q())return;try{const i=await Wn(o,a,n);if(!i)continue;const{text:s,files:l}=n.data,u={...s?{text:String(s)}:{},...Array.isArray(l)&&l.length?{files:l}:{}};try{await ne(o,{...i,...u})}catch(k){await ke(o,a,["planner-failed",{kind:"visual agent",error:k.message}],i.agent)}}catch(i){if(!(i instanceof $))throw i;await C(o,a,"planner-failed",{kind:"visual agent",error:i.message})}}}c(hs,"drawVisuals");const ee=p.join(v,"queued-planners.json");async function ra(e){await b.mkdir(v,{recursive:!0}),await b.writeFile(ee+".tmp",JSON.stringify(e)+`
13
+ `),await b.rename(ee+".tmp",ee)}c(ra,"saveQueuedPlanners");async function te(e,t,a={}){const n=K({dir:o});if(V(n,"claude")){D=!0,await ra({...await O(ee,{}),[e]:{kind:t,ask:a}}),j({call:"planner-queued",key:e,result:t,reason:xe(n,"claude"),inputs:{kind:t,gate:n}}),await C(o,e,n.machine.state==="busy"?"host-busy":"planner-held",{kind:t});return}try{const i=await di(o,e,t);if(!i)return;try{await ne(o,{...i,...a})}catch(s){await ke(o,e,["planner-failed",{kind:t,error:s.message}])}}catch(i){if(!(i instanceof $))throw i;await C(o,e,"planner-failed",{kind:t,error:i.message})}}c(te,"launchPlanner");async function sa(e){const t=await gi(o,e);t&&(j({call:"resume",key:e,result:t.kind,reason:"Its hold stopped this run.",inputs:t}),t.kind==="grill"||t.kind==="prototype"?await te(e,t.kind):t.label==="Builder"&&await je(await Ie(o,e,a=>Ee(o,e.split("/")[1],a),"hub")))}c(sa,"resumeRun");let oa=!1;async function ps(e,t){const a=Ja(e,{usageHeld:c(n=>_t(t,n),"usageHeld")});for(const n of a.ask)await jt(o,n,{cause:"usage",words:nr(t)})&&j({call:"pause",key:n,result:"asked",reason:xe(t,"claude"),inputs:{gate:t}});for(const n of a.drop)await Cn(o,n),j({call:"pause",key:n,result:"dropped",reason:"Its cause cleared before its builder reached a stopping point.",inputs:{}});for(const n of a.stop){await Ot(o,n);const i=$n((await Bn(o,n)).filter(l=>!Ga(l)))[0],s=i?await xn([i.pid]):[];j({call:"pause",key:n,result:"hard stop",reason:"Its builder reached no stopping point within 15 minutes of the pause.",inputs:{stopped:s.map(l=>l.pid)}})}return(a.ask.length||a.drop.length||a.stop.length)&&R++,oa=a.ask.length>0||e.some(n=>n.builder?.pause),a.resume}c(ps,"checkPauses");async function ws(e){const t=await Hn(o,e);if(!t)return;const{pause:a,record:n}=t,i=Ma[a.cause][1];R++;const s={call:"resume",key:e,reason:`Nothing pauses it any more: ${i}.`,inputs:{pause:a}};if(!n.session?.pid)return j({...s,result:"woke the orchestrator"}),U("builder-resume",{key:e,cleared:i});const l=Ka(n.kind)??"claude";try{const{pid:u,log:k}=await _n(o,{key:e,kind:l,model:n.model,session:{id:n.session.id},brief:c(w=>Rn({...w,cleared:i}),"brief")});await Pe(e,{kind:l,model:n.model,pid:u,log:k,relaunched:!1}),j({...s,result:"resumed"})}catch(u){j({...s,result:"failed",reason:`${s.reason} The launch failed: ${u.message}`}),await C(o,e,"resume-failed",{error:u.message})}}c(ws,"resumeBuild");async function ms(){for(const[e,{kind:t,ask:a}]of Object.entries(await O(ee,{}))){if(Q())return;const{[e]:n,...i}=await O(ee,{});await ra(i),j({call:"planner-queued",key:e,result:"launching",reason:"The hold it waited on has lifted.",inputs:{kind:t}}),await te(e,t,a),R++}}c(ms,"launchQueuedPlanners");async function Pe(e,t){const a=p.join(v,"headless-builders.json"),n=await O(a,{});t?n[e]=t:delete n[e],await b.mkdir(v,{recursive:!0}),await b.writeFile(a+".tmp",JSON.stringify(n)+`
14
+ `),await b.rename(a+".tmp",a)}c(Pe,"trackLaunch");async function gs(){try{const e=await O(p.join(v,"headless-builders.json"),{});for(const t of(await ni(o)).filter(a=>!e[a.key]))j({call:"launch-cut-off",key:t.key,result:"launching",reason:"A restart cut off the launch after its build started.",inputs:{kind:t.kind,model:t.model??null}}),await je(t)}catch(e){console.error("Cut-off launch check failed:",e.message)}}c(gs,"launchCutOffBuilds");const la=c(e=>new Set(e.filter(t=>t.builder?.stopped).map(t=>t.key)),"stoppedKeys");let Ze=!1;async function ys(){if(!Ze){Ze=!0;try{const e=p.join(v,"headless-builders.json"),t=await O(e,{});if(!Object.keys(t).length)return;const{items:a}=Te(await Jt(await Le())),n=new Set(a.filter(w=>w.lane!=="done"&&w.builder&&!w.builder.planner).map(w=>w.key)),i=c(async w=>{j({call:"relaunch",key:w.key,result:"handed back",reason:"Its builder exited twice with its record open.",inputs:t[w.key]}),await C(o,w.key,"exited-twice")},"handBack"),s=c(async w=>{const y=t[w.key],A={call:"relaunch",key:w.key,inputs:y,reason:y.pid===null?"Its launch was held by a pause, a busy host or a start near the line, which has lifted.":"Its builder exited with its record open, for the first time."};try{const{pid:_,log:g}=await ne(o,w);return j({...A,result:y.pid===null?"launched":"relaunched"}),{pid:_,log:g}}catch(_){return j({...A,result:"failed",reason:`${A.reason} The launch failed: ${_.message}`}),await C(o,w.key,"relaunch-failed",{error:_.message}),{pid:null}}},"launch"),l=c(async(w,y)=>{j({call:"relaunch",key:w.key,result:"paused",reason:`Its run stopped at the usage limit, which resets at ${y}.`,inputs:t[w.key]}),await jt(o,w.key,{cause:"usage",words:"usage limit",until:y}),await Ot(o,w.key),R++},"atLimit"),u=new Set(a.filter(w=>w.builder?.pause?.paused).map(w=>w.key)),k=await In(t,n,{launch:s,handBack:i,held:c(w=>V(K({dir:o}),w),"held"),stopped:la(a),paused:u,limitedUntil:c(w=>w.log?rr(w.log):null,"limitedUntil"),atLimit:l});JSON.stringify(k)!==JSON.stringify(t)&&(await b.writeFile(e+".tmp",JSON.stringify(k)+`
15
+ `),await b.rename(e+".tmp",e))}catch(e){console.error("Headless builder check failed:",e.message)}finally{Ze=!1}}}c(ys,"reviveHeadless");function ks(e){const t=p.join(ae.homedir(),".claude","kickflip","logs");vn(t,{recursive:!0});const a=On(p.join(t,`merge-${e.split("/")[1]}-${new Date().toISOString().replace(/[:.]/g,"-")}.log`),"a"),n=process.env.KICKFLIP_MERGE_CMD?JSON.parse(process.env.KICKFLIP_MERGE_CMD):[process.execPath,Tn],i=Ha(n[0],[...n.slice(1),"--site",Z,"merge",e],{detached:!0,stdio:["ignore",a,a]});bn(a),i.on("error",s=>C(o,e,"land-failed",{why:`the land couldn't start (${s.message})`}).catch(()=>{})),i.unref()}c(ks,"runHeldMerge");async function bs(e,t,a){const n=e.some(g=>g.waitsOn==="checking"),i=Object.fromEntries(e.map(g=>[g.key,g.waitsOn==="checking"?"checking":g.lane!=="done"&&g.agents?.some(r=>r.started&&!r.done)?"building":null])),s=p.join(v,"landers.json"),l=Object.fromEntries(Object.entries(await O(s,{})).map(([g,r])=>[g,typeof r=="string"?{verified:r,pid:null}:r])),u=new Set,k=new Set,w={};for(const[g,r]of Object.entries(l))if(!(!r.pid||vt(r.pid)||t.has(g))){if(i[g]==="checking"){r.resumed&&(w[g]={verified:null,pid:null,fresh:!0},j({call:"lander",key:g,result:"fresh next",reason:"Its resumed builder exited without taking the land up.",inputs:r}));continue}if(i[g]!=="building"){k.add(g);continue}await ri(o,g),u.add(g),j({call:"lander",key:g,result:"handed back again",reason:"Its lander exited with its builder still open.",inputs:r})}const y={...Object.fromEntries(Object.entries(l).filter(([g])=>!u.has(g)&&!k.has(g))),...w},A=c(async()=>{await b.writeFile(s+".tmp",JSON.stringify(y)+`
16
+ `),await b.rename(s+".tmp",s)},"save");if(k.size&&await A(),!n&&!u.size)return;const _=ca(e,(await ii(o)).filter(g=>y[g.key]?.verified!==g.verified));if(_.length){for(const g of _){if(Q(g.kind??"claude"))break;if(!ua(e,a,g.key))continue;const r={call:"lander",key:g.key,reason:"Its build was verified and waits to land.",inputs:{verified:g.verified,before:y[g.key]??null}};try{const{pid:d,resumed:f}=await St(o,g,{fresh:y[g.key]?.fresh});y[g.key]={verified:g.verified,pid:d,...f?{resumed:f}:{}},j({...r,result:f?"resumed its builder":"launched"}),await Pe(g.key,null)}catch(d){j({...r,result:"failed",reason:`${r.reason} The launch failed: ${d.message}`}),y[g.key]={verified:g.verified,pid:null},await C(o,g.key,"lander-failed",{error:d.message})}}await A()}}c(bs,"launchLanders");const de=c((e,t)=>e.find(a=>a.key===t)?.bumpedAt||"","bumpOf"),ca=c((e,t)=>[...t.filter(a=>de(e,a.key)).sort((a,n)=>de(e,a.key).localeCompare(de(e,n.key))),...t.filter(a=>!de(e,a.key))],"byBump");function ua(e,t,a){return!de(e,a)&&t.free<=0?!1:(t.free--,!0)}c(ua,"takeSlot");async function Ss(e,t){const a=p.join(v,"rebases.json"),n=await O(a,{}),{base:i}=Sa(o),s=ca(e,un(e,Object.fromEntries(Object.entries(n).map(([l,u])=>[l,u.conflict])),i));if(s.length)try{for(const l of s){if(Q())break;if(!ua(e,t,l.key))continue;const u=l.key.split("/")[1],k={call:"rebase-run",key:l.key,reason:`Its PR #${l.pr} conflicts with ${i}.`,inputs:{pr:l.pr,conflict:l.conflict,before:n[l.key]??null}};n[l.key]={conflict:l.conflict,pid:null};let w;try{w=await pi(o,l.key,{pr:l.pr,conflict:l.conflict,base:i},()=>ja(o,u,l.branch))}catch(y){if(!(y instanceof $))throw y;j({...k,result:"refused",reason:`${k.reason} The start refused: ${y.message}`}),await C(o,l.key,"rebase-failed",{pr:l.pr,error:y.message});continue}finally{R++}try{n[l.key].pid=(await St(o,w)).pid,j({...k,result:"launched"})}catch(y){j({...k,result:"failed",reason:`${k.reason} The launch failed: ${y.message}`}),await C(o,l.key,"rebase-launch-failed",{error:y.message})}}}finally{await b.writeFile(a+".tmp",JSON.stringify(n)+`
17
+ `),await b.rename(a+".tmp",a)}}c(Ss,"launchRebases");async function da(){const e=gn(new Date,mt(await O(p.join(ae.homedir(),".claude","kickflip","settings.json"),{})));return e&&wn(e)}c(da,"triageNight");async function vs(e,t,a,n){const i=p.join(v,"triaged.json"),s=await O(i,{}),{waiting:l,seen:u}=pn(a),k=c(async()=>{await b.mkdir(v,{recursive:!0}),await b.writeFile(i+".tmp",JSON.stringify({seen:u})+`
18
+ `),await b.rename(i+".tmp",i)},"save"),w=t.filter(g=>g.kind==="overnight-triage").at(-1);if(w&&!(await se(w)).reply){s.seen!==u&&await k();return}if(!e.auto_triage||!n||l.length<2||u===s.seen)return;const y=await da();if(!y||w?.data?.date===y)return;const A=`overnight-triage-${y}`;((await O(p.join(v,"owner.json"),{topics:[]})).topics||[]).some(g=>g.id===A)||await Xn(o,{id:A,title:`Overnight triage ${y}`}),j({call:"triage",result:"sent",reason:`Auto triage is on, ${l.length} cards wait on answers, and tonight's triage hadn't gone out.`,inputs:{date:y,cards:l}}),await U("overnight-triage",{date:y,topic:A,cards:l}),await k()}c(vs,"checkTriage");async function fa(e){const t=e.filter(a=>a.kind==="overnight-triage").at(-1);return t&&!(await se(t)).reply?t:null}c(fa,"unansweredTriage");async function Os(e){const t=await fa(e),a=mt(await O(p.join(ae.homedir(),".claude","kickflip","settings.json"),{}));return t&&mn(new Date,a)?t.data.cards||[]:[]}c(Os,"triageHold");async function js(){if(!B(await W()).auto_triage)return!1;const e=await da();return!!e&&(await J(F)).filter(t=>t.kind==="overnight-triage").at(-1)?.data?.date!==e}c(js,"triageDue");const qe={file:"pulled.json",line:"pulled"},ha=c(e=>e.keys||(e.key?[e.key]:[]),"markedOf"),pa=c(async({file:e})=>ha(await O(p.join(v,e),{})),"sentOf");async function Ps({file:e,line:t},a,n,i,s){const l=p.join(v,e),u=a?[...n,...i]:[];for(const k of a?i:[])await C(o,k,t),await s(k);return JSON.stringify(ha(await O(l,{})))!==JSON.stringify(u)&&(await b.mkdir(v,{recursive:!0}),await b.writeFile(l+".tmp",JSON.stringify({keys:u,at:new Date().toISOString()})+`
19
+ `),await b.rename(l+".tmp",l)),u.length>0}c(Ps,"sendPulls");async function et(e,{key:t,reason:a,inputs:n}){const i={call:"push",key:t,reason:a,inputs:{tag:e.tag,title:e.title,...n}};try{await wi(o,e),j({...i,result:"sent"})}catch(s){if(!(s instanceof $))throw s;const l=s.message.split(`
20
+ `).at(-2)||s.message;j({...i,result:"not sent",reason:`${a} Not sent: ${l}`}),console.error(`Push ${e.tag} not sent:`,l)}}c(et,"sendOwnPush");let fe=null,tt=!1;function at(){return fe?(tt=!0,fe):(fe=(async()=>{do{tt=!1;try{await As()}catch(e){console.error("Action push check failed:",e.message)}}while(tt)})().finally(()=>{fe=null}),fe)}c(at,"checkActions");async function As(){const e=p.join(v,"action-pushes.json"),t=await O(e,null),a=on(await x()),n=Object.fromEntries(a.map(i=>[i.tag,{kind:i.kind,ids:i.questions.map(s=>s.id)}]));if(t){for(const s of a){const l=M(t,s.tag),u=s.questions.filter(y=>!l?.ids?.includes(y.id));if(l?.kind===s.kind&&!u.length)continue;const k=u.length?`questions.html#${encodeURIComponent(u[0].id)}`:s.url,w=l?l.kind!==s.kind?`What waits on the owner changed from ${l.kind} to ${s.kind}.`:"A new question waits on the owner.":"Something new waits on the owner.";await et({kind:"action",title:s.title,body:u[0]?.question||s.body,url:`/${Z}/${k}`,tag:s.tag,now:s.now},{key:s.tag.startsWith("item-")?s.tag.slice(5):void 0,reason:w,inputs:{was:l??null,kind:s.kind,fresh:u.map(y=>y.id)}})}const i=Object.keys(t).filter(s=>!M(n,s));i.length&&await kn(i,Z)}JSON.stringify(n)!==JSON.stringify(t)&&(await b.mkdir(v,{recursive:!0}),await b.writeFile(e+".tmp",JSON.stringify(n)+`
21
+ `),await b.rename(e+".tmp",e))}c(As,"pushActions");let he=null,nt=!1;function it(){return he?(nt=!0,he):(he=(async()=>{do{nt=!1;try{await Es()}catch(e){console.error("Orchestrator wait check failed:",e.message)}}while(nt)})().finally(()=>{he=null}),he)}c(it,"checkWaits");async function Es(){const e=p.join(v,"orchestrator-waits.json"),t=await O(e,null),a=t?.waits||[],n=await fa(await J(F)),i=[...Xa(await x()),...n?[Za(n,M(await Se(o),n.id))]:[]],s=qa(a,i,new Date),l=[];for(const u of s.waits)u.kind!=="triage"&&!u.picked&&!u.pushed&&Date.now()-Date.parse(u.since)>=Ya?(await et({kind:"reminder",title:`${u.ref} waits on the orchestrator`,body:`${u.title}: ${u.next}`,url:`/${Z}/#selectedCard=${encodeURIComponent(u.key)}`,tag:`wait-${u.key}`,now:!0},{key:u.key,reason:`It has waited on the orchestrator since ${u.since} and nobody picked it up.`,inputs:{kind:u.kind,since:u.since}}),l.push({...u,pushed:!0})):l.push(u);(!t||JSON.stringify(l)!==JSON.stringify(a))&&(await b.mkdir(v,{recursive:!0}),await b.writeFile(e+".tmp",JSON.stringify({waits:l})+`
22
+ `),await b.rename(e+".tmp",e)),(s.added.length||s.again.length)&&await U("orchestrator-waits",{added:s.added,again:s.again})}c(Es,"stepWaits");let pe=null,rt=!1;function st(){return pe?(rt=!0,pe):(pe=(async()=>{do{rt=!1;try{await Ts()}catch(e){console.error("Idle check failed:",e.message)}}while(rt)})().finally(()=>{pe=null}),pe)}c(st,"checkIdle");async function Ts(){const e=p.join(v,"idle.json"),t=await W();if(!B(t).idle_notification)return b.rm(e,{force:!0});const a=await O(e,{}),n=await x(),i=fn(n,{pulled:await pa(qe)}),{state:s,push:l}=an(a,{idle:i,now:new Date});l&&await et({kind:"reminder",title:`${t.title||Z} has gone idle`,body:nn(n.items,a.last),url:`/${Z}/`,tag:"idle",now:!0},{reason:"The board has been strictly idle long enough, with idle notifications on.",inputs:{previous:a}}),JSON.stringify(s)!==JSON.stringify(a)&&(await b.mkdir(v,{recursive:!0}),await b.writeFile(e+".tmp",JSON.stringify(s)+`
23
+ `),await b.rename(e+".tmp",e))}c(Ts,"stepIdle");let we=null,ot=!1;function wa(){return we?(ot=!0,we):(we=(async()=>{do{ot=!1;try{const e=za(await x());for(const t of e)await ci(o,t.key,t.to,t.why,t.phase,t.from);e.length&&R++}catch(e){console.error("Lane settle failed:",e.message)}}while(ot)})().finally(()=>{we=null}),we)}c(wa,"settleLanes");let me=null,lt=!1;function ma(){return me?(lt=!0,me):(me=(async()=>{do{lt=!1;try{await $s()}catch(e){console.error("Done archive failed:",e.message)}}while(lt)})().finally(()=>{me=null}),me)}c(ma,"checkArchive");async function ga(){try{await Et(o,(await x()).items.map(e=>e.key))}catch(e){console.error("Read marks failed:",e.message)}}c(ga,"markNewCards");async function $s(){if(!ft(await x()).length)return;const e=ft(await le()),t=e.length?await Zn(o,e.map(a=>({key:a.key,closed_at:a.sortAt,prs:a.prs}))):[];t.length&&console.log(`Done archive: ${t.length} card${t.length===1?"":"s"} moved`)}c($s,"archiveDone");try{for(const e of await si(o))console.log(e)}catch(e){console.error("follow-up migration:",e.message)}try{for(const e of await li(o))console.log(e)}catch(e){console.error("status migration:",e.message)}try{for(const e of await oi(o))console.log(e)}catch(e){console.error("phase migration:",e.message)}try{for(const e of await ui(o))console.log(e)}catch(e){console.error("staging migration:",e.message)}try{for(const e of await Ci(o))console.log(e)}catch(e){console.error("notes split:",e.message)}try{await fi(o)}catch(e){console.error("dead-field strip:",e.message)}await gs(),await wa(),await ma(),await ga(),await ss(),setInterval(()=>{R++,Kt(),ys(),Fn(o).then(t=>{t.length&&R++},t=>console.error("Screen publish failed:",t.message)),aa();const e=K({dir:o});D&&!V(e,"claude")||oa||!D&&_t(e,"claude")?ue():js().then(t=>{t&&ue()},t=>console.error("Triage check failed:",t.message)),at(),it(),st()},6e4).unref(),await Kt(),await aa(),await ue(),at(),it(),st(),os.listen(Ct,"127.0.0.1",()=>console.log(`site on http://127.0.0.1:${Ct}/ (prefix ${z||"none"})`));
package/board/stop.mjs CHANGED
@@ -1 +1 @@
1
- var R=Object.defineProperty;var e=(n,t)=>R(n,"name",{value:t,configurable:!0});import{spawnSync as v}from"node:child_process";import*as s from"./store.mjs";import{launchedAgents as l,stopRuns as m}from"./headless.mjs";import{clearStoppedLand as g,landLock as B,landQueue as N,moveQueuedLand as A,runningLand as T}from"./worktree.mjs";import{orchestratorDid as C,plans as f,stoppedWords as b}from"./domain.mjs";import{processStarts as O}from"../hub/live.mjs";const d=e(n=>{throw new s.BoardError(n)},"fail"),$=120,h=e(n=>n.map(t=>`stopped ${t.pid} ${t.args.length>$?`${t.args.slice(0,$-1)}\u2026`:t.args}`),"pidLines"),x=e((n,t,a)=>[`${n}: ${b(t)}`,...h(a)],"stoppedLines");async function L(n,t,a,i){await s.markStopped(n,t,a);const o=await m([i]);return await s.appendThread(n,t,{text:b(a)}),o}e(L,"stopped");async function q(n,t,a){const i=await s.openRuns(n,t),o=i.filter(r=>!f(r));return!o.length&&i.length&&d(`${a} has a ${i[0].kind} planner running, not a build; builder stop stops builds, landers and rebase runs`),o.length&&o.every(r=>!r.pid)&&d(`${a}'s builder wasn't launched by the board, so it isn't the board's to stop; stop it in its own session`),l(o)[0]??d(`${a}'s builder isn't running`)}e(q,"launchedBuild");async function S(n,t,a,i){const o=await s.isLanding(n,t)?"land":"build",r=await L(n,t,o,i.pid);return g(a,r.map(p=>p.pid)),x(a,o,r)}e(S,"stopAgent");async function P(n,t){const a=await s.cardName(n,t);return{lines:await S(n,t,a,await q(n,t,a))}}e(P,"stopBuilder");async function W(n,t){const a=await s.cardName(n,t),i=(await s.openRuns(n,t)).filter(r=>!f(r));i.every(r=>!r.pid)&&d(`${a}'s builder runs in a session the board didn't launch, so blocking it can't stop it; finish or stop it in that session with builder done --next, then block it`);const o=l(i)[0];return o?S(n,t,a,o):[]}e(W,"stopForBlock");async function _(n,t,a){await s.markStopped(n,t,"build");const i=l((await s.openRuns(n,t)).filter(r=>!f(r)))[0],o=i?await m([i.pid]):[];return[await s.sendBackBumpedOut(n,t,a),...h(o)]}e(_,"bumpOut");async function j(n,t,a){const i=await s.cardName(n,t),o=await s.markStalled(n,t,a);await s.markStopped(n,t,await s.isLanding(n,t)?"land":"build");const r=l((await s.openRuns(n,t)).filter(u=>!f(u)))[0],p=r?await m([r.pid]):[];return g(i,p.map(u=>u.pid)),await s.appendThread(n,t,{text:`stalled by orchestrator: ${o}`}),{lines:[`${i} stalled: ${o}`,...h(p)]}}e(j,"stallCard");const D=e(n=>Number(v("ps",["-o","pgid=","-p",String(n)],{encoding:"utf8"}).stdout.trim())||null,"pgidOf");async function z(n,t){const a=await s.cardName(n,t),i=T(a),o=i&&O([i.pid])?.get(i.pid);(!i||!(o<=Date.parse(i.at)+2e3))&&d(`${a} has no land running`),B()?.pid===i.pid&&d(`${a} is fast-forwarding main now; it can't be stopped partway`);const r=D(i.pid),p=await s.openRuns(n,t),u=l(p.filter(c=>!f(c))).find(c=>c.pid===r);let w;return u?w=await L(n,t,"land",u.pid):i.merge&&r===i.pid?(await s.stopHeldMerge(n,t),w=await m([i.pid]),await s.appendThread(n,t,{text:b("land")})):d(`${a}'s land runs in a session the board didn't launch; stop it in that session`),g(a,w.map(c=>c.pid)),{lines:x(a,"land",w)}}e(z,"stopLand");async function I(n,t,a){const i=await s.cardName(n,t),o=await s.bumpCard(n,t,a),r=A(n,t,a?o:null);return a&&r&&await s.appendThread(n,t,{text:C("land","moved first")}),{lines:[a?`${i} bumped${r?": its land moved first":""}`:`${i} unbumped`]}}e(I,"bump");function J(){const n=N();if(!n.length)return{lines:["no land queued"]};const t=n.filter(a=>!a.running);return{lines:[...n.filter(a=>a.running).map(a=>`${a.card} landing now`),...t.map((a,i)=>`${i+1}. ${a.card}${a.first?" (bumped)":""}`)]}}e(J,"queueLines");export{I as bump,_ as bumpOut,J as queueLines,j as stallCard,P as stopBuilder,W as stopForBlock,z as stopLand};
1
+ var x=Object.defineProperty;var d=(n,a)=>x(n,"name",{value:a,configurable:!0});import{spawnSync as N}from"node:child_process";import*as r from"./store.mjs";import{launchedAgents as m,stopRuns as w}from"./headless.mjs";import{clearStoppedLand as f,landLock as v,landQueue as R,moveQueuedLand as A,runningLand as q}from"./worktree.mjs";import{orchestratorDid as B,plans as g,stoppedWords as $}from"./domain.mjs";import{processStarts as C}from"../hub/live.mjs";const u=d(n=>{throw new r.BoardError(n)},"fail"),b=120,h=d(n=>n.map(a=>`stopped ${a.pid} ${a.args.length>b?`${a.args.slice(0,b-1)}\u2026`:a.args}`),"pidLines"),S=d((n,a,t)=>[`${n}: ${$(a)}`,...h(t)],"stoppedLines");async function L(n,a,t,s){await r.markStopped(n,a,t);const i=await w([s]);return await r.appendStatus(n,a,"run-stopped",{words:$(t)}),i}d(L,"stopped");async function D(n,a,t){const s=await r.openRuns(n,a),i=s.filter(o=>!g(o));return!i.length&&s.length&&u(`${t} has a ${s[0].kind} planner running, not a build; builder stop stops builds, landers and rebase runs`),i.length&&i.every(o=>!o.pid)&&u(`${t}'s builder wasn't launched by the board, so it isn't the board's to stop; stop it in its own session`),m(i)[0]??u(`${t}'s builder isn't running`)}d(D,"launchedBuild");async function M(n,a,t,s){const i=await r.isLanding(n,a)?"land":"build",o=await L(n,a,i,s.pid);return f(t,o.map(e=>e.pid)),S(t,i,o)}d(M,"stopAgent");async function _(n,a){const t=await r.cardName(n,a);return{lines:await M(n,a,t,await D(n,a,t))}}d(_,"stopBuilder");async function j(n,a,t){const s=`bumped out by ${await r.cardName(n,t)}`;return await r.requestPause(n,a,{cause:"bumped",words:s,by:t}),[`${await r.cardName(n,a)} pauses: ${s}`]}d(j,"bumpOut");async function z(n,a,t){const s=await r.cardName(n,a),i=await r.markStalled(n,a,t);await r.markStopped(n,a,await r.isLanding(n,a)?"land":"build");const o=m((await r.openRuns(n,a)).filter(p=>!g(p)))[0],e=o?await w([o.pid]):[];return f(s,e.map(p=>p.pid)),await r.appendMessage(n,a,{text:`stalled by orchestrator: ${i}`}),{lines:[`${s} stalled: ${i}`,...h(e)]}}d(z,"stallCard");const O=d(n=>Number(N("ps",["-o","pgid=","-p",String(n)],{encoding:"utf8"}).stdout.trim())||null,"pgidOf");async function F(n,a){const t=await r.cardName(n,a),s=q(t),i=s&&C([s.pid])?.get(s.pid);(!s||!(i<=Date.parse(s.at)+2e3))&&u(`${t} has no land running`),v()?.pid===s.pid&&u(`${t} is fast-forwarding main now; it can't be stopped partway`);const o=O(s.pid),e=await r.openRuns(n,a),p=m(e.filter(c=>!g(c))).find(c=>c.pid===o);let l;return p?l=await L(n,a,"land",p.pid):s.merge&&o===s.pid?(await r.stopHeldMerge(n,a),l=await w([s.pid]),await r.appendStatus(n,a,"run-stopped",{words:$("land")})):u(`${t}'s land runs in a session the board didn't launch; stop it in that session`),f(t,l.map(c=>c.pid)),{lines:S(t,"land",l)}}d(F,"stopLand");async function I(n,a,t){const s=await r.cardName(n,a),i=await r.bumpCard(n,a,t),o=A(n,a,t?i:null);return t&&o&&await r.appendStatus(n,a,"land-moved-first",{words:B("land","moved first")}),{lines:[t?`${s} bumped${o?": its land moved first":""}`:`${s} unbumped`]}}d(I,"bump");function J(){const n=R();if(!n.length)return{lines:["no land queued"]};const a=n.filter(t=>!t.running);return{lines:[...n.filter(t=>t.running).map(t=>`${t.card} landing now`),...a.map((t,s)=>`${s+1}. ${t.card}${t.first?" (bumped)":""}`)]}}d(J,"queueLines");export{I as bump,j as bumpOut,J as queueLines,z as stallCard,_ as stopBuilder,F as stopLand};