End-to-end guide to docmgr’s diagnostics taxonomy, rule renderers, CLI verbs, and JSON outputs.
Docmgr’s diagnostics system turns raw errors into actionable guidance for humans and machines. It wraps parsing/validation failures into structured taxonomies, renders guidance via rules, and exposes the output through CLI verbs (human text and JSON for CI). This page explains the architecture, the existing domains, how CLI verbs emit diagnostics, and how to extend the system.
The diagnostics stack is split into small packages so domain logic stays reusable and testable:
pkg/diagnostics/core): Taxonomy, ContextPayload, severities, stage/symptom codes, and error wrapping (WrapWithCause, AsTaxonomy).pkg/diagnostics/docmgrctx): Domain-specific stages, symptom codes, and context structs (frontmatter, vocabulary, related-files, templates, listing, workspace) plus constructors in constructors.go.pkg/diagnostics/docmgrrules): Renderers that match a taxonomy and produce RuleResult with headline, body, severity, and suggested actions.pkg/diagnostics/docmgr/adapter.go): Default registry + text rendering; supports collectors for JSON output and context attachment (ContextWithRenderer).pkg/diagnostics/render): Text and JSON helpers.pkg/diagnostics/rules): Rule registration and scoring.The flow: a verb detects an issue → builds a taxonomy via constructors → docmgr.RenderTaxonomy runs registered rules → text goes to stderr (default) and optionally into a collector for JSON.
Diagnostics are emitted in a uniform pattern so verbs stay simple:
// Wrap an error with taxonomy for Go error chains
return core.WrapWithCause(err, docmgrctx.NewFrontmatterParse(path, line, col, snippet, problem, err))
// Render ad-hoc taxonomy (e.g., missing related file)
docmgr.RenderTaxonomy(ctx, docmgrctx.NewRelatedFileMissing(docPath, rf.Path, rf.Note))
// Attach a collector for JSON output (doctor uses this for --diagnostics-json)
renderer := docmgr.NewRenderer(docmgr.WithCollector())
ctx = docmgr.ContextWithRenderer(ctx, renderer)
// ... emit taxonomies ...
data, _ := renderer.JSON() // pretty JSON of all rule results
Rules stay decoupled from producers: producers only construct taxonomies; renderers decide how to display or serialize them.
Each domain defines stage/symptom codes and context structs under pkg/diagnostics/docmgrctx:
frontmatter.go): StageFrontmatterParse, SymptomYAMLSyntax, SymptomSchemaViolation; contexts carry file, line/col, snippet, field, and detail. Constructors: NewFrontmatterParse, NewFrontmatterSchema.vocabulary.go): StageVocabulary, SymptomUnknownValue; context holds file, field, offending value, known values. Constructor: NewVocabularyUnknown.related_files.go): StageRelatedFiles, SymptomMissingFile; context includes doc path, related path, and note. Constructor: NewRelatedFileMissing.templates.go): StageTemplateParse, SymptomTemplateParseError; context has template path and problem. Constructor: NewTemplateParse.listing.go): StageListing, SymptomSkippedDueToParse; context records command (list_docs/search), file, and reason. Constructor: NewListingSkip.workspace.go): StageWorkspace, SymptomMissingIndex, SymptomStaleDoc; contexts note missing ticket path or staleness metadata. Constructors: NewWorkspaceMissingIndex, NewWorkspaceStale.All constructors are re-exported via pkg/diagnostics/docmgrctx/constructors.go for consistent usage in verbs.
Rules live in pkg/diagnostics/docmgrrules and register in default.go:
FrontmatterSyntaxRule): Points to YAML line/col and suggests docmgr validate-frontmatter.FrontmatterSchemaRule): Highlights missing/invalid fields and suggests docmgr meta update --field ....VocabularySuggestionRule): Lists known values and offers vocab add / vocab list.RelatedFileMissingRule): Suggests removing/fixing the path.TemplateParseRule): Surfaces .templ parsing errors with file and parser message.ListingSkipRule): Explains why list/search skipped a doc (usually bad frontmatter).WorkspaceRule): Covers missing index and stale docs.Renderer: docmgr.RenderTaxonomy looks up matches in the default registry, renders text to stderr, and, if a collector is attached, accumulates RuleResult objects for JSON (render.RenderToJSON).
Diagnostics are emitted from verbs and helpers so users see consistent guidance:
pkg/commands/doctor.go): Emits workspace missing index/stale, frontmatter schema (required fields + missing Status/Topics), vocabulary warnings, related file missing, and invalid frontmatter anywhere under the ticket. Supports --diagnostics-json <path|-> to write rule results for CI while preserving --fail-on semantics.pkg/commands/list_docs.go, search.go): Emit listing-skip taxonomies when a doc is skipped due to bad frontmatter instead of silently ignoring it.pkg/commands/template_validate.go): Wraps .templ parse errors into template taxonomies so users see parser details.pkg/commands/meta_update.go, relate.go, rename_ticket.go): Wrap frontmatter parse errors into taxonomies for actionable output.internal/workspace/discovery.go) and frontmatter parsing (internal/documents/frontmatter.go): Wrap parse errors so callers (doctor, listing) receive taxonomies in error chains.Several core commands are backed by the workspace index + QueryDocs (search/list/doctor). In those flows, diagnostics can be produced directly by QueryDocs in addition to the “classic” command-level checks:
QueryDocs can emit a warning diagnostic to explain why the match happened.The taxonomy constructors for these live in:
pkg/diagnostics/docmgrctx/query_docs.go# Human + JSON (file)
docmgr doctor --all --stale-after 30 --diagnostics-json diagnostics.json --fail-on warning
# Human + JSON to stdout (useful in CI pipelines)
docmgr doctor --ticket MEN-4242 --diagnostics-json - --fail-on error
JSON payload is an array of RuleResult:
[
{
"Headline": "Unknown vocabulary value for Topics",
"Body": "File: ttmp/.../index.md\nField: Topics\nValue: \"custom\"\nKnown values: chat, backend\n",
"Severity": "warning",
"Actions": [
{ "Label": "Add to vocabulary", "Command": "docmgr", "Args": ["vocab", "add", "--category", "topics", "--slug", "custom"] }
]
}
]
The fastest path to add a domain is captured in ttmp/2025/12/01/DOCMGR-ERROR-TAXONOMY-diagnostics-taxonomy-unification/playbook/01-how-to-add-a-new-diagnostics-domain.md, but the essentials are:
pkg/diagnostics/docmgrctx.pkg/diagnostics/docmgrrules and register it in default.go.docmgr.RenderTaxonomy.go test ./pkg/diagnostics/... ./pkg/commands and relevant scenario scripts.Automated coverage keeps diagnostics stable across verbs:
pkg/diagnostics/docmgrrules (rules), pkg/diagnostics/docmgr (renderer JSON collector), plus command tests where applicable.test-scenarios/testing-doc-manager/15-diagnostics-smoke.sh exercises vocabulary, related files, listing skips, workspace staleness/missing index, frontmatter parse, template parse, and writes diagnostics JSON.go test ./pkg/diagnostics/... ./pkg/commands validates rule wiring and command helpers.Use this list to jump directly to the implementation:
pkg/diagnostics/core/types.gopkg/diagnostics/docmgrctx/*.go, constructors in constructors.gopkg/diagnostics/docmgrrules/*.go, default.gopkg/diagnostics/docmgr/adapter.go (+ tests)pkg/commands/doctor.go, list_docs.go, search.go, template_validate.go, meta_update.go, relate.go, rename_ticket.gointernal/documents/frontmatter.go, internal/workspace/discovery.gotest-scenarios/testing-doc-manager/15-diagnostics-smoke.sh