@lovek4d/kickflip 0.4.0 → 0.5.3
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 +69 -48
- package/README.md +1 -1
- package/SKILL.md +22 -20
- package/bin/doctor.mjs +4 -4
- package/bin/install.mjs +3 -3
- package/bin/kickflip.mjs +27 -18
- package/bin/wait-events.mjs +2 -2
- package/board/agents.mjs +2 -0
- package/board/bin/board.mjs +108 -39
- package/board/domain.mjs +7 -7
- package/board/finish.mjs +4 -3
- package/board/headless.mjs +5 -3
- package/board/merge3.mjs +1 -0
- package/board/on-host.mjs +1 -1
- package/board/permissions.mjs +2 -2
- package/board/public/app-asks.js +9 -92
- package/board/public/app-banner.js +7 -13
- package/board/public/app-cards.js +70 -37
- package/board/public/app-core.js +24 -25
- package/board/public/app-header.js +2 -1
- package/board/public/app-lanes.js +55 -23
- package/board/public/app-needs.js +129 -4
- package/board/public/app-reader.js +174 -0
- package/board/public/app-snapshot.js +6 -5
- package/board/public/app-threads.js +83 -58
- package/board/public/app.html +2 -0
- package/board/public/app.js +66 -24
- package/board/public/attach.js +2 -2
- package/board/public/dialogs.js +9 -39
- package/board/public/hub-kit.js +4 -2
- package/board/public/hub.css +1 -0
- package/board/public/linear-key.js +46 -35
- package/board/public/new-task.js +100 -0
- package/board/public/prototype-kit.js +28 -3
- package/board/public/prototype-switcher.js +42 -107
- package/board/public/shell.js +7 -1
- package/board/public/sidebar.css +1 -0
- package/board/public/sidebar.js +44 -5
- package/board/public/style.css +194 -35
- package/board/public/ui-cards.js +80 -44
- package/board/public/ui-enums.js +1 -1
- package/board/public/ui-header.js +2 -2
- package/board/public/ui-order.js +12 -10
- package/board/public/ui-phase-bars.js +4 -4
- package/board/public/ui-questions.js +63 -47
- package/board/public/ui-sidebar.js +27 -15
- package/board/public/ui-threads.js +27 -14
- package/board/public/ui.js +76 -14
- package/board/public/view-frame.js +1 -0
- package/board/public/view-hub-settings.js +11 -10
- package/board/public/view-hub.js +17 -3
- package/board/public/view-questions.js +174 -202
- package/board/public/view-settings.js +208 -70
- package/board/server.mjs +19 -18
- package/board/store.mjs +20 -13
- package/board/transcript.mjs +2 -0
- package/board/worktree.mjs +24 -20
- package/hooks-plugin/hooks/board-data-gate.mjs +50 -0
- package/hooks-plugin/hooks/hooks.json +9 -0
- package/hub/boards.mjs +4 -4
- package/hub/front-door.mjs +20 -0
- package/hub/github.mjs +3 -3
- package/hub/hub.mjs +11 -14
- package/hub/linear.mjs +1 -2
- package/hub/live.mjs +2 -2
- package/hub/locks.mjs +1 -1
- package/hub/new-board.mjs +2 -2
- package/hub/owner-checks.mjs +1 -0
- package/hub/pr-report.mjs +2 -2
- package/hub/presence.mjs +1 -1
- package/hub/push.mjs +2 -2
- package/hub/readiness.mjs +1 -2
- package/hub/review-send.mjs +1 -0
- package/hub/secrets.mjs +2 -0
- package/hub/self-restart.mjs +2 -0
- package/hub/sessions.mjs +2 -2
- package/hub/share.mjs +1 -0
- package/hub/slack.mjs +2 -2
- package/hub/stream.mjs +10 -13
- package/hub/try.mjs +7 -7
- package/hub/upgrade.mjs +1 -1
- package/hub/usage.mjs +3 -3
- package/hub/views.mjs +1 -1
- package/package.json +2 -1
- package/shared/audit.mjs +4 -0
- package/shared/fake-gh.mjs +17 -4
- package/shared/owner-check.mjs +3 -0
- package/shared/service.mjs +4 -4
- package/shared/tailscale.mjs +1 -0
- package/shared/usage-gate.mjs +1 -1
package/board/bin/board.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
var
|
|
2
|
+
var H=Object.defineProperty;var r=(e,a)=>H(e,"name",{value:a,configurable:!0});import{readiness as L}from"../../hub/readiness.mjs";import{readSlack as D,setSlack as W}from"../../hub/slack.mjs";import{routeRecord as v}from"../../hub/routes.mjs";import g from"node:fs";import y from"node:path";import{pathToFileURL as M}from"node:url";import*as n from"../store.mjs";import{boardWorktree as O,builderRepo as z,joiningLand as G,landInTurn as J,worktreesDir as U}from"../worktree.mjs";import{fileFindings as F}from"../arch-review.mjs";import{finishCard as x,landHeld as K}from"../finish.mjs";import{ITEM_STATUSES as Q}from"../domain.mjs";import{boardAt as S}from"../../hub/boards.mjs";import"../../hub/board-repo.mjs";const V=`usage: node board.mjs <command> [--site <route>] ...
|
|
3
3
|
|
|
4
4
|
Writes a kickflip board's data files. --site <route> picks ~/.claude/kickflip/sites/<route>, and its
|
|
5
5
|
board.json says where the board's waves live. Without --site, waves come from ~/.claude/waves.
|
|
@@ -28,11 +28,16 @@ command fails and changes nothing.
|
|
|
28
28
|
topic add <id> --title <t> [--summary <s>] a question topic in data/owner.json
|
|
29
29
|
question add --file <json> one question or an array of them:
|
|
30
30
|
{id, topic, question, options: [{id, label, recommended?, detail?, diagram?, practice?}],
|
|
31
|
-
source?, context?, url?, urgent?, round_of?, ask?}; topic is declared, item:<wave>/<id>, item:ask:<eventId>
|
|
31
|
+
source?, context?, url?, urgent?, round_of?, reask_of?, ask?, suggested?, prototype_gate?}; topic is declared, item:<wave>/<id>, item:ask:<eventId>
|
|
32
32
|
for a New card not sorted yet, lineup:<slug> for a wave's triage, or wave:<slug> for a wave file's triage on
|
|
33
33
|
a board over ~/.claude/waves. A question on a done or merged card reopens it.
|
|
34
|
+
A question on an item: topic whose card has a grill or prototype planner running is staged, not published,
|
|
35
|
+
whoever adds it: it shows nowhere until the planner finishes, fails or is stopped, when every staged question
|
|
36
|
+
on the card publishes in the order added, with one owner push. The command says "staged" and why.
|
|
34
37
|
url is http(s), or a site page such as prototype-<topic>.html?q=<id>, stored as given.
|
|
35
38
|
Prototype options keep take order, ids matching take keys; recommended: true tags the recommendation.
|
|
39
|
+
suggested: {option, card, answer} pre-checks the option the owner already chose on an earlier card, with a
|
|
40
|
+
"suggested" chip and the line You answered this on <card>: "<answer>"; card is that card's ref, answer their words.
|
|
36
41
|
Waiting on you shows fitting takes in a grid, or a chooser above one full-width take for a single take or if any
|
|
37
42
|
scrolls or fills the screen.
|
|
38
43
|
diagram, with practice: rows of 1-4 boxes [[{text, tone?, to?}]], tone quiet|info|notice|
|
|
@@ -45,6 +50,11 @@ command fails and changes nothing.
|
|
|
45
50
|
and printed as "replaced: <old id>". round_of names the question on the same topic whose
|
|
46
51
|
prototype-request this answers; the question needs a prototype url, takes the next round
|
|
47
52
|
number, and resolves that one, noting "Followed by prototype round N".
|
|
53
|
+
prototype_gate: true marks a grill's closing "Prototype round first?" question (options yes, no, on a card's
|
|
54
|
+
topic): the owner's yes moves the card to prototyping and starts its prototype planner, a no moves it to ready
|
|
55
|
+
once no question on it is left open, the board doing both with no orchestrator step.
|
|
56
|
+
reask_of names a resolved question on the same topic that the owner asked to ask again
|
|
57
|
+
(reask-request): it resolves that one again, noting "Asked again as <new id>", printed as "resolved: <old id>".
|
|
48
58
|
ask names a builder ask the session deferred on the item (builder defer): the question puts it to the
|
|
49
59
|
owner, and while a card's only open questions are such, it waits on them with no Action needed.
|
|
50
60
|
The board server pushes the new open questions itself, under the card's tag (item-<wave>/<id>, or
|
|
@@ -53,6 +63,10 @@ command fails and changes nothing.
|
|
|
53
63
|
prototype page until an answer settles its round: a pick, with a note or none, or an answer to
|
|
54
64
|
an "Is this it?" confirm already on the board. A question already resolved, such as one an
|
|
55
65
|
answer with a note and no option resolved, is left as it is
|
|
66
|
+
question move <id>,<id>,\u2026 --to <wave>/<id> [--block <wave>/<id>,\u2026] merges open questions that ask one thing onto
|
|
67
|
+
one card: the first id moves to that card (topic item:<card>), the others resolve "Duplicate of
|
|
68
|
+
<card>'s question <first id>". Each --block card, in order, is blocked by the one before it (the
|
|
69
|
+
first by --to) and related to --to
|
|
56
70
|
prototype add <topic> --title <t> --take <K>:<label> records a prototype in data/prototypes.json: the board
|
|
57
71
|
[--take <K>:<label> \u2026] [--card <wave>/<id>] serves prototype-<topic>.html from its one template, with
|
|
58
72
|
the title, a back arrow to --card (else the board), the
|
|
@@ -77,7 +91,7 @@ command fails and changes nothing.
|
|
|
77
91
|
reads as Claude's own. Refuses words that already carry
|
|
78
92
|
either. Prints the post's url
|
|
79
93
|
item set <wave>/<id> [--status <s> | --clear-status] edits the item in its wave file
|
|
80
|
-
[--next <t> | --clear-next] status: ${
|
|
94
|
+
[--next <t> | --clear-next] status: ${Q.join(", ")}
|
|
81
95
|
an empty --next "" clears it, as --clear-next does
|
|
82
96
|
naming another unfinished card requires it in --deps
|
|
83
97
|
[--pr <n> | --pr <owner/repo#n>]... adds a PR to the card, a bare number in the board's repo;
|
|
@@ -97,7 +111,14 @@ command fails and changes nothing.
|
|
|
97
111
|
from another ask (use item relate-ask), and one with no
|
|
98
112
|
state when --state is missing
|
|
99
113
|
[--state ready|grilling|prototyping] what a card from an ask does next; it reads New until set.
|
|
100
|
-
ready refuses while the card has an open question
|
|
114
|
+
ready refuses while the card has an open question, and
|
|
115
|
+
a card without --size, a notes file, or the id of each
|
|
116
|
+
answered question in that file, since a builder starts
|
|
117
|
+
from it; it resolves those answered questions. On a card
|
|
118
|
+
an Implement message waits on you for, it releases the
|
|
119
|
+
build: the board server starts it at once, and it prints
|
|
120
|
+
"released: <key> starts building now"; one a dep still
|
|
121
|
+
blocks is bumped to the top of Todo instead ("bumped: \u2026")
|
|
101
122
|
[--held | --unheld | --pick-up-at <time>] holds or resumes the card on the owner's words; the board
|
|
102
123
|
server records the owner's own Hold and Resume taps. A later
|
|
103
124
|
--held/--unheld overrides a tap; --unheld also clears a held status.
|
|
@@ -110,6 +131,9 @@ command fails and changes nothing.
|
|
|
110
131
|
--held holds it with no time; --pick-up-at "" resumes it
|
|
111
132
|
[--ticket <id>] links a ticket in the board\u2019s source
|
|
112
133
|
[--title <t>] renames the card
|
|
134
|
+
[--check "<name> <args>"] adds or changes the code check on the card's owner step,
|
|
135
|
+
as builder done --check takes it, clearing a broken mark;
|
|
136
|
+
--check "" removes it. Refused on a card with no owner step
|
|
113
137
|
[--size small|large] [--model opus|sonnet] what builder pick reads while the hub's Claude model is
|
|
114
138
|
Auto: a small card gets Claude Sonnet, any other Opus, and
|
|
115
139
|
--model wins over --size; "" clears either
|
|
@@ -125,6 +149,9 @@ command fails and changes nothing.
|
|
|
125
149
|
--parent "" clears it
|
|
126
150
|
[--related <id>,<wave>/<id>] replaces the cards it is Related to, which both cards
|
|
127
151
|
show; --related "" clears it, on both
|
|
152
|
+
[--relate <id>,<wave>/<id>] adds cards to its Related to, keeping the rest
|
|
153
|
+
[--unrelate <id>,<wave>/<id>] takes cards off its Related to, on both cards; neither
|
|
154
|
+
goes with --related
|
|
128
155
|
[--install <url> [--install-sha <sha>] a device build the owner installs from the card's Install
|
|
129
156
|
[--install-label <text>] | --clear-install] button, stamped built_at now: itms-services://?action=
|
|
130
157
|
download-manifest&url=https://\u2026/manifest.plist for iOS,
|
|
@@ -153,15 +180,22 @@ command fails and changes nothing.
|
|
|
153
180
|
for each. It refuses while one has no answer yet; [--force] closes it and resolves that one too.
|
|
154
181
|
--status done refuses a done card the owner messaged since it closed: it reads reopened, and closes
|
|
155
182
|
again only on the owner's Done
|
|
156
|
-
item add <wave>[/<id>] --title <t> [--notes <t>] a new card,
|
|
183
|
+
item add <wave>[/<id>] --title <t> [--notes <t>] a new card, unstarted (waiting when it has --deps); never
|
|
157
184
|
[--ticket <id>] [--deps <id>,<wave>/<id>] hand-edit wave.json to add one. Refuses an id the wave has.
|
|
158
185
|
[--notes-file <file>] --plan is an alias of --notes-file
|
|
159
186
|
[--pick-up-at <time>] holds it until then, as item set's --pick-up-at
|
|
160
187
|
[--size small|large] [--model opus|sonnet] as item set's
|
|
188
|
+
[--state ready] a Quick build: the pull takes it as it is, so it needs
|
|
189
|
+
--size and --notes-file. Without it the board grills the
|
|
190
|
+
card as soon as it lands in Todo
|
|
161
191
|
[--parent <wave>/<id>] the card it was spun out of, such as one filed from
|
|
162
192
|
another card's thread, grill or answer: it reads as a
|
|
163
193
|
follow-up, and its Parent lists it in Children. A card
|
|
164
194
|
from the + or New task has none
|
|
195
|
+
[--state ready|grilling|prototyping] files it at once, as item set's --state; the session's
|
|
196
|
+
[--relate <id>,<wave>/<id>] own form, linking it with --relate in the same call.
|
|
197
|
+
Without --state it reads New and a card-added event
|
|
198
|
+
wakes the board's session, which links and files it
|
|
165
199
|
Without /<id>, the card takes its ticket's id (GH-<n> for issue #n); pass an id only to import a
|
|
166
200
|
card. Prints "<label> added as <key>": use the key in later commands
|
|
167
201
|
item relate-ask <eventId> <wave>/<id> --summary <t> folds an ask about an existing item into it: the ask's
|
|
@@ -231,21 +265,31 @@ command fails and changes nothing.
|
|
|
231
265
|
--channel <dm>
|
|
232
266
|
review react <wave>/<id> <reviewer> <emoji> records a reaction once on that reviewer's link
|
|
233
267
|
integrations readiness rows as one JSON line
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
268
|
+
review updated <wave>/<id> --text "<what changed>" posts \u201C\u{1F916} Updated: <what changed>\u201D under each recorded link
|
|
269
|
+
of a reviewer who hasn't approved, as you, through Hub's
|
|
270
|
+
Slack token
|
|
271
|
+
review due one JSON line of the Slack sends the hub owes, from local state:
|
|
272
|
+
{dms: [{key, reviewer, slack_user, channel, text, record}], reactions:
|
|
238
273
|
[{key, reviewer, channel, ts, emoji, record}], digest:
|
|
239
274
|
review digest's answer while due (with record), else null}.
|
|
240
275
|
dms: a PR link to each reviewer of an open, non-draft PR with
|
|
241
276
|
no link recorded; reactions: git_merged on each recorded link
|
|
242
277
|
of a merged card. record is the command to run once sent.
|
|
243
278
|
While Slack isn't ready: {waiting} and nothing owed
|
|
244
|
-
review
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
279
|
+
review post <pr> --file <review.md> posts the session's words as one Comment review (never an
|
|
280
|
+
--read-at <time> approval or a request for changes) on an incoming PR at
|
|
281
|
+
its listed head, as the board's github-login (else gh's
|
|
282
|
+
account), under "\u{1F916} Claude review from the <board title>
|
|
283
|
+
board" and the <!-- claude-bridge --> marker, and records
|
|
284
|
+
it posted with the review's link. Refuses words that carry
|
|
285
|
+
the header or marker, and a PR that got
|
|
286
|
+
another review after that time. The PR must be one the hub
|
|
248
287
|
listed in data/incoming.json or reviewed before
|
|
288
|
+
review record <pr> --state posted|needs the session's review of an incoming PR that was not posted
|
|
289
|
+
--text <markdown> | --file <path> through review post, in data/reviews.json: posted for a PR
|
|
290
|
+
it chose to say nothing on, needs while it waits on the
|
|
291
|
+
owner, which puts it in Needs you and pushes. Same PRs as
|
|
292
|
+
review post
|
|
249
293
|
review digest pending reviews as JSON; due weekday mornings from 09:00
|
|
250
294
|
review digest sent <reviewer> --ts <ts> records a digest in data/review-digests.json;
|
|
251
295
|
--channel <dm> --items <key,key> marks today sent in the Slack identity timezone
|
|
@@ -259,18 +303,22 @@ command fails and changes nothing.
|
|
|
259
303
|
settings the board's settings as JSON; flags default off, optional
|
|
260
304
|
settings appear when set: {"auto_merge": false,
|
|
261
305
|
"auto_build": false, "auto_pull": false,
|
|
262
|
-
"max_implementers": 10,
|
|
306
|
+
"max_implementers": 10, "max_planners": 3,
|
|
263
307
|
"auto_triage": false, "auto_review": false,
|
|
264
|
-
"headless_builders": false,
|
|
265
308
|
"idle_notification": false, "skip_draft": false,
|
|
266
|
-
"github_login": "owner"}
|
|
267
|
-
settings set auto-merge on|off on: the
|
|
309
|
+
"prs": true, "github_login": "owner"}
|
|
310
|
+
settings set auto-merge on|off on: the hub's tick merges an approved PR once CI is green and
|
|
268
311
|
the merge state is CLEAN. off (the default): the card
|
|
269
|
-
shows the owner a Merge button, and the
|
|
270
|
-
|
|
312
|
+
shows the owner a Merge button, and the board server merges
|
|
313
|
+
when it is tapped. Board Settings' toggle writes it too
|
|
271
314
|
settings set skip-draft on|off on: the hub's poll marks a card's draft PR ready for
|
|
272
315
|
review once its CI passes. off (the default): the draft
|
|
273
316
|
waits in In Review on the owner's review and Ready
|
|
317
|
+
settings set prs on|off on (the default): a board on GitHub opens a PR for each
|
|
318
|
+
card. off: no PR; the card lands on main and main is
|
|
319
|
+
pushed, which needs Push main after a land granted.
|
|
320
|
+
A local board never opens PRs. Without auto-merge, a
|
|
321
|
+
card with no PR waits in In Review for the owner's Merge
|
|
274
322
|
build <wave>/<id> --site <route> asks the hub to run this card's .kickflip/build; prints
|
|
275
323
|
--asked "<owner's words>" started and exits. install is an alias. Only the owner's
|
|
276
324
|
Serve / Build and Auto-build start a card run, so --asked
|
|
@@ -287,32 +335,28 @@ command fails and changes nothing.
|
|
|
287
335
|
settings set auto-build on|off on: each push builds its card. Off by default.
|
|
288
336
|
Main builds after every merge either way
|
|
289
337
|
settings set auto-pull on|off Auto implement Todo cards. on: on every board change the
|
|
290
|
-
board server
|
|
291
|
-
cards nothing blocks, holds or asks about, one
|
|
292
|
-
implementer slot (see max-implementers), each
|
|
293
|
-
|
|
294
|
-
|
|
338
|
+
board server starts and launches the build of the top
|
|
339
|
+
ready Todo cards nothing blocks, holds or asks about, one
|
|
340
|
+
per free implementer slot (see max-implementers), each
|
|
341
|
+
once, and launches a grill on each card that arrives in
|
|
342
|
+
Todo, up to max-planners at a time (an arrival: not ready
|
|
343
|
+
or held, with no planner record and no questions
|
|
344
|
+
yet). Off by default.
|
|
295
345
|
Only on the owner's word
|
|
296
346
|
settings set max-implementers <1..99> Maximum parallel implementers, 10 by default: the cards
|
|
297
347
|
building or waiting in Orchestrator review the pull fills
|
|
298
348
|
up to. Planners and cards waiting on the owner don't count.
|
|
349
|
+
It limits the pull only: an Implement the owner asks for
|
|
350
|
+
starts past it.
|
|
299
351
|
Only on the owner's word
|
|
352
|
+
settings set max-planners <1..99> Maximum parallel planners, 3 by default: the grills and
|
|
353
|
+
prototypes running at once; arrivals past it wait in board
|
|
354
|
+
order. Only on the owner's word
|
|
300
355
|
settings set auto-review on|off on: while the board is idle and nothing is pulled, the board
|
|
301
356
|
server sends pr-review {pr, head, repo, title, author, url,
|
|
302
357
|
touches, asked} for one incoming PR at a time that touches
|
|
303
358
|
this board's cards or asks the owner for review. Off by
|
|
304
359
|
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
360
|
settings set auto-triage on|off on: once a night, from the middle of the hub's quiet hours
|
|
317
361
|
to their end, when two or more cards wait on open
|
|
318
362
|
questions, nothing builds and something changed since the
|
|
@@ -345,7 +389,7 @@ command fails and changes nothing.
|
|
|
345
389
|
opened, it keeps that record's kind and worktree.
|
|
346
390
|
[--dispatch] --dispatch: the session runs this right before it launches
|
|
347
391
|
an agent of any kind (build, prototype, grill) so the record
|
|
348
|
-
is open at launch and the card reads
|
|
392
|
+
is open at launch and the card reads starting; the agent's
|
|
349
393
|
own builder start takes it up. On a record the Build tap
|
|
350
394
|
opened it changes nothing and prints "already open". The
|
|
351
395
|
record keeps dispatched: tap or session, whoever opened it.
|
|
@@ -401,12 +445,32 @@ command fails and changes nothing.
|
|
|
401
445
|
then waits in In Review with it as its Next, its ticket
|
|
402
446
|
open, until the owner's Done; refused on a card already
|
|
403
447
|
done, merged or dropped. land takes it too
|
|
448
|
+
[--check "<name> <args>"] with --owner-step: a read-only check on the step's final
|
|
449
|
+
evidence, which the hub's poll runs on each check once the
|
|
450
|
+
code lands, closing the card when it passes: tag
|
|
451
|
+
<owner/repo> <pattern>, npm <package> <version-or-range>,
|
|
452
|
+
run <owner/repo> <workflow> (a successful run since it was
|
|
453
|
+
attached), gh <owner/repo> <api-path> <field>=<value>, or
|
|
454
|
+
url <address> [<status> | <text>]. An unknown check or bad
|
|
455
|
+
arguments are refused. Three runs in a row that can't run
|
|
456
|
+
mark it broken and append owner-check-broken {key, check,
|
|
457
|
+
error}; fix it with item set --check. land takes it too
|
|
404
458
|
builder takes --dir <dir> for a wave file in <dir>; its records go in <dir>/agents
|
|
459
|
+
lander <wave>/<id> relaunch the headless lander of a card it stopped on (a usage
|
|
460
|
+
pause ends one): the card goes back to checking and the
|
|
461
|
+
server launches a fresh lander; refused unless it is building
|
|
405
462
|
builder pick <wave>/<id> who builds the card, from the hub's Codex share and the pause
|
|
406
463
|
line, and for claude which model, from the hub's Claude model
|
|
407
464
|
and the card's --size and --model: "codex: <reason>" or
|
|
408
465
|
"claude sonnet: <reason>". Pass that kind to builder start
|
|
409
466
|
--kind, and launch that model
|
|
467
|
+
audit [<wave>/<id>] the board's audit log of the last 30 days, data/audit.jsonl:
|
|
468
|
+
each call the code made on its own (builder-pick, pull,
|
|
469
|
+
released-start, launch-held, arrival-planner, relaunch,
|
|
470
|
+
lander, triage, pr-review, push, pick-up, auto-merge,
|
|
471
|
+
ready-draft, session-resume, compact) as "<at> <call>
|
|
472
|
+
[<key>]: <result>. <reason>" and the inputs it read; with
|
|
473
|
+
a card, only that card's. The hub's own: kickflip audit
|
|
410
474
|
builder call <wave>/<id> <judgment call> appends one line to the agent's open builder record: a
|
|
411
475
|
[--agent <name>] judgment call as the builder makes it, or a "Question: \u2026"
|
|
412
476
|
for the session; refused with no open record. A builder start
|
|
@@ -459,7 +523,7 @@ command fails and changes nothing.
|
|
|
459
523
|
branch push (and draft PR) instead of the fast-forward.
|
|
460
524
|
--owner-step <t>, as builder done takes it, keeps the
|
|
461
525
|
landed card in In Review on that step until the owner's
|
|
462
|
-
Done. KICKFLIP_RESTART_CMD='["<cmd>"]' replaces
|
|
526
|
+
Done, or until its --check passes. KICKFLIP_RESTART_CMD='["<cmd>"]' replaces
|
|
463
527
|
the restarts, called with hub or board <route>
|
|
464
528
|
merge <wave>/<id> [--after-approval "<why trivial>"] merges the card's PRs (gh pr merge --squash) in its order and
|
|
465
529
|
[--confirmed "<owner's words>"] records it merged, once GitHub reads every open one approved,
|
|
@@ -469,7 +533,12 @@ command fails and changes nothing.
|
|
|
469
533
|
commits after the newest approval unless --after-approval
|
|
470
534
|
says why they're trivial (its closing line says so), and a
|
|
471
535
|
card with manual steps unless --confirmed quotes the owner.
|
|
472
|
-
A refusal also goes in the thread while the owner's Merge waits
|
|
536
|
+
A refusal also goes in the thread while the owner's Merge waits.
|
|
537
|
+
On a card with no PR waiting for the owner's Merge, which
|
|
538
|
+
the board server runs on the tap: rebases onto the base as
|
|
539
|
+
it is now, reruns the tests, fast-forwards, closes the card
|
|
540
|
+
and pushes when Push main is on; a conflict or failing
|
|
541
|
+
tests launch a headless lander that lands it
|
|
473
542
|
arch file <wave>/<id> turns the architecture review audit card's answered findings
|
|
474
543
|
into cards in the review's wave: titled with the picked
|
|
475
544
|
option's label, its detail, practice and the owner's note as
|
|
@@ -493,4 +562,4 @@ command fails and changes nothing.
|
|
|
493
562
|
(overnight, snoozed or the weekend). --urgent: a
|
|
494
563
|
production fault only; it beats those holds. While
|
|
495
564
|
notifications are off every push is dropped, urgent too
|
|
496
|
-
KICKFLIP_PUSH_CMD='["<cmd>","<arg>"]' replaces \`node ../../bin/kickflip.mjs push\`; the flags follow it`;class d extends Error{static{i(this,"UsageError")}}const Z=new Set(["clear-status","clear-next","urgent","now","clear-eta","held","unheld","idle","hub","clear-install","force","worktree","dry-run","dispatch"]);function Y(e){const s=Object.create(null),t=[];for(let a=0;a<e.length;a++){const r=e[a];if(!r.startsWith("--")){t.push(r);continue}const l=r.slice(2);if(Z.has(l)){if(Object.hasOwn(s,l))throw new d(`--${l} is given twice`);s[l]=!0}else if(a+1<e.length)(s[l]||=[]).push(e[++a]);else throw new d(`--${l} needs a value`)}return{flags:s,rest:t}}i(Y,"parse");function ee(e,s,t){const a=new Set(s.filter(o=>o.startsWith("...")).map(o=>o.slice(3))),r=new Set(s.map(o=>o.replace(/^\.\.\./,""))),l=Object.keys(t).filter(o=>!r.has(o));if(l.length)throw new d(`${e} does not take ${l.map(o=>"--"+o).join(", ")}`);return Object.fromEntries(Object.entries(t).map(([o,c])=>{if(c===!0||a.has(o))return[o,c];if(c.length>1)throw new d(`--${o} is given twice`);return[o,c[0]]}))}i(ee,"flagsOf");const te=["html","text","note","notes","summary","title","body","next","progress","install-label","confirmed","asked"],se=/^(?:\/|~\/|\.{1,2}\/)\S*\.(?:html?|md|json|txt)$/;function ae(e){const s=e.trim();if(!s||/\s/.test(s))return!1;if(se.test(s))return!0;try{return w.statSync(s).isFile()}catch{return!1}}i(ae,"looksLikePath");function ne(e,s){for(const t of te){if(typeof s[t]!="string"||!ae(s[t]))continue;const a=e.includes("file")?`; for a file use --file ${s[t].trim()}`:"";throw new d(`--${t} takes text, not a file path (${s[t].trim()})${a}`)}if(typeof s.file=="string"&&s.file!=="-"&&!w.existsSync(s.file))throw new d(`--file ${s.file}: no such file`)}i(ne,"checkInputs");const f=i(e=>w.readFileSync(e==="-"?0:e,"utf8"),"readInput");function $(e){if(!e)throw new d("--file <path> is required");try{return JSON.parse(f(e))}catch(s){throw new n.BoardError(`${e}: ${s.message}`)}}i($,"readJSONInput");function A(e){if(e.plan!==void 0&&e["notes-file"]!==void 0)throw new d("pass --notes-file or --plan, not both");return e["notes-file"]??e.plan}i(A,"notesFileOf");function P(e){if(e.note!==void 0&&e.file!==void 0)throw new d("pass --note or --file, not both");return e.file===void 0?e.note:f(e.file)}i(P,"noteOf");const g={pr:[["wave/id"],[],async(e,[s])=>({lines:(await n.prReport(e,s)).map(t=>JSON.stringify(t))})],"pr comment":[["pr"],["body"],(e,[s],t)=>{if(t.body===void 0)throw new d('pr comment needs --body "<what Claude says>"');return n.postPR(e,s,{body:t.body})}],"pr reply":[["pr","comment-id"],["body"],(e,[s,t],a)=>{if(a.body===void 0)throw new d('pr reply needs --body "<what Claude says>"');return n.postPR(e,s,{replyTo:t,body:a.body})}],reply:[["eventId"],["html","file"],(e,[s],t)=>{if(t.html===void 0==(t.file===void 0))throw new d("reply takes --html or --file");return n.writeReply(e,s,t.html??f(t.file))}],thread:[["wave/id"],["text","link","who","via"],(e,[s],t)=>n.appendThread(e,s,t)],"topic add":[["id"],["title","summary"],(e,[s],t)=>n.addTopic(e,{id:s,...t})],"question add":[[],["file"],async(e,s,t)=>{const a=$(t.file);return{...await n.addQuestions(e,a),asked:[a].flat()}}],"question resolve":[["id"],["note"],(e,[s],t)=>n.resolveQuestion(e,s,t.note)],"prototype add":[["topic"],["title","...take","card"],(e,[s],t)=>n.addPrototype(e,{topic:s,title:t.title,card:t.card,takes:t.take})],"prototype list":[[],[],e=>n.listPrototypes(e)],"prototype rm":[["topic"],[],(e,[s])=>n.removePrototype(e,s)],"item set":[["wave/id"],["status","clear-status","next","clear-next","...pr","pushed","gh-since","from-ask","state","held","unheld","pick-up-at","notes-file","plan","deps","parent","related","install","install-sha","install-label","clear-install","title","size","model","force","ticket","text"],async(e,[s],t,a)=>(t.status==="done"&&e&&await n.checkNotReopened(e,s),n.setItem(e,s,{status:t.status,clearStatus:t["clear-status"],next:t.next,clearNext:t["clear-next"],pr:t.pr,pushed:t.pushed,ghSince:t["gh-since"],fromAsk:t["from-ask"],state:t.state,held:t.held,unheld:t.unheld,pickUpAt:t["pick-up-at"],notesFile:A(t),deps:t.deps,parent:t.parent,related:t.related,install:t.install,installSha:t["install-sha"],installLabel:t["install-label"],clearInstall:t["clear-install"],title:t.title,size:t.size,model:t.model,force:t.force,ticket:t.ticket,text:t.text},a))],"item add":[["wave[/id]"],["title","notes","deps","notes-file","plan","ticket","pick-up-at","parent","size","model"],(e,[s],t,a)=>n.addItem(e,s,{title:t.title,notes:t.notes,deps:t.deps,notesFile:A(t),ticket:t.ticket,pickUpAt:t["pick-up-at"],parent:t.parent,size:t.size,model:t.model},a)],"item relate-ask":[["eventId","wave/id"],["summary"],(e,[s,t],a)=>n.relateAsk(e,s,t,a.summary)],"ask progress":[["eventId"],["next"],(e,[s],t)=>n.markAsk(e,s,"progress",t.next)],"ask done":[["eventId"],[],(e,[s])=>n.markAsk(e,s,"done")],"ask unfold":[["eventId"],[],(e,[s])=>n.unfoldAsk(e,s)],"ask move":[["eventId"],["to"],(e,[s],t)=>n.moveAsk(e,s,t.to)],"tickets backfill":[[],["dry-run"],(e,s,t,a)=>n.backfillTickets(e,{dryRun:t["dry-run"]},a)],"lineup create":[["slug"],["title","goal","...phase","note","file"],(e,[s],t)=>n.createLineup(e,s,{title:t.title,goal:t.goal,phases:t.phase,note:P(t)})],"lineup add":[["slug","wave/id"],["after"],(e,[s,t],a)=>n.changeLineup(e,s,{add:t,after:a.after})],"lineup remove":[["slug","wave/id"],[],(e,[s,t])=>n.changeLineup(e,s,{remove:t})],"lineup set":[["slug"],["title","goal","note","file","...phase"],(e,[s],t)=>n.changeLineup(e,s,{title:t.title,goal:t.goal,note:P(t),phases:t.phase})],"lineup finish":[["slug"],[],(e,[s])=>n.finishLineup(e,s)],slack:[[],[],()=>({lines:[JSON.stringify(L())]})],"slack set":[[],["self-dm","user","domain","timezone"],async(e,s,t)=>{const a={"self-dm":"self_dm",user:"owner_slack_user",domain:"slack_domain",timezone:"timezone"};try{return{lines:[JSON.stringify(await D(Object.fromEntries(Object.entries(t).map(([r,l])=>[a[r],l]))))]}}catch(r){throw new n.BoardError(r.message)}}],integrations:[[],[],async()=>({lines:[JSON.stringify(await B.rows())]})],"integrations report":[[],["slack"],async(e,s,t)=>{if(!["yes","no"].includes(t.slack))throw new d("integrations report takes --slack yes|no");if(!e)throw new n.BoardError("integrations report needs --site <route>");const a=m.basename(e);return H(t.slack==="yes",{route:a,title:V().sites?.[a]?.title||(await n.boardConfig(e)).title}),{lines:[`Slack connector reported: ${t.slack}.`]}}],"review sent":[["wave/id","reviewer"],["ts","channel"],(e,[s,t],a)=>n.reviewSent(e,s,t,a)],"review react":[["wave/id","reviewer","emoji"],[],(e,[s,t,a])=>n.reviewReact(e,s,t,a)],"review record":[["pr"],["state","text","file","url"],(e,[s],t)=>{if(t.text===void 0==(t.file===void 0))throw new d("review record takes --text or --file");return n.recordReview(e,s,{state:t.state,text:t.text??f(t.file),url:t.url})}],"review digest":[[],[],async(e,s,t,a)=>({lines:[JSON.stringify(await n.reviewDigest(e,a))]})],"review due":[[],[],async(e,s,t,a)=>({lines:[JSON.stringify(await n.reviewDue(e,a))]})],"review digest sent":[["reviewer"],["ts","channel","items"],(e,[s],t,a)=>n.reviewDigestSent(e,s,t,a)],"reviewers set":[[],["file"],(e,s,t)=>n.setReviewers(e,$(t.file))],"reviewers add":[[],["file"],(e,s,t)=>n.addReviewers(e,$(t.file))],"reviewers remove":[["...name"],[],(e,s)=>n.removeReviewers(e,s)],"rules list":[[],[],e=>n.listRules(e)],"rules add":[[],["text","hub","by"],(e,s,t)=>n.addRuleTo(e,t)],"rules edit":[["id"],["text","hub"],(e,[s],t)=>n.editRuleIn(e,s,t)],"rules remove":[["id"],["hub"],(e,[s],t)=>n.removeRuleFrom(e,s,t)],settings:[[],[],async e=>({lines:[JSON.stringify(await n.readBoardSettings(e))]})],build:[["wave/id"],["asked"],async(e,[s],t)=>{if(!e)throw new n.BoardError("build needs --site <route>");n.parseKey(s);const a=await n.askedGate(e,s,t.asked);try{const r=await fetch(`http://127.0.0.1:${process.env.KICKFLIP_HUB_PORT||4500}/_hub/build`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({route:m.basename(e),key:s}),signal:AbortSignal.timeout(1e4)}),l=await r.json();if(!r.ok)throw new n.BoardError(l.error||"The hub refused the build.");return{lines:["started",...(await n.appendThread(e,s,{text:a})).lines]}}catch(r){throw r instanceof n.BoardError?r:new n.BoardError(`Could not start the build: ${r.message}`)}}],"main set":[[],["install","install-sha"],(e,s,t)=>n.setMainInstall(e,{install:t.install,installSha:t["install-sha"]})],"settings set":[["name","value"],[],(e,[s,t])=>n.setBoardSetting(e,s,t)],"builder start":[["wave/id"],["label","phase","progress","dir","agent","kind","model","eta","worktree","repo","dispatch"],(e,[s],{worktree:t,repo:a,dispatch:r,...l})=>{if(a!==void 0&&!t)throw new d("--repo goes with --worktree");const o=i(b=>z(e,s.split("/")[1],{repo:a,...b}),"make"),c=e&&l.dir===void 0&&Q(e);return n.startBuilder(e,s,{...l,dispatch:r?"session":void 0,worktree:t?o:void 0,ownWorktree:c?o:void 0})}],"builder progress":[["wave/id","...text?"],["phase","text","dir","agent","eta","clear-eta"],(e,[s,...t],a)=>{if(t.length&&a.text!==void 0)throw new d("builder progress takes its text once: as words or as --text");if(!t.length&&a.text===void 0)throw new d("builder progress needs its text, as words or as --text");return n.builderProgress(e,s,a.text??t.join(" "),{...a,clearEta:a["clear-eta"]})}],"builder done":[["wave/id"],["dir","agent","next","owner-step","text"],async(e,[s],{"owner-step":t,text:a,...r})=>{if(t!==void 0&&(!e||r.dir))throw new d("--owner-step goes on a board card: pass --site, not --dir");t!==void 0&&await n.setItem(e,s,{ownerStep:t});const l=e&&!r.dir,o=l&&await n.handBackCalls(e,s,r);if(o)return o;if(l&&r.next===void 0&&await n.finishesCard(e,s,r)){if(a===void 0)throw new n.BoardError(`builder done finishes ${s}: pass --text "<what you checked>", or --next "<what happens next>" to stop without finishing`);return x(e,s,{text:a,ownerStep:t})}if(a!==void 0)throw new d("--text goes on a builder done that finishes the card: the main build, with a worktree and no --next");return n.stopBuilder(e,s,{...r,requireNext:!0})}],"builder pick":[["wave/id"],[],(e,[s])=>n.pickBuilder(e,s)],"builder call":[["wave/id","...text"],["dir","agent"],(e,[s,...t],a)=>n.recordCall(e,s,t.join(" "),a)],"builder calls":[["wave/id"],["dir","agent"],(e,[s],t)=>n.listCalls(e,s,t)],"builder ask":[["wave/id","...text"],["agent"],(e,[s,...t],a)=>n.askOrchestrator(e,s,t.join(" "),a)],"builder answer":[["wave/id","ask"],["text"],(e,[s,t],a)=>n.answerBuilder(e,s,t,a.text)],"builder defer":[["wave/id","ask"],["text"],(e,[s,t],a)=>n.answerBuilder(e,s,t,a.text,{deferred:!0})],"builder asks":[["wave/id"],[],(e,[s])=>n.listAsks(e,s)],"builder verified":[["wave/id"],["dir","agent","note"],async(e,[s],t)=>{await n.verifyCalls(e,s,t);const a=e&&!t.dir?await n.verifiedFinish(e,s):null;if(!a)return{};if(a.waits.length)return{lines:[`${await n.cardName(e,s)} finishes once the owner answers ${a.waits.join(", ")}`]};try{return await x(e,s,{text:t.note??"Judgment calls verified."})}catch(r){throw r instanceof n.BoardError?Object.assign(new n.BoardError(`judgment calls verified, but ${await n.cardName(e,s)} didn't finish: ${r.message}`),{exitCode:r.exitCode}):r}}],land:[["wave/id"],["repo","dir","text","owner-step"],async(e,[s],t)=>{const a=s.split("/")[1];if(e&&t.text===void 0)throw new d(`land needs --text "<what was checked>", which closes ${s} as done once it lands`);if(!e&&t.text!==void 0)throw new d("--text closes the card, which needs --site");if(!e&&t["owner-step"]!==void 0)throw new d("--owner-step keeps a board card open, which needs --site");return e&&t.dir===void 0?x(e,s,{text:t.text,ownerStep:t["owner-step"],repo:t.repo}):M(e?await n.cardName(e,s):s,async()=>{e&&await n.checkClose(e,s,t.text,t["owner-step"]),await n.checkCallsVerified(e,s,{dir:t.dir});const r=await n.recordedWorktree(e,s,{dir:t.dir}),{repo:l,base:o}=G(e,{repo:t.repo??r?.repo});return J({path:r?.path??m.join(F(),a),branch:r?.branch??a,repo:m.resolve(l),base:o,ref:e?await n.cardName(e,s):void 0}),e?n.setItem(e,s,{status:"done",text:t.text,ownerStep:t["owner-step"]}):{lines:[]}})}],merge:[["wave/id"],["after-approval","confirmed"],(e,[s],t)=>n.mergeCard(e,s,{afterApproval:t["after-approval"],confirmed:t.confirmed})],"arch file":[["wave/id"],[],(e,[s])=>e?U(e,s):Promise.reject(new n.BoardError("arch file needs --site <route>"))],checked:[[],["every","idle"],(e,s,t)=>n.writeChecked(e,t.every,{idle:t.idle})],push:[[],["kind","title","body","url","tag","urgent","now"],(e,s,t)=>{if(t.kind!=="fault"&&t.kind!=="reminder")throw new d(`a session pushes only --kind fault or reminder${t.kind===void 0||t.kind===!0?"":`, not "${t.kind}"`}: the board server pushes Action needed, and news goes on the board`);if(t.title===void 0)throw new d("push needs --title");return n.sendPush(e,t)}]};g.install=g.build;const h=i((e,s)=>n.cardName(e,s),"card"),C=i((e,s)=>`${s}${e.length===1?"":"s"} ${e.join(", ")}`,"plural"),q=i(e=>e.agent===void 0||e.agent==="main"?"builder":`builder ${e.agent}`,"builderOf");function ie(e){const s=e["pick-up-at"];return[e.status,e["clear-status"]&&"status cleared",e.next!==void 0&&(e.next.trim()?"next line set":"next line cleared"),e["clear-next"]&&"next line cleared",e.pr!==void 0&&`PR #${e.pr}`,e.pushed!==void 0&&`pushed ${e.pushed}`,e["gh-since"]&&`GitHub read to ${e["gh-since"]}`,e["from-ask"]&&`from task ${e["from-ask"]}`,e.state,e.held&&"held",e.unheld&&"resumed",s!==void 0&&(s?`held until ${s}`:"resumed"),A(e)!==void 0&&"notes recorded",e.deps!==void 0&&(e.deps?`blocked by ${e.deps}`:"deps cleared"),e.parent!==void 0&&(e.parent?`parent ${e.parent}`:"parent cleared"),e.related!==void 0&&(e.related?`related to ${e.related.split(",").join(", ")}`:"related cleared"),e.install!==void 0&&"install published",e["clear-install"]&&"install cleared",e.title!==void 0&&"renamed",e.ticket!==void 0&&`ticket ${e.ticket}`].filter(Boolean).join(", ")}i(ie,"itemChanges");const re={reply:i(async(e,[s])=>`reply written to ${await n.eventCard(e,s)}`,"reply"),thread:i(async(e,[s])=>`thread line on ${await h(e,s)}`,"thread"),"topic add":i((e,[s])=>`topic ${s} added`,"topic add"),"question add":i(async(e,s,t,{asked:a})=>{const r=[...new Set(a.map(o=>o.topic))],l=r.length!==1?"":r[0].startsWith("item:")?` on ${await h(e,r[0].slice(5))}`:` on topic ${r[0]}`;return`${C(a.map(o=>o.id),"question")} added${l}`},"question add"),"question resolve":i((e,[s])=>`question ${s} resolved`,"question resolve"),"prototype add":i(async(e,[s],t)=>`prototype ${s} recorded${t.card?` for ${await h(e,t.card)}`:""}`,"prototype add"),"prototype rm":i((e,[s])=>`prototype ${s} removed`,"prototype rm"),"item set":i(async(e,[s],t)=>`${await h(e,s)}: ${ie(t)}`,"item set"),"item add":i(async(e,s,t,{key:a})=>`${await h(e,a)} added as ${a}`,"item add"),"item relate-ask":i(async(e,[s,t])=>`task ${s} folded into ${await h(e,t)}`,"item relate-ask"),"ask progress":i(async(e,[s])=>`${await h(e,`ask:${s}`)}: in progress`,"ask progress"),"ask done":i(async(e,[s])=>`${await h(e,`ask:${s}`)}: done`,"ask done"),"ask unfold":i((e,[s])=>`task ${s} back on a card of its own`,"ask unfold"),"ask move":i((e,[s],t)=>`task ${s} moved to /${t.to}`,"ask move"),checked:i((e,s,t,{every:a})=>a===null?"GitHub checked; no open PR":`GitHub checked; next in ${a} min`,"checked"),"lineup create":i((e,[s])=>`lineup ${s} created`,"lineup create"),"lineup add":i(async(e,[s,t])=>`${await h(e,t)} added to lineup ${s}`,"lineup add"),"lineup remove":i(async(e,[s,t])=>`${await h(e,t)} taken out of lineup ${s}`,"lineup remove"),"lineup set":i((e,[s])=>`lineup ${s} updated`,"lineup set"),"lineup finish":i((e,[s])=>`lineup ${s} finished`,"lineup finish"),"review sent":i(async(e,[s,t])=>`review link to ${t} recorded on ${await h(e,s)}`,"review sent"),"review react":i(async(e,[s,t,a])=>`${a} on ${t}'s review link recorded on ${await h(e,s)}`,"review react"),"review digest sent":i((e,[s])=>`digest to ${s} recorded`,"review digest sent"),"review record":i((e,[s],t)=>`review of PR #${s} recorded: ${t.state==="needs"?"needs you":"posted"}`,"review record"),"reviewers set":i(()=>"reviewers replaced","reviewers set"),"reviewers add":i(()=>"reviewers added","reviewers add"),"reviewers remove":i((e,s)=>`${C(s,"reviewer")} removed`,"reviewers remove"),"rules add":i((e,s,t)=>`${t.hub?"hub":"board"} rule added`,"rules add"),"rules edit":i((e,[s],t)=>`${t.hub?"hub":"board"} rule ${s} reworded`,"rules edit"),"rules remove":i((e,[s],t)=>`${t.hub?"hub":"board"} rule ${s} removed`,"rules remove"),"builder start":i(async(e,[s],t,a)=>`${await h(e,s)}: ${a.alreadyOpen?"already open: the Implement tap opened its record; launch the agent, whose own builder start takes it up":`${q(t)} started`}`,"builder start"),"builder progress":i(async(e,[s])=>`progress line on ${await h(e,s)}`,"builder progress"),"builder done":i(async(e,[s],t,a)=>`${await h(e,s)}: ${a?.said??`${q(t)} done${t["owner-step"]===void 0?"":`; owner step "${t["owner-step"].trim()}"`}`}`,"builder done"),"builder call":i(async(e,[s])=>`judgment call recorded on ${await h(e,s)}`,"builder call"),"builder ask":i(async(e,[s],t,{id:a})=>`${await h(e,s)}: asked the orchestrator, ${a}`,"builder ask"),"builder answer":i(async(e,[s,t])=>`${await h(e,s)}: ${t} answered`,"builder answer"),"builder defer":i(async(e,[s,t])=>`${await h(e,s)}: ${t} deferred to the owner`,"builder defer"),"builder verified":i(async(e,[s],t,a)=>`${await h(e,s)}: judgment calls verified${a?.said?`; ${a.said}`:""}`,"builder verified"),land:i(async(e,[s],t,a)=>e?a?.said?`${await h(e,s)}: ${a.said}`:t["owner-step"]===void 0?`${await h(e,s)}: landed and done`:`${await h(e,s)}: landed; waits on the owner step "${t["owner-step"].trim()}"`:null,"land"),merge:i(async(e,[s])=>`${await h(e,s)}: merged`,"merge"),"arch file":i(async(e,[s])=>`findings of ${await h(e,s)} filed`,"arch file")};async function oe(e,s={}){try{const{flags:t,rest:a}=Y(e),r=[3,2,1].map(u=>a.slice(0,u).join(" ")).find(u=>y(g,u)),l=y(g,r);if(!l)throw new d(a.length?`unknown command "${a.slice(0,2).join(" ")}"`:"no command given");const[o,c,b]=l,p=a.slice(r.split(" ").length),_=o.at(-1)?.startsWith("..."),N=o.at(-1)?.endsWith("?")?o.length-1:o.length;if(_?p.length<N:p.length!==o.length)throw new d(`${r} takes ${o.map(u=>`<${u.replace(/^\.\.\.|\?$/g,"")}>`).join(" ")||"no arguments"}`);const{site:S,...T}=t;if(S?.length>1)throw new d("--site is given twice");const O=S?.[0],k=ee(r,c,T);ne(c,k);const R=O===void 0?null:await n.siteDir(O),v=await b(R,p,k,s),I=y(re,r),j=I&&await I(R,p,k,v);j&&console.log(j);for(const u of typeof v=="object"&&v?.lines||[])console.log(u);return 0}catch(t){if(t instanceof d)return console.error(`${t.message} (node board.mjs --help lists the commands)`),2;if(t instanceof n.BoardError)return console.error(t.message),t.exitCode??1;throw t}}if(i(oe,"main"),process.argv[1]&&import.meta.url===W(w.realpathSync(process.argv[1])).href){const e=process.argv.slice(2);!e.length||e.includes("--help")||e[0]==="help"?console.log(X):process.exitCode=await oe(e)}export{X as USAGE,oe as main};
|
|
565
|
+
KICKFLIP_PUSH_CMD='["<cmd>","<arg>"]' replaces \`node ../../bin/kickflip.mjs push\`; the flags follow it`;class l extends Error{static{r(this,"UsageError")}}const X=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 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(X.has(o)){if(Object.hasOwn(a,o))throw new l(`--${o} is given twice`);a[o]=!0}else if(s+1<e.length)(a[o]||=[]).push(e[++s]);else throw new l(`--${o} needs a value`)}return{flags:a,rest:t}}r(Y,"parse");function Z(e,a,t){const s=new Set(a.filter(d=>d.startsWith("...")).map(d=>d.slice(3))),i=new Set(a.map(d=>d.replace(/^\.\.\./,""))),o=Object.keys(t).filter(d=>!i.has(d));if(o.length)throw new l(`${e} does not take ${o.map(d=>"--"+d).join(", ")}`);return Object.fromEntries(Object.entries(t).map(([d,c])=>{if(c===!0||s.has(d))return[d,c];if(c.length>1)throw new l(`--${d} is given twice`);return[d,c[0]]}))}r(Z,"flagsOf");const ee=["html","text","note","notes","summary","title","body","next","progress","install-label","confirmed","asked"],te=/^(?:\/|~\/|\.{1,2}\/)\S*\.(?:html?|md|json|txt)$/;function ae(e){const a=e.trim();if(!a||/\s/.test(a))return!1;if(te.test(a))return!0;try{return g.statSync(a).isFile()}catch{return!1}}r(ae,"looksLikePath");function se(e,a){for(const t of ee){if(typeof a[t]!="string"||!ae(a[t]))continue;const s=e.includes("file")?`; for a file use --file ${a[t].trim()}`:"";throw new l(`--${t} takes text, not a file path (${a[t].trim()})${s}`)}if(typeof a.file=="string"&&a.file!=="-"&&!g.existsSync(a.file))throw new l(`--file ${a.file}: no such file`)}r(se,"checkInputs");const w=r(e=>g.readFileSync(e==="-"?0:e,"utf8"),"readInput");function $(e){if(!e)throw new l("--file <path> is required");try{return JSON.parse(w(e))}catch(a){throw new n.BoardError(`${e}: ${a.message}`)}}r($,"readJSONInput");function R(e){if(e.plan!==void 0&&e["notes-file"]!==void 0)throw new l("pass --notes-file or --plan, not both");return e["notes-file"]??e.plan}r(R,"notesFileOf");function C(e){if(e.note!==void 0&&e.file!==void 0)throw new l("pass --note or --file, not both");return e.file===void 0?e.note:w(e.file)}r(C,"noteOf");const f={pr:[["wave/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 l('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 l('pr reply needs --body "<what Claude says>"');return n.postPR(e,a,{replyTo:t,body:s.body})}],reply:[["eventId"],["html","file"],(e,[a],t)=>{if(t.html===void 0==(t.file===void 0))throw new l("reply takes --html or --file");return n.writeReply(e,a,t.html??w(t.file))}],thread:[["wave/id"],["text","link","who","via"],(e,[a],t)=>n.appendThread(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=$(t.file);return{...await n.addQuestions(e,s),asked:[s].flat()}}],"question resolve":[["id"],["note"],(e,[a],t)=>n.resolveQuestion(e,a,t.note)],"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"],(e,[a],t)=>n.addPrototype(e,{topic:a,title:t.title,card:t.card,takes:t.take})],"prototype list":[[],[],e=>n.listPrototypes(e)],"prototype rm":[["topic"],[],(e,[a])=>n.removePrototype(e,a)],"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","relate","unrelate","install","install-sha","install-label","clear-install","title","size","model","force","ticket","text","check"],async(e,[a],t,s)=>(t.status==="done"&&e&&await n.checkNotReopened(e,a),t.status==="building"&&e&&S(e)&&await n.ensureWorktree(e,a,i=>O(e,a.split("/")[1],i)),n.setItem(e,a,{status:t.status,clearStatus:t["clear-status"],next:t.next,clearNext:t["clear-next"],pr:t.pr,pushed:t.pushed,ghSince:t["gh-since"],fromAsk:t["from-ask"],state:t.state,held:t.held,unheld:t.unheld,pickUpAt:t["pick-up-at"],notesFile:R(t),deps:t.deps,parent:t.parent,related:t.related,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":[["wave[/id]"],["title","notes","deps","notes-file","plan","ticket","pick-up-at","parent","size","model","state","relate"],(e,[a],t,s)=>n.addItem(e,a,{title:t.title,notes:t.notes,deps:t.deps,notesFile:R(t),ticket:t.ticket,pickUpAt:t["pick-up-at"],parent:t.parent,size:t.size,model:t.model,state:t.state,relate:t.relate,intake:!0},s)],"item relate-ask":[["eventId","wave/id"],["summary"],(e,[a,t],s)=>n.relateAsk(e,a,t,s.summary)],"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)],"lineup create":[["slug"],["title","goal","...phase","note","file"],(e,[a],t)=>n.createLineup(e,a,{title:t.title,goal:t.goal,phases:t.phase,note:C(t)})],"lineup add":[["slug","wave/id"],["after"],(e,[a,t],s)=>n.changeLineup(e,a,{add:t,after:s.after})],"lineup remove":[["slug","wave/id"],[],(e,[a,t])=>n.changeLineup(e,a,{remove:t})],"lineup set":[["slug"],["title","goal","note","file","...phase"],(e,[a],t)=>n.changeLineup(e,a,{title:t.title,goal:t.goal,note:C(t),phases:t.phase})],"lineup finish":[["slug"],[],(e,[a])=>n.finishLineup(e,a)],slack:[[],[],()=>({lines:[JSON.stringify(D())]})],"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 W(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 L.rows())]})],"review updated":[["wave/id"],["text"],(e,[a],t)=>n.reviewUpdated(e,a,t.text)],"review sent":[["wave/id","reviewer"],["ts","channel"],(e,[a,t],s)=>n.reviewSent(e,a,t,s)],"review react":[["wave/id","reviewer","emoji"],[],(e,[a,t,s])=>n.reviewReact(e,a,t,s)],"review record":[["pr"],["state","text","file"],(e,[a],t)=>{if(t.text===void 0==(t.file===void 0))throw new l("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 l("review post needs --file <review.md>");if(t["read-at"]===void 0)throw new l("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))]})],"review digest sent":[["reviewer"],["ts","channel","items"],(e,[a],t,s)=>n.reviewDigestSent(e,a,t,s)],"reviewers set":[[],["file"],(e,a,t)=>n.setReviewers(e,$(t.file))],"reviewers add":[[],["file"],(e,a,t)=>n.addReviewers(e,$(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:[["wave/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:y.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.appendThread(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":[["wave/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 l("--repo goes with --worktree");const d=r(p=>O(e,a.split("/")[1],{repo:s,...p}),"make"),c=e&&o.dir===void 0&&S(e);return n.startBuilder(e,a,{...o,dispatch:i?"session":void 0,worktree:t?d:void 0,ownWorktree:c?d:void 0})}],"builder progress":[["wave/id","...text?"],["phase","text","dir","agent","eta","clear-eta"],(e,[a,...t],s)=>{if(t.length&&s.text!==void 0)throw new l("builder progress takes its text once: as words or as --text");if(!t.length&&s.text===void 0)throw new l("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":[["wave/id"],["dir","agent","next","owner-step","check","text"],async(e,[a],{"owner-step":t,check:s,text:i,...o})=>{if(t!==void 0&&(!e||o.dir))throw new l("--owner-step goes on a board card: pass --site, not --dir");if(s!==void 0&&t===void 0)throw new l("--check goes with --owner-step: it is the code check that closes the card once the step is done");t!==void 0&&await n.setItem(e,a,{ownerStep:t,check:s});const d=e&&!o.dir,c=d&&await n.handBackCalls(e,a,o);if(c)return c;if(d&&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 x(e,a,{text:i,ownerStep:t})}if(i!==void 0)throw new l("--text goes on a builder done that finishes the card: the main build, with a worktree and no --next");return n.stopBuilder(e,a,{...o,requireNext:!0})}],"builder pick":[["wave/id"],[],(e,[a])=>n.pickBuilder(e,a)],audit:[["...wave/id?"],[],(e,[a,...t])=>{if(t.length)throw new l("audit takes one <wave/id> at most");return n.auditLog(e,a)}],"builder call":[["wave/id","...text"],["dir","agent"],(e,[a,...t],s)=>n.recordCall(e,a,t.join(" "),s)],"builder calls":[["wave/id"],["dir","agent"],(e,[a],t)=>n.listCalls(e,a,t)],"builder ask":[["wave/id","...text"],["agent"],(e,[a,...t],s)=>n.askOrchestrator(e,a,t.join(" "),s)],"builder answer":[["wave/id","ask"],["text"],(e,[a,t],s)=>n.answerBuilder(e,a,t,s.text)],"builder defer":[["wave/id","ask"],["text"],(e,[a,t],s)=>n.answerBuilder(e,a,t,s.text,{deferred:!0})],"builder asks":[["wave/id"],[],(e,[a])=>n.listAsks(e,a)],"builder verified":[["wave/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 x(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:[["wave/id"],["repo","dir","text","owner-step","check"],async(e,[a],t)=>{const s=a.split("/")[1];if(e&&t.text===void 0)throw new l(`land needs --text "<what was checked>", which closes ${a} as done once it lands`);if(!e&&t.text!==void 0)throw new l("--text closes the card, which needs --site");if(!e&&t["owner-step"]!==void 0)throw new l("--owner-step keeps a board card open, which needs --site");if(t.check!==void 0&&t["owner-step"]===void 0)throw new l("--check goes with --owner-step: it is the code check that closes the card once the step is done");if(e&&t.dir===void 0)return x(e,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 G(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 d=await n.recordedWorktree(e,a,{dir:t.dir}),{repo:c,base:p}=z(e,{repo:t.repo??d?.repo});return await J({path:d?.path??y.join(U(),s),branch:d?.branch??s,repo:y.resolve(c),base:p,ref:i}),e?n.setItem(e,a,{status:"done",text:t.text,ownerStep:t["owner-step"],check:t.check}):{lines:[]}});return o.joined?{lines:[`joined the land already running for ${i??a}`]}:o}],lander:[["wave/id"],[],async(e,[a])=>{if(!e)throw new l("lander needs --site <route>");return await n.relaunchLander(e,a),{lines:[`${await n.cardName(e,a)} is back at checking; the server launches a lander for it within a minute`]}}],merge:[["wave/id"],["after-approval","confirmed"],async(e,[a],t)=>e&&await n.heldMerge(e,a)?K(e,a):n.mergeCard(e,a,{afterApproval:t["after-approval"],confirmed:t.confirmed})],"arch file":[["wave/id"],[],(e,[a])=>e?F(e,a):Promise.reject(new n.BoardError("arch file needs --site <route>"))],checked:[[],["every","idle"],(e,a,t)=>n.writeChecked(e,t.every,{idle:t.idle})],push:[[],["kind","title","body","url","tag","urgent","now"],(e,a,t)=>{if(t.kind!=="fault"&&t.kind!=="reminder")throw new l(`a session pushes only --kind fault or reminder${t.kind===void 0||t.kind===!0?"":`, not "${t.kind}"`}: the board server pushes Action needed, and news goes on the board`);if(t.title===void 0)throw new l("push needs --title");return n.sendPush(e,t)}]};f.install=f.build;const h=r((e,a)=>n.cardName(e,a),"card"),T=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 ne(e){const a=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",a!==void 0&&(a?`held until ${a}`:"resumed"),R(e)!==void 0&&"notes recorded",e.deps!==void 0&&(e.deps?`blocked by ${e.deps}`:"deps cleared"),e.parent!==void 0&&(e.parent?`parent ${e.parent}`:"parent cleared"),e.related!==void 0&&(e.related?`related to ${e.related.split(",").join(", ")}`:"related cleared"),e.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(ne,"itemChanges");const re={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))],d=o.length!==1?"":o[0].startsWith("item:")?` on ${await h(e,o[0].slice(5))}`:` on topic ${o[0]}`;return`${T(s.map(c=>c.id),"question")} ${i===s.length?"staged":"added"}${d}`},"question add"),"question resolve":r((e,[a])=>`question ${a} resolved`,"question resolve"),"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)}: ${ne(t)}`,"item set"),"item add":r(async(e,a,t,{key:s})=>`${await h(e,s)} added as ${s}`,"item add"),"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"),checked:r((e,a,t,{every:s})=>s===null?"GitHub checked; no open PR":`GitHub checked; next in ${s} min`,"checked"),"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 sent":r(async(e,[a,t])=>`review link to ${t} recorded on ${await h(e,a)}`,"review sent"),"review react":r(async(e,[a,t,s])=>`${s} on ${t}'s review link recorded on ${await h(e,a)}`,"review react"),"review digest sent":r((e,[a])=>`digest to ${a} recorded`,"review digest sent"),"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)=>`${T(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":`${_(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??`${_(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 ie(e,a={}){try{const{flags:t,rest:s}=Y(e),i=[3,2,1].map(u=>s.slice(0,u).join(" ")).find(u=>v(f,u)),o=v(f,i);if(!o)throw new l(s.length?`unknown command "${s.slice(0,2).join(" ")}"`:"no command given");const[d,c,p]=o,m=s.slice(i.split(" ").length),N=d.at(-1)?.startsWith("..."),B=d.at(-1)?.endsWith("?")?d.length-1:d.length;if(N?m.length<B:m.length!==d.length)throw new l(`${i} takes ${d.map(u=>`<${u.replace(/^\.\.\.|\?$/g,"")}>`).join(" ")||"no arguments"}`);const{site:P,...E}=t;if(P?.length>1)throw new l("--site is given twice");const A=P?.[0],k=Z(i,c,E);se(c,k);const j=A===void 0?null:await n.siteDir(A),b=await p(j,m,k,a),q=v(re,i),I=q&&await q(j,m,k,b);I&&console.log(I);for(const u of typeof b=="object"&&b?.lines||[])console.log(u);return 0}catch(t){if(t instanceof l)return console.error(`${t.message} (node board.mjs --help lists the commands)`),2;if(t instanceof n.BoardError)return console.error(t.message),t.exitCode??1;throw t}}if(r(ie,"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(V):process.exitCode=await ie(e)}export{V as USAGE,ie as main};
|