Skip to content

Releases: daneb/keel

v0.6.6

Choose a tag to compare

@daneb daneb released this 10 Sep 14:39

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 in app.js. No change to
    data flow or wire format.

v0.6.5

Choose a tag to compare

@daneb daneb released this 10 Sep 09:01

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/overview already serves; no wire-format change.

v0.6.4

Choose a tag to compare

@daneb daneb released this 10 Sep 08:36

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) as current, while a later G1 run
    had genuinely regressed the spec back before Complete. The timeline
    reported the stale approval as if nothing were wrong. A new stale()
    check flags the approve merge and done nodes and explains the
    discrepancy in the detail panel when either is opened.
  • plan_gate was the only stage named after its gate. Every other stage
    used plain English (spec, plan, approve plan); plan_gate was
    labelled bare G1, with no equivalent for spec's own gate. Both
    gate-backed stages now name their gate: spec (G0) and plan (G1).
  • The current-stage node and an open detail node were indistinguishable.
    .here and [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. .here now
    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 --muted onto an ink-mixed token for contrast.

v0.6.3

Choose a tag to compare

@daneb daneb released this 10 Sep 07:27

Added

  • The pipeline spine is a connected, clickable timeline. keel serve's
    detail view rendered #spine as 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 #rail already 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/overview already serves; no wire-format
    change.

v0.6.2

Choose a tag to compare

@daneb daneb released this 10 Sep 06:11

Fixed

  • keel serve no longer dies from SIGPIPE on its own startup banner.
    cmd::serve::run printed two startup lines before serve() re-armed
    SIGPIPE-ignore, which main had 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 second println! and kill the
    whole server before it served a single request. This was the cause behind
    tests/serve.rs failing intermittently on every CI run since keel 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
    through println!, 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 approve now refuses
    once a spec has reached Stage::Complete, naming the slug and why;
    --force remains as a deliberate, noted override.

v0.6.1

Choose a tag to compare

@daneb daneb released this 09 Sep 19:54

Added

  • A spec-authoring loop convention. conventions.md now states the
    protocol for writing .keel/specs/<slug>/spec.md: draft one acceptance
    criterion, run keel gate g0 <slug>, fix only what the verdict names, and
    repeat until it passes. Written once in the store, it projects identically
    into CLAUDE.md, AGENTS.md, .kiro/steering/keel.md and
    .github/copilot-instructions.md, so the loop is the same regardless of
    which agent is driving.

v0.6.0

Choose a tag to compare

@daneb daneb released this 09 Sep 16:54

Added

  • An executive summary, in keel serve and keel report. The per-spec
    spine keel serve shipped 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 in keel metrics text output), a sortable
    per-spec comparison table, and a lessons panel showing occurrences and idle
    days against each lesson's decay window. keel report with 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 aggregation keel metrics already trusts (now factored out
    as crate::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 buckets RunMeta.started_at (already written to every
    run.json) into ISO weeks. New keel.insights/1 — not on the spine
    freeze, the same as per-run detail JSON: it is keel 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_rate reaches a wire for the first time.
    failure::distribution has computed it since Phase 3; keel metrics
    silently dropped it from both its JSON and its text report. It's now on
    Metrics, printed by keel 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.runs inherits
    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. Reading runs.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

Choose a tag to compare

@daneb daneb released this 09 Sep 13:30

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
    cheap stat-based change token. No new dependency: the HTTP/1.1 server is
    hand-rolled over std::net, on the same reasoning as src/mcp, and the page,
    its CSS and its JavaScript are compiled into the binary.

    The server is read-only mechanically — anything but GET/HEAD is refused
    in the parser, before routing, so no route can quietly acquire the power to
    approve or gate. It binds 127.0.0.1 with no flag to widen it, validates the
    Host header against DNS rebinding, refuses cross-origin requests, serves
    evidence only as text/plain under a Content-Security-Policy that forbids
    the network outright, and reaches the filesystem by name on exactly one route
    — which serves only names read_dir returned and then canonicalises to prove
    containment, so a symlink planted in evidence/ by a hostile driver cannot
    escape. New threat-model section in SECURITY.md.

  • keel report [slug] — the same assembled view in the terminal, and
    --json for anything that wants to render its own. New keel.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.

  • --json on keel next, keel runs and keel approvals — the three
    human-facing commands that had no machine-readable form. keel.next/1 carries
    a stable stage key and the one command that advances it; repo-level obstacles
    arrive as blockers rather than replacing the answer, so an uninitialised
    repository still returns valid JSON. (keel status is deliberately left out:
    its schema has real design content and gets its own decision.)

  • Colour and glyphs in terminal output, matching release.sh so the script
    and the binary read as one product. Decided per write against the real stdout
    handle, honouring NO_COLOR, TERM=dumb and CLICOLOR_FORCE. When stdout is
    not a terminal the bytes are exactly what they were before, which is asserted
    rather than assumed.

Fixed

  • run.json and 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, and Run::finish rewrites run.json at exactly the moment
    someone would look. Now written to a sibling temporary and renamed. This was a
    latent race before keel serve existed; the server only made it frequent.

Changed

  • The pipeline state machine lives in one place (src/pipeline.rs).
    keel next derived 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 next explains 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 strict trajectory::read is unchanged
    in behaviour and is now implemented on top of scan, so the two cannot drift.
    Nothing is ever dropped silently: what could not be read is reported.

v0.4.10

Choose a tag to compare

@daneb daneb released this 01 Sep 06:40

Fixed

  • gate_base no longer diffs against a stale local trunk branch. The
    trunk-candidate lookup tried bare local main/master before the
    remote-qualified origin/main/origin/master. A local trunk branch left
    un-checked-out since clone never advances even as origin/<trunk> moves
    forward on fetch, but it is still an ancestor of HEAD — so merge-base
    against it "succeeded" anyway, landing on a much older commit and dragging
    everything a teammate pushed since into keel gate g2's blast-radius and
    line-budget diffs. Remote-qualified names are now tried first.

v0.4.9

Choose a tag to compare

@daneb daneb released this 31 Aug 19:35

Added

  • keel driver default <id> — sets which configured driver keel run
    uses when --driver is omitted. Edits keel.toml as raw text, flipping
    default on 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.