Deep dive into how doctor and validate frontmatter detect, classify, and report issues (with links to code and rules).
This page walks through the end-to-end validation pipeline used by docmgr doctor and docmgr validate frontmatter. It explains how these commands discover documents, parse frontmatter, validate schema, generate fix suggestions, and render diagnostics through the rule system. Each step is tied to specific code locations and algorithms so you can reason about behavior, extend the system, or debug issues.
For user-facing guidance on using these commands, see:
docmgr help yaml-frontmatter-validation
For implementation details on parsing and fix heuristics, see:
docmgr help yaml-frontmatter-validation-reference
For the diagnostics taxonomy system architecture, see:
docmgr help diagnostics-taxonomy-and-rules
The validation pipeline follows a consistent pattern across both doctor and validate frontmatter: discover inputs → parse frontmatter → validate schema → generate fixes (optional) → render diagnostics. The key difference is scope: doctor scans entire workspaces and checks multiple document types, while validate frontmatter focuses on a single file with optional auto-fix capabilities.
Input discovery determines which files to validate. The strategy differs by command:
Doctor workspace scan (--all or --ticket):
workspace.DiscoverWorkspace (see internal/workspace/workspace.go)Workspace.InitIndex (see internal/workspace/index_builder.go)
.meta/ and _*/ like _templates/ / _guidelines/) — see internal/workspace/skip_policy.go.docmgrignore matcher before frontmatter parsing, so ignored dependency/generated Markdown is not indexed.Workspace.QueryDocs (see internal/workspace/query_docs.go)
doctor requests IncludeErrors=true so parse-error docs are available as DocHandle{ReadErr: ...} for repair workflowsdoctor requests IncludeDiagnostics=true so QueryDocs can emit structured diagnostics (for example parse-skip and normalization fallback)workspace.FindTicketScaffoldsMissingIndex (see internal/workspace/discovery.go).docmgrignore through the shared workspace matcher, not a doctor-only post-filter. Explicit --ignore-dir / --ignore-glob flags remain doctor command filters for compatibility.Doctor single-file mode (--doc):
--doc path--root (defaults to ttmp/)validateSingleDoc function (pkg/commands/doctor.go)Validate frontmatter (--doc):
--doc parameter)All commands use the same frontmatter parser: ReadDocumentWithFrontmatter (internal/documents/frontmatter.go). This function extracts the YAML block between --- delimiters, preprocesses risky scalars to quote colons and special characters, decodes with yaml.Node to preserve position information, and wraps any failures in a FrontmatterParseTaxonomy with line numbers, column positions, code snippets, and user-friendly problem descriptions.
Key parsing steps:
--- delimiters)frontmatter.PreprocessYAML)yaml.Decoder into yaml.Node (preserves line/col)models.Document structAfter successful parsing, schema validation checks that required fields are present and optionally warns about missing recommended fields or vocabulary mismatches.
Required fields (all commands):
models.Document.Validate() checks Title, Ticket, DocTypeFrontmatterSchema taxonomies with field names and detailsdoctor and validate frontmatter use this validationOptional checks (doctor only):
vocabulary.yamlpkg/commands/doctor.go after successful parseThe validate frontmatter command can generate fix suggestions and optionally apply them automatically. Fix generation operates on raw file bytes (not parsed structure), allowing repairs even when parsing fails completely.
Fix heuristics (generateFixes in pkg/commands/validate_frontmatter.go):
---, handle stray delimiter lines): from frontmatter to body)PreprocessYAMLFix application (--auto-fix):
<path>.bak)All validation errors flow through docmgr's diagnostics taxonomy system. Taxonomies are rendered via the default rule registry (pkg/diagnostics/docmgrrules), which matches taxonomies to rules based on stage and symptom codes. Rules produce RuleResult objects with headlines, bodies, severities, and suggested actions.
Frontmatter rule output:
docmgr validate frontmatter --doc <path>File: internal/documents/frontmatter.go (ReadDocumentWithFrontmatter)
Algorithm:
raw = read file bytes
fm, body, fmStartLine = extractFrontmatter(raw)
// Scans for --- delimiters
// Returns frontmatter bytes, body bytes, starting line number (1-based)
fm = frontmatter.PreprocessYAML(fm)
// Walks top-level key-value lines
// Quotes risky scalars (colons, special chars, etc.)
decoder = yaml.NewDecoder(bytes.NewReader(fm))
node = yaml.Node{}
if err := decoder.Decode(&node); err != nil {
// Extract line number from error message (regex: "line ([0-9]+)")
yamlLine = extractLineNumber(err)
absoluteLine = fmStartLine + yamlLine - 1
// Build snippet: 3 lines of context with line numbers
snippet = buildSnippet(fileLines, absoluteLine, column)
// Classify error into user-friendly problem text
problem = classifyYAMLError(err)
// Wrap in taxonomy
taxonomy = docmgrctx.NewFrontmatterParse(path, absoluteLine, column, snippet, problem, err)
return nil, "", core.WrapWithCause(err, taxonomy)
}
// Decode node into Document struct
doc = models.Document{}
if err := node.Decode(&doc); err != nil {
// Same error handling as above (extract line/col, build snippet, emit taxonomy)
}
return &doc, body, nil
Outputs:
*models.Document, body string, nil errornil, empty string, taxonomy-wrapped error with line/col/snippet/problemError extraction details:
---)fmStartLine + yamlLine - 1max(1, line-1) to min(len(lines), line+1) with optional caretRequired fields: models.Document.Validate() (pkg/models/document.go)
Algorithm:
missing := []string{}
if doc.Title == "" {
missing = append(missing, "Title")
}
if doc.Ticket == "" {
missing = append(missing, "Ticket")
}
if doc.DocType == "" {
missing = append(missing, "DocType")
}
if len(missing) > 0 {
return fmt.Errorf("missing required fields: %s", strings.Join(missing, ", "))
}
return nil
Taxonomy emission:
doctor and validate frontmatter emit FrontmatterSchema taxonomiesFrontmatterSchemaRule) suggests docmgr meta update --field <field> --value <value>Optional doctor checks:
docmgr.RenderTaxonomyvocabulary.yamlVocabularyUnknown taxonomies with known values list and vocab add suggestionFile: pkg/commands/validate_frontmatter.go (generateFixes)
Algorithm:
// Try normal frontmatter extraction
fm, body, _, err := documents.SplitFrontmatter(raw)
if err != nil {
// Fallback: normalize delimiters heuristically
fixed, fixErr := normalizeDelimiters(raw)
if fixErr == nil {
fixes = []string{"Normalize frontmatter delimiters (add missing closing ---)"}
return fixes, fixed, nil
}
return nil, nil, err
}
// Clean up frontmatter content
cleaned := scrubStrayDelimiters(fm)
// Remove lines that look like --- inside frontmatter
fmLines := bytes.Split(cleaned, []byte("\n"))
peeledBody := peelTrailingNonKeyLines(&fmLines)
// Move trailing lines without ":" from frontmatter to body
cleaned = bytes.Join(fmLines, []byte("\n"))
// Quote unsafe scalars
quoted := frontmatter.PreprocessYAML(cleaned)
// Quotes colons, special chars, etc.
// Build fix descriptions
fixes := []string{}
if !bytes.Equal(quoted, cleaned) {
fixes = append(fixes, "Quote unsafe scalars (colons, hashes, special leading chars)")
}
if !bytes.Equal(cleaned, fm) {
fixes = append(fixes, "Remove stray delimiter lines inside frontmatter")
}
if len(peeledBody) > 0 {
fixes = append(fixes, "Move non key/value lines out of frontmatter")
}
// Reconstruct file
buf := bytes.Buffer{}
buf.WriteString("---\n")
buf.Write(quoted)
buf.WriteString("\n---\n")
if len(peeledBody) > 0 {
buf.Write(peeledBody)
buf.WriteByte('\n')
}
buf.Write(body)
return fixes, buf.Bytes(), nil
Fix application (applyAutoFix):
// Write backup
backupPath := path + ".bak"
os.WriteFile(backupPath, original, 0644)
// Write fixed content
os.WriteFile(path, fixed, 0644)
// Re-parse to verify
doc, _, err := documents.ReadDocumentWithFrontmatter(path)
if err == nil {
// Success: suppress original error taxonomy, print success message
fmt.Printf("Frontmatter auto-fixed: %s\n", path)
return doc, nil
}
// Failure: emit new taxonomy from failed re-parse
if tax, ok := core.AsTaxonomy(err); ok {
docmgr.RenderTaxonomy(ctx, tax)
}
return nil, fmt.Errorf("auto-fix applied but re-parse failed: %w", err)
File: pkg/commands/doctor.go (validateSingleDoc)
Algorithm:
// Parse frontmatter
doc, _, err := documents.ReadDocumentWithFrontmatter(path)
if err != nil {
// Render taxonomy if it's a taxonomy-wrapped error
if tax, ok := core.AsTaxonomy(err); ok {
docmgr.RenderTaxonomy(ctx, tax)
}
return severityError, err
}
// Validate required fields
if err := doc.Validate(); err != nil {
// Emit FrontmatterSchema taxonomy for each missing field
// Render via rule system
return severityError, err
}
// Optional checks (warnings)
if doc.Status == "" {
docmgr.RenderTaxonomy(ctx, docmgrctx.NewFrontmatterSchema(path, "Status", "missing recommended field"))
}
if len(doc.Topics) == 0 {
docmgr.RenderTaxonomy(ctx, docmgrctx.NewFrontmatterSchema(path, "Topics", "missing recommended field"))
}
// Vocabulary checks
checkVocabularyMismatches(ctx, doc, topicSet, docTypeSet, intentSet, statusSet)
// Success: emit success row
row := types.NewRow(
types.MRP("doc", path),
types.MRP("status", "ok"),
)
gp.AddRow(ctx, row)
return severityOK, nil
Note: Related file checks (missing files in RelatedFiles) are currently only performed in workspace scan mode (for ticket index files), not in single-file mode.
Command: docmgr doctor --all or docmgr doctor --ticket <TICKET>
Behavior:
workspace.DiscoverWorkspace) and resolves the effective docs root/config/repo anchors.Workspace.InitIndex), applying the canonical skip policy during ingestion.index.md (workspace.FindTicketScaffoldsMissingIndex) and emits missing_index findings (scoped to --ticket when provided).Workspace.QueryDocs (typically with IncludeErrors=true and IncludeDiagnostics=true) and groups findings by ticket.RelatedFiles existence checks (doc-anchored path normalization)LastUpdated older than --stale-after days).docmgrignore matcher during index ingestion; explicit --ignore-glob / --ignore-dir flags are applied as doctor command filters.--diagnostics-json)--fail-on threshold is metOutput:
--with-glaze-output --output json): Array of row objects--diagnostics-json): Array of RuleResult objectsCommand: docmgr doctor --doc <file>
Behavior:
Limitations:
validate frontmatter --auto-fix instead)Command: docmgr validate frontmatter --doc <file> [--suggest-fixes] [--auto-fix]
Behavior:
--suggest-fixes or --auto-fix:
FrontmatterParseContext.Fixes--auto-fix:
.bak backup)Output:
--with-glaze-output --output json): Row object with doc path, title, ticket, docType, statusFrontmatter validation errors flow through docmgr's diagnostics taxonomy system. This design allows consistent error reporting across all commands (doctor, list docs, search, meta update, etc.) and enables fix suggestions to be attached to taxonomies and rendered by rules.
Parse errors:
ReadDocumentWithFrontmatter wraps failures in FrontmatterParseTaxonomydocmgr.RenderTaxonomy(ctx, taxonomy) to renderSchema errors:
doc.Validate() returns error listing missing fieldsFrontmatterSchema taxonomies per missing fieldFix suggestions:
validate frontmatter --suggest-fixes generates fixesFixes []string to FrontmatterParseContextFrontmatterSyntaxRule renders fixes as numbered listFrontmatterSyntaxRule (pkg/diagnostics/docmgrrules/frontmatter_rules.go):
StageFrontmatterParse + SymptomYAMLSyntaxdocmgr validate frontmatter --doc <path>FrontmatterSchemaRule:
StageFrontmatterParse + SymptomSchemaViolationdocmgr meta update --doc <path> --field <field> --value <value>Commands that emit frontmatter taxonomies:
doctor: Workspace scan and single-file modevalidate frontmatter: Single-file validationlist docs / search: Skip docs with parse errors, emit ListingSkip taxonomiesmeta update / relate / rename_ticket: Wrap parse errors in taxonomiesHelper functions:
internal/documents/frontmatter.go: ReadDocumentWithFrontmatter wraps parse errorsinternal/workspace/index_builder.go: ingestion stores parse_ok / parse_err so parse failures can be surfaced consistently via diagnostics and IncludeErrorsinternal/workspace/discovery.go: missing-index scaffold detection (FindTicketScaffoldsMissingIndex)Parser:
internal/documents/frontmatter.go: ReadDocumentWithFrontmatter, extractFrontmatter, classifyYAMLError, buildSnippet, SplitFrontmatter, WriteDocumentWithFrontmatterFix engine:
pkg/commands/validate_frontmatter.go: ValidateFrontmatterCommand, generateFixes, normalizeDelimiters, scrubStrayDelimiters, peelTrailingNonKeyLines, applyAutoFix, tryAttachFixesPreprocessing:
pkg/frontmatter/frontmatter.go: NeedsQuoting, QuoteValue, PreprocessYAMLDiagnostics:
pkg/diagnostics/docmgrctx/frontmatter.go: FrontmatterParseContext (with Fixes field), NewFrontmatterParse, NewFrontmatterSchemapkg/diagnostics/docmgrrules/frontmatter_rules.go: FrontmatterSyntaxRule, FrontmatterSchemaRuleDoctor:
pkg/commands/doctor.go: DoctorCommand, validateSingleDoc, workspace scan logicinternal/workspace/workspace.go: DiscoverWorkspace, Workspace, InitIndex, QueryDocsinternal/workspace/discovery.go: FindTicketScaffoldsMissingIndexModels:
pkg/models/document.go: Document, Validate(), RelatedFilesDocumentation:
pkg/doc/docmgr-diagnostics-and-rules.md: Diagnostics taxonomy system architecturepkg/doc/docmgr-yaml-frontmatter-validation.md: User guide for validation and auto-fixpkg/doc/docmgr-yaml-frontmatter-validation-reference.md: Technical reference for parsing and fix algorithmsttmp/2025/11/29/DOCMGR-YAML-001.../reference/02-frontmatter-healing-and-validation-guide.mdTo add auto-fix capabilities to doctor:
validate_frontmatter.go (generateFixes, applyAutoFix)--auto-fix flag to DoctorSettingsgenerateFixes to get suggestions and fixed content--auto-fix, call applyAutoFix (creates .bak, rewrites, re-parses)validate frontmatter for backup handling and error suppressionTo add new schema validation rules:
FrontmatterSchema taxonomies on violationsFrontmatterSchemaRule if rendering needs changesdocmgr meta update)To add new fix heuristics:
pkg/commands/validate_frontmatter.gogenerateFixes (order matters: delimiters → cleanup → quoting)fixes arrayvalidate_frontmatter_test.goWhen modifying rule rendering:
pkg/diagnostics/docmgrrules/frontmatter_rules.goUnit tests:
pkg/commands/validate_frontmatter_test.go: Fix heuristic tests (delimiter normalization, stray cleanup)pkg/frontmatter/frontmatter_test.go: Quoting helper tests (NeedsQuoting, QuoteValue, PreprocessYAML)internal/documents/frontmatter_test.go: Parsing and error extraction testspkg/commands/doctor_test.go: Doctor command testsSmoke tests:
test-scenarios/testing-doc-manager/18-validate-frontmatter-smoke.sh: Validation verb smoke (fail → suggest → auto-fix → success, verifies .bak creation)test-scenarios/testing-doc-manager/15-diagnostics-smoke.sh: Diagnostics smoke (exercises frontmatter parse taxonomy via doctor/list/search, writes diagnostics JSON)Manual testing:
docmgr doctor --all on a workspace with known issuesvalidate frontmatter --auto-fix on broken files.bak files are created and fixes are applied correctly