f321dabce3
Phase 3 of the 2026-05-24 config-migration plan. Read-only
diagnostic over config files. Pairs with `gnoma upgrade-config`
from the previous slice: doctor finds things upgrade-config
can't fix, upgrade-config fixes the things it can.
What doctor surfaces (severity-ranked):
error — file unreadable, file unparseable
warn — unknown top-level keys (decoder silently
ignores them today)
— invalid enum values (permission.mode,
router.prefer, slm.backend)
— explicit-zero pointer fields whose resolved
value diverges from the default (e.g.
max_tokens = 0 when default is 8192)
info — (reserved; current diagnostics are warn+)
What doctor does NOT yet surface:
- Per-field zero-spam inside a partially-set section
(e.g. user wrote [provider] default = "anthropic" with
no other fields — those are at Go zero but the
encoder's omitempty handles them on the next write).
Catching this requires per-key source-tracking that
BurntSushi's MetaData doesn't expose for nested
fields; tracked as a follow-up.
- Cross-file layering bugs (e.g. project file's
prefer = "" silently shadows global's prefer = "cloud").
That requires loading the full layered config and
diffing per-section — could be a follow-up to doctor,
or the per-project upgrade-config --all flow.
CLI surface (`cmd/gnoma doctor`):
gnoma doctor scan the project config
(default — cwd's .gnoma/config.toml)
gnoma doctor <path> scan a specific file
gnoma doctor --all-projects walk the registry, scan
global + every known project
gnoma doctor --json structured JSON to stdout
(severity as string, suitable
for CI/scripts)
exit code: 0 = clean, 1 = any warn/error
Help text: `gnoma -h` now lists `doctor` alongside the
other subcommands.
Implementation:
internal/config/doctor.go Severity, Finding, Doctor,
DiagnoseFile, DiagnoseFiles
(~150 lines).
internal/config/doctor_test.go 11 tests covering each
finding type + Severity.String.
cmd/gnoma/doctor_cmd.go CLI dispatch + JSON / text
rendering + exit code.
cmd/gnoma/doctor_cmd_test.go 5 tests for the CLI surface.
internal/config/load.go new ProjectConfigPathFor
helper for --all-projects
(constructs a project config
path from an arbitrary root
without chdir).
cmd/gnoma/main.go dispatch case + -h help text.
Severity.MarshalJSON is custom: encodes the int as its
lower-case name string ("warn" not 1) for stable CI
consumption. Tests assert on the string form.
End-to-end check on a synthetic config with multiple
findings:
$ gnoma doctor
warn ...:permission.mode invalid permission.mode "yes" ...
→ fix the value, or remove the line
warn ...:provider.max_tokens explicit zero for provider.max_tokens
(resolved to 0); the default is 8192. ...
warn ...:unknown_section unknown top-level key "unknown_section" ...
warn ...:unknown_section.foo unknown top-level key "unknown_section.foo" ...
exit: 1
$ gnoma doctor --json
[
{ "severity": "warn", "path": "...",
"key": "permission.mode", "message": "..." },
...
]
Quality pipeline:
gofmt -l . clean
go vet ./... clean
golangci-lint run ... 0 issues on touched packages
go test ./... all pass (only the pre-existing
TestStartBackend_Auto_NothingReachable
environmental failure remains)
Refs: docs/superpowers/plans/2026-05-24-config-migration.md
§ Phase 3.