kadence 0.3.1 → 0.4.1
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 +374 -0
- package/README.md +90 -11
- package/dist/chunks/board-YUJ6VL5O.js +12 -0
- package/dist/chunks/chunk-CDGGOWGY.js +10 -0
- package/dist/chunks/chunk-FKPFXIL4.js +224 -0
- package/dist/chunks/ui-BZTPZE22.js +7 -0
- package/dist/cli.js +247 -60
- package/package.json +1 -1
- 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,379 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.4.1] — 2026-09-13
|
|
4
|
+
|
|
5
|
+
`0.4.0` was tagged and never published: these three were found by using it
|
|
6
|
+
before it reached anyone, so `0.4.1` is the first published 0.4. Nothing below
|
|
7
|
+
changes the shape of an event or renames a field in `kadence/v1`.
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **The fold dropped who wrote a comment or a history line.** Every event
|
|
12
|
+
carries `source` — `"human"` or `"agent"`, from `KADENCE_SOURCE` — and it is
|
|
13
|
+
validated on read, but the projection kept it only on decisions and notes. So
|
|
14
|
+
`task show` printed an agent's comment exactly like a person's, and
|
|
15
|
+
`task show --json` gave an agent no way to tell them apart: `actor` is the git
|
|
16
|
+
identity, which a person and their agent share. The one surface the product's
|
|
17
|
+
headline is about could not show the thing it claims. Comments and history
|
|
18
|
+
entries now carry `source`, `task show` marks an agent's line `[agent]` the
|
|
19
|
+
way `decision list` already did, and a note shows the mark it always knew.
|
|
20
|
+
The snapshot shape test now records nested records too — it is what should
|
|
21
|
+
have failed when `source` reached the event and not the comment. Cache version
|
|
22
|
+
bumped to `kadence-snapshot/11`.
|
|
23
|
+
- **`kadence ready` offered work that had already started.** The filter knew
|
|
24
|
+
about finished, blocked-by and claimed-by-others, and nothing about the board:
|
|
25
|
+
a task sitting in `in_review`, or parked in the `blocked` column, was listed
|
|
26
|
+
first with `kadence task claim KAD-1` printed under it. An agent following
|
|
27
|
+
`prime → ready → claim` took work somebody was reviewing. Ready now means
|
|
28
|
+
ready to *start* — anything in the started column or past it is left out,
|
|
29
|
+
using the board's own boundary, so a team that renames its columns keeps a
|
|
30
|
+
`ready` that means what its board means. When that is why the list is empty,
|
|
31
|
+
the message says so: `Nothing ready (2 already started)`.
|
|
32
|
+
- **The performance fixture moved all 500 tasks into `in_progress`**, so the
|
|
33
|
+
`ready` guardrail was timing a filter over an empty answer and asserting only
|
|
34
|
+
that it was fast. Every third move parks a task back in `todo` now; the
|
|
35
|
+
guardrail times a real result.
|
|
36
|
+
|
|
37
|
+
## [0.4.0] — 2026-09-13
|
|
38
|
+
|
|
39
|
+
Planned as slices 0.4.0 through 0.4.4 and published as one minor release.
|
|
40
|
+
|
|
41
|
+
The agent loop: four commands that answer "what next" without folding the whole
|
|
42
|
+
board by hand. Everything here is additive within `kadence/v1` — no field or
|
|
43
|
+
command was renamed, and `--json` responses gain keys rather than losing them.
|
|
44
|
+
|
|
45
|
+
### Added
|
|
46
|
+
|
|
47
|
+
- **`kadence ready`** — open tasks with no live blockers, not claimed by anyone
|
|
48
|
+
else, priority first and age second. `--json` returns five fields per task
|
|
49
|
+
rather than the whole record, because this goes into an agent's context on
|
|
50
|
+
every session. An empty result names the reason: how many are blocked, how
|
|
51
|
+
many belong to someone else. Measured at 9 ms over 10,000 events.
|
|
52
|
+
- **`kadence prime`** — the session preamble: active sprint and days left, your
|
|
53
|
+
claimed and in-progress work, how many tasks are ready, decisions in force by
|
|
54
|
+
title, the last five notes, and four commands to go deeper. Held to 40 lines
|
|
55
|
+
and 3 KB by a test; 16 lines and 493 bytes on this repository. It carries the
|
|
56
|
+
part that changes and points at the part that does not, because the static
|
|
57
|
+
half already lives in `AGENTS.md`.
|
|
58
|
+
- **`kadence init --hooks`** — adds a `SessionStart` hook running `kadence prime`
|
|
59
|
+
to `.claude/settings.json`. Only with the flag: that file belongs to the user
|
|
60
|
+
and is committed to their repository. The write is an upsert — hooks of other
|
|
61
|
+
events, other matchers and other commands survive, running it twice leaves one
|
|
62
|
+
hook, and a file that does not parse is reported rather than replaced.
|
|
63
|
+
- **`kadence task claim` / `task release`** — take a task, or give it back.
|
|
64
|
+
`claim` with no argument takes the top of `ready`, which is one step instead
|
|
65
|
+
of two. There is **no lock**, and the message says so: two machines can each
|
|
66
|
+
claim before either pushes. See [ADR-011](docs/decisions/011-claims-as-events.md).
|
|
67
|
+
- **`kadence note "text" [--task KAD-1]`** and `note list` — something learned
|
|
68
|
+
that was never a choice. Deliberately not a decision: no `--why`, no
|
|
69
|
+
`supersedes`, no number. The help says when to reach for `decision add`
|
|
70
|
+
instead. Notes show up in `task show` and in `prime`.
|
|
71
|
+
- **`claimedBy` and `contestedBy`** on the task record in `--json`, and a
|
|
72
|
+
`Claimed:` line in `task show`.
|
|
73
|
+
- **TUI**: `R` shows only what can be started now, `C` claims the selected task
|
|
74
|
+
or releases it if it is yours, and a card carries a claim mark — a separate
|
|
75
|
+
colour when the claim is contested. Both actions call the same functions the
|
|
76
|
+
CLI does.
|
|
77
|
+
|
|
78
|
+
### Changed
|
|
79
|
+
|
|
80
|
+
- **A contested claim is reported, not rejected.** When two people claim one
|
|
81
|
+
task, the earliest by ULID holds and the later one is kept, with the task
|
|
82
|
+
reading `contested` under both names. Rejecting the second would make the
|
|
83
|
+
owner depend on which branch merged first — the same invariant that keeps
|
|
84
|
+
dependency cycles and orphaned status columns alive rather than dropped. It is
|
|
85
|
+
the first conflict in kadence that names people rather than values.
|
|
86
|
+
- **The state cache version moved from 2 to 4.** The projected shape changed
|
|
87
|
+
twice: three claim fields on a task, then notes on the project state. Missing
|
|
88
|
+
exactly this bump is what shipped as a bug in 0.3.1, so it is now pinned to
|
|
89
|
+
the field lists by a test.
|
|
90
|
+
- The agent section written into `AGENTS.md` and `CLAUDE.md` grew by one line —
|
|
91
|
+
`kadence prime` first — and by no more than that.
|
|
92
|
+
|
|
93
|
+
### Added — the branch, the human, and the evidence behind done (0.4.1)
|
|
94
|
+
|
|
95
|
+
- **`kadence task list --branch`** — the work this branch introduced, measured
|
|
96
|
+
against `--base` (`main`, or `init.defaultBranch`). Membership is derived from
|
|
97
|
+
git at read time and never stored: writing a branch name into an event would
|
|
98
|
+
go stale the moment the branch is renamed or merged, and would make the same
|
|
99
|
+
events fold differently depending on where they were written. A detached HEAD
|
|
100
|
+
and an unknown base both fail by name rather than reporting no work.
|
|
101
|
+
The measurement that justified the flag, including what would make it wrong,
|
|
102
|
+
is in [branch-context-2026-09.md](docs/research/branch-context-2026-09.md).
|
|
103
|
+
- **Acceptance criteria** — `task ac add|check|uncheck|list`. The number is a
|
|
104
|
+
position in the folded list, assigned like `KAD-N` and never stored, so two
|
|
105
|
+
branches can each add a criterion and merge without renumbering. Moving a task
|
|
106
|
+
to `done` with unchecked criteria **warns and proceeds**: the checklist is
|
|
107
|
+
evidence, not a gate, and refusing would make the board lie about where the
|
|
108
|
+
work is.
|
|
109
|
+
- **Definition of done** — `board config --dod "tests green,docs updated"`. The
|
|
110
|
+
criteria are **copied** into each new task, never referenced, so raising the
|
|
111
|
+
standard cannot reach back and change what finished work had promised.
|
|
112
|
+
`task add --no-dod` skips them.
|
|
113
|
+
- **Milestones** — `milestone create|add|list|close`. Grouping by outcome, where
|
|
114
|
+
an epic groups by structure and a sprint by time. Not a third hierarchy: a
|
|
115
|
+
task carries at most one, as a field beside `sprint`. `MS-N` is derived from
|
|
116
|
+
ULID order like every other label, and progress is counted in points, the same
|
|
117
|
+
unit as velocity and burndown.
|
|
118
|
+
- **`kadence stats`** — counts by status and assignee, open blockers, contested
|
|
119
|
+
claims, and the velocity of the last three closed sprints.
|
|
120
|
+
- **`kadence completion install`** — zsh, bash and fish, generated from one list
|
|
121
|
+
rather than three hand-written scripts. Without a terminal it prints the
|
|
122
|
+
script instead of writing, because that is how people pipe it into a file.
|
|
123
|
+
For zsh it writes the file and prints the `fpath` line rather than editing a
|
|
124
|
+
shell rc, which is the user's file.
|
|
125
|
+
- **`board --json --summary`** — the column state without `history` and
|
|
126
|
+
`comments`. Those are the two fields that grow with how long a task has been
|
|
127
|
+
worked on rather than with how many tasks there are.
|
|
128
|
+
- **TUI**: `b` shows only what the branch introduced, `M` filters to one
|
|
129
|
+
milestone, and the card dialog carries the acceptance-criteria checklist with
|
|
130
|
+
space to toggle. Every toggle calls the same command the CLI does.
|
|
131
|
+
|
|
132
|
+
### Changed
|
|
133
|
+
|
|
134
|
+
- `task list --json` and `board --json` gain a `milestone` field on each task,
|
|
135
|
+
carrying the `MS-N` label. The response without `--summary` is otherwise
|
|
136
|
+
unchanged; the contract only ever gains.
|
|
137
|
+
- The state cache version moved again with the projected shape.
|
|
138
|
+
|
|
139
|
+
### Added — reports, and the two things they needed first (0.4.3)
|
|
140
|
+
|
|
141
|
+
What the neighbours call reports is, here, a fold over timestamps the journal
|
|
142
|
+
already carries. The reasoning, the sources and the verdict per report are in
|
|
143
|
+
[reports-discovery-2026-09.md](docs/research/reports-discovery-2026-09.md).
|
|
144
|
+
Velocity and cycle time stay out of the headline; they are a consequence, served
|
|
145
|
+
to the person who asks.
|
|
146
|
+
|
|
147
|
+
- **`kadence report flow`** — the four flow metrics the Kanban Guide mandates,
|
|
148
|
+
and what sits around them: work in progress, throughput per ISO week, cycle /
|
|
149
|
+
lead / response time as **p50 / p85 / p95 in calendar days**, aging work
|
|
150
|
+
against the p85, created versus resolved, and days spent blocked. There is no
|
|
151
|
+
mean anywhere, on purpose. Every response names the window and the column
|
|
152
|
+
work counts as started from, so a number is never quoted without its terms.
|
|
153
|
+
Empty sections say why they are empty.
|
|
154
|
+
- **`kadence report cfd`** — tasks per column at the end of every day of the
|
|
155
|
+
window. A move backwards subtracts. The first version re-walked each task's
|
|
156
|
+
history per day and cost 150 ms on 10,000 events; it is a single pass now, at
|
|
157
|
+
4 ms.
|
|
158
|
+
- **`kadence board config --started <status>`** — the column cycle time and a
|
|
159
|
+
sprint's actual hours are measured from. It was the literal `in_progress` in
|
|
160
|
+
three places while columns were configurable, so a board named
|
|
161
|
+
`todo,doing,review,done` closed every sprint with `actualHours: null` and
|
|
162
|
+
never said why. It is a folded setting now, validated against the live
|
|
163
|
+
columns, and a column change that strands it warns instead of going quiet.
|
|
164
|
+
- **`kadence compact`** — folds months older than `--keep-months` into one
|
|
165
|
+
archive file each. The primitive existed since 0.1 and was reachable only from
|
|
166
|
+
tests. `--dry-run` says what would move and writes nothing. It touches no git
|
|
167
|
+
state, and it says the one thing worth knowing: an archived month is a single
|
|
168
|
+
file, so compact on one branch and merge before compacting on another.
|
|
169
|
+
|
|
170
|
+
### Added — three experiments (0.4.2)
|
|
171
|
+
|
|
172
|
+
Each of these was a standing "no". Each is now tried in the shape that survives
|
|
173
|
+
the three constraints, and each has a kill condition written before the code, in
|
|
174
|
+
[feature-adoption-2026-09.md](docs/product/feature-adoption-2026-09.md).
|
|
175
|
+
|
|
176
|
+
- **`kadence board export --html`** — the board, the sprint, the burndown,
|
|
177
|
+
milestones and decisions in force, as **one self-contained file**. No script
|
|
178
|
+
tag, no stylesheet link, no font, no image: the page asks the network for
|
|
179
|
+
nothing and opens from disk. That is what makes it an experiment in place of a
|
|
180
|
+
web UI rather than a web UI with extra steps. A test asserts the absence, not
|
|
181
|
+
the appearance. Acceptance criteria are printed in full rather than counted,
|
|
182
|
+
because a file attached to a pull request is evidence and "2/3" is not.
|
|
183
|
+
- **`kadence board export --md`**, and `--readme` to update the section between
|
|
184
|
+
markers in an existing README. It will not create one — that would be a
|
|
185
|
+
different command and a bigger promise.
|
|
186
|
+
- **`@kadence/github`** — a separate package that publishes tasks to GitHub
|
|
187
|
+
Issues **in one direction**, by shelling out to `gh`. The core is unchanged
|
|
188
|
+
and still opens no socket; the credential stays with a tool the user already
|
|
189
|
+
trusts. A marker in the issue body makes a second publish an edit rather than
|
|
190
|
+
a duplicate, and the issue itself says that edits made there are overwritten.
|
|
191
|
+
Nothing is ever read back. See
|
|
192
|
+
[ADR-012](docs/decisions/012-network-only-in-packages.md), written before the
|
|
193
|
+
code. Every test runs against a recorded `gh`, never the real one.
|
|
194
|
+
- **`kadence task doc add KAD-1 docs/design.md`** — creates the file from a
|
|
195
|
+
four-line template and records the link in one call. It never overwrites: a
|
|
196
|
+
file that is already there is the document, and replacing it with a template
|
|
197
|
+
would destroy the thing being linked.
|
|
198
|
+
|
|
199
|
+
### Added — attention signals, and labels that survive a merge (0.4.4)
|
|
200
|
+
|
|
201
|
+
- **`kadence report attention`** — work the board presents as active while
|
|
202
|
+
nobody is moving it. Four signals, one definition: `stalled` (no event for N
|
|
203
|
+
days), `unowned` (in flight for N days with no assignee and no claim),
|
|
204
|
+
`stale_claim` (a claim older than N days with no move since), and
|
|
205
|
+
`dead_blocker` (`blockedBy` pointing at a finished task — the one signal that
|
|
206
|
+
waits for no threshold). `--since` is the days of silence, default 7. No new
|
|
207
|
+
event type, no new field: the report is a fold over what the journal already
|
|
208
|
+
holds, and it costs 0.5 ms over 10,000 events on top of the fold.
|
|
209
|
+
|
|
210
|
+
Two candidates were left out on purpose and the module says why. "Open task
|
|
211
|
+
with no owner" is the definition of a backlog. "Criteria added and never
|
|
212
|
+
checked" would fire on every task in any repository that configured a
|
|
213
|
+
Definition of Done, because `task add` copies it in unchecked — loudest in
|
|
214
|
+
the repositories that took our advice.
|
|
215
|
+
- **`prime` carries an attention line** — up to three tasks, and only when
|
|
216
|
+
there is something to say.
|
|
217
|
+
- **`task.label_added` / `task.label_removed`** — labels move as deltas. This
|
|
218
|
+
was the one field where "every intent is preserved" was untrue: `task.updated`
|
|
219
|
+
carried the whole array and the fold replaced the whole array, so two branches
|
|
220
|
+
adding different labels merged without a conflict and one label vanished with
|
|
221
|
+
no warning. Not a merge failure — a fold storing *state* in an event, the
|
|
222
|
+
exact mistake the product exists to avoid. Reasoning in
|
|
223
|
+
[ADR-013](docs/decisions/013-labels-as-deltas.md).
|
|
224
|
+
- **`labels` in `ready --json`**, the seventh field, and in the guaranteed set.
|
|
225
|
+
|
|
226
|
+
Source for this slice: a tech lead's feedback of 2026-09-10 — drift comes not
|
|
227
|
+
from where state lives but from work that stops being watched. Append-only
|
|
228
|
+
removes the drift between board and journal; it promises nothing about the
|
|
229
|
+
drift between journal and reality. The README now says so.
|
|
230
|
+
|
|
231
|
+
### Fixed before release
|
|
232
|
+
|
|
233
|
+
- **`compact` could destroy an archive it had already written.** A branch that
|
|
234
|
+
compacts a month, merges, and then delivers one more event for that month
|
|
235
|
+
gets a fresh month directory beside the archive — `append` files an event by
|
|
236
|
+
its own timestamp. The second compaction replaced the archive with the
|
|
237
|
+
directory's contents alone. Reproduced: three events became one, silently, in
|
|
238
|
+
a journal whose one promise is that nothing is lost. Archives are merged by
|
|
239
|
+
id now, and the dry run counts only what is not archived yet. The primitive
|
|
240
|
+
had been this way since 0.1; this release is what made it reachable.
|
|
241
|
+
- **A reopened task stayed `done` on the cumulative flow chart** and was not
|
|
242
|
+
counted as work in progress, because `task.reopened` moves a task without a
|
|
243
|
+
`task.moved`. Both now follow it.
|
|
244
|
+
- **A finished task went on accruing blocked days.** Work that is over is not
|
|
245
|
+
blocked, whatever its blocker is doing.
|
|
246
|
+
- **A started boundary that is not one of the columns inflated work in progress**
|
|
247
|
+
instead of emptying the report — the default state of every board that renames
|
|
248
|
+
`in_progress`, and the opposite of what `board config` warns.
|
|
249
|
+
- **`--started` was dropped when given with `--statuses` or `--dod`**, which is
|
|
250
|
+
the natural first command on a custom board. It travels in the same event now,
|
|
251
|
+
validated against the list being set.
|
|
252
|
+
- **The sprint report and `report flow` disagreed about what counts as started.**
|
|
253
|
+
A task that skipped the started column had a cycle time in one and no actual
|
|
254
|
+
hours in the other. One definition serves both.
|
|
255
|
+
- **A deferred `task.reopened` read the boundary as of the end of the fold**
|
|
256
|
+
rather than as of its own position in ULID order. Resolved the way claims and
|
|
257
|
+
criteria already are.
|
|
258
|
+
- `--started done` was accepted, `--since 007` became 7 before the parser saw
|
|
259
|
+
it, `--keep-months abc` reported "NaN", and nothing said the dates are UTC.
|
|
260
|
+
- **The "cold start" performance figure was a warm one.** The perf test removed
|
|
261
|
+
the state cache once, then took the best of three runs — the first run built
|
|
262
|
+
the cache and the next two read it, so "11 ms cold" was a 12 ms warm read.
|
|
263
|
+
Measured with the cache removed before every run: **199 ms cold when every
|
|
264
|
+
event is a separate file, 21 ms cold with a compacted archive.** The first of
|
|
265
|
+
those is the budget itself, with no room. The test now removes the cache per
|
|
266
|
+
iteration, the README carries the honest numbers, and the finding feeds the
|
|
267
|
+
reports work: compaction exists in the core but is not yet a command anyone
|
|
268
|
+
can run.
|
|
269
|
+
- **Two commands could write outside the repository.** `task doc add` and
|
|
270
|
+
`board export --file` both described containment in a comment and did not
|
|
271
|
+
enforce it: `../pwned.md` created a file in the parent directory, and the doc
|
|
272
|
+
link went into the append-only journal as a path meaningless on any other
|
|
273
|
+
machine. Both now resolve and verify before anything is written.
|
|
274
|
+
- **A task title containing the board end marker corrupted README.md**, and the
|
|
275
|
+
corruption grew by one copy on every export. The marker is now neutralised
|
|
276
|
+
where text enters, and the end marker is searched for after the begin marker
|
|
277
|
+
rather than from the top of the file.
|
|
278
|
+
- **A failed `gh issue list` read as "no issue exists"**, so a rate limit
|
|
279
|
+
created a duplicate and reported success. The lookup is now a value rather
|
|
280
|
+
than an absence, and runs once for the batch instead of once per task.
|
|
281
|
+
- **A batch that failed halfway said nothing about what was already
|
|
282
|
+
published**, and the JSON path discarded the partial record entirely. Both now
|
|
283
|
+
name it.
|
|
284
|
+
- **`@kadence/github` was never typechecked or built in CI** — nothing imports
|
|
285
|
+
its entry point, so the root config never saw it. That is why two of the bugs
|
|
286
|
+
above survived to review. CI now gates the package, and greps the core bundle
|
|
287
|
+
for network imports.
|
|
288
|
+
- Decisions and milestones vanished from an export of a board with no tasks yet.
|
|
289
|
+
- A `|` in a configured status broke the Markdown table.
|
|
290
|
+
- **Three event types were dropped instead of held pending.**
|
|
291
|
+
`milestone.task_added`, `milestone.closed` and `task.criterion_checked` were
|
|
292
|
+
handled as they arrived, so an event carrying a lower ULID than the entity it
|
|
293
|
+
names vanished — no `pending`, no `rejected`, no trace. Reachable for the same
|
|
294
|
+
reason claims are: clocks disagree between machines, so an assignment written
|
|
295
|
+
on a lagging clock sorts before the `task.created` it refers to. Milestone
|
|
296
|
+
events now go through the same defer path every task event uses, and criterion
|
|
297
|
+
checked-state is folded from the task's history, where the order is ULID order.
|
|
298
|
+
- **`completion install` could overwrite a file it did not write.** Every
|
|
299
|
+
generated script now carries a marker; a file without it is left alone and the
|
|
300
|
+
command says what is there, with `--force` for the deliberate case. Both target
|
|
301
|
+
directories are shared — a distribution package writes into the bash one.
|
|
302
|
+
- **The `milestone` field could carry a raw ULID** when the milestone was created
|
|
303
|
+
on a branch that has not merged. The contract promises `MS-N`; it is now `MS-N`
|
|
304
|
+
or `null`.
|
|
305
|
+
- `--branch` on a detached HEAD returned `invalid_argument` and exit 2. The flag
|
|
306
|
+
was understood; the repository state is what refuses, so it is
|
|
307
|
+
`conflicting_state` and exit 1.
|
|
308
|
+
- `--summary` omitted `contestedBy`, so a contested task read as a cleanly owned
|
|
309
|
+
one in the response large boards are steered towards.
|
|
310
|
+
- Acceptance criteria were reachable only through `task show`, which made
|
|
311
|
+
"which tasks have unchecked criteria" cost one call per task. `criteria` and
|
|
312
|
+
`openCriteria` are now on every task record.
|
|
313
|
+
|
|
314
|
+
### Notes
|
|
315
|
+
|
|
316
|
+
The plan set a target of 80 KB for `--summary` on a 1000-task board, taken from
|
|
317
|
+
Probe C. **That target was wrong, and the measurement says so:** a full board of
|
|
318
|
+
1000 tasks is 855 KB, `--summary` is 275 KB, and even the narrowest useful set
|
|
319
|
+
of three fields is 104 KB. Task titles are irreducible — 1000 of them do not fit
|
|
320
|
+
in 80 KB. The property the flag actually exists for is a different one and it
|
|
321
|
+
holds: a summary record costs the same whether the task carries one event or
|
|
322
|
+
five hundred, while the full record grows past fifty times the size. That is
|
|
323
|
+
what the test pins.
|
|
324
|
+
|
|
325
|
+
### Notes
|
|
326
|
+
|
|
327
|
+
The bundle grew from 35 KB to 71 KB across 0.4. The export templates sit in the
|
|
328
|
+
fast path rather than behind a dynamic import, and that was measured rather than
|
|
329
|
+
assumed: reading the whole bundle costs under a millisecond, and every
|
|
330
|
+
non-interactive command still runs in about 100 ms against a 200 ms budget. The
|
|
331
|
+
lazy-import rule exists for `blessed`, a heavy third-party dependency that reads
|
|
332
|
+
terminfo at runtime — not for 16 KB of our own strings. `blessed` is still absent
|
|
333
|
+
from `dist/cli.js`.
|
|
334
|
+
|
|
335
|
+
At release the build prints an 82 KB bundle, `npm pack` a 76 KB tarball with
|
|
336
|
+
233 KB unpacked, and `--version` returns in 60–70 ms on this machine; `blessed`
|
|
337
|
+
is still absent from `dist/cli.js`.
|
|
338
|
+
|
|
339
|
+
Claim state is folded from each task's own history rather than as events
|
|
340
|
+
arrive. It has to be: an event about a task whose `task.created` has not merged
|
|
341
|
+
is replayed after the main loop, so a claim carrying a lower ULID could be
|
|
342
|
+
applied last and hand the task to the wrong person. Reachable rather than
|
|
343
|
+
theoretical, because clocks disagree between machines.
|
|
344
|
+
|
|
345
|
+
The TUI additions were exercised by hand by the owner on 2026-09-13 before the
|
|
346
|
+
tag, as CLAUDE.md requires: four TUI bugs have shipped past a green suite in
|
|
347
|
+
this project, so a green suite alone is not evidence there.
|
|
348
|
+
|
|
349
|
+
## [0.3.2] — 2026-09-09
|
|
350
|
+
|
|
351
|
+
Nothing in the package changed. `src/`, `scripts/`, `package.json` and the
|
|
352
|
+
lockfile are identical to 0.3.1 — the diff across all four is empty — so the code
|
|
353
|
+
you get is the code you already have, and there is no behavioural reason to take
|
|
354
|
+
this one.
|
|
355
|
+
|
|
356
|
+
What changed is the pipeline that produces it. From here on the tarball is built
|
|
357
|
+
in a job that does not execute dependency install scripts and that pins every
|
|
358
|
+
action it runs to a commit rather than a tag.
|
|
359
|
+
|
|
360
|
+
### Security
|
|
361
|
+
|
|
362
|
+
- **Dependency lifecycle scripts no longer run in CI or on release.** `npm ci`
|
|
363
|
+
passes `--ignore-scripts` in both workflows. A postinstall runs with the full
|
|
364
|
+
rights of the job, which is the primary npm supply-chain vector. Exactly one
|
|
365
|
+
dependency here declares one — esbuild — and in 0.28 the platform binary
|
|
366
|
+
arrives through optionalDependencies, so the script has nothing left to do.
|
|
367
|
+
Measured rather than assumed: clean install, 35 KB bundle, full suite green on
|
|
368
|
+
Node 20 and 22.
|
|
369
|
+
- **Both workflows pin `actions/checkout` and `actions/setup-node` to commit
|
|
370
|
+
SHAs**, with the version in a trailing comment. A tag is mutable: `@v4` is
|
|
371
|
+
whatever its owner last moved it to, and it runs with the job's permissions.
|
|
372
|
+
|
|
373
|
+
Publishing is unaffected: `--ignore-scripts` governs installing dependencies, not
|
|
374
|
+
this package's own lifecycle, so `prepublishOnly` still gates every release on
|
|
375
|
+
typecheck, tests and build.
|
|
376
|
+
|
|
3
377
|
## [0.3.1] — 2026-09-08
|
|
4
378
|
|
|
5
379
|
A patch, and the reason to take it is the fix: anyone already on 0.3.0 has a
|
package/README.md
CHANGED
|
@@ -76,7 +76,15 @@ journal is append-only, one file per event.
|
|
|
76
76
|
keeps every step, so «how did we get here» has an answer.
|
|
77
77
|
|
|
78
78
|
State is still there when you want it — it is folded from the journal on read,
|
|
79
|
-
which is why the board can never drift from
|
|
79
|
+
which is why the board can never drift from the journal. No one maintains a
|
|
80
|
+
column by hand, so no column can be stale in the way a task file can.
|
|
81
|
+
|
|
82
|
+
**What that does not buy you.** A journal records what was written to it. If a
|
|
83
|
+
piece of work stops being touched, the board stays perfectly accurate and
|
|
84
|
+
perfectly uninformative about it — the same way `git log` is honest about a
|
|
85
|
+
branch nobody pushed to. kadence removes the drift between what is shown and
|
|
86
|
+
what was recorded. It cannot remove the drift between what was recorded and
|
|
87
|
+
what is actually happening; that still takes someone looking.
|
|
80
88
|
|
|
81
89
|
---
|
|
82
90
|
|
|
@@ -104,11 +112,69 @@ kadence init
|
|
|
104
112
|
|
|
105
113
|
kadence sprint create "Sprint 14"
|
|
106
114
|
kadence task add "Fix login" -d "Broken since 2.3" --type bug --priority high --estimate 3
|
|
115
|
+
kadence task ac add KAD-1 "tests green"
|
|
107
116
|
kadence task comment KAD-1 "Session cookie is fine — the redirect drops it."
|
|
108
117
|
kadence task move KAD-1 done
|
|
109
118
|
kadence sprint close
|
|
110
119
|
```
|
|
111
120
|
|
|
121
|
+
**When several people — or several agents — work the same board:**
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
kadence ready # open, unblocked, nobody else's
|
|
125
|
+
kadence task claim # take the top of that list, in one step
|
|
126
|
+
kadence task release KAD-1
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
There is no lock, and the message says so. Two machines can each claim before
|
|
130
|
+
either pushes; the merge keeps both claims and the task reads `contested` with
|
|
131
|
+
both names. Refusing the second one would make the owner depend on which branch
|
|
132
|
+
merged first, and that is the property the whole design rests on.
|
|
133
|
+
|
|
134
|
+
**Grouping, and the evidence behind "done":**
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
kadence milestone create "1.0" --due 2026-12-01
|
|
138
|
+
kadence milestone add KAD-1 --milestone 1.0
|
|
139
|
+
kadence board config --dod "tests green,docs updated" # every new task starts with these
|
|
140
|
+
kadence task ac check KAD-1 1
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Moving a task to `done` with unchecked criteria **warns and carries on**. The
|
|
144
|
+
checklist is evidence, not a gate: what `done` costs is the team's call, not the
|
|
145
|
+
tool's.
|
|
146
|
+
|
|
147
|
+
**A snapshot somebody can open, without running anything:**
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
kadence board export --html # one self-contained file: no server, no network
|
|
151
|
+
kadence board export --md --readme
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**When someone asks how long things take** — the answer is a fold over the same
|
|
155
|
+
journal, in the Kanban Guide's own terms:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
kadence board config --started doing # the column cycle time counts from, if it is not in_progress
|
|
159
|
+
kadence report flow # p50 / p85 / p95 in calendar days, WIP, aging work, blocked days
|
|
160
|
+
kadence report cfd # tasks per column, per day
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
No averages, on purpose. Every line names the window and the column it measured
|
|
164
|
+
from. Velocity is not the pitch here and neither is cycle time; they are what the
|
|
165
|
+
journal happens to know.
|
|
166
|
+
|
|
167
|
+
**And what the branch you are on is actually about:**
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
kadence task list --branch
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Nothing is stored for that: which tasks belong to a branch lives in git's
|
|
174
|
+
history and is read when you ask. It narrows the answer between three and
|
|
175
|
+
twenty times, [measured](docs/research/branch-context-2026-09.md) on real board
|
|
176
|
+
sizes.
|
|
177
|
+
|
|
112
178
|
**The board, when you want to look at it:**
|
|
113
179
|
|
|
114
180
|
```
|
|
@@ -119,7 +185,7 @@ $ kadence ui
|
|
|
119
185
|
| ^# KAD-1 Auth epic || . KAD-4 Tokens @dev||!! KAD-7 Crash [] || v KAD-2 Export |
|
|
120
186
|
| * KAD-3 Login form || || || v KAD-5 Docs |
|
|
121
187
|
+---------------------++--------------------++--------------------++--------------------+
|
|
122
|
-
arrows move enter details m status a assign e edit s sprint
|
|
188
|
+
arrows move enter details m status a assign e edit s sprint R ready b branch q quit
|
|
123
189
|
```
|
|
124
190
|
|
|
125
191
|
Keyboard, mouse, drag between columns, every field editable in place. It calls
|
|
@@ -149,6 +215,18 @@ from state changes the team already made.
|
|
|
149
215
|
Files first. Every command speaks `--json`, every response carries
|
|
150
216
|
`schema: "kadence/v1"`, stdout is JSON and nothing else, warnings go to stderr.
|
|
151
217
|
|
|
218
|
+
Start a session with one command:
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
kadence prime # active sprint, your work, what is ready, decisions in force
|
|
222
|
+
kadence ready --json # five fields per task, not the whole record
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
`prime` is held to forty lines and three kilobytes by a test, because it is paid
|
|
226
|
+
for on every turn that follows. `kadence init --hooks` will add it as a
|
|
227
|
+
`SessionStart` hook — only with the flag, because `.claude/settings.json` is
|
|
228
|
+
yours.
|
|
229
|
+
|
|
152
230
|
```bash
|
|
153
231
|
kadence board --json
|
|
154
232
|
kadence task show KAD-1 --json # full history and comments
|
|
@@ -209,11 +287,11 @@ either moves both or changes nothing. A typo does not leave half a board.
|
|
|
209
287
|
|
|
210
288
|
| | |
|
|
211
289
|
|---|---|
|
|
212
|
-
| Install |
|
|
213
|
-
| Startup |
|
|
214
|
-
| 10,000 events |
|
|
215
|
-
| Journal on disk |
|
|
216
|
-
| One task, as an agent reads it |
|
|
290
|
+
| Install | 76 KB packed — 233 KB of kadence, plus 1.8 MB of blessed |
|
|
291
|
+
| Startup | 65 ms |
|
|
292
|
+
| 10,000 events | 21 ms cold with a compacted archive, 12 ms warm — 199 ms cold if every event is still a separate file |
|
|
293
|
+
| Journal on disk | under 5 MB |
|
|
294
|
+
| One task, as an agent reads it | 982 bytes, or 304 with `--summary` — the same however long the task has been worked on |
|
|
217
295
|
|
|
218
296
|
These are tests. They fail the build on regression, which is why they are still
|
|
219
297
|
true.
|
|
@@ -224,7 +302,7 @@ true.
|
|
|
224
302
|
|
|
225
303
|
**Verified.** The merge thesis, on real git branches. Performance and size, by
|
|
226
304
|
tests that fail if they regress. That the conflict problem exists in the wild —
|
|
227
|
-
measured, not assumed.
|
|
305
|
+
measured, not assumed. 898 tests, including an end-to-end run through the
|
|
228
306
|
installed binary.
|
|
229
307
|
|
|
230
308
|
**Not verified.** That teams and their AI agents actually lose enough context to want
|
|
@@ -256,8 +334,9 @@ unit-tested.
|
|
|
256
334
|
```
|
|
257
335
|
|
|
258
336
|
Every command appends one event. State is folded from the journal on read, so
|
|
259
|
-
the board cannot drift from
|
|
260
|
-
different files, and git merges them
|
|
337
|
+
the board cannot drift from the journal — nothing is maintained by hand. Two
|
|
338
|
+
branches writing at once produce two different files, and git merges them
|
|
339
|
+
without a conflict by construction.
|
|
261
340
|
|
|
262
341
|
Design decisions, each recording what was measured and what would make us
|
|
263
342
|
revisit it: [docs/decisions/](docs/decisions/).
|
|
@@ -266,7 +345,7 @@ revisit it: [docs/decisions/](docs/decisions/).
|
|
|
266
345
|
|
|
267
346
|
```bash
|
|
268
347
|
npm install
|
|
269
|
-
npm test #
|
|
348
|
+
npm test # 898 tests
|
|
270
349
|
npm run build # 35 KB bundle
|
|
271
350
|
```
|
|
272
351
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{p as ee}from"./chunk-CDGGOWGY.js";import S from"blessed";var u={border:"gray",borderFocus:"cyan",headerFg:"white",headerBg:"blue",selectedFg:"black",selectedBg:"cyan",dim:"gray",hint:"gray",warn:"yellow",danger:"red"},he={backlog:"gray",todo:"white",in_progress:"blue",doing:"blue",blocked:"red",in_review:"magenta",review:"magenta",done:"green",shipped:"green"};function te(n){return he[n]??"white"}var pe={urgent:"\u203C",high:"\u2191",normal:" ",low:"\u2193"},me={urgent:"red",high:"yellow",normal:"white",low:"gray"},ye={bug:"\u2716",story:"\u25C6",epic:"\u2B22",task:"\xB7"};function k(n,o){return`{${o}-fg}${n}{/}`}function q(n,o){let d=k(pe[n.priority],me[n.priority]),h=k(ye[n.type]??"\xB7","gray"),f=k(n.label,"cyan"),c=[];n.blockedBy.length>0&&c.push(k("\u2298","red")),n.contestedBy.length>0?c.push(k("\u2691","magenta")):n.claimedBy!==null&&c.push(k("\u2713","green"));let E=n.criteria.filter(L=>!L.checked).length;if(E>0?c.push(k(`${n.criteria.length-E}/${n.criteria.length}`,"yellow")):n.criteria.length>0&&c.push(k("\u2611","green")),n.comments.length>0&&c.push(k("\u{1F4AC}","gray")),n.due!==null){let L=n.due<new Date().toISOString().slice(0,10);c.push(k(L?"\u23F0":"\u{1F4C5}",L?"red":"gray"))}let C=[n.assignee!==null?k(`@${n.assignee.split("@")[0]}`,"gray"):"",n.estimate!==null?k(String(n.estimate),"yellow"):"",c.join("")].filter(L=>L.length>0).join(" "),p=`${N(d)}${N(h)} ${N(f)} `,y=C.length>0?` ${N(C)}`:"",T=Math.max(4,o-p.length-y.length),O=[...n.title],U=O.length>T?`${O.slice(0,T-1).join("")}\u2026`:n.title;return`${d}${h} ${f} ${U}${C.length>0?` ${C}`:""}`}function G(n,o){return o?`{cyan-bg}{black-fg}${N(n)}{/}`:` ${n}`}function W(n,o,d,h=12){let f=`${n.padEnd(h)} ${o}`;return d?`{cyan-bg}{black-fg}\u25B8 ${f}{/}`:` ${f}`}function N(n){return n.replace(/\{[^}]*\}/g,"")}var ne=["\u2190\u2192\u2191\u2193 move","enter details","[ ] shift","m status","a assign","c comment","e edit","n new","s sprint","/ filter","R ready","b branch","M milestone","C claim","? help","q quit (Ctrl-C forces)"].join(" ");function ie(n){let o=[],d=!1;function h(){d=!0,setImmediate(()=>{d=!1})}return{dispatch(f){if(d)return;let c=o[o.length-1];c!==void 0?c(f):n(f)},push(f){o.push(f),h();let c=!1;return()=>{if(c)return;c=!0;let E=o.lastIndexOf(f);E!==-1&&o.splice(E,1),h()}},isDialogOpen:()=>o.length>0,depth:()=>o.length}}function se(n){if(n.ch==="")return!1;let o=n.ch.charCodeAt(0);return o<32||o===127}var be=["up","down","pageup","pagedown","home","end"],re=["escape","q"];function z(n){return be.includes(n.name)}function oe(n){return re.includes(n.name)||re.includes(n.ch)}function _(n,o,d){return d===0?0:(n+o+d)%d}var le="cancelled";function Se(n){let o=S.screen({smartCSR:!0,title:"kadence",fullUnicode:!0,mouse:!0}),{state:d}=n.reload(),h=n.branchEventIds(),f="",c=!1,E=!1,C="",p=[],y=0,T=ie(t=>ce(t));function O(t=0){try{o.destroy()}catch{}process.exit(t)}process.on("SIGINT",()=>O(0)),process.on("SIGTERM",()=>O(0)),o.program.on("keypress",(t,r)=>{let i={ch:t??"",name:r?.name??""};(i.name==="C-c"||i.ch==="")&&O(0),!(se(i)&&i.name!=="enter"&&i.name!=="escape")&&T.dispatch(i)});let U=S.box({parent:o,top:0,height:1,width:"100%",tags:!0,style:{fg:u.headerFg,bg:u.headerBg}}),L=S.box({parent:o,bottom:0,height:1,width:"100%",tags:!0,content:` ${ne}`,style:{fg:u.hint}}),J=S.box({parent:o,bottom:1,height:1,width:"100%",tags:!0,style:{fg:u.warn}});function b(t,r=u.warn){J.setContent(` ${t}`),J.style.fg=r,o.render()}function A(){let t=d.statuses.filter(i=>i!==le),r=d.orphanStatuses.filter(i=>i!==le);return[...t,...r]}function ae(t){let r=f.toLowerCase(),i=c?new Set(ee(d.tasks,{viewer:n.actor,statuses:d.statuses,started:d.started}).map(s=>s.id)):null,e=E&&!("reason"in h)?h:null,l=e===null?null:s=>e.ids.has(s.id)||s.history.some(w=>e.ids.has(w.id)),a=C===""?null:d.milestones.find(s=>s.label===C)?.id??null;return d.tasks.filter(s=>s.status===t&&(i===null||i.has(s.id))&&(l===null||l(s))&&(a===null||s.milestone===a)&&(r===""||s.title.toLowerCase().includes(r)||(s.assignee??"").toLowerCase().includes(r)||s.labels.some(w=>w.toLowerCase().includes(r))))}function Q(){for(let i of p)i.box.destroy();p=[];let t=A(),r=Math.max(Math.floor(100/Math.max(t.length,1)),12);t.forEach((i,e)=>{let l=S.box({parent:o,top:1,left:`${e*r}%`,width:`${r}%`,bottom:2,label:` ${i} `,border:{type:"line"},style:{border:{fg:u.border},label:{fg:te(i)}}}),a=S.list({parent:l,top:0,left:0,right:0,bottom:0,keys:!1,mouse:!1,interactive:!1,tags:!0,scrollable:!0,style:{selected:{fg:u.selectedFg,bg:u.selectedBg},item:{fg:"white"}}});p.push({status:i,box:l,list:a,tasks:[],cursor:0})})}function m(t=!1){t&&(d=n.reload().state,h=n.branchEventIds()),p.length!==A().length&&Q();let r=0,i=0;p.forEach((s,w)=>{s.tasks=ae(s.status),r+=s.tasks.length,i+=s.tasks.reduce((R,F)=>R+(F.estimate??0),0);let P=s.box.width-4;s.cursor=Math.min(s.cursor,Math.max(0,s.tasks.length-1)),s.list.setItems(s.tasks.map((R,F)=>G(q(R,P),w===y&&F===s.cursor)));let Y=s.tasks.reduce((R,F)=>R+(F.estimate??0),0);s.box.setLabel(` ${s.status} (${s.tasks.length}${Y>0?`, ${Y}`:""}) `),s.box.style.border.fg=w===y?u.borderFocus:u.border});let e=d.sprints.find(s=>s.status==="active"),l=e===void 0?"no active sprint":e.name,a=(f===""?"":` filter: "${f}"`)+(c?" ready only":"")+(E&&!("reason"in h)?` branch: ${h.name}`:"")+(C===""?"":` milestone: ${C}`);U.setContent(` kadence ${l} ${r} tasks, ${i} points${a}`),d.cycles.length>0&&b(`${d.cycles.length} dependency cycle(s) \u2014 see kadence task list`,u.danger),o.render()}function I(){let t=p[y];return t?.tasks[t.cursor]}function V(){p.forEach((t,r)=>{let i=t.box.width-4;t.list.setItems(t.tasks.map((e,l)=>G(q(e,i),r===y&&l===t.cursor))),t.box.style.border.fg=r===y?u.borderFocus:u.border}),o.render()}function x(t){t!==null?b(t,u.warn):b(""),m(!0)}function K(t,r,i){let e=T.push(()=>{}),l=S.prompt({parent:o,top:"center",left:"center",width:"60%",height:7,border:{type:"line"},style:{border:{fg:u.borderFocus}},keys:!0,mouse:!0});l.input(t,r,(a,s)=>{l.destroy(),e(),o.render(),typeof s=="string"&&i(s.trim())})}function de(t){let r=S.box({parent:o,top:"center",left:"center",width:"75%",height:"80%",border:{type:"line"},label:` ${t.label} `,style:{border:{fg:u.borderFocus}},tags:!0,keys:!0,mouse:!0}),i=[{key:"title",label:"title",value:()=>t.title,hint:"Title:"},{key:"description",label:"description",value:()=>(t.description??"\u2014").split(`
|
|
3
|
+
`)[0]??"\u2014",hint:"Description (one line here, e for $EDITOR):"},{key:"status",label:"status",value:()=>t.status,hint:`Status (${A().join(", ")}):`},{key:"type",label:"type",value:()=>t.type,hint:"Type (task, bug, story, epic):"},{key:"priority",label:"priority",value:()=>t.priority,hint:"Priority (low, normal, high, urgent):"},{key:"assignee",label:"assignee",value:()=>t.assignee??"\u2014",hint:'Assignee (or "none"):'},{key:"estimate",label:"estimate",value:()=>t.estimate===null?"\u2014":String(t.estimate),hint:"Estimate in points:"},{key:"due",label:"due",value:()=>t.due??"\u2014",hint:"Due date YYYY-MM-DD (empty clears):"},{key:"labels",label:"labels",value:()=>t.labels.length>0?t.labels.join(", "):"\u2014",hint:"Labels, comma separated:"}],e=()=>[...i.map((v,g)=>({kind:"field",index:g})),...t.criteria.map(v=>({kind:"criterion",n:v.n}))],l=S.list({parent:r,top:0,left:1,right:1,height:i.length+t.criteria.length,keys:!1,mouse:!1,interactive:!1,tags:!0,style:{selected:{fg:u.selectedFg,bg:u.selectedBg}}}),a=0,s=S.box({parent:r,top:i.length+t.criteria.length+1,left:1,right:1,bottom:0,tags:!0,scrollable:!0});function w(){l.setItems(e().map((g,M)=>g.kind==="field"?W(i[g.index].label,i[g.index].value(),M===a):W(` ${g.n}.`,`[${t.criteria.find($=>$.n===g.n)?.checked===!0?"x":" "}] ${t.criteria.find($=>$.n===g.n)?.text??""}`,M===a)));let v=[t.blockedBy.length>0?`{red-fg}blocked by ${t.blockedBy.length} task(s){/}`:"",t.parent!==null?"{gray-fg}has a parent{/}":"",t.loggedHours>0?`{gray-fg}logged ${t.loggedHours.toFixed(1)}h{/}`:"","",t.description!==null&&t.description.includes(`
|
|
4
|
+
`)?`{gray-fg}${t.description.split(`
|
|
5
|
+
`).slice(1).join(`
|
|
6
|
+
`)}{/}`:"",t.comments.length>0?`{cyan-fg}comments (${t.comments.length}){/}`:"",...t.comments.map(g=>` {gray-fg}${g.author}:{/} ${g.text}`),"",t.criteria.length>0?"{gray-fg}space toggles the criterion under the cursor{/}":"","{gray-fg}\u2191\u2193 field enter edit e description in $EDITOR esc close{/}","{gray-fg}description opens the editor, so it can hold paragraphs{/}"].filter(g=>g!=="");s.setContent(v.join(`
|
|
7
|
+
`)),o.render()}w();let P=T.push(v=>ge(v)),Y=()=>{P(),r.destroy(),m(!0)};function R(){d=n.reload().state;let v=d.tasks.find(g=>g.id===t.id);if(v===void 0){Y();return}Object.assign(t,v),w()}function F(){let v=Z(()=>n.edit(t.id));v!==null&&b(v),R()}function ge(v){let{ch:g,name:M}=v;if(M==="up"||g==="k")return a=_(a,-1,e().length),w();if(M==="down"||g==="j")return a=_(a,1,e().length),w();if(g===" "||M==="space"){let $=e()[a];if($===void 0||$.kind!=="criterion")return;let j=t.criteria.find(D=>D.n===$.n);if(j===void 0)return;let B=n.toggleCriterion(t.id,$.n,j.checked);return B!==null&&b(B),R()}if(oe(v))return Y();if(g==="e")return F();if(M==="enter"){let $=e()[a];if($!==void 0&&$.kind==="criterion"){let B=t.criteria.find(fe=>fe.n===$.n);if(B===void 0)return;let D=n.toggleCriterion(t.id,$.n,B.checked);return D!==null&&b(D),R()}let j=i[a];if(j===void 0)return;if(j.key==="description")return F();K(j.hint,j.value()==="\u2014"?"":j.value(),B=>{let D=n.setField(t.id,j.key,B);D!==null&&b(D),R()})}}o.render()}function ue(){let t=["{cyan-fg}Navigation{/}"," \u2190 \u2192 h l move between columns"," \u2191 \u2193 k j move between tasks"," enter task details"," / filter escape clears it"," r reload from the journal"," R show only what can be started now (ready)"," b show only what this branch introduced"," M show only one milestone (empty clears it)"," C claim the selected task, or release it if it is yours","","{cyan-fg}Task actions{/}"," [ ] move one column left or right"," m move to a named status"," a assign c comment"," e edit description in $EDITOR"," p priority t log time"," n new task d delete","","{cyan-fg}Sprint{/}"," s sprint menu: status, start, close, burndown"," S add the selected task to the active sprint","","{cyan-fg}Mouse{/}"," click select a card"," drag move a card to another column","","{gray-fg}press any key to close{/}"],r=S.box({parent:o,top:"center",left:"center",width:60,height:Math.min(t.length+2,30),border:{type:"line"},label:" Keys ",style:{border:{fg:u.borderFocus}},tags:!0,scrollable:!0,keys:!0,mouse:!0,content:t.join(`
|
|
8
|
+
`)}),i=T.push(e=>{z(e)||(i(),r.destroy(),m())});r.on("click",()=>{i(),r.destroy(),m()}),o.render()}function X(t,r,i){let e=i.length>0?`
|
|
9
|
+
|
|
10
|
+
{gray-fg}${i.map(s=>`${s.key} ${s.label}`).join(" ")} esc close{/}`:`
|
|
11
|
+
|
|
12
|
+
{gray-fg}press any key to close{/}`,l=S.box({parent:o,top:"center",left:"center",width:"80%",height:"70%",border:{type:"line"},label:` ${t} `,style:{border:{fg:u.borderFocus}},tags:!0,scrollable:!0,mouse:!0,content:r+e}),a=T.push(s=>{if(z(s))return;let w=i.find(P=>P.key===s.ch);a(),l.destroy(),w!==void 0?w.run():m()});o.render()}function ce(t){let{ch:r,name:i}=t;if(i==="left"||r==="h")return y=Math.max(0,y-1),m();if(i==="right"||r==="l")return y=Math.min(p.length-1,y+1),m();if(i==="up"||r==="k"){let e=p[y];e!==void 0&&(e.cursor=_(e.cursor,-1,e.tasks.length),V());return}if(i==="down"||r==="j"){let e=p[y];e!==void 0&&(e.cursor=_(e.cursor,1,e.tasks.length),V());return}if(i==="enter"){let e=I();e!==void 0&&de(e);return}if(r==="["||r==="]"){let e=I();if(e===void 0)return;let l=r==="]"?1:-1,a=p[y+l];return a===void 0?void 0:(x(n.move(e.id,a.status)),y+=l,m())}if(r==="m"){let e=I();return e===void 0?void 0:K(`Move ${e.label} to (${A().join(", ")}):`,"",l=>x(n.move(e.id,l)))}if(r==="a"){let e=I();return e===void 0?void 0:K(`Assign ${e.label} to (or "none"):`,e.assignee??"",l=>x(n.assign(e.id,l)))}if(r==="c"){let e=I();return e===void 0?void 0:K(`Comment on ${e.label}:`,"",l=>x(n.comment(e.id,l)))}if(r==="t"){let e=I();return e===void 0?void 0:K(`Log time on ${e.label} (2h, 90m, -30m):`,"",l=>x(n.logTime(e.id,l)))}if(r==="p"){let e=I();return e===void 0?void 0:K(`Priority for ${e.label} (low, normal, high, urgent):`,e.priority,l=>x(n.setPriority(e.id,l)))}if(r==="n")return K("New task title:","",e=>x(n.create(e)));if(r==="d"){let e=I();return e===void 0?void 0:K(`Delete ${e.label}? type "yes":`,"",l=>{l.toLowerCase()==="yes"?x(n.remove(e.id)):b("Not deleted.")})}if(r==="e"){let e=I();return e===void 0?void 0:x(Z(()=>n.edit(e.id)))}if(r==="S"){let e=I();return e===void 0?void 0:x(n.addToSprint(e.id))}if(r==="s")return X("Sprint",n.sprintStatus(),[{key:"b",label:"burndown",run:()=>X("Burndown",n.burndown(),[])},{key:"n",label:"start next sprint",run:()=>x(n.sprintStart())},{key:"x",label:"close sprint",run:()=>x(n.sprintClose())}]);if(r==="/")return K("Filter (title, assignee or label):",f,e=>{f=e==="*"?"":e,m()});if(i==="escape"&&f!=="")return f="",b("Filter cleared."),m();if(r==="r")return b("Reloaded."),m(!0);if(r==="R")return c=!c,b(c?"Showing only what can be started now.":"Showing every task."),m();if(r==="b")return"reason"in h?b(h.reason==="detached"?"HEAD is detached, so there is no branch to filter by.":`There is no branch "${h.base}" to compare against.`,u.warn):(E=!E,b(E?`Showing only what ${h.name} introduced since ${h.base}.`:"Showing every task."),m());if(r==="M"){if(d.milestones.length===0)return b("No milestones yet. Create one with kadence milestone create.",u.warn);let e=d.milestones.map(l=>`${l.label} ${l.name}`).join(", ");return K(`Milestone (${e}; empty for all):`,C,l=>{let a=l.trim();if(a==="")return C="",b("Showing every task."),m();let s=d.milestones.find(w=>w.label.toUpperCase()===a.toUpperCase()||w.name.toLowerCase()===a.toLowerCase());if(s===void 0)return b(`No milestone "${a}".`,u.warn);C=s.label,b(`Showing ${s.label} ${s.name}.`),m()})}if(r==="C"){let e=I();return e===void 0?void 0:x(e.claimedBy===null?n.claim(e.id):n.release(e.id))}if(r==="?")return ue();(r==="q"||i==="C-c")&&O(0)}function Z(t){let r=o;r.leave();let i=t();return r.enter(),i}let H=null;p.forEach(()=>{}),o.on("mousedown",t=>{if(T.isDialogOpen())return;let r=p.findIndex(a=>{let s=a.box.left;return t.x>=s&&t.x<s+a.box.width});if(r===-1)return;y=r;let i=p[r],e=t.y-i.box.top-1,l=i.tasks[e];l!==void 0&&(i.cursor=e,H={task:l,from:r}),m()}),o.on("mouseup",t=>{if(T.isDialogOpen()||H===null)return;let r=p.findIndex(i=>{let e=i.box.left;return t.x>=e&&t.x<e+i.box.width});r!==-1&&r!==H.from&&(x(n.move(H.task.id,p[r].status)),y=r,m()),H=null}),Q(),m()}export{Se as runBoardUi};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var I=["backlog","todo","in_progress","blocked","in_review","done","cancelled"],S="done",A="cancelled",P="in_progress",O="backlog",nt=["task","bug","story","epic"],st=["low","normal","high","urgent"];function $(t,e){for(let n=t.length-1;n>=0;n--)if(t[n].id<e)return t[n].started;return P}function pt(t){let e=[...t].sort((r,d)=>r.id<d.id?-1:r.id>d.id?1:0),n=new Map,u=new Map,a=new Map,o=new Map,p=new Map,s=[],l=null,i=[],f={started:P,changes:[]},y=[],g=[];for(let r of e)r.type==="task.created"?Y(n,r):r.type==="sprint.created"?Z(u,r):r.type==="milestone.created"&&q(a,r);for(let r of e){if(r.type==="decision.recorded"){let d=r.data??{},w=T=>typeof d[T]=="string"&&d[T].length>0?d[T]:null,c=w("title"),h=w("why");c!==null&&h!==null&&p.set(r.id,{id:r.id,label:"",title:c,why:h,rejected:w("rejected"),task:w("task"),docs:Array.isArray(d.docs)?d.docs.filter(T=>typeof T=="string"):[],supersedes:w("supersedes"),supersededBy:null,at:r.ts,by:r.actor,source:r.source});continue}if(r.type==="note.recorded"){let d=b(r.data?.text);d!==null&&s.push({id:r.id,text:d,task:b(r.data?.task),at:r.ts,by:r.actor,source:r.source});continue}if(r.type==="template.saved"){let d=typeof r.data?.name=="string"?r.data.name:null;d!==null&&o.set(d,{name:d,fields:r.data?.fields??{},author:r.actor});continue}if(r.type==="board.configured"){let d=r.data?.statuses;if(Array.isArray(d)){let h=d.filter(T=>typeof T=="string"&&T.length>0);h.length>0&&(l=h)}let w=r.data?.dod;Array.isArray(w)&&(i=w.filter(h=>typeof h=="string"&&h.length>0));let c=b(r.data?.started);c!==null&&(f.started=c,f.changes.push({id:r.id,started:c}));continue}if(r.type==="template.deleted"){let d=typeof r.data?.name=="string"?r.data.name:null;d!==null&&o.delete(d);continue}rt(r,n,u,a,y,g,f)}let k=g,m=[...n.values()].sort((r,d)=>r.id<d.id?-1:1);m.forEach((r,d)=>{r.label=`KAD-${d+1}`,ot(r),r.criteria.sort((w,c)=>w.id<c.id?-1:1),r.criteria.forEach((w,c)=>{w.n=c+1}),it(r)});let x=new Set(m.map(r=>r.id));for(let r of m)r.parent!==null&&!x.has(r.parent)&&(r.parent=null),r.blockedBy=r.blockedBy.filter(d=>x.has(d));let v=[...p.values()].sort((r,d)=>r.id<d.id?-1:1);v.forEach((r,d)=>{r.label=`DEC-${d+1}`});for(let r of v){if(r.supersedes===null)continue;let d=p.get(r.supersedes);d!==void 0&&(d.supersededBy=r.id)}let M=[...a.values()].sort((r,d)=>r.id<d.id?-1:1);M.forEach((r,d)=>{r.label=`MS-${d+1}`});let B=new Map;for(let r of m){if(r.milestone===null||!a.has(r.milestone))continue;let d=B.get(r.milestone)??[];d.push(r.id),B.set(r.milestone,d)}for(let r of M){r.taskIds=B.get(r.id)??[];let d=r.taskIds.map(w=>n.get(w)).filter(w=>w!==void 0);r.totalTasks=d.length,r.doneTasks=d.filter(w=>w.status===S).length,r.totalPoints=d.reduce((w,c)=>w+(c.estimate??0),0),r.donePoints=d.filter(w=>w.status===S).reduce((w,c)=>w+(c.estimate??0),0)}for(let r of v)r.task!==null&&!x.has(r.task)&&(r.task=null);for(let r of s)r.task!==null&&!x.has(r.task)&&(r.task=null);return{tasks:m,decisions:v,milestones:M,notes:s,sprints:[...u.values()].sort((r,d)=>r.id<d.id?-1:1),statuses:l??[...I],dod:i,started:f.started,startedChanges:[...f.changes],orphanStatuses:[...new Set(m.map(r=>r.status).filter(r=>!(l??I).includes(r)))].sort(),templates:[...o.values()].sort((r,d)=>r.name.localeCompare(d.name)),cycles:at(m),pending:k,rejected:y}}function Y(t,e){if(t.has(e.entity))return;let n=e.data??{};t.set(e.entity,{id:e.entity,label:"",title:typeof n.title=="string"?n.title:"(untitled)",description:b(n.description),type:z(n.type),priority:G(n.priority),status:O,labels:J(n.labels),assignee:b(n.assignee),reporter:e.actor,sprint:null,milestone:null,parent:b(n.parent),blockedBy:[],due:b(n.due),claimedBy:null,claimedAt:null,claimedEventId:null,contestedBy:[],criteria:[],comments:[],docs:[],estimate:typeof n.estimate=="number"?n.estimate:null,loggedHours:0,createdAt:e.ts,updatedAt:e.ts,history:[]})}function Z(t,e){if(t.has(e.entity))return;let n=e.data??{};t.set(e.entity,{id:e.entity,name:typeof n.name=="string"?n.name:"(untitled)",description:b(n.description),startDate:b(n.startDate),endDate:b(n.endDate),status:"planned",closedBy:null,taskIds:[]})}function q(t,e){if(t.has(e.entity))return;let n=e.data??{};t.set(e.entity,{id:e.entity,label:"",name:typeof n.name=="string"?n.name:"(untitled)",due:b(n.due),status:"open",closedBy:null,taskIds:[],donePoints:0,totalPoints:0,doneTasks:0,totalTasks:0})}function rt(t,e,n,u,a,o,p){let s=t.data??{};if(t.type==="milestone.created")return q(u,t),!0;if(t.type==="milestone.task_added"){let i=b(s.task);if(i===null)return!0;let f=e.get(i);return f===void 0?(o.push(t),!1):(f.milestone=t.entity,!0)}if(t.type==="milestone.closed"){let i=u.get(t.entity);return i===void 0?(o.push(t),!1):(i.closedBy===null?(i.status="closed",i.closedBy=t.id):a.push(t),!0)}if(t.type==="task.created")return Y(e,t),W(e.get(t.entity),t),!0;if(t.type==="sprint.created")return Z(n,t),!0;if(t.type.startsWith("sprint.")){let i=n.get(t.entity);if(i===void 0)return o.push(t),!1;if(t.type==="sprint.started")return i.status==="planned"&&(i.status="active"),!0;if(t.type==="sprint.updated")return i.status==="closed"?(a.push(t),!0):(typeof s.name=="string"&&(i.name=s.name),s.description!==void 0&&(i.description=b(s.description)),s.startDate!==void 0&&(i.startDate=b(s.startDate)),s.endDate!==void 0&&(i.endDate=b(s.endDate)),!0);if(t.type==="sprint.closed")return i.closedBy===null?(i.status="closed",i.closedBy=t.id):a.push(t),!0;if(t.type==="sprint.cancelled")return i.status==="planned"?i.status="cancelled":a.push(t),!0;if(t.type==="sprint.task_added"){if(i.status==="closed")return a.push(t),!0;let f=typeof s.task=="string"?s.task:null;if(f===null)return!0;let y=e.get(f);return y===void 0?(o.push(t),!1):(y.sprint=i.id,i.taskIds.includes(f)||i.taskIds.push(f),W(y,t),!0)}return!0}let l=e.get(t.entity);if(l===void 0)return o.push(t),!1;if(t.type==="task.deleted")return e.delete(t.entity),!0;switch(t.type){case"task.moved":{let i=s.to;typeof i=="string"&&i.length>0&&(l.status=i);break}case"task.cancelled":l.status=A;break;case"task.reopened":l.status=$(p.changes,t.id);break;case"task.assigned":l.assignee=b(s.assignee);break;case"task.updated":typeof s.title=="string"&&(l.title=s.title),s.description!==void 0&&(l.description=b(s.description)),s.type!==void 0&&(l.type=z(s.type)),s.priority!==void 0&&(l.priority=G(s.priority)),s.labels!==void 0&&(l.labels=J(s.labels)),s.due!==void 0&&(l.due=b(s.due)),typeof s.estimate=="number"&&(l.estimate=s.estimate);break;case"task.parent_set":l.parent=b(s.parent);break;case"task.blocked_by_added":{let i=b(s.blocker);i!==null&&!l.blockedBy.includes(i)&&l.blockedBy.push(i);break}case"task.blocked_by_removed":{let i=b(s.blocker);i!==null&&(l.blockedBy=l.blockedBy.filter(f=>f!==i));break}case"task.label_added":{let i=b(s.label);i!==null&&!l.labels.includes(i)&&l.labels.push(i);break}case"task.label_removed":{let i=b(s.label);i!==null&&(l.labels=l.labels.filter(f=>f!==i));break}case"task.criterion_added":{let i=b(s.text);i!==null&&l.criteria.push({id:t.id,n:0,text:i,checked:!1,checkedBy:null});break}case"task.criterion_checked":case"task.criterion_unchecked":break;case"task.doc_linked":{let i=b(s.path);i!==null&&!l.docs.includes(i)&&l.docs.push(i);break}case"task.time_logged":{let i=s.hours;typeof i=="number"&&Number.isFinite(i)&&(l.loggedHours=Math.max(0,l.loggedHours+i));break}case"task.commented":{let i=b(s.text);i!==null&&l.comments.push({id:t.id,author:t.actor,ts:t.ts,text:i,source:t.source});break}default:break}return W(l,t),!0}function it(t){let e=new Map(t.criteria.map(n=>[n.id,n]));for(let n of t.criteria)n.checked=!1,n.checkedBy=null;for(let n of t.history){if(n.type!=="task.criterion_checked"&&n.type!=="task.criterion_unchecked")continue;let u=b(n.data.criterion),a=u===null?void 0:e.get(u);if(a===void 0)continue;let o=n.type==="task.criterion_checked";a.checked=o,a.checkedBy=o?n.actor:null}}function ot(t){let e=null,n=null,u=null,a=[],o=()=>{e=null,n=null,u=null,a=[]};for(let p of t.history)if(p.type==="task.claimed"){let s=b(p.data.by)??p.actor;e===null?(e=s,n=p.ts,u=p.id):s!==e&&!a.includes(s)&&a.push(s)}else if(p.type==="task.released"){let s=b(p.data.by)??p.actor;e===s?o():a=a.filter(l=>l!==s)}else(p.type==="task.cancelled"||p.type==="task.moved"&&p.data.to===S)&&o();t.claimedBy=e,t.claimedAt=n,t.claimedEventId=u,t.contestedBy=a}function W(t,e){t.history.push({id:e.id,type:e.type,actor:e.actor,ts:e.ts,source:e.source,data:e.data??{}}),e.ts>t.updatedAt&&(t.updatedAt=e.ts)}function b(t){return typeof t=="string"&&t.length>0?t:null}function z(t){return nt.includes(t)?t:"task"}function G(t){return st.includes(t)?t:"normal"}function J(t){return Array.isArray(t)?t.filter(e=>typeof e=="string"&&e.length>0):[]}function at(t){let e=new Map(t.map(o=>[o.id,o])),n=[],u=new Set,a=(o,p)=>{let s=new Map,l=(i,f)=>{let y=s.get(i);if(y==="done")return;if(y==="visiting"){let k=f.indexOf(i),m=f.slice(k),x=`${o}:${[...m].sort().join(",")}`;u.has(x)||(u.add(x),n.push({kind:o,path:[...m,i]}));return}s.set(i,"visiting");let g=e.get(i);if(g!==void 0)for(let k of p(g))l(k,[...f,i]);s.set(i,"done")};for(let i of t)l(i.id,[])};return a("parent",o=>o.parent===null?[]:[o.parent]),a("blocking",o=>o.blockedBy),n}var D=864e5;function C(t){return new Date(t).toISOString().slice(0,10)}function E(t){return Date.parse(`${t}T00:00:00.000Z`)}function lt(t){let e=new Date(E(t)),n=(e.getUTCDay()+6)%7;return C(e.getTime()-n*D)}function V(t,e){let n=C(t.getTime());return{from:C(E(n)-(e-1)*D),to:n,days:e}}function Q(t,e){let n=t.slice(0,10);return n>=e.from&&n<=e.to}function F(t,e){if(t.length===0)return 0;let n=Math.max(1,Math.ceil(e*t.length));return t[n-1]}var _=t=>Math.round(t*10)/10;function K(t){if(t.length===0)return null;let e=[...t].sort((n,u)=>n-u);return{shown:{n:e.length,p50:_(F(e,.5)),p85:_(F(e,.85)),p95:_(F(e,.95))},rawP85:F(e,.85)}}function N(t,e){let n=t.indexOf(e);return n===-1?null:new Set(t.slice(0,n))}function H(t,e,n=[],u=[]){let a=N(n,e),o=s=>s===S||s===A?!1:a===null?s===e:s===e||!a.has(s);return t.history.find(s=>s.type==="task.reopened"?o($(u,s.id)):s.type==="task.moved"&&typeof s.data.to=="string"&&o(s.data.to))?.ts??null}function dt(t,e){if(e.status===S||e.status===A)return!1;let n=N(t.statuses,t.started);return n===null?e.status===t.started:n.has(e.status)?!1:H(e,t.started,t.statuses,t.startedChanges)!==null}function R(t){if(t.status!==S)return null;for(let e=t.history.length-1;e>=0;e--){let n=t.history[e];if(n.type==="task.moved"&&n.data.to===S)return n.ts}return null}function ut(t){if(t.status!==A)return null;for(let e=t.history.length-1;e>=0;e--){let n=t.history[e];if(n.type==="task.cancelled"||n.type==="task.moved"&&n.data.to===A)return n.ts}return null}function L(t,e){return Math.max(0,(Date.parse(e)-Date.parse(t))/D)}function ct(t,e,n,u){let a=R(t)??ut(t),o=a===null?u:Date.parse(a),p=[],s=new Map;for(let k of t.history){let m=typeof k.data.blocker=="string"?k.data.blocker:null;if(m!==null){if(k.type==="task.blocked_by_added")s.has(m)||s.set(m,Date.parse(k.ts));else if(k.type==="task.blocked_by_removed"){let x=s.get(m);x!==void 0&&(p.push([x,Math.min(Date.parse(k.ts),o)]),s.delete(m))}}}for(let[k,m]of s){let x=Math.min(e.get(k)??u,o);p.push([m,Math.max(m,x)])}let l=E(n.from),i=E(n.to)+D,f=p.map(([k,m])=>[Math.max(k,l),Math.min(m,i)]).filter(([k,m])=>m>k).sort((k,m)=>k[0]-m[0]),y=0,g=null;for(let[k,m]of f)g===null||k>g[1]?(g!==null&&(y+=g[1]-g[0]),g=[k,m]):m>g[1]&&(g[1]=m);return g!==null&&(y+=g[1]-g[0]),y/D}function mt(t,e=new Date,n=30){let u=V(e,n),a=e.getTime(),o=[],p=t.tasks.filter(c=>c.status!==A),s=p.filter(c=>{let h=R(c);return h!==null&&Q(h,u)}),l=[],i=[],f=[];for(let c of s){let h=R(c),T=H(c,t.started,t.statuses,t.startedChanges);i.push(L(c.createdAt,h)),T!==null&&(l.push(L(T,h)),f.push(L(c.createdAt,T)))}let y=K(l),g=y?.shown??null,k=p.filter(c=>dt(t,c)),m=k.map(c=>{let h=L(H(c,t.started,t.statuses,t.startedChanges),e.toISOString());return{label:c.label,title:c.title,status:c.status,ageDays:_(h),overP85:y!==null&&h>y.rawP85}}).sort((c,h)=>h.ageDays-c.ageDays),x=new Map,v=c=>{let h=lt(c),T=x.get(h);return T===void 0&&(T={week:h,finished:0,created:0},x.set(h,T)),T};for(let c=E(u.from);c<=E(u.to);c+=D)v(C(c));for(let c of s)v(R(c).slice(0,10)).finished+=1;for(let c of p)Q(c.createdAt,u)&&(v(c.createdAt.slice(0,10)).created+=1);let M=[...x.values()].sort((c,h)=>c.week<h.week?-1:1),B=new Map;for(let c of t.tasks){let h=R(c);h!==null&&B.set(c.id,Date.parse(h))}let r=0,d=0;for(let c of p){let h=ct(c,B,u,a);h>0&&(r+=1,d+=h)}t.tasks.length===0?o.push("No tasks yet."):(k.length===0&&s.length===0&&o.push(`Nothing has crossed "${t.started}" yet. If work starts in another column, say so: kadence board config --started <status>`),s.length===0?o.push(`Nothing finished between ${u.from} and ${u.to}; cycle time needs finished work.`):l.length===0&&o.push(`${s.length} item(s) finished without ever entering "${t.started}", so there is no cycle time to measure.`),t.statuses.includes(t.started)||o.push(`"${t.started}" is not one of the board's columns; set one with kadence board config --started.`));let w=g===null?null:`${g.n<10?`From ${g.n} finished items (a small sample): `:""}85% of finished items took ${g.p85} calendar days or less.`;return{window:u,started:t.started,unit:"calendar days",wip:k.length,finished:s.length,perWeek:M,cycleTime:g,leadTime:K(i)?.shown??null,responseTime:K(f)?.shown??null,sle:w,aging:m,blocked:{tasks:r,days:_(d)},notes:o}}function ht(t,e=new Date,n=30){let u=V(e,n),a=[...t.statuses];for(let l of t.tasks)a.includes(l.status)||a.push(l.status);let o=O;!a.includes(o)&&t.tasks.length>0&&a.unshift(o);let p=t.tasks.map(l=>{let i=[];for(let f of l.history)f.type==="task.moved"&&typeof f.data.to=="string"?i.push({at:Date.parse(f.ts),status:f.data.to}):f.type==="task.cancelled"?i.push({at:Date.parse(f.ts),status:A}):f.type==="task.reopened"&&i.push({at:Date.parse(f.ts),status:$(t.startedChanges,f.id)});return{createdAt:Date.parse(l.createdAt),changes:i,next:0,status:o}}),s=[];for(let l=E(u.from);l<=E(u.to);l+=D){let i=l+D,f={};for(let y of a)f[y]=0;for(let y of p){for(;y.next<y.changes.length&&y.changes[y.next].at<i;)y.status=y.changes[y.next].status,y.next+=1;y.createdAt>=i||(f[y.status]=(f[y.status]??0)+1)}s.push({date:C(l),counts:f})}return{window:u,statuses:a,days:s}}var j={urgent:0,high:1,normal:2,low:3};function X(t,e){return t!==null&&t.toLowerCase().includes(e)}function Tt(t,e,n=new Date){let u=n.toISOString().slice(0,10),a=e.search?.toLowerCase();return t.filter(o=>{if(a!==void 0&&a.length>0){let p=o.comments.some(s=>s.text.toLowerCase().includes(a));if(!X(o.title,a)&&!X(o.description,a)&&!p)return!1}if(e.status!==void 0&&o.status!==e.status||e.type!==void 0&&o.type!==e.type||e.priority!==void 0&&o.priority!==e.priority)return!1;if(e.assignee!==void 0){let p=e.assignee.toLowerCase(),s=(o.assignee??"").toLowerCase();if(p==="none"?s!=="":s!==p)return!1}if(e.label!==void 0){let p=e.label.toLowerCase();if(!o.labels.some(s=>s.toLowerCase()===p))return!1}return!(e.sprint!==void 0&&o.sprint!==e.sprint||e.overdue===!0&&(o.due===null||o.due>=u)||e.dueBefore!==void 0&&(o.due===null||o.due>=e.dueBefore))})}function xt(t,e){let n=[...t];switch(e){case"priority":return n.sort((u,a)=>j[u.priority]-j[a.priority]);case"due":return n.sort((u,a)=>tt(u.due,a.due,(o,p)=>o<p?-1:o>p?1:0));case"estimate":return n.sort((u,a)=>tt(u.estimate,a.estimate,(o,p)=>p-o));default:return n.sort((u,a)=>u.id<a.id?-1:u.id>a.id?1:0)}}function tt(t,e,n){return t===null&&e===null?0:t===null?1:e===null?-1:n(t,e)}function St(t){let e=[];return t.search!==void 0&&e.push(`search="${t.search}"`),t.status!==void 0&&e.push(`status=${t.status}`),t.type!==void 0&&e.push(`type=${t.type}`),t.priority!==void 0&&e.push(`priority=${t.priority}`),t.assignee!==void 0&&e.push(`assignee=${t.assignee}`),t.label!==void 0&&e.push(`label=${t.label}`),t.overdue===!0&&e.push("overdue"),t.dueBefore!==void 0&&e.push(`due before ${t.dueBefore}`),e.length===0?`No tasks yet.
|
|
3
|
+
Create the first one:
|
|
4
|
+
kadence task add "title"`:`No tasks match ${e.join(" and ")}.
|
|
5
|
+
Try fewer filters:
|
|
6
|
+
kadence task list`}function et(t){return N(t.statuses??I,t.started??P)}function U(t){return t.status===S||t.status===A}function At(t,e={}){let n=e.viewer??null,u=new Set(t.filter(U).map(s=>s.id)),a=e.assignee==="me"?n:e.assignee??null,o=et(e);return t.filter(s=>{if(U(s)||o!==null&&!o.has(s.status)||s.blockedBy.some(l=>!u.has(l))||s.claimedBy!==null&&s.claimedBy!==n&&s.contestedBy.length===0)return!1;if(a!==null){if(a==="none")return s.assignee===null;if(s.assignee!==a)return!1}return!0}).sort((s,l)=>j[s.priority]-j[l.priority]||(s.id<l.id?-1:1))}function vt(t,e,n,u={}){if(t.length===0)return`No tasks yet.
|
|
7
|
+
Create the first one:
|
|
8
|
+
kadence task add "title"`;let a=t.filter(g=>!U(g));if(a.length===0)return"Nothing ready: every task is done or cancelled.";let o=new Set(t.filter(U).map(g=>g.id)),p=et(u),s=p===null?0:a.filter(g=>!p.has(g.status)).length,l=a.filter(g=>g.blockedBy.some(k=>!o.has(k))).length,i=a.filter(g=>g.claimedBy!==null&&g.claimedBy!==e&&g.contestedBy.length===0).length,f=[];return s>0&&f.push(`${s} already started`),l>0&&f.push(`${l} blocked`),i>0&&f.push(`${i} claimed by someone else`),n!==void 0&&f.push(`filtered to assignee=${n}`),`Nothing ready${f.length>0?` (${f.join(", ")})`:""}.
|
|
9
|
+
See the whole board:
|
|
10
|
+
kadence task list`}var ft=["created","priority","due","estimate"];function Dt(t){return ft.includes(t)}export{I as a,S as b,A as c,nt as d,st as e,pt as f,H as g,dt as h,R as i,mt as j,ht as k,Tt as l,xt as m,St as n,U as o,At as p,vt as q,ft as r,Dt as s};
|