From 5d65c8af877553ba2147e85211178b493a852e2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guilherme=20Ara=C3=BAjo?= Date: Wed, 30 Jul 2025 20:42:47 -0300 Subject: [PATCH 1/5] feat: camelize llm_description --- src/__tests__/metadata.test.mjs | 2 +- .../llms-txt/utils/__tests__/buildApiDocLink.test.mjs | 8 ++++---- src/generators/llms-txt/utils/buildApiDocLink.mjs | 6 +++--- src/linter/constants.mjs | 4 ++-- src/linter/rules/__tests__/missing-metadata.test.mjs | 10 +++++----- src/linter/rules/missing-metadata.mjs | 4 ++-- src/metadata.mjs | 4 ++-- src/types.d.ts | 4 ++-- src/utils/parser/__tests__/index.test.mjs | 4 ++-- src/utils/parser/index.mjs | 1 - 10 files changed, 23 insertions(+), 24 deletions(-) diff --git a/src/__tests__/metadata.test.mjs b/src/__tests__/metadata.test.mjs index 8cafb4ab..fa7f9500 100644 --- a/src/__tests__/metadata.test.mjs +++ b/src/__tests__/metadata.test.mjs @@ -73,7 +73,7 @@ describe('createMetadata', () => { heading, n_api_version: undefined, introduced_in: undefined, - llm_description: undefined, + llmDescription: undefined, removed_in: undefined, slug: 'test-heading', source_link: 'test.com', diff --git a/src/generators/llms-txt/utils/__tests__/buildApiDocLink.test.mjs b/src/generators/llms-txt/utils/__tests__/buildApiDocLink.test.mjs index d9ef8737..31a1335f 100644 --- a/src/generators/llms-txt/utils/__tests__/buildApiDocLink.test.mjs +++ b/src/generators/llms-txt/utils/__tests__/buildApiDocLink.test.mjs @@ -4,9 +4,9 @@ import { describe, it } from 'node:test'; import { getEntryDescription, buildApiDocLink } from '../buildApiDocLink.mjs'; describe('getEntryDescription', () => { - it('returns llm_description when available', () => { + it('returns llmDescription when available', () => { const entry = { - llm_description: 'LLM generated description', + llmDescription: 'LLM generated description', content: { children: [] }, }; @@ -14,7 +14,7 @@ describe('getEntryDescription', () => { assert.equal(result, 'LLM generated description'); }); - it('extracts first paragraph when no llm_description', () => { + it('extracts first paragraph when no llmDescription', () => { const entry = { content: { children: [ @@ -66,7 +66,7 @@ describe('buildApiDocLink', () => { const entry = { heading: { data: { name: 'Test API' } }, api_doc_source: 'doc/api/test.md', - llm_description: 'Test description', + llmDescription: 'Test description', }; const result = buildApiDocLink(entry); diff --git a/src/generators/llms-txt/utils/buildApiDocLink.mjs b/src/generators/llms-txt/utils/buildApiDocLink.mjs index 33b6a0c1..055183f5 100644 --- a/src/generators/llms-txt/utils/buildApiDocLink.mjs +++ b/src/generators/llms-txt/utils/buildApiDocLink.mjs @@ -3,15 +3,15 @@ import { transformNodeToString } from '../../../utils/unist.mjs'; /** * Retrieves the description of a given API doc entry. It first checks whether - * the entry has a llm_description property. If not, it extracts the first + * the entry has a llmDescription property. If not, it extracts the first * paragraph from the entry's content. * * @param {ApiDocMetadataEntry} entry * @returns {string} */ export const getEntryDescription = entry => { - if (entry.llm_description) { - return entry.llm_description; + if (entry.llmDescription) { + return entry.llmDescription; } const descriptionNode = entry.content.children.find( diff --git a/src/linter/constants.mjs b/src/linter/constants.mjs index d32fbf8b..eba94588 100644 --- a/src/linter/constants.mjs +++ b/src/linter/constants.mjs @@ -2,7 +2,7 @@ export const INTRODUCED_IN_REGEX = //; -export const LLM_DESCRIPTION_REGEX = //; +export const llmDescription_REGEX = //; export const LINT_MESSAGES = { missingIntroducedIn: "Missing 'introduced_in' field in the API doc entry", @@ -11,5 +11,5 @@ export const LINT_MESSAGES = { invalidChangeVersion: 'Invalid version number: {{version}}', duplicateStabilityNode: 'Duplicate stability node', missingLlmDescription: - 'Missing llm_description field or paragraph node in the API doc entry', + 'Missing llmDescription field or paragraph node in the API doc entry', }; diff --git a/src/linter/rules/__tests__/missing-metadata.test.mjs b/src/linter/rules/__tests__/missing-metadata.test.mjs index f6cf8d28..1b0eef30 100644 --- a/src/linter/rules/__tests__/missing-metadata.test.mjs +++ b/src/linter/rules/__tests__/missing-metadata.test.mjs @@ -8,14 +8,14 @@ describe('missingMetadata', () => { it('should not report when both fields are present', () => { const context = createContext([ { type: 'html', value: '' }, - { type: 'html', value: '' }, + { type: 'html', value: '' }, ]); missingMetadata(context); strictEqual(context.report.mock.callCount(), 0); }); - it('should report only llm_description when introduced_in is present', () => { + it('should report only llmDescription when introduced_in is present', () => { const context = createContext([ { type: 'html', value: '' }, ]); @@ -25,7 +25,7 @@ describe('missingMetadata', () => { strictEqual(context.report.mock.calls[0].arguments[0].level, 'warn'); }); - it('should not report llm_description when paragraph fallback exists', () => { + it('should not report llmDescription when paragraph fallback exists', () => { const context = createContext([ { type: 'html', value: '' }, { type: 'paragraph', children: [{ type: 'text', value: 'desc' }] }, @@ -42,9 +42,9 @@ describe('missingMetadata', () => { strictEqual(context.report.mock.callCount(), 2); }); - it('should report only introduced_in when llm_description is present', () => { + it('should report only introduced_in when llmDescription is present', () => { const context = createContext([ - { type: 'html', value: '' }, + { type: 'html', value: '' }, ]); missingMetadata(context); diff --git a/src/linter/rules/missing-metadata.mjs b/src/linter/rules/missing-metadata.mjs index f07e3366..8184d7ea 100644 --- a/src/linter/rules/missing-metadata.mjs +++ b/src/linter/rules/missing-metadata.mjs @@ -6,7 +6,7 @@ import { findBefore } from 'unist-util-find-before'; import { INTRODUCED_IN_REGEX, LINT_MESSAGES, - LLM_DESCRIPTION_REGEX, + llmDescription_REGEX, } from '../constants.mjs'; /** @@ -31,7 +31,7 @@ const METADATA_CHECKS = Object.freeze([ }, { name: 'llmDescription', - regex: LLM_DESCRIPTION_REGEX, + regex: llmDescription_REGEX, level: 'warn', message: LINT_MESSAGES.missingLlmDescription, }, diff --git a/src/metadata.mjs b/src/metadata.mjs index b8bac68b..53e95e51 100644 --- a/src/metadata.mjs +++ b/src/metadata.mjs @@ -113,7 +113,7 @@ const createMetadata = slugger => { updates = [], changes = [], tags = [], - llm_description, + llmDescription, } = internalMetadata.properties; // Also add the slug to the heading data as it is used to build the heading @@ -140,7 +140,7 @@ const createMetadata = slugger => { content: section, tags, introduced_in, - llm_description, + llmDescription, yaml_position: internalMetadata.yaml_position, }; }, diff --git a/src/types.d.ts b/src/types.d.ts index ca1bfaa6..b5679c3f 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -65,7 +65,7 @@ declare global { introduced_in?: string; napiVersion?: number; tags?: Array; - llm_description?: string; + llmDescription?: string; } export interface ApiDocMetadataEntry { @@ -101,7 +101,7 @@ declare global { // to provide additional metadata about the API doc entry tags: Array; // The llms.txt specific description - llm_description: string | undefined; + llmDescription: string | undefined; // The postion of the YAML of the API doc entry yaml_position: Position; } diff --git a/src/utils/parser/__tests__/index.test.mjs b/src/utils/parser/__tests__/index.test.mjs index 53b97a0c..e7dc3e04 100644 --- a/src/utils/parser/__tests__/index.test.mjs +++ b/src/utils/parser/__tests__/index.test.mjs @@ -30,7 +30,7 @@ describe('normalizeYamlSyntax', () => { source_link=lib/test.js type=module name=test_module -llm_description=This is a test module`; +llmDescription: This is a test module`; const normalizedYaml = normalizeYamlSyntax(input); @@ -40,7 +40,7 @@ llm_description=This is a test module`; source_link: lib/test.js type: module name: test_module -llm_description: This is a test module` +llmDescription: This is a test module` ); }); diff --git a/src/utils/parser/index.mjs b/src/utils/parser/index.mjs index f964ba1f..e0c86562 100644 --- a/src/utils/parser/index.mjs +++ b/src/utils/parser/index.mjs @@ -41,7 +41,6 @@ export const normalizeYamlSyntax = yamlContent => { .replace('source_link=', 'source_link: ') .replace('type=', 'type: ') .replace('name=', 'name: ') - .replace('llm_description=', 'llm_description: ') .replace(/^[\r\n]+|[\r\n]+$/g, ''); // Remove initial and final line breaks }; From 56c44d9980b6a201d01f300c572364483463cfb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guilherme=20Ara=C3=BAjo?= Date: Wed, 30 Jul 2025 20:44:03 -0300 Subject: [PATCH 2/5] fix: trim descriptions --- src/generators/llms-txt/utils/buildApiDocLink.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/generators/llms-txt/utils/buildApiDocLink.mjs b/src/generators/llms-txt/utils/buildApiDocLink.mjs index 055183f5..6c599ddd 100644 --- a/src/generators/llms-txt/utils/buildApiDocLink.mjs +++ b/src/generators/llms-txt/utils/buildApiDocLink.mjs @@ -11,7 +11,7 @@ import { transformNodeToString } from '../../../utils/unist.mjs'; */ export const getEntryDescription = entry => { if (entry.llmDescription) { - return entry.llmDescription; + return entry.llmDescription.trim(); } const descriptionNode = entry.content.children.find( From 229552cfdf2cab20168ab193bdf81acd23dd8569 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guilherme=20Ara=C3=BAjo?= Date: Wed, 30 Jul 2025 20:58:29 -0300 Subject: [PATCH 3/5] fix: handle multi-line paragraphs --- src/utils/unist.mjs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/utils/unist.mjs b/src/utils/unist.mjs index c365beb5..5ec5fc7b 100644 --- a/src/utils/unist.mjs +++ b/src/utils/unist.mjs @@ -16,8 +16,14 @@ export const transformNodeToString = node => { return `**${transformNodesToString(node.children)}**`; case 'emphasis': return `_${transformNodesToString(node.children)}_`; - default: - return node.children ? transformNodesToString(node.children) : node.value; + default: { + if (node.children) { + return transformNodesToString(node.children); + } + + // Replace line breaks (\n) with spaces to keep text in a single line + return node.value?.replace(/\n/g, ' ') || ''; + } } }; From 3ec833e82562698c3c65c54fc66a0ad184af5484 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guilherme=20Ara=C3=BAjo?= Date: Wed, 30 Jul 2025 21:02:48 -0300 Subject: [PATCH 4/5] refactor: conventions --- src/linter/constants.mjs | 2 +- src/linter/rules/missing-metadata.mjs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/linter/constants.mjs b/src/linter/constants.mjs index eba94588..fc723d2a 100644 --- a/src/linter/constants.mjs +++ b/src/linter/constants.mjs @@ -2,7 +2,7 @@ export const INTRODUCED_IN_REGEX = //; -export const llmDescription_REGEX = //; +export const LLM_DESCRIPTION_REGEX = //; export const LINT_MESSAGES = { missingIntroducedIn: "Missing 'introduced_in' field in the API doc entry", diff --git a/src/linter/rules/missing-metadata.mjs b/src/linter/rules/missing-metadata.mjs index 8184d7ea..f07e3366 100644 --- a/src/linter/rules/missing-metadata.mjs +++ b/src/linter/rules/missing-metadata.mjs @@ -6,7 +6,7 @@ import { findBefore } from 'unist-util-find-before'; import { INTRODUCED_IN_REGEX, LINT_MESSAGES, - llmDescription_REGEX, + LLM_DESCRIPTION_REGEX, } from '../constants.mjs'; /** @@ -31,7 +31,7 @@ const METADATA_CHECKS = Object.freeze([ }, { name: 'llmDescription', - regex: llmDescription_REGEX, + regex: LLM_DESCRIPTION_REGEX, level: 'warn', message: LINT_MESSAGES.missingLlmDescription, }, From 62acb4438e65a921cd4fe32d3caeb92c0e33f617 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guilherme=20Ara=C3=BAjo?= Date: Sat, 2 Aug 2025 14:26:55 -0300 Subject: [PATCH 5/5] Revert "feat: camelize llm_description" This reverts commit 5d65c8af877553ba2147e85211178b493a852e2a. --- src/__tests__/metadata.test.mjs | 2 +- .../llms-txt/utils/__tests__/buildApiDocLink.test.mjs | 8 ++++---- src/generators/llms-txt/utils/buildApiDocLink.mjs | 6 +++--- src/linter/constants.mjs | 4 ++-- src/linter/rules/__tests__/missing-metadata.test.mjs | 10 +++++----- src/metadata.mjs | 4 ++-- src/types.d.ts | 4 ++-- src/utils/parser/__tests__/index.test.mjs | 4 ++-- src/utils/parser/index.mjs | 1 + 9 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/__tests__/metadata.test.mjs b/src/__tests__/metadata.test.mjs index fa7f9500..8cafb4ab 100644 --- a/src/__tests__/metadata.test.mjs +++ b/src/__tests__/metadata.test.mjs @@ -73,7 +73,7 @@ describe('createMetadata', () => { heading, n_api_version: undefined, introduced_in: undefined, - llmDescription: undefined, + llm_description: undefined, removed_in: undefined, slug: 'test-heading', source_link: 'test.com', diff --git a/src/generators/llms-txt/utils/__tests__/buildApiDocLink.test.mjs b/src/generators/llms-txt/utils/__tests__/buildApiDocLink.test.mjs index 31a1335f..d9ef8737 100644 --- a/src/generators/llms-txt/utils/__tests__/buildApiDocLink.test.mjs +++ b/src/generators/llms-txt/utils/__tests__/buildApiDocLink.test.mjs @@ -4,9 +4,9 @@ import { describe, it } from 'node:test'; import { getEntryDescription, buildApiDocLink } from '../buildApiDocLink.mjs'; describe('getEntryDescription', () => { - it('returns llmDescription when available', () => { + it('returns llm_description when available', () => { const entry = { - llmDescription: 'LLM generated description', + llm_description: 'LLM generated description', content: { children: [] }, }; @@ -14,7 +14,7 @@ describe('getEntryDescription', () => { assert.equal(result, 'LLM generated description'); }); - it('extracts first paragraph when no llmDescription', () => { + it('extracts first paragraph when no llm_description', () => { const entry = { content: { children: [ @@ -66,7 +66,7 @@ describe('buildApiDocLink', () => { const entry = { heading: { data: { name: 'Test API' } }, api_doc_source: 'doc/api/test.md', - llmDescription: 'Test description', + llm_description: 'Test description', }; const result = buildApiDocLink(entry); diff --git a/src/generators/llms-txt/utils/buildApiDocLink.mjs b/src/generators/llms-txt/utils/buildApiDocLink.mjs index 6c599ddd..1177e6be 100644 --- a/src/generators/llms-txt/utils/buildApiDocLink.mjs +++ b/src/generators/llms-txt/utils/buildApiDocLink.mjs @@ -3,15 +3,15 @@ import { transformNodeToString } from '../../../utils/unist.mjs'; /** * Retrieves the description of a given API doc entry. It first checks whether - * the entry has a llmDescription property. If not, it extracts the first + * the entry has a llm_description property. If not, it extracts the first * paragraph from the entry's content. * * @param {ApiDocMetadataEntry} entry * @returns {string} */ export const getEntryDescription = entry => { - if (entry.llmDescription) { - return entry.llmDescription.trim(); + if (entry.llm_description) { + return entry.llm_description.trim(); } const descriptionNode = entry.content.children.find( diff --git a/src/linter/constants.mjs b/src/linter/constants.mjs index fc723d2a..d32fbf8b 100644 --- a/src/linter/constants.mjs +++ b/src/linter/constants.mjs @@ -2,7 +2,7 @@ export const INTRODUCED_IN_REGEX = //; -export const LLM_DESCRIPTION_REGEX = //; +export const LLM_DESCRIPTION_REGEX = //; export const LINT_MESSAGES = { missingIntroducedIn: "Missing 'introduced_in' field in the API doc entry", @@ -11,5 +11,5 @@ export const LINT_MESSAGES = { invalidChangeVersion: 'Invalid version number: {{version}}', duplicateStabilityNode: 'Duplicate stability node', missingLlmDescription: - 'Missing llmDescription field or paragraph node in the API doc entry', + 'Missing llm_description field or paragraph node in the API doc entry', }; diff --git a/src/linter/rules/__tests__/missing-metadata.test.mjs b/src/linter/rules/__tests__/missing-metadata.test.mjs index 1b0eef30..f6cf8d28 100644 --- a/src/linter/rules/__tests__/missing-metadata.test.mjs +++ b/src/linter/rules/__tests__/missing-metadata.test.mjs @@ -8,14 +8,14 @@ describe('missingMetadata', () => { it('should not report when both fields are present', () => { const context = createContext([ { type: 'html', value: '' }, - { type: 'html', value: '' }, + { type: 'html', value: '' }, ]); missingMetadata(context); strictEqual(context.report.mock.callCount(), 0); }); - it('should report only llmDescription when introduced_in is present', () => { + it('should report only llm_description when introduced_in is present', () => { const context = createContext([ { type: 'html', value: '' }, ]); @@ -25,7 +25,7 @@ describe('missingMetadata', () => { strictEqual(context.report.mock.calls[0].arguments[0].level, 'warn'); }); - it('should not report llmDescription when paragraph fallback exists', () => { + it('should not report llm_description when paragraph fallback exists', () => { const context = createContext([ { type: 'html', value: '' }, { type: 'paragraph', children: [{ type: 'text', value: 'desc' }] }, @@ -42,9 +42,9 @@ describe('missingMetadata', () => { strictEqual(context.report.mock.callCount(), 2); }); - it('should report only introduced_in when llmDescription is present', () => { + it('should report only introduced_in when llm_description is present', () => { const context = createContext([ - { type: 'html', value: '' }, + { type: 'html', value: '' }, ]); missingMetadata(context); diff --git a/src/metadata.mjs b/src/metadata.mjs index 53e95e51..b8bac68b 100644 --- a/src/metadata.mjs +++ b/src/metadata.mjs @@ -113,7 +113,7 @@ const createMetadata = slugger => { updates = [], changes = [], tags = [], - llmDescription, + llm_description, } = internalMetadata.properties; // Also add the slug to the heading data as it is used to build the heading @@ -140,7 +140,7 @@ const createMetadata = slugger => { content: section, tags, introduced_in, - llmDescription, + llm_description, yaml_position: internalMetadata.yaml_position, }; }, diff --git a/src/types.d.ts b/src/types.d.ts index b5679c3f..ca1bfaa6 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -65,7 +65,7 @@ declare global { introduced_in?: string; napiVersion?: number; tags?: Array; - llmDescription?: string; + llm_description?: string; } export interface ApiDocMetadataEntry { @@ -101,7 +101,7 @@ declare global { // to provide additional metadata about the API doc entry tags: Array; // The llms.txt specific description - llmDescription: string | undefined; + llm_description: string | undefined; // The postion of the YAML of the API doc entry yaml_position: Position; } diff --git a/src/utils/parser/__tests__/index.test.mjs b/src/utils/parser/__tests__/index.test.mjs index e7dc3e04..53b97a0c 100644 --- a/src/utils/parser/__tests__/index.test.mjs +++ b/src/utils/parser/__tests__/index.test.mjs @@ -30,7 +30,7 @@ describe('normalizeYamlSyntax', () => { source_link=lib/test.js type=module name=test_module -llmDescription: This is a test module`; +llm_description=This is a test module`; const normalizedYaml = normalizeYamlSyntax(input); @@ -40,7 +40,7 @@ llmDescription: This is a test module`; source_link: lib/test.js type: module name: test_module -llmDescription: This is a test module` +llm_description: This is a test module` ); }); diff --git a/src/utils/parser/index.mjs b/src/utils/parser/index.mjs index e0c86562..f964ba1f 100644 --- a/src/utils/parser/index.mjs +++ b/src/utils/parser/index.mjs @@ -41,6 +41,7 @@ export const normalizeYamlSyntax = yamlContent => { .replace('source_link=', 'source_link: ') .replace('type=', 'type: ') .replace('name=', 'name: ') + .replace('llm_description=', 'llm_description: ') .replace(/^[\r\n]+|[\r\n]+$/g, ''); // Remove initial and final line breaks };