@lovek4d/kickflip 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTEXT.md +151 -115
- package/README.md +23 -14
- package/SKILL.md +37 -37
- package/bin/doctor.mjs +4 -4
- package/bin/install.mjs +3 -3
- package/bin/kickflip.mjs +34 -26
- package/bin/wait-events.mjs +2 -2
- package/board/arch-review.mjs +1 -1
- package/board/auto-review.mjs +2 -0
- package/board/bin/board.mjs +186 -69
- package/board/domain.mjs +8 -5
- package/board/finish.mjs +3 -0
- package/board/headless.mjs +3 -0
- package/board/on-host.mjs +1 -1
- package/board/permissions.mjs +2 -0
- package/board/public/app-arch.js +2 -2
- package/board/public/app-asks.js +57 -38
- package/board/public/app-banner.js +2 -2
- package/board/public/app-cards.js +156 -72
- package/board/public/app-core.js +2 -4
- package/board/public/app-header.js +12 -5
- package/board/public/app-lanes.js +201 -47
- package/board/public/app-needs.js +2 -1
- package/board/public/app-snapshot.js +2 -2
- package/board/public/app-threads.js +11 -14
- package/board/public/app.html +2 -1
- package/board/public/app.js +53 -83
- package/board/public/attach.js +39 -3
- package/board/public/dialogs.js +59 -14
- package/board/public/hub-kit.js +9 -6
- package/board/public/hub.css +5 -18
- package/board/public/linear-key.js +6 -4
- package/board/public/log.html +48 -0
- package/board/public/prompts.js +9 -8
- package/board/public/prototype-kit.js +6 -6
- package/board/public/prototype-switcher.js +13 -5
- package/board/public/prototype.html +1 -0
- package/board/public/router.js +8 -7
- package/board/public/rules-editor.js +3 -4
- package/board/public/shell.js +20 -6
- package/board/public/sidebar.css +21 -1
- package/board/public/sidebar.js +64 -31
- package/board/public/style.css +155 -62
- package/board/public/toasts.js +2 -2
- package/board/public/ui-cards.js +176 -71
- package/board/public/ui-enums.js +5 -4
- package/board/public/ui-header.js +10 -10
- package/board/public/ui-hub.js +4 -4
- package/board/public/ui-markdown.js +1 -1
- package/board/public/ui-order.js +9 -6
- package/board/public/ui-page-header.js +10 -7
- package/board/public/ui-patch.js +53 -0
- package/board/public/ui-phase-bars.js +17 -0
- package/board/public/ui-prompts.js +8 -8
- package/board/public/ui-questions.js +26 -23
- package/board/public/ui-sidebar.js +46 -22
- package/board/public/ui-snapshot.js +20 -9
- package/board/public/ui-threads.js +17 -11
- package/board/public/ui.js +80 -12
- package/board/public/view-frame.js +1 -1
- package/board/public/view-hub-settings.js +25 -18
- package/board/public/view-hub.js +53 -12
- package/board/public/view-notes.js +1 -1
- package/board/public/view-questions.js +101 -50
- package/board/public/view-settings.js +167 -49
- package/board/review.mjs +1 -1
- package/board/server.mjs +18 -12
- package/board/store.mjs +13 -12
- package/board/tickets.mjs +3 -3
- package/board/worktree.mjs +20 -14
- package/docs/hub-session.md +4 -4
- package/hooks-plugin/hooks/codex-share-auto.mjs +1 -1
- package/hooks-plugin/hooks/watcher-stop.mjs +2 -2
- package/hub/board-repo.mjs +1 -1
- package/hub/boards.mjs +4 -4
- package/hub/close-board.mjs +1 -1
- package/hub/github.mjs +4 -4
- package/hub/home.mjs +1 -1
- package/hub/hub.mjs +12 -12
- package/hub/incoming.mjs +2 -0
- package/hub/linear.mjs +2 -2
- package/hub/live.mjs +2 -2
- package/hub/new-board.mjs +2 -2
- package/hub/permission-denied-hook.mjs +2 -0
- package/hub/pr-report.mjs +3 -1
- package/hub/presence.mjs +1 -1
- package/hub/prompts.mjs +1 -1
- package/hub/push.mjs +2 -2
- package/hub/readiness.mjs +2 -2
- package/hub/repos.mjs +3 -3
- package/hub/restart-board.mjs +1 -1
- package/hub/schedule.mjs +1 -1
- package/hub/sessions.mjs +2 -2
- package/hub/sw.js +1 -1
- package/hub/try.mjs +8 -4
- package/hub/usage.mjs +3 -3
- package/hub/views.mjs +1 -1
- package/package.json +5 -1
- package/shared/card-prs.mjs +1 -0
- package/shared/colors.css +5 -4
- package/shared/fake-gh.mjs +59 -8
- package/shared/repos.mjs +1 -1
- package/shared/usage-gate.mjs +1 -1
- package/board/public/view-polling.js +0 -87
package/board/bin/board.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
var
|
|
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>] ...
|
|
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.
|
|
@@ -12,14 +12,14 @@ Asks have no ticket; item set --from-ask creates one when the card has none.
|
|
|
12
12
|
A card moves its ticket one way: --status building or fixing \u2192 In Progress, --pr \u2192 In Review, --status done or
|
|
13
13
|
merged \u2192 Done (a GitHub issue closes), only for a ticket assigned to the owner or created by kickflip. A ticket that
|
|
14
14
|
can't move prints one "ticket <id>: \u2026" line; the card's change still stands.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
Linear needs Hub's Linear key: settings set tickets refuses it without one, and once the key is removed the hub
|
|
16
|
+
moves every Linear board to internal tickets. A card is never left without a ticket: if Linear can't make one, the
|
|
17
|
+
command fails and changes nothing.
|
|
18
18
|
|
|
19
19
|
reply <eventId> (--html <text> | --file <path>) data/replies/<eventId>.html. A reply to an ask resolves its
|
|
20
20
|
answered questions (topic item:ask:<eventId>), and one to
|
|
21
21
|
wave-triage its wave's, noting "Acted on: replied". A
|
|
22
|
-
reply to
|
|
22
|
+
reply to overnight-triage ends that triage
|
|
23
23
|
thread <wave>/<id> --text <t> [--link <url>] one line in the item's thread; ask:<eventId> in place
|
|
24
24
|
[--who claude|love|<name>] [--via github|slack] of <wave>/<id> writes in that ask's thread
|
|
25
25
|
A Claude thread line, or a reply to an ask or item-message, rewrites the body of the thread's
|
|
@@ -28,9 +28,9 @@ creating the issue and running item set <wave>/<id> --ticket <id>.
|
|
|
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?}; topic is declared, item:<wave>/<id>, item:ask:<eventId>
|
|
31
|
+
source?, context?, url?, urgent?, round_of?, ask?}; 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
|
-
a board over ~/.claude/waves. A
|
|
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
36
|
Waiting on you shows fitting takes in a grid, or a chooser above one full-width take for a single take or if any
|
|
@@ -45,6 +45,8 @@ creating the issue and running item set <wave>/<id> --ticket <id>.
|
|
|
45
45
|
and printed as "replaced: <old id>". round_of names the question on the same topic whose
|
|
46
46
|
prototype-request this answers; the question needs a prototype url, takes the next round
|
|
47
47
|
number, and resolves that one, noting "Followed by prototype round N".
|
|
48
|
+
ask names a builder ask the session deferred on the item (builder defer): the question puts it to the
|
|
49
|
+
owner, and while a card's only open questions are such, it waits on them with no Action needed.
|
|
48
50
|
The board server pushes the new open questions itself, under the card's tag (item-<wave>/<id>, or
|
|
49
51
|
ask-<eventId>) or topic-<topic> off the cards
|
|
50
52
|
question resolve <id> --note <text> sets resolved and resolved_at; refuses a question on a
|
|
@@ -64,12 +66,23 @@ creating the issue and running item set <wave>/<id> --ticket <id>.
|
|
|
64
66
|
prototype list one line per recorded prototype
|
|
65
67
|
prototype rm <topic> drops the record, keeping its take files; refuses while an
|
|
66
68
|
open question links it
|
|
67
|
-
pr <wave>/<id> one JSON line
|
|
68
|
-
|
|
69
|
+
pr <wave>/<id> one JSON line per PR on the card, in its order: the PR's
|
|
70
|
+
repo, state, checks and new events, using the board's
|
|
71
|
+
github-login and the card's gh_since
|
|
72
|
+
pr comment <pr> --body <text> posts Claude's words on PR <pr> through gh, a number in the
|
|
73
|
+
board's repo or owner/repo#<n> for a card's PR in another,
|
|
74
|
+
pr reply <pr> <comment id> --body <text> or replies in that review comment's thread, as the board's
|
|
75
|
+
github-login (else gh's account), adding "\u{1F916} " before and a
|
|
76
|
+
blank line and <!-- claude-bridge --> after, the marker pr
|
|
77
|
+
reads as Claude's own. Refuses words that already carry
|
|
78
|
+
either. Prints the post's url
|
|
69
79
|
item set <wave>/<id> [--status <s> | --clear-status] edits the item in its wave file
|
|
70
|
-
[--next <t> | --clear-next]
|
|
80
|
+
[--next <t> | --clear-next] status: ${K.join(", ")}
|
|
71
81
|
an empty --next "" clears it, as --clear-next does
|
|
72
82
|
naming another unfinished card requires it in --deps
|
|
83
|
+
[--pr <n> | --pr <owner/repo#n>]... adds a PR to the card, a bare number in the board's repo;
|
|
84
|
+
repeatable, and never replaces one. Each PR gets its own
|
|
85
|
+
badge and polling; the least-ready decides the card's lane
|
|
73
86
|
[--pushed <branch>] on a board whose repo is a plain git remote (no GitHub):
|
|
74
87
|
the card's branch is pushed there, so it waits In Review
|
|
75
88
|
on the owner's merge. Push it first (git push origin
|
|
@@ -81,7 +94,8 @@ creating the issue and running item set <wave>/<id> --ticket <id>.
|
|
|
81
94
|
[--from-ask <eventId> --text <t>] the ask it came from: its New card gives way to this item,
|
|
82
95
|
whose thread opens with the ask and its thread, then --text
|
|
83
96
|
as the item's first line; it leaves a card it was folded into; refuses a card
|
|
84
|
-
from another ask (use item relate-ask)
|
|
97
|
+
from another ask (use item relate-ask), and one with no
|
|
98
|
+
state when --state is missing
|
|
85
99
|
[--state ready|grilling|prototyping] what a card from an ask does next; it reads New until set.
|
|
86
100
|
ready refuses while the card has an open question
|
|
87
101
|
[--held | --unheld | --pick-up-at <time>] holds or resumes the card on the owner's words; the board
|
|
@@ -124,7 +138,8 @@ creating the issue and running item set <wave>/<id> --ticket <id>.
|
|
|
124
138
|
questions, noting "Acted on: build started".
|
|
125
139
|
--status building refuses while the item is held or a dep isn't done, merged or dropped,
|
|
126
140
|
and while the newest owner answer to its questions that link a prototype-*.html page picks
|
|
127
|
-
no take
|
|
141
|
+
no take, which leaves the card prototyping with Next "Prototype again" until the next
|
|
142
|
+
round's pick; a pick's note is the build's guidance;
|
|
128
143
|
done, merged and dropped print "unblocked: <wave>/<id>" and append card-unblocked {key}
|
|
129
144
|
once per unblocking, including held cards: start each unheld card next.
|
|
130
145
|
building and fixing count as working only while a builder record on the card is open.
|
|
@@ -135,7 +150,9 @@ creating the issue and running item set <wave>/<id> --ticket <id>.
|
|
|
135
150
|
Every --status or --clear-status records status_at; a later status change overrides the tap.
|
|
136
151
|
--status done, merged or dropped closes the card: it resolves each of its questions (topic
|
|
137
152
|
item:<wave>/<id>) that has an answer, noting "Closed with the card", and prints "resolved: <id>"
|
|
138
|
-
for each. It refuses while one has no answer yet; [--force] closes it and resolves that one too
|
|
153
|
+
for each. It refuses while one has no answer yet; [--force] closes it and resolves that one too.
|
|
154
|
+
--status done refuses a done card the owner messaged since it closed: it reads reopened, and closes
|
|
155
|
+
again only on the owner's Done
|
|
139
156
|
item add <wave>[/<id>] --title <t> [--notes <t>] a new card, planned (waiting when it has --deps); never
|
|
140
157
|
[--ticket <id>] [--deps <id>,<wave>/<id>] hand-edit wave.json to add one. Refuses an id the wave has.
|
|
141
158
|
[--notes-file <file>] --plan is an alias of --notes-file
|
|
@@ -165,29 +182,36 @@ creating the issue and running item set <wave>/<id> --ticket <id>.
|
|
|
165
182
|
Refuses a route that isn't another board, and an ask
|
|
166
183
|
that isn't New
|
|
167
184
|
checked [--every <minutes> | --idle] data/checked.json: GitHub was just checked, next in
|
|
168
|
-
<minutes> (default the
|
|
169
|
-
|
|
185
|
+
<minutes> (default 5, the away cadence); run it at the
|
|
186
|
+
end of each GitHub tick.
|
|
170
187
|
--idle: the board shows no open PR, so never late; with
|
|
171
188
|
neither flag it is idle while no card has an open PR.
|
|
172
189
|
Then sends the queued pushes, each on its own, unless
|
|
173
190
|
pushes are held (overnight, snoozed, the weekend or off)
|
|
174
191
|
lineup create <slug> --title <t> --goal <t> a wave, in the owner's words: a named set of cards in
|
|
175
|
-
--
|
|
176
|
-
|
|
177
|
-
the
|
|
178
|
-
|
|
179
|
-
|
|
192
|
+
--phase <wave>/<id>,\u2026 [--phase \u2026] phases, all from one wave file, one --phase per phase
|
|
193
|
+
(--note <t> | --file <f>) in build order. Each card is then blocked by every card
|
|
194
|
+
of the phase before (its other deps stay), so one
|
|
195
|
+
phase's cards build side by side, and the note goes to
|
|
196
|
+
lineup-<slug>.md beside the wave file.
|
|
197
|
+
Todo shows it as a band of its cards in order, numbered
|
|
198
|
+
by phase, its goal on its name, and Notes opens the
|
|
180
199
|
goal, the order and the note. Refuses a card already in
|
|
181
|
-
another open wave. Prints the order by card label
|
|
182
|
-
|
|
183
|
-
|
|
200
|
+
another open wave. Prints the order by card label, one
|
|
201
|
+
phase's cards by commas and the phases by arrows
|
|
202
|
+
lineup add <slug> <wave>/<id> [--after <wave>/<id>] puts a card in the wave as a phase of its own, last
|
|
203
|
+
unless --after's phase, and links the phases again
|
|
184
204
|
lineup remove <slug> <wave>/<id> takes a card out: it loses its links to the wave's cards,
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
205
|
+
its phase goes if it held only that card, and the phases
|
|
206
|
+
are linked again
|
|
207
|
+
lineup set <slug> [--title <t>] [--goal <t>] renames the wave, sets its goal, rewrites its note or
|
|
208
|
+
[--note <t> | --file <f>] puts its cards in new phases (--phase per phase, every
|
|
209
|
+
[--phase <wave>/<id>,\u2026 \u2026] card once) and links them again. A card's \u22EF in its
|
|
210
|
+
band swaps it with the Todo card before or after it,
|
|
211
|
+
each taking the other's phase
|
|
212
|
+
lineup finish <slug> the wave's band leaves Todo; its cards keep their
|
|
189
213
|
links. A wave whose cards are all Done leaves it too
|
|
190
|
-
|
|
214
|
+
A band's Triage sends wave-triage {lineup}, or {wave} for a wave file's band; it reads
|
|
191
215
|
Triaging\u2026 until you reply to that event, then "Triage: N open questions" while questions on topic
|
|
192
216
|
lineup:<slug> or wave:<slug> are open or answered
|
|
193
217
|
tickets backfill [--dry-run] gives every open card without a ticket one: the first
|
|
@@ -217,6 +241,11 @@ creating the issue and running item set <wave>/<id> --ticket <id>.
|
|
|
217
241
|
no link recorded; reactions: git_merged on each recorded link
|
|
218
242
|
of a merged card. record is the command to run once sent.
|
|
219
243
|
While Slack isn't ready: {waiting} and nothing owed
|
|
244
|
+
review record <pr> --state posted|needs the session's review of an incoming PR, after a pr-review
|
|
245
|
+
--text <markdown> | --file <path> event, in data/reviews.json: posted once it is on GitHub
|
|
246
|
+
[--url <review link>] (--url its link), needs while it waits on the owner, which
|
|
247
|
+
puts it in Needs you and pushes. The PR must be one the hub
|
|
248
|
+
listed in data/incoming.json or reviewed before
|
|
220
249
|
review digest pending reviews as JSON; due weekday mornings from 09:00
|
|
221
250
|
review digest sent <reviewer> --ts <ts> records a digest in data/review-digests.json;
|
|
222
251
|
--channel <dm> --items <key,key> marks today sent in the Slack identity timezone
|
|
@@ -230,11 +259,18 @@ creating the issue and running item set <wave>/<id> --ticket <id>.
|
|
|
230
259
|
settings the board's settings as JSON; flags default off, optional
|
|
231
260
|
settings appear when set: {"auto_merge": false,
|
|
232
261
|
"auto_build": false, "auto_pull": false,
|
|
233
|
-
"
|
|
262
|
+
"max_implementers": 10,
|
|
263
|
+
"auto_triage": false, "auto_review": false,
|
|
264
|
+
"headless_builders": false,
|
|
265
|
+
"idle_notification": false, "skip_draft": false,
|
|
266
|
+
"github_login": "owner"}
|
|
234
267
|
settings set auto-merge on|off on: the session merges an approved PR once CI is green and
|
|
235
268
|
the merge state is CLEAN. off (the default): the card
|
|
236
269
|
shows the owner a Merge button, and the session merges on its
|
|
237
270
|
card-merge event. Board Settings' toggle writes it too
|
|
271
|
+
settings set skip-draft on|off on: the hub's poll marks a card's draft PR ready for
|
|
272
|
+
review once its CI passes. off (the default): the draft
|
|
273
|
+
waits in In Review on the owner's review and Ready
|
|
238
274
|
build <wave>/<id> --site <route> asks the hub to run this card's .kickflip/build; prints
|
|
239
275
|
--asked "<owner's words>" started and exits. install is an alias. Only the owner's
|
|
240
276
|
Serve / Build and Auto-build start a card run, so --asked
|
|
@@ -250,16 +286,39 @@ creating the issue and running item set <wave>/<id> --ticket <id>.
|
|
|
250
286
|
the hub asks gh api user. Pass "" to clear it
|
|
251
287
|
settings set auto-build on|off on: each push builds its card. Off by default.
|
|
252
288
|
Main builds after every merge either way
|
|
253
|
-
settings set auto-pull on|off
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
289
|
+
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.
|
|
258
295
|
Only on the owner's word
|
|
259
|
-
settings set
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
296
|
+
settings set max-implementers <1..99> Maximum parallel implementers, 10 by default: the cards
|
|
297
|
+
building or waiting in Orchestrator review the pull fills
|
|
298
|
+
up to. Planners and cards waiting on the owner don't count.
|
|
299
|
+
Only on the owner's word
|
|
300
|
+
settings set auto-review on|off on: while the board is idle and nothing is pulled, the board
|
|
301
|
+
server sends pr-review {pr, head, repo, title, author, url,
|
|
302
|
+
touches, asked} for one incoming PR at a time that touches
|
|
303
|
+
this board's cards or asks the owner for review. Off by
|
|
304
|
+
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
|
+
settings set auto-triage on|off on: once a night, from the middle of the hub's quiet hours
|
|
317
|
+
to their end, when two or more cards wait on open
|
|
318
|
+
questions, nothing builds and something changed since the
|
|
319
|
+
last triage, the board server declares topic
|
|
320
|
+
overnight-triage-<date> ("Overnight triage <date>") and
|
|
321
|
+
sends overnight-triage {date, topic, cards}; the pull
|
|
263
322
|
waits until you reply to it. Off by default. Only on the
|
|
264
323
|
owner's word
|
|
265
324
|
settings set tickets <json> the board's ticket source, as Board Settings' Tickets row
|
|
@@ -286,8 +345,13 @@ creating the issue and running item set <wave>/<id> --ticket <id>.
|
|
|
286
345
|
opened, it keeps that record's kind and worktree.
|
|
287
346
|
[--dispatch] --dispatch: the session runs this right before it launches
|
|
288
347
|
an agent of any kind (build, prototype, grill) so the record
|
|
289
|
-
is open at launch and the card reads
|
|
290
|
-
own builder start takes it up.
|
|
348
|
+
is open at launch and the card reads preheating; the agent's
|
|
349
|
+
own builder start takes it up. On a record the Build tap
|
|
350
|
+
opened it changes nothing and prints "already open". The
|
|
351
|
+
record keeps dispatched: tap or session, whoever opened it.
|
|
352
|
+
--agent <other name> refuses while the tap's main record
|
|
353
|
+
is open and not taken up.
|
|
354
|
+
A build's start clears the card's Next.
|
|
291
355
|
A build (--kind build, claude or codex) on an open card sets
|
|
292
356
|
--status building first, refusing what that refuses;
|
|
293
357
|
Prints "ref: <ref>", the card's label, which commit subjects start with.
|
|
@@ -297,7 +361,11 @@ creating the issue and running item set <wave>/<id> --ticket <id>.
|
|
|
297
361
|
~/.claude/kickflip/worktrees/<id> on a new branch <id> from
|
|
298
362
|
the board's branch of its clone (--repo overrides the clone), kept on
|
|
299
363
|
the record and printed as "worktree: <path>". Refuses when
|
|
300
|
-
that path or branch exists
|
|
364
|
+
that path or branch exists, unless it is a leftover
|
|
365
|
+
holding no work, which it remakes. A build's main record
|
|
366
|
+
on a board makes it without --worktree when it keeps none
|
|
367
|
+
(a taken-up or resumed record's, or the worktree the
|
|
368
|
+
card's last record left while it is still there)
|
|
301
369
|
builder progress <wave>/<id> --phase <phase> one status line: a phase, reading, building, testing,
|
|
302
370
|
(<detail> | --text <d>) [--agent <name>] checking or landing, and a present-tense detail of at most
|
|
303
371
|
80 characters in plain words, which the owner reads at a
|
|
@@ -307,13 +375,32 @@ creating the issue and running item set <wave>/<id> --ticket <id>.
|
|
|
307
375
|
for another agent, and the card "Testing \xB7 <detail>"
|
|
308
376
|
[--eta <ISO time> | --clear-eta] an ETA shows as a line under the card's status
|
|
309
377
|
A progress line, --progress included, needs --site: it is a status line in the item's thread, and
|
|
310
|
-
the card's status shows the newest one from an agent still working
|
|
378
|
+
the card's status shows the newest one from an agent still working. builder progress refuses
|
|
379
|
+
without an open record for that agent, as builder call does
|
|
311
380
|
builder done <wave>/<id> [--agent <name>] marks that agent's record done; the item's thread
|
|
312
|
-
[--next <t>]
|
|
313
|
-
when the card has no open builder records left.
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
381
|
+
[--next <t>] [--text <what you checked>] shows when it started and finished; clears building/fixing
|
|
382
|
+
when the card has no open builder records left. On a
|
|
383
|
+
record with unverified judgment calls it hands the card
|
|
384
|
+
back: --status checking with Next "Answer the builder's
|
|
385
|
+
questions" when a call starts "Question:", else "Verify
|
|
386
|
+
the builder's judgment calls"; --next is refused there.
|
|
387
|
+
Else, on a board, the main build's record that keeps a
|
|
388
|
+
worktree finishes the card, as land does, --text its
|
|
389
|
+
closing line: lands it, closes the card and its ticket
|
|
390
|
+
and, with the board's "Push main after a land" on, pushes
|
|
391
|
+
the branch to origin; on a GitHub board it pushes the
|
|
392
|
+
card's branch, opens a draft PR and moves the card to it,
|
|
393
|
+
and on a plain git remote pushes the branch for the
|
|
394
|
+
owner's merge (item set --pushed). A finish that fails
|
|
395
|
+
refuses with the reason and leaves the record open; one
|
|
396
|
+
refused while another agent's record is open names it.
|
|
397
|
+
--next stops a build without finishing it. Closing the
|
|
398
|
+
last record of a card left In Progress with no Next is
|
|
399
|
+
refused without --next, which sets it
|
|
400
|
+
[--owner-step <t>] a step only the owner can do once the code lands: the card
|
|
401
|
+
then waits in In Review with it as its Next, its ticket
|
|
402
|
+
open, until the owner's Done; refused on a card already
|
|
403
|
+
done, merged or dropped. land takes it too
|
|
317
404
|
builder takes --dir <dir> for a wave file in <dir>; its records go in <dir>/agents
|
|
318
405
|
builder pick <wave>/<id> who builds the card, from the hub's Codex share and the pause
|
|
319
406
|
line, and for claude which model, from the hub's Claude model
|
|
@@ -327,29 +414,58 @@ creating the issue and running item set <wave>/<id> --ticket <id>.
|
|
|
327
414
|
its calls and worktree. The thread shows them, collapsed
|
|
328
415
|
builder calls <wave>/<id> [--agent <name>] prints the record's calls, numbered, then "verified <time>"
|
|
329
416
|
or "not verified yet"
|
|
417
|
+
builder ask <wave>/<id> <question> [--agent <name>] a running builder's one-line question for the board's session,
|
|
418
|
+
which it asks at any point and carries on meanwhile: a thread
|
|
419
|
+
line named by its model ("Claude Opus builder"), never unread,
|
|
420
|
+
and a builder-ask {key, ask, agent, text} event. Prints its
|
|
421
|
+
id, ask<n>. Needs --site and an open builder record
|
|
422
|
+
builder answer <wave>/<id> <ask> --text <t> the session's answer to that ask, a thread line named
|
|
423
|
+
Orchestrator that leaves the card quiet
|
|
424
|
+
builder defer <wave>/<id> <ask> --text <t> the session's deferral of an ask it can't answer confidently:
|
|
425
|
+
the same thread line, and a "Question: <ask> (deferred to the
|
|
426
|
+
owner)" call on the asking agent's record. After its review the
|
|
427
|
+
session puts it to the owner as a card question with
|
|
428
|
+
"ask": "<ask>"; builder verified refuses until then
|
|
429
|
+
builder asks <wave>/<id> prints each ask, then its answer, its deferral or "waiting on
|
|
430
|
+
the orchestrator"
|
|
330
431
|
builder verified <wave>/<id> [--agent <name>] the session's mark that it checked the record's calls; land
|
|
331
|
-
[--note <t>] refuses while any record on the card has a call after it
|
|
432
|
+
[--note <t>] refuses while any record on the card has a call after it.
|
|
433
|
+
On a handed-back build (checking, no builder open, every
|
|
434
|
+
call verified) it finishes the card in the same step, as
|
|
435
|
+
builder done does, --note its closing line (else "Judgment
|
|
436
|
+
calls verified."). While a question on the card is open it
|
|
437
|
+
says so and the card waits; a finish that fails keeps the
|
|
438
|
+
mark, the card checking, and says why
|
|
332
439
|
land <wave>/<id> --text <t> [--repo <dir>] brings the worktree its builder record keeps (else
|
|
333
440
|
[--dir <dir>]
|
|
334
|
-
~/.claude/kickflip/worktrees/<id> of --repo) into
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
441
|
+
~/.claude/kickflip/worktrees/<id> of --repo) into the board's
|
|
442
|
+
branch (the one its builders branch from, else main), one
|
|
443
|
+
line per step: refuses unless the repo has that branch
|
|
444
|
+
checked out, naming it, or while the worktree has uncommitted
|
|
445
|
+
changes or adds no commit; rebases onto the branch when it
|
|
446
|
+
moved, refusing on a conflict with the rebase undone; refuses
|
|
447
|
+
a new ADR named with a number instead of a timestamp; refuses
|
|
448
|
+
unless its full node --test suite passes, naming each failing
|
|
449
|
+
test and saving the TAP output under ~/.claude/kickflip/logs;
|
|
450
|
+
fast-forwards the branch in the repo;
|
|
341
451
|
restarts the hub when its code changed, else each board
|
|
342
452
|
server when board/ changed; removes the worktree and its
|
|
343
|
-
branch.
|
|
453
|
+
branch. Without --site it never pushes. One land runs at a time on the host: a
|
|
344
454
|
second refuses, naming the card landing. With --site, --text <what was checked>
|
|
345
455
|
is required, and once it lands the card closes as item set
|
|
346
456
|
--status done --text does; it refuses first what that close
|
|
347
|
-
would.
|
|
457
|
+
would. With --site it is builder done's finish: the push
|
|
458
|
+
after a land, and on a GitHub or plain-remote board the
|
|
459
|
+
branch push (and draft PR) instead of the fast-forward.
|
|
460
|
+
--owner-step <t>, as builder done takes it, keeps the
|
|
461
|
+
landed card in In Review on that step until the owner's
|
|
462
|
+
Done. KICKFLIP_RESTART_CMD='["<cmd>"]' replaces
|
|
348
463
|
the restarts, called with hub or board <route>
|
|
349
|
-
merge <wave>/<id> [--after-approval "<why trivial>"] merges the card's
|
|
350
|
-
[--confirmed "<owner's words>"] once GitHub reads
|
|
351
|
-
pending and its merge state CLEAN,
|
|
352
|
-
uses
|
|
464
|
+
merge <wave>/<id> [--after-approval "<why trivial>"] merges the card's PRs (gh pr merge --squash) in its order and
|
|
465
|
+
[--confirmed "<owner's words>"] records it merged, once GitHub reads every open one approved,
|
|
466
|
+
its checks green with none pending and its merge state CLEAN,
|
|
467
|
+
the test the Merge button uses; a merged or closed PR is left
|
|
468
|
+
as it is. Refuses, naming each reason ("pending: 2 checks"),
|
|
353
469
|
commits after the newest approval unless --after-approval
|
|
354
470
|
says why they're trivial (its closing line says so), and a
|
|
355
471
|
card with manual steps unless --confirmed quotes the owner.
|
|
@@ -362,18 +478,19 @@ creating the issue and running item set <wave>/<id> --ticket <id>.
|
|
|
362
478
|
Prints "filed: <ref> from <id>", "skipped: <id>", and
|
|
363
479
|
"waiting: <ids>" while any are unanswered; once none is, it
|
|
364
480
|
closes the audit card as done
|
|
365
|
-
push --kind
|
|
481
|
+
push --kind fault|reminder --title <t> runs kickflip push with these flags, adding
|
|
366
482
|
[--body <b>] [--url </route/path>] "\xB7 and N replies" to the body for the replies
|
|
367
483
|
[--tag <tag>] [--now | --urgent] written since the site's last push through this
|
|
368
|
-
command (none before its first).
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
484
|
+
command (none before its first). A session pushes only a
|
|
485
|
+
fault, what only the owner can fix, or a reminder the
|
|
486
|
+
owner asked for in words ("push me when it's all done"):
|
|
487
|
+
the board server pushes each card's Action needed and the
|
|
488
|
+
hub a session's prompt, so any other --kind is refused. It records the
|
|
489
|
+
push in data/push-state.json only when kickflip push
|
|
490
|
+
succeeds, and exits with kickflip push's code. Without --now
|
|
374
491
|
it queues until the tick ends (checked) or the hub's
|
|
375
492
|
next check; --now sends at once unless pushes are held
|
|
376
493
|
(overnight, snoozed or the weekend). --urgent: a
|
|
377
494
|
production fault only; it beats those holds. While
|
|
378
495
|
notifications are off every push is dropped, urgent too
|
|
379
|
-
KICKFLIP_PUSH_CMD='["<cmd>","<arg>"]' replaces \`node ../../bin/kickflip.mjs push\`; the flags follow it`;class o extends Error{static{n(this,"UsageError")}}const V=new Set(["clear-status","clear-next","urgent","now","clear-eta","held","unheld","idle","hub","clear-install","force","worktree","dry-run","dispatch"]),X=new Set(["take"]);function Z(e){const t=Object.create(null),s=[];for(let a=0;a<e.length;a++){const r=e[a];if(!r.startsWith("--")){s.push(r);continue}const d=r.slice(2);if(X.has(d)){if(a+1>=e.length)throw new o(`--${d} needs a value`);(t[d]||=[]).push(e[++a]);continue}if(Object.hasOwn(t,d))throw new o(`--${d} is given twice`);if(V.has(d))t[d]=!0;else if(a+1<e.length)t[d]=e[++a];else throw new o(`--${d} needs a value`)}return{flags:t,rest:s}}n(Z,"parse");const Y=["html","text","note","notes","summary","title","body","next","progress","install-label","confirmed","asked"],ee=/^(?:\/|~\/|\.{1,2}\/)\S*\.(?:html?|md|json|txt)$/;function te(e){const t=e.trim();if(!t||/\s/.test(t))return!1;if(ee.test(t))return!0;try{return w.statSync(t).isFile()}catch{return!1}}n(te,"looksLikePath");function se(e,t){for(const s of Y){if(typeof t[s]!="string"||!te(t[s]))continue;const a=e.includes("file")?`; for a file use --file ${t[s].trim()}`:"";throw new o(`--${s} takes text, not a file path (${t[s].trim()})${a}`)}if(typeof t.file=="string"&&t.file!=="-"&&!w.existsSync(t.file))throw new o(`--file ${t.file}: no such file`)}n(se,"checkInputs");const b=n(e=>w.readFileSync(e==="-"?0:e,"utf8"),"readInput");function v(e){if(!e)throw new o("--file <path> is required");try{return JSON.parse(b(e))}catch(t){throw new i.BoardError(`${e}: ${t.message}`)}}n(v,"readJSONInput");function y(e){if(e.plan!==void 0&&e["notes-file"]!==void 0)throw new o("pass --notes-file or --plan, not both");return e["notes-file"]??e.plan}n(y,"notesFileOf");function O(e){if(e.note!==void 0&&e.file!==void 0)throw new o("pass --note or --file, not both");return e.file===void 0?e.note:b(e.file)}n(O,"noteOf");const g={pr:[["wave/id"],[],async(e,[t])=>({lines:[JSON.stringify(await i.prReport(e,t))]})],reply:[["eventId"],["html","file"],(e,[t],s)=>{if(s.html===void 0==(s.file===void 0))throw new o("reply takes --html or --file");return i.writeReply(e,t,s.html??b(s.file))}],thread:[["wave/id"],["text","link","who","via"],(e,[t],s)=>i.appendThread(e,t,s)],"topic add":[["id"],["title","summary"],(e,[t],s)=>i.addTopic(e,{id:t,...s})],"question add":[[],["file"],async(e,t,s)=>{const a=v(s.file);return{...await i.addQuestions(e,a),asked:[a].flat()}}],"question resolve":[["id"],["note"],(e,[t],s)=>i.resolveQuestion(e,t,s.note)],"prototype add":[["topic"],["title","take","card"],(e,[t],s)=>i.addPrototype(e,{topic:t,title:s.title,card:s.card,takes:s.take})],"prototype list":[[],[],e=>i.listPrototypes(e)],"prototype rm":[["topic"],[],(e,[t])=>i.removePrototype(e,t)],"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"],(e,[t],s,a)=>i.setItem(e,t,{status:s.status,clearStatus:s["clear-status"],next:s.next,clearNext:s["clear-next"],pr:s.pr,pushed:s.pushed,ghSince:s["gh-since"],fromAsk:s["from-ask"],state:s.state,held:s.held,unheld:s.unheld,pickUpAt:s["pick-up-at"],notesFile:y(s),deps:s.deps,parent:s.parent,related:s.related,install:s.install,installSha:s["install-sha"],installLabel:s["install-label"],clearInstall:s["clear-install"],title:s.title,size:s.size,model:s.model,force:s.force,ticket:s.ticket,text:s.text},a)],"item add":[["wave[/id]"],["title","notes","deps","notes-file","plan","ticket","pick-up-at","parent","size","model"],(e,[t],s,a)=>i.addItem(e,t,{title:s.title,notes:s.notes,deps:s.deps,notesFile:y(s),ticket:s.ticket,pickUpAt:s["pick-up-at"],parent:s.parent,size:s.size,model:s.model},a)],"item relate-ask":[["eventId","wave/id"],["summary"],(e,[t,s],a)=>i.relateAsk(e,t,s,a.summary)],"ask progress":[["eventId"],["next"],(e,[t],s)=>i.markAsk(e,t,"progress",s.next)],"ask done":[["eventId"],[],(e,[t])=>i.markAsk(e,t,"done")],"ask unfold":[["eventId"],[],(e,[t])=>i.unfoldAsk(e,t)],"ask move":[["eventId"],["to"],(e,[t],s)=>i.moveAsk(e,t,s.to)],"tickets backfill":[[],["dry-run"],(e,t,s,a)=>i.backfillTickets(e,{dryRun:s["dry-run"]},a)],"lineup create":[["slug"],["title","goal","cards","note","file"],(e,[t],s)=>i.createLineup(e,t,{title:s.title,goal:s.goal,cards:s.cards,note:O(s)})],"lineup add":[["slug","wave/id"],["after"],(e,[t,s],a)=>i.changeLineup(e,t,{add:s,after:a.after})],"lineup remove":[["slug","wave/id"],[],(e,[t,s])=>i.changeLineup(e,t,{remove:s})],"lineup set":[["slug"],["title","goal","note","file"],(e,[t],s)=>i.changeLineup(e,t,{title:s.title,goal:s.goal,note:O(s)})],"lineup finish":[["slug"],[],(e,[t])=>i.finishLineup(e,t)],slack:[[],[],()=>({lines:[JSON.stringify(L())]})],"slack set":[[],["self-dm","user","domain","timezone"],async(e,t,s)=>{const a={"self-dm":"self_dm",user:"owner_slack_user",domain:"slack_domain",timezone:"timezone"};try{return{lines:[JSON.stringify(await H(Object.fromEntries(Object.entries(s).map(([r,d])=>[a[r],d]))))]}}catch(r){throw new i.BoardError(r.message)}}],integrations:[[],[],async()=>({lines:[JSON.stringify(await N.rows())]})],"integrations report":[[],["slack"],async(e,t,s)=>{if(!["yes","no"].includes(s.slack))throw new o("integrations report takes --slack yes|no");if(!e)throw new i.BoardError("integrations report needs --site <route>");const a=m.basename(e);return E(s.slack==="yes",{route:a,title:U().sites?.[a]?.title||(await i.boardConfig(e)).title}),{lines:[`Slack connector reported: ${s.slack}.`]}}],"review sent":[["wave/id","reviewer"],["ts","channel"],(e,[t,s],a)=>i.reviewSent(e,t,s,a)],"review react":[["wave/id","reviewer","emoji"],[],(e,[t,s,a])=>i.reviewReact(e,t,s,a)],"review digest":[[],[],async(e,t,s,a)=>({lines:[JSON.stringify(await i.reviewDigest(e,a))]})],"review due":[[],[],async(e,t,s,a)=>({lines:[JSON.stringify(await i.reviewDue(e,a))]})],"review digest sent":[["reviewer"],["ts","channel","items"],(e,[t],s,a)=>i.reviewDigestSent(e,t,s,a)],"reviewers set":[[],["file"],(e,t,s)=>i.setReviewers(e,v(s.file))],"reviewers add":[[],["file"],(e,t,s)=>i.addReviewers(e,v(s.file))],"reviewers remove":[["...name"],[],(e,t)=>i.removeReviewers(e,t)],"rules list":[[],[],e=>i.listRules(e)],"rules add":[[],["text","hub","by"],(e,t,s)=>i.addRuleTo(e,s)],"rules edit":[["id"],["text","hub"],(e,[t],s)=>i.editRuleIn(e,t,s)],"rules remove":[["id"],["hub"],(e,[t],s)=>i.removeRuleFrom(e,t,s)],settings:[[],[],async e=>({lines:[JSON.stringify(await i.readBoardSettings(e))]})],build:[["wave/id"],["asked"],async(e,[t],s)=>{if(!e)throw new i.BoardError("build needs --site <route>");i.parseKey(t);const a=await i.askedGate(e,t,s.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:t}),signal:AbortSignal.timeout(1e4)}),d=await r.json();if(!r.ok)throw new i.BoardError(d.error||"The hub refused the build.");return{lines:["started",...(await i.appendThread(e,t,{text:a})).lines]}}catch(r){throw r instanceof i.BoardError?r:new i.BoardError(`Could not start the build: ${r.message}`)}}],"main set":[[],["install","install-sha"],(e,t,s)=>i.setMainInstall(e,{install:s.install,installSha:s["install-sha"]})],"settings set":[["name","value"],[],(e,[t,s])=>i.setBoardSetting(e,t,s)],"builder start":[["wave/id"],["label","phase","progress","dir","agent","kind","model","eta","worktree","repo","dispatch"],(e,[t],{worktree:s,repo:a,...r})=>{if(a!==void 0&&!s)throw new o("--repo goes with --worktree");return i.startBuilder(e,t,{...r,worktree:s&&(()=>D(e,t.split("/")[1],{repo:a}))})}],"builder progress":[["wave/id","...text?"],["phase","text","dir","agent","eta","clear-eta"],(e,[t,...s],a)=>{if(s.length&&a.text!==void 0)throw new o("builder progress takes its text once: as words or as --text");if(!s.length&&a.text===void 0)throw new o("builder progress needs its text, as words or as --text");return i.builderProgress(e,t,a.text??s.join(" "),{...a,clearEta:a["clear-eta"]})}],"builder done":[["wave/id"],["dir","agent","next"],(e,[t],s)=>i.stopBuilder(e,t,{...s,requireNext:!0})],"builder pick":[["wave/id"],[],(e,[t])=>i.pickBuilder(e,t)],"builder call":[["wave/id","...text"],["dir","agent"],(e,[t,...s],a)=>i.recordCall(e,t,s.join(" "),a)],"builder calls":[["wave/id"],["dir","agent"],(e,[t],s)=>i.listCalls(e,t,s)],"builder verified":[["wave/id"],["dir","agent","note"],(e,[t],s)=>i.verifyCalls(e,t,s)],land:[["wave/id"],["repo","dir","text"],async(e,[t],s)=>{const a=t.split("/")[1];if(e&&s.text===void 0)throw new o(`land needs --text "<what was checked>", which closes ${t} as done once it lands`);if(!e&&s.text!==void 0)throw new o("--text closes the card, which needs --site");return W(e?await i.cardName(e,t):t,async()=>{e&&await i.checkClose(e,t,s.text),await i.checkCallsVerified(e,t,{dir:s.dir});const r=await i.recordedWorktree(e,t,{dir:s.dir}),d=m.resolve(s.repo??r?.repo??M(e).repo);return z({path:r?.path??m.join(G(),a),branch:r?.branch??a,repo:d}),e?i.setItem(e,t,{status:"done",text:s.text}):{lines:[]}})}],merge:[["wave/id"],["after-approval","confirmed"],(e,[t],s)=>i.mergeCard(e,t,{afterApproval:s["after-approval"],confirmed:s.confirmed})],"arch file":[["wave/id"],[],(e,[t])=>e?J(e,t):Promise.reject(new i.BoardError("arch file needs --site <route>"))],checked:[[],["every","idle"],(e,t,s)=>i.writeChecked(e,s.every,{idle:s.idle})],push:[[],["kind","title","body","url","tag","urgent","now"],(e,t,s)=>{const a=K(s.kind);if(a)throw new o(a);if(s.title===void 0)throw new o("push needs --title");return i.sendPush(e,s)}]};g.install=g.build;const l=n((e,t)=>i.cardName(e,t),"card"),C=n((e,t)=>`${t}${e.length===1?"":"s"} ${e.join(", ")}`,"plural"),T=n(e=>e.agent===void 0||e.agent==="main"?"builder":`builder ${e.agent}`,"builderOf");function ae(e){const t=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",t!==void 0&&(t?`held until ${t}`:"resumed"),y(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(", ")}n(ae,"itemChanges");const ie={reply:n(async(e,[t])=>`reply written to ${await i.eventCard(e,t)}`,"reply"),thread:n(async(e,[t])=>`thread line on ${await l(e,t)}`,"thread"),"topic add":n((e,[t])=>`topic ${t} added`,"topic add"),"question add":n(async(e,t,s,{asked:a})=>{const r=[...new Set(a.map(c=>c.topic))],d=r.length!==1?"":r[0].startsWith("item:")?` on ${await l(e,r[0].slice(5))}`:` on topic ${r[0]}`;return`${C(a.map(c=>c.id),"question")} added${d}`},"question add"),"question resolve":n((e,[t])=>`question ${t} resolved`,"question resolve"),"prototype add":n(async(e,[t],s)=>`prototype ${t} recorded${s.card?` for ${await l(e,s.card)}`:""}`,"prototype add"),"prototype rm":n((e,[t])=>`prototype ${t} removed`,"prototype rm"),"item set":n(async(e,[t],s)=>`${await l(e,t)}: ${ae(s)}`,"item set"),"item add":n(async(e,t,s,{key:a})=>`${await l(e,a)} added as ${a}`,"item add"),"item relate-ask":n(async(e,[t,s])=>`task ${t} folded into ${await l(e,s)}`,"item relate-ask"),"ask progress":n(async(e,[t])=>`${await l(e,`ask:${t}`)}: in progress`,"ask progress"),"ask done":n(async(e,[t])=>`${await l(e,`ask:${t}`)}: done`,"ask done"),"ask unfold":n((e,[t])=>`task ${t} back on a card of its own`,"ask unfold"),"ask move":n((e,[t],s)=>`task ${t} moved to /${s.to}`,"ask move"),checked:n((e,t,s,{every:a})=>a===null?"GitHub checked; no open PR":`GitHub checked; next in ${a} min`,"checked"),"lineup create":n((e,[t])=>`lineup ${t} created`,"lineup create"),"lineup add":n(async(e,[t,s])=>`${await l(e,s)} added to lineup ${t}`,"lineup add"),"lineup remove":n(async(e,[t,s])=>`${await l(e,s)} taken out of lineup ${t}`,"lineup remove"),"lineup set":n((e,[t])=>`lineup ${t} updated`,"lineup set"),"lineup finish":n((e,[t])=>`lineup ${t} finished`,"lineup finish"),"review sent":n(async(e,[t,s])=>`review link to ${s} recorded on ${await l(e,t)}`,"review sent"),"review react":n(async(e,[t,s,a])=>`${a} on ${s}'s review link recorded on ${await l(e,t)}`,"review react"),"review digest sent":n((e,[t])=>`digest to ${t} recorded`,"review digest sent"),"reviewers set":n(()=>"reviewers replaced","reviewers set"),"reviewers add":n(()=>"reviewers added","reviewers add"),"reviewers remove":n((e,t)=>`${C(t,"reviewer")} removed`,"reviewers remove"),"rules add":n((e,t,s)=>`${s.hub?"hub":"board"} rule added`,"rules add"),"rules edit":n((e,[t],s)=>`${s.hub?"hub":"board"} rule ${t} reworded`,"rules edit"),"rules remove":n((e,[t],s)=>`${s.hub?"hub":"board"} rule ${t} removed`,"rules remove"),"builder start":n(async(e,[t],s)=>`${await l(e,t)}: ${T(s)} started`,"builder start"),"builder progress":n(async(e,[t])=>`progress line on ${await l(e,t)}`,"builder progress"),"builder done":n(async(e,[t],s)=>`${await l(e,t)}: ${T(s)} done`,"builder done"),"builder call":n(async(e,[t])=>`judgment call recorded on ${await l(e,t)}`,"builder call"),"builder verified":n(async(e,[t])=>`${await l(e,t)}: judgment calls verified`,"builder verified"),land:n(async(e,[t])=>e?`${await l(e,t)}: landed and done`:null,"land"),merge:n(async(e,[t])=>`${await l(e,t)}: merged`,"merge"),"arch file":n(async(e,[t])=>`findings of ${await l(e,t)} filed`,"arch file")};async function ne(e,t={}){try{const{flags:s,rest:a}=Z(e),r=[3,2,1].map(h=>a.slice(0,h).join(" ")).find(h=>k(g,h)),d=k(g,r);if(!d)throw new o(a.length?`unknown command "${a.slice(0,2).join(" ")}"`:"no command given");const[c,x,R]=d,u=a.slice(r.split(" ").length),_=c.at(-1)?.startsWith("..."),q=c.at(-1)?.endsWith("?")?c.length-1:c.length;if(_?u.length<q:u.length!==c.length)throw new o(`${r} takes ${c.map(h=>`<${h.replace(/^\.\.\.|\?$/g,"")}>`).join(" ")||"no arguments"}`);const{site:$,...p}=s,S=Object.keys(p).filter(h=>!x.includes(h));if(S.length)throw new o(`${r} does not take ${S.map(h=>"--"+h).join(", ")}`);se(x,p);const I=$===void 0?null:await i.siteDir($),f=await R(I,u,p,t),A=k(ie,r),j=A&&await A(I,u,p,f);j&&console.log(j);for(const h of typeof f=="object"&&f?.lines||[])console.log(h);return 0}catch(s){if(s instanceof o)return console.error(`${s.message} (node board.mjs --help lists the commands)`),2;if(s instanceof i.BoardError)return console.error(s.message),s.exitCode??1;throw s}}if(n(ne,"main"),process.argv[1]&&import.meta.url===B(w.realpathSync(process.argv[1])).href){const e=process.argv.slice(2);!e.length||e.includes("--help")||e[0]==="help"?console.log(Q):process.exitCode=await ne(e)}export{Q as USAGE,ne as main};
|
|
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};
|