@lovek4d/kickflip 0.5.3 → 0.6.0

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