Verdica
Team decisions as files in the repo, enforced on every diff.
Teams decide things — "integrations must not add YAML config options", "Postgres, not Mongo, for the order store" — and then the decision gets violated months later by someone who wasn't in the room. Review misses it, the change merges, and the cost is paid twice: once as an incident or a revert, once as the re-litigation of a settled question. It happens to the best-run projects: home-assistant/core#149379 passed review and merged, then had to be reverted because it violated two recorded architecture decisions.
verdica keeps decisions where the code is and checks every pull request against them.
- Decisions are files in
.decisions/— reviewed, ratified, and superseded through ordinary PRs.CODEOWNERSon the directory defines who ratifies. No lock-in: it's your repo. See SPEC.md. - The gate is precision-first. A deterministic path-scope match decides which decisions govern a change; only on a scope hit does an LLM judge whether the diff contradicts the decision. No key configured → the check still reports "this change touches the scope of DEC-0007", which is most of the value at zero cost.
- Agents read files. Decisions in the repo are automatically part of the context of any coding agent working there — the registry is the memory that agents lack between sessions.
Use as a GitHub Action
# .github/workflows/verdica.yml
name: verdica
on: [pull_request]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: verdicahq/verdica@v0
with:
base: ${{ github.base_ref }}
mistral-api-key: ${{ secrets.MISTRAL_API_KEY }} # optional
comment: "true" # needs pull-requests: write
Runs in your CI, with your key. Nothing leaves your infrastructure except the diff hunks sent to the model you configured — and nothing at all if you run without a key.
CLI
pip install verdica
verdica init # scaffold .decisions/ with a template
verdica check --base origin/main # check the current branch's diff
verdica digest # what changed in the registry, and what stands
verdica mine owner/repo # mine a repo's history for decision citations
verdica mine classifies every PR that cites a decision record (ADR/KEP/DEC/RFC) by timeline: cited during review, cited only after the merge, or a revert citing a decision — the last two are violations that slipped through. Run it on your own repository to see what a gate would have caught; it needs GITHUB_TOKEN and nothing else.
Already have ADRs?
verdica reads an existing registry where it already is — docs/adr, doc/adr, docs/decisions, and the other conventional locations — in the format teams already write (Nygard, MADR). No migration, no reformatting:
cd your-repo && verdica digest
Imported decisions are visible and citable from the first run. They carry no scope yet, so they are digest-only; add a scope.paths to any one of them and it starts gating pull requests. .decisions/ takes precedence when present.
Digest
verdica digest is the non-gating half: what was ratified or proposed this week, the standing registry grouped by category, what still awaits ratification, and registry hygiene — decisions whose scope points at code that no longer exists, decisions not yet enforceable, and pairs where one decision appears to reverse another while both are still marked accepted.
That last check exists because of a measurement: across 166 public registries, 93% had never marked a single decision superseded and half had taken no new decision in over a year while their code kept changing. Decisions do get reversed; registries just don't record it, and a reader cannot tell what is still in force. The digest is the going back.
Rule previews
A rule is easy to agree with in the abstract and expensive in practice. When a pull request proposes or edits a decision, verdica replays it over the recent history and reports what it would have done:
## If you merge this, the rules change
- DEC-0001 Every change to the web build re-verifies the shaders — would have
flagged 5 of the last 26 merges: #140 release/0.1.0-11, #135 feat/apple-sign-in, …
Reviewers ratify a rule knowing its cost, not guessing at it. A decision that would have flagged nothing is a decision nobody will feel; one that would have flagged half your merges needs a narrower scope before it is worth merging.
Scope suggestions
A change can touch a decision's subject while sitting outside its declared scope — a palette decision scoped to the theme directory says nothing about a colour landing in a static page. When a changed file carries a decision's own marks (a hex colour, an identifier it names) outside every scope, the check says so under Scope suggestions. It never gates: widening a ratified scope changes what the team promised to enforce, so it takes a ratified edit.
Severity
Each decision declares how the gate reacts to a violation: warn (default — the finding goes to the check summary) or block (the check fails, only on high-confidence contradictions). Start everything at warn; promote a decision to block when you trust it.
Configuration
| Env var | Default | |
|---|---|---|
MISTRAL_API_KEY |
— | enables judge + distillation on Mistral (the reference provider) |
ANTHROPIC_API_KEY |
— | same, on Anthropic (pip install verdica[anthropic]) |
VERDICA_PROVIDER |
auto (mistral first) | force mistral or anthropic when both keys exist |
VERDICA_MODEL |
per provider | mistral-large-latest / claude-opus-5 |
GITHUB_TOKEN |
— | required by verdica mine |
Bootstrap (from zero to hero)
verdica bootstrap # report-only: what it would propose
verdica bootstrap --notes ~/meetings # include meeting exports (Granola, retros)
verdica bootstrap --write # write proposals into .decisions/
verdica bootstrap --html report.html # a page anyone can review, terminal or not
verdica bootstrap --pr # write, branch, and open the bootstrap PR
The bootstrap extracts decision candidates from what the repo already contains — normative comments and docs, reverts, convention files, plus any notes folder you point it at — clusters the evidence, backtests the proposals against your recent merges (noisy scopes get demoted, decisions that would have caught a reverted merge get a receipt), and asks at most five one-tap questions for the calls it genuinely cannot make alone. Everything it proposes cites its evidence; nothing is invented. Machine-enforced rules (lint, CI) are recognized and linked, never duplicated.
Reviewing without a terminal
--html renders the same run as a page: each proposed rule with the evidence it came from, the scope it would take, and what it would have done to recent merges. It is meant for the people who care most that decisions survive — leads, founders, product — who are not going to read a terminal. Keep what is real, drop the rest, merge the pull request: that is the ratification.
The hosted flow follows the same shape and the same rule: the dashboard composes pull requests, it is never the source of truth. Whatever it shows lives in your repository, and uninstalling leaves it there.
Meeting notes ingestion
Decisions are born in meetings; three ways to get them in front of the extractor:
- Export a note (any notetaker → Markdown) into a folder and run
verdica bootstrap --notes <folder>. - Keep notes in the repo: files under
meetings/,minutes/,verbali/,retros/,postmortems/are treated as meeting sources — full provenance, digest-only (they never gate a diff and never invent scope paths). - Granola via Zapier (Granola's official outbound channel; its local cache is encrypted): Zap trigger "Note added to Granola folder" → GitHub action "Create file" into
docs/meetings/{{title}}-{{date}}.md. Every meeting becomes a commit; the next bootstrap run distills it.
Say decisions out loud in decision form — "decided: …", "from now on …" — those markers are what the extractor anchors on. An inbound-email ingestion address (works with every notetaker that can email a summary) is planned for the hosted tier.
License
Apache-2.0
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file verdica-0.11.0.tar.gz.
File metadata
- Download URL: verdica-0.11.0.tar.gz
- Upload date:
- Size: 311.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.12.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
59322229c2a9efed0dd50106047f477546ce4fedbc7b7cd0114f18b0ed82d0e0
|
|
| MD5 |
8398538e936afcca0384edbbe7410c23
|
|
| BLAKE2b-256 |
3c1ec094018072c9cf7ecfd439cab7045e9d0acdb259595aeac729c7e07c2da9
|
File details
Details for the file verdica-0.11.0-py3-none-any.whl.
File metadata
- Download URL: verdica-0.11.0-py3-none-any.whl
- Upload date:
- Size: 43.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.12.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f7896a8cc4635b7783c1441fda7287c806e7323352bb884b07edb59020d2a9d8
|
|
| MD5 |
d89e72f7849ad9fca970b0be7a02e5e0
|
|
| BLAKE2b-256 |
980241f25a6b40b0a43402e76118f53ad1b1cb1595bf82598e95f0f7c76f5783
|