Releases: LeahyCC/kungfu-kanban
Release list
v1.12.0 — A release is cut on every merged PR
Added
- A release is cut on every merged PR. Four versions (1.8.0 through 1.10.1)
reachedmainwithout ever being tagged, because cutting the tag and the
GitHub Release was a manual step nobody owned. Merging tomainnow runs
scripts/cut-release.js: it reads the version, and if there is a dated
## [X.Y.Z]section for it that isn't tagged yet, creates the annotated tag
and publishes the Release with that section as the notes. Tag and Release
only — it never commits, somain's ruleset can't block it. A PR that bumped
no version, or left its notes under[Unreleased], releases nothing and
stays green. The convention that makes it work: PRs write their changelog
section dated, not under[Unreleased](see CLAUDE.md and the PR
template). Publishing is opt-in when run by hand — a bare
node scripts/cut-release.jsprints what it would do and exits, because the
first local smoke run of it published a real v1.12.0 that had to be deleted
from the feed. CI passesCI=true; a human passes--yes. (#113 dated the
1.11.0 section this builds on; #115 shipped the automation itself.) - A release no longer has to cite its own PR. The audit reconciles every PR
merged since the prior tag against the release section, and the release's own
merge commit joins that range the instant it lands — so the section had to
reference the PR that wrote it, which you can only manage by amending a PR
after opening it. It cost the automation its first run: #115 merged, the audit
failed on the missing#115,testwas skipped, thereleasejob never ran,
and nothing was tagged. The PR whose merge commit is HEAD is now exempt. On a
pull_requestthe checked-out HEAD is a merge preview with no
"Merge pull request #N" subject, so nothing is exempt there and the PR-time
audit keeps its full strength.
Fixed
- The terminal no longer scrambles fast typing. Each keystroke was its own
POST, and concurrent requests have no ordering guarantee — so typing quickly
(or holding a key down, or being on a tailnet where the round trip is tens of
milliseconds) could deliverlsto the shell assl. Caught in the wild:
echo "shell=$SHELL…arrived asecho "shell$=HSLEL zs=hZ$SH_VESROIN….
Sends are now serialized — one request in flight, anything typed meanwhile
coalesces into the next — so a burst costs fewer requests than before, not
more, and arrives in order. A failed send no longer wedges the queue or
raises an unhandled rejection either. Shipped tomainas 1.11.1 in #114 but
never tagged — its notes were undated, which is the exact hole the automation
above closes, so it is released here instead.
v1.11.0 — built-in terminal, terminal-style transcript
Bookkeeping: this section is four versions wide. 1.8.0, 1.9.x, 1.10.0 and
1.10.1 all merged to main without ever being tagged, so their notes ship
here rather than as releases of their own — the same call the 1.8.0 note made,
applied to everything that piled up behind it. The last tagged release was
1.7.1 (2026-07-28); this one covers the 27 commits since. Individual entries
carry their own (v1.10.0)-style markers where the version mattered.
Added
- A built-in terminal (⌘J). A real shell on the host — your
$SHELL,
your~/.zshrc, your starship prompt, your aliases — in a panel at the
bottom of the board. It is a genuine pty, so colours, job control, and
full-screen TUIs (vim, lazygit,claudeitself) all work, and resizing the
window resizes the shell. Sessions live on the SERVER: closing the panel or
reloading the board leaves whatever you started running, and reopening
reattaches with scrollback — including from your phone over the tailnet.
Several shells can be open at once (tabs in the panel bar, ⏻ kill ends one
for good). No native module: the pty comes from Python's stdlib via
lib/ptyhost.py, so the install staysgit pull && npm i.
Off switch: ⚙ Settings → System → "built-in terminal" (it is a shell behind
your board token; turning it off also ends any open sessions). - The board now says WHY CI is red when it's the GitHub account, not the
code. When a PR's checks settle red, the watcher probes the failed run's
jobs once; jobs that died with no runner assigned (runner_id 0, zero
steps) are the Actions billing/spending-limit signature. The card gets a
"⛔ CI blocked — GitHub account" badge quoting GitHub's own annotation
("recent account payments have failed…"), the drawer spells it out, one
notification fires per red spell, the auto CI fixer stands down (no session
resumes burned on failures no push can fix), and the Sensei snapshot
carriesinfra: truewith explicit do-not-retry guidance. (v1.10.0) - The empty board is now a composer. A quiet dojo opens with a prompt box
and the settings that matter — project, model, effort, permissions, and
worktree+PR — plus four ways out: ✨ Improve prompt (rewrites what you typed
to be sharper and self-contained, with an undo; it creates nothing),
⇪ Split into cards (hands the text to the Sensei's drafting flow with your
project carried across), ▶ Create & run, and + Add to backlog. ⌘↵ in the box
creates and runs. Your last-used settings are remembered per device. POST /api/prompt/improve— sharpen one prompt in place. No cards, no board
side effects.- Board defaults for model and effort. ⚙ Settings gains "Default model" and
"Default effort" — a card left ondefaultnow resolves to the board default
at launch (fresh runs, follow-ups, and the fallback ladder all respect it; an
explicit card choice always wins). The card editor'sdefaultoptions stop
being a mystery: they readdefault (sonnet)/default (CLI picks). - Ultracode cards wear the rainbow. Max-effort cards get a rainbow ring
that shifts continuously through the spectrum, plus a 🌈 max badge, so
full-power runs are unmissable. The spin animates a registered<angle>, so
it repaints only the 2px ring and stops under reduced-motion. - Appearance settings. A new tab in ⚙ Settings, saved per device: theme
(night / day / follow the OS), text size, accent colour (seven swatches plus
a custom picker — the whole vermillion family, including on-accent contrast,
is derived from your choice), 🌈 rainbow mode for every card, compact
density, and a per-board reduce-motion switch. A custom accent is never
allowed to render link text below AA: the derived ink shifts away from the
card surface only as far as 4.5:1 demands, so a mid-tone or very bright
pick stays readable on both dojos.
Changed
- The card transcript reads like a terminal. A tool call used to wrap
across four or five lines, so a run ofgh api … | jq …calls was a wall of
monospace with the tool name buried in it. Each call is now one line: a
vermillion❯prompt, the tool name bright, its argument dimmed and clipped
with an ellipsis. Hover for the full text, click the row to unclip it. - Settings is four short tabs, not one wall. The modal grew past the point
of being scannable, so it's now Cards / Appearance / Alerts / System, each a
handful of related fields, opening on Cards. - The SHIPPED seal no longer has its top edge clipped (cards carry paint
containment fromcontent-visibility, so the stamp's overhang was cut off),
and it straightens as you hover the card. - Logged-out guard. When a run or Sensei pass dies on "Not logged in", the
board trips one deduped ⚠ entry plus a 🔐 chip, pauses auto launches and
Sensei triggers, and requeues the victim card — instead of spamming the
activity log on every interval. Every human-initiated run stays ungated as
the probe (Run button, drawer follow-up, Sensei chat), so after
claude /loginany success clears the gate and resumes the queue.
Fixed
- A red PR no longer re-fires on every CI attempt. The PR watcher compared
each sweep's failed checks against the immediately previous sweep, so a new
CI attempt on a red PR (push or re-run) — which passes through a pending
phase and re-settles with the same failed set — read as a fresh failure every
time: a duplicateci-failingerror entry, a notification, and a Sensei
re-invoke per attempt (~50 duplicates and ~10 sensei runs in 40 minutes while
Actions was billing-blocked). The watcher now remembers the last settled
outcome and only fires when a settled state genuinely differs — a different
failed set, or recovery to green — while pending phases in between are
no-ops. This is the other half of the v1.10.0 account-block work: that one
stopped the auto-fixer and the Sensei from acting on a billing block, this
one stops the watcher from re-reporting the same red set at all. The verified
infraverdict now also survives the pending phase, so a card keeps its
"⛔ CI blocked" badge across a re-run instead of losing it the moment the
counts blank. The 60s hot poll is unchanged. (v1.10.1) - A stray second server drove the board for six days. A card's own smoke
test on 2026-07-22 backgroundednode -e "require('./server.js')"in the
checkout and leaked it. It never held:4747, so nothing looked wrong — but
it ran a full automation loop against the realdata/: a Sensei pass every
five minutes whose CLI could not authenticate (1331Not logged inrows
in the error tracker), a second inbox watcher racing the first (duplicate
imports, "could not archive" errors, one duplicate card launched and failed),
and a second PR watcher. The port was never a lock — a scratch server picks
its own — so the data dir is one now: a second server against the same
data/refuses to start and says who holds it. It waits out a
launchctl kickstart -khandover, takes over a lock whose process is gone,
and ignores a garbage lock file, so the board can't be stranded by one. - ▶ Run looked broken on a card that couldn't start yet. A card with an
unmet dependency (or a full board, or a gate up) is parked rather than
launched — correct, but the drawer's Run button ignored the answer entirely
and the board only explained the dependency case, so the click just seemed to
do nothing. Every refusal now carries a reason and every Run path shows it:
⛓ queued — waits on <card>,— all 4 run slots are busy,— subscription limits are cooling down. ⚡ Bypass & re-run no longer claims a parked card is
"running unrestricted". - Merged PRs left their cards stuck in Review. When the executing agent
opened its own PR under its own branch name, the card'sgh pr createcame
back "already exists" and the card keptprUrl: null— and the PR watcher
skips cards with no PR URL, so merging the PR never shipped the card and
every dependent stayed queued behind it. Six cards sat that way on
2026-07-28. The sweep now backfills a missing PR URL first: it reads the
branch out of the card's own worktree and asks GitHub which PR belongs to it,
so a merge always moves the card to Done no matter who opened the PR. - A failing card reported "claude exited with code 1" and nothing else. The
CLI writes its own failures to stdout (the result event) with stderr empty,
so stamping the exit code overtask.errordiscarded the only text the
auth-gate, cooldown, and offline detectors match on. A logged-out CLI failed
cards one at a time instead of pausing the board once; the real message is
now kept. - The Sensei logged the same unactionable error every five minutes. A CLI
that answers with its trouble in prose ("Not logged in · Please run /login")
still reports success, so the decision JSON failed to parse and the run was
logged as a plain error — 1331 identical entries accumulated over six days,
and the auth gate was cleared on the way past. Both the errored and the
unparsable paths now trip the right gate (limit → cooldown, logged out →
auth gate) and log one pause; the gate clears only on a parsed decision, and
an errored run reports its stderr. - ntfy push spammed the server log. A topic that isn't a legal ntfy topic —
including the common trick of editing it to disable push — was POSTed anyway
and earned an HTTP 400 per notification. An invalid topic now means push is
off. - A raced inbox import logged a permanent "could not archive" error. Two
scans of the same file left the second one unable to move an already-archived
file, so the tracker carried an open error claiming the card would re-import
when it never would. Gone-means-archived is no longer an error. - ✨ Draft and ↻ Refine killed the agent they had just spawned. The
cancel-on-disconnect guard hung off the *reques...
v1.7.1 — Archive tab, command palette, budget enforcement
Added
- Archive tab. Done cards swept out of the board by
archiveDaysare no
longer gone from sight: a third tab browsesdata/archive.jsonlnewest-first
with search, per-week throughput, per-repo counts, and token totals
(GET /api/archive,GET /api/archive/:id). Rows expand to the full prompt
and result; PR links come along. Read-only — restore can come later if it's
ever missed. - Command palette + global shortcuts.
⌘K(or?) opens a palette that
runs commands and jumps to cards by fuzzy title/prompt match;nnew card,
iimport,/focus filter,1/2/3switch tabs. Shortcuts never fire
while typing or while a modal/drawer is open, and the empty palette doubles
as the shortcut cheat-sheet. - Usage budget enforcement. The 5-hour ⛽ budget (
usageBudgetM) now has
teeth: when rolling output-token burn meets it, auto launches pause and
manual runs park in Queued — exactly like the subscription cooldown — and
the chip shows "5h budget spent · paused". Flow resumes on its own as the
window slides; the escape hatch is raising or clearing the budget in ⚙
Settings. The Sensei also sits out while the budget is spent.
Changed
- Archive tab polish: titles ellipsize so meta columns stay aligned, rows share
the board's hover/focus treatment, timestamps carry the full date on hover
(<time>), and empty states now match the rest of the app — distinguishing an
empty archive from a search with no matches.
Fixed
- Offline-guard error entries were stored under the wrong kind (
run-failed)
becauseofflinewas missing from the error tracker's kind whitelist — so
they never auto-resolved when the connection came back and sat in the ⚠
tracker until dismissed by hand. Found while wiring the budget guard's
entries the same way; both kinds are whitelisted now, with a regression test.
Notes
- Cards imported from GitHub issues already auto-close their source issue on
merge (Fixes #Nin the PR body) — now locked by a regression test. Known
limits, accepted: retargeting a card's repo after import can point#Nat
the wrong repo, and PRs into a non-default base don't auto-close (GitHub
semantics).
v1.6.1 — performance & UX overhaul, Sensei idle gate
Release bookkeeping: the last tagged release is v1.1.0 (1.2–1.5.x were dated
in this changelog but never tagged), so this section formally also accounts for
#90 (release-integrity CI) — already described under [1.5.0] below.
Fixed
- The Sensei no longer fires scheduled interval runs on an idle board. When
nothing is queued/running/in review and there are no open errors or pending
suggestions, the interval check is skipped silently (one explanatory log
line per idle transition) instead of a "nothing to do — standing by" chat
message — or a CLI error — everyintervalMin. Event-driven triggers (new
card, card finished, PR checks settled, chat) always run. test/manager.test.jsnow isolatesKFK_DATA_DIR— it previously ran
against the checkout's realdata/, appending fakemerged PR of "undefined"entries todata/manager-log.jsonlon every test run.
Changed
- Full-scope performance & UX overhaul (measured against a seeded 120-card
board; before/after numbers in the PR):- Rendering: the board no longer rebuilds all DOM on every SSE event. A
keyed reconciler patches only changed cards (server-stamped per-task
revisionv, field-compare fallback), columns/groups persist across
renders, and listeners are delegated. SSE task/deleted tails are
rAF-coalesced (N events/frame → one render) and manager/attention
refreshes are badge-only while their views are hidden. - Network: SSE task frames drop heavy text fields (~2.4 KB → ~0.6 KB per
event) with 250 ms per-id coalescing and slow-client backpressure;
GET /api/tasks?v=<seq>answers 304 when nothing changed;
GET /api/tasks/:idreturns one full task; text assets are gzip'd and
images get day-immutable cache headers. - Server: task saves no longer rewrite settings.json (and vice versa),
tasks.json is compact with.bakonly at shutdown, transcript appends
are batched, the auth token is mtime-cached, the manager activity log is
parse-cached, stream entries are capped at 20 KB, and PR sweeps poll
with bounded concurrency. - Perceived performance: skeleton columns + inline critical CSS on first
paint, non-blocking webfonts, modulepreloaded module graph, optimistic
card moves/edits/deletes with rollback toasts, FLIP card motion
(prefers-reduced-motion aware), and a 120 ms-debounced filter with a
cached search haystack. - CSS paint: full-viewport
mix-blend-modegrain replaced by plain alpha,
the running-card ink stroke now animatestransforminstead of
background-position, and cards/transcript/log entries use
content-visibility: auto. - Service worker: the app shell is one versioned, atomically-installed
cache unit (stale-while-revalidate) — a partial fetch can no longer mix
module versions;/api/stays live.
- Rendering: the board no longer rebuilds all DOM on every SSE event. A
- Accessibility: drawer is a true modal (focus trap +
aria-modal), toasts
are live-announced, tabs/panels are wired, transcript and chat are
role="log", columns/cards carry region/list semantics with arrow-key
navigation and roving tabindex, touch targets are 44 px, and the settings
modal takes initial focus.
Changed (structure)
- The frontend
public/app.js(one ~2200-line script) is split into native ES
modules underpublic/js/—state,util,api,deps,markdown,
board,drawer,modals,chips,manager,sse— withapp.jsreduced
to a small entry point. No bundler and no build step:index.htmlloads it as
<script type="module">and Express serves the modules straight from disk, so
the "frontend serves fresh from disk" rule still holds. Pure structure — no
behavior change; shared mutable state (tasks/config/drawerId/mgrState) lives on
one exportedstateobject.
v1.1.0 — the attention popup (plus repos auto-scan, express 5)
[1.1.0] — 2026-07-20
Added
- A header 🔔 chip counts Sensei suggestions awaiting your verdict plus cards
stopped on a permission block, and opens a "Needs your attention" popup
with per-item and batch Approve/Reject and an Open-card shortcut. It
auto-opens once on the 0→N transition and never nags after that. (#83) - Blocked cards get a one-click "⚡ Bypass & re-run": it re-runs the card
withbypassPermissionsinstead of making you raise the permission mode
and re-launch by hand. This is a deliberate human override, so it isn't
clamped by the manager's permission ceiling the way auto-picked modes are. (#87) - When suggestions are being held by the hourly launch cap, the attention
popup shows a notice with an "Approve-all now" path and a "Raise cap"
control, instead of leaving them silently stuck. (#88)
Changed
- The repos directory now auto-scans the common dev-folder conventions
(code,src,projects,Documents/Code, …) and picks whichever holds
the most git repos, falling back to$HOME— instead of defaulting to one
person's~/Documents/Code/Git. It's configured by whoever runs the board,
never baked in. (#82) - Upgraded the one runtime dependency from Express 4 to Express 5. (#59)
- The README now opens with a dark-mode board screenshot that adapts to the
reader's GitHub theme. (#80)
Fixed
- Stale Sensei suggestions are now auto-pruned instead of piling up.
Takes effect after the next board restart on servers already running;
fresh installs get it immediately. (#81)
Security
- Transient browser-verification screenshots and
.playwright-mcp/output are
gitignored, so a board screenshot — which can contain private card content —
can never ride along in a PR. (#84)
Chore
- CI: bumped
actions/checkoutto v7. (#58)
v1.0.1 — heavy tests, contributor gating, humanizer ships
[1.0.1] — 2026-07-20
Added
test/server.integration.test.jsboots the real server as a child process
to cover route-level regressions unit tests miss: validation coercions,
the auth gate, and status-code contracts.parseSchedule/scheduleDue
moved tolib/schedule.jsso they're directly unit-testable. Every
spawned server gets its ownKFK_DATA_DIR(a fresh temp directory), an
env override honored bylib/store.jsand everything that derives its
data paths from it, so the integration suite never touches this
checkout's realdata/.- Split the parser smoke test into per-module suites under
test/with
decision-table coverage of the importer, deps, models, cooldown, prwatch,
runner, errlog, auth, and skill install/status logic. - CI now runs the suite on ubuntu-latest and macos-latest (matrix) and prints
node --testcoverage on every run (no threshold gate yet). - Open-source contribution gating: CODEOWNERS, dependabot, contributor docs.
- Release tags + GitHub Releases published (v0.13.0–v1.0.0 backfilled), and
required for future releases.
Changed
- README: badge row (CI, latest release, MIT license), hero image slot, and a
tighter positioning paragraph up top for people landing cold. - The humanizer skill (MIT, vendored at
skills/humanizer/SKILL.md) now ships
with the board alongside ponytail: installs to~/.claude/skillsat boot,
pre-selects on new cards, and the kungfu-todo template tells agents to apply
it to PR titles, PR descriptions, and result summaries. - Mobile layout fixes: site manual overflow, masthead CTA wrap, app header under 360px.
- site: humanizer pass
- docs: humanizer audit — already clean (README.md, CONTRIBUTING.md, SECURITY.md, lib/skill.js template strings)
- site: accuracy pass on the manual/FAQ/llms.txt — dependency chains, group
batching, and the Sensei's diff review/merge/follow-up/error-tracker
powers were live since 0.12–0.14 but undocumented on kungfu-kanban.com. - site: added
/compare(honest comparison vs Vibe Kanban, Conductor, and
Crystal/Nimbalyst) and/docs(linkable reference sections — import
format, the Sensei's actions/autonomy, dependency/merge-gate semantics,
groups & lanes), wired into nav, sitemap, and structured data.
v1.0.0 — the board runs its whole loop
Kungfu Kanban hits 1.0: the board now runs its whole intended loop. 0.13.0
cleared a full quality audit — atomic state writes, safe restarts,
merge-gated dependency chains, hardened import/PR plumbing. 0.14.0 closed the
loop on the Sensei — it reviews the actual PR diff, merges green PRs within
its autonomy setting, and sends precise same-session follow-ups instead of
blind retries, with every PR gated by CI. 0.15.0 made batched work flow as
grouped lanes with one-click queueing and group-complete notifications. 1.0.0
declares the surfaces stable: the import format, the HTTP API, the on-disk
data files, and the generated skill.
v0.15.0 — batched work flows as grouped lanes
Added
- Grouped cards now run one lane at a time: at most one card per group is
in progress at once, an in-progress group drains before a fresh one
starts, and a manual run still bypasses the lane when you need it now. - A queue-group button on group headers launches every backlog card in
that group with one click, instead of queuing them one by one.
Changed
- A group now sends a single notification when it completes instead of one
per card, and the Sensei prefers finishing an in-progress group over
starting a new one and reviews a completed group's cards as a batch.
v0.14.0 — the Sensei reviews diffs and merges
Added
- The Sensei now pulls the actual PR diff (
gh pr diff, capped and truncated)
into its review context for cards under review, instead of judging only
the agent's self-report — the two serious flaws a human caught in the
0.13.0 batch were invisible in the self-report and obvious in the diff. - The Sensei can now merge a card's PR itself (
merge_pr), gated the same way
as its other powers: a one-click suggestion undersuggest, held for
approval undersemi, done alone underauto. It only merges when the
card is in review, its PR checks are all green (no failing, no pending, no
unknown/absent checks, right base), and it skips with a note otherwise —
closing the gap where approve-without-merge left a "done" card still
blocking its dependents. The 0.13.0 batch stalled at 14 finished cards and
0 merges for exactly this reason; the human no longer has to be the merge
button. - The Sensei can now
followup_taska review card: resume its own agent
session with precise feedback instead of alwaysreject_tasking into a
full fresh run. Reserved for small, specific flaws (name the file, name the
fix);reject_taskstill handles approach-level failures that need a
restart.run_taskon an already-running/stopping card is now a no-op
instead of an error, matching the other stale-snapshot races. - Import frontmatter now takes
queue: true(aliasautoqueue) to auto-launch
every card in the file on import, instead of dropping them in Backlog where
nothing picks them up. Chained cards (after:/sequential:) park in Queued
and cascade automatically as their prerequisites ship. - Every PR now runs the test suite in CI, so a red run is machine-checkable
before the Sensei ever considers merging it.
Fixed
- PR-watch conflict fixer no longer spawns against an already-resolved PR: the
sweep re-checks state/mergeable right before creating the fix card, and the
fixer prompt itself re-verifies and bails as a no-op if the PR turns out
merged, closed, or no-longer-conflicting by the time it starts.
v0.13.0 — quality audit hardens the board
Added
- Stuck cards are now surfaced with a hung-agent watchdog, so a card whose
agent silently stops making progress gets flagged instead of sitting idle
forever. - The SSE connection now sends a heartbeat ping, so the board recovers
cleanly from flaky wifi/sleep instead of silently going stale. - Imported cards are grouped by their import batch, making large imports
easier to scan. - Cards held back by an unmet dependency now show their held-until-merge
state, and the previously-silent 20-dependency cap is now surfaced
instead of failing quietly. - A node test harness now covers the import/markdown parsers, catching
parser regressions before they ship.
Changed
- Dependent cards now release only once their prerequisite's PR has actually
merged, instead of as soon as the card closes. - Card drag focus and selection now survive live board re-renders instead of
resetting. - iOS PWA safe-area insets and the home-screen icon got a polish pass.
Fixed
- A model-fallback retry storm, double-started runners, and double-fired
card spawns are fixed — cards no longer race or duplicate their own runs. - Data file writes are now atomic (write-then-rename, with a safe backup
ordering), so a crash mid-save can no longer leave the board file missing
or corrupt. - The write API and auth checks are hardened against edge cases, and the
import markdown parser no longer chokes on malformed input. - PR-watch base detection, merge-notification spam, and dead PR watches are
fixed. - The usage scan no longer blocks the UI while it runs.
- The manager log and imported-inbox no longer grow unbounded.
- Failed inbox imports are now visible and won't duplicate on retry.
- Fixed the transcript markdown renderer, drawer/modal state bugs, and
Sensei actions repeating themselves against stale state. - Worktree cards now sync local main before launching, and base-branch sync
no longer refuses to fetch into the currently-checked-out branch. - Server shutdown now stops running agents and flushes state cleanly before
exiting.