mirror of
https://github.com/iamlukethedev/Claw3D.git
synced 2026-07-31 03:52:15 +00:00
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:
+17
-31
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
DOCTOR_STATUSES,
|
||||
buildGatewayWarnings,
|
||||
formatDoctorReport,
|
||||
parseDoctorArgs,
|
||||
resolveRuntimeContext,
|
||||
shouldRunCustomChecks,
|
||||
shouldRunDemoChecks,
|
||||
@@ -277,3 +278,74 @@ describe("claw3doctor core", () => {
|
||||
expect(report).toContain("Check counts:");
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseDoctorArgs", () => {
|
||||
it("returns defaults when no flags are supplied", () => {
|
||||
expect(parseDoctorArgs([])).toEqual({ json: false, allProfiles: false, profile: null });
|
||||
});
|
||||
|
||||
it("sets json flag", () => {
|
||||
expect(parseDoctorArgs(["--json"])).toMatchObject({ json: true });
|
||||
});
|
||||
|
||||
it("sets allProfiles flag", () => {
|
||||
expect(parseDoctorArgs(["--all-profiles"])).toMatchObject({ allProfiles: true, profile: null });
|
||||
});
|
||||
|
||||
it("sets profile to lower-cased value", () => {
|
||||
expect(parseDoctorArgs(["--profile", "Hermes"])).toMatchObject({ profile: "hermes", allProfiles: false });
|
||||
});
|
||||
|
||||
it("ignores --profile flag when no value follows", () => {
|
||||
expect(parseDoctorArgs(["--profile"])).toMatchObject({ profile: null });
|
||||
});
|
||||
|
||||
it("combines flags", () => {
|
||||
expect(parseDoctorArgs(["--json", "--profile", "openclaw"])).toEqual({
|
||||
json: true,
|
||||
allProfiles: false,
|
||||
profile: "openclaw",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("adapterInScope scoping semantics", () => {
|
||||
// Mirror the adapterInScope helper used in claw3doctor.mjs so the logic can
|
||||
// be verified independently of the full CLI entrypoint.
|
||||
const makeAdapterInScope =
|
||||
(args: { allProfiles: boolean; profile: string | null }) =>
|
||||
(adapterType: string, defaultBehavior: boolean): boolean => {
|
||||
if (args.allProfiles) return true;
|
||||
if (args.profile) return args.profile === adapterType;
|
||||
return defaultBehavior;
|
||||
};
|
||||
|
||||
it("default (no flags): delegates to defaultBehavior", () => {
|
||||
const inScope = makeAdapterInScope({ allProfiles: false, profile: null });
|
||||
expect(inScope("openclaw", true)).toBe(true);
|
||||
expect(inScope("openclaw", false)).toBe(false);
|
||||
expect(inScope("hermes", false)).toBe(false);
|
||||
});
|
||||
|
||||
it("--profile hermes: only hermes is in scope", () => {
|
||||
const inScope = makeAdapterInScope({ allProfiles: false, profile: "hermes" });
|
||||
expect(inScope("hermes", false)).toBe(true);
|
||||
expect(inScope("openclaw", true)).toBe(false); // openclaw would default to true but is suppressed
|
||||
expect(inScope("demo", true)).toBe(false);
|
||||
expect(inScope("custom", false)).toBe(false);
|
||||
});
|
||||
|
||||
it("--profile openclaw: only openclaw is in scope", () => {
|
||||
const inScope = makeAdapterInScope({ allProfiles: false, profile: "openclaw" });
|
||||
expect(inScope("openclaw", false)).toBe(true);
|
||||
expect(inScope("hermes", true)).toBe(false);
|
||||
});
|
||||
|
||||
it("--all-profiles: every adapter is in scope regardless of default", () => {
|
||||
const inScope = makeAdapterInScope({ allProfiles: true, profile: null });
|
||||
expect(inScope("hermes", false)).toBe(true);
|
||||
expect(inScope("openclaw", false)).toBe(true);
|
||||
expect(inScope("demo", false)).toBe(true);
|
||||
expect(inScope("custom", false)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user