From bed280bd42e9ca9337e28577d9c62a0ac41536c0 Mon Sep 17 00:00:00 2001 From: bitfathers94 <237535319+bitfathers94@users.noreply.github.com> Date: Fri, 31 Jul 2026 08:58:14 +0000 Subject: [PATCH] fix(engine): add the missing documentation_url snake_case alias, derive the urlFields pairing test urlFields/sourceUrlFields carried snake_case aliases for eight of the nine camelCase URL fields; documentationUrl had none, so a submitted entry spelled documentation_url was invisible to both duplicate detection and the source-evidence fetch-verification gate. The regression test that was supposed to pin the pairing invariant enumerated the aliases by hand instead of deriving them from the set, so it couldn't have caught the gap in the first place. --- .../review/content-lane/content-repo-spec.ts | 2 ++ .../test/content-repo-spec.test.ts | 22 +++++++++++++++++++ test/unit/content-lane-duplicates.test.ts | 16 ++++++++++++++ .../unit/content-lane-source-evidence.test.ts | 20 +++++++++++++---- 4 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 packages/loopover-engine/test/content-repo-spec.test.ts diff --git a/packages/loopover-engine/src/review/content-lane/content-repo-spec.ts b/packages/loopover-engine/src/review/content-lane/content-repo-spec.ts index eae52a3d3e..853f384509 100644 --- a/packages/loopover-engine/src/review/content-lane/content-repo-spec.ts +++ b/packages/loopover-engine/src/review/content-lane/content-repo-spec.ts @@ -102,6 +102,7 @@ export const AWESOME_CLAUDE_CONTENT_SPEC: ContentRepoSpec = { "sourceUrl", "websiteUrl", "docs_url", + "documentation_url", "download_url", "github_url", "package_url", @@ -141,6 +142,7 @@ export const AWESOME_CLAUDE_CONTENT_SPEC: ContentRepoSpec = { "repository_url", "source_url", "website_url", + "documentation_url", ], sourceUrlListFields: new Set(["sourceUrls", "retrievalSources"]), // snake_case aliases match urlFields / sourceUrlFields (#7446): without them, a site-relative diff --git a/packages/loopover-engine/test/content-repo-spec.test.ts b/packages/loopover-engine/test/content-repo-spec.test.ts new file mode 100644 index 0000000000..0e7c195bb6 --- /dev/null +++ b/packages/loopover-engine/test/content-repo-spec.test.ts @@ -0,0 +1,22 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; + +import { AWESOME_CLAUDE_CONTENT_SPEC } from "../dist/review/content-lane/content-repo-spec.js"; + +// Mirrors the derived pairing assertion at test/unit/content-lane-duplicates.test.ts (protectedFrontmatterFields) +// and test/unit/content-lane-source-evidence.test.ts (urlFields): a hand-enumerated alias list can't catch the +// next camelCase URL field added to the spec without a matching snake_case alias — a derived walk can (#9992). +test("every camelCase urlFields member has its snake_case alias present in both urlFields and sourceUrlFields", () => { + const toSnakeCase = (field: string): string => field.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`); + for (const field of AWESOME_CLAUDE_CONTENT_SPEC.urlFields) { + if (!/[A-Z]/.test(field)) continue; + const snakeCase = toSnakeCase(field); + assert.equal(AWESOME_CLAUDE_CONTENT_SPEC.urlFields.has(snakeCase), true); + assert.equal(AWESOME_CLAUDE_CONTENT_SPEC.sourceUrlFields.includes(snakeCase), true); + } +}); + +test("documentation_url is present in both urlFields and sourceUrlFields (#9992)", () => { + assert.equal(AWESOME_CLAUDE_CONTENT_SPEC.urlFields.has("documentation_url"), true); + assert.equal(AWESOME_CLAUDE_CONTENT_SPEC.sourceUrlFields.includes("documentation_url"), true); +}); diff --git a/test/unit/content-lane-duplicates.test.ts b/test/unit/content-lane-duplicates.test.ts index fdb70b7c15..f5bac1b100 100644 --- a/test/unit/content-lane-duplicates.test.ts +++ b/test/unit/content-lane-duplicates.test.ts @@ -214,6 +214,22 @@ describe("extractContentDuplicateSignals + strict match", () => { const m = findStrictContentDuplicateMatch(a, [b]); expect(m?.reasons.some((r) => r.includes("same normalized description"))).toBe(true); }); + + it("REGRESSION (#9992): an entry whose only URL frontmatter key is documentation_url still yields a non-empty urls signal", () => { + const sig = extractContentDuplicateSignals({ + filePath: "content/skills/doc-only.mdx", + content: mdx({ title: "Doc Only", slug: "doc-only", documentation_url: "https://docs.acme.example/guide" }), + }); + expect(sig.urls).toContain("https://docs.acme.example/guide"); + }); + + it("does not pick up an unrelated non-URL frontmatter key into the urls signal", () => { + const sig = extractContentDuplicateSignals({ + filePath: "content/skills/no-url.mdx", + content: mdx({ title: "No Url", slug: "no-url", pricing_model: "https://example.com/pricing-not-a-url-field" }), + }); + expect(sig.urls).toEqual([]); + }); }); describe("normalizeUrl — RFC 3986 percent-encoding canonicalization for duplicate detection", () => { diff --git a/test/unit/content-lane-source-evidence.test.ts b/test/unit/content-lane-source-evidence.test.ts index acd762ed8a..50823a2962 100644 --- a/test/unit/content-lane-source-evidence.test.ts +++ b/test/unit/content-lane-source-evidence.test.ts @@ -67,12 +67,24 @@ describe("extractSubmittedSourceUrls", () => { // was invisible to the source-evidence gate even though duplicates.ts (which reads urlFields) saw it. const pairs = extractSubmittedSourceUrls(mdx({ source_url: "https://github.com/acme/y" })).map((u) => `${u.field}:${u.url}`); expect(pairs).toContain("source_url:https://github.com/acme/y"); - // Every snake_case alias urlFields carries is now recognized here too. - for (const field of ["docs_url", "download_url", "github_url", "package_url", "repo_url", "repository_url", "source_url", "website_url"]) { - expect(AWESOME_CLAUDE_CONTENT_SPEC.sourceUrlFields).toContain(field); - expect(AWESOME_CLAUDE_CONTENT_SPEC.urlFields.has(field)).toBe(true); + }); + + it("every camelCase urlFields member has its snake_case alias present in both urlFields and sourceUrlFields (mirrors #7250/#7445's pairing assertion; #9992)", () => { + // Derived, not hand-enumerated: a hard-coded list here (as before #9992) cannot catch the next URL field + // that gets added to urlFields without a matching snake_case alias — it would silently pass regardless. + const toSnakeCase = (field: string): string => field.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`); + for (const field of AWESOME_CLAUDE_CONTENT_SPEC.urlFields) { + if (!/[A-Z]/.test(field)) continue; + const snakeCase = toSnakeCase(field); + expect(AWESOME_CLAUDE_CONTENT_SPEC.urlFields.has(snakeCase)).toBe(true); + expect(AWESOME_CLAUDE_CONTENT_SPEC.sourceUrlFields).toContain(snakeCase); } }); + + it("REGRESSION (#9992): extracts documentation_url the same way as documentationUrl", () => { + const urls = extractSubmittedSourceUrls(mdx({ documentation_url: "https://docs.acme.example/guide" })); + expect(urls).toEqual([{ field: "documentation_url", url: "https://docs.acme.example/guide" }]); + }); }); describe("checkSubmittedSourceEvidence", () => {