Skip to content

fix(qwen): generate Markdown commands instead of deprecated TOML format#1191

Open
mc856 wants to merge 1 commit into
Fission-AI:mainfrom
mc856:fix/qwen-markdown-commands
Open

fix(qwen): generate Markdown commands instead of deprecated TOML format#1191
mc856 wants to merge 1 commit into
Fission-AI:mainfrom
mc856:fix/qwen-markdown-commands

Conversation

@mc856

@mc856 mc856 commented Jun 9, 2026

Copy link
Copy Markdown

Summary

Qwen Code has deprecated TOML custom commands in favor of Markdown files with YAML frontmatter (docs). This PR updates the Qwen adapter to emit .qwen/commands/opsx-<id>.md and registers the old opsx-*.toml files as legacy artifacts so they are cleaned up on update.

Closes #838

Changes

  • qwen adapter now generates Markdown with description frontmatter (YAML values escaped, same as the pi/claude adapters)
  • legacy-cleanup gains a .qwen/commands/opsx-*.toml pattern, following the junie/opencode precedent
  • Tests updated for the new format, plus a YAML-escaping test and a negative test ensuring new .md files are not flagged as legacy
  • docs/supported-tools.md and changeset updated

Design decisions

  • Args handling: the Markdown format supports {{args}}, but the old TOML adapter relied on Qwen's default argument appending, so this PR intentionally preserves that behavior for parity. Explicit {{args}} injection could be a follow-up.
  • Migration path: existing .qwen/commands/opsx-*.toml files are cleaned up via the same legacy-cleanup mechanism used for the junie/opencode migrations (interactive confirm on update, auto with --force). Non-interactive runs without --force warn and leave the old files in place, matching existing behavior.

Testing

  • npx vitest run — full suite passes (1664 tests)
  • npx eslint clean on changed files

Generated with Claude (Cowork) using claude-fable-5.

Summary by CodeRabbit

  • New Features

    • Qwen Code commands now use Markdown format with YAML frontmatter instead of TOML for improved readability and compatibility.
  • Bug Fixes

    • Enhanced detection of legacy TOML command files to ensure proper cleanup during updates.
  • Documentation

    • Updated Qwen Code command path documentation to reflect the new Markdown file format.

Qwen Code deprecated TOML custom commands in favor of Markdown files
with YAML frontmatter. Update the Qwen adapter to emit
.qwen/commands/opsx-<id>.md and register the old opsx-*.toml files as
legacy artifacts so they are cleaned up on update.

Closes Fission-AI#838

Generated with Claude (Cowork) using claude-fable-5; tested with the
full vitest suite (1663 tests passing).
@mc856 mc856 requested a review from TabishB as a code owner June 9, 2026 19:01
@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR migrates Qwen command generation from deprecated TOML format to Markdown with YAML frontmatter, aligning with Qwen Code's current recommendations. The adapter gains a YAML-safe value escaper, legacy TOML artifacts are marked for cleanup detection, and tests are updated to validate the new format and proper character escaping.

Changes

Qwen Markdown Command Format Migration

Layer / File(s) Summary
Markdown adapter with YAML frontmatter
src/core/command-generation/adapters/qwen.ts, test/core/command-generation/adapters.test.ts
Qwen adapter now generates .md files instead of .toml. A new escapeYamlValue helper safely quotes description values with YAML-special characters. Module docs and tests updated to validate YAML frontmatter output with proper escaping.
Legacy TOML cleanup infrastructure
src/core/legacy-cleanup.ts, test/core/legacy-cleanup.test.ts
Legacy command path detection expanded to match both opsx-*.toml and openspec-*.toml patterns. Tests confirm old TOML files are flagged as legacy while new Markdown files are not.
Documentation and integration updates
.changeset/qwen-markdown-commands.md, docs/supported-tools.md, test/core/update.test.ts
Changeset documents the format migration. Supported tools table shows .md extension. Integration test validates generated commands produce Markdown with YAML frontmatter delimiters and description: fields.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • Fission-AI/OpenSpec#250: Adds Qwen slash command configurators that generate .qwen/commands entries with YAML frontmatter, directly aligning with this PR's format migration.
  • Fission-AI/OpenSpec#317: Previous Qwen command-config generation logic that this PR replaces by switching from TOML to Markdown format with YAML frontmatter.

Suggested reviewers

  • TabishB
  • alfred-openspec

Poem

🐰 From TOML's old nest to Markdown's bright home,
YAML frontmatter now guides each custom command roam,
Escaping special characters with careful, patient care,
Qwen Code hops forward—the format fresh and fair! 📝✨

🚥 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 PR title accurately describes the main change: updating Qwen adapter to generate Markdown commands instead of deprecated TOML format.
Linked Issues check ✅ Passed All coding requirements from issue #838 are met: file path changed to .md, content format changed to YAML frontmatter, YAML escaping implemented, and legacy TOML files cleanup configured.
Out of Scope Changes check ✅ Passed All changes are directly related to converting Qwen commands from TOML to Markdown format and handling legacy cleanup as specified in issue #838.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 and usage tips.

@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.

Actionable comments posted: 1

🤖 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.

Inline comments:
In `@src/core/command-generation/adapters/qwen.ts`:
- Around line 18-27: The escapeYamlValue function correctly detects values
needing quotes but doesn't escape carriage returns; update the escaping step in
escapeYamlValue to also replace '\r' with '\\r' (in addition to existing
backslash, double-quote, and newline escapes) so double-quoted YAML strings
conform to YAML 1.2; modify the replace chain inside escapeYamlValue
accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c1c9ab7b-d581-4492-929b-bc9c6e993f95

📥 Commits

Reviewing files that changed from the base of the PR and between 1b06fdd and c19d629.

📒 Files selected for processing (7)
  • .changeset/qwen-markdown-commands.md
  • docs/supported-tools.md
  • src/core/command-generation/adapters/qwen.ts
  • src/core/legacy-cleanup.ts
  • test/core/command-generation/adapters.test.ts
  • test/core/legacy-cleanup.test.ts
  • test/core/update.test.ts

Comment on lines +18 to +27
function escapeYamlValue(value: string): string {
// Check if value needs quoting (contains special YAML characters or starts/ends with whitespace)
const needsQuoting = /[:\n\r#{}[\],&*!|>'"%@`]|^\s|\s$/.test(value);
if (needsQuoting) {
// Use double quotes and escape internal double quotes and backslashes
const escaped = value.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n');
return `"${escaped}"`;
}
return value;
}

@coderabbitai coderabbitai Bot Jun 9, 2026

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add carriage return escape sequence for complete YAML compliance.

The regex on line 20 correctly detects \r as requiring quotes, but line 23 doesn't escape it. Per the YAML 1.2 specification, carriage returns inside double-quoted strings must be escaped. While carriage returns are rare in description strings, adding the escape ensures valid YAML output.

🛡️ Proposed fix to add carriage return escaping
   // Use double quotes and escape internal double quotes and backslashes
-  const escaped = value.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n');
+  const escaped = value.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n').replace(/\r/g, '\\r');
   return `"${escaped}"`;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function escapeYamlValue(value: string): string {
// Check if value needs quoting (contains special YAML characters or starts/ends with whitespace)
const needsQuoting = /[:\n\r#{}[\],&*!|>'"%@`]|^\s|\s$/.test(value);
if (needsQuoting) {
// Use double quotes and escape internal double quotes and backslashes
const escaped = value.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n');
return `"${escaped}"`;
}
return value;
}
function escapeYamlValue(value: string): string {
// Check if value needs quoting (contains special YAML characters or starts/ends with whitespace)
const needsQuoting = /[:\n\r#{}[\],&*!|>'"%@`]|^\s|\s$/.test(value);
if (needsQuoting) {
// Use double quotes and escape internal double quotes and backslashes
const escaped = value.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n').replace(/\r/g, '\\r');
return `"${escaped}"`;
}
return value;
}
🤖 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 `@src/core/command-generation/adapters/qwen.ts` around lines 18 - 27, The
escapeYamlValue function correctly detects values needing quotes but doesn't
escape carriage returns; update the escaping step in escapeYamlValue to also
replace '\r' with '\\r' (in addition to existing backslash, double-quote, and
newline escapes) so double-quoted YAML strings conform to YAML 1.2; modify the
replace chain inside escapeYamlValue accordingly.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good catch on the detect/escape asymmetry. This function is an intentional verbatim copy of the existing escapeYamlValue in pi.ts and claude.ts (current convention is one copy per adapter), so the same gap exists in both siblings. Since descriptions come from internal command templates, a literal CR can't occur in practice. Rather than diverging this copy from the other two or expanding this PR's scope, I'd prefer a follow-up PR that extracts escapeYamlValue into a shared util and adds \r escaping in one place. Happy to fix it here instead if maintainers prefer.

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.

@mc856, that context makes sense — the detect/escape asymmetry is a pre-existing gap shared across all three adapters, and fixing it consistently via a shared utility is the right approach. Happy to leave this PR as-is.

Would you like me to open a follow-up GitHub issue to track:

  • Extracting escapeYamlValue into a shared YAML utility
  • Adding \r → \\r escaping in that shared location
  • Updating pi.ts, claude.ts, and qwen.ts to consume it

Just let me know and I'll file it.


✏️ Learnings added
Learnt from: mc856
Repo: Fission-AI/OpenSpec PR: 1191
File: src/core/command-generation/adapters/qwen.ts:18-27
Timestamp: 2026-06-09T19:17:10.070Z
Learning: In the OpenSpec repository, `escapeYamlValue` is intentionally duplicated per adapter file (e.g., `src/core/command-generation/adapters/pi.ts`, `claude.ts`, `qwen.ts`) — one copy per adapter is the current convention. There is a known detect/escape asymmetry where `\r` is detected as needing quoting but is not escaped in the replace chain; this gap exists in all three siblings and is tracked for a future refactor that would extract the helper into a shared util.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

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.

OpenSpec generates deprecated TOML commands for Qwen Code

1 participant