Replace description regex with yaml.parse/stringify in codex.js

Use the yaml library (already a project dependency) instead of a
hand-rolled regex to parse and re-serialize frontmatter descriptions,
matching the pattern used in manifest-generator.js. Update tests to
validate round-trip correctness rather than exact quoting style.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Wendy Smoak 2026-02-15 07:13:30 -05:00
parent 43672d33c1
commit 7f81518896
2 changed files with 53 additions and 32 deletions

View File

@ -1,12 +1,14 @@
/** /**
* Tests for CodexSetup.transformToSkillFormat * Tests for CodexSetup.transformToSkillFormat
* *
* Demonstrates that the description regex mangles descriptions containing quotes. * Validates that descriptions round-trip correctly through parse/stringify,
* producing valid YAML regardless of input quoting style.
* *
* Usage: node test/test-codex-transform.js * Usage: node test/test-codex-transform.js
*/ */
const path = require('node:path'); const path = require('node:path');
const yaml = require('yaml');
// ANSI colors // ANSI colors
const colors = { const colors = {
@ -31,6 +33,17 @@ function assert(condition, testName, detail) {
} }
} }
/**
* Parse the output frontmatter and return the description value.
* Validates the output is well-formed YAML that parses back correctly.
*/
function parseOutputDescription(output) {
const match = output.match(/^---\n([\s\S]*?)\n---/);
if (!match) return null;
const parsed = yaml.parse(match[1]);
return parsed?.description;
}
// Import the class under test // Import the class under test
const { CodexSetup } = require(path.join(__dirname, '..', 'tools', 'cli', 'installers', 'lib', 'ide', 'codex.js')); const { CodexSetup } = require(path.join(__dirname, '..', 'tools', 'cli', 'installers', 'lib', 'ide', 'codex.js'));
@ -38,39 +51,56 @@ const setup = new CodexSetup();
console.log(`\n${colors.cyan}CodexSetup.transformToSkillFormat tests${colors.reset}\n`); console.log(`\n${colors.cyan}CodexSetup.transformToSkillFormat tests${colors.reset}\n`);
// --- Passing case: simple description, no quotes --- // --- Simple description, no quotes ---
{ {
const input = `---\ndescription: A simple description\n---\n\nBody content here.`; const input = `---\ndescription: A simple description\n---\n\nBody content here.`;
const result = setup.transformToSkillFormat(input, 'my-skill'); const result = setup.transformToSkillFormat(input, 'my-skill');
const expected = `---\nname: my-skill\ndescription: 'A simple description'\n---\n\nBody content here.`; const desc = parseOutputDescription(result);
assert(result === expected, 'simple description without quotes', `got: ${JSON.stringify(result)}`); assert(desc === 'A simple description', 'simple description round-trips', `got description: ${JSON.stringify(desc)}`);
assert(result.includes('\nBody content here.'), 'body preserved for simple description');
} }
// --- Description with embedded single quotes (from double-quoted YAML input) --- // --- Description with embedded single quotes (from double-quoted YAML input) ---
{ {
const input = `---\ndescription: "can't stop won't stop"\n---\n\nBody content here.`; const input = `---\ndescription: "can't stop won't stop"\n---\n\nBody content here.`;
const result = setup.transformToSkillFormat(input, 'my-skill'); const result = setup.transformToSkillFormat(input, 'my-skill');
const desc = parseOutputDescription(result);
// Output should have properly escaped YAML single-quoted scalar: '' for each ' assert(desc === "can't stop won't stop", 'description with apostrophes round-trips', `got description: ${JSON.stringify(desc)}`);
const expected = `---\nname: my-skill\ndescription: 'can''t stop won''t stop'\n---\n\nBody content here.`; assert(result.includes('\nBody content here.'), 'body preserved for quoted description');
assert(result === expected, 'description with embedded single quotes produces valid escaped YAML', `got: ${JSON.stringify(result)}`);
} }
// --- Description with embedded single quote produces valid YAML --- // --- Description with embedded single quote ---
{ {
const input = `---\ndescription: "it's a test"\n---\n\nBody.`; const input = `---\ndescription: "it's a test"\n---\n\nBody.`;
const result = setup.transformToSkillFormat(input, 'test-skill'); const result = setup.transformToSkillFormat(input, 'test-skill');
const expected = `---\nname: test-skill\ndescription: 'it''s a test'\n---\n\nBody.`; const desc = parseOutputDescription(result);
assert(result === expected, 'description with apostrophe produces valid YAML', `got: ${JSON.stringify(result)}`); assert(desc === "it's a test", 'description with apostrophe round-trips', `got description: ${JSON.stringify(desc)}`);
} }
// --- Single-quoted input with pre-escaped apostrophe (YAML '' escape) --- // --- Single-quoted input with pre-escaped apostrophe (YAML '' escape) ---
{ {
const input = `---\ndescription: 'don''t panic'\n---\n\nBody.`; const input = `---\ndescription: 'don''t panic'\n---\n\nBody.`;
const result = setup.transformToSkillFormat(input, 'test-skill'); const result = setup.transformToSkillFormat(input, 'test-skill');
// Input has don''t (YAML-escaped). Should round-trip to don''t in output. const desc = parseOutputDescription(result);
const expected = `---\nname: test-skill\ndescription: 'don''t panic'\n---\n\nBody.`; assert(desc === "don't panic", 'single-quoted escaped apostrophe round-trips', `got description: ${JSON.stringify(desc)}`);
assert(result === expected, 'single-quoted description with escaped apostrophe round-trips correctly', `got: ${JSON.stringify(result)}`); }
// --- Verify name is set correctly ---
{
const input = `---\ndescription: test\n---\n\nBody.`;
const result = setup.transformToSkillFormat(input, 'my-custom-skill');
const match = result.match(/^---\n([\s\S]*?)\n---/);
const parsed = yaml.parse(match[1]);
assert(parsed.name === 'my-custom-skill', 'name field matches skillName argument', `got name: ${JSON.stringify(parsed.name)}`);
}
// --- No frontmatter wraps content ---
{
const input = 'Just some content without frontmatter.';
const result = setup.transformToSkillFormat(input, 'bare-skill');
const desc = parseOutputDescription(result);
assert(desc === 'bare-skill', 'no-frontmatter fallback uses skillName as description', `got description: ${JSON.stringify(desc)}`);
assert(result.includes('Just some content without frontmatter.'), 'body preserved when no frontmatter');
} }
// --- Summary --- // --- Summary ---

View File

@ -7,6 +7,7 @@ const { AgentCommandGenerator } = require('./shared/agent-command-generator');
const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator'); const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator');
const { getTasksFromBmad } = require('./shared/bmad-artifacts'); const { getTasksFromBmad } = require('./shared/bmad-artifacts');
const { toDashPath, customAgentDashName } = require('./shared/path-utils'); const { toDashPath, customAgentDashName } = require('./shared/path-utils');
const yaml = require('yaml');
const prompts = require('../../../lib/prompts'); const prompts = require('../../../lib/prompts');
/** /**
@ -205,28 +206,18 @@ class CodexSetup extends BaseIdeSetup {
const frontmatter = fmMatch[1]; const frontmatter = fmMatch[1];
const body = fmMatch[2]; const body = fmMatch[2];
// Extract description from existing frontmatter, handling quoted and unquoted values // Parse frontmatter with yaml library to handle all quoting variants
const descMatch = frontmatter.match(/^description:\s*(?:'((?:[^']|'')*)'|"((?:[^"\\]|\\.)*)"|(.*))\s*$/m);
let description; let description;
if (descMatch) { try {
if (descMatch[1] != null) { const parsed = yaml.parse(frontmatter);
// Single-quoted YAML: unescape '' to ' description = parsed?.description || `${skillName} skill`;
description = descMatch[1].replaceAll("''", "'"); } catch {
} else if (descMatch[2] == null) {
description = descMatch[3];
} else {
// Double-quoted YAML: unescape \" to "
description = descMatch[2].replaceAll(String.raw`\"`, '"');
}
} else {
description = `${skillName} skill`; description = `${skillName} skill`;
} }
// Escape single quotes for YAML single-quoted scalar (a literal ' becomes '') // Build new frontmatter with only skills-spec fields, let yaml handle quoting
const safeDescription = description.replaceAll("'", "''"); const newFrontmatter = yaml.stringify({ name: skillName, description }).trimEnd();
return `---\n${newFrontmatter}\n---\n${body}`;
// Build new frontmatter with only skills-spec fields
return `---\nname: ${skillName}\ndescription: '${safeDescription}'\n---\n${body}`;
} }
/** /**