diff --git a/src/signals/focus-manifest.ts b/src/signals/focus-manifest.ts index 0b57e9be5b..f820770524 100644 --- a/src/signals/focus-manifest.ts +++ b/src/signals/focus-manifest.ts @@ -418,7 +418,10 @@ function normalizeSource(raw: FocusManifestSource | undefined, value: JsonValue function normalizeOptionalGateMode(value: JsonValue | undefined, field: string, warnings: string[]): GateRuleMode | null { if (value === undefined || value === null) return null; - if (value === "off" || value === "advisory" || value === "block") return value; + if (typeof value === "string") { + const normalized = value.trim().toLowerCase(); + if (normalized === "off" || normalized === "advisory" || normalized === "block") return normalized; + } warnings.push(`Manifest gate field "${field}" must be one of off, advisory, block; ignoring "${String(value)}".`); return null; } diff --git a/test/unit/focus-manifest.test.ts b/test/unit/focus-manifest.test.ts index c885e3b959..77dae6f686 100644 --- a/test/unit/focus-manifest.test.ts +++ b/test/unit/focus-manifest.test.ts @@ -1543,6 +1543,18 @@ describe("parseFocusManifest review config", () => { expect(parseFocusManifest({ review: reviewConfigToJson(chill.review) }).review).toEqual(chill.review); }); + it("parses gate tri-state modes case-insensitively like review.profile", () => { + const manifest = parseFocusManifest({ + gate: { linkedIssue: "BLOCK", duplicates: "Advisory", size: { mode: "OFF" } }, + settings: { linkedIssueGateMode: "Block" }, + }); + expect(manifest.gate.linkedIssue).toBe("block"); + expect(manifest.gate.duplicates).toBe("advisory"); + expect(manifest.gate.sizeMode).toBe("off"); + expect(manifest.settings.linkedIssueGateMode).toBe("block"); + expect(resolveEffectiveSettings({ linkedIssueGateMode: "off" } as RepositorySettings, manifest).linkedIssueGateMode).toBe("block"); + }); + it("ignores an invalid review.profile with a warning", () => { const m = parseFocusManifest({ review: { profile: "spicy" } }); expect(m.review.profile).toBeNull();