Title
worklog doctor: environment and connectivity diagnostics
Summary
Implement worklog doctor: a check matrix over config, each enabled
source, the LLM endpoint, and the output directory, printing PASS/WARN/FAIL
lines with actionable fixes (notably the zsh EXTENDED_HISTORY guidance) and
exiting non-zero only on FAIL.
Context
The reference machine demonstrates the two onboarding cliffs doctor exists
for: zsh history without timestamps, and an LLM endpoint/model that isn't
running/pulled yet. Doctor turns both from silent degradation into guided
setup.
Scope
internal/cli/doctor.go (replace stub) + tests.
Detailed Requirements
- Output format (stdout — doctor's report IS its output; stderr only for
harness errors): one line per check:
PASS config: loaded (<path>) / WARN zsh: … / FAIL llm: …,
followed by an indented fix: line for every WARN/FAIL with a concrete
command or config edit. Final summary line
doctor: N pass, M warn, K fail. Exit 1 iff K>0, else 0.
- Checks (in this order; each isolated — one failure never skips later
checks):
config: load + validate (validation errors → FAIL each, fix
references the key). Unknown-key warnings → WARN each.
zsh (when enabled): file exists+readable (missing → WARN, fix:
check path/HISTFILE); parse first 2000 entries: ExtendedCount==0 →
WARN with the exact fix
echo 'setopt EXTENDED_HISTORY INC_APPEND_HISTORY' >> ~/.zshrc
(new entries only — note that old entries stay undated); else PASS
with entry count.
git: binary found (ErrGitNotFound → FAIL, fix: install git);
author identity resolvable (else WARN, fix: set [sources.git] authors or git config --global user.email); each root exists
(missing → WARN); repos discovered (count in PASS line; zero repos →
WARN).
claude-code / codex (when enabled): dir exists (missing → WARN,
fix mentions the config key), count of session files with mtime within
7 days (0 → WARN "no recent sessions").
llm (when llm.enabled): endpoint parses + static loopback IP check
(violation → FAIL with ADR-002 one-liner); Models(ctx, 5s timeout)
reachable (refused → FAIL, fix: ollama serve or start the server;
ErrNonLoopbackBlocked → FAIL with the blocked host/IP); configured
model ∈ returned ids (empty model → FAIL listing first 5 available
ids as the fix; not-in-list → WARN listing near matches). When
llm.enabled=false: single line PASS llm: disabled.
output (when [output].directory set): exists → writable check via
os.Stat + unix.Access(W_OK)-equivalent (use
os.CreateTemp? NO — doctor must not write (I3 applies to doctor
strictly: it's a diagnostic); use permission-bit heuristic +
Stat-based ownership check and label it WARN (heuristic) when
uncertain); missing → WARN "will be created on first --save".
- All checks run with a 5s per-check timeout (hung LLM endpoint must not
hang doctor).
- Global flags (DESIGN §4):
--config and --verbose are honored;
--date and --lang parse successfully but are ignored by doctor
(doctor output is English-only and date-independent; --verbose prints
a one-line note that they were ignored). --verbose adds detail lines
(resolved paths, counts) — still stdout.
- Testability: checks are small funcs returning
{Status, Name, Detail, Fix string}; the runner formats. Fake
providers/clients via the same seams as earlier issues (fixture HOME +
httptest fake for LLM).
Acceptance Criteria
Validation
go test -race ./internal/cli/ -run Doctor -v in PR + healthy-case golden
attached. Manual run on the reference machine encouraged; paste is fine
(doctor output contains no personal content beyond paths — verify before
pasting).
Dependencies
07, 09, 11, 14, 15, 23.
Non-goals
Auto-fixing anything (doctor never writes), model pulling, benchmark/latency
measurement.
Design References
docs/DESIGN.md §4, §13, §15 (U2/U3), research docs (fix texts)
Source of truth: docs/issues/28-cmd-doctor.md (PR #1, branch docs/v1-design). If this issue and the repo docs disagree, the docs win. Execution order and dependencies: docs/ISSUE_PLAN.md (this is issue 28 of 33).
Title
worklog doctor: environment and connectivity diagnosticsSummary
Implement
worklog doctor: a check matrix over config, each enabledsource, the LLM endpoint, and the output directory, printing PASS/WARN/FAIL
lines with actionable fixes (notably the zsh EXTENDED_HISTORY guidance) and
exiting non-zero only on FAIL.
Context
The reference machine demonstrates the two onboarding cliffs doctor exists
for: zsh history without timestamps, and an LLM endpoint/model that isn't
running/pulled yet. Doctor turns both from silent degradation into guided
setup.
Scope
internal/cli/doctor.go(replace stub) + tests.Detailed Requirements
harness errors): one line per check:
PASS config: loaded (<path>)/WARN zsh: …/FAIL llm: …,followed by an indented
fix:line for every WARN/FAIL with a concretecommand or config edit. Final summary line
doctor: N pass, M warn, K fail. Exit 1 iff K>0, else 0.checks):
config: load + validate (validation errors → FAIL each, fixreferences the key). Unknown-key warnings → WARN each.
zsh(when enabled): file exists+readable (missing → WARN, fix:check path/HISTFILE); parse first 2000 entries: ExtendedCount==0 →
WARN with the exact fix
echo 'setopt EXTENDED_HISTORY INC_APPEND_HISTORY' >> ~/.zshrc(new entries only — note that old entries stay undated); else PASS
with entry count.
git: binary found (ErrGitNotFound→ FAIL, fix: install git);author identity resolvable (else WARN, fix: set
[sources.git] authorsorgit config --global user.email); each root exists(missing → WARN); repos discovered (count in PASS line; zero repos →
WARN).
claude-code/codex(when enabled): dir exists (missing → WARN,fix mentions the config key), count of session files with mtime within
7 days (0 → WARN "no recent sessions").
llm(whenllm.enabled): endpoint parses + static loopback IP check(violation → FAIL with ADR-002 one-liner);
Models(ctx, 5s timeout)reachable (refused → FAIL, fix:
ollama serveor start the server;ErrNonLoopbackBlocked→ FAIL with the blocked host/IP); configuredmodel ∈ returned ids (empty model → FAIL listing first 5 available
ids as the fix; not-in-list → WARN listing near matches). When
llm.enabled=false: single linePASS llm: disabled.output(when[output].directoryset): exists → writable check viaos.Stat+unix.Access(W_OK)-equivalent (useos.CreateTemp? NO — doctor must not write (I3 applies to doctorstrictly: it's a diagnostic); use permission-bit heuristic +
Stat-based ownership check and label it
WARN (heuristic)whenuncertain); missing → WARN "will be created on first --save".
hang doctor).
--configand--verboseare honored;--dateand--langparse successfully but are ignored by doctor(doctor output is English-only and date-independent;
--verboseprintsa one-line note that they were ignored).
--verboseadds detail lines(resolved paths, counts) — still stdout.
{Status, Name, Detail, Fix string}; the runner formats. Fakeproviders/clients via the same seams as earlier issues (fixture HOME +
httptest fake for LLM).
Acceptance Criteria
failure (missing zsh file, simple-format history, git absent from
PATH via env manipulation, missing model, endpoint down, non-loopback
IP endpoint) produces its documented line+fix and correct exit code.
the output dir case).
< 10s (timeout proven).
test for the healthy case (doctor output is English-only per DESIGN
§4;
--langparses and is ignored).worklog doctor --date 2026-01-01 --lang enparses and behavesidentically to a plain run (ignored-flags contract).
Validation
go test -race ./internal/cli/ -run Doctor -vin PR + healthy-case goldenattached. Manual run on the reference machine encouraged; paste is fine
(doctor output contains no personal content beyond paths — verify before
pasting).
Dependencies
07, 09, 11, 14, 15, 23.
Non-goals
Auto-fixing anything (doctor never writes), model pulling, benchmark/latency
measurement.
Design References
docs/DESIGN.md§4, §13, §15 (U2/U3), research docs (fix texts)Source of truth:
docs/issues/28-cmd-doctor.md(PR #1, branchdocs/v1-design). If this issue and the repo docs disagree, the docs win. Execution order and dependencies:docs/ISSUE_PLAN.md(this is issue 28 of 33).