Finding Codes

Every finding DocGuard reports carries a stable code. This page lists all 188 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
AI-DLC AID001, AID002, AID003, AID004, MEM001, MEM002
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
Decision-Records DEC001, DEC002, DEC003, DEC004, DEC005, DEC006
Diff-Suspicion DSP001
Doc Sections STR003, STR006, STR007, STR009
Doc-Dependency DLK001, DLK002, DLK003, DLK004, DLK005
Doc-Ownership OWN001, OWN002, OWN003, OWN004, OWN005, OWN006, OWN007, OWN008
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, FRS006, FRS007, FRS009, FRS010, FRS003, FRS004, FRS005
Generated-Staleness GST001, GST002
Metadata-Sync MDS001, MDS002
Metrics-Consistency MET001, MET002, MET003, MET004
Path-Scoped-Rules PSR001, PSR002, PSR003, PSR004, PSR005, PSR006, PSR007, PSR008
Reference-Existence REF001, REF002
requiredChecks REQ001, REQ002
Requirement-Anchors ANC001, ANC002, ANC003, ANC004, ANC005, ANC006, ANC007, ANC008, ANC009, ANC010, ANC011
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, SPK013, SPK014, SPK015, SPK016, SPK009
Spec-Registry SPR001, SPR002, SPR003, SPR004, SPR008, SPR010, SPR011, SPR009, SPR007, SPR006, SPR005
Structure STR001, STR002, STR004, STR005, STR008
Suppressions IGN001, IGN004, IGN005, 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

AI-DLC (aidlc)

AID001: AI-DLC traceability target no longer exists

An AI-DLC traceability.json row marked OK or Deferred names a workspace path that no longer exists: the code it was traced to at the gate was renamed or removed. Point the requirement at the code that implements it now, or reopen it. Withheld while an observation gap stands. Informational by default.

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

AID002: AI-DLC gap left in a completed stage

A traceability row is GAP or ORPHAN in a stage aidlc-state.md marks completed. AI-DLC’s traceability sensor is advisory by default, so a gate can close with gaps. Build the requirement, record it Deferred/N/A with a reason, or fix the orphan. Informational by default.

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

AID003: AI-DLC unit deliverable removed after the unit

A path a unit’s source-manifest.json claims existed when the manifest was committed and no longer exists. Paths the unit itself deleted are not reported. Confirm the move or removal was intended and update the record. Withheld while an observation gap stands. Informational by default.

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

AID004: AI-DLC record unreadable

A traceability.json or source-manifest.json is not valid JSON in AI-DLC’s shape, or names a path that escapes the project. Restore the file or regenerate the stage output. Informational by default.

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

MEM001: Learned rule names something gone

A rule in an AI-DLC memory layer (memory/org.md, team.md, project.md or phases/*.md) names, in backticks, a path that no longer exists or an environment variable no code or env example uses. The rule keeps steering every later stage. Update or retire it through AI-DLC; DocGuard never edits memory layers. Withheld while an observation gap stands. Informational by default.

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

MEM002: CodeKB names a path that is gone

A file in an AI-DLC CodeKB (codekb//*.md) names, in backticks, a path that no longer exists, so agents load a stale map of the code. Rerun AI-DLC reverse engineering for that repository. Informational by default.

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

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.

Decision-Records (decisionRecords)

DEC001: ADR status missing or unknown

An ADR has no status line, or one that is not proposed, accepted, rejected, deprecated or superseded (Approved, Draft, Proposal, Obsolete and Replaced are read as synonyms). Without a known status nobody can tell whether the decision is in force. Informational by default.

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

A Supersedes / Superseded-by link, or an index row, names an ADR number that no decision record defines. Fix the number or write the record. Withheld while an observation gap stands (spec 081). Informational by default.

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

DEC003: One-way supersession

One ADR says it supersedes (or is superseded by) another, and the other does not say the reverse, so a reader of the older record follows a decision that is no longer in force. Make both records name each other and set the older one to superseded. Informational by default.

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

DEC004: Superseded status and successor disagree

An ADR is superseded but names no successor, or names a successor while its status is still something else. Add the link or correct the status. Informational by default.

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

DEC005: ADR index disagrees with the record

The ADR index table gives a status that differs from the record’s own status line. Make the row match the record. Informational by default.

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

DEC006: Question with no answer

A Spec Kit clarification (- Q: … → A:) or an AI-DLC [Answer]: tag has no answer, so whatever was written after it rests on a guess. Answer it and revisit those artifacts, or remove the question. Informational by default.

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

Diff-Suspicion (diffSuspicion)

DSP001: Doc describes code that just changed

A canonical doc (or agent-instruction file) references a code file AND, in the section that names the file, shares wording with symbols removed/changed in that file since the compared revision. Other sections and other rows of an inventory table do not count. 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. The doc is read where its role resolves: a docs.roles mapping, the docs-canonical/ default, or the one same-named entry of requiredFiles.canonical (spec 093). Add the section — or, if it is genuinely not applicable, own the absence with an inline marker: <!-- docguard:section <slug> n/a — reason -->. A document in another language is read by id: set docs.language (a BCP 47 tag; pt and es ship heading sets, docs.labelAliases adds your own wording), or put <!-- docguard:heading <id> --> on the line under the heading (spec 104; docguard explain STR009 lists the ids).

Marker:

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

STR006: Canonical doc is an unedited scaffold

A canonical doc holds nothing a reader wrote beyond the template docguard init created it from: every line is template text, an HTML-comment placeholder, an empty table cell, the Metadata table or a date. It scores as unfinished in docQuality, ALCOA+ Legible and Machine markers. Write the document (or run docguard generate --plan --write, which fills code-derived sections), or — if it is intentionally blank — own it with <!-- docguard:section scaffold n/a — reason --> (any reasoned section N/A marker owns the document, and it is then scored as written). A placeholder owner or unchosen status alone is not reported: generate --plan --write leaves both. A document that docs.roles remaps is compared with the template of its role.

Marker:

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

STR007: Section the active profile requires is missing

A profile can require sections beyond the canonical ones (the enterprise-ai profile requires Risk Assessment and Threat Model in SECURITY.md; requiredSections in .docguard.json adds more). Add the heading, or own its absence with <!-- docguard:section <slug> n/a — reason -->. Informational by default (spec 082): it never changes the exit code unless findingSeverity.STR007 says so. A heading that is one of the canonical labels also resolves by docs.language, docs.labelAliases or a <!-- docguard:heading <id> --> marker, as STR003 does.

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

Marker:

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

STR009: Heading marker that cannot work as written

A <!-- docguard:heading <id> --> comment on the line under a heading is the language-neutral way to say which required section that heading is, whatever language it is written in (spec 104; docs.language in .docguard.json also selects built-in Portuguese and Spanish headings, and docs.labelAliases adds your own). This finding means a marker cannot do that: its id is not one of the closed list (system-overview, component-map, tech-stack, layer-boundaries, data-flow, external-dependencies, revision-history, entities, relationships, indexes, authentication, secrets-management, authorization, security-boundaries, test-categories, coverage-rules, test-commands, source-to-test-map, environment-variables, setup-steps, prerequisites, configuration-files, installation, usage, license, contributing, user-scenarios, requirements, success-criteria, summary, technical-context, project-structure, root-cause, fix, unreleased), a second heading carries the same id (the first counts), its heading is the English label of a different id (the marker wins, so the other id stays unsatisfied), or it is not on the next non-blank line under a heading (after prose, inside a code fence, inside a larger comment) although it names a valid id. Fix the id, or move the marker directly under its heading. A marker in a project with no docs.language still satisfies its id. Advisory (a warning).

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.

OWN008: Ownership entry has a placeholder purpose

An ownership.entries[] entry carries a purpose that is blank or a <placeholder> such as <purpose>, usually a drafted block applied unedited. The entry names a doc section and says nothing about what it is responsible for, so it looks like coverage and documents none. Write one sentence for the entry, or remove the purpose (the field is optional). An entry with no purpose is not reported.

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. In another language, docs.language (pt and es ship the headings), docs.labelAliases or <!-- docguard:heading installation --> (or usage, license) under the heading satisfies it.

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. A technology is not read as documented when a negator governs it (no CDK, Terraform or Pulumi stacks negates every item of the list; No Docker, Express serves the API negates Docker only), when a removal or migration verb takes it as its object (removed Terraform, migrated off Terraform, was replaced by — but not removed all Redis keys), or when it appears under a Revision History, Changelog or Release Notes heading, which describe past state.

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, and a brace pattern (src/tests/{alpha,beta}.test.ts, foo.{test,spec}.ts) documents every path it expands to (single level, at most 64 expansions); a brace glob (src/**/*.test.{js,jsx}) is one pattern that documents what any alternative matches.

Docs-Sync (docsSync)

DSY001: Route not referenced in canonical docs

A route file exists but no docs-canonical .md file names it. Only text a reader sees counts: a name inside an HTML comment, such as a template placeholder, does not. A Next.js route handler (app/api/**/route.ts) is named by the URL path it serves (/api/users/{id}, GET /api/users/:id, or its file path), and /api/users/* names the routes under it; the word route names none. Any other route is named by its path, file name or module name (the file name without its extension) as a whole name, so users or user-service does not document user. A module name that several checked files share needs its directory (auth/types), and an index file is named by its directory (routes/users) or users/index. A file tree drawn in a doc (├──, └──) names the paths it lists, and a docguard:section marker whose covers lists the file documents it too. 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 no docs-canonical .md file names it. Only text a reader sees counts: a name inside an HTML comment, such as a template placeholder, does not. Its path, file name or module name (the file name without its extension) must appear as a whole name, so users or user-service does not document user. A module name that several checked files share needs its directory (auth/types), and an index file is named by its directory (services/auth) or auth/index. A file tree drawn in a doc (├──, └──) names the paths it lists, and a docguard:section marker whose covers lists the file documents it too. 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. Headings match as STR003 matches them: any case, section numbers allowed, synonyms such as “Getting Started” accepted. Add a Setup Steps section describing how to get the project running from a fresh clone. In another language, docs.language, docs.labelAliases or <!-- docguard:heading setup-steps --> (or prerequisites) under the heading satisfies it, as for STR003.

ENV002: Missing Environment Variables section

ENVIRONMENT.md has no Environment Variables heading (matched as STR003 matches headings: any case, section numbers allowed, synonyms such as “Env Vars” accepted; “Environment” and “Configuration” only as the whole heading or followed by a word such as “Reference”, “Variables” or “(.env)”, so “Configuration Files” or “Production environment” does not count). Add the section and document each variable the app reads — backticked names or a pipe table both count. In another language, docs.language, docs.labelAliases or <!-- docguard:heading environment-variables --> under the heading satisfies it, as for STR003.

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. Agent-instruction documents come from one role (spec 094): root AGENTS.md, CLAUDE.md, GEMINI.md, .github/copilot-instructions.md, configured requiredFiles.agentFile, nested AGENTS/CLAUDE/GEMINI files, and harness rules and skills. A pointer file (a body of at most 6 lines, at most 2 of them naming no file, that imports a file or names another agent-instruction document, such as Follow AGENTS.md or @AGENTS.md) is reviewed through its target and gets no finding of its own; a copy installed by Spec Kit, a Spec Kit extension, DocGuard or a tool that marks it <!-- generated by <tool> --> is reviewed upstream (PSR005 checks a Spec Kit copy still matches its install manifest).

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. Agent-instruction documents come from one role (spec 094): root AGENTS.md, CLAUDE.md, GEMINI.md, .github/copilot-instructions.md, configured requiredFiles.agentFile, nested AGENTS/CLAUDE/GEMINI files, and harness rules and skills. A pointer file (a body of at most 6 lines, at most 2 of them naming no file, that imports a file or names another agent-instruction document, such as Follow AGENTS.md or @AGENTS.md) is reviewed through its target and gets no finding of its own; a copy installed by Spec Kit, a Spec Kit extension, DocGuard or a tool that marks it <!-- generated by <tool> --> is reviewed upstream (PSR005 checks a Spec Kit copy still matches its install manifest).

FRS006: Agent instructions name a file that changed since their review

An agent-instruction document (AGENTS.md, CLAUDE.md, GEMINI.md, Copilot instructions, an authored skill or rule) names a file in backticks, a link or a path, and at least freshness.referenceShare (default 0.5) of that file’s current lines were added since the document was reviewed. The review point is a recorded review (docguard review --accept <doc>), whose fingerprint must also have changed, or else the docguard:last-reviewed marker; a line of the document edited after the review is measured from that edit. Directories, vendored copies, other documents, test/fixture/example paths, changelogs, lockfiles, claims/evidence JSON, DocGuard state files, generated files and files under 20 lines are not measured. Read what the document says about each named file against the file now, update it, and record the review. Agent instructions are executed by every session, so this is reported for that role only; canonical documents carry the same evidence on FRS002 (spec 095). Make it fail the build with findingSeverity: { "FRS006": { "agent-instruction": "high" } }.

FRS007: Agent instructions never mention surface added since their review

A package script (package.json scripts) or a top-level directory was added after the agent-instruction document’s review point, and neither the document nor a pointer file that points to it mentions it (npm run <name>, `<name>`, or the directory as a whole word). Surface that existed at the review is not reported: this is about what changed, not about completeness. Decide whether agents need to know about it, say so, and record the review with docguard review --accept <doc> (spec 095).

FRS009: Review date advanced without a recorded review

A document’s docguard:last-reviewed marker is later than its recorded review, and the commit that set it changed nothing else in the document. A date alone does not say what was checked, so staleness is still measured from the recorded review. If the document was reviewed, record it with docguard review --accept <doc> --reason "…" (spec 095).

FRS010: Recorded document reviews cannot be read

.docguard-doc-reviews.json is not valid JSON, has an unsupported schemaVersion, or has a field outside the schema. Every recorded review is then unusable, so this fails instead of reading as “no reviews”. Restore the file from git, or delete it and record the reviews again. A new field belongs under an extensions object (top level or per document), which every reader ignores (spec 095).

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. Agent-instruction documents come from one role (spec 094): root AGENTS.md, CLAUDE.md, GEMINI.md, .github/copilot-instructions.md, configured requiredFiles.agentFile, nested AGENTS/CLAUDE/GEMINI files, and harness rules and skills. A pointer file (a body of at most 6 lines, at most 2 of them naming no file, that imports a file or names another agent-instruction document, such as Follow AGENTS.md or @AGENTS.md) is reviewed through its target and gets no finding of its own; a copy installed by Spec Kit, a Spec Kit extension, DocGuard or a tool that marks it <!-- generated by <tool> --> is reviewed upstream (PSR005 checks a Spec Kit copy still matches its install manifest).

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. “1,183 checks” reads as 1183; a ratio (“8/8 checks”) or an identifier (“FR-365 checks”) is not a count. docguard fix --write rewrites it, keeping the doc’s digit grouping (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. An identifier (“FR365 tests”) and a count of something else (“20 test files”, “3 test suites”) are not test-case counts. 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.

PSR005: Installed instruction copy differs from what its installer recorded

A skill or instruction file that a Spec Kit integration installed (it is listed, with its sha256, in .specify/integrations/<integration>.manifest.json) no longer matches any recorded hash. The file is owned upstream: the next specify integration install overwrites a local edit, and DocGuard does not review it for staleness. Reinstall the integration to restore the copy, or move the change into a file the project owns. A manifest that does not parse vendors nothing and is reported as partial coverage. DocGuard-installed copies are compared with the running package by docguard upgrade instead (spec 094). When the only difference is CRLF line endings (a Windows checkout with core.autocrlf), the message says so: mark the file text eol=lf in .gitattributes.

PSR006: Instruction-file copy shares the real file’s name

A file named like one of this project’s instruction files (AGENTS.md, AGENTS.override.md, CLAUDE.md, GEMINI.md, .github/copilot-instructions.md or a requiredFiles.agentFile) is a copy for another project, and a file that governs this project has the same name. An agent searching the repository for that name may read whichever it finds first, and Codex and Claude Code load a nested AGENTS.md or CLAUDE.md when they work in its folder. A copy sits below a template directory (templates, template, _templates, __templates__, scaffold(s), boilerplate(s), starter-kit(s), skeleton(s), in any case; high confidence) or below a test, fixture or example directory (low confidence: a fixture project may need the name). The folder that holds the file does not count, so templates/AGENTS.md and packages/templates/AGENTS.md govern their folders, and monorepo package files such as packages/api/AGENTS.md are never reported; nor are files under a dot-directory segment (.cursor/rules/templates/). Rename the copy (AGENTS.md.template) and have the scaffolder or test drop the suffix when it writes the file out, or list a fixture in .docguardignore with a reason. If the file does govern its folder, list it in fileKinds.own. Template copies are also left out of the agent-instruction role, so Freshness never reviews them and the instruction-chain budget (STR004) does not count them (specs 099 and 100); guard --format json lists every file left out under files.kinds.excluded.

PSR007: Mirrored skill or command copies differ

The same skill or command exists at the same relative path under two agent roots (.agent/, .agents/, .claude/, .codex/, .gemini/, .github/ or an installed extension folder .specify/extensions/<ext>/), for example .claude/skills/x/SKILL.md and .agents/skills/x/SKILL.md, and the two files differ. Each harness loads only its own copy, so the harnesses are running different procedures under one name. The message names both paths, how many lines differ and which was committed more recently (or says it cannot tell, as in a shallow clone). A copy that DocGuard installed is refreshed by docguard upgrade --apply --schema-only only in the one folder DocGuard writes for the project (the skills folder of the detected agent); the message says which copy that is, and for any other folder says no DocGuard command writes it (copy the newer file over it, or delete it). Copies that differ by design are not reported: two Spec Kit installs for different harnesses (each is checked against its own install manifest by PSR005) and renderings of one generated file that differ only in the harness the generator header names. Files that merely share a name in different folders are different documents, never mirrors. Make one copy the source and refresh or delete the others, or list a copy that is meant to differ in .docguardignore. guard --format json carries mirrors on the finding and files.mirrors for the whole project.

PSR008: Root instruction files nearly match but disagree

Two of the root instruction files AGENTS.md, CLAUDE.md, GEMINI.md and .github/copilot-instructions.md (or the same set inside one nested folder) share at least 80% of their non-blank lines and are not identical, so they are hand-written copies that drifted: one can say npm where the other says yarn, and each agent follows only the file its harness loads. A pointer file (a body of at most 6 lines that imports the other file) and a file written by docguard agents --sync are not compared here (docguard agents --check compares those). Two files that are simply different documents share few lines and are never reported. Keep one file as the source and make the other import it (@AGENTS.md) or generate it with docguard agents --sync.

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

requiredChecks (requiredChecks)

REQ001: Required check disabled in config

A validator named in requiredChecks is turned off with "validators": { "<key>": false }, so the configuration both requires and disables it. Enable it, or remove it from requiredChecks. If the check genuinely does not apply to this project, declare that in-repo with a reasoned <!-- docguard:validator <key> n/a — reason --> marker: a declared N/A satisfies a required check, a config switch does not.

REQ002: Required check names no validator

An entry in requiredChecks is not a validator key, so nothing is required by it. The message lists the valid keys; docguard guard --format json shows each validator’s key. The other required checks still apply.

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.

No longer reported (spec 102, docguard.anchor-lock-merge); the code stays registered so configuration and pragmas that name it keep loading. Two lines of .docguard-anchor-lock.jsonl for one link, usually from a union merge of two branches that both reviewed it, used to be refused. DocGuard now reads the line whose recorded fingerprints match the code and the requirement as they are now, and the link needs review (ANC001 or ANC002, with its docguard anchors accept command) only when none does. Every write leaves one line per link; docguard anchors merge --write does the same without accepting anything, and also resolves git conflict markers.

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.

ANC011: Requirement anchor covers only volatile lines

Every line of the code this tag anchors carries a docguard:volatile marker, so its fingerprint is a placeholder and the link can never turn suspect. A marker is for a release-managed value inside code that implements the requirement, not for the code itself. Anchor the tag to that code, or remove the marker. Reported by docguard anchors only, never by guard.

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. A value that names itself a placeholder (placeholder, synthetic, not-a-real, notreal, dummy, changeme, change-me, or one repeated character) is not reported. Every SEC001-SEC004 finding carries context: product or test-path; the level is the same either way, but a test-path finding can never be a gating finding.

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. A path git cannot observe (ignored and untracked, such as a local diagnostic receipt) is never reported: its absence from history proves nothing. 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.

SPK013: Requirement no task or plan cites

spec.md defines this FR/SC ID, and tasks.md cites requirement IDs, but neither tasks.md nor plan.md names this one, so nothing plans its implementation on purpose. Add a task (or a plan decision) that names it, or remove the requirement. A task list that cites no IDs at all is not checked. Informational by default; docguard policy shows when it is quiet enough to promote with findingSeverity.

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

SPK014: Task or plan cites a requirement the spec does not define

tasks.md or plan.md cites an unqualified FR/SC ID that spec.md does not define: the spec was renumbered or trimmed, or the ID belongs to another spec. Point it at the requirement it means, or qualify it (spec 013 FR-020, docguard.other-spec#FR-020). IDs inside inline code, fences and HTML comments are ignored. Informational by default.

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

SPK015: Spec Kit artifact names no upstream

plan.md names neither spec.md nor any requirement ID it defines, or tasks.md names neither plan.md, spec.md nor any such ID, so nothing shows it was made from them. Link the upstream artifact or cite the requirements it addresses. Informational by default.

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

SPK016: User story with no labeled task

tasks.md labels tasks with [USn], and no task carries this story’s label, so the story is unplanned. Plan and label its tasks, or drop the story. A task list without [USn] labels is not checked. Informational by default.

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

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, and it is not anchored by its content either. A squash merge or a rebase discards the commit specs complete reviewed; a record written by a current DocGuard also carries the git blob of every evidence file (the Blobs: segment of its outcome line in the spec), and stays valid when the commit that landed it has those blobs. So this fires for a record written before blobs were recorded, or when the evidence changed between review and landing (the message names the files that differ). 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. For changed evidence, review it and complete again. In a shallow clone ancestry cannot be known, so the check reports partial coverage instead.

SPR010: Verified spec with no recorded outcome

A current spec’s delivery is verified or released, but its registry entry records no reconciliation outcome. specs approve cannot set those states and specs complete always records an outcome with them, so the state was set by hand (or the spec predates outcomes): no review is on record, and a verified living spec is excluded from the lifecycle review signal DLC002. Record the review with docguard specs complete --id <spec-id> --since <ref> --reason <text> --write, which also says which evidence files were reviewed. This is an escalation: DocGuard knows no review was recorded, not that none happened.

SPR011: Closed spec whose evidence changed after its review

A current spec is verified or released, and an implementation or test file its latest completion recorded (the Blobs: segment of its outcome line in the spec) is different at HEAD, or is gone. The record no longer describes the code. SPR008 cannot see this on a pull request’s merge ref, because the commit that landed the record is the main branch’s squash commit, whose files match the record; only a developer who merged main into the branch saw it. The spec’s canonical documents are not compared byte for byte (Freshness and requirement anchors cover them), nor is a hub file that four or more closed specs list as evidence (the per-link anchor lock keeps watching it; guard JSON lists the hubs under files.completionHubs), and a record written before blobs existed is not judged. specs complete takes an uncommitted edit of the spec’s evidence files and records their working-tree blobs, so the edit and the completion land in one commit and a gate on every commit never sees this warning. Review the change against the spec’s requirements, then run docguard specs complete --stale --reason <text> --write: it closes every spec that drifted, in one transaction, keeping each spec’s canonical documents and persistence, and writes nothing, listing each blocker, if one spec cannot be closed. It is a warning, so a strict gate fails on it; it cannot be suppressed inline. This is an escalation: DocGuard knows the files changed, not whether the spec is still met.

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.

STR008: Backup copy of a document in a documentation directory

A file inside a documentation directory (docs, docs-canonical, docs-implementation, documentation, doc, at any depth) is named like a backup of a document: ARCHITECTURE.md.bak, .orig, .backup, a trailing ~, SECURITY copy.md, API (1).md, DATA-old.md or DATA.old.md. DocGuard reads no backup as a document (it is not a canonical document, not reviewed by Freshness, not read for cross-references, not listed by docguard llms) and never lets one stand in for a missing role document, so it is dead weight that can still mislead a reader. docguard generate --force and docguard init --force write a .bak beside the file they replace. Delete it (git keeps the history), add it to .gitignore, or, if it is a real document, rename it or list it in fileKinds.own. A document below an archive/, old/, backup/ or deprecated/ folder of a documentation directory also leaves the document role, but only the file name makes this finding: an archive is usually kept on purpose. guard --format json lists what was left out under files.kinds.excluded.backup. The finding reads tracked files only; the first ten are listed, then a count.

Suppressions (suppressions)

IGN001: Suppression expired

A suppression carries expires:YYYY-MM-DD and that day has passed (UTC): a .docguardignore entry, a .docguard.json object entry, a validator or section N/A marker, or (when a waiver policy is set) an inline pragma. It no longer suppresses 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 }.

IGN004: Suppression names no owner

"suppressions": { "requireOwner": true } is set and this suppression has no owner: token. The suppression still applies. End its reason with owner:@handle, owner:team/name or an email address so someone answers for the waiver (spec 084). Baseline fingerprints are exempt.

IGN005: Suppression outlives the allowed window

"suppressions": { "maxDays": N } is set and this suppression expires more than N days from today (UTC), or never. The suppression still applies. Set expires:YYYY-MM-DD within the window and renew it then if the reason still holds (spec 084). Baseline fingerprints are exempt.

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. Only real comments count: a keyword inside a string, or quoted in backticks within a comment, is a quotation and is not reported.

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. A requirement with no code target (a documentation requirement, a one-time outcome such as an A/B run or a budget run) takes its evidence from a test that checks the result: describes() asserts that each named document carries the terms the requirement asks for, and recorded() asserts that the spec’s Verification Notes answer each measured criterion; tag that test @req <specId>#<requirement>, as tests/program-verification.test.mjs does for specs 081 to 095.

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.