Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/scripts/create-release-packages.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@ function Build-Variant {
}
'qwen' {
$cmdDir = Join-Path $baseDir ".qwen/commands"
Generate-Commands -Agent 'qwen' -Extension 'toml' -ArgFormat '{{args}}' -OutputDir $cmdDir -ScriptVariant $Script
Generate-Commands -Agent 'qwen' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script
if (Test-Path "agent_templates/qwen/QWEN.md") {
Copy-Item -Path "agent_templates/qwen/QWEN.md" -Destination (Join-Path $baseDir "QWEN.md")
}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/scripts/create-release-packages.sh
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ build_variant() {
generate_commands cursor-agent md "\$ARGUMENTS" "$base_dir/.cursor/commands" "$script" ;;
qwen)
mkdir -p "$base_dir/.qwen/commands"
generate_commands qwen toml "{{args}}" "$base_dir/.qwen/commands" "$script"
generate_commands qwen md "\$ARGUMENTS" "$base_dir/.qwen/commands" "$script"
[[ -f agent_templates/qwen/QWEN.md ]] && cp agent_templates/qwen/QWEN.md "$base_dir/QWEN.md" ;;
opencode)
mkdir -p "$base_dir/.opencode/command"
Expand Down
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Specify supports multiple AI agents by generating agent-specific command files a
| **Gemini CLI** | `.gemini/commands/` | TOML | `gemini` | Google's Gemini CLI |
| **GitHub Copilot** | `.github/agents/` | Markdown | N/A (IDE-based) | GitHub Copilot in VS Code |
| **Cursor** | `.cursor/commands/` | Markdown | `cursor-agent` | Cursor CLI |
| **Qwen Code** | `.qwen/commands/` | TOML | `qwen` | Alibaba's Qwen Code CLI |
| **Qwen Code** | `.qwen/commands/` | Markdown | `qwen` | Alibaba's Qwen Code CLI |
| **opencode** | `.opencode/command/` | Markdown | `opencode` | opencode CLI |
| **Codex CLI** | `.codex/commands/` | Markdown | `codex` | Codex CLI |
| **Windsurf** | `.windsurf/workflows/` | Markdown | N/A (IDE-based) | Windsurf IDE workflows |
Expand Down Expand Up @@ -339,7 +339,7 @@ Work within integrated development environments:

### Markdown Format

Used by: Claude, Cursor, opencode, Windsurf, Kiro CLI, Amp, SHAI, IBM Bob, Kimi Code
Used by: Claude, Cursor, opencode, Windsurf, Kiro CLI, Amp, SHAI, IBM Bob, Kimi Code, Qwen

**Standard format:**

Expand All @@ -364,7 +364,7 @@ Command content with {SCRIPT} and $ARGUMENTS placeholders.

### TOML Format

Used by: Gemini, Qwen, Tabnine
Used by: Gemini, Tabnine

```toml
description = "Command description"
Expand Down
6 changes: 3 additions & 3 deletions src/specify_cli/extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -692,9 +692,9 @@ class CommandRegistrar:
},
"qwen": {
"dir": ".qwen/commands",
"format": "toml",
"args": "{{args}}",
"extension": ".toml"
"format": "markdown",
"args": "$ARGUMENTS",
"extension": ".md"
},
"opencode": {
"dir": ".opencode/command",
Expand Down
41 changes: 41 additions & 0 deletions tests/test_ai_skills.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,16 @@ def commands_dir_gemini(project_dir):
return cmd_dir


@pytest.fixture
def commands_dir_qwen(project_dir):
"""Create a populated .qwen/commands directory (Markdown format)."""
cmd_dir = project_dir / ".qwen" / "commands"
cmd_dir.mkdir(parents=True, exist_ok=True)
for name in ["speckit.specify.md", "speckit.plan.md", "speckit.tasks.md"]:
(cmd_dir / name).write_text(f"# {name}\nContent here\n")
return cmd_dir


# ===== _get_skills_dir Tests =====

class TestGetSkillsDir:
Expand Down Expand Up @@ -390,6 +400,28 @@ def test_non_md_commands_dir_falls_back(self, project_dir):
# .toml commands should be untouched
assert (cmds_dir / "speckit.specify.toml").exists()

def test_qwen_md_commands_dir_installs_skills(self, project_dir):
"""Qwen now uses Markdown format; skills should install directly from .qwen/commands/."""
cmds_dir = project_dir / ".qwen" / "commands"
cmds_dir.mkdir(parents=True)
(cmds_dir / "speckit.specify.md").write_text(
"---\ndescription: Create or update the feature specification.\n---\n\n# Specify\n\nBody.\n"
)
(cmds_dir / "speckit.plan.md").write_text(
"---\ndescription: Generate implementation plan.\n---\n\n# Plan\n\nBody.\n"
)

result = install_ai_skills(project_dir, "qwen")

assert result is True
skills_dir = project_dir / ".qwen" / "skills"
assert skills_dir.exists()
skill_dirs = [d.name for d in skills_dir.iterdir() if d.is_dir()]
assert len(skill_dirs) >= 1
# .md commands should be untouched
assert (cmds_dir / "speckit.specify.md").exists()
assert (cmds_dir / "speckit.plan.md").exists()

@pytest.mark.parametrize("agent_key", [k for k in AGENT_CONFIG.keys() if k != "generic"])
def test_skills_install_for_all_agents(self, temp_dir, agent_key):
"""install_ai_skills should produce skills for every configured agent."""
Expand Down Expand Up @@ -446,6 +478,15 @@ def test_existing_commands_preserved_gemini(self, project_dir, templates_dir, co
remaining = list(commands_dir_gemini.glob("speckit.*"))
assert len(remaining) == 3

def test_existing_commands_preserved_qwen(self, project_dir, templates_dir, commands_dir_qwen):
"""install_ai_skills must NOT remove pre-existing .qwen/commands files."""
assert len(list(commands_dir_qwen.glob("speckit.*"))) == 3

install_ai_skills(project_dir, "qwen")

remaining = list(commands_dir_qwen.glob("speckit.*"))
assert len(remaining) == 3

def test_commands_dir_not_removed(self, project_dir, templates_dir, commands_dir_claude):
"""install_ai_skills must not remove the commands directory."""
install_ai_skills(project_dir, "claude")
Expand Down
9 changes: 9 additions & 0 deletions tests/test_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,15 @@ def test_codex_agent_config_present(self):
assert "codex" in CommandRegistrar.AGENT_CONFIGS
assert CommandRegistrar.AGENT_CONFIGS["codex"]["dir"] == ".codex/prompts"

def test_qwen_agent_config_is_markdown(self):
"""Qwen should use Markdown format with $ARGUMENTS (not TOML)."""
assert "qwen" in CommandRegistrar.AGENT_CONFIGS
cfg = CommandRegistrar.AGENT_CONFIGS["qwen"]
assert cfg["dir"] == ".qwen/commands"
assert cfg["format"] == "markdown"
assert cfg["args"] == "$ARGUMENTS"
assert cfg["extension"] == ".md"

def test_parse_frontmatter_valid(self):
"""Test parsing valid YAML frontmatter."""
content = """---
Expand Down
Loading