State machine
stateDiagram-v2
[*] --> brainstorm: kf new
brainstorm --> planning: requirement/bug report filled + confirmed
planning --> backlog: kf approve --backlog
planning --> implementation: kf approve
backlog --> implementation: user chooses start
backlog --> planning: revise scope/plan
implementation --> testing: implementation complete
implementation --> planning: scope/plan needs change
testing --> review: testing result PASS + stage record at HEAD (or waiver)
testing --> implementation: testing FAIL
testing --> planning: scope change
review --> done: review PASS + PR merged (or nothing to land)
review --> implementation: review FAIL
review --> planning: scope change
done --> [*]
brainstorm --> cancelled: kf cancel --reason
planning --> cancelled: kf cancel --reason
backlog --> cancelled: kf cancel --reason
implementation --> cancelled: kf cancel --reason
testing --> cancelled: kf cancel --reason
review --> cancelled: kf cancel --reason
done --> cancelled: kf cancel --reason
cancelled --> brainstorm: reopen at cancellation.fromStage
cancelled --> [*]
state testing {
[*] --> execution
execution: executionId is required
}
state review {
[*] --> review_execution
review_execution: report must use current executionId
}
States and transitions
| State | What it means | Allowed transitions |
|---|---|---|
brainstorm |
Working the requirement out with the user | planning |
planning |
A feature settles its execution contract, a bug confirms its triage contract; then approval, which starts the item (kf approve) or defers it (kf approve --backlog) |
backlog, implementation |
backlog |
The contract is approved but work has not started | implementation, planning |
implementation |
The agent executes against the approved contract | testing, planning |
testing |
Read the stage record at HEAD, map each test case to its test, run the changed behaviour no test covers, measure line coverage per changed file, and record the outcome | review, implementation, planning |
review |
Review the code, the scope and the architecture, and write the report | done, implementation, planning |
done |
Closed and archived (folder .works/done/); a terminal state |
cancelled, via kf cancel |
cancelled |
Stopped for good; it sits off the linear track, so no artifact is ever demanded of it | Back to cancellation.fromStage only |
The CLI allows only the edges above. Never move a .works/ folder by hand.
The guards that matter
- The requirement or bug report must be filled in and marked confirmed before it leaves
brainstorm. - Leaving
planningneeds all four planning artifacts and the UC files for a feature; a bug needs only a filled bug report and a real human’s approval. The approval stores a fingerprint of the matching contract, so editing the contents afterwards makes it stale. - Every entry into
testingmints a newexecutionId. Bothphase-4-testing-result.mdandphase-5-review-report.mdmust carry the current one. FAILreturns toimplementation, where each failure is repaired on the task that owns it.BLOCKEDstops the flow.REQUIREMENT_BUGis a stop condition in review: never rewrite the requirement or move the state on your own.- Only a
PASSreview whose testing and review match the current execution reachesdone, and its report must carrycigreen or none and alandingvalue (review_ci_missing,review_ci_red,review_landing_missing,review_pr_missing). A feature also needsphase-6-feature-report.md; a bug does not. On top of that, closing is the delivery gate: a recordedprmust be verifiably merged — an item that passed review but has not landed waits inreview(“awaiting merge” in status/doctor) — anddelivered/deliveredAtis stamped on.kfw.jsonwhen finalization completes. - There is no
shippingstage. Review opens the PR (after the independentcode-reviewerpasses) and watches CI; the merge happens on GitHub and kf never merges. Merging closes the item on the nextkf status --change <feature>(or--all),kf syncorkf archive: gates, close-in-progress and dirty-worktree refusals, thedonehook, worktree teardown, branch deletion, the move, the metadata stamp — in that order, writing nothing underdocs/. The PR head must be the item branch; the local branch is deleted only when its tip equals the PR head, the origin branch is deleted with a lease, and a branch with commits past the merged head is kept and reported.landing: nonecloses when the branch has 0 commits ahead of origin’s default branch. An item an earlier release closed may still carry an unpusheddocs/<context>-<feature>branch of canonical docs (meta.docsBranch), which the close waits on or re-lands. A project from before this change is migrated once:.works/dones/*moves todone,.works/shipping/*returns toreview.kf stage <item> shipping|donesis refused with a hint, andkf shipis a deprecated alias that exits 1. - Entering
implementationwithworktree.enabled(opt-in, off by default) also provisions the item’s git worktree on branch<prefix>/<feature>plus its<feature>.<baseDomain>route — the transition fails closed when that cannot happen, and code work happens there rather than on the main checkout. The prefix resolvesworktree.branchPrefix→worktree.gitconfigName→git config user.name→kf/. Closing andkf canceltear the worktree and route down but keep the branch, refuse while it is dirty, and warn when its commits are not merged into HEAD. - The in-session loop moves an item only through the transitions above, with the same gates and hooks and never with
--forceor--skip-hooks. It stops once review has opened the PR: the merge, and with itdone, stays a human decision on GitHub. - The only way into
cancellediskf cancel, and--reasonis mandatory. The only way out is back to the stage it stopped in (cancellation.fromStage), at which pointcancellationandstatusare cleared from the metadata — except a reopen straight back intodone, which restoresstatus: archivedand writes no docs.kf archiverefuses a cancelled item.
The minimum metadata looks like this:
{
"schema": "kanban-flow",
"kind": "feature",
"feature": "payment-retry",
"context": "billing",
"created": "20260917_1405",
"approval": {
"status": "approved",
"by": "human",
"at": "20260917_1430",
"contractHash": "sha256:..."
},
"executionId": "uuid-of-the-current-testing-run"
}
The executionId is cleared on a return to planning and on every entry into implementation, then reissued when a new testing execution begins. The implementation reset is the one that matters in a FAIL loop: it is what stops the old testing evidence from counting for the repaired code. A bug report lives in phase-1-spec-requirement.md using the bug template; it needs none of the feature planning artifacts. If new behaviour appears beyond the scope of the fix, tell the user and let them decide before opening a separate feature.