Skip to content
Open
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 @@ -298,7 +298,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
6 changes: 3 additions & 3 deletions .github/workflows/scripts/create-release-packages.sh
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,8 @@ build_variant() {
[[ -d templates ]] && { mkdir -p "$SPEC_DIR/templates"; find templates -type f -not -path "templates/commands/*" -not -name "vscode-settings.json" -exec cp --parents {} "$SPEC_DIR"/ \; ; echo "Copied templates -> .specify/templates"; }

# NOTE: We substitute {ARGS} internally. Outward tokens differ intentionally:
# * Markdown/prompt (claude, copilot, cursor-agent, opencode): $ARGUMENTS
# * TOML (gemini, qwen, tabnine): {{args}}
# * Markdown/prompt (claude, copilot, cursor-agent, opencode, qwen): $ARGUMENTS
# * TOML (gemini, tabnine): {{args}}
# This keeps formats readable without extra abstraction.
Comment on lines 156 to 159
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

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

This comment lists only a subset of Markdown/prompt agents, but many other agents in this script also use $ARGUMENTS. Consider either enumerating all Markdown-based agents here or rewording to avoid an incomplete list (e.g., “Markdown/prompt-based agents: $ARGUMENTS”).

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

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

Since Qwen was changed to Markdown/prompt, I only need to move Qwen up from the TOML list in the comment, that’s all.


case $agent in
Expand All @@ -180,7 +180,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 @@ -337,7 +337,7 @@ Work within integrated development environments:

### Markdown Format

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

**Standard format:**

Expand All @@ -362,7 +362,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
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
Comment on lines +139 to +142
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

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

The fixture writes Markdown files without an explicit encoding. Path.write_text() defaults to the platform locale, while install_ai_skills() reads with UTF-8, so this can fail on non-UTF-8 locales. Write these files with encoding="utf-8" (as the other fixtures in this file already do) to keep tests deterministic.

This issue also appears on line 407 of the same file.

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

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

We intentionally did not specify the encoding here to keep the handling style consistent with lines 119-122 of this file.



# ===== _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 @@ -443,6 +475,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")

Comment on lines +478 to +483
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

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

templates_dir is included as a fixture parameter but is not used by this test (and adds unrelated setup of .claude/commands). Drop the unused fixture to keep the test focused and reduce unnecessary work.

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

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

We intentionally retained templates_dir as a fixture parameter here to keep the handling style consistent with lines 458-464 of this file.

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