Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
469 changes: 469 additions & 0 deletions scripts/bash/create-ado-workitems.sh

Large diffs are not rendered by default.

574 changes: 574 additions & 0 deletions scripts/powershell/create-ado-workitems.ps1

Large diffs are not rendered by default.

496 changes: 496 additions & 0 deletions templates/commands/adosync.md

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions templates/commands/plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ handoffs:
agent: speckit.tasks
prompt: Break the plan into tasks
send: true
- label: Sync Tasks to Azure DevOps
agent: speckit.adosync
prompt: |
Read the tasks.md file and show me all the tasks that will be created in Azure DevOps.
Ask me which tasks I want to sync (I can say "all", specific numbers like "1,2,3", or ranges like "1-10").
Then use the create-ado-workitems-oauth.ps1 script with the -FromTasks flag to create Task work items in Azure DevOps.
Comment thread
pragya247 marked this conversation as resolved.
Outdated
The script will automatically link tasks to their parent User Stories based on the [US#] references in the task descriptions.
Make sure to show me a preview before creating the work items.
send: true
- label: Create Checklist
agent: speckit.checklist
prompt: Create a checklist for the following domain...
Expand Down
14 changes: 13 additions & 1 deletion templates/commands/specify.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
---
description: Create or update the feature specification from a natural language feature description.
handoffs:
- label: Sync to Azure DevOps
agent: speckit.adosync
prompt: |
Sync user stories from the spec.md we just created to Azure DevOps.

The spec file path is: {spec_file_path}

Please:
1. Show me the list of user stories found
2. Ask which ones I want to sync (or suggest 'all')
3. Create the work items in Azure DevOps
send: true
- label: Build Technical Plan
agent: speckit.plan
prompt: Create a plan for the spec. I am building with...
Expand Down Expand Up @@ -193,7 +205,7 @@ Given that feature description, do this:

d. **Update Checklist**: After each validation iteration, update the checklist file with current pass/fail status

7. Report completion with branch name, spec file path, checklist results, and readiness for the next phase (`/speckit.clarify` or `/speckit.plan`).
7. Report completion with branch name, spec file path, checklist results, and readiness for the next phase (`/speckit.clarify` or `/speckit.adosync` or `/speckit.plan`).

**NOTE:** The script creates and checks out the new branch and initializes the spec file before writing.

Expand Down
4 changes: 4 additions & 0 deletions templates/commands/tasks.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
---
description: Generate an actionable, dependency-ordered tasks.md for the feature based on available design artifacts.
handoffs:
- label: Sync to Azure DevOps
agent: speckit.adosync
prompt: Sync user stories to Azure DevOps
Comment thread
pragya247 marked this conversation as resolved.
Outdated
send: false
- label: Analyze For Consistency
agent: speckit.analyze
prompt: Run a project analysis for consistency
Expand Down
266 changes: 266 additions & 0 deletions tests/test_ai_skills.py
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,272 @@ def test_ai_skills_flag_appears_in_help(self):
assert "agent skills" in plain.lower()


class TestHandoffsFieldInSkills:
"""Test handling of handoffs field in command templates for AI skills (ADO sync feature)."""

def test_skill_generation_with_handoffs_in_template(self, project_dir):
"""Skills should generate successfully from templates containing handoffs field."""
# Create template with handoffs
cmds_dir = project_dir / ".claude" / "commands"
cmds_dir.mkdir(parents=True)

(cmds_dir / "specify.md").write_text(
"---\n"
"description: Create specification\n"
"handoffs:\n"
" - label: Sync to Azure DevOps\n"
" agent: speckit.adosync\n"
" prompt: Sync user stories to ADO\n"
" send: true\n"
" - label: Build Plan\n"
" agent: speckit.plan\n"
" send: false\n"
"---\n"
"\n"
"# Specify Command\n"
"\n"
"Create specs.\n",
encoding="utf-8",
)

result = install_ai_skills(project_dir, "claude")

assert result is True
skill_file = project_dir / ".claude" / "skills" / "speckit-specify" / "SKILL.md"
assert skill_file.exists()
content = skill_file.read_text()

# Verify skill has valid structure
assert "name: speckit-specify" in content
assert "description:" in content
# Body content should be preserved
assert "Create specs." in content

def test_skill_generation_with_multiline_handoffs_prompt(self, project_dir):
"""Skills should generate successfully from templates with multiline handoffs prompts."""
cmds_dir = project_dir / ".claude" / "commands"
cmds_dir.mkdir(parents=True)

(cmds_dir / "plan.md").write_text(
"---\n"
"description: Create plan\n"
"handoffs:\n"
" - label: Sync Tasks\n"
" agent: speckit.adosync\n"
" prompt: |\n"
" Read the tasks.md file and show me all the tasks.\n"
" Ask me which tasks I want to sync.\n"
" Then create Task work items in Azure DevOps.\n"
" send: true\n"
"---\n"
"\n"
"# Plan\n"
"\n"
"Plan body.\n",
encoding="utf-8",
)

result = install_ai_skills(project_dir, "claude")

assert result is True
skill_file = project_dir / ".claude" / "skills" / "speckit-plan" / "SKILL.md"
content = skill_file.read_text()

# Verify skill was generated successfully
assert "name: speckit-plan" in content
assert "Plan body." in content

def test_handoffs_field_parseable_in_generated_skill(self, project_dir):
"""Generated SKILL.md should have valid parseable YAML regardless of source frontmatter."""
cmds_dir = project_dir / ".claude" / "commands"
cmds_dir.mkdir(parents=True)

(cmds_dir / "tasks.md").write_text(
"---\n"
"description: Generate tasks\n"
"handoffs:\n"
" - label: Sync to ADO\n"
" agent: speckit.adosync\n"
" prompt: Sync tasks to Azure DevOps\n"
"---\n"
"\n"
"# Tasks\n"
"\n"
"Task content.\n",
encoding="utf-8",
)

install_ai_skills(project_dir, "claude")

skill_file = project_dir / ".claude" / "skills" / "speckit-tasks" / "SKILL.md"
content = skill_file.read_text()

# Extract and parse frontmatter to verify it's valid YAML
parts = content.split("---", 2)
assert len(parts) >= 3
parsed = yaml.safe_load(parts[1])

# The generated SKILL.md should have agentskills.io compliant frontmatter
assert isinstance(parsed, dict)
assert "name" in parsed
assert parsed["name"] == "speckit-tasks"
assert "description" in parsed
assert "compatibility" in parsed

# Body should be preserved
assert "Task content." in content

def test_templates_with_handoffs_and_scripts_fields(self, project_dir):
"""Skills should generate from templates with multiple complex fields like handoffs and scripts."""
cmds_dir = project_dir / ".claude" / "commands"
cmds_dir.mkdir(parents=True)

(cmds_dir / "specify.md").write_text(
"---\n"
"description: Spec command\n"
"handoffs:\n"
" - label: Sync to ADO\n"
" agent: speckit.adosync\n"
" prompt: |\n"
" Sync user stories from spec.md.\n"
" The spec file path is: {spec_file_path}\n"
"scripts:\n"
" sh: scripts/bash/create-new-feature.sh\n"
" ps: scripts/powershell/create-new-feature.ps1\n"
"---\n"
"\n"
"# Specify\n"
"\n"
"Command body.\n",
encoding="utf-8",
)

install_ai_skills(project_dir, "claude")

skill_file = project_dir / ".claude" / "skills" / "speckit-specify" / "SKILL.md"
content = skill_file.read_text()

# Skill should be generated successfully
assert "name: speckit-specify" in content
assert "Command body." in content

def test_multiple_handoffs_dont_break_skill_generation(self, project_dir):
"""Templates with multiple handoffs should generate skills without errors."""
cmds_dir = project_dir / ".claude" / "commands"
cmds_dir.mkdir(parents=True)

(cmds_dir / "plan.md").write_text(
"---\n"
"description: Plan command\n"
"handoffs:\n"
" - label: Sync User Stories\n"
" agent: speckit.adosync\n"
" prompt: Sync user stories\n"
" send: true\n"
" - label: Sync Tasks\n"
" agent: speckit.adosync\n"
" prompt: Sync tasks with -FromTasks\n"
" send: false\n"
" - label: Create Checklist\n"
" agent: speckit.checklist\n"
" send: true\n"
"---\n"
"\n"
"# Plan\n"
"\n"
"Planning content.\n",
encoding="utf-8",
)

result = install_ai_skills(project_dir, "claude")

assert result is True
skill_file = project_dir / ".claude" / "skills" / "speckit-plan" / "SKILL.md"
content = skill_file.read_text()

# Skill should be generated with valid structure
assert "name: speckit-plan" in content
assert "Planning content." in content

def test_handoffs_field_optional_in_skills(self, project_dir):
"""Commands without handoffs should still generate valid skills."""
cmds_dir = project_dir / ".claude" / "commands"
cmds_dir.mkdir(parents=True)

(cmds_dir / "legacy.md").write_text(
"---\n"
"description: Legacy command without handoffs\n"
"---\n"
"\n"
"# Legacy Command\n",
encoding="utf-8",
)

result = install_ai_skills(project_dir, "claude")

assert result is True
skill_file = project_dir / ".claude" / "skills" / "speckit-legacy" / "SKILL.md"
assert skill_file.exists()
content = skill_file.read_text()

# Should have valid structure without handoffs
assert "name: speckit-legacy" in content
assert "Legacy command without handoffs" in content

def test_empty_handoffs_array_in_skills(self, project_dir):
"""Commands with empty handoffs array should generate valid skills."""
cmds_dir = project_dir / ".claude" / "commands"
cmds_dir.mkdir(parents=True)

(cmds_dir / "test.md").write_text(
"---\n"
"description: Test command\n"
"handoffs: []\n"
"---\n"
"\n"
"# Test\n",
encoding="utf-8",
)

result = install_ai_skills(project_dir, "claude")

assert result is True
skill_file = project_dir / ".claude" / "skills" / "speckit-test" / "SKILL.md"
content = skill_file.read_text()

# Should handle empty handoffs gracefully
assert "name: speckit-test" in content

def test_adosync_command_generates_skill(self, project_dir):
"""The adosync command itself should generate a valid skill."""
cmds_dir = project_dir / ".claude" / "commands"
cmds_dir.mkdir(parents=True)

(cmds_dir / "adosync.md").write_text(
"---\n"
"description: Sync selected user stories or tasks to Azure DevOps\n"
"scripts:\n"
" sh: scripts/bash/create-ado-workitems.sh\n"
" ps: scripts/powershell/create-ado-workitems.ps1\n"
"---\n"
"\n"
"# ADO Sync Command\n"
"\n"
"Sync to Azure DevOps.\n",
encoding="utf-8",
)

result = install_ai_skills(project_dir, "claude")

assert result is True
skill_file = project_dir / ".claude" / "skills" / "speckit-adosync" / "SKILL.md"
assert skill_file.exists()
content = skill_file.read_text()

assert "name: speckit-adosync" in content
assert "Azure DevOps" in content


class TestParameterOrderingIssue:
"""Test fix for GitHub issue #1641: parameter ordering issues."""

Expand Down
Loading
Loading