Imported from EdgeVector/last-stack (
skills/kanban/SKILL.md). Install upstream withnpx skills add EdgeVector/last-stack --skill kanban. Copyright stays with the author.
NO REVIEW COLUMN (Tom 2026-07-16 — won't-undo)
There is no review column. Board columns are only:
backlog → todo → doing → done.
- Incomplete work that is still agent-actionable: stay in
todoordoing - Complete work:
doneonly with merge/END-STATE proof - Intentional holds: set
block_status=needs_human|deferred|design_first+ reason and park inbacklog(not the default pickuptodolane — see below)
Never kanban move <slug> review. The live board rejects it. Do not invent
a review lane on custom boards either.
Default todo is the pickup claim lane (won't-undo — 2026-08-15)
On the default board, todo is the autonomous pickup lane. The CLI/MCP
enforces pickup-readiness on writes into that lane. Agents that ignore these
rules burn a retry (and often a whole routine turn) on hard rejections:
| Rejection (real CLI/MCP error) | Correct action |
|---|---|
cannot be placed in default/todo with block_status=deferred (same family for human-gated holds) |
Put holds in backlog with block_status + reason. Do not park deferred/human-gated work in default todo. |
Kind:pr card … cannot enter todo without a milestone |
File Kind:pr via $LAST_STACK_ROOT/bin/last-stack-kanban-file-pr with live --north-star and --milestone (or --ensure-milestone). Raw kanban add --column todo without both is refused or becomes unattached-outcome. |
cannot carry --pr-url / --branch in default/todo |
Leave todo cards claim-ready without in-flight PR fields. Stamp --pr-url / --branch only after the card is in doing (or when re-adding mid-claim). |
Milestone "…" not found |
Use the full milestone slug (kanban milestone list / milestone show). Never truncate slugs. |
Quick rules of thumb:
- Pickup-ready Kind:pr → always
last-stack-kanban-file-pr(not rawkanban add). - Human/deferred hold →
backlog+block_status+ reason. - In-flight PR linkage →
doing+--pr-url/--branch. - Non-PR trackers →
Kind: tracker|validation|meta+DONE-WHEN:; still need cleanRepo:/Base:headers; do not force them into the Kind:pr helper path.
Settled decisions before file (won't-undo — 2026-08-20)
A new Kind:pr card must not contradict a settled brain record.
- Producer:
last-stack-kanban-file-prrunslast-stack-kanban-decision-checkand stamps## DECISION-CHECK. A mechanical conflict exits 2 and does not write the card. - Scope: brain
type: decision,type: design, andtype: preference. Search is candidate slugs only. Each slug is point-got. - Honor: if the stamp lists slugs, read those records with the stamp's
read:lines (for exampleread: gbrain get decision/<slug>while gbrain is primary;brain getreads LastDB, which does not hold most settled records). Rewrite the brief so it follows them, or do not file. - Unavailable:
verdict: unavailablemeans the check could not run. Only repair/incident/proof/closeout cards are filed that way; re-runlast-stack-kanban-decision-check --title ... --inject < bodybefore you act on the card. - Do not pass
--skip-decision-checkfrom a generator or routine. That flag is tests/operator only. - Raw
kanban add/ MCPfkanban_addfor Kind:pr skips this gate. Uselast-stack-kanban-file-pr. - Metadata-only upserts (
--pr-url,--branchon a live card) are not a create. Do not re-run the helper for those.
--force waives the gate for operator override only — unattended routines and
generator agents must not use it to hide unattached or deferred work.
kanban — board management
kanban is a kanban task board stored in LastDB: a thin CLI/MCP client of a LastDB node. Cards move through columns; every change persists in the node.
- Location / how to run: clone
kanbanand run itssrc/cli.tsunder bun. A global shim (kanban) can be symlinked onto your PATH so a barekanban <command>resolves from anywhere; without it, runbun run src/cli.ts <command>from the repo directory. See the kanban-setup skill for install. - ⚠️ PATH gotcha (sandboxed shells): some agent harnesses run shell commands
sandboxed with a stripped
$PATH, so a barekanban …fails withcommand not found: kanban, and even after you add the shim's directory the shim's internalexec buncan then fail withcommand not found: bun. Both the shim dir and the bun dir must be on PATH. The robust fix is to prepend a complete PATH that includes both at the start of every kanban Bash call (PATH does not persist between calls), or invoke the CLI directly:cd <kanban-repo> && PATH="$HOME/.bun/bin:$PATH" bun run src/cli.ts <command>. Confirm the shim withcommand -v kanbanand a narrow read. - Where the data lives: the board records live on your LastDB node. The
CLI talks to the node over its configured transport. Local daily-driver nodes
may be Unix-socket only with HTTP intentionally shut down; use a
socket-backed narrow read as the routine health check. The node URL is
configurable —
initdefaults to a node running locally on your machine; point it elsewhere with--node-url/ the config file (~/.kanban/config.json). - Columns:
backlog → todo → doing → done.
Before doing anything non-trivial, sanity-check the setup with a socket-backed narrow read:
kanban list --column todo --json
Commands
(With the shim on PATH these run from anywhere; otherwise replace kanban
with bun run src/cli.ts and run from the kanban repo directory.)
kanban list --column todo --json # narrow column preview
kanban list --board <b> --column todo --json # board-scoped column preview
# Avoid kanban list --full-body in routines; use show for selected cards.
kanban search "<text>" --json # text search; no --full-body flag
kanban show <slug> --json # one card in detail
kanban add <slug> [flags] # create OR update a card
kanban mark <slug> "<line>" # append one PROGRESS/HANDOFF line
kanban move <slug> <column> [--position N]
kanban set <slug> --block-status <s> [--block-reason "..."]
kanban rank # rewrite the column order (no slug)
kanban rm <slug> # delete (hard erase; no trash)
kanban board create <slug> --title ... --columns a,b,c
kanban board list
mark takes the slug and the line as positional arguments. There is no
--note and no --line. Those flags exit 2 with Unknown option. MCP
fkanban_mark is the structured equivalent.
rank rewrites every work card's position in one column (default todo).
It takes no card slug and no --top. kanban rank <slug> --top exits 2 with
Unknown option '--top'. To place one card, use
kanban move <slug> <column> --position N (lowest N is first for pickup).
move changes column (and optional --position). It has no --block-status.
Stamp a hold with kanban set <slug> --block-status needs_human|deferred|design_first --block-reason "...",
then kanban move <slug> backlog if it must leave the pickup lane. add can
set --block-status on create; set is the metadata-only path for an existing
card.
RELEASE-WHEN: every hold names what releases it
A deferred or needs_human hold must say what ends it in a form a machine
can check. Put one body line (append it with kanban mark; the last line wins):
RELEASE-WHEN: papercut-<slug>, card:<slug>, http://localhost:3300/EdgeVector/<repo>/pulls/<n>, after 2026-10-01
| Condition | Holds when |
|---|---|
papercut-<slug> |
brain get <slug> --type papercut status is fixed, verified, wontfix or duplicate |
card:<slug> (or a bare slug) |
the card is in column done |
Forge PR URL or owner/name#N |
the PR is merged |
after <YYYY-MM-DD[THH:MMZ]> |
the time is past |
none |
never: an explicit hold (for example superseded by <slugs>) |
Separate conditions with , or ;. ALL conditions must hold.
last-stack-kanban-deferral-release (the groom-board routine runs it with
--apply) reads backlog and todo. When all conditions hold, it clears the
hold, marks the evidence, and moves a Kind: pr card with a ## GOAL +
## END STATE brief to todo if kanban pickup explain accepts it.
With no RELEASE-WHEN: line, it reads block_reason only when the reason
starts with awaiting, waiting on|for, blocked on|by, until or after,
and only up to the first ;, . or dash. There it reads papercut-* slugs,
PR URLs, card:<slug>, after <date>, and a card slug directly after the
lead phrase (blocked on <card-slug>). A hold with no checkable condition
is reported unconditioned and is not changed. Deploy-parked cards
(awaiting-deploy tags) belong to last-stack-kanban-reopen-deferred.
list flags: --board --column --tag --assignee --wide --field --limit N --all --json --full-body --full_body.
search flags: --board --column --field --limit N --all --json.
kanban search has no full-body option — there is no --full-body
(or --full_body); passing it fails with Unknown option '--full-body'.
If you need full bodies from search results, use kanban search <query> --json
and then kanban show <slug> --json for a selected card, or pass
full_body: true to the MCP kanban_search tool (the underscore form is the
MCP tool argument, never a CLI flag).
Search is useful, but it may be temporarily unavailable while a board backend is
blocking full-schema scans. In routines, prefer scoped reads first:
kanban list --column todo --json, kanban list --column doing --json, and
kanban show <known-slug> --json. If kanban search returns
full_schema_scan_not_allowed, do not treat that as board outage and do not run
doctor/restart paths; fall back to column previews plus slug-pattern checks, then
file/update the best deduped card you can prove from those bounded reads.
show, move, rm, rank, dep, and tag operate on the default board
implicitly and reject --board. Only add --board to commands whose help lists
one, such as list, search, and add.
add flags: --title --board --column --assignee --tags --body. Re-running
add with the same slug updates the card (upsert), so it's safe to edit a
card by re-adding it. Default column for a fresh card is backlog; for a task
you want worked soon, pass --column todo.
Every live card must carry body ownership headers. Even tracker, validation, or
meta cards that are not normal pickup work need explicit Repo: and Base:
lines so watch/groom/pickup routines can classify them consistently. For non-PR
cards, set Kind: tracker|validation|meta, include an explicit DONE-WHEN:
predicate, and pass the matching --kind value when the CLI supports it.
Legacy registry cards are only for registry-record maintenance.
Filing a card with a real body — feed it via stdin
The card body is usually a multi-paragraph spec. Write it to a temp file and
pipe it in on stdin — that sidesteps shell quoting entirely. Do not
inline it with a nested heredoc (--body "$(cat <<'EOF' ... EOF)") — that
mangles and can silently produce an empty card.
# 1. write the spec
cat > /tmp/card-body.md <<'EOF'
...full markdown body...
EOF
# 2. file the card (stdin body)
kanban add my-slug \
--title "Short imperative title" \
--column todo --tags "app,cli,perf" < /tmp/card-body.md
# 3. verify it landed
kanban show my-slug | head -8
Always confirm with show <slug> after writing — the add is only successful
if the card actually reads back.
add is two keyed point reads + one write (~0.2s) and every request carries a
30s deadline (FKANBAN_HTTP_TIMEOUT_MS to override), so commands fail fast
instead of hanging on a busy node. service_timeout, "node did not respond
within 30000ms", or "too many concurrent reads" means load/backpressure, not a
dead node. Do not run doctor/restart loops for that class of error; retry the
idempotent slug upsert after a short backoff or raise FKANBAN_HTTP_TIMEOUT_MS
for one bounded command, then verify with show.
The card brief is the spec — and must trigger the agent
Name the card after the layer it changes. The slug is permanent, so get
it right at filing. A wire or frame change is "transport", not "streaming",
and not a storage-limit change. When the brief cites a byte ceiling, cite the
constant and the encoding that produce it (for example MAX_BODY_LEN and
base64 4:3), and state the tier (atom vs file/CAS) when a number sounds like
it crosses the 64 KiB atom fence
(papercut-card-naming-streaming-vs-transport-file-blob).
A card that's meant to be implemented should carry, in its --body:
-
A header so the agent picks it up and drives it to merge (kanban does not auto-spawn agents and finished cards don't reach
doneon their own):Follow the kanban-agent skill — drive this through to a MERGED PR. A card is only
donewhen its code is actually in the repo. -
A work/ownership header telling the agent where to work or which repo owns the tracker/registry context. The CLI stores structured fields too, but routines still parse body headers directly, so the body header is mandatory:
Repo: owner/name Base: main Branch: kanban/<slug> Kind: prField meanings —
Repo:owner/name(e.g.EdgeVector/fold) or an absolute local Git checkout path;Base: base branch;Branch: optional, defaults tokanban/<slug>;Kind:pr | tracker | validation | metafor new cards (registryonly for legacy registry-record cards). OptionalDifficulty: fast | normal | hard(Loom land-card v2, 2026-09-24): IMPLEMENT and REVISE route at that tier of the routing matrix; REVIEW staysfast. Omit it for a well-scoped card (defaultfast, the low quick model). Sethardonly for a card that a small model is likely to get wrong. An unknown value falls back tofastwith a PROGRESS line.⚠️ Keep each header value a single clean token on its own line.
kanban-pickupresolvesRepo:literally — it does NOT strip trailing# comments, parentheticals, or prose. A dirty value (Repo: EdgeVector/fold # defaulted,Repo: fold (also touches exemem-infra),Repo: last-stack,Repo: none, orBase:/Branch:mashed onto theRepo:line) is treated as unresolvable and the card is force-blocked intoreview/needs_human— the #1 cause of stranded cards. So:- Use the full
owner/name(EdgeVector/last-stack, not barelast-stack). - No trailing
#comment and no(parenthetical)on the value line. - Put
Base:,Branch:,Kind:each on their own line. - Multi-repo notes ("also touches X", "sibling repos …") go in the spec
body prose, never on the
Repo:line. Pick the ONE primary repo.
- Use the full
-
The spec itself: GOAL / CONTEXT / STEPS / VERIFY (exact commands that must pass) / DONE WHEN (PR merged into ) / OUT OF SCOPE.
For non-PR cards, replace the PR merge terminal condition with one single-line machine-checkable predicate:
Kind: tracker|validation|meta DONE-WHEN: brain <slug> exists DONE-WHEN: brain <slug> updated-after <YYYY-MM-DD> DONE-WHEN: routine <name> heartbeat matches /<regex>/ after <YYYY-MM-DD> DONE-WHEN: date >= <YYYY-MM-DD> DONE-WHEN: file <path> matches /<regex>/DONE-WHENpredicates are read-only and deterministic. The reconciler and groomer may move a non-PR card todoneonly when the predicate is satisfied. Pending predicates stay quiet. Malformed or missing predicates on non-PR cards become a visible card-authoring issue.Kind: prcards ignoreDONE-WHENfor closure and still require a merged PR.
Verify the facts you put in a brief against origin/main before filing —
local checkouts lag, so git fetch and read origin/<base>:<file> rather
than describing stale "current state".
Grooming / triage
- "What's on the board" →
list --json, then summarize by column. - "What's stuck" → look for cards long in
review(PR open, not merged) ordoing(claimed, no PR). Surface them; don't silently re-drive — that's the kanban-agent reconcile pass's job. - Superseded / wrong card →
rm <slug>(deletes; hard erase, no trash), or re-addto fix it. - A PR card in
donemeans its PR merged — that's the normal terminal state, not a kill. A non-PR card indonemeans itsDONE-WHENpredicate was satisfied and cited by watch/groom.
Guardrails
- Never kill a LastDB node you didn't start — the board lives on it. If
doctorsays the node is unreachable, surface it; don't restart things blindly. Do not treat a disabled HTTP health endpoint as failure whendoctorsucceeds over the Unix socket. For destructive/migration testing, spin up an ephemeral node on another port rather than touching a shared daily-driver node. - If a board/brain command returns
HTTP 423,keyring_undecryptable, or "the node is up but cannot decrypt your data", the node is alive but locked. Stop and surface that exact state; do not run restart/doctor loops or attempt keychain/passphrase repair unattended. - If a board/brain command returns
service_timeout, "node did not respond within 30000ms", or "too many concurrent reads", the shared node is busy. Prefer targeted reads (show, typedbrain get) over broad lists, retry only idempotent upserts by slug, and never restart the node to clear load. - This skill only manages the board. To actually implement a card, hand off to the kanban-agent skill (or tell the user it's ready to be worked).
