Doctor: add health-check contract and --lint validation#80055
Conversation
|
Codex review: needs changes before merge. Workflow note: Future ClawSweeper reviews update this same comment in place. How this review workflow works
Summary Reproducibility: yes. for the review finding: source inspection at PR head shows Real behavior proof Next step before merge Security Review findings
Review detailsBest possible solution: Land the doctor-owned lint and repair contract only after every lint check is non-mutating and the new health SDK boundary is explicitly approved. Do we have a high-confidence way to reproduce the issue? Yes for the review finding: source inspection at PR head shows Is this the best way to solve the issue? No for the current patch: the contract direction is plausible, but lint detection must reuse non-observing config state or pass Full review comments:
Overall correctness: patch is incorrect Acceptance criteria:
What I checked:
Likely related people:
Remaining risk / open question:
Codex review notes: model gpt-5.5, reasoning high; reviewed against fb53c2d61092. |
7a83aea to
cc848f4
Compare
2372d24 to
584507a
Compare
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
584507a to
e8b26f1
Compare
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
e8b26f1 to
5038cc8
Compare
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
5038cc8 to
f5f503c
Compare
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
f5f503c to
244d15f
Compare
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
244d15f to
1f2d5b3
Compare
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
|
Maintainer update on the lint direction: I am closing the alternate standalone lint-command draft (#80800) and keeping this PR as the active path. The current plan is to avoid creating a second health surface: To keep the follow-up reviewable, I split the larger conversion work into small area-specific branches. The first on-deck branch converts only read-only checks and adds the checked conversion plan: giodl73-repo#1. That branch is stacked on this PR for now and can be opened upstream once this base is accepted. |
…lidation # Conflicts: # docs/.generated/plugin-sdk-api-baseline.sha256 # src/config/io.ts
Update the read-only doctor health conversion branch after openclaw#80055 landed on main, keep the conversion inventory aligned with the new session snapshot contribution, and remove the brittle doctor-lint exact core-check count assertion. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(doctor): add --lint mode + structured HealthFinding shape
Adds the core machinery for `openclaw doctor --lint` per the
doctor-lint-and-oc-rules upstream proposal. PR-1 of the proposal:
no new top-level verb, no public plugin SDK; everything internal.
Files:
- src/flows/checks.ts ? HealthFinding / HealthCheck / HealthCheckContext
types. Findings carry severity per-finding; checks return
readonly HealthFinding[]. Mode tag (doctor/lint/fix) lets a check
distinguish the calling posture.
- src/flows/health-check-registry.ts ? module-level registry with
duplicate-id rejection + test reset helper.
- src/flows/doctor-lint-flow.ts ? runner over registered checks.
Catches throws into synthetic error findings (anchored at check id;
message scrubbed of control chars, capped at 256 bytes). Sorts
findings by severity desc, check id, path. Exports
exitCodeFromFindings (1 if any warning/error, 0 otherwise).
- src/flows/doctor-core-checks.ts ? 4 modern HealthChecks rewriting
logic from existing legacy run*Health functions:
core/doctor/gateway-config (warning)
core/doctor/command-owner (info)
core/doctor/workspace-status (info)
core/doctor/final-config-validation (error)
Each was audited safe per the proposal's adapter constraints
(no writes, no repair calls, no prompts, no probes incl. local-bind).
Legacy run*Health contributions in doctor-health-contributions.ts
are unchanged ? doctor mode (no --lint) still runs the existing 35.
- src/commands/doctor-lint.ts ? CLI dispatch for --lint. Reads config
snapshot, builds HealthCheckContext (mode: "lint"), runs the registry,
filters by --severity-min, emits human or JSON output, returns exit
code from unfiltered set so --severity-min hides info findings
without changing CI signal.
- src/cli/program/register.maintenance.ts ? adds --lint, --json,
--severity-min, --skip, --only flags to existing doctor command.
--lint branches to runDoctorLintCli; without --lint, doctor runs
unchanged.
LoC: 382 src across 6 files. Tests + doc + oc-path-side rule packs
follow as separate commits on this branch.
* fix: avoid string spread in doctor errors
* chore: refresh plugin SDK API baseline
* docs: clarify doctor lint usage
* feat(doctor): prepare repairs for dry-run reporting
* feat(doctor): add --lint mode + structured HealthFinding shape
Adds the core machinery for `openclaw doctor --lint` per the
doctor-lint-and-oc-rules upstream proposal. PR-1 of the proposal:
no new top-level verb, no public plugin SDK; everything internal.
Files:
- src/flows/checks.ts ? HealthFinding / HealthCheck / HealthCheckContext
types. Findings carry severity per-finding; checks return
readonly HealthFinding[]. Mode tag (doctor/lint/fix) lets a check
distinguish the calling posture.
- src/flows/health-check-registry.ts ? module-level registry with
duplicate-id rejection + test reset helper.
- src/flows/doctor-lint-flow.ts ? runner over registered checks.
Catches throws into synthetic error findings (anchored at check id;
message scrubbed of control chars, capped at 256 bytes). Sorts
findings by severity desc, check id, path. Exports
exitCodeFromFindings (1 if any warning/error, 0 otherwise).
- src/flows/doctor-core-checks.ts ? 4 modern HealthChecks rewriting
logic from existing legacy run*Health functions:
core/doctor/gateway-config (warning)
core/doctor/command-owner (info)
core/doctor/workspace-status (info)
core/doctor/final-config-validation (error)
Each was audited safe per the proposal's adapter constraints
(no writes, no repair calls, no prompts, no probes incl. local-bind).
Legacy run*Health contributions in doctor-health-contributions.ts
are unchanged ? doctor mode (no --lint) still runs the existing 35.
- src/commands/doctor-lint.ts ? CLI dispatch for --lint. Reads config
snapshot, builds HealthCheckContext (mode: "lint"), runs the registry,
filters by --severity-min, emits human or JSON output, returns exit
code from unfiltered set so --severity-min hides info findings
without changing CI signal.
- src/cli/program/register.maintenance.ts ? adds --lint, --json,
--severity-min, --skip, --only flags to existing doctor command.
--lint branches to runDoctorLintCli; without --lint, doctor runs
unchanged.
LoC: 382 src across 6 files. Tests + doc + oc-path-side rule packs
follow as separate commits on this branch.
* fix: avoid string spread in doctor errors
* chore: refresh plugin SDK API baseline
* docs: clarify doctor lint usage
* feat(doctor): prepare repairs for dry-run reporting
* feat(doctor): add --lint mode + structured HealthFinding shape
Adds the core machinery for `openclaw doctor --lint` per the
doctor-lint-and-oc-rules upstream proposal. PR-1 of the proposal:
no new top-level verb, no public plugin SDK; everything internal.
Files:
- src/flows/checks.ts ? HealthFinding / HealthCheck / HealthCheckContext
types. Findings carry severity per-finding; checks return
readonly HealthFinding[]. Mode tag (doctor/lint/fix) lets a check
distinguish the calling posture.
- src/flows/health-check-registry.ts ? module-level registry with
duplicate-id rejection + test reset helper.
- src/flows/doctor-lint-flow.ts ? runner over registered checks.
Catches throws into synthetic error findings (anchored at check id;
message scrubbed of control chars, capped at 256 bytes). Sorts
findings by severity desc, check id, path. Exports
exitCodeFromFindings (1 if any warning/error, 0 otherwise).
- src/flows/doctor-core-checks.ts ? 4 modern HealthChecks rewriting
logic from existing legacy run*Health functions:
core/doctor/gateway-config (warning)
core/doctor/command-owner (info)
core/doctor/workspace-status (info)
core/doctor/final-config-validation (error)
Each was audited safe per the proposal's adapter constraints
(no writes, no repair calls, no prompts, no probes incl. local-bind).
Legacy run*Health contributions in doctor-health-contributions.ts
are unchanged ? doctor mode (no --lint) still runs the existing 35.
- src/commands/doctor-lint.ts ? CLI dispatch for --lint. Reads config
snapshot, builds HealthCheckContext (mode: "lint"), runs the registry,
filters by --severity-min, emits human or JSON output, returns exit
code from unfiltered set so --severity-min hides info findings
without changing CI signal.
- src/cli/program/register.maintenance.ts ? adds --lint, --json,
--severity-min, --skip, --only flags to existing doctor command.
--lint branches to runDoctorLintCli; without --lint, doctor runs
unchanged.
LoC: 382 src across 6 files. Tests + doc + oc-path-side rule packs
follow as separate commits on this branch.
* fix: avoid string spread in doctor errors
* chore: refresh plugin SDK API baseline
* docs: clarify doctor lint usage
* feat(doctor): prepare repairs for dry-run reporting
* feat(doctor): add --lint mode + structured HealthFinding shape
Adds the core machinery for `openclaw doctor --lint` per the
doctor-lint-and-oc-rules upstream proposal. PR-1 of the proposal:
no new top-level verb, no public plugin SDK; everything internal.
Files:
- src/flows/checks.ts ? HealthFinding / HealthCheck / HealthCheckContext
types. Findings carry severity per-finding; checks return
readonly HealthFinding[]. Mode tag (doctor/lint/fix) lets a check
distinguish the calling posture.
- src/flows/health-check-registry.ts ? module-level registry with
duplicate-id rejection + test reset helper.
- src/flows/doctor-lint-flow.ts ? runner over registered checks.
Catches throws into synthetic error findings (anchored at check id;
message scrubbed of control chars, capped at 256 bytes). Sorts
findings by severity desc, check id, path. Exports
exitCodeFromFindings (1 if any warning/error, 0 otherwise).
- src/flows/doctor-core-checks.ts ? 4 modern HealthChecks rewriting
logic from existing legacy run*Health functions:
core/doctor/gateway-config (warning)
core/doctor/command-owner (info)
core/doctor/workspace-status (info)
core/doctor/final-config-validation (error)
Each was audited safe per the proposal's adapter constraints
(no writes, no repair calls, no prompts, no probes incl. local-bind).
Legacy run*Health contributions in doctor-health-contributions.ts
are unchanged ? doctor mode (no --lint) still runs the existing 35.
- src/commands/doctor-lint.ts ? CLI dispatch for --lint. Reads config
snapshot, builds HealthCheckContext (mode: "lint"), runs the registry,
filters by --severity-min, emits human or JSON output, returns exit
code from unfiltered set so --severity-min hides info findings
without changing CI signal.
- src/cli/program/register.maintenance.ts ? adds --lint, --json,
--severity-min, --skip, --only flags to existing doctor command.
--lint branches to runDoctorLintCli; without --lint, doctor runs
unchanged.
LoC: 382 src across 6 files. Tests + doc + oc-path-side rule packs
follow as separate commits on this branch.
* fix: avoid string spread in doctor errors
* chore: refresh plugin SDK API baseline
* docs: clarify doctor lint usage
* feat(doctor): prepare repairs for dry-run reporting
* feat(doctor): add --lint mode + structured HealthFinding shape
Adds the core machinery for `openclaw doctor --lint` per the
doctor-lint-and-oc-rules upstream proposal. PR-1 of the proposal:
no new top-level verb, no public plugin SDK; everything internal.
Files:
- src/flows/checks.ts ? HealthFinding / HealthCheck / HealthCheckContext
types. Findings carry severity per-finding; checks return
readonly HealthFinding[]. Mode tag (doctor/lint/fix) lets a check
distinguish the calling posture.
- src/flows/health-check-registry.ts ? module-level registry with
duplicate-id rejection + test reset helper.
- src/flows/doctor-lint-flow.ts ? runner over registered checks.
Catches throws into synthetic error findings (anchored at check id;
message scrubbed of control chars, capped at 256 bytes). Sorts
findings by severity desc, check id, path. Exports
exitCodeFromFindings (1 if any warning/error, 0 otherwise).
- src/flows/doctor-core-checks.ts ? 4 modern HealthChecks rewriting
logic from existing legacy run*Health functions:
core/doctor/gateway-config (warning)
core/doctor/command-owner (info)
core/doctor/workspace-status (info)
core/doctor/final-config-validation (error)
Each was audited safe per the proposal's adapter constraints
(no writes, no repair calls, no prompts, no probes incl. local-bind).
Legacy run*Health contributions in doctor-health-contributions.ts
are unchanged ? doctor mode (no --lint) still runs the existing 35.
- src/commands/doctor-lint.ts ? CLI dispatch for --lint. Reads config
snapshot, builds HealthCheckContext (mode: "lint"), runs the registry,
filters by --severity-min, emits human or JSON output, returns exit
code from unfiltered set so --severity-min hides info findings
without changing CI signal.
- src/cli/program/register.maintenance.ts ? adds --lint, --json,
--severity-min, --skip, --only flags to existing doctor command.
--lint branches to runDoctorLintCli; without --lint, doctor runs
unchanged.
LoC: 382 src across 6 files. Tests + doc + oc-path-side rule packs
follow as separate commits on this branch.
* fix: avoid string spread in doctor errors
* chore: refresh plugin SDK API baseline
* docs: clarify doctor lint usage
* feat(doctor): prepare repairs for dry-run reporting
* feat(doctor): add --lint mode + structured HealthFinding shape
Adds the core machinery for `openclaw doctor --lint` per the
doctor-lint-and-oc-rules upstream proposal. PR-1 of the proposal:
no new top-level verb, no public plugin SDK; everything internal.
Files:
- src/flows/checks.ts ? HealthFinding / HealthCheck / HealthCheckContext
types. Findings carry severity per-finding; checks return
readonly HealthFinding[]. Mode tag (doctor/lint/fix) lets a check
distinguish the calling posture.
- src/flows/health-check-registry.ts ? module-level registry with
duplicate-id rejection + test reset helper.
- src/flows/doctor-lint-flow.ts ? runner over registered checks.
Catches throws into synthetic error findings (anchored at check id;
message scrubbed of control chars, capped at 256 bytes). Sorts
findings by severity desc, check id, path. Exports
exitCodeFromFindings (1 if any warning/error, 0 otherwise).
- src/flows/doctor-core-checks.ts ? 4 modern HealthChecks rewriting
logic from existing legacy run*Health functions:
core/doctor/gateway-config (warning)
core/doctor/command-owner (info)
core/doctor/workspace-status (info)
core/doctor/final-config-validation (error)
Each was audited safe per the proposal's adapter constraints
(no writes, no repair calls, no prompts, no probes incl. local-bind).
Legacy run*Health contributions in doctor-health-contributions.ts
are unchanged ? doctor mode (no --lint) still runs the existing 35.
- src/commands/doctor-lint.ts ? CLI dispatch for --lint. Reads config
snapshot, builds HealthCheckContext (mode: "lint"), runs the registry,
filters by --severity-min, emits human or JSON output, returns exit
code from unfiltered set so --severity-min hides info findings
without changing CI signal.
- src/cli/program/register.maintenance.ts ? adds --lint, --json,
--severity-min, --skip, --only flags to existing doctor command.
--lint branches to runDoctorLintCli; without --lint, doctor runs
unchanged.
LoC: 382 src across 6 files. Tests + doc + oc-path-side rule packs
follow as separate commits on this branch.
* fix: avoid string spread in doctor errors
* chore: refresh plugin SDK API baseline
* docs: clarify doctor lint usage
* feat(doctor): prepare repairs for dry-run reporting
* feat(doctor): add --lint mode + structured HealthFinding shape
Adds the core machinery for `openclaw doctor --lint` per the
doctor-lint-and-oc-rules upstream proposal. PR-1 of the proposal:
no new top-level verb, no public plugin SDK; everything internal.
Files:
- src/flows/checks.ts ? HealthFinding / HealthCheck / HealthCheckContext
types. Findings carry severity per-finding; checks return
readonly HealthFinding[]. Mode tag (doctor/lint/fix) lets a check
distinguish the calling posture.
- src/flows/health-check-registry.ts ? module-level registry with
duplicate-id rejection + test reset helper.
- src/flows/doctor-lint-flow.ts ? runner over registered checks.
Catches throws into synthetic error findings (anchored at check id;
message scrubbed of control chars, capped at 256 bytes). Sorts
findings by severity desc, check id, path. Exports
exitCodeFromFindings (1 if any warning/error, 0 otherwise).
- src/flows/doctor-core-checks.ts ? 4 modern HealthChecks rewriting
logic from existing legacy run*Health functions:
core/doctor/gateway-config (warning)
core/doctor/command-owner (info)
core/doctor/workspace-status (info)
core/doctor/final-config-validation (error)
Each was audited safe per the proposal's adapter constraints
(no writes, no repair calls, no prompts, no probes incl. local-bind).
Legacy run*Health contributions in doctor-health-contributions.ts
are unchanged ? doctor mode (no --lint) still runs the existing 35.
- src/commands/doctor-lint.ts ? CLI dispatch for --lint. Reads config
snapshot, builds HealthCheckContext (mode: "lint"), runs the registry,
filters by --severity-min, emits human or JSON output, returns exit
code from unfiltered set so --severity-min hides info findings
without changing CI signal.
- src/cli/program/register.maintenance.ts ? adds --lint, --json,
--severity-min, --skip, --only flags to existing doctor command.
--lint branches to runDoctorLintCli; without --lint, doctor runs
unchanged.
LoC: 382 src across 6 files. Tests + doc + oc-path-side rule packs
follow as separate commits on this branch.
* fix: avoid string spread in doctor errors
* chore: refresh plugin SDK API baseline
* docs: clarify doctor lint usage
* feat(doctor): prepare repairs for dry-run reporting
* feat(doctor): add --lint mode + structured HealthFinding shape
Adds the core machinery for `openclaw doctor --lint` per the
doctor-lint-and-oc-rules upstream proposal. PR-1 of the proposal:
no new top-level verb, no public plugin SDK; everything internal.
Files:
- src/flows/checks.ts ? HealthFinding / HealthCheck / HealthCheckContext
types. Findings carry severity per-finding; checks return
readonly HealthFinding[]. Mode tag (doctor/lint/fix) lets a check
distinguish the calling posture.
- src/flows/health-check-registry.ts ? module-level registry with
duplicate-id rejection + test reset helper.
- src/flows/doctor-lint-flow.ts ? runner over registered checks.
Catches throws into synthetic error findings (anchored at check id;
message scrubbed of control chars, capped at 256 bytes). Sorts
findings by severity desc, check id, path. Exports
exitCodeFromFindings (1 if any warning/error, 0 otherwise).
- src/flows/doctor-core-checks.ts ? 4 modern HealthChecks rewriting
logic from existing legacy run*Health functions:
core/doctor/gateway-config (warning)
core/doctor/command-owner (info)
core/doctor/workspace-status (info)
core/doctor/final-config-validation (error)
Each was audited safe per the proposal's adapter constraints
(no writes, no repair calls, no prompts, no probes incl. local-bind).
Legacy run*Health contributions in doctor-health-contributions.ts
are unchanged ? doctor mode (no --lint) still runs the existing 35.
- src/commands/doctor-lint.ts ? CLI dispatch for --lint. Reads config
snapshot, builds HealthCheckContext (mode: "lint"), runs the registry,
filters by --severity-min, emits human or JSON output, returns exit
code from unfiltered set so --severity-min hides info findings
without changing CI signal.
- src/cli/program/register.maintenance.ts ? adds --lint, --json,
--severity-min, --skip, --only flags to existing doctor command.
--lint branches to runDoctorLintCli; without --lint, doctor runs
unchanged.
LoC: 382 src across 6 files. Tests + doc + oc-path-side rule packs
follow as separate commits on this branch.
* fix: avoid string spread in doctor errors
* chore: refresh plugin SDK API baseline
* docs: clarify doctor lint usage
* feat(doctor): prepare repairs for dry-run reporting
* feat(doctor): add --lint mode + structured HealthFinding shape
Adds the core machinery for `openclaw doctor --lint` per the
doctor-lint-and-oc-rules upstream proposal. PR-1 of the proposal:
no new top-level verb, no public plugin SDK; everything internal.
Files:
- src/flows/checks.ts ? HealthFinding / HealthCheck / HealthCheckContext
types. Findings carry severity per-finding; checks return
readonly HealthFinding[]. Mode tag (doctor/lint/fix) lets a check
distinguish the calling posture.
- src/flows/health-check-registry.ts ? module-level registry with
duplicate-id rejection + test reset helper.
- src/flows/doctor-lint-flow.ts ? runner over registered checks.
Catches throws into synthetic error findings (anchored at check id;
message scrubbed of control chars, capped at 256 bytes). Sorts
findings by severity desc, check id, path. Exports
exitCodeFromFindings (1 if any warning/error, 0 otherwise).
- src/flows/doctor-core-checks.ts ? 4 modern HealthChecks rewriting
logic from existing legacy run*Health functions:
core/doctor/gateway-config (warning)
core/doctor/command-owner (info)
core/doctor/workspace-status (info)
core/doctor/final-config-validation (error)
Each was audited safe per the proposal's adapter constraints
(no writes, no repair calls, no prompts, no probes incl. local-bind).
Legacy run*Health contributions in doctor-health-contributions.ts
are unchanged ? doctor mode (no --lint) still runs the existing 35.
- src/commands/doctor-lint.ts ? CLI dispatch for --lint. Reads config
snapshot, builds HealthCheckContext (mode: "lint"), runs the registry,
filters by --severity-min, emits human or JSON output, returns exit
code from unfiltered set so --severity-min hides info findings
without changing CI signal.
- src/cli/program/register.maintenance.ts ? adds --lint, --json,
--severity-min, --skip, --only flags to existing doctor command.
--lint branches to runDoctorLintCli; without --lint, doctor runs
unchanged.
LoC: 382 src across 6 files. Tests + doc + oc-path-side rule packs
follow as separate commits on this branch.
* fix: avoid string spread in doctor errors
* chore: refresh plugin SDK API baseline
* docs: clarify doctor lint usage
* feat(doctor): prepare repairs for dry-run reporting
* feat(doctor): add --lint mode + structured HealthFinding shape
Adds the core machinery for `openclaw doctor --lint` per the
doctor-lint-and-oc-rules upstream proposal. PR-1 of the proposal:
no new top-level verb, no public plugin SDK; everything internal.
Files:
- src/flows/checks.ts ? HealthFinding / HealthCheck / HealthCheckContext
types. Findings carry severity per-finding; checks return
readonly HealthFinding[]. Mode tag (doctor/lint/fix) lets a check
distinguish the calling posture.
- src/flows/health-check-registry.ts ? module-level registry with
duplicate-id rejection + test reset helper.
- src/flows/doctor-lint-flow.ts ? runner over registered checks.
Catches throws into synthetic error findings (anchored at check id;
message scrubbed of control chars, capped at 256 bytes). Sorts
findings by severity desc, check id, path. Exports
exitCodeFromFindings (1 if any warning/error, 0 otherwise).
- src/flows/doctor-core-checks.ts ? 4 modern HealthChecks rewriting
logic from existing legacy run*Health functions:
core/doctor/gateway-config (warning)
core/doctor/command-owner (info)
core/doctor/workspace-status (info)
core/doctor/final-config-validation (error)
Each was audited safe per the proposal's adapter constraints
(no writes, no repair calls, no prompts, no probes incl. local-bind).
Legacy run*Health contributions in doctor-health-contributions.ts
are unchanged ? doctor mode (no --lint) still runs the existing 35.
- src/commands/doctor-lint.ts ? CLI dispatch for --lint. Reads config
snapshot, builds HealthCheckContext (mode: "lint"), runs the registry,
filters by --severity-min, emits human or JSON output, returns exit
code from unfiltered set so --severity-min hides info findings
without changing CI signal.
- src/cli/program/register.maintenance.ts ? adds --lint, --json,
--severity-min, --skip, --only flags to existing doctor command.
--lint branches to runDoctorLintCli; without --lint, doctor runs
unchanged.
LoC: 382 src across 6 files. Tests + doc + oc-path-side rule packs
follow as separate commits on this branch.
* fix: avoid string spread in doctor errors
* chore: refresh plugin SDK API baseline
* docs: clarify doctor lint usage
* feat(doctor): prepare repairs for dry-run reporting
* feat(doctor): add --lint mode + structured HealthFinding shape
Adds the core machinery for `openclaw doctor --lint` per the
doctor-lint-and-oc-rules upstream proposal. PR-1 of the proposal:
no new top-level verb, no public plugin SDK; everything internal.
Files:
- src/flows/checks.ts ? HealthFinding / HealthCheck / HealthCheckContext
types. Findings carry severity per-finding; checks return
readonly HealthFinding[]. Mode tag (doctor/lint/fix) lets a check
distinguish the calling posture.
- src/flows/health-check-registry.ts ? module-level registry with
duplicate-id rejection + test reset helper.
- src/flows/doctor-lint-flow.ts ? runner over registered checks.
Catches throws into synthetic error findings (anchored at check id;
message scrubbed of control chars, capped at 256 bytes). Sorts
findings by severity desc, check id, path. Exports
exitCodeFromFindings (1 if any warning/error, 0 otherwise).
- src/flows/doctor-core-checks.ts ? 4 modern HealthChecks rewriting
logic from existing legacy run*Health functions:
core/doctor/gateway-config (warning)
core/doctor/command-owner (info)
core/doctor/workspace-status (info)
core/doctor/final-config-validation (error)
Each was audited safe per the proposal's adapter constraints
(no writes, no repair calls, no prompts, no probes incl. local-bind).
Legacy run*Health contributions in doctor-health-contributions.ts
are unchanged ? doctor mode (no --lint) still runs the existing 35.
- src/commands/doctor-lint.ts ? CLI dispatch for --lint. Reads config
snapshot, builds HealthCheckContext (mode: "lint"), runs the registry,
filters by --severity-min, emits human or JSON output, returns exit
code from unfiltered set so --severity-min hides info findings
without changing CI signal.
- src/cli/program/register.maintenance.ts ? adds --lint, --json,
--severity-min, --skip, --only flags to existing doctor command.
--lint branches to runDoctorLintCli; without --lint, doctor runs
unchanged.
LoC: 382 src across 6 files. Tests + doc + oc-path-side rule packs
follow as separate commits on this branch.
* fix: avoid string spread in doctor errors
* chore: refresh plugin SDK API baseline
* docs: clarify doctor lint usage
* feat(doctor): prepare repairs for dry-run reporting
* feat(doctor): add --lint mode + structured HealthFinding shape
Adds the core machinery for `openclaw doctor --lint` per the
doctor-lint-and-oc-rules upstream proposal. PR-1 of the proposal:
no new top-level verb, no public plugin SDK; everything internal.
Files:
- src/flows/checks.ts ? HealthFinding / HealthCheck / HealthCheckContext
types. Findings carry severity per-finding; checks return
readonly HealthFinding[]. Mode tag (doctor/lint/fix) lets a check
distinguish the calling posture.
- src/flows/health-check-registry.ts ? module-level registry with
duplicate-id rejection + test reset helper.
- src/flows/doctor-lint-flow.ts ? runner over registered checks.
Catches throws into synthetic error findings (anchored at check id;
message scrubbed of control chars, capped at 256 bytes). Sorts
findings by severity desc, check id, path. Exports
exitCodeFromFindings (1 if any warning/error, 0 otherwise).
- src/flows/doctor-core-checks.ts ? 4 modern HealthChecks rewriting
logic from existing legacy run*Health functions:
core/doctor/gateway-config (warning)
core/doctor/command-owner (info)
core/doctor/workspace-status (info)
core/doctor/final-config-validation (error)
Each was audited safe per the proposal's adapter constraints
(no writes, no repair calls, no prompts, no probes incl. local-bind).
Legacy run*Health contributions in doctor-health-contributions.ts
are unchanged ? doctor mode (no --lint) still runs the existing 35.
- src/commands/doctor-lint.ts ? CLI dispatch for --lint. Reads config
snapshot, builds HealthCheckContext (mode: "lint"), runs the registry,
filters by --severity-min, emits human or JSON output, returns exit
code from unfiltered set so --severity-min hides info findings
without changing CI signal.
- src/cli/program/register.maintenance.ts ? adds --lint, --json,
--severity-min, --skip, --only flags to existing doctor command.
--lint branches to runDoctorLintCli; without --lint, doctor runs
unchanged.
LoC: 382 src across 6 files. Tests + doc + oc-path-side rule packs
follow as separate commits on this branch.
* fix: avoid string spread in doctor errors
* chore: refresh plugin SDK API baseline
* docs: clarify doctor lint usage
* feat(doctor): prepare repairs for dry-run reporting
Branch:
doctor-detection-validationBase:
upstream/mainHead:
a0eaef75ccSummary
Today doctor health work is mostly procedural: a contribution runs, reports
status, and may repair inside the same
run(ctx)flow. This PR introduces thenext doctor health-check contract, following the existing config-change pattern
of separating detection from application.
detect()powers the new read-onlyopenclaw doctor --lintmode and alsopowers post-fix validation after a modern
repair()runs.--fixremains theoperator repair path;
--lintis the structured detection/reporting path.repair()now also carries the reporting shape needed for dry-run/diff follow-up: repair contexts can receivedryRun/diff, and repair results can report concretediffsplus non-fileeffects. Detection stays read-only; repair owns mutation planning and preview reporting.Motivation
Doctor already owns workspace health, but most checks today are shaped around a
single procedural
run(ctx)path. That works for guided repair, but it blursthree separate operator needs:
doctor --fixpathThe result is that users and automation often have to treat
doctor --fixasboth diagnosis and repair. That is awkward for CI, risky for preflight checks,
and hard to reason about when a repair partially succeeds or needs manual
follow-up.
This PR keeps doctor as the owner of health checks and upgrades the contract
toward a detect/apply/validate flow.
doctor --lintbecomes the structuredreporting mode, while
detect()givesdoctor --fixthe same logic forpost-repair validation.
Related issue signal:
doctor dry-run/diff mode because
doctor --fixapplies changes without astructured preview.
doctor --fixreports repairs, then fails validation and the same stale findings return on
the next run.
doctor --fixpartially runs against a live gateway and exits abruptly, leaving the user
unsure what was applied.
already treats lint as a gate, but operators need clearer machine-readable
diagnostics when a gate fails.
This PR does not claim to close those issues. It adds the shared doctor
detection, repair, and validation contract that current and follow-up checks can
use.
Maintainer Input Requested
Should lint live inside doctor, or be a separate
openclaw lintcommand?This PR treats linting as doctor's read-only health detection mode rather
than a separate extension or command. The intent is to avoid creating a
second health surface that would need its own check registry, docs, severity
rules, and repair story.
I also prepared an alternate draft that exposes the same diagnostic contract
as top-level
openclaw lintwhile keepingdoctorfocused on guided repair:Lint: add standalone diagnostic command #80800
Is
detect()/repair()the right health-check contract?Today many doctor contributions combine detection, reporting, and repair
inside
run(ctx). This PR keeps that legacy path, but follows the existingconfig change model by separating the needed change from the code that
applies it. The doctor driver can then report, apply, and validate the result
through:
detect()powersdoctor --lintand post-repair validation.repair()letsa modern check report whether it repaired, skipped, or failed the requested
findings and why.
Is this the right migration path for existing doctor rules?
The PR migrates a small set of core checks first and leaves existing doctor
checks on the legacy path. If accepted, remaining hard-coded doctor rules can
migrate incrementally: move diagnostics into
detect(ctx)first, addrepair(ctx, findings)only when repair behavior is understood and covered,and keep legacy
run(ctx)for checks that still need prompts, servicechanges, backups, or platform-specific flows.
Is
src/flowsthe right home, or should health move toward a capabilityhome?
This PR keeps the new doctor health code near the existing doctor flow
machinery to minimize movement during the contract change. If maintainers
expect health checks to become part of the broader capability ownership
model, the same contract could move under a clearer
healthor capabilityhome as the remaining rules migrate.
What Changed
HealthFinding,HealthCheck,HealthCheckContext,HealthCheckScope, andHealthRepairResult.detect()/repair()checks, alongside the existing legacy doctor contribution flow.openclaw/plugin-sdk/healthforbundled health-check consumers. Test-only registry reset helpers remain
internal.
doctor --lintwith--json,--severity-min,--skip, and--only.doctor --lintplugin-free at CLI startup. This preserves structuredconfig/error reporting even when plugin loading would otherwise fail.
core/doctor/gateway-configcore/doctor/command-ownercore/doctor/workspace-statuscore/doctor/skills-readinesscore/doctor/final-config-validationrepair()support for modern checks indoctor --fix.repaired,skipped,failed) so a checkcan tell the driver it did not perform the requested repair and why.
dryRun/diffrepair context flags, structureddiffsfor config/file edits, andeffectsfor service, process, package,state, or other side effects.
repair(), doctor runsdetect(ctx, scope)again and reports remaining findings as repair warnings.Behavior
Example lint output:
Exit codes:
0: no findings at or above the selected threshold1: one or more findings met the selected threshold2: command/runtime failure before findings could be emittedRepair validation behavior:
repair(ctx, findings)receives findings from its own check.repair()reportsrepaired,skipped, orfailedwith an optional reason.repairedresult, doctor re-runs scopeddetect(ctx, scope)forvalidation.
pass is the proof.
Review Notes
This is an internal contract, not a public repair API. Existing doctor repairs
still cover prompts, backups, service changes, plugin installs, and
platform-specific flows through the legacy contribution pipeline.
This PR prepares the modern repair contract for future
doctor --fix --dry-run/ diff output, but does not expose a whole-doctor dry-run flag yet. Legacy repairs still need to migrate or explicitly classify their side effects before that CLI can be honest across the full doctor surface.Structured repairs run inside the existing doctor contribution sequence before
legacy repair steps, so rules can migrate gradually. Checks that are not ready
for
detect()/repair()keep usingrun(ctx).Real behavior proof
Behavior addressed:
openclaw doctor --lintexposes read-only doctor health detection backed by the newdetect()/repair()contract, and the repair contract now carries dry-run/diff reporting metadata without moving mutation planning intodetect().Real environment tested: WSL Ubuntu-24.04 source checkout on branch
doctor-detection-validation, PR heada0eaef75cc.Exact steps or command run after this patch:
Evidence after fix: Real CLI output from the WSL checkout:
The focused repair-flow test also now proves
runDoctorHealthRepairs(..., { dryRun: true, diff: true })passesdryRun/diffintorepair(), collects returneddiffsandeffects, does not apply returned config, and skips post-repair validation during preview mode.Observed result after fix: The lint command ran a real registered core
detect()check and emitted structured JSON findings. The repair contract now reports preview metadata throughrepair()results, whiledetect()remains read-only diagnosis.What was not tested: No external channel or gateway service was started. This proof covers the doctor lint/health-check contract, core detection/reporting path, and structured repair dry-run plumbing.
Testing
Passed focused doctor lint/repair/core-check tests, CLI registration coverage,
plugin SDK API checks, docs checks, and targeted oxlint on the changed doctor
flow files.
Follow-Up
If this model is accepted, migrate remaining hard-coded doctor rules
incrementally: move diagnostics into
detect(ctx)first, then addrepair(ctx, findings)only when the repair behavior is understood and covered.The stacked policy drafts show bundled-extension consumers of this model. The
channels draft is the smaller first example: policy contributes doctor findings
through a narrow bundled health registration path, uses
doctor --lintforworkspace validation, and participates in
doctor --fixwith scopedpost-repair validation.
Related stacked drafts:
Policy: add runtime audit metadata and attestation enforcement #81104