@lovek4d/kickflip 0.5.0 → 0.6.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 +105 -68
- package/README.md +1 -1
- package/SKILL.md +27 -21
- package/bin/kickflip.mjs +27 -18
- package/bin/wait-events.mjs +2 -2
- package/board/agents.mjs +2 -0
- package/board/arch-review.mjs +4 -4
- package/board/bin/board.mjs +199 -79
- package/board/build-script.mjs +2 -1
- package/board/domain.mjs +9 -7
- package/board/finish.mjs +4 -4
- package/board/headless.mjs +9 -4
- package/board/on-host.mjs +1 -1
- package/board/onboarding.mjs +11 -0
- package/board/prototype-base.mjs +1 -0
- package/board/prototype-page.mjs +1 -0
- package/board/public/app-arch.js +1 -1
- package/board/public/app-asks.js +6 -6
- package/board/public/app-banner.js +7 -13
- package/board/public/app-cards.js +75 -37
- package/board/public/app-core.js +26 -8
- package/board/public/app-header.js +8 -8
- package/board/public/app-lanes.js +67 -58
- package/board/public/app-needs.js +140 -10
- package/board/public/app-reader.js +1 -1
- package/board/public/app-snapshot.js +46 -72
- package/board/public/app-threads.js +78 -75
- package/board/public/app.html +9 -4
- package/board/public/app.js +114 -38
- package/board/public/attach.js +2 -2
- package/board/public/dialogs.js +9 -39
- package/board/public/hub-kit.js +66 -4
- package/board/public/hub.css +2 -0
- package/board/public/linear-key.js +46 -35
- package/board/public/new-task.js +20 -39
- package/board/public/progress-review.html +46 -0
- package/board/public/progress-review.js +158 -0
- package/board/public/prototype-kit.js +137 -7
- package/board/public/prototype-switcher.js +152 -123
- package/board/public/prototype.html +57 -3
- package/board/public/router.js +7 -6
- package/board/public/shell.js +68 -26
- package/board/public/sidebar.css +1 -0
- package/board/public/sidebar.js +16 -30
- package/board/public/style.css +205 -83
- package/board/public/ui-cards.js +91 -65
- package/board/public/ui-enums.js +1 -1
- package/board/public/ui-header.js +27 -18
- package/board/public/ui-hub.js +12 -1
- package/board/public/ui-links.js +7 -7
- package/board/public/ui-order.js +12 -10
- package/board/public/ui-page-header.js +19 -7
- package/board/public/ui-patch.js +7 -33
- package/board/public/ui-progress-bar.js +28 -0
- package/board/public/ui-questions.js +47 -52
- package/board/public/ui-sidebar.js +34 -26
- package/board/public/ui-snapshot.js +43 -52
- package/board/public/ui-threads.js +59 -30
- package/board/public/ui.js +125 -18
- package/board/public/view-hub-settings.js +22 -67
- package/board/public/view-hub.js +19 -4
- package/board/public/view-notes.js +27 -7
- package/board/public/view-questions.js +205 -240
- package/board/public/view-settings.js +313 -76
- package/board/render-check.mjs +10 -0
- package/board/review.mjs +1 -1
- package/board/server.mjs +19 -20
- package/board/stop.mjs +1 -0
- package/board/store.mjs +29 -15
- package/board/sync.mjs +2 -0
- package/board/tickets.mjs +3 -3
- package/board/transcript.mjs +2 -0
- package/board/worktree.mjs +25 -20
- package/hooks-plugin/hooks/board-data-gate.mjs +50 -0
- package/hooks-plugin/hooks/hooks.json +9 -0
- package/hooks-plugin/hooks/usage-gate.mjs +20 -3
- package/hub/board-repo.mjs +1 -1
- package/hub/boards.mjs +4 -4
- package/hub/front-door.mjs +5 -5
- package/hub/github.mjs +3 -3
- package/hub/hub.mjs +11 -11
- package/hub/incoming.mjs +2 -2
- package/hub/linear.mjs +1 -2
- package/hub/live.mjs +2 -2
- package/hub/locks.mjs +1 -1
- package/hub/owner-checks.mjs +1 -1
- 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/card-prs.mjs +1 -1
- package/shared/fake-gh.mjs +17 -4
- package/shared/machine-gate.mjs +2 -0
- package/shared/prototype-frame.js +1 -0
- package/shared/service.mjs +4 -4
- package/shared/usage-gate.mjs +1 -1
- package/board/public/ui-phase-bars.js +0 -17
package/board/bin/board.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
var
|
|
2
|
+
var L=Object.defineProperty;var r=(e,a)=>L(e,"name",{value:a,configurable:!0});import{readiness as H}from"../../hub/readiness.mjs";import{readSlack as D,setSlack as M}from"../../hub/slack.mjs";import{routeRecord as y}from"../../hub/routes.mjs";import g from"node:fs";import x from"node:path";import{pathToFileURL as W}from"node:url";import*as s from"../store.mjs";import{boardWorktree as z,builderRepo as U,joiningLand as F,landInTurn as J,mergeHelper as G,worktreesDir as K}from"../worktree.mjs";import{pidAlive as Q}from"../headless.mjs";import{fileFindings as V}from"../arch-review.mjs";import{finishCard as $,landHeld as X}from"../finish.mjs";import{bump as S,queueLines as Y,stopBuilder as Z,stopForBlock as ee,stopLand as te}from"../stop.mjs";import{boardAt as ae}from"../../hub/boards.mjs";import"../../hub/board-repo.mjs";import{checkTakesRender as se}from"../render-check.mjs";import{prototypeBase as ne}from"../prototype-base.mjs";const re=`usage: node board.mjs <command> [--site <route>] ...
|
|
3
3
|
|
|
4
4
|
Writes a kickflip board's data files. --site <route> picks ~/.claude/kickflip/sites/<route>, and its
|
|
5
5
|
board.json says where the board's waves live. Without --site, waves come from ~/.claude/waves.
|
|
@@ -9,8 +9,8 @@ item add creates a ticket in the board\u2019s source unless --ticket <id> links
|
|
|
9
9
|
Internal defaults to INT; GitHub and Linear create an issue from the title and notes, Linear in the board's team and
|
|
10
10
|
project through Hub's Linear key. A linked ticket must exist in the source; an Internal one no other card holds.
|
|
11
11
|
Asks have no ticket; item set --from-ask creates one when the card has none.
|
|
12
|
-
A card moves its ticket one way:
|
|
13
|
-
|
|
12
|
+
A card moves its ticket one way: a build's start \u2192 In Progress, --pr, --pushed or a verified merge-ready build \u2192 In
|
|
13
|
+
Review, --done \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
15
|
Linear needs Hub's Linear key: settings set tickets refuses it without one, and once the key is removed the hub
|
|
16
16
|
moves every Linear board to internal tickets. A card is never left without a ticket: if Linear can't make one, the
|
|
@@ -25,12 +25,18 @@ command fails and changes nothing.
|
|
|
25
25
|
A Claude thread line, or a reply to an ask or item-message, rewrites the body of the thread's
|
|
26
26
|
queued push (tag ask-<eventId> or item-<wave>/<id>) to the message's first line, keeping
|
|
27
27
|
"\xB7 and N replies", and prints the new body. It never pushes: the thread's unread mark carries it
|
|
28
|
+
visual add <wave>/<id> --file <page.html> --title <t> a visual agent's page: copied to public/visuals/<id>/<slug>.html
|
|
29
|
+
(a new name each time), linked by a visual line in the
|
|
30
|
+
item's thread; prints the page's link
|
|
28
31
|
topic add <id> --title <t> [--summary <s>] a question topic in data/owner.json
|
|
29
32
|
question add --file <json> one question or an array of them:
|
|
30
33
|
{id, topic, question, options: [{id, label, recommended?, detail?, diagram?, practice?}],
|
|
31
|
-
source?, context?, url?, urgent?, round_of?, reask_of?, ask?, suggested?}; topic is declared, item:<wave>/<id>, item:ask:<eventId>
|
|
34
|
+
source?, context?, url?, urgent?, round_of?, reask_of?, ask?, suggested?, prototype_gate?}; topic is declared, item:<wave>/<id>, item:ask:<eventId>
|
|
32
35
|
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 question on a done
|
|
36
|
+
a board over ~/.claude/waves. A question on a done card reopens it.
|
|
37
|
+
A question on an item: topic whose card has a grill or prototype planner running is staged, not published,
|
|
38
|
+
whoever adds it: it shows nowhere until the planner finishes, fails or is stopped, when every staged question
|
|
39
|
+
on the card publishes in the order added, with one owner push. The command says "staged" and why.
|
|
34
40
|
url is http(s), or a site page such as prototype-<topic>.html?q=<id>, stored as given.
|
|
35
41
|
Prototype options keep take order, ids matching take keys; recommended: true tags the recommendation.
|
|
36
42
|
suggested: {option, card, answer} pre-checks the option the owner already chose on an earlier card, with a
|
|
@@ -41,12 +47,17 @@ command fails and changes nothing.
|
|
|
41
47
|
danger|success, to the next row's box indexes (default all); practice: ["<line>", \u2026]
|
|
42
48
|
A prototype url's topic must be recorded (prototype add), its option ids a non-empty subset of the
|
|
43
49
|
take keys in take order, without repeats, and every take file written; a hand-written prototype-*.html
|
|
44
|
-
is refused.
|
|
50
|
+
is refused. Each take is then loaded headlessly at 390px and 1280px, and the question is refused,
|
|
51
|
+
naming the take and width, if one throws, logs a console error or draws an empty stage.
|
|
52
|
+
On an item: topic, each other question answered and still waiting on Claude is
|
|
45
53
|
resolved, noting "Followed by <new id>", and printed as "resolved: <old id>". Any other unresolved
|
|
46
54
|
question on the same topic that links the same prototype is resolved, noting "Replaced by <new id>",
|
|
47
55
|
and printed as "replaced: <old id>". round_of names the question on the same topic whose
|
|
48
56
|
prototype-request this answers; the question needs a prototype url, takes the next round
|
|
49
57
|
number, and resolves that one, noting "Followed by prototype round N".
|
|
58
|
+
prototype_gate: true marks a grill's closing "Prototype round first?" question (options yes, no, on a card's
|
|
59
|
+
topic): the owner's yes moves the card to prototyping and starts its prototype planner, a no stages it
|
|
60
|
+
once no question on it is left open, the board doing both with no orchestrator step.
|
|
50
61
|
reask_of names a resolved question on the same topic that the owner asked to ask again
|
|
51
62
|
(reask-request): it resolves that one again, noting "Asked again as <new id>", printed as "resolved: <old id>".
|
|
52
63
|
ask names a builder ask the session deferred on the item (builder defer): the question puts it to the
|
|
@@ -57,6 +68,10 @@ command fails and changes nothing.
|
|
|
57
68
|
prototype page until an answer settles its round: a pick, with a note or none, or an answer to
|
|
58
69
|
an "Is this it?" confirm already on the board. A question already resolved, such as one an
|
|
59
70
|
answer with a note and no option resolved, is left as it is
|
|
71
|
+
question move <id>,<id>,\u2026 --to <wave>/<id> [--block <wave>/<id>,\u2026] merges open questions that ask one thing onto
|
|
72
|
+
one card: the first id moves to that card (topic item:<card>), the others resolve "Duplicate of
|
|
73
|
+
<card>'s question <first id>". Each --block card, in order, is blocked by the one before it (the
|
|
74
|
+
first by --to) and related to --to
|
|
60
75
|
prototype add <topic> --title <t> --take <K>:<label> records a prototype in data/prototypes.json: the board
|
|
61
76
|
[--take <K>:<label> \u2026] [--card <wave>/<id>] serves prototype-<topic>.html from its one template, with
|
|
62
77
|
the title, a back arrow to --card (else the board), the
|
|
@@ -67,6 +82,17 @@ command fails and changes nothing.
|
|
|
67
82
|
public/prototypes/<topic>/<K>.js, which exports
|
|
68
83
|
render(stage, kit) (board/public/prototype-kit.js lists kit).
|
|
69
84
|
--card starts that card: it leaves Todo
|
|
85
|
+
[--served <path> [--state <key>:<label> \u2026]] a served round: no take files; the template frames <path>
|
|
86
|
+
from the board's base prototype worktree (prototype base),
|
|
87
|
+
passing ?variant=<K>&state=<key>, then each switch by
|
|
88
|
+
message (shared/prototype-frame.js). The viewer bar lists
|
|
89
|
+
the states; key: a-z, 0-9 and "-"
|
|
90
|
+
prototype base makes the board's base prototype worktree, a detached
|
|
91
|
+
checkout of its repo's main, or moves it to main's tip,
|
|
92
|
+
keeping the rounds' pages; prints its path, whether its
|
|
93
|
+
build script is there and the listener a page loads.
|
|
94
|
+
The hub serves it while an open question links a served
|
|
95
|
+
round (docs/adr/2026-11-06-1500-a-prototype-round-is-a-page-in-the-boards-base-worktree.md)
|
|
70
96
|
prototype list one line per recorded prototype
|
|
71
97
|
prototype rm <topic> drops the record, keeping its take files; refuses while an
|
|
72
98
|
open question links it
|
|
@@ -80,9 +106,12 @@ command fails and changes nothing.
|
|
|
80
106
|
blank line and <!-- claude-bridge --> after, the marker pr
|
|
81
107
|
reads as Claude's own. Refuses words that already carry
|
|
82
108
|
either. Prints the post's url
|
|
83
|
-
item set <wave>/<id> [--
|
|
84
|
-
|
|
85
|
-
|
|
109
|
+
item set <wave>/<id> [--done --text <t> [--force]] edits the item in its wave file; --done closes the card
|
|
110
|
+
(below). A card has no status: it stores its lane, which
|
|
111
|
+
a build's start, a handover, a block and a close move,
|
|
112
|
+
and the board server moves the rest, each with a thread
|
|
113
|
+
line; --status is refused
|
|
114
|
+
[--next <t> | --clear-next] an empty --next "" clears it, as --clear-next does
|
|
86
115
|
naming another unfinished card requires it in --deps
|
|
87
116
|
[--pr <n> | --pr <owner/repo#n>]... adds a PR to the card, a bare number in the board's repo;
|
|
88
117
|
repeatable, and never replaces one. Each PR gets its own
|
|
@@ -92,7 +121,7 @@ command fails and changes nothing.
|
|
|
92
121
|
on the owner's merge. Push it first (git push origin
|
|
93
122
|
<branch>). The hub's repo fetch moves it to Done once the
|
|
94
123
|
branch is merged into the board's branch or gone from the
|
|
95
|
-
remote.
|
|
124
|
+
remote. Moves it to In Review as --pr does
|
|
96
125
|
[--gh-since <ISO time>] records the last handled GitHub event, an ISO-8601 UTC
|
|
97
126
|
time such as 2026-10-23T11:00:00Z, stored as given
|
|
98
127
|
[--from-ask <eventId> --text <t>] the ask it came from: its New card gives way to this item,
|
|
@@ -101,13 +130,20 @@ command fails and changes nothing.
|
|
|
101
130
|
from another ask (use item relate-ask), and one with no
|
|
102
131
|
state when --state is missing
|
|
103
132
|
[--state ready|grilling|prototyping] what a card from an ask does next; it reads New until set.
|
|
104
|
-
|
|
133
|
+
Staging is the board's: it stages a card on the owner's
|
|
134
|
+
Send, Implement or Reply on a ready card, and refuses
|
|
135
|
+
--state staging, bar --from-ask on a Quick build ask,
|
|
136
|
+
which files it staging for the orchestrator to judge.
|
|
137
|
+
ready refuses a card not staging or ready, a Quick
|
|
138
|
+
build ask's included, a card with an open question, and
|
|
105
139
|
a card without --size, a notes file, or the id of each
|
|
106
140
|
answered question in that file, since a builder starts
|
|
107
|
-
from it; it resolves those answered questions
|
|
141
|
+
from it; it resolves those answered questions.
|
|
142
|
+
grilling or prototyping on a staging or ready card sends
|
|
143
|
+
it back, and the board starts that round's planner
|
|
108
144
|
[--held | --unheld | --pick-up-at <time>] holds or resumes the card on the owner's words; the board
|
|
109
145
|
server records the owner's own Hold and Resume taps. A later
|
|
110
|
-
--held/--unheld overrides a tap
|
|
146
|
+
--held/--unheld overrides a tap.
|
|
111
147
|
--pick-up-at holds it until an ISO time such as
|
|
112
148
|
2026-11-04T09:00 (the Mac's local time without an
|
|
113
149
|
offset; a past time is refused), stored as UTC
|
|
@@ -131,39 +167,47 @@ command fails and changes nothing.
|
|
|
131
167
|
Claude, noting "Recorded in the card's notes", and
|
|
132
168
|
prints "resolved: <id>" for each
|
|
133
169
|
[--deps <id>,<wave>/<id>] replaces its blocked-by list; --deps "" clears it
|
|
170
|
+
[--blocked-until "<condition>"] blocks it until a condition an agent can check, which
|
|
171
|
+
the orchestrator checks on its passes over the cards;
|
|
172
|
+
--blocked-until "" clears it, which threads and emits
|
|
173
|
+
card-unblocked {key} once nothing else blocks it
|
|
174
|
+
[--block-reason "<reason>"] why it is blocked, by its deps or its condition, which
|
|
175
|
+
goes when the block does; --block-reason "" clears it
|
|
176
|
+
A block on a card In Progress sends it back to Todo,
|
|
177
|
+
stopping its board-launched builder (refused for a
|
|
178
|
+
session's own builder); one In Review stays, unlanded
|
|
134
179
|
[--parent <wave>/<id>] sets its Parent, refusing itself or a card below it;
|
|
135
180
|
--parent "" clears it
|
|
136
181
|
[--related <id>,<wave>/<id>] replaces the cards it is Related to, which both cards
|
|
137
182
|
show; --related "" clears it, on both
|
|
183
|
+
[--relate <id>,<wave>/<id>] adds cards to its Related to, keeping the rest
|
|
184
|
+
[--unrelate <id>,<wave>/<id>] takes cards off its Related to, on both cards; neither
|
|
185
|
+
goes with --related
|
|
138
186
|
[--install <url> [--install-sha <sha>] a device build the owner installs from the card's Install
|
|
139
187
|
[--install-label <text>] | --clear-install] button, stamped built_at now: itms-services://?action=
|
|
140
188
|
download-manifest&url=https://\u2026/manifest.plist for iOS,
|
|
141
189
|
an https:// .apk URL for Android. --install-sha is the
|
|
142
190
|
head it was built from: the badge reads "Install (stale)"
|
|
143
191
|
once the branch moves past it. Hidden once the card is Done
|
|
144
|
-
[--text <t>] with --
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
builder start for a build
|
|
148
|
-
questions, noting "Acted on: build started".
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
building and fixing count as working only while a builder record on the card is open.
|
|
156
|
-
--pr clears building/fixing and closes ordinary builders; install builders keep running.
|
|
157
|
-
checking is a builder's hand-back of work it couldn't land: In Progress, reading "Orchestrator
|
|
158
|
-
review", until the owning session takes it on; it unblocks nothing.
|
|
192
|
+
[--text <t>] with --done, required on a site: the card's closing line,
|
|
193
|
+
written last in its thread, saying why it closed (merged,
|
|
194
|
+
dropped, superseded)
|
|
195
|
+
builder start for a build moves the card to In Progress with "Moved to In Progress: a build
|
|
196
|
+
started", resolving its answered questions, noting "Acted on: build started". It refuses while
|
|
197
|
+
the item is held or a dep isn't done, and while the newest owner answer to its questions that
|
|
198
|
+
link a prototype-*.html page picks no take, which leaves the card prototyping with Next
|
|
199
|
+
"Prototype again" until the next round's pick; a pick's note is the build's guidance.
|
|
200
|
+
--pr closes ordinary builders and moves the card to In Review; install builders keep running.
|
|
201
|
+
A builder's hand-back of work it couldn't land (builder done with judgment calls) keeps the card
|
|
202
|
+
In Progress, reading "Orchestrator review", until the owning session lands it; it unblocks nothing.
|
|
159
203
|
The board server records the owner's card-done in the wave file itself.
|
|
160
|
-
|
|
161
|
-
--status done, merged or dropped closes the card: it resolves each of its questions (topic
|
|
204
|
+
--done moves the card to Done and its ticket to Done: it resolves each of its questions (topic
|
|
162
205
|
item:<wave>/<id>) that has an answer, noting "Closed with the card", and prints "resolved: <id>"
|
|
163
206
|
for each. It refuses while one has no answer yet; [--force] closes it and resolves that one too.
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
207
|
+
It prints "unblocked: <wave>/<id>" and appends card-unblocked {key} once per unblocking,
|
|
208
|
+
including held cards: start each unheld card next. It refuses a card the owner reopened: it
|
|
209
|
+
closes again only on the owner's Done
|
|
210
|
+
item add <wave>[/<id>] --title <t> [--notes <t>] a new card, unstarted; never
|
|
167
211
|
[--ticket <id>] [--deps <id>,<wave>/<id>] hand-edit wave.json to add one. Refuses an id the wave has.
|
|
168
212
|
[--notes-file <file>] --plan is an alias of --notes-file
|
|
169
213
|
[--pick-up-at <time>] holds it until then, as item set's --pick-up-at
|
|
@@ -172,6 +216,11 @@ command fails and changes nothing.
|
|
|
172
216
|
another card's thread, grill or answer: it reads as a
|
|
173
217
|
follow-up, and its Parent lists it in Children. A card
|
|
174
218
|
from the + or New task has none
|
|
219
|
+
[--state grilling|prototyping] files it at once, as item set's --state; the session's
|
|
220
|
+
[--relate <id>,<wave>/<id>] own form, linking it with --relate in the same call.
|
|
221
|
+
Never ready: a card reaches ready through staging.
|
|
222
|
+
Without --state it reads New and a card-added event
|
|
223
|
+
brings in intake triage, which links and files it
|
|
175
224
|
Without /<id>, the card takes its ticket's id (GH-<n> for issue #n); pass an id only to import a
|
|
176
225
|
card. Prints "<label> added as <key>": use the key in later commands
|
|
177
226
|
item relate-ask <eventId> <wave>/<id> --summary <t> folds an ask about an existing item into it: the ask's
|
|
@@ -179,11 +228,15 @@ command fails and changes nothing.
|
|
|
179
228
|
thread marked Moved, then the summary line. Again with
|
|
180
229
|
another item moves it there
|
|
181
230
|
ask progress <eventId> [--next <text>] keeps an answered ask in In Review, --next as its next
|
|
182
|
-
line, until the owner's ask-done (
|
|
183
|
-
|
|
231
|
+
line, until the owner's ask-done (the Done beside Reply
|
|
232
|
+
in its thread) or ask done is newer. Use it when the answer
|
|
184
233
|
waits on the owner, such as a pick between prototypes
|
|
185
234
|
ask done <eventId> moves the ask to Done, as the owner's Done button does
|
|
186
235
|
ask unfold <eventId> moves a folded ask back out to a card of its own
|
|
236
|
+
intake handoff <eventId> --text <why> an intake triage run unsure how to sort a new task, a
|
|
237
|
+
message in an ask's thread or an agent-added card hands it
|
|
238
|
+
to the orchestrator: an intake-handoff event, whose wait
|
|
239
|
+
wakes the session, and a status line in its thread
|
|
187
240
|
ask move <eventId> --to <route> sends a New ask to another board in the hub's manifest:
|
|
188
241
|
a New card there with the owner's text, attachments and
|
|
189
242
|
"Moved from <this board>", which wakes its session; here the
|
|
@@ -191,11 +244,6 @@ command fails and changes nothing.
|
|
|
191
244
|
A card the ask became is dropped, unless it has started.
|
|
192
245
|
Refuses a route that isn't another board, and an ask
|
|
193
246
|
that isn't New
|
|
194
|
-
checked [--every <minutes> | --idle] data/checked.json: GitHub was just checked, next in
|
|
195
|
-
<minutes> (default 5, the away cadence); run it at the
|
|
196
|
-
end of each GitHub tick.
|
|
197
|
-
--idle: the board shows no open PR, so never late; with
|
|
198
|
-
neither flag it is idle while no card has an open PR.
|
|
199
247
|
Then sends the queued pushes, each on its own, unless
|
|
200
248
|
pushes are held (overnight, snoozed, the weekend or off)
|
|
201
249
|
lineup create <slug> --title <t> --goal <t> a wave, in the owner's words: a named set of cards in
|
|
@@ -237,28 +285,36 @@ command fails and changes nothing.
|
|
|
237
285
|
slack Slack identity as JSON; imports slack-bridge config once
|
|
238
286
|
slack set [--self-dm D..] [--user U..] sets hub Slack identity in ~/.claude/kickflip/slack.json
|
|
239
287
|
[--domain x] [--timezone Area/City]
|
|
240
|
-
review sent <wave>/<id> <reviewer> --ts <ts> records the PR link's message in the item
|
|
241
|
-
--channel <dm>
|
|
242
|
-
review react <wave>/<id> <reviewer> <emoji> records a reaction once on that reviewer's link
|
|
243
288
|
integrations readiness rows as one JSON line
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
review due one JSON line of the Slack sends
|
|
247
|
-
{
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
289
|
+
review updated <wave>/<id> --text "<what changed>" queues \u201CUpdated: <what changed>\u201D in the thread of every
|
|
290
|
+
posted link on the card's PRs; the hub's next poll sends it
|
|
291
|
+
review due one JSON line of the Slack sends the hub owes, from local state
|
|
292
|
+
and data/pr-slack.json: {links: [{pr, key, reviewer,
|
|
293
|
+
slack_user, head, text}], replies: [{pr, id, reviewer,
|
|
294
|
+
channel, thread_ts, text}], reactions: [{pr, reviewer,
|
|
295
|
+
channel, ts, emoji}], digest: review digest's answer while
|
|
296
|
+
due, else null}. links: each open, non-draft PR's link to
|
|
297
|
+
each reviewer without one; reactions: git_approved once
|
|
298
|
+
anyone approved, git_merged on merge, x on close. The hub
|
|
299
|
+
sends and records each. While Slack isn't ready: {waiting}
|
|
300
|
+
and nothing owed
|
|
301
|
+
review post <pr> --file <review.md> posts the session's words as one Comment review (never an
|
|
302
|
+
--read-at <time> approval or a request for changes) on an incoming PR at
|
|
303
|
+
its listed head, as the board's github-login (else gh's
|
|
304
|
+
account), under "\u{1F916} Claude review from the <board title>
|
|
305
|
+
board" and the <!-- claude-bridge --> marker, and records
|
|
306
|
+
it posted with the review's link. Refuses words that carry
|
|
307
|
+
the header or marker, and a PR that got
|
|
308
|
+
another review after that time. The PR must be one the hub
|
|
258
309
|
listed in data/incoming.json or reviewed before
|
|
259
|
-
review
|
|
260
|
-
|
|
261
|
-
|
|
310
|
+
review record <pr> --state posted|needs the session's review of an incoming PR that was not posted
|
|
311
|
+
--text <markdown> | --file <path> through review post, in data/reviews.json: posted for a PR
|
|
312
|
+
it chose to say nothing on, needs while it waits on the
|
|
313
|
+
owner, which puts it in Needs you and pushes. Same PRs as
|
|
314
|
+
review post
|
|
315
|
+
review digest pending reviews as JSON, one item per PR; a reviewer is
|
|
316
|
+
owed the day's digest weekdays 09:00-17:00 in the Slack
|
|
317
|
+
identity timezone until data/review-digests.json has it all
|
|
262
318
|
rules list the hub rules, then with --site the board's, one
|
|
263
319
|
"<hub|board> <id> <owner|claude> <text>" line each
|
|
264
320
|
rules add --text <t> [--hub] [--by claude] a rule in board.json, or with --hub in the hub's
|
|
@@ -269,7 +325,7 @@ command fails and changes nothing.
|
|
|
269
325
|
settings the board's settings as JSON; flags default off, optional
|
|
270
326
|
settings appear when set: {"auto_merge": false,
|
|
271
327
|
"auto_build": false, "auto_pull": false,
|
|
272
|
-
"max_implementers": 10,
|
|
328
|
+
"max_implementers": 10, "max_planners": 3,
|
|
273
329
|
"auto_triage": false, "auto_review": false,
|
|
274
330
|
"idle_notification": false, "skip_draft": false,
|
|
275
331
|
"prs": true, "github_login": "owner"}
|
|
@@ -285,7 +341,7 @@ command fails and changes nothing.
|
|
|
285
341
|
pushed, which needs Push main after a land granted.
|
|
286
342
|
A local board never opens PRs. Without auto-merge, a
|
|
287
343
|
card with no PR waits in In Review for the owner's Merge
|
|
288
|
-
build <wave>/<id> --site <route> asks the hub to run this card's
|
|
344
|
+
build <wave>/<id> --site <route> asks the hub to run this card's build script; prints
|
|
289
345
|
--asked "<owner's words>" started and exits. install is an alias. Only the owner's
|
|
290
346
|
Serve / Build and Auto-build start a card run, so --asked
|
|
291
347
|
must quote the owner's ask, card message or answer note on
|
|
@@ -300,18 +356,27 @@ command fails and changes nothing.
|
|
|
300
356
|
the hub asks gh api user. Pass "" to clear it
|
|
301
357
|
settings set auto-build on|off on: each push builds its card. Off by default.
|
|
302
358
|
Main builds after every merge either way
|
|
359
|
+
settings set build-target <target>|default the script the hub runs for each card and main:
|
|
360
|
+
.kickflip/build-<target>, or .kickflip/build with
|
|
361
|
+
default (unset). Board Settings' Build target picks it
|
|
303
362
|
settings set auto-pull on|off Auto implement Todo cards. on: on every board change the
|
|
304
363
|
board server starts and launches the build of the top
|
|
305
364
|
ready Todo cards nothing blocks, holds or asks about, one
|
|
306
365
|
per free implementer slot (see max-implementers), each
|
|
307
|
-
once
|
|
308
|
-
|
|
309
|
-
|
|
366
|
+
once, and launches a grill on each card that arrives in
|
|
367
|
+
Todo, up to max-planners at a time (an arrival: not ready,
|
|
368
|
+
staging or held, with no planner record and no questions
|
|
369
|
+
yet). Off by default.
|
|
310
370
|
Only on the owner's word
|
|
311
371
|
settings set max-implementers <1..99> Maximum parallel implementers, 10 by default: the cards
|
|
312
372
|
building or waiting in Orchestrator review the pull fills
|
|
313
373
|
up to. Planners and cards waiting on the owner don't count.
|
|
374
|
+
It limits the pull only: an Implement the owner asks for
|
|
375
|
+
starts past it.
|
|
314
376
|
Only on the owner's word
|
|
377
|
+
settings set max-planners <1..99> Maximum parallel planners, 3 by default: the grills and
|
|
378
|
+
prototypes running at once; arrivals past it wait in board
|
|
379
|
+
order. Only on the owner's word
|
|
315
380
|
settings set auto-review on|off on: while the board is idle and nothing is pulled, the board
|
|
316
381
|
server sends pr-review {pr, head, repo, title, author, url,
|
|
317
382
|
touches, asked} for one incoming PR at a time that touches
|
|
@@ -349,15 +414,16 @@ command fails and changes nothing.
|
|
|
349
414
|
opened, it keeps that record's kind and worktree.
|
|
350
415
|
[--dispatch] --dispatch: the session runs this right before it launches
|
|
351
416
|
an agent of any kind (build, prototype, grill) so the record
|
|
352
|
-
is open at launch and the card reads
|
|
417
|
+
is open at launch and the card reads starting; the agent's
|
|
353
418
|
own builder start takes it up. On a record the Build tap
|
|
354
419
|
opened it changes nothing and prints "already open". The
|
|
355
420
|
record keeps dispatched: tap or session, whoever opened it.
|
|
356
421
|
--agent <other name> refuses while the tap's main record
|
|
357
422
|
is open and not taken up.
|
|
358
423
|
A build's start clears the card's Next.
|
|
359
|
-
A build (--kind build, claude or codex) on
|
|
360
|
-
|
|
424
|
+
A build (--kind build, claude or codex) on a card not done
|
|
425
|
+
and not In Progress moves it to In Progress first,
|
|
426
|
+
refusing what item set's build start refuses;
|
|
361
427
|
Prints "ref: <ref>", the card's label, which commit subjects start with.
|
|
362
428
|
Resolves the card's answered questions, noting "Acted on:
|
|
363
429
|
builder started"
|
|
@@ -382,10 +448,9 @@ command fails and changes nothing.
|
|
|
382
448
|
the card's status shows the newest one from an agent still working. builder progress refuses
|
|
383
449
|
without an open record for that agent, as builder call does
|
|
384
450
|
builder done <wave>/<id> [--agent <name>] marks that agent's record done; the item's thread
|
|
385
|
-
[--next <t>] [--text <what you checked>] shows when it started and finished
|
|
386
|
-
when the card has no open builder records left. On a
|
|
451
|
+
[--next <t>] [--text <what you checked>] shows when it started and finished. On a
|
|
387
452
|
record with unverified judgment calls it hands the card
|
|
388
|
-
back:
|
|
453
|
+
back: its records close marked handed back, with Next "Answer the builder's
|
|
389
454
|
questions" when a call starts "Question:", else "Verify
|
|
390
455
|
the builder's judgment calls"; --next is refused there.
|
|
391
456
|
Else, on a board, the main build's record that keeps a
|
|
@@ -398,13 +463,15 @@ command fails and changes nothing.
|
|
|
398
463
|
owner's merge (item set --pushed). A finish that fails
|
|
399
464
|
refuses with the reason and leaves the record open; one
|
|
400
465
|
refused while another agent's record is open names it.
|
|
466
|
+
A helper's done takes --text, its hand-back, which agent
|
|
467
|
+
wait prints.
|
|
401
468
|
--next stops a build without finishing it. Closing the
|
|
402
469
|
last record of a card left In Progress with no Next is
|
|
403
470
|
refused without --next, which sets it
|
|
404
471
|
[--owner-step <t>] a step only the owner can do once the code lands: the card
|
|
405
472
|
then waits in In Review with it as its Next, its ticket
|
|
406
473
|
open, until the owner's Done; refused on a card already
|
|
407
|
-
done
|
|
474
|
+
done. land takes it too
|
|
408
475
|
[--check "<name> <args>"] with --owner-step: a read-only check on the step's final
|
|
409
476
|
evidence, which the hub's poll runs on each check once the
|
|
410
477
|
code lands, closing the card when it passes: tag
|
|
@@ -415,15 +482,68 @@ command fails and changes nothing.
|
|
|
415
482
|
arguments are refused. Three runs in a row that can't run
|
|
416
483
|
mark it broken and append owner-check-broken {key, check,
|
|
417
484
|
error}; fix it with item set --check. land takes it too
|
|
485
|
+
[--updated "<what changed>" | --updated none] on a card whose PR link a reviewer already has, done
|
|
486
|
+
refuses without it: the text queues \u201CUpdated: <what
|
|
487
|
+
changed>\u201D in each link's thread, as review updated does;
|
|
488
|
+
none sends nothing, for a change only to comments, names,
|
|
489
|
+
docs or tests
|
|
418
490
|
builder takes --dir <dir> for a wave file in <dir>; its records go in <dir>/agents
|
|
491
|
+
builder stop <wave>/<id> the orchestrator's stop of the card's run the board launched,
|
|
492
|
+
its headless builder, lander or rebase run, with every process
|
|
493
|
+
under it: the record stays open, marked stopped, so the server
|
|
494
|
+
relaunches nothing and the card stays In Progress reading
|
|
495
|
+
"build stopped by orchestrator" ("land \u2026" while it lands),
|
|
496
|
+
waiting on the orchestrator; the same words go in its thread.
|
|
497
|
+
Prints what it stopped, one process a line. Refuses a planner,
|
|
498
|
+
a builder a session started itself, or none running
|
|
499
|
+
land stop <wave>/<id> the orchestrator's stop of the card's land and its suite: one a
|
|
500
|
+
headless builder or lander runs stops that agent as builder
|
|
501
|
+
stop does; the land a held card's Merge tap started stops
|
|
502
|
+
alone, leaving the land queue, and the card's Merge shows
|
|
503
|
+
again, reading "land stopped by orchestrator". Refuses while
|
|
504
|
+
the land fast-forwards main, or a land in a session the board
|
|
505
|
+
didn't launch
|
|
506
|
+
land queue the host's land queue in order: the land running, then each
|
|
507
|
+
waiting, "(bumped)" on one a bump moved
|
|
508
|
+
item bump <wave>/<id> the card's bump, as the owner's bump icon sends it (card-bump;
|
|
509
|
+
item unbump <wave>/<id> card-unbump). A bumped card's land goes ahead in the land queue
|
|
510
|
+
of every land not bumped since, the latest bump first, without
|
|
511
|
+
stopping the one running; a bump of a queued land says so in
|
|
512
|
+
its thread ("land moved first by orchestrator")
|
|
419
513
|
lander <wave>/<id> relaunch the headless lander of a card it stopped on (a usage
|
|
420
|
-
pause ends one):
|
|
421
|
-
server launches a fresh lander; refused unless
|
|
514
|
+
pause ends one): its open record closes as handed back and
|
|
515
|
+
the server launches a fresh lander; refused unless a
|
|
516
|
+
builder record on the card is open
|
|
422
517
|
builder pick <wave>/<id> who builds the card, from the hub's Codex share and the pause
|
|
423
518
|
line, and for claude which model, from the hub's Claude model
|
|
424
519
|
and the card's --size and --model: "codex: <reason>" or
|
|
425
520
|
"claude sonnet: <reason>". Pass that kind to builder start
|
|
426
521
|
--kind, and launch that model
|
|
522
|
+
audit [<wave>/<id>] the board's audit log of the last 30 days, data/audit.jsonl:
|
|
523
|
+
each call the code made on its own (builder-pick, pull,
|
|
524
|
+
round-planner, launch-held, arrival-planner, relaunch,
|
|
525
|
+
lander, triage, pr-review, push, pick-up, auto-merge,
|
|
526
|
+
ready-draft, session-resume, compact) as "<at> <call>
|
|
527
|
+
[<key>]: <result>. <reason>" and the inputs it read; with
|
|
528
|
+
a card, only that card's. The hub's own: kickflip audit
|
|
529
|
+
agent launch <wave>/<id> --name <n> --task <t> any session's helper on an open card, an agent the board server
|
|
530
|
+
[--writes] [--model <opus|sonnet>] launches headless with the task as its brief, under its own
|
|
531
|
+
name (lowercase, "-", not main or visual-\u2026), its record
|
|
532
|
+
<id>@<name> requested until then. A read-only helper launches
|
|
533
|
+
at once and takes no implementer slot; one with --writes
|
|
534
|
+
takes one, waits while Maximum parallel implementers is
|
|
535
|
+
reached, ahead of the pull and the topmost card's first,
|
|
536
|
+
and works in its own worktree, branch <id>-<name> from the
|
|
537
|
+
card's, so it needs the card's worktree. --model else
|
|
538
|
+
builder pick's Claude model. Its lines carry its name
|
|
539
|
+
agent wait <wave>/<id> --name <n> [--agent <me>] blocks until the helper's builder done, then prints its
|
|
540
|
+
[--timeout <minutes>] --text and, for one that writes, its branch. Meanwhile
|
|
541
|
+
--agent's record (main by default) takes no slot. Refuses
|
|
542
|
+
after --timeout (9) minutes, saying whether it runs or waits
|
|
543
|
+
for a slot, and once its process is gone without done
|
|
544
|
+
agent merge <wave>/<id> --name <n> merges a done helper's branch into the card's worktree and
|
|
545
|
+
removes the helper's worktree and branch; on a conflict it
|
|
546
|
+
names the files: resolve, commit and run it again
|
|
427
547
|
builder call <wave>/<id> <judgment call> appends one line to the agent's open builder record: a
|
|
428
548
|
[--agent <name>] judgment call as the builder makes it, or a "Question: \u2026"
|
|
429
549
|
for the session; refused with no open record. A builder start
|
|
@@ -447,12 +567,12 @@ command fails and changes nothing.
|
|
|
447
567
|
the orchestrator"
|
|
448
568
|
builder verified <wave>/<id> [--agent <name>] the session's mark that it checked the record's calls; land
|
|
449
569
|
[--note <t>] refuses while any record on the card has a call after it.
|
|
450
|
-
On a handed-back build (
|
|
570
|
+
On a handed-back build (not done, no builder open, every
|
|
451
571
|
call verified) it finishes the card in the same step, as
|
|
452
572
|
builder done does, --note its closing line (else "Judgment
|
|
453
573
|
calls verified."). While a question on the card is open it
|
|
454
574
|
says so and the card waits; a finish that fails keeps the
|
|
455
|
-
mark, the card
|
|
575
|
+
mark, the card handed back, and says why
|
|
456
576
|
land <wave>/<id> --text <t> [--repo <dir>] brings the worktree its builder record keeps (else
|
|
457
577
|
[--dir <dir>]
|
|
458
578
|
~/.claude/kickflip/worktrees/<id> of --repo) into the board's
|
|
@@ -470,7 +590,7 @@ command fails and changes nothing.
|
|
|
470
590
|
branch. Without --site it never pushes. One land runs at a time on the host: a
|
|
471
591
|
second refuses, naming the card landing. With --site, --text <what was checked>
|
|
472
592
|
is required, and once it lands the card closes as item set
|
|
473
|
-
--
|
|
593
|
+
--done --text does; it refuses first what that close
|
|
474
594
|
would. With --site it is builder done's finish: the push
|
|
475
595
|
after a land, and on a GitHub or plain-remote board the
|
|
476
596
|
branch push (and draft PR) instead of the fast-forward.
|
|
@@ -510,9 +630,9 @@ command fails and changes nothing.
|
|
|
510
630
|
hub a session's prompt, so any other --kind is refused. It records the
|
|
511
631
|
push in data/push-state.json only when kickflip push
|
|
512
632
|
succeeds, and exits with kickflip push's code. Without --now
|
|
513
|
-
it queues until the
|
|
633
|
+
it queues until the hub's
|
|
514
634
|
next check; --now sends at once unless pushes are held
|
|
515
635
|
(overnight, snoozed or the weekend). --urgent: a
|
|
516
636
|
production fault only; it beats those holds. While
|
|
517
637
|
notifications are off every push is dropped, urgent too
|
|
518
|
-
KICKFLIP_PUSH_CMD='["<cmd>","<arg>"]' replaces \`node ../../bin/kickflip.mjs push\`; the flags follow it`;class l extends Error{static{r(this,"UsageError")}}const Z=new Set(["clear-status","clear-next","urgent","now","clear-eta","held","unheld","idle","hub","clear-install","force","worktree","dry-run","dispatch"]);function ee(e){const s=Object.create(null),t=[];for(let a=0;a<e.length;a++){const i=e[a];if(!i.startsWith("--")){t.push(i);continue}const d=i.slice(2);if(Z.has(d)){if(Object.hasOwn(s,d))throw new l(`--${d} is given twice`);s[d]=!0}else if(a+1<e.length)(s[d]||=[]).push(e[++a]);else throw new l(`--${d} needs a value`)}return{flags:s,rest:t}}r(ee,"parse");function te(e,s,t){const a=new Set(s.filter(o=>o.startsWith("...")).map(o=>o.slice(3))),i=new Set(s.map(o=>o.replace(/^\.\.\./,""))),d=Object.keys(t).filter(o=>!i.has(o));if(d.length)throw new l(`${e} does not take ${d.map(o=>"--"+o).join(", ")}`);return Object.fromEntries(Object.entries(t).map(([o,c])=>{if(c===!0||a.has(o))return[o,c];if(c.length>1)throw new l(`--${o} is given twice`);return[o,c[0]]}))}r(te,"flagsOf");const se=["html","text","note","notes","summary","title","body","next","progress","install-label","confirmed","asked"],ae=/^(?:\/|~\/|\.{1,2}\/)\S*\.(?:html?|md|json|txt)$/;function ne(e){const s=e.trim();if(!s||/\s/.test(s))return!1;if(ae.test(s))return!0;try{return m.statSync(s).isFile()}catch{return!1}}r(ne,"looksLikePath");function re(e,s){for(const t of se){if(typeof s[t]!="string"||!ne(s[t]))continue;const a=e.includes("file")?`; for a file use --file ${s[t].trim()}`:"";throw new l(`--${t} takes text, not a file path (${s[t].trim()})${a}`)}if(typeof s.file=="string"&&s.file!=="-"&&!m.existsSync(s.file))throw new l(`--file ${s.file}: no such file`)}r(re,"checkInputs");const f=r(e=>m.readFileSync(e==="-"?0:e,"utf8"),"readInput");function $(e){if(!e)throw new l("--file <path> is required");try{return JSON.parse(f(e))}catch(s){throw new n.BoardError(`${e}: ${s.message}`)}}r($,"readJSONInput");function R(e){if(e.plan!==void 0&&e["notes-file"]!==void 0)throw new l("pass --notes-file or --plan, not both");return e["notes-file"]??e.plan}r(R,"notesFileOf");function O(e){if(e.note!==void 0&&e.file!==void 0)throw new l("pass --note or --file, not both");return e.file===void 0?e.note:f(e.file)}r(O,"noteOf");const k={pr:[["wave/id"],[],async(e,[s])=>({lines:(await n.prReport(e,s)).map(t=>JSON.stringify(t))})],"pr comment":[["pr"],["body"],(e,[s],t)=>{if(t.body===void 0)throw new l('pr comment needs --body "<what Claude says>"');return n.postPR(e,s,{body:t.body})}],"pr reply":[["pr","comment-id"],["body"],(e,[s,t],a)=>{if(a.body===void 0)throw new l('pr reply needs --body "<what Claude says>"');return n.postPR(e,s,{replyTo:t,body:a.body})}],reply:[["eventId"],["html","file"],(e,[s],t)=>{if(t.html===void 0==(t.file===void 0))throw new l("reply takes --html or --file");return n.writeReply(e,s,t.html??f(t.file))}],thread:[["wave/id"],["text","link","who","via"],(e,[s],t)=>n.appendThread(e,s,t)],"topic add":[["id"],["title","summary"],(e,[s],t)=>n.addTopic(e,{id:s,...t})],"question add":[[],["file"],async(e,s,t)=>{const a=$(t.file);return{...await n.addQuestions(e,a),asked:[a].flat()}}],"question resolve":[["id"],["note"],(e,[s],t)=>n.resolveQuestion(e,s,t.note)],"prototype add":[["topic"],["title","...take","card"],(e,[s],t)=>n.addPrototype(e,{topic:s,title:t.title,card:t.card,takes:t.take})],"prototype list":[[],[],e=>n.listPrototypes(e)],"prototype rm":[["topic"],[],(e,[s])=>n.removePrototype(e,s)],"item set":[["wave/id"],["status","clear-status","next","clear-next","...pr","pushed","gh-since","from-ask","state","held","unheld","pick-up-at","notes-file","plan","deps","parent","related","install","install-sha","install-label","clear-install","title","size","model","force","ticket","text","check"],async(e,[s],t,a)=>(t.status==="done"&&e&&await n.checkNotReopened(e,s),n.setItem(e,s,{status:t.status,clearStatus:t["clear-status"],next:t.next,clearNext:t["clear-next"],pr:t.pr,pushed:t.pushed,ghSince:t["gh-since"],fromAsk:t["from-ask"],state:t.state,held:t.held,unheld:t.unheld,pickUpAt:t["pick-up-at"],notesFile:R(t),deps:t.deps,parent:t.parent,related:t.related,install:t.install,installSha:t["install-sha"],installLabel:t["install-label"],clearInstall:t["clear-install"],title:t.title,size:t.size,model:t.model,force:t.force,ticket:t.ticket,text:t.text,check:t.check},a))],"item add":[["wave[/id]"],["title","notes","deps","notes-file","plan","ticket","pick-up-at","parent","size","model"],(e,[s],t,a)=>n.addItem(e,s,{title:t.title,notes:t.notes,deps:t.deps,notesFile:R(t),ticket:t.ticket,pickUpAt:t["pick-up-at"],parent:t.parent,size:t.size,model:t.model},a)],"item relate-ask":[["eventId","wave/id"],["summary"],(e,[s,t],a)=>n.relateAsk(e,s,t,a.summary)],"ask progress":[["eventId"],["next"],(e,[s],t)=>n.markAsk(e,s,"progress",t.next)],"ask done":[["eventId"],[],(e,[s])=>n.markAsk(e,s,"done")],"ask unfold":[["eventId"],[],(e,[s])=>n.unfoldAsk(e,s)],"ask move":[["eventId"],["to"],(e,[s],t)=>n.moveAsk(e,s,t.to)],"tickets backfill":[[],["dry-run"],(e,s,t,a)=>n.backfillTickets(e,{dryRun:t["dry-run"]},a)],"lineup create":[["slug"],["title","goal","...phase","note","file"],(e,[s],t)=>n.createLineup(e,s,{title:t.title,goal:t.goal,phases:t.phase,note:O(t)})],"lineup add":[["slug","wave/id"],["after"],(e,[s,t],a)=>n.changeLineup(e,s,{add:t,after:a.after})],"lineup remove":[["slug","wave/id"],[],(e,[s,t])=>n.changeLineup(e,s,{remove:t})],"lineup set":[["slug"],["title","goal","note","file","...phase"],(e,[s],t)=>n.changeLineup(e,s,{title:t.title,goal:t.goal,note:O(t),phases:t.phase})],"lineup finish":[["slug"],[],(e,[s])=>n.finishLineup(e,s)],slack:[[],[],()=>({lines:[JSON.stringify(L())]})],"slack set":[[],["self-dm","user","domain","timezone"],async(e,s,t)=>{const a={"self-dm":"self_dm",user:"owner_slack_user",domain:"slack_domain",timezone:"timezone"};try{return{lines:[JSON.stringify(await D(Object.fromEntries(Object.entries(t).map(([i,d])=>[a[i],d]))))]}}catch(i){throw new n.BoardError(i.message)}}],integrations:[[],[],async()=>({lines:[JSON.stringify(await B.rows())]})],"integrations report":[[],["slack"],async(e,s,t)=>{if(!["yes","no"].includes(t.slack))throw new l("integrations report takes --slack yes|no");if(!e)throw new n.BoardError("integrations report needs --site <route>");const a=g.basename(e);return H(t.slack==="yes",{route:a,title:X().sites?.[a]?.title||(await n.boardConfig(e)).title}),{lines:[`Slack connector reported: ${t.slack}.`]}}],"review sent":[["wave/id","reviewer"],["ts","channel"],(e,[s,t],a)=>n.reviewSent(e,s,t,a)],"review react":[["wave/id","reviewer","emoji"],[],(e,[s,t,a])=>n.reviewReact(e,s,t,a)],"review record":[["pr"],["state","text","file","url"],(e,[s],t)=>{if(t.text===void 0==(t.file===void 0))throw new l("review record takes --text or --file");return n.recordReview(e,s,{state:t.state,text:t.text??f(t.file),url:t.url})}],"review digest":[[],[],async(e,s,t,a)=>({lines:[JSON.stringify(await n.reviewDigest(e,a))]})],"review due":[[],[],async(e,s,t,a)=>({lines:[JSON.stringify(await n.reviewDue(e,a))]})],"review digest sent":[["reviewer"],["ts","channel","items"],(e,[s],t,a)=>n.reviewDigestSent(e,s,t,a)],"reviewers set":[[],["file"],(e,s,t)=>n.setReviewers(e,$(t.file))],"reviewers add":[[],["file"],(e,s,t)=>n.addReviewers(e,$(t.file))],"reviewers remove":[["...name"],[],(e,s)=>n.removeReviewers(e,s)],"rules list":[[],[],e=>n.listRules(e)],"rules add":[[],["text","hub","by"],(e,s,t)=>n.addRuleTo(e,t)],"rules edit":[["id"],["text","hub"],(e,[s],t)=>n.editRuleIn(e,s,t)],"rules remove":[["id"],["hub"],(e,[s],t)=>n.removeRuleFrom(e,s,t)],settings:[[],[],async e=>({lines:[JSON.stringify(await n.readBoardSettings(e))]})],build:[["wave/id"],["asked"],async(e,[s],t)=>{if(!e)throw new n.BoardError("build needs --site <route>");n.parseKey(s);const a=await n.askedGate(e,s,t.asked);try{const i=await fetch(`http://127.0.0.1:${process.env.KICKFLIP_HUB_PORT||4500}/_hub/build`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({route:g.basename(e),key:s}),signal:AbortSignal.timeout(1e4)}),d=await i.json();if(!i.ok)throw new n.BoardError(d.error||"The hub refused the build.");return{lines:["started",...(await n.appendThread(e,s,{text:a})).lines]}}catch(i){throw i instanceof n.BoardError?i:new n.BoardError(`Could not start the build: ${i.message}`)}}],"main set":[[],["install","install-sha"],(e,s,t)=>n.setMainInstall(e,{install:t.install,installSha:t["install-sha"]})],"settings set":[["name","value"],[],(e,[s,t])=>n.setBoardSetting(e,s,t)],"builder start":[["wave/id"],["label","phase","progress","dir","agent","kind","model","eta","worktree","repo","dispatch"],(e,[s],{worktree:t,repo:a,dispatch:i,...d})=>{if(a!==void 0&&!t)throw new l("--repo goes with --worktree");const o=r(p=>W(e,s.split("/")[1],{repo:a,...p}),"make"),c=e&&d.dir===void 0&&V(e);return n.startBuilder(e,s,{...d,dispatch:i?"session":void 0,worktree:t?o:void 0,ownWorktree:c?o:void 0})}],"builder progress":[["wave/id","...text?"],["phase","text","dir","agent","eta","clear-eta"],(e,[s,...t],a)=>{if(t.length&&a.text!==void 0)throw new l("builder progress takes its text once: as words or as --text");if(!t.length&&a.text===void 0)throw new l("builder progress needs its text, as words or as --text");return n.builderProgress(e,s,a.text??t.join(" "),{...a,clearEta:a["clear-eta"]})}],"builder done":[["wave/id"],["dir","agent","next","owner-step","check","text"],async(e,[s],{"owner-step":t,check:a,text:i,...d})=>{if(t!==void 0&&(!e||d.dir))throw new l("--owner-step goes on a board card: pass --site, not --dir");if(a!==void 0&&t===void 0)throw new l("--check goes with --owner-step: it is the code check that closes the card once the step is done");t!==void 0&&await n.setItem(e,s,{ownerStep:t,check:a});const o=e&&!d.dir,c=o&&await n.handBackCalls(e,s,d);if(c)return c;if(o&&d.next===void 0&&await n.finishesCard(e,s,d)){if(i===void 0)throw new n.BoardError(`builder done finishes ${s}: pass --text "<what you checked>", or --next "<what happens next>" to stop without finishing`);return x(e,s,{text:i,ownerStep:t})}if(i!==void 0)throw new l("--text goes on a builder done that finishes the card: the main build, with a worktree and no --next");return n.stopBuilder(e,s,{...d,requireNext:!0})}],"builder pick":[["wave/id"],[],(e,[s])=>n.pickBuilder(e,s)],"builder call":[["wave/id","...text"],["dir","agent"],(e,[s,...t],a)=>n.recordCall(e,s,t.join(" "),a)],"builder calls":[["wave/id"],["dir","agent"],(e,[s],t)=>n.listCalls(e,s,t)],"builder ask":[["wave/id","...text"],["agent"],(e,[s,...t],a)=>n.askOrchestrator(e,s,t.join(" "),a)],"builder answer":[["wave/id","ask"],["text"],(e,[s,t],a)=>n.answerBuilder(e,s,t,a.text)],"builder defer":[["wave/id","ask"],["text"],(e,[s,t],a)=>n.answerBuilder(e,s,t,a.text,{deferred:!0})],"builder asks":[["wave/id"],[],(e,[s])=>n.listAsks(e,s)],"builder verified":[["wave/id"],["dir","agent","note"],async(e,[s],t)=>{await n.verifyCalls(e,s,t);const a=e&&!t.dir?await n.verifiedFinish(e,s):null;if(!a)return{};if(a.waits.length)return{lines:[`${await n.cardName(e,s)} finishes once the owner answers ${a.waits.join(", ")}`]};try{return await x(e,s,{text:t.note??"Judgment calls verified."})}catch(i){throw i instanceof n.BoardError?Object.assign(new n.BoardError(`judgment calls verified, but ${await n.cardName(e,s)} didn't finish: ${i.message}`),{exitCode:i.exitCode}):i}}],land:[["wave/id"],["repo","dir","text","owner-step","check"],async(e,[s],t)=>{const a=s.split("/")[1];if(e&&t.text===void 0)throw new l(`land needs --text "<what was checked>", which closes ${s} as done once it lands`);if(!e&&t.text!==void 0)throw new l("--text closes the card, which needs --site");if(!e&&t["owner-step"]!==void 0)throw new l("--owner-step keeps a board card open, which needs --site");if(t.check!==void 0&&t["owner-step"]===void 0)throw new l("--check goes with --owner-step: it is the code check that closes the card once the step is done");if(e&&t.dir===void 0)return x(e,s,{text:t.text,ownerStep:t["owner-step"],check:t.check,repo:t.repo});const i=e?await n.cardName(e,s):void 0,d=await z(i??s,async()=>{e&&await n.checkClose(e,s,t.text,t["owner-step"],t.check),await n.checkCallsVerified(e,s,{dir:t.dir});const o=await n.recordedWorktree(e,s,{dir:t.dir}),{repo:c,base:p}=G(e,{repo:t.repo??o?.repo});return await J({path:o?.path??g.join(F(),a),branch:o?.branch??a,repo:g.resolve(c),base:p,ref:i}),e?n.setItem(e,s,{status:"done",text:t.text,ownerStep:t["owner-step"],check:t.check}):{lines:[]}});return d.joined?{lines:[`joined the land already running for ${i??s}`]}:d}],lander:[["wave/id"],[],async(e,[s])=>{if(!e)throw new l("lander needs --site <route>");return await n.relaunchLander(e,s),{lines:[`${await n.cardName(e,s)} is back at checking; the server launches a lander for it within a minute`]}}],merge:[["wave/id"],["after-approval","confirmed"],async(e,[s],t)=>e&&await n.heldMerge(e,s)?K(e,s):n.mergeCard(e,s,{afterApproval:t["after-approval"],confirmed:t.confirmed})],"arch file":[["wave/id"],[],(e,[s])=>e?U(e,s):Promise.reject(new n.BoardError("arch file needs --site <route>"))],checked:[[],["every","idle"],(e,s,t)=>n.writeChecked(e,t.every,{idle:t.idle})],push:[[],["kind","title","body","url","tag","urgent","now"],(e,s,t)=>{if(t.kind!=="fault"&&t.kind!=="reminder")throw new l(`a session pushes only --kind fault or reminder${t.kind===void 0||t.kind===!0?"":`, not "${t.kind}"`}: the board server pushes Action needed, and news goes on the board`);if(t.title===void 0)throw new l("push needs --title");return n.sendPush(e,t)}]};k.install=k.build;const h=r((e,s)=>n.cardName(e,s),"card"),q=r((e,s)=>`${s}${e.length===1?"":"s"} ${e.join(", ")}`,"plural"),C=r(e=>e.agent===void 0||e.agent==="main"?"builder":`builder ${e.agent}`,"builderOf");function ie(e){const s=e["pick-up-at"];return[e.status,e["clear-status"]&&"status cleared",e.next!==void 0&&(e.next.trim()?"next line set":"next line cleared"),e["clear-next"]&&"next line cleared",e.pr!==void 0&&`PR #${e.pr}`,e.pushed!==void 0&&`pushed ${e.pushed}`,e["gh-since"]&&`GitHub read to ${e["gh-since"]}`,e["from-ask"]&&`from task ${e["from-ask"]}`,e.state,e.held&&"held",e.unheld&&"resumed",s!==void 0&&(s?`held until ${s}`:"resumed"),R(e)!==void 0&&"notes recorded",e.deps!==void 0&&(e.deps?`blocked by ${e.deps}`:"deps cleared"),e.parent!==void 0&&(e.parent?`parent ${e.parent}`:"parent cleared"),e.related!==void 0&&(e.related?`related to ${e.related.split(",").join(", ")}`:"related cleared"),e.install!==void 0&&"install published",e["clear-install"]&&"install cleared",e.title!==void 0&&"renamed",e.ticket!==void 0&&`ticket ${e.ticket}`,e.check!==void 0&&(e.check.trim()?`check ${e.check.trim().split(/\s+/).join(" ")}`:"check removed")].filter(Boolean).join(", ")}r(ie,"itemChanges");const oe={reply:r(async(e,[s])=>`reply written to ${await n.eventCard(e,s)}`,"reply"),thread:r(async(e,[s])=>`thread line on ${await h(e,s)}`,"thread"),"topic add":r((e,[s])=>`topic ${s} added`,"topic add"),"question add":r(async(e,s,t,{asked:a})=>{const i=[...new Set(a.map(o=>o.topic))],d=i.length!==1?"":i[0].startsWith("item:")?` on ${await h(e,i[0].slice(5))}`:` on topic ${i[0]}`;return`${q(a.map(o=>o.id),"question")} added${d}`},"question add"),"question resolve":r((e,[s])=>`question ${s} resolved`,"question resolve"),"prototype add":r(async(e,[s],t)=>`prototype ${s} recorded${t.card?` for ${await h(e,t.card)}`:""}`,"prototype add"),"prototype rm":r((e,[s])=>`prototype ${s} removed`,"prototype rm"),"item set":r(async(e,[s],t)=>`${await h(e,s)}: ${ie(t)}`,"item set"),"item add":r(async(e,s,t,{key:a})=>`${await h(e,a)} added as ${a}`,"item add"),"item relate-ask":r(async(e,[s,t])=>`task ${s} folded into ${await h(e,t)}`,"item relate-ask"),"ask progress":r(async(e,[s])=>`${await h(e,`ask:${s}`)}: in progress`,"ask progress"),"ask done":r(async(e,[s])=>`${await h(e,`ask:${s}`)}: done`,"ask done"),"ask unfold":r((e,[s])=>`task ${s} back on a card of its own`,"ask unfold"),"ask move":r((e,[s],t)=>`task ${s} moved to /${t.to}`,"ask move"),checked:r((e,s,t,{every:a})=>a===null?"GitHub checked; no open PR":`GitHub checked; next in ${a} min`,"checked"),"lineup create":r((e,[s])=>`lineup ${s} created`,"lineup create"),"lineup add":r(async(e,[s,t])=>`${await h(e,t)} added to lineup ${s}`,"lineup add"),"lineup remove":r(async(e,[s,t])=>`${await h(e,t)} taken out of lineup ${s}`,"lineup remove"),"lineup set":r((e,[s])=>`lineup ${s} updated`,"lineup set"),"lineup finish":r((e,[s])=>`lineup ${s} finished`,"lineup finish"),"review sent":r(async(e,[s,t])=>`review link to ${t} recorded on ${await h(e,s)}`,"review sent"),"review react":r(async(e,[s,t,a])=>`${a} on ${t}'s review link recorded on ${await h(e,s)}`,"review react"),"review digest sent":r((e,[s])=>`digest to ${s} recorded`,"review digest sent"),"review record":r((e,[s],t)=>`review of PR #${s} recorded: ${t.state==="needs"?"needs you":"posted"}`,"review record"),"reviewers set":r(()=>"reviewers replaced","reviewers set"),"reviewers add":r(()=>"reviewers added","reviewers add"),"reviewers remove":r((e,s)=>`${q(s,"reviewer")} removed`,"reviewers remove"),"rules add":r((e,s,t)=>`${t.hub?"hub":"board"} rule added`,"rules add"),"rules edit":r((e,[s],t)=>`${t.hub?"hub":"board"} rule ${s} reworded`,"rules edit"),"rules remove":r((e,[s],t)=>`${t.hub?"hub":"board"} rule ${s} removed`,"rules remove"),"builder start":r(async(e,[s],t,a)=>`${await h(e,s)}: ${a.alreadyOpen?"already open: the Implement tap opened its record; launch the agent, whose own builder start takes it up":`${C(t)} started`}`,"builder start"),"builder progress":r(async(e,[s])=>`progress line on ${await h(e,s)}`,"builder progress"),"builder done":r(async(e,[s],t,a)=>`${await h(e,s)}: ${a?.said??`${C(t)} done${t["owner-step"]===void 0?"":`; owner step "${t["owner-step"].trim()}"`}`}`,"builder done"),"builder call":r(async(e,[s])=>`judgment call recorded on ${await h(e,s)}`,"builder call"),"builder ask":r(async(e,[s],t,{id:a})=>`${await h(e,s)}: asked the orchestrator, ${a}`,"builder ask"),"builder answer":r(async(e,[s,t])=>`${await h(e,s)}: ${t} answered`,"builder answer"),"builder defer":r(async(e,[s,t])=>`${await h(e,s)}: ${t} deferred to the owner`,"builder defer"),"builder verified":r(async(e,[s],t,a)=>`${await h(e,s)}: judgment calls verified${a?.said?`; ${a.said}`:""}`,"builder verified"),land:r(async(e,[s],t,a)=>e?a?.said?`${await h(e,s)}: ${a.said}`:t["owner-step"]===void 0?`${await h(e,s)}: landed and done`:`${await h(e,s)}: landed; waits on the owner step "${t["owner-step"].trim()}"`:null,"land"),merge:r(async(e,[s],t,a)=>`${await h(e,s)}: ${a?.said??"merged"}`,"merge"),"arch file":r(async(e,[s])=>`findings of ${await h(e,s)} filed`,"arch file")};async function de(e,s={}){try{const{flags:t,rest:a}=ee(e),i=[3,2,1].map(u=>a.slice(0,u).join(" ")).find(u=>y(k,u)),d=y(k,i);if(!d)throw new l(a.length?`unknown command "${a.slice(0,2).join(" ")}"`:"no command given");const[o,c,p]=d,w=a.slice(i.split(" ").length),_=o.at(-1)?.startsWith("..."),T=o.at(-1)?.endsWith("?")?o.length-1:o.length;if(_?w.length<T:w.length!==o.length)throw new l(`${i} takes ${o.map(u=>`<${u.replace(/^\.\.\.|\?$/g,"")}>`).join(" ")||"no arguments"}`);const{site:A,...N}=t;if(A?.length>1)throw new l("--site is given twice");const P=A?.[0],b=te(i,c,N);re(c,b);const j=P===void 0?null:await n.siteDir(P),v=await p(j,w,b,s),S=y(oe,i),I=S&&await S(j,w,b,v);I&&console.log(I);for(const u of typeof v=="object"&&v?.lines||[])console.log(u);return 0}catch(t){if(t instanceof l)return console.error(`${t.message} (node board.mjs --help lists the commands)`),2;if(t instanceof n.BoardError)return console.error(t.message),t.exitCode??1;throw t}}if(r(de,"main"),process.argv[1]&&import.meta.url===M(m.realpathSync(process.argv[1])).href){const e=process.argv.slice(2);!e.length||e.includes("--help")||e[0]==="help"?console.log(Y):process.exitCode=await de(e)}export{Y as USAGE,de as main};
|
|
638
|
+
KICKFLIP_PUSH_CMD='["<cmd>","<arg>"]' replaces \`node ../../bin/kickflip.mjs push\`; the flags follow it`;class d extends Error{static{r(this,"UsageError")}}const ie=new Set(["done","clear-status","clear-next","urgent","now","clear-eta","held","unheld","idle","hub","clear-install","force","worktree","dry-run","dispatch","writes"]);function oe(e){const a=Object.create(null),t=[];for(let n=0;n<e.length;n++){const i=e[n];if(!i.startsWith("--")){t.push(i);continue}const o=i.slice(2);if(ie.has(o)){if(Object.hasOwn(a,o))throw new d(`--${o} is given twice`);a[o]=!0}else if(n+1<e.length)(a[o]||=[]).push(e[++n]);else throw new d(`--${o} needs a value`)}return{flags:a,rest:t}}r(oe,"parse");function de(e,a,t){const n=new Set(a.filter(l=>l.startsWith("...")).map(l=>l.slice(3))),i=new Set(a.map(l=>l.replace(/^\.\.\./,""))),o=Object.keys(t).filter(l=>!i.has(l));if(o.length)throw new d(`${e} does not take ${o.map(l=>"--"+l).join(", ")}`);return Object.fromEntries(Object.entries(t).map(([l,c])=>{if(c===!0||n.has(l))return[l,c];if(c.length>1)throw new d(`--${l} is given twice`);return[l,c[0]]}))}r(de,"flagsOf");const le=["html","text","note","notes","summary","title","body","next","progress","install-label","confirmed","asked","task"],he=/^(?:\/|~\/|\.{1,2}\/)\S*\.(?:html?|md|json|txt)$/;function ce(e){const a=e.trim();if(!a||/\s/.test(a))return!1;if(he.test(a))return!0;try{return g.statSync(a).isFile()}catch{return!1}}r(ce,"looksLikePath");function ue(e,a){for(const t of le){if(typeof a[t]!="string"||!ce(a[t]))continue;const n=e.includes("file")?`; for a file use --file ${a[t].trim()}`:"";throw new d(`--${t} takes text, not a file path (${a[t].trim()})${n}`)}if(typeof a.file=="string"&&a.file!=="-"&&!g.existsSync(a.file))throw new d(`--file ${a.file}: no such file`)}r(ue,"checkInputs");const w=r(e=>g.readFileSync(e==="-"?0:e,"utf8"),"readInput");function R(e){if(!e)throw new d("--file <path> is required");try{return JSON.parse(w(e))}catch(a){throw new s.BoardError(`${e}: ${a.message}`)}}r(R,"readJSONInput");function I(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}r(I,"notesFileOf");function C(e){if(e.note!==void 0&&e.file!==void 0)throw new d("pass --note or --file, not both");return e.file===void 0?e.note:w(e.file)}r(C,"noteOf");async function pe(e,a,{"owner-step":t,check:n,text:i,...o}){if(t!==void 0&&(!e||o.dir))throw new d("--owner-step goes on a board card: pass --site, not --dir");if(n!==void 0&&t===void 0)throw new d("--check goes with --owner-step: it is the code check that closes the card once the step is done");t!==void 0&&await s.setItem(e,a,{ownerStep:t,check:n});const l=e&&!o.dir,c=l&&await s.handBackCalls(e,a,o);if(c)return c;if(l&&o.next===void 0&&await s.finishesCard(e,a,o)){if(i===void 0)throw new s.BoardError(`builder done finishes ${a}: pass --text "<what you checked>", or --next "<what happens next>" to stop without finishing`);return $(e,a,{text:i,ownerStep:t})}if(l&&await s.isHelper(e,a,o.agent)){if(i===void 0)throw new d('a helper hands back its result: pass --text "<what you did or found>"');return s.stopBuilder(e,a,{...o,note:i})}if(i!==void 0)throw new d("--text goes on a builder done that finishes the card, the main build with a worktree and no --next, or on a helper's");return s.stopBuilder(e,a,{...o,requireNext:!0})}r(pe,"builderDone");const f={pr:[["wave/id"],[],async(e,[a])=>({lines:(await s.prReport(e,a)).map(t=>JSON.stringify(t))})],"pr comment":[["pr"],["body"],(e,[a],t)=>{if(t.body===void 0)throw new d('pr comment needs --body "<what Claude says>"');return s.postPR(e,a,{body:t.body})}],"pr reply":[["pr","comment-id"],["body"],(e,[a,t],n)=>{if(n.body===void 0)throw new d('pr reply needs --body "<what Claude says>"');return s.postPR(e,a,{replyTo:t,body:n.body})}],reply:[["eventId"],["html","file"],(e,[a],t)=>{if(t.html===void 0==(t.file===void 0))throw new d("reply takes --html or --file");return s.writeReply(e,a,t.html??w(t.file))}],thread:[["wave/id"],["text","link","who","via"],(e,[a],t)=>s.appendThread(e,a,t)],"visual add":[["wave/id"],["file","title"],(e,[a],t)=>s.addVisual(e,a,t)],"topic add":[["id"],["title","summary"],(e,[a],t)=>s.addTopic(e,{id:a,...t})],"question add":[[],["file"],async(e,a,t)=>{const n=R(t.file);return{...await s.addQuestions(e,n,process.env.KICKFLIP_RENDER_CHECK==="off"?void 0:se),asked:[n].flat()}}],"question resolve":[["id"],["note"],(e,[a],t)=>s.resolveQuestion(e,a,t.note)],"question move":[["ids"],["to","block"],(e,[a],t)=>s.moveQuestions(e,a.split(",").filter(Boolean),t.to,(t.block||"").split(",").filter(Boolean))],"prototype add":[["topic"],["title","...take","card","served","...state"],(e,[a],t)=>s.addPrototype(e,{topic:a,title:t.title,card:t.card,takes:t.take,served:t.served,states:t.state})],"prototype base":[[],[],e=>ne(e)],"prototype list":[[],[],e=>s.listPrototypes(e)],"prototype rm":[["topic"],[],(e,[a])=>s.removePrototype(e,a)],"item set":[["wave/id"],["done","status","clear-status","next","clear-next","...pr","pushed","gh-since","from-ask","state","held","unheld","pick-up-at","notes-file","plan","deps","blocked-until","block-reason","parent","related","relate","unrelate","install","install-sha","install-label","clear-install","title","size","model","force","ticket","text","check"],async(e,[a],t,n)=>(t.done&&e&&await s.checkNotReopened(e,a),s.setItem(e,a,{done:t.done,status:t.status,clearStatus:t["clear-status"],next:t.next,clearNext:t["clear-next"],pr:t.pr,pushed:t.pushed,ghSince:t["gh-since"],fromAsk:t["from-ask"],state:t.state,held:t.held,unheld:t.unheld,pickUpAt:t["pick-up-at"],notesFile:I(t),deps:t.deps,blockedUntil:t["blocked-until"],blockReason:t["block-reason"],parent:t.parent,related:t.related,relate:t.relate,unrelate:t.unrelate,install:t.install,installSha:t["install-sha"],installLabel:t["install-label"],clearInstall:t["clear-install"],title:t.title,size:t.size,model:t.model,force:t.force,ticket:t.ticket,text:t.text,check:t.check,stopRun:r(()=>ee(e,a),"stopRun")},n))],"item add":[["wave[/id]"],["title","notes","deps","notes-file","plan","ticket","pick-up-at","parent","size","model","state","relate"],(e,[a],t,n)=>s.addItem(e,a,{title:t.title,notes:t.notes,deps:t.deps,notesFile:I(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},n)],"item relate-ask":[["eventId","wave/id"],["summary"],(e,[a,t],n)=>s.relateAsk(e,a,t,n.summary)],"ask progress":[["eventId"],["next"],(e,[a],t)=>s.markAsk(e,a,"progress",t.next)],"ask done":[["eventId"],[],(e,[a])=>s.markAsk(e,a,"done")],"intake handoff":[["eventId"],["text"],(e,[a],t)=>s.intakeHandoff(e,a,t.text)],"ask unfold":[["eventId"],[],(e,[a])=>s.unfoldAsk(e,a)],"ask move":[["eventId"],["to"],(e,[a],t)=>s.moveAsk(e,a,t.to)],"tickets backfill":[[],["dry-run"],(e,a,t,n)=>s.backfillTickets(e,{dryRun:t["dry-run"]},n)],"lineup create":[["slug"],["title","goal","...phase","note","file"],(e,[a],t)=>s.createLineup(e,a,{title:t.title,goal:t.goal,phases:t.phase,note:C(t)})],"lineup add":[["slug","wave/id"],["after"],(e,[a,t],n)=>s.changeLineup(e,a,{add:t,after:n.after})],"lineup remove":[["slug","wave/id"],[],(e,[a,t])=>s.changeLineup(e,a,{remove:t})],"lineup set":[["slug"],["title","goal","note","file","...phase"],(e,[a],t)=>s.changeLineup(e,a,{title:t.title,goal:t.goal,note:C(t),phases:t.phase})],"lineup finish":[["slug"],[],(e,[a])=>s.finishLineup(e,a)],slack:[[],[],()=>({lines:[JSON.stringify(D())]})],"slack set":[[],["self-dm","user","domain","timezone"],async(e,a,t)=>{const n={"self-dm":"self_dm",user:"owner_slack_user",domain:"slack_domain",timezone:"timezone"};try{return{lines:[JSON.stringify(await M(Object.fromEntries(Object.entries(t).map(([i,o])=>[n[i],o]))))]}}catch(i){throw new s.BoardError(i.message)}}],integrations:[[],[],async()=>({lines:[JSON.stringify(await H.rows())]})],"review updated":[["wave/id"],["text"],(e,[a],t)=>s.reviewUpdated(e,a,t.text)],"review record":[["pr"],["state","text","file"],(e,[a],t)=>{if(t.text===void 0==(t.file===void 0))throw new d("review record takes --text or --file");return s.recordReview(e,a,{state:t.state,text:t.text??w(t.file)})}],"review post":[["pr"],["file","read-at"],(e,[a],t)=>{if(t.file===void 0)throw new d("review post needs --file <review.md>");if(t["read-at"]===void 0)throw new d("review post needs --read-at <time>, when you read the PR, so a review that landed since is caught");return s.postIncomingReview(e,a,{words:w(t.file),readAt:t["read-at"]})}],"review digest":[[],[],async(e,a,t,n)=>({lines:[JSON.stringify(await s.reviewDigest(e,n))]})],"review due":[[],[],async(e,a,t,n)=>({lines:[JSON.stringify(await s.reviewDue(e,n))]})],"reviewers set":[[],["file"],(e,a,t)=>s.setReviewers(e,R(t.file))],"reviewers add":[[],["file"],(e,a,t)=>s.addReviewers(e,R(t.file))],"reviewers remove":[["...name"],[],(e,a)=>s.removeReviewers(e,a)],"rules list":[[],[],e=>s.listRules(e)],"rules add":[[],["text","hub","by"],(e,a,t)=>s.addRuleTo(e,t)],"rules edit":[["id"],["text","hub"],(e,[a],t)=>s.editRuleIn(e,a,t)],"rules remove":[["id"],["hub"],(e,[a],t)=>s.removeRuleFrom(e,a,t)],settings:[[],[],async e=>({lines:[JSON.stringify(await s.readBoardSettings(e))]})],build:[["wave/id"],["asked"],async(e,[a],t)=>{if(!e)throw new s.BoardError("build needs --site <route>");s.parseKey(a);const n=await s.askedGate(e,a,t.asked);try{const i=await fetch(`http://127.0.0.1:${process.env.KICKFLIP_HUB_PORT||4500}/_hub/build`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({route:x.basename(e),key:a}),signal:AbortSignal.timeout(1e4)}),o=await i.json();if(!i.ok)throw new s.BoardError(o.error||"The hub refused the build.");return{lines:["started",...(await s.appendThread(e,a,{text:n})).lines]}}catch(i){throw i instanceof s.BoardError?i:new s.BoardError(`Could not start the build: ${i.message}`)}}],"main set":[[],["install","install-sha"],(e,a,t)=>s.setMainInstall(e,{install:t.install,installSha:t["install-sha"]})],"settings set":[["name","value"],[],(e,[a,t])=>s.setBoardSetting(e,a,t)],"builder start":[["wave/id"],["label","phase","progress","dir","agent","kind","model","eta","worktree","repo","dispatch"],(e,[a],{worktree:t,repo:n,dispatch:i,...o})=>{if(n!==void 0&&!t)throw new d("--repo goes with --worktree");const l=r(p=>z(e,a.split("/")[1],{repo:n,...p}),"make"),c=e&&o.dir===void 0&&ae(e);return s.startBuilder(e,a,{...o,dispatch:i?"session":void 0,worktree:t?l:void 0,ownWorktree:c?l:void 0})}],"builder progress":[["wave/id","...text?"],["phase","text","dir","agent","eta","clear-eta"],(e,[a,...t],n)=>{if(t.length&&n.text!==void 0)throw new d("builder progress takes its text once: as words or as --text");if(!t.length&&n.text===void 0)throw new d("builder progress needs its text, as words or as --text");return s.builderProgress(e,a,n.text??t.join(" "),{...n,clearEta:n["clear-eta"]})}],"builder done":[["wave/id"],["dir","agent","next","owner-step","check","text","updated"],async(e,[a],{updated:t,...n})=>{const i=e&&!n.dir;if(i&&t===void 0&&await s.cardLinked(e,a))throw new s.BoardError(`a reviewer has ${a}'s PR link: pass --updated "<what changed>" for its thread, or --updated none for a change only to comments, names, docs or tests`);const o=await pe(e,a,n);return i&&t!==void 0&&t.trim().toLowerCase()!=="none"&&await s.reviewUpdated(e,a,t),o}],"builder pick":[["wave/id"],[],(e,[a])=>s.pickBuilder(e,a)],"agent launch":[["wave/id"],["name","task","writes","model"],(e,[a],t)=>s.requestHelper(e,a,t)],"agent wait":[["wave/id"],["name","agent","timeout"],(e,[a],t)=>s.waitHelper(e,a,{...t,alive:Q})],"agent merge":[["wave/id"],["name"],async(e,[a],t)=>{const{record:n,card:i}=await s.helperOf(e,a,t.name);if(!n.done)throw new s.BoardError(`helper ${t.name} is still open; run agent wait ${a} --name ${t.name} first`);if(!n.writes||!n.worktree)throw new s.BoardError(`helper ${t.name} writes no code, so there is nothing to merge`);if(!i)throw new s.BoardError(`${a} has no worktree to merge helper ${t.name} into`);return G(i,n.worktree)}],audit:[["...wave/id?"],[],(e,[a,...t])=>{if(t.length)throw new d("audit takes one <wave/id> at most");return s.auditLog(e,a)}],"builder call":[["wave/id","...text"],["dir","agent"],(e,[a,...t],n)=>s.recordCall(e,a,t.join(" "),n)],"builder calls":[["wave/id"],["dir","agent"],(e,[a],t)=>s.listCalls(e,a,t)],"builder ask":[["wave/id","...text"],["agent"],(e,[a,...t],n)=>s.askOrchestrator(e,a,t.join(" "),n)],"builder answer":[["wave/id","ask"],["text"],(e,[a,t],n)=>s.answerBuilder(e,a,t,n.text)],"builder defer":[["wave/id","ask"],["text"],(e,[a,t],n)=>s.answerBuilder(e,a,t,n.text,{deferred:!0})],"builder asks":[["wave/id"],[],(e,[a])=>s.listAsks(e,a)],"builder verified":[["wave/id"],["dir","agent","note"],async(e,[a],t)=>{await s.verifyCalls(e,a,t);const n=e&&!t.dir?await s.verifiedFinish(e,a):null;if(!n)return{};if(n.waits.length)return{lines:[`${await s.cardName(e,a)} finishes once the owner answers ${n.waits.join(", ")}`]};try{return await $(e,a,{text:t.note??"Judgment calls verified."})}catch(i){throw i instanceof s.BoardError?Object.assign(new s.BoardError(`judgment calls verified, but ${await s.cardName(e,a)} didn't finish: ${i.message}`),{exitCode:i.exitCode}):i}}],land:[["wave/id"],["repo","dir","text","owner-step","check"],async(e,[a],t)=>{const n=a.split("/")[1];if(e&&t.text===void 0)throw new d(`land needs --text "<what was checked>", which closes ${a} as done once it lands`);if(!e&&t.text!==void 0)throw new d("--text closes the card, which needs --site");if(!e&&t["owner-step"]!==void 0)throw new d("--owner-step keeps a board card open, which needs --site");if(t.check!==void 0&&t["owner-step"]===void 0)throw new d("--check goes with --owner-step: it is the code check that closes the card once the step is done");if(e&&t.dir===void 0)return $(e,a,{text:t.text,ownerStep:t["owner-step"],check:t.check,repo:t.repo});const i=e?await s.cardName(e,a):void 0,o=await F(i??a,async()=>{e&&await s.checkClose(e,a,t.text,t["owner-step"],t.check),await s.checkCallsVerified(e,a,{dir:t.dir});const l=await s.recordedWorktree(e,a,{dir:t.dir}),{repo:c,base:p}=U(e,{repo:t.repo??l?.repo});return await J({path:l?.path??x.join(K(),n),branch:l?.branch??n,repo:x.resolve(c),base:p,ref:i}),e?s.setItem(e,a,{done:!0,text:t.text,ownerStep:t["owner-step"],check:t.check}):{lines:[]}});return o.joined?{lines:[`joined the land already running for ${i??a}`]}:o}],"builder stop":[["wave/id"],[],(e,[a])=>Z(b(e,"builder stop"),a)],"land stop":[["wave/id"],[],(e,[a])=>te(b(e,"land stop"),a)],"land queue":[[],[],()=>Y()],"item bump":[["wave/id"],[],(e,[a])=>S(b(e,"item bump"),a,!0)],"item unbump":[["wave/id"],[],(e,[a])=>S(b(e,"item unbump"),a,!1)],lander:[["wave/id"],[],async(e,[a])=>{if(!e)throw new d("lander needs --site <route>");return await s.relaunchLander(e,a),{lines:[`${await s.cardName(e,a)} is handed back again; the server launches a lander for it within a minute`]}}],merge:[["wave/id"],["after-approval","confirmed"],async(e,[a],t)=>e&&await s.heldMerge(e,a)?X(e,a):s.mergeCard(e,a,{afterApproval:t["after-approval"],confirmed:t.confirmed})],"arch file":[["wave/id"],[],(e,[a])=>e?V(e,a):Promise.reject(new s.BoardError("arch file needs --site <route>"))],push:[[],["kind","title","body","url","tag","urgent","now"],(e,a,t)=>{if(t.kind!=="fault"&&t.kind!=="reminder")throw new d(`a session pushes only --kind fault or reminder${t.kind===void 0||t.kind===!0?"":`, not "${t.kind}"`}: the board server pushes Action needed, and news goes on the board`);if(t.title===void 0)throw new d("push needs --title");return s.sendPush(e,t)}]};f.install=f.build;function b(e,a){if(!e)throw new d(`${a} needs --site <route>`);return e}r(b,"needSite");const h=r((e,a)=>s.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 we(e){const a=e["pick-up-at"];return[e.done&&"done",e.next!==void 0&&(e.next.trim()?"next line set":"next line cleared"),e["clear-next"]&&"next line cleared",e.pr!==void 0&&`PR #${e.pr}`,e.pushed!==void 0&&`pushed ${e.pushed}`,e["gh-since"]&&`GitHub read to ${e["gh-since"]}`,e["from-ask"]&&`from task ${e["from-ask"]}`,e.state,e.held&&"held",e.unheld&&"resumed",a!==void 0&&(a?`held until ${a}`:"resumed"),I(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(we,"itemChanges");const me={reply:r(async(e,[a])=>`reply written to ${await s.eventCard(e,a)}`,"reply"),thread:r(async(e,[a])=>`thread line on ${await h(e,a)}`,"thread"),"topic add":r((e,[a])=>`topic ${a} added`,"topic add"),"question add":r(async(e,a,t,{asked:n,staged:i})=>{const o=[...new Set(n.map(c=>c.topic))],l=o.length!==1?"":o[0].startsWith("item:")?` on ${await h(e,o[0].slice(5))}`:` on topic ${o[0]}`;return`${T(n.map(c=>c.id),"question")} ${i===n.length?"staged":"added"}${l}`},"question add"),"question resolve":r((e,[a])=>`question ${a} resolved`,"question resolve"),"question move":r((e,[a],t)=>`questions ${a} merged onto ${t.to}`,"question move"),"prototype add":r(async(e,[a],t)=>`prototype ${a} recorded${t.card?` for ${await h(e,t.card)}`:""}`,"prototype add"),"prototype rm":r((e,[a])=>`prototype ${a} removed`,"prototype rm"),"item set":r(async(e,[a],t)=>`${await h(e,a)}: ${we(t)}`,"item set"),"item add":r(async(e,a,t,{key:n})=>`${await h(e,n)} added as ${n}`,"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"),"intake handoff":r(async(e,[a])=>`${await s.eventCard(e,a)} handed to the orchestrator`,"intake handoff"),"ask unfold":r((e,[a])=>`task ${a} back on a card of its own`,"ask unfold"),"ask move":r((e,[a],t)=>`task ${a} moved to /${t.to}`,"ask move"),"lineup create":r((e,[a])=>`lineup ${a} created`,"lineup create"),"lineup add":r(async(e,[a,t])=>`${await h(e,t)} added to lineup ${a}`,"lineup add"),"lineup remove":r(async(e,[a,t])=>`${await h(e,t)} taken out of lineup ${a}`,"lineup remove"),"lineup set":r((e,[a])=>`lineup ${a} updated`,"lineup set"),"lineup finish":r((e,[a])=>`lineup ${a} finished`,"lineup finish"),"review record":r((e,[a],t)=>`review of PR #${a} recorded: ${t.state==="needs"?"needs you":"posted"}`,"review record"),"reviewers set":r(()=>"reviewers replaced","reviewers set"),"reviewers add":r(()=>"reviewers added","reviewers add"),"reviewers remove":r((e,a)=>`${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,n)=>`${await h(e,a)}: ${n.alreadyOpen?"already open: the Implement tap opened its record; launch the agent, whose own builder start takes it up":`${_(t)} started`}`,"builder start"),"builder progress":r(async(e,[a])=>`progress line on ${await h(e,a)}`,"builder progress"),"builder done":r(async(e,[a],t,n)=>`${await h(e,a)}: ${n?.said??`${_(t)} done${t["owner-step"]===void 0?"":`; owner step "${t["owner-step"].trim()}"`}`}`,"builder done"),"builder call":r(async(e,[a])=>`judgment call recorded on ${await h(e,a)}`,"builder call"),"builder ask":r(async(e,[a],t,{id:n})=>`${await h(e,a)}: asked the orchestrator, ${n}`,"builder ask"),"builder answer":r(async(e,[a,t])=>`${await h(e,a)}: ${t} answered`,"builder answer"),"builder defer":r(async(e,[a,t])=>`${await h(e,a)}: ${t} deferred to the owner`,"builder defer"),"builder verified":r(async(e,[a],t,n)=>`${await h(e,a)}: judgment calls verified${n?.said?`; ${n.said}`:""}`,"builder verified"),land:r(async(e,[a],t,n)=>e?n?.said?`${await h(e,a)}: ${n.said}`:t["owner-step"]===void 0?`${await h(e,a)}: landed and done`:`${await h(e,a)}: landed; waits on the owner step "${t["owner-step"].trim()}"`:null,"land"),merge:r(async(e,[a],t,n)=>`${await h(e,a)}: ${n?.said??"merged"}`,"merge"),"arch file":r(async(e,[a])=>`findings of ${await h(e,a)} filed`,"arch file")};async function ge(e,a={}){try{const{flags:t,rest:n}=oe(e),i=[3,2,1].map(u=>n.slice(0,u).join(" ")).find(u=>y(f,u)),o=y(f,i);if(!o)throw new d(n.length?`unknown command "${n.slice(0,2).join(" ")}"`:"no command given");const[l,c,p]=o,m=n.slice(i.split(" ").length),N=l.at(-1)?.startsWith("..."),B=l.at(-1)?.endsWith("?")?l.length-1:l.length;if(N?m.length<B:m.length!==l.length)throw new d(`${i} takes ${l.map(u=>`<${u.replace(/^\.\.\.|\?$/g,"")}>`).join(" ")||"no arguments"}`);const{site:q,...E}=t;if(q?.length>1)throw new d("--site is given twice");const P=q?.[0],k=de(i,c,E);ue(c,k);const A=P===void 0?null:await s.siteDir(P),v=await p(A,m,k,a),j=y(me,i),O=j&&await j(A,m,k,v);O&&console.log(O);for(const u of typeof v=="object"&&v?.lines||[])console.log(u);return 0}catch(t){if(t instanceof d)return console.error(`${t.message} (node board.mjs --help lists the commands)`),2;if(t instanceof s.BoardError)return console.error(t.message),t.exitCode??1;throw t}}if(r(ge,"main"),process.argv[1]&&import.meta.url===W(g.realpathSync(process.argv[1])).href){const e=process.argv.slice(2);!e.length||e.includes("--help")||e[0]==="help"?console.log(re):process.exitCode=await ge(e)}export{re as USAGE,ge as main};
|