@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.
Files changed (51) hide show
  1. package/CONTEXT.md +32 -16
  2. package/SKILL.md +8 -6
  3. package/bin/doctor.mjs +4 -4
  4. package/bin/install.mjs +3 -3
  5. package/bin/kickflip.mjs +8 -8
  6. package/bin/wait-events.mjs +2 -2
  7. package/board/bin/board.mjs +50 -28
  8. package/board/domain.mjs +7 -7
  9. package/board/finish.mjs +4 -3
  10. package/board/headless.mjs +4 -3
  11. package/board/merge3.mjs +1 -0
  12. package/board/on-host.mjs +1 -1
  13. package/board/permissions.mjs +2 -2
  14. package/board/public/app-asks.js +6 -89
  15. package/board/public/app-cards.js +19 -10
  16. package/board/public/app-core.js +1 -20
  17. package/board/public/app-reader.js +174 -0
  18. package/board/public/app-threads.js +49 -35
  19. package/board/public/app.html +2 -0
  20. package/board/public/app.js +28 -16
  21. package/board/public/hub.css +1 -0
  22. package/board/public/new-task.js +121 -0
  23. package/board/public/shell.js +4 -0
  24. package/board/public/sidebar.css +1 -0
  25. package/board/public/style.css +91 -3
  26. package/board/public/ui-cards.js +46 -21
  27. package/board/public/ui-enums.js +1 -1
  28. package/board/public/ui-phase-bars.js +4 -4
  29. package/board/public/ui-questions.js +47 -18
  30. package/board/public/ui-sidebar.js +9 -3
  31. package/board/public/ui-threads.js +25 -12
  32. package/board/public/ui.js +42 -12
  33. package/board/public/view-frame.js +1 -0
  34. package/board/public/view-questions.js +45 -16
  35. package/board/public/view-settings.js +38 -19
  36. package/board/server.mjs +20 -18
  37. package/board/store.mjs +15 -13
  38. package/board/worktree.mjs +20 -20
  39. package/hub/boards.mjs +4 -4
  40. package/hub/front-door.mjs +20 -0
  41. package/hub/github.mjs +3 -3
  42. package/hub/hub.mjs +9 -12
  43. package/hub/new-board.mjs +2 -2
  44. package/hub/owner-checks.mjs +1 -0
  45. package/hub/readiness.mjs +2 -2
  46. package/hub/sessions.mjs +2 -2
  47. package/hub/try.mjs +7 -7
  48. package/package.json +1 -1
  49. package/shared/owner-check.mjs +3 -0
  50. package/shared/tailscale.mjs +1 -0
  51. package/shared/usage-gate.mjs +1 -1
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- var E=Object.defineProperty;var i=(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 w from"node:fs";import m from"node:path";import{pathToFileURL as W}from"node:url";import*as n from"../store.mjs";import{boardWorktree as z,builderRepo as G,holdingLandLock as M,land as J,worktreesDir as F}from"../worktree.mjs";import{fileFindings as U}from"../arch-review.mjs";import{finishCard as x}from"../finish.mjs";import{ITEM_STATUSES as K}from"../domain.mjs";import{boardAt as Q,readManifest as V}from"../../hub/boards.mjs";import"../../hub/board-repo.mjs";const X=`usage: node board.mjs <command> [--site <route>] ...
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: ${K.join(", ")}
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 session merges an approved PR once CI is green and
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 session merges on its
270
- card-merge event. Board Settings' toggle writes it too
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 sends card-pull {key} for the top ready Todo
291
- cards nothing blocks, holds or asks about, one per free
292
- implementer slot (see max-implementers), each once; with
293
- none ready and nothing building, card-plan {key} for the
294
- top unready card to grill or prototype. Off by default.
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]||c.map(S=>S.name).join(", ")||"Ask",k=u.state==="replied"||u.state==="answered",i=r?.state==="progress"&&!(String(a||"")>String(r.at||"")),h=!i&&(!!r||!!a),O=k&&!h&&!i&&p.open>0,w=k&&!h&&!i&&!O&&p.answered>0,M=h||k&&!i&&!O&&!w,Q=k&&!o&&!r?.moved&&[...n.filter(S=>S.who==="claude"&&S.kind!=="ask-link"&&S.kind!=="status").map(S=>S.ts),...n.map(S=>S.reply?.at)].filter(Boolean).sort().pop()||null,E=M||i||O||w,G=n.some(S=>S.who==="love"&&S.kind!=="ask"&&(S.state==="waiting"||S.state==="working")),V=E?"answered":u.state,P=E?null:s||null,l=[...n.filter(ze).map(S=>S.ts),...n.map(S=>S.reply?.at)].filter(Boolean).sort(),$=[...n.map(S=>S.ts),...l,...i?[r.at]:[]].filter(Boolean).sort().pop();return{key:`ask:${e.id}`,kind:"ask",order:0,wave:null,waveTitle:null,id:e.id,ref:E?tn(e.ts):"New",isNew:!E,title:f,status:null,internal:!1,repo:null,lane:i?"progress":M?"asks":"todo",waitsOn:i&&!G?"owner":null,builder:null,agents:[],eta:null,working:n.some(S=>S.state==="working"),deps:[],held:!1,risky:!1,bumpable:!E,bumpedAt:P,manualSteps:[],fromAsk:null,from:null,next:i&&r.next||null,hasNotes:!1,branch:null,finishable:E&&!M,linear:[],pr:null,prs:[],reviewers:[],review:{},inReviewSince:null,progressSince:i&&r.at||null,closedAt:null,threadCount:n.length,lastTs:$,othersAt:E?l:[],grilling:!1,prototyping:!1,created:e.ts,updatedAt:E?$:e.ts,ask:{eventId:e.id,state:V,askedAt:e.ts,bumpedAt:P,edited:!!u.edited,canEdit:!E,text:u.text,files:c,answerAt:Q,from:u.from||null},fold:null,action:null}}d(nn,"askItem");const sn=new Set(["grill","prototype"]),re=d(e=>sn.has(e.kind),"plans"),Y=d(e=>!re(e),"builds"),on={grill:"preheating",prototype:"prototyping"},rn={grill:"grill-more",prototype:"prototype-request"},Le=d((e,n)=>e.kind===rn[n]&&!e.data?.question,"isRequest");function nt(e,n,s,o){if(s.some(a=>a.kind===e&&a.started&&!a.done))return!0;const r=n.filter(a=>Le(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(nt,"planningOf");function Te(e={}){return Object.entries(e).filter(([,n])=>n&&typeof n=="object").map(([n,s])=>({name:n,kind:st(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((n,s)=>String(n.started||"").localeCompare(String(s.started||""))||n.name.localeCompare(s.name))}d(Te,"agentsOf");function an(e={}){return Object.values(e).filter(n=>n?.worktree?.path).sort((n,s)=>String(n.started||"").localeCompare(String(s.started||""))).at(-1)?.worktree.path??null}d(an,"worktreeOf");function ln(e,n=[]){const s=e.filter(Y).map(o=>String(o.started||"")).filter(Boolean).sort().pop();return s===void 0?null:n.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(ln,"reachedPhaseOf");function dn(e,n=[],s){const o=e.filter(i=>i.started&&!i.done),r=o.every(re),a=r?o:o.filter(Y),p=a.map(i=>String(i.started||"")).sort()[0];if(p===void 0)return null;const u=n.filter(i=>i.kind==="status"&&String(i.ts)>=p&&(r||!i.planner)).sort((i,h)=>String(i.ts).localeCompare(String(h.ts))).pop(),c=a.some(i=>i.kind===$e.install)?$e.install:st(a.at(-1).kind),f=u?.ts||p;return{progress:u?u.phase||u.planner?u.text.replace(": "," \xB7 "):u.text||null:r?on[c]:null,phase:u?.phase||null,at:f,kind:c,planner:r,silentSince:f,stalled:s-Date.parse(f)>=qt}}d(dn,"builderOf");const Pe={opus:"Claude Opus",sonnet:"Claude Sonnet",codex:"Codex"},un=d(({model:e,kind:n})=>Pe[e]||Pe[n]||(n==="claude"?Pe.opus:null),"modelName"),st=d(e=>e==="opus"||e==="sonnet"?"claude":e,"providerOf");function je(e){const n=un(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(n&&re(e))return`${n} ${s}`;if(n)return`${n} ${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(je,"agentName");const ot=d((e,n)=>!!e.resolved&&(!e.resolved_at||String(e.resolved_at)>=String(n)),"resolvedSince");function cn(e,n,s=[]){const o=n.filter(f=>f.data?.id===e.id),r=o[o.length-1]||null,a=!!(e.resolved&&e.resolved_at&&r&&String(r.ts)>e.resolved_at),p=e.resolved&&!a,u=s.filter(f=>f.data?.question===e.id&&String(f.ts)>String(r?.ts||"")).at(-1),c=p?"resolved":r?r.reply?"replied":"answered":"open";return{...e,state:c,request:u?{id:u.id,ts:u.ts,note:u.data.note||"",choice:u.data.choice||null}:null,answers:o.map(f=>({ts:f.ts,choice:f.data.choice||null,note:f.data.note||"",files:f.data.files||[],reply:f.reply,acted:ot(e,f.ts)}))}}d(cn,"ownerQuestion");const ae=d(e=>e.round??(xe.test(e.url||"")?1:0),"roundOf");function pn(e,n){const s=d((o,r)=>o.confirm_of||(o.options||[]).some(a=>a.id===r.choice),"settles");return e.filter(o=>ae(o)===n&&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(pn,"roundSettledBy");function fn(e){const n=e.find(r=>r.request&&!e.some(a=>ae(a)>ae(r)));if(n)return{n:ae(n)+1,state:"making"};const s=Math.max(0,...e.map(ae));if(!s)return null;const o=pn(e,s);return o?{n:s,state:"closed",take:o.choice}:e.some(r=>ae(r)===s&&r.state!=="resolved")?{n:s,state:"back"}:null}d(fn,"roundState");const fe=d((e,n,s)=>{const o=e.get(n);o?o.push(s):e.set(n,[s])},"add"),Be=d((e,n)=>{const s=new Map;for(const o of e)fe(s,n(o),o);return s},"groupBy");function gn(e,n,s=new Set){const o=new Set(e.map(p=>p.id)),r=[...e];n.some(p=>!o.has(p.topic))&&r.push({id:"",title:"Other"});const a=Be(n,p=>o.has(p.topic)?p.topic:"");return r.map(p=>{const u=a.get(p.id)||[],c=d(i=>u.filter(h=>h.state===i).length,"count"),f=u.every(i=>i.state==="resolved")||s.has(p.id)&&!c("open"),k=u.length&&f?"resolved":"open";return{...p,state:k,questions:u.map(i=>i.id),open:c("open"),answered:c("answered"),replied:c("replied"),resolved:c("resolved"),round:fn(u)}}).filter(p=>p.questions.length)}d(gn,"ownerTopics");const mn={"mark-ready":"Mark ready","grill-more":"grill requested","prototype-request":"prototype requested"},kn="quick build: no grill, as you asked",wn=d((e,n)=>{if(e.kind!=="prototype-request"||!n.has(e.data.question))return e.data.text||A(mn,e.kind)||"";const s=n.get(e.data.question),o=(s.options||[]).find(r=>r.id===e.data.choice);return[`Prototype please:${s.question}`,o&&`${o.id}. ${o.label}`,e.data.note].filter(Boolean).join(`
3
- \u2192 `)},"actionText"),Ue=d(e=>[...e.filter(n=>n.who==="claude"&&!["status","agent",Ce.answer].includes(n.kind)).map(n=>n.ts),...e.filter(n=>n.kind==="ask").map(n=>n.reply?.at)].filter(Boolean).map(String).sort().pop()||"","claudeAtOf");function rt(e,n={}){const s=new Map,o=new Map;for(const k of n.items||[])fe(s,k.topic,k),o.set(k.id,k.topic);const r=new Map,a=new Map,p=new Map,u=new Map,c=new Map,f=new Map;e.forEach((k,i)=>{f.set(k,i),k.data?.key&&At.includes(k.kind)&&fe(r,k.data.key,k),k.kind==="owner-answer"&&o.has(k.data?.id)&&fe(a,o.get(k.data.id),k),k.kind==="ask"&&p.set(k.id,k)});for(const k of e.filter(i=>i.kind==="ask-edited").sort((i,h)=>String(i.ts).localeCompare(String(h.ts)))){u.set(k.data?.id,k.data?.text);for(const i of k.data?.files||[])fe(c,k.data?.id,i)}return{byKey:r,answers:a,asks:p,edits:u,editFiles:c,questions:s,order:f}}d(rt,"threadIndex");const We=d((e,n)=>{const s=n.filter(o=>o?.length);return s.length>1?s.flat().sort((o,r)=>e.order.get(o)-e.order.get(r)):[...s[0]||[]]},"inLogOrder"),hn=/; its log is \S*\/\.claude\/kickflip\/logs\/([^/\s]+\.log)\.$/,yn=d(e=>{const n=!e.log&&typeof e.text=="string"?hn.exec(e.text):null;return n?{...e,text:`${e.text.slice(0,n.index)}.`,log:n[1]}:e},"withLog");function at(e,{lines:n=[],events:s=[],owner:o={},working:r={},fromAsk:a=null,links:p=[],agents:u={},moved:c=[],index:f=rt(s,o)}){const k=e.startsWith("ask:")?[e.slice(4)]:[a,...c].filter(Boolean),i=[...new Set([e,...k.map(l=>`ask:${l}`)])],h=new Map((f.questions.get(`item:${e}`)||[]).map(l=>[l.id,l])),O=f.answers.get(`item:${e}`)||[],w=new Set([...h.values()].map(l=>l.round_of).filter(Boolean)),M=Te(u).filter(Y).map(l=>String(l.started||"")).filter(Boolean),Q=new Set(O.filter(l=>ot(h.get(l.data.id),l.ts)||w.has(l.data.id)||M.some($=>$>String(l.ts))).map(l=>l.id)),E=[...Te(u).flatMap(l=>[...l.started?[{ts:l.started,who:"claude",kind:"agent",text:`${je(l)} started`,finished:!1,eta:l.done?null:l.eta,...re(l)?{planner:!0}:{}}]:[],...l.done?[{ts:l.done,who:"claude",kind:"agent",text:`${je(l)} finished`,finished:!0,eta:null,...re(l)?{planner:!0}:{}}]:[],...l.calls.length?[{ts:l.done||l.calls.at(-1).at,who:"claude",kind:"calls",text:je(l),calls:l.calls.map($=>$.text),verified:l.verified}]:[]]),...n.map(yn).map(l=>({ts:l.ts,who:l.who||"claude",...l.log?{log:l.log}:{},...l.kind==="status"?{kind:"status",...l.planner?{planner:!0}:{}}:{},...Object.values(Ce).includes(l.kind)?{kind:l.kind,ask:l.ask}:{},text:l.text||"",html:l.html||null,link:l.link||null,via:l.via||null})),...We(f,i.map(l=>f.byKey.get(l))).map(l=>({ts:l.ts,who:"love",via:"site",kind:l.kind,text:wn(l,h),files:l.data.files||[],eventId:l.id,reply:l.reply,...Le(l,"grill")||Le(l,"prototype")?{request:!0}:{}})),...O.map(l=>{const $=[l.data.label||l.data.choice,l.data.note].filter(Boolean).join(" \xB7 ");return{ts:l.ts,who:"love",via:"questions",kind:l.kind,text:`${h.get(l.data.id).question}
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(_n,"todoOf");function Jn(e){const n=e.filter(o=>o.kind==="work"&&o.lane!=="done"&&o.questions?.open>0),s=n.map(o=>JSON.stringify([o.key,o.questions.open,o.questions.askedAt||[]])).sort().join(`
6
- `);return{waiting:n.map(o=>o.key),seen:`${s}
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
- ${_n(e)}`}}d(Jn,"triageOf");const Cn=100,Rn=["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"],Zn=d(e=>Object.fromEntries(Rn.filter(n=>n in e).map(n=>[n,e[n]])),"slimDone");function es(e,n=Cn){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(n).filter(r=>["done","merged","dropped"].includes(r.status)&&!s.has(r.key)&&!o.has(r.key)&&(!r.questions||r.questions.state==="resolved"))}d(es,"archivedOf");const Nn=d(e=>e.startsWith("ask:")?`ask-${e.slice(4)}`:`item-${e}`,"pushTag");function ts(e){const n=d(a=>e.owner.filter(p=>p.topic===a&&p.state==="open").map(({id:p,question:u})=>({id:p,question:u})),"open"),s=[I.ready,I.merge],o=e.items.filter(a=>a.action).map(a=>{const p=Ze.has(a.action.kind)?n(a.questions.topic):[];return{tag:Nn(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=n(a.topic);return{tag:`topic-${a.topic}`,kind:I.questions,title:a.text,body:p[0]?.question||a.title,url:a.url,now:!0,questions:p}});return[...o,...r,...bt(e.reviews)]}d(ts,"ownerActions");function ns(e,n){const s=e.owner.find(a=>a.id===n),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(ns,"startedByAnswer");function ss(e,{pulled:n=[]}={}){return!e.items.filter(r=>!pe(r)).some(r=>r.waitsOn==="checking"||r.grilling||r.prototyping||(r.agents||[]).some(a=>!a.done))&&!e.working&&!Dn(e.items,{pulled:n}).waiting.length}d(ss,"strictlyIdle");const Ln=15;function os(e,{idle:n,now:s}){const o=s.toISOString(),r=e.last||o;return n?e.since?e.pushed||s-Date.parse(e.since)<Ln*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(os,"idleStep");function rs(e,n){const s=d(c=>String(c.closedAt||"")>n,"after"),o=e.filter(c=>c.kind==="work"&&c.lane==="done"&&!Ne(c)&&s(c)),r=new Map;for(const c of e)c.lineup&&r.set(c.lineup.slug,[...r.get(c.lineup.slug)||[],c]);const a=[...r.values()].filter(c=>c.every(f=>f.lane==="done")&&c.some(s)).map(c=>c[0].lineup.title),p=o.slice(0,3).map(c=>c.ref).join(", ")+(o.length>3?` and ${o.length-3} more`:"");return[o.length&&`${o.length} ${o.length===1?"card":"cards"} landed: ${p}`,...a.map(c=>`${c} finished`)].filter(Boolean).join(" \xB7 ")||"Nothing closed since the last idle notification."}d(rs,"idleSummary");export{I as ACTION_KIND,Ce as BUILDER_ASK,$e as BUILDER_KIND,qt as BUILDER_STALLED_AFTER,Et as BUILD_REFUSED,F as BUTTON,Rn as DONE_FIELDS,Cn as DONE_KEPT,Ln as IDLE_MINUTES,At as ITEM_EVENTS,Vn as ITEM_STATUSES,Mn as MAX_IMPLEMENTERS,en as NO_NEXT,on as PLANNER_WORD,zt as PROTOTYPE_AGAIN,Z as PR_STATE,es as archivedOf,Hn as buildBoard,Y as builds,os as idleStep,rs as idleSummary,Yn as lineupMoved,vn as lineupPhases,Qt as mergeBlockers,un as modelName,zn as needsAnswer,Ht as noTakePicked,Xn as owesNext,ts as ownerActions,cn as ownerQuestion,Yt as picksNoTake,re as plans,st as providerOf,Dn as pullOf,Nn as pushTag,Re as refOf,ae as roundOf,pn as roundSettledBy,Zn as slimDone,ns as startedByAnswer,ss as strictlyIdle,rt as threadIndex,at as threadOf,_n as todoOf,Jn as triageOf,Mt as waveFold,On as withHub};
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};