diff --git a/scripts/gen-skill-docs.ts b/scripts/gen-skill-docs.ts index b348c5162..fbff381b7 100644 --- a/scripts/gen-skill-docs.ts +++ b/scripts/gen-skill-docs.ts @@ -413,6 +413,7 @@ export function toYamlInlineScalar(s: string): string { s !== s.trim() || // leading/trailing whitespace /:(\s|$)/.test(s) || // "foo: bar" / trailing colon → mapping ambiguity /\s#/.test(s) || // " #" → inline comment + /\.\.\./.test(s) || // "..." → document-end marker; strict parsers reject mid-scalar (catalog-trim truncation appends it) /^[\s>|&*!%@`"'#,\[\]{}?-]/.test(s); // leading YAML indicator char return needsQuote ? JSON.stringify(s) : s; } diff --git a/test/catalog-trim.test.ts b/test/catalog-trim.test.ts index 6ff8cb4dd..79380c341 100644 --- a/test/catalog-trim.test.ts +++ b/test/catalog-trim.test.ts @@ -23,8 +23,37 @@ import { buildTrimmedDescription, buildWhenToInvokeSection, applyCatalogTrim, + toYamlInlineScalar, } from '../scripts/gen-skill-docs'; +describe('toYamlInlineScalar', () => { + const parses = (out: string) => Bun.YAML.parse(`d: ${out}`); + + test("scalar containing '...' (YAML document-end marker) is quoted and round-trips", () => { + const out = toYamlInlineScalar('Truncated lead ends with... more (gstack)'); + expect(out.startsWith('"')).toBe(true); + expect((parses(out) as { d: string }).d).toContain('...'); + }); + + test("interior ': ' is quoted (nested-mapping ambiguity, #1778)", () => { + const out = toYamlInlineScalar('Ship workflow: detect and merge'); + expect(out.startsWith('"')).toBe(true); + expect((parses(out) as { d: string }).d).toBe('Ship workflow: detect and merge'); + }); + + test('plain safe scalar passes through unquoted', () => { + expect(toYamlInlineScalar('Simple description here')).toBe('Simple description here'); + }); + + test('leading YAML indicator char is quoted', () => { + expect(toYamlInlineScalar('- leading dash').startsWith('"')).toBe(true); + }); + + test('trailing whitespace is quoted', () => { + expect(toYamlInlineScalar('has trailing space ').startsWith('"')).toBe(true); + }); +}); + describe('splitCatalogDescription', () => { test('extracts lead sentence + routing prose from simple multi-line description', () => { const desc =