Expand description
The backlog CLI: kranz ticket …, kranz draft, kranz queue, and the
kranz work dispatcher (design: docs/backlog-and-slack.md).
Tickets are missions-in-waiting authored as .kranz/tickets/<slug>.md.
The pipeline is: ticket new scaffolds one; draft runs the planning
conversation non-interactively (orchestrator only, budget-capped) and parks
a committed plan.md for review (or bounces the ticket back with the
orchestrator’s questions); ticket approve enqueues the parked mission;
work drains the per-repo queue, one mission at a time.
The rendering and decision logic here are pure functions (data → String,
or state → next-action) so they are unit-tested without a backend; the two
handlers that spawn claude (draft, work) are thin async wrappers over
the shared engine + run_mission_loop.
Structs§
- Ticket
Row - One row of the
kranz ticket listtable: the ticket plus its resolved terminal label (already split into DELIVERED/LANDED for a Done ticket).
Enums§
- Draft
Decision - What a
draftturn resolved to, given thePlanRequestand whether--yes(auto-approve+enqueue) was passed. Separating the decision from the I/O keeps the state-machine unit-testable without a backend. - Work
Action - The dispatcher’s next action given the queue front and repo-busy state.
Functions§
- cmd_
decompose kranz decompose <goal> [--yes]: one planner turn decomposes a complex goal into a blocked-by ticket DAG (design: .kranz/tickets/ ticket-dag-decomposition.md). The proposed DAG is always printed; tickets are written only with--yes(dry-run preview otherwise), all-or-none — any validation refusal (slug rules, unknown blocker, missing root, cycle) writes nothing.- cmd_
draft kranz draft <slug> [--yes] [--from-mission m-xxxx]: non-interactive plan drafting.- cmd_
queue kranz queue.- cmd_
queue_ remove kranz queue --remove <mission-id>: retire only the runnable queue entry. The mission record remains append-only/auditable and may be abandoned or re-enqueued by the caller’s own lifecycle policy.- cmd_
ticket_ approve - Deprecated alias for
cmd_ticket_queue.kranz ticket approveused to be the only spelling for ticket-queueing; D-A renamed it toqueueand reserved “approve” for plan approval. Kept one release for compatibility. - cmd_
ticket_ list kranz ticket list.- cmd_
ticket_ migrate_ state kranz ticket migrate-state [--yes]: the one-time fold of terminal.statussidecars into committed frontmatterstate:keys (design ticket-state-frontmatter, rule 4). Dry-run by default — the report lists every fold it WOULD make plus the loud per-name skips —--yesapplies. The fold logic and its skip rules live inkranz_engine::migrate_state; this wrapper only renders.- cmd_
ticket_ new - Scaffold
.kranz/tickets/<slug>.md. Refuses (error) if a ticket with that slug already exists. Thin wrapper overTicket::scaffold. Returns the written path. - cmd_
ticket_ queue kranz ticket queue <slug> [--mission <id>]: enqueue the parked (Review) mission for the ticket and set the ticket Queued.- cmd_
ticket_ ready kranz ticket ready [--include-deferred]: the pick-up-now listing (D-BW-3). Ready = an actionable pipeline state (not in flight, not terminal) AND nodefer-untilstill in the future — a deferred ticket simply becomes listable on its day; the clock at listing time is the only arbiter, there is no scheduler.--include-deferredappends the parked deferred tickets with their defer times for operator visibility.- cmd_
ticket_ show kranz ticket show <slug>.- cmd_
work kranz work [--once]: the dispatcher. Drains the per-repo queue one mission at a time;--onceprocesses exactly one front entry (or exits if the repo is busy). Per-repo serialization is enforced byis_repo_busy.- draft_
decision - Map a completed plan request + the
--yesflag to the next action. Pure: the caller performs the git/state side effects the decision names. - next_
work_ action - Decide the dispatcher’s next step from the queue front + busy state.
Pure:
frontisqueue::peek,busy_withisqueue::is_repo_busy. - render_
migration_ report - Render the fold report: one line per fold (or would-fold), one loud line per dirty skip, a line per ticket with a NON-terminal sidecar (pipeline state is left sidecar-owned by design), and a summary. No-sidecar tickets are summary-counted only — a line each would drown the signal.
- render_
queue - Render the
kranz queuetable: position, priority, mission id, ticket slug, plus a header noting whether the repo is currently busy. - render_
ticket_ list - Render the
kranz ticket listtable: slug, priority, state, title. - render_
ticket_ ready - Render the
kranz ticket readytable: the ready rows in the same shape asrender_ticket_list, then — only wheninclude_deferredasked for it — a second table of the not-yet-ready deferred tickets with their defer times (D-BW-3: operator visibility without polluting the default listing). - render_
ticket_ show - Render
kranz ticket show <slug>: the parsed ticket, its resolved terminal label, and any “needs context” / “wrong plan” block appended to the ticket body. - split_
questions - Split the orchestrator’s “not ready” prose into individual questions: each non-empty line, with any leading bullet/number marker stripped. A reply with no line breaks becomes a single one-item list.
- ticket_
state_ for_ mission - Map a terminal (or blocked) mission status to the ticket state recorded
after a run. Only called by
reconcile_ticket_for_missionfor terminal/blocked statuses — live statuses are gated out before this runs. - ticket_
state_ label - UPPERCASE label for a ticket pipeline state (mirrors mission status labels).
- ticket_
template - Back-compat wrapper (no context) over
Ticket::ticket_template, which now lives inkranz-engineso the RESTPOST /api/ticketshandler shares it instead of duplicating. - ticket_
terminal_ label - A ticket’s terminal-state label, splitting
DoneintoDELIVERED(mission complete but its branch is not yet merged into base) vsLANDED(mission branch merged, or no mission-merge information to distinguish otherwise) — reusing the engine’s merged-ancestor probe (kranz_engine::merged::ticket_merged) so this can never drift from the REST/api/ticketsprojection. Non-Donestates render exactly asticket_state_label. - work_
skip_ for_ failed_ blocker - Work-time re-check for a claimed queue entry with a ticket:
Some(blocker)when one of the ticket’s unsatisfiedblocked-byentries is unsatisfied because that blocker’s own ticket ended up Failed (its mission reached a terminal non-Complete state — Failed/Abandoned/Blocked — after batch-approval queued this entry alongside it). The dispatcher must skip such an entry rather than run it: re-driving a mission whose dependency failed can never succeed, and retrying forever would hot-loop.