Detailed reference for ccg lint categories, how the report is computed, and how to interpret the results.
ccg lint cross-checks three things:
- Generated Markdown docs under the configured docs output directory
- Source files and symbols already stored in the graph database
- Structured annotations attached to graph symbols
The command reports documentation coverage issues and annotation quality issues together.
# Check docs and annotation health
ccg lint
# Fail the command when strict-mode rules require it
ccg lint --strict
# Lint a non-default docs directory
ccg lint --out docsFor CLI flags and lint rules in .ccg.yaml, see CLI Reference.
At a high level, the linter performs these steps:
- Walk the docs output directory and collect Markdown files
- Query graph nodes from the database and derive the source files currently represented in the graph
- Cross-check docs against graph files
- Load symbol annotations and their tags
- Classify each issue into one of the lint categories
Notable implementation details:
index.mdis skipped because it is not treated as a per-file doc.missingandstaleare file-level categories.unannotated,contradiction,dead-ref,incomplete, anddriftedare symbol-level categories.- Tests are included when computing graph-backed file docs coverage, but tests are excluded from
unannotatedsymbol checks.
ccg lint reports these categories.
A documentation file exists, but the graph no longer contains a matching source file.
- Typical cause: code was deleted or moved, but the generated doc file remained.
- Practical meaning: the doc is now detached from the live codebase.
- Typical fix: regenerate docs or delete the stale doc file.
A source file exists in the graph, but there is no matching Markdown doc file.
- Typical cause: docs were never generated for that file, or the docs directory is incomplete.
- Practical meaning: file-level documentation coverage is missing.
- Typical fix: run docs generation for the current graph state.
A documentation file exists, but its modification time is older than the latest update time recorded for the corresponding source file in the graph.
- Typical cause: code changed after the last docs generation.
- Practical meaning: the doc may still exist, but it may no longer describe the current code accurately.
- Typical fix: regenerate docs after rebuilding or updating the graph.
A function, class, or type symbol has no annotation record at all.
- Typical cause: the symbol has no structured annotation such as
@intent. - Practical meaning: search and AI context can still use the code, but they lose the explicit human-written intent layer.
- Typical fix: add structured annotation comments above the declaration.
An annotation exists, includes one or more @param tags, and the code node was updated after that annotation was written.
- Typical cause: signature or behavior changed, but
@paramdetails were not refreshed. - Practical meaning: the detailed annotation is likely unreliable.
- Typical fix: review the symbol and update the detailed annotation tags.
This is intentionally stricter than general drift because @param tags are especially likely to become wrong when code changes.
An @see tag points to a qualified name that does not exist in the graph.
- Typical cause: referenced symbol was renamed, removed, or moved.
- Practical meaning: cross-reference navigation is broken.
- Typical fix: update or remove the
@seetag.
An annotation exists, but it does not contain an @intent tag.
- Typical cause: a doc comment was added without the intent annotation, or only partial tags were written.
- Practical meaning: the symbol has some documentation, but it is missing the most important structured explanation of why it exists.
- Typical fix: add
@intentto the annotation block.
An annotation exists, but the code node was updated after the annotation was written.
- Typical cause: code evolved and the annotation was not refreshed.
- Practical meaning: the annotation may be directionally correct, but it is no longer guaranteed to match the current implementation.
- Typical fix: review and refresh the annotation.
drifted is broader than contradiction. A symbol can be drifted even if it has no detailed tags such as @param.
Some categories overlap, and some do not.
| Category A | Category B | Relationship |
|---|---|---|
contradiction |
drifted |
Every contradiction is also a kind of drift, but not every drift is a contradiction |
unannotated |
incomplete |
Mutually exclusive in practice: incomplete means an annotation exists, unannotated means none exists |
missing |
stale |
Mutually exclusive per file: a file cannot both lack a doc and have an out-of-date doc |
orphanmissingstale
These categories are about whether generated Markdown files match the current graph-backed source files.
unannotatedcontradictiondead-refincompletedrifted
These categories are about the quality and freshness of structured annotations attached to symbols in the graph.
Lint results can be narrowed by graph scope and path filtering.
If lint runs with a namespace, node queries and @see resolution are scoped to that namespace.
Practical effect:
dead-refchecks only look for referenced symbols inside the same namespace scope.- file and symbol category counts can differ between a full-repo lint and a namespace-scoped lint.
Excluded file paths are skipped when collecting graph-backed source files and symbol candidates.
Practical effect:
- excluded files do not contribute to
missing,stale, or annotation-related categories - generated code or intentionally ignored paths can be filtered out through lint rules and config
The user-facing category labels map to LintReport fields like this:
| Category label | LintReport field |
|---|---|
orphan |
Orphans |
missing |
Missing |
stale |
Stale |
unannotated |
Unannotated |
contradiction |
Contradictions |
dead-ref |
DeadRefs |
incomplete |
Incomplete |
drifted |
Drifted |
For rule matching and generated lint state, CCG also accepts the alias drift. In user-facing reports and guide text, the category name remains drifted.
If you are reading the implementation, the exact logic lives in internal/docs/lint.go.
This usually means docs generation has not been run recently, or code changed after docs were generated.
Typical response:
- rebuild or update the graph
- regenerate docs
- rerun lint
This means the code is present, but structured intent metadata is still sparse.
Typical response:
- prioritize high-value symbols first
- add English annotations consistently
- rerun lint to measure coverage improvement
This usually means comments were added, but @intent was forgotten.
Typical response:
- locate the listed symbols
- add
@intent - rerun lint
These usually need manual review because they indicate stale or broken semantic metadata.
Typical response:
- inspect the symbol
- update or remove outdated tags
- rerun lint
Per-category rules can be configured in .ccg.yaml.
Example:
rules:
- pattern: "pkg/store/.*"
category: unannotated
action: ignoreRules with action: ignore suppress matching findings, including under --strict. Patterns may be exact qualified names or regular expressions.
See CLI Reference for the full rule reference.
ccg lint --strict is intended for CI or pre-commit enforcement.
Typical uses:
- fail CI on documentation regressions
- block commits when important lint categories exceed policy
- keep generated docs and annotations in sync over time