diff --git a/get-shit-done/bin/lib/frontmatter.cjs b/get-shit-done/bin/lib/frontmatter.cjs index 3ea7f1334..f8e4ee7fc 100644 --- a/get-shit-done/bin/lib/frontmatter.cjs +++ b/get-shit-done/bin/lib/frontmatter.cjs @@ -8,6 +8,38 @@ const { safeReadFile, normalizeMd, output, error } = require('./core.cjs'); // ─── Parsing engine ─────────────────────────────────────────────────────────── +/** + * Split a YAML inline array body on commas, respecting quoted strings. + * e.g. '"a, b", c' → ['a, b', 'c'] + */ +function splitInlineArray(body) { + const items = []; + let current = ''; + let inQuote = null; // null | '"' | "'" + + for (let i = 0; i < body.length; i++) { + const ch = body[i]; + if (inQuote) { + if (ch === inQuote) { + inQuote = null; + } else { + current += ch; + } + } else if (ch === '"' || ch === "'") { + inQuote = ch; + } else if (ch === ',') { + const trimmed = current.trim(); + if (trimmed) items.push(trimmed); + current = ''; + } else { + current += ch; + } + } + const trimmed = current.trim(); + if (trimmed) items.push(trimmed); + return items; +} + function extractFrontmatter(content) { const frontmatter = {}; // Find ALL frontmatter blocks at the start of the file. @@ -53,8 +85,8 @@ function extractFrontmatter(content) { // Push new context for potential nested content stack.push({ obj: current.obj[key], key: null, indent }); } else if (value.startsWith('[') && value.endsWith(']')) { - // Inline array: key: [a, b, c] - current.obj[key] = value.slice(1, -1).split(',').map(s => s.trim().replace(/^["']|["']$/g, '')).filter(Boolean); + // Inline array: key: [a, b, c] — quote-aware split (REG-04 fix) + current.obj[key] = splitInlineArray(value.slice(1, -1)); current.key = null; } else { // Simple key: value diff --git a/tests/frontmatter.test.cjs b/tests/frontmatter.test.cjs index 8cacd08c3..2cbe219d4 100644 --- a/tests/frontmatter.test.cjs +++ b/tests/frontmatter.test.cjs @@ -54,21 +54,22 @@ describe('extractFrontmatter', () => { assert.deepStrictEqual(result.key, ['a', 'b', 'c']); }); - test('handles quoted commas in inline arrays — REG-04 known limitation', () => { - // REG-04: The split(',') on line 53 does NOT respect quotes. - // The parser WILL split on commas inside quotes, producing wrong results. - // This test documents the CURRENT (buggy) behavior. + test('handles quoted commas in inline arrays — REG-04 fixed', () => { const content = '---\nkey: ["a, b", c]\n---\n'; const result = extractFrontmatter(content); - // Current behavior: splits on ALL commas, producing 3 items instead of 2 - // Expected correct behavior would be: ["a, b", "c"] - // Actual current behavior: ["a", "b", "c"] (split ignores quotes) - assert.ok(Array.isArray(result.key), 'should produce an array'); - assert.ok(result.key.length >= 2, 'should produce at least 2 items from comma split'); - // The bug produces ["a", "b\"", "c"] or similar — the exact output depends on - // how the regex strips quotes after the split. - // We verify the key insight: the result has MORE items than intended (known limitation). - assert.ok(result.key.length > 2, 'REG-04: split produces more items than intended due to quoted comma bug'); + assert.deepStrictEqual(result.key, ['a, b', 'c']); + }); + + test('handles single-quoted commas in inline arrays', () => { + const content = "---\nkey: ['x, y', z]\n---\n"; + const result = extractFrontmatter(content); + assert.deepStrictEqual(result.key, ['x, y', 'z']); + }); + + test('handles mixed quotes in inline arrays', () => { + const content = '---\nkey: ["a, b", \'c, d\', e]\n---\n'; + const result = extractFrontmatter(content); + assert.deepStrictEqual(result.key, ['a, b', 'c, d', 'e']); }); test('returns empty object for no frontmatter', () => {