kadence

Changelog

What shipped, release by release, with the reasoning that came with it.

[0.10.1] — 2026-10-12

A patch by the owner's rule for what follows 0.10.0: the instructions name --rejected, task add takes --ac, and option values that read as a number stay text. Nothing in the event format changes; --json gains only additively.

Added

  • task add "…" --ac "…", repeatable: acceptance criteria on the command that creates the task, after the board's definition of done, each printed with the number ac check takes. Reached for by an agent checking 0.10.0, and refused; criteria went on one command at a time.

Fixed

  • An option value that reads as a number, or is empty, is kept as the text typed. The parser turned --title 2026 into a number and "" into 0: task edit --title 2026 wrote {"title":2026} into the journal and reported success, task add -d 42 stored a numeric description, and decision add --why "", --rejected "" and task ac waive --why "" crashed with a TypeError. Empty ones are now usage errors (exit 2).

Changed

  • The section kadence init writes into AGENTS.md and CLAUDE.md, prime's hint on a journal with no decision, search's footer and init's own output name --rejected beside decision add, with what it is for. In KAD-75 nine of ten agents recorded a decision from that section alone and none used --rejected; seven wrote the lost option into --why, where the prompt hook's rejected-before cannot read it (DEC-61). Refresh an existing repository's section with kadence init.

[0.10.0] — 2026-10-12

What the journal kept noticing about itself, fixed — and a way to see what kadence costs an agent. Every item here was written down as a friction note while using kadence on kadence.

⚠️ Changed — task claim starts the work

WasIsTo get the old answer
a claimed task stayed in its columna claim that holds moves a task before the started column into it (backlog → in_progress); the message says so and --json carries movedmove it back: kadence task move KAD-1 backlog — the claim stays
a contested task left ready once starteda contested task stays in ready whatever its column: it needs a person—

The board showed taken work as not started, and the release task that claimed KAD-81 moved backlog → done (DEC-58).

Added

  • task claim shows what done means. The open acceptance criteria, in the message and as criteria in --json — the definition of done reached an agent only at done, as a warning, unless it also ran task show.
  • task ac waive KAD-1 2 --why "…" — a criterion that does not apply to this task, with the reason. It shows as [-], counts as settled, and stops the warning at done; checking or unchecking it takes the waiver back. A board-wide definition of done put "npm test green" on interviews; 10 of 67 done tasks in kadence's journal closed with criteria that never applied (DEC-59). A new event type, task.criterion_waived: an older kadence skips it and says it came from a newer version. The cache rebuilds once.
  • kadence hook log — what prime and the prompt hook put into the agent's context in the latest session, with the records named and the size in estimated tokens. Kept in the system temp directory, never in git; prompts to 80 characters (KAD-76, DEC-60).
  • task list --milestone MS-2 (or the milestone's name).

Fixed

  • Text that starts with dashes — a criterion "--history returns the full shape", a note "--fields is cheaper" — was refused as an unknown option. A token with a space before any = is text, and -- ends the options; a real unknown flag now says how to pass it as text.
  • The message for an unknown task action named 13 actions while allowed in the same response named 17; the text now comes from the same list.
  • The guided init's Undo line wrapped mid-word in a narrow terminal; it now wraps at a word, under its text.
  • perf.test.ts measured cold loadState and loadOrBuild one after the other, so load on a shared runner failed it with nothing changed; the two are now taken in turns.
  • The prompt hook's "rejected before" raised the alarm on "write a test for the prompt hook" because a decision had rejected "a flag on hook prompt". A word the chosen side shares is the topic; only the alternative's own words now count towards naming it.

[0.9.1] — 2026-10-12

A patch: one fix to the guided init, nothing in the contract or the event format.

Fixed

  • The guided kadence init recommends Claude Code's hooks whether or not .claude/ is already there, as plain init always has (DEC-35). In a repository that showed only Codex or Cursor, Recommended meant those alone and wrote no Claude Code hooks. What was found is still said as found ((detected)); the recommended setup adds Claude Code to it. Found re-taking the site's screenshot of init.

[0.9.0] — 2026-10-12

A minor by DOC-64's rule: the default answer of board --json changed (see ⚠️ Upgrading below). Everything since 0.8.2 is in it.

Coda sets kadence up with you — and the hook catches a reopened decision from the first day.

Added — a guided kadence init for a person at a terminal

Recorded first as a prototype, then built (KAD-89):

  • What it found, then one question. The agents in use, an ADR folder, the repository — then "How do you want to set it up?" with Recommended, Customize (agents, columns, a definition of done that also fits work that is not code) and Not now. Nothing is written before the answer.
  • The decisions you already have. ADRs in docs/adr, docs/decisions or adr (MADR and Nygard) are offered with a preview — ◼ to keep, space to leave one out — and recorded with a link to their file. Without ADRs, Coda asks for one decision by hand.
  • What a teammate's agent will see. The real prompt hook, run on what was just written, for should we switch to <what you ruled out>?.
  • A line for a teammate, the files to commit, how to undo. kadence still never commits.
  • Run it again for a checkup: what is in place, the ADRs not in the journal yet, whether a second person has written to it.
  • Ctrl-C at any question keeps what is written and says so. clack-style prompts (○ ●, ◻ ◼, one rail), hand-rolled with no dependency, in their own chunk: no other command loads them, and CI checks that.

Changed — kadence init never asks an agent, and tells it what next

  • No question when stdin or stdout is a pipe, with any flag, in CI, or where AI_AGENT, CLAUDECODE, CURSOR_AGENT, CODEX_SANDBOX, GEMINI_CLI, OPENCODE_CLIENT or KADENCE_SOURCE=agent is set. KADENCE_INTERACTIVE=0 turns questions off; =1 allows them past an agent marker, never past a pipe (KAD-90).
  • The plain output ends with "Next, if an agent is reading this:" — the ADR folder and the command to record each one, one decision to ask the human for, and that a person can run the guided setup.
  • Coda now greets only a person: an agent whose output is a terminal no longer gets the drawing (KAD-80's rule, which a TTY alone did not hold).

Fixed — rejected-before, on a journal one day old and on long ones

The prompt hook names a decision when a prompt proposes what it turned down (KAD-61). It was silent on a fresh journal — exactly where onboarding shows it — and on this repository's journal had drifted, unnoticed, to 4 of 10 named and 3 false alarms. It now reads only the alternative's name (not the reason it lost, which ordinary work shares), puts a title's "not X" on X's side, needs the prompt to name an alternative rather than share one word with it, and weighs the choice by its title: 9 of 10, no false alarm, and a one-decision journal answers. The set is a test now (KAD-88, DEC-56).

⚠️ Upgrading — board --json returns the state, not the history

Nothing was renamed or removed from kadence schema --json, so kadence/v1 stands. What board --json returns by default changed:

WasIsTo get the old answer
every task in full, history and comments includedthe state of each task: the fields --summary selected--history

The response grew with how long the work took, not with how much of it there was: 17 KB at 30 events and 82 KB at 300 on the same thirty tasks, about 240 bytes for every event. Agents are who call board --json, and one that did not know --summary paid for the whole history on every call (G5, DEC-55, KAD-84). --summary still works and now answers the same as no flag, so instructions that say board --json --summary read the same on old and new versions.

Added — kadence doctor

Rebuild, verify, report (G5, KAD-85). It folds the whole journal whatever the cache says, writes the result to .kadence/state.json, and reports three things: how many events read and which files did not; whether the cache it found had agreed with the journal; and what the fold surfaces — contested claims, cycles, columns not in the board, documents edited on two branches.

  • The cache check is the part a normal read cannot do. The fingerprint is names, count and bytes, and an edit that keeps all three passes it; doctor compares the state itself and says when a cache looked current and was not.
  • Never touches an event. The one file it writes is the cache, which I6 says changes nothing.
  • Exit 1 with the new error code journal_damaged when a file cannot be read, and the files named in events.damaged. Surfaced conflicts are not errors: they are the journal keeping both sides, and a doctor that failed on them would fail on every team that merges.
  • 31 ms on 10,000 events with a compacted archive, in the perf suite against the same 200 ms budget as a cold start.

Tested

  • Every command and action the binary accepts is named in schema --json. The suite proved only the other direction — that what the schema names exists — so an action added without a contract entry passed it (G3, KAD-82).
  • The install smoke test runs the packed tarball without installing it, the way a person first tries a tool: npx, pnpm dlx and bunx, on macOS, Linux and Windows. A global kadence on PATH is kept out of those runs, because npx answers with it when the package brings no bin (G4, KAD-83).
  • schema --json only ever gains, now under test. Each release fixture keeps the contract that version published, captured from npm by make-fixtures.mjs (--schema-only backfills without touching a committed journal); a command, flag, error code or field missing today fails against every release that had it. All nine since 0.2.2 hold (G3, KAD-86).
  • Compaction runs on a real journal: a copy of this repository's own, 1,400+ events over two months, every month archived, then held to the same folded state, the same board --json --history and a clean doctor (G5, KAD-87).

[0.8.2] — 2026-10-11

Coda says hello, once, to the person who ran init.

Added

  • kadence init greets a person at a terminal with Coda, the character the site carries: three rows of half blocks in one colour beside kadence vX, the message's first line and "Coda keeps what you decide, and why." Only on a fresh setup and only when stdout is a TTY; colour only when colours are enabled, NO_COLOR honoured (KAD-80).

Nothing an agent reads changes: in a pipe, under --json, in prime and in the hooks, init answers byte for byte as in 0.8.1 — a test runs it through a real pipe to hold that. No kadence/v1 field and no event shape changes.

[0.8.1] — 2026-10-09

A journal outlives the version that wrote it, and now that is tested.

A patch number on a minor's worth of change

By DOC-64's own rule this is a 0.9.0: two commands answer differently with the same input. It is 0.8.1 by the owner's call (DEC-53, KAD-72). Nothing below renames or removes a field in kadence/v1 or changes the shape of an event; every --json change is an added field.

Unchanged command0.8.00.8.1The old answer
kadence init in a repository with .codex/, .cursor/, .github/hooks/ or .github/copilot-instructions.mdwrote hooks for Claude Code onlyalso writes .codex/hooks.json, .cursor/hooks.json or .github/hooks/kadence.json, merged into any file already there--no-hooks (writes no hooks at all)
kadence search, the prompt hook, task list --searchmatched words as written; a record needed more of the question to ranktasks finds task, blocked finds block; a record ranks from a tenth of the question, answers from 0.35none

Added

  • Compatibility suite: real journals written by every published minor version, 0.1.5 to 0.8.0 (scripts/make-fixtures.mjs installs each from npm and drives one scenario), folded by the current code and held to what each version itself reported. A change that reads an old journal differently fails CI.
  • A written compatibility and deprecation policy (DOC-65 in the journal): additive-only inside kadence/v1, migration by new events and never by rewriting, a deprecation period of two minor releases and 90 days.
  • CI installs the packed tarball with npm, pnpm, yarn, bun and bunx --bun on Linux, macOS and Windows, and drives the installed binary.
  • The prompt hook leads with a decision that already rejected what the prompt proposes — "DEC-43 rejected this before: …". On this repository's journal it named the right decision for 8 of 10 such prompts, with no false alarm on ten ordinary code prompts.
  • kadence report flow --by-status — where the time went: for every column, p50 and p85 of the visits that ended in the window, marked before or past the started boundary, and flow efficiency — the share of start-to-done not spent blocked. Text, --json (under byStatus), --html.
  • kadence report burnup — scope and done, day by day, so a missed goal and a goal that grew do not look alike. Text, --json, --html; --sprint names one. Scope goes down only through cancellation or a move to another sprint, and the report says so: the journal has no event for removing a task from a sprint.
  • kadence doc edit DOC-1 with no text opens $EDITOR at a terminal, starting from the current revision; saving it unchanged or empty writes nothing. Markdown headings survive — the editor helper used to strip every line starting with #, as git does, which would have removed them. Without a terminal (an agent, a pipe) it says how to pass the text instead.
  • kadence note show <ULID> — one note, whole, by the id that search and the prompt hook hand back (~180 tokens). A note has no label, so KAD-N is refused with note_not_found, a new error code. The hook's last line names it.
  • kadence doc show DOC-31:163-172 opens just that section — the reference search and the prompt hook already print. On the hook's references for the golden questions: ~340 tokens instead of ~2,300 (median ratio 0.17). In --json the lines are in body and section says where they sit; bytes is still the whole document.
  • A document older than the work it describes is said so: when a task linked to it reaches done a day or more after its last revision, prime lists it (at most three, under "Documentation older than the work it describes") and task show marks it, with stale on each documentation entry in --json. A new revision clears it. Within a day it stays quiet — on this repository's journal the literal rule flagged two documents, both written minutes before their task closed (DEC-49).
  • Search folds plurals, -ed and -ing onto the word they came from, on both sides, and leaves identifiers (KAD-42, state.json, blocked_by) as written. A record is ranked when it holds a tenth of the question; the search answers only when its best record holds 0.35, so nonsense still gets nothing. On 50 held-out questions: top three 33 to 38, top five 35 to 40, first place 27 to 26 (DEC-50).
  • The relevance benchmark runs on a frozen corpus — the journal as of a pinned event — so a note quoting the golden questions can no longer move the number; the live journal is still searched as a drift alarm. Fifty held-out questions with their target records and three blind phrasings each are a fixture, reported on every run and never a floor.
  • kadence search "q" --also "p1" --also "p2" — the same question in other words, searched one by one and fused by reciprocal rank into one answer, each passage once. On 50 held-out questions with three blind phrasings: first place 26 to 30, top three 38 to 40, none left unanswered. --json gains also, always an array. The instruction section and the prompt hook's last line name it (DEC-51).
  • An agent skill package: agents/kadence/SKILL.md (60 lines), and from it a Claude Code plugin and a Cursor rule, written by node scripts/agent-package.mjs; --check fails when either has drifted from the source. A test holds every command the skill names to the ones schema --json publishes. Not in the npm package.
  • Hooks for Codex, Cursor and Copilot (DEC-48, DEC-52). Codex gets the same two as Claude Code in .codex/hooks.json; Cursor (.cursor/hooks.json) and Copilot (.github/hooks/kadence.json) get a session-start hook, through the new kadence hook cursor-session / copilot-session, which print prime as the JSON each reads — neither takes plain stdout. init writes them where the agent's directory is already there, or for those named with --hooks-for; existing files are merged, never overwritten.
  • A test that runs agents side by side: eight processes in one working tree, three worktrees merged at once, two claims on one task. Nothing is lost; the double claim is reported.

[0.8.0] — 2026-09-30

The journal is searched before the agent starts guessing. Agents told to use kadence search did not: in natural runs on this repository 1 in 10 reached for it, and 0 in 10 after the instruction files said "before grep". Every one opened with grep over the whole tree. So kadence init now installs a UserPromptSubmit hook that searches the journal for each prompt and puts up to three records next to it — by default, next to the prime hook.

⚠️ init now writes .claude/settings.json

It used to only with --hooks. It now installs both hooks unless you pass --no-hooks, creates the file when there is none, keeps every hook that was already there, says what it added, and never rewrites a file it cannot parse. --hooks is still accepted.

Measured on the same ten questions, same prompt, Sonnet agents:

without the hookwith it
Used the journal1 / 109 / 10 (6 as the first step)
Right answer9 / 1010 / 10
Context added, median~10.9k tokens~7.9k tokens
Time, median44 s25 s
Answered from files a clone does not have10

One repository, ten questions, one model. The hook's output was placed in the prompt by hand, because subagents do not fire hooks, so this measures what the records do once they arrive, not the hook firing in a live session. A signal, not a benchmark.

Added

  • kadence hook prompt — run by the hook, not by hand. Reads the UserPromptSubmit payload on stdin and prints up to three records (1 KB at most), or nothing. A passage is quoted only when its record holds the whole question; a note is given whole, since there is no command to open one. Always exits 0: exit 2 from this hook erases the prompt.
  • A record is shown once per session: what a session has seen is kept in the system temp directory, keyed by session and repository — never in .kadence/. The lines are offered on a condition ("if this is about why or how something was decided"), not as an order. On ten ordinary code tasks the hook added ~180 tokens and sent no agent into the journal; same answers, same time.
  • init installs it next to the SessionStart hook. The command is silent where kadence is missing and swallows an older kadence's exit 2, so a teammate who has not upgraded does not lose every prompt they type.

Changed

  • task list --search runs on the search engine. It was a substring over title, description and comments — a second idea of what "matches" means. Now it matches words the way kadence search does, reads acceptance criteria too, returns every matching task rather than the top five, and lists the most relevant first unless --sort is given. A fragment no longer matches: --search coo does not find cookie. The flag and the --json fields are unchanged. The TUI / filter stays a substring, since it filters as a person types.
  • The instruction section and .kadence/README.md list the ways to look something up cheapest first, with what each costs in tokens — from the prompt hook's ~90 to board --json's ~51,000, with grep at ~11,000.
  • prime names kadence search first under "Go deeper", with when to use it.
  • The instruction section in AGENTS.md / CLAUDE.md and .kadence/README.md say when to search — before grep or a list. On their own these changed nothing measurable; they stay because they cost one line.

Fixed

  • search missed the last word of every sentence. A full stop stayed inside the token, so "release steps." was indexed as steps. and a search for "steps" never found it. Golden-set recall@3 0.64 to 0.68.

[0.7.0] — 2026-09-25

One question over everything written down — and three fixes that had to come first. kadence search answers "what do we know about X" across tasks, decisions, notes and documents at once. Getting there meant admitting that the commands an agent already calls were returning answers it could not use: one that could be forged by a note, one that read the whole journal twice on every call, and one that returned four thousand tasks into a window that truncates at twenty-five thousand tokens.

⚠️ Upgrading — three defaults changed

Nothing was renamed or removed from kadence schema --json, so kadence/v1 stands. What a caller gets by default changed in three places, and a script that assumed otherwise needs one flag:

WasIsTo get the old answer
task list --json carried historyit does not--fields id,label,history
list commands returned everythingone page of 100--limit 0
prime --json carried full note textcapped at 2000 characterskadence note list --json

history was never promised there — the schema has always placed it in task show — but it was emitted, and Hyrum's Law says somebody depended on it. It is 61% of that response at 4000 tasks, which put the command past the point where an agent's tool output is silently cut in half.

  • One query, every kind of record. Tasks with their descriptions, acceptance criteria and comments; decisions with their reason and the alternative they rejected; notes; documents cut at their headings. Most of that was unreachable by any command before this: task list --search covered 24% of the journal's prose and only tasks.
  • Lexical, not semantic, and measured rather than assumed. BM25, hand-rolled, no dependency, over the folded projection — so labels exist (I7), a superseded decision does not answer as though it were current, and search cannot disagree with task show. An embedding model is 200 MB and seconds of cold start against a 200 ms budget and a promise to work offline; on a vocabulary of ULID, KAD-N and FlowEvent it is also the weaker method. 26 ms a query over 829 units; 158 ms end to end (DEC-26).
  • A hit is something you can cite. The ULID, never the derived label; the lines a document section came from; and a quoted span whose offsets land on the quoted text, so an agent quotes the journal instead of paraphrasing it.
  • coverage — how much of the question a record actually holds, 0 to 1, weighted by how rare each word is. Where the first hit is right this is 1.00 at the median; where it is wrong, 0.62. Below 0.7 the human output says the passage answers part of the question. The obvious signal — the gap to the second hit — was measured and does not work: 0.10 against 0.08 (DEC-30).
  • An empty answer, said plainly. Below the coverage floor the result is nothing, and the message says the journal has no answer. A model handed passages that merely look relevant fabricates more than one handed none, so this is a feature and not a cop-out. Nine of ten questions about things this journal has never discussed get silence.
  • Quality is held by a test, not by hope. test/search-relevance.test.ts runs 25 real questions against the repository's own journal and fails below recall@3 of 0.64. Three ranking ideas were measured this release and two were removed for making it worse (DEC-27, DEC-28).

Fixed — prime could be forged, and repainted your terminal

  • A note carrying newlines rendered as sections of its own, and the cheapest section to forge is the Go deeper: block prime ends with. The length was capped; the shape was not. An escape sequence went further and cleared the screen — prime runs from the SessionStart hook without being asked.
  • prime --json was unbounded. One 200 KB note made the session preamble 200,728 bytes. Free text is now capped at 2000 characters with bytes alongside, the signal documentation already gave (DEC-21).

Fixed — a warm command read the whole journal anyway

  • loadState called readAll on every command, purely to count corrupted and unknown events for a warning, after the snapshot had already answered. A warm call read all 10,103 files; a cold one read every event twice. 345 ms to 201 ms end to end at 10k events; 204 ms to 5 ms in process.
  • The fingerprint counts bytes as well as names, so a file rewritten in place invalidates the cache. That also means git checkout .kadence/ — the remedy the corruption warning itself recommends — finally invalidates the cache it needed to, which it never did (DEC-22).
  • Every performance guardrail measured loadOrBuild; the CLI calls loadState. The bug lived in the gap between them. Both are measured now.

Fixed — list commands returned the whole board

  • One page of 100, with the real total and the offset beside it. task list --json was 1.3M tokens at 4000 tasks against the 25k at which an agent's tool output is cut — silently, so the agent answers on a fraction while believing it saw everything. Now 50 KB. --limit 0 returns everything; --offset walks (DEC-24).
  • 100 rather than 200 because this repository's own tasks run 1100 bytes each, where the ceiling is 93.

Changed

  • Documentation moved into the journal: 65 documents, DOC-2 through DOC-66, as doc.written events. docs/ stays on disk as the working draft and links are rewritten to DOC-N on publication (DEC-25). The npm package is unaffected — .kadence/ is not in files.

[0.6.0] — 2026-09-23

Documentation in the journal, and an honest answer when something goes wrong. Two halves of the same session. Documentation stops being a filename someone has to know and becomes an entity an agent is handed — DOC-N, revised, conflict-aware. And the three places where kadence told an agent something that was not true — a claim it had already lost, a failure with no JSON, a flag written twice — now answer straight. The second half came out of a contract stress audit run against 0.5.0; the report is in docs/research/contract-stress-audit-2026-09.md.

⚠️ Upgrading — everyone updates before anyone runs compact

compact in 0.5.0 and earlier archives the events it can parse and then removes the whole month directory, which deletes doc.* events written by 0.6.0. An older kadence reads the journal fine; it just must not compact it. From this release on, compact leaves alone any month holding an event it cannot read. If someone compacted with an old binary, git status shows the deleted event files — restore them, do not re-run.

Added — documentation is an entity

  • kadence doc — add, edit, show, list, link. A document is DOC-N, stored as doc.written revisions in the journal rather than as an .md file beside it (ADR-014, superseding that part of ADR-010). Text comes from --body, --file or --stdin; --file reads any path and keeps nothing (DEC-15).
  • A revision names its parents. Two people revising the same version leave a conflict that is reported and settled by writing text — never a silent loss. doc edit with no new text on a conflicted document is refused with conflicting_state, because otherwise it would keep whichever version ULID order happened to show and nobody would have read the other.
  • Agents reach documentation without a search term. task show --json carries documentation[] (label, title, bytes, updatedAt), prime names the documentation of the work you hold, and doc list --json carries no bodies. One body arrives only when you ask for it with doc show.
  • A body past 16 KiB is written with a warning, not refused. Agents read long inputs measurably worse; splitting is the author's call, not ours.
  • KADENCE_ACTOR names the claimant (DEC-16). Without it, an agent in a linked worktree claims as email#<worktree>. A person owns what their agents hold; an agent owns only its own.
  • task claim reports what actually happened — claim: claimed | already_yours | contested, read back out of the journal after the write. Additive to kadence/v1.

Changed

  • prime puts contested claims first in Yours, each carrying claimedBy and contestedBy, so an agent holding a task it no longer owns sees that before it starts working. When every claim is the person's own agents, it says so instead: N of your agents collided — keep one, release the rest.
  • task doc stays for linking files that already exist; new writing goes to kadence doc.
  • A repeated single-value flag keeps its last value (DEC-18). Only --label, --add-label, --remove-label, --rejected and --doc accumulate. --title a --title b had been storing both and reporting success.

Fixed — the contract, where it was quiet

  • A parser failure answers in JSON. An unknown flag, a missing value or an extra argument is thrown by cac before any handler runs, and it used to reach stderr as a bare sentence with nothing on stdout — the one failure --json did not cover. It now goes through failure(): invalid_argument with received and allowed, exit 2.
  • An unknown command no longer exits 0 with both streams empty, and a bare kadence prints help instead of nothing. An exception that is not a cac error prints its stack and exits 1, rather than being mislabelled as usage.
  • compact no longer deletes events it cannot parse along with their month directory — see Upgrading.
  • task doc recorded a URL as the mangled path https:/… and task doc add created a directory named https: (KAD-33).
  • --body - was read as a missing value and --body 007 became 7. --stdin is its own flag now, and text flags are read raw from argv. Two text sources at once are refused rather than one winning silently.
  • doc link returned a partial document; it returns the full shape, pinned in the schema contract.

Measured

  • claim re-reads the journal after writing: ~121 ms median at 400 events, against ~105 ms for a plain read. An agent without KADENCE_ACTOR pays one git spawn (~10 ms) in ready, prime and claim to detect a linked worktree.
  • ready and prime now sit at the edge of the 200 ms budget as this repository's own journal grows. Recorded as a note, not yet as a fix.

[0.5.0] — 2026-09-16

The second author. kadence was built for a team, and until now its first run spoke to one person with a sprint. This release is the path the second person walks: their agent learns to write the why, a machine without kadence gets an install hint instead of an error, and the first screens talk about the work and its decisions rather than points. Reports are still here; they are just no longer the first thing anyone meets.

Added — the teammate path

  • The section init writes into AGENTS.md and CLAUDE.md teaches writes, not only reads. task claim, note "…" --task, and decision add "…" --why sit beside the read commands, plus one line for an agent on a machine where kadence is not on PATH: ask the human to install it; npx needs the network and that is their call. Still 23 lines and 1.2 KB, under a test of 25 lines and 2 KB; an older section is replaced in place by its version marker.
  • The SessionStart hook survives a machine without kadence. It runs kadence prime when the binary is there and otherwise prints one line asking the human to install it, exiting 0 (DEC-12: no npx, because a hook written by init must not download code on a teammate's machine). init --hooks upgrades the old kadence prime hook in place instead of adding a second.
  • .kadence/README.md leads to decisions and teammates. decision list and decision add replace sprint status among the top commands, and a new "Adding a teammate" section covers install, one user.email per person and KADENCE_SOURCE=agent. Existing READMEs are still never overwritten.
  • README: the first ten minutes, "Adding a teammate" and "Removing kadence" — the last one lists everything init touches, so leaving is a table, not a guess. Sprints, reports and exports moved to docs/reports.md.

Changed — the first run speaks about the work

  • init suggests a first task and a first decision, names what to commit so a teammate's agent finds it, and mentions --hooks when .claude/ exists.
  • prime no longer opens with "No active sprint." when there is none; with no decision in force it shows one line on how to record one. --json unchanged.
  • task add without --estimate says nothing outside a sprint; inside one it names the sprint instead of velocity. sprint add says the same.
  • kadence --help lists commands in the order a session meets them: init, prime, ready, task, decision, note, board, ui, schema, then planning, reports and maintenance.
  • The TUI header without a sprint reads N open · M ready · K decisions in force; points appear only when something is estimated.
  • report --list puts attention first and groups burndown, velocity and workload under "Sprint and team".

Fixed — the contract

  • schema --json lists every shipped command. 21 entries were missing, among them sprint add/edit/start/list/burndown, task parent/unblock/cancel/delete, template, completion and ui — additive within kadence/v1. A test now compares the binary's own commands and actions against the contract and fails on any gap. Unknown decision actions now return allowed, and task lists doc in it.

Tooling

  • scripts/north-star.mjs counts repositories with two authors fourteen days after their first event — --local for a checkout, --search through gh for public ones — and appends a weekly row to docs/research/north-star-log.md. A maintainer script: the product itself still opens no socket. First row: 0, with the reason GitHub code search has not indexed even this repository.

The command reference stops being prose. And the first two bugs found by using the product on itself.

Added

  • kadence report is the whole catalogue. report burndown, report velocity and report workload join flow, cfd and attention, and report --list names all six with what each one answers.

    The folds were already here — the burndown under sprint, the workload inside stats — so an agent reading schema --json could not find them and kadence report was not the catalogue it looked like. report burndown calls the same function sprint burndown does, resolved in one place, so the two cannot drift. report velocity is new arithmetic over sprintReport, and it answers with a range rather than an average (DEC-9): the spread between sprints is the forecast, and a series shorter than four sprints says so. report workload counts open tasks, points, work in progress and blocked work per owner, with a row for unassigned work — no hours, no capacity, since the journal has neither.

    --since on a report that has no window, or --sprint on anything but the burndown, is now an error rather than a silently ignored flag (DEC-10).

  • kadence report <name> --html — a report as one self-contained page, with charts. flow gets a percentile dot plot, a created-vs-finished column chart and an aging-work chart with the p85 cycle time drawn as a reference line; cfd gets a stacked area chart with the bands named where they are wide enough; attention gets the idle days against the threshold. --file puts it where you want it, and with --json the response is the path, not the report.

    The charts are hand-written inline SVG. Every chart library is a script tag, and the page's one promise is the one the board export already makes: it opens from disk and asks the network for nothing — asserted structurally, down to url( in the stylesheet and the xmlns an inline <svg> does not need. Measured at 0.1 ms and 14 KB for the flow page, 1.7 ms and 301 KB for a two-year cumulative flow diagram. Every chart is followed by the rows it was drawn from: three of the eight categorical colours sit below 3:1 against white, and the rule that buys them is that colour is never the only channel.

    Reasoning in DEC-7 (a page per report rather than a Reports section inside the board export — a report is a question with a window, and the board export has none) and DEC-8 (the charts).

  • kadence schema --json and every --help now have a machine-readable sibling. npm run reference writes dist/reference.json: every command, its usage line, its flags with their descriptions, its examples, and the whole agent contract, read out of the binary rather than written about it. The release workflow attaches it to the GitHub release of each tag.

    This exists because the hand-typed CLI page on the site described 0.3 for the whole of 0.4 — nine commands and two dozen flags shipped without it noticing, and the agents page named ten of the fifteen error codes. A paragraph has no test. A test now reads src/cli/commands/ and fails when a command file has no help behind it, which is the only way that gap stays closed.

    The generator ships in the package rather than its output: measured at 1.9 KB packed against 6.2 KB, and 4.9 KB unpacked against 39.7 KB, for the same result. Nothing in the data is unavailable from the CLI itself.

  • scripts/kadence.mjs — runs the build in this working tree, rebuilding when src/ is newer. For working on kadence with kadence, which is now how this repository is run; CLAUDE.md says what that means in practice.

Fixed

  • burndown.finalRemaining was always null. Both branches of sprint.status === 'closed' ? null : null read the same, so the one number the field exists for — what a closed sprint did not finish — was never in the response, including in sprint burndown --json. It now carries the points left on the sprint's last day.

  • A repeated flag crashed decision add. --rejected A --rejected B threw o.rejected.trim is not a function: cac hands a single flag back as a string and a repeat as an array, and only --doc was normalised for it. The four single-value flags went into .trim() as arrays.

    Repeats now mean what each flag means. A second --why, --task or --supersedes is a correction, so the last wins; a second --rejected is a second alternative that was turned down — which is what a decision record is for — so both are kept, joined rather than stored as an array because rejected is a string in kadence/v1 and the contract only ever gains fields.

    Found by recording a real decision about this repository with two rejected options, with 898 tests green. The tests for it go through the built binary, because calling the command directly is exactly the path that could not see it.

Notes

Deleting several tasks by KAD-N in one loop removes the wrong ones. Labels are derived while folding (I7), so removing KAD-1 renumbers everything after it and the next label in the list now belongs to a different task. Working as designed, and a sharp edge: delete by ULID, or one at a time. Recorded as a note in this repository's own journal rather than fixed, because the fix is not obvious — warning on a bulk delete of labels would fire on the common case too.

A board-wide Definition of Done is engineering-shaped. board config --dod copies its criteria into every new task, so "typecheck clean" landed on the Probe B interview tasks, where it means nothing, and there is no task ac remove to take it off. Noted, not yet answered.

[0.4.1] — 2026-09-13

0.4.0 was tagged and never published: these three were found by using it before it reached anyone, so 0.4.1 is the first published 0.4. Nothing below changes the shape of an event or renames a field in kadence/v1.

Fixed

  • The fold dropped who wrote a comment or a history line. Every event carries source — "human" or "agent", from KADENCE_SOURCE — and it is validated on read, but the projection kept it only on decisions and notes. So task show printed an agent's comment exactly like a person's, and task show --json gave an agent no way to tell them apart: actor is the git identity, which a person and their agent share. The one surface the product's headline is about could not show the thing it claims. Comments and history entries now carry source, task show marks an agent's line [agent] the way decision list already did, and a note shows the mark it always knew. The snapshot shape test now records nested records too — it is what should have failed when source reached the event and not the comment. Cache version bumped to kadence-snapshot/11.
  • kadence ready offered work that had already started. The filter knew about finished, blocked-by and claimed-by-others, and nothing about the board: a task sitting in in_review, or parked in the blocked column, was listed first with kadence task claim KAD-1 printed under it. An agent following prime, ready, claim took work somebody was reviewing. Ready now means ready to start — anything in the started column or past it is left out, using the board's own boundary, so a team that renames its columns keeps a ready that means what its board means. When that is why the list is empty, the message says so: Nothing ready (2 already started).
  • The performance fixture moved all 500 tasks into in_progress, so the ready guardrail was timing a filter over an empty answer and asserting only that it was fast. Every third move parks a task back in todo now; the guardrail times a real result.

[0.4.0] — 2026-09-13

Planned as slices 0.4.0 through 0.4.4 and published as one minor release.

The agent loop: four commands that answer "what next" without folding the whole board by hand. Everything here is additive within kadence/v1 — no field or command was renamed, and --json responses gain keys rather than losing them.

Added

  • kadence ready — open tasks with no live blockers, not claimed by anyone else, priority first and age second. --json returns five fields per task rather than the whole record, because this goes into an agent's context on every session. An empty result names the reason: how many are blocked, how many belong to someone else. Measured at 9 ms over 10,000 events.
  • kadence prime — the session preamble: active sprint and days left, your claimed and in-progress work, how many tasks are ready, decisions in force by title, the last five notes, and four commands to go deeper. Held to 40 lines and 3 KB by a test; 16 lines and 493 bytes on this repository. It carries the part that changes and points at the part that does not, because the static half already lives in AGENTS.md.
  • kadence init --hooks — adds a SessionStart hook running kadence prime to .claude/settings.json. Only with the flag: that file belongs to the user and is committed to their repository. The write is an upsert — hooks of other events, other matchers and other commands survive, running it twice leaves one hook, and a file that does not parse is reported rather than replaced.
  • kadence task claim / task release — take a task, or give it back. claim with no argument takes the top of ready, which is one step instead of two. There is no lock, and the message says so: two machines can each claim before either pushes. See ADR-011 (kadence doc show DOC-14).
  • kadence note "text" [--task KAD-1] and note list — something learned that was never a choice. Deliberately not a decision: no --why, no supersedes, no number. The help says when to reach for decision add instead. Notes show up in task show and in prime.
  • claimedBy and contestedBy on the task record in --json, and a Claimed: line in task show.
  • TUI: R shows only what can be started now, C claims the selected task or releases it if it is yours, and a card carries a claim mark — a separate colour when the claim is contested. Both actions call the same functions the CLI does.

Changed

  • A contested claim is reported, not rejected. When two people claim one task, the earliest by ULID holds and the later one is kept, with the task reading contested under both names. Rejecting the second would make the owner depend on which branch merged first — the same invariant that keeps dependency cycles and orphaned status columns alive rather than dropped. It is the first conflict in kadence that names people rather than values.
  • The state cache version moved from 2 to 4. The projected shape changed twice: three claim fields on a task, then notes on the project state. Missing exactly this bump is what shipped as a bug in 0.3.1, so it is now pinned to the field lists by a test.
  • The agent section written into AGENTS.md and CLAUDE.md grew by one line — kadence prime first — and by no more than that.

Added — the branch, the human, and the evidence behind done (0.4.1)

  • kadence task list --branch — the work this branch introduced, measured against --base (main, or init.defaultBranch). Membership is derived from git at read time and never stored: writing a branch name into an event would go stale the moment the branch is renamed or merged, and would make the same events fold differently depending on where they were written. A detached HEAD and an unknown base both fail by name rather than reporting no work. The measurement that justified the flag, including what would make it wrong, is in DOC-40 (kadence doc show DOC-40).
  • Acceptance criteria — task ac add|check|uncheck|list. The number is a position in the folded list, assigned like KAD-N and never stored, so two branches can each add a criterion and merge without renumbering. Moving a task to done with unchecked criteria warns and proceeds: the checklist is evidence, not a gate, and refusing would make the board lie about where the work is.
  • Definition of done — board config --dod "tests green,docs updated". The criteria are copied into each new task, never referenced, so raising the standard cannot reach back and change what finished work had promised. task add --no-dod skips them.
  • Milestones — milestone create|add|list|close. Grouping by outcome, where an epic groups by structure and a sprint by time. Not a third hierarchy: a task carries at most one, as a field beside sprint. MS-N is derived from ULID order like every other label, and progress is counted in points, the same unit as velocity and burndown.
  • kadence stats — counts by status and assignee, open blockers, contested claims, and the velocity of the last three closed sprints.
  • kadence completion install — zsh, bash and fish, generated from one list rather than three hand-written scripts. Without a terminal it prints the script instead of writing, because that is how people pipe it into a file. For zsh it writes the file and prints the fpath line rather than editing a shell rc, which is the user's file.
  • board --json --summary — the column state without history and comments. Those are the two fields that grow with how long a task has been worked on rather than with how many tasks there are.
  • TUI: b shows only what the branch introduced, M filters to one milestone, and the card dialog carries the acceptance-criteria checklist with space to toggle. Every toggle calls the same command the CLI does.

Changed

  • task list --json and board --json gain a milestone field on each task, carrying the MS-N label. The response without --summary is otherwise unchanged; the contract only ever gains.
  • The state cache version moved again with the projected shape.

Added — reports, and the two things they needed first (0.4.3)

What the neighbours call reports is, here, a fold over timestamps the journal already carries. The reasoning, the sources and the verdict per report are in DOC-60 (kadence doc show DOC-60). Velocity and cycle time stay out of the headline; they are a consequence, served to the person who asks.

  • kadence report flow — the four flow metrics the Kanban Guide mandates, and what sits around them: work in progress, throughput per ISO week, cycle / lead / response time as p50 / p85 / p95 in calendar days, aging work against the p85, created versus resolved, and days spent blocked. There is no mean anywhere, on purpose. Every response names the window and the column work counts as started from, so a number is never quoted without its terms. Empty sections say why they are empty.
  • kadence report cfd — tasks per column at the end of every day of the window. A move backwards subtracts. The first version re-walked each task's history per day and cost 150 ms on 10,000 events; it is a single pass now, at 4 ms.
  • kadence board config --started <status> — the column cycle time and a sprint's actual hours are measured from. It was the literal in_progress in three places while columns were configurable, so a board named todo,doing,review,done closed every sprint with actualHours: null and never said why. It is a folded setting now, validated against the live columns, and a column change that strands it warns instead of going quiet.
  • kadence compact — folds months older than --keep-months into one archive file each. The primitive existed since 0.1 and was reachable only from tests. --dry-run says what would move and writes nothing. It touches no git state, and it says the one thing worth knowing: an archived month is a single file, so compact on one branch and merge before compacting on another.

Added — three experiments (0.4.2)

Each of these was a standing "no". Each is now tried in the shape that survives the three constraints, and each has a kill condition written before the code, in DOC-24 (kadence doc show DOC-24).

  • kadence board export --html — the board, the sprint, the burndown, milestones and decisions in force, as one self-contained file. No script tag, no stylesheet link, no font, no image: the page asks the network for nothing and opens from disk. That is what makes it an experiment in place of a web UI rather than a web UI with extra steps. A test asserts the absence, not the appearance. Acceptance criteria are printed in full rather than counted, because a file attached to a pull request is evidence and "2/3" is not.
  • kadence board export --md, and --readme to update the section between markers in an existing README. It will not create one — that would be a different command and a bigger promise.
  • @kadence/github — a separate package that publishes tasks to GitHub Issues in one direction, by shelling out to gh. The core is unchanged and still opens no socket; the credential stays with a tool the user already trusts. A marker in the issue body makes a second publish an edit rather than a duplicate, and the issue itself says that edits made there are overwritten. Nothing is ever read back. See ADR-012 (kadence doc show DOC-15), written before the code. Every test runs against a recorded gh, never the real one.
  • kadence task doc add KAD-1 docs/design.md — creates the file from a four-line template and records the link in one call. It never overwrites: a file that is already there is the document, and replacing it with a template would destroy the thing being linked.

Added — attention signals, and labels that survive a merge (0.4.4)

  • kadence report attention — work the board presents as active while nobody is moving it. Four signals, one definition: stalled (no event for N days), unowned (in flight for N days with no assignee and no claim), stale_claim (a claim older than N days with no move since), and dead_blocker (blockedBy pointing at a finished task — the one signal that waits for no threshold). --since is the days of silence, default 7. No new event type, no new field: the report is a fold over what the journal already holds, and it costs 0.5 ms over 10,000 events on top of the fold.

    Two candidates were left out on purpose and the module says why. "Open task with no owner" is the definition of a backlog. "Criteria added and never checked" would fire on every task in any repository that configured a Definition of Done, because task add copies it in unchecked — loudest in the repositories that took our advice.

  • prime carries an attention line — up to three tasks, and only when there is something to say.

  • task.label_added / task.label_removed — labels move as deltas. This was the one field where "every intent is preserved" was untrue: task.updated carried the whole array and the fold replaced the whole array, so two branches adding different labels merged without a conflict and one label vanished with no warning. Not a merge failure — a fold storing state in an event, the exact mistake the product exists to avoid. Reasoning in ADR-013 (kadence doc show DOC-16).

  • labels in ready --json, the seventh field, and in the guaranteed set.

Source for this slice: a tech lead's feedback of 2026-09-10 — drift comes not from where state lives but from work that stops being watched. Append-only removes the drift between board and journal; it promises nothing about the drift between journal and reality. The README now says so.

Fixed before release

  • compact could destroy an archive it had already written. A branch that compacts a month, merges, and then delivers one more event for that month gets a fresh month directory beside the archive — append files an event by its own timestamp. The second compaction replaced the archive with the directory's contents alone. Reproduced: three events became one, silently, in a journal whose one promise is that nothing is lost. Archives are merged by id now, and the dry run counts only what is not archived yet. The primitive had been this way since 0.1; this release is what made it reachable.
  • A reopened task stayed done on the cumulative flow chart and was not counted as work in progress, because task.reopened moves a task without a task.moved. Both now follow it.
  • A finished task went on accruing blocked days. Work that is over is not blocked, whatever its blocker is doing.
  • A started boundary that is not one of the columns inflated work in progress instead of emptying the report — the default state of every board that renames in_progress, and the opposite of what board config warns.
  • --started was dropped when given with --statuses or --dod, which is the natural first command on a custom board. It travels in the same event now, validated against the list being set.
  • The sprint report and report flow disagreed about what counts as started. A task that skipped the started column had a cycle time in one and no actual hours in the other. One definition serves both.
  • A deferred task.reopened read the boundary as of the end of the fold rather than as of its own position in ULID order. Resolved the way claims and criteria already are.
  • --started done was accepted, --since 007 became 7 before the parser saw it, --keep-months abc reported "NaN", and nothing said the dates are UTC.
  • The "cold start" performance figure was a warm one. The perf test removed the state cache once, then took the best of three runs — the first run built the cache and the next two read it, so "11 ms cold" was a 12 ms warm read. Measured with the cache removed before every run: 199 ms cold when every event is a separate file, 21 ms cold with a compacted archive. The first of those is the budget itself, with no room. The test now removes the cache per iteration, the README carries the honest numbers, and the finding feeds the reports work: compaction exists in the core but is not yet a command anyone can run.
  • Two commands could write outside the repository. task doc add and board export --file both described containment in a comment and did not enforce it: ../pwned.md created a file in the parent directory, and the doc link went into the append-only journal as a path meaningless on any other machine. Both now resolve and verify before anything is written.
  • A task title containing the board end marker corrupted README.md, and the corruption grew by one copy on every export. The marker is now neutralised where text enters, and the end marker is searched for after the begin marker rather than from the top of the file.
  • A failed gh issue list read as "no issue exists", so a rate limit created a duplicate and reported success. The lookup is now a value rather than an absence, and runs once for the batch instead of once per task.
  • A batch that failed halfway said nothing about what was already published, and the JSON path discarded the partial record entirely. Both now name it.
  • @kadence/github was never typechecked or built in CI — nothing imports its entry point, so the root config never saw it. That is why two of the bugs above survived to review. CI now gates the package, and greps the core bundle for network imports.
  • Decisions and milestones vanished from an export of a board with no tasks yet.
  • A | in a configured status broke the Markdown table.
  • Three event types were dropped instead of held pending. milestone.task_added, milestone.closed and task.criterion_checked were handled as they arrived, so an event carrying a lower ULID than the entity it names vanished — no pending, no rejected, no trace. Reachable for the same reason claims are: clocks disagree between machines, so an assignment written on a lagging clock sorts before the task.created it refers to. Milestone events now go through the same defer path every task event uses, and criterion checked-state is folded from the task's history, where the order is ULID order.
  • completion install could overwrite a file it did not write. Every generated script now carries a marker; a file without it is left alone and the command says what is there, with --force for the deliberate case. Both target directories are shared — a distribution package writes into the bash one.
  • The milestone field could carry a raw ULID when the milestone was created on a branch that has not merged. The contract promises MS-N; it is now MS-N or null.
  • --branch on a detached HEAD returned invalid_argument and exit 2. The flag was understood; the repository state is what refuses, so it is conflicting_state and exit 1.
  • --summary omitted contestedBy, so a contested task read as a cleanly owned one in the response large boards are steered towards.
  • Acceptance criteria were reachable only through task show, which made "which tasks have unchecked criteria" cost one call per task. criteria and openCriteria are now on every task record.

Notes

The plan set a target of 80 KB for --summary on a 1000-task board, taken from Probe C. That target was wrong, and the measurement says so: a full board of 1000 tasks is 855 KB, --summary is 275 KB, and even the narrowest useful set of three fields is 104 KB. Task titles are irreducible — 1000 of them do not fit in 80 KB. The property the flag actually exists for is a different one and it holds: a summary record costs the same whether the task carries one event or five hundred, while the full record grows past fifty times the size. That is what the test pins.

Notes

The bundle grew from 35 KB to 71 KB across 0.4. The export templates sit in the fast path rather than behind a dynamic import, and that was measured rather than assumed: reading the whole bundle costs under a millisecond, and every non-interactive command still runs in about 100 ms against a 200 ms budget. The lazy-import rule exists for blessed, a heavy third-party dependency that reads terminfo at runtime — not for 16 KB of our own strings. blessed is still absent from dist/cli.js.

At release the build prints an 82 KB bundle, npm pack a 76 KB tarball with 233 KB unpacked, and --version returns in 60–70 ms on this machine; blessed is still absent from dist/cli.js.

Claim state is folded from each task's own history rather than as events arrive. It has to be: an event about a task whose task.created has not merged is replayed after the main loop, so a claim carrying a lower ULID could be applied last and hand the task to the wrong person. Reachable rather than theoretical, because clocks disagree between machines.

The TUI additions were exercised by hand by the owner on 2026-09-13 before the tag, as CLAUDE.md requires: four TUI bugs have shipped past a green suite in this project, so a green suite alone is not evidence there.

[0.3.2] — 2026-09-09

Nothing in the package changed. src/, scripts/, package.json and the lockfile are identical to 0.3.1 — the diff across all four is empty — so the code you get is the code you already have, and there is no behavioural reason to take this one.

What changed is the pipeline that produces it. From here on the tarball is built in a job that does not execute dependency install scripts and that pins every action it runs to a commit rather than a tag.

Security

  • Dependency lifecycle scripts no longer run in CI or on release. npm ci passes --ignore-scripts in both workflows. A postinstall runs with the full rights of the job, which is the primary npm supply-chain vector. Exactly one dependency here declares one — esbuild — and in 0.28 the platform binary arrives through optionalDependencies, so the script has nothing left to do. Measured rather than assumed: clean install, 35 KB bundle, full suite green on Node 20 and 22.
  • Both workflows pin actions/checkout and actions/setup-node to commit SHAs, with the version in a trailing comment. A tag is mutable: @v4 is whatever its owner last moved it to, and it runs with the job's permissions.

Publishing is unaffected: --ignore-scripts governs installing dependencies, not this package's own lifecycle, so prepublishOnly still gates every release on typecheck, tests and build.

[0.3.1] — 2026-09-08

A patch, and the reason to take it is the fix: anyone already on 0.3.0 has a state cache that can serve records folded by the previous build. The added field is additive within kadence/v1, which the contract permits at any version.

Added

  • decision list and decision show now report who wrote the record — source: "human" | "agent", from KADENCE_SOURCE at the time of writing. kadence has always refused to guess this when writing an event; that refusal only means something if the source survives to the point of reading. Human output marks only agent-written records, because labelling every human one in a repository written mostly by people hides the exception.

Fixed

  • The state cache could serve a shape folded by an older version of kadence. .kadence/state.json carries a version, and it was not bumped when a projected record gained a field — so after an upgrade the cache kept returning records without the new field, and any consumer branching on it saw undefined. Invariant I6 says deleting the cache changes nothing, which is true; what bites is keeping it. The version is now pinned to the projected shape by a test, so adding a field without invalidating the cache fails the build.

    Found by using the product on itself: three decisions recorded minutes earlier refused to show their source.

[0.3.0] — 2026-09-08

The journal held what happened. It now holds why — and the reasoning cannot quietly go stale, because superseding a decision is one event rather than two edits somebody has to remember to make. Reasoning in ADR-010; the guide is Recording why.

Added

  • kadence decision — why a choice was made, as an event next to the work. --why is required; without it the record is a changelog line. --supersedes writes a single record and the backward link is derived while folding, so the two directions cannot fall out of step — the failure that makes a reversed decision keep looking authoritative in file-based tools. decision list returns what is still in force; --all adds the history.

    Numbers are derived, so two branches can each record a decision and merge without a conflict or a renumber. Both are covered by integration tests on real git branches.

  • Documents linked to work — kadence task doc KAD-1 docs/design.md, and --doc on a decision. kadence does not store the document: it stays plain markdown and git keeps versioning it. What git cannot say is that this file explains this task, and that is the only thing recorded. A missing file is a warning, not a refusal — it may arrive in a later commit.

    Measured before building (Probe D): across five real questions, grep finds the answering document every time and buries it among 10–35 candidates.

  • task show --json now carries decisions and docs; both are always arrays, so an agent never has to branch on their absence.

    A decision that supersedes another inherits the task it was about, unless you name a different one. Without that, superseding quietly stripped a task of its reasoning — the old decision dropped out of task show and the replacement had never been attached. Found by installing the package and using it, with 538 tests green.

  • The files init writes into your repository now say what produced them: <!-- generated by kadence X.Y.Z — refresh with kadence init -->. A context file with no marker rots silently — a year on, nobody knows whether the section is current or what put it there, and we write into other people's repositories. Deliberately no date: it would rewrite the file on every new day and fill diffs with noise, while the version changes exactly when the content might have.

[0.2.2] — 2026-09-08

Two promises from 0.2.1 that the code did not keep, found by reading the published package rather than the source.

Fixed

  • Most --json failures carried no error.code. ADR-009 says every failure has one; thirty-seven paths returned a bare sentence — most of sprint, all of template, board config, and every "unknown action". An AI agent could tell that something failed and nothing else. The existing tests compared the published code list against the constant, so they could not see that the commands were not using it; a new test now provokes twenty-seven real failures and requires a code from the list on each.
  • unknown_status hinted at a command that does not exist. It named kadence board statuses --json; following it produced a second failure, which itself had no code. The hint is now kadence board config --json, which already returns the configured columns, and every hint the CLI can emit is executed by a test that requires it to succeed.
  • cac was a runtime dependency that nothing loaded. esbuild bundles it, so the only external import in the published output is blessed — every install downloaded 52 KB for nothing. Moved to devDependencies, with a test that compares what the manifest declares against what the build actually imports.

Added

  • Two error codes, additive within kadence/v1: template_not_found, and conflicting_state for arguments that are understood but refused by the current state — a closed sprint, one already started, one still open at close.
  • received and allowed on the failures where they are knowable, including the action lists behind "unknown action".

[0.2.1] — 2026-09-08

Why 0.2.1 and not 0.2.0

0.2.0 cannot be published under this name. A different package called kadence used that version on 2026-02-05 — before this one claimed the name — and npm never allows a version number to be reused, not after an unpublish and not by a new owner. Nothing was released as 0.2.0; this is the first release of the 0.2 line.

The agent contract, made real. schema: "kadence/v1" used to be a version string that nothing checked; now the contract is published, the failures are machine-readable, and the responses can be narrowed to what an AI agent actually reads. Reasoning in ADR-009, measurements in Probe C.

Added

  • kadence schema --json — the machine-readable contract behind schema: "kadence/v1": every command, the fields you can rely on, and every error code. It works outside a repository, because an AI agent asks what the tool does before it has a project to ask about.
  • Failed --json calls now carry error.code from a closed list, plus received, allowed and hint where each is knowable. allowed matters most for statuses: they are configured per project, so an agent cannot learn the valid set from documentation. There is deliberately no retryable field — with no network and no lock, the same input always fails the same way.
  • --fields on board --json and task list --json, so an agent can ask for the columns it reads instead of every description. On a 200-task board that is 130,799 bytes down to 11,015. An unknown name fails with unknown_field and the list of what exists.

Changed

  • init now writes the kadence section into CLAUDE.md as well as AGENTS.md. Claude Code does not read AGENTS.md, so a repository carrying only the latter was invisible to the largest agent audience — the promise that "the AI agent finds it on its own" was not true for them. Text a human wrote in either file is left untouched, and a repeat init does not duplicate the section.

Fixed

  • Any --json response larger than the pipe buffer was truncated mid-string. process.exit() does not wait for an asynchronous write to drain, and writing to a pipe — how every agent reads us — is asynchronous, while writing to a file is not. A 200-task board produced 131,072 bytes and a parse error; the same command redirected to a file was valid. Output is now written synchronously.

Note on the roadmap

kadence context <task> will not be built. task show --json already returns the whole history of one piece of work in 948 bytes, and that number does not grow with the project — the only thing left to add was a format nobody has asked for.

[0.1.5] — 2026-09-03

Changed

  • README rewritten to lead with the number the product exists for — what a story point actually costs — instead of a feature list. Claims now carry their evidence inline: the merge thesis links to the 8,396-commit study and to the integration test that proves it, and the performance table says these are tests that fail the build.
  • Repository URLs follow the rename to bogutskiandriy/kadence.

[0.1.4] — 2026-09-03

Fixed

  • The package depended on itself. kadence@^0.1.1 sat in dependencies, so every install pulled a second, older copy of the tool into node_modules and shipped it to users. Removed; the published tarball is now 32.7 kB with eight files in it.
  • @types/blessed moved to devDependencies. Type definitions are not needed at runtime, and every install was paying for them.

Repository

  • Developer tooling (.claude/, .serena/) is no longer committed: 381 files and 3.8 MB of it, against 94 files of actual product. What belongs in git and what does not is written down in ADR-007, and .gitignore now also covers .env, coverage output and editor leftovers.

[0.1.3] — 2026-09-03

Testing

  • End-to-end coverage through the real binary: a full sprint from init to sprint close with velocity computed from actual events, every reachable command checked for a clean exit, three branches editing one task, an agent driving the tool with JSON alone, and the events folder vanishing on a branch switch. 390 tests.

[0.1.2] — 2026-09-03

Documentation

  • The README opened with npx kadence init and said nothing more, so the obvious next step — kadence board — failed with "command not found". npx fetches a package for one command and leaves nothing installed. Global install is now the first instruction, with the npx path shown as the alternative it is.

[0.1.1] — 2026-09-03

Fixed

  • kadence --version reported 0.1.0-dev while the published package was 0.1.0. The CLI carried its own copy of the version string; it is now injected from package.json at build time, with a test that fails if the two ever diverge again.

[0.1.0] — 2026-09-03

First release. Tasks, sprints and velocity as plain files inside a git repository, with no server, account or network.

Core

  • Append-only event journal: one file per event, never rewritten. Two branches editing the same task merge without a conflict — verified on real git branches, not only in theory.
  • State folded from the journal on every read, so the board cannot drift from reality. A snapshot cache makes that cost 7 ms on 10,000 events.
  • ULID identifiers, so event order is a property of the id rather than of how far apart machine clocks have drifted.

Tasks

  • Title, description, type (task/bug/story/epic), priority, labels, assignee, due date, estimate, comments, logged time.
  • Subtasks and blocking dependencies, with cycle detection that reports a loop instead of rejecting the later edit — rejecting it would make the state depend on merge order.
  • Search across titles, descriptions and comments; filters, sorting, and bulk operations that apply all-or-nothing.
  • Templates for repeated task shapes.

Sprints

  • Plan the next sprint while the current one runs.
  • Velocity and hours-per-point derived from events, so the numbers cannot be forgotten or faked.
  • Burndown reconstructed from the journal for any day — including days before the feature existed.

Board

  • kadence board — plain columns for pipes and scripts.
  • kadence ui — interactive kanban: keyboard, mouse, drag between columns, and every field editable in place. Loads lazily, so kadence task add never pays for it.
  • Custom columns per team; done cannot be removed because every analytic is computed from it.

For agents

  • --json on every command with a stable schema: "kadence/v1".
  • stdout carries JSON only; warnings go to stderr.
  • init writes a guide the agent finds on its own.

Known limits

  • The velocity bet is not yet validated with users. See README, Honest status.
  • Terminal interaction is covered by manual testing; only the key router is unit-tested.

The canonical file lives in the product repository: CHANGELOG.md.

On this page

[0.10.1] — 2026-10-12AddedFixedChanged[0.10.0] — 2026-10-12⚠️ Changed — task claim starts the workAddedFixed[0.9.1] — 2026-10-12Fixed[0.9.0] — 2026-10-12Added — a guided kadence init for a person at a terminalChanged — kadence init never asks an agent, and tells it what nextFixed — rejected-before, on a journal one day old and on long ones⚠️ Upgrading — board --json returns the state, not the historyAdded — kadence doctorTested[0.8.2] — 2026-10-11Added[0.8.1] — 2026-10-09A patch number on a minor's worth of changeAdded[0.8.0] — 2026-09-30⚠️ init now writes .claude/settings.jsonAddedChangedFixed[0.7.0] — 2026-09-25⚠️ Upgrading — three defaults changedAdded — kadence searchFixed — prime could be forged, and repainted your terminalFixed — a warm command read the whole journal anywayFixed — list commands returned the whole boardChanged[0.6.0] — 2026-09-23⚠️ Upgrading — everyone updates before anyone runs compactAdded — documentation is an entityChangedFixed — the contract, where it was quietMeasured[0.5.0] — 2026-09-16Added — the teammate pathChanged — the first run speaks about the workFixed — the contractToolingAddedFixedNotes[0.4.1] — 2026-09-13Fixed[0.4.0] — 2026-09-13AddedChangedAdded — the branch, the human, and the evidence behind done (0.4.1)ChangedAdded — reports, and the two things they needed first (0.4.3)Added — three experiments (0.4.2)Added — attention signals, and labels that survive a merge (0.4.4)Fixed before releaseNotesNotes[0.3.2] — 2026-09-09Security[0.3.1] — 2026-09-08AddedFixed[0.3.0] — 2026-09-08Added[0.2.2] — 2026-09-08FixedAdded[0.2.1] — 2026-09-08AddedChangedFixedNote on the roadmap[0.1.5] — 2026-09-03Changed[0.1.4] — 2026-09-03FixedRepository[0.1.3] — 2026-09-03Testing[0.1.2] — 2026-09-03Documentation[0.1.1] — 2026-09-03Fixed[0.1.0] — 2026-09-03CoreTasksSprintsBoardFor agentsKnown limits