fix(claw3doctor): scope provider-specific checks to --profile / --all-profiles flags

PR #101 finding:
- Medium: --profile <adapter> and --all-profiles only scoped the profile health
  probe loop; OpenClaw/Hermes/Demo/Custom check blocks still ran based on the
  selected adapter type in runtimeContext, making CLI output misleading.

Fix: introduce adapterInScope(adapterType, defaultBehavior) helper in main().
  - --profile <adapter>  -> only that adapter's checks run
  - --all-profiles       -> all adapter checks run
  - no flag              -> falls back to existing shouldRun* predicate (unchanged)

Also:
- Export parseDoctorArgs from claw3doctor-core.mjs (removed duplicate in script)
- Add test suites: parseDoctorArgs flag parsing (6 cases) and
  adapterInScope scoping semantics (4 cases) — 10 new tests, all green
This commit is contained in:
gsknnft
2026-04-06 11:12:41 -04:00
parent f392729fcf
commit d799f84658
3 changed files with 120 additions and 31 deletions
+17 -31
View File
@@ -14,6 +14,7 @@ import {
buildGatewayWarnings,
buildOpenClawWarnings,
formatDoctorReport,
parseDoctorArgs,
resolveRuntimeContext,
shouldRunCustomChecks,
shouldRunDemoChecks,
@@ -106,33 +107,6 @@ const checkFail = (category, label, message, actions) => ({
const trim = (value) => (typeof value === "string" ? value.trim() : "");
const parseDoctorArgs = (argv) => {
const args = {
json: false,
allProfiles: false,
profile: null,
};
for (let index = 0; index < argv.length; index += 1) {
const entry = argv[index];
if (entry === "--json") {
args.json = true;
continue;
}
if (entry === "--all-profiles") {
args.allProfiles = true;
continue;
}
if (entry === "--profile") {
const next = trim(argv[index + 1]).toLowerCase();
if (next) {
args.profile = next;
index += 1;
}
}
}
return args;
};
const probeWebSocket = async (url, timeoutMs = 3500) =>
await new Promise((resolve) => {
let settled = false;
@@ -285,6 +259,18 @@ async function main() {
});
const workspace = detectWorkspaceState();
/**
* Returns whether provider-specific checks for `adapterType` should run.
* When --profile <adapter> is set, only that adapter is in scope.
* When --all-profiles is set, all adapters are in scope.
* Otherwise falls back to `defaultBehavior` (the existing shouldRun* predicate).
*/
const adapterInScope = (adapterType, defaultBehavior) => {
if (args.allProfiles) return true;
if (args.profile) return args.profile === adapterType;
return defaultBehavior;
};
const checks = [];
checks.push(
@@ -367,7 +353,7 @@ async function main() {
checks.push(checkWarn("Gateway access", "Gateway hints", warning));
}
if (runtimeContext.adapterType === "openclaw") {
if (adapterInScope("openclaw", runtimeContext.adapterType === "openclaw")) {
for (const warning of buildOpenClawWarnings({
gatewayUrl: runtimeContext.gatewayUrl,
tokenConfigured: runtimeContext.tokenConfigured,
@@ -378,7 +364,7 @@ async function main() {
}
}
if (shouldRunCustomChecks({ runtimeContext })) {
if (adapterInScope("custom", shouldRunCustomChecks({ runtimeContext }))) {
for (const warning of buildCustomRuntimeWarnings({
gatewayUrl: runtimeContext.gatewayUrl,
allowlist: trim(env.CUSTOM_RUNTIME_ALLOWLIST) || trim(env.UPSTREAM_ALLOWLIST),
@@ -454,7 +440,7 @@ async function main() {
);
}
if (shouldRunDemoChecks({ runtimeContext, env })) {
if (adapterInScope("demo", shouldRunDemoChecks({ runtimeContext, env }))) {
const configuredPort = trim(env.DEMO_ADAPTER_PORT) || "18789";
checks.push(
checkPass(
@@ -466,7 +452,7 @@ async function main() {
);
}
if (shouldRunHermesChecks({ runtimeContext, env })) {
if (adapterInScope("hermes", shouldRunHermesChecks({ runtimeContext, env }))) {
const hermes = await detectHermesModelHealth();
checks.push(
checkPass(
+31
View File
@@ -437,3 +437,34 @@ export const buildDoctorJsonReport = ({ summary, runtimeContext, paths, checks }
fail: checks.filter((check) => check.status === DOCTOR_STATUSES.fail).length,
},
});
/**
* Parse CLI argv into structured doctor args.
* Exported so the flag behaviour can be unit tested without spawning a process.
*/
export const parseDoctorArgs = (argv) => {
const args = {
json: false,
allProfiles: false,
profile: null,
};
for (let index = 0; index < argv.length; index += 1) {
const entry = argv[index];
if (entry === "--json") {
args.json = true;
continue;
}
if (entry === "--all-profiles") {
args.allProfiles = true;
continue;
}
if (entry === "--profile") {
const next = trimString(argv[index + 1] ?? "").toLowerCase();
if (next) {
args.profile = next;
index += 1;
}
}
}
return args;
};