From c59aa2ce688de837d8b074a2154d0b7bac79f204 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 1 Oct 2026 13:58:24 -0700 Subject: [PATCH 1/2] fix(library): ignore sentence punctuation after bare internal URLs in check:library-content --- scripts/check-library-content.test.ts | 7 +++++++ scripts/check-library-content.ts | 4 +++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/scripts/check-library-content.test.ts b/scripts/check-library-content.test.ts index 65db6936e0d..52eb2b3a44a 100644 --- a/scripts/check-library-content.test.ts +++ b/scripts/check-library-content.test.ts @@ -77,6 +77,7 @@ describe('check-library-content', () => { [ '## Overview', 'See [the guide](/library/kept-guide) and https://www.sim.ai/library/kept-guide#intro.', + 'Read https://www.sim.ai/library/kept-guide. Then https://www.sim.ai/library/moved-post, too.', '![diagram](/library/clean/diagram.png) and [tags](/library/tags) are not posts.', 'An apex https://sim.ai/library/missing link belongs to check:site-urls.', '| a | b |', @@ -177,6 +178,7 @@ describe('check-library-content', () => { '[b](https://www.sim.ai/library/old-guide)', 'c', '[d](/blog/kept-guide)', + 'Also see https://www.sim.ai/library/gone.', ].join('\n') ) expect(await findingsFor('library', 'post')).toEqual([ @@ -200,6 +202,11 @@ describe('check-library-content', () => { rule: 'internal-link', message: '/blog/kept-guide does not exist (no apps/sim/content/blog/kept-guide/index.mdx).', }, + { + line: 14, + rule: 'internal-link', + message: '/library/gone does not exist (no apps/sim/content/library/gone/index.mdx).', + }, ]) }) diff --git a/scripts/check-library-content.ts b/scripts/check-library-content.ts index 796ebb36cdd..5ba4199c41a 100644 --- a/scripts/check-library-content.ts +++ b/scripts/check-library-content.ts @@ -76,6 +76,8 @@ export interface PostRef { */ const INTERNAL_LINK = /(?:https?:\/\/www\.sim\.ai|(?<=\]\(\s*|href=\{?["'`]))\/(library|blog|customers)\/([^\s)"'`#?/<>\]]+)(\/[^\s)"'`#?<>\]]*)?/g +/** Sentence punctuation that ends a bare URL in prose (`…see https://www.sim.ai/library/x.`). */ +const TRAILING_PUNCTUATION = /[.,;:!]+$/ const MARKDOWN_LINK = /\[[^\]\n]*\]\([^)\n]*\)/ const FAQ_HEADING = /^#{1,6}\s+FAQs?\s*:?\s*$/i const CODE_FENCE = /^\s*(```|~~~)/ @@ -330,7 +332,7 @@ export async function checkPost( } for (const match of text.matchAll(INTERNAL_LINK)) { const linkSection = match[1] as Section - const target = match[2] + const target = match[2].replace(TRAILING_PUNCTUATION, '') const rest = match[3] ?? '' // A deeper path is a public asset (`/library//cover.jpg`) or a static sub-route. if (rest !== '' && rest !== '/') continue From fb9099f1b49d9ef9860763681b38153c1f8971e4 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 1 Oct 2026 14:02:04 -0700 Subject: [PATCH 2/2] fix(library): only strip trailing punctuation from bare URLs, not explicit link targets --- scripts/check-library-content.test.ts | 13 +++++++++++++ scripts/check-library-content.ts | 5 ++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/scripts/check-library-content.test.ts b/scripts/check-library-content.test.ts index 52eb2b3a44a..ce8d01c9649 100644 --- a/scripts/check-library-content.test.ts +++ b/scripts/check-library-content.test.ts @@ -179,6 +179,7 @@ describe('check-library-content', () => { 'c', '[d](/blog/kept-guide)', 'Also see https://www.sim.ai/library/gone.', + '[e](/library/kept-guide.) and f', ].join('\n') ) expect(await findingsFor('library', 'post')).toEqual([ @@ -207,6 +208,18 @@ describe('check-library-content', () => { rule: 'internal-link', message: '/library/gone does not exist (no apps/sim/content/library/gone/index.mdx).', }, + { + line: 15, + rule: 'internal-link', + message: + '/library/kept-guide. does not exist (no apps/sim/content/library/kept-guide./index.mdx).', + }, + { + line: 15, + rule: 'internal-link', + message: + '/library/kept-guide. does not exist (no apps/sim/content/library/kept-guide./index.mdx).', + }, ]) }) diff --git a/scripts/check-library-content.ts b/scripts/check-library-content.ts index 5ba4199c41a..e59d8432d76 100644 --- a/scripts/check-library-content.ts +++ b/scripts/check-library-content.ts @@ -78,6 +78,8 @@ const INTERNAL_LINK = /(?:https?:\/\/www\.sim\.ai|(?<=\]\(\s*|href=\{?["'`]))\/(library|blog|customers)\/([^\s)"'`#?/<>\]]+)(\/[^\s)"'`#?<>\]]*)?/g /** Sentence punctuation that ends a bare URL in prose (`…see https://www.sim.ai/library/x.`). */ const TRAILING_PUNCTUATION = /[.,;:!]+$/ +/** Text just before a Markdown link target or `href` value, whose URL ends at its delimiter. */ +const LINK_TARGET_OPENER = /(?:\]\(\s*|href=\{?["'`])$/ const MARKDOWN_LINK = /\[[^\]\n]*\]\([^)\n]*\)/ const FAQ_HEADING = /^#{1,6}\s+FAQs?\s*:?\s*$/i const CODE_FENCE = /^\s*(```|~~~)/ @@ -332,7 +334,8 @@ export async function checkPost( } for (const match of text.matchAll(INTERNAL_LINK)) { const linkSection = match[1] as Section - const target = match[2].replace(TRAILING_PUNCTUATION, '') + const isBareUrl = !LINK_TARGET_OPENER.test(text.slice(0, match.index)) + const target = isBareUrl ? match[2].replace(TRAILING_PUNCTUATION, '') : match[2] const rest = match[3] ?? '' // A deeper path is a public asset (`/library//cover.jpg`) or a static sub-route. if (rest !== '' && rest !== '/') continue