diff --git a/docs/cli/doctor.md b/docs/cli/doctor.md index aeabd4f33929..33106b99f744 100644 --- a/docs/cli/doctor.md +++ b/docs/cli/doctor.md @@ -40,6 +40,7 @@ openclaw doctor openclaw doctor --lint openclaw doctor --lint --json openclaw doctor --lint --severity-min warning +openclaw doctor --lint --all openclaw doctor --lint --allow-exec openclaw doctor --deep openclaw doctor --fix @@ -73,6 +74,7 @@ The targeted Discord capabilities probe reports the bot's effective channel perm - `--post-upgrade`: run post-upgrade plugin compatibility probes; emits findings to stdout; exits with code 1 if any error-level findings are present - `--json`: with `--lint`, emit JSON findings instead of human output; with `--post-upgrade`, emit a machine-readable JSON envelope (`{ probesRun, findings }`) - `--severity-min `: with `--lint`, drop findings below `info`, `warning`, or `error` +- `--all`: with `--lint`, run all registered checks, including opt-in checks excluded from the default automation set - `--skip `: with `--lint`, skip a check id; repeat to skip more than one - `--only `: with `--lint`, run only a check id; repeat to run a small selected set @@ -82,13 +84,14 @@ The targeted Discord capabilities probe reports the bot's effective channel perm It uses the structured health-check path, does not prompt, and does not repair or rewrite config/state. Use it in CI, preflight scripts, and review workflows when you want machine-readable findings instead of guided repair prompts. -Lint-output options such as `--json`, `--severity-min`, `--only`, and `--skip` +Lint-output options such as `--json`, `--severity-min`, `--all`, `--only`, and `--skip` are only accepted with `--lint`. ```bash openclaw doctor --lint openclaw doctor --lint --severity-min warning openclaw doctor --lint --json +openclaw doctor --lint --all openclaw doctor --lint --allow-exec openclaw doctor --lint --only core/doctor/gateway-config --json ``` @@ -130,6 +133,13 @@ Exit behavior: example, `openclaw doctor --lint --severity-min error` can print no findings and exit `0` even when lower-severity `info` or `warning` findings exist. +`--all` controls which checks are selected before severity filtering. The +default lint run is the stable automation gate and excludes checks that are +intentionally opt-in because they are deep, historical, or more likely to +surface repairable legacy residue. Use `--all` when you want the complete lint +inventory without listing each check id. `--only ` remains the most precise +selector and can run any registered check by id. + ## Structured Health Checks Modern doctor checks use a small structured contract: @@ -186,6 +196,7 @@ Use `--only` and `--skip` when a workflow wants a focused gate: ```bash openclaw doctor --lint --only core/doctor/gateway-config --json openclaw doctor --lint --skip core/doctor/skills-readiness +openclaw doctor --lint --all --skip core/doctor/session-locks ``` `--only` and `--skip` accept full check ids and may be repeated. If an `--only` diff --git a/docs/gateway/doctor.md b/docs/gateway/doctor.md index 5b90a631e926..0e82e469a850 100644 --- a/docs/gateway/doctor.md +++ b/docs/gateway/doctor.md @@ -104,6 +104,7 @@ Examples: openclaw doctor --lint openclaw doctor --lint --severity-min warning openclaw doctor --lint --json +openclaw doctor --lint --all openclaw doctor --lint --only core/doctor/gateway-config --json ``` @@ -111,7 +112,7 @@ JSON output includes: - `ok`: whether any visible finding met the selected severity threshold - `checksRun`: number of health checks executed -- `checksSkipped`: checks skipped by `--only` or `--skip` +- `checksSkipped`: checks skipped by the selected profile, `--only`, or `--skip` - `findings`: structured diagnostics with `checkId`, `severity`, `message`, and optional `path`, `line`, `column`, `ocPath`, and `fixHint` @@ -122,11 +123,13 @@ Exit codes: - `2`: command/runtime failure before lint findings could be emitted Use `--severity-min info|warning|error` to control both what is printed and what -causes a non-zero lint exit. Use `--only ` for narrow preflight gates and +causes a non-zero lint exit. Use `--all` to run the complete lint inventory, +including deeper opt-in checks excluded from the default automation set. Use `--only ` for narrow preflight gates and `--skip ` to temporarily exclude a noisy check while keeping the rest of the lint run active. -Lint-output options such as `--json`, `--severity-min`, `--only`, and `--skip` -must be paired with `--lint`; regular doctor and repair runs reject them. +Lint-output options such as `--json`, `--severity-min`, `--all`, `--only`, and +`--skip` must be paired with `--lint`; regular doctor and repair runs reject +them. ## What it does (summary) diff --git a/src/cli/program/register.maintenance.test.ts b/src/cli/program/register.maintenance.test.ts index 246d0918cb2f..e905a3c2391a 100644 --- a/src/cli/program/register.maintenance.test.ts +++ b/src/cli/program/register.maintenance.test.ts @@ -112,6 +112,7 @@ describe("registerMaintenanceCommands doctor action", () => { "--json", "--severity-min", "error", + "--all", "--skip", "a", "--only", @@ -123,6 +124,7 @@ describe("registerMaintenanceCommands doctor action", () => { expect(runDoctorLintCli).toHaveBeenCalledWith(runtime, { json: true, severityMin: "error", + includeAllChecks: true, skipIds: ["a"], onlyIds: ["b"], allowExec: true, @@ -141,6 +143,17 @@ describe("registerMaintenanceCommands doctor action", () => { expect(runtime.exit).toHaveBeenCalledWith(2); }); + it("rejects --all outside doctor lint mode", async () => { + await runMaintenanceCli(["doctor", "--all"]); + + expect(doctorCommand).not.toHaveBeenCalled(); + expect(runDoctorLintCli).not.toHaveBeenCalled(); + expect(runtime.error).toHaveBeenCalledWith( + "doctor lint options require --lint. Use `openclaw doctor --lint ...`.", + ); + expect(runtime.exit).toHaveBeenCalledWith(2); + }); + it("exits with code 2 when doctor lint mode fails before findings are emitted", async () => { runDoctorLintCli.mockRejectedValue(new Error("lint failed")); diff --git a/src/cli/program/register.maintenance.ts b/src/cli/program/register.maintenance.ts index ed7fa200ff06..83090498c2ee 100644 --- a/src/cli/program/register.maintenance.ts +++ b/src/cli/program/register.maintenance.ts @@ -39,6 +39,7 @@ export function registerMaintenanceCommands(program: Command) { "--severity-min ", "With --lint: drop findings below this severity (info|warning|error)", ) + .option("--all", "With --lint: run all registered checks, including opt-in checks", false) .option( "--skip ", "With --lint: skip a specific check id (repeatable)", @@ -60,6 +61,7 @@ export function registerMaintenanceCommands(program: Command) { const exitCode = await runDoctorLintCli(defaultRuntime, { json: Boolean(opts.json), severityMin: typeof opts.severityMin === "string" ? opts.severityMin : undefined, + includeAllChecks: Boolean(opts.all), skipIds: Array.isArray(opts.skip) ? opts.skip : [], onlyIds: Array.isArray(opts.only) ? opts.only : [], allowExec: Boolean(opts.allowExec), @@ -180,12 +182,14 @@ function hasLintOnlyDoctorOptions(opts: { readonly json?: boolean; readonly postUpgrade?: boolean; readonly severityMin?: unknown; + readonly all?: boolean; readonly skip?: unknown; readonly only?: unknown; }): boolean { return ( (opts.json === true && opts.postUpgrade !== true) || typeof opts.severityMin === "string" || + opts.all === true || (Array.isArray(opts.skip) && opts.skip.length > 0) || (Array.isArray(opts.only) && opts.only.length > 0) ); diff --git a/src/commands/doctor-lint.ts b/src/commands/doctor-lint.ts index b3de814da928..d11c0a91540d 100644 --- a/src/commands/doctor-lint.ts +++ b/src/commands/doctor-lint.ts @@ -26,6 +26,7 @@ interface DoctorLintCliOptions { readonly onlyIds?: readonly string[]; readonly allowExec?: boolean; readonly deep?: boolean; + readonly includeAllChecks?: boolean; } function detectMode(opts: DoctorLintCliOptions): "human" | "json" { @@ -86,6 +87,7 @@ export async function runDoctorLintCli( const runOpts: DoctorLintRunOptions = { checks: [...coreChecks.map((check) => withCoreLintContext(check, coreCtx)), ...extensionChecks], + includeAllChecks: opts.includeAllChecks === true, ...(opts.skipIds && opts.skipIds.length > 0 ? { skipIds: opts.skipIds } : {}), ...(opts.onlyIds && opts.onlyIds.length > 0 ? { onlyIds: opts.onlyIds } : {}), }; diff --git a/src/flows/doctor-lint-flow.test.ts b/src/flows/doctor-lint-flow.test.ts index 117ea3936d79..deef65db2b83 100644 --- a/src/flows/doctor-lint-flow.test.ts +++ b/src/flows/doctor-lint-flow.test.ts @@ -69,6 +69,27 @@ describe("runDoctorLintChecks", () => { }); }); + it("runs default-disabled checks when all checks are requested", async () => { + const defaultDisabled = normalizeHealthCheck({ + ...check("targeted", async () => [ + { checkId: "targeted", severity: "warning" as const, message: "warn" }, + ]), + defaultEnabled: false, + }); + const defaultEnabled = check("regular", async () => []); + + const result = await runDoctorLintChecks(ctx, { + checks: [defaultDisabled, defaultEnabled], + includeAllChecks: true, + }); + + expect(result).toMatchObject({ + checksRun: 2, + checksSkipped: 0, + findings: [expect.objectContaining({ checkId: "targeted" })], + }); + }); + it("supports single-run checks in lint mode", async () => { const runnable: RunnableHealthCheck = { id: "run-check", diff --git a/src/flows/doctor-lint-flow.ts b/src/flows/doctor-lint-flow.ts index 178d02cb9a61..b7bf33bd95f0 100644 --- a/src/flows/doctor-lint-flow.ts +++ b/src/flows/doctor-lint-flow.ts @@ -15,6 +15,7 @@ export interface DoctorLintRunOptions { readonly checks?: readonly HealthCheck[]; readonly skipIds?: ReadonlySet | readonly string[]; readonly onlyIds?: ReadonlySet | readonly string[]; + readonly includeAllChecks?: boolean; } export interface DoctorLintRunResult { @@ -32,12 +33,13 @@ export async function runDoctorLintChecks( const skip = opts.skipIds instanceof Set ? opts.skipIds : new Set(opts.skipIds ?? []); const only = opts.onlyIds instanceof Set ? opts.onlyIds : new Set(opts.onlyIds ?? []); const allIds = new Set(all.map((check) => check.id)); + const includeDefaultDisabled = opts.includeAllChecks === true; const selected = all.filter((c) => { if (only.size > 0 && !only.has(c.id)) { return false; } - if (only.size === 0 && isDefaultDisabled(c)) { + if (only.size === 0 && !includeDefaultDisabled && isDefaultDisabled(c)) { return false; } if (skip.has(c.id)) {