@lovek4d/kickflip 0.7.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/CONTEXT.md +43 -43
  2. package/SKILL.md +12 -10
  3. package/bin/event-lines.mjs +1 -1
  4. package/bin/kickflip.mjs +2 -2
  5. package/board/agents.mjs +2 -2
  6. package/board/arch-review.mjs +4 -4
  7. package/board/bin/board.mjs +87 -87
  8. package/board/domain.mjs +7 -7
  9. package/board/finish.mjs +4 -4
  10. package/board/headless.mjs +8 -8
  11. package/board/hub-lines.mjs +1 -0
  12. package/board/onboarding.mjs +1 -1
  13. package/board/public/app-arch.js +1 -1
  14. package/board/public/app-cards.js +5 -6
  15. package/board/public/app-header.js +1 -1
  16. package/board/public/app-lanes.js +37 -59
  17. package/board/public/app-needs.js +16 -11
  18. package/board/public/app-reader.js +2 -2
  19. package/board/public/app.js +2 -2
  20. package/board/public/dialogs.js +1 -1
  21. package/board/public/hub-kit.js +1 -1
  22. package/board/public/hub.css +2 -0
  23. package/board/public/linear-key.js +1 -1
  24. package/board/public/new-task.js +1 -1
  25. package/board/public/prototype-kit.js +2 -5
  26. package/board/public/router.js +1 -1
  27. package/board/public/sidebar.css +2 -2
  28. package/board/public/sidebar.js +2 -2
  29. package/board/public/style.css +20 -29
  30. package/board/public/ui-cards.js +19 -17
  31. package/board/public/ui-hub.js +1 -5
  32. package/board/public/ui-links.js +5 -5
  33. package/board/public/ui-order.js +5 -6
  34. package/board/public/ui-questions.js +12 -10
  35. package/board/public/ui-threads.js +10 -7
  36. package/board/public/ui.js +11 -2
  37. package/board/public/view-hub-settings.js +28 -31
  38. package/board/public/view-hub.js +7 -9
  39. package/board/public/view-notes.js +1 -1
  40. package/board/public/view-questions.js +18 -5
  41. package/board/public/view-settings.js +3 -3
  42. package/board/render-check.mjs +10 -10
  43. package/board/review.mjs +1 -1
  44. package/board/server.mjs +18 -19
  45. package/board/stop.mjs +1 -1
  46. package/board/store.mjs +26 -25
  47. package/hooks-plugin/hooks/board-data-gate.mjs +6 -6
  48. package/hub/awake.mjs +1 -1
  49. package/hub/boards.mjs +4 -4
  50. package/hub/github.mjs +3 -3
  51. package/hub/hub.mjs +11 -11
  52. package/hub/incoming.mjs +2 -2
  53. package/hub/locks.mjs +1 -1
  54. package/hub/new-board.mjs +2 -2
  55. package/hub/owner-checks.mjs +1 -1
  56. package/hub/try.mjs +6 -7
  57. package/hub/views.mjs +1 -1
  58. package/package.json +1 -1
  59. package/shared/usage-gate.mjs +1 -1
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
- var L=Object.defineProperty;var r=(e,a)=>L(e,"name",{value:a,configurable:!0});import{readiness as D}from"../../hub/readiness.mjs";import{readSlack as H,setSlack as M}from"../../hub/slack.mjs";import{routeRecord as y}from"../../hub/routes.mjs";import f from"node:fs";import x from"node:path";import{pathToFileURL as W}from"node:url";import*as s from"../store.mjs";import{boardWorktree as z,builderRepo as U,joiningLand as F,landInTurn as J,mergeHelper as G,worktreesDir as K}from"../worktree.mjs";import{pidAlive as Q}from"../headless.mjs";import{fileFindings as V}from"../arch-review.mjs";import{finishCard as $,landHeld as X}from"../finish.mjs";import{bump as S,queueLines as Y,stallCard as Z,stopBuilder as ee,stopForBlock as te,stopLand as ae}from"../stop.mjs";import{boardAt as se}from"../../hub/boards.mjs";import"../../hub/board-repo.mjs";import{checkTakesRender as ne}from"../render-check.mjs";import{prototypeBase as re}from"../prototype-base.mjs";const ie=`usage: node board.mjs <command> [--site <route>] ...
2
+ var W=Object.defineProperty;var r=(e,a)=>W(e,"name",{value:a,configurable:!0});import{readiness as D}from"../../hub/readiness.mjs";import{readSlack as H,setSlack as L}from"../../hub/slack.mjs";import{routeRecord as y}from"../../hub/routes.mjs";import g from"node:fs";import x from"node:path";import{pathToFileURL as M}from"node:url";import*as n from"../store.mjs";import{boardWorktree as z,builderRepo as U,joiningLand as J,landInTurn as F,mergeHelper as G,worktreesDir as K}from"../worktree.mjs";import{pidAlive as Q}from"../headless.mjs";import{fileFindings as V}from"../arch-review.mjs";import{finishCard as $,landHeld as X}from"../finish.mjs";import{bump as S,queueLines as Z,stallCard as Y,stopBuilder as ee,stopLand as te}from"../stop.mjs";import{boardAt as ae}from"../../hub/boards.mjs";import"../../hub/board-repo.mjs";import{checkTakesRender as se}from"../render-check.mjs";import{prototypeBase as ne}from"../prototype-base.mjs";const re=`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
- board.json says where the board's waves live. Without --site, waves come from ~/.claude/waves.
5
+ board.json says where the board's item files live. Without --site, they come from ~/.claude/waves.
6
6
  Paths resolve under $HOME, so HOME=<dir> points every command at a copy.
7
7
  A --file argument of - reads stdin.
8
8
  item add creates a ticket in the board\u2019s source unless --ticket <id> links one.
@@ -23,27 +23,28 @@ command fails and changes nothing.
23
23
  [--title <t>] retitles the ask it answers, or whose thread the message
24
24
  is in, which then wears an Ask badge in place of its first
25
25
  line as title (data/ask-titles.json)
26
- thread <wave>/<id> --text <t> [--link <url>] one line in the item's thread; ask:<eventId> in place
27
- [--who claude|love|<name>] [--via github|slack] of <wave>/<id> writes in that ask's thread
26
+ thread <file>/<id> --text <t> [--link <url>] one line in the item's thread; ask:<eventId> in place
27
+ [--who claude|love|<name>] [--via github|slack] of <file>/<id> writes in that ask's thread
28
28
  A Claude thread line, or a reply to an ask or item-message, rewrites the body of the thread's
29
- queued push (tag ask-<eventId> or item-<wave>/<id>) to the message's first line, keeping
29
+ queued push (tag ask-<eventId> or item-<file>/<id>) to the message's first line, keeping
30
30
  "\xB7 and N replies", and prints the new body. It never pushes: the thread's unread mark carries it
31
- visual add <wave>/<id> --file <page.html> --title <t> a visual agent's page: copied to public/visuals/<id>/<slug>.html
31
+ visual add <file>/<id> --file <page.html> --title <t> a visual agent's page: copied to public/visuals/<id>/<slug>.html
32
32
  (a new name each time), linked by a visual line in the
33
33
  item's thread; prints the page's link
34
34
  topic add <id> --title <t> [--summary <s>] a question topic in data/owner.json
35
35
  question add --file <json> one question or an array of them:
36
36
  {id, topic, question, options: [{id, label, recommended?, detail?, diagram?, practice?}],
37
- source?, context?, url?, urgent?, round_of?, reask_of?, ask?, suggested?, prototype_gate?}; topic is declared, item:<wave>/<id>, item:ask:<eventId>
38
- for a New card not sorted yet, lineup:<slug> for a wave's triage, or wave:<slug> for a wave file's triage on
37
+ source?, context?, url?, urgent?, round_of?, reask_of?, ask?, suggested?, prototype_gate?}; topic is declared, item:<file>/<id>, item:ask:<eventId>
38
+ for a New card not sorted yet, wave:<slug> for a wave's triage, or file:<slug> for an item file's triage on
39
39
  a board over ~/.claude/waves. A question on a done card reopens it.
40
40
  A question on an item: topic whose card has a grill or prototype planner running is staged, not published,
41
41
  whoever adds it: it shows nowhere until the planner finishes, fails or is stopped, when every staged question
42
42
  on the card publishes in the order added, with one owner push. The command says "staged" and why.
43
43
  url is http(s), or a site page such as prototype-<topic>.html?q=<id>, stored as given.
44
44
  Prototype options keep take order, ids matching take keys; recommended: true tags the recommendation.
45
- suggested: {option, card, answer} pre-checks the option the owner already chose on an earlier card, with a
46
- "suggested" chip and the line You answered this on <card>: "<answer>"; card is that card's ref, answer their words.
45
+ suggested: {option, card, answer, reason?} pre-checks the option the owner already chose on an earlier card, with a blue
46
+ line above its description, Orchestrator choice: <reason>; give a reason, your own words for why. Without one the line
47
+ reads Orchestrator choice: you answered this on <card>: "<answer>"; card is that card's ref, answer their words.
47
48
  Waiting on you shows fitting takes in a grid, or a chooser above one full-width take for a single take or if any
48
49
  scrolls or fills the screen.
49
50
  diagram, with practice: rows of 1-4 boxes [[{text, tone?, to?}]], tone quiet|info|notice|
@@ -65,7 +66,7 @@ command fails and changes nothing.
65
66
  (reask-request): it resolves that one again, noting "Asked again as <new id>", printed as "resolved: <old id>".
66
67
  ask names a builder ask the session deferred on the item (builder defer): the question puts it to the
67
68
  owner, and while a card's only open questions are such, it waits on them with no Action needed.
68
- The board server pushes the new open questions itself, under the card's tag (item-<wave>/<id>, or
69
+ The board server pushes the new open questions itself, under the card's tag (item-<file>/<id>, or
69
70
  ask-<eventId>) or topic-<topic> off the cards
70
71
  question resolve <id> --note <text> sets resolved and resolved_at; refuses a question on a
71
72
  prototype page until an answer settles its round: a pick, with a note or none, or an answer to
@@ -75,14 +76,14 @@ command fails and changes nothing.
75
76
  (a screen wait), which the board already answers: it is published resolved "Settled by the
76
77
  orchestrator: <text>", and the answer goes into the card's notes. Refuses the prototype question,
77
78
  which is the owner's
78
- question publish <wave>/<id> passes the rest of the card's screened questions on to the owner.
79
+ question publish <file>/<id> passes the rest of the card's screened questions on to the owner.
79
80
  The board server publishes any left unscreened 15 minutes on, with a thread line
80
- question move <id>,<id>,\u2026 --to <wave>/<id> [--block <wave>/<id>,\u2026] merges open questions that ask one thing onto
81
+ question move <id>,<id>,\u2026 --to <file>/<id> [--block <file>/<id>,\u2026] merges open questions that ask one thing onto
81
82
  one card: the first id moves to that card (topic item:<card>), the others resolve "Duplicate of
82
83
  <card>'s question <first id>". Each --block card, in order, is blocked by the one before it (the
83
84
  first by --to) and related to --to
84
85
  prototype add <topic> --title <t> --take <K>:<label> records a prototype in data/prototypes.json: the board
85
- [--take <K>:<label> \u2026] [--card <wave>/<id>] serves prototype-<topic>.html from its one template, with
86
+ [--take <K>:<label> \u2026] [--card <file>/<id>] serves prototype-<topic>.html from its one template, with
86
87
  the title, a back arrow to --card (else the board), the
87
88
  take pill and a stage. topic: a-z, 0-9 and "-"; K: one
88
89
  capital letter, in take order; a label never starts with
@@ -105,7 +106,7 @@ command fails and changes nothing.
105
106
  prototype list one line per recorded prototype
106
107
  prototype rm <topic> drops the record, keeping its take files; refuses while an
107
108
  open question links it
108
- pr <wave>/<id> one JSON line per PR on the card, in its order: the PR's
109
+ pr <file>/<id> one JSON line per PR on the card, in its order: the PR's
109
110
  repo, state, checks and new events, using the board's
110
111
  github-login and the card's gh_since
111
112
  pr comment <pr> --body <text> posts Claude's words on PR <pr> through gh, a number in the
@@ -115,7 +116,7 @@ command fails and changes nothing.
115
116
  blank line and <!-- claude-bridge --> after, the marker pr
116
117
  reads as Claude's own. Refuses words that already carry
117
118
  either. Prints the post's url
118
- item set <wave>/<id> [--done --text <t> [--force]] edits the item in its wave file; --done closes the card
119
+ item set <file>/<id> [--done --text <t> [--force]] edits the item in its item file; --done closes the card
119
120
  (below). A card has no status: it stores its lane, which
120
121
  a build's start, a handover, a block and a close move,
121
122
  and the board server moves the rest, each with a thread
@@ -169,7 +170,7 @@ command fails and changes nothing.
169
170
  [--size small|large] [--model opus|sonnet] what builder pick reads while the hub's Claude model is
170
171
  Auto: a small card gets Claude Sonnet, any other Opus, and
171
172
  --model wins over --size; "" clears either
172
- [--deps <id>,<wave>/<id>] replaces its blocked-by list; --deps "" clears it
173
+ [--deps <id>,<file>/<id>] replaces its blocked-by list; --deps "" clears it
173
174
  [--blocked-until "<condition>"] blocks it until a condition an agent can check, which
174
175
  the orchestrator checks on its passes over the cards;
175
176
  --blocked-until "" clears it, which threads and emits
@@ -179,12 +180,12 @@ command fails and changes nothing.
179
180
  A block on a card In Progress sends it back to Todo,
180
181
  stopping its board-launched builder (refused for a
181
182
  session's own builder); one In Review stays, unlanded
182
- [--parent <wave>/<id>] sets its Parent, refusing itself or a card below it;
183
+ [--parent <file>/<id>] sets its Parent, refusing itself or a card below it;
183
184
  --parent "" clears it
184
- [--related <id>,<wave>/<id>] replaces the cards it is Related to, which both cards
185
+ [--related <id>,<file>/<id>] replaces the cards it is Related to, which both cards
185
186
  show; --related "" clears it, on both
186
- [--relate <id>,<wave>/<id>] adds cards to its Related to, keeping the rest
187
- [--unrelate <id>,<wave>/<id>] takes cards off its Related to, on both cards; neither
187
+ [--relate <id>,<file>/<id>] adds cards to its Related to, keeping the rest
188
+ [--unrelate <id>,<file>/<id>] takes cards off its Related to, on both cards; neither
188
189
  goes with --related
189
190
  [--install <url> [--install-sha <sha>] a device build the owner installs from the card's Install
190
191
  [--install-label <text>] | --clear-install] button, stamped built_at now: itms-services://?action=
@@ -203,27 +204,27 @@ command fails and changes nothing.
203
204
  --pr closes ordinary builders and moves the card to In Review; install builders keep running.
204
205
  A builder's hand-back of work it couldn't land (builder done with judgment calls) keeps the card
205
206
  In Progress, reading "Orchestrator review", until the owning session lands it; it unblocks nothing.
206
- The board server records the owner's card-done in the wave file itself.
207
+ The board server records the owner's card-done in the item file itself.
207
208
  --done moves the card to Done and its ticket to Done: it resolves each of its questions (topic
208
- item:<wave>/<id>) that has an answer, noting "Closed with the card", and prints "resolved: <id>"
209
+ item:<file>/<id>) that has an answer, noting "Closed with the card", and prints "resolved: <id>"
209
210
  for each. It refuses while one has no answer yet; [--force] closes it and resolves that one too.
210
- It prints "unblocked: <wave>/<id>" and appends card-unblocked {key} once per unblocking,
211
+ It prints "unblocked: <file>/<id>" and appends card-unblocked {key} once per unblocking,
211
212
  including held cards: start each unheld card next. It refuses a card the owner reopened: it
212
213
  closes again only on the owner's Done
213
- item add <wave>[/<id>] --title <t> a new card, unstarted; never
214
- [--ticket <id>] [--deps <id>,<wave>/<id>] hand-edit wave.json to add one. Refuses an id the wave has.
214
+ item add <file>[/<id>] --title <t> a new card, unstarted; never
215
+ [--ticket <id>] [--deps <id>,<file>/<id>] hand-edit the item file to add one. Refuses an id the file has.
215
216
  [--notes <t> | --notes-from <draft.md>] the board makes the card's own notes, <id>-notes.md beside
216
- its wave file, read-only: the draft verbatim, or "# <title>"
217
+ its item file, read-only: the draft verbatim, or "# <title>"
217
218
  then the --notes text. Refuses a draft that is any card's
218
219
  notes file, live, done or archived, naming that card
219
220
  [--pick-up-at <time> | --held] holds it until then, or until resumed, as item set's
220
221
  [--size small|large] [--model opus|sonnet] as item set's
221
- [--parent <wave>/<id>] the card it was spun out of, such as one filed from
222
+ [--parent <file>/<id>] the card it was spun out of, such as one filed from
222
223
  another card's thread, grill or answer: it reads as a
223
224
  follow-up, and its Parent lists it in Children. A card
224
225
  from the + or New task has none
225
226
  [--phase grilling|prototyping|ready] files it at once, as item set's --phase; the session's
226
- [--relate <id>,<wave>/<id>] own form, linking it with --relate in the same call.
227
+ [--relate <id>,<file>/<id>] own form, linking it with --relate in the same call.
227
228
  ready is for a Quick build ask (--from-ask), with --size
228
229
  and --notes or --notes-from, since a builder starts from it
229
230
  [--from-ask <eventId> --text <t>] files the card from that ask in this one command, as
@@ -233,8 +234,8 @@ command fails and changes nothing.
233
234
  adds reads New
234
235
  Without /<id>, the card takes its ticket's id (GH-<n> for issue #n); pass an id only to import a
235
236
  card. Prints "<label> added as <key>": use the key in later commands
236
- item notes <wave>/<id> --heading <h> appends "## <h>" and the draft to the card's notes, making
237
- (--file <draft.md> | --from <wave>/<id>) its <id>-notes.md when it has none; the only way to write
237
+ item notes <file>/<id> --heading <h> appends "## <h>" and the draft to the card's notes, making
238
+ (--file <draft.md> | --from <file>/<id>) its <id>-notes.md when it has none; the only way to write
238
239
  notes (--notes-file and --plan are gone). Write the draft
239
240
  outside the board's data; one that is any card's notes
240
241
  file, live, done or archived, is refused, naming that
@@ -243,7 +244,7 @@ command fails and changes nothing.
243
244
  Resolves answered questions still waiting on Claude,
244
245
  noting "Recorded in the card's notes", and prints
245
246
  "resolved: <id>" for each
246
- item relate-ask <eventId> <wave>/<id> --summary <t> folds an ask about an existing item into it: the ask's
247
+ item relate-ask <eventId> <file>/<id> --summary <t> folds an ask about an existing item into it: the ask's
247
248
  card goes, its message and thread show in the item's
248
249
  thread marked Moved, then the summary line. Again with
249
250
  another item moves it there
@@ -263,47 +264,46 @@ command fails and changes nothing.
263
264
  that isn't New
264
265
  Then sends the queued pushes, each on its own, unless
265
266
  pushes are held (overnight, snoozed, the weekend or off)
266
- lineup create <slug> --title <t> --goal <t> a wave, in the owner's words: a named set of cards in
267
- --step <wave>/<id>,\u2026 [--step \u2026] steps, all from one wave file, one --step per step
268
- (--note <t> | --file <f>) in build order. Each card is then blocked by every card
269
- of the step before (its other deps stay), so one
270
- step's cards build side by side, and the note goes to
271
- lineup-<slug>.md beside the wave file.
272
- Todo shows it as a band of its cards in order, numbered
273
- by step, its goal on its name, and Notes opens the
274
- goal, the order and the note. Refuses a card already in
275
- another open wave. Prints the order by card label, one
276
- step's cards by commas and the steps by arrows
277
- lineup add <slug> <wave>/<id> [--after <wave>/<id>] puts a card in the wave as a step of its own, last
278
- unless --after's step, and links the steps again
279
- lineup remove <slug> <wave>/<id> takes a card out: it loses its links to the wave's cards,
280
- its step goes if it held only that card, and the steps
281
- are linked again
282
- lineup set <slug> [--title <t>] [--goal <t>] renames the wave, sets its goal, rewrites its note or
283
- [--note <t> | --file <f>] puts its cards in new steps (--step per step, every
284
- [--step <wave>/<id>,\u2026 \u2026] card once) and links them again. A card's \u22EF in its
285
- band swaps it with the Todo card before or after it,
286
- each taking the other's step
287
- lineup finish <slug> the wave's band leaves Todo; its cards keep their
267
+ wave create <slug> --title <t> --goal <t> a wave, in the owner's words: a named set of cards from
268
+ --card <file>/<id>[:<file>/<id>,\u2026] [--card \u2026] one item file, one --card per card in build order, each
269
+ (--note <t> | --file <f>) naming after a colon the earlier cards it would conflict
270
+ with (as item set --deps takes them). Each card is then
271
+ blocked by those cards alone (its links outside the wave
272
+ stay), so cards that don't conflict build side by side,
273
+ and the note goes to wave-<slug>.md beside the item file.
274
+ Todo shows it as a band of its cards in order, each with
275
+ the board's blocked-by line, its goal on its name, and
276
+ Notes opens the goal, the order and the note. Refuses a
277
+ card already in another open wave, and a conflict that
278
+ isn't an earlier card of the wave. Prints the order by
279
+ card label, each card with "(after \u2026)" its conflicts
280
+ wave add <slug> <file>/<id> [--after <file>/<id>,\u2026] puts a card in the wave, last, blocked by the earlier
281
+ cards --after names
282
+ wave remove <slug> <file>/<id> takes a card out: it and the wave's cards waiting on it
283
+ lose their links to each other
284
+ wave set <slug> [<file>/<id> --after <file>/<id>,\u2026] rewrites only that card's links to the wave's cards to
285
+ [--title <t>] [--goal <t>] --after's earlier cards ("" clears them), renames the
286
+ [--note <t> | --file <f>] wave, sets its goal or rewrites its note
287
+ wave finish <slug> the wave's band leaves Todo; its cards keep their
288
288
  links. A wave whose cards are all Done leaves it too
289
- A band's Triage sends wave-triage {lineup}, or {wave} for a wave file's band; it reads
289
+ A band's Triage sends wave-triage {wave}, or {file} for an item file's band; it reads
290
290
  Triaging\u2026 until you reply to that event, then "Triage: N open questions" while questions on topic
291
- lineup:<slug> or wave:<slug> are open or answered
291
+ wave:<slug> or file:<slug> are open or answered
292
292
  tickets backfill [--dry-run] gives every open card without a ticket one: the first
293
293
  id in its title (or legacy linear list) from the board's
294
294
  source is linked, else a new ticket is made. Done cards are
295
295
  left alone. Prints each card's result; --dry-run changes and
296
296
  calls nothing
297
297
  reviewers set --file <json> the board's reviewers in board.json, replacing them:
298
- [{name, slack_user, dm, github}], as in wave.json.
299
- The settings page lists them; new waves copy them
298
+ [{name, slack_user, dm, github}], as in an item file.
299
+ The settings page lists them; new item files copy them
300
300
  reviewers add --file <json> one reviewer or an array; replaces the same name
301
301
  reviewers remove <name>... refuses a name the board doesn't have
302
302
  slack Slack identity as JSON; imports slack-bridge config once
303
303
  slack set [--self-dm D..] [--user U..] sets hub Slack identity in ~/.claude/kickflip/slack.json
304
304
  [--domain x] [--timezone Area/City]
305
305
  integrations readiness rows as one JSON line
306
- review updated <wave>/<id> --text "<what changed>" queues \u201CUpdated: <what changed>\u201D in the thread of every
306
+ review updated <file>/<id> --text "<what changed>" queues \u201CUpdated: <what changed>\u201D in the thread of every
307
307
  posted link on the card's PRs; the hub's next poll sends it
308
308
  review due one JSON line of the Slack sends the hub owes, from local state
309
309
  and data/pr-slack.json: {links: [{pr, key, reviewer,
@@ -358,7 +358,7 @@ command fails and changes nothing.
358
358
  pushed, which needs Push main after a land granted.
359
359
  A local board never opens PRs. Without auto-merge, a
360
360
  card with no PR waits in In Review for the owner's Merge
361
- build <wave>/<id> --site <route> asks the hub to run this card's build script; prints
361
+ build <file>/<id> --site <route> asks the hub to run this card's build script; prints
362
362
  --asked "<owner's words>" started and exits. install is an alias. Only the owner's
363
363
  Serve / Build and Auto-build start a card run, so --asked
364
364
  must quote the owner's ask, card message or answer note on
@@ -413,7 +413,7 @@ command fails and changes nothing.
413
413
  {"source":"linear","workspace","team","project"}. A
414
414
  GitHub account runs the board's issue calls as that gh
415
415
  login; without one they run as gh's active account
416
- builder start <wave>/<id> [--agent <name>] [--kind <claude|codex|prototype|grill|install|\u2026>] [--label <who>]
416
+ builder start <file>/<id> [--agent <name>] [--kind <claude|codex|prototype|grill|install|\u2026>] [--label <who>]
417
417
  [--model <opus|sonnet>] the Claude model running the builder, recorded and printed as
418
418
  "model: Claude Sonnet" (kind claude takes builder pick's model;
419
419
  codex prints "model: Codex"). The thread's agent line reads it: "Claude
@@ -453,7 +453,7 @@ command fails and changes nothing.
453
453
  on a board makes it without --worktree when it keeps none
454
454
  (a taken-up or resumed record's, or the worktree the
455
455
  card's last record left while it is still there)
456
- builder progress <wave>/<id> --phase <phase> one status line: a phase, reading, building, testing,
456
+ builder progress <file>/<id> --phase <phase> one status line: a phase, reading, building, testing,
457
457
  (<detail> | --text <d>) [--agent <name>] checking or landing, and a present-tense detail of at most
458
458
  80 characters in plain words, which the owner reads at a
459
459
  glance; a longer detail is refused with its length. Hashes,
@@ -464,7 +464,7 @@ command fails and changes nothing.
464
464
  A progress line, --progress included, needs --site: it is a status line in the item's thread, and
465
465
  the card's status shows the newest one from an agent still working. builder progress refuses
466
466
  without an open record for that agent, as builder call does
467
- builder done <wave>/<id> [--agent <name>] marks that agent's record done; the item's thread
467
+ builder done <file>/<id> [--agent <name>] marks that agent's record done; the item's thread
468
468
  [--next <t>] [--text <what you checked>] shows when it started and finished. On a
469
469
  record with unverified judgment calls it hands the card
470
470
  back: its records close marked handed back, with Next "Answer the builder's
@@ -505,8 +505,8 @@ command fails and changes nothing.
505
505
  changed>\u201D in each link's thread, as review updated does;
506
506
  none sends nothing, for a change only to comments, names,
507
507
  docs or tests
508
- builder takes --dir <dir> for a wave file in <dir>; its records go in <dir>/agents
509
- builder stop <wave>/<id> the orchestrator's stop of the card's run the board launched,
508
+ builder takes --dir <dir> for an item file in <dir>; its records go in <dir>/agents
509
+ builder stop <file>/<id> the orchestrator's stop of the card's run the board launched,
510
510
  its headless builder, lander or rebase run, with every process
511
511
  under it: the record stays open, marked stopped, so the server
512
512
  relaunches nothing and the card stays In Progress reading
@@ -514,7 +514,7 @@ command fails and changes nothing.
514
514
  waiting on the orchestrator; the same words go in its thread.
515
515
  Prints what it stopped, one process a line. Refuses a planner,
516
516
  a builder a session started itself, or none running
517
- item stall <wave>/<id> --reason "<why>" the orchestrator's flag on a stale card it can't restart: the
517
+ item stall <file>/<id> --reason "<why>" the orchestrator's flag on a stale card it can't restart: the
518
518
  card stays in its lane reading red "stalled", an Action needed
519
519
  "stalled: <why>" with one push, waiting on nobody else; its
520
520
  run stops as builder stop stops it (nothing to stop when it
@@ -522,7 +522,7 @@ command fails and changes nothing.
522
522
  stays, and it frees its implementer slot. Its thread reads
523
523
  "stalled by orchestrator: <why>". A message on the card
524
524
  clears it
525
- land stop <wave>/<id> the orchestrator's stop of the card's land and its suite: one a
525
+ land stop <file>/<id> the orchestrator's stop of the card's land and its suite: one a
526
526
  headless builder or lander runs stops that agent as builder
527
527
  stop does; the land a held card's Merge tap started stops
528
528
  alone, leaving the land queue, and the card's Merge shows
@@ -531,28 +531,28 @@ command fails and changes nothing.
531
531
  didn't launch
532
532
  land queue the host's land queue in order: the land running, then each
533
533
  waiting, "(bumped)" on one a bump moved
534
- item bump <wave>/<id> the card's bump, as the owner's bump icon sends it (card-bump;
535
- item unbump <wave>/<id> card-unbump). A bumped card's land goes ahead in the land queue
534
+ item bump <file>/<id> the card's bump, as the owner's bump icon sends it (card-bump;
535
+ item unbump <file>/<id> card-unbump). A bumped card's land goes ahead in the land queue
536
536
  of every land not bumped since, the latest bump first, without
537
537
  stopping the one running; a bump of a queued land says so in
538
538
  its thread ("land moved first by orchestrator")
539
- lander <wave>/<id> relaunch the headless lander of a card it stopped on (a usage
539
+ lander <file>/<id> relaunch the headless lander of a card it stopped on (a usage
540
540
  pause ends one): its open record closes as handed back and
541
541
  the server launches a fresh lander; refused unless a
542
542
  builder record on the card is open
543
- builder pick <wave>/<id> who builds the card, from the hub's Codex share and the pause
543
+ builder pick <file>/<id> who builds the card, from the hub's Codex share and the pause
544
544
  line, and for claude which model, from the hub's Claude model
545
545
  and the card's --size and --model: "codex: <reason>" or
546
546
  "claude sonnet: <reason>". Pass that kind to builder start
547
547
  --kind, and launch that model
548
- audit [<wave>/<id>] the board's audit log of the last 30 days, data/audit.jsonl:
548
+ audit [<file>/<id>] the board's audit log of the last 30 days, data/audit.jsonl:
549
549
  each call the code made on its own (builder-pick, pull,
550
550
  round-planner, launch-held, arrival-planner, relaunch,
551
551
  lander, triage, pr-review, push, pick-up, auto-merge,
552
552
  ready-draft, session-resume, compact) as "<at> <call>
553
553
  [<key>]: <result>. <reason>" and the inputs it read; with
554
554
  a card, only that card's. The hub's own: kickflip audit
555
- agent launch <wave>/<id> --name <n> --task <t> any session's helper on an open card, an agent the board server
555
+ agent launch <file>/<id> --name <n> --task <t> any session's helper on an open card, an agent the board server
556
556
  [--writes] [--model <opus|sonnet>] launches headless with the task as its brief, under its own
557
557
  name (lowercase, "-", not main or visual-\u2026), its record
558
558
  <id>@<name> requested until then. A read-only helper launches
@@ -562,38 +562,38 @@ command fails and changes nothing.
562
562
  and works in its own worktree, branch <id>-<name> from the
563
563
  card's, so it needs the card's worktree. --model else
564
564
  builder pick's Claude model. Its lines carry its name
565
- agent wait <wave>/<id> --name <n> [--agent <me>] blocks until the helper's builder done, then prints its
565
+ agent wait <file>/<id> --name <n> [--agent <me>] blocks until the helper's builder done, then prints its
566
566
  [--timeout <minutes>] --text and, for one that writes, its branch. Meanwhile
567
567
  --agent's record (main by default) takes no slot. Refuses
568
568
  after --timeout (9) minutes, saying whether it runs or waits
569
569
  for a slot, and once its process is gone without done
570
- agent merge <wave>/<id> --name <n> merges a done helper's branch into the card's worktree and
570
+ agent merge <file>/<id> --name <n> merges a done helper's branch into the card's worktree and
571
571
  removes the helper's worktree and branch; on a conflict it
572
572
  names the files: resolve, commit and run it again
573
- builder call <wave>/<id> <judgment call> appends one line to the agent's open builder record: a
573
+ builder call <file>/<id> <judgment call> appends one line to the agent's open builder record: a
574
574
  [--agent <name>] judgment call as the builder makes it, or a "Question: \u2026"
575
575
  for the session; refused with no open record. A builder start
576
576
  on a record with unverified calls resumes that build, keeping
577
577
  its calls and worktree. The thread shows them, collapsed
578
- builder calls <wave>/<id> [--agent <name>] prints the record's calls, numbered, then "verified <time>"
578
+ builder calls <file>/<id> [--agent <name>] prints the record's calls, numbered, then "verified <time>"
579
579
  or "not verified yet"
580
- builder ask <wave>/<id> <question> [--agent <name>] a running builder's one-line question for the board's session,
580
+ builder ask <file>/<id> <question> [--agent <name>] a running builder's one-line question for the board's session,
581
581
  which it asks only when blocked (every way forward would likely
582
582
  be wasted work), then waits with builder asks --wait: a thread
583
583
  line named by its model ("Claude Opus builder"), never unread,
584
584
  and a builder-ask {key, ask, agent, text} event. Prints its
585
585
  id, ask<n>. Needs --site and an open builder record
586
- builder answer <wave>/<id> <ask> --text <t> the session's answer to that ask, a thread line named
586
+ builder answer <file>/<id> <ask> --text <t> the session's answer to that ask, a thread line named
587
587
  Orchestrator that leaves the card quiet
588
- builder defer <wave>/<id> <ask> --text <t> the session's deferral of an ask it can't answer confidently:
588
+ builder defer <file>/<id> <ask> --text <t> the session's deferral of an ask it can't answer confidently:
589
589
  the same thread line, and a "Question: <ask> (deferred to the
590
590
  owner)" call on the asking agent's record. After its review the
591
591
  session puts it to the owner as a card question with
592
592
  "ask": "<ask>"; builder verified refuses until then
593
- builder asks <wave>/<id> [--wait] [--timeout <min>] prints each ask, then its answer, its deferral or "waiting on
593
+ builder asks <file>/<id> [--wait] [--timeout <min>] prints each ask, then its answer, its deferral or "waiting on
594
594
  the orchestrator"; --wait blocks until none is waiting, and
595
595
  after --timeout (9) minutes says so and fails: run it again
596
- builder verified <wave>/<id> [--agent <name>] the session's mark that it checked the record's calls; land
596
+ builder verified <file>/<id> [--agent <name>] the session's mark that it checked the record's calls; land
597
597
  [--note <t>] refuses while any record on the card has a call after it.
598
598
  On a handed-back build (not done, no builder open, every
599
599
  call verified) it finishes the card in the same step, as
@@ -601,7 +601,7 @@ command fails and changes nothing.
601
601
  calls verified."). While a question on the card is open it
602
602
  says so and the card waits; a finish that fails keeps the
603
603
  mark, the card handed back, and says why
604
- land <wave>/<id> --text <t> [--repo <dir>] brings the worktree its builder record keeps (else
604
+ land <file>/<id> --text <t> [--repo <dir>] brings the worktree its builder record keeps (else
605
605
  [--dir <dir>]
606
606
  ~/.claude/kickflip/worktrees/<id> of --repo) into the board's
607
607
  branch (the one its builders branch from, else main), one
@@ -626,7 +626,7 @@ command fails and changes nothing.
626
626
  landed card in In Review on that step until the owner's
627
627
  Done, or until its --check passes. KICKFLIP_RESTART_CMD='["<cmd>"]' replaces
628
628
  the restarts, called with hub or board <route>
629
- merge <wave>/<id> [--after-approval "<why trivial>"] merges the card's PRs (gh pr merge --squash) in its order and
629
+ merge <file>/<id> [--after-approval "<why trivial>"] merges the card's PRs (gh pr merge --squash) in its order and
630
630
  [--confirmed "<owner's words>"] records it merged, once GitHub reads every open one approved,
631
631
  its checks green with none pending and its merge state CLEAN,
632
632
  the test the Merge button uses; a merged or closed PR is left
@@ -640,10 +640,10 @@ command fails and changes nothing.
640
640
  it is now, reruns the tests, fast-forwards, closes the card
641
641
  and pushes when Push main is on; a conflict or failing
642
642
  tests launch a headless lander that lands it
643
- arch file <wave>/<id> turns the architecture review audit card's answered findings
643
+ arch file <file>/<id> turns the architecture review audit card's answered findings
644
644
  into cards in the review's wave: titled with the picked
645
645
  option's label, its detail, practice and the owner's note as
646
- notes, each after the wave's last card, its question resolved
646
+ notes, each after the audit card, its question resolved
647
647
  "Filed as <ref>."; a Skip pick resolves its question alone.
648
648
  Prints "filed: <ref> from <id>", "skipped: <id>", and
649
649
  "waiting: <ids>" while any are unanswered; once none is, it
@@ -663,4 +663,4 @@ command fails and changes nothing.
663
663
  (overnight, snoozed or the weekend). --urgent: a
664
664
  production fault only; it beats those holds. While
665
665
  notifications are off every push is dropped, urgent too
666
- KICKFLIP_PUSH_CMD='["<cmd>","<arg>"]' replaces \`node ../../bin/kickflip.mjs push\`; the flags follow it`;class d extends Error{static{r(this,"UsageError")}}const oe=new Set(["done","clear-status","clear-next","urgent","now","clear-eta","held","unheld","idle","wait","hub","clear-install","force","worktree","dry-run","dispatch","writes"]);function de(e){const a=Object.create(null),t=[];for(let n=0;n<e.length;n++){const i=e[n];if(!i.startsWith("--")){t.push(i);continue}const o=i.slice(2);if(oe.has(o)){if(Object.hasOwn(a,o))throw new d(`--${o} is given twice`);a[o]=!0}else if(n+1<e.length)(a[o]||=[]).push(e[++n]);else throw new d(`--${o} needs a value`)}return{flags:a,rest:t}}r(de,"parse");function le(e,a,t){const n=new Set(a.filter(l=>l.startsWith("...")).map(l=>l.slice(3))),i=new Set(a.map(l=>l.replace(/^\.\.\./,""))),o=Object.keys(t).filter(l=>!i.has(l));if(o.length)throw new d(`${e} does not take ${o.map(l=>"--"+l).join(", ")}`);return Object.fromEntries(Object.entries(t).map(([l,c])=>{if(c===!0||n.has(l))return[l,c];if(c.length>1)throw new d(`--${l} is given twice`);return[l,c[0]]}))}r(le,"flagsOf");const he=["html","text","note","notes","heading","summary","title","body","next","progress","install-label","confirmed","asked","task","reason"],ce=/^(?:\/|~\/|\.{1,2}\/)\S*\.(?:html?|md|json|txt)$/;function ue(e){const a=e.trim();if(!a||/\s/.test(a))return!1;if(ce.test(a))return!0;try{return f.statSync(a).isFile()}catch{return!1}}r(ue,"looksLikePath");function pe(e,a){for(const t of he){if(typeof a[t]!="string"||!ue(a[t]))continue;const n=e.includes("file")?`; for a file use --file ${a[t].trim()}`:"";throw new d(`--${t} takes text, not a file path (${a[t].trim()})${n}`)}if(typeof a.file=="string"&&a.file!=="-"&&!f.existsSync(a.file))throw new d(`--file ${a.file}: no such file`)}r(pe,"checkInputs");const w=r(e=>f.readFileSync(e==="-"?0:e,"utf8"),"readInput");function q(e){if(!e)throw new d("--file <path> is required");try{return JSON.parse(w(e))}catch(a){throw new s.BoardError(`${e}: ${a.message}`)}}r(q,"readJSONInput");function C(e,a){const t=["notes-file","plan"].find(n=>e[n]!==void 0);if(t)throw new d(`--${t} is gone: the board names a card's notes file, <id>-notes.md, and writes it itself; ${a}`)}r(C,"refuseNotesFile");function T(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:w(e.file)}r(T,"noteOf");async function we(e,a,{"owner-step":t,check:n,text:i,...o}){if(t!==void 0&&(!e||o.dir))throw new d("--owner-step goes on a board card: pass --site, not --dir");if(n!==void 0&&t===void 0)throw new d("--check goes with --owner-step: it is the code check that closes the card once the step is done");t!==void 0&&await s.setItem(e,a,{ownerStep:t,check:n});const l=e&&!o.dir,c=l&&await s.handBackCalls(e,a,o);if(c)return c;if(l&&o.next===void 0&&await s.finishesCard(e,a,o)){if(i===void 0)throw new s.BoardError(`builder done finishes ${a}: pass --text "<what you checked>", or --next "<what happens next>" to stop without finishing`);return $(e,a,{text:i,ownerStep:t})}if(l&&await s.isHelper(e,a,o.agent)){if(i===void 0)throw new d('a helper hands back its result: pass --text "<what you did or found>"');return s.stopBuilder(e,a,{...o,note:i})}if(l&&i!==void 0&&await s.isPlanner(e,a,o.agent))return s.stopBuilder(e,a,{...o,note:i,requireNext:!0});if(i!==void 0)throw new d("--text goes on a builder done that finishes the card, the main build with a worktree and no --next, or on a helper's or a planner's");return s.stopBuilder(e,a,{...o,requireNext:!0})}r(we,"builderDone");const b={pr:[["wave/id"],[],async(e,[a])=>({lines:(await s.prReport(e,a)).map(t=>JSON.stringify(t))})],"pr comment":[["pr"],["body"],(e,[a],t)=>{if(t.body===void 0)throw new d('pr comment needs --body "<what Claude says>"');return s.postPR(e,a,{body:t.body})}],"pr reply":[["pr","comment-id"],["body"],(e,[a,t],n)=>{if(n.body===void 0)throw new d('pr reply needs --body "<what Claude says>"');return s.postPR(e,a,{replyTo:t,body:n.body})}],reply:[["eventId"],["html","file","title"],(e,[a],t)=>{if(t.html===void 0==(t.file===void 0))throw new d("reply takes --html or --file");return s.writeReply(e,a,t.html??w(t.file),t.title)}],thread:[["wave/id"],["text","link","who","via"],(e,[a],t)=>s.appendThread(e,a,t)],"visual add":[["wave/id"],["file","title"],(e,[a],t)=>s.addVisual(e,a,t)],"topic add":[["id"],["title","summary"],(e,[a],t)=>s.addTopic(e,{id:a,...t})],"question add":[[],["file"],async(e,a,t)=>{const n=q(t.file);return{...await s.addQuestions(e,n,process.env.KICKFLIP_RENDER_CHECK==="off"?void 0:ne),asked:[n].flat()}}],"question resolve":[["id"],["note"],(e,[a],t)=>s.resolveQuestion(e,a,t.note)],"question settle":[["id"],["note"],(e,[a],t)=>s.settleQuestion(e,a,t.note)],"question publish":[["key"],[],async(e,[a])=>({ids:await s.publishScreened(e,a)})],"question move":[["ids"],["to","block"],(e,[a],t)=>s.moveQuestions(e,a.split(",").filter(Boolean),t.to,(t.block||"").split(",").filter(Boolean))],"prototype add":[["topic"],["title","...take","card","served","...state"],(e,[a],t)=>s.addPrototype(e,{topic:a,title:t.title,card:t.card,takes:t.take,served:t.served,states:t.state})],"prototype base":[[],[],e=>re(e)],"prototype list":[[],[],e=>s.listPrototypes(e)],"prototype rm":[["topic"],[],(e,[a])=>s.removePrototype(e,a)],"item set":[["wave/id"],["done","status","clear-status","next","clear-next","...pr","pushed","gh-since","from-ask","phase","state","held","unheld","pick-up-at","notes-file","plan","deps","blocked-until","block-reason","parent","related","relate","unrelate","install","install-sha","install-label","clear-install","title","size","model","force","ticket","text","check"],async(e,[a],t,n)=>(C(t,`add to its notes with item notes ${a} --heading "<heading>" --file <draft.md>`),t.done&&e&&await s.checkNotReopened(e,a),s.setItem(e,a,{done:t.done,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"],phase:t.phase,state:t.state,held:t.held,unheld:t.unheld,pickUpAt:t["pick-up-at"],deps:t.deps,blockedUntil:t["blocked-until"],blockReason:t["block-reason"],parent:t.parent,related:t.related,relate:t.relate,unrelate:t.unrelate,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,stopRun:r(()=>te(e,a),"stopRun")},n))],"item add":[["wave[/id]"],["title","notes","notes-from","deps","notes-file","plan","ticket","pick-up-at","held","parent","size","model","phase","state","relate","from-ask","text"],(e,[a],t,n)=>{if(C(t,"seed a new card's notes with item add --notes-from <draft.md>"),t.notes!==void 0&&t["notes-from"]!==void 0)throw new d("pass --notes or --notes-from, not both");return s.addItem(e,a,{title:t.title,notes:t.notes,notesFrom:t["notes-from"],deps:t.deps,ticket:t.ticket,pickUpAt:t["pick-up-at"],held:t.held,parent:t.parent,size:t.size,model:t.model,phase:t.phase,state:t.state,relate:t.relate,fromAsk:t["from-ask"],text:t.text,intake:!0},n)}],"item notes":[["wave/id"],["heading","file","from"],(e,[a],t)=>{if(t.heading===void 0&&t.from===void 0)throw new d(`item notes needs --heading "<the section's title>"`);if(t.file===void 0==(t.from===void 0))throw new d("item notes takes --file <draft.md> or --from <wave>/<id>");return s.addNotes(e,a,t)}],"item relate-ask":[["eventId","wave/id"],["summary","title"],(e,[a,t],n)=>s.relateAsk(e,a,t,n.summary,n.title)],"ask progress":[["eventId"],["next"],(e,[a],t)=>s.markAsk(e,a,"progress",t.next)],"ask done":[["eventId"],[],(e,[a])=>s.markAsk(e,a,"done")],"ask unfold":[["eventId"],[],(e,[a])=>s.unfoldAsk(e,a)],"ask move":[["eventId"],["to"],(e,[a],t)=>s.moveAsk(e,a,t.to)],"tickets backfill":[[],["dry-run"],(e,a,t,n)=>s.backfillTickets(e,{dryRun:t["dry-run"]},n)],"lineup create":[["slug"],["title","goal","...step","...phase","note","file"],(e,[a],t)=>s.createLineup(e,a,{title:t.title,goal:t.goal,steps:t.step,phase:t.phase,note:T(t)})],"lineup add":[["slug","wave/id"],["after"],(e,[a,t],n)=>s.changeLineup(e,a,{add:t,after:n.after})],"lineup remove":[["slug","wave/id"],[],(e,[a,t])=>s.changeLineup(e,a,{remove:t})],"lineup set":[["slug"],["title","goal","note","file","...step","...phase"],(e,[a],t)=>s.changeLineup(e,a,{title:t.title,goal:t.goal,note:T(t),steps:t.step,phase:t.phase})],"lineup finish":[["slug"],[],(e,[a])=>s.finishLineup(e,a)],slack:[[],[],()=>({lines:[JSON.stringify(H())]})],"slack set":[[],["self-dm","user","domain","timezone"],async(e,a,t)=>{const n={"self-dm":"self_dm",user:"owner_slack_user",domain:"slack_domain",timezone:"timezone"};try{return{lines:[JSON.stringify(await M(Object.fromEntries(Object.entries(t).map(([i,o])=>[n[i],o]))))]}}catch(i){throw new s.BoardError(i.message)}}],integrations:[[],[],async()=>({lines:[JSON.stringify(await D.rows())]})],"review updated":[["wave/id"],["text"],(e,[a],t)=>s.reviewUpdated(e,a,t.text)],"review record":[["pr"],["state","text","file"],(e,[a],t)=>{if(t.text===void 0==(t.file===void 0))throw new d("review record takes --text or --file");return s.recordReview(e,a,{state:t.state,text:t.text??w(t.file)})}],"review post":[["pr"],["file","read-at"],(e,[a],t)=>{if(t.file===void 0)throw new d("review post needs --file <review.md>");if(t["read-at"]===void 0)throw new d("review post needs --read-at <time>, when you read the PR, so a review that landed since is caught");return s.postIncomingReview(e,a,{words:w(t.file),readAt:t["read-at"]})}],"review digest":[[],[],async(e,a,t,n)=>({lines:[JSON.stringify(await s.reviewDigest(e,n))]})],"review due":[[],[],async(e,a,t,n)=>({lines:[JSON.stringify(await s.reviewDue(e,n))]})],"reviewers set":[[],["file"],(e,a,t)=>s.setReviewers(e,q(t.file))],"reviewers add":[[],["file"],(e,a,t)=>s.addReviewers(e,q(t.file))],"reviewers remove":[["...name"],[],(e,a)=>s.removeReviewers(e,a)],"rules list":[[],[],e=>s.listRules(e)],"rules add":[[],["text","hub","by"],(e,a,t)=>s.addRuleTo(e,t)],"rules edit":[["id"],["text","hub"],(e,[a],t)=>s.editRuleIn(e,a,t)],"rules remove":[["id"],["hub"],(e,[a],t)=>s.removeRuleFrom(e,a,t)],settings:[[],[],async e=>({lines:[JSON.stringify(await s.readBoardSettings(e))]})],build:[["wave/id"],["asked"],async(e,[a],t)=>{if(!e)throw new s.BoardError("build needs --site <route>");s.parseKey(a);const n=await s.askedGate(e,a,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:x.basename(e),key:a}),signal:AbortSignal.timeout(1e4)}),o=await i.json();if(!i.ok)throw new s.BoardError(o.error||"The hub refused the build.");return{lines:["started",...(await s.appendThread(e,a,{text:n})).lines]}}catch(i){throw i instanceof s.BoardError?i:new s.BoardError(`Could not start the build: ${i.message}`)}}],"main set":[[],["install","install-sha"],(e,a,t)=>s.setMainInstall(e,{install:t.install,installSha:t["install-sha"]})],"settings set":[["name","value"],[],(e,[a,t])=>s.setBoardSetting(e,a,t)],"builder start":[["wave/id"],["label","phase","progress","dir","agent","kind","model","eta","worktree","repo","dispatch"],(e,[a],{worktree:t,repo:n,dispatch:i,...o})=>{if(n!==void 0&&!t)throw new d("--repo goes with --worktree");const l=r(p=>z(e,a.split("/")[1],{repo:n,...p}),"make"),c=e&&o.dir===void 0&&se(e);return s.startBuilder(e,a,{...o,dispatch:i?"session":void 0,worktree:t?l:void 0,ownWorktree:c?l:void 0})}],"builder progress":[["wave/id","...text?"],["phase","text","dir","agent","eta","clear-eta"],(e,[a,...t],n)=>{if(t.length&&n.text!==void 0)throw new d("builder progress takes its text once: as words or as --text");if(!t.length&&n.text===void 0)throw new d("builder progress needs its text, as words or as --text");return s.builderProgress(e,a,n.text??t.join(" "),{...n,clearEta:n["clear-eta"]})}],"builder done":[["wave/id"],["dir","agent","next","owner-step","check","text","updated"],async(e,[a],{updated:t,...n})=>{const i=e&&!n.dir;if(i&&t===void 0&&await s.cardLinked(e,a))throw new s.BoardError(`a reviewer has ${a}'s PR link: pass --updated "<what changed>" for its thread, or --updated none for a change only to comments, names, docs or tests`);const o=await we(e,a,n);return i&&t!==void 0&&t.trim().toLowerCase()!=="none"&&await s.reviewUpdated(e,a,t),o}],"builder pick":[["wave/id"],[],(e,[a])=>s.pickBuilder(e,a)],"agent launch":[["wave/id"],["name","task","writes","model"],(e,[a],t)=>s.requestHelper(e,a,t)],"agent wait":[["wave/id"],["name","agent","timeout"],(e,[a],t)=>s.waitHelper(e,a,{...t,alive:Q})],"agent merge":[["wave/id"],["name"],async(e,[a],t)=>{const{record:n,card:i}=await s.helperOf(e,a,t.name);if(!n.done)throw new s.BoardError(`helper ${t.name} is still open; run agent wait ${a} --name ${t.name} first`);if(!n.writes||!n.worktree)throw new s.BoardError(`helper ${t.name} writes no code, so there is nothing to merge`);if(!i)throw new s.BoardError(`${a} has no worktree to merge helper ${t.name} into`);return G(i,n.worktree)}],audit:[["...wave/id?"],[],(e,[a,...t])=>{if(t.length)throw new d("audit takes one <wave/id> at most");return s.auditLog(e,a)}],"builder call":[["wave/id","...text"],["dir","agent"],(e,[a,...t],n)=>s.recordCall(e,a,t.join(" "),n)],"builder calls":[["wave/id"],["dir","agent"],(e,[a],t)=>s.listCalls(e,a,t)],"builder ask":[["wave/id","...text"],["agent"],(e,[a,...t],n)=>s.askOrchestrator(e,a,t.join(" "),n)],"builder answer":[["wave/id","ask"],["text"],(e,[a,t],n)=>s.answerBuilder(e,a,t,n.text)],"builder defer":[["wave/id","ask"],["text"],(e,[a,t],n)=>s.answerBuilder(e,a,t,n.text,{deferred:!0})],"builder asks":[["wave/id"],["wait","timeout"],(e,[a],t)=>s.listAsks(e,a,t)],"builder verified":[["wave/id"],["dir","agent","note"],async(e,[a],t)=>{await s.verifyCalls(e,a,t);const n=e&&!t.dir?await s.verifiedFinish(e,a):null;if(!n)return{};if(n.waits.length)return{lines:[`${await s.cardName(e,a)} finishes once the owner answers ${n.waits.join(", ")}`]};try{return await $(e,a,{text:t.note??"Judgment calls verified."})}catch(i){throw i instanceof s.BoardError?Object.assign(new s.BoardError(`judgment calls verified, but ${await s.cardName(e,a)} didn't finish: ${i.message}`),{exitCode:i.exitCode}):i}}],land:[["wave/id"],["repo","dir","text","owner-step","check"],async(e,[a],t)=>{const n=a.split("/")[1];if(e&&t.text===void 0)throw new d(`land needs --text "<what was checked>", which closes ${a} 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");if(t.check!==void 0&&t["owner-step"]===void 0)throw new d("--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 $(e,a,{text:t.text,ownerStep:t["owner-step"],check:t.check,repo:t.repo});const i=e?await s.cardName(e,a):void 0,o=await F(i??a,async()=>{e&&await s.checkClose(e,a,t.text,t["owner-step"],t.check),await s.checkCallsVerified(e,a,{dir:t.dir});const l=await s.recordedWorktree(e,a,{dir:t.dir}),{repo:c,base:p}=U(e,{repo:t.repo??l?.repo});return await J({path:l?.path??x.join(K(),n),branch:l?.branch??n,repo:x.resolve(c),base:p,ref:i}),e?s.setItem(e,a,{done:!0,text:t.text,ownerStep:t["owner-step"],check:t.check}):{lines:[]}});return o.joined?{lines:[`joined the land already running for ${i??a}`]}:o}],"builder stop":[["wave/id"],[],(e,[a])=>ee(m(e,"builder stop"),a)],"item stall":[["wave/id"],["reason"],(e,[a],t)=>Z(m(e,"item stall"),a,t.reason)],"land stop":[["wave/id"],[],(e,[a])=>ae(m(e,"land stop"),a)],"land queue":[[],[],()=>Y()],"item bump":[["wave/id"],[],(e,[a])=>S(m(e,"item bump"),a,!0)],"item unbump":[["wave/id"],[],(e,[a])=>S(m(e,"item unbump"),a,!1)],lander:[["wave/id"],[],async(e,[a])=>{if(!e)throw new d("lander needs --site <route>");return await s.relaunchLander(e,a),{lines:[`${await s.cardName(e,a)} is handed back again; the server launches a lander for it within a minute`]}}],merge:[["wave/id"],["after-approval","confirmed"],async(e,[a],t)=>e&&await s.heldMerge(e,a)?X(e,a):s.mergeCard(e,a,{afterApproval:t["after-approval"],confirmed:t.confirmed})],"arch file":[["wave/id"],[],(e,[a])=>e?V(e,a):Promise.reject(new s.BoardError("arch file needs --site <route>"))],push:[[],["kind","title","body","url","tag","urgent","now"],(e,a,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 s.sendPush(e,t)}]};b.install=b.build;function m(e,a){if(!e)throw new d(`${a} needs --site <route>`);return e}r(m,"needSite");const h=r((e,a)=>s.cardName(e,a),"card"),R=r((e,a)=>`${a}${e.length===1?"":"s"} ${e.join(", ")}`,"plural"),_=r(e=>e.agent===void 0||e.agent==="main"?"builder":`builder ${e.agent}`,"builderOf");function me(e){const a=e["pick-up-at"];return[e.done&&"done",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.phase,e.held&&"held",e.unheld&&"resumed",a!==void 0&&(a?`held until ${a}`:"resumed"),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.relate!==void 0&&`related to ${e.relate.split(",").join(", ")} added`,e.unrelate!==void 0&&`unrelated from ${e.unrelate.split(",").join(", ")}`,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(me,"itemChanges");const ge={reply:r(async(e,[a])=>`reply written to ${await s.eventCard(e,a)}`,"reply"),thread:r(async(e,[a])=>`thread line on ${await h(e,a)}`,"thread"),"topic add":r((e,[a])=>`topic ${a} added`,"topic add"),"question add":r(async(e,a,t,{asked:n,staged:i})=>{const o=[...new Set(n.map(c=>c.topic))],l=o.length!==1?"":o[0].startsWith("item:")?` on ${await h(e,o[0].slice(5))}`:` on topic ${o[0]}`;return`${R(n.map(c=>c.id),"question")} ${i===n.length?"staged":"added"}${l}`},"question add"),"question resolve":r((e,[a])=>`question ${a} resolved`,"question resolve"),"question settle":r((e,[a])=>`question ${a} settled`,"question settle"),"question publish":r(async(e,[a],t,{ids:n})=>`${R(n,"question")} on ${await h(e,a)} passed to the owner`,"question publish"),"question move":r((e,[a],t)=>`questions ${a} merged onto ${t.to}`,"question move"),"prototype add":r(async(e,[a],t)=>`prototype ${a} recorded${t.card?` for ${await h(e,t.card)}`:""}`,"prototype add"),"prototype rm":r((e,[a])=>`prototype ${a} removed`,"prototype rm"),"item set":r(async(e,[a],t)=>`${await h(e,a)}: ${me(t)}`,"item set"),"item add":r(async(e,a,t,{key:n})=>`${await h(e,n)} added as ${n}`,"item add"),"item notes":r(async(e,[a],t,{file:n,heading:i})=>`${await h(e,a)}: notes section "${i}" added to ${n}`,"item notes"),"item relate-ask":r(async(e,[a,t])=>`task ${a} folded into ${await h(e,t)}`,"item relate-ask"),"ask progress":r(async(e,[a])=>`${await h(e,`ask:${a}`)}: in progress`,"ask progress"),"ask done":r(async(e,[a])=>`${await h(e,`ask:${a}`)}: done`,"ask done"),"ask unfold":r((e,[a])=>`task ${a} back on a card of its own`,"ask unfold"),"ask move":r((e,[a],t)=>`task ${a} moved to /${t.to}`,"ask move"),"lineup create":r((e,[a])=>`lineup ${a} created`,"lineup create"),"lineup add":r(async(e,[a,t])=>`${await h(e,t)} added to lineup ${a}`,"lineup add"),"lineup remove":r(async(e,[a,t])=>`${await h(e,t)} taken out of lineup ${a}`,"lineup remove"),"lineup set":r((e,[a])=>`lineup ${a} updated`,"lineup set"),"lineup finish":r((e,[a])=>`lineup ${a} finished`,"lineup finish"),"review record":r((e,[a],t)=>`review of PR #${a} 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,a)=>`${R(a,"reviewer")} removed`,"reviewers remove"),"rules add":r((e,a,t)=>`${t.hub?"hub":"board"} rule added`,"rules add"),"rules edit":r((e,[a],t)=>`${t.hub?"hub":"board"} rule ${a} reworded`,"rules edit"),"rules remove":r((e,[a],t)=>`${t.hub?"hub":"board"} rule ${a} removed`,"rules remove"),"builder start":r(async(e,[a],t,n)=>`${await h(e,a)}: ${n.alreadyOpen?"already open: the Implement tap opened its record; launch the agent, whose own builder start takes it up":`${_(t)} started`}`,"builder start"),"builder progress":r(async(e,[a])=>`progress line on ${await h(e,a)}`,"builder progress"),"builder done":r(async(e,[a],t,n)=>`${await h(e,a)}: ${n?.said??`${_(t)} done${t["owner-step"]===void 0?"":`; owner step "${t["owner-step"].trim()}"`}`}`,"builder done"),"builder call":r(async(e,[a])=>`judgment call recorded on ${await h(e,a)}`,"builder call"),"builder ask":r(async(e,[a],t,{id:n})=>`${await h(e,a)}: asked the orchestrator, ${n}`,"builder ask"),"builder answer":r(async(e,[a,t])=>`${await h(e,a)}: ${t} answered`,"builder answer"),"builder defer":r(async(e,[a,t])=>`${await h(e,a)}: ${t} deferred to the owner`,"builder defer"),"builder verified":r(async(e,[a],t,n)=>`${await h(e,a)}: judgment calls verified${n?.said?`; ${n.said}`:""}`,"builder verified"),land:r(async(e,[a],t,n)=>e?n?.said?`${await h(e,a)}: ${n.said}`:t["owner-step"]===void 0?`${await h(e,a)}: landed and done`:`${await h(e,a)}: landed; waits on the owner step "${t["owner-step"].trim()}"`:null,"land"),merge:r(async(e,[a],t,n)=>`${await h(e,a)}: ${n?.said??"merged"}`,"merge"),"arch file":r(async(e,[a])=>`findings of ${await h(e,a)} filed`,"arch file")};async function fe(e,a={}){try{const{flags:t,rest:n}=de(e),i=[3,2,1].map(u=>n.slice(0,u).join(" ")).find(u=>y(b,u)),o=y(b,i);if(!o)throw new d(n.length?`unknown command "${n.slice(0,2).join(" ")}"`:"no command given");const[l,c,p]=o,g=n.slice(i.split(" ").length),N=l.at(-1)?.startsWith("..."),B=l.at(-1)?.endsWith("?")?l.length-1:l.length;if(N?g.length<B:g.length!==l.length)throw new d(`${i} takes ${l.map(u=>`<${u.replace(/^\.\.\.|\?$/g,"")}>`).join(" ")||"no arguments"}`);const{site:I,...E}=t;if(I?.length>1)throw new d("--site is given twice");const A=I?.[0],k=le(i,c,E);pe(c,k);const P=A===void 0?null:await s.siteDir(A),v=await p(P,g,k,a),j=y(ge,i),O=j&&await j(P,g,k,v);O&&console.log(O);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 s.BoardError)return console.error(t.message),t.exitCode??1;throw t}}if(r(fe,"main"),process.argv[1]&&import.meta.url===W(f.realpathSync(process.argv[1])).href){const e=process.argv.slice(2);!e.length||e.includes("--help")||e[0]==="help"?console.log(ie):process.exitCode=await fe(e)}export{ie as USAGE,fe as main};
666
+ KICKFLIP_PUSH_CMD='["<cmd>","<arg>"]' replaces \`node ../../bin/kickflip.mjs push\`; the flags follow it`;class d extends Error{static{r(this,"UsageError")}}const ie=new Set(["done","clear-status","clear-next","urgent","now","clear-eta","held","unheld","idle","wait","hub","clear-install","force","worktree","dry-run","dispatch","writes"]);function oe(e){const a=Object.create(null),t=[];for(let s=0;s<e.length;s++){const i=e[s];if(!i.startsWith("--")){t.push(i);continue}const o=i.slice(2);if(ie.has(o)){if(Object.hasOwn(a,o))throw new d(`--${o} is given twice`);a[o]=!0}else if(s+1<e.length)(a[o]||=[]).push(e[++s]);else throw new d(`--${o} needs a value`)}return{flags:a,rest:t}}r(oe,"parse");function de(e,a,t){const s=new Set(a.filter(l=>l.startsWith("...")).map(l=>l.slice(3))),i=new Set(a.map(l=>l.replace(/^\.\.\./,""))),o=Object.keys(t).filter(l=>!i.has(l));if(o.length)throw new d(`${e} does not take ${o.map(l=>"--"+l).join(", ")}`);return Object.fromEntries(Object.entries(t).map(([l,c])=>{if(c===!0||s.has(l))return[l,c];if(c.length>1)throw new d(`--${l} is given twice`);return[l,c[0]]}))}r(de,"flagsOf");const le=["html","text","note","notes","heading","summary","title","body","next","progress","install-label","confirmed","asked","task","reason"],he=/^(?:\/|~\/|\.{1,2}\/)\S*\.(?:html?|md|json|txt)$/;function ce(e){const a=e.trim();if(!a||/\s/.test(a))return!1;if(he.test(a))return!0;try{return g.statSync(a).isFile()}catch{return!1}}r(ce,"looksLikePath");function ue(e,a){for(const t of le){if(typeof a[t]!="string"||!ce(a[t]))continue;const s=e.includes("file")?`; for a file use --file ${a[t].trim()}`:"";throw new d(`--${t} takes text, not a file path (${a[t].trim()})${s}`)}if(typeof a.file=="string"&&a.file!=="-"&&!g.existsSync(a.file))throw new d(`--file ${a.file}: no such file`)}r(ue,"checkInputs");const w=r(e=>g.readFileSync(e==="-"?0:e,"utf8"),"readInput");function q(e){if(!e)throw new d("--file <path> is required");try{return JSON.parse(w(e))}catch(a){throw new n.BoardError(`${e}: ${a.message}`)}}r(q,"readJSONInput");function C(e,a){const t=["notes-file","plan"].find(s=>e[s]!==void 0);if(t)throw new d(`--${t} is gone: the board names a card's notes file, <id>-notes.md, and writes it itself; ${a}`)}r(C,"refuseNotesFile");function _(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:w(e.file)}r(_,"noteOf");async function pe(e,a,{"owner-step":t,check:s,text:i,...o}){if(t!==void 0&&(!e||o.dir))throw new d("--owner-step goes on a board card: pass --site, not --dir");if(s!==void 0&&t===void 0)throw new d("--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,a,{ownerStep:t,check:s});const l=e&&!o.dir,c=l&&await n.handBackCalls(e,a,o);if(c)return c;if(l&&o.next===void 0&&await n.finishesCard(e,a,o)){if(i===void 0)throw new n.BoardError(`builder done finishes ${a}: pass --text "<what you checked>", or --next "<what happens next>" to stop without finishing`);return $(e,a,{text:i,ownerStep:t})}if(l&&await n.isHelper(e,a,o.agent)){if(i===void 0)throw new d('a helper hands back its result: pass --text "<what you did or found>"');return n.stopBuilder(e,a,{...o,note:i})}if(l&&i!==void 0&&await n.isPlanner(e,a,o.agent))return n.stopBuilder(e,a,{...o,note:i,requireNext:!0});if(i!==void 0)throw new d("--text goes on a builder done that finishes the card, the main build with a worktree and no --next, or on a helper's or a planner's");return n.stopBuilder(e,a,{...o,requireNext:!0})}r(pe,"builderDone");const b={pr:[["file/id"],[],async(e,[a])=>({lines:(await n.prReport(e,a)).map(t=>JSON.stringify(t))})],"pr comment":[["pr"],["body"],(e,[a],t)=>{if(t.body===void 0)throw new d('pr comment needs --body "<what Claude says>"');return n.postPR(e,a,{body:t.body})}],"pr reply":[["pr","comment-id"],["body"],(e,[a,t],s)=>{if(s.body===void 0)throw new d('pr reply needs --body "<what Claude says>"');return n.postPR(e,a,{replyTo:t,body:s.body})}],reply:[["eventId"],["html","file","title"],(e,[a],t)=>{if(t.html===void 0==(t.file===void 0))throw new d("reply takes --html or --file");return n.writeReply(e,a,t.html??w(t.file),t.title)}],thread:[["file/id"],["text","link","who","via"],(e,[a],t)=>n.appendMessage(e,a,t)],"visual add":[["file/id"],["file","title"],(e,[a],t)=>n.addVisual(e,a,t)],"topic add":[["id"],["title","summary"],(e,[a],t)=>n.addTopic(e,{id:a,...t})],"question add":[[],["file"],async(e,a,t)=>{const s=q(t.file);return{...await n.addQuestions(e,s,process.env.KICKFLIP_RENDER_CHECK==="off"?void 0:se),asked:[s].flat()}}],"question resolve":[["id"],["note"],(e,[a],t)=>n.resolveQuestion(e,a,t.note)],"question settle":[["id"],["note"],(e,[a],t)=>n.settleQuestion(e,a,t.note)],"question publish":[["key"],[],async(e,[a])=>({ids:await n.publishScreened(e,a)})],"question move":[["ids"],["to","block"],(e,[a],t)=>n.moveQuestions(e,a.split(",").filter(Boolean),t.to,(t.block||"").split(",").filter(Boolean))],"prototype add":[["topic"],["title","...take","card","served","...state"],(e,[a],t)=>n.addPrototype(e,{topic:a,title:t.title,card:t.card,takes:t.take,served:t.served,states:t.state})],"prototype base":[[],[],e=>ne(e)],"prototype list":[[],[],e=>n.listPrototypes(e)],"prototype rm":[["topic"],[],(e,[a])=>n.removePrototype(e,a)],"item set":[["file/id"],["done","status","clear-status","next","clear-next","...pr","pushed","gh-since","from-ask","phase","state","held","unheld","pick-up-at","notes-file","plan","deps","blocked-until","block-reason","parent","related","relate","unrelate","install","install-sha","install-label","clear-install","title","size","model","force","ticket","text","check"],async(e,[a],t,s)=>(C(t,`add to its notes with item notes ${a} --heading "<heading>" --file <draft.md>`),t.done&&e&&await n.checkNotReopened(e,a),n.setItem(e,a,{done:t.done,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"],phase:t.phase,state:t.state,held:t.held,unheld:t.unheld,pickUpAt:t["pick-up-at"],deps:t.deps,blockedUntil:t["blocked-until"],blockReason:t["block-reason"],parent:t.parent,related:t.related,relate:t.relate,unrelate:t.unrelate,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},s))],"item add":[["file[/id]"],["title","notes","notes-from","deps","notes-file","plan","ticket","pick-up-at","held","parent","size","model","phase","state","relate","from-ask","text"],(e,[a],t,s)=>{if(C(t,"seed a new card's notes with item add --notes-from <draft.md>"),t.notes!==void 0&&t["notes-from"]!==void 0)throw new d("pass --notes or --notes-from, not both");return n.addItem(e,a,{title:t.title,notes:t.notes,notesFrom:t["notes-from"],deps:t.deps,ticket:t.ticket,pickUpAt:t["pick-up-at"],held:t.held,parent:t.parent,size:t.size,model:t.model,phase:t.phase,state:t.state,relate:t.relate,fromAsk:t["from-ask"],text:t.text,intake:!0},s)}],"item notes":[["file/id"],["heading","file","from"],(e,[a],t)=>{if(t.heading===void 0&&t.from===void 0)throw new d(`item notes needs --heading "<the section's title>"`);if(t.file===void 0==(t.from===void 0))throw new d("item notes takes --file <draft.md> or --from <file>/<id>");return n.addNotes(e,a,t)}],"item relate-ask":[["eventId","file/id"],["summary","title"],(e,[a,t],s)=>n.relateAsk(e,a,t,s.summary,s.title)],"ask progress":[["eventId"],["next"],(e,[a],t)=>n.markAsk(e,a,"progress",t.next)],"ask done":[["eventId"],[],(e,[a])=>n.markAsk(e,a,"done")],"ask unfold":[["eventId"],[],(e,[a])=>n.unfoldAsk(e,a)],"ask move":[["eventId"],["to"],(e,[a],t)=>n.moveAsk(e,a,t.to)],"tickets backfill":[[],["dry-run"],(e,a,t,s)=>n.backfillTickets(e,{dryRun:t["dry-run"]},s)],"wave create":[["slug"],["title","goal","...card","note","file"],(e,[a],t)=>n.createWave(e,a,{title:t.title,goal:t.goal,cards:t.card,note:_(t)})],"wave add":[["slug","file/id"],["after"],(e,[a,t],s)=>n.changeWave(e,a,{add:t,after:s.after})],"wave remove":[["slug","file/id"],[],(e,[a,t])=>n.changeWave(e,a,{remove:t})],"wave set":[["slug","...file/id?"],["after","title","goal","note","file"],(e,[a,...t],s)=>{if(t.length>1)throw new d("wave set takes <slug> [<file/id>]");return n.changeWave(e,a,{card:t[0],after:s.after,title:s.title,goal:s.goal,note:_(s)})}],"wave finish":[["slug"],[],(e,[a])=>n.finishWave(e,a)],slack:[[],[],()=>({lines:[JSON.stringify(H())]})],"slack set":[[],["self-dm","user","domain","timezone"],async(e,a,t)=>{const s={"self-dm":"self_dm",user:"owner_slack_user",domain:"slack_domain",timezone:"timezone"};try{return{lines:[JSON.stringify(await L(Object.fromEntries(Object.entries(t).map(([i,o])=>[s[i],o]))))]}}catch(i){throw new n.BoardError(i.message)}}],integrations:[[],[],async()=>({lines:[JSON.stringify(await D.rows())]})],"review updated":[["file/id"],["text"],(e,[a],t)=>n.reviewUpdated(e,a,t.text)],"review record":[["pr"],["state","text","file"],(e,[a],t)=>{if(t.text===void 0==(t.file===void 0))throw new d("review record takes --text or --file");return n.recordReview(e,a,{state:t.state,text:t.text??w(t.file)})}],"review post":[["pr"],["file","read-at"],(e,[a],t)=>{if(t.file===void 0)throw new d("review post needs --file <review.md>");if(t["read-at"]===void 0)throw new d("review post needs --read-at <time>, when you read the PR, so a review that landed since is caught");return n.postIncomingReview(e,a,{words:w(t.file),readAt:t["read-at"]})}],"review digest":[[],[],async(e,a,t,s)=>({lines:[JSON.stringify(await n.reviewDigest(e,s))]})],"review due":[[],[],async(e,a,t,s)=>({lines:[JSON.stringify(await n.reviewDue(e,s))]})],"reviewers set":[[],["file"],(e,a,t)=>n.setReviewers(e,q(t.file))],"reviewers add":[[],["file"],(e,a,t)=>n.addReviewers(e,q(t.file))],"reviewers remove":[["...name"],[],(e,a)=>n.removeReviewers(e,a)],"rules list":[[],[],e=>n.listRules(e)],"rules add":[[],["text","hub","by"],(e,a,t)=>n.addRuleTo(e,t)],"rules edit":[["id"],["text","hub"],(e,[a],t)=>n.editRuleIn(e,a,t)],"rules remove":[["id"],["hub"],(e,[a],t)=>n.removeRuleFrom(e,a,t)],settings:[[],[],async e=>({lines:[JSON.stringify(await n.readBoardSettings(e))]})],build:[["file/id"],["asked"],async(e,[a],t)=>{if(!e)throw new n.BoardError("build needs --site <route>");n.parseKey(a);const s=await n.askedGate(e,a,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:x.basename(e),key:a}),signal:AbortSignal.timeout(1e4)}),o=await i.json();if(!i.ok)throw new n.BoardError(o.error||"The hub refused the build.");return{lines:["started",...(await n.appendMessage(e,a,{text:s})).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,a,t)=>n.setMainInstall(e,{install:t.install,installSha:t["install-sha"]})],"settings set":[["name","value"],[],(e,[a,t])=>n.setBoardSetting(e,a,t)],"builder start":[["file/id"],["label","phase","progress","dir","agent","kind","model","eta","worktree","repo","dispatch"],(e,[a],{worktree:t,repo:s,dispatch:i,...o})=>{if(s!==void 0&&!t)throw new d("--repo goes with --worktree");const l=r(p=>z(e,a.split("/")[1],{repo:s,...p}),"make"),c=e&&o.dir===void 0&&ae(e);return n.startBuilder(e,a,{...o,dispatch:i?"session":void 0,worktree:t?l:void 0,ownWorktree:c?l:void 0})}],"builder progress":[["file/id","...text?"],["phase","text","dir","agent","eta","clear-eta"],(e,[a,...t],s)=>{if(t.length&&s.text!==void 0)throw new d("builder progress takes its text once: as words or as --text");if(!t.length&&s.text===void 0)throw new d("builder progress needs its text, as words or as --text");return n.builderProgress(e,a,s.text??t.join(" "),{...s,clearEta:s["clear-eta"]})}],"builder done":[["file/id"],["dir","agent","next","owner-step","check","text","updated"],async(e,[a],{updated:t,...s})=>{const i=e&&!s.dir;if(i&&t===void 0&&await n.cardLinked(e,a))throw new n.BoardError(`a reviewer has ${a}'s PR link: pass --updated "<what changed>" for its thread, or --updated none for a change only to comments, names, docs or tests`);const o=await pe(e,a,s);return i&&t!==void 0&&t.trim().toLowerCase()!=="none"&&await n.reviewUpdated(e,a,t),o}],"builder pick":[["file/id"],[],(e,[a])=>n.pickBuilder(e,a)],"agent launch":[["file/id"],["name","task","writes","model"],(e,[a],t)=>n.requestHelper(e,a,t)],"agent wait":[["file/id"],["name","agent","timeout"],(e,[a],t)=>n.waitHelper(e,a,{...t,alive:Q})],"agent merge":[["file/id"],["name"],async(e,[a],t)=>{const{record:s,card:i}=await n.helperOf(e,a,t.name);if(!s.done)throw new n.BoardError(`helper ${t.name} is still open; run agent wait ${a} --name ${t.name} first`);if(!s.writes||!s.worktree)throw new n.BoardError(`helper ${t.name} writes no code, so there is nothing to merge`);if(!i)throw new n.BoardError(`${a} has no worktree to merge helper ${t.name} into`);return G(i,s.worktree)}],audit:[["...file/id?"],[],(e,[a,...t])=>{if(t.length)throw new d("audit takes one <file/id> at most");return n.auditLog(e,a)}],"builder call":[["file/id","...text"],["dir","agent"],(e,[a,...t],s)=>n.recordCall(e,a,t.join(" "),s)],"builder calls":[["file/id"],["dir","agent"],(e,[a],t)=>n.listCalls(e,a,t)],"builder ask":[["file/id","...text"],["agent"],(e,[a,...t],s)=>n.askOrchestrator(e,a,t.join(" "),s)],"builder answer":[["file/id","ask"],["text"],(e,[a,t],s)=>n.answerBuilder(e,a,t,s.text)],"builder defer":[["file/id","ask"],["text"],(e,[a,t],s)=>n.answerBuilder(e,a,t,s.text,{deferred:!0})],"builder asks":[["file/id"],["wait","timeout"],(e,[a],t)=>n.listAsks(e,a,t)],"builder verified":[["file/id"],["dir","agent","note"],async(e,[a],t)=>{await n.verifyCalls(e,a,t);const s=e&&!t.dir?await n.verifiedFinish(e,a):null;if(!s)return{};if(s.waits.length)return{lines:[`${await n.cardName(e,a)} finishes once the owner answers ${s.waits.join(", ")}`]};try{return await $(e,a,{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,a)} didn't finish: ${i.message}`),{exitCode:i.exitCode}):i}}],land:[["file/id"],["repo","dir","text","owner-step","check"],async(e,[a],t)=>{const s=a.split("/")[1];if(e&&t.text===void 0)throw new d(`land needs --text "<what was checked>", which closes ${a} 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");if(t.check!==void 0&&t["owner-step"]===void 0)throw new d("--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 $(e,a,{text:t.text,ownerStep:t["owner-step"],check:t.check,repo:t.repo});const i=e?await n.cardName(e,a):void 0,o=await J(i??a,async()=>{e&&await n.checkClose(e,a,t.text,t["owner-step"],t.check),await n.checkCallsVerified(e,a,{dir:t.dir});const l=await n.recordedWorktree(e,a,{dir:t.dir}),{repo:c,base:p}=U(e,{repo:t.repo??l?.repo});return await F({path:l?.path??x.join(K(),s),branch:l?.branch??s,repo:x.resolve(c),base:p,ref:i}),e?n.setItem(e,a,{done:!0,text:t.text,ownerStep:t["owner-step"],check:t.check}):{lines:[]}});return o.joined?{lines:[`joined the land already running for ${i??a}`]}:o}],"builder stop":[["file/id"],[],(e,[a])=>ee(m(e,"builder stop"),a)],"item stall":[["file/id"],["reason"],(e,[a],t)=>Y(m(e,"item stall"),a,t.reason)],"land stop":[["file/id"],[],(e,[a])=>te(m(e,"land stop"),a)],"land queue":[[],[],()=>Z()],"item bump":[["file/id"],[],(e,[a])=>S(m(e,"item bump"),a,!0)],"item unbump":[["file/id"],[],(e,[a])=>S(m(e,"item unbump"),a,!1)],lander:[["file/id"],[],async(e,[a])=>{if(!e)throw new d("lander needs --site <route>");return await n.relaunchLander(e,a),{lines:[`${await n.cardName(e,a)} is handed back again; the server launches a lander for it within a minute`]}}],merge:[["file/id"],["after-approval","confirmed"],async(e,[a],t)=>e&&await n.heldMerge(e,a)?X(e,a):n.mergeCard(e,a,{afterApproval:t["after-approval"],confirmed:t.confirmed})],"arch file":[["file/id"],[],(e,[a])=>e?V(e,a):Promise.reject(new n.BoardError("arch file needs --site <route>"))],push:[[],["kind","title","body","url","tag","urgent","now"],(e,a,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)}]};b.install=b.build;function m(e,a){if(!e)throw new d(`${a} needs --site <route>`);return e}r(m,"needSite");const h=r((e,a)=>n.cardName(e,a),"card"),R=r((e,a)=>`${a}${e.length===1?"":"s"} ${e.join(", ")}`,"plural"),N=r(e=>e.agent===void 0||e.agent==="main"?"builder":`builder ${e.agent}`,"builderOf");function we(e){const a=e["pick-up-at"];return[e.done&&"done",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.phase,e.held&&"held",e.unheld&&"resumed",a!==void 0&&(a?`held until ${a}`:"resumed"),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.relate!==void 0&&`related to ${e.relate.split(",").join(", ")} added`,e.unrelate!==void 0&&`unrelated from ${e.unrelate.split(",").join(", ")}`,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(we,"itemChanges");const me={reply:r(async(e,[a])=>`reply written to ${await n.eventCard(e,a)}`,"reply"),thread:r(async(e,[a])=>`thread line on ${await h(e,a)}`,"thread"),"topic add":r((e,[a])=>`topic ${a} added`,"topic add"),"question add":r(async(e,a,t,{asked:s,staged:i})=>{const o=[...new Set(s.map(c=>c.topic))],l=o.length!==1?"":o[0].startsWith("item:")?` on ${await h(e,o[0].slice(5))}`:` on topic ${o[0]}`;return`${R(s.map(c=>c.id),"question")} ${i===s.length?"staged":"added"}${l}`},"question add"),"question resolve":r((e,[a])=>`question ${a} resolved`,"question resolve"),"question settle":r((e,[a])=>`question ${a} settled`,"question settle"),"question publish":r(async(e,[a],t,{ids:s})=>`${R(s,"question")} on ${await h(e,a)} passed to the owner`,"question publish"),"question move":r((e,[a],t)=>`questions ${a} merged onto ${t.to}`,"question move"),"prototype add":r(async(e,[a],t)=>`prototype ${a} recorded${t.card?` for ${await h(e,t.card)}`:""}`,"prototype add"),"prototype rm":r((e,[a])=>`prototype ${a} removed`,"prototype rm"),"item set":r(async(e,[a],t)=>`${await h(e,a)}: ${we(t)}`,"item set"),"item add":r(async(e,a,t,{key:s})=>`${await h(e,s)} added as ${s}`,"item add"),"item notes":r(async(e,[a],t,{file:s,heading:i})=>`${await h(e,a)}: notes section "${i}" added to ${s}`,"item notes"),"item relate-ask":r(async(e,[a,t])=>`task ${a} folded into ${await h(e,t)}`,"item relate-ask"),"ask progress":r(async(e,[a])=>`${await h(e,`ask:${a}`)}: in progress`,"ask progress"),"ask done":r(async(e,[a])=>`${await h(e,`ask:${a}`)}: done`,"ask done"),"ask unfold":r((e,[a])=>`task ${a} back on a card of its own`,"ask unfold"),"ask move":r((e,[a],t)=>`task ${a} moved to /${t.to}`,"ask move"),"wave create":r((e,[a])=>`wave ${a} created`,"wave create"),"wave add":r(async(e,[a,t])=>`${await h(e,t)} added to wave ${a}`,"wave add"),"wave remove":r(async(e,[a,t])=>`${await h(e,t)} taken out of wave ${a}`,"wave remove"),"wave set":r((e,[a])=>`wave ${a} updated`,"wave set"),"wave finish":r((e,[a])=>`wave ${a} finished`,"wave finish"),"review record":r((e,[a],t)=>`review of PR #${a} 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,a)=>`${R(a,"reviewer")} removed`,"reviewers remove"),"rules add":r((e,a,t)=>`${t.hub?"hub":"board"} rule added`,"rules add"),"rules edit":r((e,[a],t)=>`${t.hub?"hub":"board"} rule ${a} reworded`,"rules edit"),"rules remove":r((e,[a],t)=>`${t.hub?"hub":"board"} rule ${a} removed`,"rules remove"),"builder start":r(async(e,[a],t,s)=>`${await h(e,a)}: ${s.alreadyOpen?"already open: the Implement tap opened its record; launch the agent, whose own builder start takes it up":`${N(t)} started`}`,"builder start"),"builder progress":r(async(e,[a])=>`progress line on ${await h(e,a)}`,"builder progress"),"builder done":r(async(e,[a],t,s)=>`${await h(e,a)}: ${s?.said??`${N(t)} done${t["owner-step"]===void 0?"":`; owner step "${t["owner-step"].trim()}"`}`}`,"builder done"),"builder call":r(async(e,[a])=>`judgment call recorded on ${await h(e,a)}`,"builder call"),"builder ask":r(async(e,[a],t,{id:s})=>`${await h(e,a)}: asked the orchestrator, ${s}`,"builder ask"),"builder answer":r(async(e,[a,t])=>`${await h(e,a)}: ${t} answered`,"builder answer"),"builder defer":r(async(e,[a,t])=>`${await h(e,a)}: ${t} deferred to the owner`,"builder defer"),"builder verified":r(async(e,[a],t,s)=>`${await h(e,a)}: judgment calls verified${s?.said?`; ${s.said}`:""}`,"builder verified"),land:r(async(e,[a],t,s)=>e?s?.said?`${await h(e,a)}: ${s.said}`:t["owner-step"]===void 0?`${await h(e,a)}: landed and done`:`${await h(e,a)}: landed; waits on the owner step "${t["owner-step"].trim()}"`:null,"land"),merge:r(async(e,[a],t,s)=>`${await h(e,a)}: ${s?.said??"merged"}`,"merge"),"arch file":r(async(e,[a])=>`findings of ${await h(e,a)} filed`,"arch file")};async function fe(e,a={}){try{const{flags:t,rest:s}=oe(e),i=[3,2,1].map(u=>s.slice(0,u).join(" ")).find(u=>y(b,u)),o=y(b,i);if(!o)throw new d(s.length?`unknown command "${s.slice(0,2).join(" ")}"`:"no command given");const[l,c,p]=o,f=s.slice(i.split(" ").length),T=l.at(-1)?.startsWith("..."),B=l.at(-1)?.endsWith("?")?l.length-1:l.length;if(T?f.length<B:f.length!==l.length)throw new d(`${i} takes ${l.map(u=>`<${u.replace(/^\.\.\.|\?$/g,"")}>`).join(" ")||"no arguments"}`);const{site:I,...E}=t;if(I?.length>1)throw new d("--site is given twice");const A=I?.[0],k=de(i,c,E);ue(c,k);const P=A===void 0?null:await n.siteDir(A),v=await p(P,f,k,a),O=y(me,i),j=O&&await O(P,f,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(r(fe,"main"),process.argv[1]&&import.meta.url===M(g.realpathSync(process.argv[1])).href){const e=process.argv.slice(2);!e.length||e.includes("--help")||e[0]==="help"?console.log(re):process.exitCode=await fe(e)}export{re as USAGE,fe as main};