Skip to content

fix(command-generation): escape YAML frontmatter name in codebuddy/crush/lingma/qoder#1330

Open
seattled23 wants to merge 1 commit into
Fission-AI:mainfrom
seattled23:fix/yaml-frontmatter-escaping
Open

fix(command-generation): escape YAML frontmatter name in codebuddy/crush/lingma/qoder#1330
seattled23 wants to merge 1 commit into
Fission-AI:mainfrom
seattled23:fix/yaml-frontmatter-escaping

Conversation

@seattled23

@seattled23 seattled23 commented Jul 8, 2026

Copy link
Copy Markdown

Problem

The codebuddy, crush, lingma, and qoder adapters interpolate name: ${content.name} raw into command frontmatter. Every OPSX command name contains a colon (OPSX: Explore, OPSX: Apply, …), so the emitted frontmatter is:

name: OPSX: Explore

which is invalid YAML — parsers throw mapping values are not allowed here (reproduced). On a default openspec init for any of those 4 tools, the generated slash-commands ship broken into the user's repo.

Fix

Route the field through the existing escapeYamlValue helper — the same one claude / windsurf / trae already use. OPSX: Explore"OPSX: Explore", valid YAML, value preserved.

The prior escapeYamlValue refactor (#1204 / #1205) wired the helper into pi / claude / qwen but didn't reach these 4 adapters.

Test

Adds tests that feed a colon-bearing name through each of the 4 adapters, extract the frontmatter, parse it with the real yaml library, and assert the name round-trips.

Note on scope (follow-up, not in this PR)

This PR fixes the field that actually breaks today — name, because it's the one carrying a colon. For completeness: ~11 other adapters still interpolate description: ${content.description} raw (amazon-q, antigravity, auggie, codex, continue, factory, github-copilot, iflow, junie, kiro, opencode), and iflow also emits category: ${content.category} raw. None break today (no current description/category value contains a colon), so it's a latent instance of the same class rather than a live bug. Happy to follow up with a sweep if you'd like it in one pass.

Co-authored-by: Sōren Vale soren@tessara.us

Summary by CodeRabbit

  • Bug Fixes
    • Improved generated command files so titles, descriptions, categories, and tags are safely escaped in YAML.
    • Fixed cases where special characters like colons could break command metadata rendering.
  • Tests
    • Added coverage to verify generated command metadata remains valid YAML.
    • Updated expectations to match the new escaping behavior.

The codebuddy, crush, lingma, and qoder adapters interpolated
`name: ${content.name}` raw into command frontmatter. Every OPSX command name
contains a colon ("OPSX: Explore", "OPSX: Apply", ...), so the emitted
frontmatter is `name: OPSX: Explore` — invalid YAML ("mapping values are not
allowed here"). On a default `openspec init` for any of those 4 tools, the
generated slash-commands ship broken to the user's repo.

Fix routes the field through the existing `escapeYamlValue` helper, matching
the claude/windsurf/trae adapters. The prior escapeYamlValue refactor
(Fission-AI#1204/Fission-AI#1205) wired the helper into pi/claude/qwen but did not reach these 4.
Adds tests that parse each adapter's frontmatter with the real YAML library and
assert the name round-trips.

Co-authored-by: Sōren Vale <soren@tessara.us>
@seattled23 seattled23 requested a review from TabishB as a code owner July 8, 2026 17:21
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Four command-generation adapters (CodeBuddy, Crush, Lingma, Qoder) now import and apply escapeYamlValue to sanitize name, description, category, and tags fields when generating YAML frontmatter, replacing direct string interpolation. Tests are updated to reflect new escaping behavior and add YAML parseability validation.

Changes

YAML frontmatter escaping across adapters

Layer / File(s) Summary
CodeBuddy escaping
src/core/command-generation/adapters/codebuddy.ts
Imports escapeYamlValue and applies it to name and description frontmatter fields.
Crush escaping
src/core/command-generation/adapters/crush.ts
Imports escapeYamlValue and applies it to name, description, category, and each tag.
Lingma escaping
src/core/command-generation/adapters/lingma.ts
Imports escapeYamlValue and applies it to name, description, category, and each tag in formatFile.
Qoder escaping
src/core/command-generation/adapters/qoder.ts
Imports escapeYamlValue and applies it to name, description, category, and each tag.
Adapter test updates
test/core/command-generation/adapters.test.ts
Imports yaml's parse and lingmaAdapter, updates the codebuddy description assertion to expect unquoted output, and adds a new suite verifying that adapters produce parseable YAML frontmatter preserving colon-containing name values.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

  • Fission-AI/OpenSpec#1240: Both PRs depend on the shared escapeYamlValue utility for generating YAML frontmatter safely across adapters.
  • Fission-AI/OpenSpec#1276: Both PRs update YAML frontmatter escaping via escapeYamlValue and extend the same adapter test suite.

Suggested reviewers: TabishB

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: escaping YAML frontmatter in the command-generation adapters.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
test/core/command-generation/adapters.test.ts (1)

844-879: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider expanding coverage to other escaped fields.

The suite name says "colon-bearing fields" (plural) but only name is tested with a colon. Since description, category, and tags are also now routed through escapeYamlValue, adding at least one case where those fields contain YAML-special characters (e.g., colons, brackets) would strengthen the round-trip guarantee and catch regressions if an adapter reverts to raw interpolation.

♻️ Suggested additional test case
     it(`${name} emits parseable YAML frontmatter when the name contains a colon`, () => {
       const contentWithColon: CommandContent = {
         ...sampleContent,
         name: 'OPSX: Explore',
       };

       const output = adapter.formatFile(contentWithColon);
       const frontmatter = extractFrontmatter(output);

       // Must parse without throwing and preserve the exact name (colon intact).
       const parsed = parseYaml(frontmatter) as Record<string, unknown>;
       expect(parsed.name).toBe('OPSX: Explore');
     });
+
+    it(`${name} preserves all fields when multiple contain YAML-special characters`, () => {
+      const contentWithSpecialChars: CommandContent = {
+        ...sampleContent,
+        name: 'OPSX: Explore',
+        description: 'Explore: think, plan & act',
+        category: 'Workflow: Core',
+        tags: ['opsx:explore', 'plan & act'],
+      };
+
+      const output = adapter.formatFile(contentWithSpecialChars);
+      const frontmatter = extractFrontmatter(output);
+
+      const parsed = parseYaml(frontmatter) as Record<string, unknown>;
+      expect(parsed.name).toBe('OPSX: Explore');
+      expect(parsed.description).toBe('Explore: think, plan & act');
+      expect(parsed.category).toBe('Workflow: Core');
+      expect(parsed.tags).toEqual(['opsx:explore', 'plan & act']);
+    });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/core/command-generation/adapters.test.ts` around lines 844 - 879, Expand
the YAML frontmatter coverage in the adapters test by adding cases for other
escaped fields besides `name`, since `description`, `category`, and `tags` also
flow through `escapeYamlValue`. Update the
`yamlFrontmatterAdapters`/`extractFrontmatter` test block to include at least
one input where `description`, `category`, or `tags` contains YAML-special
characters like colons or brackets, then parse the emitted frontmatter with
`parseYaml` and assert the value round-trips correctly. Use the existing adapter
helpers (`formatFile`, `parseYaml`, `extractFrontmatter`) so the new test stays
aligned with the current colon-handling checks.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@test/core/command-generation/adapters.test.ts`:
- Around line 844-879: Expand the YAML frontmatter coverage in the adapters test
by adding cases for other escaped fields besides `name`, since `description`,
`category`, and `tags` also flow through `escapeYamlValue`. Update the
`yamlFrontmatterAdapters`/`extractFrontmatter` test block to include at least
one input where `description`, `category`, or `tags` contains YAML-special
characters like colons or brackets, then parse the emitted frontmatter with
`parseYaml` and assert the value round-trips correctly. Use the existing adapter
helpers (`formatFile`, `parseYaml`, `extractFrontmatter`) so the new test stays
aligned with the current colon-handling checks.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 37644f05-a474-4dbf-942d-8d3ff54dc113

📥 Commits

Reviewing files that changed from the base of the PR and between 93e27a7 and c12a8a5.

📒 Files selected for processing (5)
  • src/core/command-generation/adapters/codebuddy.ts
  • src/core/command-generation/adapters/crush.ts
  • src/core/command-generation/adapters/lingma.ts
  • src/core/command-generation/adapters/qoder.ts
  • test/core/command-generation/adapters.test.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant