Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ export const AWESOME_CLAUDE_CONTENT_SPEC: ContentRepoSpec = {
"sourceUrl",
"websiteUrl",
"docs_url",
"documentation_url",
"download_url",
"github_url",
"package_url",
Expand Down Expand Up @@ -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
Expand Down
22 changes: 22 additions & 0 deletions packages/loopover-engine/test/content-repo-spec.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
16 changes: 16 additions & 0 deletions test/unit/content-lane-duplicates.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down
20 changes: 16 additions & 4 deletions test/unit/content-lane-source-evidence.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down