diff --git a/src/signals/engine.ts b/src/signals/engine.ts index a8af3c19f5..2ced7adc7d 100644 --- a/src/signals/engine.ts +++ b/src/signals/engine.ts @@ -4953,7 +4953,11 @@ export function buildPrTextLint(input: PrTextLintInput): PrTextLintReport { // Exported so the deterministic no-linked-issue slop signal (#562) and the public PR-panel traceability check // share ONE definition of a "clear no-issue rationale" (maintenance / docs-only / "no issue: …" in the PR text). export function hasClearNoIssueRationale(pr: Pick): boolean { - return /\b(?:no issue\s*(?:because\b|:)|no linked issue\s*(?:because\b|:)|no ticket\s*(?:because\b|:)|(?:maintenance|docs? only|typo|chore|cleanup)\b)/i.test([pr.title, pr.body ?? ""].join(" ")); + // `docs?[\s-]+only` matches the space form ("docs only") AND the hyphenated "docs-only" / "doc-only" + // spelling this function's own docstring uses — the dominant GitHub/Conventional-Commits form. A bare + // `docs? only` missed the hyphen, so a docs-only PR with no linked issue was wrongly denied a clear + // no-issue rationale and hard-blocked under `linkedIssueGateMode === "block"`. + return /\b(?:no issue\s*(?:because\b|:)|no linked issue\s*(?:because\b|:)|no ticket\s*(?:because\b|:)|(?:maintenance|docs?[\s-]+only|typo|chore|cleanup)\b)/i.test([pr.title, pr.body ?? ""].join(" ")); } function hasValidationNote(value: string): boolean { diff --git a/test/unit/signals-v2.test.ts b/test/unit/signals-v2.test.ts index b6f6e22caa..1f1a44f0d7 100644 --- a/test/unit/signals-v2.test.ts +++ b/test/unit/signals-v2.test.ts @@ -26,6 +26,7 @@ import { buildRegistryChangeReport, buildRepoFitRecommendation, buildRoleContext, + hasClearNoIssueRationale, type ContributorFit, type ContributorOutcomeHistory, type ContributorScoringProfile, @@ -1710,6 +1711,23 @@ describe("v2 signal builders", () => { }); }); +describe("hasClearNoIssueRationale docs-only spelling", () => { + it("recognizes the hyphenated docs-only rationale, not only the space form", () => { + // The space form already worked; these hyphenated forms (the dominant GitHub / Conventional-Commits + // spelling, and the one this function's own docstring uses) were wrongly missed, hard-blocking a + // docs-only PR with no linked issue under linkedIssueGateMode === "block". + expect(hasClearNoIssueRationale({ title: "docs only: clarify README", body: "" })).toBe(true); + expect(hasClearNoIssueRationale({ title: "docs-only: clarify README", body: "" })).toBe(true); + expect(hasClearNoIssueRationale({ title: "doc-only update", body: "" })).toBe(true); + expect(hasClearNoIssueRationale({ title: "Improve install steps", body: "This is a docs-only change." })).toBe(true); + }); + + it("still rejects PR text with no clear no-issue rationale", () => { + expect(hasClearNoIssueRationale({ title: "Improve install steps", body: "Adds a new option." })).toBe(false); + expect(hasClearNoIssueRationale({ title: "Add documentation site", body: "" })).toBe(false); + }); +}); + function snapshot( id: string, repositories: Array<{