Skip to main content

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. CODEOWNERS on 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 isdocs/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:

  1. Export a note (any notetaker → Markdown) into a folder and run verdica bootstrap --notes <folder>.
  2. 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).
  3. 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

verdica-0.11.0.tar.gz (311.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

verdica-0.11.0-py3-none-any.whl (43.8 kB view details)

Uploaded Python 3

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

Hashes for verdica-0.11.0.tar.gz
Algorithm Hash digest
SHA256 59322229c2a9efed0dd50106047f477546ce4fedbc7b7cd0114f18b0ed82d0e0
MD5 8398538e936afcca0384edbbe7410c23
BLAKE2b-256 3c1ec094018072c9cf7ecfd439cab7045e9d0acdb259595aeac729c7e07c2da9

See more details on using hashes here.

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

Hashes for verdica-0.11.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f7896a8cc4635b7783c1441fda7287c806e7323352bb884b07edb59020d2a9d8
MD5 d89e72f7849ad9fca970b0be7a02e5e0
BLAKE2b-256 980241f25a6b40b0a43402e76118f53ad1b1cb1595bf82598e95f0f7c76f5783

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.11.0 This release

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page