docir — design documents
Recently updated
-
Keep the corpus trustworthy (maintenance, CI, staleness)
How the corpus stays consistent: reindex, check, repair, and the merge guard.
active -
Doc-Index CLI — validation strictness tiers
The three tiers deciding what blocks a write, what a check reports and what stays advisory, plus how a schema change is detected against the index build.
active -
check drops every code finding outside a git repository and says nothing, so the silence reads as clean
With no repository above the store, docir check skips unmatched-code, code-unwatched, code-changed and code-drifted without a word, even when documents declare code globs.
resolved -
0.30.0 — an audit is docir check, and check says when it could not look
An agent audits a store by trusting docir check's findings at under half the cost of auditing without docir, and a check that could not resolve code globs says so instead of reading clean.
published -
A code glob nothing is watching is a finding, not a silence
Why check reports code-unwatched for a glob that resolves and carries no digest, why check --fix may repair it when its three siblings need a judgement, and why it is a warning.
proposed
Architecture 12
-
Doc-Index CLI — the write path
What happens when a document is created, edited, archived or deleted: id allocation, schema validation, the index update, and how a diverged file is handled.
active -
Doc-Index CLI — the read path
How a query becomes results: full-text and vector rankings fused, section-level embeddings, default status visibility, and reads spanning peer stores.
active -
Doc-Index CLI — the file format on disk
What a document is as markdown: every frontmatter field, what validates it, and the tag registry the tags resolve against.
active -
Doc-Index CLI — the CLI surface
The command vocabulary agents drive docir with, the static site build, and a worked flow through them end to end.
active -
Doc-Index CLI — validation strictness tiers
The three tiers deciding what blocks a write, what a check reports and what stays advisory, plus how a schema change is detected against the index build.
active -
Keep the corpus trustworthy (maintenance, CI, staleness)
How the corpus stays consistent: reindex, check, repair, and the merge guard.
active -
Doc-Index CLI — Architecture
The shape of the system: git as the source of truth, the SQLite index as a derived projection, the layer map and the daemon — with each part of the pipeline in its own document.
active -
Architecture Rules — Modular DDD
The MUST/SHOULD module rules this codebase is held to, and how CI proves them.
active -
Capture a decision (the write path)
How a decision is captured: validate, allocate an id, write the file, project the index.
active -
Adopt docir in a repository (bootstrap, schema, agent onboarding)
How a repository gets a store and an agent learns to drive it.
active -
Maintain the tag vocabulary
How the controlled vocabulary is registered, renamed and retired.
active -
Retrieve relevant context (the read path)
How a task turns into a ranked document set: hybrid fusion plus graph expansion.
active
Decisions 77
-
Contradiction is authored, never detected
Why lint --deep gets no contradiction-candidate finding: the embedder scores subject and not stance, and every threshold reaching this corpus's three recorded contradictions admits sixteen thousand pairs.
accepted -
Which of two colliding files keeps the id is decided by git, then created, then the alphabet
Why check --fix reads commit history to pick the established document, and why a filename tiebreak now says it could not tell.
proposed -
--force overrides incoming references, not ambiguity about which document is meant
Why delete refuses an id more than one file claims whatever flags it is given, and why that refusal reads the files rather than the index.
proposed -
The environment an MCP client runs in is a tool, because it is not a command
Why docir_doctor is the second exception to one-tool-per-dispatcher-command, and why the composition and payload moved out of the CLI to get there.
proposed -
The release notice is a store's decision, and says nothing it cannot act on
Why the ambient update notice is opted into by `docir init` rather than a shell, what it refuses to tell a checkout, and where MCP carries it.
accepted -
The id collision is reported before the merge, against a base ref
Why check gains --against rather than a command of its own, why both its findings are errors, and why an unreadable ref must not be silence.
proposed -
A code glob nothing is watching is a finding, not a silence
Why check reports code-unwatched for a glob that resolves and carries no digest, why check --fix may repair it when its three siblings need a judgement, and why it is a warning.
proposed -
A code glob a repository ignores entirely is still the code it governs
Why .gitignore drops files from a code: glob only while the glob reaches something else, and why a glob that reaches nothing but ignored files is honoured instead of erased.
proposed -
A code glob hashes what the repository tracks, not what it builds
Why the fingerprint behind code-drifted consults the repository's own .gitignore files, and only those — not git check-ignore, not the user's global excludes.
proposed -
Where a downloaded embedding model lives
Why the model is cached in the user-level ~/.docir/models, passed as fastembed's cache_dir argument rather than left to its temp-directory default.
proposed -
A document is the current state; git is the history
Why the writing skill forbids appending a dated section for every change, and why that rule ships as prevention rather than as a new lint finding.
proposed -
A dispatcher command may answer about the build, when place cannot change the answer
Why the deprecation register is a command and a tool, what test lets it be one when the rest of doctor is not, and why it is not folded into store_status.
accepted -
A committed file changes by adding a key, or by raising a floor
Why new meaning goes in a new key older builds ignore, why the fallback is a store_format floor read before the schema resolves, and why neither reaches a build already installed.
accepted -
A code glob is watched from the write that declares it
Why drift is measured from a baseline the write mints, why that is a second digest beside the verified one, and why re-declaring a glob never clears a drift.
accepted -
A breaking change is announced with a date, by doctor
Why deprecations carry a sunset date, why docir doctor is the channel rather than a release note, and why --strict stays green until the date passes.
accepted -
A partial type block overlays the type the package ships
An inline types: entry missing prefix/statuses/default_status is folded into the resolved type key by key instead of replacing it, so a store can set one property of a core or profile type without pinning the rest.
accepted -
One max_body_chars per type, and a flag that picks the tier it acts at
One max_body_chars per type, read by both tiers; max_body_chars_enforce decides whether it refuses the write or only reports it, so the Tier 2 smell is never a second number.
accepted -
A third, opt-in skill for reporting docir defects upstream
Why upstream feedback ships as its own opt-in skill that only drafts a report, why it is suggested at every adoption moment but never installed by default, and why the agent never files it.
accepted -
The look-alikes the audit keeps, and why DocumentService stays whole
The eleven shapes that look like catalogue smells and are deliberately kept, and the shared read/write predicate that makes splitting DocumentService a regression.
accepted -
A prose link resolves, renders and is checked — and stays out of the graph
Why [[...]] cross-references now resolve against ids, filename stems and title slugs, why the unresolved ones are a Tier 1 warning where unresolved mentions are not, and why they still feed no check.
accepted -
The daemon keepalives; the reply timeout bounds silence, not work
A flat reply budget made `docir self upgrade` impossible on a large corpus, because the command that always makes docir's most expensive request was bounded by a number chosen for an ordinary one.
accepted -
A verification is withdrawn when the content it covered moves
Why a content edit revokes a verification, why the cadence restarts from the revocation rather than from creation, and why only a standing verification can be revoked.
accepted -
The orphan exemption is a recorded field, not a prose mention
Why check stopped reading the mention graph and gained an isolated: reason instead — a judgement about a queue must not empty the queue.
accepted -
Staleness ages from creation, never from the last edit
Why an absent `verified` falls back to `created` rather than `updated`, and why absent is not treated as infinitely stale.
accepted -
Opening a store with no index builds one, deferring the vectors
Why a fresh clone or git worktree now rebuilds its index on open instead of failing until someone runs reindex, why only the empty case qualifies, and why the embedding drain stays off that path.
accepted -
Federated rows carry the store's own description
Why a store describes itself in its own stores.yaml, and why that sentence rides on every federated row beside the store path.
accepted -
Verify a change against the published build, not just this one
Why every change touching a committed file, the on-disk contract or the index schema is run against the docir release on PyPI, in both directions, before it ships.
accepted -
check refuses to report a verdict it could not reach
Why an index holding nothing beside files on disk is an error rather than a warning: --strict was a merge gate that passed by reading nothing.
accepted -
Blocking is its own relation property, split from dependency
One flag was answering two questions — where two types sit, and whether one waits for the other — so a decision refining a superseded one read as ready to start.
accepted -
A JMESPath expression over the corpus, and no rules of docir's own
query --expr filters on an expression over each document and its resolved edges, which is the ability to state a rule without docir shipping any.
accepted -
docir doctor — one report for every way docir is subtly wrong
Why the environment checks scattered across five commands became one command, and why the corpus is deliberately not part of it.
accepted -
No type key, no body, and an unknown field is refused
The two questions store checks left open answer to 'nothing to add', and answering them found that a mistyped field matched nothing silently.
accepted -
implements carries no property, and that is the decision
Giving implements a machine meaning would warn on ordinary modelling, so it stays a word for the reader — and a kind gains a property on evidence, not on symmetry.
accepted -
A store declares its own checks; docir ships none
checks: in docs-schema.yaml runs a store's own JMESPath rules as Tier 1 warnings, which is how docir gains a validator without gaining an opinion.
accepted -
The agent skill is a directory, regenerated on every install
Why the packaged CLI guide became SKILL.md plus one-level-deep reference files, and why installing a skill sweeps the files a build no longer ships.
accepted -
A code-coverage advisory was measured and not built
Prose naming a source path is not evidence a document governs it — the examples, the historical records and the inventories are indistinguishable from the text.
accepted -
Every change is exercised against docir's own corpus before it is done
The gates pass on defects that only a real store with history and a warm daemon exposes — 0.18.0 shipped three of them past a fully green suite.
accepted -
The deep read is batched, and its shape follows the key
Why get takes several ids and id#heading addresses in one request, why the plural payload key rather than the result count decides the reply shape, and why an address that does not resolve is data instead of an error.
accepted -
docir does not generate: the caller is already a model
Query rewriting belongs at the caller, which is a frontier model that has read the code, so docir ships no generative model and accepts several queries instead.
accepted -
Pseudo-relevance feedback, built and rejected on measurement
Rewriting a query with its top hits' own words costs 0.13 recall@5 on docir's corpus, because the first pass is already good enough that feedback only drifts.
accepted -
Several queries take turns rather than pooling their scores
Interleaving per-query rankings keeps what a correct extra phrasing finds while bounding what a wrong one costs — the only one of three fusion shapes that does both.
accepted -
A feature ships with its instructions, verified by use
A business feature is done when an agent holding only the installed package can tell what it is, when to reach for it and how to invoke it — and somebody has followed those instructions to check.
accepted -
The task is not weighted above the caller's extra phrasings
Weighting the literal task removes --also's gain along with its risk, because an extra query is powerful exactly to the degree it can outvote the task.
accepted -
A version stamp that moved is not a content change
Why docir agent update reports 'unchanged' when a release moved only the version stamp, and why the file is rewritten anyway.
accepted -
A second relation graph, derived from the prose
Why ids named in a body become mention edges, why no Tier 1 check reads them (orphan did, until adr-e98749aa457d), why they stay out of frontmatter, and what following them cost in the benchmark.
accepted -
A rebuild is the only way to recompute vectors
Retire reindex --embeddings instead of repairing it: it recomputed exactly the vectors a rebuild recomputes anyway, for the same time, and skipped both stamps.
accepted -
A verification records what the code looked like
Why --verified fingerprints the globs a document governs, why the digests live in the file, and why the resulting code-changed finding stays a warning.
accepted -
schema validate measures the corpus, not just the file
Why the command run after a schema edit reports what that schema costs the corpus, reads files rather than the index, and never changes the exit code.
accepted -
AGENTS.md points at the skill instead of inlining it
Why the AGENTS.md block became a pointer (description + path) and now always installs the skill it names, instead of embedding a second copy of the guide.
accepted -
A second, optional skill for how to write the documents
Why documentation-writing rules ship as their own opt-in skill rather than a section of the CLI guide, and why the length rule is a section limit rather than a word count.
accepted -
docir's own prose is validated against the CLI, all six sources
Why the command-resolution guard covers CLAUDE.md and the project store as well as the shipped guide, why a retired binary name needs a separate check, why the unreal-command exemption list may only shrink, and why type/status values are checked against the shipped vocabulary.
accepted -
A document's type is mutable; its id is not
Why docir update can retype a document while its id and prefix stay put, and why a schema can subtract a type the core or a profile contributed.
accepted -
Mermaid diagrams draw from a supplied runtime, not a bundled one
Why docir build renders mermaid fences as diagrams, and why the runtime is a build input like --logo rather than a vendored asset.
accepted -
Reads federate across declared stores; writes never do
Why docir reads fan out across a committed list of peer stores, why peers are opened read-only, and why the merge sorts on similarity rather than score.
accepted -
docir self upgrade: the local half of an upgrade
One command for the steps that follow a new docir release, and why installing the new docir is not one of them.
accepted -
Upgrading the package: re-exec, and only where docir owns its environment
How docir self upgrade installs a new docir, which installs it refuses to touch, and why the release check is opt-in.
accepted -
Schema drift is reported, never enforced
Why a store records the resolved schema it was built against, why reindex is the only thing that advances it, and why the finding is a warning that must never become an error.
accepted -
Decisions are enforced by tests, not by a rule engine
Gap 6 closes as a decision: a testable decision is bound to the test that enforces it, CI prints the decisions a branch touches as a notice, and docir builds no DSL, sandbox or per-language analyzer.
accepted -
Relation-kind meaning is schema data, not three hardcoded name sets
Custom relation kinds could join none of the sets that decide cycles, layering and successor traversal, so they were silently exempt from all three; symmetric/dependency/successor become declarable properties.
accepted -
The index filter is chip-displayed state with a token grammar
Why the published index shows applied filters as removable chips, accepts tracker-style tokens (type:x, is:stale, -status:y), records each facet step in history, and dims zero-count options instead of dropping selections.
accepted -
The site publishes the relation graph as an interactive constellation page
Why docir build emits graph.html — a deterministic per-type constellation map of the corpus — plus a landing-page index, and why the graph is a domain projection (graph_payload) rather than a second data path.
accepted -
MCP as a second transport over the dispatcher
Why docir mcp serve is a third client of the dispatcher rather than a second implementation.
accepted -
Embed each section, because the model never read the rest
Why per-section vectors exist: 56% of the corpus was outside the model's token window and absent from the semantic index.
accepted -
A publishing module that takes documents as data
Why docir build renders a static site, and why the module takes JSON rather than a DocumentService.
accepted -
Cross-encoder reranking, built and rejected on measurement
Why docir has no reranker: three models and three shortlist widths all ranked worse than plain RRF fusion.
rejected -
A platform.naming leaf for shared name grammars
Why the tag-key grammar lives in platform rather than being written twice or crossing a module boundary.
accepted -
Semantic embeddings on by default
Why a real embedding model is the default and the hashing embedder only the fallback.
accepted -
A qa profile, a release_note type, and schema introspection
Why a qa profile, a release_note type and schema introspection were added.
accepted -
Per-project store discovery + docir init
Why a project-local .docir store is discovered the way git finds .git.
accepted -
Agent-instruction scaffolding as a self-contained module
Why agent-instruction scaffolding is a self-contained module that bypasses the daemon.
accepted -
A frozen core schema + swappable domain profiles
Why the schema is a frozen domain-agnostic core plus swappable domain profiles.
accepted -
Typed relation edges + per-type allowed relations
Why relation edges carry a kind, and how the on-disk form stays backward compatible.
accepted -
Staleness as data (owner + verified + review cadence)
Why staleness is owner + verified + review cadence data rather than a heuristic.
accepted -
Keep a central test tree, organized per module
Why tests live in a central tree mirroring the modules instead of beside them.
accepted -
Authorization and cross-cutting concerns are not instantiated
Why no authorization or cross-cutting machinery exists in a single-user local CLI.
accepted -
Keep the shared derived index and single unit-of-work
Why one shared SQLite schema and unit-of-work spans all contexts, and what that costs.
accepted -
Adopt Modular DDD enforced by tach
Why the codebase is vertical bounded-context modules with tach proving the boundaries in CI.
accepted
Issues 140
-
check drops every code finding outside a git repository and says nothing, so the silence reads as clean
With no repository above the store, docir check skips unmatched-code, code-unwatched, code-changed and code-drifted without a word, even when documents declare code globs.
resolved -
A repeated list flag kept only its last value, silently
Why add --code a --code b wrote only b, and why every list flag now takes both the repeated and the comma-separated form.
resolved -
A frontmatter list naming one entry twice wedged reindex or refused --replace-body
Why a repeated tag failed every reindex, why a repeated glob or edge made --replace-body refuse the document for good, and where the repeat is now dropped.
resolved -
An installer that exits 0 without upgrading was reported as already current
Why a pinned uv-tool receipt made `docir self upgrade` claim the newest build, and what docir now says instead of guessing.
resolved -
The model-cache temp-directory guard builds its fake home inside the temp directory
The guard uses tmp_path for a home it asserts is not under the temp directory, so it fails on Linux whatever the code does, and a symlinked temp dir hid that on macOS.
resolved -
update on a duplicated id edits whichever file the index happened to keep, silently
update, archive and unarchive act on one of two files claiming an id without saying which; nothing is lost, but the edit lands somewhere the caller did not choose.
open -
Sequential id collisions surface only at the merge, and the surviving id was decided by filename order
Two branches off one base allocate the same next id; nothing warns before the merge, and check --fix picked the survivor by filename whenever both were created the same day.
resolved -
delete on a duplicated id deletes one file, strips every citer's edge, and drops the other from the index
Every step of delete reads the index, which holds one row per id, so a delete against two files claiming one id acts on a file nobody chose and breaks edges to the survivor.
resolved -
The embedding model takes every core, so warming, reindexing and searching saturate the CPU
fastembed builds its ONNX session with no thread limit unless one is passed; DOCIR_EMBED_THREADS caps it, and the daemon respawns when it changes.
resolved -
Code globs stop matching under a gitignored path, so governed clones lose invalidation
The fix for issue-ec3819b1f13c excluded ignored files from matching as well as fingerprinting, silencing every document governed by a vendored or mirrored source.
resolved -
A command the CLI-surface document never names is invisible to every prose guard
The oracle checks that each line the prose carries resolves; nothing checked that every command is carried, so doctor and two daemon subcommands went unlisted.
resolved -
A code glob verified while it matches nothing records an empty baseline that never re-arms
A glob declared before the code it governs records no digest, so no finding reads it and no edit to that code is ever reported.
resolved -
A file.py:line citation rots silently, and nothing checked one
Line numbers move with every insertion above them; five documents carried citation columns that had shifted wholesale, and a sample of eighteen in-bounds ones found seven pointing at the wrong symbol.
resolved -
docir query --code finds nothing for a dot-prefixed path, so the CI notice step is blind to workflow changes
_normalize strips a leading dot as a character class, so a document governing .github/workflows/** can never be returned by the reverse query — while check's own matcher, which does not share the function, reports its drift correctly.
resolved -
The store's generated .gitignore misses release-check.json and is never refreshed
The generated ignore list omits release-check.json, and an existing store's copy is written once by init and never brought up to a later release — so the feedback/ entry never reaches the stores that predate it.
resolved -
A peer that was unreadable when the daemon first tried it stays skipped for the daemon's life
The federated reader caches each opened peer per container, so a peer repaired after first contact keeps being skipped over the socket while --no-daemon reads it — and the comment beside the cache promises the opposite.
resolved -
The retired-binary guard read inline spans and never a fenced block
cli_oracle had two extractors and they disagreed about fenced code, so a dead command name sat in an architecture note's worked example with every prose guard green.
resolved -
doctor and daemon status still crash where the daemon has nowhere to listen
Both read Settings.socket_path directly to report it, so the sandbox that made every dispatched command fall back still ends these two in a traceback.
resolved -
A docs-schema.yaml edit is invisible to every daemon-served command until the daemon is replaced
The daemon loads the schema once at startup, its watcher does not react to the schema file, and nothing stamps that file into the pid record — so a schema change is honoured only in-process until the daemon is stopped.
resolved -
A daemon that cannot be started fails the command instead of running in process
The client spawns the daemon on first dispatch and has no fallback, so a sandbox that denies the socket or log directory turns every read into a traceback — though --no-daemon answers the same read.
resolved -
The embedding model is downloaded into a directory the system may delete
fastembed's default cache is under the temp directory, so the 64 MB download is re-paid whenever the OS sweeps — and where there is no temp directory, every read crashes before docir runs.
resolved -
A code glob fingerprints gitignored build output, so a rebuild reports drift
Every file under a code: glob is hashed, ignored ones included, so one compile drifts every document governing a source tree that holds build output.
resolved -
A ** glob hashes the package's bytecode, so it drifts on its own
code: patterns ending in ** fingerprinted __pycache__ alongside the source, so a digest moved when no line was edited and differed across machines.
resolved -
A daemon that dies at startup is reported as a timeout
The client spawns a daemon, waits, and reports the wait — so a store whose schema will not load reaches the caller as a timeout instead of the reason, which stays in daemon.log.
resolved -
An unloadable schema kills the MCP server with no message
docir mcp serve dies in build_server when the schema will not load, so the client sees only a closed connection where the CLI prints the error that names the fix.
resolved -
A code glob watches nothing until somebody verifies the document
Drift digests are written only by --verified, and in docir's own store none of the 99 documents naming the code they govern ever was — so nothing reported when that code moved.
resolved -
The deprecation register is not askable over MCP
doctor carries every dated announcement and has no MCP tool, so the agent the dates were written for cannot read them.
resolved -
A schema overlay makes the store unreadable by every published docir
A partial type block loads only on an unreleased docir, so every command refuses for anyone on the current release — and it has already disabled docir's own published-build check.
resolved -
publishing/infra/rendering.py changes for three unrelated reasons
The one audited smell still untreated: 2,072 lines over CSS, the index page, the document page, the chips and the markdown pipeline.
resolved -
Prose cross-references resolve to nothing, render as markup, and no check sees them
162 prose links in a maintained store were parsed by nothing: build published brackets, a retitle broke inbound links in silence, and finding the one that was broken took four hand-written passes.
resolved -
Every release re-embeds the whole corpus, because the resync stamp is the version
`resync` keys the full rebuild on docir's version, so a release that changed neither the model nor the chunking still recomputes every vector — 58.4s against 1.5s on this repository.
resolved -
An index migrated by a newer docir crashes every command of an older one
An older build finds a migration revision it does not ship and dies in alembic before any docir code runs, so the finding that exists to report exactly this is never reached.
resolved -
No CLI clears a --verified stamp
A verification could be stamped and never withdrawn, so correcting a bad stamp meant the hand-edit the CLI exists to prevent.
resolved -
A document leaves the staleness queue when anyone edits it
Staleness fell back to `updated` when `verified` was absent, so writing a re-check into a document nobody had vouched for cleared it from the review queue.
resolved -
The orphan queue empties itself when somebody triages it
Naming an id in prose cleared the orphan warning, so writing the triage of the orphan list closed every id on it — including the ones the triage said were still unwired.
resolved -
get --section returns the heading, --replace-section writes it again — and nothing can remove the duplicate
The read/write round trip on one section duplicates its heading, and no edit mode but --replace-body --force can undo it.
resolved -
A miss on an unbuilt index reports the document as nonexistent
update and get answer 'no document with id' in any checkout whose derived index was never built, so the message denies a document that is on disk.
resolved -
The mermaid guidance sent adopters to a version docir itself stopped using
skill and README named mermaid 10.9.3 on the false grounds that 11 is ESM-only, while docir's own pages.yml published with 11.16.1.
resolved -
CI cached a fastembed directory fastembed stopped writing to
The model cache never hit: the workflow cached ~/.cache/fastembed while fastembed 0.8 writes to $TMPDIR/fastembed_cache, so every run re-downloaded 64MB.
resolved -
CI's document-integrity gate ran over an empty index
check --strict ran on a gitignored index, so dangling edges — half the merge guard — never fired; CI now reindexes and runs doctor --strict first.
resolved -
A workflow context error took main red, and nothing local could have caught it
A job-level env used the runner context, so GitHub rejected ci.yml and zero jobs ran; actionlint now gates workflows locally and in CI.
resolved -
Embedder.dimension is declared and never read
The port declares a vector-width property nothing outside the embedding package consumes, and the fastembed adapter keeps it correct with a self-correcting field the library can answer directly.
resolved -
The embedding model is pinned, and it is English-only
bge-small-en-v1.5 is compiled in as the only real embedder, so a corpus not written in English retrieves worse than plain full-text search, with nothing to report it.
resolved -
The benchmark proves docir, not the adopter's corpus
benchmarks/ lives in the repository, so an adopter inherits docir's retrieval numbers as a claim with no way to reproduce them on their own documents.
resolved -
A ranked result cannot say why it ranked
context and search return score and similarity with none of the terms behind them, so an agent cannot tell a bad ranking from an empty corpus and ranking changes are debugged with print statements.
resolved -
context takes one query, and the caller has better ones
Both halves are answered: the model-free rewrite measured worse, and generation is closed by adr-27c63ad02695 — what remains is accepting caller-supplied queries.
resolved -
A published page executes raw HTML from a document body
docir build parses bodies with the CommonMark preset, which passes raw HTML through, so a script tag in a contributed document runs on the published site.
open -
reindex --embeddings replaces the rebuild rather than adding to it
The --embeddings flag re-embeds exactly what a plain reindex already re-embeds, for the same time, while skipping the two stamps the rebuild writes — so it is dominated by the command it modifies.
resolved -
A full rebuild on every upgrade, even when nothing changed
docir self upgrade ran an unconditional full reindex, and 96% of that is re-embedding: 58.4s of 60s on a 315-document store whose files had not moved.
resolved -
A document's type cannot be changed after it is created
docir update patches every other frontmatter field but not type, so retyping a corpus means hand-editing the markdown the CLI exists to own.
resolved -
A short section before an over-long one erases the long one's heading from the index
merge-forward keeps the first heading, then the merged block hard-splits, so the second section's heading names no chunk and matched_section can never point at it.
resolved -
Nothing reports a heading that appears twice in one document
A repeated heading makes --section resolve to the first occurrence and leaves the second unreachable by name, silently.
resolved -
The core schema cannot be subtracted, so its type names and prefixes are claimed forever
Every profiles: key merges the core unconditionally, so decision and its adr prefix exist in every store — an unused name stays addable and its prefix cannot be reused.
resolved -
Section reads and edits treat a fenced ## as a heading; the chunker does not
markdown_sections.py has no fence tracking, so --section returns a fragment ending in an unclosed fence and --replace-section silently orphans the rest of the body.
resolved -
The retrieval benchmark corpus cannot exercise any of the chunking rules
26 documents, 30 sections, none over the chunk ceiling and none quoting a fenced heading — so the splitter never runs and a chunking regression scores identically.
resolved -
Interpreter startup, not retrieval, dominates read latency
Startup still dominates after the SQLAlchemy fix: ~0.49s of a 0.53s warm-daemon read is process start and imports. What remains is pydantic-settings (~97ms) and docir's own 130 modules.
open -
No expression language over the corpus
Both halves shipped: query --expr asks a question, and checks: in docs-schema.yaml states a rule the store runs.
resolved -
A test asserts a phrase against rich-wrapped stderr, so it passes on path length
test_an_empty_store_says_so holds or fails on where rich broke the line, which depends on the tmp path inside the message.
resolved -
No way to see what a docs-schema.yaml edit will change before it lands
schema-drift reports a schema change after the fact; nothing renders the effect of an edit beforehand, and the file's meaning is not its text because the core and profiles merge into it.
resolved -
docir check has no finding for an edge whose relation kind the registry no longer knows
unknown-tag and unknown-status are reported, but an edge carrying an unregistered kind is served by get, traversed by context and flagged by nothing; only rewriting it is refused.
resolved -
A newly-required field is invisible to docir check, and surfaces one write at a time
Adding 'required:' to a live type leaves every existing document in violation with no finding of any kind; the first report is an unrelated update failing, one document at a time.
resolved -
A store cannot tell that its schema changed under it on upgrade
docs-schema.yaml has no version and the index records nothing about the schema it was built against, so a release editing the core or a profile changes what every store enforces with no local edit and no report.
resolved -
Nothing in a document names the code it governs, so a decision cannot be found from, or checked against, the codebase
No frontmatter field points at code, and the schema's 'required' hook that appears to allow one is unsatisfiable — which blocks enforcement against code and AST-anchored staleness alike.
resolved -
A ranked hit names the document but not the section that matched, so the paired section read is a guess
Chunk vectors decide the ranking and the winning ordinal is discarded before the result is built, so an agent must fetch the whole body or discover headings in a second round trip.
resolved -
--append-section accepts a heading that already carries its ##, and writes it twice
The flag takes heading text and adds the ## itself; passing '## Resolution' silently writes '## ## Resolution', which no section-edit mode can repair.
resolved -
A type may declare a required field no document can carry, and the schema loads anyway
'required:' accepts any name but is checked with getattr on the entity, so an unsatisfiable name loads fine and then rejects every add of that type, naming the write rather than the schema.
resolved -
The cycle check counts symmetric `relates_to` edges, so a mutual reference is a permanent warning
A cycle is only meaningful for relation kinds that assert direction; counting the default symmetric kind made 120 correct edges unrecordable in this store.
resolved -
The daemon keeps serving the code it started with, so a fix silently does not take effect
Nothing compares the running daemon's version against the installed one; after an upgrade or a source edit every command is answered by the old code, and the wrong answer looks normal.
resolved -
A transport failure prints a Python traceback and exits 1 instead of the error it raised
Every client-side daemon error escapes the CLI's error mapping, so the default execution mode reports failure as a stack trace.
resolved -
`--override` bypasses the transition rules and leaves no trace
Coverage checklist: "Admin: who can override a rule, and is the override audited?" — the override exists, the audit does not.
resolved -
`lint --deep` reports a duplicate for two documents that are explicitly related
On docir's own corpus all 14 duplicate findings are pairs already joined by an edge: the relation is the answer to the finding.
resolved -
Should the corrupt-state findings have a repair path, or only detection?
Every confirmed failure mode in this analysis ends in a state the product detects and cannot exit, and the documented recovery contradicts the product's second thesis.
resolved -
The default embedder path was excluded from every quality gate
A typo in `FastEmbedEmbedder.embed` would have shipped green to every default install.
resolved -
Should `tag rename` advance `updated` on every referencing document
ADR-0006 argues staleness must be honest human re-verification; a bulk administrative edit silently forges it.
resolved -
No import path: a repository with existing ADRs must re-create every document by hand
The most likely adopter is precisely a project that already keeps ADRs in markdown — the audience the product describes.
resolved -
Should `schema validate` check that transition targets name declared statuses?
The command exists to catch schema edits before they reach a write; this is the most likely schema error and it passes.
resolved -
A document may relate to itself, which manufactures the cycle warning `check` exists to report
Tier 0 accepts a self-edge; `docir check` then reports a one-node cycle that no edit but removing the edge can clear.
resolved -
`owner` is captured and only ever interpolated into a `check` message
'Knows what's stale' is one of six rows in the README comparison table. Detection works; the loop that would make it matter is absent.
resolved -
An uninitialised repository silently falls back to the global `~/.docir` store
Documents intended for a repo land in the user's home directory, ungitted and invisible to teammates, with no error at any point.
resolved -
With `--no-daemon`, concurrent `add` invocations all receive the same id
Contradicts a documented invariant.
resolved -
Linking a decision to its issue is a permanent `layering` warning in the `software` profile
The most natural thing a user will model produces a warning they cannot silence, which trains them to ignore `check` output — and compounds GAP-006.
resolved -
Four of the eight finding kinds are detected with no way to repair them
Every confirmed failure mode in this run (GAP-003, GAP-007, GAP-009) terminates in a state the product detects and cannot exit.
resolved -
No usage counts, so dead tags are invisible
The registry can only grow.
resolved -
Expansion is outgoing-only, so the document that supersedes a hit is never reached
"Is this decision still current?" is the question the relation graph most needs to answer, and it is the one direction not traversed.
resolved -
`scope-creep` uses one character threshold for every type, so a register is always too long
A glossary, a rule register and a probe log are long by definition; the check has no way to know that and advises splitting them.
resolved -
`reindex` silently skips unparseable files and reports the rebuild as a success
A user rebuilding after a hand-edit that broke frontmatter is told the rebuild succeeded.
resolved -
The markdown file is written before the transaction commits, so an interruption duplicates an id
A third route to a duplicate id, and the sharpest one.
resolved -
`docir init` ignores the global `--home` flag and creates the store under the CWD
A store is created somewhere the user did not ask for, and if the CWD happens to be a repository they now have an unrequested `.docir/` in it — which is exactly what happened to the analyst while…
resolved -
"Agents never edit markdown directly" is stated for agents and never for humans
The single most likely user action outside the CLI has no stated contract.
resolved -
How is a repository that already has ADRs supposed to adopt docir
The most likely adopter is exactly a project already keeping markdown ADRs.
resolved -
A random id is ~3x the length of a sequential one, and the cost is paid on every read
Small per document, paid on every read.
resolved -
The no-op early return skips staleness, so a no-op `update` reports a stale document as fresh
One field is wrong on one rarely-hit path.
resolved -
Dates use the writer's local date with no timezone, so teammates stamp different days
Two teammates in different timezones can stamp different dates for the same moment; staleness can differ by a day.
resolved -
The agent guide told agents to run `docir reindex --all`, a flag that does not exist
An agent following the guide after a merge gets an error instead of a rebuilt index, and the merge-safety workflow the guide exists to teach silently does not run.
resolved -
Should `reindex` restore the id counter, or is the index not fully rebuildable?
Fires on the documented happy path with no concurrency, no --force and no unusual input. It contradicts the project's first thesis, so the answer also decides whether that thesis…
resolved -
Should `--limit` bound the whole `context` response, or only the ranked seeds?
docir's headline claim is "token-cheap for agents" and `context` is the flagship command with a default limit of 5. The overrun scales with exactly the well-linked corpus the…
resolved -
The inactive-status filter is enforced on three read paths and skipped on the fourth
An agent asking for current context receives closed work items it was promised it would not see, and cannot tell which results honoured the filter.
resolved -
An edge's target key is `to` in frontmatter and `target` in JSON output
An agent that reads output and then hand-writes frontmatter will use the wrong key.
resolved -
Should graph expansion honour the inactive-status filter?
There is a defensible argument for the current behaviour — "the issue this decision resolved is worth seeing even though it is closed" — but nothing states it, and the…
resolved -
No relevance floor, and `score` carries no absolute meaning, so noise reads like a match
An agent cannot distinguish relevant context from noise, so it either always trusts the results (and is misled on unrelated tasks) or never does.
resolved -
Should `docir context` be able to return nothing?
Compounds Q-003 — the response is unbounded in size and unfiltered in quality.
resolved -
`--force-schema` alone is a silent no-op; it only takes effect together with `--force`
A user who reaches for the more specific-sounding flag gets nothing and no explanation.
resolved -
Six concurrent `--no-daemon` adds all returned `adr-0002`
"Parallel agents" is the exact scenario the invariant claims to protect, and `--no-daemon` is the mode most likely to appear in scripts and CI.
resolved -
`--limit` bounds the ranked seed set but not the response
Directly negates the product's headline claim.
resolved -
Should `--override` leave a trace of a forced illegal transition?
Coverage checklist: 'who can override a rule, and is the override audited?'
resolved -
Which findings should `check --strict` block a merge on?
A team that adopts the documented gate gets a red build on day one and will remove it — which also removes the duplicate-id detection that is the gate's stated purpose and the…
resolved -
`tag rename --merge` onto itself deletes the tag and leaves every document carrying it
Data corruption reachable from a plausible command, shipped in 0.7.0.
resolved -
`check --strict` exits 1 on any finding, so the advertised CI gate fails on a healthy corpus
The one CI integration the product advertises cannot be adopted as documented.
resolved -
`tag rename` sets `updated = today` on every referencing document, resetting the review clock
Launders the one trust signal the product offers.
resolved -
'home', 'store' and 'data root' name one concept in three places
Minor discoverability friction.
resolved -
Tags are in neither the FTS index nor the embedded text, so `search` cannot find them
Reasonable design; undocumented, and surprising given tags appear in output.
resolved -
`schema validate` does not check that transition targets are declared statuses
A command whose entire purpose is "check an edit before it reaches a write" passes the most likely schema error, and the eventual message misdirects.
resolved -
Staleness is detected and never routed: `owner` reaches one `check` message and nothing else
"Knows what's stale" is one of six rows in the README's comparison table (README:44) and one of four bullets in "The model".
resolved -
`reindex` does not restore the id counter, so a fresh clone re-mints a live id
Silent loss of a decision record on the documented happy path.
resolved -
An unrecognised agent target name is silently ignored: no error, exit 0, nothing written
A once-per-repo onboarding command that reports success while doing nothing; the user proceeds believing their agent knows how to drive docir.
resolved -
Should `docir add` in an uninitialised repository succeed against the global store?
`docir init` is step 2 of the quickstart; forgetting it produces no error.
resolved -
Should `agent install --agent <unknown>` fail instead of being a silent no-op?
Once-per-repo onboarding command reporting success while doing nothing.
resolved -
Only `--replace-body` has a stale-write guard; the other four edit modes have none
The guard exists and is computed; applying it to one of five edit modes is a decision nothing records.
resolved -
Write commands report the resolved `store`; the read paths do not
Introduced by the GAP-023 fix, which reasoned about writes only.
resolved -
`reindex --changed` skips the removal sweep, so deleted documents stay in the index
The fast path silently has different semantics from the full path — deletions are invisible to it.
resolved -
Renaming a tag onto an existing key is rejected, so two tags cannot be merged
Vocabularies drift and need consolidating; the registry can only grow.
resolved -
`tag rm` reports only `removed <key>`, never how many documents it rewrote
A bulk mutation reports as a single-key operation.
resolved -
Nothing recorded which model produced a vector, so changing embedder broke every read
A dead column that turned out to be load-bearing.
resolved -
The client's connect timeout also bounded the daemon's reply, so any command slower than 5s failed
The client reported failure for work the daemon completed, then resent the request — a write slower than five seconds ran twice.
resolved -
`stale` names three unrelated concepts in one codebase
Maintenance hazard only; no user-visible defect.
resolved -
`docir init` inside an existing store's tree creates a shadowing store in silence
A nested `.docir` captures every command run beneath it, and the parent store's `check` never sees those documents.
resolved -
Nothing measures whether docir achieves its purpose
Not a defect in the code; a defect in the ability to make decisions about the code.
resolved -
`search` fetches `limit * 2` candidates then filters, so it under-returns on a closed corpus
Under-return looks like corpus scarcity rather than filtering.
resolved -
Should graph expansion follow incoming edges too?
'Is this decision still current?' is the question the relation graph most needs to answer, and it is the one direction not traversed.
resolved -
No format rule for tag keys — any non-empty string is accepted
Two agents will produce `auth`, `Auth` and `authentication` and nothing objects.
resolved -
Documented as leaving `unknown-type` documents; the recovery path is not documented
Detection is good; the exit is undocumented.
resolved -
The flag is `--include-resolved` but the concept is "inactive status"
A user querying decisions has no reason to guess that a flag named --include-resolved controls whether superseded decisions appear.
resolved -
The default embedder does not capture meaning; it is the signal FTS5 already gives
README:42 claims "Retrieval by meaning ✅ (lexical + semantic)" against RAG's ✅ and plain files' ❌.
resolved -
resolved
-
The benchmark corpus has no `supersedes` edge and no inactive document, so both go unmeasured
A harness that cannot move is read as "no regression" when it means "not measured".
resolved -
Should linking a decision to its motivating issue be a `layering` warning?
Compounds Q-004: it guarantees a finding on the most natural modelling, which trains users to ignore `check` output entirely.
resolved -
Nothing measures whether docir retrieves better or cheaper than the alternatives
This is why every gap in the register carries `frequency: unknown`, why the retrieval constants (25 FTS candidates, 0.9 similarity, 8000 chars, RRF k=60) cannot be tuned on…
resolved -
No stated corpus ceiling: `context` loads every active embedding into memory per call
Fine at the 'thousands of documents' scale the id-entropy comment in identifiers.py assumes; nothing states the ceiling or what happens past it.
resolved -
`delete --force` leaves referencing documents pointing at a document that no longer exists
The graph — the feature that distinguishes docir from a folder of files — silently accumulates unresolvable edges, in the canonical files, permanently.
resolved -
`init --force` overwrites a customised `docs-schema.yaml` along with the `.gitignore`
A user re-running init to refresh the .gitignore destroys a customised schema.
resolved
Reference 11
-
Competitive landscape — docir vs. the alternatives (2026-08-24)
Where docir stands against the retrieval, agent-memory, ADR and spec-driven markets on 2026-08-24, and the six gaps still open.
active -
How to read a ranked result
What score and similarity each mean on a context or search hit, which one --min-score filters, and the two hits it never drops.
active -
The embedding model: what it costs, and what the fallback loses
What the default embedder costs to install, what the model-free fallback loses, and how a store names a different model.
active -
Competitive landscape — docir vs. the alternatives (2026-08-03)
What the adjacent tools do, where docir is unique, and the ranked list of features it does not have.
superseded -
Discovery frame — docir at 0.8.0+, run 2026-07-30
Scope, method and budget for the second discovery pass: the surface v0.2.1 never examined, probed rather than read.
active -
Discovery probe log — PROBE-1..N and the delta pass
The executed probes behind the gap register: what was run, what came back, and what it proved.
active -
Actor catalog — who and what drives docir
The eight actors that drive docir, their goals and their authority (there is no permission model).
active -
Business rule register — 47 rules, BR-001..BR-074
47 reconstructed business rules with evidence, examples and boundaries; every one unconfirmed by a human.
active -
Discovery frame — docir at v0.2.1, run 2026-07-26
The business outcome, scope and coverage the discovery run was framed against.
superseded -
Probe log — second discovery pass, 2026-07-30
What was executed against the CLI in the 0.8.0+ pass, what it returned, and the four areas deliberately not reached.
active -
Glossary — one term, one definition, one owner
13 terms defined with owner and evidence, including the words that mean two things.
active
Release notes 13
-
0.30.0 — an audit is docir check, and check says when it could not look
An agent audits a store by trusting docir check's findings at under half the cost of auditing without docir, and a check that could not resolve code globs says so instead of reading clean.
published -
0.29.0 — a newer docir tells your agent, once
The release check moved out of one person's shell and into the store, and stopped naming a command four of six installations cannot run.
published -
0.28.0 — a daemon you can do without, and a build that is not a change
Three reported defects closed, all the same shape: docir answering about something it had not looked at — a daemon it could not start, a file git already ignored, a generated file written once.
published -
0.27.0 — the glob that watches from the moment you write it
A code: glob now reports drift from the write that declared it rather than from a review almost nobody performed, and a store says which docir it needs before an older build fails on the wrong thing.
published -
0.26.0 — the link written in prose is a link docir reads
[[...]] cross-references resolve against ids, filename stems, title slugs and titles, render as the target's current title, and the ones pointing at nothing are a Tier 1 warning; underneath, the six largest classes were split with every surface held byte-identical.
published -
0.25.0 — an upgrade that finishes, and stops redoing work nobody asked for
The reply timeout stopped bounding how long a command may take, and vectors stopped being recomputed when nothing the model reads had changed: 146s to 2.6s on docir's own corpus.
published -
0.24.0 — the review queue you cannot clear by writing in it
What shipped in 0.24.0: a verification that is withdrawn when its content moves, a way to take a stamp back, and the two queues that emptied themselves.
published -
0.23.0 — the section you read is the section you can write back
The read/write round trip on one section stopped duplicating its heading, and --remove-section became the exit for a body that already has one.
published -
0.22.0 — a store that builds itself, and a message that stops denying the document
Opening a store with files and no index now rebuilds it with the vectors deferred, and the miss that used to report the document as gone names the unbuilt index instead.
published -
0.21.0 — say what corpus answered, and prove it against the build people have
The release that labelled every federated hit with the corpus behind it, and found that a fully green change can still break every read for anyone a version behind.
published -
0.20.0 — doctor, and the gates that passed by not looking
`docir doctor` reports the conditions that make docir answer wrongly without failing — added while fixing four of this project's own gates that were green because they read nothing.
published -
0.18.0 — retrieval you can measure, see into, and steer
The release that made retrieval inspectable: a swappable model, a benchmark an adopter can run, a rank trace, and two ways to hand docir a better question.
published -
0.19.0 — state a rule, and find the question that never ran
checks: lets a store declare its own rules in docir's grammar, and the guard added alongside found that a mistyped expression had been silently matching nothing.
published
Runbooks 7
-
Connect an agent to docir
The two transports an agent can use — the installed CLI skill or the bundled MCP server — and why both answer identically.
active -
Rename a document type
How a corpus renames a type and keeps the ids it already has: disable_types frees the prefix, then documents are retyped one at a time.
active -
Read across repositories
How a store federates reads over declared peers: stores.yaml, --store, why writes and build never federate, and why ranking merges on similarity.
active -
Publish the store as a static site
How docir build renders the corpus for the people who approve decisions: the flags, publishing from CI, the --out guard, and mermaid diagrams.
active -
Upgrade docir in a project
What to run after a new docir release: the package, the derived index, and the generated files — the ones self upgrade refreshes and the ones it does not.
active -
AI Code-Check Checklist
The staged procedure an AI agent follows when reviewing new code.
active -
Publishing to PyPI
How to publish docir to PyPI with uv and GitHub Actions trusted publishing.
active
No documents match this combination.