@lovek4d/kickflip 0.9.0 → 0.10.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 +2 -2
- package/SKILL.md +8 -8
- package/bin/event-lines.mjs +1 -1
- package/board/bin/board.mjs +20 -23
- package/board/domain.mjs +7 -7
- package/board/headless.mjs +3 -3
- package/board/public/app-cards.js +31 -8
- package/board/public/prototype-kit.js +1 -0
- package/board/public/prototype.html +6 -0
- package/board/public/style.css +2 -2
- package/board/public/ui-cards.js +6 -1
- package/board/public/ui.js +1 -1
- package/board/public/view-questions.js +8 -4
- package/board/public/view-settings.js +9 -3
- package/board/render-check.mjs +8 -8
- package/board/server.mjs +17 -17
- package/board/store.mjs +29 -29
- package/hub/github.mjs +1 -1
- package/hub/hub.mjs +11 -11
- package/hub/pr-report.mjs +2 -2
- package/hub/presence.mjs +1 -1
- package/hub/review-send.mjs +1 -1
- package/hub/try.mjs +12 -11
- package/hub/upgrade.mjs +1 -1
- package/package.json +1 -1
- package/shared/colors.css +3 -3
package/board/public/style.css
CHANGED
|
@@ -468,7 +468,7 @@ button.attach[aria-pressed="true"] { color: var(--accent); background: var(--chi
|
|
|
468
468
|
.view-only-strip b { color: var(--accent); }
|
|
469
469
|
/* A board out of reach (docs/adr/2026-11-06-0135-a-board-out-of-reach-keeps-its-copy-or-shows-a-landing.md): it reads as before,
|
|
470
470
|
undimmed, and only the buttons that send, ui.js SENDS, are held, the labelled ones saying why; app-header.js stops their taps. */
|
|
471
|
-
.offline :is(form button[type="submit"], .actions button, button[data-confirm], button[data-deck-pick], button[data-review-answer], button[data-prompt-send], button[data-prompt-choice], button[data-quick], button[data-card-bump], button[data-bump], button[data-withdraw], button[data-grill], button[data-proto], button[data-visual], button[data-plan], button[data-main-pull], button[data-arch-run], button[data-try], button[data-reopen]) { opacity: .55; cursor: not-allowed; }
|
|
471
|
+
.offline :is(form button[type="submit"], .actions button, button[data-confirm], button[data-deck-pick], button[data-review-answer], button[data-prompt-send], button[data-prompt-choice], button[data-quick], button[data-card-bump], button[data-bump], button[data-withdraw], button[data-grill], button[data-proto], button[data-visual], button[data-plan], button[data-main-pull], button[data-arch-run], button[data-try], button[data-try-kill], button[data-reopen]) { opacity: .55; cursor: not-allowed; }
|
|
472
472
|
/* With no copy to show, the landing takes the board's place: the kickflip mark and two lines, as hub/front-door.mjs's page has them. */
|
|
473
473
|
.down-landing { min-height: 60vh; display: grid; place-content: center; justify-items: center; gap: 10px; padding: 32px 24px; text-align: center; }
|
|
474
474
|
.down-landing-mark { display: block; margin-bottom: 8px; width: 70px; height: 60px; object-fit: contain; }
|
|
@@ -866,7 +866,7 @@ button[aria-busy="true"]:is([data-card-bump]) svg { fill: currentColor; }
|
|
|
866
866
|
.tb-main-text { flex: 1 1 0; min-width: 0; overflow-wrap: anywhere; }
|
|
867
867
|
.tb-main .meta { justify-content: flex-end; margin-left: auto; }
|
|
868
868
|
/* #60: Main's refresh icon right of its timestamp; a failed pull's reason shows on hover, or on a tap on touch */
|
|
869
|
-
.tb-main .main-pull { position: relative; display: inline-flex; align-items: center; vertical-align:
|
|
869
|
+
.tb-main .main-pull { position: relative; display: inline-flex; align-items: center; vertical-align: text-bottom; margin: -7px 0 -7px 6px; }
|
|
870
870
|
.tb-main .gh-refresh { display: inline-flex; align-items: center; justify-content: center; font-size: 15px; line-height: 1; padding: 2px 4px; color: var(--muted); min-width: 28px; min-height: 28px; }
|
|
871
871
|
.tb-main .gh-refresh[aria-disabled="true"] { opacity: .45; cursor: default; }
|
|
872
872
|
.tb-main .gh-refresh.failed { color: var(--color-danger); }
|
package/board/public/ui-cards.js
CHANGED
|
@@ -125,9 +125,14 @@
|
|
|
125
125
|
return `<${tag} class="install tone-${s.tone}"${attrs}>${s.html}</${tag}>`;
|
|
126
126
|
}
|
|
127
127
|
// A card's Serve / Build, only while the board's Auto-build is off and its branch has changes
|
|
128
|
-
// (docs/adr/2026-11-02-1806-only-the-owner-or-auto-build-starts-a-card-run.md).
|
|
128
|
+
// (docs/adr/2026-11-02-1806-only-the-owner-or-auto-build-starts-a-card-run.md). A website card's serve that is running
|
|
129
|
+
// or up has Kill in its place, Auto serve on or off
|
|
130
|
+
// (docs/adr/2026-11-12-0112-a-card-serve-stays-up-while-its-worktree-does.md).
|
|
129
131
|
function tryButton(it) {
|
|
130
132
|
const t = tryOf(it);
|
|
133
|
+
if (it.kind === 'work' && t?.kind === 'web' && ['running', 'ready'].includes(t.state)) {
|
|
134
|
+
return `<button type="button" data-try-kill="${esc(it.key)}"${BoardUI.pending.has(`${it.key}:try`) ? BoardUI.BUSY : ''}>Kill</button>`;
|
|
135
|
+
}
|
|
131
136
|
if (it.kind !== 'work' || !t?.script || t.autoBuild || !t.changes) return '';
|
|
132
137
|
const words = tryWords(t.kind);
|
|
133
138
|
const reason = t.state === 'running' ? words.busy : t.state === 'ready' && !t.stale ? words.current : '';
|
package/board/public/ui.js
CHANGED
|
@@ -410,7 +410,7 @@
|
|
|
410
410
|
const copyNote = (what, savedAt) => `<p class="note copy-note">Copy of ${esc(what)} as of ${since(savedAt)}. Read only.</p>`;
|
|
411
411
|
// The buttons that send, which a board out of reach holds (style.css .offline): every one greyed and stopped, keeping its
|
|
412
412
|
// label and size. Reading controls, folds, links, Edit and drafts stay live.
|
|
413
|
-
const SENDS = `form button[type="submit"], .actions button, button[data-confirm], button[data-deck-pick], button[data-review-answer], button[data-prompt-send], button[data-prompt-choice], button[data-quick], button[data-card-bump], button[data-bump], button[data-withdraw], button[data-grill], button[data-proto], button[data-visual], button[data-plan], button[data-main-pull], button[data-arch-run], button[data-try], button[data-reopen]`;
|
|
413
|
+
const SENDS = `form button[type="submit"], .actions button, button[data-confirm], button[data-deck-pick], button[data-review-answer], button[data-prompt-send], button[data-prompt-choice], button[data-quick], button[data-card-bump], button[data-bump], button[data-withdraw], button[data-grill], button[data-proto], button[data-visual], button[data-plan], button[data-main-pull], button[data-arch-run], button[data-try], button[data-try-kill], button[data-reopen]`;
|
|
414
414
|
// While held(), a tap on one of root's sending buttons, and any form's submit there, Enter's included, goes nowhere, and onHeld
|
|
415
415
|
// tells the tap it is waiting. on is the view's B.on.
|
|
416
416
|
function holdSends(on, root, held, onHeld) {
|
|
@@ -51,6 +51,8 @@
|
|
|
51
51
|
let topics = [];
|
|
52
52
|
let topicId = null;
|
|
53
53
|
let soloId = null;
|
|
54
|
+
// Counts the owner's own topic picks (a pill, or the shell opening a topic), which a Send in flight compares against.
|
|
55
|
+
let picks = 0;
|
|
54
56
|
let shown = '';
|
|
55
57
|
let sentLine = '';
|
|
56
58
|
// The shown topic's questions this page has drawn unsent, which stay in place once sent or resolved until you leave
|
|
@@ -157,15 +159,17 @@
|
|
|
157
159
|
// The topic a tap on this page acts for, the pills before it and its name, which moveOn reads once it is recorded.
|
|
158
160
|
function leaving() {
|
|
159
161
|
const from = soloId ? topicOf(soloId)?.id : topicId;
|
|
160
|
-
return { from, pills: topicRow(topics, from).pills, name: toastName(topics.find((t) => t.id === from) || { title: '' }) };
|
|
162
|
+
return { from, picks, pills: topicRow(topics, from).pills, name: toastName(topics.find((t) => t.id === from) || { title: '' }) };
|
|
161
163
|
}
|
|
162
164
|
// After a recorded tap: a load that started before it went out would read its question as still open.
|
|
163
165
|
// `footer` is a line or, for a Send, a function of the reloaded board giving the whole line.
|
|
164
166
|
// The tap's POST answers with the board as it left it, which lands here in place of a reload.
|
|
165
|
-
async function moveOnOnceLoaded({ from, pills }, what, out, footer = what) {
|
|
167
|
+
async function moveOnOnceLoaded({ from, pills, picks: at }, what, out, footer = what) {
|
|
166
168
|
await loading;
|
|
167
169
|
if (!B.alive()) return;
|
|
168
170
|
if (!land(out.board, false)) await load(false);
|
|
171
|
+
// Tapped to another topic meanwhile: the page stays there and only the toast says what was recorded.
|
|
172
|
+
if (picks !== at) { B.toast(what); return; }
|
|
169
173
|
moveOn(from, pills, what, typeof footer === 'function' ? footer() : footer, typeof footer !== 'function');
|
|
170
174
|
}
|
|
171
175
|
|
|
@@ -386,7 +390,7 @@
|
|
|
386
390
|
const b = ev.target.closest && ev.target.closest('[data-topic]');
|
|
387
391
|
if (b) {
|
|
388
392
|
// Embedded in the desktop pane, one click also focuses the topic's card on the board; a phone only picks the topic.
|
|
389
|
-
soloId = null; setTopic(b.dataset.topic); sentLine = ''; shown = '';
|
|
393
|
+
picks++; soloId = null; setTopic(b.dataset.topic); sentLine = ''; shown = '';
|
|
390
394
|
const first = topics.find((t) => t.id === topicId)?.questions[0];
|
|
391
395
|
if (first) setHash(first);
|
|
392
396
|
ctx.onTopic?.(topicId);
|
|
@@ -468,7 +472,7 @@
|
|
|
468
472
|
if (ctx.embedded) B.openTopic = (id) => {
|
|
469
473
|
if (!loaded) return false;
|
|
470
474
|
const topic = topics.find((t) => t.id === id);
|
|
471
|
-
if (topic) { soloId = null; setTopic(topic.id); } else pick(id || '');
|
|
475
|
+
if (topic) { picks++; soloId = null; setTopic(topic.id); } else pick(id || '');
|
|
472
476
|
ctx.onTopic?.(topicId);
|
|
473
477
|
sentLine = '';
|
|
474
478
|
shown = '';
|
|
@@ -49,6 +49,10 @@
|
|
|
49
49
|
<span class="muted small sub">Grills and prototypes running at once. A card that lands in Todo past it waits its turn.</span></span>
|
|
50
50
|
<input type="number" class="count" id="maxPlanners" min="1" max="99" step="1" inputmode="numeric" disabled></label>
|
|
51
51
|
<p class="muted small" id="maxPlannersStatus" role="status"></p>
|
|
52
|
+
<label class="who" for="maxServes"><span><span>Maximum parallel serves</span>
|
|
53
|
+
<span class="muted small sub">Each open card worktree keeps one serve up. Installs aren’t counted. A busy host asks before it starts another.</span></span>
|
|
54
|
+
<input type="number" class="count" id="maxServes" min="1" max="99" step="1" inputmode="numeric" disabled></label>
|
|
55
|
+
<p class="muted small" id="maxServesStatus" role="status"></p>
|
|
52
56
|
<label class="who" for="autoBuild"><span><span>Auto serve</span>
|
|
53
57
|
<span class="muted small sub" id="autoBuildSub">${BoardUI.esc(BoardUI.tryWords('web').autoSub)}</span></span>
|
|
54
58
|
<input type="checkbox" class="toggle" id="autoBuild" disabled></label>
|
|
@@ -508,7 +512,7 @@
|
|
|
508
512
|
showBuildTargets();
|
|
509
513
|
$('#autoPull').checked = d.settings?.auto_pull === true;
|
|
510
514
|
$('#autoPull').disabled = false;
|
|
511
|
-
for (const [id, key] of [['maxImplementers', 'max_implementers'], ['maxPlanners', 'max_planners']]) {
|
|
515
|
+
for (const [id, key] of [['maxImplementers', 'max_implementers'], ['maxPlanners', 'max_planners'], ['maxServes', 'max_serves']]) {
|
|
512
516
|
$(`#${id}`).value = savedCaps[key] = String(d.settings?.[key]);
|
|
513
517
|
$(`#${id}`).disabled = false;
|
|
514
518
|
}
|
|
@@ -621,8 +625,9 @@
|
|
|
621
625
|
});
|
|
622
626
|
autoToggle('autoPull', 'auto_pull', (on) => (on ? 'Saved. Claude grills each card that lands in Todo and starts ready ones while an implementer slot is free.' : 'Saved. Todo cards wait for you.'));
|
|
623
627
|
|
|
624
|
-
// docs/adr/2026-11-04-0057-auto-implement-fills-in-progress-up-to-a-cap.md: Maximum parallel implementers,
|
|
625
|
-
// Maximum parallel planners (docs/adr/2026-11-04-2359-every-card-arriving-in-todo-is-grilled-by-the-board.md)
|
|
628
|
+
// docs/adr/2026-11-04-0057-auto-implement-fills-in-progress-up-to-a-cap.md: Maximum parallel implementers,
|
|
629
|
+
// Maximum parallel planners (docs/adr/2026-11-04-2359-every-card-arriving-in-todo-is-grilled-by-the-board.md) and
|
|
630
|
+
// Maximum parallel serves (docs/adr/2026-11-12-0112-a-card-serve-stays-up-while-its-worktree-does.md), save on
|
|
626
631
|
// change, waiting on the server like a toggle; a number outside 1 to 99 or a refusal puts the saved one back.
|
|
627
632
|
const capInput = (id, key, saidOf) => B.on($(`#${id}`), 'change', async () => {
|
|
628
633
|
const input = $(`#${id}`);
|
|
@@ -642,6 +647,7 @@
|
|
|
642
647
|
});
|
|
643
648
|
capInput('maxImplementers', 'max_implementers', (n) => `Saved. Up to ${n === 1 ? '1 agent builds' : `${n} agents build`} at once.`);
|
|
644
649
|
capInput('maxPlanners', 'max_planners', (n) => `Saved. Up to ${n === 1 ? '1 grill or prototype runs' : `${n} grills or prototypes run`} at once.`);
|
|
650
|
+
capInput('maxServes', 'max_serves', (n) => `Saved. Up to ${n === 1 ? '1 serve stays' : `${n} serves stay`} up at once.`);
|
|
645
651
|
autoToggle('autoReview', 'auto_review', (on) => (on ? 'Saved. When idle, Claude reviews incoming PRs that touch this board’s work.' : 'Saved. Claude leaves incoming PRs alone.'));
|
|
646
652
|
autoToggle('idleNotification', 'idle_notification', (on) => (on ? 'Saved. One push once the board has been idle for 15 minutes.' : 'Saved. No push when the board goes idle.'));
|
|
647
653
|
autoToggle('autoTriage', 'auto_triage', (on) => (on ? 'Saved. Once a night, when cards wait on your answers, Claude triages them.' : 'Saved. Questions and Todo are left as they are.'));
|
package/board/render-check.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
var
|
|
2
|
-
`)[0];let
|
|
3
|
-
`)[0]||
|
|
4
|
-
`)[0]}`):
|
|
1
|
+
var L=Object.defineProperty;var u=(n,e)=>L(n,"name",{value:e,configurable:!0});import g from"node:fs";import k from"node:fs/promises";import B from"node:http";import O from"node:os";import m from"node:path";import{spawn as P}from"node:child_process";import{setTimeout as $}from"node:timers/promises";import{fileURLToPath as D}from"node:url";import{createInterface as C}from"node:readline";import{BoardError as w,readPrototypes as F}from"./store.mjs";import{prototypePage as G}from"./prototype-page.mjs";import{BASE_KEY as N,baseWorktreeOf as U}from"./prototype-base.mjs";import{buildScriptError as W,buildScriptOf as _}from"./build-script.mjs";import{boardAt as q}from"../hub/boards.mjs";const j=m.dirname(D(import.meta.url)),b=[390,1280],E=process.env.CHROME||"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",R={".js":"text/javascript",".css":"text/css",".html":"text/html",".json":"application/json",".png":"image/png",".svg":"image/svg+xml"},K=3e3,J=300,V=12e4,A="(document.getElementById('stage')?.innerHTML || '').trim()",Y="[...document.querySelectorAll('#stage *')].map((e) => getComputedStyle(e).display !== 'none' ? null : (e.getAttribute('class') || '').trim().split(/\\s+/).filter(Boolean).map((c) => '.' + c).join('') || e.localName)",z="(document.body?.innerText || '').trim()";function Q(n,e){const o=[m.join(n,"public"),m.join(j,"public"),m.join(j,"..","starter","public"),m.join(j,"..","shared")];return B.createServer(async(p,t)=>{let r=decodeURIComponent(new URL(p.url,"http://x").pathname);const d=/^\/_hub\/(icon-(?:180|192|512)\.png)$/.exec(r);if(d){t.writeHead(200,{"content-type":R[".png"]}).end(await k.readFile(m.join(j,"..","hub",d[1])));return}if(["/_hub/kickflip.png","/_hub/hub.css","/_hub/colors.css"].includes(r)&&(r=r.slice(5)),r===`/prototype-${e.topic}.html`){t.writeHead(200,{"content-type":R[".html"]}).end(await G(n,e));return}for(const c of o){const s=m.resolve(c,`.${r}`);if(s.startsWith(c+m.sep)&&g.existsSync(s)&&g.statSync(s).isFile()){t.writeHead(200,{"content-type":R[m.extname(s)]||"application/octet-stream"}).end(await k.readFile(s));return}}t.writeHead(r==="/favicon.ico"?204:404).end()})}u(Q,"serve");async function M(n){if(!g.existsSync(E))throw new w(`the render check needs Chrome; none at ${E}, set CHROME to its path`);const e=P(E,["--headless=new","--remote-debugging-port=0",`--user-data-dir=${n}`,"--no-first-run","about:blank"],{stdio:"ignore",env:{...process.env,HOME:O.userInfo().homedir}}),o=m.join(n,"DevToolsActivePort");for(let a=0;a<100&&!g.existsSync(o);a++)await $(100);if(!g.existsSync(o))throw e.kill(),new w("the render check could not start Chrome");const p=g.readFileSync(o,"utf8").split(`
|
|
2
|
+
`)[0];let t;for(let a=0;a<100&&!t;a++)t=(await fetch(`http://127.0.0.1:${p}/json`).then(l=>l.json(),()=>[])).find(l=>l.type==="page"),t||await $(100);if(!t)throw e.kill(),new w("the render check could not start Chrome");const r=new WebSocket(t.webSocketDebuggerUrl);try{await new Promise((a,l)=>{r.onopen=a,r.onerror=l})}catch{throw e.kill(),new w("the render check could not start Chrome")}let d=0;const c=new Map,s=[];return r.onmessage=({data:a})=>{const l=JSON.parse(a);l.id?c.get(l.id)?.(l):s.forEach(f=>f(l))},{cdp:u((a,l={})=>new Promise((f,x)=>{const y=++d;c.set(y,v=>v.error?x(new Error(v.error.message)):f(v.result)),r.send(JSON.stringify({id:y,method:a,params:l}))}),"cdp"),on:u(a=>(s.push(a),()=>s.splice(s.indexOf(a),1)),"on"),close:u(async()=>{r.close();const a=new Promise(l=>e.once("exit",l));e.kill(),await a},"close")}}u(M,"launchOnce");async function X(n){try{return await M(n)}catch(e){if(!(e instanceof w)||!/could not start Chrome/.test(e.message))throw e;return await k.rm(n,{recursive:!0,force:!0}),await k.mkdir(n),M(n)}}u(X,"launch");const Z=u(n=>n.exception?.description?.split(`
|
|
3
|
+
`)[0]||n.text,"described");async function H({cdp:n,on:e},o,p,t=A){const r=[],d=e(i=>{i.method==="Runtime.exceptionThrown"?r.push(Z(i.params.exceptionDetails)):i.method==="Runtime.consoleAPICalled"&&i.params.type==="error"?r.push(`console error: ${i.params.args.map(h=>h.value??h.description).join(" ").split(`
|
|
4
|
+
`)[0]}`):i.method==="Log.entryAdded"&&i.params.entry.level==="error"&&i.params.entry.source!=="javascript"&&!/\/favicon\.ico$/.test(i.params.entry.url||"")&&r.push(`${i.params.entry.text}${i.params.entry.url?` ${i.params.entry.url}`:""}`)});await n("Emulation.setDeviceMetricsOverride",{width:p,height:900,deviceScaleFactor:1,mobile:p<600}),await n("Page.navigate",{url:o});const c=u(async()=>(await n("Runtime.evaluate",{expression:t,returnByValue:!0})).result.value,"stage");let s="";for(let i=0;i<K&&!s;i+=100)await $(100),s=await c();return await $(J),d(),s||r.push(t===A?"renders an empty stage":"renders an empty page"),[...new Set(r)]}u(H,"checkTake");function ee(n){const[e,o]=n,p=[];for(let t=0;t<Math.min(e.length,o.length);t++)e[t]&&!o[t]&&p.push(`${e[t]} is hidden at ${b[0]}px and shown at ${b[1]}px`),o[t]&&!e[t]&&p.push(`${o[t]} is hidden at ${b[1]}px and shown at ${b[0]}px`);return[...new Set(p)]}u(ee,"hiddenAtOneWidth");async function te(n){const e=q(n);if(!e)throw new w(`${n} is not a board in the hub's manifest, so it has no base prototype worktree to check`);const o=U(e.route),p=m.join(o,_(e.build_target)),t=W(p);if(t)throw new w(`the base prototype worktree can't serve the round: ${t} (${p})`);const r=P(p,[N,o],{cwd:o,detached:!0,stdio:["ignore","pipe","pipe"],env:{...process.env,KICKFLIP_BOARD:e.route,HOST:"127.0.0.1"}}),d=[],c=u(f=>{d.push(f),d.length>5&&d.shift()},"keep");C({input:r.stderr}).on("line",c);const s=new Promise(f=>r.once("exit",f)),i=u(async()=>{try{process.kill(-r.pid,"SIGTERM")}catch{return}if(!await Promise.race([s.then(()=>!0),$(4e3).then(()=>!1)])){try{process.kill(-r.pid,"SIGKILL")}catch{}await s}},"stop"),h=await new Promise(f=>{const x=C({input:r.stdout}),y=u(S=>{clearTimeout(v),x.close(),f(S)},"done"),v=setTimeout(()=>y(null),V);x.on("line",S=>{const T=/^port (\d{1,5})$/.exec(S);T?y(Number(T[1])):c(S)}),s.then(()=>y(null))}),a=u(()=>fetch(`http://127.0.0.1:${h}/`,{signal:AbortSignal.timeout(1e3)}).then(()=>!0,()=>!1),"answers");let l=!!h;for(let f=0;l&&!await a();f+=250)f>=6e4?l=!1:await $(250);if(!l)throw await i(),new w(`the base prototype worktree's ${_(e.build_target)} didn't serve on a port, so the round's question is not published:
|
|
5
5
|
${d.join(`
|
|
6
|
-
`)}`);return{origin:`http://127.0.0.1:${h}`,dir:
|
|
6
|
+
`)}`);return{origin:`http://127.0.0.1:${h}`,dir:o,stop:i}}u(te,"serveBase");async function I(n){const e=await k.mkdtemp(m.join(O.tmpdir(),"render-check-"));let o;try{return o=await X(e),await o.cdp("Runtime.enable"),await o.cdp("Log.enable"),await n(o)}finally{await o?.close(),await k.rm(e,{recursive:!0,force:!0,maxRetries:30,retryDelay:200})}}u(I,"withChrome");async function ne(n,e){const o=await te(n);try{const p=e.takes[0].key,t=(e.states||[]).map(c=>c.key),r=[...e.takes.map(c=>[c.key,t[0]??null]),...t.slice(1).map(c=>[p,c])],d=await I(async c=>{const s=[];for(const[i,h]of r){const a=new URL(e.served,o.origin);a.searchParams.set("variant",i),h&&a.searchParams.set("state",h);for(const l of b)for(const f of await H(c,a.href,l,z))s.push(`take ${i}${h?`, state ${h}`:""} at ${l}px: ${f}`)}return s});if(d.length)throw new w(`prototype ${e.topic} does not render, so its question is not published; fix its page in ${o.dir} and ask again:
|
|
7
7
|
${d.join(`
|
|
8
|
-
`)}`)}finally{await
|
|
9
|
-
${
|
|
10
|
-
`)}`)}finally{
|
|
8
|
+
`)}`)}finally{await o.stop()}}u(ne,"checkServedRender");async function ge(n,e){const o=(await F(n)).find(t=>t.topic===e);if(o.served)return ne(n,o);const p=Q(n,o);await new Promise(t=>p.listen(0,"127.0.0.1",t));try{const t=`http://127.0.0.1:${p.address().port}/prototype-${e}.html`,r=await I(async d=>{const c=[];for(const s of o.takes){const i=[];for(const h of b){for(const a of await H(d,`${t}?variant=${s.key}`,h))c.push(`take ${s.key} at ${h}px: ${a}`);i.push((await d.cdp("Runtime.evaluate",{expression:Y,returnByValue:!0})).result.value||[])}for(const h of ee(i))c.push(`take ${s.key}: ${h}`)}return c});if(r.length)throw new w(`prototype ${e} does not render, so its question is not published; fix the take files and ask again:
|
|
9
|
+
${r.join(`
|
|
10
|
+
`)}`)}finally{p.close()}}u(ge,"checkTakesRender");export{b as WIDTHS,ge as checkTakesRender};
|
package/board/server.mjs
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
var ka=Object.defineProperty;var
|
|
3
|
-
`).filter(Boolean).map(t=>JSON.parse(t))}catch{return[]}},"readLines"),He=
|
|
4
|
-
`).join("")),N++,Zt=qt(C),a.map(n=>n.id)}c(ve,"appendEvents");async function Wt(e){const t=await Li(o,e),a=new Map(((await A(h.join(j,"owner.json"),{items:[]})).items||[]).map(l=>[l.id,l]));if(!t.length)return[];const n=c(l=>a.has(String(l.id))&&ln(a.get(String(l.id)),l.choice),"again"),r=await ve(t.map(l=>["owner-answer",n(l)?{...l,prototype_again:!0}:l]));await Fi(o,t);const{prototype:s}=await Ci(o,t);N++;for(const l of s)await te(l,"prototype");return r}c(Wt,"recordAnswers");async function _r(e){if(!Array.isArray(e)||!e.length)return"Send at least one answer.";const t=new Set(((await A(h.join(j,"owner.json"),{items:[]})).items||[]).map(n=>n.id)),a=e.filter(n=>!t.has(String(n?.id)));return a.length?`${a.map(n=>String(n?.id)).join(", ")} ${a.length===1?"is":"are"} no longer asked here. Reload to see what is, then send again.`:null}c(_r,"answersRefused");async function xr(e){const t=(await _()).owner.find(a=>a.id===e);return t?["open","requested"].includes(t.state)?`${e} has no answer sent, so there is nothing to reopen.`:null:`${e} is no longer asked here. Reload to see what is.`}c(xr,"reopenRefused");async function Dr(e){const t=(await _()).owner.find(a=>a.id===e);return t?t.state!=="resolved"?`${e} isn\u2019t resolved, so there is nothing to ask again.`:null:`${e} is no longer asked here. Reload to see what is.`}c(Dr,"reaskRefused");async function se(e){const a=(await Promise.all((await Bn(re,e.id)).map(async({name:n,ext:r})=>{const s=h.join(re,n);try{const[l,u]=await Promise.all([b.readFile(s,"utf8"),b.stat(s)]);return[{[r]:r==="json"?JSON.parse(l):l,at:u.mtime.toISOString()}]}catch{return[]}}))).flat();return{...e,reply:a.at(-1)||null,replies:a}}c(se,"withReply");async function Br(e,t){const a=await Se(o);let n=await Fe();return e&&(n=n.filter(r=>r.kind===e)),Promise.all(n.reverse().slice(0,t).map(async r=>{const s=await se(r);return s.reply||!F(a,r.id)?s:{...s,working:F(a,r.id)}}))}c(Br,"listEvents");const Hr=c((e,t)=>e?{title:e.title,url:e.url,state:e.state,isDraft:e.isDraft,reviewDecision:e.reviewDecision,mergeStateStatus:e.mergeStateStatus,mergeable:e.mergeable,checks:e.checks,mergedAt:e.mergedAt,headRefName:e.headRefName,headRefOid:e.headRefOid,updatedAt:e.updatedAt,reviews:e.reviews,codexOpen:e.codexOpen,limited:t}:null,"prViewOf"),L=c(()=>si(o),"config");async function Fr(){const e=await b.readdir(re).catch(()=>[]),t=await Promise.all(e.map(n=>b.stat(h.join(re,n)).then(r=>r.mtimeMs,()=>0))),a=Math.max(0,...t);return a?new Date(a).toISOString():null}c(Fr,"latestReply");async function Lt(e){let t=[];try{t=(await b.readdir(e)).filter(r=>r.endsWith(".json"))}catch{}const a=await Promise.all(t.map(r=>A(h.join(e,r),null))),n=Object.create(null);return t.forEach((r,s)=>{if(!a[s])return;const[l,u="main"]=h.basename(r,".json").split("@");(n[l]||=Object.create(null))[u]=a[s]}),n}c(Lt,"readAgents");async function Ce(){const e=X(o);return{cfg:await L(),github:e?.github,local:e?.local,hub:e?.hub,remote:e?.remote,branch:e?.branch,topicItems:ba(),hostBusy:K({dir:o}).machine.state==="busy"}}c(Ce,"outsideData");async function Ut(){return(await Promise.all((await be(o)).map(async e=>{const t=await A(e.file,null);return!t||!Array.isArray(t.items)?null:{slug:e.slug,itemFile:t,agents:await Lt(h.join(e.dir,"agents"))}}))).filter(Boolean)}c(Ut,"liveFiles");async function Cr(e){const t=await Ut();for(const a of e){const n=t.find(r=>r.slug===a.file);n?n.itemFile.items.push(a.item):t.push({slug:a.file,itemFile:{items:[a.item]},agents:{}})}return t}c(Cr,"withArchived");async function Mt({cfg:e,github:t,local:a,hub:n,remote:r,branch:s},l){const u=e.items==="waves";l??=await Ut();const S=Object.create(null);return await Promise.all(l.flatMap(({slug:p,itemFile:y})=>y.items.map(async E=>{S[`${p}/${E.id}`]=await U(He(`${p}/${E.id}`))}))),{github:t,remote:r,branch:s,reviewers:ei({github:t,local:a,hub:n},H(e,{hub:n}))?e.reviewers||[]:[],fromWaves:u,autoMerge:H(e).auto_merge,skipDraft:H(e).skip_draft,autoBuild:H(e).auto_build,tickets:H(e).tickets,files:l,threads:S,working:await Se(o),autoReview:H(e).auto_review,incoming:await A(h.join(j,"incoming.json"),null),reviewRecords:await A(h.join(j,"reviews.json"),{})}}c(Mt,"itemSnapshot");async function Jt(e,t=[]){const a=await Mt(e,t.length?await Cr(t):void 0),{files:n,threads:r,working:s}=a,l=Object.create(null),u=new Set(t.map(k=>k.key)),S=(await A(Xi,{})).prs||{},p=er({budget:"graphql"});for(const{slug:k,itemFile:P}of n){const m=a.github||P.repo;for(const v of P.items)for(const I of kn(v,m))l[wt(I)]=u.has(`${k}/${v.id}`)?null:Hr(F(S,wt(I)),p)}const y=new Set(["ask","owner-answer","grill-more","visual-request","item-message","wave-triage","overnight-triage"]),E=await Promise.all((await Fe()).map(k=>y.has(k.kind)||F(s,k.id)?se(k):{...k,reply:null}));await Promise.all(E.filter(k=>k.kind==="ask").map(async k=>{const P=await U(He(`ask:${k.id}`));P.length&&(r[`ask:${k.id}`]=P)}));const x=Object.create(null),g=Object.create(null);for(const{agents:k}of n)for(const P of Object.values(k||{}).flatMap(m=>Object.values(m))){const m=P.worktree?.path,v=m&&!P.done?Aa(m):null;v!==null&&(x[m]=v),m&&!P.done&&Pa(m)&&(g[m]=!0)}const i=Object.create(null),d=await A(h.join(j,"headless-builders.json"),{}),w=await A(h.join(j,"landers.json"),{});for(const[k,P]of[...Object.entries(w),...Object.entries(d)])P?.pid===null&&F(d,k)===P&&(i[k]="held"),Number.isInteger(P?.pid)&&(bt(P.pid)?i[k]=!0:P.handedBack?i[k]=!1:delete i[k]);return{...a,moved:x,commandRunning:g,alive:i,try:await A(h.join(j,"try.json"),null),main:await A(h.join(j,"main.json"),null),prs:l,events:E,askLinks:await xe(o),checked:await A(h.join(j,"checked.json"),null),watcher:await A(h.join(j,"watcher.json"),null),replyAt:await Fr(),owner:(k=>({...k,items:[...k.items||[],...t.flatMap(P=>P.questions||[])]}))(await A(h.join(j,"owner.json"),{items:[]})),topicItems:e.topicItems,prSlack:await A(h.join(j,"pr-slack.json"),{}),hostBusy:e.hostBusy,screening:await Gn(o)}}c(Jt,"snapshot");async function Wr(e){if(!e.length)return[];const t=new Map($e(await Jt(await Ce(),e)).items.map(a=>[a.key,a]));return e.map(a=>{const n=t.get(a.key);return ft({...n,sortAt:a.closed_at,worktree:n.worktree&&!mt(n.worktree)?null:n.worktree})})}c(Wr,"archivedCards");const Z=z.slice(1)||h.basename(o);let N=0,oe=null;async function _(){const e=await Ce(),t=JSON.stringify([N,e]);if(oe?.key!==t){const a=Jt(e).then($e);oe={key:t,built:a},a.catch(()=>{oe?.built===a&&(oe=null)})}return oe.built}c(_,"sharedBoard");let We=!1;async function Gt(){if(!We){We=!0;try{const e=await U(C),t=new Set(e.filter(a=>a.kind==="prototype-overdue").map(a=>a.data?.request));for(const a of an(await _(),e))t.has(a.request)||Date.now()-Date.parse(a.since)<tn||await M("prototype-overdue",a)}catch(e){console.error("Prototype check failed:",e.message)}finally{We=!1}}}c(Gt,"checkPrototypeWaits");async function Lr(e){const t=await Ii(o);if(!t?.ping?.id||t.done_at)return null;const a=e?.find(r=>r.link==="github"),n={events:t.ack?.id===t.ping.id,...a||!e?{github:!!a?.ok}:{}};return n.events&&n.github!==!1?(await Ti(o),null):n}c(Lr,"setupOf");const Kt=ir.map(([e,t])=>({value:e,label:t,ms:e===Tt?null:Et(new Date(0),e)-new Date(0)})),Ur=Number(process.env.KICKFLIP_PEOPLE_WAIT_MS)||12e3,zt=c((e,t)=>Promise.race([e,new Promise((a,n)=>setTimeout(()=>n(new Error(`${t} did not answer in time`)),Ur).unref())]),"withinWait");async function Mr(){const e={slack:null,github:[]},t=Rt(),a=X(o)?.github,[n,r]=await Promise.allSettled([!t||Nt().refused?Promise.resolve(null):zt(t.people(),"Slack"),a?zt(vr(a),"GitHub"):Promise.resolve([])]);if(!t||Nt().refused)e.slackProblem=It;else if(n.status==="fulfilled")e.slack=n.value;else{const s=n.reason;e.slackProblem=s.message==="Slack: missing_scope"?"Slack needs the users:read scope; update the app on Hub \u203A Integrations \u203A Slack.":s.message}return r.status==="fulfilled"?e.github=r.value:e.githubProblem=`GitHub: ${r.reason.message}`,e}c(Mr,"reviewerPeople");async function Jr(e){const t=Rt();if(!t)throw new $(It);return Promise.all(e.map(async a=>{const n=await Or(a.github.trim());if(!n)throw new $(`GitHub has no user ${a.github.trim()}.`);return{name:a.name.trim(),slack_user:a.slack_user.trim(),dm:await t.openDM(a.slack_user.trim()),github:n}}))}c(Jr,"withDMs");async function ce(){const e=new Date().toISOString(),{title:t,reviewers:a}=await L(),n=X(o),r=n?Zi(n):"deaf",s=Ya(await _(),{listen:r,prompts:n?tr(n.route).map(ar):[]});s.items=s.items.map(u=>u.worktree&&!mt(u.worktree)?{...u,worktree:null}:u);const l=n?qi(n,{listen:r}):null;return{generatedAt:e,title:t,reviewers:(a||[]).map(u=>u?.name).filter(u=>typeof u=="string"),reviewerGithub:Object.fromEntries((a||[]).filter(u=>typeof u?.name=="string").map(u=>[u.name,u.github])),...s,links:l,setup:await Lr(l),holdChoices:Kt,arch:n?_a(await xa(o),s.items,s.owner,{repo:Da(Be(n.route)),waits:wr()}):null}}c(ce,"board");async function Gr(e){["GET","HEAD"].includes(e.method)||N++;const t={...await ce(),doneArchived:await ii(o)};t.owner=t.owner.filter(r=>r.state!=="resolved");const a=await _i(o);t.items=t.items.map(r=>({...r.lane==="done"?ft(r):r,readAt:a[r.key]||null}));const n=new Set(t.items.map(r=>r.key));return t.ownerTopics=t.ownerTopics.map(({title:r,summary:s,askedAt:l,...u})=>n.has(u.card)?u:{...u,title:r,summary:s,...l?{askedAt:l}:{}}),t}c(Gr,"buildPageBoard");const Le=h.join(j,"sync.json");let q=await $a(Le),Ue=q.n,Me=Promise.resolve(),Oe=null;function Kr(){return Me=Me.then(async()=>{const e=q;e.n!==Ue&&(await b.mkdir(j,{recursive:!0}),await Ia(Le,e),Ue=e.n)}),Me}c(Kr,"saveSync");async function zr(e){const t=await Gr(e);for((!Oe||t.generatedAt>=Oe.generatedAt)&&({sync:q,board:Oe}=Ra(q,t));Ue<q.n;)await Kr();return{sync:q,board:Oe}}c(zr,"pageBoard");async function G(e,t){if(new URL(e.url,"http://site").searchParams.get("board")==="0")return;const a=await Ea(e),{sync:n,board:r}=await zr(e);return e.boardRead=!0,{...(/^\d+$/.test(t)?Na(n,r,Number(t)):null)||r,onHost:a}}c(G,"answerBoard");let Vt=Promise.resolve();function Vr(){const e=Vt.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 ce();if(a.last?.open)return{key:a.last.key,ref:a.last.ref,existing:!0};const{key:n,data:r}=await Ba(o,t),s=await M("arch-review",r);return{key:n,id:s,existing:!1}});return Vt=e.catch(()=>{}),e}c(Vr,"runReview");async function Qr(e){const[t,a]=e.split("/"),n=(await be(o)).find(u=>u.slug===t),r=n?await A(n.file,null):null,s=!r?.items?.some(u=>u.id===a)&&(await jt(o)).find(u=>u.key===e),l=Array.isArray(r?.items)&&r.items.find(u=>u.id===a)||s?.item||null;return{item:l,questions:s?.questions||[],agents:l&&n?F(await Lt(h.join(n.dir,"agents")),a)||{}:{}}}c(Qr,"itemOf");async function Yr(e){const t=F(await xe(o),e),{tickets:a}=H(await L()),n=[];for(const r of await be(o)){const s=await A(r.file,null);for(const l of Array.isArray(s?.items)?s.items:[]){const u=`${r.slug}/${l.id}`;l.from_ask===e?n.unshift({key:u,ref:Re(s,l,a),became:!0}):u===t&&n.push({key:u,ref:Re(s,l,a),became:!1})}}return n}c(Yr,"askLinksOf");async function Xr(e){const{item:t,agents:a,questions:n}=e.startsWith("ask:")?{item:null,agents:{},questions:[]}:await Qr(e),r=await A(h.join(j,"owner.json"),{items:[]}),s={...r,items:[...r.items||[],...n]},l=new Set(s.items.filter(i=>i.topic===`item:${e}`).map(i=>i.id)),u=e.startsWith("ask:"),S=u?e.slice(4):t?.from_ask||null,p=u?[]:Object.entries(await xe(o)).filter(([,i])=>i===e).map(([i])=>i),y=[S,...p].filter(Boolean),E=new Set([e,...y.map(i=>`ask:${i}`)]),x=(await Fe()).filter(i=>E.has(i.data?.key)&&sn.includes(i.kind)||i.kind==="owner-answer"&&l.has(i.data?.id)||i.kind===Ie&&l.has(i.data?.question)||i.kind==="ask"&&y.includes(i.id)||i.kind==="ask-edited"),g=(await Promise.all([...E].map(i=>U(He(i))))).flat();return pn(e,{lines:g,events:await Promise.all(x.map(se)),owner:s,working:await Se(o),fromAsk:S,links:u?await Yr(S):[],agents:a,moved:p})}c(Xr,"thread");async function Qt(e){for(const t of Ar){let a=h.resolve(t,"."+e);if(a!==t&&!a.startsWith(t+h.sep))return null;if((await b.stat(a).catch(()=>null))?.isDirectory()&&(a=h.join(a,"index.html")),await b.stat(a).then(r=>r.isFile(),()=>!1))return a}return null}c(Qt,"staticFile");async function Zr(e){const t=/^\/prototype-([a-z0-9-]+)\.html$/.exec(e)?.[1];return t&&(await Pi(o)).find(a=>a.topic===t)||null}c(Zr,"recordedPrototype");async function qr(e,t){try{const a=decodeURIComponent(String(e.headers["x-filename"]||""));return f(t,200,await Gi(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(),f(t,a.status||400,{error:a.message})}}c(qr,"upload");const es=/^(text\/html|image\/svg\+xml|application\/(xhtml\+)?xml|text\/xml)$/;async function ts(e,t,a){const n=await Di(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",...es.test(n.type)?{"content-security-policy":"sandbox"}:{}}),e.method==="HEAD"){t.end();return}vn(n.path).pipe(t)}c(ts,"sendUpload");const Je=new Set;function as(e,t){t.writeHead(200,{"content-type":"text/event-stream; charset=utf-8","cache-control":"no-store",connection:"keep-alive"}),t.write(`retry: 3000
|
|
2
|
+
var ka=Object.defineProperty;var l=(e,t)=>ka(e,"name",{value:t,configurable:!0});async function ct(e,t){let a;try{a=await b.open(p.join(e,"data","try",`${t}.log`),"r");const{size:n}=await a.stat(),r=Math.max(0,n-64*1024),s=Buffer.alloc(n-r),{bytesRead:u}=await a.read(s,0,s.length,r),c=s.subarray(0,u).toString("utf8").split(/\r?\n/);return r&&c.shift(),c.at(-1)===""&&c.pop(),c.slice(-40)}catch(n){if(n.code==="ENOENT")return[];throw n}finally{await a?.close()}}l(ct,"tryLogTail");import{topicItems as ba}from"./review.mjs";import{boardWorktree as Ee,builderRepo as Sa,helperWorktree as va,moveQueuedLand as Oa,prWorktree as ja}from"./worktree.mjs";import{transcriptMovedAt as Aa,transcriptCommandRunning as Pa,readRunTranscript as Ta}from"./transcript.mjs";import{fromHost as Ea}from"./on-host.mjs";import{readSync as $a,writeSync as Ia,stamp as Ra,patchSince as Na}from"./sync.mjs";import{archOf as _a,readReviews as xa,repoName as Da,startReview as Ba}from"./arch-review.mjs";import Ha from"node:http";import b from"node:fs/promises";import ae from"node:os";import p from"node:path";import{spawn as Fa}from"node:child_process";import{fileURLToPath as Ca}from"node:url";import lt from"node:vm";import Wa from"node:zlib";import La from"node:crypto";import{promisify as Ma}from"node:util";import{PAUSE_CLEARS as Ua,pauseWork as Ja,confirmedDrops as Ga,plans as Ka,providerOf as za,archivedOf as ut,buildBoard as $e,movesOf as Va,visualsDue as Qa,withHub as Ya,DONE_KEPT as dt,ORCHESTRATOR_PUSH_AFTER as Xa,orchestratorWaits as Za,triageWaitOf as qa,waitsStep as en,PROTOTYPE_WAIT as tn,prototypeWaits as an,idleStep as nn,idleSummary as rn,ITEM_EVENTS as sn,QUESTION_REOPEN as Ie,slimDone as ft,needsAnswer as on,ownerActions as cn,picksNoTake as ln,pullOf as ht,arrivalsOf as un,rebasesDue as dn,refOf as Re,roundsDue as fn,strictlyIdle as hn,threadOf as pn,triageOf as wn}from"./domain.mjs";import{localDay as mn,quietAt as gn,quietMiddleOf as yn,quietOf as pt}from"../hub/schedule.mjs";import{cardPRs as kn,prKeyOf as wt}from"../shared/card-prs.mjs";import{dropQueued as bn}from"../hub/push.mjs";import{closeSync as Sn,createReadStream as vn,existsSync as mt,mkdirSync as On,openSync as jn,readdirSync as An,statSync as gt,watch as yt}from"node:fs";import{PROTOTYPE_TEMPLATE as Pn,esc as Tn,prototypePage as En,versionOf as Ne}from"./prototype-page.mjs";import{BOARD_CLI as $n,landByBuilder as kt,launchBuilder as ne,launchedAgents as In,pidAlive as bt,resumeBrief as Rn,resumeBuilder as Nn,reviveBuilders as _n,stopAgents as xn,stopRuns as Dn}from"./headless.mjs";import{replyFilesOf as Bn,dropAskCard as Hn,fileAsk as Fn,fileAsks as Cn,retitleAskCard as Wn,dropPause as Ln,markPaused as St,openRuns as Mn,requestPause as vt,resumePaused as Un,BoardError as $,publishUnscreened as Jn,readScreening as Gn,startHelper as Kn,startVisual as zn,openAgents as Ot,isLanding as Vn,implementAsIs as Qn,stageImplement as Yn,stageReply as Xn,planAgain as Zn,heldMerge as qn,opensPRs as ei,tapMerge as ti,addTopic as ai,appendStatus as B,archiveCards as ni,archiveCount as ii,archiveRank as ri,readArchive as jt,boardConfig as si,abandonLaunch as oi,cutOffBuilds as ci,abandonPlanner as ke,landable as li,relaunchLander as ui,migrateFollowups as di,migrateGates as fi,migratePhases as hi,migrateStatuses as pi,moveCard as wi,stageImplementAsks as mi,startPlanner as gi,stripDeadFields as yi,pickUp as ki,startRebase as bi,startTappedBuild as _e,sendPush as Si,recordOwnerTap as vi,takeHoldStopped as Oi,clearStall as ji,mergeCard as Ai,readPrototypes as Pi,finishHandshake as Ti,itemSources as be,readAskLinks as xe,readBoardRules as Ei,readBoardSettings as $i,readHandshake as Ii,readHidden as Ri,readWaveNotes as Ni,readMarks as _i,readNotes as xi,readWorking as Se,readUpload as Di,addReviewers as Bi,removeReviewers as Hi,markRead as At,resolveNoteAnswers as Fi,settleAnswers as Ci,splitSharedNotes as Wi,unrecorded as Li,saveBoardRules as Mi,saveBoardSettings as Ui,saveBoardShare as Ji,saveUpload as Gi,setBoardPause as Ki,setHidden as Pt,settingsOf as H,threadFile as zi,wavesDir as Vi}from"./store.mjs";import{changed as Qi,readHubRules as Yi}from"../hub/rules.mjs";import{GITHUB_STATE as Xi,boardAt as X,listenState as Zi,linksOf as qi}from"../hub/boards.mjs";import{limitedUntil as er}from"../shared/gh.mjs";import{pendingFor as tr,shown as ar}from"../hub/prompts.mjs";import{nodeBin as nr}from"../bin/install.mjs";import{routeRecord as F}from"../hub/routes.mjs";import{CHOICES as ir,FOREVER as Tt,durationEnd as Et}from"../hub/duration.mjs";import{VIEW_PAGES as rr,shellHtml as sr}from"../hub/shell.mjs";import{crossSite as or,isJSON as cr}from"../starter/cross-site.mjs";import{shareEntries as lr}from"../hub/share.mjs";import{readUsageGate as K,launchHeld as V,pauseReasonFor as De,pausedWords as ur,pausesRunning as $t,boardPausedUntil as dr}from"../shared/usage-gate.mjs";import{runLimit as fr}from"../hub/usage.mjs";import{audit as hr,boardAudit as pr}from"../shared/audit.mjs";import{reviewWaits as wr}from"../shared/skills.mjs";import{boardRepo as Be,mainRefOf as mr}from"../hub/board-repo.mjs";import{listBuildTargets as gr}from"./build-script.mjs";import{permissionsView as yr,setPermission as kr}from"./permissions.mjs";import{bumpOut as br}from"./stop.mjs";import{SlackError as Sr,SLACK_NEEDS_TOKEN as It,savedSlack as Rt,slackStatus as Nt}from"../hub/slack.mjs";import{collaborators as vr,githubUser as Or}from"../hub/pr-report.mjs";const ie=p.dirname(Ca(import.meta.url));async function _t(){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(_t,"touchIcon");async function jr(e){if(!e.includes("<!--header "))return e;const t=lt.createContext({});return lt.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(jr,"pageHeaders");const o=p.resolve(process.argv[2]||process.cwd()),Ar=[p.join(o,"public"),p.join(ie,"public"),p.join(ie,"..","starter","public"),p.join(ie,"..","shared")],j=p.join(o,"data"),T=l(e=>hr(pr(o),e),"logCall"),C=p.join(j,"events.jsonl"),re=p.join(j,"replies"),xt=Number(process.env.PORT||4600),z=(process.env.PREFIX||"").replace(/\/+$/,""),Dt={".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"},Pr=Ma(Wa.gzip),Tr=/^(text\/|application\/json|image\/svg\+xml)/;async function Bt(e,t,a,n){!["GET","HEAD"].includes(e.req?.method)&&!e.req.boardRead&&N++;const r={...a};let s=Buffer.isBuffer(n)?n:Buffer.from(n??"");if(Tr.test(r["content-type"]||"")&&(r.vary="accept-encoding",s.length>=1024&&/\bgzip\b/.test(e.req?.headers["accept-encoding"]||"")))try{s=await Pr(s),r["content-encoding"]="gzip"}catch{}r["content-length"]=s.length,e.writeHead(t,r),e.end(e.req?.method==="HEAD"?void 0:s)}l(Bt,"send");const f=l((e,t,a,n={})=>Bt(e,t,{"content-type":Dt[".json"],"cache-control":"no-store",...n},JSON.stringify(a)),"json"),Ht=/\b(src|href)="([\w.][\w./-]*\.(?:js|css))"/g;async function Er(e,t){const a=t.endsWith("/")?t:p.posix.dirname(t),n=[...new Set([...e.matchAll(Ht)].map(s=>s[2]))],r=new Map(await Promise.all(n.map(async s=>{const u=await Qt(p.posix.join(a,s)),c=u?await b.stat(u).catch(()=>null):null;return[s,c?Ne(c):null]})));return e.replace(Ht,(s,u,c)=>r.get(c)?`${u}="${c}?v=${r.get(c)}"`:s)}l(Er,"versionAssets");const Ft=l((e,t)=>String(e||"").split(",").map(a=>a.trim().replace(/^W\//,"")).includes(t.replace(/^W\//,"")),"sameTag"),A=l(async(e,t)=>{try{return JSON.parse(await b.readFile(e,"utf8"))}catch{return t}},"readJSON"),M=l(async e=>{try{return(await b.readFile(e,"utf8")).split(`
|
|
3
|
+
`).filter(Boolean).map(t=>JSON.parse(t))}catch{return[]}},"readLines"),He=l(e=>zi(o,e),"threadFile"),Fe=l(async()=>{const e=await Ri(o);return(await M(C)).filter(t=>!e.has(t.id))},"shownEvents");async function W(e,t=64*1024){if(!cr(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(W,"readBody");const $r=new Set(["owner-answer","owner-answers","prototype-request","reask-request",Ie,"grill-more","visual-request","item-message","ask","ask-edited","ask-withdrawn","card-build","card-hold","card-done","card-bump","card-unbump","plan-waves"]),Ir="This board is shared with you to look at only.",Rr="Merge and Board settings stay the owner\u2019s.";async function Nr(e,t){const a=e.headers["x-kickflip-shared"];return a?lr(await L()).find(r=>r.login===a)?.level!=="full"||!["/_claude/events","/api/upload"].includes(t)||e.method!=="POST"?{refused:Ir}:{by:{login:a,name:e.headers["tailscale-user-name"]||null}}:null}l(Nr,"sharedWriter");const U=l(async(e,t)=>(await ve([[e,t]]))[0],"appendEvent"),Ct=l(()=>"e"+Date.now().toString(36)+Math.random().toString(36).slice(2,7),"newEventId");async function ve(e){const t=new Date().toISOString(),a=e.map(([n,r,s=Ct()])=>({id:s,ts:t,kind:n,data:r}));return await b.mkdir(re,{recursive:!0}),await b.appendFile(C,a.map(n=>JSON.stringify(n)+`
|
|
4
|
+
`).join("")),N++,Zt=qt(C),a.map(n=>n.id)}l(ve,"appendEvents");async function Wt(e){const t=await Li(o,e),a=new Map(((await A(p.join(j,"owner.json"),{items:[]})).items||[]).map(c=>[c.id,c]));if(!t.length)return[];const n=l(c=>a.has(String(c.id))&&ln(a.get(String(c.id)),c.choice),"isAgain"),r=await ve(t.map(c=>["owner-answer",n(c)?{...c,prototype_again:!0}:c]));await Fi(o,t);const{prototype:s}=await Ci(o,t);N++;const u=new Map;for(const c of t.filter(n))u.set(a.get(String(c.id)).topic.slice(5),{question:String(c.id),note:c.note?String(c.note):void 0});for(const c of new Set([...s,...u.keys()]))await te(c,"prototype",u.get(c));return r}l(Wt,"recordAnswers");async function _r(e){if(!Array.isArray(e)||!e.length)return"Send at least one answer.";const t=new Set(((await A(p.join(j,"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(_r,"answersRefused");async function xr(e){const t=(await _()).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(xr,"reopenRefused");async function Dr(e){const t=(await _()).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(Dr,"reaskRefused");async function se(e){const a=(await Promise.all((await Bn(re,e.id)).map(async({name:n,ext:r})=>{const s=p.join(re,n);try{const[u,c]=await Promise.all([b.readFile(s,"utf8"),b.stat(s)]);return[{[r]:r==="json"?JSON.parse(u):u,at:c.mtime.toISOString()}]}catch{return[]}}))).flat();return{...e,reply:a.at(-1)||null,replies:a}}l(se,"withReply");async function Br(e,t){const a=await Se(o);let n=await Fe();return e&&(n=n.filter(r=>r.kind===e)),Promise.all(n.reverse().slice(0,t).map(async r=>{const s=await se(r);return s.reply||!F(a,r.id)?s:{...s,working:F(a,r.id)}}))}l(Br,"listEvents");const Hr=l((e,t)=>e?{title:e.title,url:e.url,state:e.state,isDraft:e.isDraft,reviewDecision:e.reviewDecision,mergeStateStatus:e.mergeStateStatus,mergeable:e.mergeable,checks:e.checks,mergedAt:e.mergedAt,headRefName:e.headRefName,headRefOid:e.headRefOid,updatedAt:e.updatedAt,reviews:e.reviews,codexOpen:e.codexOpen,limited:t}:null,"prViewOf"),L=l(()=>si(o),"config");async function Fr(){const e=await b.readdir(re).catch(()=>[]),t=await Promise.all(e.map(n=>b.stat(p.join(re,n)).then(r=>r.mtimeMs,()=>0))),a=Math.max(0,...t);return a?new Date(a).toISOString():null}l(Fr,"latestReply");async function Lt(e){let t=[];try{t=(await b.readdir(e)).filter(r=>r.endsWith(".json"))}catch{}const a=await Promise.all(t.map(r=>A(p.join(e,r),null))),n=Object.create(null);return t.forEach((r,s)=>{if(!a[s])return;const[u,c="main"]=p.basename(r,".json").split("@");(n[u]||=Object.create(null))[c]=a[s]}),n}l(Lt,"readAgents");async function Ce(){const e=X(o);return{cfg:await L(),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(Ce,"outsideData");async function Mt(){return(await Promise.all((await be(o)).map(async e=>{const t=await A(e.file,null);return!t||!Array.isArray(t.items)?null:{slug:e.slug,itemFile:t,agents:await Lt(p.join(e.dir,"agents"))}}))).filter(Boolean)}l(Mt,"liveFiles");async function Cr(e){const t=await Mt();for(const a of e){const n=t.find(r=>r.slug===a.file);n?n.itemFile.items.push(a.item):t.push({slug:a.file,itemFile:{items:[a.item]},agents:{}})}return t}l(Cr,"withArchived");async function Ut({cfg:e,github:t,local:a,hub:n,remote:r,branch:s},u){const c=e.items==="waves";u??=await Mt();const S=Object.create(null);return await Promise.all(u.flatMap(({slug:w,itemFile:y})=>y.items.map(async E=>{S[`${w}/${E.id}`]=await M(He(`${w}/${E.id}`))}))),{github:t,remote:r,branch:s,reviewers:ei({github:t,local:a,hub:n},H(e,{hub:n}))?e.reviewers||[]:[],fromWaves:c,autoMerge:H(e).auto_merge,skipDraft:H(e).skip_draft,autoBuild:H(e).auto_build,tickets:H(e).tickets,files:u,threads:S,working:await Se(o),autoReview:H(e).auto_review,incoming:await A(p.join(j,"incoming.json"),null),reviewRecords:await A(p.join(j,"reviews.json"),{})}}l(Ut,"itemSnapshot");async function Jt(e,t=[]){const a=await Ut(e,t.length?await Cr(t):void 0),{files:n,threads:r,working:s}=a,u=Object.create(null),c=new Set(t.map(k=>k.key)),S=(await A(Xi,{})).prs||{},w=er({budget:"graphql"});for(const{slug:k,itemFile:P}of n){const m=a.github||P.repo;for(const v of P.items)for(const I of kn(v,m))u[wt(I)]=c.has(`${k}/${v.id}`)?null:Hr(F(S,wt(I)),w)}const y=new Set(["ask","owner-answer","grill-more","visual-request","item-message","wave-triage","overnight-triage"]),E=await Promise.all((await Fe()).map(k=>y.has(k.kind)||F(s,k.id)?se(k):{...k,reply:null}));await Promise.all(E.filter(k=>k.kind==="ask").map(async k=>{const P=await M(He(`ask:${k.id}`));P.length&&(r[`ask:${k.id}`]=P)}));const x=Object.create(null),g=Object.create(null);for(const{agents:k}of n)for(const P of Object.values(k||{}).flatMap(m=>Object.values(m))){const m=P.worktree?.path,v=m&&!P.done?Aa(m):null;v!==null&&(x[m]=v),m&&!P.done&&Pa(m)&&(g[m]=!0)}const i=Object.create(null),d=await A(p.join(j,"headless-builders.json"),{}),h=await A(p.join(j,"landers.json"),{});for(const[k,P]of[...Object.entries(h),...Object.entries(d)])P?.pid===null&&F(d,k)===P&&(i[k]="held"),Number.isInteger(P?.pid)&&(bt(P.pid)?i[k]=!0:P.handedBack?i[k]=!1:delete i[k]);return{...a,moved:x,commandRunning:g,alive:i,try:await A(p.join(j,"try.json"),null),main:await A(p.join(j,"main.json"),null),prs:u,events:E,askLinks:await xe(o),checked:await A(p.join(j,"checked.json"),null),watcher:await A(p.join(j,"watcher.json"),null),replyAt:await Fr(),owner:(k=>({...k,items:[...k.items||[],...t.flatMap(P=>P.questions||[])]}))(await A(p.join(j,"owner.json"),{items:[]})),topicItems:e.topicItems,prSlack:await A(p.join(j,"pr-slack.json"),{}),hostBusy:e.hostBusy,screening:await Gn(o)}}l(Jt,"snapshot");async function Wr(e){if(!e.length)return[];const t=new Map($e(await Jt(await Ce(),e)).items.map(a=>[a.key,a]));return e.map(a=>{const n=t.get(a.key);return ft({...n,sortAt:a.closed_at,worktree:n.worktree&&!mt(n.worktree)?null:n.worktree})})}l(Wr,"archivedCards");const Z=z.slice(1)||p.basename(o);let N=0,oe=null;async function _(){const e=await Ce(),t=JSON.stringify([N,e]);if(oe?.key!==t){const a=Jt(e).then($e);oe={key:t,built:a},a.catch(()=>{oe?.built===a&&(oe=null)})}return oe.built}l(_,"sharedBoard");let We=!1;async function Gt(){if(!We){We=!0;try{const e=await M(C),t=new Set(e.filter(a=>a.kind==="prototype-overdue").map(a=>a.data?.request));for(const a of an(await _(),e))t.has(a.request)||Date.now()-Date.parse(a.since)<tn||await U("prototype-overdue",a)}catch(e){console.error("Prototype check failed:",e.message)}finally{We=!1}}}l(Gt,"checkPrototypeWaits");async function Lr(e){const t=await Ii(o);if(!t?.ping?.id||t.done_at)return null;const a=e?.find(r=>r.link==="github"),n={events:t.ack?.id===t.ping.id,...a||!e?{github:!!a?.ok}:{}};return n.events&&n.github!==!1?(await Ti(o),null):n}l(Lr,"setupOf");const Kt=ir.map(([e,t])=>({value:e,label:t,ms:e===Tt?null:Et(new Date(0),e)-new Date(0)})),Mr=Number(process.env.KICKFLIP_PEOPLE_WAIT_MS)||12e3,zt=l((e,t)=>Promise.race([e,new Promise((a,n)=>setTimeout(()=>n(new Error(`${t} did not answer in time`)),Mr).unref())]),"withinWait");async function Ur(){const e={slack:null,github:[]},t=Rt(),a=X(o)?.github,[n,r]=await Promise.allSettled([!t||Nt().refused?Promise.resolve(null):zt(t.people(),"Slack"),a?zt(vr(a),"GitHub"):Promise.resolve([])]);if(!t||Nt().refused)e.slackProblem=It;else if(n.status==="fulfilled")e.slack=n.value;else{const s=n.reason;e.slackProblem=s.message==="Slack: missing_scope"?"Slack needs the users:read scope; update the app on Hub \u203A Integrations \u203A Slack.":s.message}return r.status==="fulfilled"?e.github=r.value:e.githubProblem=`GitHub: ${r.reason.message}`,e}l(Ur,"reviewerPeople");async function Jr(e){const t=Rt();if(!t)throw new $(It);return Promise.all(e.map(async a=>{const n=await Or(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(Jr,"withDMs");async function ce(){const e=new Date().toISOString(),{title:t,reviewers:a}=await L(),n=X(o),r=n?Zi(n):"deaf",s=Ya(await _(),{listen:r,prompts:n?tr(n.route).map(ar):[]});s.items=s.items.map(c=>c.worktree&&!mt(c.worktree)?{...c,worktree:null}:c);const u=n?qi(n,{listen:r}):null;return{generatedAt:e,title:t,reviewers:(a||[]).map(c=>c?.name).filter(c=>typeof c=="string"),reviewerGithub:Object.fromEntries((a||[]).filter(c=>typeof c?.name=="string").map(c=>[c.name,c.github])),...s,links:u,setup:await Lr(u),holdChoices:Kt,arch:n?_a(await xa(o),s.items,s.owner,{repo:Da(Be(n.route)),waits:wr()}):null}}l(ce,"board");async function Gr(e){["GET","HEAD"].includes(e.method)||N++;const t={...await ce(),doneArchived:await ii(o)};t.owner=t.owner.filter(r=>r.state!=="resolved");const a=await _i(o);t.items=t.items.map(r=>({...r.lane==="done"?ft(r):r,readAt:a[r.key]||null}));const n=new Set(t.items.map(r=>r.key));return t.ownerTopics=t.ownerTopics.map(({title:r,summary:s,askedAt:u,...c})=>n.has(c.card)?c:{...c,title:r,summary:s,...u?{askedAt:u}:{}}),t}l(Gr,"buildPageBoard");const Le=p.join(j,"sync.json");let q=await $a(Le),Me=q.n,Ue=Promise.resolve(),Oe=null;function Kr(){return Ue=Ue.then(async()=>{const e=q;e.n!==Me&&(await b.mkdir(j,{recursive:!0}),await Ia(Le,e),Me=e.n)}),Ue}l(Kr,"saveSync");async function zr(e){const t=await Gr(e);for((!Oe||t.generatedAt>=Oe.generatedAt)&&({sync:q,board:Oe}=Ra(q,t));Me<q.n;)await Kr();return{sync:q,board:Oe}}l(zr,"pageBoard");async function G(e,t){if(new URL(e.url,"http://site").searchParams.get("board")==="0")return;const a=await Ea(e),{sync:n,board:r}=await zr(e);return e.boardRead=!0,{...(/^\d+$/.test(t)?Na(n,r,Number(t)):null)||r,onHost:a}}l(G,"answerBoard");let Vt=Promise.resolve();function Vr(){const e=Vt.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 ce();if(a.last?.open)return{key:a.last.key,ref:a.last.ref,existing:!0};const{key:n,data:r}=await Ba(o,t),s=await U("arch-review",r);return{key:n,id:s,existing:!1}});return Vt=e.catch(()=>{}),e}l(Vr,"runReview");async function Qr(e){const[t,a]=e.split("/"),n=(await be(o)).find(c=>c.slug===t),r=n?await A(n.file,null):null,s=!r?.items?.some(c=>c.id===a)&&(await jt(o)).find(c=>c.key===e),u=Array.isArray(r?.items)&&r.items.find(c=>c.id===a)||s?.item||null;return{item:u,questions:s?.questions||[],agents:u&&n?F(await Lt(p.join(n.dir,"agents")),a)||{}:{}}}l(Qr,"itemOf");async function Yr(e){const t=F(await xe(o),e),{tickets:a}=H(await L()),n=[];for(const r of await be(o)){const s=await A(r.file,null);for(const u of Array.isArray(s?.items)?s.items:[]){const c=`${r.slug}/${u.id}`;u.from_ask===e?n.unshift({key:c,ref:Re(s,u,a),became:!0}):c===t&&n.push({key:c,ref:Re(s,u,a),became:!1})}}return n}l(Yr,"askLinksOf");async function Xr(e){const{item:t,agents:a,questions:n}=e.startsWith("ask:")?{item:null,agents:{},questions:[]}:await Qr(e),r=await A(p.join(j,"owner.json"),{items:[]}),s={...r,items:[...r.items||[],...n]},u=new Set(s.items.filter(i=>i.topic===`item:${e}`).map(i=>i.id)),c=e.startsWith("ask:"),S=c?e.slice(4):t?.from_ask||null,w=c?[]:Object.entries(await xe(o)).filter(([,i])=>i===e).map(([i])=>i),y=[S,...w].filter(Boolean),E=new Set([e,...y.map(i=>`ask:${i}`)]),x=(await Fe()).filter(i=>E.has(i.data?.key)&&sn.includes(i.kind)||i.kind==="owner-answer"&&u.has(i.data?.id)||i.kind===Ie&&u.has(i.data?.question)||i.kind==="ask"&&y.includes(i.id)||i.kind==="ask-edited"),g=(await Promise.all([...E].map(i=>M(He(i))))).flat();return pn(e,{lines:g,events:await Promise.all(x.map(se)),owner:s,working:await Se(o),fromAsk:S,links:c?await Yr(S):[],agents:a,moved:w})}l(Xr,"thread");async function Qt(e){for(const t of Ar){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(r=>r.isFile(),()=>!1))return a}return null}l(Qt,"staticFile");async function Zr(e){const t=/^\/prototype-([a-z0-9-]+)\.html$/.exec(e)?.[1];return t&&(await Pi(o)).find(a=>a.topic===t)||null}l(Zr,"recordedPrototype");async function qr(e,t){try{const a=decodeURIComponent(String(e.headers["x-filename"]||""));return f(t,200,await Gi(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(),f(t,a.status||400,{error:a.message})}}l(qr,"upload");const es=/^(text\/html|image\/svg\+xml|application\/(xhtml\+)?xml|text\/xml)$/;async function ts(e,t,a){const n=await Di(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",...es.test(n.type)?{"content-security-policy":"sandbox"}:{}}),e.method==="HEAD"){t.end();return}vn(n.path).pipe(t)}l(ts,"sendUpload");const Je=new Set;function as(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
|
-
`),Je.add(t),e.on("close",()=>Je.delete(t))}
|
|
6
|
+
`),Je.add(t),e.on("close",()=>Je.delete(t))}l(as,"stream");const Yt=l(e=>{for(const t of Je)t.write(e)},"broadcast");setInterval(()=>Yt(`: ping
|
|
7
7
|
|
|
8
8
|
`),25e3).unref();const ns=100,is=500;let je=null,Xt=0;function Ge({read:e=!1}={}){e||N++;const t=Date.now();je||(Xt=t),clearTimeout(je),je=setTimeout(()=>{je=null,Yt(`event: change
|
|
9
9
|
data: ${JSON.stringify({at:new Date().toISOString()})}
|
|
10
10
|
|
|
11
|
-
`),ma(),pa(),wa(),ga(),tt(),nt(),ue(),rt()},Math.max(0,Math.min(ns,Xt+is-t)))}c(Ge,"changed");const rs=new Set(["node_modules","dd","DerivedData"]),Ke=c(e=>String(e).endsWith(".tmp")||String(e).split(/[\\/]/).some(t=>t.startsWith(".")||rs.has(t)),"ignored");let Zt=null;const qt=c(e=>{try{const t=gt(e);return`${t.size}:${t.mtimeMs}`}catch{return null}},"statOf");function ea(e,t){if(!t)return Ge();if(Ke(t))return;const a=h.join(e,String(t));a!==Le&&Ge({read:a===C&&qt(C)===Zt})}c(ea,"heard");const ze=c(e=>{try{return gt(e).isDirectory()}catch{return!1}},"isFolder");function ss(e){let t=c(()=>{},"stop"),a=!1,n=null;const r=c(()=>{n||(t(),a=!0,n=setTimeout(()=>{n=null,s()},2e3).unref())},"retry"),s=c(()=>{try{t=(process.platform==="linux"?cs:os)(e,r)}catch{r();return}a&&Ge()},"start");s()}c(ss,"watchTree");function os(e,t){const a=yt(e,{recursive:!0},(n,r)=>{ea(e,r),ze(e)||t()});return a.on("error",t),()=>a.close()}c(os,"watchRecursive");function cs(e,t){const a=new Map,n=c(s=>{for(const[l,u]of a)(l===s||l.startsWith(s+h.sep))&&(u.close(),a.delete(l))},"unwatch"),r=c(s=>{if(a.has(s))return;const l=yt(s,(u,S)=>{if(ea(s,S),s===e&&!ze(e))return t();if(!S||Ke(S))return;const p=h.join(s,String(S));if(!ze(p))n(p);else try{r(p)}catch{n(p)}});a.set(s,l),l.on("error",()=>s===e?t():n(s));for(const u of An(s,{withFileTypes:!0}))if(!(!u.isDirectory()||Ke(u.name)))try{r(h.join(s,u.name))}catch{n(h.join(s,u.name))}},"watchFolder");try{r(e)}catch(s){throw n(e),s}return()=>n(e)}c(cs,"watchEachFolder");async function ls(){const e=(await L()).items==="waves"?[Vi()]:(await be(o)).map(a=>a.dir),t=[...new Set([j,...e].map(a=>h.resolve(a)))];for(const a of t.filter(n=>!t.some(r=>r!==n&&n.startsWith(r+h.sep))))ss(a)}c(ls,"watchBoard");const us=Ha.createServer(async(e,t)=>{try{if(e.method!=="GET"&&e.method!=="HEAD"&&N++,e.method!=="GET"&&e.method!=="HEAD"&&or(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 r=e.method!=="GET"&&e.method!=="HEAD"?await Nr(e,n):null;if(r?.refused)return f(t,403,{error:r.refused});if(n==="/_claude/health")return f(t,200,{ok:!0});if(n==="/api/board"){const i=await G(e,String(a.searchParams.get("since")||"")),d=`W/"${i.version}"`;if(Ft(e.headers["if-none-match"],d)){t.writeHead(304,{etag:d,"cache-control":"no-store",vary:"accept-encoding"}).end();return}return f(t,200,i,{etag:d})}if(n==="/api/reviewer-people")return f(t,200,await Mr());if(n==="/api/questions"){const i=await _(),d=new Set(i.ownerTopics.find(w=>w.id===String(a.searchParams.get("topic")??""))?.questions||[]);return f(t,200,{questions:i.owner.filter(w=>d.has(w.id)&&w.state==="resolved")})}if(n==="/api/done"){const i=String(a.searchParams.get("before")||""),d=(await jt(o)).filter(w=>!i||ri(w)<i);return f(t,200,{cards:await Wr(d.slice(0,dt)),left:Math.max(0,d.length-dt)})}if(n==="/api/links")return f(t,200,{links:(await ce()).links});if(n==="/api/stream"&&e.method==="GET")return as(e,t);if(n==="/_claude/needs")return f(t,200,on(await ce()));if(n==="/api/working"){const i=await _(),d=new Map(i.items.filter(w=>w.kind==="work").map(w=>[w.key,w.ref]));return f(t,200,{working:i.workingOn.map(w=>({id:w.id,kind:w.kind,card:d.get(w.key)||null,since:w.since}))})}if(n==="/api/thread")return f(t,200,await Xr(String(a.searchParams.get("key")||"")));if(n==="/api/notes"&&a.searchParams.has("wave"))try{return f(t,200,await Ni(o,String(a.searchParams.get("wave")),H(await L()).tickets))}catch(i){if(i instanceof $)return f(t,404,{error:i.message});throw i}if(n==="/api/notes"||n==="/api/plan"){const i=String(a.searchParams.get("item")||"");try{const{itemFile:d,item:w,markdown:k}=await xi(o,i);return f(t,200,{key:i,ref:Re(d,w,H(await L()).tickets),title:w.title,markdown:k})}catch(d){if(d instanceof $)return f(t,404,{error:d.message});throw d}}if(n==="/_claude/events"&&e.method==="POST"){const i=await W(e,524288),d=String(i.kind||"message").slice(0,64);delete i.kind;const w=String(i.since||"");if(delete i.since,r&&!$r.has(d))return f(t,403,{error:Rr});for(const m of[i,...Array.isArray(i.answers)?i.answers.filter(v=>v&&typeof v=="object"):[]])delete m.shared_by,r&&(m.shared_by=r.by);if(d==="card-hold"&&i.hold&&i.for!==void 0){const m=Et(new Date,i.for);if(!m)return f(t,400,{error:"That hold ends in the past or more than 30 days on. Pick another time."});delete i.for,m!==Tt&&(i.until=m.toISOString())}if(d==="card-build"){const m=String(i.key||""),v=await _e(o,m,I=>Ee(o,m.split("/")[1],I));return f(t,200,{id:await Ae(v),board:await G(e,w)})}if(d==="item-message"){const m=String(i.key||""),v=(await _()).items.find(R=>R.key===m&&R.kind==="work"),I=!String(i.text||"").trim()&&!(Array.isArray(i.files)&&i.files.length);if(v?.stalled&&await ji(o,m),I&&(v?.ready||v?.lane==="todo"))return await Qn(o,m,r?r.by.name||r.by.login:void 0),N++,f(t,200,{id:null,board:await G(e,w)});v?.lane==="todo"&&i.intent==="build"?await Yn(o,m,i):v?.lane==="todo"&&v.phase==="ready"&&await Xn(o,m,i),N++}if(d==="owner-answer")return f(t,200,{id:(await Wt([i]))[0]??null,board:await G(e,w)});if(d==="owner-answers"){const m=await _r(i.answers);return m?f(t,400,{error:m}):f(t,200,{ids:await Wt(i.answers),board:await G(e,w)})}if(d==="card-merge"&&await qn(o,String(i.key||"")).catch(()=>null)){const m=String(i.key);try{await ti(o,m)}catch(v){if(v instanceof $)return f(t,400,{error:v.message});throw v}return vs(m),f(t,200,{id:null,board:await G(e,w)})}if(d==="card-merge"){try{await Ai(o,String(i.key||""),{tapped:!0})}catch(m){if(m instanceof $)return f(t,400,{error:m.message});throw m}return f(t,200,{id:null,board:await G(e,w)})}if(d==="card-hold"||d==="card-done"){const m=String(i.key||"");let v,I;try{I=d==="card-hold"&&!i.hold?[]:await Ot(o,m),d==="card-done"&&await Vn(o,m)&&(I=[]),v=await vi(o,d,i)}catch(J){return f(t,J instanceof $?400:500,{error:`Couldn\u2019t write ${m} to its item file: ${J.message}`})}if(!v)return f(t,400,{error:`${m} is already in Done, so its item file stays as it is.`});const R=await Ot(o,m);I=I.filter(J=>!R.some(O=>O.pid===J.pid)),I.length&&(xn(I),await B(o,m,"builder-stopped")),d==="card-hold"&&!i.hold&&await ra(m)}if(d==="reviewers"){const m=c(O=>O===void 0?[]:Array.isArray(O)?O:null,"list"),v=m(i.add),I=m(i.remove),R=c(O=>typeof O=="string"&&O.trim(),"text");if(!v||!I||!v.length&&!I.length)return f(t,400,{error:"Send the people to add and the names to remove, each as a list."});if(!I.every(R)||!v.every(O=>R(O?.name)&&R(O.slack_user)&&R(O.github)))return f(t,400,{error:"Each person to add needs a name, a Slack member and a GitHub login."});try{I.length&&await Hi(o,I.map(O=>O.trim())),v.length&&await Bi(o,await Jr(v))}catch(O){if(O instanceof $||O instanceof Sr)return f(t,400,{error:O.message});throw O}const J=await ve([...I.length?[["reviewers-removed",{remove:I}]]:[],...v.length?[["reviewers-added",{add:v.map(O=>O.name)}]]:[]]);return f(t,200,{id:J.at(-1),board:await G(e,w)})}if(d==="reask-request"){const m=await Dr(String(i.question||""));if(m)return f(t,400,{error:m})}if(d===Ie){const m=await xr(String(i.question||""));if(m)return f(t,400,{error:m})}const k=d==="ask"?Ct():void 0;k&&await Fn(o,k,{id:k,ts:new Date().toISOString(),kind:d,data:i});const P=(await ve([[d,i,k]]))[0];if(d==="ask-edited"&&await Wn(o,String(i.id||"")),d==="ask-withdrawn"&&await Hn(o,String(i.id||"")),(d==="card-bump"||d==="card-unbump")&&Oa(o,String(i.key||""),d==="card-bump"?new Date().toISOString():null),(d==="grill-more"||d==="prototype-request")&&i.key){const m={...i.text?{text:String(i.text)}:{},...Array.isArray(i.files)&&i.files.length?{files:i.files}:{},...i.question?{question:String(i.question),note:i.note?String(i.note):void 0,choice:i.choice??void 0}:{}},v=d==="grill-more"?"grill":"prototype";await Zn(o,String(i.key),v),await te(String(i.key),v,m)}return d==="visual-request"&&i.key&&await na((await _()).items,await U(C)),d==="ask-withdrawn"&&await Pt(o,String(i.id||""),!0),f(t,200,{id:P,board:await G(e,w)})}if(n==="/api/rules"&&e.method==="GET")return f(t,200,{board:await Ei(o),hub:Yi()});if(n==="/api/rules"&&e.method==="POST"){const i=await W(e);let d;try{d=await Ui(o,i.rules,i.base??[])}catch(k){if(k instanceof $)return f(t,400,{error:k.message});throw k}const w=Qi(d.changes)?await M("rules",{scope:"board",...d.changes}):null;return f(t,200,{rules:d.rules,id:w})}if(n==="/api/settings"&&e.method==="GET")return f(t,200,{settings:await $i(o),paused_until:dr(o),pause_choices:Kt});if(n==="/api/build-targets"&&e.method==="GET"){const i=X(o);if(!i)return f(t,404,{error:"This board isn\u2019t in the hub\u2019s manifest."});const d=Be(i.route);return f(t,200,{targets:await gr(d.repo,mr(d))})}if(n==="/api/pause"&&e.method==="POST"){const i=await W(e);if(!i||typeof i!="object"||Array.isArray(i)||Object.keys(i).join()!=="pause"||typeof i.pause!="string")return f(t,400,{error:"Send only {pause: a duration, a date and time, or off}."});let d;try{d=await Ki(o,i.pause)}catch(w){if(w instanceof $)return f(t,400,{error:w.message});throw w}return d&&(Ye=!0),ue(),f(t,200,{paused_until:d})}if(n==="/api/permissions"){const i=X(o);if(!i)return f(t,404,{error:"This board isn\u2019t in the hub\u2019s manifest."});const d=Be(i.route).repo,w=e.method==="POST"?await W(e):null;try{return w&&await kr(d,o,w.id,w.on),f(t,200,await yr(d,o))}catch(k){if(k instanceof $)return f(t,400,{error:k.message});throw k}}if(n==="/api/try/main/log"&&e.method==="GET")return f(t,200,{lines:await ct(o,"main")});if(n==="/api/try/prototypes"&&e.method==="GET"){const i=(await A(h.join(o,"data","try.json"),{}))?.prototypes||{};return f(t,200,{state:i.state||null,https:i.state==="ready"&&i.https||null,since:i.state==="ready"&&i.since||null,lines:i.state==="failed"?await ct(o,"prototypes"):[]})}if(n==="/api/log"&&e.method==="GET"){const i=a.searchParams.get("file")||"";if(!/^[\w.-]+\.log$/.test(i)||i.startsWith("."))return f(t,400,{error:"file takes a log file\u2019s name."});let d;try{d=await b.open(h.join(ae.homedir(),".claude","kickflip","logs",i),"r");const{size:w}=await d.stat(),k=Math.max(0,w-256*1024),P=Buffer.alloc(w-k),{bytesRead:m}=await d.read(P,0,P.length,k),v=P.subarray(0,m).toString("utf8");return f(t,200,{text:k?v.slice(v.indexOf(`
|
|
12
|
-
`)+1):v,truncated:k>0,size:w})}catch(w){if(w.code==="ENOENT")return f(t,404,{error:"No such log."});throw w}finally{await d?.close()}}if(n==="/api/transcript"&&e.method==="GET"){const i=a.searchParams.get("file")||"";if(!/^[\w.-]+\.log$/.test(i)||i.startsWith("."))return f(t,400,{error:"file takes a log file\u2019s name."});if(!/^\d+$/.test(a.searchParams.get("from")||""))return f(t,400,{error:"from takes a byte offset."});try{return f(t,200,await Ta(h.join(ae.homedir(),".claude","kickflip","logs",i),Number(a.searchParams.get("from"))))}catch(d){if(d.code==="ENOENT")return f(t,404,{error:"No such log."});throw d}}if(n==="/api/try/main/pull"&&e.method==="POST"){const i=await W(e);if(!i||typeof i!="object"||Array.isArray(i)||Object.keys(i).length)return f(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)||h.basename(o)}),signal:AbortSignal.timeout(3e4)});return f(t,d.status,await d.json())}catch(d){return f(t,502,{error:`Could not pull: ${d.message}`})}}if(n==="/api/try"&&e.method==="POST"){const i=await W(e);if(!i||typeof i!="object"||Array.isArray(i)||Object.keys(i).some(d=>d!=="key")||typeof i.key!="string")return f(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)||h.basename(o),key:i.key}),signal:AbortSignal.timeout(1e4)});return f(t,d.status,await d.json())}catch(d){return f(t,502,{error:`Could not start the build: ${d.message}`})}}if(n==="/api/share"&&e.method==="POST")try{return f(t,200,await Ji(o,await W(e)))}catch(i){if(i instanceof $)return f(t,400,{error:i.message});throw i}if(n==="/api/settings"&&e.method==="POST"){const i=await W(e);let d;try{d=await Mi(o,i)}catch(P){if(P instanceof $)return f(t,400,{error:P.message});throw P}const w=d.changed.filter(P=>P!=="idle_notification"),k=w.length?await M("settings",Object.fromEntries(w.map(P=>[P,d.settings[P]??null]))):null;return d.changed.includes("auto_merge")&&d.settings.auto_merge&&await M("check-github",{reason:"auto-merge"}),f(t,200,{settings:d.settings,id:k})}if(n==="/api/arch-review"&&e.method==="POST"){await W(e);try{return f(t,200,await Vr())}catch(i){if(i instanceof $)return f(t,i.status||400,{error:i.message});throw i}}if(n==="/api/hide"&&e.method==="POST"){const i=await W(e);try{await Pt(o,String(i.id||""),i.hidden!==!1)}catch(d){if(d instanceof $)return f(t,400,{error:d.message});throw d}return f(t,200,{ok:!0})}if(n==="/api/read"&&e.method==="POST"){const{marks:i}=await W(e);return i&&typeof i=="object"&&!Array.isArray(i)&&Object.values(i).every(w=>typeof w=="string"&&!Number.isNaN(Date.parse(w)))?(await At(o,(await _()).items.map(w=>w.key),i),f(t,200,{ok:!0})):f(t,400,{error:"Send {marks: {card key: ISO time}}."})}if(n==="/_claude/events"&&e.method==="GET")return f(t,200,await Br(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 ts(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=rr.has(n.slice(1)),l=s?null:await Zr(n),u=s?null:l?Pn:n==="/prototype.html"?null:await Qt(decodeURIComponent(n));if(!s&&!u){t.writeHead(404).end();return}const S=s?".html":h.extname(u).toLowerCase(),p=s?null:await b.stat(u);let y=s?Buffer.from(sr({touchIcon:await _t()})):l?Buffer.from(await En(o,l)):await b.readFile(u);!s&&S===".html"&&(y=(await jr(y.toString("utf8"))).replaceAll("{{title}}",Tn((await L()).title)).replaceAll("{{touchIcon}}",await _t())),!s&&S===".html"&&(y=Buffer.from(await Er(y,n)));const E=S===".html"?`W/"${La.createHash("sha1").update(y).digest("base64url").slice(0,20)}"`:`W/"${Ne(p)}"`,x=S!==".html"&&a.searchParams.get("v")===Ne(p),g={"content-type":F(Dt,S)||"application/octet-stream","cache-control":x?"public, max-age=31536000, immutable":"no-cache",etag:E};if(Ft(e.headers["if-none-match"],E)){t.writeHead(304,{etag:E,"cache-control":g["cache-control"],vary:"accept-encoding"}).end();return}return await Bt(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 $?f(t,a.status||400,{error:a.message}):t.writeHead(a.status||(a.code==="ENOENT"?404:400)).end())}});let Ve=!1;async function ta(){if(!Ve){Ve=!0;try{const e=Date.now(),t=(await _()).items.filter(n=>n.heldUntil&&n.lane!=="done"&&Date.parse(n.heldUntil)<=e),a=t.length?await U(C):[];for(const{key:n,heldUntil:r}of t)await ki(o,n,r,{announce:!a.some(s=>s.kind==="card-unblocked"&&s.data?.key===n&&s.data?.until===r)}),await ra(n),T({call:"pick-up",key:n,result:"resumed",reason:"Its hold ended.",inputs:{heldUntil:r}})}catch(e){console.error("Pick-up check failed:",e.message)}finally{Ve=!1}}}c(ta,"checkPickUps");let le=null,Qe=!1,D=!1,Ye=!1;function ue(){return le?(Qe=!0,le):(le=(async()=>{do{Qe=!1;try{await ds()}catch(e){console.error("Auto check failed:",e.message)}}while(Qe)})().finally(()=>{le=null}),le)}c(ue,"checkAuto");async function ds(){const e=H(await L()),t=await U(C),{items:a,reviews:n}=await _(),r=await Ps(t),s=await ha(Ze),l=e.max_implementers,u={free:ht(a,{cap:l,pulled:s,hold:r}).free},S=u.free,p=K({dir:o});Ye&&p.state!=="paused"&&await M("usage-resumed",{window:"manual"}),Ye=p.manual==="board",D=V(p,"claude"),D||await ks(),D||await ps(fn(a)),D||await Os(a,oa(a),u),D||await js(a,u);const y=await gs(a,p);if(!D)for(const O of y.slice(0,u.free))u.free--,await ys(O);const{idle:E,pullable:x,waiting:g,pulls:i,helpers:d,bumpOuts:w,pastCap:k}=ht(a,{cap:l,pulled:s,hold:r,taken:S-u.free}),P=E&&!D;D||await na(a,t),D||await ws(d),e.auto_pull&&!D&&await hs(un(a,{cap:e.max_planners}).map(O=>a.find(ge=>ge.key===O))),await As(e,t,a,P);const m=x.filter(O=>r.includes(O));m.length&&T({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:m}});const v=c(O=>V(O,"claude")?[]:O.near&&!O.near.going?i.slice(0,1):i,"pullNow"),I=c(async O=>{for(const ye of w.filter(Y=>Y.by===O)){const Y={call:"bump-out",key:ye.key,reason:`${O} is bumped and every implementer slot is full; this was the newest unbumped build.`,inputs:{by:O,cap:l}};try{T({...Y,result:"pausing",inputs:{...Y.inputs,lines:await br(o,ye.key,O)}})}catch(Te){if(!(Te instanceof $))throw Te;T({...Y,result:"refused",reason:`${Y.reason} The bump-out refused: ${Te.message}`})}finally{N++}}const ge=await _e(o,O,ye=>Ee(o,O.split("/")[1],ye),"hub"),ya=k.includes(O)?"It is bumped, so it starts past the cap.":"An implementer slot was free and it was the top ready card in Todo.";return T({call:"pull",key:O,result:fs(ge),reason:ya,inputs:{cap:l,pullable:x,waiting:g}}),Ae(ge)},"startPull");if(await Es(Ze,e.auto_pull,g,D?[]:v(K({dir:o})),I))return;const{due:R,repo:J}=n;!P||!R||(T({call:"pr-review",result:"asked",reason:`The board is idle and PR #${R.number} touches its work.`,inputs:{repo:J,pr:R.number,head:R.head,touches:R.touches,asked:!!R.asked}}),await M("pr-review",{pr:R.number,head:R.head,repo:J,title:R.title,author:R.author,url:R.url,touches:R.touches,asked:!!R.asked}))}c(ds,"runAuto");function Q(e="claude"){const t=V(K({dir:o}),e);return t&&(D=!0),t}c(Q,"launchHeldNow");const fs=c(e=>e?e.bumped?"bumped":"started":"refused","startResult");async function Ae(e){if(N++,!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))T({call:"launch-held",key:e.key,result:"held",reason:De(a,e.kind),inputs:{kind:e.kind,model:e.model??null,gate:a}}),a.machine.state==="busy"&&await B(o,e.key,"host-busy"),await Pe(e.key,{...t,pid:null});else{const{pid:n,log:r}=await ne(o,e);await Pe(e.key,{...t,pid:n,log:r})}}catch(a){await oi(o,e.key,`the launch failed: ${a.message}`)}return null}c(Ae,"handOff");async function hs(e){for(const{key:t,phase:a}of e){if(Q())return;const n=a==="prototyping"?"prototype":"grill";T({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),N++}}c(hs,"launchArrivals");async function ps(e){for(const{key:t,kind:a}of e){if(Q())return;T({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),N++}}c(ps,"launchRounds");async function ws(e){for(const{key:t,name:a}of e){if(Q())return;N++;try{const n=await Kn(o,t,a,r=>va(t.split("/")[1],a,r));if(!n)continue;T({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(r){await ke(o,t,["helper-failed",{name:a,error:r.message}],a)}}catch(n){if(!(n instanceof $))throw n;await ke(o,t,["helper-failed",{name:a,error:n.message}],a)}}}c(ws,"launchHelpers");let aa=Promise.resolve();function na(e,t){const a=aa.then(()=>ms(e,t));return aa=a.catch(()=>{}),a}c(na,"launchVisuals");async function ms(e,t){for(const{key:a,request:n}of Qa(e,t)){if(Q())return;try{const r=await zn(o,a,n);if(!r)continue;const{text:s,files:l}=n.data,u={...s?{text:String(s)}:{},...Array.isArray(l)&&l.length?{files:l}:{}};try{await ne(o,{...r,...u})}catch(S){await ke(o,a,["planner-failed",{kind:"visual agent",error:S.message}],r.agent)}}catch(r){if(!(r instanceof $))throw r;await B(o,a,"planner-failed",{kind:"visual agent",error:r.message})}}}c(ms,"drawVisuals");const ee=h.join(j,"queued-planners.json");async function ia(e){await b.mkdir(j,{recursive:!0}),await b.writeFile(ee+".tmp",JSON.stringify(e)+`
|
|
13
|
-
`),await b.rename(ee+".tmp",ee)}
|
|
14
|
-
`),await b.rename(a+".tmp",a)}
|
|
15
|
-
`),await b.rename(e+".tmp",e))}catch(e){console.error("Headless builder check failed:",e.message)}finally{Xe=!1}}}
|
|
16
|
-
`),await b.rename(s+".tmp",s)},"save");if(S.size&&await E(),!n&&!
|
|
17
|
-
`),await b.rename(a+".tmp",a)}}
|
|
18
|
-
`),await b.rename(r+".tmp",r)},"save"),
|
|
19
|
-
`),await b.rename(
|
|
20
|
-
`).at(-2)||s.message;T({...r,result:"not sent",reason:`${a} Not sent: ${
|
|
21
|
-
`),await b.rename(e+".tmp",e))}
|
|
22
|
-
`),await b.rename(e+".tmp",e)),(s.added.length||s.again.length)&&await
|
|
23
|
-
`),await b.rename(e+".tmp",e))}
|
|
11
|
+
`),ma(),pa(),wa(),ga(),tt(),nt(),ue(),rt()},Math.max(0,Math.min(ns,Xt+is-t)))}l(Ge,"changed");const rs=new Set(["node_modules","dd","DerivedData"]),Ke=l(e=>String(e).endsWith(".tmp")||String(e).split(/[\\/]/).some(t=>t.startsWith(".")||rs.has(t)),"ignored");let Zt=null;const qt=l(e=>{try{const t=gt(e);return`${t.size}:${t.mtimeMs}`}catch{return null}},"statOf");function ea(e,t){if(!t)return Ge();if(Ke(t))return;const a=p.join(e,String(t));a!==Le&&Ge({read:a===C&&qt(C)===Zt})}l(ea,"heard");const ze=l(e=>{try{return gt(e).isDirectory()}catch{return!1}},"isFolder");function ss(e){let t=l(()=>{},"stop"),a=!1,n=null;const r=l(()=>{n||(t(),a=!0,n=setTimeout(()=>{n=null,s()},2e3).unref())},"retry"),s=l(()=>{try{t=(process.platform==="linux"?cs:os)(e,r)}catch{r();return}a&&Ge()},"start");s()}l(ss,"watchTree");function os(e,t){const a=yt(e,{recursive:!0},(n,r)=>{ea(e,r),ze(e)||t()});return a.on("error",t),()=>a.close()}l(os,"watchRecursive");function cs(e,t){const a=new Map,n=l(s=>{for(const[u,c]of a)(u===s||u.startsWith(s+p.sep))&&(c.close(),a.delete(u))},"unwatch"),r=l(s=>{if(a.has(s))return;const u=yt(s,(c,S)=>{if(ea(s,S),s===e&&!ze(e))return t();if(!S||Ke(S))return;const w=p.join(s,String(S));if(!ze(w))n(w);else try{r(w)}catch{n(w)}});a.set(s,u),u.on("error",()=>s===e?t():n(s));for(const c of An(s,{withFileTypes:!0}))if(!(!c.isDirectory()||Ke(c.name)))try{r(p.join(s,c.name))}catch{n(p.join(s,c.name))}},"watchFolder");try{r(e)}catch(s){throw n(e),s}return()=>n(e)}l(cs,"watchEachFolder");async function ls(){const e=(await L()).items==="waves"?[Vi()]:(await be(o)).map(a=>a.dir),t=[...new Set([j,...e].map(a=>p.resolve(a)))];for(const a of t.filter(n=>!t.some(r=>r!==n&&n.startsWith(r+p.sep))))ss(a)}l(ls,"watchBoard");const us=Ha.createServer(async(e,t)=>{try{if(e.method!=="GET"&&e.method!=="HEAD"&&N++,e.method!=="GET"&&e.method!=="HEAD"&&or(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 r=e.method!=="GET"&&e.method!=="HEAD"?await Nr(e,n):null;if(r?.refused)return f(t,403,{error:r.refused});if(n==="/_claude/health")return f(t,200,{ok:!0});if(n==="/api/board"){const i=await G(e,String(a.searchParams.get("since")||"")),d=`W/"${i.version}"`;if(Ft(e.headers["if-none-match"],d)){t.writeHead(304,{etag:d,"cache-control":"no-store",vary:"accept-encoding"}).end();return}return f(t,200,i,{etag:d})}if(n==="/api/reviewer-people")return f(t,200,await Ur());if(n==="/api/questions"){const i=await _(),d=new Set(i.ownerTopics.find(h=>h.id===String(a.searchParams.get("topic")??""))?.questions||[]);return f(t,200,{questions:i.owner.filter(h=>d.has(h.id)&&h.state==="resolved")})}if(n==="/api/done"){const i=String(a.searchParams.get("before")||""),d=(await jt(o)).filter(h=>!i||ri(h)<i);return f(t,200,{cards:await Wr(d.slice(0,dt)),left:Math.max(0,d.length-dt)})}if(n==="/api/links")return f(t,200,{links:(await ce()).links});if(n==="/api/stream"&&e.method==="GET")return as(e,t);if(n==="/_claude/needs")return f(t,200,on(await ce()));if(n==="/api/working"){const i=await _(),d=new Map(i.items.filter(h=>h.kind==="work").map(h=>[h.key,h.ref]));return f(t,200,{working:i.workingOn.map(h=>({id:h.id,kind:h.kind,card:d.get(h.key)||null,since:h.since}))})}if(n==="/api/thread")return f(t,200,await Xr(String(a.searchParams.get("key")||"")));if(n==="/api/notes"&&a.searchParams.has("wave"))try{return f(t,200,await Ni(o,String(a.searchParams.get("wave")),H(await L()).tickets))}catch(i){if(i instanceof $)return f(t,404,{error:i.message});throw i}if(n==="/api/notes"||n==="/api/plan"){const i=String(a.searchParams.get("item")||"");try{const{itemFile:d,item:h,markdown:k}=await xi(o,i);return f(t,200,{key:i,ref:Re(d,h,H(await L()).tickets),title:h.title,markdown:k})}catch(d){if(d instanceof $)return f(t,404,{error:d.message});throw d}}if(n==="/_claude/events"&&e.method==="POST"){const i=await W(e,524288),d=String(i.kind||"message").slice(0,64);delete i.kind;const h=String(i.since||"");if(delete i.since,r&&!$r.has(d))return f(t,403,{error:Rr});for(const m of[i,...Array.isArray(i.answers)?i.answers.filter(v=>v&&typeof v=="object"):[]])delete m.shared_by,r&&(m.shared_by=r.by);if(d==="card-hold"&&i.hold&&i.for!==void 0){const m=Et(new Date,i.for);if(!m)return f(t,400,{error:"That hold ends in the past or more than 30 days on. Pick another time."});delete i.for,m!==Tt&&(i.until=m.toISOString())}if(d==="card-build"){const m=String(i.key||""),v=await _e(o,m,I=>Ee(o,m.split("/")[1],I));return f(t,200,{id:await Ae(v),board:await G(e,h)})}if(d==="item-message"){const m=String(i.key||""),v=(await _()).items.find(R=>R.key===m&&R.kind==="work"),I=!String(i.text||"").trim()&&!(Array.isArray(i.files)&&i.files.length);if(v?.stalled&&await ji(o,m),I&&(v?.ready||v?.lane==="todo"))return await Qn(o,m,r?r.by.name||r.by.login:void 0),N++,f(t,200,{id:null,board:await G(e,h)});v?.lane==="todo"&&i.intent==="build"?await Yn(o,m,i):v?.lane==="todo"&&v.phase==="ready"&&await Xn(o,m,i),N++}if(d==="owner-answer")return f(t,200,{id:(await Wt([i]))[0]??null,board:await G(e,h)});if(d==="owner-answers"){const m=await _r(i.answers);return m?f(t,400,{error:m}):f(t,200,{ids:await Wt(i.answers),board:await G(e,h)})}if(d==="card-merge"&&await qn(o,String(i.key||"")).catch(()=>null)){const m=String(i.key);try{await ti(o,m)}catch(v){if(v instanceof $)return f(t,400,{error:v.message});throw v}return vs(m),f(t,200,{id:null,board:await G(e,h)})}if(d==="card-merge"){try{await Ai(o,String(i.key||""),{tapped:!0})}catch(m){if(m instanceof $)return f(t,400,{error:m.message});throw m}return f(t,200,{id:null,board:await G(e,h)})}if(d==="card-hold"||d==="card-done"){const m=String(i.key||"");let v,I;try{I=d==="card-hold"&&!i.hold?[]:await Ot(o,m),d==="card-done"&&await Vn(o,m)&&(I=[]),v=await vi(o,d,i)}catch(J){return f(t,J instanceof $?400:500,{error:`Couldn\u2019t write ${m} to its item file: ${J.message}`})}if(!v)return f(t,400,{error:`${m} is already in Done, so its item file stays as it is.`});const R=await Ot(o,m);I=I.filter(J=>!R.some(O=>O.pid===J.pid)),I.length&&(xn(I),await B(o,m,"builder-stopped")),d==="card-hold"&&!i.hold&&await ra(m)}if(d==="reviewers"){const m=l(O=>O===void 0?[]:Array.isArray(O)?O:null,"list"),v=m(i.add),I=m(i.remove),R=l(O=>typeof O=="string"&&O.trim(),"text");if(!v||!I||!v.length&&!I.length)return f(t,400,{error:"Send the people to add and the names to remove, each as a list."});if(!I.every(R)||!v.every(O=>R(O?.name)&&R(O.slack_user)&&R(O.github)))return f(t,400,{error:"Each person to add needs a name, a Slack member and a GitHub login."});try{I.length&&await Hi(o,I.map(O=>O.trim())),v.length&&await Bi(o,await Jr(v))}catch(O){if(O instanceof $||O instanceof Sr)return f(t,400,{error:O.message});throw O}const J=await ve([...I.length?[["reviewers-removed",{remove:I}]]:[],...v.length?[["reviewers-added",{add:v.map(O=>O.name)}]]:[]]);return f(t,200,{id:J.at(-1),board:await G(e,h)})}if(d==="reask-request"){const m=await Dr(String(i.question||""));if(m)return f(t,400,{error:m})}if(d===Ie){const m=await xr(String(i.question||""));if(m)return f(t,400,{error:m})}const k=d==="ask"?Ct():void 0;k&&await Fn(o,k,{id:k,ts:new Date().toISOString(),kind:d,data:i});const P=(await ve([[d,i,k]]))[0];if(d==="ask-edited"&&await Wn(o,String(i.id||"")),d==="ask-withdrawn"&&await Hn(o,String(i.id||"")),(d==="card-bump"||d==="card-unbump")&&Oa(o,String(i.key||""),d==="card-bump"?new Date().toISOString():null),(d==="grill-more"||d==="prototype-request")&&i.key){const m={...i.text?{text:String(i.text)}:{},...Array.isArray(i.files)&&i.files.length?{files:i.files}:{},...i.question?{question:String(i.question),note:i.note?String(i.note):void 0,choice:i.choice??void 0}:{}},v=d==="grill-more"?"grill":"prototype";await Zn(o,String(i.key),v),await te(String(i.key),v,m)}return d==="visual-request"&&i.key&&await na((await _()).items,await M(C)),d==="ask-withdrawn"&&await Pt(o,String(i.id||""),!0),f(t,200,{id:P,board:await G(e,h)})}if(n==="/api/rules"&&e.method==="GET")return f(t,200,{board:await Ei(o),hub:Yi()});if(n==="/api/rules"&&e.method==="POST"){const i=await W(e);let d;try{d=await Mi(o,i.rules,i.base??[])}catch(k){if(k instanceof $)return f(t,400,{error:k.message});throw k}const h=Qi(d.changes)?await U("rules",{scope:"board",...d.changes}):null;return f(t,200,{rules:d.rules,id:h})}if(n==="/api/settings"&&e.method==="GET")return f(t,200,{settings:await $i(o),paused_until:dr(o),pause_choices:Kt});if(n==="/api/build-targets"&&e.method==="GET"){const i=X(o);if(!i)return f(t,404,{error:"This board isn\u2019t in the hub\u2019s manifest."});const d=Be(i.route);return f(t,200,{targets:await gr(d.repo,mr(d))})}if(n==="/api/pause"&&e.method==="POST"){const i=await W(e);if(!i||typeof i!="object"||Array.isArray(i)||Object.keys(i).join()!=="pause"||typeof i.pause!="string")return f(t,400,{error:"Send only {pause: a duration, a date and time, or off}."});let d;try{d=await Ki(o,i.pause)}catch(h){if(h instanceof $)return f(t,400,{error:h.message});throw h}return d&&(Ye=!0),ue(),f(t,200,{paused_until:d})}if(n==="/api/permissions"){const i=X(o);if(!i)return f(t,404,{error:"This board isn\u2019t in the hub\u2019s manifest."});const d=Be(i.route).repo,h=e.method==="POST"?await W(e):null;try{return h&&await kr(d,o,h.id,h.on),f(t,200,await yr(d,o))}catch(k){if(k instanceof $)return f(t,400,{error:k.message});throw k}}if(n==="/api/try/main/log"&&e.method==="GET")return f(t,200,{lines:await ct(o,"main")});if(n==="/api/try/prototypes"&&e.method==="GET"){const i=(await A(p.join(o,"data","try.json"),{}))?.prototypes||{};return f(t,200,{state:i.state||null,https:i.state==="ready"&&i.https||null,since:i.state==="ready"&&i.since||null,lines:i.state==="failed"?await ct(o,"prototypes"):[]})}if(n==="/api/log"&&e.method==="GET"){const i=a.searchParams.get("file")||"";if(!/^[\w.-]+\.log$/.test(i)||i.startsWith("."))return f(t,400,{error:"file takes a log file\u2019s name."});let d;try{d=await b.open(p.join(ae.homedir(),".claude","kickflip","logs",i),"r");const{size:h}=await d.stat(),k=Math.max(0,h-256*1024),P=Buffer.alloc(h-k),{bytesRead:m}=await d.read(P,0,P.length,k),v=P.subarray(0,m).toString("utf8");return f(t,200,{text:k?v.slice(v.indexOf(`
|
|
12
|
+
`)+1):v,truncated:k>0,size:h})}catch(h){if(h.code==="ENOENT")return f(t,404,{error:"No such log."});throw h}finally{await d?.close()}}if(n==="/api/transcript"&&e.method==="GET"){const i=a.searchParams.get("file")||"";if(!/^[\w.-]+\.log$/.test(i)||i.startsWith("."))return f(t,400,{error:"file takes a log file\u2019s name."});if(!/^\d+$/.test(a.searchParams.get("from")||""))return f(t,400,{error:"from takes a byte offset."});try{return f(t,200,await Ta(p.join(ae.homedir(),".claude","kickflip","logs",i),Number(a.searchParams.get("from"))))}catch(d){if(d.code==="ENOENT")return f(t,404,{error:"No such log."});throw d}}if(n==="/api/try/main/pull"&&e.method==="POST"){const i=await W(e);if(!i||typeof i!="object"||Array.isArray(i)||Object.keys(i).length)return f(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 f(t,d.status,await d.json())}catch(d){return f(t,502,{error:`Could not pull: ${d.message}`})}}if((n==="/api/try"||n==="/api/try/kill")&&e.method==="POST"){const i=await W(e),d=n==="/api/try/kill";if(!i||typeof i!="object"||Array.isArray(i)||Object.keys(i).some(h=>h!=="key"&&(d||h!=="anyway"))||typeof i.key!="string"||d&&["main","prototypes"].includes(i.key)||!["undefined","boolean"].includes(typeof i.anyway))return f(t,400,{error:d?"Send only the card key.":"Send only the card key and anyway."});try{const h=await fetch(`http://127.0.0.1:${process.env.KICKFLIP_HUB_PORT||4500}/_hub/${d?"kill":"build"}`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({route:z.slice(1)||p.basename(o),key:i.key,...i.anyway?{anyway:!0}:{}}),signal:AbortSignal.timeout(1e4)});return f(t,h.status,await h.json())}catch(h){return f(t,502,{error:`Could not ${d?"kill the serve":"start the build"}: ${h.message}`})}}if(n==="/api/share"&&e.method==="POST")try{return f(t,200,await Ji(o,await W(e)))}catch(i){if(i instanceof $)return f(t,400,{error:i.message});throw i}if(n==="/api/settings"&&e.method==="POST"){const i=await W(e);let d;try{d=await Ui(o,i)}catch(P){if(P instanceof $)return f(t,400,{error:P.message});throw P}const h=d.changed.filter(P=>P!=="idle_notification"),k=h.length?await U("settings",Object.fromEntries(h.map(P=>[P,d.settings[P]??null]))):null;return d.changed.includes("auto_merge")&&d.settings.auto_merge&&await U("check-github",{reason:"auto-merge"}),f(t,200,{settings:d.settings,id:k})}if(n==="/api/arch-review"&&e.method==="POST"){await W(e);try{return f(t,200,await Vr())}catch(i){if(i instanceof $)return f(t,i.status||400,{error:i.message});throw i}}if(n==="/api/hide"&&e.method==="POST"){const i=await W(e);try{await Pt(o,String(i.id||""),i.hidden!==!1)}catch(d){if(d instanceof $)return f(t,400,{error:d.message});throw d}return f(t,200,{ok:!0})}if(n==="/api/read"&&e.method==="POST"){const{marks:i}=await W(e);return i&&typeof i=="object"&&!Array.isArray(i)&&Object.values(i).every(h=>typeof h=="string"&&!Number.isNaN(Date.parse(h)))?(await At(o,(await _()).items.map(h=>h.key),i),f(t,200,{ok:!0})):f(t,400,{error:"Send {marks: {card key: ISO time}}."})}if(n==="/_claude/events"&&e.method==="GET")return f(t,200,await Br(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 ts(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=rr.has(n.slice(1)),u=s?null:await Zr(n),c=s?null:u?Pn:n==="/prototype.html"?null:await Qt(decodeURIComponent(n));if(!s&&!c){t.writeHead(404).end();return}const S=s?".html":p.extname(c).toLowerCase(),w=s?null:await b.stat(c);let y=s?Buffer.from(sr({touchIcon:await _t()})):u?Buffer.from(await En(o,u)):await b.readFile(c);!s&&S===".html"&&(y=(await jr(y.toString("utf8"))).replaceAll("{{title}}",Tn((await L()).title)).replaceAll("{{touchIcon}}",await _t())),!s&&S===".html"&&(y=Buffer.from(await Er(y,n)));const E=S===".html"?`W/"${La.createHash("sha1").update(y).digest("base64url").slice(0,20)}"`:`W/"${Ne(w)}"`,x=S!==".html"&&a.searchParams.get("v")===Ne(w),g={"content-type":F(Dt,S)||"application/octet-stream","cache-control":x?"public, max-age=31536000, immutable":"no-cache",etag:E};if(Ft(e.headers["if-none-match"],E)){t.writeHead(304,{etag:E,"cache-control":g["cache-control"],vary:"accept-encoding"}).end();return}return await Bt(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 $?f(t,a.status||400,{error:a.message}):t.writeHead(a.status||(a.code==="ENOENT"?404:400)).end())}});let Ve=!1;async function ta(){if(!Ve){Ve=!0;try{const e=Date.now(),t=(await _()).items.filter(n=>n.heldUntil&&n.lane!=="done"&&Date.parse(n.heldUntil)<=e),a=t.length?await M(C):[];for(const{key:n,heldUntil:r}of t)await ki(o,n,r,{announce:!a.some(s=>s.kind==="card-unblocked"&&s.data?.key===n&&s.data?.until===r)}),await ra(n),T({call:"pick-up",key:n,result:"resumed",reason:"Its hold ended.",inputs:{heldUntil:r}})}catch(e){console.error("Pick-up check failed:",e.message)}finally{Ve=!1}}}l(ta,"checkPickUps");let le=null,Qe=!1,D=!1,Ye=!1;function ue(){return le?(Qe=!0,le):(le=(async()=>{do{Qe=!1;try{await ds()}catch(e){console.error("Auto check failed:",e.message)}}while(Qe)})().finally(()=>{le=null}),le)}l(ue,"checkAuto");async function ds(){const e=H(await L()),t=await M(C),{items:a,reviews:n}=await _(),r=await Ps(t),s=await ha(Ze),u=e.max_implementers,c={free:ht(a,{cap:u,pulled:s,hold:r}).free},S=c.free,w=K({dir:o});Ye&&w.state!=="paused"&&await U("usage-resumed",{window:"manual"}),Ye=w.manual==="board",D=V(w,"claude"),D||await ks(),D||await ps(fn(a)),D||await Os(a,oa(a),c),D||await js(a,c);const y=await gs(a,w);if(!D)for(const O of y.slice(0,c.free))c.free--,await ys(O);const{idle:E,pullable:x,waiting:g,pulls:i,helpers:d,bumpOuts:h,pastCap:k}=ht(a,{cap:u,pulled:s,hold:r,taken:S-c.free}),P=E&&!D;D||await na(a,t),D||await ws(d),e.auto_pull&&!D&&await hs(un(a,{cap:e.max_planners}).map(O=>a.find(ge=>ge.key===O))),await As(e,t,a,P);const m=x.filter(O=>r.includes(O));m.length&&T({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:m}});const v=l(O=>V(O,"claude")?[]:O.near&&!O.near.going?i.slice(0,1):i,"pullNow"),I=l(async O=>{for(const ye of h.filter(Y=>Y.by===O)){const Y={call:"bump-out",key:ye.key,reason:`${O} is bumped and every implementer slot is full; this was the newest unbumped build.`,inputs:{by:O,cap:u}};try{T({...Y,result:"pausing",inputs:{...Y.inputs,lines:await br(o,ye.key,O)}})}catch(Te){if(!(Te instanceof $))throw Te;T({...Y,result:"refused",reason:`${Y.reason} The bump-out refused: ${Te.message}`})}finally{N++}}const ge=await _e(o,O,ye=>Ee(o,O.split("/")[1],ye),"hub"),ya=k.includes(O)?"It is bumped, so it starts past the cap.":"An implementer slot was free and it was the top ready card in Todo.";return T({call:"pull",key:O,result:fs(ge),reason:ya,inputs:{cap:u,pullable:x,waiting:g}}),Ae(ge)},"startPull");if(await Es(Ze,e.auto_pull,g,D?[]:v(K({dir:o})),I))return;const{due:R,repo:J}=n;!P||!R||(T({call:"pr-review",result:"asked",reason:`The board is idle and PR #${R.number} touches its work.`,inputs:{repo:J,pr:R.number,head:R.head,touches:R.touches,asked:!!R.asked}}),await U("pr-review",{pr:R.number,head:R.head,repo:J,title:R.title,author:R.author,url:R.url,touches:R.touches,asked:!!R.asked}))}l(ds,"runAuto");function Q(e="claude"){const t=V(K({dir:o}),e);return t&&(D=!0),t}l(Q,"launchHeldNow");const fs=l(e=>e?e.bumped?"bumped":"started":"refused","startResult");async function Ae(e){if(N++,!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))T({call:"launch-held",key:e.key,result:"held",reason:De(a,e.kind),inputs:{kind:e.kind,model:e.model??null,gate:a}}),a.machine.state==="busy"&&await B(o,e.key,"host-busy"),await Pe(e.key,{...t,pid:null});else{const{pid:n,log:r}=await ne(o,e);await Pe(e.key,{...t,pid:n,log:r})}}catch(a){await oi(o,e.key,`the launch failed: ${a.message}`)}return null}l(Ae,"handOff");async function hs(e){for(const{key:t,phase:a}of e){if(Q())return;const n=a==="prototyping"?"prototype":"grill";T({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),N++}}l(hs,"launchArrivals");async function ps(e){for(const{key:t,kind:a}of e){if(Q())return;T({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),N++}}l(ps,"launchRounds");async function ws(e){for(const{key:t,name:a}of e){if(Q())return;N++;try{const n=await Kn(o,t,a,r=>va(t.split("/")[1],a,r));if(!n)continue;T({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(r){await ke(o,t,["helper-failed",{name:a,error:r.message}],a)}}catch(n){if(!(n instanceof $))throw n;await ke(o,t,["helper-failed",{name:a,error:n.message}],a)}}}l(ws,"launchHelpers");let aa=Promise.resolve();function na(e,t){const a=aa.then(()=>ms(e,t));return aa=a.catch(()=>{}),a}l(na,"launchVisuals");async function ms(e,t){for(const{key:a,request:n}of Qa(e,t)){if(Q())return;try{const r=await zn(o,a,n);if(!r)continue;const{text:s,files:u}=n.data,c={...s?{text:String(s)}:{},...Array.isArray(u)&&u.length?{files:u}:{}};try{await ne(o,{...r,...c})}catch(S){await ke(o,a,["planner-failed",{kind:"visual agent",error:S.message}],r.agent)}}catch(r){if(!(r instanceof $))throw r;await B(o,a,"planner-failed",{kind:"visual agent",error:r.message})}}}l(ms,"drawVisuals");const ee=p.join(j,"queued-planners.json");async function ia(e){await b.mkdir(j,{recursive:!0}),await b.writeFile(ee+".tmp",JSON.stringify(e)+`
|
|
13
|
+
`),await b.rename(ee+".tmp",ee)}l(ia,"saveQueuedPlanners");async function te(e,t,a={}){const n=K({dir:o});if(V(n,"claude")){D=!0,await ia({...await A(ee,{}),[e]:{kind:t,ask:a}}),T({call:"planner-queued",key:e,result:t,reason:De(n,"claude"),inputs:{kind:t,gate:n}}),await B(o,e,n.machine.state==="busy"?"host-busy":"planner-held",{kind:t});return}try{const r=await gi(o,e,t);if(!r)return;try{await ne(o,{...r,...a})}catch(s){await ke(o,e,["planner-failed",{kind:t,error:s.message}])}}catch(r){if(!(r instanceof $))throw r;await B(o,e,"planner-failed",{kind:t,error:r.message})}}l(te,"launchPlanner");async function ra(e){const t=await Oi(o,e);t&&(T({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 Ae(await _e(o,e,a=>Ee(o,e.split("/")[1],a),"hub")))}l(ra,"resumeRun");let sa=!1;async function gs(e,t){const a={usageHeld:l(r=>$t(t,r),"usageHeld")},n=Ja(e,a);n.drop.length&&(N++,n.drop=Ga(n.drop,(await _()).items,a));for(const r of n.ask)await vt(o,r,{cause:"usage",words:ur(t)})&&T({call:"pause",key:r,result:"asked",reason:De(t,"claude"),inputs:{gate:t}});for(const r of n.drop)await Ln(o,r),T({call:"pause",key:r,result:"dropped",reason:"Its cause cleared before its builder reached a stopping point.",inputs:{}});for(const r of n.stop){await St(o,r);const s=In((await Mn(o,r)).filter(c=>!Ka(c)))[0],u=s?await Dn([s.pid]):[];T({call:"pause",key:r,result:"hard stop",reason:"Its builder reached no stopping point within 15 minutes of the pause.",inputs:{stopped:u.map(c=>c.pid)}})}return(n.ask.length||n.drop.length||n.stop.length)&&N++,sa=n.ask.length>0||e.some(r=>r.builder?.pause),n.resume}l(gs,"checkPauses");async function ys(e){const t=await Un(o,e);if(!t)return;const{pause:a,record:n}=t,r=Ua[a.cause][1];N++;const s={call:"resume",key:e,reason:`Nothing pauses it any more: ${r}.`,inputs:{pause:a}};if(!n.session?.pid)return T({...s,result:"woke the orchestrator"}),U("builder-resume",{key:e,cleared:r});const u=za(n.kind)??"claude";try{const{pid:c,log:S}=await Nn(o,{key:e,kind:u,model:n.model,session:{id:n.session.id},brief:l(w=>Rn({...w,cleared:r}),"brief")});await Pe(e,{kind:u,model:n.model,pid:c,log:S,relaunched:!1}),T({...s,result:"resumed"})}catch(c){T({...s,result:"failed",reason:`${s.reason} The launch failed: ${c.message}`}),await B(o,e,"resume-failed",{error:c.message})}}l(ys,"resumeBuild");async function ks(){for(const[e,{kind:t,ask:a}]of Object.entries(await A(ee,{}))){if(Q())return;const{[e]:n,...r}=await A(ee,{});await ia(r),T({call:"planner-queued",key:e,result:"launching",reason:"The hold it waited on has lifted.",inputs:{kind:t}}),await te(e,t,a),N++}}l(ks,"launchQueuedPlanners");async function Pe(e,t){const a=p.join(j,"headless-builders.json"),n=await A(a,{});t?n[e]=t:delete n[e],await b.mkdir(j,{recursive:!0}),await b.writeFile(a+".tmp",JSON.stringify(n)+`
|
|
14
|
+
`),await b.rename(a+".tmp",a)}l(Pe,"trackLaunch");async function bs(){try{const e=await A(p.join(j,"headless-builders.json"),{});for(const t of(await ci(o)).filter(a=>!e[a.key]))T({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 Ae(t)}catch(e){console.error("Cut-off launch check failed:",e.message)}}l(bs,"launchCutOffBuilds");const oa=l(e=>new Set(e.filter(t=>t.builder?.stopped).map(t=>t.key)),"stoppedKeys");let Xe=!1;async function Ss(){if(!Xe){Xe=!0;try{const e=p.join(j,"headless-builders.json"),t=await A(e,{});if(!Object.keys(t).length)return;const{items:a}=$e(await Ut(await Ce())),n=new Set(a.filter(w=>w.lane!=="done"&&w.builder&&!w.builder.planner).map(w=>w.key)),r=l(async w=>{T({call:"relaunch",key:w.key,result:"handed back",reason:"Its builder exited twice with its record open.",inputs:t[w.key]}),await B(o,w.key,"exited-twice")},"handBack"),s=l(async w=>{const y=t[w.key],E={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:x,log:g}=await ne(o,w);return T({...E,result:y.pid===null?"launched":"relaunched"}),{pid:x,log:g}}catch(x){return T({...E,result:"failed",reason:`${E.reason} The launch failed: ${x.message}`}),await B(o,w.key,"relaunch-failed",{error:x.message}),{pid:null}}},"launch"),u=l(async(w,y)=>{T({call:"relaunch",key:w.key,result:"paused",reason:`Its run stopped at the usage limit, which resets at ${y}.`,inputs:t[w.key]}),await vt(o,w.key,{cause:"usage",words:"usage limit",until:y}),await St(o,w.key),N++},"atLimit"),c=new Set(a.filter(w=>w.builder?.pause?.paused).map(w=>w.key)),S=await _n(t,n,{launch:s,handBack:r,held:l(w=>V(K({dir:o}),w),"held"),stopped:oa(a),paused:c,limitedUntil:l(w=>w.log?fr(w.log):null,"limitedUntil"),atLimit:u});JSON.stringify(S)!==JSON.stringify(t)&&(await b.writeFile(e+".tmp",JSON.stringify(S)+`
|
|
15
|
+
`),await b.rename(e+".tmp",e))}catch(e){console.error("Headless builder check failed:",e.message)}finally{Xe=!1}}}l(Ss,"reviveHeadless");function vs(e){const t=p.join(ae.homedir(),".claude","kickflip","logs");On(t,{recursive:!0});const a=jn(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):[nr(),$n],r=Fa(n[0],[...n.slice(1),"--site",Z,"merge",e],{detached:!0,stdio:["ignore",a,a]});Sn(a),r.on("error",s=>B(o,e,"land-failed",{why:`the land couldn't start (${s.message})`}).catch(()=>{})),r.unref()}l(vs,"runHeldMerge");async function Os(e,t,a){const n=e.some(g=>g.waitsOn==="checking"),r=Object.fromEntries(e.map(g=>[g.key,g.waitsOn==="checking"?"checking":g.lane!=="done"&&g.agents?.some(i=>i.started&&!i.done)?"building":null])),s=p.join(j,"landers.json"),u=Object.fromEntries(Object.entries(await A(s,{})).map(([g,i])=>[g,typeof i=="string"?{verified:i,pid:null}:i])),c=new Set,S=new Set,w={};for(const[g,i]of Object.entries(u))if(!(!i.pid||bt(i.pid)||t.has(g))){if(r[g]==="checking"){i.resumed&&(w[g]={verified:null,pid:null,fresh:!0},T({call:"lander",key:g,result:"fresh next",reason:"Its resumed builder exited without taking the land up.",inputs:i}));continue}if(r[g]!=="building"){S.add(g);continue}await ui(o,g),c.add(g),T({call:"lander",key:g,result:"handed back again",reason:"Its lander exited with its builder still open.",inputs:i})}const y={...Object.fromEntries(Object.entries(u).filter(([g])=>!c.has(g)&&!S.has(g))),...w},E=l(async()=>{await b.writeFile(s+".tmp",JSON.stringify(y)+`
|
|
16
|
+
`),await b.rename(s+".tmp",s)},"save");if(S.size&&await E(),!n&&!c.size)return;const x=ca(e,(await li(o)).filter(g=>y[g.key]?.verified!==g.verified));if(x.length){for(const g of x){if(Q(g.kind??"claude"))break;if(!la(e,a,g.key))continue;const i={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:h}=await kt(o,g,{fresh:y[g.key]?.fresh});y[g.key]={verified:g.verified,pid:d,...h?{resumed:h}:{}},T({...i,result:h?"resumed its builder":"launched"}),await Pe(g.key,null)}catch(d){T({...i,result:"failed",reason:`${i.reason} The launch failed: ${d.message}`}),y[g.key]={verified:g.verified,pid:null},await B(o,g.key,"lander-failed",{error:d.message})}}await E()}}l(Os,"launchLanders");const de=l((e,t)=>e.find(a=>a.key===t)?.bumpedAt||"","bumpOf"),ca=l((e,t)=>[...t.filter(a=>de(e,a.key)).sort((a,n)=>de(e,a.key).localeCompare(de(e,n.key))),...t.filter(a=>!de(e,a.key))],"byBump");function la(e,t,a){return!de(e,a)&&t.free<=0?!1:(t.free--,!0)}l(la,"takeSlot");async function js(e,t){const a=p.join(j,"rebases.json"),n=await A(a,{}),{base:r}=Sa(o),s=ca(e,dn(e,Object.fromEntries(Object.entries(n).map(([u,c])=>[u,c.conflict])),r));if(s.length)try{for(const u of s){if(Q())break;if(!la(e,t,u.key))continue;const c=u.key.split("/")[1],S={call:"rebase-run",key:u.key,reason:`Its PR #${u.pr} conflicts with ${r}.`,inputs:{pr:u.pr,conflict:u.conflict,before:n[u.key]??null}};n[u.key]={conflict:u.conflict,pid:null};let w;try{w=await bi(o,u.key,{pr:u.pr,conflict:u.conflict,base:r},()=>ja(o,c,u.branch))}catch(y){if(!(y instanceof $))throw y;T({...S,result:"refused",reason:`${S.reason} The start refused: ${y.message}`}),await B(o,u.key,"rebase-failed",{pr:u.pr,error:y.message});continue}finally{N++}try{n[u.key].pid=(await kt(o,w)).pid,T({...S,result:"launched"})}catch(y){T({...S,result:"failed",reason:`${S.reason} The launch failed: ${y.message}`}),await B(o,u.key,"rebase-launch-failed",{error:y.message})}}}finally{await b.writeFile(a+".tmp",JSON.stringify(n)+`
|
|
17
|
+
`),await b.rename(a+".tmp",a)}}l(js,"launchRebases");async function ua(){const e=yn(new Date,pt(await A(p.join(ae.homedir(),".claude","kickflip","settings.json"),{})));return e&&mn(e)}l(ua,"triageNight");async function As(e,t,a,n){const r=p.join(j,"triaged.json"),s=await A(r,{}),{waiting:u,seen:c}=wn(a),S=l(async()=>{await b.mkdir(j,{recursive:!0}),await b.writeFile(r+".tmp",JSON.stringify({seen:c})+`
|
|
18
|
+
`),await b.rename(r+".tmp",r)},"save"),w=t.filter(g=>g.kind==="overnight-triage").at(-1);if(w&&!(await se(w)).reply){s.seen!==c&&await S();return}if(!e.auto_triage||!n||u.length<2||c===s.seen)return;const y=await ua();if(!y||w?.data?.date===y)return;const E=`overnight-triage-${y}`;((await A(p.join(j,"owner.json"),{topics:[]})).topics||[]).some(g=>g.id===E)||await ai(o,{id:E,title:`Overnight triage ${y}`}),T({call:"triage",result:"sent",reason:`Auto triage is on, ${u.length} cards wait on answers, and tonight's triage hadn't gone out.`,inputs:{date:y,cards:u}}),await U("overnight-triage",{date:y,topic:E,cards:u}),await S()}l(As,"checkTriage");async function da(e){const t=e.filter(a=>a.kind==="overnight-triage").at(-1);return t&&!(await se(t)).reply?t:null}l(da,"unansweredTriage");async function Ps(e){const t=await da(e),a=pt(await A(p.join(ae.homedir(),".claude","kickflip","settings.json"),{}));return t&&gn(new Date,a)?t.data.cards||[]:[]}l(Ps,"triageHold");async function Ts(){if(!H(await L()).auto_triage)return!1;const e=await ua();return!!e&&(await M(C)).filter(t=>t.kind==="overnight-triage").at(-1)?.data?.date!==e}l(Ts,"triageDue");const Ze={file:"pulled.json",line:"pulled"},fa=l(e=>e.keys||(e.key?[e.key]:[]),"markedOf"),ha=l(async({file:e})=>fa(await A(p.join(j,e),{})),"sentOf");async function Es({file:e,line:t},a,n,r,s){const u=p.join(j,e),c=a?[...n,...r]:[];for(const S of a?r:[])await B(o,S,t),await s(S);return JSON.stringify(fa(await A(u,{})))!==JSON.stringify(c)&&(await b.mkdir(j,{recursive:!0}),await b.writeFile(u+".tmp",JSON.stringify({keys:c,at:new Date().toISOString()})+`
|
|
19
|
+
`),await b.rename(u+".tmp",u)),c.length>0}l(Es,"sendPulls");async function qe(e,{key:t,reason:a,inputs:n}){const r={call:"push",key:t,reason:a,inputs:{tag:e.tag,title:e.title,...n}};try{await Si(o,e),T({...r,result:"sent"})}catch(s){if(!(s instanceof $))throw s;const u=s.message.split(`
|
|
20
|
+
`).at(-2)||s.message;T({...r,result:"not sent",reason:`${a} Not sent: ${u}`}),console.error(`Push ${e.tag} not sent:`,u)}}l(qe,"sendOwnPush");let fe=null,et=!1;function tt(){return fe?(et=!0,fe):(fe=(async()=>{do{et=!1;try{await $s()}catch(e){console.error("Action push check failed:",e.message)}}while(et)})().finally(()=>{fe=null}),fe)}l(tt,"checkActions");async function $s(){const e=p.join(j,"action-pushes.json"),t=await A(e,null),a=cn(await _()),n=Object.fromEntries(a.map(r=>[r.tag,{kind:r.kind,ids:r.questions.map(s=>s.id)}]));if(t){for(const s of a){const u=F(t,s.tag),c=s.questions.filter(y=>!u?.ids?.includes(y.id));if(u?.kind===s.kind&&!c.length)continue;const S=c.length?`questions.html#${encodeURIComponent(c[0].id)}`:s.url,w=u?u.kind!==s.kind?`What waits on the owner changed from ${u.kind} to ${s.kind}.`:"A new question waits on the owner.":"Something new waits on the owner.";await qe({kind:"action",title:s.title,body:c[0]?.question||s.body,url:`/${Z}/${S}`,tag:s.tag,now:s.now},{key:s.tag.startsWith("item-")?s.tag.slice(5):void 0,reason:w,inputs:{was:u??null,kind:s.kind,fresh:c.map(y=>y.id)}})}const r=Object.keys(t).filter(s=>!F(n,s));r.length&&await bn(r,Z)}JSON.stringify(n)!==JSON.stringify(t)&&(await b.mkdir(j,{recursive:!0}),await b.writeFile(e+".tmp",JSON.stringify(n)+`
|
|
21
|
+
`),await b.rename(e+".tmp",e))}l($s,"pushActions");let he=null,at=!1;function nt(){return he?(at=!0,he):(he=(async()=>{do{at=!1;try{await Is()}catch(e){console.error("Orchestrator wait check failed:",e.message)}}while(at)})().finally(()=>{he=null}),he)}l(nt,"checkWaits");async function Is(){const e=p.join(j,"orchestrator-waits.json"),t=await A(e,null),a=t?.waits||[],n=await da(await M(C)),r=[...Za(await _()),...n?[qa(n,F(await Se(o),n.id))]:[]],s=en(a,r,new Date),u=[];for(const c of s.waits)c.kind!=="triage"&&!c.picked&&!c.pushed&&Date.now()-Date.parse(c.since)>=Xa?(await qe({kind:"reminder",title:`${c.ref} waits on the orchestrator`,body:`${c.title}: ${c.next}`,url:`/${Z}/#selectedCard=${encodeURIComponent(c.key)}`,tag:`wait-${c.key}`,now:!0},{key:c.key,reason:`It has waited on the orchestrator since ${c.since} and nobody picked it up.`,inputs:{kind:c.kind,since:c.since}}),u.push({...c,pushed:!0})):u.push(c);(!t||JSON.stringify(u)!==JSON.stringify(a))&&(await b.mkdir(j,{recursive:!0}),await b.writeFile(e+".tmp",JSON.stringify({waits:u})+`
|
|
22
|
+
`),await b.rename(e+".tmp",e)),(s.added.length||s.again.length)&&await U("orchestrator-waits",{added:s.added,again:s.again})}l(Is,"stepWaits");let pe=null,it=!1;function rt(){return pe?(it=!0,pe):(pe=(async()=>{do{it=!1;try{await Rs()}catch(e){console.error("Idle check failed:",e.message)}}while(it)})().finally(()=>{pe=null}),pe)}l(rt,"checkIdle");async function Rs(){const e=p.join(j,"idle.json"),t=await L();if(!H(t).idle_notification)return b.rm(e,{force:!0});const a=await A(e,{}),n=await _(),r=hn(n,{pulled:await ha(Ze)}),{state:s,push:u}=nn(a,{idle:r,now:new Date});u&&await qe({kind:"reminder",title:`${t.title||Z} has gone idle`,body:rn(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(j,{recursive:!0}),await b.writeFile(e+".tmp",JSON.stringify(s)+`
|
|
23
|
+
`),await b.rename(e+".tmp",e))}l(Rs,"stepIdle");let we=null,st=!1;function pa(){return we?(st=!0,we):(we=(async()=>{do{st=!1;try{const e=Va(await _());for(const t of e)await wi(o,t.key,t.to,t.why,t.phase,t.from);e.length&&N++}catch(e){console.error("Lane settle failed:",e.message)}}while(st)})().finally(()=>{we=null}),we)}l(pa,"settleLanes");let me=null,ot=!1;function wa(){return me?(ot=!0,me):(me=(async()=>{do{ot=!1;try{await Ns()}catch(e){console.error("Done archive failed:",e.message)}}while(ot)})().finally(()=>{me=null}),me)}l(wa,"checkArchive");async function ma({starting:e=!1}={}){try{if(!e&&!(await _()).unfiled.length)return;for(const t of await Cn(o))console.log(`filed ask card ${t}`)}catch(t){console.error("Ask filing failed:",t.message)}}l(ma,"fileNewAsks");async function ga(){try{await At(o,(await _()).items.map(e=>e.key))}catch(e){console.error("Read marks failed:",e.message)}}l(ga,"markNewCards");async function Ns(){if(!ut(await _()).length)return;const e=ut(await ce()),t=e.length?await ni(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(Ns,"archiveDone");try{for(const e of await di(o))console.log(e)}catch(e){console.error("follow-up migration:",e.message)}try{for(const e of await pi(o))console.log(e)}catch(e){console.error("status migration:",e.message)}try{for(const e of await hi(o))console.log(e)}catch(e){console.error("phase migration:",e.message)}try{for(const e of await fi(o))console.log(e)}catch(e){console.error("gate backfill:",e.message)}try{for(const e of await mi(o))console.log(e)}catch(e){console.error("staging migration:",e.message)}try{for(const e of await Wi(o))console.log(e)}catch(e){console.error("notes split:",e.message)}try{await yi(o)}catch(e){console.error("dead-field strip:",e.message)}await bs(),await ma({starting:!0}),await pa(),await wa(),await ga(),await ls(),setInterval(()=>{N++,Gt(),Ss(),Jn(o).then(t=>{t.length&&N++},t=>console.error("Screen publish failed:",t.message)),ta();const e=K({dir:o});D&&!V(e,"claude")||sa||!D&&$t(e,"claude")?ue():Ts().then(t=>{t&&ue()},t=>console.error("Triage check failed:",t.message)),tt(),nt(),rt()},6e4).unref(),await Gt(),await ta(),await ue(),tt(),nt(),rt(),us.listen(xt,"127.0.0.1",()=>console.log(`site on http://127.0.0.1:${xt}/ (prefix ${z||"none"})`));
|