Skip to content

Commit 560d59f

Browse files
committed
feat(doctor): add lint profiles
1 parent 46ded52 commit 560d59f

7 files changed

Lines changed: 62 additions & 9 deletions

File tree

docs/cli/doctor.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ openclaw doctor
4040
openclaw doctor --lint
4141
openclaw doctor --lint --json
4242
openclaw doctor --lint --severity-min warning
43+
openclaw doctor --lint --all
4344
openclaw doctor --lint --allow-exec
4445
openclaw doctor --deep
4546
openclaw doctor --fix
@@ -73,6 +74,7 @@ The targeted Discord capabilities probe reports the bot's effective channel perm
7374
- `--post-upgrade`: run post-upgrade plugin compatibility probes; emits findings to stdout; exits with code 1 if any error-level findings are present
7475
- `--json`: with `--lint`, emit JSON findings instead of human output; with `--post-upgrade`, emit a machine-readable JSON envelope (`{ probesRun, findings }`)
7576
- `--severity-min <level>`: with `--lint`, drop findings below `info`, `warning`, or `error`
77+
- `--all`: with `--lint`, run all registered checks, including opt-in checks excluded from the default automation set
7678
- `--skip <id>`: with `--lint`, skip a check id; repeat to skip more than one
7779
- `--only <id>`: with `--lint`, run only a check id; repeat to run a small selected set
7880

@@ -82,13 +84,14 @@ The targeted Discord capabilities probe reports the bot's effective channel perm
8284
It uses the structured health-check path, does not prompt, and does not repair
8385
or rewrite config/state. Use it in CI, preflight scripts, and review workflows
8486
when you want machine-readable findings instead of guided repair prompts.
85-
Lint-output options such as `--json`, `--severity-min`, `--only`, and `--skip`
87+
Lint-output options such as `--json`, `--severity-min`, `--all`, `--only`, and `--skip`
8688
are only accepted with `--lint`.
8789

8890
```bash
8991
openclaw doctor --lint
9092
openclaw doctor --lint --severity-min warning
9193
openclaw doctor --lint --json
94+
openclaw doctor --lint --all
9295
openclaw doctor --lint --allow-exec
9396
openclaw doctor --lint --only core/doctor/gateway-config --json
9497
```
@@ -130,6 +133,13 @@ Exit behavior:
130133
example, `openclaw doctor --lint --severity-min error` can print no findings and
131134
exit `0` even when lower-severity `info` or `warning` findings exist.
132135

136+
`--all` controls which checks are selected before severity filtering. The
137+
default lint run is the stable automation gate and excludes checks that are
138+
intentionally opt-in because they are deep, historical, or more likely to
139+
surface repairable legacy residue. Use `--all` when you want the complete lint
140+
inventory without listing each check id. `--only <id>` remains the most precise
141+
selector and can run any registered check by id.
142+
133143
## Structured Health Checks
134144

135145
Modern doctor checks use a small structured contract:
@@ -186,6 +196,7 @@ Use `--only` and `--skip` when a workflow wants a focused gate:
186196
```bash
187197
openclaw doctor --lint --only core/doctor/gateway-config --json
188198
openclaw doctor --lint --skip core/doctor/skills-readiness
199+
openclaw doctor --lint --all --skip core/doctor/session-locks
189200
```
190201

191202
`--only` and `--skip` accept full check ids and may be repeated. If an `--only`

docs/gateway/doctor.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,14 +104,15 @@ Examples:
104104
openclaw doctor --lint
105105
openclaw doctor --lint --severity-min warning
106106
openclaw doctor --lint --json
107+
openclaw doctor --lint --all
107108
openclaw doctor --lint --only core/doctor/gateway-config --json
108109
```
109110

110111
JSON output includes:
111112

112113
- `ok`: whether any visible finding met the selected severity threshold
113114
- `checksRun`: number of health checks executed
114-
- `checksSkipped`: checks skipped by `--only` or `--skip`
115+
- `checksSkipped`: checks skipped by the selected profile, `--only`, or `--skip`
115116
- `findings`: structured diagnostics with `checkId`, `severity`, `message`, and
116117
optional `path`, `line`, `column`, `ocPath`, and `fixHint`
117118

@@ -122,11 +123,13 @@ Exit codes:
122123
- `2`: command/runtime failure before lint findings could be emitted
123124

124125
Use `--severity-min info|warning|error` to control both what is printed and what
125-
causes a non-zero lint exit. Use `--only <id>` for narrow preflight gates and
126+
causes a non-zero lint exit. Use `--all` to run the complete lint inventory,
127+
including deeper opt-in checks excluded from the default automation set. Use `--only <id>` for narrow preflight gates and
126128
`--skip <id>` to temporarily exclude a noisy check while keeping the rest of the
127129
lint run active.
128-
Lint-output options such as `--json`, `--severity-min`, `--only`, and `--skip`
129-
must be paired with `--lint`; regular doctor and repair runs reject them.
130+
Lint-output options such as `--json`, `--severity-min`, `--all`, `--only`, and
131+
`--skip` must be paired with `--lint`; regular doctor and repair runs reject
132+
them.
130133

131134
## What it does (summary)
132135

src/cli/program/register.maintenance.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ describe("registerMaintenanceCommands doctor action", () => {
112112
"--json",
113113
"--severity-min",
114114
"error",
115+
"--all",
115116
"--skip",
116117
"a",
117118
"--only",
@@ -123,6 +124,7 @@ describe("registerMaintenanceCommands doctor action", () => {
123124
expect(runDoctorLintCli).toHaveBeenCalledWith(runtime, {
124125
json: true,
125126
severityMin: "error",
127+
includeAllChecks: true,
126128
skipIds: ["a"],
127129
onlyIds: ["b"],
128130
allowExec: true,
@@ -141,6 +143,17 @@ describe("registerMaintenanceCommands doctor action", () => {
141143
expect(runtime.exit).toHaveBeenCalledWith(2);
142144
});
143145

146+
it("rejects --all outside doctor lint mode", async () => {
147+
await runMaintenanceCli(["doctor", "--all"]);
148+
149+
expect(doctorCommand).not.toHaveBeenCalled();
150+
expect(runDoctorLintCli).not.toHaveBeenCalled();
151+
expect(runtime.error).toHaveBeenCalledWith(
152+
"doctor lint options require --lint. Use `openclaw doctor --lint ...`.",
153+
);
154+
expect(runtime.exit).toHaveBeenCalledWith(2);
155+
});
156+
144157
it("exits with code 2 when doctor lint mode fails before findings are emitted", async () => {
145158
runDoctorLintCli.mockRejectedValue(new Error("lint failed"));
146159

src/cli/program/register.maintenance.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export function registerMaintenanceCommands(program: Command) {
3939
"--severity-min <level>",
4040
"With --lint: drop findings below this severity (info|warning|error)",
4141
)
42+
.option("--all", "With --lint: run all registered checks, including opt-in checks", false)
4243
.option(
4344
"--skip <id>",
4445
"With --lint: skip a specific check id (repeatable)",
@@ -60,6 +61,7 @@ export function registerMaintenanceCommands(program: Command) {
6061
const exitCode = await runDoctorLintCli(defaultRuntime, {
6162
json: Boolean(opts.json),
6263
severityMin: typeof opts.severityMin === "string" ? opts.severityMin : undefined,
64+
includeAllChecks: Boolean(opts.all),
6365
skipIds: Array.isArray(opts.skip) ? opts.skip : [],
6466
onlyIds: Array.isArray(opts.only) ? opts.only : [],
6567
allowExec: Boolean(opts.allowExec),
@@ -180,12 +182,14 @@ function hasLintOnlyDoctorOptions(opts: {
180182
readonly json?: boolean;
181183
readonly postUpgrade?: boolean;
182184
readonly severityMin?: unknown;
185+
readonly all?: boolean;
183186
readonly skip?: unknown;
184187
readonly only?: unknown;
185188
}): boolean {
186189
return (
187190
(opts.json === true && opts.postUpgrade !== true) ||
188191
typeof opts.severityMin === "string" ||
192+
opts.all === true ||
189193
(Array.isArray(opts.skip) && opts.skip.length > 0) ||
190194
(Array.isArray(opts.only) && opts.only.length > 0)
191195
);

src/commands/doctor-lint.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ interface DoctorLintCliOptions {
2626
readonly onlyIds?: readonly string[];
2727
readonly allowExec?: boolean;
2828
readonly deep?: boolean;
29+
readonly includeAllChecks?: boolean;
2930
}
3031

3132
function detectMode(opts: DoctorLintCliOptions): "human" | "json" {
@@ -86,6 +87,7 @@ export async function runDoctorLintCli(
8687

8788
const runOpts: DoctorLintRunOptions = {
8889
checks: [...coreChecks.map((check) => withCoreLintContext(check, coreCtx)), ...extensionChecks],
90+
includeAllChecks: opts.includeAllChecks === true,
8991
...(opts.skipIds && opts.skipIds.length > 0 ? { skipIds: opts.skipIds } : {}),
9092
...(opts.onlyIds && opts.onlyIds.length > 0 ? { onlyIds: opts.onlyIds } : {}),
9193
};

src/flows/doctor-lint-flow.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,27 @@ describe("runDoctorLintChecks", () => {
6969
});
7070
});
7171

72+
it("runs default-disabled checks when all checks are requested", async () => {
73+
const defaultDisabled = normalizeHealthCheck({
74+
...check("targeted", async () => [
75+
{ checkId: "targeted", severity: "warning" as const, message: "warn" },
76+
]),
77+
defaultEnabled: false,
78+
});
79+
const defaultEnabled = check("regular", async () => []);
80+
81+
const result = await runDoctorLintChecks(ctx, {
82+
checks: [defaultDisabled, defaultEnabled],
83+
includeAllChecks: true,
84+
});
85+
86+
expect(result).toMatchObject({
87+
checksRun: 2,
88+
checksSkipped: 0,
89+
findings: [expect.objectContaining({ checkId: "targeted" })],
90+
});
91+
});
92+
7293
it("supports single-run checks in lint mode", async () => {
7394
const runnable: RunnableHealthCheck = {
7495
id: "run-check",

src/flows/doctor-lint-flow.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export interface DoctorLintRunOptions {
1515
readonly checks?: readonly HealthCheck[];
1616
readonly skipIds?: ReadonlySet<string> | readonly string[];
1717
readonly onlyIds?: ReadonlySet<string> | readonly string[];
18+
readonly includeAllChecks?: boolean;
1819
}
1920

2021
export interface DoctorLintRunResult {
@@ -32,20 +33,18 @@ export async function runDoctorLintChecks(
3233
const skip = opts.skipIds instanceof Set ? opts.skipIds : new Set(opts.skipIds ?? []);
3334
const only = opts.onlyIds instanceof Set ? opts.onlyIds : new Set(opts.onlyIds ?? []);
3435
const allIds = new Set(all.map((check) => check.id));
36+
const includeDefaultDisabled = opts.includeAllChecks === true;
3537

3638
const selected = all.filter((c) => {
3739
if (only.size > 0 && !only.has(c.id)) {
3840
return false;
3941
}
40-
if (only.size === 0 && isDefaultDisabled(c)) {
42+
if (only.size === 0 && !includeDefaultDisabled && isDefaultDisabled(c)) {
4143
return false;
4244
}
4345
if (skip.has(c.id)) {
4446
return false;
4547
}
46-
if (only.size === 0 && isDefaultDisabled(c)) {
47-
return false;
48-
}
4948
return true;
5049
});
5150

0 commit comments

Comments
 (0)