@lovek4d/kickflip 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTEXT.md +32 -16
- package/SKILL.md +8 -6
- package/bin/doctor.mjs +4 -4
- package/bin/install.mjs +3 -3
- package/bin/kickflip.mjs +8 -8
- package/bin/wait-events.mjs +2 -2
- package/board/bin/board.mjs +50 -28
- package/board/domain.mjs +7 -7
- package/board/finish.mjs +4 -3
- package/board/headless.mjs +4 -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 +6 -89
- package/board/public/app-cards.js +19 -10
- package/board/public/app-core.js +1 -20
- package/board/public/app-reader.js +174 -0
- package/board/public/app-threads.js +49 -35
- package/board/public/app.html +2 -0
- package/board/public/app.js +28 -16
- package/board/public/hub.css +1 -0
- package/board/public/new-task.js +121 -0
- package/board/public/shell.js +4 -0
- package/board/public/sidebar.css +1 -0
- package/board/public/style.css +91 -3
- package/board/public/ui-cards.js +46 -21
- package/board/public/ui-enums.js +1 -1
- package/board/public/ui-phase-bars.js +4 -4
- package/board/public/ui-questions.js +47 -18
- package/board/public/ui-sidebar.js +9 -3
- package/board/public/ui-threads.js +25 -12
- package/board/public/ui.js +42 -12
- package/board/public/view-frame.js +1 -0
- package/board/public/view-questions.js +45 -16
- package/board/public/view-settings.js +38 -19
- package/board/server.mjs +20 -18
- package/board/store.mjs +15 -13
- package/board/worktree.mjs +20 -20
- package/hub/boards.mjs +4 -4
- package/hub/front-door.mjs +20 -0
- package/hub/github.mjs +3 -3
- package/hub/hub.mjs +9 -12
- package/hub/new-board.mjs +2 -2
- package/hub/owner-checks.mjs +1 -0
- package/hub/readiness.mjs +2 -2
- package/hub/sessions.mjs +2 -2
- package/hub/try.mjs +7 -7
- package/package.json +1 -1
- package/shared/owner-check.mjs +3 -0
- package/shared/tailscale.mjs +1 -0
- package/shared/usage-gate.mjs +1 -1
package/CONTEXT.md
CHANGED
|
@@ -302,9 +302,13 @@ _Avoid_: triage state, status (the wave entry's `status` is a different field)
|
|
|
302
302
|
The orange words last among the orange ones on a card's time line ("10:26 · held · blocked by #53 · Action needed"), on a card waiting on the owner, in any lane ([Build on a blocked card queues it, and blocked, bumped and Action needed leave the badges](docs/adr/2026-11-03-0301-build-on-a-blocked-card-queues-it.md)): an open question or a prototype pick, a green draft's Ready, the Done of an ask, manual steps on an approved PR, or, with **auto-merge** off, an approved PR's Merge. It links to the thing to act on. Reviewer waits and held cards get none ([the lane follows who acts next, and a card waiting on the owner says Action needed](docs/adr/2026-10-29-1639-the-lane-follows-who-acts-next.md)), and so does a reopened card waiting only on the owner's Done, which is only waiting to be closed ([a card waiting only to be closed has no Action needed](docs/adr/2026-11-03-0137-a-card-waiting-only-to-be-closed-has-no-action-needed.md)). An unstarted card with open questions carries it in Todo ([an unstarted card waits on its questions in Todo, and its one action button follows what it waits on](docs/adr/2026-10-30-1309-an-unstarted-card-waits-on-its-questions-in-todo.md)).
|
|
303
303
|
_Avoid_: open items, needs you (Needs you is the box at the top of the board)
|
|
304
304
|
|
|
305
|
+
**Request**:
|
|
306
|
+
The owner's ask for a grill or a prototype on a card: the thread composer's fire or eye, toggles that Send carries, or /grill or /prototype typed in the box, sending `grill-more` or `prototype-request` with whatever was typed; a question's own eye on Waiting on you asks for a prototype of that question. A request with nothing typed is one gray **status line** in the thread, "grill requested" or "prototype requested"; one with text is the owner's message carrying it, with that status in the message's footer, and "Waiting for Claude…" to its right until Claude picks it up. An answer submitted from Waiting on you is likewise a message whose footer reads "answers submitted". A question asked for a prototype reads "prototype requested", locked, until a question naming it in `round_of` resolves it, and `question resolve` refuses it; a prototype request still waiting 30 minutes on with no prototype agent running comes back to the session as `prototype-overdue` ([a button tap is a status line or a message footer](docs/adr/2026-11-04-2130-a-button-tap-is-a-status-line-or-a-message-footer.md), [a prototype request waits for its prototype](docs/adr/2026-11-04-2130-a-prototype-request-waits-for-its-prototype.md)).
|
|
307
|
+
_Avoid_: Grill button, Prototype button (on a card)
|
|
308
|
+
|
|
305
309
|
**Action button**:
|
|
306
|
-
The open card's one workflow button, which `board/domain.mjs` picks as the card's `actionButton`: **Open questions** (`openQuestions`) while it has open questions or a prototype pick, opening the first; **Mark as done** (`markDone`) on an ask waiting on the owner's Done, sending the same `ask-done` as the thread's Done; otherwise
|
|
307
|
-
While the card carries **Action needed**, the button that clears it is orange, in the notice color: the card's `resolvedBy` from `board/domain.mjs`, by the action's kind. A question or a pick makes Open questions orange, the Done of an ask makes Mark as done and the thread's Done orange, a green draft's Ready or manual steps make Review on GitHub orange, and an approved PR's Merge makes Merge orange. A held card's Open questions
|
|
310
|
+
The open card's one workflow button, which `board/domain.mjs` picks as the card's `actionButton`: **Open questions** (`openQuestions`) while it has open questions or a prototype pick, opening the first; **Mark as done** (`markDone`) on an ask waiting on the owner's Done, sending the same `ask-done` as the thread's Done; otherwise none. A grill or prototype is asked for from the thread's fire and eye (**request**), and the card reads a blue planner line "preheating" while a grill is under way: a grill request not yet handled (replied to, questions posted on the card after it, or a grill agent started after it finished), a running grill agent, or a **grilling** card with no questions posted yet ([a new card is ready, grilling or prototyping, and reads ready in green when it is](docs/adr/2026-11-03-0516-a-new-card-is-ready-grilling-or-prototyping.md)). Open questions win over Mark as done. A Done card has none ([an unstarted card waits on its questions in Todo, and its one action button follows what it waits on](docs/adr/2026-10-30-1309-an-unstarted-card-waits-on-its-questions-in-todo.md)).
|
|
311
|
+
While the card carries **Action needed**, the button that clears it is orange, in the notice color: the card's `resolvedBy` from `board/domain.mjs`, by the action's kind. A question or a pick makes Open questions orange, the Done of an ask makes Mark as done and the thread's Done orange, a green draft's Ready or manual steps make Review on GitHub orange, and an approved PR's Merge makes Merge orange. A held card's Open questions stay plain. A reopened card waiting only on the owner's Done has its thread's Done primary, in the accent color ([a card waiting only to be closed has no Action needed](docs/adr/2026-11-03-0137-a-card-waiting-only-to-be-closed-has-no-action-needed.md)).
|
|
308
312
|
The button names (`BUTTON`: the action buttons, `github` and `merge`) and the action kinds (`ACTION_KIND`) are two frozen enums in `board/public/ui-enums.js`, which the page loads as a script and `board/domain.mjs` runs in a vm context; code compares against their members, never a bare string.
|
|
309
313
|
_Avoid_: Open items, Action needed (the badge)
|
|
310
314
|
|
|
@@ -312,9 +316,15 @@ _Avoid_: Open items, Action needed (the badge)
|
|
|
312
316
|
The session's steps for PR comments, CI, review, conflicts and merging, defined in SKILL.md. The hub polls GitHub with Node and gh, writes new owner and reviewer comments into each card's thread, closes a card whose PR merged, then wakes the board on a change ([the hub's poll records merges and PR comments on the card](docs/adr/2026-11-02-2335-the-hubs-poll-records-merges-and-pr-comments-on-the-card.md)); the tick reads each card with `board.mjs pr` and records the newest handled event with `item set --gh-since`. With Hub's Linear key set, the poll also moves the owner's Linear tickets from PR state, so the session leaves those moves to the hub; without a key the poll moves none ([the hub's poll moves the owner's Linear tickets](docs/adr/2026-11-02-0023-the-hubs-poll-moves-the-owners-linear-tickets.md)). The owner's monitor skill is optional ([the hub reads GitHub itself](docs/adr/2026-11-01-2314-the-hub-reads-github-itself.md)).
|
|
313
317
|
|
|
314
318
|
**Auto-merge**:
|
|
315
|
-
A board setting, `auto_merge` in its `board.json`, off unless set: on, the
|
|
319
|
+
A board setting, `auto_merge` in its `board.json`, off unless set: on, the hub's GitHub tick merges a PR once it is approved, CI is green and its merge state is CLEAN; off, the card shows the owner Action needed and a Merge button, which reads "Merging…" until the board server merges the PR or says in the thread why it didn't. The board's Settings modal toggles it, and the watcher prints it with every wake as `setting auto-merge on|off`. A setting, not a **rule**, since the board itself acts on it ([Auto-merge is a board setting, off by default, and the owner merges from the card](docs/adr/2026-10-29-1752-auto-merge-is-a-board-setting-off-by-default.md)).
|
|
316
320
|
_Avoid_: merge rule, merge policy
|
|
317
321
|
|
|
322
|
+
A card with no PR lands the same way: with Auto-merge off its builder's `done` stops after the tests, and the card waits in In Review with a Merge button; the tap makes the board server rebase it onto main as it is now, rerun the tests and land it, sending a conflict or a failing test to a headless lander ([a land waits for your Merge](docs/adr/2026-11-04-2100-a-land-waits-for-your-merge.md)).
|
|
323
|
+
|
|
324
|
+
**PRs**:
|
|
325
|
+
A board setting, `prs` in its `board.json`, on unless set, shown on a GitHub board that builds in its own clone only. On, every card opens a PR, and with no reviewers on the board that PR merges once it is green and clean, with no approval. Off, no PR opens and each card lands on main, which is pushed; Skip the draft and Reviewers are hidden, and turning it off needs Push main granted ([a land waits for your Merge](docs/adr/2026-11-04-2100-a-land-waits-for-your-merge.md)).
|
|
326
|
+
_Avoid_: PR mode, no-PR mode
|
|
327
|
+
|
|
318
328
|
**Skip the draft**:
|
|
319
329
|
A board setting, `skip_draft` in its `board.json`, off unless set, shown on a GitHub board only. On, the hub's GitHub check marks a card's draft PR ready for review once its CI passes, and the card waits in In Review with no Action needed until then; off, the draft waits in In Review with Action needed "Review draft PR #n". With **Auto-merge** on too, a builder's PR can merge once reviewers approve it, without the owner seeing it ([a board can skip the draft](docs/adr/2026-11-04-1913-a-board-can-skip-the-draft.md)).
|
|
320
330
|
_Avoid_: auto-ready, skip review
|
|
@@ -324,7 +334,7 @@ The open card's link to its PR, on every card with an open PR, last on the detai
|
|
|
324
334
|
_Avoid_: Review #<n> on GitHub, PR link
|
|
325
335
|
|
|
326
336
|
**Install**:
|
|
327
|
-
The app card's one badge that opens its build on the owner's phone, or the badge on main's slim bar above the lanes. A ready card reads "Install ↗", or "Install (stale) ↗" when its build's sha differs from the current branch head. While a build runs it reads "
|
|
337
|
+
The app card's one badge that opens its build on the owner's phone, or the badge on main's slim bar above the lanes. A ready card reads "Install ↗", or "Install (stale) ↗" when its build's sha differs from the current branch head. While a build runs it reads "Serving…" with an inert spinner; a failed build reads "Serve failed ↗" and opens its thread. The open card's badge sits between Notes and Review on GitHub. The workflow button starts a Serve, or Retry after failure, while Auto serve is off and the card's branch has changes ([only the owner or Auto serve starts a card run](docs/adr/2026-11-02-1806-only-the-owner-or-auto-build-starts-a-card-run.md)). Main’s red Build failed button opens a bottom sheet with the last 40 lines of `data/try/main.log` and Retry at the recorded failed tip. Retry shows readiness refusals in the sheet, reads Building… in orange while running, refreshes the log after failure and closes with a toast when ready. On a local board, a refresh icon right of main's timestamp pulls its branch from origin now, as every poll does, and turns red with the reason when a pull can't fast-forward ([a local board's Main pulls from origin on its own](docs/adr/2026-11-03-1914-a-local-boards-main-pulls-from-origin-on-its-own.md)). A Done card has no try badge ([a board's build script serves or builds each card and main](docs/adr/2026-11-01-1846-a-boards-build-script-serves-or-builds-each-card-and-main.md)).
|
|
328
338
|
_Avoid_: download, TestFlight (a different channel), older build
|
|
329
339
|
|
|
330
340
|
**Build script**:
|
|
@@ -332,31 +342,31 @@ The repo's reviewed, executable `.kickflip/build`, run by the hub from each card
|
|
|
332
342
|
_Avoid_: install script, session install runner
|
|
333
343
|
|
|
334
344
|
**Serve**:
|
|
335
|
-
The workflow button after the **action button** for a website work card with a build script, last but for **Implement**, while Auto
|
|
345
|
+
The workflow button after the **action button** for a website work card with a build script, last but for **Implement**, while Auto serve is off and the card's branch has changes ([only the owner or Auto serve starts a card run](docs/adr/2026-11-02-1806-only-the-owner-or-auto-build-starts-a-card-run.md)). It confirms before the hub runs the script. Its one badge reads "Serving…", "Visit ↗", "Visit (stale) ↗" or "Serve failed ↗". A served card opens at `https://<host>:<port>/`, with a port from 8500–8599 kept through re-serves. A loopback relay tracks visits; 30 idle minutes stops the card and releases its port. Main has a fixed port recorded per board, keeps its link across merges and restarts and never idles out. Its red Serve failed button opens the same log and Retry sheet as an app’s, reading Serving… in orange while retrying the recorded failed tip, even if origin has moved. Both open the server itself, outside the app shell ([a try build serves on its own Tailscale port](docs/adr/2026-11-01-2254-a-try-build-serves-on-its-own-tailscale-port.md)).
|
|
336
346
|
_Avoid_: preview site, separate page
|
|
337
347
|
|
|
338
|
-
**Auto
|
|
339
|
-
The board's `auto_build` setting, off by default. On, each card with commits beyond main builds or serves after each new head, and cards show no Serve
|
|
340
|
-
_Avoid_:
|
|
348
|
+
**Auto serve**:
|
|
349
|
+
The board's `auto_build` setting (the key keeps its name), labelled "Auto serve" in Settings, off by default. On, each card with commits beyond main builds or serves after each new head, and cards show no Serve; off, cards wait for the owner's Serve. A session starts a card run only to carry out the owner's words, quoting them with `build <key> --asked` ([only the owner or Auto serve starts a card run](docs/adr/2026-11-02-1806-only-the-owner-or-auto-build-starts-a-card-run.md)). Main always builds after every merge. The hub runs a nonoverlapping tick every 15 seconds and fetches other boards' origin branches on each check. The Settings toggle saves immediately; `settings set auto-build on|off` sets it through the CLI.
|
|
350
|
+
_Avoid_: Auto-build, build rule
|
|
341
351
|
|
|
342
352
|
**Implement**:
|
|
343
|
-
The orange Implement last in the open card's workflow row, on a work card still unstarted in Todo, held or not, blocked or not, that has no **Action needed**: the card's `buildable` from `board/domain.mjs`. Its confirm reads "Implement #92? Claude starts its implementer now with the notes as they stand." Owner-facing words call a card's agent an implementer and its `building` status implementing, so Build, Serve and Install only mean the package of a worktree or main; the code keeps `buildable`, `card-build`, the `building` status and builder records ([a Todo card's button reads Implement, and Build and Serve only mean the package](docs/adr/2026-11-03-1549-a-todo-cards-button-reads-implement.md)). On a blocked card it queues a **queued build**, its confirm "Implement #92 once #53 is done?" ([Build on a blocked card queues it, and blocked, bumped and Action needed leave the badges](docs/adr/2026-11-03-0301-build-on-a-blocked-card-queues-it.md)). On a held card it reads "Unhold and implement" in its confirm and the board server clears the hold as Unhold does before the start; a held card outside Todo keeps Unhold ([a held Todo card shows Build in place of Unhold](docs/adr/2026-11-03-0234-a-held-todo-card-shows-build-in-place-of-unhold.md)). It confirms, sends `card-build {key}` and reads "Starting…" while the board server starts the build: the refusals, the builder pick, the worktree and the building mark that `builder start --kind build --worktree` makes. Started, the card is in In Progress reading "starting" until its implementer's first status line, its Next cleared, and the server
|
|
353
|
+
The orange Implement last in the open card's workflow row, on a work card still unstarted in Todo, held or not, blocked or not, that has no **Action needed**: the card's `buildable` from `board/domain.mjs`. Its confirm reads "Implement #92? Claude starts its implementer now with the notes as they stand." Owner-facing words call a card's agent an implementer and its `building` status implementing, so Build, Serve and Install only mean the package of a worktree or main; the code keeps `buildable`, `card-build`, the `building` status and builder records ([a Todo card's button reads Implement, and Build and Serve only mean the package](docs/adr/2026-11-03-1549-a-todo-cards-button-reads-implement.md)). On a blocked card it queues a **queued build**, its confirm "Implement #92 once #53 is done?" ([Build on a blocked card queues it, and blocked, bumped and Action needed leave the badges](docs/adr/2026-11-03-0301-build-on-a-blocked-card-queues-it.md)). On a held card it reads "Unhold and implement" in its confirm and the board server clears the hold as Unhold does before the start; a held card outside Todo keeps Unhold ([a held Todo card shows Build in place of Unhold](docs/adr/2026-11-03-0234-a-held-todo-card-shows-build-in-place-of-unhold.md)). It confirms, sends `card-build {key}` and reads "Starting…" while the board server starts the build: the refusals, the builder pick, the worktree and the building mark that `builder start --kind build --worktree` makes. Started, the card is in In Progress reading "starting" until its implementer's first status line, its Next cleared, and the server launches the builder itself, Claude or Codex, from the card's notes, with the thread line "Builder launched on <model> in <worktree>." linking its log; no event wakes the session ([the hub launches every agent from the card](docs/adr/2026-11-04-2130-the-hub-launches-every-agent-from-the-card.md)). A launch that fails puts the card back in Todo showing **Couldn't start implementing**. Refused, the card stays in Todo showing **Couldn't start implementing** ([a Todo card that nothing blocks has Build](docs/adr/2026-11-02-1745-a-todo-card-that-nothing-blocks-has-build.md), [a Build tap starts the build in the board server](docs/adr/2026-11-03-0219-a-build-tap-starts-the-build-in-the-board-server.md), [a card reads starting from its tap, with one dispatched mark](docs/adr/2026-11-03-1718-a-card-reads-starting-from-its-tap-with-one-dispatched-mark.md)). It isn't an app's Build, which runs the **build script**.
|
|
344
354
|
_Avoid_: card Build, go button, start button
|
|
345
355
|
|
|
346
356
|
**Queued section**:
|
|
347
|
-
The one section at the top of Todo that holds the cards waiting for an **implementer slot**: each **Queued build**, and while **Auto-pull** is on every pullable card, ready ones waiting over the cap included. Bumped cards lead it, the latest bump on top; the rest follow oldest first, by the tap's time or else when the card was made, so a card's unbump returns it to that place. Whenever In Progress is under Maximum parallel implementers the board server takes the queue from its top: it starts a Queued build nothing blocks, and
|
|
357
|
+
The one section at the top of Todo that holds the cards waiting for an **implementer slot**: each **Queued build**, and while **Auto-pull** is on every pullable card, ready ones waiting over the cap included. Bumped cards lead it, the latest bump on top; the rest follow oldest first, by the tap's time or else when the card was made, so a card's unbump returns it to that place. Whenever In Progress is under Maximum parallel implementers the board server takes the queue from its top: it starts a Queued build nothing blocks, and starts and launches the build of a ready card. Queued cards take no slot ([In Progress has a Queued section, and the pull takes its top under the cap](docs/adr/2026-11-04-0121-in-progress-has-a-queued-section.md); [Queued sits at the top of Todo](docs/adr/2026-11-04-0150-queued-sits-at-the-top-of-todo.md)).
|
|
348
358
|
_Avoid_: backlog, waiting lane
|
|
349
359
|
|
|
350
360
|
**Queued build**:
|
|
351
|
-
An Implement the owner tapped on a card an unfinished dep blocks: `queued_at` on its wave entry, with no builder record or worktree, and the thread line "Implement queued: it starts once #53 is done." The card sits in Todo's **Queued section**, its time line reading "blocked by #53" in place of a builder line, and once nothing holds or blocks it and it reaches the queue's top with an **implementer slot** free the board server starts the build as the tap would, from the newest main,
|
|
361
|
+
An Implement the owner tapped on a card an unfinished dep blocks: `queued_at` on its wave entry, with no builder record or worktree, and the thread line "Implement queued: it starts once #53 is done." The card sits in Todo's **Queued section**, its time line reading "blocked by #53" in place of a builder line, and once nothing holds or blocks it and it reaches the queue's top with an **implementer slot** free the board server starts the build as the tap would, from the newest main, and launches its builder. `item set` prints `queued build starts: <key>` for it and sends no `card-unblocked`. A start that is refused or fails sends the card back to Todo with **Couldn't start implementing** ([Build on a blocked card queues it, and blocked, bumped and Action needed leave the badges](docs/adr/2026-11-03-0301-build-on-a-blocked-card-queues-it.md)).
|
|
352
362
|
_Avoid_: pending build, waiting build
|
|
353
363
|
|
|
354
364
|
**Couldn't start implementing**:
|
|
355
|
-
The thread line, of kind `build-refused`, the board server writes when it refuses an Implement tap, with the reason in the owner's words: paused, or a noted prototype pick that waits on its confirm, or why a **queued build** couldn't start. The Todo card shows it in its status line while it is the card's newest line other than a status line ([a Build tap starts the build in the board server](docs/adr/2026-11-03-0219-a-build-tap-starts-the-build-in-the-board-server.md)).
|
|
365
|
+
The thread line, of kind `build-refused`, the board server writes when it refuses an Implement tap, with the reason in the owner's words: paused, or a noted prototype pick that waits on its confirm, or why a **queued build** couldn't start, or a builder launch that failed ("the launch failed (<reason>). Tap Implement to try again."). The Todo card shows it in its status line while it is the card's newest line other than a status line ([a Build tap starts the build in the board server](docs/adr/2026-11-03-0219-a-build-tap-starts-the-build-in-the-board-server.md)).
|
|
356
366
|
_Avoid_: Couldn't start the build, build error, failed start
|
|
357
367
|
|
|
358
368
|
**Auto-pull**:
|
|
359
|
-
The board's `auto_pull` setting, off by default, which reads "Auto implement Todo cards" in Board settings' Auto section and turns on or off with no confirmation. On, the board server
|
|
369
|
+
The board's `auto_pull` setting, off by default, which reads "Auto implement Todo cards" in Board settings' Auto section and turns on or off with no confirmation. On, the board server starts and launches the build of the top **pullable** cards in board order, one per **implementer slot** free, on every board change and at start ([auto implement fills In Progress up to Maximum parallel implementers, on every board change](docs/adr/2026-11-04-0057-auto-implement-fills-in-progress-up-to-a-cap.md)): a pullable card is a Ready, unstarted work card that nothing blocks or holds, with no question open or waiting on Claude, no grill under way and no message a session is working on, which waits in the **Queued section** while the switch is on ([the idle pull takes ready cards first, and plans the top card when none is ready](docs/adr/2026-11-03-1631-the-idle-pull-takes-ready-cards-first.md), [a card moves to In Progress only once building starts](docs/adr/2026-11-03-0202-a-card-moves-to-in-progress-only-once-building-starts.md)). The slots are the board's **Maximum parallel implementers** (`max_implementers`, 10 by default, the number beside the switch), less the cards with a running build or waiting in Orchestrator review, less the pulled cards whose build hasn't started; a running grill or prototype and a card waiting on the owner take none ([planners and implementers are two kinds of agent](docs/adr/2026-11-03-1551-planners-and-implementers-are-two-kinds-of-agent.md)). New cards are never pulled, nor a card from an ask still New, grilling or prototyping; a Ready one is ([a new card is ready, grilling or prototyping, and reads ready in green when it is](docs/adr/2026-11-03-0516-a-new-card-is-ready-grilling-or-prototyping.md)). It writes "Pulled in: an implementer slot was free." in the card's thread ([an idle board pulls in its top Todo card](docs/adr/2026-11-02-1754-an-idle-board-pulls-in-its-top-todo-card.md)). While the board is idle (no build running and nothing in Orchestrator review) with nothing pullable, it launches a planner instead, one at a time, on the top Todo card not set Ready that isn't held, owes or awaits no answer and has no planner running, while no card is being grilled or prototyped: a prototype when the card is prototyping, else a grill ([the hub launches every agent from the card](docs/adr/2026-11-04-2130-the-hub-launches-every-agent-from-the-card.md)).
|
|
360
370
|
_Avoid_: auto-start, idle pickup
|
|
361
371
|
|
|
362
372
|
**Auto-triage**:
|
|
@@ -388,14 +398,14 @@ An agent (Claude Opus, Claude Sonnet, Codex…) working on a work item, which ow
|
|
|
388
398
|
_Avoid_: worker, bot
|
|
389
399
|
|
|
390
400
|
**Dispatched record**:
|
|
391
|
-
A builder record open before its agent runs, marked with who opened it: `dispatched: 'tap'` when the owner's Implement tap (or a queued build's start) opened it, `dispatched: 'session'` when the session ran `builder start --dispatch` right before it launches
|
|
401
|
+
A builder record open before its agent runs, marked with who opened it: `dispatched: 'tap'` when the owner's Implement tap (or a queued build's start) opened it, `dispatched: 'hub'` when the board server opened it to launch a pulled build or a planner (a grill or a prototype) itself, `dispatched: 'session'` when the session ran `builder start --dispatch` right before it launches some other agent. Either way the card's builder line reads "starting" from then until the agent's first status line. The agent's own `builder start` takes it up: the kind, a Claude model and the worktree stay, the record starts again at now and is no longer dispatched. A `--dispatch` on a record the tap opened changes nothing and prints "already open"; a `builder start --agent <other name>` refuses while the tap's main record is open and not taken up. The session never posts "a builder is working" itself ([the session opens the builder record when it dispatches an agent](docs/adr/2026-11-03-0513-the-session-opens-the-builder-record-when-it-dispatches-an-agent.md), [a card reads starting from its tap, with one dispatched mark](docs/adr/2026-11-03-1718-a-card-reads-starting-from-its-tap-with-one-dispatched-mark.md)).
|
|
392
402
|
|
|
393
403
|
**Judgment call**:
|
|
394
404
|
A decision a builder makes that its brief didn't settle, recorded on its builder record as it makes it (`builder call <key> "<call>"`, one plain line), or a "Question: …" it asks the session before building. The thread shows a record's calls as one collapsed entry under its agent lines. The session checks them against the owner's words and the other cards and marks the record **verified** (`builder verified`), which finishes a handed-back build in the same step; finishing refuses while a record on the card has a call after that mark. A builder start on a record with unverified calls resumes that build, calls and worktree kept ([a builder records its judgment calls and the session verifies them before land](docs/adr/2026-11-03-0514-a-builder-records-its-judgment-calls-and-the-session-verifies-them.md)).
|
|
395
405
|
_Avoid_: guess, assumption, decision log
|
|
396
406
|
|
|
397
407
|
**Builder ask**:
|
|
398
|
-
A question a running builder puts to the board's session, the **orchestrator**, at any point mid-build (`builder ask <key> "<question>"`, id `ask<n>`), carrying on while it waits. It shows in the card's thread as a normal message named by the builder's model, "Claude Opus builder", and wakes the session with `builder-ask {key, ask, agent, text}`. The session answers it (`builder answer`) or, when it isn't confident or wants the owner's opinion, defers it (`builder defer`); both show as "Orchestrator", the name the thread gives every line the session writes. The card stays quiet meanwhile: neither line is unread, pushed, nor answers an owner message. A deferred ask is a "Question:" **judgment call**, and the session's review puts it to the owner as a card question naming the ask, after its other checks and before a draft PR or In Review; `builder verified` refuses until it has. A card whose only open questions are deferred asks waits on the owner from In Progress with Open questions and
|
|
408
|
+
A question a running builder puts to the board's session, the **orchestrator**, at any point mid-build (`builder ask <key> "<question>"`, id `ask<n>`), carrying on while it waits. It shows in the card's thread as a normal message named by the builder's model, "Claude Opus builder", and wakes the session with `builder-ask {key, ask, agent, text}`. The session answers it (`builder answer`) or, when it isn't confident or wants the owner's opinion, defers it (`builder defer`); both show as "Orchestrator", the name the thread gives every line the session writes. The card stays quiet meanwhile: neither line is unread, pushed, nor answers an owner message. A deferred ask is a "Question:" **judgment call**, and the session's review puts it to the owner as a card question naming the ask, after its other checks and before a draft PR or In Review; `builder verified` refuses until it has. A card whose only open questions are deferred asks waits on the owner from In Progress with Open questions, its new questions one unread message on the card and one "N new questions" line in its thread, and no **Action needed** ([a builder asks the orchestrator mid-build, and only what it defers reaches the owner](docs/adr/2026-11-04-0247-a-builder-asks-the-orchestrator-mid-build.md)). The owner never answers a builder mid-build.
|
|
399
409
|
_Avoid_: builder question, check-in
|
|
400
410
|
|
|
401
411
|
**Stalled**:
|
|
@@ -434,6 +444,10 @@ _Avoid_: notification, toast
|
|
|
434
444
|
A multiple-choice question Claude needs the owner to decide, attached to a topic. A builder's judgement call becomes one. One marked **urgent** blocks a build or a merge and shows alone; Claude resolves a question in the step that acts on its answer ([Needs you gathers what waits on the owner, and a topic's questions are answered on one page](docs/adr/2026-10-29-1639-needs-you-gathers-what-waits-and-questions-answer-on-one-page.md)), except an answer with a note and no option, which resolves its question as it arrives ([a pick with no note settles a prototype round](docs/adr/2026-11-02-1607-a-pick-with-no-note-settles-a-prototype-round.md)), and recording a card's close in the wave file resolves each of its questions that has an answer; one with no answer yet stops that write unless forced. The owner's Done still moves the card on the board at once ([the board applies the owner's Hold, Resume and Done at once](docs/adr/2026-11-02-0009-the-board-applies-the-owners-hold-resume-and-done-at-once.md)). Adding a question on the same card or setting its notes file resolves answered questions still waiting on Claude; once a card's Action needed is gone, the board server drops its queued push ([the board records what it knows, and commands make the rule calls](docs/adr/2026-11-03-0003-the-board-records-what-it-knows-and-commands-make-the-rule-calls.md)).
|
|
435
445
|
_Avoid_: poll, decision card
|
|
436
446
|
|
|
447
|
+
**Suggested answer**:
|
|
448
|
+
The option of an owner question the overnight triage is confident the owner already chose, from their answer on another card or their long-running context, never from an ADR or CONTEXT.md. The question shows it pre-checked with a green "suggested" chip and the line You answered this on #N: "<answer>", and only the owner's Send answers it ([overnight triage suggests your earlier answers](docs/adr/2026-11-04-2200-overnight-triage-suggests-your-earlier-answers.md)).
|
|
449
|
+
_Avoid_: auto-answer, default, recommendation (the grill's own pick)
|
|
450
|
+
|
|
437
451
|
**Topic**:
|
|
438
452
|
The group an owner question belongs to; an item's questions use the topic `item:<wave>/<id>`. A Done card's topic with no open question is resolved, so it never waits on Claude.
|
|
439
453
|
_Avoid_: category, section
|
|
@@ -538,11 +552,13 @@ Refs in text that Claude or the owner wrote are linked by the renderer, never by
|
|
|
538
552
|
| `app-threads.js` | Loading a thread, once at a time per card; an open card's thread, its reply box, quick replies and Done |
|
|
539
553
|
| `app-cards.js` | Cards, their open detail with the details list ending in Notes ↗ · Visit/Install ↗ · Review on GitHub ↗ when present, and the workflow row: Merge, Hold, the **action button**, Serve/Build and rightmost **Implement**, each confirming but Hold, which opens its sheet with the duration picker; try requests, failure-thread links and main’s failed-build log and Retry sheet; the bump icon's bump and unbump, with no confirmation; an ask's detail shows only its action button; the thread's Done confirmation and send |
|
|
540
554
|
| `app-lanes.js` | Lanes, Done's and Asks' Show more, Done's finished-wave folds, Todo's **wave bands** with their Implement, Triage and ⋯ moves, and Plan waves |
|
|
541
|
-
| `
|
|
555
|
+
| `new-task.js` | The + button, its new-task sheet and press-n-twice shortcut, on every page of a board |
|
|
556
|
+
| `app-asks.js` | Bump, edit and delete for a New card or pending message, and ⌘↵ in a thread box |
|
|
542
557
|
| `app-needs.js` | The Needs you box, led by the board session's pending prompts |
|
|
543
558
|
| `ui-prompts.js` | A pending prompt's Needs you line: its details, Allow once, Always allow and Deny, or a question's options, Skip and Send |
|
|
544
559
|
| `prompts.js` | `PromptAnswers`, answering pending prompts from their lines on a board and the hub page: which taps send at once, Always allow's confirmation, and the picks a Send waits on |
|
|
545
560
|
| `app-arch.js` | The header's bulb and its **architecture review** sheet: the last review, Run with its confirm, what a review waits on, and the bulb's glow while one runs |
|
|
561
|
+
| `app-reader.js` | The **reader sheet**: a builder's log (readable or raw, following it as it grows) and a card's or lineup's Notes, opened over the board from their links, closing on ✕, Escape, the dim area or Back |
|
|
546
562
|
| `app-banner.js` | The answer banner, its tap and its ✕ |
|
|
547
563
|
| `app-header.js` | The board's header controls and time, refresh, the offline state, the In Review header's GitHub line, the Hub links sheet and the setup checklist |
|
|
548
564
|
| `app-snapshot.js` | Reading and saving the last visit, also as the view unmounts, keeping every board's within three at the cap, holding the controls that send while it shows, the flash when the fresh board lands, and each card's sign: the flash on a card that changed lanes or took a New task's place, and the tint on a card otherwise updated, unless the owner changed it from this page |
|
package/SKILL.md
CHANGED
|
@@ -28,11 +28,11 @@ Init also links `~/.claude/statusline.sh` through `kickflip/bin/statusline.mjs`,
|
|
|
28
28
|
|
|
29
29
|
Kickflip doesn't ship `wave-build`, `slack-bridge` or `codex-share`; init doesn't add them and doctor doesn't check them. The mattpocock skills an architecture review follows, improve-codebase-architecture, codebase-design, grilling and prototype, are a prerequisite kickflip doesn't pin: init adds any that are missing with the skills CLI (`npx skills add mattpocock/skills -g -y -a claude-code --skill …`), and doctor marks a missing or out-of-date one optional with the command that fixes it (`shared/skills.mjs`). The `monitor` skill is optional: the hub polls GitHub itself and, when Hub's Linear key is set, moves the owner's Linear tickets from PR state ([the hub's poll moves the owner's Linear tickets](docs/adr/2026-11-02-0023-the-hubs-poll-moves-the-owners-linear-tickets.md)); without a key it moves none. The session leaves these PR-based moves to the hub. This skill's GitHub tick handles `github-change`, `check-github` and `card-merge`. When the owner's monitor skill is installed, a session may run its tick instead, which can add its own steps; kickflip owns the reviewer steps below.
|
|
30
30
|
|
|
31
|
-
The same command installs the hub's service, which starts the hub through the `~/.claude/skills/kickflip` link, starts it, and maps Tailscale `/` to it. `shared/service.mjs` picks the service by platform: on macOS a launchd login item, `~/Library/LaunchAgents/local.claude.kickflip-hub.plist`; on Linux a systemd user unit, `~/.config/systemd/user/kickflip-hub.service`, enabled and started with `systemctl --user`. A user unit stops with the owner's last login session unless the user lingers, so on a server run `sudo loginctl enable-linger "$USER"` once; doctor notes it ([the hub runs under the host's service manager](docs/adr/2026-11-01-1542-the-hub-runs-under-the-hosts-service-manager.md)). Live state (manifest, sites, settings and builder worktrees) stays in `~/.claude/kickflip/`. Paths saved for later use inside the kickflip package folder or clone go through `~/.claude/skills/kickflip` when that link resolves to the running root, including board commands, session hooks and prompts, session folders and builder records; moving the folder and repointing that link keeps them valid. `storedPath()` in `bin/install.mjs` owns this conversion; paths used only while code runs can stay real. Board repo clones live in `~/.claude/kickflip/repos/<owner>/<repo>`, or `<host>/<path…>` there for a repo on another git host, whether kickflip runs from a clone or an installed package; `shared/repos.mjs` owns the base path. Doctor lists each clone left in an old place (a clone's `.repos/`) as optional, naming the boards that use it, and `doctor --fix` asks in a terminal whether to remove each one ([board clones live outside the package](docs/adr/2026-11-04-0103-board-clones-live-outside-the-package.md)). `.repos/` stays gitignored and excluded from land's test suite along with `node_modules/` ([kickflip ships as a minified npm package](docs/adr/2026-11-01-2247-kickflip-ships-as-a-minified-npm-package.md)).
|
|
31
|
+
The same command installs the hub's service, which starts the hub through the `~/.claude/skills/kickflip` link, starts it, and maps Tailscale `/` to it. Every tailscale call runs the CLI `shared/tailscale.mjs` finds on each call: `tailscale` on PATH, else on macOS the GUI app's own binary, `/Applications/Tailscale.app/Contents/MacOS/Tailscale` (or in `~/Applications`), which the App Store build never puts on PATH. With neither, the hub still starts and serves this host, init skips only the mapping with the reason and the install hint, and doctor and the Hub page's Tailscale row say what's missing ([a board starts and lands without Tailscale](docs/adr/2026-11-04-1513-a-board-starts-and-lands-without-tailscale.md)). `shared/service.mjs` picks the service by platform: on macOS a launchd login item, `~/Library/LaunchAgents/local.claude.kickflip-hub.plist`; on Linux a systemd user unit, `~/.config/systemd/user/kickflip-hub.service`, enabled and started with `systemctl --user`. A user unit stops with the owner's last login session unless the user lingers, so on a server run `sudo loginctl enable-linger "$USER"` once; doctor notes it ([the hub runs under the host's service manager](docs/adr/2026-11-01-1542-the-hub-runs-under-the-hosts-service-manager.md)). Live state (manifest, sites, settings and builder worktrees) stays in `~/.claude/kickflip/`. Paths saved for later use inside the kickflip package folder or clone go through `~/.claude/skills/kickflip` when that link resolves to the running root, including board commands, session hooks and prompts, session folders and builder records; moving the folder and repointing that link keeps them valid. `storedPath()` in `bin/install.mjs` owns this conversion; paths used only while code runs can stay real. Board repo clones live in `~/.claude/kickflip/repos/<owner>/<repo>`, or `<host>/<path…>` there for a repo on another git host, whether kickflip runs from a clone or an installed package; `shared/repos.mjs` owns the base path. Doctor lists each clone left in an old place (a clone's `.repos/`) as optional, naming the boards that use it, and `doctor --fix` asks in a terminal whether to remove each one ([board clones live outside the package](docs/adr/2026-11-04-0103-board-clones-live-outside-the-package.md)). `.repos/` stays gitignored and excluded from land's test suite along with `node_modules/` ([kickflip ships as a minified npm package](docs/adr/2026-11-01-2247-kickflip-ships-as-a-minified-npm-package.md)).
|
|
32
32
|
|
|
33
33
|
## Prereqs
|
|
34
34
|
|
|
35
|
-
`node $KF doctor` checks Git; Node on the PATH this Claude Code session inherited; gh installed and signed in; Git reaching GitHub over https with gh's token, as the hub clones; Tailscale installed, signed in and serving `/` to the hub; an optional Codex, installed on that PATH and logged in (`~/.codex/auth.json`); the optional mattpocock skills, installed and matching their source's tree in `~/.agents/.skill-lock.json`; and an optional tmux, which only a hub board's session needs. It also checks the kickflip and kickflip-hooks links, `~/.claude/statusline.sh`, `statusLine` in Claude settings, the running hub service (the login item on macOS), the hub and every site, and each board's repo as the hub fetches it, with Git's own login (`git ls-remote --exit-code origin HEAD` from the repo), which can fail while gh's token reaches GitHub; on Linux also whether the user lingers and what reads differently off macOS. It changes nothing. Install hints name Homebrew on macOS and apt elsewhere, with Tailscale's install script and `npm i -g @openai/codex` where apt has no package. Each line is `<mark> <item>: <detail>`, marked **ok**; **fixable**, which `kickflip doctor --fix` sets up; **yours**, which only the person can fix; **optional**, Codex, needed only for Codex builds and reviews, the mattpocock skills, needed only for architecture reviews, or tmux, needed only for a hub board's session; or **note**, a down site the running hub restarts. A fixable, yours or optional line is followed by `→ <command>`. `--json` prints `{items: [{group, item, state, detail, fix}]}`. Exit 1 means something is fixable or yours.
|
|
35
|
+
`node $KF doctor` checks Git; Node on the PATH this Claude Code session inherited; gh installed and signed in; Git reaching GitHub over https with gh's token, as the hub clones; Tailscale installed, on PATH or as the macOS app, naming the binary it found, signed in and serving `/` to the hub; an optional Codex, installed on that PATH and logged in (`~/.codex/auth.json`); the optional mattpocock skills, installed and matching their source's tree in `~/.agents/.skill-lock.json`; and an optional tmux, which only a hub board's session needs. It also checks the kickflip and kickflip-hooks links, `~/.claude/statusline.sh`, `statusLine` in Claude settings, the running hub service (the login item on macOS), the hub and every site, and each board's repo as the hub fetches it, with Git's own login (`git ls-remote --exit-code origin HEAD` from the repo), which can fail while gh's token reaches GitHub; on Linux also whether the user lingers and what reads differently off macOS. It changes nothing. Install hints name Homebrew on macOS and apt elsewhere, with Tailscale's install script and `npm i -g @openai/codex` where apt has no package. Each line is `<mark> <item>: <detail>`, marked **ok**; **fixable**, which `kickflip doctor --fix` sets up; **yours**, which only the person can fix; **optional**, Codex, needed only for Codex builds and reviews, the mattpocock skills, needed only for architecture reviews, or tmux, needed only for a hub board's session; or **note**, a down site the running hub restarts. A fixable, yours or optional line is followed by `→ <command>`. `--json` prints `{items: [{group, item, state, detail, fix}]}`. Exit 1 means something is fixable or yours.
|
|
36
36
|
|
|
37
37
|
## Add a repo as a board
|
|
38
38
|
|
|
@@ -55,13 +55,13 @@ To put a page on the owner's phone with nothing for Claude to answer, join it as
|
|
|
55
55
|
These make a site Claude answers: a board, or a page joined with `--watch`.
|
|
56
56
|
|
|
57
57
|
1. **Who you are.** Your session id is the directory name just above your scratchpad (`…/<session-id>/scratchpad`). Your label is the session's name if it has one, otherwise two or three words for its work.
|
|
58
|
-
2. **Hub up.** Run `node $KF doctor`. When anything is fixable, ask the user once (it adds the hub's service, a login item on macOS or a systemd user unit on Linux, maps Tailscale `/` to the hub and sets up kickflip's status line and hooks as above), then run `node $KF doctor --fix` and read its new report. For anything marked yours, tell the person the printed command; a foreign status line needs their explicit replacement. Done when doctor
|
|
58
|
+
2. **Hub up.** Run `node $KF doctor`. When anything is fixable, ask the user once (it adds the hub's service, a login item on macOS or a systemd user unit on Linux, maps Tailscale `/` to the hub and sets up kickflip's status line and hooks as above), then run `node $KF doctor --fix` and read its new report. For anything marked yours, tell the person the printed command once and go on; a foreign status line needs their explicit replacement. Done when doctor reports nothing fixable: yours, optional and note lines are the person's and never hold up the join, so a session the hub started in the background, which nobody watches, still joins and lands its board ([a board starts and lands without Tailscale](docs/adr/2026-11-04-1513-a-board-starts-and-lands-without-tailscale.md)). See [doctor checks and fixes a hub](docs/adr/2026-11-01-1433-doctor-checks-and-fixes-a-hub.md).
|
|
59
59
|
3. **Join.** Run `node $KF join --name "<Board name>" --session <id> --watch --label <label> --description "<one line>"`. The board name is the `--name` your first prompt gave (`/kickflip --name '<Board name>' …`, plus any join flags after it, which you pass on), else the route you were given, which keeps the site's title; the route is the name hyphenated. A new route copies the starter kit into `~/.claude/kickflip/sites/<route>/`, picks a free port from 4600, and the hub starts it within 2 seconds. An existing route keeps its files and command. To serve an existing project, pass `--dir` and `--cmd '["/abs/node","/abs/server.mjs"]'`, and `--port` if it has a fixed one. `join` also records the session's folder (`--repo`, by default the session's own cwd) and an optional `--model`, which `start` and `resume` use (Board sessions, below).
|
|
60
60
|
- **Icon** ([site icons are line icons from a built-in set](docs/adr/2026-10-29-1639-site-icons-are-line-icons-from-a-built-in-set.md)). The sidebar draws each site's row with a line icon from `hub/icons.mjs` on a tint of the site's color. Pass `--icon <key>`, such as `phone` or `book` (`--icon x` lists the keys), and `--color <css hex>`. Without a key the hub picks an icon from words in the route and title, else shows the title's first letter, on a color picked from the route. `node $KF set <route> --icon … --color …` changes them later without joining.
|
|
61
61
|
- **A board** (Linear-style lanes of PRs and tickets, item threads, questions for Love): add `--template board`, plus `--items <file>` when the items come from the site's own wave.json-format file rather than every `~/.claude/waves/*/wave.json`. At each session start after joining, run `board.mjs --site <route> integrations report --slack yes|no`: yes when Claude’s Slack connector tools such as `slack_send_message` are available in this session, no otherwise ([integrations show readiness, and features wait until theirs is ready](docs/adr/2026-11-02-1118-integrations-show-readiness-and-features-wait.md)). The site then runs the shared `board/` code, whose `server.mjs` header documents `board.json` and the API. Every board shares that code, so make board changes in `board/` and copy none of it into a site. A site's `public/` holds only its own files, such as prototype pages, and any page it overrides. Changes to the hub or the board template come in as asks on the hub board ([hub changes come as asks, not Improvements rounds](docs/adr/2026-10-29-1639-hub-changes-come-as-asks-not-improvement-rounds.md)).
|
|
62
62
|
- **Exit 3 means another session owns it.** Ask the user with AskUserQuestion: take it over (recommended when the old session has ended or the user asked for this), or leave it. On yes, rerun with `--force`. The takeover is total: the old owner's watcher prints `handed-over` and that session stops serving the route.
|
|
63
63
|
4. **Build or resume the site.** Done when `curl -s -o /dev/null -w '%{http_code}' <url>` returns 200 for each page you changed, and a page meant for the phone renders at 390px wide with no sideways scroll. Follow the prefix rule below. The page design is yours; the starter's `public/index.html` is a placeholder.
|
|
64
|
-
5. **Watch.** Run the `watcher` command `join` printed as a background Bash task. It exits on the next event. For each `event` line, relaunch the watcher first, then act and reply (below). Its cursor means no line is missed. The board reads "not listening" while no watcher runs in your running session, or while your session isn't running and the hub can't wake it. A background session that idles out reads asleep, in grey, and the hub resumes it when the next event lands or the owner taps the board's page, the header's refresh included; the header reads waking… until it listens ([a board's session wakes when an event arrives, and live means its session runs](docs/adr/2026-10-30-1601-a-boards-session-wakes-when-an-event-arrives.md), [boards run in the background, sleep and wake from the page, and the hub board runs in tmux](docs/adr/2026-11-03-0313-boards-run-in-the-background-and-the-hub-board-in-tmux.md)). On `handed-over` or `site-removed`, stop; don't relaunch. Tell the user. The Stop hook `hooks-plugin/hooks/watcher-stop.mjs` keeps a turn from ending once while a board your session owns has no live watcher, naming the command to relaunch, or while a message there reads Working on it with no answer, naming each ([the CLI and board server do the board's housekeeping](docs/adr/2026-11-02-2044-the-cli-and-board-server-do-the-boards-housekeeping.md), [the board records what it knows, and commands make the rule calls](docs/adr/2026-11-03-0003-the-board-records-what-it-knows-and-commands-make-the-rule-calls.md)). On a board, follow the `quiet` line the watcher prints on every wake: the terminal gets only the one-liners `board.mjs` and the watcher print, except in answer to a prompt typed there.
|
|
64
|
+
5. **Watch.** Run the `watcher` command `join` printed as a background Bash task. It exits on the next event. For each `event` line, relaunch the watcher first, then act and reply (below). Its cursor means no line is missed. The board reads "not listening" while no watcher runs in your running session, or while your session isn't running and the hub can't wake it. A background session that idles out reads asleep, in grey, and the hub resumes it when the next event lands or the owner taps the board's page, the header's refresh included; the header reads waking… until it listens ([a board's session wakes when an event arrives, and live means its session runs](docs/adr/2026-10-30-1601-a-boards-session-wakes-when-an-event-arrives.md), [boards run in the background, sleep and wake from the page, and the hub board runs in tmux](docs/adr/2026-11-03-0313-boards-run-in-the-background-and-the-hub-board-in-tmux.md)). On `handed-over` or `site-removed`, stop; don't relaunch. Tell the user. The Stop hook `hooks-plugin/hooks/watcher-stop.mjs` keeps a turn from ending once while a board your session owns has no live watcher, naming the command to relaunch, or while a message there reads Working on it with no answer, naming each ([the CLI and board server do the board's housekeeping](docs/adr/2026-11-02-2044-the-cli-and-board-server-do-the-boards-housekeeping.md), [the board records what it knows, and commands make the rule calls](docs/adr/2026-11-03-0003-the-board-records-what-it-knows-and-commands-make-the-rule-calls.md)). On a board, follow the `quiet` line the watcher prints on every wake: the terminal gets only the one-liners `board.mjs` and the watcher print, except in answer to a prompt typed there. Every wake on a board also prints `waits <kind> <ref> <key> since <iso>[, picked up <iso>]: <next>`, one line per card idle on you, In Progress then Todo in the board's order. Each lane works as your queue: after the events, walk that list top-down and handle every card on it, not only the cards the events named: `calls`, verify the builder's judgment calls; `checking`, land the checked build; `answer`, act on the owner's answer; `ask`, answer or defer the builder's ask; `grill` or `prototype`, start the planner asked for; `refused`, fix what refused the build and start it, or say on the card why it waits; `unowned`, start a builder on the card or set its Next. A line with `picked up` is one you started and didn't finish. The server appends `orchestrator-waits {added: [key]}` when a card joins the list, a hand-back with calls included, and pushes the owner once about a card left waiting 30 minutes. The card reads waiting for orchestrator until your first write on it (a thread line, `builder verified`, a builder start, or the watcher's working mark on its event), then orchestrator review ([each lane is a queue for the orchestrator](docs/adr/2026-11-04-2300-each-lane-is-a-queue-for-the-orchestrator.md)).
|
|
65
65
|
6. **Record** the watcher's task id, and the site's route and URL, in your scratchpad `watchers.md`, with how to restart them after a session restart: rerun step 3 with the same `--name` and `--session`, then step 5. When the hub resumes a session, it asks for exactly that, naming the id to rejoin with; use that id, since a resumed session can come back under a new one. A rejoin keeps the label, model and folder it leaves out.
|
|
66
66
|
|
|
67
67
|
## Reference
|
|
@@ -84,7 +84,7 @@ These make a site Claude answers: a board, or a page joined with `--watch`.
|
|
|
84
84
|
|
|
85
85
|
**Sidebar, app shell and hub page** ([a sidebar of every board replaces the hub home](docs/adr/2026-10-29-1639-a-sidebar-of-every-board-replaces-the-hub-home.md), [one app for the whole hub](docs/adr/2026-10-29-2005-one-app-for-the-whole-hub.md)). Every page shows a sidebar of every site from `/_hub/sidebar.json`, Boards apart from Pages: a rail from 900px wide, and narrower a drawer. The header's kickflip logo toggles it at every width, opening the drawer or collapsing and restoring the rail, which this device remembers. The last row of Boards is New board: a + tile opening a repo picker, newest push first, with search over the owner's and their orgs' repos. Signed-out owners see `gh auth login` and a field to paste a git link. The next step shows the clone path, boards sharing it, a branch picker defaulting to the repo's default, Name prefilled from the repo, optional What it’s for, and Tickets, defaulting to Internal · INT. Start confirms: the hub clones into `~/.claude/kickflip/repos/<owner>/<repo>` if needed, then starts the board's session there with its join flags ([a new board chooses a repo and branch](docs/adr/2026-10-31-1636-a-new-board-chooses-a-repo-and-branch.md)). A muted, non-link row shows the board's name, three start steps on the board's phase bars (cloning, starting orchestrator, landing it) and the step with its seconds: cloning fills from git's real progress as cloning (NN%), and the later steps pulse; at join it turns into the board's row, which fades in ([a new board shows its start steps and live clone progress](docs/adr/2026-11-04-1914-a-new-board-shows-its-start-steps-and-clone-progress.md)); a failed clone or session start keeps the board's name in red with the sub line failed to start, as does a start that never joins after 15 minutes. Tapping it opens a sheet with its error line ([a clone uses gh's token over https, and a failed start shows git's real error](docs/adr/2026-11-02-1207-a-clone-uses-ghs-token-over-https.md)), repo, branch, tickets and when it was asked, with Dismiss and Retry, which stops a timed-out start's session and starts it again from the same record, its clone path taken again from the repo and the current clone home ([a failed start retries from its sheet](docs/adr/2026-11-02-2006-a-failed-start-retries-from-its-sheet.md)). Its order is New version when ready, Hub, Turn on notifications when needed, Pages, then Boards with New board last. The app shows New version with Reload when the hub’s code changed since it loaded, and never reloads by itself ([the app reloads for new hub code only on the owner's tap](docs/adr/2026-11-01-1824-the-app-reloads-for-new-hub-code-only-on-the-owners-tap.md)). An npm install's row reads Update available · Upgrade while npm has a newer kickflip ([the hub offers a newer kickflip from npm](docs/adr/2026-11-03-1521-the-hub-offers-a-newer-kickflip-from-npm.md)). Its header reads Kickflip. Below 900px every page header is a bottom bar and the drawer header sits under New board; desktop headers stay at the top ([phone headers sit at the bottom, with boards nearest the thumb](docs/adr/2026-10-31-1532-phone-headers-sit-at-the-bottom.md)). Hub shows notification problems and labelled Claude and Codex 5-hour usage bars under its sub line; the whole row opens the Hub page and stays selected on the hub page and its settings modals. On a keyboard, s pressed twice shows each linked row's key, a board's number or a letter from the row's name claimed in the order sites joined (`created` in the manifest), and the next key opens that row ([Switch mode's letters come from names, claimed in the order sites joined](docs/adr/2026-10-30-1102-switch-mode-letters-come-from-names-in-join-order.md)). h pressed twice shows or hides a centred Keyboard shortcuts sheet listing every shortcut, from one list in `board/public/ui-sidebar.js`; Escape closes it, and a phone never shows it. Pages open in one app shell (`board/public/app.html`), where the sidebar stays put and a site's row, a utility row, or a link to a board's Waiting on you or Notes, swaps the view in place, with no reload; the shell keeps one stream to the hub, `/_hub/stream`, for every board's change signal and the sidebar's updates. Other sites' pages and a board's prototypes show in a frame under the board header, whose title follows the page's. `/` and the Home Screen app open this device's last board, or on a first visit the board with the most waiting; never a page. While the host is asleep the app still opens, from the shell the service worker `/sw.js` saved, with the last sidebar and each board's last visit dimmed; it caches only the shell and its `/_hub/app/` files, never a site's page, API call or upload. The hub page, `/_hub/`, is a view of the shell too; it and its settings modals read their data from `/_hub/api/…`, refreshing it every 30 seconds. `/_hub/` is the hub's status page: pause and Keep going banners, then Usage (5-hour and weekly percentages, reset times and when read), then the hub's health, then Boards with their links and watchers, then Pages and polling status. Below them, Notifications, Quiet hours, Linear, Rules for every board and Closed boards keep one-line summaries and each open a centred settings modal. Claude appears after the status line writes `~/.claude/kickflip/usage.json`. Codex's block opens with the Codex share control, and Codex's bars sit under it unless share is Off. While Codex isn't installed or logged in, the control is greyed out on Off, with the reason and the command to run (`brew install --cask codex` on macOS, `npm i -g @openai/codex` elsewhere, or `codex login`) ([Codex share sits in the Usage card](docs/adr/2026-11-01-1428-codex-share-sits-in-the-usage-card.md), [Codex share follows whether Codex is usable](docs/adr/2026-11-01-1521-codex-share-follows-whether-codex-is-usable.md)). Usage and the `pause` gate come through `/_hub/sidebar.json` and `/_hub/api/hub`; older-than-window readings show “last seen” with grey stripes, otherwise a window whose reset passed reads as reset. Bars turn amber from 75% and red from 90% ([the sidebar shows Claude and Codex usage under Hub](docs/adr/2026-11-01-1338-the-sidebar-shows-claude-and-codex-usage-under-hub.md)). The Hub row opens this page; old `/_hub/settings#<section>` links open its modal, and the old `/_hub/settings/polling` opens Quiet hours. Opening or closing a Hub modal replaces the URL without adding history entries ([hub settings open as modals](docs/adr/2026-11-01-1221-hub-settings-open-as-modals.md)). Board Settings opens only that board’s controls in a centred modal at every width, scrolling inside on a phone; its gear and old `settings.html` links open it over the board, and closing preserves the board and open card without adding history entries ([hub settings expand in place and board settings open in a modal](docs/adr/2026-10-31-1721-hub-settings-expand-in-place-and-board-settings-open-in-a-modal.md)).
|
|
86
86
|
|
|
87
|
-
**Board template** (`--template board`, `~/.claude/skills/kickflip/board/`): a work board over wave files. A running builder with no progress for 30 minutes (from its start before its first progress) reads Stalled. The server sends `builder-stalled {key, silentSince}` once per silent spell, including across a restart; tapping Stalled and confirming sends `builder-nudge {key}`. On either event, check the builder's process and log, restart or finish the run as needed, and write one thread line on the card saying what you found. A nudge on a card that is no longer stalled gets a thread line too. Every write an agent makes to a board site goes through `node ~/.claude/skills/kickflip/board/bin/board.mjs --site <route> <command>`: replies, thread lines, question topics and questions, item status and `next`, and builder records. Its usage text lists the commands. Hand-edited JSON skips the checks the pages rely on. A new card is `board.mjs item add <wave> --title …`, never a hand edit of a wave file: it makes the card's ticket, names the card from it and prints `<label> added as <key>`; use that key, and pass `<wave>/<id>` only to import a card with its own id ([a card is named by its board's ticket source](docs/adr/2026-11-02-2100-a-card-is-named-by-its-boards-ticket-source.md)). A card filed from another card's thread, grill or answer, a follow-up included, takes `--parent <that card's key>`; one from the + has none ([a follow-up is a work card with a Parent](docs/adr/2026-11-03-0526-a-follow-up-is-a-work-card-with-a-parent.md)). A board serves `GET /_claude/needs` itself, one item per Needs you line. The + sends an `ask` event, shown as a New card at the top of Todo until you sort it ([the + makes a New card, which Claude files, folds or answers](docs/adr/2026-10-29-1639-the-plus-makes-a-new-card.md)). Take New cards from the top. Sort New cards: file it under a wave (`--from-ask`), fold it into the card it's about (`item relate-ask`), or answer it (`reply`), all below. **Every new task is grilled or prototyped at once, unless the owner chose Quick build** ([every new task is grilled or prototyped at once](docs/adr/2026-11-04-0131-every-new-task-is-grilled-or-prototyped-at-once.md)). The New task sheet asks Discuss (the default) or Quick build above Send; a Quick build ask comes as `ask {text, quick: true}`, and its thread reads "quick build: no grill, as you asked" under it. On a Discuss ask, as soon as the card is filed and titled, set it `--state grilling` and dispatch a grill (`builder start <key> --dispatch --kind grill`, then the agent), or `--state prototyping` and a prototype agent when it's a UI ask; which planner is your call. Never wait for the idle pull or `card-plan`. On a Quick build ask, file it `--state ready` with its plan in the notes, never on the card, and dispatch nothing; with auto implement on, the board takes it from there. A `grill-more {key}` (the card's Grill) or a `prototype-request {key}` with no question (the card's Prototype) is the owner's request: the card already reads "preheating" or "prototyping" in planner blue and the thread a muted "grill requested" or "prototype requested", so write no thread message; dispatch that planner, building its prompt from the event. First check it against the other boards: each one's title and description in `~/.claude/kickflip/manifest.json`, its folder (`node $KF sessions`) and its cards. Only when another board clearly fits better, add an open question on the New card (`question add`, topic `item:ask:<eventId>`): "Move to <board>" recommended, then "Keep here", each with `diagram` and `practice`. On Move, run `board.mjs --site <route> ask move <eventId> --to <other route>`, which makes it a New card there and wakes that board's session, then `question resolve`; on Keep, sort it here. Hand a long build or investigation to a builder, which reports in the card's thread. The owning session only triages events, writes board lines and dispatches: a full test suite, a scratch hub, screenshots, `board.mjs land` and anything else that takes more than a few seconds run in a background agent or background task. A session sitting in foreground work leaves events waiting, and the board reads stalled. The watcher marks each ask, thread message and wave triage it prints as Working, so the page reads "Working on it" until you reply, write a later thread line on its card, reply to its ask, turn or fold its ask into a card, or the card closes. Answer a quick ask with `board.mjs reply <eventId>`. An answer never pushes: the board's answer banner and the thread's unread mark carry it ([a push goes out only when something waits on the owner, on the hub's schedule](docs/adr/2026-10-29-1639-pushes-only-when-something-waits-on-the-owner.md)). When the answer waits on the owner, such as a pick between prototypes, also run `board.mjs --site <route> ask progress <eventId> --next "<what the owner picks>"`. An `ask-done {id}` event is the owner closing it, which needs nothing beyond relaunching the watcher. `board.mjs ask done <eventId>` closes it from your side ([an ask can wait on the owner before Done, and the owner closes any open card from its thread](docs/adr/2026-10-29-1639-an-ask-can-wait-in-progress-until-the-owner-marks-it-done.md)). To turn an ask into work, add the work item named for the change and run `board.mjs item set <wave>/<id> --from-ask <eventId> --text "<first line>"`: the New card gives way to the item, whose thread opens with the ask and then that line, so the item stops reading "Working on it". A card's **Notes** is your understanding of the task so far: what was asked, what's settled, what's still open. Keep it as a `.md` file in its wave's folder, set it with `board.mjs item set <wave>/<id> --notes-file <file>` (`--plan` still works), and rewrite it as the grill settles things rather than freezing it as a plan; the thread keeps the short news. Setting the notes file resolves the card's answered questions still waiting on Claude, noting "Recorded in the card's notes", and prints `resolved: <id>` for each. The open card's details list ends with a "Notes ↗" link, which opens `notes.html`, a view that renders the file for the phone, or the item's short `notes` text when it has no file ([notes replace the full plan, and a card starts on its own](docs/adr/2026-10-29-1816-notes-replace-the-full-plan-and-cards-start-on-their-own.md), [notes is a link in the details list, with no stored summary](docs/adr/2026-10-30-1626-notes-is-a-link-in-the-details-list.md)). `item set --title` renames a card. When an ask is about an existing item, fold it in with `board.mjs item relate-ask <eventId> <wave>/<id> --summary "<one concise line>"`: the New card goes, and the owner's message moves into the item's thread marked Moved, with the summary after it. When the owner says it went to the wrong card, relate-ask it to the right one, or run `board.mjs ask unfold <eventId>` to give it a New card again. An `item-message` whose `key` is `ask:<eventId>` is the owner replying in a Done ask's thread: answer it with `board.mjs reply <its eventId>` or `board.mjs thread ask:<eventId> --text …`, as in a work item's thread. An `ask-withdrawn {id}` event (with `key` from a thread) deletes a pending message ([a pending ask or thread message can be deleted or edited, and a pending ask bumped](docs/adr/2026-10-29-1639-a-pending-ask-can-be-deleted-or-bumped.md)). An `ask-edited {id, text}` event (with `key` from a thread) replaces a pending message's text: use the new text, and if you had started, adjust the work to it and say what changed. An `ask-bump {id}` event means take that ask, or that pending prototype request, next. A `card-bump {key}` event bumps a work card ([a card that hasn't started, or is running, can be bumped](docs/adr/2026-10-29-1639-a-card-that-hasnt-started-can-be-bumped.md)): a card that hasn't started starts next once it is unblocked, and a running card's next steps (its builder's questions, review fixes, CI) come ahead of other work, and it goes first among cards waiting for a free builder. A `card-hold {key, hold, until?}` event is the owner's Hold (`hold: true`, with `until` when they picked a time) or Unhold (`false`) on a work card. A held card never starts on its own; stop work on a held running card, with no new builder, review fixes or CI reruns until it is resumed; once resumed, start it if nothing blocks it. When the owner's words give a time to pick a card up, hold it with `item set <key> --pick-up-at <ISO time>` (no offset is the Mac's local time). Within a minute of that time the board server resumes the card itself and appends `card-unblocked {key, until}` unless a dep is unfinished ([a card can be held until a time](docs/adr/2026-11-02-1727-a-card-can-be-held-until-a-time.md)); treat it as any `card-unblocked`, carrying on with a card that had started. A `card-done {key}` event is the owner closing a card from its thread's Done: follow up only by stopping any builder on it. Closing a card yourself (`--status done` or `dropped`) takes `--text "<what was done or left>"`, which becomes its last thread line; `--status merged` writes "Merged PR #n." unless `--text` says otherwise. Closing resolves each of its questions that has an answer, noting "Closed with the card"; one still open, with no answer yet, stops it, and the refusal names that question. `--force` closes the card anyway and resolves that one too. Never touch GitHub for it: an open PR stays as it is. A `card-merge {key}` event is the owner's Merge on a card whose PR is approved, green and CLEAN while the board's auto-merge is off (Auto-merge, below): run `board.mjs --site <route> merge <key>` now. When it refuses, it writes why as a thread line, which ends the button's "Merging…". Start a card as soon as its open questions are all answered and nothing blocks it, without waiting for a go; `unblocked: <key>` or a `card-unblocked {key}` event means start it now, unless held ([notes replace the full plan, and a card starts on its own](docs/adr/2026-10-29-1816-notes-replace-the-full-plan-and-cards-start-on-their-own.md)). A `card-build {key, ref, kind, reason, worktree}` event means the board server has already started the owner's Build on that card: it made the refusals a build's `builder start` makes, picked the builder (`kind`, claude or codex, with `model` for claude, for `reason`), made the worktree at `worktree` and marked the card building and cleared its Next, so it sits in In Progress reading "starting". Your only job is to write the builder's brief and launch it now, with the card's notes as they stand: a `kind` builder working only in `worktree`. Don't run `builder pick` for it yourself. The builder's own first step is still `builder start <key> --kind build`, which takes up the tap's record, keeping its kind and worktree; its first status line then replaces "starting". It takes up the record before any agent under another `--agent` name may start on the card. A refused tap sends no event: the board writes the reason in the card's thread and the card stays in Todo showing it ([a Build tap starts the build in the board server](docs/adr/2026-11-03-0219-a-build-tap-starts-the-build-in-the-board-server.md)). **Builders launch headless** on a board with `setting headless-builders on` (set with `settings set headless-builders on|off` only on the owner's word): the board server launches each Claude build itself, as `claude -p` in the card's worktree with a standard brief built from the card's notes and the rules, for the Implement tap, a queued start and Auto implement's pull alike, so no `card-build` or `card-pull` reaches you; a codex pick, or a launch that fails, still sends `card-build`. On such a board `item set --state ready` refuses a card without `--size`, without a notes file, or whose notes don't name the id of each question the owner answered on it, so record every answer in the notes before marking it ready. The builder asks you as any builder does, and finishes its own work with `builder done --text`, rebasing and resolving conflicts itself; finishing never comes to you. A build with calls hands back as before: verify them (`builder verified`), which finishes the card in the same step; when a question on the card was still open, or that finish refused, the server launches a headless lander once no question is open, which acts on the owner's answers and finishes it, so don't land it yourself ([builders launch headless, check their notes and land on their own](docs/adr/2026-11-04-0306-builders-launch-headless-check-their-notes-and-land-on-their-own.md)). A `card-pull {key}` event is the board pulling a ready Todo card into a free implementer slot, with Auto implement Todo cards on (`setting auto-pull on|off`, set with `settings set auto-pull on|off` only on the owner's word); several can arrive together, one per free slot, up to Maximum parallel implementers (`setting max-implementers <n>`, 10 by default, set with `settings set max-implementers <n>` only on the owner's word), counting cards building or in Orchestrator review: start each one's build yourself, or a grill when its notes still leave something open ([an idle board pulls in its top Todo card](docs/adr/2026-11-02-1754-an-idle-board-pulls-in-its-top-todo-card.md), [auto implement fills In Progress up to Maximum parallel implementers, on every board change](docs/adr/2026-11-04-0057-auto-implement-fills-in-progress-up-to-a-cap.md)). A `card-plan {key}` event is the idle board finding nothing ready to pull: start a grill on that card, or a prototype when it's UI work, so it moves toward ready ([the idle pull takes ready cards first, and plans the top card when none is ready](docs/adr/2026-11-03-1631-the-idle-pull-takes-ready-cards-first.md)). A `pr-review {pr, head, repo, title, author, url, touches, asked}` event, with auto review on, asks you to review a teammate's PR that touches the listed cards or asks the owner: read its diff and the reviews already on it, and, rereading them right before posting, post one Comment review (never approve or request changes) as the owner's gh login, its first line "Claude review from <owner>'s board" plus `<!-- claude-bridge -->`, with only points no other review made, or post nothing; then `review record <pr> --state posted --file <review.md> [--url <review link>]`. When it finds a blocker, disagrees with a decision on a card or clashes with a card in progress, post nothing and `review record <pr> --state needs --file <draft.md>` instead; the owner's Post it or Skip comes back as `review-answer {pr, answer}`, after which you post (or don't) and record it as posted ([each board reviews incoming PRs that touch its work, in an Auto Review lane](docs/adr/2026-11-03-1731-each-board-reviews-incoming-prs-that-touch-its-work.md)). An `overnight-triage {date, topic, cards}` event is the board asking for its overnight triage, with auto-triage on (`setting auto-triage on|off`, set only on the owner's word): it goes out once a night from the middle of quiet hours, when two or more cards, `cards`, wait on the owner's open questions and nothing builds. First read those cards' open questions against each other. Questions that ask the same thing become one: `question add` the merged question on the newest of their cards by `created`, naming in its `context` each card it answers for, and `question resolve` each of the others with `--note "Replaced by <new id>"`. When the owner answers a merged question, copy the answer onto each card it names, as a thread line and in its notes, before acting on it on any of them. Cards whose grills show they are the same work merge as a clear duplicate does below. Leave the questions you could answer yourself as they are. Then read the Todo and Stale cards against each other and against merged work, leaving Done alone (old Done cards leave by the archive rule, [the board keeps the newest 100 Done cards](docs/adr/2026-11-04-0307-the-board-keeps-the-newest-100-done-cards.md)), and find duplicates, cards a newer card covers, cards merged work already did, and deps that no longer hold. Link related cards (`item set --related`) and merge a clear duplicate into the newer card yourself, dropping the older with a thread line naming the one it went into. A close, or anything you aren't sure of, is one question per finding on `topic`, all in one `question add` array; quiet hours hold their push until morning. Then reply to the event, even when you changed nothing ([triage runs once overnight on cards waiting on your answers](docs/adr/2026-11-04-1900-triage-runs-once-overnight-on-cards-waiting-on-your-answers.md)). Set `deps` for work that waits on other cards. When `--status done`, `merged` or `dropped` finishes the last dep, it prints `unblocked: <key>` and appends a `card-unblocked {key}` event; leave a held card held until the owner unholds it ([cards wait on explicit blocked-by links](docs/adr/2026-10-29-1639-cards-wait-on-explicit-blocked-by-links.md), [Build on a blocked card queues it, and blocked, bumped and Action needed leave the badges](docs/adr/2026-11-03-0301-build-on-a-blocked-card-queues-it.md)). Every builder you start for a card works in its own git worktree, never in the folder you, a running app or the hub use ([every builder works in a worktree, and the hub's own fault shows once](docs/adr/2026-10-30-1654-every-builder-works-in-a-worktree.md)). A repo card's builder uses the board's manifest `github` and `branch` when following wave-build; it never switches the shared clone's checkout. A build's `builder start` on a board makes the card's worktree when its record keeps none, `~/.claude/kickflip/worktrees/<card id>` on branch `<card id>`, and prints `worktree: <path>`; the builder's prompt names that path as the only place it edits, tests and runs scratch hubs. Right before it launches any builder agent, the session runs `board.mjs --site <route> builder start <key> --dispatch --kind <kind> [--model …]`, which opens the record (`dispatched: 'session'`) so the card reads "starting" at launch, and changes nothing on a record the Implement tap already opened (`dispatched: 'tap'`), printing "already open"; it never posts a "builder is working" line itself ([the session opens the builder record when it dispatches an agent](docs/adr/2026-11-03-0513-the-session-opens-the-builder-record-when-it-dispatches-an-agent.md), [a card reads starting from its tap, with one dispatched mark](docs/adr/2026-11-03-1718-a-card-reads-starting-from-its-tap-with-one-dispatched-mark.md)). Every builder, prototype and grill agents included (`--kind prototype` or `--kind grill`), runs, as required steps in its prompt, `board.mjs --site <route> builder start <key> --kind <kind> --label "<label>"` first, which takes up the dispatched record (the label carries no model word) (a build agent passes `--kind build`, which records the kind and model `builder pick <key>` prints), `builder progress <key> --phase <reading|building|testing|checking|landing> "<detail>"` (or `--text "<detail>"`) at each milestone (a planner, kind grill or prototype, passes no `--phase`: its line reads "Preheating: <detail>" or "Prototyping: <detail>" in planner blue, never a builder phase word, and a `--phase` it passes is dropped) and `builder done <key>` before it hands back, so the card sits in In Progress with a live status line. Closing a card's last open record when it stays In Progress with no Next is refused: pass `builder done <key> --next "<what happens next>"`, or hand back with `item set <key> --status checking` or `land`, which close the record themselves. A card with a step only the owner can do once its code lands (a publisher to add, a release to tag) takes `--owner-step "<the step>"` on whichever of `builder done` and `land` finishes it: it then waits in In Review with that step as its Next and its ticket open, and only the owner's Done closes it ([a card whose code landed stays in review while an owner step remains](docs/adr/2026-11-04-0158-a-card-whose-code-landed-stays-in-review-while-an-owner-step-remains.md)); `item set` likewise refuses a write that would leave a built card with neither ([an In Progress card always has a builder line or a Next](docs/adr/2026-11-03-0237-an-in-progress-card-always-has-a-builder-line-or-a-next.md)). The detail is plain words: what the builder is doing and why it matters to the owner, such as `--phase testing "fix for the scroll jump into Done"`. A wait on a lock or a Codex limit goes in the detail. Commits, test counts, ADR numbers, file names and screenshots go in the hand-back and the session's thread message, not the line ([a builder status line is a phase and a short detail](docs/adr/2026-11-03-0128-a-builder-status-line-is-a-phase-and-a-short-detail.md)). A build's `builder start` is the one write that marks the card building: it moves the ticket to In Progress and prints `ref: <label>`, the card's name for its commit subject. `building` and `fixing` count as working only while a builder record on that card is open; with none open, its PR and other state place it as if the status were absent. `builder done` clears either status when it closes the card’s last open builder record; `item set --pr` clears either status too and closes ordinary builders while install builders keep running. When a subagent stops, a SubagentStop hook, `board/bin/builder-stop-hook.mjs`, closes the records it started and left open, but not one another agent restarted later, and not while background work the subagent started still runs (the input's `background_tasks`), since it resumes when that work ends. A skill's own hooks don't fire for SubagentStop, so the hook ships as the plugin `hooks-plugin/`, which Claude Code loads as `kickflip-hooks@skills-dir` through the link `~/.claude/skills/kickflip-hooks` (`claude plugin list` shows it); a new host needs that link. A site card's builder lands its own card: it runs the tests and checks the board at phone width in its worktree, commits there as the repo's configured user (`git -c core.commentChar=';' commit -F <file>`, or `--cleanup=verbatim`, so git keeps a "#N:" subject), then runs `board.mjs --site <route> builder done <key> --text "<what it checked>"`. With no unverified calls that finishes the card, the one step `land` also runs, as `builder done`'s usage lists: it lands the worktree, closes the card as done with that text as its last thread line, and with the board's "Push main after a land" switch on pushes main to origin itself, so no session pushes after a land; on a GitHub board it pushes the branch and opens a draft PR instead, and on a plain git remote pushes the branch ([finishing a build is one step](docs/adr/2026-11-04-1611-finishing-a-build-is-one-step.md)). A finish that fails refuses with the reason and the builder stays on: it fixes what the refusal names and runs `builder done` again. Only a builder that can't finish hands back, with a `builder call` naming why, then `builder done`: the card sits in In Progress reading "Orchestrator review" and unblocks nothing, and the owning session checks it, and its `builder verified` finishes it ([site cards build in parallel when their files don't overlap](docs/adr/2026-10-29-1639-site-cards-build-in-parallel-when-their-files-dont-overlap.md), [the board records what it knows, and commands make the rule calls](docs/adr/2026-11-03-0003-the-board-records-what-it-knows-and-commands-make-the-rule-calls.md)). On a refusal, fix what its line names in the worktree and finish again. **Judgment calls** ([a builder records its judgment calls and the session verifies them before land](docs/adr/2026-11-03-0514-a-builder-records-its-judgment-calls-and-the-session-verifies-them.md)): every builder's brief includes these steps. Right after `builder start`, it records each gap or ambiguity in its brief as `builder call <key> "Question: …"`; with any, it runs `builder done <key>`, which hands the card back for them, and stops, its report the questions. Answer them from the card's notes and thread, asking the owner on the card only what those don't settle, and resume it with SendMessage; its `builder start` then resumes the record, calls kept. While building, it records each judgment call as it makes it, `builder call <key> "<call>"`, one plain line each. A builder with calls doesn't finish: `builder done <key>` hands the card back for them to be verified, and its report lists them. Check each (`builder calls <key>`) against the owner's words and the other cards: one that conflicts with what the owner said goes to the owner as a question on the card, any other wrong one back to the builder by SendMessage. Then run `builder verified <key> [--note "<what you checked>"]`, which finishes the card in the same step, the note its closing line; while a question on the card is open it says so and the card waits, and a finish that fails keeps the mark and says why. `land` refuses while a record on the card has unverified calls. **Builder asks** ([a builder asks the orchestrator mid-build, and only what it defers reaches the owner](docs/adr/2026-11-04-0247-a-builder-asks-the-orchestrator-mid-build.md)): a builder's brief also says that, at any point, it can ask you a question with `builder ask <key> "<question>"` and carry on meanwhile, checking `builder asks <key>` for your reply. The ask shows in the card's thread as "Claude Opus builder" (by its model) and wakes you as a `builder-ask {key, ask, agent, text}` event; the card itself stays quiet. Answer from the card's notes, thread and the owner's words with `builder answer <key> <ask> --text "<answer>"`, or, when you aren't confident or the owner's opinion is wanted, `builder defer <key> <ask> --text "<what to carry on with>"`: never ask the owner mid-build. Both show in the thread as Orchestrator. A deferral is a "Question:" call on the builder's record, so its build hands back to you; after you've reviewed everything else, and before a draft PR or In Review, put it to the owner as a card question naming the ask (`"ask": "<ask>"` in the question file), which `builder verified` requires. The card then shows Open questions and "1 new question", with no Action needed. **A repo card's built work always goes straight to a draft PR** once it is checked (owner, 2026-10-23): on a GitHub board the finish (`builder done --text`, or your `builder verified` on a handed-back build) pushes the card's branch, opens the draft with `gh pr create --draft` and records it as `item set --pr` does, closing the builder records. For a PR opened by hand, run `item set <key> --pr <n> --clear-status`. Either way the card sits in In Progress while CI runs or fails, then in In Review with Action needed linking to the PR once CI passes, also while the owner tries it on a test deploy. A card whose work spans repos holds a PR in each: run `item set <key> --pr <owner/repo#n>` once per PR (it adds, never replaces; a bare number is the board's repo). Each PR gets its own badge and polling, the least-ready one decides the lane and Next, `merge` merges them all in order, and `pr` prints a line per PR ([a card holds several PRs across repos](docs/adr/2026-11-03-1904-a-card-holds-several-prs-across-repos.md)). A board whose repo is a plain git remote (manifest `remote`, no `github`) has no PRs: the finish pushes the card's branch to origin and records it as `item set <key> --pushed <branch>` does (by hand: `git push origin <branch>` in its worktree, then that command), so the card waits In Review on the owner's merge; the hub's poll moves it to Done once the branch is merged into the board's branch or gone from the remote, with a thread line ([a board can work on any git remote](docs/adr/2026-11-02-1850-a-board-can-work-on-any-git-remote.md)). A card's lane follows who acts next: one waiting only on the owner's Ready, Merge, manual steps or an ask's Done sits in In Review with Action needed, and goes back to In Progress once Claude works on it again ([the lane follows who acts next, and a card waiting on the owner says Action needed](docs/adr/2026-10-29-1639-the-lane-follows-who-acts-next.md)). One that hasn't started and waits only on its open questions or a prototype pick, ask or work card, stays in Todo with Action needed. An owner `item-message` on a Done card, or a question you add on one, reopens it on the board with no step from you: it sits in In Progress until you answer the message, then waits in In Review with Action needed until the owner's Done (an open question puts it there at once). Answer and leave it ([a message or question on a Done card reopens it until the owner's Done](docs/adr/2026-11-02-1952-a-message-or-question-on-a-done-card-reopens-it.md), [a merged card reopens on a message, and its PR badge shows the PR's state](docs/adr/2026-11-03-1843-a-merged-card-reopens-and-its-pr-badge-shows-the-prs-state.md)). Never park built work as `waiting`, which reads as blocked. `item set <key> --deps <list>` sets a card's blockers after it exists. A PR that changes what a page looks like carries screenshots in its description, as wave-build's build step says. **Each card and main has a version to try** ([a board's build script serves or builds each card and main](docs/adr/2026-11-01-1846-a-boards-build-script-serves-or-builds-each-card-and-main.md)). When setting up a board whose repo lacks `.kickflip/build`, write that executable repo file for the project. The hub runs each branch's copy with `<key> <worktree>` (key `main` for main), cwd the worktree, `KICKFLIP_BOARD=<route>`. Keep it owned by the current user and writable by neither group nor others. A website script starts its server, prints exactly `port <n>` on stdout and stays running as its server process. Serve browser paths at `/`; each served card and main gets its own Tailscale HTTPS port, with no `KICKFLIP_BASE` ([a try build serves on its own Tailscale port](docs/adr/2026-11-01-2254-a-try-build-serves-on-its-own-tailscale-port.md)). An app script exits 0 after publishing its install: `board.mjs --site "$KICKFLIP_BOARD" item set <key> --install <url> --install-sha <head sha>`, or `main set --install <url> [--install-sha <sha>]` for main. iOS uses `itms-services://?action=download-manifest&url=https://…/manifest.plist`; Android uses an https `.apk`. Keep install files under the site's `public/installs/`. Signing still needs the login keychain unlocked on macOS. The hub owns runs and their logs in `data/try/<id>.log`, reports only failed card runs in the thread and leaves each card in its lane. Try builds wait on Tailscale; the hub refuses a run with the readiness reason until it is ready. Only the owner's Serve or Build and Auto-build start a card run; a session never starts one on its own, not even for a device app after a push, and a board rule asking for that is the owner's cue to turn Auto-build on. When the owner asks for a run in words, run `board.mjs --site <route> build <key> --asked "<the owner's words>"` (`install` is an alias), quoting their ask, card message or answer note; it refuses words the owner didn't write on the board, prints `started` and exits ([only the owner or Auto-build starts a card run](docs/adr/2026-11-02-1806-only-the-owner-or-auto-build-starts-a-card-run.md)). Auto-build cards, off by default, runs each new head when on, and the card then has no Serve or Build; otherwise the card waits for Serve or Build, shown once its branch has changes. Set `settings set auto-build on|off` only on the owner's word. Main always builds after each merge, independent of the toggle, in the hub's detached worktree, and shows its version above the lanes. A served card stops after 30 minutes without a visit; main stays up. The card badge reads Visit/Install, Visit (stale)/Install (stale), an inert Serving…/Building… spinner or Serve failed/Build failed opening its thread. Main’s red Serve failed/Build failed button opens a bottom sheet with the last 40 log lines and Retry at the recorded failed tip, even if origin has moved. Retry waits on Tailscale readiness and shows refusals in the sheet; while running it reads Serving…/Building… in orange, then closes with a toast when main is ready or refreshes the log after another failure. Maintain a broken script through a card. **Marking a PR ready for a reviewer includes the Slack link** (owner, 2026-10-24): add them to the board's reviewers if they're missing (Board reviewers, below), then DM the bare PR link as the Reviewers steps below describe, even when no monitor or bridge runs for the wave, and record its ts in the wave's `review_link_ts`. Every hub feature finds boards through one registry, `hub/boards.mjs`, so each serves both kinds alike ([one board registry for every hub feature](docs/adr/2026-10-29-1639-one-board-registry-for-every-hub-feature.md)): the hub polls every open PR on any board's card, whatever `monitor.json` watches (a card's PR is in the board's `github` repo, else its wave's `repo`; with neither, the card has no PR link and nothing polls it), and a PR's `github-change` goes to each board with an eligible changed card. Boards over their own file (`--items`) use the same GitHub tick and Reviewers steps as wave boards. The hub checks GitHub every minute while Love is here and every 5 when away, and wakes you only on a change, or at the daily wake when quiet hours end ([the hub checks every minute while you’re here and every 5 when away](docs/adr/2026-11-04-0226-the-hub-checks-every-minute-while-youre-here-and-every-5-when-away.md)): a `github-change {prs: [{wave, id, pr, changes}]}` event, where `changes` lists `ci`, `review`, `comments`, `merged`, `mergeable`, or `draft`, means run one GitHub tick for those PRs. A `check-github {}` event is the owner's refresh asking for a check, and `check-github {reason: "morning"}` is the hub's daily wake (8:00am by default), so kickflip's reviewer digests go out on a quiet day: for either, run one GitHub tick. Run the Reviewers steps below as part of either tick. End each tick with `board.mjs --site <route> checked`, which takes `--every` as the away cadence, 5 minutes, so the In Review header reads late only once a check is overdue, and records idle by itself when no card has an open PR, so the time stays muted rather than late. It then sends the pushes the tick queued. The hub writes the same `checked.json` after each of its checks. When the header reads "not listening" in your own session, relaunch the watcher ([the board shows GitHub checks and Claude's activity, with one refresh](docs/adr/2026-10-29-1639-the-board-shows-github-checks-and-watcher-health.md)); it also reads so while the board's session isn't running and the hub can't wake it. When an event has waited 15 minutes while the board reads so, the hub pushes the owner "<title> isn't listening" once. A `hello` event is the hub testing a new board's links (`join --template board` runs `node $KF handshake <route>`, then asks the hub for one poll so the GitHub link passes at once): the watcher answers it itself and prints `hello <eventId> from the hub answered`. The board shows a Connecting checklist until that and the hub's GitHub check reach it; cards can start meanwhile. The In Review header's GitHub mark opens the Hub links sheet (GitHub polling, the repo fetch on a board with a repo, events, daily wakes, pushes, reviewer links). The watcher prints each broken one at once, after the rules, as `link <name> <detail>`; the mark gets a red dot only for a link still broken past its grace period, and the sheet shows it settling until then ([hub links turn red only after a grace period](docs/adr/2026-10-29-1639-hub-links-turn-red-only-after-a-grace-period.md)). Fix what is yours straight away, such as a missing reviewer link or a stopped watcher. `link hub` means the hub runs older code than its files, which the sidebar's Hub row and the hub page show in amber, never a board: restart it with the command the link names: `launchctl kickstart -k gui/$(id -u)/local.claude.kickflip-hub` on macOS, `systemctl --user restart kickflip-hub.service` on Linux. Tell the owner the rest. A card that changes the hub's or a board's UI starts with an in-situ prototype before any builder touches the skill: one prototype per question, one take per option, and an open question on the card whose `url` opens it; build only after the owner picks. Answer a `prototype-request {key}` event, the owner's ask from a card's thread, the same way, with a thread line linking the page ([prototypes come only when obviously helpful or on request](docs/adr/2026-10-29-1639-prototypes-come-on-request.md)). A `prototype-request {key?, question, note, choice}` is the owner's eye on one question, asked in place of an answer: build takes of that whole question, following the note and the option `choice` names when it isn't null, and post them as the round's question with `question add`, adding `round_of: "<that question id>"` beside the prototype url. The board server has already resolved the asked question, noting "Prototype requested", and the card reads prototyping until a take is picked; `question add` numbers the round and notes "Followed by prototype round N" on it ([a prototype round ends with a one-take confirm](docs/adr/2026-11-02-1332-a-prototype-round-ends-with-a-one-take-confirm.md), [a question asked for a prototype is resolved, and the page moves on](docs/adr/2026-11-03-1803-a-question-asked-for-a-prototype-is-resolved.md)). **The board owns the page; you write only the takes** ([the board serves every prototype from one template](docs/adr/2026-11-01-1448-the-board-serves-every-prototype-from-one-template.md)). Record the prototype with `board.mjs --site <route> prototype add <topic> --title "…" --card <wave>/<id> --take A:"Label" --take B:"Label"` (topic `a-z0-9-`, each key one capital letter, labels never starting with "A. ", since the take pill shows the label alone), which prints the take files to write, `public/prototypes/<topic>/<KEY>.js`. The board then serves `prototype-<topic>.html` from its one template: the header with back to the card, the shared take pill, `?take=` and `?q=`, and a stage for the take. `prototype list` shows what is recorded, and `prototype rm <topic>` drops a record once no open question uses it. Each take file is a module, `export function render(stage, kit) { stage.innerHTML = …; return cleanup }`, rerun with a fresh stage on every switch. `kit` draws the real components fed fake data: `kit.sidebar()`, `kit.hub({usage, afterUsage, usageCard, settings…})`, `kit.cards()` and `kit.sheet(html, {title})`, with `kit.toast`, `kit.on`, `kit.fake` and `kit.esc`; `BoardUI` and `HubUI` are globals too, and `board/public/prototype-kit.js` documents each. Build each take from these, changing only what the take is about; a look-alike copy drifts from what ships (owner, on back-to-card). The stage contains its take, `position: fixed` included, so nothing covers the header or pill; links and forms in it only toast, and the listeners and timers a take adds end on the next switch. Every interaction must work on a tap. Include this prototype paragraph in the brief of any builder or agent asked to make a prototype. With each prototype, add an open question on its card: one option per take, the option ids the take keys in take order (a later round may list only some takes, still in take order), and the recommended option tagged with `recommended: true`, with `url` set to `prototype-<topic>.html?q=<question id>`, relative to the site root and stored as given. A question added on a card's topic resolves each question there that is answered and still waiting on Claude, noting "Followed by <new id>", and prints `resolved: <old id>`; a question re-asked on the same topic and prototype resolves any other unresolved one it replaces, noting "Replaced by <new id>", and prints `replaced: <old id>` ([an answered question stops waiting once Claude acts on its card](docs/adr/2026-11-01-1813-an-answered-question-stops-waiting-once-claude-acts-on-its-card.md)). Acting on a card's answers resolves them the same way, noting what acted: `item set --status building` ("Acted on: build started"), `builder start` ("Acted on: builder started"), and a `reply` to an ask or a wave triage for the answered questions on its topic ("Acted on: replied") ([the CLI and board server do the board's housekeeping](docs/adr/2026-11-02-2044-the-cli-and-board-server-do-the-boards-housekeeping.md)). A pick, with a note or none, settles its round: the note is the build's guidance, and the build starts as any card's does ([a prototype pick is the build's go, and more of the skill moves into code](docs/adr/2026-11-03-0504-a-prototype-pick-is-the-builds-go.md)). An answer with a note and no take, the owner having cleared the pick, resolves its question and leaves the card prototyping with Next "Prototype again" until a take is picked; its owner-answer event carries `prototype_again: true`: draw the next round from the note as for a `prototype-request`, or grill when the note asks a question ([a prototype answer with no pick prototypes again, so a card is never stuck](docs/adr/2026-11-03-1646-a-prototype-answer-with-no-pick-prototypes-again.md)). The questions page measures each take in a frame: a single take, or a question where any take scrolls or fills the screen, gets one full-width take below the option row, switching the take in place with no reload; if all fit, they show in a grid. Every frame takes taps. Both layouts keep the note and Send on Waiting on you; a page opened from a thread keeps its own header and take pill, where the owner can also pick ([a question's takes show inline, and back goes one step](docs/adr/2026-10-29-1639-a-questions-takes-show-inline.md)). In the app, back from the page goes to Waiting on you on that question and then to the board, however the owner arrived; the header's `backHref` applies only when the page is opened alone ([Back follows the page tree](docs/adr/2026-10-30-1125-back-follows-the-page-tree.md)). A page that links the board's `style.css` links `colors.css` before it, since every color lives there ([one colour constants file shared by the hub and every board](docs/adr/2026-10-29-1639-one-colour-constants-file-shared-by-the-hub-and-every-board.md)). The board pushes it (Push notifications, below). Add prototypes to a card's question unasked only when they obviously help. A choice with nothing to see, such as a code structure, a data flow or a policy, gets `diagram` and `practice` on each option instead (`question add` usage): boxes and arrows the questions page draws, and a line or two of what it means in practice (what a new feature must do, what changes, what it costs), so the pick is between concrete options, not labels (owner, 2026-10-24, on hb-model; [a question's takes show inline, and back goes one step](docs/adr/2026-10-29-1639-a-questions-takes-show-inline.md)). **Grill a card through open questions on the card**, never a separate page or a link ([a card is grilled through its own open questions](docs/adr/2026-10-29-1639-a-card-is-grilled-through-its-own-open-questions.md)): `board.mjs question add` each question with topic `item:<wave>/<id>`, options in take order with the recommended option tagged, and a prototype page in `url` when one helps. They land in Needs you and on the open card. When the ask is to grill new work, make the card first (`--from-ask`), then the questions. Questions that span several cards go under a declared topic (`topic add`). **Messages stand alone.** Every reply, thread line and question reads on its own in the card's thread, where the owner reads it: name cards by their label (e.g. #96), and a pointer such as "above", "below" or "here" must make sense in the card's own thread, never point into this session. The owner reads only the board, not this session.
|
|
87
|
+
**Board template** (`--template board`, `~/.claude/skills/kickflip/board/`): a work board over wave files. A running builder with no progress for 30 minutes (from its start before its first progress) reads Stalled. The server sends `builder-stalled {key, silentSince}` once per silent spell, including across a restart; tapping Stalled and confirming sends `builder-nudge {key}`. On either event, check the builder's process and log, restart or finish the run as needed, and write one thread line on the card saying what you found. A nudge on a card that is no longer stalled gets a thread line too. Every write an agent makes to a board site goes through `node ~/.claude/skills/kickflip/board/bin/board.mjs --site <route> <command>`: replies, thread lines, question topics and questions, item status and `next`, and builder records. Its usage text lists the commands. Hand-edited JSON skips the checks the pages rely on. A new card is `board.mjs item add <wave> --title …`, never a hand edit of a wave file: it makes the card's ticket, names the card from it and prints `<label> added as <key>`; use that key, and pass `<wave>/<id>` only to import a card with its own id ([a card is named by its board's ticket source](docs/adr/2026-11-02-2100-a-card-is-named-by-its-boards-ticket-source.md)). A card filed from another card's thread, grill or answer, a follow-up included, takes `--parent <that card's key>`; one from the + has none ([a follow-up is a work card with a Parent](docs/adr/2026-11-03-0526-a-follow-up-is-a-work-card-with-a-parent.md)). A board serves `GET /_claude/needs` itself, one item per Needs you line. The + sends an `ask` event, shown as a New card at the top of Todo until you sort it ([the + makes a New card, which Claude files, folds or answers](docs/adr/2026-10-29-1639-the-plus-makes-a-new-card.md)). Take New cards from the top. Sort New cards: file it under a wave (`--from-ask`), fold it into the card it's about (`item relate-ask`), or answer it (`reply`), all below. **Every new task is grilled or prototyped at once, unless the owner chose Quick build** ([every new task is grilled or prototyped at once](docs/adr/2026-11-04-0131-every-new-task-is-grilled-or-prototyped-at-once.md)). The New task sheet asks Discuss (the default) or Quick build above Send; a Quick build ask comes as `ask {text, quick: true}`, and its thread reads "quick build: no grill, as you asked" under it. On a Discuss ask, as soon as the card is filed and titled, set it `--state grilling` and dispatch a grill (`builder start <key> --dispatch --kind grill`, then the agent), or `--state prototyping` and a prototype agent when it's a UI ask; which planner is your call. Never wait for the idle pull to plan it. On a Quick build ask, file it `--state ready` with its `--size` and its plan in the notes file, never on the card, and dispatch nothing; with auto implement on, the board takes it from there. A `grill-more {key, text?, files?}` (the thread's fire, or /grill) or a `prototype-request {key, text?, files?}` (the thread's eye, or /prototype, or the owner's eye on one question) is the owner's request: the card already reads "preheating" or "prototyping" in planner blue and the thread a muted "grill requested" or "prototype requested", or, with text, the owner's message with that status in its footer, and the board server has already launched that planner itself, headless in the board's repo folder with a brief built from the card and the owner's text and files (Claude Opus for a grill, Claude Sonnet for a prototype), and written "Planner launched on <model> in <folder>." in the thread, so leave both events alone ([the hub launches every agent from the card](docs/adr/2026-11-04-2130-the-hub-launches-every-agent-from-the-card.md)). A prototype request is answered only by a prototype: never answer it in the thread instead. One still waiting 30 minutes on, with no prototype agent running on the card, comes back as `prototype-overdue {key, request, question?, since}`, once per request: dispatch its prototype agent now ([a prototype request waits for its prototype](docs/adr/2026-11-04-2130-a-prototype-request-waits-for-its-prototype.md)). First check it against the other boards: each one's title and description in `~/.claude/kickflip/manifest.json`, its folder (`node $KF sessions`) and its cards. Only when another board clearly fits better, add an open question on the New card (`question add`, topic `item:ask:<eventId>`): "Move to <board>" recommended, then "Keep here", each with `diagram` and `practice`. On Move, run `board.mjs --site <route> ask move <eventId> --to <other route>`, which makes it a New card there and wakes that board's session, then `question resolve`; on Keep, sort it here. Hand a long build or investigation to a builder, which reports in the card's thread. The owning session only triages events, writes board lines and dispatches: a full test suite, a scratch hub, screenshots, `board.mjs land` and anything else that takes more than a few seconds run in a background agent or background task. A session sitting in foreground work leaves events waiting, and the board reads stalled. The watcher marks each ask, thread message and wave triage it prints as Working, so the page reads "Working on it" until you reply, write a later thread line on its card, reply to its ask, turn or fold its ask into a card, or the card closes. Answer a quick ask with `board.mjs reply <eventId>`. An answer never pushes: the board's answer banner and the thread's unread mark carry it ([a push goes out only when something waits on the owner, on the hub's schedule](docs/adr/2026-10-29-1639-pushes-only-when-something-waits-on-the-owner.md)). When the answer waits on the owner, such as a pick between prototypes, also run `board.mjs --site <route> ask progress <eventId> --next "<what the owner picks>"`. An `ask-done {id}` event is the owner closing it, which needs nothing beyond relaunching the watcher. `board.mjs ask done <eventId>` closes it from your side ([an ask can wait on the owner before Done, and the owner closes any open card from its thread](docs/adr/2026-10-29-1639-an-ask-can-wait-in-progress-until-the-owner-marks-it-done.md)). To turn an ask into work, add the work item named for the change and run `board.mjs item set <wave>/<id> --from-ask <eventId> --text "<first line>"`: the New card gives way to the item, whose thread opens with the ask and then that line, so the item stops reading "Working on it". A card's **Notes** is your understanding of the task so far: what was asked, what's settled, what's still open. Keep it as a `.md` file in its wave's folder, set it with `board.mjs item set <wave>/<id> --notes-file <file>` (`--plan` still works), and rewrite it as the grill settles things rather than freezing it as a plan; the thread keeps the short news. Setting the notes file resolves the card's answered questions still waiting on Claude, noting "Recorded in the card's notes", and prints `resolved: <id>` for each. The open card's details list ends with a "Notes ↗" link, which opens `notes.html`, a view that renders the file for the phone, or the item's short `notes` text when it has no file ([notes replace the full plan, and a card starts on its own](docs/adr/2026-10-29-1816-notes-replace-the-full-plan-and-cards-start-on-their-own.md), [notes is a link in the details list, with no stored summary](docs/adr/2026-10-30-1626-notes-is-a-link-in-the-details-list.md)). `item set --title` renames a card. When an ask is about an existing item, fold it in with `board.mjs item relate-ask <eventId> <wave>/<id> --summary "<one concise line>"`: the New card goes, and the owner's message moves into the item's thread marked Moved, with the summary after it. When the owner says it went to the wrong card, relate-ask it to the right one, or run `board.mjs ask unfold <eventId>` to give it a New card again. An `item-message` whose `key` is `ask:<eventId>` is the owner replying in a Done ask's thread: answer it with `board.mjs reply <its eventId>` or `board.mjs thread ask:<eventId> --text …`, as in a work item's thread. An `ask-withdrawn {id}` event (with `key` from a thread) deletes a pending message ([a pending ask or thread message can be deleted or edited, and a pending ask bumped](docs/adr/2026-10-29-1639-a-pending-ask-can-be-deleted-or-bumped.md)). An `ask-edited {id, text}` event (with `key` from a thread) replaces a pending message's text: use the new text, and if you had started, adjust the work to it and say what changed. An `ask-bump {id}` event means take that ask, or that pending prototype request, next. A `card-bump {key}` event bumps a work card ([a card that hasn't started, or is running, can be bumped](docs/adr/2026-10-29-1639-a-card-that-hasnt-started-can-be-bumped.md)): a card that hasn't started starts next once it is unblocked, and a running card's next steps (its builder's questions, review fixes, CI) come ahead of other work, and it goes first among cards waiting for a free builder. A `card-hold {key, hold, until?}` event is the owner's Hold (`hold: true`, with `until` when they picked a time) or Unhold (`false`) on a work card. A held card never starts on its own; stop work on a held running card, with no new builder, review fixes or CI reruns until it is resumed; once resumed, start it if nothing blocks it. When the owner's words give a time to pick a card up, hold it with `item set <key> --pick-up-at <ISO time>` (no offset is the Mac's local time). Within a minute of that time the board server resumes the card itself and appends `card-unblocked {key, until}` unless a dep is unfinished ([a card can be held until a time](docs/adr/2026-11-02-1727-a-card-can-be-held-until-a-time.md)); treat it as any `card-unblocked`, carrying on with a card that had started. A `card-done {key}` event is the owner closing a card from its thread's Done: follow up only by stopping any builder on it. Closing a card yourself (`--status done` or `dropped`) takes `--text "<what was done or left>"`, which becomes its last thread line; `--status merged` writes "Merged PR #n." unless `--text` says otherwise. Closing resolves each of its questions that has an answer, noting "Closed with the card"; one still open, with no answer yet, stops it, and the refusal names that question. `--force` closes the card anyway and resolves that one too. Never touch GitHub for it: an open PR stays as it is. A `card-merge {key}` event is the owner's Merge on a card whose PR is approved, green and CLEAN while the board's auto-merge is off (Auto-merge, below): run `board.mjs --site <route> merge <key>` now. When it refuses, it writes why as a thread line, which ends the button's "Merging…". Start a card as soon as its open questions are all answered and nothing blocks it, without waiting for a go; `unblocked: <key>` or a `card-unblocked {key}` event means start it now, unless held ([notes replace the full plan, and a card starts on its own](docs/adr/2026-10-29-1816-notes-replace-the-full-plan-and-cards-start-on-their-own.md)). The Implement tap, a queued start and Auto implement's pull all start the build in the board server: it makes the refusals a build's `builder start` makes, picks the builder (claude with a model, or codex), makes the worktree, marks the card building and clears its Next, then launches the builder itself, headless in the worktree (`claude -p`, or `codex exec` for a codex pick) with a standard brief built from the card's notes and the rules, and writes "Builder launched on <model> in <worktree>." in the thread; no event reaches you. The builder's own first step is `builder start <key> --kind build`, which takes up the record the board opened; its first status line then replaces "starting". A refused tap, or a launch that fails, puts the card back in Todo with the reason in its thread ([a Build tap starts the build in the board server](docs/adr/2026-11-03-0219-a-build-tap-starts-the-build-in-the-board-server.md), [the hub launches every agent from the card](docs/adr/2026-11-04-2130-the-hub-launches-every-agent-from-the-card.md)). `item set --state ready` refuses a card without `--size`, without a notes file, or whose notes don't name the id of each question the owner answered on it, and marking it ready resolves those questions, so record every answer in the notes before marking it ready. The builder asks you as any builder does, and finishes its own work with `builder done --text`, rebasing and resolving conflicts itself; finishing never comes to you. A build with calls hands back as before: verify them (`builder verified`), which finishes the card in the same step; when a question on the card was still open, or that finish refused, the server launches a headless lander once no question is open, which acts on the owner's answers and finishes it, so don't land it yourself ([builders launch headless, check their notes and land on their own](docs/adr/2026-11-04-0306-builders-launch-headless-check-their-notes-and-land-on-their-own.md)). With Auto implement Todo cards on (`setting auto-pull on|off`, set with `settings set auto-pull on|off` only on the owner's word), the board pulls ready Todo cards into free implementer slots and launches their builds, one per free slot, up to Maximum parallel implementers (`setting max-implementers <n>`, 10 by default, set with `settings set max-implementers <n>` only on the owner's word), counting cards building or in Orchestrator review ([an idle board pulls in its top Todo card](docs/adr/2026-11-02-1754-an-idle-board-pulls-in-its-top-todo-card.md), [auto implement fills In Progress up to Maximum parallel implementers, on every board change](docs/adr/2026-11-04-0057-auto-implement-fills-in-progress-up-to-a-cap.md)). When nothing is ready to pull, the idle board launches a planner on its top unready Todo card, a prototype when the card is prototyping and a grill otherwise, so it moves toward ready ([the idle pull takes ready cards first, and plans the top card when none is ready](docs/adr/2026-11-03-1631-the-idle-pull-takes-ready-cards-first.md)). A `pr-review {pr, head, repo, title, author, url, touches, asked}` event, with auto review on, asks you to review a teammate's PR that touches the listed cards or asks the owner: read its diff and the reviews already on it, and, rereading them right before posting, post one Comment review (never approve or request changes) as the owner's gh login, its first line "Claude review from <owner>'s board" plus `<!-- claude-bridge -->`, with only points no other review made, or post nothing; then `review record <pr> --state posted --file <review.md> [--url <review link>]`. When it finds a blocker, disagrees with a decision on a card or clashes with a card in progress, post nothing and `review record <pr> --state needs --file <draft.md>` instead; the owner's Post it or Skip comes back as `review-answer {pr, answer}`, after which you post (or don't) and record it as posted ([each board reviews incoming PRs that touch its work, in an Auto Review lane](docs/adr/2026-11-03-1731-each-board-reviews-incoming-prs-that-touch-its-work.md)). An `overnight-triage {date, topic, cards}` event is the board asking for its overnight triage, with auto-triage on (`setting auto-triage on|off`, set only on the owner's word): it goes out once a night from the middle of quiet hours, when two or more cards, `cards`, wait on the owner's open questions and nothing builds. First read those cards' open questions against each other. Questions that ask the same thing become one: `question add` the merged question on the newest of their cards by `created`, naming in its `context` each card it answers for, and `question resolve` each of the others with `--note "Replaced by <new id>"`. When the owner answers a merged question, copy the answer onto each card it names, as a thread line and in its notes, before acting on it on any of them. Cards whose grills show they are the same work merge as a clear duplicate does below. Then check each open question left against the owner's earlier answers on other cards and their long-running context, such as what they said in other cards' threads and notes, not against ADRs or CONTEXT.md, which the grill already read. When one of those answers settles a question and you are confident it carries over, `question add` it again on the same topic with a new id, its options unchanged and `suggested: {option, card, answer}`: the option, the earlier card's ref and the owner's answer in their words. Then `question resolve` the old one with `--note "Replaced by <new id>"`. Never act on a suggestion yourself: the page pre-checks it, and it waits for the owner's Send ([overnight triage suggests your earlier answers](docs/adr/2026-11-04-2200-overnight-triage-suggests-your-earlier-answers.md)). Leave the other questions you could answer yourself as they are. Then read the Todo and Stale cards against each other and against merged work, leaving Done alone (old Done cards leave by the archive rule, [the board keeps the newest 100 Done cards](docs/adr/2026-11-04-0307-the-board-keeps-the-newest-100-done-cards.md)), and find duplicates, cards a newer card covers, cards merged work already did, and deps that no longer hold. Link related cards (`item set --related`) and merge a clear duplicate into the newer card yourself, dropping the older with a thread line naming the one it went into. A close, or anything you aren't sure of, is one question per finding on `topic`, all in one `question add` array; quiet hours hold their push until morning. Then reply to the event, even when you changed nothing ([triage runs once overnight on cards waiting on your answers](docs/adr/2026-11-04-1900-triage-runs-once-overnight-on-cards-waiting-on-your-answers.md)). Set `deps` for work that waits on other cards. When `--status done`, `merged` or `dropped` finishes the last dep, it prints `unblocked: <key>` and appends a `card-unblocked {key}` event; leave a held card held until the owner unholds it ([cards wait on explicit blocked-by links](docs/adr/2026-10-29-1639-cards-wait-on-explicit-blocked-by-links.md), [Build on a blocked card queues it, and blocked, bumped and Action needed leave the badges](docs/adr/2026-11-03-0301-build-on-a-blocked-card-queues-it.md)). Every builder you start for a card works in its own git worktree, never in the folder you, a running app or the hub use ([every builder works in a worktree, and the hub's own fault shows once](docs/adr/2026-10-30-1654-every-builder-works-in-a-worktree.md)). A repo card's builder uses the board's manifest `github` and `branch` when following wave-build; it never switches the shared clone's checkout. A build's `builder start` on a board makes the card's worktree when its record keeps none, `~/.claude/kickflip/worktrees/<card id>` on branch `<card id>`, and prints `worktree: <path>`; the builder's prompt names that path as the only place it edits, tests and runs scratch hubs. Right before it launches any builder agent, the session runs `board.mjs --site <route> builder start <key> --dispatch --kind <kind> [--model …]`, which opens the record (`dispatched: 'session'`) so the card reads "starting" at launch, and changes nothing on a record the Implement tap already opened (`dispatched: 'tap'`), printing "already open"; it never posts a "builder is working" line itself ([the session opens the builder record when it dispatches an agent](docs/adr/2026-11-03-0513-the-session-opens-the-builder-record-when-it-dispatches-an-agent.md), [a card reads starting from its tap, with one dispatched mark](docs/adr/2026-11-03-1718-a-card-reads-starting-from-its-tap-with-one-dispatched-mark.md)). Every builder, prototype and grill agents included (`--kind prototype` or `--kind grill`), runs, as required steps in its prompt, `board.mjs --site <route> builder start <key> --kind <kind> --label "<label>"` first, which takes up the dispatched record (the label carries no model word) (a build agent passes `--kind build`, which records the kind and model `builder pick <key>` prints), `builder progress <key> --phase <reading|building|testing|checking|landing> "<detail>"` (or `--text "<detail>"`) at each milestone (a planner, kind grill or prototype, passes no `--phase`: its line reads "Preheating: <detail>" or "Prototyping: <detail>" in planner blue, never a builder phase word, and a `--phase` it passes is dropped) and `builder done <key>` before it hands back, so the card sits in In Progress with a live status line. Closing a card's last open record when it stays In Progress with no Next is refused: pass `builder done <key> --next "<what happens next>"`, or hand back with `item set <key> --status checking` or `land`, which close the record themselves. A card with a step only the owner can do once its code lands (a publisher to add, a release to tag) takes `--owner-step "<the step>"` on whichever of `builder done` and `land` finishes it: it then waits in In Review with that step as its Next and its ticket open, and only the owner's Done closes it ([a card whose code landed stays in review while an owner step remains](docs/adr/2026-11-04-0158-a-card-whose-code-landed-stays-in-review-while-an-owner-step-remains.md)). When code can prove the step done, add one `--check "<name> <args>"` on the final evidence beside `--owner-step`: `tag <owner/repo> <pattern>`, `npm <package> <version-or-range>`, `run <owner/repo> <workflow>`, `gh <owner/repo> <api-path> <field>=<value>` or `url <address> [<status> | <text>]`, never a shell command. The hub's poll runs it once the code lands and closes the card when it passes; on `owner-check-broken {key, check, error}`, fix it with `item set <key> --check "<check>"`, or remove it with `--check ""`, and write one thread line saying which ([an owner step can carry a code check](docs/adr/2026-11-04-2308-an-owner-step-can-carry-a-code-check; `item set` likewise refuses a write that would leave a built card with neither ([an In Progress card always has a builder line or a Next](docs/adr/2026-11-03-0237-an-in-progress-card-always-has-a-builder-line-or-a-next.md)). The detail is plain words: what the builder is doing and why it matters to the owner, such as `--phase testing "fix for the scroll jump into Done"`. A wait on a lock or a Codex limit goes in the detail. Commits, test counts, ADR numbers, file names and screenshots go in the hand-back and the session's thread message, not the line ([a builder status line is a phase and a short detail](docs/adr/2026-11-03-0128-a-builder-status-line-is-a-phase-and-a-short-detail.md)). A build's `builder start` is the one write that marks the card building: it moves the ticket to In Progress and prints `ref: <label>`, the card's name for its commit subject. `building` and `fixing` count as working only while a builder record on that card is open; with none open, its PR and other state place it as if the status were absent. `builder done` clears either status when it closes the card’s last open builder record; `item set --pr` clears either status too and closes ordinary builders while install builders keep running. When a subagent stops, a SubagentStop hook, `board/bin/builder-stop-hook.mjs`, closes the records it started and left open, but not one another agent restarted later, and not while background work the subagent started still runs (the input's `background_tasks`), since it resumes when that work ends. A skill's own hooks don't fire for SubagentStop, so the hook ships as the plugin `hooks-plugin/`, which Claude Code loads as `kickflip-hooks@skills-dir` through the link `~/.claude/skills/kickflip-hooks` (`claude plugin list` shows it); a new host needs that link. A site card's builder lands its own card: it runs the tests and checks the board at phone width in its worktree, commits there as the repo's configured user (`git -c core.commentChar=';' commit -F <file>`, or `--cleanup=verbatim`, so git keeps a "#N:" subject), then runs `board.mjs --site <route> builder done <key> --text "<what it checked>"`. With no unverified calls that finishes the card, the one step `land` also runs, as `builder done`'s usage lists: it lands the worktree, closes the card as done with that text as its last thread line, and with the board's "Push main after a land" switch on pushes main to origin itself, so no session pushes after a land; on a GitHub board it pushes the branch and opens a draft PR instead, and on a plain git remote pushes the branch ([finishing a build is one step](docs/adr/2026-11-04-1611-finishing-a-build-is-one-step.md)). A finish that fails refuses with the reason and the builder stays on: it fixes what the refusal names and runs `builder done` again. Only a builder that can't finish hands back, with a `builder call` naming why, then `builder done`: the card sits in In Progress reading "Orchestrator review" and unblocks nothing, and the owning session checks it, and its `builder verified` finishes it ([site cards build in parallel when their files don't overlap](docs/adr/2026-10-29-1639-site-cards-build-in-parallel-when-their-files-dont-overlap.md), [the board records what it knows, and commands make the rule calls](docs/adr/2026-11-03-0003-the-board-records-what-it-knows-and-commands-make-the-rule-calls.md)). On a refusal, fix what its line names in the worktree and finish again. **Judgment calls** ([a builder records its judgment calls and the session verifies them before land](docs/adr/2026-11-03-0514-a-builder-records-its-judgment-calls-and-the-session-verifies-them.md)): every builder's brief includes these steps. Right after `builder start`, it records each gap or ambiguity in its brief as `builder call <key> "Question: …"`; with any, it runs `builder done <key>`, which hands the card back for them, and stops, its report the questions. Answer them from the card's notes and thread, asking the owner on the card only what those don't settle, and resume it with SendMessage; its `builder start` then resumes the record, calls kept. While building, it records each judgment call as it makes it, `builder call <key> "<call>"`, one plain line each. A builder with calls doesn't finish: `builder done <key>` hands the card back for them to be verified, and its report lists them. Check each (`builder calls <key>`) against the owner's words and the other cards: one that conflicts with what the owner said goes to the owner as a question on the card, any other wrong one back to the builder by SendMessage. Then run `builder verified <key> [--note "<what you checked>"]`, which finishes the card in the same step, the note its closing line; while a question on the card is open it says so and the card waits, and a finish that fails keeps the mark and says why. `land` refuses while a record on the card has unverified calls. **Builder asks** ([a builder asks the orchestrator mid-build, and only what it defers reaches the owner](docs/adr/2026-11-04-0247-a-builder-asks-the-orchestrator-mid-build.md)): a builder's brief also says that, at any point, it can ask you a question with `builder ask <key> "<question>"` and carry on meanwhile, checking `builder asks <key>` for your reply. The ask shows in the card's thread as "Claude Opus builder" (by its model) and wakes you as a `builder-ask {key, ask, agent, text}` event; the card itself stays quiet. Answer from the card's notes, thread and the owner's words with `builder answer <key> <ask> --text "<answer>"`, or, when you aren't confident or the owner's opinion is wanted, `builder defer <key> <ask> --text "<what to carry on with>"`: never ask the owner mid-build. Both show in the thread as Orchestrator. A deferral is a "Question:" call on the builder's record, so its build hands back to you; after you've reviewed everything else, and before a draft PR or In Review, put it to the owner as a card question naming the ask (`"ask": "<ask>"` in the question file), which `builder verified` requires. The card then shows Open questions and "1 new question", with no Action needed. **A repo card's built work always goes straight to a draft PR** once it is checked (owner, 2026-10-23): on a GitHub board the finish (`builder done --text`, or your `builder verified` on a handed-back build) pushes the card's branch, opens the draft with `gh pr create --draft` and records it as `item set --pr` does, closing the builder records. For a PR opened by hand, run `item set <key> --pr <n> --clear-status`. Either way the card sits in In Progress while CI runs or fails, then in In Review with Action needed linking to the PR once CI passes, also while the owner tries it on a test deploy. A card whose work spans repos holds a PR in each: run `item set <key> --pr <owner/repo#n>` once per PR (it adds, never replaces; a bare number is the board's repo). Each PR gets its own badge and polling, the least-ready one decides the lane and Next, `merge` merges them all in order, and `pr` prints a line per PR ([a card holds several PRs across repos](docs/adr/2026-11-03-1904-a-card-holds-several-prs-across-repos.md)). A board whose repo is a plain git remote (manifest `remote`, no `github`) has no PRs: the finish pushes the card's branch to origin and records it as `item set <key> --pushed <branch>` does (by hand: `git push origin <branch>` in its worktree, then that command), so the card waits In Review on the owner's merge; the hub's poll moves it to Done once the branch is merged into the board's branch or gone from the remote, with a thread line ([a board can work on any git remote](docs/adr/2026-11-02-1850-a-board-can-work-on-any-git-remote.md)). A card's lane follows who acts next: one waiting only on the owner's Ready, Merge, manual steps or an ask's Done sits in In Review with Action needed, and goes back to In Progress once Claude works on it again ([the lane follows who acts next, and a card waiting on the owner says Action needed](docs/adr/2026-10-29-1639-the-lane-follows-who-acts-next.md)). One that hasn't started and waits only on its open questions or a prototype pick, ask or work card, stays in Todo with Action needed. An owner `item-message` on a Done card, or a question you add on one, reopens it on the board with no step from you: it sits in In Progress until you answer the message, then waits in In Review with Action needed until the owner's Done (an open question puts it there at once). Answer and leave it ([a message or question on a Done card reopens it until the owner's Done](docs/adr/2026-11-02-1952-a-message-or-question-on-a-done-card-reopens-it.md), [a merged card reopens on a message, and its PR badge shows the PR's state](docs/adr/2026-11-03-1843-a-merged-card-reopens-and-its-pr-badge-shows-the-prs-state.md)). Never park built work as `waiting`, which reads as blocked. `item set <key> --deps <list>` sets a card's blockers after it exists. A PR that changes what a page looks like carries screenshots in its description, as wave-build's build step says. **Each card and main has a version to try** ([a board's build script serves or builds each card and main](docs/adr/2026-11-01-1846-a-boards-build-script-serves-or-builds-each-card-and-main.md)). When setting up a board whose repo lacks `.kickflip/build`, write that executable repo file for the project. The hub runs each branch's copy with `<key> <worktree>` (key `main` for main), cwd the worktree, `KICKFLIP_BOARD=<route>`. Keep it owned by the current user and writable by neither group nor others. A website script starts its server, prints exactly `port <n>` on stdout and stays running as its server process. It listens on `HOST` (127.0.0.1), so only the hub's gated relay reaches it. Serve browser paths at `/`; each served card and main gets its own Tailscale HTTPS port, with no `KICKFLIP_BASE` ([a try build serves on its own Tailscale port](docs/adr/2026-11-01-2254-a-try-build-serves-on-its-own-tailscale-port.md)). An app script exits 0 after publishing its install: `board.mjs --site "$KICKFLIP_BOARD" item set <key> --install <url> --install-sha <head sha>`, or `main set --install <url> [--install-sha <sha>]` for main. iOS uses `itms-services://?action=download-manifest&url=https://…/manifest.plist`; Android uses an https `.apk`. Keep install files under the site's `public/installs/`. Signing still needs the login keychain unlocked on macOS. The hub owns runs and their logs in `data/try/<id>.log`, reports only failed card runs in the thread and leaves each card in its lane. Try builds wait on Tailscale; the hub refuses a run with the readiness reason until it is ready. Only the owner's Serve or Build and Auto-build start a card run; a session never starts one on its own, not even for a device app after a push, and a board rule asking for that is the owner's cue to turn Auto-build on. When the owner asks for a run in words, run `board.mjs --site <route> build <key> --asked "<the owner's words>"` (`install` is an alias), quoting their ask, card message or answer note; it refuses words the owner didn't write on the board, prints `started` and exits ([only the owner or Auto-build starts a card run](docs/adr/2026-11-02-1806-only-the-owner-or-auto-build-starts-a-card-run.md)). Auto-build cards, off by default, runs each new head when on, and the card then has no Serve or Build; otherwise the card waits for Serve or Build, shown once its branch has changes. Set `settings set auto-build on|off` only on the owner's word. Main always builds after each merge, independent of the toggle, in the hub's detached worktree, and shows its version above the lanes. A served card stops after 30 minutes without a visit; main stays up. The card badge reads Visit/Install, Visit (stale)/Install (stale), an inert Serving…/Building… spinner or Serve failed/Build failed opening its thread. Main’s red Serve failed/Build failed button opens a bottom sheet with the last 40 log lines and Retry at the recorded failed tip, even if origin has moved. Retry waits on Tailscale readiness and shows refusals in the sheet; while running it reads Serving…/Building… in orange, then closes with a toast when main is ready or refreshes the log after another failure. Maintain a broken script through a card. **Marking a PR ready for a reviewer includes the Slack link** (owner, 2026-10-24): add them to the board's reviewers if they're missing (Board reviewers, below), then DM the bare PR link as the Reviewers steps below describe, even when no monitor or bridge runs for the wave, and record its ts in the wave's `review_link_ts`. Every hub feature finds boards through one registry, `hub/boards.mjs`, so each serves both kinds alike ([one board registry for every hub feature](docs/adr/2026-10-29-1639-one-board-registry-for-every-hub-feature.md)): the hub polls every open PR on any board's card, whatever `monitor.json` watches (a card's PR is in the board's `github` repo, else its wave's `repo`; with neither, the card has no PR link and nothing polls it), and a PR's `github-change` goes to each board with an eligible changed card. Boards over their own file (`--items`) use the same GitHub tick and Reviewers steps as wave boards. The hub checks GitHub every minute while Love is here and every 5 when away, and wakes you only on a change, or at the daily wake when quiet hours end ([the hub checks every minute while you’re here and every 5 when away](docs/adr/2026-11-04-0226-the-hub-checks-every-minute-while-youre-here-and-every-5-when-away.md)): a `github-change {prs: [{wave, id, pr, changes}]}` event, where `changes` lists `ci`, `review`, `comments`, `merged`, `mergeable`, or `draft`, means run one GitHub tick for those PRs. A `check-github {}` event is the owner's refresh asking for a check, and `check-github {reason: "morning"}` is the hub's daily wake (8:00am by default), so kickflip's reviewer digests go out on a quiet day: for either, run one GitHub tick. Run the Reviewers steps below as part of either tick. End each tick with `board.mjs --site <route> checked`, which takes `--every` as the away cadence, 5 minutes, so the In Review header reads late only once a check is overdue, and records idle by itself when no card has an open PR, so the time stays muted rather than late. It then sends the pushes the tick queued. The hub writes the same `checked.json` after each of its checks. When the header reads "not listening" in your own session, relaunch the watcher ([the board shows GitHub checks and Claude's activity, with one refresh](docs/adr/2026-10-29-1639-the-board-shows-github-checks-and-watcher-health.md)); it also reads so while the board's session isn't running and the hub can't wake it. When an event has waited 15 minutes while the board reads so, the hub pushes the owner "<title> isn't listening" once. A `hello` event is the hub testing a new board's links (`join --template board` runs `node $KF handshake <route>`, then asks the hub for one poll so the GitHub link passes at once): the watcher answers it itself and prints `hello <eventId> from the hub answered`. The board shows a Connecting checklist until that and the hub's GitHub check reach it; cards can start meanwhile. The In Review header's GitHub mark opens the Hub links sheet (GitHub polling, the repo fetch on a board with a repo, events, daily wakes, pushes, reviewer links). The watcher prints each broken one at once, after the rules, as `link <name> <detail>`; the mark gets a red dot only for a link still broken past its grace period, and the sheet shows it settling until then ([hub links turn red only after a grace period](docs/adr/2026-10-29-1639-hub-links-turn-red-only-after-a-grace-period.md)). Fix what is yours straight away, such as a missing reviewer link or a stopped watcher. `link hub` means the hub runs older code than its files, which the sidebar's Hub row and the hub page show in amber, never a board: restart it with the command the link names: `launchctl kickstart -k gui/$(id -u)/local.claude.kickflip-hub` on macOS, `systemctl --user restart kickflip-hub.service` on Linux. Tell the owner the rest. A card that changes the hub's or a board's UI starts with an in-situ prototype before any builder touches the skill: one prototype per question, one take per option, and an open question on the card whose `url` opens it; build only after the owner picks. The prototype planner the board launches for a `prototype-request {key}` posts its page as a card question ([prototypes come only when obviously helpful or on request](docs/adr/2026-10-29-1639-prototypes-come-on-request.md)); for a `prototype-request {key?, question, note, choice}`, the owner's eye on one question asked in place of an answer, it builds takes of that whole question, following the note and the option `choice` names when it isn't null, and posts them as the round's question with `question add`, adding `round_of: "<that question id>"` beside the prototype url. The asked question reads "prototype requested", locked, and `question resolve` refuses it, until that round's `question add` with `round_of` resolves it, numbering the round and noting "Followed by prototype round N" on it; the card reads prototyping until a take is picked ([a prototype round ends with a one-take confirm](docs/adr/2026-11-02-1332-a-prototype-round-ends-with-a-one-take-confirm.md), [a question asked for a prototype is resolved, and the page moves on](docs/adr/2026-11-03-1803-a-question-asked-for-a-prototype-is-resolved.md), [a prototype request waits for its prototype](docs/adr/2026-11-04-2130-a-prototype-request-waits-for-its-prototype.md)). A resolved question on Waiting on you is read-only; tapping its green resolved badge and confirming sends `reask-request {key?, question}`, and the card's thread already reads a muted "re-ask requested", so write no thread message. Re-read the card's notes and thread, and ask that question again as they stand today with `question add`, adding `reask_of: "<that question id>"`; on a card with a planner running, have the planner ask it. `question add` notes "Asked again as <new id>" on the old question, which ends the request ([a resolved question is read-only and can be asked again](docs/adr/2026-11-04-2001-a-resolved-question-is-read-only-and-can-be-asked-again.md)). **The board owns the page; you write only the takes** ([the board serves every prototype from one template](docs/adr/2026-11-01-1448-the-board-serves-every-prototype-from-one-template.md)). Record the prototype with `board.mjs --site <route> prototype add <topic> --title "…" --card <wave>/<id> --take A:"Label" --take B:"Label"` (topic `a-z0-9-`, each key one capital letter, labels never starting with "A. ", since the take pill shows the label alone), which prints the take files to write, `public/prototypes/<topic>/<KEY>.js`. The board then serves `prototype-<topic>.html` from its one template: the header with back to the card, the shared take pill, `?take=` and `?q=`, and a stage for the take. `prototype list` shows what is recorded, and `prototype rm <topic>` drops a record once no open question uses it. Each take file is a module, `export function render(stage, kit) { stage.innerHTML = …; return cleanup }`, rerun with a fresh stage on every switch. `kit` draws the real components fed fake data: `kit.sidebar()`, `kit.hub({usage, afterUsage, usageCard, settings…})`, `kit.cards()` and `kit.sheet(html, {title})`, with `kit.toast`, `kit.on`, `kit.fake` and `kit.esc`; `BoardUI` and `HubUI` are globals too, and `board/public/prototype-kit.js` documents each. Build each take from these, changing only what the take is about; a look-alike copy drifts from what ships (owner, on back-to-card). The stage contains its take, `position: fixed` included, so nothing covers the header or pill; links and forms in it only toast, and the listeners and timers a take adds end on the next switch. Every interaction must work on a tap. Include this prototype paragraph in the brief of any builder or agent asked to make a prototype. With each prototype, add an open question on its card: one option per take, the option ids the take keys in take order (a later round may list only some takes, still in take order), and the recommended option tagged with `recommended: true`, with `url` set to `prototype-<topic>.html?q=<question id>`, relative to the site root and stored as given. A question added on a card's topic resolves each question there that is answered and still waiting on Claude, noting "Followed by <new id>", and prints `resolved: <old id>`; a question re-asked on the same topic and prototype resolves any other unresolved one it replaces, noting "Replaced by <new id>", and prints `replaced: <old id>` ([an answered question stops waiting once Claude acts on its card](docs/adr/2026-11-01-1813-an-answered-question-stops-waiting-once-claude-acts-on-its-card.md)). Acting on a card's answers resolves them the same way, noting what acted: `item set --status building` ("Acted on: build started"), `builder start` ("Acted on: builder started"), and a `reply` to an ask or a wave triage for the answered questions on its topic ("Acted on: replied") ([the CLI and board server do the board's housekeeping](docs/adr/2026-11-02-2044-the-cli-and-board-server-do-the-boards-housekeeping.md)). A pick, with a note or none, settles its round: the note is the build's guidance, and the build starts as any card's does ([a prototype pick is the build's go, and more of the skill moves into code](docs/adr/2026-11-03-0504-a-prototype-pick-is-the-builds-go.md)). An answer with a note and no take, the owner having cleared the pick, resolves its question and leaves the card prototyping with Next "Prototype again" until a take is picked; its owner-answer event carries `prototype_again: true`: draw the next round from the note as for a `prototype-request`, or grill when the note asks a question ([a prototype answer with no pick prototypes again, so a card is never stuck](docs/adr/2026-11-03-1646-a-prototype-answer-with-no-pick-prototypes-again.md)). The questions page measures each take in a frame: a single take, or a question where any take scrolls or fills the screen, gets one full-width take below the option row, switching the take in place with no reload; if all fit, they show in a grid. Every frame takes taps. Both layouts keep the note and Send on Waiting on you; a page opened from a thread keeps its own header and take pill, where the owner can also pick ([a question's takes show inline, and back goes one step](docs/adr/2026-10-29-1639-a-questions-takes-show-inline.md)). In the app, back from the page goes to Waiting on you on that question and then to the board, however the owner arrived; the header's `backHref` applies only when the page is opened alone ([Back follows the page tree](docs/adr/2026-10-30-1125-back-follows-the-page-tree.md)). A page that links the board's `style.css` links `colors.css` before it, since every color lives there ([one colour constants file shared by the hub and every board](docs/adr/2026-10-29-1639-one-colour-constants-file-shared-by-the-hub-and-every-board.md)). The board pushes it (Push notifications, below). Add prototypes to a card's question unasked only when they obviously help. A choice with nothing to see, such as a code structure, a data flow or a policy, gets `diagram` and `practice` on each option instead (`question add` usage): boxes and arrows the questions page draws, and a line or two of what it means in practice (what a new feature must do, what changes, what it costs), so the pick is between concrete options, not labels (owner, 2026-10-24, on hb-model; [a question's takes show inline, and back goes one step](docs/adr/2026-10-29-1639-a-questions-takes-show-inline.md)). **Grill a card through open questions on the card**, never a separate page or a link ([a card is grilled through its own open questions](docs/adr/2026-10-29-1639-a-card-is-grilled-through-its-own-open-questions.md)): `board.mjs question add` each question with topic `item:<wave>/<id>`, options in take order with the recommended option tagged, and a prototype page in `url` when one helps. They land in Needs you and on the open card. When the ask is to grill new work, make the card first (`--from-ask`), then the questions. Questions that span several cards go under a declared topic (`topic add`). **Messages stand alone.** Every reply, thread line and question reads on its own in the card's thread, where the owner reads it: name cards by their label (e.g. #96), and a pointer such as "above", "below" or "here" must make sense in the card's own thread, never point into this session. The owner reads only the board, not this session.
|
|
88
88
|
|
|
89
89
|
**Waves** ([a wave the owner asks for is a lineup of cards](docs/adr/2026-11-02-1202-a-wave-the-owner-asks-for-is-a-lineup-of-cards.md)). What the owner calls a wave is a **lineup** in `board.mjs`: a named set of cards from one wave file, in phases in build order, shown as a band in Todo with Implement for its next ready card ([waves are bands in Todo, each with its next card's Implement](docs/adr/2026-11-03-1840-waves-are-bands-in-todo.md)). Make one only when the owner asks with the +, or when you spot a grouping in Todo: then propose it as an open board question on a declared topic, with the cards, the phases and the goal, and make it only on a yes. Group the cards into phases by the files and areas they touch ([a wave builds in phases, and cards in one phase build side by side](docs/adr/2026-11-04-0238-a-wave-builds-in-phases.md)): read each card's notes and the code it will change, put cards that touch the same files or area in different phases, and let cards that don't share a phase so their builders run side by side; order the phases so a card comes after what it builds on. Two cards in one phase that still touch the same file need nothing special: the later one rebases at land. Planning produces three things, through `board.mjs --site <route> lineup create <slug> --title "<name>" --goal "<one line>" --phase <wave>/<id>,… --phase <wave>/<id>,… --note "<why these phases, in a few lines>"`, one `--phase` per phase in build order: the wave's band in Todo, each card blocked by every card of the phase before (its other `deps` stay), and the wave note that Notes opens. Each card stays one PR, and starts on its own as it unblocks, with no approval step. `lineup add <slug> <wave>/<id> [--after <wave>/<id>]` puts a card in a phase of its own and `lineup remove` takes one out, `lineup set` renames it, sets its goal, rewrites its note or (`--phase` per phase, every card once) re-phases it, and `lineup finish` takes its band out of Todo. A wave made before phases is a chain, one card per phase, and stays so until it is re-triaged. The owner's ⋯ Move up / Move down on a band card swaps it with the Todo card above or below on the server, each taking the other's phase, with no event. A wave whose cards are all Done leaves on its own. A `plan-waves {keys}` event is Plan waves at the foot of Todo, with the keys of its free cards ([Plan waves plans from any Todo cards](docs/adr/2026-11-03-0509-plan-waves-plans-from-any-todo-cards.md)). Triage those cards into one or more waves, each a lineup within one wave file, in phases grouped by the files and areas the cards touch, as above: ask each as one open question on a declared topic, with its phases and goal, and `lineup create` only on a yes. Then `reply <eventId>` with one line, such as "2 waves proposed". A `wave-triage {lineup}` event is the owner's Triage on a wave's band, which reads Triaging… until you reply to that event. Read the code for the wave's unstarted cards (Todo, not started) and decide what to change: split a card that is more than one PR, merge cards too small to stand alone, add a card the goal needs that none covers, close one already covered, or re-phase the wave by the files and areas its cards touch, as Plan waves does (a chain's cards that touch different areas can share a phase). Add one open question per change, all in one `question add` file, on topic `lineup:<slug>`, with source "Wave triage", the change as the question, what it does to the order in `context`, and options for yes (recommended) and no. Then `reply <eventId>` with one line, such as "3 questions" or "No changes". Needs you shows them as "<wave> · N questions", and the band reads "Triage: N open questions" until they are resolved. Act on each answer: `lineup set --phase …` for a re-phase, `item add` and `lineup add` for a split's new card or an added one, `item set --status dropped` and `lineup remove` for a close, `item set --title`/`--notes-file` for a merge's surviving card, then `question resolve`. On a board over `~/.claude/waves`, each unfinished wave file is a band too, with Triage in its head, which sends `wave-triage {wave}` with the file's slug. Check each unfinished card in that file: its PR, CI, reviews and blockers, and for a Todo card whether a merged card already covers it. Fix what you can yourself, such as a rebase or a CI re-run. Then add one open question per card that needs the owner, all in one `question add` file, on topic `wave:<slug>`, with source "Wave triage", and `reply <eventId>` with one line, such as "2 questions" or "Nothing needs you". Needs you and the band show them as a lineup's.
|
|
90
90
|
|
|
@@ -110,7 +110,9 @@ Reviewer DMs and digests wait on Slack. Before steps 1–3, run `board.mjs --sit
|
|
|
110
110
|
|
|
111
111
|
**Rules** ([rules live on Settings, at hub and board level](docs/adr/2026-10-29-1639-rules-live-on-settings-at-hub-and-board-level.md)). On a board, the watcher prints `rule hub <text>` and `rule board <text>` lines after the `event` lines. They are the owner's standing instructions: follow them as you follow this skill. Hub rules apply to every board, and the board's own come on top. A `rules {scope, added, edited, removed}` event means the owner saved them on hub or board Settings. Read the printed list, then write one reply (`board.mjs reply <eventId>`, shown under the Rules section) saying what you will do differently. When the owner says "codify" in a thread, run `board.mjs --site <route> rules add --by claude --text "<the rule>"`. Add it at board level unless it concerns every board; then pass `--hub`. `rules list` shows both lists with their ids, for `rules edit` and `rules remove`.
|
|
112
112
|
|
|
113
|
-
**Auto-merge** ([Auto-merge is a board setting, off by default, and the owner merges from the card](docs/adr/2026-10-29-1752-auto-merge-is-a-board-setting-off-by-default.md)). A board's `auto_merge` setting, off unless set, decides who merges an approved PR: on, the GitHub tick does; off, the card shows the owner Merge, and the
|
|
113
|
+
**Auto-merge** ([Auto-merge is a board setting, off by default, and the owner merges from the card](docs/adr/2026-10-29-1752-auto-merge-is-a-board-setting-off-by-default.md)). A board's `auto_merge` setting, off unless set, decides who merges an approved PR: on, the GitHub tick does; off, the card shows the owner Merge, and the board server merges when it is tapped, with no event for the session ([the Merge tap and auto-merge run in code](docs/adr/2026-11-04-2045-the-merge-tap-and-auto-merge-run-in-code.md)). A refusal goes in the card's thread; with commits after the approval, the tick leaves the card to the session, which merges it with `board.mjs merge <key> --after-approval "<why>"`. The watcher prints `setting auto-merge on|off` after the `rule` lines. The owner sets it with the toggle on board Settings, which writes `board.json` itself; a `settings {auto_merge}` event says it changed. `board.mjs --site <route> settings` prints the settings, and `settings set auto-merge on|off` sets auto-merge, only on the owner's word. `settings set github-login <login>` sets the GitHub account Claude posts as on this board; pass `""` to clear it.
|
|
114
|
+
|
|
115
|
+
**Held Merge and PRs** ([a land waits for your Merge](docs/adr/2026-11-04-2100-a-land-waits-for-your-merge.md)). With auto-merge off, a card with no PR stops after its tests at `done` and waits in In Review with a Merge button. Its tap never reaches the session: the board server runs `board.mjs merge <key>`, which rebases onto main as it is now, reruns the tests, lands and pushes main if Push main is on; a conflict or failing tests launch a headless lander, whose `done` lands it with no second tap. The `prs` setting (`setting prs on|off`, on unless set) applies to a GitHub board that builds in its own clone: off, no card opens a PR and each lands on main. `settings set prs off` is refused until Push main is granted, and only on the owner's word.
|
|
114
116
|
|
|
115
117
|
**Board sessions** ([the hub resumes a session only when nothing runs it](docs/adr/2026-10-29-1811-the-hub-resumes-a-session-only-when-nothing-runs-it.md)). The manifest records each site's session in its owner: the id, its folder (`repo`) and an optional model. `node $KF start <route> [--repo <dir>] [--model <m>] [--prompt <text>]` starts a background session (`claude --bg`) in the folder, named after the board, in auto mode, with `worktree.bgIsolation` set to `"none"` so it works in the repo folder; its builders make their own worktrees. Its first prompt is `/kickflip --name '<board name>'` plus the prompt. When the folder is a board repo the hub cloned, `start` first marks it trusted (`kickflip join` marks its own folder once, as it adds the board) in Claude Code's `.claude.json`, since `claude --bg` refuses an untrusted folder; it writes nothing when that file is missing or isn't valid JSON, and the start fails with the reason ([the hub trusts the clones it makes](docs/adr/2026-11-02-1549-the-hub-trusts-the-clones-it-makes.md)). On a route with no site yet, the session waits in the manifest's `starting` until it joins. `node $KF resume <route>` runs `claude --resume <id> --bg` in the recorded folder, with no other flags for a background session so it wakes in place under its id, tells the session to rejoin and relaunch its watcher, and records the id it comes back under as the owner, keeping its label, model and folder. `node $KF sessions` prints each board's route, session, whether it is live (in the background or a terminal) and its folder. A session is live while a `~/.claude/sessions/<pid>.json` record names it and that process runs; `start` and `resume` refuse a live session (exit 3), so a terminal session never gets a second process. A board listens only while its session is live and a watcher runs for it. Claude Code stops a background session that has idled unattached for about an hour; it stays stopped until an event lands for its board, and then the hub resumes it at once, as `resume` does. As a fallback, a board not listening for 3 minutes whose session hasn't heard an event is resumed too. Either happens once per outage, and if the board still isn't listening 5 minutes later, the hub pushes "<title>'s session didn't come back" once. A board whose owner has no folder recorded is never resumed ([a board's session wakes when an event arrives, and live means its session runs](docs/adr/2026-10-30-1601-a-boards-session-wakes-when-an-event-arrives.md)). A session the hub starts or resumes also gets a `PermissionRequest` hook (`hub/permission-hook.mjs`), so its permission prompts and `AskUserQuestion`s, which have no terminal to show in, come to the owner's phone: the hub pushes "<title> wants to <action>", or the question, and the board's Needs you shows the pending prompt with Allow once, Always allow and Deny, or the question's options. Always allow returns a session rule to Claude Code and keeps the same rule in the hub's `prompts.json`, keyed by session id. The hub remembers rules only with a reliable process start time and binds board rules to the current ownership's `owner.since`; ownership commands clear remembered rules and pending prompts for both the old and new sessions, including boardless entries, even while the hub is stopped. The hub immediately allows a later exact match on its command, absolute path, domain or bare tool name, with no push or Needs you line; prefixes and globs never widen a match, and `AskUserQuestion` always asks. The remembered rules end when that session stops or the board's owning session changes, and the hub page lists them read-only under the board. With no answer in 10 minutes, the call is denied with a note telling you to ask on the card instead: do that, with `question add` on the card's topic. A session started by hand in a terminal keeps its own dialog ([a background session's prompts and questions come to the phone](docs/adr/2026-10-30-1516-background-sessions-prompts-come-to-the-phone.md)). Board settings' Permissions switches (push main after a land, push release tags, open draft PRs) write allow rules into the board folder's `.claude/settings.local.json`, which your session picks up live; only the signed-in owner can change them, from any device signed in to Tailscale as the owner ([only the signed-in owner changes permissions or restarts](docs/adr/2026-11-04-0502-only-the-signed-in-owner-changes-permissions-or-restarts.md)). A `PermissionDenied` hook (`hub/permission-denied-hook.mjs`) notes under its switch a command auto mode still refused ([board settings grant a board's session permissions](docs/adr/2026-11-03-1921-board-settings-grant-a-boards-session-permissions.md)).
|
|
116
118
|
|