@lovek4d/kickflip 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTEXT.md +32 -16
- package/SKILL.md +8 -6
- package/bin/doctor.mjs +4 -4
- package/bin/install.mjs +3 -3
- package/bin/kickflip.mjs +8 -8
- package/bin/wait-events.mjs +2 -2
- package/board/bin/board.mjs +50 -28
- package/board/domain.mjs +7 -7
- package/board/finish.mjs +4 -3
- package/board/headless.mjs +4 -3
- package/board/merge3.mjs +1 -0
- package/board/on-host.mjs +1 -1
- package/board/permissions.mjs +2 -2
- package/board/public/app-asks.js +6 -89
- package/board/public/app-cards.js +19 -10
- package/board/public/app-core.js +1 -20
- package/board/public/app-reader.js +174 -0
- package/board/public/app-threads.js +49 -35
- package/board/public/app.html +2 -0
- package/board/public/app.js +28 -16
- package/board/public/hub.css +1 -0
- package/board/public/new-task.js +121 -0
- package/board/public/shell.js +4 -0
- package/board/public/sidebar.css +1 -0
- package/board/public/style.css +91 -3
- package/board/public/ui-cards.js +46 -21
- package/board/public/ui-enums.js +1 -1
- package/board/public/ui-phase-bars.js +4 -4
- package/board/public/ui-questions.js +47 -18
- package/board/public/ui-sidebar.js +9 -3
- package/board/public/ui-threads.js +25 -12
- package/board/public/ui.js +42 -12
- package/board/public/view-frame.js +1 -0
- package/board/public/view-questions.js +45 -16
- package/board/public/view-settings.js +38 -19
- package/board/server.mjs +20 -18
- package/board/store.mjs +15 -13
- package/board/worktree.mjs +20 -20
- package/hub/boards.mjs +4 -4
- package/hub/front-door.mjs +20 -0
- package/hub/github.mjs +3 -3
- package/hub/hub.mjs +9 -12
- package/hub/new-board.mjs +2 -2
- package/hub/owner-checks.mjs +1 -0
- package/hub/readiness.mjs +2 -2
- package/hub/sessions.mjs +2 -2
- package/hub/try.mjs +7 -7
- package/package.json +1 -1
- package/shared/owner-check.mjs +3 -0
- package/shared/tailscale.mjs +1 -0
- package/shared/usage-gate.mjs +1 -1
package/board/bin/board.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
var E=Object.defineProperty;var
|
|
2
|
+
var E=Object.defineProperty;var r=(e,s)=>E(e,"name",{value:s,configurable:!0});import{readiness as B,reportSlack as H}from"../../hub/readiness.mjs";import{readSlack as L,setSlack as D}from"../../hub/slack.mjs";import{routeRecord as y}from"../../hub/routes.mjs";import m from"node:fs";import g from"node:path";import{pathToFileURL as M}from"node:url";import*as n from"../store.mjs";import{boardWorktree as W,builderRepo as G,joiningLand as z,landInTurn as J,worktreesDir as F}from"../worktree.mjs";import{fileFindings as U}from"../arch-review.mjs";import{finishCard as x,landHeld as K}from"../finish.mjs";import{ITEM_STATUSES as Q}from"../domain.mjs";import{boardAt as V,readManifest as X}from"../../hub/boards.mjs";import"../../hub/board-repo.mjs";const Y=`usage: node board.mjs <command> [--site <route>] ...
|
|
3
3
|
|
|
4
4
|
Writes a kickflip board's data files. --site <route> picks ~/.claude/kickflip/sites/<route>, and its
|
|
5
5
|
board.json says where the board's waves live. Without --site, waves come from ~/.claude/waves.
|
|
@@ -28,11 +28,13 @@ command fails and changes nothing.
|
|
|
28
28
|
topic add <id> --title <t> [--summary <s>] a question topic in data/owner.json
|
|
29
29
|
question add --file <json> one question or an array of them:
|
|
30
30
|
{id, topic, question, options: [{id, label, recommended?, detail?, diagram?, practice?}],
|
|
31
|
-
source?, context?, url?, urgent?, round_of?, ask?}; topic is declared, item:<wave>/<id>, item:ask:<eventId>
|
|
31
|
+
source?, context?, url?, urgent?, round_of?, reask_of?, ask?, suggested?}; topic is declared, item:<wave>/<id>, item:ask:<eventId>
|
|
32
32
|
for a New card not sorted yet, lineup:<slug> for a wave's triage, or wave:<slug> for a wave file's triage on
|
|
33
33
|
a board over ~/.claude/waves. A question on a done or merged card reopens it.
|
|
34
34
|
url is http(s), or a site page such as prototype-<topic>.html?q=<id>, stored as given.
|
|
35
35
|
Prototype options keep take order, ids matching take keys; recommended: true tags the recommendation.
|
|
36
|
+
suggested: {option, card, answer} pre-checks the option the owner already chose on an earlier card, with a
|
|
37
|
+
"suggested" chip and the line You answered this on <card>: "<answer>"; card is that card's ref, answer their words.
|
|
36
38
|
Waiting on you shows fitting takes in a grid, or a chooser above one full-width take for a single take or if any
|
|
37
39
|
scrolls or fills the screen.
|
|
38
40
|
diagram, with practice: rows of 1-4 boxes [[{text, tone?, to?}]], tone quiet|info|notice|
|
|
@@ -45,6 +47,8 @@ command fails and changes nothing.
|
|
|
45
47
|
and printed as "replaced: <old id>". round_of names the question on the same topic whose
|
|
46
48
|
prototype-request this answers; the question needs a prototype url, takes the next round
|
|
47
49
|
number, and resolves that one, noting "Followed by prototype round N".
|
|
50
|
+
reask_of names a resolved question on the same topic that the owner asked to ask again
|
|
51
|
+
(reask-request): it resolves that one again, noting "Asked again as <new id>", printed as "resolved: <old id>".
|
|
48
52
|
ask names a builder ask the session deferred on the item (builder defer): the question puts it to the
|
|
49
53
|
owner, and while a card's only open questions are such, it waits on them with no Action needed.
|
|
50
54
|
The board server pushes the new open questions itself, under the card's tag (item-<wave>/<id>, or
|
|
@@ -77,7 +81,7 @@ command fails and changes nothing.
|
|
|
77
81
|
reads as Claude's own. Refuses words that already carry
|
|
78
82
|
either. Prints the post's url
|
|
79
83
|
item set <wave>/<id> [--status <s> | --clear-status] edits the item in its wave file
|
|
80
|
-
[--next <t> | --clear-next] status: ${
|
|
84
|
+
[--next <t> | --clear-next] status: ${Q.join(", ")}
|
|
81
85
|
an empty --next "" clears it, as --clear-next does
|
|
82
86
|
naming another unfinished card requires it in --deps
|
|
83
87
|
[--pr <n> | --pr <owner/repo#n>]... adds a PR to the card, a bare number in the board's repo;
|
|
@@ -97,7 +101,10 @@ command fails and changes nothing.
|
|
|
97
101
|
from another ask (use item relate-ask), and one with no
|
|
98
102
|
state when --state is missing
|
|
99
103
|
[--state ready|grilling|prototyping] what a card from an ask does next; it reads New until set.
|
|
100
|
-
ready refuses while the card has an open question
|
|
104
|
+
ready refuses while the card has an open question, and
|
|
105
|
+
a card without --size, a notes file, or the id of each
|
|
106
|
+
answered question in that file, since a builder starts
|
|
107
|
+
from it; it resolves those answered questions
|
|
101
108
|
[--held | --unheld | --pick-up-at <time>] holds or resumes the card on the owner's words; the board
|
|
102
109
|
server records the owner's own Hold and Resume taps. A later
|
|
103
110
|
--held/--unheld overrides a tap; --unheld also clears a held status.
|
|
@@ -110,6 +117,9 @@ command fails and changes nothing.
|
|
|
110
117
|
--held holds it with no time; --pick-up-at "" resumes it
|
|
111
118
|
[--ticket <id>] links a ticket in the board\u2019s source
|
|
112
119
|
[--title <t>] renames the card
|
|
120
|
+
[--check "<name> <args>"] adds or changes the code check on the card's owner step,
|
|
121
|
+
as builder done --check takes it, clearing a broken mark;
|
|
122
|
+
--check "" removes it. Refused on a card with no owner step
|
|
113
123
|
[--size small|large] [--model opus|sonnet] what builder pick reads while the hub's Claude model is
|
|
114
124
|
Auto: a small card gets Claude Sonnet, any other Opus, and
|
|
115
125
|
--model wins over --size; "" clears either
|
|
@@ -261,16 +271,20 @@ command fails and changes nothing.
|
|
|
261
271
|
"auto_build": false, "auto_pull": false,
|
|
262
272
|
"max_implementers": 10,
|
|
263
273
|
"auto_triage": false, "auto_review": false,
|
|
264
|
-
"headless_builders": false,
|
|
265
274
|
"idle_notification": false, "skip_draft": false,
|
|
266
|
-
"github_login": "owner"}
|
|
267
|
-
settings set auto-merge on|off on: the
|
|
275
|
+
"prs": true, "github_login": "owner"}
|
|
276
|
+
settings set auto-merge on|off on: the hub's tick merges an approved PR once CI is green and
|
|
268
277
|
the merge state is CLEAN. off (the default): the card
|
|
269
|
-
shows the owner a Merge button, and the
|
|
270
|
-
|
|
278
|
+
shows the owner a Merge button, and the board server merges
|
|
279
|
+
when it is tapped. Board Settings' toggle writes it too
|
|
271
280
|
settings set skip-draft on|off on: the hub's poll marks a card's draft PR ready for
|
|
272
281
|
review once its CI passes. off (the default): the draft
|
|
273
282
|
waits in In Review on the owner's review and Ready
|
|
283
|
+
settings set prs on|off on (the default): a board on GitHub opens a PR for each
|
|
284
|
+
card. off: no PR; the card lands on main and main is
|
|
285
|
+
pushed, which needs Push main after a land granted.
|
|
286
|
+
A local board never opens PRs. Without auto-merge, a
|
|
287
|
+
card with no PR waits in In Review for the owner's Merge
|
|
274
288
|
build <wave>/<id> --site <route> asks the hub to run this card's .kickflip/build; prints
|
|
275
289
|
--asked "<owner's words>" started and exits. install is an alias. Only the owner's
|
|
276
290
|
Serve / Build and Auto-build start a card run, so --asked
|
|
@@ -287,11 +301,12 @@ command fails and changes nothing.
|
|
|
287
301
|
settings set auto-build on|off on: each push builds its card. Off by default.
|
|
288
302
|
Main builds after every merge either way
|
|
289
303
|
settings set auto-pull on|off Auto implement Todo cards. on: on every board change the
|
|
290
|
-
board server
|
|
291
|
-
cards nothing blocks, holds or asks about, one
|
|
292
|
-
implementer slot (see max-implementers), each
|
|
293
|
-
none ready and nothing building,
|
|
294
|
-
top unready card
|
|
304
|
+
board server starts and launches the build of the top
|
|
305
|
+
ready Todo cards nothing blocks, holds or asks about, one
|
|
306
|
+
per free implementer slot (see max-implementers), each
|
|
307
|
+
once; with none ready and nothing building, it launches a
|
|
308
|
+
planner on the top unready card: a prototype when it is
|
|
309
|
+
prototyping, else a grill. Off by default.
|
|
295
310
|
Only on the owner's word
|
|
296
311
|
settings set max-implementers <1..99> Maximum parallel implementers, 10 by default: the cards
|
|
297
312
|
building or waiting in Orchestrator review the pull fills
|
|
@@ -302,17 +317,6 @@ command fails and changes nothing.
|
|
|
302
317
|
touches, asked} for one incoming PR at a time that touches
|
|
303
318
|
this board's cards or asks the owner for review. Off by
|
|
304
319
|
default. Only on the owner's word
|
|
305
|
-
settings set headless-builders on|off on: the board server launches each Claude build itself, as
|
|
306
|
-
claude -p in the card's worktree with a brief built from the
|
|
307
|
-
card's notes and rules, for the Implement tap, a queued start
|
|
308
|
-
and Auto implement's pull, instead of waking you with
|
|
309
|
-
card-build or card-pull; a codex pick, or a launch that
|
|
310
|
-
fails, still sends card-build. The builder lands its own
|
|
311
|
-
work; once you verify a handed-back build's calls and its
|
|
312
|
-
card has no open question, the server launches a headless
|
|
313
|
-
lander. item set --state ready then refuses a card without
|
|
314
|
-
--size, a notes file, or the id of each answered question in
|
|
315
|
-
that file. Off by default. Only on the owner's word
|
|
316
320
|
settings set auto-triage on|off on: once a night, from the middle of the hub's quiet hours
|
|
317
321
|
to their end, when two or more cards wait on open
|
|
318
322
|
questions, nothing builds and something changed since the
|
|
@@ -401,7 +405,20 @@ command fails and changes nothing.
|
|
|
401
405
|
then waits in In Review with it as its Next, its ticket
|
|
402
406
|
open, until the owner's Done; refused on a card already
|
|
403
407
|
done, merged or dropped. land takes it too
|
|
408
|
+
[--check "<name> <args>"] with --owner-step: a read-only check on the step's final
|
|
409
|
+
evidence, which the hub's poll runs on each check once the
|
|
410
|
+
code lands, closing the card when it passes: tag
|
|
411
|
+
<owner/repo> <pattern>, npm <package> <version-or-range>,
|
|
412
|
+
run <owner/repo> <workflow> (a successful run since it was
|
|
413
|
+
attached), gh <owner/repo> <api-path> <field>=<value>, or
|
|
414
|
+
url <address> [<status> | <text>]. An unknown check or bad
|
|
415
|
+
arguments are refused. Three runs in a row that can't run
|
|
416
|
+
mark it broken and append owner-check-broken {key, check,
|
|
417
|
+
error}; fix it with item set --check. land takes it too
|
|
404
418
|
builder takes --dir <dir> for a wave file in <dir>; its records go in <dir>/agents
|
|
419
|
+
lander <wave>/<id> relaunch the headless lander of a card it stopped on (a usage
|
|
420
|
+
pause ends one): the card goes back to checking and the
|
|
421
|
+
server launches a fresh lander; refused unless it is building
|
|
405
422
|
builder pick <wave>/<id> who builds the card, from the hub's Codex share and the pause
|
|
406
423
|
line, and for claude which model, from the hub's Claude model
|
|
407
424
|
and the card's --size and --model: "codex: <reason>" or
|
|
@@ -459,7 +476,7 @@ command fails and changes nothing.
|
|
|
459
476
|
branch push (and draft PR) instead of the fast-forward.
|
|
460
477
|
--owner-step <t>, as builder done takes it, keeps the
|
|
461
478
|
landed card in In Review on that step until the owner's
|
|
462
|
-
Done. KICKFLIP_RESTART_CMD='["<cmd>"]' replaces
|
|
479
|
+
Done, or until its --check passes. KICKFLIP_RESTART_CMD='["<cmd>"]' replaces
|
|
463
480
|
the restarts, called with hub or board <route>
|
|
464
481
|
merge <wave>/<id> [--after-approval "<why trivial>"] merges the card's PRs (gh pr merge --squash) in its order and
|
|
465
482
|
[--confirmed "<owner's words>"] records it merged, once GitHub reads every open one approved,
|
|
@@ -469,7 +486,12 @@ command fails and changes nothing.
|
|
|
469
486
|
commits after the newest approval unless --after-approval
|
|
470
487
|
says why they're trivial (its closing line says so), and a
|
|
471
488
|
card with manual steps unless --confirmed quotes the owner.
|
|
472
|
-
A refusal also goes in the thread while the owner's Merge waits
|
|
489
|
+
A refusal also goes in the thread while the owner's Merge waits.
|
|
490
|
+
On a card with no PR waiting for the owner's Merge, which
|
|
491
|
+
the board server runs on the tap: rebases onto the base as
|
|
492
|
+
it is now, reruns the tests, fast-forwards, closes the card
|
|
493
|
+
and pushes when Push main is on; a conflict or failing
|
|
494
|
+
tests launch a headless lander that lands it
|
|
473
495
|
arch file <wave>/<id> turns the architecture review audit card's answered findings
|
|
474
496
|
into cards in the review's wave: titled with the picked
|
|
475
497
|
option's label, its detail, practice and the owner's note as
|
|
@@ -493,4 +515,4 @@ command fails and changes nothing.
|
|
|
493
515
|
(overnight, snoozed or the weekend). --urgent: a
|
|
494
516
|
production fault only; it beats those holds. While
|
|
495
517
|
notifications are off every push is dropped, urgent too
|
|
496
|
-
KICKFLIP_PUSH_CMD='["<cmd>","<arg>"]' replaces \`node ../../bin/kickflip.mjs push\`; the flags follow it`;class d extends Error{static{i(this,"UsageError")}}const Z=new Set(["clear-status","clear-next","urgent","now","clear-eta","held","unheld","idle","hub","clear-install","force","worktree","dry-run","dispatch"]);function Y(e){const s=Object.create(null),t=[];for(let a=0;a<e.length;a++){const r=e[a];if(!r.startsWith("--")){t.push(r);continue}const l=r.slice(2);if(Z.has(l)){if(Object.hasOwn(s,l))throw new d(`--${l} is given twice`);s[l]=!0}else if(a+1<e.length)(s[l]||=[]).push(e[++a]);else throw new d(`--${l} needs a value`)}return{flags:s,rest:t}}i(Y,"parse");function ee(e,s,t){const a=new Set(s.filter(o=>o.startsWith("...")).map(o=>o.slice(3))),r=new Set(s.map(o=>o.replace(/^\.\.\./,""))),l=Object.keys(t).filter(o=>!r.has(o));if(l.length)throw new d(`${e} does not take ${l.map(o=>"--"+o).join(", ")}`);return Object.fromEntries(Object.entries(t).map(([o,c])=>{if(c===!0||a.has(o))return[o,c];if(c.length>1)throw new d(`--${o} is given twice`);return[o,c[0]]}))}i(ee,"flagsOf");const te=["html","text","note","notes","summary","title","body","next","progress","install-label","confirmed","asked"],se=/^(?:\/|~\/|\.{1,2}\/)\S*\.(?:html?|md|json|txt)$/;function ae(e){const s=e.trim();if(!s||/\s/.test(s))return!1;if(se.test(s))return!0;try{return w.statSync(s).isFile()}catch{return!1}}i(ae,"looksLikePath");function ne(e,s){for(const t of te){if(typeof s[t]!="string"||!ae(s[t]))continue;const a=e.includes("file")?`; for a file use --file ${s[t].trim()}`:"";throw new d(`--${t} takes text, not a file path (${s[t].trim()})${a}`)}if(typeof s.file=="string"&&s.file!=="-"&&!w.existsSync(s.file))throw new d(`--file ${s.file}: no such file`)}i(ne,"checkInputs");const f=i(e=>w.readFileSync(e==="-"?0:e,"utf8"),"readInput");function $(e){if(!e)throw new d("--file <path> is required");try{return JSON.parse(f(e))}catch(s){throw new n.BoardError(`${e}: ${s.message}`)}}i($,"readJSONInput");function A(e){if(e.plan!==void 0&&e["notes-file"]!==void 0)throw new d("pass --notes-file or --plan, not both");return e["notes-file"]??e.plan}i(A,"notesFileOf");function P(e){if(e.note!==void 0&&e.file!==void 0)throw new d("pass --note or --file, not both");return e.file===void 0?e.note:f(e.file)}i(P,"noteOf");const g={pr:[["wave/id"],[],async(e,[s])=>({lines:(await n.prReport(e,s)).map(t=>JSON.stringify(t))})],"pr comment":[["pr"],["body"],(e,[s],t)=>{if(t.body===void 0)throw new d('pr comment needs --body "<what Claude says>"');return n.postPR(e,s,{body:t.body})}],"pr reply":[["pr","comment-id"],["body"],(e,[s,t],a)=>{if(a.body===void 0)throw new d('pr reply needs --body "<what Claude says>"');return n.postPR(e,s,{replyTo:t,body:a.body})}],reply:[["eventId"],["html","file"],(e,[s],t)=>{if(t.html===void 0==(t.file===void 0))throw new d("reply takes --html or --file");return n.writeReply(e,s,t.html??f(t.file))}],thread:[["wave/id"],["text","link","who","via"],(e,[s],t)=>n.appendThread(e,s,t)],"topic add":[["id"],["title","summary"],(e,[s],t)=>n.addTopic(e,{id:s,...t})],"question add":[[],["file"],async(e,s,t)=>{const a=$(t.file);return{...await n.addQuestions(e,a),asked:[a].flat()}}],"question resolve":[["id"],["note"],(e,[s],t)=>n.resolveQuestion(e,s,t.note)],"prototype add":[["topic"],["title","...take","card"],(e,[s],t)=>n.addPrototype(e,{topic:s,title:t.title,card:t.card,takes:t.take})],"prototype list":[[],[],e=>n.listPrototypes(e)],"prototype rm":[["topic"],[],(e,[s])=>n.removePrototype(e,s)],"item set":[["wave/id"],["status","clear-status","next","clear-next","...pr","pushed","gh-since","from-ask","state","held","unheld","pick-up-at","notes-file","plan","deps","parent","related","install","install-sha","install-label","clear-install","title","size","model","force","ticket","text"],async(e,[s],t,a)=>(t.status==="done"&&e&&await n.checkNotReopened(e,s),n.setItem(e,s,{status:t.status,clearStatus:t["clear-status"],next:t.next,clearNext:t["clear-next"],pr:t.pr,pushed:t.pushed,ghSince:t["gh-since"],fromAsk:t["from-ask"],state:t.state,held:t.held,unheld:t.unheld,pickUpAt:t["pick-up-at"],notesFile:A(t),deps:t.deps,parent:t.parent,related:t.related,install:t.install,installSha:t["install-sha"],installLabel:t["install-label"],clearInstall:t["clear-install"],title:t.title,size:t.size,model:t.model,force:t.force,ticket:t.ticket,text:t.text},a))],"item add":[["wave[/id]"],["title","notes","deps","notes-file","plan","ticket","pick-up-at","parent","size","model"],(e,[s],t,a)=>n.addItem(e,s,{title:t.title,notes:t.notes,deps:t.deps,notesFile:A(t),ticket:t.ticket,pickUpAt:t["pick-up-at"],parent:t.parent,size:t.size,model:t.model},a)],"item relate-ask":[["eventId","wave/id"],["summary"],(e,[s,t],a)=>n.relateAsk(e,s,t,a.summary)],"ask progress":[["eventId"],["next"],(e,[s],t)=>n.markAsk(e,s,"progress",t.next)],"ask done":[["eventId"],[],(e,[s])=>n.markAsk(e,s,"done")],"ask unfold":[["eventId"],[],(e,[s])=>n.unfoldAsk(e,s)],"ask move":[["eventId"],["to"],(e,[s],t)=>n.moveAsk(e,s,t.to)],"tickets backfill":[[],["dry-run"],(e,s,t,a)=>n.backfillTickets(e,{dryRun:t["dry-run"]},a)],"lineup create":[["slug"],["title","goal","...phase","note","file"],(e,[s],t)=>n.createLineup(e,s,{title:t.title,goal:t.goal,phases:t.phase,note:P(t)})],"lineup add":[["slug","wave/id"],["after"],(e,[s,t],a)=>n.changeLineup(e,s,{add:t,after:a.after})],"lineup remove":[["slug","wave/id"],[],(e,[s,t])=>n.changeLineup(e,s,{remove:t})],"lineup set":[["slug"],["title","goal","note","file","...phase"],(e,[s],t)=>n.changeLineup(e,s,{title:t.title,goal:t.goal,note:P(t),phases:t.phase})],"lineup finish":[["slug"],[],(e,[s])=>n.finishLineup(e,s)],slack:[[],[],()=>({lines:[JSON.stringify(L())]})],"slack set":[[],["self-dm","user","domain","timezone"],async(e,s,t)=>{const a={"self-dm":"self_dm",user:"owner_slack_user",domain:"slack_domain",timezone:"timezone"};try{return{lines:[JSON.stringify(await D(Object.fromEntries(Object.entries(t).map(([r,l])=>[a[r],l]))))]}}catch(r){throw new n.BoardError(r.message)}}],integrations:[[],[],async()=>({lines:[JSON.stringify(await B.rows())]})],"integrations report":[[],["slack"],async(e,s,t)=>{if(!["yes","no"].includes(t.slack))throw new d("integrations report takes --slack yes|no");if(!e)throw new n.BoardError("integrations report needs --site <route>");const a=m.basename(e);return H(t.slack==="yes",{route:a,title:V().sites?.[a]?.title||(await n.boardConfig(e)).title}),{lines:[`Slack connector reported: ${t.slack}.`]}}],"review sent":[["wave/id","reviewer"],["ts","channel"],(e,[s,t],a)=>n.reviewSent(e,s,t,a)],"review react":[["wave/id","reviewer","emoji"],[],(e,[s,t,a])=>n.reviewReact(e,s,t,a)],"review record":[["pr"],["state","text","file","url"],(e,[s],t)=>{if(t.text===void 0==(t.file===void 0))throw new d("review record takes --text or --file");return n.recordReview(e,s,{state:t.state,text:t.text??f(t.file),url:t.url})}],"review digest":[[],[],async(e,s,t,a)=>({lines:[JSON.stringify(await n.reviewDigest(e,a))]})],"review due":[[],[],async(e,s,t,a)=>({lines:[JSON.stringify(await n.reviewDue(e,a))]})],"review digest sent":[["reviewer"],["ts","channel","items"],(e,[s],t,a)=>n.reviewDigestSent(e,s,t,a)],"reviewers set":[[],["file"],(e,s,t)=>n.setReviewers(e,$(t.file))],"reviewers add":[[],["file"],(e,s,t)=>n.addReviewers(e,$(t.file))],"reviewers remove":[["...name"],[],(e,s)=>n.removeReviewers(e,s)],"rules list":[[],[],e=>n.listRules(e)],"rules add":[[],["text","hub","by"],(e,s,t)=>n.addRuleTo(e,t)],"rules edit":[["id"],["text","hub"],(e,[s],t)=>n.editRuleIn(e,s,t)],"rules remove":[["id"],["hub"],(e,[s],t)=>n.removeRuleFrom(e,s,t)],settings:[[],[],async e=>({lines:[JSON.stringify(await n.readBoardSettings(e))]})],build:[["wave/id"],["asked"],async(e,[s],t)=>{if(!e)throw new n.BoardError("build needs --site <route>");n.parseKey(s);const a=await n.askedGate(e,s,t.asked);try{const r=await fetch(`http://127.0.0.1:${process.env.KICKFLIP_HUB_PORT||4500}/_hub/build`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({route:m.basename(e),key:s}),signal:AbortSignal.timeout(1e4)}),l=await r.json();if(!r.ok)throw new n.BoardError(l.error||"The hub refused the build.");return{lines:["started",...(await n.appendThread(e,s,{text:a})).lines]}}catch(r){throw r instanceof n.BoardError?r:new n.BoardError(`Could not start the build: ${r.message}`)}}],"main set":[[],["install","install-sha"],(e,s,t)=>n.setMainInstall(e,{install:t.install,installSha:t["install-sha"]})],"settings set":[["name","value"],[],(e,[s,t])=>n.setBoardSetting(e,s,t)],"builder start":[["wave/id"],["label","phase","progress","dir","agent","kind","model","eta","worktree","repo","dispatch"],(e,[s],{worktree:t,repo:a,dispatch:r,...l})=>{if(a!==void 0&&!t)throw new d("--repo goes with --worktree");const o=i(b=>z(e,s.split("/")[1],{repo:a,...b}),"make"),c=e&&l.dir===void 0&&Q(e);return n.startBuilder(e,s,{...l,dispatch:r?"session":void 0,worktree:t?o:void 0,ownWorktree:c?o:void 0})}],"builder progress":[["wave/id","...text?"],["phase","text","dir","agent","eta","clear-eta"],(e,[s,...t],a)=>{if(t.length&&a.text!==void 0)throw new d("builder progress takes its text once: as words or as --text");if(!t.length&&a.text===void 0)throw new d("builder progress needs its text, as words or as --text");return n.builderProgress(e,s,a.text??t.join(" "),{...a,clearEta:a["clear-eta"]})}],"builder done":[["wave/id"],["dir","agent","next","owner-step","text"],async(e,[s],{"owner-step":t,text:a,...r})=>{if(t!==void 0&&(!e||r.dir))throw new d("--owner-step goes on a board card: pass --site, not --dir");t!==void 0&&await n.setItem(e,s,{ownerStep:t});const l=e&&!r.dir,o=l&&await n.handBackCalls(e,s,r);if(o)return o;if(l&&r.next===void 0&&await n.finishesCard(e,s,r)){if(a===void 0)throw new n.BoardError(`builder done finishes ${s}: pass --text "<what you checked>", or --next "<what happens next>" to stop without finishing`);return x(e,s,{text:a,ownerStep:t})}if(a!==void 0)throw new d("--text goes on a builder done that finishes the card: the main build, with a worktree and no --next");return n.stopBuilder(e,s,{...r,requireNext:!0})}],"builder pick":[["wave/id"],[],(e,[s])=>n.pickBuilder(e,s)],"builder call":[["wave/id","...text"],["dir","agent"],(e,[s,...t],a)=>n.recordCall(e,s,t.join(" "),a)],"builder calls":[["wave/id"],["dir","agent"],(e,[s],t)=>n.listCalls(e,s,t)],"builder ask":[["wave/id","...text"],["agent"],(e,[s,...t],a)=>n.askOrchestrator(e,s,t.join(" "),a)],"builder answer":[["wave/id","ask"],["text"],(e,[s,t],a)=>n.answerBuilder(e,s,t,a.text)],"builder defer":[["wave/id","ask"],["text"],(e,[s,t],a)=>n.answerBuilder(e,s,t,a.text,{deferred:!0})],"builder asks":[["wave/id"],[],(e,[s])=>n.listAsks(e,s)],"builder verified":[["wave/id"],["dir","agent","note"],async(e,[s],t)=>{await n.verifyCalls(e,s,t);const a=e&&!t.dir?await n.verifiedFinish(e,s):null;if(!a)return{};if(a.waits.length)return{lines:[`${await n.cardName(e,s)} finishes once the owner answers ${a.waits.join(", ")}`]};try{return await x(e,s,{text:t.note??"Judgment calls verified."})}catch(r){throw r instanceof n.BoardError?Object.assign(new n.BoardError(`judgment calls verified, but ${await n.cardName(e,s)} didn't finish: ${r.message}`),{exitCode:r.exitCode}):r}}],land:[["wave/id"],["repo","dir","text","owner-step"],async(e,[s],t)=>{const a=s.split("/")[1];if(e&&t.text===void 0)throw new d(`land needs --text "<what was checked>", which closes ${s} as done once it lands`);if(!e&&t.text!==void 0)throw new d("--text closes the card, which needs --site");if(!e&&t["owner-step"]!==void 0)throw new d("--owner-step keeps a board card open, which needs --site");return e&&t.dir===void 0?x(e,s,{text:t.text,ownerStep:t["owner-step"],repo:t.repo}):M(e?await n.cardName(e,s):s,async()=>{e&&await n.checkClose(e,s,t.text,t["owner-step"]),await n.checkCallsVerified(e,s,{dir:t.dir});const r=await n.recordedWorktree(e,s,{dir:t.dir}),{repo:l,base:o}=G(e,{repo:t.repo??r?.repo});return J({path:r?.path??m.join(F(),a),branch:r?.branch??a,repo:m.resolve(l),base:o,ref:e?await n.cardName(e,s):void 0}),e?n.setItem(e,s,{status:"done",text:t.text,ownerStep:t["owner-step"]}):{lines:[]}})}],merge:[["wave/id"],["after-approval","confirmed"],(e,[s],t)=>n.mergeCard(e,s,{afterApproval:t["after-approval"],confirmed:t.confirmed})],"arch file":[["wave/id"],[],(e,[s])=>e?U(e,s):Promise.reject(new n.BoardError("arch file needs --site <route>"))],checked:[[],["every","idle"],(e,s,t)=>n.writeChecked(e,t.every,{idle:t.idle})],push:[[],["kind","title","body","url","tag","urgent","now"],(e,s,t)=>{if(t.kind!=="fault"&&t.kind!=="reminder")throw new d(`a session pushes only --kind fault or reminder${t.kind===void 0||t.kind===!0?"":`, not "${t.kind}"`}: the board server pushes Action needed, and news goes on the board`);if(t.title===void 0)throw new d("push needs --title");return n.sendPush(e,t)}]};g.install=g.build;const h=i((e,s)=>n.cardName(e,s),"card"),C=i((e,s)=>`${s}${e.length===1?"":"s"} ${e.join(", ")}`,"plural"),q=i(e=>e.agent===void 0||e.agent==="main"?"builder":`builder ${e.agent}`,"builderOf");function ie(e){const s=e["pick-up-at"];return[e.status,e["clear-status"]&&"status cleared",e.next!==void 0&&(e.next.trim()?"next line set":"next line cleared"),e["clear-next"]&&"next line cleared",e.pr!==void 0&&`PR #${e.pr}`,e.pushed!==void 0&&`pushed ${e.pushed}`,e["gh-since"]&&`GitHub read to ${e["gh-since"]}`,e["from-ask"]&&`from task ${e["from-ask"]}`,e.state,e.held&&"held",e.unheld&&"resumed",s!==void 0&&(s?`held until ${s}`:"resumed"),A(e)!==void 0&&"notes recorded",e.deps!==void 0&&(e.deps?`blocked by ${e.deps}`:"deps cleared"),e.parent!==void 0&&(e.parent?`parent ${e.parent}`:"parent cleared"),e.related!==void 0&&(e.related?`related to ${e.related.split(",").join(", ")}`:"related cleared"),e.install!==void 0&&"install published",e["clear-install"]&&"install cleared",e.title!==void 0&&"renamed",e.ticket!==void 0&&`ticket ${e.ticket}`].filter(Boolean).join(", ")}i(ie,"itemChanges");const re={reply:i(async(e,[s])=>`reply written to ${await n.eventCard(e,s)}`,"reply"),thread:i(async(e,[s])=>`thread line on ${await h(e,s)}`,"thread"),"topic add":i((e,[s])=>`topic ${s} added`,"topic add"),"question add":i(async(e,s,t,{asked:a})=>{const r=[...new Set(a.map(o=>o.topic))],l=r.length!==1?"":r[0].startsWith("item:")?` on ${await h(e,r[0].slice(5))}`:` on topic ${r[0]}`;return`${C(a.map(o=>o.id),"question")} added${l}`},"question add"),"question resolve":i((e,[s])=>`question ${s} resolved`,"question resolve"),"prototype add":i(async(e,[s],t)=>`prototype ${s} recorded${t.card?` for ${await h(e,t.card)}`:""}`,"prototype add"),"prototype rm":i((e,[s])=>`prototype ${s} removed`,"prototype rm"),"item set":i(async(e,[s],t)=>`${await h(e,s)}: ${ie(t)}`,"item set"),"item add":i(async(e,s,t,{key:a})=>`${await h(e,a)} added as ${a}`,"item add"),"item relate-ask":i(async(e,[s,t])=>`task ${s} folded into ${await h(e,t)}`,"item relate-ask"),"ask progress":i(async(e,[s])=>`${await h(e,`ask:${s}`)}: in progress`,"ask progress"),"ask done":i(async(e,[s])=>`${await h(e,`ask:${s}`)}: done`,"ask done"),"ask unfold":i((e,[s])=>`task ${s} back on a card of its own`,"ask unfold"),"ask move":i((e,[s],t)=>`task ${s} moved to /${t.to}`,"ask move"),checked:i((e,s,t,{every:a})=>a===null?"GitHub checked; no open PR":`GitHub checked; next in ${a} min`,"checked"),"lineup create":i((e,[s])=>`lineup ${s} created`,"lineup create"),"lineup add":i(async(e,[s,t])=>`${await h(e,t)} added to lineup ${s}`,"lineup add"),"lineup remove":i(async(e,[s,t])=>`${await h(e,t)} taken out of lineup ${s}`,"lineup remove"),"lineup set":i((e,[s])=>`lineup ${s} updated`,"lineup set"),"lineup finish":i((e,[s])=>`lineup ${s} finished`,"lineup finish"),"review sent":i(async(e,[s,t])=>`review link to ${t} recorded on ${await h(e,s)}`,"review sent"),"review react":i(async(e,[s,t,a])=>`${a} on ${t}'s review link recorded on ${await h(e,s)}`,"review react"),"review digest sent":i((e,[s])=>`digest to ${s} recorded`,"review digest sent"),"review record":i((e,[s],t)=>`review of PR #${s} recorded: ${t.state==="needs"?"needs you":"posted"}`,"review record"),"reviewers set":i(()=>"reviewers replaced","reviewers set"),"reviewers add":i(()=>"reviewers added","reviewers add"),"reviewers remove":i((e,s)=>`${C(s,"reviewer")} removed`,"reviewers remove"),"rules add":i((e,s,t)=>`${t.hub?"hub":"board"} rule added`,"rules add"),"rules edit":i((e,[s],t)=>`${t.hub?"hub":"board"} rule ${s} reworded`,"rules edit"),"rules remove":i((e,[s],t)=>`${t.hub?"hub":"board"} rule ${s} removed`,"rules remove"),"builder start":i(async(e,[s],t,a)=>`${await h(e,s)}: ${a.alreadyOpen?"already open: the Implement tap opened its record; launch the agent, whose own builder start takes it up":`${q(t)} started`}`,"builder start"),"builder progress":i(async(e,[s])=>`progress line on ${await h(e,s)}`,"builder progress"),"builder done":i(async(e,[s],t,a)=>`${await h(e,s)}: ${a?.said??`${q(t)} done${t["owner-step"]===void 0?"":`; owner step "${t["owner-step"].trim()}"`}`}`,"builder done"),"builder call":i(async(e,[s])=>`judgment call recorded on ${await h(e,s)}`,"builder call"),"builder ask":i(async(e,[s],t,{id:a})=>`${await h(e,s)}: asked the orchestrator, ${a}`,"builder ask"),"builder answer":i(async(e,[s,t])=>`${await h(e,s)}: ${t} answered`,"builder answer"),"builder defer":i(async(e,[s,t])=>`${await h(e,s)}: ${t} deferred to the owner`,"builder defer"),"builder verified":i(async(e,[s],t,a)=>`${await h(e,s)}: judgment calls verified${a?.said?`; ${a.said}`:""}`,"builder verified"),land:i(async(e,[s],t,a)=>e?a?.said?`${await h(e,s)}: ${a.said}`:t["owner-step"]===void 0?`${await h(e,s)}: landed and done`:`${await h(e,s)}: landed; waits on the owner step "${t["owner-step"].trim()}"`:null,"land"),merge:i(async(e,[s])=>`${await h(e,s)}: merged`,"merge"),"arch file":i(async(e,[s])=>`findings of ${await h(e,s)} filed`,"arch file")};async function oe(e,s={}){try{const{flags:t,rest:a}=Y(e),r=[3,2,1].map(u=>a.slice(0,u).join(" ")).find(u=>y(g,u)),l=y(g,r);if(!l)throw new d(a.length?`unknown command "${a.slice(0,2).join(" ")}"`:"no command given");const[o,c,b]=l,p=a.slice(r.split(" ").length),_=o.at(-1)?.startsWith("..."),N=o.at(-1)?.endsWith("?")?o.length-1:o.length;if(_?p.length<N:p.length!==o.length)throw new d(`${r} takes ${o.map(u=>`<${u.replace(/^\.\.\.|\?$/g,"")}>`).join(" ")||"no arguments"}`);const{site:S,...T}=t;if(S?.length>1)throw new d("--site is given twice");const O=S?.[0],k=ee(r,c,T);ne(c,k);const R=O===void 0?null:await n.siteDir(O),v=await b(R,p,k,s),I=y(re,r),j=I&&await I(R,p,k,v);j&&console.log(j);for(const u of typeof v=="object"&&v?.lines||[])console.log(u);return 0}catch(t){if(t instanceof d)return console.error(`${t.message} (node board.mjs --help lists the commands)`),2;if(t instanceof n.BoardError)return console.error(t.message),t.exitCode??1;throw t}}if(i(oe,"main"),process.argv[1]&&import.meta.url===W(w.realpathSync(process.argv[1])).href){const e=process.argv.slice(2);!e.length||e.includes("--help")||e[0]==="help"?console.log(X):process.exitCode=await oe(e)}export{X as USAGE,oe as main};
|
|
518
|
+
KICKFLIP_PUSH_CMD='["<cmd>","<arg>"]' replaces \`node ../../bin/kickflip.mjs push\`; the flags follow it`;class l extends Error{static{r(this,"UsageError")}}const Z=new Set(["clear-status","clear-next","urgent","now","clear-eta","held","unheld","idle","hub","clear-install","force","worktree","dry-run","dispatch"]);function ee(e){const s=Object.create(null),t=[];for(let a=0;a<e.length;a++){const i=e[a];if(!i.startsWith("--")){t.push(i);continue}const d=i.slice(2);if(Z.has(d)){if(Object.hasOwn(s,d))throw new l(`--${d} is given twice`);s[d]=!0}else if(a+1<e.length)(s[d]||=[]).push(e[++a]);else throw new l(`--${d} needs a value`)}return{flags:s,rest:t}}r(ee,"parse");function te(e,s,t){const a=new Set(s.filter(o=>o.startsWith("...")).map(o=>o.slice(3))),i=new Set(s.map(o=>o.replace(/^\.\.\./,""))),d=Object.keys(t).filter(o=>!i.has(o));if(d.length)throw new l(`${e} does not take ${d.map(o=>"--"+o).join(", ")}`);return Object.fromEntries(Object.entries(t).map(([o,c])=>{if(c===!0||a.has(o))return[o,c];if(c.length>1)throw new l(`--${o} is given twice`);return[o,c[0]]}))}r(te,"flagsOf");const se=["html","text","note","notes","summary","title","body","next","progress","install-label","confirmed","asked"],ae=/^(?:\/|~\/|\.{1,2}\/)\S*\.(?:html?|md|json|txt)$/;function ne(e){const s=e.trim();if(!s||/\s/.test(s))return!1;if(ae.test(s))return!0;try{return m.statSync(s).isFile()}catch{return!1}}r(ne,"looksLikePath");function re(e,s){for(const t of se){if(typeof s[t]!="string"||!ne(s[t]))continue;const a=e.includes("file")?`; for a file use --file ${s[t].trim()}`:"";throw new l(`--${t} takes text, not a file path (${s[t].trim()})${a}`)}if(typeof s.file=="string"&&s.file!=="-"&&!m.existsSync(s.file))throw new l(`--file ${s.file}: no such file`)}r(re,"checkInputs");const f=r(e=>m.readFileSync(e==="-"?0:e,"utf8"),"readInput");function $(e){if(!e)throw new l("--file <path> is required");try{return JSON.parse(f(e))}catch(s){throw new n.BoardError(`${e}: ${s.message}`)}}r($,"readJSONInput");function R(e){if(e.plan!==void 0&&e["notes-file"]!==void 0)throw new l("pass --notes-file or --plan, not both");return e["notes-file"]??e.plan}r(R,"notesFileOf");function O(e){if(e.note!==void 0&&e.file!==void 0)throw new l("pass --note or --file, not both");return e.file===void 0?e.note:f(e.file)}r(O,"noteOf");const k={pr:[["wave/id"],[],async(e,[s])=>({lines:(await n.prReport(e,s)).map(t=>JSON.stringify(t))})],"pr comment":[["pr"],["body"],(e,[s],t)=>{if(t.body===void 0)throw new l('pr comment needs --body "<what Claude says>"');return n.postPR(e,s,{body:t.body})}],"pr reply":[["pr","comment-id"],["body"],(e,[s,t],a)=>{if(a.body===void 0)throw new l('pr reply needs --body "<what Claude says>"');return n.postPR(e,s,{replyTo:t,body:a.body})}],reply:[["eventId"],["html","file"],(e,[s],t)=>{if(t.html===void 0==(t.file===void 0))throw new l("reply takes --html or --file");return n.writeReply(e,s,t.html??f(t.file))}],thread:[["wave/id"],["text","link","who","via"],(e,[s],t)=>n.appendThread(e,s,t)],"topic add":[["id"],["title","summary"],(e,[s],t)=>n.addTopic(e,{id:s,...t})],"question add":[[],["file"],async(e,s,t)=>{const a=$(t.file);return{...await n.addQuestions(e,a),asked:[a].flat()}}],"question resolve":[["id"],["note"],(e,[s],t)=>n.resolveQuestion(e,s,t.note)],"prototype add":[["topic"],["title","...take","card"],(e,[s],t)=>n.addPrototype(e,{topic:s,title:t.title,card:t.card,takes:t.take})],"prototype list":[[],[],e=>n.listPrototypes(e)],"prototype rm":[["topic"],[],(e,[s])=>n.removePrototype(e,s)],"item set":[["wave/id"],["status","clear-status","next","clear-next","...pr","pushed","gh-since","from-ask","state","held","unheld","pick-up-at","notes-file","plan","deps","parent","related","install","install-sha","install-label","clear-install","title","size","model","force","ticket","text","check"],async(e,[s],t,a)=>(t.status==="done"&&e&&await n.checkNotReopened(e,s),n.setItem(e,s,{status:t.status,clearStatus:t["clear-status"],next:t.next,clearNext:t["clear-next"],pr:t.pr,pushed:t.pushed,ghSince:t["gh-since"],fromAsk:t["from-ask"],state:t.state,held:t.held,unheld:t.unheld,pickUpAt:t["pick-up-at"],notesFile:R(t),deps:t.deps,parent:t.parent,related:t.related,install:t.install,installSha:t["install-sha"],installLabel:t["install-label"],clearInstall:t["clear-install"],title:t.title,size:t.size,model:t.model,force:t.force,ticket:t.ticket,text:t.text,check:t.check},a))],"item add":[["wave[/id]"],["title","notes","deps","notes-file","plan","ticket","pick-up-at","parent","size","model"],(e,[s],t,a)=>n.addItem(e,s,{title:t.title,notes:t.notes,deps:t.deps,notesFile:R(t),ticket:t.ticket,pickUpAt:t["pick-up-at"],parent:t.parent,size:t.size,model:t.model},a)],"item relate-ask":[["eventId","wave/id"],["summary"],(e,[s,t],a)=>n.relateAsk(e,s,t,a.summary)],"ask progress":[["eventId"],["next"],(e,[s],t)=>n.markAsk(e,s,"progress",t.next)],"ask done":[["eventId"],[],(e,[s])=>n.markAsk(e,s,"done")],"ask unfold":[["eventId"],[],(e,[s])=>n.unfoldAsk(e,s)],"ask move":[["eventId"],["to"],(e,[s],t)=>n.moveAsk(e,s,t.to)],"tickets backfill":[[],["dry-run"],(e,s,t,a)=>n.backfillTickets(e,{dryRun:t["dry-run"]},a)],"lineup create":[["slug"],["title","goal","...phase","note","file"],(e,[s],t)=>n.createLineup(e,s,{title:t.title,goal:t.goal,phases:t.phase,note:O(t)})],"lineup add":[["slug","wave/id"],["after"],(e,[s,t],a)=>n.changeLineup(e,s,{add:t,after:a.after})],"lineup remove":[["slug","wave/id"],[],(e,[s,t])=>n.changeLineup(e,s,{remove:t})],"lineup set":[["slug"],["title","goal","note","file","...phase"],(e,[s],t)=>n.changeLineup(e,s,{title:t.title,goal:t.goal,note:O(t),phases:t.phase})],"lineup finish":[["slug"],[],(e,[s])=>n.finishLineup(e,s)],slack:[[],[],()=>({lines:[JSON.stringify(L())]})],"slack set":[[],["self-dm","user","domain","timezone"],async(e,s,t)=>{const a={"self-dm":"self_dm",user:"owner_slack_user",domain:"slack_domain",timezone:"timezone"};try{return{lines:[JSON.stringify(await D(Object.fromEntries(Object.entries(t).map(([i,d])=>[a[i],d]))))]}}catch(i){throw new n.BoardError(i.message)}}],integrations:[[],[],async()=>({lines:[JSON.stringify(await B.rows())]})],"integrations report":[[],["slack"],async(e,s,t)=>{if(!["yes","no"].includes(t.slack))throw new l("integrations report takes --slack yes|no");if(!e)throw new n.BoardError("integrations report needs --site <route>");const a=g.basename(e);return H(t.slack==="yes",{route:a,title:X().sites?.[a]?.title||(await n.boardConfig(e)).title}),{lines:[`Slack connector reported: ${t.slack}.`]}}],"review sent":[["wave/id","reviewer"],["ts","channel"],(e,[s,t],a)=>n.reviewSent(e,s,t,a)],"review react":[["wave/id","reviewer","emoji"],[],(e,[s,t,a])=>n.reviewReact(e,s,t,a)],"review record":[["pr"],["state","text","file","url"],(e,[s],t)=>{if(t.text===void 0==(t.file===void 0))throw new l("review record takes --text or --file");return n.recordReview(e,s,{state:t.state,text:t.text??f(t.file),url:t.url})}],"review digest":[[],[],async(e,s,t,a)=>({lines:[JSON.stringify(await n.reviewDigest(e,a))]})],"review due":[[],[],async(e,s,t,a)=>({lines:[JSON.stringify(await n.reviewDue(e,a))]})],"review digest sent":[["reviewer"],["ts","channel","items"],(e,[s],t,a)=>n.reviewDigestSent(e,s,t,a)],"reviewers set":[[],["file"],(e,s,t)=>n.setReviewers(e,$(t.file))],"reviewers add":[[],["file"],(e,s,t)=>n.addReviewers(e,$(t.file))],"reviewers remove":[["...name"],[],(e,s)=>n.removeReviewers(e,s)],"rules list":[[],[],e=>n.listRules(e)],"rules add":[[],["text","hub","by"],(e,s,t)=>n.addRuleTo(e,t)],"rules edit":[["id"],["text","hub"],(e,[s],t)=>n.editRuleIn(e,s,t)],"rules remove":[["id"],["hub"],(e,[s],t)=>n.removeRuleFrom(e,s,t)],settings:[[],[],async e=>({lines:[JSON.stringify(await n.readBoardSettings(e))]})],build:[["wave/id"],["asked"],async(e,[s],t)=>{if(!e)throw new n.BoardError("build needs --site <route>");n.parseKey(s);const a=await n.askedGate(e,s,t.asked);try{const i=await fetch(`http://127.0.0.1:${process.env.KICKFLIP_HUB_PORT||4500}/_hub/build`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({route:g.basename(e),key:s}),signal:AbortSignal.timeout(1e4)}),d=await i.json();if(!i.ok)throw new n.BoardError(d.error||"The hub refused the build.");return{lines:["started",...(await n.appendThread(e,s,{text:a})).lines]}}catch(i){throw i instanceof n.BoardError?i:new n.BoardError(`Could not start the build: ${i.message}`)}}],"main set":[[],["install","install-sha"],(e,s,t)=>n.setMainInstall(e,{install:t.install,installSha:t["install-sha"]})],"settings set":[["name","value"],[],(e,[s,t])=>n.setBoardSetting(e,s,t)],"builder start":[["wave/id"],["label","phase","progress","dir","agent","kind","model","eta","worktree","repo","dispatch"],(e,[s],{worktree:t,repo:a,dispatch:i,...d})=>{if(a!==void 0&&!t)throw new l("--repo goes with --worktree");const o=r(p=>W(e,s.split("/")[1],{repo:a,...p}),"make"),c=e&&d.dir===void 0&&V(e);return n.startBuilder(e,s,{...d,dispatch:i?"session":void 0,worktree:t?o:void 0,ownWorktree:c?o:void 0})}],"builder progress":[["wave/id","...text?"],["phase","text","dir","agent","eta","clear-eta"],(e,[s,...t],a)=>{if(t.length&&a.text!==void 0)throw new l("builder progress takes its text once: as words or as --text");if(!t.length&&a.text===void 0)throw new l("builder progress needs its text, as words or as --text");return n.builderProgress(e,s,a.text??t.join(" "),{...a,clearEta:a["clear-eta"]})}],"builder done":[["wave/id"],["dir","agent","next","owner-step","check","text"],async(e,[s],{"owner-step":t,check:a,text:i,...d})=>{if(t!==void 0&&(!e||d.dir))throw new l("--owner-step goes on a board card: pass --site, not --dir");if(a!==void 0&&t===void 0)throw new l("--check goes with --owner-step: it is the code check that closes the card once the step is done");t!==void 0&&await n.setItem(e,s,{ownerStep:t,check:a});const o=e&&!d.dir,c=o&&await n.handBackCalls(e,s,d);if(c)return c;if(o&&d.next===void 0&&await n.finishesCard(e,s,d)){if(i===void 0)throw new n.BoardError(`builder done finishes ${s}: pass --text "<what you checked>", or --next "<what happens next>" to stop without finishing`);return x(e,s,{text:i,ownerStep:t})}if(i!==void 0)throw new l("--text goes on a builder done that finishes the card: the main build, with a worktree and no --next");return n.stopBuilder(e,s,{...d,requireNext:!0})}],"builder pick":[["wave/id"],[],(e,[s])=>n.pickBuilder(e,s)],"builder call":[["wave/id","...text"],["dir","agent"],(e,[s,...t],a)=>n.recordCall(e,s,t.join(" "),a)],"builder calls":[["wave/id"],["dir","agent"],(e,[s],t)=>n.listCalls(e,s,t)],"builder ask":[["wave/id","...text"],["agent"],(e,[s,...t],a)=>n.askOrchestrator(e,s,t.join(" "),a)],"builder answer":[["wave/id","ask"],["text"],(e,[s,t],a)=>n.answerBuilder(e,s,t,a.text)],"builder defer":[["wave/id","ask"],["text"],(e,[s,t],a)=>n.answerBuilder(e,s,t,a.text,{deferred:!0})],"builder asks":[["wave/id"],[],(e,[s])=>n.listAsks(e,s)],"builder verified":[["wave/id"],["dir","agent","note"],async(e,[s],t)=>{await n.verifyCalls(e,s,t);const a=e&&!t.dir?await n.verifiedFinish(e,s):null;if(!a)return{};if(a.waits.length)return{lines:[`${await n.cardName(e,s)} finishes once the owner answers ${a.waits.join(", ")}`]};try{return await x(e,s,{text:t.note??"Judgment calls verified."})}catch(i){throw i instanceof n.BoardError?Object.assign(new n.BoardError(`judgment calls verified, but ${await n.cardName(e,s)} didn't finish: ${i.message}`),{exitCode:i.exitCode}):i}}],land:[["wave/id"],["repo","dir","text","owner-step","check"],async(e,[s],t)=>{const a=s.split("/")[1];if(e&&t.text===void 0)throw new l(`land needs --text "<what was checked>", which closes ${s} as done once it lands`);if(!e&&t.text!==void 0)throw new l("--text closes the card, which needs --site");if(!e&&t["owner-step"]!==void 0)throw new l("--owner-step keeps a board card open, which needs --site");if(t.check!==void 0&&t["owner-step"]===void 0)throw new l("--check goes with --owner-step: it is the code check that closes the card once the step is done");if(e&&t.dir===void 0)return x(e,s,{text:t.text,ownerStep:t["owner-step"],check:t.check,repo:t.repo});const i=e?await n.cardName(e,s):void 0,d=await z(i??s,async()=>{e&&await n.checkClose(e,s,t.text,t["owner-step"],t.check),await n.checkCallsVerified(e,s,{dir:t.dir});const o=await n.recordedWorktree(e,s,{dir:t.dir}),{repo:c,base:p}=G(e,{repo:t.repo??o?.repo});return await J({path:o?.path??g.join(F(),a),branch:o?.branch??a,repo:g.resolve(c),base:p,ref:i}),e?n.setItem(e,s,{status:"done",text:t.text,ownerStep:t["owner-step"],check:t.check}):{lines:[]}});return d.joined?{lines:[`joined the land already running for ${i??s}`]}:d}],lander:[["wave/id"],[],async(e,[s])=>{if(!e)throw new l("lander needs --site <route>");return await n.relaunchLander(e,s),{lines:[`${await n.cardName(e,s)} is back at checking; the server launches a lander for it within a minute`]}}],merge:[["wave/id"],["after-approval","confirmed"],async(e,[s],t)=>e&&await n.heldMerge(e,s)?K(e,s):n.mergeCard(e,s,{afterApproval:t["after-approval"],confirmed:t.confirmed})],"arch file":[["wave/id"],[],(e,[s])=>e?U(e,s):Promise.reject(new n.BoardError("arch file needs --site <route>"))],checked:[[],["every","idle"],(e,s,t)=>n.writeChecked(e,t.every,{idle:t.idle})],push:[[],["kind","title","body","url","tag","urgent","now"],(e,s,t)=>{if(t.kind!=="fault"&&t.kind!=="reminder")throw new l(`a session pushes only --kind fault or reminder${t.kind===void 0||t.kind===!0?"":`, not "${t.kind}"`}: the board server pushes Action needed, and news goes on the board`);if(t.title===void 0)throw new l("push needs --title");return n.sendPush(e,t)}]};k.install=k.build;const h=r((e,s)=>n.cardName(e,s),"card"),q=r((e,s)=>`${s}${e.length===1?"":"s"} ${e.join(", ")}`,"plural"),C=r(e=>e.agent===void 0||e.agent==="main"?"builder":`builder ${e.agent}`,"builderOf");function ie(e){const s=e["pick-up-at"];return[e.status,e["clear-status"]&&"status cleared",e.next!==void 0&&(e.next.trim()?"next line set":"next line cleared"),e["clear-next"]&&"next line cleared",e.pr!==void 0&&`PR #${e.pr}`,e.pushed!==void 0&&`pushed ${e.pushed}`,e["gh-since"]&&`GitHub read to ${e["gh-since"]}`,e["from-ask"]&&`from task ${e["from-ask"]}`,e.state,e.held&&"held",e.unheld&&"resumed",s!==void 0&&(s?`held until ${s}`:"resumed"),R(e)!==void 0&&"notes recorded",e.deps!==void 0&&(e.deps?`blocked by ${e.deps}`:"deps cleared"),e.parent!==void 0&&(e.parent?`parent ${e.parent}`:"parent cleared"),e.related!==void 0&&(e.related?`related to ${e.related.split(",").join(", ")}`:"related cleared"),e.install!==void 0&&"install published",e["clear-install"]&&"install cleared",e.title!==void 0&&"renamed",e.ticket!==void 0&&`ticket ${e.ticket}`,e.check!==void 0&&(e.check.trim()?`check ${e.check.trim().split(/\s+/).join(" ")}`:"check removed")].filter(Boolean).join(", ")}r(ie,"itemChanges");const oe={reply:r(async(e,[s])=>`reply written to ${await n.eventCard(e,s)}`,"reply"),thread:r(async(e,[s])=>`thread line on ${await h(e,s)}`,"thread"),"topic add":r((e,[s])=>`topic ${s} added`,"topic add"),"question add":r(async(e,s,t,{asked:a})=>{const i=[...new Set(a.map(o=>o.topic))],d=i.length!==1?"":i[0].startsWith("item:")?` on ${await h(e,i[0].slice(5))}`:` on topic ${i[0]}`;return`${q(a.map(o=>o.id),"question")} added${d}`},"question add"),"question resolve":r((e,[s])=>`question ${s} resolved`,"question resolve"),"prototype add":r(async(e,[s],t)=>`prototype ${s} recorded${t.card?` for ${await h(e,t.card)}`:""}`,"prototype add"),"prototype rm":r((e,[s])=>`prototype ${s} removed`,"prototype rm"),"item set":r(async(e,[s],t)=>`${await h(e,s)}: ${ie(t)}`,"item set"),"item add":r(async(e,s,t,{key:a})=>`${await h(e,a)} added as ${a}`,"item add"),"item relate-ask":r(async(e,[s,t])=>`task ${s} folded into ${await h(e,t)}`,"item relate-ask"),"ask progress":r(async(e,[s])=>`${await h(e,`ask:${s}`)}: in progress`,"ask progress"),"ask done":r(async(e,[s])=>`${await h(e,`ask:${s}`)}: done`,"ask done"),"ask unfold":r((e,[s])=>`task ${s} back on a card of its own`,"ask unfold"),"ask move":r((e,[s],t)=>`task ${s} moved to /${t.to}`,"ask move"),checked:r((e,s,t,{every:a})=>a===null?"GitHub checked; no open PR":`GitHub checked; next in ${a} min`,"checked"),"lineup create":r((e,[s])=>`lineup ${s} created`,"lineup create"),"lineup add":r(async(e,[s,t])=>`${await h(e,t)} added to lineup ${s}`,"lineup add"),"lineup remove":r(async(e,[s,t])=>`${await h(e,t)} taken out of lineup ${s}`,"lineup remove"),"lineup set":r((e,[s])=>`lineup ${s} updated`,"lineup set"),"lineup finish":r((e,[s])=>`lineup ${s} finished`,"lineup finish"),"review sent":r(async(e,[s,t])=>`review link to ${t} recorded on ${await h(e,s)}`,"review sent"),"review react":r(async(e,[s,t,a])=>`${a} on ${t}'s review link recorded on ${await h(e,s)}`,"review react"),"review digest sent":r((e,[s])=>`digest to ${s} recorded`,"review digest sent"),"review record":r((e,[s],t)=>`review of PR #${s} recorded: ${t.state==="needs"?"needs you":"posted"}`,"review record"),"reviewers set":r(()=>"reviewers replaced","reviewers set"),"reviewers add":r(()=>"reviewers added","reviewers add"),"reviewers remove":r((e,s)=>`${q(s,"reviewer")} removed`,"reviewers remove"),"rules add":r((e,s,t)=>`${t.hub?"hub":"board"} rule added`,"rules add"),"rules edit":r((e,[s],t)=>`${t.hub?"hub":"board"} rule ${s} reworded`,"rules edit"),"rules remove":r((e,[s],t)=>`${t.hub?"hub":"board"} rule ${s} removed`,"rules remove"),"builder start":r(async(e,[s],t,a)=>`${await h(e,s)}: ${a.alreadyOpen?"already open: the Implement tap opened its record; launch the agent, whose own builder start takes it up":`${C(t)} started`}`,"builder start"),"builder progress":r(async(e,[s])=>`progress line on ${await h(e,s)}`,"builder progress"),"builder done":r(async(e,[s],t,a)=>`${await h(e,s)}: ${a?.said??`${C(t)} done${t["owner-step"]===void 0?"":`; owner step "${t["owner-step"].trim()}"`}`}`,"builder done"),"builder call":r(async(e,[s])=>`judgment call recorded on ${await h(e,s)}`,"builder call"),"builder ask":r(async(e,[s],t,{id:a})=>`${await h(e,s)}: asked the orchestrator, ${a}`,"builder ask"),"builder answer":r(async(e,[s,t])=>`${await h(e,s)}: ${t} answered`,"builder answer"),"builder defer":r(async(e,[s,t])=>`${await h(e,s)}: ${t} deferred to the owner`,"builder defer"),"builder verified":r(async(e,[s],t,a)=>`${await h(e,s)}: judgment calls verified${a?.said?`; ${a.said}`:""}`,"builder verified"),land:r(async(e,[s],t,a)=>e?a?.said?`${await h(e,s)}: ${a.said}`:t["owner-step"]===void 0?`${await h(e,s)}: landed and done`:`${await h(e,s)}: landed; waits on the owner step "${t["owner-step"].trim()}"`:null,"land"),merge:r(async(e,[s],t,a)=>`${await h(e,s)}: ${a?.said??"merged"}`,"merge"),"arch file":r(async(e,[s])=>`findings of ${await h(e,s)} filed`,"arch file")};async function de(e,s={}){try{const{flags:t,rest:a}=ee(e),i=[3,2,1].map(u=>a.slice(0,u).join(" ")).find(u=>y(k,u)),d=y(k,i);if(!d)throw new l(a.length?`unknown command "${a.slice(0,2).join(" ")}"`:"no command given");const[o,c,p]=d,w=a.slice(i.split(" ").length),_=o.at(-1)?.startsWith("..."),T=o.at(-1)?.endsWith("?")?o.length-1:o.length;if(_?w.length<T:w.length!==o.length)throw new l(`${i} takes ${o.map(u=>`<${u.replace(/^\.\.\.|\?$/g,"")}>`).join(" ")||"no arguments"}`);const{site:A,...N}=t;if(A?.length>1)throw new l("--site is given twice");const P=A?.[0],b=te(i,c,N);re(c,b);const j=P===void 0?null:await n.siteDir(P),v=await p(j,w,b,s),S=y(oe,i),I=S&&await S(j,w,b,v);I&&console.log(I);for(const u of typeof v=="object"&&v?.lines||[])console.log(u);return 0}catch(t){if(t instanceof l)return console.error(`${t.message} (node board.mjs --help lists the commands)`),2;if(t instanceof n.BoardError)return console.error(t.message),t.exitCode??1;throw t}}if(r(de,"main"),process.argv[1]&&import.meta.url===M(m.realpathSync(process.argv[1])).href){const e=process.argv.slice(2);!e.length||e.includes("--help")||e[0]==="help"?console.log(Y):process.exitCode=await de(e)}export{Y as USAGE,de as main};
|
package/board/domain.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
var pt=Object.defineProperty;var d=(e,n)=>pt(e,"name",{value:n,configurable:!0});import{routeRecord as A}from"../hub/routes.mjs";import ft from"node:fs";import{ticketBadge as Qe,ticketIn as gt}from"./tickets.mjs";import{reviewLinks as mt,reviewTime as kt,reviewersOf as wt}from"./review.mjs";import{remoteFromInput as ht}from"../hub/repos.mjs";import{reviewsOf as yt,reviewNeeds as St,reviewActions as bt}from"./auto-review.mjs";import{cardPRs as vt,hasPR as Ge,leastReady as Ot,prKeyOf as $t,prURL as xt}from"../shared/card-prs.mjs";import Ve from"node:vm";const _e=Ve.createContext({});for(const e of["ui-enums.js","ui-order.js"])Ve.runInContext(ft.readFileSync(new URL(`./public/${e}`,import.meta.url),"utf8"),_e);const{BUTTON:F,ACTION_KIND:I,BUILDER_KIND:$e,PR_STATE:Z}=_e.BoardUI,{laneOrder:Bt}=_e.BoardUI,At=["item-message","mark-ready","grill-more","prototype-request"],Vn=["planned","waiting","building","fixing","checking","held","merged","done","dropped"],qt=30*6e4,Et="build-refused",Ce=Object.freeze({ask:"builder-ask",answer:"builder-answer"}),It=864e5,pe=d(e=>e.lane==="done"||e.lane==="asks","closedLane"),Mt=d(e=>`wave:${e}`,"waveFold"),Dt=7*It,Xe=d((e,n)=>({id:e,url:n?.workspace?`https://linear.app/${n.workspace}/issue/${e}`:null}),"linearRef"),_t=d((e,n)=>String(e).replace(/^S(\d+)$/,`${n||"INT"}-$1`),"internalRef"),Re=d((e,n,s)=>gt(s,n.ticket)?n.ticket:e.internal===!0?_t(n.id,s?.prefix):n.id,"refOf"),Ct=d((e,n,s,o)=>Qe(e||(n?{source:"internal",prefix:"INT"}:null),s,o)||(n?{id:String(n),url:null,source:null}:null),"ticketOf");function Rt(e,n,s,o,r,a){const p=(e.status||"").toLowerCase();return p==="merged"||p==="done"||p==="dropped"||!r&&(n?.state==="MERGED"||n?.state==="CLOSED")?"done":s||p==="checking"?"progress":o?"review":n?.state==="OPEN"?n.checks?.state==="fail"&&(!n.isDraft||a)?"progress":"review":n?"progress":"todo"}d(Rt,"laneOf");function Nt(e,n){return e?.state==="MERGED"||n.status==="merged"||n.merged_at?Z.merged:e?.state==="CLOSED"?Z.closed:e?.state==="OPEN"?e.isDraft?Z.draft:Z.open:null}d(Nt,"prStateOf");const Ne=d(e=>{const n=(e.status||"").toLowerCase();return n==="dropped"||e.pr?.state==="CLOSED"&&n!=="merged"&&n!=="done"},"droppedCard");function Lt(e){if(!e.some(o=>o.kind==="work")||e.some(o=>o.lane!=="done"))return null;const n=e.filter(o=>o.lane==="done"),s=n.filter(Ne).length;return{merged:n.length-s,dropped:s}}d(Lt,"finishedOf");function Tt(e,n,s){return Object.fromEntries(e.map(o=>{const r=A(n?.reviews,o.github),a=r==="APPROVED"?"approved":r==="CHANGES_REQUESTED"?"changes":Object.hasOwn(s,o.name)?"sent":"unsent";return[o.name,{state:a,since:a==="sent"?A(s,o.name):null,github:o.github||null}]}))}d(Tt,"reviewOf");function Pt(e,n,s,o,r,a){return s?.kind===$e.install?"builder":o==="checking"?"checking":s?"builder":r&&e==="review"?"owner":e==="review"&&n?.state==="OPEN"&&n.isDraft?a?"ci":"ready":e==="review"||e==="stale"?n?.reviewDecision==="APPROVED"?"merge":"reviewers":null}d(Pt,"waitsOnOf");const xe=/(^|\/)prototype-[\w-]+\.html/,jt={reviewers:"Waiting for PR reviewers",merge:"Approved, merges once CI and the merge state allow",ci:"Marked ready for review once CI passes"},Ye={waiting:"Approved, Merge shows once CI and the merge state allow",ready:"Approved and green: tap Merge",merging:"Merging"},Ut="Fix the failing CI",He="Tap Done once it is settled",Wt=["status","agent","ask-link",Ce.answer],ze=d(e=>(e.who==="claude"||e.via==="github")&&!Wt.includes(e.kind),"countsUnread");function Kt({slug:e,wave:n,it:s,order:o,prs:r,repo:a,remote:p,base:u,builder:c,reachedPhase:f,agents:k,worktree:i,eta:h,thread:O,events:w,working:M,said:Q,askedAt:E,topicItems:G,boardReviewers:V,now:P,bumpedAt:l,holdAsk:$,closedAt:S,mergeAt:x,autoMerge:ee,skipDraft:z,tickets:N}){const J=r.map(m=>({repo:m.repo,number:m.number,url:xt(m),...m.gh||{},status:Nt(m.gh,s)})),j=Ot(r.map(m=>m.gh)),C=r[j]?.gh||null,K=J[j]?.status??null,T=!!S&&String(S)>String(s.status_at||"");T&&s.status!=="merged"&&(s={...s,status:"done"});const ge=[T&&S,s.status_at,s.merged_at,...r.map(m=>m.gh?.mergedAt)].filter(Boolean).map(String).sort().pop()||null,L=String(ge||""),D=["done","merged"].includes(s.status)||K===Z.merged&&s.status!=="dropped"?[...Q.filter(m=>m.kind==="item-message").map(m=>m.ts),...E].map(String).filter(m=>m>L).sort().pop():void 0,me=s.owner_step&&!T&&s.status!=="dropped"?String(s.owner_step):null,le=["done","merged"].includes(s.status)||K===Z.merged;D&&(s={...s,status:void 0,next:void 0});const ke=!!D&&Q.some(m=>String(m.ts)>L&&(m.state==="waiting"||m.state==="working")),te=`${e}/${s.id}`,we=wt(n,V),U=Object.fromEntries(Object.entries(mt(n,s,te,G)).map(([m,v])=>[m,kt(v.ts)])),X=Object.values(U).filter(Boolean).sort()[0]||null,he=[...O.map(m=>m.ts),...w.map(m=>m.ts)].sort().pop()||null,Ae=[...O.filter(m=>ze({...m,who:m.who||"claude"})).map(m=>m.ts),...w.map(m=>m.reply?.at)].filter(Boolean).sort(),W=!Ge(s)&&s.pushed?.branch?{branch:s.pushed.branch,at:s.pushed.at||null,host:p?.host||null}:null;let B=Rt(s,C,k.some(m=>m.started&&!m.done&&Y(m)),W,!!D,z);D&&B==="todo"&&(B="progress"),B==="review"&&X&&P-Date.parse(X)>Dt&&(B="stale");const ne=!!me&&le&&B==="done";ne&&(B="review");let R=Pt(B,C,c,(s.status||"").toLowerCase(),W,z);const ie=R==="builder"&&C?.state==="OPEN"&&!C.isDraft,de=B==="progress"&&!R&&C?.state==="OPEN"&&(!C.isDraft||z),se=ne||!!D&&B==="progress"&&!R&&!de&&!ke;se&&(R="owner");const ue=$&&String($.ts)>String(s.held_at||""),ye=B==="todo"&&(ue?!!$.data.hold:s.status==="held"||!!s.held),qe=ye&&(ue?$.data.until:s.pick_up_at)||null,oe=B==="todo"||B==="progress",Se=k.filter(Y).map(m=>String(m.started||"")).filter(Boolean).sort()[0]||"",Ee=l&&!(Se&&String(l)<Se),be=O.filter(m=>(m.who||"claude")==="claude"&&m.kind!=="status").map(m=>String(m.ts)).sort().pop()||"",ce=R==="merge"&&!!x&&String(x)>be,ve=O.filter(m=>m.kind!=="status").sort((m,v)=>String(m.ts).localeCompare(String(v.ts))).at(-1),Ie=B==="todo"&&ve?.kind===Et?ve.text:null,t=D||k.filter(Y).map(m=>m.started).filter(Boolean).map(String).sort().pop()||s.queued_at||["building","fixing","checking"].includes(String(s.status||"").toLowerCase())&&s.status_at||null,g=W&&R==="owner"?`merge ${W.branch} into ${u}`:R==="merge"&&!ee?Ye[ce?"merging":"waiting"]:jt[R],y=K===Z.draft?`Review draft PR #${J[j].number}`:null;return{key:te,kind:"work",order:o,wave:e,waveTitle:n.title||e,id:s.id,ref:Re(n,s,N),title:s.title,status:s.status||null,created:s.created||null,progressSince:t,closedAt:B==="done"?ge:null,internal:n.internal===!0,repo:n.internal===!0&&!n.repo?null:a,lane:B,waitsOn:R,builder:R==="checking"?null:c,reachedPhase:f,agents:k,worktree:i??null,eta:h,working:M,reopened:!!D,awaitsDone:se,ownerStep:se?me:null,queuedAt:B!=="done"&&s.queued_at||null,queued:!1,deps:s.deps||[],held:ye,heldUntil:qe,risky:!!s.risky,bumpable:oe,bumpedAt:oe&&Ee?l:null,holdable:B!=="done",hasNotes:!!(s.plan||String(s.notes||"").trim()),finishable:B!=="done",merging:ce,refusal:Ie,manualSteps:s.manual_steps||[],fromAsk:s.from_ask||null,next:ie||B==="done"||R==="checking"?null:de?Ut:g||s.next||y||null,branch:s.branch||C?.headRefName||W?.branch||null,pushed:B==="done"||ne?null:W,ticket:N?.source==="linear"&&!s.ticket&&s.linear?.length?null:Ct(N,s.ticket,s.ticket||(n.internal?Re(n,s,N):s.linear?.[0]),{repo:a,key:te}),ticketSource:N?.source||null,linear:(s.linear||[]).map(m=>N?.source==="linear"&&Qe(N,m,{key:te,repo:a})||Xe(m,N)),pr:J[j]||null,prs:J,reviewers:we.map(m=>m.name),review:Tt(we,C,U),inReviewSince:X,threadCount:O.length+w.length,lastTs:he,othersAt:Ae,grilling:nt("grill",w,k,E),prototyping:nt("prototype",w,k,E),state:s.state||null,updatedAt:C?.mergedAt||C?.updatedAt||s.merged_at||c?.at||he||null,ask:null,fold:null,action:null,install:B==="done"?null:Ft(s.install,C)}}d(Kt,"workItem");function Ft(e,n){if(!e?.url)return null;const s=e.sha?String(e.sha).toLowerCase():null,o=n?.headRefOid?String(n.headRefOid).toLowerCase():null;return{url:e.url,label:e.label||null,builtAt:e.built_at||null,sha:s,older:!!(s&&o&&!o.startsWith(s))}}d(Ft,"installOf");function Qt(e){if(e?.state!=="OPEN")return[`PR is ${String(e?.state||"unknown").toLowerCase()}`];const{state:n,pending:s,failing:o}=e.checks||{};return[e.isDraft&&"it is a draft",e.reviewDecision!=="APPROVED"&&`review: ${String(e.reviewDecision||"none").toLowerCase().replace(/_/g," ")}`,n==="fail"&&`failing: ${o?.length?o.join(", "):"a check"}`,n==="pending"&&`pending: ${s?H(s,"check"):"checks"}`,e.mergeStateStatus!=="CLEAN"&&`merge state: ${String(e.mergeStateStatus||"unknown").toLowerCase()}`].filter(Boolean)}d(Qt,"mergeBlockers");const Gt=d(e=>!Qt(e).length,"mergeable");function Vt(e,n,s){if(e.held||pe(e))return null;const o=`#item=${encodeURIComponent(e.key)}`;if(Je(e)){const r=n.get(e.questions.first);return xe.test(r?.url||"")?{kind:I.pick,url:`questions.html#${r.id}`,label:"Prototypes ready: pick a take"}:{kind:I.questions,url:`questions.html#${e.questions.first}`,label:`Answer ${H(e.questions.open,"question")}`}}return e.waitsOn==="ready"?{kind:I.ready,url:e.pr.url,label:`Review draft PR #${e.pr.number}`}:e.kind==="ask"&&e.waitsOn==="owner"?{kind:I.done,url:o,label:e.next||He}:e.waitsOn==="merge"&&e.manualSteps.length?{kind:I.steps,url:o,label:"Run the manual steps, then merge"}:e.waitsOn==="merge"&&!s&&!e.merging&&e.prs.every(r=>["MERGED","CLOSED"].includes(r.state)||Gt(r))?{kind:I.merge,url:o,label:"Merge"}:null}d(Vt,"actionOf");const Je=d(e=>!!(e.questions&&e.questions.state!=="resolved"&&e.questions.open>0),"asking"),Ze=new Set([I.pick,I.questions]);function et(e){return pe(e)?null:Je(e)?F.openQuestions:e.action?.kind===I.done?F.markDone:e.kind==="ask"?null:F.grill}d(et,"actionButtonOf");const Xt=d(e=>e.kind==="work"&&e.lane==="todo"&&!e.action&&!e.unpicked,"buildableOf"),Yt=d((e,n)=>/^item:/.test(e.topic||"")&&xe.test(e.url||"")&&!(e.options||[]).some(s=>s.id===n),"picksNoTake");function Ht(e,n){const s=e.filter(a=>a.topic===`item:${n}`),r=[...s.filter(a=>xe.test(a.url||"")).flatMap(a=>a.answers.map(p=>({q:a,a:p,again:Yt(a,p.choice)}))),...s.filter(a=>a.request).map(a=>({q:a,a:a.request,again:!0}))].sort((a,p)=>String(a.a.ts).localeCompare(String(p.a.ts))).at(-1);return r?.again?{id:r.q.id,note:r.a.note.replace(/\s+/g," ").trim()}:null}d(Ht,"noTakePicked");const zt="Prototype again",Jt=Object.freeze({[I.pick]:F.openQuestions,[I.questions]:F.openQuestions,[I.ready]:F.github,[I.steps]:F.github,[I.merge]:F.merge,[I.done]:F.markDone});function Zt(e,n,s){if(e.action=Vt(e,n,s),e.action?.kind===I.questions&&e.questions.fromAsks){Object.assign(e,{action:null,waitsOn:"owner",next:e.action.label,actionButton:et(e),resolvedBy:null});return}e.awaitsDone&&=!e.action,e.awaitsDone||(e.ownerStep=null),e.actionButton=et(e),e.resolvedBy=Jt[e.action?.kind]||null,e.action?.kind===I.merge&&(e.next=Ye.ready),e.awaitsDone&&Object.assign(e,{lane:"review",next:e.ownerStep||He,bumpable:!1,bumpedAt:null});const o=e.lane==="todo"||e.waitsOn==="ready"||e.lane==="progress"&&e.waitsOn==="owner";if(!e.action||!o)return;const r=e.lane==="todo"&&Ze.has(e.action.kind)?"todo":"review";Object.assign(e,{lane:r,waitsOn:e.waitsOn==="ready"?"ready":"owner",next:e.action.label}),r==="review"&&Object.assign(e,{bumpable:!1,bumpedAt:null})}d(Zt,"placeOwnerWait");const tt=d(e=>e.queuedAt||e.agents.some(Y)||["building","fixing"].includes(String(e.status||"").toLowerCase()),"started"),en="none set yet";function Xn(e,n){const s=String(e.status||"").toLowerCase();if(e.next||Ge(e)||e.pushed||e.held||e.queued_at||["held","checking","done","merged","dropped"].includes(s))return!1;const o=n.filter(r=>r?.started);return!o.some(r=>!r.done)&&tt({status:s,agents:o})}d(Xn,"owesNext");function tn(e,n=new Date){const s=new Date(e),o=s.toLocaleTimeString("en-GB",{hour:"2-digit",minute:"2-digit"});return s.toDateString()===n.toDateString()?o:`${s.getMonth()+1}/${s.getDate()} ${o}`}d(tn,"askedLabel");function nn(e,n,s,o,r,a,p){const u=n.find(S=>S.kind==="ask"&&S.eventId===e.id),c=u.files,f=(u.text||"").trim().split(`
|
|
2
|
-
`)[0]||
|
|
3
|
-
\u2192 `)},"actionText"),
|
|
4
|
-
\u2192 ${$}`,files:l.data.files||[],eventId:l.id,reply:l.reply}}),...We(f,[...new Set(k)].map(l=>f.asks.has(l)?[f.asks.get(l)]:[])).flatMap(l=>[{ts:l.ts,who:"love",via:"ask",kind:l.kind,text:l.data?.text||"",files:l.data?.files||[],eventId:l.id,reply:l.reply,...c.includes(l.id)?{moved:!0}:{},...typeof l.data?.from?.title=="string"?{from:l.data.from.title}:{}},...l.data?.quick===!0?[{ts:l.ts,who:"love",kind:"quick-build",request:!0,text:kn}]:[],...p.map($=>({ts:l.ts,who:"claude",kind:"ask-link",text:`${$.became?"Became":"Added to"} ${$.ref}`,link:`#item=${encodeURIComponent($.key)}`,html:null,via:null}))])].sort((l,$)=>String(l.ts).localeCompare(String($.ts))),G=Ue(E),{edits:V,editFiles:P}=f;for(const l of E){if(!l.eventId)continue;(l.kind==="ask"||l.kind==="item-message")&&typeof V.get(l.eventId)=="string"&&Object.assign(l,{text:V.get(l.eventId),edited:!0,files:[...l.files,...P.get(l.eventId)||[]]});const $=l.kind==="ask"&&(p.length>0||!!l.moved);l.state=l.reply?"replied":$||Q.has(l.eventId)||G>String(l.ts)?"answered":A(r,l.eventId)?"working":"waiting",l.canDelete=l.kind==="item-message"&&(l.state==="working"||l.state==="waiting"),l.canEdit=l.canDelete}return E}d(at,"threadOf");function Sn(e,n,s,o,r,a){const p=d((i,h)=>{const O=a.filter(M=>M.kind==="wave-triage"&&M.data?.[i]===h).pop(),w=r.filter(M=>M.topic===`${i}:${h}`&&(M.state==="open"||M.state==="answered")).length;return{running:!!(O&&!O.reply),questions:w}},"triageOf"),u=new Map(o.map(i=>[i.key,i])),c=d(i=>i.filter(h=>h.lane==="done"&&!Ne(h)).length,"merged"),f=s.map(i=>({l:i,cards:i.keys.map(h=>u.get(h)).filter(Boolean)})).map(i=>({...i,open:!i.l.finished_at&&i.cards.some(h=>h.lane!=="done")})).sort((i,h)=>Number(i.open)-Number(h.open));for(const{l:i,cards:h}of f)for(const O of h)O.lineup={slug:i.slug,title:i.title};const k=f.filter(i=>i.open).map(({l:i,cards:h})=>({kind:"lineup",slug:i.slug,title:i.title,goal:i.goal,keys:h.map(O=>O.key),phases:i.phases.map(O=>O.filter(w=>u.has(w))).filter(O=>O.length),next:bn(h),merged:c(h),total:h.length,triage:p("lineup",i.slug)}));return e.fromWaves?[...k,...n.filter(i=>!i.finished).map(i=>({w:i,cards:o.filter(h=>h.wave===i.slug&&h.kind!=="ask")})).filter(({cards:i})=>i.length).map(({w:i,cards:h})=>({kind:"wave",slug:i.slug,title:i.title,goal:null,keys:h.map(O=>O.key),phases:null,next:null,merged:c(h),total:h.length,triage:p("wave",i.slug)}))]:k}d(Sn,"waveBandsOf");function bn(e){const n=e.filter(a=>a.lane==="todo"),s=n.find(a=>a.ready&&a.buildable)||n[0];if(!s)return null;const o=s.ready&&s.buildable,r=o?"ready":s.held?"held":s.blockedBy.length?`waits on ${s.blockedBy.map(a=>a.ref).join(", ")}`:s.action?"needs you":s.unsorted?"new":s.state||"not ready";return{key:s.key,ref:s.ref,ready:o,word:r}}d(bn,"nextOf");function Yn(e,n,s,o){const r=e.waveBands.find(i=>i.kind==="lineup"&&i.slug===n);if(!r)return null;const a=new Map(e.items.map(i=>[i.key,i.lane])),p=r.keys.filter(i=>a.get(i)==="todo"),u=p.indexOf(s),c=u+o;if(u<0||c<0||c>=p.length)return null;[p[u],p[c]]=[p[c],p[u]];let f=0;const k=r.keys.map(i=>a.get(i)==="todo"?p[f++]:i);return f=0,r.phases.map(i=>k.slice(f,f+=i.length))}d(Yn,"lineupMoved");const vn=d(e=>e.phases||e.items.map(n=>[n]),"lineupPhases");function Hn(e){const{owner:n={},topicItems:s={},events:o=[],threads:r={},prs:a={},working:p={}}=e,u=e.now??Date.now(),c=rt(o,n),f=new Set(o.filter(t=>t.kind==="ask-withdrawn"&&!t.data?.key).map(t=>t.data?.id)),k=new Map(o.filter(t=>t.kind==="ask"&&!f.has(t.id)).map(t=>[t.id,t])),i=d((t,g)=>new Map(o.filter(y=>y.kind===t).sort((y,m)=>String(y.ts).localeCompare(String(m.ts))).map(y=>[y.data?.[g],y.ts])),"latest"),h=i("ask-bump","id"),O=i("card-unbump","key"),w=new Map([...i("card-bump","key")].filter(([t,g])=>!(String(O.get(t)||"")>String(g)))),M=i("card-done","key"),Q=i("card-merge","key"),E=e.autoMerge===!0,G=e.tickets,V=new Map(o.filter(t=>t.kind==="card-hold").sort((t,g)=>String(t.ts).localeCompare(String(g.ts))).map(t=>[t.data?.key,t])),P=new Map,l=e.askLinks||{},$=new Map;for(const[t,g]of Object.entries(l))k.has(t)&&$.set(g,[...$.get(g)||[],t]);const S=[],x=[],ee=new Map,z=new Map,N=new Map,J=new Map,j=e.github?null:ht(e.remote);for(const{slug:t,wave:g,agents:y={}}of e.waves){const m=e.github||g.repo||null;S.push({slug:t,title:g.title||t,status:g.status||null,internal:!!g.internal,finished:null,reviewers:(g.reviewers||[]).map(v=>v.name)}),g.items.forEach((v,q)=>{const _=`${t}/${v.id}`,Ke=($.get(_)||[]).filter(b=>b!==v.from_ask),Me=[v.from_ask,...Ke].filter(b=>k.has(b)),ut=new Set(Me.map(b=>`ask:${b}`)),Fe=[...A(r,_)||[],...Me.flatMap(b=>A(r,`ask:${b}`)||[])];v.from_ask&&P.set(v.from_ask,[...P.get(v.from_ask)||[],{key:_,ref:Re(g,v,G),became:!0}]);const Oe=at(_,{lines:Fe,events:o,owner:n,working:p,fromAsk:v.from_ask||null,moved:Ke,index:c});for(const b of Oe)b.eventId&&ee.set(b.eventId,b.state);z.set(_,Ue(Oe)),x.push(Kt({slug:t,wave:g,it:v,order:q,repo:m,remote:j,base:e.branch||"main",prs:vt(v,m).map(b=>({...b,gh:A(a,$t(b))||null})),...(b=>({agents:b,builder:dn(b,A(r,_),u),reachedPhase:ln(b,A(r,_)),eta:b.map(ct=>ct.eta).filter(Boolean).sort().pop()||null}))(Te(A(y,v.id))),worktree:an(A(y,v.id)),thread:Fe,events:[...We(c,[_,...ut].map(b=>c.byKey.get(b))),...Me.map(b=>k.get(b))],working:Oe.some(b=>b.state==="working"),said:Oe.filter(b=>b.who==="love"&&(b.kind==="item-message"||b.kind==="owner-answer")),askedAt:(c.questions.get(`item:${_}`)||[]).filter(b=>b.asked_at).map(b=>b.asked_at),topicItems:s,boardReviewers:e.reviewers||[],now:u,bumpedAt:w.get(_),holdAsk:V.get(_),closedAt:M.get(_),mergeAt:Q.get(_),autoMerge:E,skipDraft:e.skipDraft===!0,tickets:G}));const De=x[x.length-1];J.set(_,{parent:v.parent?it(t,v.parent):null,related:(v.related||[]).map(b=>it(t,b))}),De.lane==="done"&&N.set(_,De.closedAt||String(De.updatedAt||""))})}const C=new Map(x.map(t=>[t.key,t])),K=new Map,T=e.try||{},ge=e.autoBuild===!0,L=["web","app"].includes(T.kind)?T.kind:e.waves.some(({wave:t})=>t.items.some(g=>g.install?.url))?"app":"web";for(const t of x){if(t.kind!=="work"||t.lane==="done")continue;const g=A(T.cards,t.key)||{},y=g.head||t.pr?.headRefOid,m=L==="web"?g.sha:t.install?.sha,v=t.builder?.kind===$e.install?"running":g.state||(L==="app"&&t.install?"ready":null);t.try={kind:L,state:v,stale:!!(m&&y&&!String(y).toLowerCase().startsWith(String(m).toLowerCase())),url:L==="web"?null:t.install?.url||null,port:L==="web"&&v==="ready"&&g.https||null,sha:m||null,changes:g.ahead>0,script:g.script===!0,autoBuild:ge}}const D=T.main,me=D?.script&&D.sha?{slug:D.slug,branch:D.branch||"main",sha:D.sha.slice(0,7),at:D.at,kind:L,state:D.state||null,url:L==="web"?null:e.main?.install?.url||null,port:L==="web"&&D.state==="ready"&&D.https||null,pull:T.pull?{running:T.pull.running===!0,error:T.pull.error||null}:null}:null,le=e.askProgress||{},ke=i("ask-done","id"),te=Be(o.filter(t=>t.kind==="owner-answer"),t=>t.data?.id),we=Be(o.filter(t=>t.kind==="prototype-request"),t=>t.data?.question),U=(n.items||[]).map(t=>cn(t,te.get(t.id)||[],we.get(t.id)||[])),X=new Map(U.map(t=>[t.id,t])),he=Be(U,t=>t.topic),Ae=d(t=>{const g=he.get(t)||[];return{open:g.filter(y=>y.state==="open").length,answered:g.filter(y=>y.state==="answered").length}},"pendingOn");for(const t of k.values()){const g=`ask:${t.id}`,y=C.get(A(l,t.id)),m=[...P.get(t.id)||[]];if(y&&!m.some(q=>q.key===y.key)&&m.push({key:y.key,ref:y.ref,became:!1}),m.length){K.set(g,m[0].key);continue}if(A(l,t.id)){K.set(g,A(l,t.id));continue}const v=at(g,{lines:A(r,g)||[],events:o,working:p,links:m,index:c});for(const q of v)q.eventId&&ee.set(q.eventId,q.state);z.set(g,Ue(v)),x.push(nn(t,v,h.get(t.id),m.length>0||!!A(l,t.id),A(le,t.id),ke.get(t.id),Ae(`item:${g}`)))}for(const t of S){const g=x.filter(y=>y.wave===t.slug);if(t.finished=Lt(g),t.finished)for(const y of g)y.lane==="done"&&(y.fold=Mt(t.slug))}for(const t of x)t.lineup=null;const W=new Set((n.topics||[]).map(t=>t.id)),B=e.waves.flatMap(({slug:t,wave:g})=>(g.lineups||[]).map(y=>{const m=vn(y).map(v=>v.map(q=>`${t}/${q}`));return{...y,phases:m,keys:m.flat()}})),ne=d(()=>gn([...n.topics||[],...x.filter(t=>!W.has(`item:${t.key}`)).map(t=>({id:`item:${t.key}`,card:t.key,title:`${t.ref}: ${t.title}`,...t.kind==="ask"?{askedAt:t.ask.askedAt}:{},summary:"Questions about this work item."})),...B.filter(t=>!W.has(`lineup:${t.slug}`)).map(t=>({id:`lineup:${t.slug}`,title:t.title,summary:"Wave triage."})),...(e.fromWaves?S:[]).filter(t=>!W.has(`wave:${t.slug}`)).map(t=>({id:`wave:${t.slug}`,title:t.title,summary:"Wave triage."}))],U,new Set(x.filter(pe).map(t=>`item:${t.key}`))),"topicsOf"),R=new Map(ne().map(t=>[t.id,t]));for(const t of x){const g=R.get(`item:${t.key}`);if(!g){t.questions=null;continue}const y=g.questions.filter(q=>X.get(q)?.state!=="resolved"),m=g.questions.find(q=>X.get(q)?.state==="open")||y[0]||g.questions[0],v=g.questions.map(q=>X.get(q)).filter(q=>q?.state==="open");t.questions={topic:g.id,state:g.state,open:g.open,answered:g.answered,replied:g.replied,resolved:g.resolved,first:m,askedAt:v.map(q=>q.asked_at).filter(Boolean).sort(),fromAsks:v.length>0&&v.every(q=>q.ask)}}const ie=new Map(x.map(t=>[t.key,t]));for(const t of x){t.blockedBy=t.kind!=="work"||t.lane==="done"?[]:t.deps.map(y=>ie.get(String(y).includes("/")?y:`${t.wave}/${y}`)).filter(y=>y&&y.lane!=="done"&&!y.reopened).map(lt),Zt(t,X,E),t.lane==="todo"&&t.kind==="work"&&!t.held&&tt(t)&&(t.lane="progress"),t.unpicked=t.kind==="work"&&t.lane==="todo"&&!!Ht(U,t.key),t.unpicked&&(t.state="prototyping"),t.unpicked&&!t.action&&(t.next=zt),t.buildable=Xt(t);const g=t.kind==="work"&&t.lane==="todo";t.unsorted=g&&!!t.fromAsk&&!t.state,t.ready=g&&t.state==="ready"&&!t.blockedBy.length&&!t.action}xn(x,ie,J);const de=new Map((n.items||[]).map(t=>[t.id,t.topic])),se=d(t=>t.kind==="owner-answer"?de.has(t.data?.id)?/^item:(.+)$/.exec(de.get(t.data.id))?.[1]||null:"":t.kind==="ask"?`ask:${t.id}`:t.data?.key||(t.kind.startsWith("ask-")&&t.data?.id?`ask:${t.data.id}`:null),"ownCardOf"),ue=d(t=>{const g=se(t);return K.get(g)||g},"cardOf"),ye=d(t=>{const g=t.startsWith("ask:")?t.slice(4):null,y=g&&A(le,g)?.state==="done"?A(le,g).at:null;return[M.get(t),g&&ke.get(g),y,N.get(t)].filter(Boolean).sort().pop()||""},"closedAt"),qe=d(t=>{const g=ue(t);return g===null?!t.reply:!ie.has(g)||!(ye(g)<String(A(p,t.id)))?!1:ee.has(t.id)?ee.get(t.id)==="working":!t.reply&&!K.has(se(t))&&!(String(z.get(g)||"")>String(t.ts))},"unanswered"),oe=o.filter(t=>A(p,t.id)&&qe(t)).map(t=>({id:t.id,kind:t.kind,key:ue(t),data:t.data,since:A(p,t.id)})),Se=e.autoPull===!0;for(const t of x)t.working=oe.some(g=>g.key===t.key),t.kind==="work"&&(t.lane==="progress"&&t.queuedAt||Se&&t.lane==="todo"&&dt(t))&&Object.assign(t,{lane:"todo",queued:!0}),t.kind==="work"&&t.lane==="progress"&&!t.builder&&!t.next&&t.waitsOn!=="checking"&&!t.working&&!t.queued&&(t.next=en),t.sortAt=(t.queued?t.queuedAt||t.created:Bn[t.lane](t))||t.updatedAt||null;x.sort(Bt);const Ee=Sn(e,S,B,x,U,o),be=ne(),ce=yt({on:e.autoReview,incoming:e.incoming,records:e.reviewRecords,events:o,now:u,refs:new Map(x.filter(t=>t.kind==="work").map(t=>[t.key,t.ref]))}),ve=[...qn(x,be,U),...St(ce)],Ie=x.filter(t=>t.ask?.answerAt).sort((t,g)=>g.ask.answerAt.localeCompare(t.ask.answerAt)).map(t=>({key:t.key,id:t.id,text:t.title,at:t.ask.answerAt}));return On({kind:L,main:me,remote:j&&{host:j.host,name:`${j.host}/${j.path}`},waves:S,waveBands:Ee,items:x,moved:Object.fromEntries(K),answers:Ie,owner:U,ownerTopics:be,reviews:ce,needs:ve,working:oe.length,workingOn:oe,checks:An(e,u)},e)}d(Hn,"buildBoard");function On(e,{listen:n,prompts:s}={}){const o=[...En(s),...e.needs];return{...e,needs:o,needsSummary:In(o),checks:{...e.checks,watcher:{listening:"running",asleep:"asleep",waking:"waking"}[n]||"stopped"}}}d(On,"withHub");const lt=d(e=>({key:e.key,ref:e.ref}),"linkOf"),it=d((e,n)=>String(n).includes("/")?String(n):`${e}/${n}`,"linkKey"),$n=d((e,n)=>e.ref.localeCompare(n.ref,void 0,{numeric:!0}),"byRef");function xn(e,n,s){const o=new Map,r=new Map,a=new Map,p=d((c,f,k)=>c.set(f,[...c.get(f)||[],k]),"add");for(const[c,f]of s){f.parent&&p(o,f.parent,c);for(const k of f.related)k!==c&&(p(r,c,k),p(r,k,c))}for(const c of e)for(const f of c.blockedBy||[])p(a,f.key,c.key);const u=d((c=[])=>[...new Set(c)].map(f=>n.get(f)).filter(Boolean).map(lt).sort($n),"cards");for(const c of e){const f=s.get(c.key)?.parent;c.parent=f?{key:f,ref:n.get(f)?.ref||f.slice(f.indexOf("/")+1)}:null,c.children=u(o.get(c.key)),c.related=u(r.get(c.key)),c.waitsOnIt=u(a.get(c.key))}}d(xn,"linkCards");const Bn={todo:d(e=>e.created,"todo"),progress:d(e=>e.progressSince,"progress"),review:d(e=>e.inReviewSince,"review"),stale:d(e=>e.inReviewSince,"stale"),done:d(e=>e.closedAt,"done"),asks:d(e=>e.ask.answerAt,"asks")},H=d((e,n)=>`${e} ${n}${e===1?"":"s"}`,"plural");function An({checked:e,watcher:n,replyAt:s,threads:o={},working:r={},waves:a=[]},p){const u=Number(e?.every)||30,f=[s,n?.since,...Object.values(r),...Object.values(o).flat().filter(i=>(i.who||"claude")==="claude").map(i=>i.ts),...a.flatMap(i=>Object.values(i.agents||{}).flatMap(h=>Object.values(h||{})).map(h=>h?.done||h?.started))].filter(i=>typeof i=="string"&&!Number.isNaN(Date.parse(i))).reduce((i,h)=>i&&Date.parse(i)>Date.parse(h)?i:h,null),k=!!e?.idle;return{at:e?.at||null,every:k?null:u,idle:k,late:!k&&!!e?.at&&p-Date.parse(e.at)>2*u*6e4,claudeAt:f}}d(An,"checksOf");function qn(e,n,s){const o=new Set(e.map(u=>u.questions?.topic).filter(Boolean)),r=d(u=>u.find(c=>s.find(f=>f.id===c)?.state==="open")||u[0],"firstOpen"),a=n.filter(u=>u.state!=="resolved"&&u.open>0&&!o.has(u.id)).map(u=>{const c=`questions.html#${r(u.questions)}`,f=/^(?:lineup|wave):(.+)$/.exec(u.id)?.[1]||null;return{kind:"Question",title:u.title,text:`${u.title} \xB7 ${H(u.open,"question")}`,url:c,topic:u.id,band:f,questions:{open:u.open,url:c},ready:null}}),p=e.filter(u=>!u.held).map(u=>{const c=u.questions&&u.questions.state!=="resolved"&&u.questions.open>0?{open:u.questions.open,url:`questions.html#${u.questions.first}`}:null,f=u.waitsOn==="ready"?u.pr.url:null,k=!c&&!f&&u.action?{label:u.action.label,url:u.action.url}:null;if(!c&&!f&&!k)return null;const i=`${u.ref}: ${u.title}`,h=[i,c&&H(c.open,"question"),f&&"Ready on GitHub",k?.label].filter(Boolean).join(" \xB7 ");return{kind:c?"Question":f?"Tap Ready":"Action needed",title:i,text:h,url:`#item=${encodeURIComponent(u.key)}`,key:u.key,questions:c,ready:f,action:k}}).filter(Boolean);return[...a,...p]}d(qn,"needsOf");function En(e=[]){return e.map(n=>{const s=n.questions?.length?`Question: ${n.questions.map(o=>o.question).join(" \xB7 ")}`:`${n.tool}: ${n.detail||n.action}`;return{kind:"Prompt",title:s,text:s,url:"",prompt:n}})}d(En,"promptLines");function In(e){const n=e.filter(a=>a.prompt).length,s=e.reduce((a,p)=>a+(p.questions?.open||0),0),o=e.filter(a=>a.ready).length,r=e.filter(a=>a.review).length;return[n&&H(n,"prompt"),s&&H(s,"question"),o&&`${H(o,"draft")} waiting on Ready`,r&&`${H(r,"review")} held`].filter(Boolean).join(" \xB7 ")}d(In,"needsSummary");function zn(e){const n=d((...s)=>e.items.filter(o=>s.includes(o.lane)).length,"count");return{items:e.needs.map(({kind:s,text:o,url:r})=>({kind:s,text:o,url:r})),working:e.working,summary:e.needsSummary,lanes:{review:n("review","stale"),progress:n("progress"),todo:n("todo")},building:e.items.some(s=>(s.agents||[]).some(o=>!o.done))}}d(zn,"needsAnswer");const dt=d(e=>e.buildable&&e.state==="ready"&&!e.held&&!e.unsorted&&!e.blockedBy.length&&!e.questions?.open&&!e.questions?.answered&&!e.grilling&&!e.prototyping,"queueable"),Mn=10;function Dn(e,{cap:n=Mn,pulled:s=[]}={}){const o=e.filter(w=>!pe(w)),r=d(w=>(w.agents||[]).filter(M=>!M.done),"running"),a=o.filter(w=>w.waitsOn==="checking"||r(w).some(Y)).length,p=o.filter(w=>dt(w)&&!w.working).map(w=>w.key),u=o.filter(w=>s.includes(w.key)&&(p.includes(w.key)||w.buildable&&w.working)).map(w=>w.key),c=d(w=>w.queued&&w.queuedAt&&!w.blockedBy.length,"tapped"),f=o.filter(w=>c(w)||p.includes(w.key)&&!u.includes(w.key)).slice(0,Math.max(0,n-a-u.length)),k=f.filter(c).map(w=>w.key),i=f.filter(w=>!c(w)).map(w=>w.key),O=o.some(w=>w.grilling||w.prototyping||r(w).some(re))?[]:o.filter(w=>(w.buildable||w.unpicked&&!w.action)&&w.state!=="ready"&&!w.held&&!w.questions?.open&&!w.questions?.answered&&!w.working).map(w=>w.key);return{idle:!a,pullable:p,waiting:u,starts:k,pulls:i,plannable:O}}d(Dn,"pullOf");function _n(e){return e.filter(n=>n.lane==="todo"&&(!n.queued||n.buildable)||n.lane==="stale").map(n=>JSON.stringify([n.key,n.title,n.deps||[]])).sort().join(`
|
|
5
|
-
`)}d(
|
|
6
|
-
`);return{waiting:
|
|
1
|
+
var ht=Object.defineProperty;var d=(e,t)=>ht(e,"name",{value:t,configurable:!0});import{routeRecord as q}from"../hub/routes.mjs";import yt from"node:fs";import{ticketBadge as Ye,ticketIn as St}from"./tickets.mjs";import{reviewLinks as vt,reviewTime as bt,reviewersOf as Ot}from"./review.mjs";import{remoteFromInput as xt}from"../hub/repos.mjs";import{reviewsOf as $t,reviewNeeds as At,reviewActions as Bt}from"./auto-review.mjs";import{cardPRs as Et,hasPR as Te,leastReady as qt,prKeyOf as _t,prURL as Rt}from"../shared/card-prs.mjs";import ze from"node:vm";import{checkWords as It,parseCheck as Dt}from"../shared/owner-check.mjs";const Le=ze.createContext({});for(const e of["ui-enums.js","ui-order.js"])ze.runInContext(yt.readFileSync(new URL(`./public/${e}`,import.meta.url),"utf8"),Le);const{BUTTON:Y,ACTION_KIND:R,BUILDER_KIND:Re,PR_STATE:oe}=Le.BoardUI,{laneOrder:Ct}=Le.BoardUI,Mt=["item-message","mark-ready","grill-more","prototype-request","reask-request"],rs=["planned","waiting","building","fixing","checking","held","merged","done","dropped"],Nt=30*6e4,Je="build-refused",Se=Object.freeze({ask:"builder-ask",answer:"builder-answer"}),Tt=864e5,de=d(e=>e.lane==="done"||e.lane==="asks","closedLane"),Lt=d(e=>`wave:${e}`,"waveFold"),Pt=7*Tt,Ze=d((e,t)=>({id:e,url:t?.workspace?`https://linear.app/${t.workspace}/issue/${e}`:null}),"linearRef"),jt=d((e,t)=>String(e).replace(/^S(\d+)$/,`${t||"INT"}-$1`),"internalRef"),Pe=d((e,t,s)=>St(s,t.ticket)?t.ticket:e.internal===!0?jt(t.id,s?.prefix):t.id,"refOf"),Ut=d((e,t,s,o)=>Ye(e||(t?{source:"internal",prefix:"INT"}:null),s,o)||(t?{id:String(t),url:null,source:null}:null),"ticketOf");function Wt(e,t,s,o,r,a){const p=(e.status||"").toLowerCase();return p==="merged"||p==="done"||p==="dropped"||!r&&(t?.state==="MERGED"||t?.state==="CLOSED")?"done":s||p==="checking"?"progress":o?"review":t?.state==="OPEN"?t.checks?.state==="fail"&&(!t.isDraft||a)?"progress":"review":t?"progress":"todo"}d(Wt,"laneOf");function Ft(e,t){return e?.state==="MERGED"||t.status==="merged"||t.merged_at?oe.merged:e?.state==="CLOSED"?oe.closed:e?.state==="OPEN"?e.isDraft?oe.draft:oe.open:null}d(Ft,"prStateOf");const je=d(e=>{const t=(e.status||"").toLowerCase();return t==="dropped"||e.pr?.state==="CLOSED"&&t!=="merged"&&t!=="done"},"droppedCard");function Kt(e){if(!e.some(o=>o.kind==="work")||e.some(o=>o.lane!=="done"))return null;const t=e.filter(o=>o.lane==="done"),s=t.filter(je).length;return{merged:t.length-s,dropped:s}}d(Kt,"finishedOf");function Qt(e,t,s){return Object.fromEntries(e.map(o=>{const r=q(t?.reviews,o.github),a=r==="APPROVED"?"approved":r==="CHANGES_REQUESTED"?"changes":Object.hasOwn(s,o.name)?"sent":"unsent";return[o.name,{state:a,since:a==="sent"?q(s,o.name):null,github:o.github||null}]}))}d(Qt,"reviewOf");function Gt(e,t,s,o,r,a,{held:p,needsApproval:c}){return s?.kind===Re.install?"builder":o==="checking"?"checking":s?"builder":p&&e==="review"?"merge":r&&e==="review"?"owner":e==="review"&&t?.state==="OPEN"&&t.isDraft?a?"ci":"ready":e==="review"||e==="stale"?et(t,c)?"merge":"reviewers":null}d(Gt,"waitsOnOf");const et=d((e,t)=>e?.reviewDecision==="APPROVED"||!t&&!["REVIEW_REQUIRED","CHANGES_REQUESTED"].includes(e?.reviewDecision),"reviewed"),Ie=/(^|\/)prototype-[\w-]+\.html/,Vt={reviewers:"Waiting for PR reviewers",merge:"Approved, merges once CI and the merge state allow",ci:"Marked ready for review once CI passes"},De={waiting:"Approved, Merge shows once CI and the merge state allow",ready:"Approved and green: tap Merge",merging:"Merging"},Ue={auto:"Ready, merges once CI and the merge state allow",waiting:"Ready, Merge shows once CI and the merge state allow",ready:"Ready and green: tap Merge"},Ht=d(e=>`Verified: tap Merge to land it on ${e}`,"heldNext"),Xt="Fix the failing CI",tt="Tap Done once it is settled",Yt=["status","agent","ask-link",Se.answer],nt=d(e=>(e.who==="claude"||e.via==="github")&&!Yt.includes(e.kind),"countsUnread"),zt=/^(?:Not merged yet: |Lander launched headless )/;function Jt({slug:e,wave:t,it:s,order:o,prs:r,repo:a,remote:p,base:c,builder:u,reachedPhase:f,agents:k,worktree:l,eta:h,thread:O,events:m,working:I,said:G,askedAt:_,topicItems:V,boardReviewers:H,now:U,bumpedAt:i,holdAsk:$,closedAt:S,mergeAt:B,autoMerge:re,skipDraft:Z,tickets:T}){const ee=r.map(w=>({repo:w.repo,number:w.number,url:Rt(w),...w.gh||{},status:Ft(w.gh,s)})),W=qt(r.map(w=>w.gh)),N=r[W]?.gh||null,F=ee[W]?.status??null,j=!!S&&String(S)>String(s.status_at||"");j&&s.status!=="merged"&&(s={...s,status:"done"});const Oe=[j&&S,s.status_at,s.merged_at,...r.map(w=>w.gh?.mergedAt)].filter(Boolean).map(String).sort().pop()||null,L=String(Oe||""),D=["done","merged"].includes(s.status)||F===oe.merged&&s.status!=="dropped"?[...G.filter(w=>w.kind==="item-message").map(w=>w.ts),..._].map(String).filter(w=>w>L).sort().pop():void 0,pe=s.owner_step&&!j&&s.status!=="dropped"?String(s.owner_step):null,fe=["done","merged"].includes(s.status)||F===oe.merged;D&&(s={...s,status:void 0,next:void 0});const xe=!!D&&G.some(w=>String(w.ts)>L&&(w.state==="waiting"||w.state==="working")),ae=`${e}/${s.id}`,ge=Ot(t,H),$e=Object.fromEntries(Object.entries(vt(t,s,ae,V)).map(([w,Q])=>[w,bt(Q.ts)])),P=Object.values($e).filter(Boolean).sort()[0]||null,te=[...O.map(w=>w.ts),...m.map(w=>w.ts)].sort().pop()||null,Ae=[...O.filter(w=>nt({...w,who:w.who||"claude"})).map(w=>w.ts),...m.map(w=>w.reply?.at)].filter(Boolean).sort(),X=!Te(s)&&s.pushed?.branch?{branch:s.pushed.branch,at:s.pushed.at||null,host:p?.host||null}:null,K=!Te(s)&&s.merge_ready?.branch?{branch:s.merge_ready.branch,at:s.merge_ready.at||null,base:c}:null;let A=Wt(s,N,k.some(w=>w.started&&!w.done&&z(w)),X||K,!!D,Z);D&&A==="todo"&&(A="progress"),A==="review"&&P&&U-Date.parse(P)>Pt&&(A="stale");const le=!!pe&&fe&&A==="done";le&&(A="review");let M=Gt(A,N,u,(s.status||"").toLowerCase(),X,Z,{held:K,needsApproval:ge.length>0});const ke=M==="builder"&&N?.state==="OPEN"&&!N.isDraft,me=A==="progress"&&!M&&N?.state==="OPEN"&&(!N.isDraft||Z),ne=le||!!D&&A==="progress"&&!M&&!me&&!xe;ne&&(M="owner");const we=$&&String($.ts)>String(s.held_at||""),Be=A==="todo"&&(we?!!$.data.hold:s.status==="held"||!!s.held),Ce=Be&&(we?$.data.until:s.pick_up_at)||null,se=A==="todo"||A==="progress",Ee=k.filter(z).map(w=>String(w.started||"")).filter(Boolean).sort()[0]||"",Me=i&&!(Ee&&String(i)<Ee),qe=O.filter(w=>(w.who||"claude")==="claude"&&w.kind!=="status"&&!zt.test(w.text||"")).map(w=>String(w.ts)).sort().pop()||"",ie=[B,K&&s.merge_ready.tapped].filter(Boolean).map(String).sort().pop(),he=M==="merge"&&!!ie&&ie>qe,ye=O.filter(w=>w.kind==="status"&&String(w.ts)>String(ie)).sort((w,Q)=>String(w.ts).localeCompare(String(Q.ts))).at(-1),n=ye?.text.startsWith("Waiting for ")?`${De.merging} \xB7 ${ye.text[0].toLowerCase()}${ye.text.slice(1)}`:De.merging,g=O.filter(w=>w.kind!=="status").sort((w,Q)=>String(w.ts).localeCompare(String(Q.ts))).at(-1),y=A==="todo"&&g?.kind===Je?g.text:null,x=D||k.filter(z).map(w=>w.started).filter(Boolean).map(String).sort().pop()||s.queued_at||["building","fixing","checking"].includes(String(s.status||"").toLowerCase())&&s.status_at||null,b=N?.reviewDecision==="APPROVED",E=X&&M==="owner"?`merge ${X.branch} into ${c}`:K&&M==="merge"?he?n:Ht(c):M==="merge"&&!re?he?n:(b?De:Ue).waiting:M==="merge"&&!b?Ue.auto:Vt[M],C=F===oe.draft?`Review draft PR #${ee[W].number}`:null;return{key:ae,kind:"work",order:o,wave:e,waveTitle:t.title||e,id:s.id,ref:Pe(t,s,T),title:s.title,status:s.status||null,created:s.created||null,reviewSince:s.review_since||P||s.merge_ready?.at||s.pushed?.at||s.created||null,progressSince:x,closedAt:A==="done"?Oe:null,internal:t.internal===!0,repo:t.internal===!0&&!t.repo?null:a,lane:A,waitsOn:M,builder:M==="checking"?null:u,reachedPhase:f,agents:k,worktree:l??null,eta:h,working:I,reopened:!!D,awaitsDone:ne,ownerStep:ne?pe:null,ownerCheck:ne&&pe&&s.owner_check?{words:It(Dt(s.owner_check.check)),broken:s.owner_check.broken||null}:null,queuedAt:A!=="done"&&s.queued_at||null,queued:!1,deps:s.deps||[],held:Be,heldUntil:Ce,risky:!!s.risky,bumpable:se,bumpedAt:se&&Me?i:null,holdable:A!=="done",hasNotes:!!(s.plan||String(s.notes||"").trim()),finishable:A!=="done",merging:he,refusal:y,manualSteps:s.manual_steps||[],fromAsk:s.from_ask||null,next:ke||A==="done"||M==="checking"?null:me?Xt:E||s.next||C||null,branch:s.branch||N?.headRefName||X?.branch||null,pushed:A==="done"||le?null:X,mergeReady:A==="review"?K:null,ticket:T?.source==="linear"&&!s.ticket&&s.linear?.length?null:Ut(T,s.ticket,s.ticket||(t.internal?Pe(t,s,T):s.linear?.[0]),{repo:a,key:ae}),ticketSource:T?.source||null,linear:(s.linear||[]).map(w=>T?.source==="linear"&&Ye(T,w,{key:ae,repo:a})||Ze(w,T)),pr:ee[W]||null,prs:ee,reviewers:ge.map(w=>w.name),review:Qt(ge,N,$e),inReviewSince:P,threadCount:O.length+m.length,lastTs:te,othersAt:Ae,grilling:lt("grill",m,k,_),prototyping:lt("prototype",m,k,_),state:s.state||null,updatedAt:N?.mergedAt||N?.updatedAt||s.merged_at||u?.at||te||null,ask:null,fold:null,action:null,install:A==="done"?null:en(s.install,N),waitFacts:Zt(s,{agents:k,thread:O,events:m,refusedAt:y?g.ts:null})}}d(Jt,"workItem");function Zt(e,{agents:t,thread:s,events:o,refusedAt:r}){const a=new Set(s.filter(f=>f.kind===Se.answer).map(f=>f.ask)),p=s.filter(f=>f.kind===Se.ask&&!a.has(f.ask)).sort((f,k)=>String(f.ts).localeCompare(String(k.ts)))[0],c=d(f=>o.filter(k=>Fe(k,f)).pop()?.ts||null,"requestAt"),u=s.filter(f=>(f.who||"claude")==="claude"&&!["status","agent",Je].includes(f.kind)).map(f=>f.ts);return{statusAt:e.status_at||null,next:e.next||null,ask:p?{at:p.ts,text:p.text}:null,grillAt:c("grill"),prototypeAt:c("prototype"),refusedAt:r,unverified:t.some(f=>f.calls.some(k=>!f.verified||String(k.at)>String(f.verified.at))),writes:[...u,...t.flatMap(f=>[f.verified?.at,f.started])].filter(Boolean).map(String).sort()}}d(Zt,"waitFactsOf");function en(e,t){if(!e?.url)return null;const s=e.sha?String(e.sha).toLowerCase():null,o=t?.headRefOid?String(t.headRefOid).toLowerCase():null;return{url:e.url,label:e.label||null,builtAt:e.built_at||null,sha:s,older:!!(s&&o&&!o.startsWith(s))}}d(en,"installOf");function tn(e,{needsApproval:t=!0}={}){if(e?.state!=="OPEN")return[`PR is ${String(e?.state||"unknown").toLowerCase()}`];const{state:s,pending:o,failing:r}=e.checks||{};return[e.isDraft&&"it is a draft",!et(e,t)&&`review: ${String(e.reviewDecision||"none").toLowerCase().replace(/_/g," ")}`,s==="fail"&&`failing: ${r?.length?r.join(", "):"a check"}`,s==="pending"&&`pending: ${o?J(o,"check"):"checks"}`,e.mergeStateStatus!=="CLEAN"&&`merge state: ${String(e.mergeStateStatus||"unknown").toLowerCase()}`].filter(Boolean)}d(tn,"mergeBlockers");const nn=d((e,t)=>!tn(e,{needsApproval:t}).length,"mergeable");function sn(e,t,s){if(e.held||de(e))return null;const o=`#item=${encodeURIComponent(e.key)}`;if(st(e)){const r=t.get(e.questions.first);return Ie.test(r?.url||"")?{kind:R.pick,url:`questions.html#${r.id}`,label:"Prototypes ready: pick a take"}:{kind:R.questions,url:`questions.html#${e.questions.first}`,label:`Answer ${J(e.questions.open,"question")}`}}return e.waitsOn==="ready"?{kind:R.ready,url:e.pr.url,label:`Review draft PR #${e.pr.number}`}:e.kind==="ask"&&e.waitsOn==="owner"?{kind:R.done,url:o,label:e.next||tt}:e.waitsOn==="merge"&&e.manualSteps.length&&!e.mergeReady?{kind:R.steps,url:o,label:"Run the manual steps, then merge"}:e.waitsOn==="merge"&&(!s||e.mergeReady)&&!e.merging&&e.prs.every(r=>["MERGED","CLOSED"].includes(r.state)||nn(r,e.reviewers.length>0))?{kind:R.merge,url:o,label:"Merge"}:null}d(sn,"actionOf");const st=d(e=>!!(e.questions&&e.questions.state!=="resolved"&&e.questions.open>0),"asking"),We=new Set([R.pick,R.questions]);function ot(e){return de(e)?null:st(e)?Y.openQuestions:e.action?.kind===R.done?Y.markDone:null}d(ot,"actionButtonOf");const on=d(e=>e.kind==="work"&&e.lane==="todo"&&!e.action&&!e.unpicked,"buildableOf"),rn=d((e,t)=>/^item:/.test(e.topic||"")&&Ie.test(e.url||"")&&!(e.options||[]).some(s=>s.id===t),"picksNoTake");function an(e,t){const s=e.filter(a=>a.topic===`item:${t}`),r=[...s.filter(a=>Ie.test(a.url||"")).flatMap(a=>a.answers.map(p=>({q:a,a:p,again:rn(a,p.choice)}))),...s.filter(a=>a.request).map(a=>({q:a,a:a.request,again:!0}))].sort((a,p)=>String(a.a.ts).localeCompare(String(p.a.ts))).at(-1);return r?.again?{id:r.q.id,note:r.a.note.replace(/\s+/g," ").trim()}:null}d(an,"noTakePicked");const ln="Prototype again",dn=Object.freeze({[R.pick]:Y.openQuestions,[R.questions]:Y.openQuestions,[R.ready]:Y.github,[R.steps]:Y.github,[R.merge]:Y.merge,[R.done]:Y.markDone});function un(e,t,s){if(e.action=sn(e,t,s),e.action?.kind===R.questions&&e.questions.fromAsks){Object.assign(e,{action:null,waitsOn:"owner",next:e.action.label,actionButton:ot(e),resolvedBy:null});return}e.awaitsDone&&=!e.action,e.awaitsDone||Object.assign(e,{ownerStep:null,ownerCheck:null}),e.actionButton=ot(e),e.resolvedBy=dn[e.action?.kind]||null,e.action?.kind===R.merge&&!e.mergeReady&&(e.next=(e.pr?.reviewDecision==="APPROVED"?De:Ue).ready),e.awaitsDone&&Object.assign(e,{lane:"review",next:e.ownerStep||tt,bumpable:!1,bumpedAt:null});const o=e.lane==="todo"||e.waitsOn==="ready"||e.lane==="progress"&&e.waitsOn==="owner";if(!e.action||!o)return;const r=e.lane==="todo"&&We.has(e.action.kind)?"todo":"review";Object.assign(e,{lane:r,waitsOn:e.waitsOn==="ready"?"ready":"owner",next:e.action.label}),r==="review"&&Object.assign(e,{bumpable:!1,bumpedAt:null})}d(un,"placeOwnerWait");const rt=d(e=>e.queuedAt||e.agents.some(z)||["building","fixing"].includes(String(e.status||"").toLowerCase()),"started"),at="none set yet";function as(e,t){const s=String(e.status||"").toLowerCase();if(e.next||Te(e)||e.pushed||e.held||e.queued_at||["held","checking","done","merged","dropped"].includes(s))return!1;const o=t.filter(r=>r?.started);return!o.some(r=>!r.done)&&rt({status:s,agents:o})}d(as,"owesNext");function cn(e,t=new Date){const s=new Date(e),o=s.toLocaleTimeString("en-GB",{hour:"2-digit",minute:"2-digit"});return s.toDateString()===t.toDateString()?o:`${s.getMonth()+1}/${s.getDate()} ${o}`}d(cn,"askedLabel");function pn(e,t,s,o,r,a,p){const c=t.find(S=>S.kind==="ask"&&S.eventId===e.id),u=c.files,f=(c.text||"").trim().split(`
|
|
2
|
+
`)[0]||u.map(S=>S.name).join(", ")||"Ask",k=c.state==="replied"||c.state==="answered",l=r?.state==="progress"&&!(String(a||"")>String(r.at||"")),h=!l&&(!!r||!!a),O=k&&!h&&!l&&p.open>0,m=k&&!h&&!l&&!O&&p.answered>0,I=h||k&&!l&&!O&&!m,G=k&&!o&&!r?.moved&&[...t.filter(S=>S.who==="claude"&&S.kind!=="ask-link"&&S.kind!=="status").map(S=>S.ts),...t.map(S=>S.reply?.at)].filter(Boolean).sort().pop()||null,_=I||l||O||m,V=t.some(S=>S.who==="love"&&S.kind!=="ask"&&(S.state==="waiting"||S.state==="working")),H=_?"answered":c.state,U=_?null:s||null,i=[...t.filter(nt).map(S=>S.ts),...t.map(S=>S.reply?.at)].filter(Boolean).sort(),$=[...t.map(S=>S.ts),...i,...l?[r.at]:[]].filter(Boolean).sort().pop();return{key:`ask:${e.id}`,kind:"ask",order:0,wave:null,waveTitle:null,id:e.id,ref:_?cn(e.ts):"New",isNew:!_,title:f,status:null,internal:!1,repo:null,lane:l?"progress":I?"asks":"todo",waitsOn:l&&!V?"owner":null,builder:null,agents:[],eta:null,working:t.some(S=>S.state==="working"),deps:[],held:!1,risky:!1,bumpable:!_,bumpedAt:U,manualSteps:[],fromAsk:null,from:null,next:l&&r.next||null,hasNotes:!1,branch:null,finishable:_&&!I,linear:[],pr:null,prs:[],reviewers:[],review:{},inReviewSince:null,reviewSince:null,progressSince:l&&r.at||null,closedAt:null,threadCount:t.length,lastTs:$,othersAt:_?i:[],grilling:!1,prototyping:!1,created:e.ts,updatedAt:_?$:e.ts,ask:{eventId:e.id,state:H,askedAt:e.ts,bumpedAt:U,edited:!!c.edited,canEdit:!_,text:c.text,files:u,answerAt:G,from:c.from||null},fold:null,action:null}}d(pn,"askItem");const fn=new Set(["grill","prototype"]),ue=d(e=>fn.has(e.kind),"plans"),z=d(e=>!ue(e),"builds"),gn={grill:"preheating",prototype:"prototyping"},kn={grill:"grill-more",prototype:"prototype-request"},Fe=d((e,t)=>e.kind===kn[t]&&!e.data?.question,"isRequest");function lt(e,t,s,o){if(s.some(a=>a.kind===e&&a.started&&!a.done))return!0;const r=t.filter(a=>Fe(a,e)).pop();return!!(r&&!r.reply&&!o.some(a=>String(a)>String(r.ts))&&!s.some(a=>a.kind===e&&a.done&&String(a.started)>=String(r.ts)))}d(lt,"planningOf");const ls=30*6e4;function is(e,t){const s=[];for(const o of e.items){if(de(o)||(o.agents||[]).some(a=>a.kind==="prototype"&&a.started&&!a.done))continue;const r=o.prototyping&&t.filter(a=>a.data?.key===o.key&&Fe(a,"prototype")).pop();r&&s.push({key:o.key,request:r.id,since:r.ts});for(const a of e.owner.filter(p=>p.topic===`item:${o.key}`&&p.state==="requested"))s.push({key:o.key,request:a.request.id,question:a.id,since:a.request.ts})}return s}d(is,"prototypeWaits");function Ke(e={}){return Object.entries(e).filter(([,t])=>t&&typeof t=="object").map(([t,s])=>({name:t,kind:it(s.kind)||null,model:s.model||(s.kind==="opus"||s.kind==="sonnet"?s.kind:null),label:s.label||null,started:s.started||null,eta:s.done?null:s.eta||null,done:s.done||null,calls:Array.isArray(s.calls)?s.calls:[],verified:s.verified||null})).sort((t,s)=>String(t.started||"").localeCompare(String(s.started||""))||t.name.localeCompare(s.name))}d(Ke,"agentsOf");function mn(e={}){return Object.values(e).filter(t=>t?.worktree?.path).sort((t,s)=>String(t.started||"").localeCompare(String(s.started||""))).at(-1)?.worktree.path??null}d(mn,"worktreeOf");function wn(e,t=[]){const s=e.filter(z).map(o=>String(o.started||"")).filter(Boolean).sort().pop();return s===void 0?null:t.filter(o=>o.kind==="status"&&o.phase&&!o.planner&&String(o.ts)>=s).sort((o,r)=>String(o.ts).localeCompare(String(r.ts))).pop()?.phase||null}d(wn,"reachedPhaseOf");function hn(e,t=[],s){const o=e.filter(l=>l.started&&!l.done),r=o.every(ue),a=r?o:o.filter(z),p=a.map(l=>String(l.started||"")).sort()[0];if(p===void 0)return null;const c=t.filter(l=>l.kind==="status"&&String(l.ts)>=p&&(r||!l.planner)).sort((l,h)=>String(l.ts).localeCompare(String(h.ts))).pop(),u=a.some(l=>l.kind===Re.install)?Re.install:it(a.at(-1).kind),f=c?.ts||p;return{progress:c?c.phase||c.planner?c.text.replace(": "," \xB7 "):c.text||null:r?gn[u]:null,phase:c?.phase||null,at:f,kind:u,planner:r,silentSince:f,stalled:s-Date.parse(f)>=Nt}}d(hn,"builderOf");const Qe={opus:"Claude Opus",sonnet:"Claude Sonnet",codex:"Codex"},yn=d(({model:e,kind:t})=>Qe[e]||Qe[t]||(t==="claude"?Qe.opus:null),"modelName"),it=d(e=>e==="opus"||e==="sonnet"?"claude":e,"providerOf");function Ge(e){const t=yn(e),{kind:s}=e,o=e.label?.replace(/\bbuilder\b/,"implementer").replace(/\bBuilder\b/,"Implementer"),r=d(p=>p[0].toLowerCase()+p.slice(1),"lower");if(t&&ue(e))return`${t} ${s}`;if(t)return`${t} ${r(o?.replace(/^(?:claude\s+)?(?:opus|sonnet|codex)\b\s*/i,"")||"implementer")}`;const a=s?s[0].toUpperCase()+s.slice(1):"";return a?o?o.toLowerCase().includes(s.toLowerCase())?o:`${a} ${r(o)}`:a:o||"Implementer"}d(Ge,"agentName");const dt=d((e,t)=>!!e.resolved&&(!e.resolved_at||String(e.resolved_at)>=String(t)),"resolvedSince");function Sn(e,t,s=[],o=[]){const r=t.filter(l=>l.data?.id===e.id),a=r[r.length-1]||null,p=!!(e.resolved&&e.resolved_at&&a&&String(a.ts)>e.resolved_at),c=e.resolved&&!p,u=s.filter(l=>l.data?.question===e.id&&String(l.ts)>String(a?.ts||"")).at(-1),f=c?o.filter(l=>l.data?.question===e.id&&String(l.ts)>String(e.resolved_at||"")).at(-1):null,k=c?"resolved":u?"requested":a?a.reply?"replied":"answered":"open";return{...e,state:k,request:u?{id:u.id,ts:u.ts,note:u.data.note||"",choice:u.data.choice||null}:null,reask:f?{id:f.id,ts:f.ts}:null,answers:r.map(l=>({ts:l.ts,choice:l.data.choice||null,note:l.data.note||"",files:l.data.files||[],reply:l.reply,acted:dt(e,l.ts)}))}}d(Sn,"ownerQuestion");const ce=d(e=>e.round??(Ie.test(e.url||"")?1:0),"roundOf");function vn(e,t){const s=d((o,r)=>o.confirm_of||(o.options||[]).some(a=>a.id===r.choice),"settles");return e.filter(o=>ce(o)===t&&o.answers.length).map(o=>[o,o.answers.at(-1)]).filter(([o,r])=>s(o,r)).map(([,o])=>o).sort((o,r)=>String(o.ts).localeCompare(String(r.ts))).at(-1)}d(vn,"roundSettledBy");function bn(e){const t=e.find(r=>r.request&&!e.some(a=>ce(a)>ce(r)));if(t)return{n:ce(t)+1,state:"making"};const s=Math.max(0,...e.map(ce));if(!s)return null;const o=vn(e,s);return o?{n:s,state:"closed",take:o.choice}:e.some(r=>ce(r)===s&&r.state!=="resolved")?{n:s,state:"back"}:null}d(bn,"roundState");const ve=d((e,t,s)=>{const o=e.get(t);o?o.push(s):e.set(t,[s])},"add"),be=d((e,t)=>{const s=new Map;for(const o of e)ve(s,t(o),o);return s},"groupBy");function On(e,t,s=new Set){const o=new Set(e.map(p=>p.id)),r=[...e];t.some(p=>!o.has(p.topic))&&r.push({id:"",title:"Other"});const a=be(t,p=>o.has(p.topic)?p.topic:"");return r.map(p=>{const c=a.get(p.id)||[],u=d(l=>c.filter(h=>h.state===l).length,"count"),f=c.every(l=>l.state==="resolved")||s.has(p.id)&&!u("open"),k=c.length&&f?"resolved":"open";return{...p,state:k,questions:c.map(l=>l.id),open:u("open"),answered:u("answered"),replied:u("replied"),resolved:u("resolved"),round:bn(c)}}).filter(p=>p.questions.length)}d(On,"ownerTopics");const ut={"mark-ready":"Mark ready","grill-more":"grill requested","prototype-request":"prototype requested","reask-request":"re-ask requested"},xn="answers submitted",$n="quick build: no grill, as you asked",An=d((e,t)=>{if(e.kind!=="prototype-request"||!t.has(e.data.question))return e.data.text||(e.data.files?.length?"":q(ut,e.kind))||"";const s=t.get(e.data.question),o=(s.options||[]).find(r=>r.id===e.data.choice);return[s.question,o&&`${o.id}. ${o.label}`,e.data.note].filter(Boolean).join(`
|
|
3
|
+
\u2192 `)},"actionText"),Bn=d((e,t)=>e.kind!=="grill-more"&&e.kind!=="prototype-request"?e.kind==="reask-request"?{request:!0}:{}:(e.data.question?t.has(e.data.question):String(e.data.text||"").trim()||e.data.files?.length)?{status:ut[e.kind]}:{request:!0},"requestOf"),Ve=d(e=>[...e.filter(t=>t.who==="claude"&&!["status","agent",Se.answer].includes(t.kind)).map(t=>t.ts),...e.filter(t=>t.kind==="ask").map(t=>t.reply?.at)].filter(Boolean).map(String).sort().pop()||"","claudeAtOf");function ct(e,t={}){const s=new Map,o=new Map;for(const k of t.items||[])ve(s,k.topic,k),o.set(k.id,k.topic);const r=new Map,a=new Map,p=new Map,c=new Map,u=new Map,f=new Map;e.forEach((k,l)=>{f.set(k,l),k.data?.key&&Mt.includes(k.kind)&&ve(r,k.data.key,k),k.kind==="owner-answer"&&o.has(k.data?.id)&&ve(a,o.get(k.data.id),k),k.kind==="ask"&&p.set(k.id,k)});for(const k of e.filter(l=>l.kind==="ask-edited").sort((l,h)=>String(l.ts).localeCompare(String(h.ts)))){c.set(k.data?.id,k.data?.text);for(const l of k.data?.files||[])ve(u,k.data?.id,l)}return{byKey:r,answers:a,asks:p,edits:c,editFiles:u,questions:s,order:f}}d(ct,"threadIndex");const He=d((e,t)=>{const s=t.filter(o=>o?.length);return s.length>1?s.flat().sort((o,r)=>e.order.get(o)-e.order.get(r)):[...s[0]||[]]},"inLogOrder"),En=/; its log is \S*\/\.claude\/kickflip\/logs\/([^/\s]+\.log)\.$/,qn=d(e=>{const t=!e.log&&typeof e.text=="string"?En.exec(e.text):null;return t?{...e,text:`${e.text.slice(0,t.index)}.`,log:t[1]}:e},"withLog");function pt(e,{lines:t=[],events:s=[],owner:o={},working:r={},fromAsk:a=null,links:p=[],agents:c={},moved:u=[],index:f=ct(s,o)}){const k=e.startsWith("ask:")?[e.slice(4)]:[a,...u].filter(Boolean),l=[...new Set([e,...k.map(i=>`ask:${i}`)])],h=new Map((f.questions.get(`item:${e}`)||[]).map(i=>[i.id,i])),O=f.answers.get(`item:${e}`)||[],m=new Set([...h.values()].map(i=>i.round_of).filter(Boolean)),I=Ke(c).filter(z).map(i=>String(i.started||"")).filter(Boolean),G=new Set(O.filter(i=>dt(h.get(i.data.id),i.ts)||m.has(i.data.id)||I.some($=>$>String(i.ts))).map(i=>i.id)),_=[...Ke(c).flatMap(i=>[...i.started?[{ts:i.started,who:"claude",kind:"agent",text:`${Ge(i)} started`,finished:!1,eta:i.done?null:i.eta,...ue(i)?{planner:!0}:{}}]:[],...i.done?[{ts:i.done,who:"claude",kind:"agent",text:`${Ge(i)} finished`,finished:!0,eta:null,...ue(i)?{planner:!0}:{}}]:[],...i.calls.length?[{ts:i.done||i.calls.at(-1).at,who:"claude",kind:"calls",text:Ge(i),calls:i.calls.map($=>$.text),verified:i.verified}]:[]]),...t.map(qn).map(i=>({ts:i.ts,who:i.who||"claude",...i.log?{log:i.log}:{},...i.kind==="status"?{kind:"status",...i.planner?{planner:!0}:{}}:{},...Object.values(Se).includes(i.kind)?{kind:i.kind,ask:i.ask}:{},text:i.text||"",html:i.html||null,link:i.link||null,via:i.via||null})),...He(f,l.map(i=>f.byKey.get(i))).map(i=>({ts:i.ts,who:"love",via:"site",kind:i.kind,text:An(i,h),files:i.data.files||[],eventId:i.id,reply:i.reply,...Bn(i,h)})),...O.map(i=>{const $=[i.data.label||i.data.choice,i.data.note].filter(Boolean).join(" \xB7 ");return{ts:i.ts,who:"love",via:"questions",kind:i.kind,text:`${h.get(i.data.id).question}
|
|
4
|
+
\u2192 ${$}`,files:i.data.files||[],eventId:i.id,reply:i.reply,status:xn}}),...He(f,[...new Set(k)].map(i=>f.asks.has(i)?[f.asks.get(i)]:[])).flatMap(i=>[{ts:i.ts,who:"love",via:"ask",kind:i.kind,text:i.data?.text||"",files:i.data?.files||[],eventId:i.id,reply:i.reply,...u.includes(i.id)?{moved:!0}:{},...typeof i.data?.from?.title=="string"?{from:i.data.from.title}:{}},...i.data?.quick===!0?[{ts:i.ts,who:"love",kind:"quick-build",request:!0,text:$n}]:[],...p.map($=>({ts:i.ts,who:"claude",kind:"ask-link",text:`${$.became?"Became":"Added to"} ${$.ref}`,link:`#item=${encodeURIComponent($.key)}`,html:null,via:null}))])].sort((i,$)=>String(i.ts).localeCompare(String($.ts))),V=Ve(_),{edits:H,editFiles:U}=f;for(const i of _){if(!i.eventId)continue;(i.kind==="ask"||i.kind==="item-message")&&typeof H.get(i.eventId)=="string"&&Object.assign(i,{text:H.get(i.eventId),edited:!0,files:[...i.files,...U.get(i.eventId)||[]]});const $=i.kind==="ask"&&(p.length>0||!!i.moved);i.state=i.reply?"replied":$||G.has(i.eventId)||V>String(i.ts)?"answered":q(r,i.eventId)?"working":"waiting",i.canDelete=i.kind==="item-message"&&(i.state==="working"||i.state==="waiting"),i.canEdit=i.canDelete}return _}d(pt,"threadOf");function _n(e,t,s,o,r,a){const p=d((l,h)=>{const O=a.filter(I=>I.kind==="wave-triage"&&I.data?.[l]===h).pop(),m=r.filter(I=>I.topic===`${l}:${h}`&&(I.state==="open"||I.state==="answered")).length;return{running:!!(O&&!O.reply),questions:m}},"triageOf"),c=new Map(o.map(l=>[l.key,l])),u=d(l=>l.filter(h=>h.lane==="done"&&!je(h)).length,"merged"),f=s.map(l=>({l,cards:l.keys.map(h=>c.get(h)).filter(Boolean)})).map(l=>({...l,open:!l.l.finished_at&&l.cards.some(h=>h.lane!=="done")})).sort((l,h)=>Number(l.open)-Number(h.open));for(const{l,cards:h}of f)for(const O of h)O.lineup={slug:l.slug,title:l.title};const k=f.filter(l=>l.open).map(({l,cards:h})=>({kind:"lineup",slug:l.slug,title:l.title,goal:l.goal,keys:h.map(O=>O.key),phases:l.phases.map(O=>O.filter(m=>c.has(m))).filter(O=>O.length),next:Rn(h),merged:u(h),total:h.length,triage:p("lineup",l.slug)}));return e.fromWaves?[...k,...t.filter(l=>!l.finished).map(l=>({w:l,cards:o.filter(h=>h.wave===l.slug&&h.kind!=="ask")})).filter(({cards:l})=>l.length).map(({w:l,cards:h})=>({kind:"wave",slug:l.slug,title:l.title,goal:null,keys:h.map(O=>O.key),phases:null,next:null,merged:u(h),total:h.length,triage:p("wave",l.slug)}))]:k}d(_n,"waveBandsOf");function Rn(e){const t=e.filter(a=>a.lane==="todo"),s=t.find(a=>a.ready&&a.buildable)||t[0];if(!s)return null;const o=s.ready&&s.buildable,r=o?"ready":s.held?"held":s.blockedBy.length?`waits on ${s.blockedBy.map(a=>a.ref).join(", ")}`:s.action?"needs you":s.unsorted?"new":s.state||"not ready";return{key:s.key,ref:s.ref,ready:o,word:r}}d(Rn,"nextOf");function ds(e,t,s,o){const r=e.waveBands.find(l=>l.kind==="lineup"&&l.slug===t);if(!r)return null;const a=new Map(e.items.map(l=>[l.key,l.lane])),p=r.keys.filter(l=>a.get(l)==="todo"),c=p.indexOf(s),u=c+o;if(c<0||u<0||u>=p.length)return null;[p[c],p[u]]=[p[u],p[c]];let f=0;const k=r.keys.map(l=>a.get(l)==="todo"?p[f++]:l);return f=0,r.phases.map(l=>k.slice(f,f+=l.length))}d(ds,"lineupMoved");const In=d(e=>e.phases||e.items.map(t=>[t]),"lineupPhases");function us(e){const{owner:t={},topicItems:s={},events:o=[],threads:r={},prs:a={},working:p={}}=e,c=e.now??Date.now(),u=ct(o,t),f=new Set(o.filter(n=>n.kind==="ask-withdrawn"&&!n.data?.key).map(n=>n.data?.id)),k=new Map(o.filter(n=>n.kind==="ask"&&!f.has(n.id)).map(n=>[n.id,n])),l=d((n,g)=>new Map(o.filter(y=>y.kind===n).sort((y,x)=>String(y.ts).localeCompare(String(x.ts))).map(y=>[y.data?.[g],y.ts])),"latest"),h=l("ask-bump","id"),O=l("card-unbump","key"),m=new Map([...l("card-bump","key")].filter(([n,g])=>!(String(O.get(n)||"")>String(g)))),I=l("card-done","key"),G=l("card-merge","key"),_=e.autoMerge===!0,V=e.tickets,H=new Map(o.filter(n=>n.kind==="card-hold").sort((n,g)=>String(n.ts).localeCompare(String(g.ts))).map(n=>[n.data?.key,n])),U=new Map,i=e.askLinks||{},$=new Map;for(const[n,g]of Object.entries(i))k.has(n)&&$.set(g,[...$.get(g)||[],n]);const S=[],B=[],re=new Map,Z=new Map,T=new Map,ee=new Map,W=e.github?null:xt(e.remote);for(const{slug:n,wave:g,agents:y={}}of e.waves){const x=e.github||g.repo||null;S.push({slug:n,title:g.title||n,status:g.status||null,internal:!!g.internal,finished:null,reviewers:(g.reviewers||[]).map(b=>b.name)}),g.items.forEach((b,E)=>{const C=`${n}/${b.id}`,w=($.get(C)||[]).filter(v=>v!==b.from_ask),Q=[b.from_ask,...w].filter(v=>k.has(v)),mt=new Set(Q.map(v=>`ask:${v}`)),Xe=[...q(r,C)||[],...Q.flatMap(v=>q(r,`ask:${v}`)||[])];b.from_ask&&U.set(b.from_ask,[...U.get(b.from_ask)||[],{key:C,ref:Pe(g,b,V),became:!0}]);const _e=pt(C,{lines:Xe,events:o,owner:t,working:p,fromAsk:b.from_ask||null,moved:w,index:u});for(const v of _e)v.eventId&&re.set(v.eventId,v.state);Z.set(C,Ve(_e)),B.push(Jt({slug:n,wave:g,it:b,order:E,repo:x,remote:W,base:e.branch||"main",prs:Et(b,x).map(v=>({...v,gh:q(a,_t(v))||null})),...(v=>({agents:v,builder:hn(v,q(r,C),c),reachedPhase:wn(v,q(r,C)),eta:v.map(wt=>wt.eta).filter(Boolean).sort().pop()||null}))(Ke(q(y,b.id))),worktree:mn(q(y,b.id)),thread:Xe,events:[...He(u,[C,...mt].map(v=>u.byKey.get(v))),...Q.map(v=>k.get(v))],working:_e.some(v=>v.state==="working"),said:_e.filter(v=>v.who==="love"&&(v.kind==="item-message"||v.kind==="owner-answer")),askedAt:(u.questions.get(`item:${C}`)||[]).filter(v=>v.asked_at).map(v=>v.asked_at),topicItems:s,boardReviewers:e.reviewers||[],now:c,bumpedAt:m.get(C),holdAsk:H.get(C),closedAt:I.get(C),mergeAt:G.get(C),autoMerge:_,skipDraft:e.skipDraft===!0,tickets:V}));const Ne=B[B.length-1];ee.set(C,{parent:b.parent?gt(n,b.parent):null,related:(b.related||[]).map(v=>gt(n,v))}),Ne.lane==="done"&&T.set(C,Ne.closedAt||String(Ne.updatedAt||""))})}const N=new Map(B.map(n=>[n.key,n])),F=new Map,j=e.try||{},Oe=e.autoBuild===!0,L=["web","app"].includes(j.kind)?j.kind:e.waves.some(({wave:n})=>n.items.some(g=>g.install?.url))?"app":"web";for(const n of B){if(n.kind!=="work"||n.lane==="done")continue;const g=q(j.cards,n.key)||{},y=g.head||n.pr?.headRefOid,x=L==="web"?g.sha:n.install?.sha,b=n.builder?.kind===Re.install?"running":g.state||(L==="app"&&n.install?"ready":null);n.try={kind:L,state:b,stale:!!(x&&y&&!String(y).toLowerCase().startsWith(String(x).toLowerCase())),url:L==="web"?null:n.install?.url||null,port:L==="web"&&b==="ready"&&g.https||null,sha:x||null,changes:g.ahead>0,script:g.script===!0,autoBuild:Oe}}const D=j.main,pe=D?.script&&D.sha?{slug:D.slug,branch:D.branch||"main",sha:D.sha.slice(0,7),at:D.at,kind:L,state:D.state||null,url:L==="web"?null:e.main?.install?.url||null,port:L==="web"&&D.state==="ready"&&D.https||null,pull:j.pull?{running:j.pull.running===!0,error:j.pull.error||null}:null}:null,fe=e.askProgress||{},xe=l("ask-done","id"),ae=be(o.filter(n=>n.kind==="owner-answer"),n=>n.data?.id),ge=be(o.filter(n=>n.kind==="prototype-request"),n=>n.data?.question),$e=be(o.filter(n=>n.kind==="reask-request"),n=>n.data?.question),P=(t.items||[]).map(n=>Sn(n,ae.get(n.id)||[],ge.get(n.id)||[],$e.get(n.id)||[])),te=new Map(P.map(n=>[n.id,n])),Ae=be(P,n=>n.topic),X=d(n=>{const g=Ae.get(n)||[];return{open:g.filter(y=>y.state==="open").length,answered:g.filter(y=>y.state==="answered").length}},"pendingOn");for(const n of k.values()){const g=`ask:${n.id}`,y=N.get(q(i,n.id)),x=[...U.get(n.id)||[]];if(y&&!x.some(E=>E.key===y.key)&&x.push({key:y.key,ref:y.ref,became:!1}),x.length){F.set(g,x[0].key);continue}if(q(i,n.id)){F.set(g,q(i,n.id));continue}const b=pt(g,{lines:q(r,g)||[],events:o,working:p,links:x,index:u});for(const E of b)E.eventId&&re.set(E.eventId,E.state);Z.set(g,Ve(b)),B.push(pn(n,b,h.get(n.id),x.length>0||!!q(i,n.id),q(fe,n.id),xe.get(n.id),X(`item:${g}`)))}for(const n of S){const g=B.filter(y=>y.wave===n.slug);if(n.finished=Kt(g),n.finished)for(const y of g)y.lane==="done"&&(y.fold=Lt(n.slug))}for(const n of B)n.lineup=null;const K=new Set((t.topics||[]).map(n=>n.id)),A=e.waves.flatMap(({slug:n,wave:g})=>(g.lineups||[]).map(y=>{const x=In(y).map(b=>b.map(E=>`${n}/${E}`));return{...y,phases:x,keys:x.flat()}})),le=d(()=>On([...t.topics||[],...B.filter(n=>!K.has(`item:${n.key}`)).map(n=>({id:`item:${n.key}`,card:n.key,title:`${n.ref}: ${n.title}`,...n.kind==="ask"?{askedAt:n.ask.askedAt}:{},summary:"Questions about this work item."})),...A.filter(n=>!K.has(`lineup:${n.slug}`)).map(n=>({id:`lineup:${n.slug}`,title:n.title,summary:"Wave triage."})),...(e.fromWaves?S:[]).filter(n=>!K.has(`wave:${n.slug}`)).map(n=>({id:`wave:${n.slug}`,title:n.title,summary:"Wave triage."}))],P,new Set(B.filter(de).map(n=>`item:${n.key}`))),"topicsOf"),M=new Map(le().map(n=>[n.id,n]));for(const n of B){const g=M.get(`item:${n.key}`);if(!g){n.questions=null;continue}const y=g.questions.filter(E=>te.get(E)?.state!=="resolved"),x=g.questions.find(E=>te.get(E)?.state==="open")||y[0]||g.questions[0],b=g.questions.map(E=>te.get(E)).filter(E=>E?.state==="open");n.questions={topic:g.id,state:g.state,open:g.open,answered:g.answered,replied:g.replied,resolved:g.resolved,first:x,askedAt:b.map(E=>E.asked_at).filter(Boolean).sort(),fromAsks:b.length>0&&b.every(E=>E.ask)}}const ke=new Map(B.map(n=>[n.key,n]));for(const n of B){n.blockedBy=n.kind!=="work"||n.lane==="done"?[]:n.deps.map(y=>ke.get(String(y).includes("/")?y:`${n.wave}/${y}`)).filter(y=>y&&y.lane!=="done"&&!y.reopened).map(ft),un(n,te,_),n.lane==="todo"&&n.kind==="work"&&!n.held&&rt(n)&&(n.lane="progress"),n.unpicked=n.kind==="work"&&n.lane==="todo"&&!!an(P,n.key),n.unpicked&&(n.state="prototyping"),n.unpicked&&!n.action&&(n.next=ln),n.buildable=on(n);const g=n.kind==="work"&&n.lane==="todo";n.unsorted=g&&!!n.fromAsk&&!n.state,n.ready=g&&n.state==="ready"&&!n.blockedBy.length&&!n.action}Mn(B,ke,ee);const me=new Map((t.items||[]).map(n=>[n.id,n.topic])),ne=d(n=>n.kind==="owner-answer"?me.has(n.data?.id)?/^item:(.+)$/.exec(me.get(n.data.id))?.[1]||null:"":n.kind==="ask"?`ask:${n.id}`:n.data?.key||(n.kind.startsWith("ask-")&&n.data?.id?`ask:${n.data.id}`:null),"ownCardOf"),we=d(n=>{const g=ne(n);return F.get(g)||g},"cardOf"),Be=d(n=>{const g=n.startsWith("ask:")?n.slice(4):null,y=g&&q(fe,g)?.state==="done"?q(fe,g).at:null;return[I.get(n),g&&xe.get(g),y,T.get(n)].filter(Boolean).sort().pop()||""},"closedAt"),Ce=d(n=>{const g=we(n);return g===null?!n.reply:!ke.has(g)||!(Be(g)<String(q(p,n.id)))?!1:re.has(n.id)?re.get(n.id)==="working":!n.reply&&!F.has(ne(n))&&!(String(Z.get(g)||"")>String(n.ts))},"unanswered"),se=o.filter(n=>q(p,n.id)&&Ce(n)).map(n=>({id:n.id,kind:n.kind,key:we(n),data:n.data,since:q(p,n.id)})),Ee=e.autoPull===!0;for(const n of B)n.working=se.some(g=>g.key===n.key),n.kind==="work"&&(n.lane==="progress"&&n.queuedAt||Ee&&n.lane==="todo"&&kt(n))&&Object.assign(n,{lane:"todo",queued:!0}),n.kind==="work"&&n.lane==="progress"&&!n.builder&&!n.next&&n.waitsOn!=="checking"&&!n.working&&!n.queued&&(n.next=at),n.sortAt=(n.queued?n.queuedAt||n.created:Nn[n.lane](n))||(["review","stale"].includes(n.lane)?null:n.updatedAt)||null;for(const n of B){if(n.kind!=="work")continue;const g=(Ae.get(`item:${n.key}`)||[]).filter(x=>x.state==="answered").map(x=>x.answers.at(-1).ts).sort().pop(),y=se.filter(x=>x.key===n.key).map(x=>String(x.since)).sort()[0];n.orchestrator=["progress","todo"].includes(n.lane)?Vn(n,n.waitFacts,{answeredAt:g,workingSince:y}):null,delete n.waitFacts}B.sort(Ct);const Me=_n(e,S,A,B,P,o),qe=le(),ie=$t({on:e.autoReview,incoming:e.incoming,records:e.reviewRecords,events:o,now:c,refs:new Map(B.filter(n=>n.kind==="work").map(n=>[n.key,n.ref]))}),he=[...Ln(B,qe,P),...At(ie)],ye=B.filter(n=>n.ask?.answerAt).sort((n,g)=>g.ask.answerAt.localeCompare(n.ask.answerAt)).map(n=>({key:n.key,id:n.id,text:n.title,at:n.ask.answerAt}));return Dn({kind:L,main:pe,remote:W&&{host:W.host,name:`${W.host}/${W.path}`},waves:S,waveBands:Me,items:B,moved:Object.fromEntries(F),answers:ye,owner:P,ownerTopics:qe,reviews:ie,needs:he,working:se.length,workingOn:se,checks:Tn(e,c)},e)}d(us,"buildBoard");function Dn(e,{listen:t,prompts:s}={}){const o=[...Pn(s),...e.needs];return{...e,needs:o,needsSummary:jn(o),checks:{...e.checks,watcher:{listening:"running",asleep:"asleep",waking:"waking"}[t]||"stopped"}}}d(Dn,"withHub");const ft=d(e=>({key:e.key,ref:e.ref}),"linkOf"),gt=d((e,t)=>String(t).includes("/")?String(t):`${e}/${t}`,"linkKey"),Cn=d((e,t)=>e.ref.localeCompare(t.ref,void 0,{numeric:!0}),"byRef");function Mn(e,t,s){const o=new Map,r=new Map,a=new Map,p=d((u,f,k)=>u.set(f,[...u.get(f)||[],k]),"add");for(const[u,f]of s){f.parent&&p(o,f.parent,u);for(const k of f.related)k!==u&&(p(r,u,k),p(r,k,u))}for(const u of e)for(const f of u.blockedBy||[])p(a,f.key,u.key);const c=d((u=[])=>[...new Set(u)].map(f=>t.get(f)).filter(Boolean).map(ft).sort(Cn),"cards");for(const u of e){const f=s.get(u.key)?.parent;u.parent=f?{key:f,ref:t.get(f)?.ref||f.slice(f.indexOf("/")+1)}:null,u.children=c(o.get(u.key)),u.related=c(r.get(u.key)),u.waitsOnIt=c(a.get(u.key))}}d(Mn,"linkCards");const Nn={todo:d(e=>e.created,"todo"),progress:d(e=>e.progressSince,"progress"),review:d(e=>e.reviewSince,"review"),stale:d(e=>e.reviewSince,"stale"),done:d(e=>e.closedAt,"done"),asks:d(e=>e.ask.answerAt,"asks")},J=d((e,t)=>`${e} ${t}${e===1?"":"s"}`,"plural");function Tn({checked:e,watcher:t,replyAt:s,threads:o={},working:r={},waves:a=[]},p){const c=Number(e?.every)||30,f=[s,t?.since,...Object.values(r),...Object.values(o).flat().filter(l=>(l.who||"claude")==="claude").map(l=>l.ts),...a.flatMap(l=>Object.values(l.agents||{}).flatMap(h=>Object.values(h||{})).map(h=>h?.done||h?.started))].filter(l=>typeof l=="string"&&!Number.isNaN(Date.parse(l))).reduce((l,h)=>l&&Date.parse(l)>Date.parse(h)?l:h,null),k=!!e?.idle;return{at:e?.at||null,every:k?null:c,idle:k,late:!k&&!!e?.at&&p-Date.parse(e.at)>2*c*6e4,claudeAt:f}}d(Tn,"checksOf");function Ln(e,t,s){const o=new Set(e.map(c=>c.questions?.topic).filter(Boolean)),r=d(c=>c.find(u=>s.find(f=>f.id===u)?.state==="open")||c[0],"firstOpen"),a=t.filter(c=>c.state!=="resolved"&&c.open>0&&!o.has(c.id)).map(c=>{const u=`questions.html#${r(c.questions)}`,f=/^(?:lineup|wave):(.+)$/.exec(c.id)?.[1]||null;return{kind:"Question",title:c.title,text:`${c.title} \xB7 ${J(c.open,"question")}`,url:u,topic:c.id,band:f,questions:{open:c.open,url:u},ready:null}}),p=e.filter(c=>!c.held).map(c=>{const u=c.questions&&c.questions.state!=="resolved"&&c.questions.open>0?{open:c.questions.open,url:`questions.html#${c.questions.first}`}:null,f=c.waitsOn==="ready"?c.pr.url:null,k=!u&&!f&&c.action?{label:c.action.label,url:c.action.url}:null;if(!u&&!f&&!k)return null;const l=`${c.ref}: ${c.title}`,h=[l,u&&J(u.open,"question"),f&&"Ready on GitHub",k?.label].filter(Boolean).join(" \xB7 ");return{kind:u?"Question":f?"Tap Ready":"Action needed",title:l,text:h,url:`#item=${encodeURIComponent(c.key)}`,key:c.key,questions:u,ready:f,action:k}}).filter(Boolean);return[...a,...p]}d(Ln,"needsOf");function Pn(e=[]){return e.map(t=>{const s=t.questions?.length?`Question: ${t.questions.map(o=>o.question).join(" \xB7 ")}`:`${t.tool}: ${t.detail||t.action}`;return{kind:"Prompt",title:s,text:s,url:"",prompt:t}})}d(Pn,"promptLines");function jn(e){const t=e.filter(a=>a.prompt).length,s=e.reduce((a,p)=>a+(p.questions?.open||0),0),o=e.filter(a=>a.ready).length,r=e.filter(a=>a.review).length;return[t&&J(t,"prompt"),s&&J(s,"question"),o&&`${J(o,"draft")} waiting on Ready`,r&&`${J(r,"review")} held`].filter(Boolean).join(" \xB7 ")}d(jn,"needsSummary");function cs(e){const t=d((...s)=>e.items.filter(o=>s.includes(o.lane)).length,"count");return{items:e.needs.map(({kind:s,text:o,url:r})=>({kind:s,text:o,url:r})),working:e.working,summary:e.needsSummary,lanes:{review:t("review","stale"),progress:t("progress"),todo:t("todo")},building:e.items.some(s=>(s.agents||[]).some(o=>!o.done))}}d(cs,"needsAnswer");const kt=d(e=>e.buildable&&e.state==="ready"&&!e.held&&!e.unsorted&&!e.blockedBy.length&&!e.questions?.open&&!e.questions?.answered&&!e.grilling&&!e.prototyping,"queueable"),Un=10;function Wn(e,{cap:t=Un,pulled:s=[]}={}){const o=e.filter(m=>!de(m)),r=d(m=>(m.agents||[]).filter(I=>!I.done),"running"),a=o.filter(m=>m.waitsOn==="checking"||r(m).some(z)).length,p=o.filter(m=>kt(m)&&!m.working).map(m=>m.key),c=o.filter(m=>s.includes(m.key)&&(p.includes(m.key)||m.buildable&&m.working)).map(m=>m.key),u=d(m=>m.queued&&m.queuedAt&&!m.blockedBy.length,"tapped"),f=o.filter(m=>u(m)||p.includes(m.key)&&!c.includes(m.key)).slice(0,Math.max(0,t-a-c.length)),k=f.filter(u).map(m=>m.key),l=f.filter(m=>!u(m)).map(m=>m.key),O=o.some(m=>m.grilling||m.prototyping||r(m).some(ue))?[]:o.filter(m=>(m.buildable||m.unpicked&&!m.action)&&m.state!=="ready"&&!m.held&&!m.questions?.open&&!m.questions?.answered&&!m.working).map(m=>m.key);return{idle:!a,pullable:p,waiting:c,starts:k,pulls:l,plannable:O}}d(Wn,"pullOf");function Fn(e){return e.filter(t=>t.lane==="todo"&&(!t.queued||t.buildable)||t.lane==="stale").map(t=>JSON.stringify([t.key,t.title,t.deps||[]])).sort().join(`
|
|
5
|
+
`)}d(Fn,"todoOf");function ps(e){const t=e.filter(o=>o.kind==="work"&&o.lane!=="done"&&o.questions?.open>0),s=t.map(o=>JSON.stringify([o.key,o.questions.open,o.questions.askedAt||[]])).sort().join(`
|
|
6
|
+
`);return{waiting:t.map(o=>o.key),seen:`${s}
|
|
7
7
|
--
|
|
8
|
-
${
|
|
8
|
+
${Fn(e)}`}}d(ps,"triageOf");const Kn=100,Qn=["key","kind","id","ref","title","lane","status","wave","waveTitle","fold","internal","ticket","ticketSource","linear","pr","prs","review","reviewers","risky","sortAt","threadCount","othersAt","hasNotes","questions","next","ask","action","actionButton","builder","working","waitsOn","reachedPhase","grilling","refusal","install","parent","children","related","waitsOnIt","blockedBy","ready","finishable","reopened","manualSteps","branch","worktree"],fs=d(e=>Object.fromEntries(Qn.filter(t=>t in e).map(t=>[t,e[t]])),"slimDone");function gs(e,t=Kn){const s=new Set((e.waveBands||[]).flatMap(r=>r.keys)),o=new Set(e.items.filter(r=>r.lane!=="done").flatMap(r=>[...(r.deps||[]).map(a=>String(a).includes("/")?String(a):`${r.wave}/${a}`),r.parent?.key,...(r.children||[]).map(a=>a.key),...(r.related||[]).map(a=>a.key)]));return e.items.filter(r=>r.lane==="done"&&r.kind==="work").sort((r,a)=>String(a.closedAt||"").localeCompare(String(r.closedAt||""))).slice(t).filter(r=>["done","merged","dropped"].includes(r.status)&&!s.has(r.key)&&!o.has(r.key)&&(!r.questions||r.questions.state==="resolved"))}d(gs,"archivedOf");const Gn=d(e=>e.startsWith("ask:")?`ask-${e.slice(4)}`:`item-${e}`,"pushTag");function ks(e){const t=d(a=>e.owner.filter(p=>p.topic===a&&p.state==="open").map(({id:p,question:c})=>({id:p,question:c})),"open"),s=[R.ready,R.merge],o=e.items.filter(a=>a.action).map(a=>{const p=We.has(a.action.kind)?t(a.questions.topic):[];return{tag:Gn(a.key),kind:a.action.kind,title:`${a.ref}: ${a.action.label}`,body:p[0]?.question||a.title,url:p.length?a.action.url:`#item=${encodeURIComponent(a.key)}`,now:!s.includes(a.action.kind),questions:p}}),r=e.needs.filter(a=>a.topic).map(a=>{const p=t(a.topic);return{tag:`topic-${a.topic}`,kind:R.questions,title:a.text,body:p[0]?.question||a.title,url:a.url,now:!0,questions:p}});return[...o,...r,...Bt(e.reviews)]}d(ks,"ownerActions");function ms(e,t){const s=e.owner.find(a=>a.id===t),o=/^item:([^/:]+\/[^/]+)$/.exec(s?.topic||"")?.[1],r=o&&e.items.find(a=>a.key===o);return!r||r.kind!=="work"||r.lane==="done"||r.held||r.blockedBy.length||r.action||r.unpicked||r.pr||r.pushed||![null,"planned","waiting"].includes(r.status)||r.agents.some(a=>!a.done)||e.owner.some(a=>a.topic===s.topic&&a.state==="open")?null:o}d(ms,"startedByAnswer");function ws(e,{pulled:t=[]}={}){return!e.items.filter(r=>!de(r)).some(r=>r.waitsOn==="checking"||r.grilling||r.prototyping||(r.agents||[]).some(a=>!a.done))&&!e.working&&!Wn(e.items,{pulled:t}).waiting.length}d(ws,"strictlyIdle");function Vn(e,t,{answeredAt:s,workingSince:o}){const r=d(f=>e.agents.some(k=>k.kind===f&&k.started&&!k.done),"running"),a=[e.progressSince,...e.agents.map(f=>f.done)].filter(Boolean).map(String).sort().pop()||null,p=t.ask?{kind:"ask",since:t.ask.at,next:`Answer the builder's ask: ${t.ask.text}`}:s?{kind:"answer",since:s,next:"Act on the owner's answer"}:We.has(e.action?.kind)?null:e.waitsOn==="checking"?t.unverified?{kind:"calls",since:t.statusAt,next:t.next||"Verify the builder's judgment calls"}:{kind:"checking",since:t.statusAt,next:t.next||"Land the checked build"}:e.grilling&&t.grillAt&&!r("grill")?{kind:"grill",since:t.grillAt,next:"Start the grill asked for"}:e.prototyping&&t.prototypeAt&&!r("prototype")?{kind:"prototype",since:t.prototypeAt,next:"Draw the prototype asked for"}:e.refusal&&t.refusedAt?{kind:"refused",since:t.refusedAt,next:e.refusal}:e.next===at&&a?{kind:"unowned",since:a,next:"Start a builder on it, or set its Next"}:null;if(!p?.since)return null;const c=String(p.since),u=[t.writes.find(f=>f>c),o].filter(Boolean).sort()[0]||null;return{kind:p.kind,since:c,picked:u,next:p.next}}d(Vn,"orchestratorWaitOf");function hs(e){return e.items.filter(t=>t.orchestrator).map(t=>({key:t.key,ref:t.ref,title:t.title,...t.orchestrator}))}d(hs,"orchestratorWaits");const ys=30*6e4,Hn=15;function Ss(e,{idle:t,now:s}){const o=s.toISOString(),r=e.last||o;return t?e.since?e.pushed||s-Date.parse(e.since)<Hn*6e4?{state:{...e,last:r},push:!1}:{state:{last:o,since:e.since,pushed:!0},push:!0}:{state:{last:r,since:o},push:!1}:{state:{last:r},push:!1}}d(Ss,"idleStep");function vs(e,t){const s=d(u=>String(u.closedAt||"")>t,"after"),o=e.filter(u=>u.kind==="work"&&u.lane==="done"&&!je(u)&&s(u)),r=new Map;for(const u of e)u.lineup&&r.set(u.lineup.slug,[...r.get(u.lineup.slug)||[],u]);const a=[...r.values()].filter(u=>u.every(f=>f.lane==="done")&&u.some(s)).map(u=>u[0].lineup.title),p=o.slice(0,3).map(u=>u.ref).join(", ")+(o.length>3?` and ${o.length-3} more`:"");return[o.length&&`${o.length} ${o.length===1?"card":"cards"} landed: ${p}`,...a.map(u=>`${u} finished`)].filter(Boolean).join(" \xB7 ")||"Nothing closed since the last idle notification."}d(vs,"idleSummary");export{R as ACTION_KIND,Se as BUILDER_ASK,Re as BUILDER_KIND,Nt as BUILDER_STALLED_AFTER,Je as BUILD_REFUSED,Y as BUTTON,Qn as DONE_FIELDS,Kn as DONE_KEPT,Hn as IDLE_MINUTES,Mt as ITEM_EVENTS,rs as ITEM_STATUSES,Un as MAX_IMPLEMENTERS,at as NO_NEXT,ys as ORCHESTRATOR_PUSH_AFTER,gn as PLANNER_WORD,ln as PROTOTYPE_AGAIN,ls as PROTOTYPE_WAIT,oe as PR_STATE,gs as archivedOf,us as buildBoard,z as builds,Ss as idleStep,vs as idleSummary,ds as lineupMoved,In as lineupPhases,tn as mergeBlockers,yn as modelName,cs as needsAnswer,an as noTakePicked,hs as orchestratorWaits,as as owesNext,ks as ownerActions,Sn as ownerQuestion,rn as picksNoTake,ue as plans,is as prototypeWaits,it as providerOf,Wn as pullOf,Gn as pushTag,Pe as refOf,ce as roundOf,vn as roundSettledBy,fs as slimDone,ms as startedByAnswer,ws as strictlyIdle,ct as threadIndex,pt as threadOf,Fn as todoOf,ps as triageOf,Lt as waveFold,Dn as withHub};
|