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