✅ fresh
last synced 2026-06-17T22:39:35.514075+00:00 · coverage 100% (
doctor)Validation by Beadloom
doc_sync— same source assync-check.
Doctor
Graph validation checks for data integrity and completeness.
Source: src/beadloom/application/doctor.py
Specification
Purpose
The doctor module runs a suite of read-only validation checks against the populated SQLite architecture graph. It identifies structural issues such as missing summaries, orphaned documents, undocumented nodes, isolated nodes, symbol drift, stale sync entries, source coverage gaps, and agent instruction file drift. Each check returns one or more Check results with an associated severity level, enabling operators and CI pipelines to assess graph health without modifying any data.
Severity Enum
Enumeration of check result severity levels.
| Value | String | Meaning |
|---|---|---|
OK | "ok" | No issues found for this check |
INFO | "info" | Informational finding, no action required |
WARNING | "warning" | Potential data quality issue, should be addressed |
ERROR | "error" | Critical integrity violation |
Backed by enum.Enum with string values.
Data Structures
Check (dataclass)
| Field | Type | Description |
|---|---|---|
name | str | Machine-readable check identifier (e.g., "empty_summaries", "isolated_nodes") |
severity | Severity | Severity level of the finding |
description | str | Human-readable description of the specific finding |
Validation Checks
The first seven checks are private functions that accept a sqlite3.Connection and return list[Check]. The eighth check (_check_agent_instructions) accepts a pathlib.Path project root and returns list[Check].
1. _check_empty_summaries
Detects nodes where the summary column is empty string or NULL.
- Query:
SELECT ref_id FROM nodes WHERE summary = '' OR summary IS NULL - On no issues: Returns
[Check("empty_summaries", Severity.OK, "All nodes have summaries.")] - On issues: Returns one
Checkper affected node withSeverity.WARNINGand message"Node '<ref_id>' has empty summary."
2. _check_unlinked_docs
Detects documents in the docs table that have no ref_id linking them to a graph node.
- Query:
SELECT path FROM docs WHERE ref_id IS NULL - On no issues: Returns
[Check("unlinked_docs", Severity.OK, "All docs are linked to nodes.")] - On issues: Returns one
Checkper affected document withSeverity.WARNINGand message"Doc '<path>' has no ref_id — unlinked from graph."
3. _check_nodes_without_docs
Detects graph nodes that have no associated documentation via a LEFT JOIN on the docs table.
- Query:
SELECT n.ref_id FROM nodes n LEFT JOIN docs d ON d.ref_id = n.ref_id WHERE d.id IS NULL - On no issues: Returns
[Check("nodes_without_docs", Severity.OK, "All nodes have documentation.")] - On issues: Returns one
Checkper affected node withSeverity.WARNINGand message"Node '<ref_id>' has no doc linked."
4. _check_isolated_nodes
Detects nodes with no incoming or outgoing edges (completely disconnected from the graph).
- Query:
SELECT n.ref_id FROM nodes n LEFT JOIN edges e1 ON e1.src_ref_id = n.ref_id LEFT JOIN edges e2 ON e2.dst_ref_id = n.ref_id WHERE e1.src_ref_id IS NULL AND e2.dst_ref_id IS NULL - On no issues: Returns
[Check("isolated_nodes", Severity.OK, "No isolated nodes.")] - On issues: Returns one
Checkper affected node withSeverity.INFOand message"Node '<ref_id>' has no edges (isolated)."
5. _check_symbol_drift
Detects nodes where code symbols have changed since the last documentation sync. Uses the symbols_hash column in sync_state (added in BEAD-08) to compare the stored hash against the current computed hash via _compute_symbols_hash().
- Query:
SELECT ref_id, doc_path, symbols_hash FROM sync_state WHERE symbols_hash != '' AND status = 'ok' - On missing column: Gracefully returns
[Check("symbol_drift", Severity.OK, "symbols_hash column not present — skipping drift check.")] - On no entries: Returns
[Check("symbol_drift", Severity.OK, "No sync entries with symbols_hash to check.")] - On no drift: Returns
[Check("symbol_drift", Severity.OK, "No symbol drift detected.")] - On drift: Returns one
Checkper drifted node withSeverity.WARNINGand message"Node '<ref_id>' has code changes since last doc update (<doc_path>)"
6. _check_stale_sync
Reports sync_state entries that are already marked as stale.
- Query:
SELECT ref_id, doc_path, code_path FROM sync_state WHERE status = 'stale' - On missing table: Returns
[Check("stale_sync", Severity.OK, "sync_state not available — skipping.")] - On no stale entries: Returns
[Check("stale_sync", Severity.OK, "No stale sync entries.")] - On stale entries: Returns one
Checkper stale entry withSeverity.WARNINGand message"Sync stale for '<ref_id>': doc=<doc_path>, code=<code_path>"
7. _check_source_coverage
Checks for nodes with untracked source files. Uses check_source_coverage() from doc_sync.engine to detect Python files in a node's source directory that are not tracked in sync_state or code_symbols. Derives project_root from the database file path via PRAGMA database_list.
- On error determining project root: Returns
[Check("source_coverage", Severity.OK, "Could not determine project root — skipping source coverage check.")] - On check failure: Returns
[Check("source_coverage", Severity.OK, "Source coverage check failed — skipping.")] - On no gaps: Returns
[Check("source_coverage", Severity.OK, "All source files are tracked.")] - On gaps: Returns one
Checkper node with untracked files,Severity.WARNINGand message"Node '<ref_id>' has untracked source files: <file_names>"
8. _check_agent_instructions
Validates agent instruction files (.claude/CLAUDE.md and .beadloom/AGENTS.md) for factual drift against actual runtime state. Accepts a pathlib.Path project root (not a database connection). Reads both instruction files, extracts factual claims via regex, and compares them with live introspection of the codebase.
This check runs only when project_root is provided to run_checks. It performs up to six sub-checks:
- Version claim (
agent_instructions_version): Extracts**Current version:** X.Y.Zfrom CLAUDE.md and compares againstbeadloom.__version__(in-tree source of truth, withimportlib.metadatafallback). - Package claims (
agent_instructions_packages): Extracts backtick-wrapped directory names (e.g.,`infrastructure/`) from lines mentioning "Architecture", "DDD", or "packages" in CLAUDE.md, and compares against actualsrc/beadloom/subdirectories containing__init__.py. - CLI command count (
agent_instructions_cli_commands): Reports the actual number of registered Click commands viamain.commandsintrospection. Always returnsSeverity.OK(informational). - MCP tool count (
agent_instructions_mcp_tools): Extracts tool names from backtick-wrapped table rows in AGENTS.md (pattern:| `tool_name` |) and compares the count againstlen(_TOOLS)frommcp_server. - Stack keywords (
agent_instructions_stack): Extracts**Stack:** <text>from CLAUDE.md and verifies it contains expected keywords (python,sqlite). - Test framework (
agent_instructions_test_framework): Extracts**Tests:** <text>from CLAUDE.md and verifies it mentionspytest.
- On neither file existing: Returns an empty list (no checks emitted).
- On each sub-check match: Returns
Check("<name>", Severity.OK, ...). - On each sub-check mismatch: Returns
Check("<name>", Severity.WARNING, ...).
Agent Instructions Freshness Helpers
Helper functions supporting _check_agent_instructions:
| Function | Visibility | Purpose |
|---|---|---|
_extract_version_claim(text) | private | Extract version from **Current version:** X.Y.Z pattern |
_extract_package_claims(text) | private | Extract backtick-wrapped package names from Architecture/DDD lines |
get_actual_version() | public | Read beadloom.__version__ (in-tree), fallback to importlib.metadata |
_get_actual_cli_commands() | private | Introspect Click main.commands for registered command names |
_get_actual_mcp_tool_count() | private | Count tools from mcp_server._TOOLS list |
_get_actual_packages(project_root) | private | Scan src/beadloom/ for directories with __init__.py |
get_actual_version() is the public version-resolution entry point. Callers and tests depend on this name (not a private symbol), so version resolution is assertable through a stable public API. The in-tree beadloom.__version__ is the source of truth; installed package metadata (importlib.metadata) is deliberately not consulted first because editable installs frequently carry stale metadata.
Regex patterns used for extraction:
| Pattern | Regex | Source |
|---|---|---|
_VERSION_RE | \*\*Current version:\*\*\s*(\d+\.\d+\.\d+) | CLAUDE.md |
_PACKAGE_RE | `(\w+)/` | CLAUDE.md (Architecture/DDD lines) |
_MCP_TOOL_RE | |\s*(\w+)\s*| | AGENTS.md |
_STACK_RE | \*\*Stack:\*\*\s*(.+) | CLAUDE.md |
_TESTS_RE | \*\*Tests:\*\*\s*(.+) | CLAUDE.md |
Execution Order
run_checks executes the seven database checks in a fixed order, then conditionally runs the agent instructions check:
_check_empty_summaries_check_unlinked_docs_check_nodes_without_docs_check_isolated_nodes_check_symbol_drift_check_stale_sync_check_source_coverage_check_agent_instructions(only whenproject_root is not None)
CLI Interface
beadloom doctor [--project DIR]- Command source:
src/beadloom/services/commands/index_ops.py(registered on the sharedmainClick group frombeadloom.services.commands._root; BDL-059 S4 split from the former monolithicservices/cli.py). --project DIR: Path to the project root (defaults to current directory).- The CLI opens the database via
open_dbfrombeadloom.infrastructure.dband passesproject_roottorun_checks, enabling the agent instructions check (check 8). - Output: Formatted list of checks with severity markers (
[ok],[info],[warn],[ERR]) viaclick.echo. - Exits with error if the database file is not found at
<project>/.beadloom/beadloom.db.
API
Public Functions
def run_checks(
conn: sqlite3.Connection,
*,
project_root: Path | None = None,
) -> list[Check]Run all validation checks against the database and return the combined list of Check results. The connection must point to a populated beadloom database (i.e., after beadloom reindex has been run). When project_root is provided, the agent instructions freshness check (check 8) is also executed.
def get_actual_version() -> strPublic version-resolution entry point. Returns the in-tree beadloom.__version__, falling back to importlib.metadata.version("beadloom") only if the source version is unavailable.
Public Classes
class Severity(enum.Enum):
OK = "ok"
INFO = "info"
WARNING = "warning"
ERROR = "error"
@dataclass
class Check:
name: str
severity: Severity
description: strInvariants
- The seven database checks always execute; there is no mechanism to skip individual checks.
- The eighth check (
_check_agent_instructions) runs only whenproject_rootis provided torun_checks. - Each check independently returns its own result list. A failure in one check does not affect others.
- When a check finds no issues, it returns exactly one
CheckwithSeverity.OK. - When a check finds N issues, it returns exactly N
Checkobjects (one per affected entity). run_checksnever modifies the database. All operations areSELECTqueries (plus thecheck_source_coveragecall which is also read-only)._check_agent_instructionsnever modifies any files. It only reads.claude/CLAUDE.mdand.beadloom/AGENTS.md._check_symbol_driftand_check_stale_syncgracefully handle missing columns or tables (e.g., pre-migration databases) by returning an OK check with a skip message._check_source_coveragegracefully handles errors in determining the project root or running the coverage check, returning an OK check with a skip message._check_agent_instructionsreturns an empty list (not an OK check) when neither instruction file exists.
Constraints
- Requires a populated SQLite database. Running doctor before
beadloom reindexwill produce misleading results (e.g., all checks return OK on empty tables). - No auto-fix capability. Doctor is strictly read-only and diagnostic.
- No check prioritization or filtering. The caller receives all results and must decide how to present or act on them.
- The
ERRORseverity level exists in the enum but is not currently emitted by any built-in check. _check_symbol_driftdepends ondoc_sync.engine._compute_symbols_hashbeing available; it imports the function at call time._check_source_coveragedepends ondoc_sync.engine.check_source_coverageand derivesproject_rootfrom the database path viaPRAGMA database_list._check_agent_instructionsdepends onbeadloom.__version__being defined in the package__init__.py, and onbeadloom.services.cli.mainbeing a Click group with a.commandsdict.get_actual_versionprefers the in-treebeadloom.__version__overimportlib.metadatato avoid false drift from stale editable-install metadata.
Testing
Test files: tests/test_doctor.py, tests/test_doctor_drift.py, tests/test_doctor_instructions.py
Tests should cover the following scenarios:
- All-clear: Verify that a well-formed graph (nodes with summaries, linked docs, edges) produces seven
Severity.OKresults (or eight whenproject_rootis provided and instruction files are valid). - Empty summaries: Insert nodes with
NULLand""summaries; verifyWARNINGchecks are returned with correctref_idin the description. - Unlinked docs: Insert docs with
ref_id IS NULL; verifyWARNINGchecks with correctpath. - Nodes without docs: Insert nodes with no corresponding
docsrows; verifyWARNINGchecks. - Isolated nodes: Insert nodes with no edges; verify
INFOchecks. - Symbol drift: Modify code symbols after establishing a baseline
symbols_hash; verifyWARNINGchecks are returned for drifted nodes. - Symbol drift graceful degradation: Verify that missing
symbols_hashcolumn returns OK with skip message. - Stale sync entries: Insert
sync_staterows withstatus = 'stale'; verifyWARNINGchecks. - Source coverage gaps: Create nodes with source directories containing untracked files; verify
WARNINGchecks. - Source coverage graceful degradation: Verify that errors in determining project root return OK with skip message.
- Agent instructions version drift: Create a CLAUDE.md with a mismatched version claim; verify
WARNINGforagent_instructions_version. - Agent instructions package drift: Create a CLAUDE.md listing a package that does not exist under
src/beadloom/; verifyWARNINGforagent_instructions_packages. - Agent instructions MCP tool drift: Create an AGENTS.md with a tool table row count that does not match
_TOOLS; verifyWARNINGforagent_instructions_mcp_tools. - Agent instructions missing files: Verify that when neither CLAUDE.md nor AGENTS.md exists,
_check_agent_instructionsreturns an empty list. - Mixed results: Combine multiple issues and verify the full result list contains the expected number and types of checks.
- Empty database: Verify behavior on a schema with no rows (all checks should return OK since there are no entities to flag).