Releases: daneb/keel
Release list
v0.6.6
Added
- Info overlays on the Overview page. The stat tiles, section headers,
ranked-bar labels (attribution classes, failure classes) and table badges
use terms — AGENTIC/PROCESS/HUMAN/UNATTRIBUTABLE, SCOPE-CREEP, "theatre",
"harness-fixable" — that were previously only documented in Rust doc
comments. A small?icon (or, where there's no room for one, a
dotted-underline hover target) now surfaces each term's definition on
hover or click/tab-focus, from a single glossary inapp.js. No change to
data flow or wire format.
v0.6.5
Added
- A per-spec summary above the Checks/Evidence drill-down. The detail
view went straight from the timeline to the Checks and Evidence tabs,
which render every run's events/tokens/failing-checks line by line —
nothing answered "how much has this feature cost, which gates has it
exercised, is anything currently failing" without reading down through
however many runs a spec has accumulated. A new summary section between
the timeline and the tabs shows runs/tokens/events totalled across the
spec's whole history, an "open fails" count scoped to the spec's own
G0/G1 plus the latest run only (so a spec passing now doesn't read as
failing because an earlier attempt once did), and one badge per gate id
the spec has ever produced a result for, coloured by that gate's latest
verdict. The Checks/Evidence tabs are unchanged — the summary is what to
read first, the tabs are still where to drill in. Built entirely from data
/api/overviewalready serves; no wire-format change.
v0.6.4
Fixed
- The pipeline timeline is legible under a real, pre-
lock-completed-specs
stale approval. Running 0.6.3's timeline against a real project
surfaced a spec whose merge approval was recorded (mistakenly, against the
wrong spec, before SPEC-0005 existed) ascurrent, while a later G1 run
had genuinely regressed the spec back beforeComplete. The timeline
reported the stale approval as if nothing were wrong. A newstale()
check flags theapprove mergeanddonenodes and explains the
discrepancy in the detail panel when either is opened. plan_gatewas the only stage named after its gate. Every other stage
used plain English (spec,plan,approve plan);plan_gatewas
labelled bareG1, with no equivalent forspec's own gate. Both
gate-backed stages now name their gate:spec (G0)andplan (G1).- The current-stage node and an open detail node were indistinguishable.
.hereand[aria-expanded="true"]shared one brass outline, so nothing
told them apart when they landed on two different nodes at once — routine,
since opening a node to inspect it doesn't move the pipeline..herenow
fills brass; an open node now outlines in ink instead. - The spine read as an afterthought next to a long Checks panel. Node
padding and font-size are larger, and pending-node text/borders move off
flat--mutedonto an ink-mixed token for contrast.
v0.6.3
Added
- The pipeline spine is a connected, clickable timeline.
keel serve's
detail view rendered#spineas disconnected badges — which stage a spec
was at, and nothing else. Stages now read as one journey (left-to-right,
or top-to-bottom under the 700px breakpoint#railalready stacks at),
connected by a visible track, and clicking a stage discloses its own
detail in place: a gate stage shows its verdict and check counts, an
approval stage shows who approved it and when — or why it's rejected,
superseded, or still absent — and the run stage shows the latest attempt.
Built entirely from data/api/overviewalready serves; no wire-format
change.
v0.6.2
Fixed
keel serveno longer dies from SIGPIPE on its own startup banner.
cmd::serve::runprinted two startup lines beforeserve()re-armed
SIGPIPE-ignore, whichmainhad set back to the default (fatal)
disposition. A caller that read only the first line and closed its end
early — a test harness that only wants the bound URL, a| head-style
consumer — could win a race against the secondprintln!and kill the
whole server before it served a single request. This was the cause behind
tests/serve.rsfailing intermittently on every CI run sincekeel serve
shipped in 0.5.0 (never reproduced locally on macOS; confirmed by
reproducing keel's own CI, Linux, in a container). SIGPIPE is now ignored
before any output, and the startup banner is written directly rather than
throughprintln!, so a closed stdout can no longer panic the process
either.- Run ids now sort in creation order within a day.
gate::run_id's
suffix was a hash of the clock and the pid, not a monotonic counter, so
run::list()'s "lexicographic order is chronological" claim was false for
two runs created the same day —pipeline::stage()could report a stale
G2 verdict as current. The suffix is now a per-day counter derived from
the highest existing run id for today.
Added
- A completed spec is locked against further approvals.
keel approve <slug> --stage <stage>took the slug on faith, so approving or rejecting
an earlier stage against an old, already-merged spec by mistake silently
appended a fresh decision to its approval log.keel approvenow refuses
once a spec has reachedStage::Complete, naming the slug and why;
--forceremains as a deliberate, noted override.
v0.6.1
Added
- A spec-authoring loop convention.
conventions.mdnow states the
protocol for writing.keel/specs/<slug>/spec.md: draft one acceptance
criterion, runkeel gate g0 <slug>, fix only what the verdict names, and
repeat until it passes. Written once in the store, it projects identically
intoCLAUDE.md,AGENTS.md,.kiro/steering/keel.mdand
.github/copilot-instructions.md, so the loop is the same regardless of
which agent is driving.
v0.6.0
Added
-
An executive summary, in
keel serveandkeel report. The per-spec
spinekeel serveshipped with in 0.5.0 answered "what's blocking this
feature"; it had nothing to say about the repository as a whole. The
browser's landing view is now an Overview: stat tiles, a pass/fail/blocked
trend by week, failure attribution and failure-class breakdowns as ranked
bars, every gate check ranked worst-pass-rate-first with a theatre flag
on any check that has never failed past its threshold (PLAN.md §6, made
visible instead of buried inkeel metricstext output), a sortable
per-spec comparison table, and a lessons panel showing occurrences and idle
days against each lesson's decay window.keel reportwith no slug prints
the same summary as text instead of dumping every spec's full run history;
keel report <slug>is unchanged.Nothing here is a new computation pretending to be one: attribution,
failure classes, check pass rates and the harness-fixable rate all come
from the same aggregationkeel metricsalready trusts (now factored out
ascrate::metrics::compute, callable from both). The one genuinely new
thing is a time axis — nothing in keel bucketed by date before this, so the
weekly trend bucketsRunMeta.started_at(already written to every
run.json) into ISO weeks. Newkeel.insights/1— not on the spine
freeze, the same as per-run detail JSON: it iskeel serve's private wire,
not a contract, until the shape has settled.Charts are hand-built inline SVG — no library, consistent with the page's
CSP and its zero-dependency stance — and the categorical palette used for
failure classes and attribution is the one that clears the dataviz
validator's CVD and contrast checks against this page's own light and dark
surfaces, not hand-picked. keel's existing pass/fail/blocked colors don't
clear those same checks when shown adjacent (a stacked bar of run
outcomes, say) — every mark using them keeps the word alongside the color,
the same discipline the rest of the app already followed. -
harness_fixable_ratereaches a wire for the first time.
failure::distributionhas computed it since Phase 3;keel metrics
silently dropped it from both its JSON and its text report. It's now on
Metrics, printed bykeel metrics, and shown as the headline figure next
to attribution in the new Overview.
Fixed
- A spec's cycle time could come out negative.
SpecReport.runsinherits
run::list()'s sort, and a run id's hex suffix is a hash of the clock and
process id — not a same-day counter — so it is not reliably chronological
within a day. Readingruns.first()/.last()as "earliest/latest" could
therefore pick the wrong ends; it did, on this repository's own history.
Cycle time is now computed from parsed timestamps across every run for the
spec, never array position.
v0.5.0
Added
-
keel serve— a read-only web view of.keel/on loopback, aimed at the
operator mid-flight: a lifecycle spine from G0 to G4, every failing check with
its expected/actual pair and the lesson that produced it, and the evidence
those checks point at. It updates while a run is in progress by polling a
cheapstat-based change token. No new dependency: the HTTP/1.1 server is
hand-rolled overstd::net, on the same reasoning assrc/mcp, and the page,
its CSS and its JavaScript are compiled into the binary.The server is read-only mechanically — anything but
GET/HEADis refused
in the parser, before routing, so no route can quietly acquire the power to
approve or gate. It binds127.0.0.1with no flag to widen it, validates the
Hostheader against DNS rebinding, refuses cross-origin requests, serves
evidence only astext/plainunder aContent-Security-Policythat forbids
the network outright, and reaches the filesystem by name on exactly one route
— which serves only namesread_dirreturned and then canonicalises to prove
containment, so a symlink planted inevidence/by a hostile driver cannot
escape. New threat-model section in SECURITY.md. -
keel report [slug]— the same assembled view in the terminal, and
--jsonfor anything that wants to render its own. Newkeel.report/1
schema, kept deliberately thin: it earns its content by embedding records that
are already frozen (keel.gate/1,keel.run/1) rather than inventing
summaries of them. -
--jsononkeel next,keel runsandkeel approvals— the three
human-facing commands that had no machine-readable form.keel.next/1carries
a stable stage key and the one command that advances it; repo-level obstacles
arrive asblockersrather than replacing the answer, so an uninitialised
repository still returns valid JSON. (keel statusis deliberately left out:
its schema has real design content and gets its own decision.) -
Colour and glyphs in terminal output, matching
release.shso the script
and the binary read as one product. Decided per write against the real stdout
handle, honouringNO_COLOR,TERM=dumbandCLICOLOR_FORCE. When stdout is
not a terminal the bytes are exactly what they were before, which is asserted
rather than assumed.
Fixed
run.jsonand gate results are now written atomically. Both used
std::fs::write, which truncates before writing, so any concurrent reader —
the pre-commit hook,keel export, a second terminal — could observe an empty
or partial file, andRun::finishrewritesrun.jsonat exactly the moment
someone would look. Now written to a sibling temporary and renamed. This was a
latent race beforekeel serveexisted; the server only made it frequent.
Changed
-
The pipeline state machine lives in one place (
src/pipeline.rs).
keel nextderived it twice — once compactly for the multi-spec listing and
once inline for the detailed guidance — and the two had already drifted: a
rejected merge approval was listed as needing approval but described as
complete. Both now render from one evaluation, and a rejected merge reads as
rejected in both. -
keel nextexplains a rejected merge instead of falling through to
"complete", and names the reason if one was recorded. -
Trajectories can be read leniently via
trajectory::scan, which reports
gaps, unparseable lines and a partial trailing record rather than refusing the
file — and distinguishes an append caught mid-write from actual corruption, so
a live run does not look damaged. The stricttrajectory::readis unchanged
in behaviour and is now implemented on top ofscan, so the two cannot drift.
Nothing is ever dropped silently: what could not be read is reported.
v0.4.10
Fixed
gate_baseno longer diffs against a stale local trunk branch. The
trunk-candidate lookup tried bare localmain/masterbefore the
remote-qualifiedorigin/main/origin/master. A local trunk branch left
un-checked-out since clone never advances even asorigin/<trunk>moves
forward on fetch, but it is still an ancestor ofHEAD— somerge-base
against it "succeeded" anyway, landing on a much older commit and dragging
everything a teammate pushed since intokeel gate g2's blast-radius and
line-budget diffs. Remote-qualified names are now tried first.
v0.4.9
Added
keel driver default <id>— sets which configured driverkeel run
uses when--driveris omitted. Editskeel.tomlas raw text, flipping
defaulton the matching[[driver]]block and clearing it elsewhere, so
hand-written comments and formatting survive rather than being lost to a
full serde round-trip.