diff --git a/src/checks.ts b/src/checks.ts index 8f77e5d..836438f 100644 --- a/src/checks.ts +++ b/src/checks.ts @@ -41,6 +41,11 @@ const KNOWN_EFFORT_LEVELS: ReadonlySet = new Set([ "max", ]); +// Regional prefixes used by cross-region inference endpoints (Bedrock, Vertex). +// Strip these before looking up in KNOWN_CLAUDE_MODELS so that IDs like +// `us.claude-sonnet-4-6` are recognised without enumerating every combination. +const REGIONAL_PREFIXES: ReadonlyArray = ["us.", "eu.", "ap."]; + export function runChecks( parsed: ParsedSkill[], config: SkillcheckConfig, @@ -298,12 +303,19 @@ function checkNameDrift(v: ValidatedSkill): Diagnostic[] { ]; } +function stripRegionalPrefix(model: string): string { + for (const prefix of REGIONAL_PREFIXES) { + if (model.startsWith(prefix)) return model.slice(prefix.length); + } + return model; +} function checkModelUnknown(v: ValidatedSkill): Diagnostic[] { const model = v.frontmatter.model; if (model === undefined) return []; if (typeof model !== "string" || model.length === 0) return []; if (KNOWN_CLAUDE_MODELS.has(model)) return []; + if (KNOWN_CLAUDE_MODELS.has(stripRegionalPrefix(model))) return []; return [ { severity: "warn", diff --git a/test/checks.test.ts b/test/checks.test.ts index cd5a723..ede5ccc 100644 --- a/test/checks.test.ts +++ b/test/checks.test.ts @@ -1114,6 +1114,57 @@ describe("runChecks", () => { const ds = runChecks([a, b], config); expect(ds.filter((d) => d.rule === "description-collision").length).toBe(2); }); + + it("does not warn model-unknown for us. prefixed Bedrock model when base is known", () => { + const s = mkSkill("/test/foo/SKILL.md", { + name: "foo", + description: "do the foo thing", + model: "us.claude-sonnet-4-6", + }); + const ds = runChecks([s], config); + expect(ds.find((d) => d.rule === "model-unknown")).toBeUndefined(); + }); + + it("does not warn model-unknown for eu. prefixed Bedrock model when base is known", () => { + const s = mkSkill("/test/foo/SKILL.md", { + name: "foo", + description: "do the foo thing", + model: "eu.claude-opus-5", + }); + const ds = runChecks([s], config); + expect(ds.find((d) => d.rule === "model-unknown")).toBeUndefined(); + }); + + it("does not warn model-unknown for ap. prefixed Bedrock model when base is known", () => { + const s = mkSkill("/test/foo/SKILL.md", { + name: "foo", + description: "do the foo thing", + model: "ap.claude-fable-5", + }); + const ds = runChecks([s], config); + expect(ds.find((d) => d.rule === "model-unknown")).toBeUndefined(); + }); + + it("still warns model-unknown when a regional prefix precedes an unrecognised base model", () => { + const s = mkSkill("/test/foo/SKILL.md", { + name: "foo", + description: "do the foo thing", + model: "us.claude-fable-6", + }); + const ds = runChecks([s], config); + expect(ds.some((d) => d.rule === "model-unknown")).toBe(true); + }); + + it("model-unknown message includes the full prefixed model id when regional prefix is used", () => { + const s = mkSkill("/test/foo/SKILL.md", { + name: "foo", + description: "do the foo thing", + model: "us.claude-fable-6", + }); + const ds = runChecks([s], config); + const d = ds.find((d) => d.rule === "model-unknown"); + expect(d?.message).toContain("us.claude-fable-6"); + }); }); describe("buildValidated", () => {