## 🎯 The release

**An audit is `docir check` — and `check` now says when it could not look.**

Asked to audit a store, an agent with docir installed either never loaded the skill — its
description named implementing, recording, searching and migrating, not checking — or loaded
it, ran `docir check`, and then re-derived every finding with grep and Python anyway. That
cost as much as auditing without docir at all.

### The skill says the findings are the answer

The installed skill now loads for checking the docs, and tells the agent that `check` applies
each rule to every file, which of a `dangling` finding's two ids is the document, and what a
clean `check` does *not* mean.

Measured with `benchmarks/agent_tokens.py` on this repository's store with five planted
defects — a dangling edge, a duplicate id, a `depends_on` cycle, an overdue review, a file that
does not parse: 12 of 12 audit runs loaded the skill and named exactly the planted defects, at
a median of $0.090–0.116 per audit against $0.23–0.38 for an agent without docir
(claude-sonnet-5, 93 to 259 documents, 2026-09-28). Take it with:

```bash
docir agent update
```

### A clean `check` now means something

With no git repository above the store, there is no tree to resolve a `code:` glob against, so
`unmatched-code`, `code-unwatched`, `code-changed` and `code-drifted` all went silent — and an
empty code section read exactly like a tree where nothing had moved. A store whose documents
declare `code:` now gets one `code-unchecked` warning saying so. A store that declares no code,
like the ordinary global one, says nothing.

## 🎯 Also in this release

- **The `duplicate` finding says what to do with it.** `docir lint --deep` names two unlinked
  documents that are the same document written twice; the skill, the docstring and the MCP
  tool now say the answer is an edge, not a delete. The skill also tells an agent to run
  `docir context` over a description before `docir add`, and to record a disagreement it finds
  by reading as a `contradicts` edge.
- **Measured and rejected: detecting contradictions** (adr-3c5eb1ff3800). The embedder scores
  subject, not stance — a claim and its own negation score up to 0.983 — so no threshold both
  reaches this corpus's recorded contradictions and stays readable.

## 🐛 Bug fixes

Each of these was docir reporting success on something it had not done.

- **A repeated list flag keeps every value.** `docir add --type decision --title T --code "a/**"
  --code "b/**"` wrote only `b/**`, and said nothing. The seven write-side list flags — `add
  --tags/--related/--code`, `update --set-tags/--set-related/--set-code` and `init --profiles` —
  now repeat the way every read-side one already did; each occurrence is still comma-split.
- **A file that lists one tag, glob or edge twice is read once.** A repeated tag failed every
  `docir reindex` with a raw `IntegrityError`; a repeated glob made `--replace-body` refuse the
  document as changed on disk, permanently. `docir check` now names such a file as
  `repeated-entry`, and `docir check --fix` rewrites it without the repeat and without moving
  `updated`.
- **`docir self upgrade` no longer calls a stalled upgrade "already the newest build".** An
  installer can exit 0 having changed nothing — a `uv tool` receipt pinned to an exact version,
  a pip held back by a constraint file. The version is now read back after the install; when
  it did not move, docir says so and quotes what the installer printed, which is where the
  cause is.

## ⬆️ Upgrading

- Run `docir agent update` so the installed skill loads for audits.
- A wrapper that passed a list flag twice and relied on the last one winning now gets both.
- If `docir check` reports `repeated-entry`, `docir check --fix` repairs it.

## 🔗 Full changelog

See [CHANGELOG.md](https://github.com/l0kifs/docir/blob/v0.30.0/CHANGELOG.md)