Diagnostics (memo doctor)
Feature Overview [edit]
memo doctor is the vault and repository health gate. It verifies the vault root, SQLite FTS index, project binding, and scans the consumer working tree for leaked workflow artifacts. It is CLI-only (no MCP tool) and implemented in src/doctor.ts (runDoctor).
Journeys:
- Health gate in CI or before shipping:
memo doctorexits 0 when healthy and clean, 1 otherwise. - Machine consumption:
memo doctor --jsonemits the fullDoctorResult. - Boundary check for one path:
memo doctor --check-capture <path>printsCAPTUREDorIGNORED (matched line N: <pattern>, source: ...). - Repair:
memo doctor --rebuildrebuilds FTS;memo doctor --fixdeletes untracked in-tree residue (skipping git-tracked files unless--include-tracked).
Business Rules & Logic [edit]
- Vault structure: warns when the vault root directory does not exist.
- FTS index: opens
memo.sqlite, counts rows inrecords_fts; a missing database is a warning, an open error is reported as a SQLite warning.--rebuildcallsrebuildIndex, setsfts.healthy = true, and marksfts.rebuilt. - Project binding:
resolveProjectIdentityresolves project id, git remote, root path,isGit, andisFallback. A fallback path id (no git remote) produces a warning that moving/renaming the repo changes the id. - Pollution scan (
scanForRepoPollution): recursively walks the product root (max depth 6) skippingnode_modules,.git,dist,build, and.spec-memo. If the scan root equals the vault root or is inside it, the scan returns empty (the vault is where records belong). Detected types:
- plan_residue: .agents/plans/ or agents/plans/. - memory_residue: MEMORY.md, */MEMORY.md, memory/, .agents/memory/, ws-shared/memory/. - state_residue: run.json matched on a filename boundary via /(^|\/)run\.json$/ against the lowercased posix relative path (so Default.abprun.json is not flagged), plus .state.md. - telemetry_residue: telemetry.jsonl, /telemetry/, telemetry/. - log_residue: audit-*.log.md, .agents/*.log.
- Ignore boundary: every candidate is filtered through
evaluatePathIgnore(the sameloadIgnoreRules/isPathIgnoredboundary used by--check-capture), honoring the product.spec-memo-ignoremarker and vaultconfig.jsonprojects.{id}.ignorePaths. Ignored candidates never appear initems; they incrementpollution.excludedByIgnoreCount. This lets a dual-mode repo with live.agents/plans/,ws-shared/memory/, and a tracked rootMEMORY.mdreachhealthy: trueonce those paths are covered by ignore rules. --fixdeletion safety: untracked residue is unlinked. Files reported bygit ls-files -zare tracked and skipped by default, collected inpollution.skippedTracked, and summarized in a warning. Only--include-trackeddeletes them (and then they are listed in a warning). If tracked status cannot be determined (non-zerogit ls-files), every candidate is treated as tracked so nothing is deleted on ambiguity.--fixalso cleans only semantically identical conflict sidecars, then rebuilds FTS and affected compiled views. Divergent sidecars requirememo reconcile --prefer local|remote --clean-sidecars.- Agent hooks:
inspectAgentHooksreports installed hosts and warnings for outdated templates compared to the running package version. - Additional diagnostics when FTS is healthy: active semantic contradictions and potentially obsolete traps/decisions (salience).
- Healthy definition:
vaultExists && ftsHealthy && pollutionItems.length === 0. Inremotemode this definition is replaced (not augmented): health requires the remote/healthprobe to be reachable, a token to be configured, and zero pollution;vaultExists/ftsHealthyare not part of the remote result. The CLI exitsresult.healthy ? 0 : 1. - Warning-only cases: hybrid/remote mode without a URL, missing bearer token, unreachable remote daemon, invalid
.spec-memo-ignorelines, and detected conflict sidecars. - Exit contract: 0 for healthy/clean, 1 for issues (pollution, unhealthy FTS, unreachable remote in remote mode) — standard CI/CD diagnostic convention.
--check-captureformatting:CAPTURED (no ignore rule matched)orIGNORED (matched line N: <pattern>, source: .spec-memo-ignore|config.json|builtin);source: builtinreflectsDEFAULT_IGNORE_PATTERNS(e.g..git/,node_modules/,*.sqlite).- Negative scenarios handled without crashing or hanging: all daemons stopped (
STOPPED, no ECONNREFUSED), malformedconfig.json(reported, not thrown), unbound non-git directory (fallback path id reported), and offline remote daemon (UNREACHABLE with message). - Live probe timeouts:
runDoctoruses a 10 s default for the remote/healthprobe (DEFAULT_HEALTH_TIMEOUT_MS, overridable viaSPEC_MEMO_HEALTH_TIMEOUT_MS/SPEC_MEMO_SYNC_TIMEOUT_MS). The 1500 ms per-local-endpointprobeHttpServiceand 3000 ms remote helper belong tomemo status(src/status-cmd.ts), notdoctor.--check-captureruns no network probes.
Technical Architecture [edit]
runDoctor(options: DoctorOptions) accepts cwd, vaultRoot, productRoot, rebuild, fix, checkCapture, and includeTracked. It returns DoctorResult containing healthy, vaultRoot, vaultExists, mode, remoteUrl, tokenConfigured, hybridState, vaultGit, remoteHealth, project, fts, pollution, agentHooks, exclusionBoundary, warnings, summary, semanticContradictions, and potentiallyObsolete.
--check-capture short-circuits all other checks: it calls checkCapturePath with the resolved project id and returns an early result where healthy means status === 'CAPTURED', includes the serialized captureCheck, and formats the summary with formatCheckCaptureResult.
Key helpers: findFilesRecursive, listTrackedFiles (returns an empty set outside git, null when git status is indeterminate), wrapSqliteOpenError, and checkRemoteHealth (10 s default timeout via SPEC_MEMO_HEALTH_TIMEOUT_MS / SPEC_MEMO_SYNC_TIMEOUT_MS).
CLI surface: memo doctor prints a text report with mode, remote origin/health, vault location, project id, product root, FTS count, agent hooks summary, exclusion boundary, pollution list, skipped-tracked list, warnings, contradictions, obsolete records, and summary. --json emits DoctorResult unchanged.
Provenance: 0005-import-and-doctor.spec.md, 0009-cli-doctor.spec.md, 0052-us-54.spec.md.