From 50c4951df5bb57bcdd0379c04e2cd5c3fca21942 Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Wed, 14 Oct 2026 04:10:25 +1100 Subject: [PATCH 1/2] fix(markdown): include rendered marketing content --- nuxt.config.ts | 3 ++ server/plugins/ai-ready-framework-source.ts | 29 ++++++++++++++++++ test/e2e/marketing-markdown.spec.ts | 33 +++++++++++++++++++++ 3 files changed, 65 insertions(+) create mode 100644 server/plugins/ai-ready-framework-source.ts create mode 100644 test/e2e/marketing-markdown.spec.ts diff --git a/nuxt.config.ts b/nuxt.config.ts index 24cbc84b..ada75e35 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -292,6 +292,9 @@ export default defineNuxtConfig({ }, aiReady: { + // Render marketing components so Markdown includes charts and curated Skill lists. + // The explicit Skill source hook still preserves original SKILL.md bodies. + contentSource: false, llmsTxt: { directive: { html: true }, pages: 'sitemap', diff --git a/server/plugins/ai-ready-framework-source.ts b/server/plugins/ai-ready-framework-source.ts new file mode 100644 index 00000000..75b517e5 --- /dev/null +++ b/server/plugins/ai-ready-framework-source.ts @@ -0,0 +1,29 @@ +import type { TagProfile } from '#layers/registry/server/api/tags/[slug].get' + +/** Markdown includes the full directory, including rows behind the browser's load-more control. */ +export default defineNitroPlugin((nitroApp) => { + nitroApp.hooks.hook('ai-ready:markdown:source', async (context) => { + const match = /^\/frameworks\/(nuxt|nextjs|react|vue)$/.exec(context.route) + if (!match) + return + const profile = await context.event.$fetch(`/api/tags/${match[1]}`, { query: { view: 'data' } }) + const text = (value: string) => value.replace(/\s+/g, ' ').replace(/[\\`*_{}[\]<>]/g, '\\$&') + const title = `Skills for ${profile.tag.label}` + const rows = profile.skills.map(skill => [ + `- [${text(skill.name)}](${skill.registryPath})`, + ' ' + text(skill.authorName || skill.owner) + ' · ' + text(skill.owner + '/' + skill.repo), + ' ' + skill.stars.toLocaleString('en-US') + ' GitHub stars', + ...(skill.description ? [` ${text(skill.description)}`] : []), + ].join('\n')) + context.source = { + title, + description: profile.tag.description, + markdown: [ + `# ${title}`, + text(profile.tag.description), + `## ${profile.tag.label} skills`, + ...rows, + ].join('\n\n'), + } + }) +}) diff --git a/test/e2e/marketing-markdown.spec.ts b/test/e2e/marketing-markdown.spec.ts new file mode 100644 index 00000000..19dfd9e0 --- /dev/null +++ b/test/e2e/marketing-markdown.spec.ts @@ -0,0 +1,33 @@ +import { expect, test } from './test-utils' + +test('Agent guide Markdown includes its rendered curated Skill section', async ({ request }) => { + const response = await request.get('/agents/cursor.md') + expect(response.status()).toBe(200) + expect(response.headers()['content-type']).toContain('text/markdown') + const markdown = await response.text() + expect(markdown).toContain('Curated Skills for Cursor') + expect(markdown).toContain('Most Cursor readers build interfaces') + expect(markdown).toContain('/gh/jakubkrehel/make-interfaces-feel-better') +}) + +test('research Markdown includes the rendered chart values', async ({ request }) => { + const response = await request.get('/learn/research/skill-md-size-study.md') + expect(response.status()).toBe(200) + const markdown = await response.text() + expect(markdown).toContain('Most Skill bodies fit below 5,000 tokens') + expect(markdown).toContain('8,496') + expect(markdown).toContain('2,564') + expect(markdown).toContain('1,078') + expect(markdown).not.toContain('::skill-size-study') +}) + +test('framework Markdown lists every Skill in the directory response', async ({ request }) => { + const source = await request.get('/api/tags/nuxt?view=data') + expect(source.status()).toBe(200) + const profile = await source.json() as { skills: { registryPath: string }[] } + const response = await request.get('/frameworks/nuxt.md') + expect(response.status()).toBe(200) + const markdown = await response.text() + for (const skill of profile.skills) + expect(markdown).toContain(skill.registryPath) +}) From abf86f054decc0f222835b1bc2325a14e382442a Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Wed, 14 Oct 2026 04:21:13 +1100 Subject: [PATCH 2/2] style: use templates in framework Markdown rows --- server/plugins/ai-ready-framework-source.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/plugins/ai-ready-framework-source.ts b/server/plugins/ai-ready-framework-source.ts index 75b517e5..1b482daf 100644 --- a/server/plugins/ai-ready-framework-source.ts +++ b/server/plugins/ai-ready-framework-source.ts @@ -11,8 +11,8 @@ export default defineNitroPlugin((nitroApp) => { const title = `Skills for ${profile.tag.label}` const rows = profile.skills.map(skill => [ `- [${text(skill.name)}](${skill.registryPath})`, - ' ' + text(skill.authorName || skill.owner) + ' · ' + text(skill.owner + '/' + skill.repo), - ' ' + skill.stars.toLocaleString('en-US') + ' GitHub stars', + ` ${text(skill.authorName || skill.owner)} · ${text(`${skill.owner}/${skill.repo}`)}`, + ` ${skill.stars.toLocaleString('en-US')} GitHub stars`, ...(skill.description ? [` ${text(skill.description)}`] : []), ].join('\n')) context.source = {