Finding Codes

Every finding DocGuard reports carries a stable code. This page lists all 152 finding codes, grouped by the validator that reports them, and the 8 codes that block a specs command. Codes are never renumbered.

The same text is available offline:

docguard explain DSY001

A code that accepts an in-place marker shows it below. Inline docguard:ignore <CODE> works only where it is shown (the security codes, REF001, REF002 and SPK010); other markers are specific to their code. A code with no marker has no in-place suppression: fix the cause, freeze it in the adoption baseline, or mute the validator with a reason. A finding’s severity, disposition, confidence and parser tier are explained in Troubleshooting.

Validators

Validator Codes
API-Doc-Smells APS001, APS002
API-Surface API001, API002, API003, API004, API005
Architecture ARC001, ARC002, ARC003
Canonical-Sync CSY001, CSY002, CSY003, CSY004
Changelog CHG001, CHG002, CHG003
Cross-Reference XRF001, XRF002
Diff-Suspicion DSP001
Doc Sections STR003
Doc-Dependency DLK001, DLK002, DLK003, DLK004, DLK005
Doc-Ownership OWN001, OWN002, OWN003, OWN004, OWN005, OWN006, OWN007
Doc-Quality DQ001, DQ002, DQ003, DQ004, DQ005, DQ006, DQ007, DQ008
Docs-Coverage DCV001, DCV002, DCV003, DCV004, DCV005, DCV006
Docs-Diff DDF001, DDF002
Docs-Sync DSY001, DSY002, DSY003
Document-Lifecycle DLC001, DLC002, DLC003, DLC004
Drift-Comments DRF001, DRF002
Environment ENV001, ENV002, ENV003, ENV004, ENV005
Evidence EVD001, EVD002, EVD003, EVD004, EVD005
Freshness FRS001, FRS002, FRS003, FRS004, FRS005
Generated-Staleness GST001, GST002
Metadata-Sync MDS001, MDS002
Metrics-Consistency MET001, MET002, MET003, MET004
Path-Scoped-Rules PSR001, PSR002, PSR003, PSR004
Reference-Existence REF001, REF002
Requirement-Anchors ANC001, ANC002, ANC003, ANC004, ANC005, ANC006, ANC007, ANC008, ANC009, ANC010
Schema-Sync SCH001, SCH002
Security SEC001, SEC002, SEC003, SEC004, SEC005, SEC006, SEC010, SEC011
Spec-Kit SPK001, SPK002, SPK003, SPK004, SPK005, SPK006, SPK007, SPK008, SPK010, SPK011, SPK012, SPK009
Spec-Registry SPR001, SPR002, SPR003, SPR004, SPR008, SPR009, SPR007, SPR006, SPR005
Structure STR001, STR002, STR004, STR005
Suppressions IGN001, IGN002, IGN003
Surface-Sync SSY001, SSY002
Test-Spec TSP001, TSP002, TSP003, TSP004, TSP005, TSP006, TSP007
TODO-Tracking TDO001, TDO002, TDO003
Traceability TRC001, TRC002, TRC003, TRC004, TRC005
Command blockers SPC001, SPC002, SPC003, SPC004, SPC005, SPC006, SPC007, SPC008

API-Doc-Smells (apiDocSmells)

APS001: Bloated API documentation

An API doc unit is excessively long / over-structured relative to the surface it documents (smell taxonomy, arXiv API-doc-smells; deterministic Bloated detector F1 0.90). Trim to the essential contract.

Marker:

<!-- docguard:quality api-smell off — your reason -->

APS002: Lazy API documentation

An API doc unit is vague/generic or barely exceeds the signature it documents (deterministic Lazy detector F1 0.95). Document parameters, return, and errors concretely.

Marker:

<!-- docguard:quality api-smell off — your reason -->

API-Surface (apiSurface)

API001: OpenAPI spec unparseable

The spec declares paths: but DocGuard’s minimal parser extracted zero endpoints (unsupported YAML: $ref, anchors, folded scalars). Code scanning takes over, but the spec cannot serve as ground truth — validate it with a full OpenAPI linter.

API002: OpenAPI specs diverge

Two or more specs in canonical locations disagree on their endpoint sets; the sourceRoot-nearest spec is treated as authoritative. Regenerate or delete the stale copy so they agree.

API003: Spec-declared endpoint has no registered route

The OpenAPI spec declares an endpoint that no scanned route registers. Either the spec is stale (remove the endpoint) or the route is registered dynamically where the scanner cannot see it — flagged low-confidence for exactly that reason.

API004: Documented endpoint missing from authoritative contract

docs-canonical/API-REFERENCE.md documents an endpoint absent from the authoritative OpenAPI contract. The contract mismatch is certain; route presence or absence is separate, low-confidence evidence. Reconcile intent, contract, implementation, and documentation manually. DocGuard never deletes an endpoint from negative route-scan evidence.

API005: Undocumented endpoint in code

A real endpoint exists in the spec/routes but is missing from docs-canonical/API-REFERENCE.md. Add a documentation block for it.

Architecture (architecture)

ARC001: Forbidden layer import (layers config)

A file imports from a layer its own layer may not import, per the layers map in .docguard.json. Remove the import or route it through an allowed layer — or update the layers config if the architecture genuinely changed.

ARC002: Circular dependency

A load-time import cycle was detected between source files. Break it by converting one edge to a dynamic import() (runtime imports do not create load-time cycle edges) or by extracting the shared code into a third module both sides import.

ARC003: Layer boundary violation (ARCHITECTURE.md)

An import crosses a boundary the Layer Boundaries table in docs-canonical/ARCHITECTURE.md forbids. Remove or invert the import — or update the table if the rule changed, so docs and code agree.

Canonical-Sync (canonicalSync)

CSY001: No surface docs to check

canonical-sync found neither README.md nor AGENTS.md, so DocGuard’s own surface-count claims cannot be checked. Add a README.md (this check only runs in the docguard-cli repo).

CSY002: Stale “ships N commands” claim

A surface doc (README.md/AGENTS.md) claims a command count that does not match the real user-facing command count parsed from –help (or the cli/commands file count). Update the claim.

CSY003: Stale “N validators” claim

A surface doc (README.md/AGENTS.md) states a validator count that does not match the validator modules shipped in the package. Guard can emit multiple check results from one module; those do not increase the public validator count. Update the claim.

CSY004: Stale architecture-diagram counts

The Commands (N) / Validators (N) labels in the README mermaid architecture diagram do not match code-truth — the exact drift that went unnoticed for 5 releases. Update the mermaid block.

Changelog (changelog)

CHG001: Missing [Unreleased] section

Keep a Changelog format expects an [Unreleased] section where in-progress work accumulates before each release. docguard fix --write inserts one.

CHG002: No version sections

CHANGELOG.md has no ## [version] headers — it does not follow Keep a Changelog format, so release tooling and readers cannot parse the history.

CHG003: Staged code without a CHANGELOG entry

Code files are staged for commit but the changelog is not. Per STANDARD.md, a code change and its changelog entry travel in the same commit.

Cross-Reference (crossReference)

A markdown link between canonical docs points to a file that does not exist (checked relative to the source doc and the project root, URL-decoded). Fix the target path or remove the dead link.

XRF002: Broken doc anchor

A #anchor link does not match any heading in the target doc (GFM slug rules). When exactly one near-miss heading exists (edit distance ≤ 2) the warning is marked [auto-fixable] and docguard fix --write rewrites it; otherwise pick the correct heading by hand.

Diff-Suspicion (diffSuspicion)

DSP001: Doc describes code that just changed

A canonical doc (or agent-instruction file) references a code file AND shares wording with symbols removed/changed in that file since the compared revision. Deterministic diff-overlap rule (arXiv 2010.01625, F1 74.7). Low-confidence by design — re-read the doc against the current code; suppress the pairing if it is a false positive.

Doc Sections (docSections)

STR003: Missing required doc section

A canonical doc exists but lacks a section its document type requires. Add the section — or, if it is genuinely not applicable, own the absence with an inline marker: <!-- docguard:section <slug> n/a — reason -->.

Marker:

<!-- docguard:section <slug> n/a — reason -->

Doc-Dependency (docDependency)

DLK001: Code a reviewed doc section describes has changed

A docguard:section declares covers="path#symbol, …", and .docguard-doc-lock.json records what that code looked like when the section was last reviewed. The fingerprint of a covered dependency no longer matches: the symbol’s normalized AST changed (formatting, comments and line moves do not count), or the file’s bytes changed where no parser applies. Read the change (the finding carries the git diff command), update the prose if it no longer holds, then record the review with docguard review --accept <doc>#<section> --reason "<what you checked>". This is a review signal: DocGuard knows the code changed, not whether the prose is now wrong.

DLK002: Covered dependency no longer exists

A path or symbol named in a section’s covers= attribute does not resolve: the file was moved or deleted, the symbol was renamed, or the path is outside the project, a symlink, or excluded by .docguardignore. Fix the declaration to name what the section now describes, update the prose, and accept the review again.

DLK003: Covered section has no accepted review

A section declares covers= but .docguard-doc-lock.json has no entry for it, or its declared dependencies changed since the last review. Read the section against the code and run docguard review --accept <doc>#<section> --reason "<what you checked>". Only this command writes the lock; sync, fix and agents never do.

DLK004: Doc lock entry has no section

The lock records a review for a doc#section that no longer declares covers= (the section was removed, renamed, or its attribute dropped). Run docguard review --prune to remove entries without a section.

DLK005: Doc lock file is unreadable

.docguard-doc-lock.json does not parse or does not match its schema (schemas/docguard-doc-lock.schema.json). Nothing in it can be trusted, so no section is reported current. Restore it from git, or delete it and accept each covered section again.

Doc-Ownership (docOwnership)

OWN001: Source directory with no owning doc section

Under an ownership root, this directory holds source code that no ownership.entries[].paths pattern covers. Nobody is named as responsible for documenting it, so a change there has no doc to update. It is reported at the highest directory with no owned file in it, not once per file. Add the directory to the paths of the doc section that describes it, or add an entry. Test, fixture and example paths are skipped under the default roots; a declared root is checked as declared.

OWN002: Two ownership entries are equally specific for one file

Two entries match the same file with equal specificity (an exact file path beats any glob; a longer literal directory prefix beats a shorter one), so the file has no single owner and the two docs will drift apart. Narrow one entry, or name the file exactly in the entry that owns it.

OWN003: Ownership pattern or root matches nothing

An ownership entry’s path pattern, or an ownership root, matches no tracked file, usually because the code moved. Low confidence when other patterns of the same entry still match. Point it at where the code lives now, or remove it.

OWN004: Ownership entry names a missing doc or section

An ownership entry names a doc that does not exist, or a section that is neither a docguard:section id nor a heading anchor in that doc. Name an existing doc and section.

OWN005: .devin/wiki.json breaks a documented Devin rule

The committed .devin/wiki.json that steers Devin’s DeepWiki breaks a rule from Devin’s documentation (checked 2026-09-30): repo_notes and pages are required, pages needs at least one page, titles must be unique and non-empty, every page needs a purpose, at most 30 pages (80 on enterprise: set devinWiki.maxPages), at most 100 notes counting repo and page notes together, at most 10,000 characters per note. Devin rejects a file without pages; what it does with other violations is not documented.

OWN006: .devin/wiki.json names a path that does not exist

A note or page purpose in .devin/wiki.json names a file or directory (src/api/, docs/x.md) that is not tracked in the repository. Devin’s documentation encourages naming paths but does not check them, so a stale path keeps steering the generated wiki toward code that is gone. This is DocGuard’s own rule. Point the note at the path that replaced it, or remove it.

OWN007: Ownership map or wiki file is unreadable

The ownership block in .docguard.json is malformed (for example entries is not a list, or a path uses .. or an absolute prefix), or .devin/wiki.json is not valid JSON. Nothing in it can be trusted, so no path counts as owned. Fix it.

Doc-Quality (docQuality)

DQ001: High passive voice ratio

More than 25% of the doc’s prose sentences are passive (configurable via docQuality.passiveVoiceThreshold). Rewrite in active voice — or opt the doc out with the inline marker if passive is intentional (sequence/flow docs).

Marker:

<!-- docguard:quality passive-voice off — your reason -->

DQ002: High ambiguous pronoun ratio

Over 15% of words are ambiguous pronouns (it/this/that/they…). Replace them with the specific noun they refer to so statements stay verifiable.

DQ003: Low atomicity (compound sentences)

Over 35% of sentences are compound. Split them so each sentence carries one verifiable statement (IEEE 830 §4.1).

DQ004: Very low Flesch reading ease

The doc scores below 5/100 — effectively unreadable even for technical material (tech docs typically score 10-30). Shorten sentences and use simpler words.

DQ005: Reading grade level too high

Flesch-Kincaid grade above 22 (PhD+). Aim for grade 12-16 for technical docs by simplifying sentence structure and vocabulary.

DQ006: Average sentence too long

Average prose sentence exceeds 30 words. Break long sentences up — target 30 words or fewer.

DQ007: High negation load

Over 20% of sentences use negation (configurable via docQuality.negationLoadThreshold). Rephrase in positive terms (“must not fail” → “must succeed”, IEEE 830 §4.3) — or opt the doc out with the inline marker (security/operational docs legitimately prohibit).

Marker:

<!-- docguard:quality negation-load off — your reason -->

DQ008: High conditional load

Over 30% of sentences are conditional (if/unless/when…). Split conditionals into separate, unconditional requirements.

Docs-Coverage (docsCoverage)

DCV001: Undocumented config file

A project-specific config/dotfile at the repo root is not mentioned in any documentation. Document its purpose in ARCHITECTURE.md or README.md, or add it to ignore in .docguard.json if it is genuinely internal.

DCV002: Undocumented CLI bin command

package.json declares a bin entry users can run, but no documentation mentions it. Document the command, typically in README.md under Usage.

DCV003: Source directory not in ARCHITECTURE.md

A directory under a source root is not referenced in ARCHITECTURE.md. Add it to the Component Map, or add an ignore pattern in .docguard.json if it is build output.

DCV004: Code-referenced config not documented

Source code reads a config file (a resolve/readFileSync/existsSync call) that no documentation mentions. Describe the file’s purpose and format in README.md or ARCHITECTURE.md.

DCV005: README missing a standard section

README.md lacks a section every well-documented project needs — Installation, Usage, or License (Standard README spec). Add the missing section.

DCV006: IaC detected but no Infrastructure section

An IaC tool (CDK, Terraform, Pulumi, SAM, or Serverless) was detected but ARCHITECTURE.md has no Infrastructure heading. Add an “Infrastructure” section covering the tool’s layout — the warning names the exact marker file and directories to describe.

Docs-Diff (docsDiff)

DDF001: Tech Stack drift

The tech named in docs-canonical/ARCHITECTURE.md and the technologies the code shows disagree. The code side reads the dependency manifests of every ecosystem (package.json across the monorepo, pyproject/requirements, go.mod, Gemfile, Cargo.toml, composer.json, *.csproj, Maven/Gradle), a Dockerfile or compose file, and .tf files; DynamoDB and Docker are also found from unambiguous source text in product files (a DynamoDB API target or endpoint, docker run/build/compose in a script, Makefile or CI workflow, a compose file), and the message names that file. A documented technology is reported missing only where a manifest could have shown it (see docs/reference/languages.md); a project with no manifest is not checked. Drift is two-sided — document the new tech or remove the stale entries after checking which side is wrong.

DDF002: Test Files drift

The test files documented in docs-canonical/TEST-SPEC.md and the test files on disk disagree. A documented test is a backticked *.test.*/*.spec.* name or one matching testPatterns/testPattern in .docguard.json, the same patterns the code side uses. Add entries for new tests or remove documented tests that no longer exist — TEST-SPEC.md entries may be glob patterns.

Docs-Sync (docsSync)

DSY001: Route not referenced in canonical docs

A route file exists but neither its path nor its basename appears in any docs-canonical .md file. Reference the route in a canonical doc (e.g. ARCHITECTURE.md or an API doc) so the documented surface matches the code.

DSY002: Service not referenced in canonical docs

A service/lib file exists but neither its path nor its basename appears in any docs-canonical .md file. Reference the service in a canonical doc (e.g. ARCHITECTURE.md) so the documented surface matches the code.

DSY003: Route file missing from OpenAPI spec

A route file defines paths (or has a route-like filename) with no matching path in the detected OpenAPI/Swagger spec. Re-run your spec generator (e.g. zod-to-openapi) or add the paths to the spec by hand.

Document-Lifecycle (documentLifecycle)

DLC001: Terminal document remains in active context

A tracked Markdown file declares itself superseded, deprecated, obsolete, or archived but remains searchable as current repository context. Confirm its outcomes are represented in current docs, then use docguard retire --write --path <path> --reason <reason>.

DLC002: Completed task list needs lifecycle review

A spec has checked tasks and no open tasks. This is a review signal, not proof that the feature shipped. Confirm release evidence and current documentation before archiving the spec.

DLC003: Document lifecycle coverage incomplete

DocGuard could not enumerate or read every tracked Markdown document. Fix Git access or file readability and rerun guard; an incomplete lifecycle scan must not be interpreted as a clean result.

DLC004: Retired document remains active

The retirement manifest records this path, but the document still exists in the working tree. Remove it through the reviewed retirement transaction or remove the incorrect manifest event; the two states must agree.

Drift-Comments (drift)

DRF001: DRIFT comment but no drift log file

Code contains a // DRIFT: comment but the drift log (DRIFT-LOG.md) does not exist, so the deviation is unlogged. Create the drift log — docguard init scaffolds it — then record this deviation in it.

DRF002: DRIFT comment not logged in DRIFT-LOG.md

A // DRIFT: comment’s file has no matching entry in DRIFT-LOG.md. Add an entry naming the file and explaining why the code deviates from docs-canonical.

Environment (environment)

ENV001: Missing setup section in ENVIRONMENT.md

ENVIRONMENT.md lacks both a “## Prerequisites” and a “## Setup Steps” heading (H2/H3 anchored, not a TOC mention). Add a Setup Steps section describing how to get the project running from a fresh clone.

ENV002: Missing Environment Variables section

ENVIRONMENT.md has no “## Environment Variables” heading. Add the section and document each variable the app reads — backticked names or a pipe table both count.

ENV003: Env vars used in code but undocumented

Variables read in code (process.env / import.meta.env, os.environ, os.Getenv, ENV[…], System.getenv, Spring ${X} placeholders, and the C#, PHP and Rust forms) were not found in ENVIRONMENT.md or .env.example. Document each listed variable in the doc, or add it to .env.example — either counts as documentation.

ENV004: ENVIRONMENT.md references a missing .env.example

The doc mentions .env.example but the file does not exist. Create it with placeholder values, or remove the stale reference.

ENV005: .env exists without a .env.example template

A local .env (or .env.local / .env.development) exists but there is no .env.example, so new contributors won’t know what vars to set. Create a .env.example listing every variable with a placeholder value.

Evidence (evidence)

EVD001: Evidence manifest is invalid

The strict .docguard-evidence.json contract is malformed, unsafe, duplicated, or combines an adapter with an incompatible predicate. Compare it with templates/evidence-manifest.json, repair the named field, and rerun docguard verify --evidence.

EVD002: Declared evidence contradicts documentation

A unique Markdown statement and its current declared source disagree under an exact predicate. Review approved intent before changing either side; implementation may have regressed from the document.

EVD003: Saved evidence is stale

A saved oasdiff or Buf report names an input whose current SHA-256 differs from the declared snapshot. Regenerate the upstream report with the recorded command and producer version, update every input digest, then review the dependent statement.

EVD004: Evidence verification is inconclusive

DocGuard could not safely and uniquely read the declaration, document statement, source, or report. Restore the evidence or narrow the target; missing evidence never becomes a pass.

EVD005: Evidence format is unsupported

The adapter version, command, source type, report shape, or predicate is outside the implemented contract. Keep the finding visible and contribute a synthetic failing fixture plus a neighboring valid control before expanding support.

Freshness (freshness)

FRS001: Document review signal unavailable

No committed update or dated review signal was found. Review the document’s intended behavior and implementation before recording a review date. An approved status is an author signal, not semantic verification.

FRS002: Document review due after repository activity

At least 10 code commits followed the document update or review. This repository-wide heuristic does not prove the document is stale. Review its purpose, intended behavior and relevant code before deciding which side needs a change.

FRS003: Document review due after elapsed repository history

The document update or review predates the latest code change by more than 30 days. This repository-wide heuristic does not establish drift. Review relevance and intent before changing documentation or code.

FRS004: Release-note review due

The changelog update predates the latest code change by more than a week. Review whether these changes need release notes; timing alone does not prove entries are missing.

FRS005: Deviation-log review due

Recent code commits added DRIFT comment lines. Review whether the deviations are already recorded or resolved; history alone does not prove the log is incomplete.

Generated-Staleness (generatedStaleness)

GST001: Generated doc stuck in draft

A docguard:generated doc has sat in status: draft beyond the staleness window (default 14 days; draftStalenessDays in .docguard.json). Draft the prose (docguard fix --doc <name> prints a research prompt) and promote it to status:current, or delete the forgotten skeleton.

GST002: Code-truth section stale

A source=code section’s body no longer matches what the scanner produces — code changed without docguard sync --write, or someone hand-edited a generated section. Run docguard sync --write (or docguard fix --write), or pin the section if it is intentionally hand-maintained.

Marker:

<!-- docguard:section id=<id> source=code pinned="reason" -->

Metadata-Sync (metadataSync)

MDS001: extension.yml version out of sync

An extension.yml declares a version that differs from package.json. docguard fix --write rewrites it to the current version.

MDS002: Stale version reference in docs

A markdown file references an older version of this package in an actionable context (download URL, install command, or version: declaration). docguard fix --write replaces it with the current version; prose mentions of old versions are intentionally not flagged.

Metrics-Consistency (metricsConsistency)

MET001: Documented count drifted from DocGuard meta-count

A docguard-bound “N checks/validators” number in a doc no longer matches the tool’s actual count. docguard fix --write rewrites it (fail-closed: only docguard-bound lines, with provenance).

MET002: Documented count drifted from a declared collection

A doc states “N " for a noun declared in `config.collections`, but the collection glob matches a different number of files. Either the doc is stale (fix with `docguard fix --write`) or the code lost/gained members unintentionally — check which side is wrong before fixing.

MET003: Documented test count is below the declared test cases

A line about the project’s own test suite (“npm test # N tests”, “N tests passing”) states fewer tests than the test files declare. The declared count is a static lower bound of what the runner reports (cases generated in loops are not expanded), so a smaller documented number is stale for certain, but the true number is only known by running the suite. Run it and update the count by hand; there is no mechanical fix.

MET004: Documented runtime-dependency count disagrees with package.json

A document (including the Spec Kit constitution) states how many runtime dependencies the project has (“zero runtime dependencies”, “Dependencies: None”, “one npm dependency”), and package.json dependencies declares a different number. Only qualified wording counts, so “no dependencies between modules” is never compared. The claim may be stale, or a dependency may have been added that the project promised not to take: decide which side is wrong. There is no mechanical fix.

Path-Scoped-Rules (pathScopedRules)

PSR001: Path-scoped rule matches no tracked file

A path-scoped agent instruction file (a Claude Code rule or skill paths:, a Cursor rule globs:, a Copilot .instructions.md applyTo:, an OpenHands skill paths:) has a pattern that matches no tracked file. The harness never loads the rule for that pattern, so its instructions silently stop applying — usually because the code moved. Point the pattern at where the code lives now, or delete the rule. Low confidence when other patterns in the same file still match. docguard rules --for <path> shows which rules apply to a path.

PSR002: Instruction file points at a path that does not exist

An agent instruction file (AGENTS.md, CLAUDE.md, a rule, a skill) names a file or directory — in backticks, a Markdown link or a table row — that is not tracked in this repository. A pointer is resolved against the instruction file’s own directory first, then the project root; a bare file name that matches exactly one tracked file counts as resolved; a path the repository’s ignore rules exclude is treated as a machine-local file and not reported. Point it at the file that replaced it, or remove the reference.

PSR003: Instructions loaded for a path exceed the budget

For some tracked path, the instruction files one harness loads (always-on rules, directory files such as nested CLAUDE.md, and the path-scoped rules that match) total more than agentInstructions.maxBytes (default 32768). Every byte is paid on every turn, and some harnesses truncate. Nested AGENTS.md chains are measured by STR004 and are not counted here. Narrow the scopes, move procedures into linked docs, or record agentInstructions.allowances["<harness>:<path>"] (any path in the reported group, or "<harness>" for all) so further growth is a reviewed change.

PSR004: Instruction scope cannot be read

The frontmatter of a path-scoped instruction file does not parse, a scope field has the wrong type (alwaysApply: "yes"), an unquoted value starts with * (YAML reads globs: **/* as an alias, not text: quote it), or a .md file sits in .cursor/rules where Cursor loads only .mdc. A scope key the harness does not read (globs: in a Claude Code rule) is ignored by it. Claude Code loads a rule whose frontmatter does not parse for every file. A pattern that is valid for the harness but uses syntax DocGuard does not evaluate ([...] classes, ! negation, nested braces) is not a finding: the check reports partial coverage and names the rule.

Reference-Existence (referenceExistence)

REF001: Doc references a code symbol that no longer exists

A code-element reference in the doc matched source when the doc was last updated, but matches ZERO source instances at HEAD (two-revision check, arXiv 2212.01479). Excludes the two documented false-positive modes (removed-but-config-relevant flags, and symbols whose literal string was deleted while logic remains). Verify and update the reference.

Marker:

<!-- docguard:ignore REF001 — still relevant, e.g. user-facing flag -->

REF002: Code cites an ADR that has no document

A code comment cites an Architecture Decision Record (e.g. ADR-012) that no ADR document defines — the citation is stale (renumbered, removed) or the ADR was never written. Numbers compare as integers, so ADR-0011 matches ADR-11. IETF RFC citations are deliberately not checked (external registry). Write the ADR, fix the number, or suppress on the citation line.

Marker:

// docguard:ignore REF002 — your reason

Requirement-Anchors (requirementAnchors)

A tag such as @implements acme.billing#FR-003 anchors a requirement to the code below it, and .docguard-anchor-lock.jsonl records that code’s fingerprint from the last review. The fingerprint no longer matches: the normalized code changed (formatting, comments and line moves do not count). Read the change (the finding carries the command), confirm the code still meets the requirement, then record the review with docguard anchors accept --link <key> --reason "<what you checked>". A warning by default; an error under anchors.failOn: reviewed-exact for reviewed, exact-tier, symbol-scope links.

The requirement block a reviewed link cites has changed since the review (whitespace-only edits do not count). The code may no longer meet the new wording. Read both, then accept the link again with a reason, or docguard anchors accept --spec <id> --bulk --reason "…" for every link of the spec. Never an error.

The lock records a reviewed or baselined link whose tag no longer exists, while the requirement still does: the implementation was deleted or the tag was dropped. Restore the tag where the requirement is now implemented, or remove the link with docguard anchors accept --remove <key> --reason "…".

A tag anchors a requirement to code, and the project has an anchor lock, but this link has never been accepted. Reported by docguard anchors only, never by guard. Read the code against the requirement, then docguard anchors accept --link <key> --reason "…".

ANC005: Anchor lock file is unreadable

.docguard-anchor-lock.jsonl does not parse, has an unsupported header, or has a line that does not match schemas/docguard-anchor-lock.schema.json. Nothing in it can be trusted, so no link is reported current. Restore it from git, or delete it and baseline again with docguard anchors accept --baseline --bulk --reason "…".

ANC006: Requirement tag scope is ambiguous or did not resolve

A tag sits where its scope is unclear (a file header directly above a declaration), asks for a scope that does not resolve ({scope=symbol} with no declaration below), lists several requirements in one tag, or sits in a file the pattern tier could not balance. It still counts as file-scope evidence. Add {scope=symbol} or {scope=file} to say which you mean, or split the tag. Reported by docguard anchors only, never by guard.

ANC007: Requirement tag cites a requirement nobody declares

An @implements or @req tag cites a requirement that no spec or requirement document declares, or a bare ID that several documents declare. Qualify it with the Spec ID (@implements acme.billing#FR-003), fix the ID, or remove the tag.

ANC008: Requirement tag cites a retired requirement

The tag cites a requirement of a retired or superseded spec. Point it at the requirement that replaced it, or remove it if the code no longer implements anything that spec described.

Two lines of .docguard-anchor-lock.jsonl record the same link with different content, usually after a union merge of two branches that both reviewed it. The link is treated as suspect until someone reviews it: docguard anchors accept --link <key> --reason "…" rewrites one entry.

ANC010: Requirement anchors were read at a lower tier

Some links could not be compared or placed at their exact tier: a parser is unavailable (Babel, python3), a file does not parse, a different interpreter version recorded the lock, or the anchor cache under .docguard/ is tracked by git and was ignored. Those links fall back to the pattern or file tier, which is less precise. Install the parser or fix the file; untrack .docguard/ with git rm --cached -r .docguard.

Schema-Sync (schemaSync)

SCH001: Database models found but no DATA-MODEL.md

Database models were detected (Prisma, Drizzle, Mongoose, TypeORM, Sequelize, Django, SQLAlchemy or SQLModel models, or Knex and Rails migration tables) but docs-canonical/DATA-MODEL.md does not exist, so the schema is undocumented. Run docguard init to create it, then document the models.

SCH002: Model not documented in DATA-MODEL.md

A model/table found in a schema file does not appear anywhere in DATA-MODEL.md (matching is case-insensitive and singular/plural tolerant). Add it to the Entity Definitions section.

Security (security)

SEC001: Hardcoded password

A password/passwd/pwd assignment with a quoted literal value (8+ chars). A password label is skipped: a capitalised word for “password” in a common language (Password, Contraseña, Mot de passe, パスワード), a short letter-only phrase containing one (Confirm Password), or the key’s own words, unless the key is an all-caps constant such as DB_PASSWORD. Other natural-language UI copy or validation messages are flagged low-confidence; suppress inline if the value is not a credential.

Marker:

// docguard:ignore SEC001 — UI copy, not a credential

SEC002: Hardcoded API key

An api_key/apikey assignment with a quoted literal value (16+ chars). Move it to an environment variable and read it via process.env.

Marker:

// docguard:ignore SEC002 — sample value in fixture

SEC003: Hardcoded secret key

A secret_key/secretkey assignment with a quoted literal value (16+ chars). Move it to an environment variable.

Marker:

// docguard:ignore SEC003 — reason

SEC004: Hardcoded access token

An access_token/accesstoken assignment with a quoted literal value (16+ chars). Move it to an environment variable.

Marker:

// docguard:ignore SEC004 — reason

SEC005: AWS Access Key ID

A string matching the AWS Access Key ID format (AKIA…). Rotate it immediately if real, and move credentials to the AWS credential chain / environment.

Marker:

// docguard:ignore SEC005 — documented example key

SEC006: API secret key (Stripe/OpenAI pattern)

A string matching a live/test secret-key format (sk-…, sk_live_…). Rotate it if real and move it to an environment variable.

Marker:

// docguard:ignore SEC006 — reason

SEC010: .env not in .gitignore

No .env entry was found in .gitignore, so a local .env could be committed. Add .env (and .env.local) to .gitignore.

SEC011: No source files scanned for secrets

The secret scan matched zero source files — usually a too-broad ignore config or a wrong sourceRoot. A scan that checks nothing is a dangerous false ✅.

Spec-Kit (specKit)

SPK001: Spec Kit not detected

No .specify/ directory, specs/ folders, constitution.md, or memory/ was found. Consider adopting spec-driven development with specify init (github.com/github/spec-kit), or disable the specKit validator in .docguard.json if it is not wanted.

SPK002: Spec Kit artifacts without .specify/ structure

Legacy spec/constitution/memory artifacts exist but the standard .specify/ directory is missing. Run specify init to create the v3+ standard structure.

SPK003: spec.md quality issue

A spec.md is missing a mandatory template element: a required section (User Scenarios, Requirements, Success Criteria), FR-/REQ- requirement IDs, or SC- success-criteria IDs. A defect spec can opt into the narrower bugfix shape (Root Cause + Fix required instead) with the inline marker.

Marker:

<!-- docguard:spec-type bugfix -->

SPK004: plan.md quality issue

A plan.md is missing a mandatory section (Summary, Technical Context, or Project Structure) per spec-kit plan-template.md. Add the section.

SPK005: tasks.md quality issue

A tasks.md lacks a phased breakdown (“Phase 1:”, “Phase 2:”, …) or task IDs (T001, T002, …) per spec-kit tasks-template.md.

SPK006: Spec Kit artifact unreadable

A spec.md/plan.md/tasks.md exists but could not be read. Check file permissions and encoding.

SPK007: Constitution without AGENTS.md

constitution.md exists but there is no AGENTS.md. AI agents look to AGENTS.md for project rules — create one (e.g. via docguard init) and reference the constitution from it.

SPK008: Phantom completion — checked task with no implementation evidence

A tasks.md task marked [x] names a deliverable path that does not exist, and no evidence tier confirms the work landed: no matching basename anywhere in the repo (moved file), no named code symbol in source, no plan.md/spec.md tie to an existing artifact, no task-ID annotation in source, and no task-ID in the git log. A checked task with no artifact corrupts agent memory — later sessions trust the checkbox and skip the work. Uncheck the task or land the implementation. Flagged low-confidence — report a false positive if the deliverable was renamed beyond recognition. Opt out with "specKit": { "phantomCheck": false } in .docguard.json.

SPK010: Completed task names a file the feature never changed

A tasks.md task marked [x] names a deliverable path that EXISTS but that no commit since this feature began — and no uncommitted change — ever touched. SPK008 asks whether the artifact exists; a task naming files that already existed passes that check whether or not the work happened, which is how a skill file listed in a completed documentation task shipped unmodified in this repository. The git fact is exact, so this is high-confidence, but it is an ESCALATION, not a verdict: a task may legitimately name a file as context, invoke it rather than change it, or the work may have landed under a different path. Read the task, then either do the work, correct the task text, or uncheck it. Suppress one task with <!-- docguard:ignore SPK010 — reason --> on its line; opt out entirely with "specKit": { "untouchedClaimCheck": false } in .docguard.json.

Marker:

<!-- docguard:ignore SPK010 — task invokes this file rather than changing it -->

SPK011: Additional untouched claims elided

Guard reports at most 10 untouched-claim findings (SPK010) per run to avoid noise; this line counts the remainder. Resolve the reported tasks and re-run guard to surface more, or set "specKit": { "untouchedClaimCheck": false } in .docguard.json to disable the check.

SPK012: Two feature directories share a spec number

Two or more top-level specs/ feature directories use the same numeric prefix (for example 016-a and 016-b), usually because parallel agents each picked “the next number” from their own checkout. References to “spec 016” become ambiguous and tools that key on the number pick one arbitrarily. Renumber the later feature with git mv and update references. Projects that create specs in parallel can set Spec Kit’s feature_numbering to timestamp in .specify/init-options.json.

SPK009: Additional phantom completions elided

Guard reports at most 10 phantom-completion findings (SPK008) per run to avoid noise; this line counts the remainder. Fix or uncheck the reported tasks and re-run guard to surface more, or set "specKit": { "phantomCheck": false } in .docguard.json to disable the check.

Spec-Registry (specRegistry)

SPR001: Spec registry missing or stale

The committed .docguard-specs.json does not match the deterministic projection of current specs, tasks, tests, and archive tombstones. Run docguard specs --write, review the diff, and commit it with the behavior change.

SPR002: Spec identity missing or reused

Every active spec needs one immutable lowercase Spec ID in authoritative metadata. IDs survive moves and retirement and may never be reused. Add **Spec ID**: followed by a unique namespaced ID in backticks.

SPR003: Spec registry is invalid

The registry is malformed or contains an unsupported lifecycle value. DocGuard refuses to overwrite invalid reviewed state. Repair the named field, then run docguard specs --write.

SPR004: Spec lifecycle contradicts storage

Working-tree presence, lifecycle context, storage state, and the recovery archive disagree. Active specs must be current/working_tree; retired specs must be retired/git_history with a reason and matching archive event.

SPR008: Recorded review revision is not on this history

A spec’s lastReviewedRevision or an outcome revision does not resolve, or is not an ancestor of HEAD. This happens when specs complete ran on a PR branch that was then squash-merged: the reviewed commit never reached the default branch, so the next maintenance completion cannot reconcile from it. Run docguard specs reanchor --id <spec-id> --write to move the anchor to the commit on this history whose evidence files are byte-identical; if the old revision no longer exists anywhere, pass --to <revision> --reason <text> to attest it. To avoid it, run completions on a branch at the default branch’s tip and commit them once. In a shallow clone ancestry cannot be known, so the check reports partial coverage instead.

SPR009: Asset path covers no file

A spec’s reviewed.scope.assetPaths names files it owns that cannot carry a spec annotation, such as digest-pinned fixtures or recorded outputs; reconciliation links changes under those paths to the spec. An entry ending in / covers a directory, any other entry one file. This entry covers no tracked file (no file on disk outside git): the files moved or were removed, so the entry no longer records anything. Remove it, or correct it to the files the spec owns. Editing assetPaths is a reviewed change, like canonicalDocs.

SPR007: As-built spec drifted from its source paths

An as-built spec (registry origin: as_built, created by docguard generate --spec <area>) records the facts under its sourcePaths — routes, exported symbols, environment variables, entities — with <!-- docguard:fact <kind> <key> --> markers. Guard re-scans those paths: a fact in the code that the spec neither marks nor lists under ## Out of Scope is unaccounted for, and a marked fact that no longer exists means the spec describes code that changed. Update the spec (add or remove the requirement), or move a deliberately unspecified fact under Out of Scope with a reason. This is an escalation: DocGuard knows the fact changed, not whether the spec or the code is right.

SPR006: Tasks checked but no implementation annotation

A spec has at least one checked task, yet no source file carries @implements <specId>#<requirement> for any of its requirements. Implementation evidence is explicit, never inferred from file names, so the lifecycle cannot tell what implements the spec: reconcile classifies the changed sources as unsupported, and specs complete blocks (SPC004/SPC006). Annotate the implementing code (// @implements …, or # @implements … in Python, shell and YAML) and run docguard specs --write. This is an escalation: if the work has not started, uncheck the tasks instead.

SPR005: New spec overlaps prior intent

Preflight found lexical overlap with a prior spec. This is a review hint, never proof of duplication or delivery. Inspect the prior lifecycle and evidence before adding an explicit supersedes or related relationship.

Structure (structure)

STR001: Missing required file

A file listed in requiredFiles (canonical doc, changelog, or drift log) does not exist. Create it from a template with docguard init, or remove it from requiredFiles in .docguard.json if your profile genuinely does not need it.

STR002: Missing agent instructions file

None of the configured agent files (AGENTS.md / CLAUDE.md) exist. AI agents working in this repo have no project contract. Create one with docguard init or write it by hand.

STR004: Agent instruction chain exceeds its byte budget

Codex concatenates AGENTS.md files from the repository root down to the working directory (AGENTS.override.md replaces AGENTS.md in its directory) and stops reading at project_doc_max_bytes, 32768 bytes by default. Text past the limit is dropped without warning, and the nested, most specific rules come last. Move long procedures into linked documents, remove duplicated rules, or — for existing debt — record an allowance in agentInstructions.allowances (keyed by the deepest file) so the chain can shrink but any growth is a reviewed configuration change. The budget itself is agentInstructions.maxBytes.

STR005: Instruction allowance has slack

An allowance in agentInstructions.allowances is at least 1024 bytes above the chain it covers. The chain shrank; lower the allowance to the current size (or remove it when the chain is within the default budget) so it cannot grow back unnoticed. Informational: it never changes the exit code unless findingSeverity.STR005 says so.

Informational by default: it does not change the exit code unless findingSeverity raises it.

Suppressions (suppressions)

IGN001: Ignore entry expired

A .docguardignore entry carries expires:YYYY-MM-DD and that day has passed (UTC). It no longer excludes anything: DocGuard validates the paths it used to hide, so findings in them can appear in this run. If the reason still holds, set a new date; otherwise delete the line. docguard ignore lists every entry with its reason and expiry. Opt out of these checks with "validators": { "suppressions": false }.

IGN002: Ignore entry has no reason

"suppressions": { "requireReason": true } is set and this .docguardignore entry has no trailing # reason. The entry still applies. Write why the path is excluded after the pattern, e.g. legacy/ # removed with the v2 API. A reason of 8 or more characters is also what docguard ignore --format claimspec needs to export the entry.

IGN003: Ignore entry unreadable

This .docguardignore line has an expires: value that is not a calendar date written YYYY-MM-DD, or an expiry or reason with no pattern. DocGuard cannot tell when the entry should end, so it excludes nothing. Write the date as expires:2026-12-31, or remove the token.

Surface-Sync (surfaceSync)

SSY001: Surface entry missing glob

A surfaceSync.surfaces entry in .docguard.json has no glob, so the code-truth set cannot be discovered and the surface is skipped. Add a glob like “cli/commands/*.mjs”.

SSY002: Surface list drift

A doc’s table/bullet inventory for a declared surface disagrees with the files its glob discovers — items exist in code but are missing from the doc, or the doc lists items with no file behind them. Update the list, or add intentional aliases/non-public items to the surface’s ignore list.

Test-Spec (testSpec)

TSP001: Source declared ❌ (missing tests) in TEST-SPEC

A Source-to-Test Map row declares its source as ❌ — the author has flagged missing tests. Write the tests, then update the row status to ✅.

TSP002: Source declared ⚠️ (partial coverage) in TEST-SPEC

A Source-to-Test Map row declares partial coverage for its source. Extend the tests, then update the row status to ✅.

TSP003: Mapped source file not found on disk

A Source-to-Test Map row points at a source file that no longer exists — usually a stale entry after a move or delete. Update or remove the row. If the flagged cell is prose rather than a real path, report it as a false positive.

TSP004: Mapped test file not found on disk

A Source-to-Test Map row declares a test file that does not exist. Create the test file, or point the row at the actual test path.

TSP005: E2E journey declared ❌ (missing test)

A Critical User Journeys / Critical CLI Flows row is marked ❌. Implement the journey test, then mark the row ✅.

TSP006: E2E journey marked ✅ but test file missing

A journey row claims ✅ but none of its referenced test paths exist on disk (globs and “(N suites)” annotations are honored). The glyph is a claim, the file is the proof — fix the path in the row or restore the missing test.

TSP007: No tests found anywhere in the project

TEST-SPEC.md maps nothing and no tests/ directory, co-located .test. files, or vitest/jest config were found. Add tests, then map them in a “## Source-to-Test Map” table so coverage claims stay verifiable.

TODO-Tracking (todoTracking)

TDO001: Skipped test without explanation

A test.skip / it.skip / xit (or similar) has no justification nearby. Add a // REASON: comment on the skip line or up to 3 lines above it explaining why the test is skipped (SKIP/NOTE/WHY/TODO/FIXME prefixes also count).

Marker:

// REASON: <why this test is skipped>

TDO002: Untracked TODO/FIXME annotation

A TODO/FIXME/HACK/XXX/TEMP/WORKAROUND comment is not tracked in any tracking doc (ROADMAP.md, CURRENT-STATE.md, TODO.md, BACKLOG.md, or docs-canonical/ equivalents). Track it there with its file location, resolve it, or exclude the path via todoIgnore in .docguard.json.

TDO003: Additional untracked TODOs elided

Guard reports only the first 5 untracked TODO/FIXME items to avoid noise; this line counts the remainder. Track or resolve the reported items and re-run guard to surface more, or exclude noisy paths via todoIgnore in .docguard.json.

Traceability (traceability)

TRC001: Required canonical doc missing (no traceability)

A doc listed in requiredFiles.canonical does not exist, so no doc-to-source link can be checked. Create it from a template with docguard init (the structure validator flags the missing file too).

TRC002: Unlinked canonical doc (no matching source)

The doc exists but no source file matches its built-in path patterns. If the implementing code lives in a non-standard location, link it explicitly by adding a // @doc <DOC-NAME>.md annotation near the top of a source file that implements the doc.

Marker:

// @doc <DOC-NAME>.md

TRC003: Orphaned doc in docs-canonical/

A doc exists in docs-canonical/ but is not listed in requiredFiles.canonical, so no validator checks it and it can silently rot. Delete it, or add it to requiredFiles.canonical in .docguard.json so it gets validated.

TRC004: Requirement ID without a recognized test annotation or label

A documented requirement has no recognized annotation or test label in the scanned tests. This is a traceability gap, not proof of missing behavioral coverage. Review existing tests and add an @req <ID> annotation or ID test label only where the requirement is verified. Write a test only if coverage is actually missing.

TRC005: Orphaned test reference to unknown requirement

A test references a requirement ID that no doc defines — the test claims to verify something the docs never specified. Remove the stale reference, or add the requirement to the documentation.

Command blockers

These codes stop a docguard specs subcommand before it writes anything. They are not guard findings.

SPC001: Spec, registry or working tree not ready

Reported by docguard specs complete | approve | reanchor.

The command cannot identify what it acts on, or the repository is not in a state it can record. Causes: --id is missing or names no spec in the registry; the committed .docguard-specs.json is missing or stale (run docguard specs --write, review and commit it); the spec is retired; completion found no Git HEAD, or tracked files changed since the recorded revision (commit them first; the lifecycle files completion itself writes are allowed).

SPC002: Lifecycle state does not allow this transition

Reported by docguard specs complete | approve.

Completion needs an approved spec whose delivery is in_progress or implemented, or a verified or released living spec for a maintenance outcome. Record approval, and the delivery state, with docguard specs approve --id <spec-id> [--delivery in_progress|implemented] --write; a person runs it and the registry diff is reviewed in the pull request. approve refuses verified and released (only specs complete records them) and refuses to move a verified or released spec back.

SPC003: Task ledger incomplete

Reported by docguard specs complete.

The spec has a tasks.md and not every task is checked, or it has no task ledger and is not a living verification contract. Finish and check the tasks, or uncheck none you have not done; a living spec with qualified evidence for every requirement may omit tasks.md.

SPC004: Requirement evidence missing

Reported by docguard specs complete.

Completion needs at least one source file annotated @implements <specId>#<requirement>, at least one test annotated @req <specId>#<requirement>, and implementation or test evidence for every requirement the spec declares. Add the annotations, then run docguard specs --write. Bare FR-001 references do not count: the same local ID appears in many specs.

SPC005: Affected canonical documents not recorded

Reported by docguard specs complete.

The registry entry names no canonical document the spec affected (reviewed.scope.canonicalDocs), or names one that does not exist. List the documents the change updated; this is a reviewed field, edited by hand and reviewed in the pull request.

SPC006: Reconciliation incomplete

Reported by docguard specs complete.

Completion reconciles every file changed since the review baseline. It blocks when there is no baseline (pass --since <ref> the first time), when the plan is unsupported, when a changed file traces to no spec (annotate it, list files that cannot carry an annotation under reviewed.scope.assetPaths, or, for a removed file, name its path in the spec that removes it), or when a living spec’s maintenance completion has no new linked change since its last reviewed revision. docguard reconcile --since <ref> shows the classification.

SPC007: Guard has errors

Reported by docguard specs complete.

Completion runs guard and refuses while it reports errors. Run docguard guard, fix the errors, commit, and complete again.

SPC008: Re-anchor target not established

Reported by docguard specs reanchor.

Re-anchoring moves a recorded revision that a squash merge discarded (SPR008). It blocks when no commit on HEAD’s first-parent history carries the reviewed evidence unchanged, when the old revision no longer resolves, or when --to names no commit on HEAD’s history. Pass --to <revision> (a SHA, branch, tag or HEAD; the full SHA is recorded) with --reason to attest the anchor, and --from <revision> when one spec has dangling revisions from different merges.