kadence 0.3.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +515 -0
- package/README.md +158 -49
- package/dist/chunks/board-6UNW5ISX.js +12 -0
- package/dist/chunks/chunk-CDGGOWGY.js +10 -0
- package/dist/chunks/chunk-FCPGSN7D.js +240 -0
- package/dist/chunks/ui-Q53SLKMA.js +7 -0
- package/dist/cli.js +332 -60
- package/package.json +10 -8
- package/scripts/reference.mjs +120 -0
- package/dist/chunks/board-7RVHIQJQ.js +0 -12
- package/dist/chunks/chunk-EPFIY2MT.js +0 -199
- package/dist/chunks/ui-HHJ5TWYZ.js +0 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,520 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [Unreleased]
|
|
4
|
+
|
|
5
|
+
## [0.5.0] — 2026-09-16
|
|
6
|
+
|
|
7
|
+
**The second author.** kadence was built for a team, and until now its first
|
|
8
|
+
run spoke to one person with a sprint. This release is the path the second
|
|
9
|
+
person walks: their agent learns to write the why, a machine without kadence
|
|
10
|
+
gets an install hint instead of an error, and the first screens talk about the
|
|
11
|
+
work and its decisions rather than points. Reports are still here; they are
|
|
12
|
+
just no longer the first thing anyone meets.
|
|
13
|
+
|
|
14
|
+
### Added — the teammate path
|
|
15
|
+
|
|
16
|
+
- **The section `init` writes into `AGENTS.md` and `CLAUDE.md` teaches writes,
|
|
17
|
+
not only reads.** `task claim`, `note "…" --task`, and `decision add "…" --why`
|
|
18
|
+
sit beside the read commands, plus one line for an agent on a machine where
|
|
19
|
+
`kadence` is not on PATH: ask the human to install it; `npx` needs the network
|
|
20
|
+
and that is their call. Still 23 lines and 1.2 KB, under a test of 25 lines and
|
|
21
|
+
2 KB; an older section is replaced in place by its version marker.
|
|
22
|
+
- **The `SessionStart` hook survives a machine without kadence.** It runs
|
|
23
|
+
`kadence prime` when the binary is there and otherwise prints one line asking
|
|
24
|
+
the human to install it, exiting 0 (DEC-12: no `npx`, because a hook written by
|
|
25
|
+
`init` must not download code on a teammate's machine). `init --hooks` upgrades
|
|
26
|
+
the old `kadence prime` hook in place instead of adding a second.
|
|
27
|
+
- **`.kadence/README.md` leads to decisions and teammates.** `decision list` and
|
|
28
|
+
`decision add` replace `sprint status` among the top commands, and a new
|
|
29
|
+
"Adding a teammate" section covers install, one `user.email` per person and
|
|
30
|
+
`KADENCE_SOURCE=agent`. Existing READMEs are still never overwritten.
|
|
31
|
+
- **README: the first ten minutes, "Adding a teammate" and "Removing kadence"** —
|
|
32
|
+
the last one lists everything `init` touches, so leaving is a table, not a
|
|
33
|
+
guess. Sprints, reports and exports moved to `docs/reports.md`.
|
|
34
|
+
|
|
35
|
+
### Changed — the first run speaks about the work
|
|
36
|
+
|
|
37
|
+
- **`init`** suggests a first task *and* a first decision, names what to commit
|
|
38
|
+
so a teammate's agent finds it, and mentions `--hooks` when `.claude/` exists.
|
|
39
|
+
- **`prime`** no longer opens with "No active sprint." when there is none; with
|
|
40
|
+
no decision in force it shows one line on how to record one. `--json` unchanged.
|
|
41
|
+
- **`task add` without `--estimate`** says nothing outside a sprint; inside one it
|
|
42
|
+
names the sprint instead of velocity. `sprint add` says the same.
|
|
43
|
+
- **`kadence --help`** lists commands in the order a session meets them: `init,
|
|
44
|
+
prime, ready, task, decision, note, board, ui, schema`, then planning, reports
|
|
45
|
+
and maintenance.
|
|
46
|
+
- **The TUI header** without a sprint reads `N open · M ready · K decisions in
|
|
47
|
+
force`; points appear only when something is estimated.
|
|
48
|
+
- **`report --list`** puts `attention` first and groups burndown, velocity and
|
|
49
|
+
workload under "Sprint and team".
|
|
50
|
+
|
|
51
|
+
### Fixed — the contract
|
|
52
|
+
|
|
53
|
+
- **`schema --json` lists every shipped command.** 21 entries were missing,
|
|
54
|
+
among them `sprint add/edit/start/list/burndown`, `task parent/unblock/cancel/delete`,
|
|
55
|
+
`template`, `completion` and `ui` — additive within `kadence/v1`. A test now
|
|
56
|
+
compares the binary's own commands and actions against the contract and fails
|
|
57
|
+
on any gap. Unknown `decision` actions now return `allowed`, and `task` lists
|
|
58
|
+
`doc` in it.
|
|
59
|
+
|
|
60
|
+
### Tooling
|
|
61
|
+
|
|
62
|
+
- **`scripts/north-star.mjs`** counts repositories with two authors fourteen days
|
|
63
|
+
after their first event — `--local` for a checkout, `--search` through `gh` for
|
|
64
|
+
public ones — and appends a weekly row to `docs/research/north-star-log.md`.
|
|
65
|
+
A maintainer script: the product itself still opens no socket. First row: 0,
|
|
66
|
+
with the reason GitHub code search has not indexed even this repository.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
The command reference stops being prose. And the first two bugs found by using
|
|
71
|
+
the product on itself.
|
|
72
|
+
|
|
73
|
+
### Added
|
|
74
|
+
|
|
75
|
+
- **`kadence report` is the whole catalogue.** `report burndown`, `report
|
|
76
|
+
velocity` and `report workload` join `flow`, `cfd` and `attention`, and
|
|
77
|
+
`report --list` names all six with what each one answers.
|
|
78
|
+
|
|
79
|
+
The folds were already here — the burndown under `sprint`, the workload
|
|
80
|
+
inside `stats` — so an agent reading `schema --json` could not find them and
|
|
81
|
+
`kadence report` was not the catalogue it looked like. `report burndown`
|
|
82
|
+
calls the same function `sprint burndown` does, resolved in one place, so the
|
|
83
|
+
two cannot drift. `report velocity` is new arithmetic over `sprintReport`,
|
|
84
|
+
and it answers with a range rather than an average (DEC-9): the spread
|
|
85
|
+
between sprints is the forecast, and a series shorter than four sprints says
|
|
86
|
+
so. `report workload` counts open tasks, points, work in progress and blocked
|
|
87
|
+
work per owner, with a row for unassigned work — no hours, no capacity, since
|
|
88
|
+
the journal has neither.
|
|
89
|
+
|
|
90
|
+
`--since` on a report that has no window, or `--sprint` on anything but the
|
|
91
|
+
burndown, is now an error rather than a silently ignored flag (DEC-10).
|
|
92
|
+
|
|
93
|
+
- **`kadence report <name> --html`** — a report as one self-contained page, with
|
|
94
|
+
charts. `flow` gets a percentile dot plot, a created-vs-finished column chart
|
|
95
|
+
and an aging-work chart with the p85 cycle time drawn as a reference line;
|
|
96
|
+
`cfd` gets a stacked area chart with the bands named where they are wide
|
|
97
|
+
enough; `attention` gets the idle days against the threshold. `--file` puts it
|
|
98
|
+
where you want it, and with `--json` the response is the path, not the report.
|
|
99
|
+
|
|
100
|
+
The charts are hand-written inline SVG. Every chart library is a script tag,
|
|
101
|
+
and the page's one promise is the one the board export already makes: it opens
|
|
102
|
+
from disk and asks the network for nothing — asserted structurally, down to
|
|
103
|
+
`url(` in the stylesheet and the `xmlns` an inline `<svg>` does not need.
|
|
104
|
+
Measured at 0.1 ms and 14 KB for the flow page, 1.7 ms and 301 KB for a
|
|
105
|
+
two-year cumulative flow diagram. Every chart is followed by the rows it was
|
|
106
|
+
drawn from: three of the eight categorical colours sit below 3:1 against
|
|
107
|
+
white, and the rule that buys them is that colour is never the only channel.
|
|
108
|
+
|
|
109
|
+
Reasoning in DEC-7 (a page per report rather than a Reports section inside the
|
|
110
|
+
board export — a report is a question with a window, and the board export has
|
|
111
|
+
none) and DEC-8 (the charts).
|
|
112
|
+
|
|
113
|
+
- **`kadence schema --json` and every `--help` now have a machine-readable
|
|
114
|
+
sibling.** `npm run reference` writes `dist/reference.json`: every command,
|
|
115
|
+
its usage line, its flags with their descriptions, its examples, and the
|
|
116
|
+
whole agent contract, read out of the binary rather than written about it.
|
|
117
|
+
The release workflow attaches it to the GitHub release of each tag.
|
|
118
|
+
|
|
119
|
+
This exists because the hand-typed CLI page on the site described 0.3 for the
|
|
120
|
+
whole of 0.4 — nine commands and two dozen flags shipped without it noticing,
|
|
121
|
+
and the agents page named ten of the fifteen error codes. A paragraph has no
|
|
122
|
+
test. A test now reads `src/cli/commands/` and fails when a command file has
|
|
123
|
+
no help behind it, which is the only way that gap stays closed.
|
|
124
|
+
|
|
125
|
+
The generator ships in the package rather than its output: measured at 1.9 KB
|
|
126
|
+
packed against 6.2 KB, and 4.9 KB unpacked against 39.7 KB, for the same
|
|
127
|
+
result. Nothing in the data is unavailable from the CLI itself.
|
|
128
|
+
|
|
129
|
+
- **`scripts/kadence.mjs`** — runs the build in this working tree, rebuilding
|
|
130
|
+
when `src/` is newer. For working on kadence with kadence, which is now how
|
|
131
|
+
this repository is run; `CLAUDE.md` says what that means in practice.
|
|
132
|
+
|
|
133
|
+
### Fixed
|
|
134
|
+
|
|
135
|
+
- **`burndown.finalRemaining` was always `null`.** Both branches of
|
|
136
|
+
`sprint.status === 'closed' ? null : null` read the same, so the one number
|
|
137
|
+
the field exists for — what a closed sprint did not finish — was never in the
|
|
138
|
+
response, including in `sprint burndown --json`. It now carries the points
|
|
139
|
+
left on the sprint's last day.
|
|
140
|
+
|
|
141
|
+
- **A repeated flag crashed `decision add`.** `--rejected A --rejected B` threw
|
|
142
|
+
`o.rejected.trim is not a function`: cac hands a single flag back as a string
|
|
143
|
+
and a repeat as an array, and only `--doc` was normalised for it. The four
|
|
144
|
+
single-value flags went into `.trim()` as arrays.
|
|
145
|
+
|
|
146
|
+
Repeats now mean what each flag means. A second `--why`, `--task` or
|
|
147
|
+
`--supersedes` is a correction, so the last wins; a second `--rejected` is a
|
|
148
|
+
second alternative that was turned down — which is what a decision record is
|
|
149
|
+
for — so both are kept, joined rather than stored as an array because
|
|
150
|
+
`rejected` is a string in `kadence/v1` and the contract only ever gains
|
|
151
|
+
fields.
|
|
152
|
+
|
|
153
|
+
Found by recording a real decision about this repository with two rejected
|
|
154
|
+
options, with 898 tests green. The tests for it go through the built binary,
|
|
155
|
+
because calling the command directly is exactly the path that could not see
|
|
156
|
+
it.
|
|
157
|
+
|
|
158
|
+
### Notes
|
|
159
|
+
|
|
160
|
+
Deleting several tasks by `KAD-N` in one loop removes the wrong ones. Labels
|
|
161
|
+
are derived while folding (I7), so removing `KAD-1` renumbers everything after
|
|
162
|
+
it and the next label in the list now belongs to a different task. Working as
|
|
163
|
+
designed, and a sharp edge: delete by ULID, or one at a time. Recorded as a
|
|
164
|
+
note in this repository's own journal rather than fixed, because the fix is not
|
|
165
|
+
obvious — warning on a bulk delete of labels would fire on the common case too.
|
|
166
|
+
|
|
167
|
+
A board-wide Definition of Done is engineering-shaped. `board config --dod`
|
|
168
|
+
copies its criteria into every new task, so "typecheck clean" landed on the
|
|
169
|
+
Probe B interview tasks, where it means nothing, and there is no `task ac
|
|
170
|
+
remove` to take it off. Noted, not yet answered.
|
|
171
|
+
|
|
172
|
+
## [0.4.1] — 2026-09-13
|
|
173
|
+
|
|
174
|
+
`0.4.0` was tagged and never published: these three were found by using it
|
|
175
|
+
before it reached anyone, so `0.4.1` is the first published 0.4. Nothing below
|
|
176
|
+
changes the shape of an event or renames a field in `kadence/v1`.
|
|
177
|
+
|
|
178
|
+
### Fixed
|
|
179
|
+
|
|
180
|
+
- **The fold dropped who wrote a comment or a history line.** Every event
|
|
181
|
+
carries `source` — `"human"` or `"agent"`, from `KADENCE_SOURCE` — and it is
|
|
182
|
+
validated on read, but the projection kept it only on decisions and notes. So
|
|
183
|
+
`task show` printed an agent's comment exactly like a person's, and
|
|
184
|
+
`task show --json` gave an agent no way to tell them apart: `actor` is the git
|
|
185
|
+
identity, which a person and their agent share. The one surface the product's
|
|
186
|
+
headline is about could not show the thing it claims. Comments and history
|
|
187
|
+
entries now carry `source`, `task show` marks an agent's line `[agent]` the
|
|
188
|
+
way `decision list` already did, and a note shows the mark it always knew.
|
|
189
|
+
The snapshot shape test now records nested records too — it is what should
|
|
190
|
+
have failed when `source` reached the event and not the comment. Cache version
|
|
191
|
+
bumped to `kadence-snapshot/11`.
|
|
192
|
+
- **`kadence ready` offered work that had already started.** The filter knew
|
|
193
|
+
about finished, blocked-by and claimed-by-others, and nothing about the board:
|
|
194
|
+
a task sitting in `in_review`, or parked in the `blocked` column, was listed
|
|
195
|
+
first with `kadence task claim KAD-1` printed under it. An agent following
|
|
196
|
+
`prime → ready → claim` took work somebody was reviewing. Ready now means
|
|
197
|
+
ready to *start* — anything in the started column or past it is left out,
|
|
198
|
+
using the board's own boundary, so a team that renames its columns keeps a
|
|
199
|
+
`ready` that means what its board means. When that is why the list is empty,
|
|
200
|
+
the message says so: `Nothing ready (2 already started)`.
|
|
201
|
+
- **The performance fixture moved all 500 tasks into `in_progress`**, so the
|
|
202
|
+
`ready` guardrail was timing a filter over an empty answer and asserting only
|
|
203
|
+
that it was fast. Every third move parks a task back in `todo` now; the
|
|
204
|
+
guardrail times a real result.
|
|
205
|
+
|
|
206
|
+
## [0.4.0] — 2026-09-13
|
|
207
|
+
|
|
208
|
+
Planned as slices 0.4.0 through 0.4.4 and published as one minor release.
|
|
209
|
+
|
|
210
|
+
The agent loop: four commands that answer "what next" without folding the whole
|
|
211
|
+
board by hand. Everything here is additive within `kadence/v1` — no field or
|
|
212
|
+
command was renamed, and `--json` responses gain keys rather than losing them.
|
|
213
|
+
|
|
214
|
+
### Added
|
|
215
|
+
|
|
216
|
+
- **`kadence ready`** — open tasks with no live blockers, not claimed by anyone
|
|
217
|
+
else, priority first and age second. `--json` returns five fields per task
|
|
218
|
+
rather than the whole record, because this goes into an agent's context on
|
|
219
|
+
every session. An empty result names the reason: how many are blocked, how
|
|
220
|
+
many belong to someone else. Measured at 9 ms over 10,000 events.
|
|
221
|
+
- **`kadence prime`** — the session preamble: active sprint and days left, your
|
|
222
|
+
claimed and in-progress work, how many tasks are ready, decisions in force by
|
|
223
|
+
title, the last five notes, and four commands to go deeper. Held to 40 lines
|
|
224
|
+
and 3 KB by a test; 16 lines and 493 bytes on this repository. It carries the
|
|
225
|
+
part that changes and points at the part that does not, because the static
|
|
226
|
+
half already lives in `AGENTS.md`.
|
|
227
|
+
- **`kadence init --hooks`** — adds a `SessionStart` hook running `kadence prime`
|
|
228
|
+
to `.claude/settings.json`. Only with the flag: that file belongs to the user
|
|
229
|
+
and is committed to their repository. The write is an upsert — hooks of other
|
|
230
|
+
events, other matchers and other commands survive, running it twice leaves one
|
|
231
|
+
hook, and a file that does not parse is reported rather than replaced.
|
|
232
|
+
- **`kadence task claim` / `task release`** — take a task, or give it back.
|
|
233
|
+
`claim` with no argument takes the top of `ready`, which is one step instead
|
|
234
|
+
of two. There is **no lock**, and the message says so: two machines can each
|
|
235
|
+
claim before either pushes. See [ADR-011](docs/decisions/011-claims-as-events.md).
|
|
236
|
+
- **`kadence note "text" [--task KAD-1]`** and `note list` — something learned
|
|
237
|
+
that was never a choice. Deliberately not a decision: no `--why`, no
|
|
238
|
+
`supersedes`, no number. The help says when to reach for `decision add`
|
|
239
|
+
instead. Notes show up in `task show` and in `prime`.
|
|
240
|
+
- **`claimedBy` and `contestedBy`** on the task record in `--json`, and a
|
|
241
|
+
`Claimed:` line in `task show`.
|
|
242
|
+
- **TUI**: `R` shows only what can be started now, `C` claims the selected task
|
|
243
|
+
or releases it if it is yours, and a card carries a claim mark — a separate
|
|
244
|
+
colour when the claim is contested. Both actions call the same functions the
|
|
245
|
+
CLI does.
|
|
246
|
+
|
|
247
|
+
### Changed
|
|
248
|
+
|
|
249
|
+
- **A contested claim is reported, not rejected.** When two people claim one
|
|
250
|
+
task, the earliest by ULID holds and the later one is kept, with the task
|
|
251
|
+
reading `contested` under both names. Rejecting the second would make the
|
|
252
|
+
owner depend on which branch merged first — the same invariant that keeps
|
|
253
|
+
dependency cycles and orphaned status columns alive rather than dropped. It is
|
|
254
|
+
the first conflict in kadence that names people rather than values.
|
|
255
|
+
- **The state cache version moved from 2 to 4.** The projected shape changed
|
|
256
|
+
twice: three claim fields on a task, then notes on the project state. Missing
|
|
257
|
+
exactly this bump is what shipped as a bug in 0.3.1, so it is now pinned to
|
|
258
|
+
the field lists by a test.
|
|
259
|
+
- The agent section written into `AGENTS.md` and `CLAUDE.md` grew by one line —
|
|
260
|
+
`kadence prime` first — and by no more than that.
|
|
261
|
+
|
|
262
|
+
### Added — the branch, the human, and the evidence behind done (0.4.1)
|
|
263
|
+
|
|
264
|
+
- **`kadence task list --branch`** — the work this branch introduced, measured
|
|
265
|
+
against `--base` (`main`, or `init.defaultBranch`). Membership is derived from
|
|
266
|
+
git at read time and never stored: writing a branch name into an event would
|
|
267
|
+
go stale the moment the branch is renamed or merged, and would make the same
|
|
268
|
+
events fold differently depending on where they were written. A detached HEAD
|
|
269
|
+
and an unknown base both fail by name rather than reporting no work.
|
|
270
|
+
The measurement that justified the flag, including what would make it wrong,
|
|
271
|
+
is in [branch-context-2026-09.md](docs/research/branch-context-2026-09.md).
|
|
272
|
+
- **Acceptance criteria** — `task ac add|check|uncheck|list`. The number is a
|
|
273
|
+
position in the folded list, assigned like `KAD-N` and never stored, so two
|
|
274
|
+
branches can each add a criterion and merge without renumbering. Moving a task
|
|
275
|
+
to `done` with unchecked criteria **warns and proceeds**: the checklist is
|
|
276
|
+
evidence, not a gate, and refusing would make the board lie about where the
|
|
277
|
+
work is.
|
|
278
|
+
- **Definition of done** — `board config --dod "tests green,docs updated"`. The
|
|
279
|
+
criteria are **copied** into each new task, never referenced, so raising the
|
|
280
|
+
standard cannot reach back and change what finished work had promised.
|
|
281
|
+
`task add --no-dod` skips them.
|
|
282
|
+
- **Milestones** — `milestone create|add|list|close`. Grouping by outcome, where
|
|
283
|
+
an epic groups by structure and a sprint by time. Not a third hierarchy: a
|
|
284
|
+
task carries at most one, as a field beside `sprint`. `MS-N` is derived from
|
|
285
|
+
ULID order like every other label, and progress is counted in points, the same
|
|
286
|
+
unit as velocity and burndown.
|
|
287
|
+
- **`kadence stats`** — counts by status and assignee, open blockers, contested
|
|
288
|
+
claims, and the velocity of the last three closed sprints.
|
|
289
|
+
- **`kadence completion install`** — zsh, bash and fish, generated from one list
|
|
290
|
+
rather than three hand-written scripts. Without a terminal it prints the
|
|
291
|
+
script instead of writing, because that is how people pipe it into a file.
|
|
292
|
+
For zsh it writes the file and prints the `fpath` line rather than editing a
|
|
293
|
+
shell rc, which is the user's file.
|
|
294
|
+
- **`board --json --summary`** — the column state without `history` and
|
|
295
|
+
`comments`. Those are the two fields that grow with how long a task has been
|
|
296
|
+
worked on rather than with how many tasks there are.
|
|
297
|
+
- **TUI**: `b` shows only what the branch introduced, `M` filters to one
|
|
298
|
+
milestone, and the card dialog carries the acceptance-criteria checklist with
|
|
299
|
+
space to toggle. Every toggle calls the same command the CLI does.
|
|
300
|
+
|
|
301
|
+
### Changed
|
|
302
|
+
|
|
303
|
+
- `task list --json` and `board --json` gain a `milestone` field on each task,
|
|
304
|
+
carrying the `MS-N` label. The response without `--summary` is otherwise
|
|
305
|
+
unchanged; the contract only ever gains.
|
|
306
|
+
- The state cache version moved again with the projected shape.
|
|
307
|
+
|
|
308
|
+
### Added — reports, and the two things they needed first (0.4.3)
|
|
309
|
+
|
|
310
|
+
What the neighbours call reports is, here, a fold over timestamps the journal
|
|
311
|
+
already carries. The reasoning, the sources and the verdict per report are in
|
|
312
|
+
[reports-discovery-2026-09.md](docs/research/reports-discovery-2026-09.md).
|
|
313
|
+
Velocity and cycle time stay out of the headline; they are a consequence, served
|
|
314
|
+
to the person who asks.
|
|
315
|
+
|
|
316
|
+
- **`kadence report flow`** — the four flow metrics the Kanban Guide mandates,
|
|
317
|
+
and what sits around them: work in progress, throughput per ISO week, cycle /
|
|
318
|
+
lead / response time as **p50 / p85 / p95 in calendar days**, aging work
|
|
319
|
+
against the p85, created versus resolved, and days spent blocked. There is no
|
|
320
|
+
mean anywhere, on purpose. Every response names the window and the column
|
|
321
|
+
work counts as started from, so a number is never quoted without its terms.
|
|
322
|
+
Empty sections say why they are empty.
|
|
323
|
+
- **`kadence report cfd`** — tasks per column at the end of every day of the
|
|
324
|
+
window. A move backwards subtracts. The first version re-walked each task's
|
|
325
|
+
history per day and cost 150 ms on 10,000 events; it is a single pass now, at
|
|
326
|
+
4 ms.
|
|
327
|
+
- **`kadence board config --started <status>`** — the column cycle time and a
|
|
328
|
+
sprint's actual hours are measured from. It was the literal `in_progress` in
|
|
329
|
+
three places while columns were configurable, so a board named
|
|
330
|
+
`todo,doing,review,done` closed every sprint with `actualHours: null` and
|
|
331
|
+
never said why. It is a folded setting now, validated against the live
|
|
332
|
+
columns, and a column change that strands it warns instead of going quiet.
|
|
333
|
+
- **`kadence compact`** — folds months older than `--keep-months` into one
|
|
334
|
+
archive file each. The primitive existed since 0.1 and was reachable only from
|
|
335
|
+
tests. `--dry-run` says what would move and writes nothing. It touches no git
|
|
336
|
+
state, and it says the one thing worth knowing: an archived month is a single
|
|
337
|
+
file, so compact on one branch and merge before compacting on another.
|
|
338
|
+
|
|
339
|
+
### Added — three experiments (0.4.2)
|
|
340
|
+
|
|
341
|
+
Each of these was a standing "no". Each is now tried in the shape that survives
|
|
342
|
+
the three constraints, and each has a kill condition written before the code, in
|
|
343
|
+
[feature-adoption-2026-09.md](docs/product/feature-adoption-2026-09.md).
|
|
344
|
+
|
|
345
|
+
- **`kadence board export --html`** — the board, the sprint, the burndown,
|
|
346
|
+
milestones and decisions in force, as **one self-contained file**. No script
|
|
347
|
+
tag, no stylesheet link, no font, no image: the page asks the network for
|
|
348
|
+
nothing and opens from disk. That is what makes it an experiment in place of a
|
|
349
|
+
web UI rather than a web UI with extra steps. A test asserts the absence, not
|
|
350
|
+
the appearance. Acceptance criteria are printed in full rather than counted,
|
|
351
|
+
because a file attached to a pull request is evidence and "2/3" is not.
|
|
352
|
+
- **`kadence board export --md`**, and `--readme` to update the section between
|
|
353
|
+
markers in an existing README. It will not create one — that would be a
|
|
354
|
+
different command and a bigger promise.
|
|
355
|
+
- **`@kadence/github`** — a separate package that publishes tasks to GitHub
|
|
356
|
+
Issues **in one direction**, by shelling out to `gh`. The core is unchanged
|
|
357
|
+
and still opens no socket; the credential stays with a tool the user already
|
|
358
|
+
trusts. A marker in the issue body makes a second publish an edit rather than
|
|
359
|
+
a duplicate, and the issue itself says that edits made there are overwritten.
|
|
360
|
+
Nothing is ever read back. See
|
|
361
|
+
[ADR-012](docs/decisions/012-network-only-in-packages.md), written before the
|
|
362
|
+
code. Every test runs against a recorded `gh`, never the real one.
|
|
363
|
+
- **`kadence task doc add KAD-1 docs/design.md`** — creates the file from a
|
|
364
|
+
four-line template and records the link in one call. It never overwrites: a
|
|
365
|
+
file that is already there is the document, and replacing it with a template
|
|
366
|
+
would destroy the thing being linked.
|
|
367
|
+
|
|
368
|
+
### Added — attention signals, and labels that survive a merge (0.4.4)
|
|
369
|
+
|
|
370
|
+
- **`kadence report attention`** — work the board presents as active while
|
|
371
|
+
nobody is moving it. Four signals, one definition: `stalled` (no event for N
|
|
372
|
+
days), `unowned` (in flight for N days with no assignee and no claim),
|
|
373
|
+
`stale_claim` (a claim older than N days with no move since), and
|
|
374
|
+
`dead_blocker` (`blockedBy` pointing at a finished task — the one signal that
|
|
375
|
+
waits for no threshold). `--since` is the days of silence, default 7. No new
|
|
376
|
+
event type, no new field: the report is a fold over what the journal already
|
|
377
|
+
holds, and it costs 0.5 ms over 10,000 events on top of the fold.
|
|
378
|
+
|
|
379
|
+
Two candidates were left out on purpose and the module says why. "Open task
|
|
380
|
+
with no owner" is the definition of a backlog. "Criteria added and never
|
|
381
|
+
checked" would fire on every task in any repository that configured a
|
|
382
|
+
Definition of Done, because `task add` copies it in unchecked — loudest in
|
|
383
|
+
the repositories that took our advice.
|
|
384
|
+
- **`prime` carries an attention line** — up to three tasks, and only when
|
|
385
|
+
there is something to say.
|
|
386
|
+
- **`task.label_added` / `task.label_removed`** — labels move as deltas. This
|
|
387
|
+
was the one field where "every intent is preserved" was untrue: `task.updated`
|
|
388
|
+
carried the whole array and the fold replaced the whole array, so two branches
|
|
389
|
+
adding different labels merged without a conflict and one label vanished with
|
|
390
|
+
no warning. Not a merge failure — a fold storing *state* in an event, the
|
|
391
|
+
exact mistake the product exists to avoid. Reasoning in
|
|
392
|
+
[ADR-013](docs/decisions/013-labels-as-deltas.md).
|
|
393
|
+
- **`labels` in `ready --json`**, the seventh field, and in the guaranteed set.
|
|
394
|
+
|
|
395
|
+
Source for this slice: a tech lead's feedback of 2026-09-10 — drift comes not
|
|
396
|
+
from where state lives but from work that stops being watched. Append-only
|
|
397
|
+
removes the drift between board and journal; it promises nothing about the
|
|
398
|
+
drift between journal and reality. The README now says so.
|
|
399
|
+
|
|
400
|
+
### Fixed before release
|
|
401
|
+
|
|
402
|
+
- **`compact` could destroy an archive it had already written.** A branch that
|
|
403
|
+
compacts a month, merges, and then delivers one more event for that month
|
|
404
|
+
gets a fresh month directory beside the archive — `append` files an event by
|
|
405
|
+
its own timestamp. The second compaction replaced the archive with the
|
|
406
|
+
directory's contents alone. Reproduced: three events became one, silently, in
|
|
407
|
+
a journal whose one promise is that nothing is lost. Archives are merged by
|
|
408
|
+
id now, and the dry run counts only what is not archived yet. The primitive
|
|
409
|
+
had been this way since 0.1; this release is what made it reachable.
|
|
410
|
+
- **A reopened task stayed `done` on the cumulative flow chart** and was not
|
|
411
|
+
counted as work in progress, because `task.reopened` moves a task without a
|
|
412
|
+
`task.moved`. Both now follow it.
|
|
413
|
+
- **A finished task went on accruing blocked days.** Work that is over is not
|
|
414
|
+
blocked, whatever its blocker is doing.
|
|
415
|
+
- **A started boundary that is not one of the columns inflated work in progress**
|
|
416
|
+
instead of emptying the report — the default state of every board that renames
|
|
417
|
+
`in_progress`, and the opposite of what `board config` warns.
|
|
418
|
+
- **`--started` was dropped when given with `--statuses` or `--dod`**, which is
|
|
419
|
+
the natural first command on a custom board. It travels in the same event now,
|
|
420
|
+
validated against the list being set.
|
|
421
|
+
- **The sprint report and `report flow` disagreed about what counts as started.**
|
|
422
|
+
A task that skipped the started column had a cycle time in one and no actual
|
|
423
|
+
hours in the other. One definition serves both.
|
|
424
|
+
- **A deferred `task.reopened` read the boundary as of the end of the fold**
|
|
425
|
+
rather than as of its own position in ULID order. Resolved the way claims and
|
|
426
|
+
criteria already are.
|
|
427
|
+
- `--started done` was accepted, `--since 007` became 7 before the parser saw
|
|
428
|
+
it, `--keep-months abc` reported "NaN", and nothing said the dates are UTC.
|
|
429
|
+
- **The "cold start" performance figure was a warm one.** The perf test removed
|
|
430
|
+
the state cache once, then took the best of three runs — the first run built
|
|
431
|
+
the cache and the next two read it, so "11 ms cold" was a 12 ms warm read.
|
|
432
|
+
Measured with the cache removed before every run: **199 ms cold when every
|
|
433
|
+
event is a separate file, 21 ms cold with a compacted archive.** The first of
|
|
434
|
+
those is the budget itself, with no room. The test now removes the cache per
|
|
435
|
+
iteration, the README carries the honest numbers, and the finding feeds the
|
|
436
|
+
reports work: compaction exists in the core but is not yet a command anyone
|
|
437
|
+
can run.
|
|
438
|
+
- **Two commands could write outside the repository.** `task doc add` and
|
|
439
|
+
`board export --file` both described containment in a comment and did not
|
|
440
|
+
enforce it: `../pwned.md` created a file in the parent directory, and the doc
|
|
441
|
+
link went into the append-only journal as a path meaningless on any other
|
|
442
|
+
machine. Both now resolve and verify before anything is written.
|
|
443
|
+
- **A task title containing the board end marker corrupted README.md**, and the
|
|
444
|
+
corruption grew by one copy on every export. The marker is now neutralised
|
|
445
|
+
where text enters, and the end marker is searched for after the begin marker
|
|
446
|
+
rather than from the top of the file.
|
|
447
|
+
- **A failed `gh issue list` read as "no issue exists"**, so a rate limit
|
|
448
|
+
created a duplicate and reported success. The lookup is now a value rather
|
|
449
|
+
than an absence, and runs once for the batch instead of once per task.
|
|
450
|
+
- **A batch that failed halfway said nothing about what was already
|
|
451
|
+
published**, and the JSON path discarded the partial record entirely. Both now
|
|
452
|
+
name it.
|
|
453
|
+
- **`@kadence/github` was never typechecked or built in CI** — nothing imports
|
|
454
|
+
its entry point, so the root config never saw it. That is why two of the bugs
|
|
455
|
+
above survived to review. CI now gates the package, and greps the core bundle
|
|
456
|
+
for network imports.
|
|
457
|
+
- Decisions and milestones vanished from an export of a board with no tasks yet.
|
|
458
|
+
- A `|` in a configured status broke the Markdown table.
|
|
459
|
+
- **Three event types were dropped instead of held pending.**
|
|
460
|
+
`milestone.task_added`, `milestone.closed` and `task.criterion_checked` were
|
|
461
|
+
handled as they arrived, so an event carrying a lower ULID than the entity it
|
|
462
|
+
names vanished — no `pending`, no `rejected`, no trace. Reachable for the same
|
|
463
|
+
reason claims are: clocks disagree between machines, so an assignment written
|
|
464
|
+
on a lagging clock sorts before the `task.created` it refers to. Milestone
|
|
465
|
+
events now go through the same defer path every task event uses, and criterion
|
|
466
|
+
checked-state is folded from the task's history, where the order is ULID order.
|
|
467
|
+
- **`completion install` could overwrite a file it did not write.** Every
|
|
468
|
+
generated script now carries a marker; a file without it is left alone and the
|
|
469
|
+
command says what is there, with `--force` for the deliberate case. Both target
|
|
470
|
+
directories are shared — a distribution package writes into the bash one.
|
|
471
|
+
- **The `milestone` field could carry a raw ULID** when the milestone was created
|
|
472
|
+
on a branch that has not merged. The contract promises `MS-N`; it is now `MS-N`
|
|
473
|
+
or `null`.
|
|
474
|
+
- `--branch` on a detached HEAD returned `invalid_argument` and exit 2. The flag
|
|
475
|
+
was understood; the repository state is what refuses, so it is
|
|
476
|
+
`conflicting_state` and exit 1.
|
|
477
|
+
- `--summary` omitted `contestedBy`, so a contested task read as a cleanly owned
|
|
478
|
+
one in the response large boards are steered towards.
|
|
479
|
+
- Acceptance criteria were reachable only through `task show`, which made
|
|
480
|
+
"which tasks have unchecked criteria" cost one call per task. `criteria` and
|
|
481
|
+
`openCriteria` are now on every task record.
|
|
482
|
+
|
|
483
|
+
### Notes
|
|
484
|
+
|
|
485
|
+
The plan set a target of 80 KB for `--summary` on a 1000-task board, taken from
|
|
486
|
+
Probe C. **That target was wrong, and the measurement says so:** a full board of
|
|
487
|
+
1000 tasks is 855 KB, `--summary` is 275 KB, and even the narrowest useful set
|
|
488
|
+
of three fields is 104 KB. Task titles are irreducible — 1000 of them do not fit
|
|
489
|
+
in 80 KB. The property the flag actually exists for is a different one and it
|
|
490
|
+
holds: a summary record costs the same whether the task carries one event or
|
|
491
|
+
five hundred, while the full record grows past fifty times the size. That is
|
|
492
|
+
what the test pins.
|
|
493
|
+
|
|
494
|
+
### Notes
|
|
495
|
+
|
|
496
|
+
The bundle grew from 35 KB to 71 KB across 0.4. The export templates sit in the
|
|
497
|
+
fast path rather than behind a dynamic import, and that was measured rather than
|
|
498
|
+
assumed: reading the whole bundle costs under a millisecond, and every
|
|
499
|
+
non-interactive command still runs in about 100 ms against a 200 ms budget. The
|
|
500
|
+
lazy-import rule exists for `blessed`, a heavy third-party dependency that reads
|
|
501
|
+
terminfo at runtime — not for 16 KB of our own strings. `blessed` is still absent
|
|
502
|
+
from `dist/cli.js`.
|
|
503
|
+
|
|
504
|
+
At release the build prints an 82 KB bundle, `npm pack` a 76 KB tarball with
|
|
505
|
+
233 KB unpacked, and `--version` returns in 60–70 ms on this machine; `blessed`
|
|
506
|
+
is still absent from `dist/cli.js`.
|
|
507
|
+
|
|
508
|
+
Claim state is folded from each task's own history rather than as events
|
|
509
|
+
arrive. It has to be: an event about a task whose `task.created` has not merged
|
|
510
|
+
is replayed after the main loop, so a claim carrying a lower ULID could be
|
|
511
|
+
applied last and hand the task to the wrong person. Reachable rather than
|
|
512
|
+
theoretical, because clocks disagree between machines.
|
|
513
|
+
|
|
514
|
+
The TUI additions were exercised by hand by the owner on 2026-09-13 before the
|
|
515
|
+
tag, as CLAUDE.md requires: four TUI bugs have shipped past a green suite in
|
|
516
|
+
this project, so a green suite alone is not evidence there.
|
|
517
|
+
|
|
3
518
|
## [0.3.2] — 2026-09-09
|
|
4
519
|
|
|
5
520
|
Nothing in the package changed. `src/`, `scripts/`, `package.json` and the
|