Skip to content

Use in CI

adrkit ships three GitHub Actions from this repository. They hold no database and never approve anything. Two run with the default GITHUB_TOKEN and write a comment or an issue; the third, the decision portal, holds no token at all and only writes a directory for your workflow to deploy.

The packages/ci Action reads a pull request’s changed files, resolves which accepted decisions govern them via each record’s affects matchers, validates any changed ADR records, and posts a single comment. It is read-only and comment-only; on a read-only fork token it degrades to a log notice instead of failing the job.

The Action’s comment on a pull request in adrkit-demo: ADR-0002 governs through the path src/money/**, and ADR-0007 through an @adr marker on line 1 of src/handlers/transfers.ts

The comment above is from adrkit-demo, which keeps pull requests open for a compliant change, a violating one, a new decision, and an invalid record.

.github/workflows/adr.yml
name: ADR governing decisions
on:
pull_request:
permissions:
contents: read
pull-requests: write
jobs:
governing-decisions:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: mbeacom/adrkit@v0.20.0
# with:
# dir: docs/adr # ADR corpus directory (default: docs/adr)
# token: ${{ github.token }}

The established mbeacom/adrkit/packages/ci@v0 nested form remains supported.

The Action updates its existing comment on later pushes, so each pull request keeps one current governing-decisions comment. No additional token environment variable is required when using the default GITHUB_TOKEN.

The comment reports marker results separately from governing decisions. Marker scan health lists changed files that could not be inspected (absent, unreadable, out-of-tree, or skipped at the scan cap), so an empty marker result is not mistaken for a healthy scan. It also reports declaration overflow after the 64-per-file or 10,000-per-batch safety limits. Marker claims needing attention lists @adr claims that either did not resolve in this corpus or resolved to superseded, rejected, or deprecated records. Both sections are bounded and advisory: they never affect the Action’s exit status, and changed-record validation errors retain priority if the comment reaches GitHub’s size limit. Keep the default checkout rooted at GITHUB_WORKSPACE; if a workflow checks out elsewhere, marker health will identify files the Action could not inspect.

The Marketplace form is pinned to the immutable v0.20.0 release. For the nested compatibility form, v0 is a moving major tag; pin an immutable release tag or commit SHA when maximum reproducibility matters.

The packages/ci/queue Action creates or updates exactly one dedicated issue whose body carries the deterministic adr queue Markdown report — the tiers, SLA state, approvals, and objections projected from the corpus’s review metadata. It needs issues: write.

.github/workflows/adr-queue.yml
name: ADR ARB queue
on:
schedule:
- cron: '0 13 * * 1' # Monday 13:00 UTC
workflow_dispatch:
permissions:
contents: read
issues: write
jobs:
arb-queue:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: mbeacom/adrkit/packages/ci/queue@v0
# with:
# dir: docs/adr
# issue-title: ADR ARB Queue

The managed issue’s body is the same report you get locally:

# ARB Queue — 2026-07-28
Corpus fingerprint: `0cd56b8…10e3eb6`
8 item(s) | 0 corpus finding(s) | 0 item(s) with findings
## Queue Items
| # | ID | Title | Tier | SLA State | Deadline | Approvals | Objections |
|---|----|-------|------|-----------|----------|-----------|------------|
| 1 | 0003 | Ship as a Spec Kit extension plus a standalone CLI … | arb | within-sla | 2026-08-27 | 0/- | 0 |
| 2 | 0006 | License Apache-2.0 with a DCO and develop in a single … | arb | within-sla | 2026-08-27 | 0/- | 0 |

Identical inputs produce byte-for-byte identical output, so a re-run only changes the issue when the corpus actually changed. Run the same projection locally with adr queue, or publish its totalItems as a README badge — see Badges.

The packages/ci/portal Action builds a static, read-only decision portal from the corpus: the record list and every record, status counts, the supersession graph, the review queue, and governance by path. It writes the bundle into a directory and does nothing else. It holds no token, needs only contents: read, and never deploys. Your workflow uploads the directory with actions/upload-pages-artifact and deploys it with actions/deploy-pages in a separate job that alone holds pages: write and id-token: write (ADR-0054).

Set the repository’s Settings → Pages → Source to GitHub Actions, then:

.github/workflows/adr-portal.yml
name: ADR decision portal
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: pages
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- id: portal
uses: mbeacom/adrkit/packages/ci/portal@vX.Y.Z # an immutable adrkit release tag
# with:
# dir: docs/adr
# out: _site
# base-url: https://<owner>.github.io/<repo>/
# as-of: 2026-07-05
# publish-private: 'true'
- uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
with:
path: _site
deploy:
needs: build
runs-on: ubuntu-latest
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- id: deployment
uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1

The default shallow checkout is enough: the Action reads HEAD, the tracked file list, and the working tree’s status, never history. The bundle uses relative assets and hash routes, so it works at a project path (/<repo>/) or a custom domain without base-url; base-url feeds absolute links only.

The step’s outcome follows the portal generator’s exit code, which it also sets as the exit-code output:

exit-code Bundle Step
0 written succeeds
1 written, with every corpus error shown in a banner on every page and governance by path withheld succeeds, with a warning annotation
2 not written; an existing out is left as it was fails

So a corpus with error findings still publishes, and says so on every page, while a corpus that cannot be read publishes nothing and leaves the last deployed site in place.

adrkit builds its own corpus this way on every deploy of this site, from the committed Action in the same checkout, and serves it at adrkit.dev/decisions/. The bundle needs no base-url to work under a subpath: its assets are relative and its routes are hash routes.