Skip to content
← All work
/ Case 02 Dev tool · GitHub Action + site · 2026

The rubber duck that talks back.

An architecture decision record is a written rubber duck: you explain the decision to it, and the act of explaining is the point. Duckadrift is the duck that talks back. It reads every ADR in a repository, checks whether the code still agrees with what was decided, and fails the pull request when a decision and reality drift apart. It never writes an ADR. It only holds you to the ones you wrote.

ProjectDuckadrift (my dev tool)
RoleFounder · sole dev
ScopeGitHub Action + CLI + site
Shipped2026 · v0.2.0 · MIT

/ two tiers · one gate

duckadrift.com ADR
Duckadrift home page, a rubber duck in sunglasses floating on simulated water

Project breakdown

/ The brief

Everyone builds ADR authoring. Nobody builds enforcement.

Teams that write architecture decision records all hit the same wall: the records rot. The code moves on, a governed path gets rewritten, a superseded decision keeps its Accepted status, and six months later the decision log is a museum. The existing ecosystem (adr-tools, log4brains, ADR Manager) builds authoring: templates, sites, editors. The part that keeps a record true once it is written did not exist.

So the wedge is deliberately narrow. Duckadrift treats each record as a set of testable claims about the repository (the paths it governs, the references it makes, the status it declares) and checks those claims on every pull request, plus a weekly decay sweep that files a single tracking issue and closes it by itself when the log comes clean.

/ Part I · The checks

Deterministic checks fail builds. Semantic findings must cite evidence.

Tier 0 is seven deterministic checks: dialect-aware parsing, governed-path matching, reference resolution, status recognition. They make zero network calls, and they are the only tier allowed to fail CI on its own authority. Where a check rests on a guess (the auto-detected dialect, say), it is downgraded to advisory: the tool does not assert as fact what it is only guessing at.

Tier 1 is five semantic checks that ask Claude whether the substance of a decision still matches the code. Every prompt treats repository content as data, never instructions: documents are fenced in envelopes whose boundary nonce is derived by hashing the document itself, so forging a fence means solving a fixed point. A finding must quote its evidence byte-for-byte or a deterministic validator throws it away and counts the discard.

  • A deterministic relevance gate decides which PRs even reach the model, from governed-path touches and a fixed vocabulary of dependency-manifest signals
  • Record/replay transport: CI replays committed model transcripts keyed by a canonical request hash, so the tested pipeline and the production pipeline are the same object, and a prompt change fails CI until it is re-recorded
  • Every attacker-authorable value in the Action (PR refs, inputs, PR numbers) passes through the environment, never string interpolation, per its own ADR-0013
  • A crash with no report fails the build red; the Action never passes green on an incomplete scan
  • A dedicated CI job re-runs the Action at the exact shallow checkout depth that crashed v0.1.5, and first proves the checkout is genuinely shallow, so the regression test cannot quietly stop testing itself
/ Part II · The gate, and the site

It refuses to raise alarms it hasn't earned. The site shows the receipts.

The semantic tier's authority is measured, not configured. A severity channel may interrupt a PR only when the Wilson 95% lower bound of its measured precision clears a declared floor. The shipped calibration, 56 labeled findings across nine repositories, opens nothing yet, so today every semantic finding lands in a report annex and no siren sounds. Version 1.0 is defined as the first time a channel opens on data, and channels open as a data change, no release required.

The landing page at duckadrift.com makes the same argument visually. It is a dive: a full-viewport GPU water simulation with three auto-detected quality tiers, a meshopt-compressed rubber duck you can grab, fling, and dunk, and six narrative beats that surface as a pure function of scroll depth, down to a sea-floor install section. No build step, one vendored three.js, strict CSP and full security headers. The calibration panel on the page fetches the tool's real published calibration.json and renders each channel's open or closed state from the measurements, not from marketing copy.

  • A deterministic render harness swaps Math.random for a seeded generator and runs the simulation in fixed steps, so a nondeterministic scene can be pixel-diffed in CI
  • Accessibility held throughout the show-off parts: skip link, keyboard controls on the canvas, a focus-trapped drawer, reduced-motion handling
  • A privacy-guard workflow scans file content, filenames, and commit messages in local hooks and un-bypassable CI, redacting matches from public logs
42
ADRs in its own decision log, checked on every PR
525
Tests in CI, replayed against recorded model transcripts
0
Alert channels open until measured precision clears the floor
/ Decisions

What I picked, and what I didn't.

  1. 01

    Enforcement, never authoring. Permanently.

    Every neighbor in the ADR ecosystem helps you write records. Duckadrift refuses to: no templates, no drafting, no auto-fix, at any version. The narrowness is the moat. An enforcement tool can be trusted precisely because it has no stake in what the records say, only in whether they are still true.

    Considered: drafting or auto-fixing ADRs (rejected as a different, worse-positioned product), a full ADR management suite (rejected, that is the crowded half of the market)

  2. 02

    Thresholds are measured, not configurable.

    There is no confidence knob. A channel interrupts when the Wilson lower bound of its measured precision clears the declared floor, and not before. A gate opened on a lucky point estimate is wrong in exactly the way the founding decision forbids, so with 56 labeled findings the honest answer is that every channel stays closed, and the tool says so in public, on its own landing page.

    Considered: a user-set confidence threshold (rejected, authority you can dial is not authority), shipping the semantic tier ungated (rejected, unearned sirens teach people to ignore the duck)

  3. 03

    CI replays the exact production pipeline.

    The semantic tests do not mock the model client. The transport records live transcripts once, keys them by a canonical hash of the request, and CI replays them through the identical code path. Change a prompt and CI fails with a stale-recording error until you re-record. The suite physically cannot drift from what ships, which is a satisfying property for a drift detector to have.

    Considered: a mocked LLM client (rejected, then the tested pipeline is not the shipped one), live API calls in CI (rejected, nondeterministic and paid)

/ Result

v0.2.0 on the GitHub Marketplace, MIT, dogfooding itself.

Duckadrift is live: a three-line workflow step, MIT licensed, running on its own repository, where 42 ADRs govern the code that checks them and a weekly decay sweep keeps the log honest. The CI suite runs 525 tests, the semantic tier ships calibrated-closed, and the whole thing runs with zero model calls if you never set an API key.

The site is up at duckadrift.com, and like every build under this name it was made to look like nothing else I have shipped: a duck in sunglasses floating on GPU water, keeping a straight face while the copy beneath it explains Wilson confidence intervals.