diff --git a/.changeset/tidy-headings-match.md b/.changeset/tidy-headings-match.md new file mode 100644 index 00000000..d2e1c2e6 --- /dev/null +++ b/.changeset/tidy-headings-match.md @@ -0,0 +1,5 @@ +--- +"@design-intelligence/ghost": patch +--- + +Resolve check heading anchors against Markdown headings with optional closing hashes. diff --git a/packages/ghost/src/ghost-core/check/source-ref.ts b/packages/ghost/src/ghost-core/check/source-ref.ts index 92030663..b3b8f940 100644 --- a/packages/ghost/src/ghost-core/check/source-ref.ts +++ b/packages/ghost/src/ghost-core/check/source-ref.ts @@ -46,7 +46,11 @@ export function sliceNodeSection(body: string, heading: string): string | null { let level = 0; for (let i = 0; i < lines.length; i += 1) { const match = headingPattern.exec(lines[i]); - if (match && match[2].trim().toLowerCase() === wanted) { + const headingText = match?.[2] + .replace(/[ \t]+#+[ \t]*$/, "") + .trim() + .toLowerCase(); + if (match && headingText === wanted) { startLine = i; level = match[1].length; break; diff --git a/packages/ghost/test/ghost-core/source-ref.test.ts b/packages/ghost/test/ghost-core/source-ref.test.ts index ccb4fb8c..8b02eab5 100644 --- a/packages/ghost/test/ghost-core/source-ref.test.ts +++ b/packages/ghost/test/ghost-core/source-ref.test.ts @@ -73,6 +73,16 @@ Other prose. ); }); + it("matches headings with optional closing hashes", () => { + const body = "## Confirmation ##\n\nConfirmation prose.\n"; + expect(sliceNodeSection(body, "Confirmation")).toBe("Confirmation prose."); + }); + + it("preserves hashes that are part of the heading text", () => { + const body = "## Confirm #1\n\nFirst confirmation.\n"; + expect(sliceNodeSection(body, "Confirm #1")).toBe("First confirmation."); + }); + it("stops at the next heading of the same level", () => { const slice = sliceNodeSection(BODY, "Confirmation"); expect(slice).not.toContain("Next Section");