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