feat(badges): ship corpus-size and ARB queue badges as recipes over existing output - #127
Merged
Conversation
…tus badge Records the badge question as three distinct claims — adoption, status, and certification — and ships only the one verifiable by the viewer. Ships a static adoption badge (shields static URL plus a committable SVG), documents the GitHub Actions workflow badge as the honest status badge, defers `adr badge` behind four stated constraints, and refuses a hosted badge endpoint. The refusal is on posture, not effort: a computed endpoint contradicts ADR-0004 and puts a dynamic surface on the origin ADR-0011 froze as static and immutable, while making badge renders a de-facto telemetry stream. Status: proposed — surfaces in `adr queue` as async tier, deadline 2027-02-09. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Signed-off-by: Mark Beacom <m@beacom.dev>
The vendored MADR record weighs a status badge *inside each ADR file* as an alternative to a `status:` frontmatter field. Its objections are about per-record proliferation and markdown-source readability, neither of which transfers to a single repository-level badge. Only the online-service dependency carries over, and that is answered by shipping a committable SVG rather than by declining to ship a badge. Cited as adjacent prior art, not as support for the decision. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Signed-off-by: Mark Beacom <m@beacom.dev>
…ting output Amends ADR-0022, whose earlier draft deferred a computed badge on the premise that it required a new CLI surface. Measurement falsified that premise: - QueueReport v1 already emits `totalItems`, `asOf`, and `corpusFingerprint`. - shields `dynamic/json` reads it with no adrkit code (observed rendering `adrkit version: 0.4.0` from this repo's raw package.json). - Its failure modes render honestly: `resource not found`, `no result`. - Queue depth is asOf-independent — buildQueueReport selects on `status === 'proposed'` — so regeneration on corpus change suffices and no daily bot commit is needed. Deadline-derived badges would not be, which is why the recipe badges `$.totalItems` and nothing else. Ships: a committable adoption SVG whose colors are converted from the site's own oklch tokens (#cb492d / #1d1311), a workflow regenerating .adrkit/queue.json on corpus change, a badges guide, and the queue badge on this README. Refuses, on posture rather than effort: an `adr badge` command (it would be public API to reformat a field already emitted) and a hosted endpoint (uptime dependency, a computed surface on the origin ADR-0011 froze as static, and usage telemetry as a side effect of rendering). Also fixes a hardcoded corpus count in lint.test.ts that broke on this record. The count is now derived from the corpus contract rather than bumped, and was observed failing under a +1 mutation before being restored (ADR-0016). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Signed-off-by: Mark Beacom <m@beacom.dev>
… entry PR #106 landed ADR-0022 (scan inbound markers in check and CI) and PR #109 is teed up as ADR-0023, so this record takes 0024. Renames the file and updates the `id`, the heading, and every cross-reference in the workflow comment and the badges guide. Regenerates .adrkit/queue.json against the rebased corpus: depth is now 8, since the newly landed ADR-0022 is itself proposed. The rebase conflicted on packages/cli/test/lint.test.ts exactly as predicted — #106 had bumped the hardcoded corpus count from 21 to 22. Resolved in favor of deriving the count, which removes the recurring bump rather than repeating it. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Signed-off-by: Mark Beacom <m@beacom.dev>
…the site build A five-lens review found the shipped design broken in two independent ways, both verified by execution rather than reading. The workflow could never have worked here. The `main` ruleset requires the `self-dogfood` check, `self-dogfood` is gated on `github.event_name == 'pull_request'`, and `github-actions[bot]` is not a bypass actor — so every bot push would be rejected and the badge would freeze at whatever this PR committed. The published recipe could never have bootstrapped. `git diff --quiet -- <path>` exits 0 for an untracked file, so `&& exit 0` fired on an adopter's first run and nothing was ever committed; `git commit -am` would not have staged it either. Both reproduced in a scratch repository, and the corrected first-run / no-change / changed paths were executed before being written down. adrkit now emits `queue.json` as a site build artifact. `site.yml` already rebuilds on `docs/adr/**`, so the file is generated into `site/public/` (gitignored, exactly like the served schema) and the badge reads adrkit.dev/queue.json. No write token, no bot commit, and no stored artifact that can fall behind — which also removes the committed-generated-file precedent the review flagged as departing from `schema/adr.schema.json` and `packages/ci/dist`. The adopter recipe keeps committing the file, because most repositories have no site to piggyback on, and now SHA-pins `actions/checkout`, pins the `@adrkit/cli` version inside a write-capable job, validates the JSON before publishing it, and names the alternatives for a protected default branch. Also corrects a false claim the record made about itself: the corpus-status badge does not detect stopped regeneration, since it reports the workflow running `adr check`, which never touches the queue report. Records the `totalItems` external-badge constraint on the field and in RELEASING.md, namespaces the SVG clipPath id, and fixes the `color=blue` / `cb492d` contradiction. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Signed-off-by: Mark Beacom <m@beacom.dev>
…ersion v0.5.0 shipped while this branch was open, which made the published recipe's `npx @adrkit/cli@0.4.0` pin stale. Bumped to 0.5.0 — and, because a pinned literal in a doc is exactly the value that rots silently at the next release, added a guard rather than trusting review to catch it. `sync-schema --check` now fails when a docs page pins a version other than the root `package.json`, which was observed failing on a reverted 0.4.0 pin before being kept. `docs/RELEASING.md` names the bump as a release step so the failure is anticipated rather than discovered as a red site deploy. Also retargets two citations the release invalidated: ADR-0021 is now superseded, so the marker-cannot-lie rule is cited as ADR-0021 -> 0022/0023 rather than as current authority, and the `adrkit version: 0.4.0` shields observation is marked as the version observed at the time rather than reading as a present claim. The rebase conflicted on the corpus count in lint.test.ts for the third time (22 -> 23 on main); resolved to the derived form again, which is the last time that conflict can occur. package.json is restored from main and re-edited as a single added line — an earlier `json.dumps` rewrite had escaped the em-dash in `description` to `\u2014`. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Signed-off-by: Mark Beacom <m@beacom.dev>
… findings PR #123/#124 landed a different ADR-0024 (measured scan extent) and v0.6.0 shipped, so this record takes 0025 and the recipe pins 0.6.0. Only this record's own references moved; the ADR-0024 citations in packages/**, CHANGELOG, and the CLI source belong to the scan-extent record and are untouched. The site deploy no longer fails on a corpus finding. `adr queue` documents exit 1 as "report emitted with error-severity corpus findings" — the report is complete and publishable — so treating it as fatal let a corpus problem block the canonical schema at its `$id`, which ADR-0011 promises to serve. Exit >=2 still fails, and the emitted JSON is validated before the build proceeds. The version guard added last commit was wrong in the case it existed for: its `\d+\.\d+\.\d+` pattern matched the `0.6.0` inside `0.6.0-rc.1` and passed. The first fix — a negative lookahead — was worse: the token stopped matching at all, so a pre-release pin passed silently. Both were caught by running the guard rather than reading it. It now captures the whole version token and compares it in full, and is scoped to executable `npx`/`bunx` invocations so prose naming an older release cannot block a deploy. Observed passing on 0.6.0 and on a prose mention, failing on 0.6.0-rc.1, 0.5.0, and `latest`. Adds `package.json` to the site trigger so a release bump re-runs the pin guard in the same push; extends this record's `affects` to the queue types and the guard script so `adr check` routes a maintainer to the `totalItems` obligation the record creates; and fills three gaps in the published guide — the protected-branch aside now shows the actual YAML for the branch and pull-request routes, the pin carries an ageing warning, and the disclosure note covers the `deciders` values that travel in queue.json. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Signed-off-by: Mark Beacom <m@beacom.dev>
Pressure-testing where the guard belongs produced a clearer answer than "leave it or move it". The repo's convention for a guard is not just a single-purpose script — every one in `scripts/` has a colocated `.test.ts` and a required-check CI step, while `site/scripts/` holds untested generators. This guard had no tests and had been wrong twice in two commits, which is the case for the convention rather than an exception to it. It also compares the root `package.json` against site content, so it is a repo-wide concern rather than a site-internal one; and a stale pin is a content defect that should block a merge, not block publishing the canonical schema at its `$id`. Extraction does not weaken enforcement: `check:doc-pins` runs in `clean-clone-builds`, which is a required status check, so a stale pin cannot reach main. `sync-schema.ts` is back to the single purpose its header claims. The nine new tests pin both historical failures — the `\d+\.\d+\.\d+` pattern that matched the `0.6.0` inside `0.6.0-rc.1`, and the anchored variant that made the token stop matching and pass silently — plus the prose-vs-recipe distinction and the global-regex `lastIndex` reset. Defers the README badge to a follow-up. `https://adrkit.dev/queue.json` does not exist until the site publishes once, so merging the badge now renders an error tile on this project's own front page — the exact failure this record exists to avoid. The record and changelog no longer claim a badge that is not there, and the action item now tracks adding it. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Signed-off-by: Mark Beacom <m@beacom.dev>
The guard had been wrong four times, and round 3 found the last two by running it rather than reading it: `npx @adrkit/cli@0.6.0.` — a pin ending a sentence — was reported stale, which fails a REQUIRED check and blocks every merge in the repository; and `npx -y @adrkit/cli@0.1.0` did not match at all, so a badly stale pin passed. A fifth regex patch was the wrong answer, so the matcher was rebuilt around the two properties that kept breaking: trailing punctuation is trimmed before comparison, and runner flags are tolerated between `npx`/`bunx` and the package. It also hands out a fresh RegExp per scan, because `matchAll` seeds its matcher from the source's `lastIndex` and a shared global instance could silently start mid-string. Twenty tests now pin all five failure modes, including the latent one. Two fixes land in the recipe published for adopters. The pull-request variant force-pushed its branch on every run, silently destroying any review commits on the open PR, and `|| true` swallowed a missing-permission failure so the workflow went green while never opening a PR. It now pushes without `--force` and creates a PR only when none is open. The whole final step is shown rather than a fragment, because the previous snippet put a step-level `env:` inside a `run:` block for anyone who followed "replace `git push` with" literally. The `--force` that remains on the artifact branch is explained, and the badge URL's percent-encoded `%2Fmain%2F` is named as the place a branch substitution goes. The site deploy no longer depends on the queue tool at all: an `adr queue` exit of 2 now warns and publishes without `queue.json` instead of aborting the job, so a queue regression can no longer strand the canonical schema at its `$id`. Also corrects a renumber miss — `site/.gitignore` cited ADR-0024, which is a real but unrelated record — and extends this record's `affects` to `.github/workflows/ci.yml` and `package.json`, so `adr explain .github/workflows/ci.yml` now names the decision that mandates the guard living there. Verified by running it. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Signed-off-by: Mark Beacom <m@beacom.dev>
Accepts the badges record after three independent review rounds. Follows the convention set by ADR-0022/0023/0024: `status` moves to `accepted` and `provenance.ratifiedBy` is set, while `date` stays the drafting date. Ratification is what removes the record from its own queue — depth drops from 7 to 6, which is the projection this decision publishes. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Signed-off-by: Mark Beacom <m@beacom.dev>
There was a problem hiding this comment.
Pull request overview
Adds adoption and ARB queue-depth badges as recipes over existing adrkit output, avoiding new CLI or hosted-service APIs.
Changes:
- Adds badge assets, documentation, navigation, and ADR-0025.
- Generates
/queue.jsonduring site deployment. - Adds CI validation for pinned documentation CLI versions.
Reviewed changes
Copilot reviewed 14 out of 15 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
site/src/content/docs/ci.mdx |
Links CI guidance to badge recipes. |
site/src/content/docs/badges.mdx |
Documents adoption, status, and queue badges. |
site/public/badge/adrkit.svg |
Adds the vendorable adoption badge. |
site/astro.config.mjs |
Adds Badges navigation. |
site/.gitignore |
Ignores generated queue output. |
scripts/check-doc-cli-versions.ts |
Detects stale documented CLI pins. |
scripts/check-doc-cli-versions.test.ts |
Tests CLI-pin detection. |
packages/core/src/queue/types.ts |
Documents totalItems compatibility. |
packages/cli/test/lint.test.ts |
Derives expected corpus count dynamically. |
package.json |
Adds queue-generation and pin-check scripts. |
docs/RELEASING.md |
Records badge compatibility and release steps. |
docs/adr/0025-ship-badges-as-recipes-over-existing-output.md |
Ratifies the badge architecture. |
CHANGELOG.md |
Documents the new badge recipes. |
.github/workflows/site.yml |
Generates and deploys queue.json. |
.github/workflows/ci.yml |
Runs the documentation pin guard. |
Suppressed comments (1)
site/src/content/docs/badges.mdx:158
- A second run will usually fail as a non-fast-forward push while
adr-queue-refreshstill exists: every run starts and commits frommain, so the existing branch's bot or review commits are not ancestors of this new commit. Fetch/check out the existing refresh branch and integrate currentmainbefore regenerating, or use a new branch per update, so an open PR can actually be refreshed without discarding review commits.
# No --force: the branch may carry review commits, and overwriting them
# silently is worse than a failed run you can see.
git push -u origin HEAD:adr-queue-refresh
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
| # reads as a bug in your tooling. Fail here instead of committing it. | ||
| - name: Validate before publishing | ||
| run: | | ||
| python3 -c "import json,sys; r=json.load(open('.adrkit/queue.json.tmp')); sys.exit(0 if isinstance(r.get('totalItems'), int) else 'totalItems missing')" |
Comment on lines
+81
to
+86
| - name: Emit the ARB queue report for the badge | ||
| working-directory: . | ||
| run: | | ||
| bun install --frozen-lockfile | ||
| set +e | ||
| bun run packages/cli/src/index.ts queue --format json > /tmp/queue.json |
| * and the package. Prose that merely names an older release is not a recipe and | ||
| * must not fail a required check. | ||
| */ | ||
| const CLI_PIN_SOURCE = String.raw`(?:npx|bunx)(?:\s+-{1,2}[A-Za-z][\w-]*)*\s+@adrkit/cli@([^\s\`'"]+)`; |
The static `ADRs | adrkit` badge failed this record's own test. The Context
argues a badge must not assert what a reader cannot check, and "this repository
uses adrkit" is exactly that: unverifiable from the image, carrying no state,
and the only badge here a reader learned nothing from.
`adr lint --json` already emits `{ checked, findings }`, so `$.checked` reports
how many decisions are on record — the same adoption signal with the evidence
attached, for the same cost and still no new CLI surface. Both published
artifacts stay verbatim command output; adrkit defines no badge format.
Two consequences are recorded rather than glossed. Dropping the static image
drops the offline escape hatch, so MADR-0008's one portable objection — reliance
on an online renderer — is now unanswered: a number cannot be a committed SVG
without being wrong as soon as the corpus changes. And the pass/fail badge is
GitHub's, not adrkit's; the guide now says so instead of presenting three badges
as though adrkit contributed all of them.
`$.checked` counts records at any status on purpose. A superseded decision is
still a decision that was recorded, and filtering to "active" would make the
number a judgement rather than a count.
Verified: the site build serves both reports (checked=25, totalItems=6), the
badge/ directory is gone from the output, and shields renders the new label and
color parameters against a live JSON source.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Mark Beacom <m@beacom.dev>
…alidation
The reviewer found a third false negative in the pin guard: `npx
--cache=/tmp/x @adrkit/cli@0.5.0` did not match, because the option grammar
accepted only bare flags. An unrecognised option makes the whole match fail, and
a guard that matches nothing reports nothing — so a stale pin bypassed a required
check. The grammar now accepts `--opt=value`, with three regression cases.
The site deploy no longer filters pushes by path. `queue.json` and `lint.json`
are projections of main produced by the CLI, so the deploy has to re-run when
the projector changes, not only when the corpus does. Enumerating the
generator's dependencies is fragile — one missed path leaves adrkit.dev serving
numbers from an older implementation, silently — so every push to main deploys.
Pull requests keep their path filter, since there the build is only a check.
Both publishers now validate the field the badge actually reads rather than that
the file merely parses. A structurally valid but wrong report would otherwise
render `no result` and read as a bug in adrkit. The check is `type(x) is int`
rather than `isinstance`, because bool subclasses int in Python and isinstance
would accept `{"totalItems": true}` — the reviewer's point, which applied to the
recipe's earlier validation and would have re-applied here.
Verified by execution: the helper publishes on int, skips on bool, missing key,
and truncation, and still publishes on exit 1 with a complete report.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Mark Beacom <m@beacom.dev>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds two badges to adrkit — corpus size and ARB queue depth — as recipes over output the tool already produces, plus a published guide. Ratifies ADR-0025.
What ships
Both badges render a number through shields.io from JSON your own repository publishes:
ADRs: 25$.checkedfromadr lint --jsonARB queue: 6 pending$.totalItemsfromadr queue --format jsonPlus a badges guide wired into the sidebar, and
scripts/check-doc-cli-versions.ts(+20 tests) inclean-clone-buildsso the recipe's pinned CLI version cannot silently rot at a release.No new CLI surface. Both files are the commands' verbatim output — adrkit defines no badge format, and an
adr badgecommand would be public API maintained forever to reformat fields that already exist.A static "uses adrkit" badge is deliberately not offered. It asserts something a reader cannot check and carries no state; a count proves the same thing for the same cost. The pass/fail badge is GitHub's, not adrkit's — documented as a pattern, with attribution, rather than presented as a third adrkit badge.
Two mechanisms, deliberately
adrkit emits both reports from its site build —
site.ymlalready rebuilds ondocs/adr/**, so they are generated intosite/public/(gitignored, exactly like the served schema) and served fromadrkit.dev. No workflow holds a write token and there is no stored artifact to fall behind.The published recipe commits the files, because most repositories have no site to piggyback on, and names the alternatives for a protected default branch rather than shipping a snippet that fails where nobody looks. The cost — adrkit no longer dogfoods the exact snippet it publishes — is recorded in the ADR.
Refused, on posture
adr badgecommand.Trade-offs recorded rather than glossed
$.checkedcounts records at any status on purpose: a superseded decision is still a decision that was recorded, and filtering to "active" would make the number a judgement rather than a count.buildQueueReportselects bystatus: 'proposed', so depth is a pure function of the corpus. SLA state advances with the calendar and would need a scheduled rebuild and a daily bot commit to stay true.Review
Three independent five-lens
deep-reviewrounds (adversarial, architect, consumer, operator, supply-chain-security), each adjudicated. Rounds 1–2 found flaws in the original design; round 3 found flaws in round 2's fixes, twice — all verified by execution rather than reading:git diff --quietexits 0 on an untracked file, so an adopter's first run committed nothing and the badge 404'd forever.mainrequiresself-dogfood, which is gated onpull_request, andgithub-actions[bot]is not a bypass actor.npx @adrkit/cli@0.6.0.— a pin ending a sentence — was reported stale, which blocks every merge in a required check;npx -y @adrkit/cli@0.1.0didn't match at all. Rebuilt rather than patched a fifth time; 20 tests pin all five failure modes.adr check, which never touches the queue report.Notes
packages/files arequeue/types.ts(a JSDoc compatibility note) andlint.test.ts.lint.test.tsno longer hardcodes the corpus count — it had been bumped21 → 22 → 23 → 24across four PRs and conflicted on every rebase.QueueReport.totalItemsnow carries an external-consumer obligation, recorded on the field and indocs/RELEASING.md.Gates: 1994 tests, typecheck, lint 8/8,
check:deps,check:doc-pins, corpus 25 records clean, site build serving both reports.