Compare commits

...

25 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
6f237803a2 Initial plan 2026-02-25 18:55:39 +00:00
Santosh Bhavani
525cdc17ec Fix version command in documentation (#1685)
Co-authored-by: Santosh Bhavani <sb@mac-mini-i7.local>
2026-02-25 08:20:18 -06:00
dsrednicki
607760e72f Add Cleanup Extension to README (#1678) 2026-02-24 15:50:04 -06:00
Emi
c7ecdfb998 Add retrospective extension to community catalog. (#1681)
Register the new retrospective extension release so users can discover and install it from the community catalog.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-24 13:56:03 -06:00
dsrednicki
f444ccba3a Add Cleanup Extension to catalog (#1617)
**Repository**: https://github.com/dsrednicki/spec-kit-cleanup
**Version**: 1.0.0
**License**: MIT
**Author**: @dsrednicki

Adds catalog entry for the Cleanup Extension - a post-implementation
quality gate that fixes small issues (scout rule), creates tasks for
medium issues, and generates analysis for large issues.
2026-02-23 13:52:40 -06:00
Manfred Riem
3040d33c31 Fix parameter ordering issues in CLI (#1669)
* chore: bump version to v0.0.6 [skip ci]

* Fix parameter ordering issues in CLI (#1641)

- Add validation to detect when option flags are consumed as values
- Provide clear error messages with helpful hints and examples
- Add 5 comprehensive tests to prevent regressions
- Update CODEOWNERS to @mnriem
- Bump version to 0.1.6 with changelog entry

Fixes: #1641

* Fix ruff linting errors: remove f-string prefix from strings without placeholders

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-23 13:17:08 -06:00
Leonardo Nascimento
6cc61025cb Update V-Model Extension Pack to v0.4.0 (#1665)
- Version: 0.2.0 → 0.4.0
- Commands: 5 → 9 (new: architecture-design, integration-test, module-design, unit-test)
- Download URL: updated to v0.4.0 tag
2026-02-23 13:14:02 -06:00
Emi
c1034f1d9d docs: Fix doc missing step (#1496)
* docs: Fix doc missing step

* docs: Update steps for generating technical plan and defining tasks
2026-02-23 11:40:41 -06:00
Leonardo Nascimento
cee4f26fac Update V-Model Extension Pack to v0.3.0 (#1661)
- Version: 0.2.0 → 0.3.0
- Commands: 5 → 7 (architecture-design, integration-test)
- Download URL updated to v0.3.0 tag
2026-02-23 09:02:24 -06:00
Manfred Riem
6f523ede22 Fix #1658: Add commands_subdir field to support non-standard agent directory structures (#1660)
- Added commands_subdir field to AGENT_CONFIG for all agents
- Updated install_ai_skills() to use commands_subdir instead of hardcoded 'commands'
- Fixed --ai-skills flag for copilot, opencode, windsurf, codex, kilocode, q, and agy
- Bumped version to 0.1.5
- Updated AGENTS.md documentation with new field

Affected agents now correctly locate their command templates:
- copilot: .github/agents/
- opencode: .opencode/command/ (singular)
- windsurf: .windsurf/workflows/
- codex: .codex/prompts/
- kilocode: .kilocode/workflows/
- q: .amazonq/prompts/
- agy: .agent/workflows/

All 51 tests pass.
2026-02-21 08:05:58 -06:00
Manfred Riem
68d1d3a0fc feat: add GitHub issue templates (#1655)
* feat: add issue templates for agent requests, bug reports, feature requests, extension submissions, and configuration

* Update .github/ISSUE_TEMPLATE/config.yml

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update .github/ISSUE_TEMPLATE/agent_request.yml

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update .github/ISSUE_TEMPLATE/config.yml

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update .github/ISSUE_TEMPLATE/bug_report.yml

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update .github/ISSUE_TEMPLATE/feature_request.yml

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-20 17:35:48 -06:00
Leonardo Nascimento
07077d0fc2 Update V-Model Extension Pack to v0.2.0 in community catalog (#1656)
- Version: 0.1.0 → 0.2.0
- Download URL updated to v0.2.0 tag
- Commands: 3 → 5 (added system-design, system-test)
2026-02-20 17:29:58 -06:00
Leonardo Nascimento
aeed11f735 Add V-Model Extension Pack to catalog (#1640)
* Add V-Model Extension Pack to catalog

Second community extension: V-Model paired dev-spec and test-spec
generation with regulatory-grade traceability.

- 3 commands: requirements, acceptance, trace
- Deterministic coverage validation (regex-based, not AI)
- Targets: IEC 62304, ISO 26262, DO-178C, FDA 21 CFR Part 820
- Repository: https://github.com/leocamello/spec-kit-v-model
- Release: v0.1.0

* Fix catalog entry: provides as number, add timestamps and statistics

Address review feedback:
- provides.commands: array → number (3), add hooks (1)
- Add created_at and updated_at timestamps
- Add statistics block (downloads: 0, stars: 0)

* Address review: use catalog.community.json and add extensions README

Per maintainer feedback:
- Revert catalog.json to its original empty state (empty by design)
- Rename catalog.example.json → catalog.community.json
- Replace example entries with real V-Model Extension Pack entry
- Add extensions/README.md with community extensions table
2026-02-20 15:11:51 -06:00
Manfred Riem
12405c01e1 refactor: remove OpenAPI/GraphQL bias from templates (#1652)
* chore: bump version to v0.0.5 [skip ci]

* refactor: update documentation for interface contracts and integration patterns

* Update pyproject.toml

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update templates/commands/tasks.md

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update templates/commands/tasks.md

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-20 14:46:56 -06:00
Manfred Riem
fc3b98ea09 fix: rename Qoder AGENT_CONFIG key from 'qoder' to 'qodercli' to match actual CLI executable (#1651)
* fix: rename Qoder CLI to QoderCLI across scripts and documentation

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update .github/workflows/scripts/create-release-packages.ps1

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-20 13:30:16 -06:00
Manfred Riem
6150f1e317 Add generic agent support with customizable command directories (#1639)
- Add --ai generic option for unsupported AI agents (bring your own agent)
- Require --ai-commands-dir to specify where agent reads commands from
- Generate Markdown commands with $ARGUMENTS format (compatible with most agents)
- Rebuild CHANGELOG from GitHub releases (last 10 releases)
- Align version to 0.1.3

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-02-20 11:05:38 -06:00
Manfred Riem
6fca5d83b2 fix: pin click>=8.1 to prevent Python 3.14/Homebrew env isolation crash (#1648)
* fix: pin click>=8.1 to prevent Python 3.14/Homebrew env isolation failures

Fixes #1631. When uv installs specify-cli on macOS with Homebrew Python
3.14, the virtual environment can fail to fully isolate from the system
site-packages, causing Homebrew's click to be loaded instead of the one
uv installed. If that system click is older than 8.1, it lacks the `ctx`
keyword argument in `ParamType.get_metavar()`, which typer 0.24.0 requires,
resulting in:

  TypeError: ParamType.get_metavar() got an unexpected keyword argument 'ctx'

Adding an explicit `click>=8.1` dependency gives uv a hard constraint so
the correct version is always resolved and installed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Update CHANGELOG.md

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-20 10:22:16 -06:00
Manfred Riem
465acd9024 fix: include 'src/**' path in release workflow triggers (#1646)
Co-authored-by: Manfred Riem <mnriem@users.noreply.github.com>
2026-02-20 09:46:29 -06:00
dependabot[bot]
04fc3fd1ba chore(deps): bump github/codeql-action from 3 to 4 (#1635)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-19 16:31:23 -06:00
Manfred Riem
24d76b5d92 Add pytest and Python linting (ruff) to CI (#1637)
* feat: add GitHub Actions workflow for testing and linting Python code

* fix: resolve ruff lint errors in specify_cli

- Remove extraneous f-string prefixes (F541)
- Split multi-statement lines (E701, E702)
- Remove unused variable assignments (F841)
- Remove ruff format check from CI workflow (format-only PR to follow)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: strip ANSI codes in ai-skills help text test

The Rich/Typer CLI injects ANSI escape codes into option names in
--help output, causing plain string matching to fail.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-02-19 16:21:58 -06:00
Manfred Riem
0f7d04b12b feat: add pull request template for better contribution guidelines (#1634) 2026-02-19 15:49:34 -06:00
Manfred Riem
9402ebd00a Feat/ai skills (#1632)
* implement ai-skills command line switch

* fix: address review comments, remove breaking change for existing projects, add tests

* fix: review comments

* fix: review comments

* fix: review comments

* fix: review comments

* fix: review comments, add test cases for all the agents

* fix: review comments

* fix: review comments

* chore: trigger CI

* chore: trigger CodeQL

* ci: add CodeQL workflow for code scanning

* ci: add actions language to CodeQL workflow, disable default setup

---------

Co-authored-by: dhilipkumars <s.dhilipkumar@gmail.com>
2026-02-19 13:24:41 -06:00
dependabot[bot]
d410d188fc chore(deps): bump actions/stale from 9 to 10 (#1623)
Bumps [actions/stale](https://github.com/actions/stale) from 9 to 10.
- [Release notes](https://github.com/actions/stale/releases)
- [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/stale/compare/v9...v10)

---
updated-dependencies:
- dependency-name: actions/stale
  dependency-version: '10'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-19 07:41:50 -06:00
Manfred Riem
686c91f94e feat: add dependabot configuration for pip and GitHub Actions updates (#1622) 2026-02-18 17:42:41 -06:00
Manfred Riem
22036732d8 Remove Maintainers section from README.md (#1618) 2026-02-18 16:11:16 -06:00
33 changed files with 2073 additions and 426 deletions

2
.github/CODEOWNERS vendored
View File

@@ -1,3 +1,3 @@
# Global code owner # Global code owner
* @localden * @mnriem

141
.github/ISSUE_TEMPLATE/agent_request.yml vendored Normal file
View File

@@ -0,0 +1,141 @@
name: Agent Request
description: Request support for a new AI agent/assistant in Spec Kit
title: "[Agent]: Add support for "
labels: ["agent-request", "enhancement", "needs-triage"]
body:
- type: markdown
attributes:
value: |
Thanks for requesting a new agent! Before submitting, please check if the agent is already supported.
**Currently supported agents**: Claude Code, Gemini CLI, GitHub Copilot, Cursor, Qwen Code, opencode, Codex CLI, Windsurf, Kilo Code, Auggie CLI, Roo Code, CodeBuddy, Qoder CLI, Amazon Q Developer CLI, Amp, SHAI, IBM Bob, Antigravity
- type: input
id: agent-name
attributes:
label: Agent Name
description: What is the name of the AI agent/assistant?
placeholder: "e.g., SuperCoder AI"
validations:
required: true
- type: input
id: website
attributes:
label: Official Website
description: Link to the agent's official website or documentation
placeholder: "https://..."
validations:
required: true
- type: dropdown
id: agent-type
attributes:
label: Agent Type
description: How is the agent accessed?
options:
- CLI tool (command-line interface)
- IDE extension/plugin
- Both CLI and IDE
- Other
validations:
required: true
- type: input
id: cli-command
attributes:
label: CLI Command (if applicable)
description: What command is used to invoke the agent from terminal?
placeholder: "e.g., supercode, ai-assistant"
- type: input
id: install-method
attributes:
label: Installation Method
description: How is the agent installed?
placeholder: "e.g., npm install -g supercode, pip install supercode, IDE marketplace"
validations:
required: true
- type: textarea
id: command-structure
attributes:
label: Command/Workflow Structure
description: How does the agent define custom commands or workflows?
placeholder: |
- Command file format (Markdown, YAML, TOML, etc.)
- Directory location (e.g., .supercode/commands/)
- Example command file structure
validations:
required: true
- type: textarea
id: argument-pattern
attributes:
label: Argument Passing Pattern
description: How does the agent handle arguments in commands?
placeholder: |
e.g., Uses {{args}}, $ARGUMENTS, %ARGS%, or other placeholder format
Example: "Run test suite with {{args}}"
- type: dropdown
id: popularity
attributes:
label: Popularity/Usage
description: How widely is this agent used?
options:
- Widely used (thousands+ of users)
- Growing adoption (hundreds of users)
- New/emerging (less than 100 users)
- Unknown
validations:
required: true
- type: textarea
id: documentation
attributes:
label: Documentation Links
description: Links to relevant documentation for custom commands/workflows
placeholder: |
- Command documentation: https://...
- API/CLI reference: https://...
- Examples: https://...
- type: textarea
id: use-case
attributes:
label: Use Case
description: Why do you want this agent supported in Spec Kit?
placeholder: Explain your workflow and how this agent fits into your development process
validations:
required: true
- type: textarea
id: example-command
attributes:
label: Example Command File
description: If possible, provide an example of a command file for this agent
render: markdown
placeholder: |
```toml
description = "Example command"
prompt = "Do something with {{args}}"
```
- type: checkboxes
id: contribution
attributes:
label: Contribution
description: Are you willing to help implement support for this agent?
options:
- label: I can help test the integration
- label: I can provide example command files
- label: I can help with documentation
- label: I can submit a pull request for the integration
- type: textarea
id: context
attributes:
label: Additional Context
description: Any other relevant information about this agent
placeholder: Screenshots, community links, comparison to existing agents, etc.

118
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,118 @@
name: Bug Report
description: Report a bug or unexpected behavior in Specify CLI or Spec Kit
title: "[Bug]: "
labels: ["bug", "needs-triage"]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to report a bug! Please fill out the sections below to help us diagnose and fix the issue.
- type: textarea
id: description
attributes:
label: Bug Description
description: A clear and concise description of what the bug is.
placeholder: What went wrong?
validations:
required: true
- type: textarea
id: reproduce
attributes:
label: Steps to Reproduce
description: Steps to reproduce the behavior
placeholder: |
1. Run command '...'
2. Execute script '...'
3. See error
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected Behavior
description: What did you expect to happen?
placeholder: Describe the expected outcome
validations:
required: true
- type: textarea
id: actual
attributes:
label: Actual Behavior
description: What actually happened?
placeholder: Describe what happened instead
validations:
required: true
- type: input
id: version
attributes:
label: Specify CLI Version
description: "Run `specify version` or `pip show spec-kit`"
placeholder: "e.g., 1.3.0"
validations:
required: true
- type: dropdown
id: ai-agent
attributes:
label: AI Agent
description: Which AI agent are you using?
options:
- Claude Code
- Gemini CLI
- GitHub Copilot
- Cursor
- Qwen Code
- opencode
- Codex CLI
- Windsurf
- Kilo Code
- Auggie CLI
- Roo Code
- CodeBuddy
- Qoder CLI
- Amazon Q Developer CLI
- Amp
- SHAI
- IBM Bob
- Antigravity
- Not applicable
validations:
required: true
- type: input
id: os
attributes:
label: Operating System
description: Your operating system and version
placeholder: "e.g., macOS 14.2, Ubuntu 22.04, Windows 11"
validations:
required: true
- type: input
id: python
attributes:
label: Python Version
description: "Run `python --version` or `python3 --version`"
placeholder: "e.g., Python 3.11.5"
validations:
required: true
- type: textarea
id: logs
attributes:
label: Error Logs
description: Please paste any relevant error messages or logs
render: shell
placeholder: Paste error output here
- type: textarea
id: context
attributes:
label: Additional Context
description: Add any other context about the problem
placeholder: Screenshots, related issues, workarounds attempted, etc.

17
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,17 @@
blank_issues_enabled: false
contact_links:
- name: 💬 General Discussion
url: https://github.com/github/spec-kit/discussions
about: Ask questions, share ideas, or discuss Spec-Driven Development
- name: 📖 Documentation
url: https://github.com/github/spec-kit/blob/main/README.md
about: Read the Spec Kit documentation and guides
- name: 🛠️ Extension Development Guide
url: https://github.com/manfredseee/spec-kit/blob/main/extensions/EXTENSION-DEVELOPMENT-GUIDE.md
about: Learn how to develop and publish Spec Kit extensions
- name: 🤝 Contributing Guide
url: https://github.com/github/spec-kit/blob/main/CONTRIBUTING.md
about: Learn how to contribute to Spec Kit
- name: 🔒 Security Issues
url: https://github.com/github/spec-kit/blob/main/SECURITY.md
about: Report security vulnerabilities privately

View File

@@ -0,0 +1,280 @@
name: Extension Submission
description: Submit your extension to the Spec Kit catalog
title: "[Extension]: Add "
labels: ["extension-submission", "enhancement", "needs-triage"]
body:
- type: markdown
attributes:
value: |
Thanks for contributing an extension! This template helps you submit your extension to the community catalog.
**Before submitting:**
- Review the [Extension Publishing Guide](https://github.com/github/spec-kit/blob/main/extensions/EXTENSION-PUBLISHING-GUIDE.md)
- Ensure your extension has a valid `extension.yml` manifest
- Create a GitHub release with a version tag (e.g., v1.0.0)
- Test installation: `specify extension add --from <your-release-url>`
- type: input
id: extension-id
attributes:
label: Extension ID
description: Unique extension identifier (lowercase with hyphens only)
placeholder: "e.g., jira-integration"
validations:
required: true
- type: input
id: extension-name
attributes:
label: Extension Name
description: Human-readable extension name
placeholder: "e.g., Jira Integration"
validations:
required: true
- type: input
id: version
attributes:
label: Version
description: Semantic version number
placeholder: "e.g., 1.0.0"
validations:
required: true
- type: textarea
id: description
attributes:
label: Description
description: Brief description of what your extension does (under 200 characters)
placeholder: Integrates Jira issue tracking with Spec Kit workflows for seamless task management
validations:
required: true
- type: input
id: author
attributes:
label: Author
description: Your name or organization
placeholder: "e.g., John Doe or Acme Corp"
validations:
required: true
- type: input
id: repository
attributes:
label: Repository URL
description: GitHub repository URL for your extension
placeholder: "https://github.com/your-org/spec-kit-your-extension"
validations:
required: true
- type: input
id: download-url
attributes:
label: Download URL
description: URL to the GitHub release archive (e.g., v1.0.0.zip)
placeholder: "https://github.com/your-org/spec-kit-your-extension/archive/refs/tags/v1.0.0.zip"
validations:
required: true
- type: input
id: license
attributes:
label: License
description: Open source license type
placeholder: "e.g., MIT, Apache-2.0"
validations:
required: true
- type: input
id: homepage
attributes:
label: Homepage (optional)
description: Link to extension homepage or documentation site
placeholder: "https://..."
- type: input
id: documentation
attributes:
label: Documentation URL (optional)
description: Link to detailed documentation
placeholder: "https://github.com/your-org/spec-kit-your-extension/blob/main/docs/"
- type: input
id: changelog
attributes:
label: Changelog URL (optional)
description: Link to changelog file
placeholder: "https://github.com/your-org/spec-kit-your-extension/blob/main/CHANGELOG.md"
- type: input
id: speckit-version
attributes:
label: Required Spec Kit Version
description: Minimum Spec Kit version required
placeholder: "e.g., >=0.1.0"
validations:
required: true
- type: textarea
id: required-tools
attributes:
label: Required Tools (optional)
description: List any external tools or dependencies required
placeholder: |
- jira-cli (>=1.0.0) - required
- python (>=3.8) - optional
render: markdown
- type: input
id: commands-count
attributes:
label: Number of Commands
description: How many commands does your extension provide?
placeholder: "e.g., 3"
validations:
required: true
- type: input
id: hooks-count
attributes:
label: Number of Hooks (optional)
description: How many hooks does your extension provide?
placeholder: "e.g., 0"
- type: textarea
id: tags
attributes:
label: Tags
description: 2-5 relevant tags (lowercase, separated by commas)
placeholder: "issue-tracking, jira, atlassian, automation"
validations:
required: true
- type: textarea
id: features
attributes:
label: Key Features
description: List the main features and capabilities of your extension
placeholder: |
- Create Jira issues from specs
- Sync task status with Jira
- Link specs to existing issues
- Generate Jira reports
validations:
required: true
- type: checkboxes
id: testing
attributes:
label: Testing Checklist
description: Confirm that your extension has been tested
options:
- label: Extension installs successfully via download URL
required: true
- label: All commands execute without errors
required: true
- label: Documentation is complete and accurate
required: true
- label: No security vulnerabilities identified
required: true
- label: Tested on at least one real project
required: true
- type: checkboxes
id: requirements
attributes:
label: Submission Requirements
description: Verify your extension meets all requirements
options:
- label: Valid `extension.yml` manifest included
required: true
- label: README.md with installation and usage instructions
required: true
- label: LICENSE file included
required: true
- label: GitHub release created with version tag
required: true
- label: All command files exist and are properly formatted
required: true
- label: Extension ID follows naming conventions (lowercase-with-hyphens)
required: true
- type: textarea
id: testing-details
attributes:
label: Testing Details
description: Describe how you tested your extension
placeholder: |
**Tested on:**
- macOS 14.0 with Spec Kit v0.1.0
- Linux Ubuntu 22.04 with Spec Kit v0.1.0
**Test project:** [Link or description]
**Test scenarios:**
1. Installed extension
2. Configured settings
3. Ran all commands
4. Verified outputs
validations:
required: true
- type: textarea
id: example-usage
attributes:
label: Example Usage
description: Provide a simple example of using your extension
render: markdown
placeholder: |
```bash
# Install extension
specify extension add --from https://github.com/your-org/spec-kit-your-extension/archive/refs/tags/v1.0.0.zip
# Use a command
/speckit.your-extension.command-name arg1 arg2
```
validations:
required: true
- type: textarea
id: catalog-entry
attributes:
label: Proposed Catalog Entry
description: Provide the JSON entry for catalog.json (helps reviewers)
render: json
placeholder: |
{
"your-extension": {
"name": "Your Extension",
"id": "your-extension",
"description": "Brief description",
"author": "Your Name",
"version": "1.0.0",
"download_url": "https://github.com/your-org/spec-kit-your-extension/archive/refs/tags/v1.0.0.zip",
"repository": "https://github.com/your-org/spec-kit-your-extension",
"homepage": "https://github.com/your-org/spec-kit-your-extension",
"license": "MIT",
"requires": {
"speckit_version": ">=0.1.0"
},
"provides": {
"commands": 3
},
"tags": ["category", "tool"],
"verified": false,
"downloads": 0,
"stars": 0,
"created_at": "2026-02-20T00:00:00Z",
"updated_at": "2026-02-20T00:00:00Z"
}
}
validations:
required: true
- type: textarea
id: additional-context
attributes:
label: Additional Context
description: Any other information that would help reviewers
placeholder: Screenshots, demo videos, links to related projects, etc.

View File

@@ -0,0 +1,104 @@
name: Feature Request
description: Suggest a new feature or enhancement for Specify CLI or Spec Kit
title: "[Feature]: "
labels: ["enhancement", "needs-triage"]
body:
- type: markdown
attributes:
value: |
Thanks for suggesting a feature! Please provide details below to help us understand and evaluate your request.
- type: textarea
id: problem
attributes:
label: Problem Statement
description: Is your feature request related to a problem? Please describe.
placeholder: "I'm frustrated when..."
validations:
required: true
- type: textarea
id: solution
attributes:
label: Proposed Solution
description: Describe the solution you'd like
placeholder: What would you like to happen?
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternatives Considered
description: Have you considered any alternative solutions or workarounds?
placeholder: What other approaches might work?
- type: dropdown
id: component
attributes:
label: Component
description: Which component does this feature relate to?
options:
- Specify CLI (initialization, commands)
- Spec templates (BDD, Testing Strategy, etc.)
- Agent integrations (command files, workflows)
- Scripts (Bash/PowerShell utilities)
- Documentation
- CI/CD workflows
- Other
validations:
required: true
- type: dropdown
id: ai-agent
attributes:
label: AI Agent (if applicable)
description: Does this feature relate to a specific AI agent?
options:
- All agents
- Claude Code
- Gemini CLI
- GitHub Copilot
- Cursor
- Qwen Code
- opencode
- Codex CLI
- Windsurf
- Kilo Code
- Auggie CLI
- Roo Code
- CodeBuddy
- Qoder CLI
- Amazon Q Developer CLI
- Amp
- SHAI
- IBM Bob
- Antigravity
- Not applicable
- type: textarea
id: use-cases
attributes:
label: Use Cases
description: Describe specific use cases where this feature would be valuable
placeholder: |
1. When working on large projects...
2. During spec review...
3. When integrating with CI/CD...
- type: textarea
id: acceptance
attributes:
label: Acceptance Criteria
description: How would you know this feature is complete and working?
placeholder: |
- [ ] Feature does X
- [ ] Documentation is updated
- [ ] Works with all supported agents
- type: textarea
id: context
attributes:
label: Additional Context
description: Add any other context, screenshots, or examples
placeholder: Links to similar features, mockups, related discussions, etc.

22
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,22 @@
## Description
<!-- What does this PR do? Why is it needed? -->
## Testing
<!-- How did you test your changes? -->
- [ ] Tested locally with `uv run specify --help`
- [ ] Ran existing tests with `uv sync && uv run pytest`
- [ ] Tested with a sample project (if applicable)
## AI Disclosure
<!-- Per our Contributing guidelines, AI assistance must be disclosed. -->
<!-- See: https://github.com/github/spec-kit/blob/main/CONTRIBUTING.md#ai-contributions-in-spec-kit -->
- [ ] I **did not** use AI assistance for this contribution
- [ ] I **did** use AI assistance (describe below)
<!-- If you used AI, briefly describe how (e.g., "Code generated by Copilot", "Consulted ChatGPT for approach"): -->

11
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
version: 2
updates:
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"

32
.github/workflows/codeql.yml vendored Normal file
View File

@@ -0,0 +1,32 @@
name: "CodeQL"
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
security-events: write
contents: read
strategy:
fail-fast: false
matrix:
language: [ 'actions', 'python' ]
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
with:
category: "/language:${{ matrix.language }}"

View File

@@ -6,6 +6,7 @@ on:
paths: paths:
- 'memory/**' - 'memory/**'
- 'scripts/**' - 'scripts/**'
- 'src/**'
- 'templates/**' - 'templates/**'
- '.github/workflows/**' - '.github/workflows/**'
workflow_dispatch: workflow_dispatch:
@@ -57,4 +58,12 @@ jobs:
run: | run: |
chmod +x .github/workflows/scripts/update-version.sh chmod +x .github/workflows/scripts/update-version.sh
.github/workflows/scripts/update-version.sh ${{ steps.get_tag.outputs.new_version }} .github/workflows/scripts/update-version.sh ${{ steps.get_tag.outputs.new_version }}
- name: Commit version bump to main
if: steps.check_release.outputs.exists == 'false'
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add pyproject.toml
git diff --cached --quiet || git commit -m "chore: bump version to ${{ steps.get_tag.outputs.new_version }} [skip ci]"
git push

View File

@@ -40,8 +40,8 @@ gh release create "$VERSION" \
.genreleases/spec-kit-template-roo-ps-"$VERSION".zip \ .genreleases/spec-kit-template-roo-ps-"$VERSION".zip \
.genreleases/spec-kit-template-codebuddy-sh-"$VERSION".zip \ .genreleases/spec-kit-template-codebuddy-sh-"$VERSION".zip \
.genreleases/spec-kit-template-codebuddy-ps-"$VERSION".zip \ .genreleases/spec-kit-template-codebuddy-ps-"$VERSION".zip \
.genreleases/spec-kit-template-qoder-sh-"$VERSION".zip \ .genreleases/spec-kit-template-qodercli-sh-"$VERSION".zip \
.genreleases/spec-kit-template-qoder-ps-"$VERSION".zip \ .genreleases/spec-kit-template-qodercli-ps-"$VERSION".zip \
.genreleases/spec-kit-template-amp-sh-"$VERSION".zip \ .genreleases/spec-kit-template-amp-sh-"$VERSION".zip \
.genreleases/spec-kit-template-amp-ps-"$VERSION".zip \ .genreleases/spec-kit-template-amp-ps-"$VERSION".zip \
.genreleases/spec-kit-template-shai-sh-"$VERSION".zip \ .genreleases/spec-kit-template-shai-sh-"$VERSION".zip \
@@ -52,5 +52,7 @@ gh release create "$VERSION" \
.genreleases/spec-kit-template-agy-ps-"$VERSION".zip \ .genreleases/spec-kit-template-agy-ps-"$VERSION".zip \
.genreleases/spec-kit-template-bob-sh-"$VERSION".zip \ .genreleases/spec-kit-template-bob-sh-"$VERSION".zip \
.genreleases/spec-kit-template-bob-ps-"$VERSION".zip \ .genreleases/spec-kit-template-bob-ps-"$VERSION".zip \
.genreleases/spec-kit-template-generic-sh-"$VERSION".zip \
.genreleases/spec-kit-template-generic-ps-"$VERSION".zip \
--title "Spec Kit Templates - $VERSION_NO_V" \ --title "Spec Kit Templates - $VERSION_NO_V" \
--notes-file release_notes.md --notes-file release_notes.md

View File

@@ -14,7 +14,7 @@
.PARAMETER Agents .PARAMETER Agents
Comma or space separated subset of agents to build (default: all) Comma or space separated subset of agents to build (default: all)
Valid agents: claude, gemini, copilot, cursor-agent, qwen, opencode, windsurf, codex, kilocode, auggie, roo, codebuddy, amp, q, bob, qoder Valid agents: claude, gemini, copilot, cursor-agent, qwen, opencode, windsurf, codex, kilocode, auggie, roo, codebuddy, amp, q, bob, qodercli, shai, agy, generic
.PARAMETER Scripts .PARAMETER Scripts
Comma or space separated subset of script types to build (default: both) Comma or space separated subset of script types to build (default: both)
@@ -343,9 +343,13 @@ function Build-Variant {
$cmdDir = Join-Path $baseDir ".bob/commands" $cmdDir = Join-Path $baseDir ".bob/commands"
Generate-Commands -Agent 'bob' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script Generate-Commands -Agent 'bob' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script
} }
'qoder' { 'qodercli' {
$cmdDir = Join-Path $baseDir ".qoder/commands" $cmdDir = Join-Path $baseDir ".qoder/commands"
Generate-Commands -Agent 'qoder' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script Generate-Commands -Agent 'qodercli' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script
}
'generic' {
$cmdDir = Join-Path $baseDir ".speckit/commands"
Generate-Commands -Agent 'generic' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script
} }
} }
@@ -356,7 +360,7 @@ function Build-Variant {
} }
# Define all agents and scripts # Define all agents and scripts
$AllAgents = @('claude', 'gemini', 'copilot', 'cursor-agent', 'qwen', 'opencode', 'windsurf', 'codex', 'kilocode', 'auggie', 'roo', 'codebuddy', 'amp', 'q', 'bob', 'qoder') $AllAgents = @('claude', 'gemini', 'copilot', 'cursor-agent', 'qwen', 'opencode', 'windsurf', 'codex', 'kilocode', 'auggie', 'roo', 'codebuddy', 'amp', 'q', 'bob', 'qodercli', 'shai', 'agy', 'generic')
$AllScripts = @('sh', 'ps') $AllScripts = @('sh', 'ps')
function Normalize-List { function Normalize-List {

View File

@@ -6,7 +6,7 @@ set -euo pipefail
# Usage: .github/workflows/scripts/create-release-packages.sh <version> # Usage: .github/workflows/scripts/create-release-packages.sh <version>
# Version argument should include leading 'v'. # Version argument should include leading 'v'.
# Optionally set AGENTS and/or SCRIPTS env vars to limit what gets built. # Optionally set AGENTS and/or SCRIPTS env vars to limit what gets built.
# AGENTS : space or comma separated subset of: claude gemini copilot cursor-agent qwen opencode windsurf codex amp shai bob (default: all) # AGENTS : space or comma separated subset of: claude gemini copilot cursor-agent qwen opencode windsurf codex amp shai bob generic (default: all)
# SCRIPTS : space or comma separated subset of: sh ps (default: both) # SCRIPTS : space or comma separated subset of: sh ps (default: both)
# Examples: # Examples:
# AGENTS=claude SCRIPTS=sh $0 v0.2.0 # AGENTS=claude SCRIPTS=sh $0 v0.2.0
@@ -203,9 +203,9 @@ build_variant() {
codebuddy) codebuddy)
mkdir -p "$base_dir/.codebuddy/commands" mkdir -p "$base_dir/.codebuddy/commands"
generate_commands codebuddy md "\$ARGUMENTS" "$base_dir/.codebuddy/commands" "$script" ;; generate_commands codebuddy md "\$ARGUMENTS" "$base_dir/.codebuddy/commands" "$script" ;;
qoder) qodercli)
mkdir -p "$base_dir/.qoder/commands" mkdir -p "$base_dir/.qoder/commands"
generate_commands qoder md "\$ARGUMENTS" "$base_dir/.qoder/commands" "$script" ;; generate_commands qodercli md "\$ARGUMENTS" "$base_dir/.qoder/commands" "$script" ;;
amp) amp)
mkdir -p "$base_dir/.agents/commands" mkdir -p "$base_dir/.agents/commands"
generate_commands amp md "\$ARGUMENTS" "$base_dir/.agents/commands" "$script" ;; generate_commands amp md "\$ARGUMENTS" "$base_dir/.agents/commands" "$script" ;;
@@ -221,13 +221,16 @@ build_variant() {
bob) bob)
mkdir -p "$base_dir/.bob/commands" mkdir -p "$base_dir/.bob/commands"
generate_commands bob md "\$ARGUMENTS" "$base_dir/.bob/commands" "$script" ;; generate_commands bob md "\$ARGUMENTS" "$base_dir/.bob/commands" "$script" ;;
generic)
mkdir -p "$base_dir/.speckit/commands"
generate_commands generic md "\$ARGUMENTS" "$base_dir/.speckit/commands" "$script" ;;
esac esac
( cd "$base_dir" && zip -r "../spec-kit-template-${agent}-${script}-${NEW_VERSION}.zip" . ) ( cd "$base_dir" && zip -r "../spec-kit-template-${agent}-${script}-${NEW_VERSION}.zip" . )
echo "Created $GENRELEASES_DIR/spec-kit-template-${agent}-${script}-${NEW_VERSION}.zip" echo "Created $GENRELEASES_DIR/spec-kit-template-${agent}-${script}-${NEW_VERSION}.zip"
} }
# Determine agent list # Determine agent list
ALL_AGENTS=(claude gemini copilot cursor-agent qwen opencode windsurf codex kilocode auggie roo codebuddy amp shai q agy bob qoder) ALL_AGENTS=(claude gemini copilot cursor-agent qwen opencode windsurf codex kilocode auggie roo codebuddy amp shai q agy bob qodercli generic)
ALL_SCRIPTS=(sh ps) ALL_SCRIPTS=(sh ps)
norm_list() { norm_list() {

View File

@@ -13,7 +13,7 @@ jobs:
stale: stale:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/stale@v9 - uses: actions/stale@v10
with: with:
# Days of inactivity before an issue or PR becomes stale # Days of inactivity before an issue or PR becomes stale
days-before-stale: 150 days-before-stale: 150

50
.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,50 @@
name: Test & Lint Python
permissions:
contents: read
on:
push:
branches: ["main"]
pull_request:
jobs:
ruff:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v6
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Run ruff check
run: uvx ruff check src/
pytest:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.11", "3.12", "3.13"]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v6
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: uv sync --extra test
- name: Run tests
run: uv run pytest

View File

@@ -43,11 +43,12 @@ Specify supports multiple AI agents by generating agent-specific command files a
| **Auggie CLI** | `.augment/rules/` | Markdown | `auggie` | Auggie CLI | | **Auggie CLI** | `.augment/rules/` | Markdown | `auggie` | Auggie CLI |
| **Roo Code** | `.roo/rules/` | Markdown | N/A (IDE-based) | Roo Code IDE | | **Roo Code** | `.roo/rules/` | Markdown | N/A (IDE-based) | Roo Code IDE |
| **CodeBuddy CLI** | `.codebuddy/commands/` | Markdown | `codebuddy` | CodeBuddy CLI | | **CodeBuddy CLI** | `.codebuddy/commands/` | Markdown | `codebuddy` | CodeBuddy CLI |
| **Qoder CLI** | `.qoder/commands/` | Markdown | `qoder` | Qoder CLI | | **Qoder CLI** | `.qoder/commands/` | Markdown | `qodercli` | Qoder CLI |
| **Amazon Q Developer CLI** | `.amazonq/prompts/` | Markdown | `q` | Amazon Q Developer CLI | | **Amazon Q Developer CLI** | `.amazonq/prompts/` | Markdown | `q` | Amazon Q Developer CLI |
| **Amp** | `.agents/commands/` | Markdown | `amp` | Amp CLI | | **Amp** | `.agents/commands/` | Markdown | `amp` | Amp CLI |
| **SHAI** | `.shai/commands/` | Markdown | `shai` | SHAI CLI | | **SHAI** | `.shai/commands/` | Markdown | `shai` | SHAI CLI |
| **IBM Bob** | `.bob/commands/` | Markdown | N/A (IDE-based) | IBM Bob IDE | | **IBM Bob** | `.bob/commands/` | Markdown | N/A (IDE-based) | IBM Bob IDE |
| **Generic** | User-specified via `--ai-commands-dir` | Markdown | N/A | Bring your own agent |
### Step-by-Step Integration Guide ### Step-by-Step Integration Guide
@@ -65,6 +66,7 @@ AGENT_CONFIG = {
"new-agent-cli": { # Use the ACTUAL CLI tool name (what users type in terminal) "new-agent-cli": { # Use the ACTUAL CLI tool name (what users type in terminal)
"name": "New Agent Display Name", "name": "New Agent Display Name",
"folder": ".newagent/", # Directory for agent files "folder": ".newagent/", # Directory for agent files
"commands_subdir": "commands", # Subdirectory name for command files (default: "commands")
"install_url": "https://example.com/install", # URL for installation docs (or None if IDE-based) "install_url": "https://example.com/install", # URL for installation docs (or None if IDE-based)
"requires_cli": True, # True if CLI tool required, False for IDE-based agents "requires_cli": True, # True if CLI tool required, False for IDE-based agents
}, },
@@ -82,6 +84,10 @@ This eliminates the need for special-case mappings throughout the codebase.
- `name`: Human-readable display name shown to users - `name`: Human-readable display name shown to users
- `folder`: Directory where agent-specific files are stored (relative to project root) - `folder`: Directory where agent-specific files are stored (relative to project root)
- `commands_subdir`: Subdirectory name within the agent folder where command/prompt files are stored (default: `"commands"`)
- Most agents use `"commands"` (e.g., `.claude/commands/`)
- Some agents use alternative names: `"agents"` (copilot), `"workflows"` (windsurf, kilocode, agy), `"prompts"` (codex, q), `"command"` (opencode - singular)
- This field enables `--ai-skills` to locate command templates correctly for skill generation
- `install_url`: Installation documentation URL (set to `None` for IDE-based agents) - `install_url`: Installation documentation URL (set to `None` for IDE-based agents)
- `requires_cli`: Whether the agent requires a CLI tool check during initialization - `requires_cli`: Whether the agent requires a CLI tool check during initialization
@@ -313,7 +319,7 @@ Require a command-line tool to be installed:
- **opencode**: `opencode` CLI - **opencode**: `opencode` CLI
- **Amazon Q Developer CLI**: `q` CLI - **Amazon Q Developer CLI**: `q` CLI
- **CodeBuddy CLI**: `codebuddy` CLI - **CodeBuddy CLI**: `codebuddy` CLI
- **Qoder CLI**: `qoder` CLI - **Qoder CLI**: `qodercli` CLI
- **Amp**: `amp` CLI - **Amp**: `amp` CLI
- **SHAI**: `shai` CLI - **SHAI**: `shai` CLI

View File

@@ -2,296 +2,81 @@
<!-- markdownlint-disable MD024 --> <!-- markdownlint-disable MD024 -->
All notable changes to the Specify CLI and templates are documented here. Recent changes to the Specify CLI and templates are documented here.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.1.0] - 2026-01-28 ## [0.1.6] - 2026-02-23
### Added
- **Extension System**: Introduced modular extension architecture for Spec Kit
- Extensions are self-contained packages that add commands and functionality without bloating core
- Extension manifest schema (`extension.yml`) with validation
- Extension registry (`.specify/extensions/.registry`) for tracking installed extensions
- Extension manager module (`src/specify_cli/extensions.py`) for installation/removal
- New CLI commands:
- `specify extension list` - List installed extensions
- `specify extension add` - Install extension from local directory or URL
- `specify extension remove` - Uninstall extension
- `specify extension search` - Search extension catalog
- `specify extension info` - Show detailed extension information
- Semantic versioning compatibility checks
- Support for extension configuration files
- Command registration system for AI agents (Claude support initially)
- Added dependencies: `pyyaml>=6.0`, `packaging>=23.0`
- **Extension Catalog**: Extension discovery and distribution system
- Central catalog (`extensions/catalog.json`) for published extensions
- Extension catalog manager (`ExtensionCatalog` class) with:
- Catalog fetching from GitHub
- 1-hour local caching for performance
- Search by query, tag, author, or verification status
- Extension info retrieval
- Catalog cache stored in `.specify/extensions/.cache/`
- Search and info commands with rich console output
- Added 9 catalog-specific unit tests (100% pass rate)
- **Jira Extension**: First official extension for Jira integration
- Extension ID: `jira`
- Version: 1.0.0
- Commands:
- `/speckit.jira.specstoissues` - Create Jira hierarchy from spec and tasks
- `/speckit.jira.discover-fields` - Discover Jira custom fields
- `/speckit.jira.sync-status` - Sync task completion status
- Comprehensive documentation (README, usage guide, examples)
- MIT licensed
- **Hook System**: Extension lifecycle hooks for automation
- `HookExecutor` class for managing extension hooks
- Hooks registered in `.specify/extensions.yml`
- Hook registration during extension installation
- Hook unregistration during extension removal
- Support for optional and mandatory hooks
- Hook execution messages for AI agent integration
- Condition support for conditional hook execution (placeholder)
- **Extension Management**: Advanced extension management commands
- `specify extension update` - Check and update extensions to latest version
- `specify extension enable` - Enable a disabled extension
- `specify extension disable` - Disable extension without removing it
- Version comparison with catalog
- Update notifications
- Preserve configuration during updates
- **Multi-Agent Support**: Extensions now work with all supported AI agents (Phase 6)
- Automatic detection and registration for all agents in project
- Support for 16+ AI agents (Claude, Gemini, Copilot, Cursor, Qwen, and more)
- Agent-specific command formats (Markdown and TOML)
- Automatic argument placeholder conversion ($ARGUMENTS → {{args}})
- Commands registered for all detected agents during installation
- Multi-agent command unregistration on extension removal
- `CommandRegistrar.register_commands_for_agent()` method
- `CommandRegistrar.register_commands_for_all_agents()` method
- **Configuration Layers**: Full configuration cascade system (Phase 6)
- **Layer 1**: Defaults from extension manifest (`extension.yml`)
- **Layer 2**: Project config (`.specify/extensions/{ext-id}/{ext-id}-config.yml`)
- **Layer 3**: Local config (`.specify/extensions/{ext-id}/local-config.yml`, gitignored)
- **Layer 4**: Environment variables (`SPECKIT_{EXT_ID}_{KEY}` pattern)
- Recursive config merging with proper precedence
- `ConfigManager` class for programmatic config access
- `get_config()`, `get_value()`, `has_value()` methods
- Support for nested configuration paths with dot-notation
- **Hook Condition Evaluation**: Smart hook execution based on runtime conditions (Phase 6)
- Config conditions: `config.key.path is set`, `config.key == 'value'`, `config.key != 'value'`
- Environment conditions: `env.VAR is set`, `env.VAR == 'value'`, `env.VAR != 'value'`
- Automatic filtering of hooks based on condition evaluation
- Safe fallback behavior on evaluation errors
- Case-insensitive pattern matching
- **Hook Integration**: Agent-level hook checking and execution (Phase 6)
- `check_hooks_for_event()` method for AI agents to query hooks after core commands
- Condition-aware hook filtering before execution
- `enable_hooks()` and `disable_hooks()` methods per extension
- Formatted hook messages for agent display
- `execute_hook()` method for hook execution information
- **Documentation Suite**: Comprehensive documentation for users and developers
- **EXTENSION-USER-GUIDE.md**: Complete user guide with installation, usage, configuration, and troubleshooting
- **EXTENSION-API-REFERENCE.md**: Technical API reference with manifest schema, Python API, and CLI commands
- **EXTENSION-PUBLISHING-GUIDE.md**: Publishing guide for extension authors
- **RFC-EXTENSION-SYSTEM.md**: Extension architecture design document
- **Extension Template**: Starter template in `extensions/template/` for creating new extensions
- Fully commented `extension.yml` manifest template
- Example command file with detailed explanations
- Configuration template with all options
- Complete project structure (README, LICENSE, CHANGELOG, .gitignore)
- EXAMPLE-README.md showing final documentation format
- **Unit Tests**: Comprehensive test suite with 39 tests covering all extension system components
- Test coverage: 83% of extension module code
- Test dependencies: `pytest>=7.0`, `pytest-cov>=4.0`
- Configured pytest in `pyproject.toml`
### Changed
- Version bumped to 0.1.0 (minor release for new feature)
## [0.0.22] - 2025-11-07
- Support for VS Code/Copilot agents, and moving away from prompts to proper agents with hand-offs.
- Move to use `AGENTS.md` for Copilot workloads, since it's already supported out-of-the-box.
- Adds support for the version command. ([#486](https://github.com/github/spec-kit/issues/486))
- Fixes potential bug with the `create-new-feature.ps1` script that ignores existing feature branches when determining next feature number ([#975](https://github.com/github/spec-kit/issues/975))
- Add graceful fallback and logging for GitHub API rate-limiting during template fetch ([#970](https://github.com/github/spec-kit/issues/970))
## [0.0.21] - 2025-10-21
- Fixes [#975](https://github.com/github/spec-kit/issues/975) (thank you [@fgalarraga](https://github.com/fgalarraga)).
- Adds support for Amp CLI.
- Adds support for VS Code hand-offs and moves prompts to be full-fledged chat modes.
- Adds support for `version` command (addresses [#811](https://github.com/github/spec-kit/issues/811) and [#486](https://github.com/github/spec-kit/issues/486), thank you [@mcasalaina](https://github.com/mcasalaina) and [@dentity007](https://github.com/dentity007)).
- Adds support for rendering the rate limit errors from the CLI when encountered ([#970](https://github.com/github/spec-kit/issues/970), thank you [@psmman](https://github.com/psmman)).
## [0.0.20] - 2025-10-14
### Added
- **Intelligent Branch Naming**: `create-new-feature` scripts now support `--short-name` parameter for custom branch names
- When `--short-name` provided: Uses the custom name directly (cleaned and formatted)
- When omitted: Automatically generates meaningful names using stop word filtering and length-based filtering
- Filters out common stop words (I, want, to, the, for, etc.)
- Removes words shorter than 3 characters (unless they're uppercase acronyms)
- Takes 3-4 most meaningful words from the description
- **Enforces GitHub's 244-byte branch name limit** with automatic truncation and warnings
- Examples:
- "I want to create user authentication" → `001-create-user-authentication`
- "Implement OAuth2 integration for API" → `001-implement-oauth2-integration-api`
- "Fix payment processing bug" → `001-fix-payment-processing`
- Very long descriptions are automatically truncated at word boundaries to stay within limits
- Designed for AI agents to provide semantic short names while maintaining standalone usability
### Changed
- Enhanced help documentation for `create-new-feature.sh` and `create-new-feature.ps1` scripts with examples
- Branch names now validated against GitHub's 244-byte limit with automatic truncation if needed
## [0.0.19] - 2025-10-10
### Added
- Support for CodeBuddy (thank you to [@lispking](https://github.com/lispking) for the contribution).
- You can now see Git-sourced errors in the Specify CLI.
### Changed
- Fixed the path to the constitution in `plan.md` (thank you to [@lyzno1](https://github.com/lyzno1) for spotting).
- Fixed backslash escapes in generated TOML files for Gemini (thank you to [@hsin19](https://github.com/hsin19) for the contribution).
- Implementation command now ensures that the correct ignore files are added (thank you to [@sigent-amazon](https://github.com/sigent-amazon) for the contribution).
## [0.0.18] - 2025-10-06
### Added
- Support for using `.` as a shorthand for current directory in `specify init .` command, equivalent to `--here` flag but more intuitive for users.
- Use the `/speckit.` command prefix to easily discover Spec Kit-related commands.
- Refactor the prompts and templates to simplify their capabilities and how they are tracked. No more polluting things with tests when they are not needed.
- Ensure that tasks are created per user story (simplifies testing and validation).
- Add support for Visual Studio Code prompt shortcuts and automatic script execution.
### Changed
- All command files now prefixed with `speckit.` (e.g., `speckit.specify.md`, `speckit.plan.md`) for better discoverability and differentiation in IDE/CLI command palettes and file explorers
## [0.0.17] - 2025-09-22
### Added
- New `/clarify` command template to surface up to 5 targeted clarification questions for an existing spec and persist answers into a Clarifications section in the spec.
- New `/analyze` command template providing a non-destructive cross-artifact discrepancy and alignment report (spec, clarifications, plan, tasks, constitution) inserted after `/tasks` and before `/implement`.
- Note: Constitution rules are explicitly treated as non-negotiable; any conflict is a CRITICAL finding requiring artifact remediation, not weakening of principles.
## [0.0.16] - 2025-09-22
### Added
- `--force` flag for `init` command to bypass confirmation when using `--here` in a non-empty directory and proceed with merging/overwriting files.
## [0.0.15] - 2025-09-21
### Added
- Support for Roo Code.
## [0.0.14] - 2025-09-21
### Changed
- Error messages are now shown consistently.
## [0.0.13] - 2025-09-21
### Added
- Support for Kilo Code. Thank you [@shahrukhkhan489](https://github.com/shahrukhkhan489) with [#394](https://github.com/github/spec-kit/pull/394).
- Support for Auggie CLI. Thank you [@hungthai1401](https://github.com/hungthai1401) with [#137](https://github.com/github/spec-kit/pull/137).
- Agent folder security notice displayed after project provisioning completion, warning users that some agents may store credentials or auth tokens in their agent folders and recommending adding relevant folders to `.gitignore` to prevent accidental credential leakage.
### Changed
- Warning displayed to ensure that folks are aware that they might need to add their agent folder to `.gitignore`.
- Cleaned up the `check` command output.
## [0.0.12] - 2025-09-21
### Changed
- Added additional context for OpenAI Codex users - they need to set an additional environment variable, as described in [#417](https://github.com/github/spec-kit/issues/417).
## [0.0.11] - 2025-09-20
### Added
- Codex CLI support (thank you [@honjo-hiroaki-gtt](https://github.com/honjo-hiroaki-gtt) for the contribution in [#14](https://github.com/github/spec-kit/pull/14))
- Codex-aware context update tooling (Bash and PowerShell) so feature plans refresh `AGENTS.md` alongside existing assistants without manual edits.
## [0.0.10] - 2025-09-20
### Fixed ### Fixed
- Addressed [#378](https://github.com/github/spec-kit/issues/378) where a GitHub token may be attached to the request when it was empty. - **Parameter Ordering Issues (#1641)**: Fixed CLI parameter parsing issue where option flags were incorrectly consumed as values for preceding options
- Added validation to detect when `--ai` or `--ai-commands-dir` incorrectly consume following flags like `--here` or `--ai-skills`
- Now provides clear error messages: "Invalid value for --ai: '--here'"
- Includes helpful hints suggesting proper usage and listing available agents
- Commands like `specify init --ai-skills --ai --here` now fail with actionable feedback instead of confusing "Must specify project name" errors
- Added comprehensive test suite (5 new tests) to prevent regressions
## [0.0.9] - 2025-09-19 ## [0.1.5] - 2026-02-21
### Changed
- Improved agent selector UI with cyan highlighting for agent keys and gray parentheses for full names
## [0.0.8] - 2025-09-19
### Added
- Windsurf IDE support as additional AI assistant option (thank you [@raedkit](https://github.com/raedkit) for the work in [#151](https://github.com/github/spec-kit/pull/151))
- GitHub token support for API requests to handle corporate environments and rate limiting (contributed by [@zryfish](https://github.com/@zryfish) in [#243](https://github.com/github/spec-kit/pull/243))
### Changed
- Updated README with Windsurf examples and GitHub token usage
- Enhanced release workflow to include Windsurf templates
## [0.0.7] - 2025-09-18
### Changed
- Updated command instructions in the CLI.
- Cleaned up the code to not render agent-specific information when it's generic.
## [0.0.6] - 2025-09-17
### Added
- opencode support as additional AI assistant option
## [0.0.5] - 2025-09-17
### Added
- Qwen Code support as additional AI assistant option
## [0.0.4] - 2025-09-14
### Added
- SOCKS proxy support for corporate environments via `httpx[socks]` dependency
### Fixed ### Fixed
N/A - **AI Skills Installation Bug (#1658)**: Fixed `--ai-skills` flag not generating skill files for GitHub Copilot and other agents with non-standard command directory structures
- Added `commands_subdir` field to `AGENT_CONFIG` to explicitly specify the subdirectory name for each agent
- Affected agents now work correctly: copilot (`.github/agents/`), opencode (`.opencode/command/`), windsurf (`.windsurf/workflows/`), codex (`.codex/prompts/`), kilocode (`.kilocode/workflows/`), q (`.amazonq/prompts/`), and agy (`.agent/workflows/`)
- The `install_ai_skills()` function now uses the correct path for all agents instead of assuming `commands/` for everyone
### Changed ## [0.1.4] - 2026-02-20
N/A ### Fixed
- **Qoder CLI detection**: Renamed `AGENT_CONFIG` key from `"qoder"` to `"qodercli"` to match the actual executable name, fixing `specify check` and `specify init --ai` detection failures
## [0.1.3] - 2026-02-20
### Added
- **Generic Agent Support**: Added `--ai generic` option for unsupported AI agents ("bring your own agent")
- Requires `--ai-commands-dir <path>` to specify where the agent reads commands from
- Generates Markdown commands with `$ARGUMENTS` format (compatible with most agents)
- Example: `specify init my-project --ai generic --ai-commands-dir .myagent/commands/`
- Enables users to start with Spec Kit immediately while their agent awaits formal support
## [0.0.102] - 2026-02-20
- fix: include 'src/**' path in release workflow triggers (#1646)
## [0.0.101] - 2026-02-19
- chore(deps): bump github/codeql-action from 3 to 4 (#1635)
## [0.0.100] - 2026-02-19
- Add pytest and Python linting (ruff) to CI (#1637)
- feat: add pull request template for better contribution guidelines (#1634)
## [0.0.99] - 2026-02-19
- Feat/ai skills (#1632)
## [0.0.98] - 2026-02-19
- chore(deps): bump actions/stale from 9 to 10 (#1623)
- feat: add dependabot configuration for pip and GitHub Actions updates (#1622)
## [0.0.97] - 2026-02-18
- Remove Maintainers section from README.md (#1618)
## [0.0.96] - 2026-02-17
- fix: typo in plan-template.md (#1446)
## [0.0.95] - 2026-02-12
- Feat: add a new agent: Google Anti Gravity (#1220)
## [0.0.94] - 2026-02-11
- Add stale workflow for 180-day inactive issues and PRs (#1594)

View File

@@ -31,7 +31,6 @@
- [📖 Learn More](#-learn-more) - [📖 Learn More](#-learn-more)
- [📋 Detailed Process](#-detailed-process) - [📋 Detailed Process](#-detailed-process)
- [🔍 Troubleshooting](#-troubleshooting) - [🔍 Troubleshooting](#-troubleshooting)
- [👥 Maintainers](#-maintainers)
- [💬 Support](#-support) - [💬 Support](#-support)
- [🙏 Acknowledgements](#-acknowledgements) - [🙏 Acknowledgements](#-acknowledgements)
- [📄 License](#-license) - [📄 License](#-license)
@@ -163,6 +162,7 @@ Want to see Spec Kit in action? Watch our [video overview](https://www.youtube.c
| [SHAI (OVHcloud)](https://github.com/ovh/shai) | ✅ | | | [SHAI (OVHcloud)](https://github.com/ovh/shai) | ✅ | |
| [Windsurf](https://windsurf.com/) | ✅ | | | [Windsurf](https://windsurf.com/) | ✅ | |
| [Antigravity (agy)](https://agy.ai/) | ✅ | | | [Antigravity (agy)](https://agy.ai/) | ✅ | |
| Generic | ✅ | Bring your own agent — use `--ai generic --ai-commands-dir <path>` for unsupported agents |
## 🔧 Specify CLI Reference ## 🔧 Specify CLI Reference
@@ -173,14 +173,15 @@ The `specify` command supports the following options:
| Command | Description | | Command | Description |
| ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `init` | Initialize a new Specify project from the latest template | | `init` | Initialize a new Specify project from the latest template |
| `check` | Check for installed tools (`git`, `claude`, `gemini`, `code`/`code-insiders`, `cursor-agent`, `windsurf`, `qwen`, `opencode`, `codex`, `shai`, `qoder`) | | `check` | Check for installed tools (`git`, `claude`, `gemini`, `code`/`code-insiders`, `cursor-agent`, `windsurf`, `qwen`, `opencode`, `codex`, `shai`, `qodercli`) |
### `specify init` Arguments & Options ### `specify init` Arguments & Options
| Argument/Option | Type | Description | | Argument/Option | Type | Description |
| ---------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ---------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `<project-name>` | Argument | Name for your new project directory (optional if using `--here`, or use `.` for current directory) | | `<project-name>` | Argument | Name for your new project directory (optional if using `--here`, or use `.` for current directory) |
| `--ai` | Option | AI assistant to use: `claude`, `gemini`, `copilot`, `cursor-agent`, `qwen`, `opencode`, `codex`, `windsurf`, `kilocode`, `auggie`, `roo`, `codebuddy`, `amp`, `shai`, `q`, `agy`, `bob`, or `qoder` | | `--ai` | Option | AI assistant to use: `claude`, `gemini`, `copilot`, `cursor-agent`, `qwen`, `opencode`, `codex`, `windsurf`, `kilocode`, `auggie`, `roo`, `codebuddy`, `amp`, `shai`, `q`, `agy`, `bob`, `qodercli`, or `generic` (requires `--ai-commands-dir`) |
| `--ai-commands-dir` | Option | Directory for agent command files (required with `--ai generic`, e.g. `.myagent/commands/`) |
| `--script` | Option | Script variant to use: `sh` (bash/zsh) or `ps` (PowerShell) | | `--script` | Option | Script variant to use: `sh` (bash/zsh) or `ps` (PowerShell) |
| `--ignore-agent-tools` | Flag | Skip checks for AI agent tools like Claude Code | | `--ignore-agent-tools` | Flag | Skip checks for AI agent tools like Claude Code |
| `--no-git` | Flag | Skip git repository initialization | | `--no-git` | Flag | Skip git repository initialization |
@@ -189,6 +190,7 @@ The `specify` command supports the following options:
| `--skip-tls` | Flag | Skip SSL/TLS verification (not recommended) | | `--skip-tls` | Flag | Skip SSL/TLS verification (not recommended) |
| `--debug` | Flag | Enable detailed debug output for troubleshooting | | `--debug` | Flag | Enable detailed debug output for troubleshooting |
| `--github-token` | Option | GitHub token for API requests (or set GH_TOKEN/GITHUB_TOKEN env variable) | | `--github-token` | Option | GitHub token for API requests (or set GH_TOKEN/GITHUB_TOKEN env variable) |
| `--ai-skills` | Flag | Install Prompt.MD templates as agent skills in agent-specific `skills/` directory (requires `--ai`) |
### Examples ### Examples
@@ -203,7 +205,7 @@ specify init my-project --ai claude
specify init my-project --ai cursor-agent specify init my-project --ai cursor-agent
# Initialize with Qoder support # Initialize with Qoder support
specify init my-project --ai qoder specify init my-project --ai qodercli
# Initialize with Windsurf support # Initialize with Windsurf support
specify init my-project --ai windsurf specify init my-project --ai windsurf
@@ -217,6 +219,9 @@ specify init my-project --ai shai
# Initialize with IBM Bob support # Initialize with IBM Bob support
specify init my-project --ai bob specify init my-project --ai bob
# Initialize with an unsupported agent (generic / bring your own agent)
specify init my-project --ai generic --ai-commands-dir .myagent/commands/
# Initialize with PowerShell scripts (Windows/cross-platform) # Initialize with PowerShell scripts (Windows/cross-platform)
specify init my-project --ai copilot --script ps specify init my-project --ai copilot --script ps
@@ -239,6 +244,12 @@ specify init my-project --ai claude --debug
# Use GitHub token for API requests (helpful for corporate environments) # Use GitHub token for API requests (helpful for corporate environments)
specify init my-project --ai claude --github-token ghp_your_token_here specify init my-project --ai claude --github-token ghp_your_token_here
# Install agent skills with the project
specify init my-project --ai claude --ai-skills
# Initialize in current directory with agent skills
specify init --here --ai gemini --ai-skills
# Check system requirements # Check system requirements
specify check specify check
``` ```
@@ -637,11 +648,6 @@ echo "Cleaning up..."
rm gcm-linux_amd64.2.6.1.deb rm gcm-linux_amd64.2.6.1.deb
``` ```
## 👥 Maintainers
- Den Delimarsky ([@localden](https://github.com/localden))
- John Lam ([@jflam](https://github.com/jflam))
## 💬 Support ## 💬 Support
For support, please open a [GitHub issue](https://github.com/github/spec-kit/issues/new). We welcome bug reports, feature requests, and questions about using Spec-Driven Development. For support, please open a [GitHub issue](https://github.com/github/spec-kit/issues/new). We welcome bug reports, feature requests, and questions about using Spec-Driven Development.

View File

@@ -81,6 +81,9 @@ Then, use the `/speckit.implement` slash command to execute the plan.
/speckit.implement /speckit.implement
``` ```
> [!TIP]
> **Phased Implementation**: For complex projects, implement in phases to avoid overwhelming the agent's context. Start with core functionality, validate it works, then add features incrementally.
## Detailed Example: Building Taskify ## Detailed Example: Building Taskify
Here's a complete example of building a team productivity platform: Here's a complete example of building a team productivity platform:
@@ -135,7 +138,15 @@ Be specific about your tech stack and technical requirements:
/speckit.plan We are going to generate this using .NET Aspire, using Postgres as the database. The frontend should use Blazor server with drag-and-drop task boards, real-time updates. There should be a REST API created with a projects API, tasks API, and a notifications API. /speckit.plan We are going to generate this using .NET Aspire, using Postgres as the database. The frontend should use Blazor server with drag-and-drop task boards, real-time updates. There should be a REST API created with a projects API, tasks API, and a notifications API.
``` ```
### Step 6: Validate and Implement ### Step 6: Define Tasks
Generate an actionable task list using the `/speckit.tasks` command:
```bash
/speckit.tasks
```
### Step 7: Validate and Implement
Have your AI agent audit the implementation plan using `/speckit.analyze`: Have your AI agent audit the implementation plan using `/speckit.analyze`:
@@ -149,6 +160,9 @@ Finally, implement the solution:
/speckit.implement /speckit.implement
``` ```
> [!TIP]
> **Phased Implementation**: For large projects like Taskify, consider implementing in phases (e.g., Phase 1: Basic project/task structure, Phase 2: Kanban functionality, Phase 3: Comments and assignments). This prevents context saturation and allows for validation at each stage.
## Key Principles ## Key Principles
- **Be explicit** about what you're building and why - **Be explicit** about what you're building and why

View File

@@ -46,7 +46,7 @@ Extensions are modular packages that add new commands and functionality to Spec
### Check Your Version ### Check Your Version
```bash ```bash
specify --version specify version
# Should show 0.1.0 or higher # Should show 0.1.0 or higher
``` ```

14
extensions/README.md Normal file
View File

@@ -0,0 +1,14 @@
# Spec Kit Community Extensions
Community-contributed extensions for [Spec Kit](https://github.com/github/spec-kit).
## Available Extensions
| Extension | Purpose | URL |
|-----------|---------|-----|
| V-Model Extension Pack | Enforces V-Model paired generation of development specs and test specs with full traceability | [spec-kit-v-model](https://github.com/leocamello/spec-kit-v-model) |
| Cleanup Extension | Post-implementation quality gate that reviews changes, fixes small issues (scout rule), creates tasks for medium issues, and generates analysis for large issues | [spec-kit-cleanup](https://github.com/dsrednicki/spec-kit-cleanup) |
## Adding Your Extension
See the [Extension Publishing Guide](EXTENSION-PUBLISHING-GUIDE.md) for instructions on how to submit your extension to the community catalog.

View File

@@ -0,0 +1,85 @@
{
"schema_version": "1.0",
"updated_at": "2026-02-24T00:00:00Z",
"catalog_url": "https://raw.githubusercontent.com/github/spec-kit/main/extensions/catalog.community.json",
"extensions": {
"cleanup": {
"name": "Cleanup Extension",
"id": "cleanup",
"description": "Post-implementation quality gate that reviews changes, fixes small issues (scout rule), creates tasks for medium issues, and generates analysis for large issues.",
"author": "dsrednicki",
"version": "1.0.0",
"download_url": "https://github.com/dsrednicki/spec-kit-cleanup/archive/refs/tags/v1.0.0.zip",
"repository": "https://github.com/dsrednicki/spec-kit-cleanup",
"homepage": "https://github.com/dsrednicki/spec-kit-cleanup",
"documentation": "https://github.com/dsrednicki/spec-kit-cleanup/blob/main/README.md",
"changelog": "https://github.com/dsrednicki/spec-kit-cleanup/blob/main/CHANGELOG.md",
"license": "MIT",
"requires": {
"speckit_version": ">=0.1.0"
},
"provides": {
"commands": 1,
"hooks": 1
},
"tags": ["quality", "tech-debt", "review", "cleanup", "scout-rule"],
"verified": false,
"downloads": 0,
"stars": 0,
"created_at": "2026-02-22T00:00:00Z",
"updated_at": "2026-02-22T00:00:00Z"
},
"retrospective": {
"name": "Retrospective Extension",
"id": "retrospective",
"description": "Post-implementation retrospective with spec adherence scoring, drift analysis, and human-gated spec updates.",
"author": "emi-dm",
"version": "1.0.0",
"download_url": "https://github.com/emi-dm/spec-kit-retrospective/archive/refs/tags/v1.0.0.zip",
"repository": "https://github.com/emi-dm/spec-kit-retrospective",
"homepage": "https://github.com/emi-dm/spec-kit-retrospective",
"documentation": "https://github.com/emi-dm/spec-kit-retrospective/blob/main/README.md",
"changelog": "https://github.com/emi-dm/spec-kit-retrospective/blob/main/CHANGELOG.md",
"license": "MIT",
"requires": {
"speckit_version": ">=0.1.0"
},
"provides": {
"commands": 1,
"hooks": 1
},
"tags": ["retrospective", "spec-drift", "quality", "analysis", "governance"],
"verified": false,
"downloads": 0,
"stars": 0,
"created_at": "2026-02-24T00:00:00Z",
"updated_at": "2026-02-24T00:00:00Z"
},
"v-model": {
"name": "V-Model Extension Pack",
"id": "v-model",
"description": "Enforces V-Model paired generation of development specs and test specs with full traceability.",
"author": "leocamello",
"version": "0.4.0",
"download_url": "https://github.com/leocamello/spec-kit-v-model/archive/refs/tags/v0.4.0.zip",
"repository": "https://github.com/leocamello/spec-kit-v-model",
"homepage": "https://github.com/leocamello/spec-kit-v-model",
"documentation": "https://github.com/leocamello/spec-kit-v-model/blob/main/README.md",
"changelog": "https://github.com/leocamello/spec-kit-v-model/blob/main/CHANGELOG.md",
"license": "MIT",
"requires": {
"speckit_version": ">=0.1.0"
},
"provides": {
"commands": 9,
"hooks": 1
},
"tags": ["v-model", "traceability", "testing", "compliance", "safety-critical"],
"verified": false,
"downloads": 0,
"stars": 0,
"created_at": "2026-02-20T00:00:00Z",
"updated_at": "2026-02-22T00:00:00Z"
}
}
}

View File

@@ -1,60 +0,0 @@
{
"schema_version": "1.0",
"updated_at": "2026-02-03T00:00:00Z",
"catalog_url": "https://your-org.example.com/speckit/catalog.json",
"extensions": {
"jira": {
"name": "Jira Integration",
"id": "jira",
"description": "Create Jira Epics, Stories, and Issues from spec-kit artifacts",
"author": "Your Organization",
"version": "2.1.0",
"download_url": "https://github.com/your-org/spec-kit-jira/archive/refs/tags/v2.1.0.zip",
"repository": "https://github.com/your-org/spec-kit-jira",
"homepage": "https://github.com/your-org/spec-kit-jira",
"documentation": "https://github.com/your-org/spec-kit-jira/blob/main/README.md",
"changelog": "https://github.com/your-org/spec-kit-jira/blob/main/CHANGELOG.md",
"license": "MIT",
"requires": {
"speckit_version": ">=0.1.0",
"tools": [
{
"name": "atlassian",
"version": ">=1.0.0",
"required": true
}
]
},
"provides": {
"commands": 3,
"hooks": 1
},
"tags": ["jira", "atlassian", "issue-tracking"],
"verified": true,
"downloads": 0,
"stars": 0,
"created_at": "2026-01-28T00:00:00Z",
"updated_at": "2026-02-03T00:00:00Z"
},
"linear": {
"name": "Linear Integration",
"id": "linear",
"description": "Sync specs and tasks with Linear issues",
"author": "Your Organization",
"version": "1.0.0",
"download_url": "https://github.com/your-org/spec-kit-linear/archive/refs/tags/v1.0.0.zip",
"repository": "https://github.com/your-org/spec-kit-linear",
"license": "MIT",
"requires": {
"speckit_version": ">=0.1.0"
},
"provides": {
"commands": 2
},
"tags": ["linear", "issue-tracking"],
"verified": false,
"created_at": "2026-01-30T00:00:00Z",
"updated_at": "2026-01-30T00:00:00Z"
}
}
}

View File

@@ -1,10 +1,11 @@
[project] [project]
name = "specify-cli" name = "specify-cli"
version = "0.1.0" version = "0.1.6"
description = "Specify CLI, part of GitHub Spec Kit. A tool to bootstrap your projects for Spec-Driven Development (SDD)." description = "Specify CLI, part of GitHub Spec Kit. A tool to bootstrap your projects for Spec-Driven Development (SDD)."
requires-python = ">=3.11" requires-python = ">=3.11"
dependencies = [ dependencies = [
"typer", "typer",
"click>=8.1",
"rich", "rich",
"httpx[socks]", "httpx[socks]",
"platformdirs", "platformdirs",

View File

@@ -35,7 +35,7 @@
# - Creates default Claude file if no agent files exist # - Creates default Claude file if no agent files exist
# #
# Usage: ./update-agent-context.sh [agent_type] # Usage: ./update-agent-context.sh [agent_type]
# Agent types: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|shai|q|agy|bob|qoder # Agent types: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|roo|codebuddy|amp|shai|q|agy|bob|qodercli
# Leave empty to update all existing agent files # Leave empty to update all existing agent files
set -e set -e
@@ -619,7 +619,7 @@ update_specific_agent() {
codebuddy) codebuddy)
update_agent_file "$CODEBUDDY_FILE" "CodeBuddy CLI" update_agent_file "$CODEBUDDY_FILE" "CodeBuddy CLI"
;; ;;
qoder) qodercli)
update_agent_file "$QODER_FILE" "Qoder CLI" update_agent_file "$QODER_FILE" "Qoder CLI"
;; ;;
amp) amp)
@@ -637,9 +637,12 @@ update_specific_agent() {
bob) bob)
update_agent_file "$BOB_FILE" "IBM Bob" update_agent_file "$BOB_FILE" "IBM Bob"
;; ;;
generic)
log_info "Generic agent: no predefined context file. Use the agent-specific update script for your agent."
;;
*) *)
log_error "Unknown agent type '$agent_type'" log_error "Unknown agent type '$agent_type'"
log_error "Expected: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|roo|amp|shai|q|agy|bob|qoder" log_error "Expected: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|roo|codebuddy|amp|shai|q|agy|bob|qodercli|generic"
exit 1 exit 1
;; ;;
esac esac
@@ -723,7 +726,6 @@ update_all_existing_agents() {
update_agent_file "$AGY_FILE" "Antigravity" update_agent_file "$AGY_FILE" "Antigravity"
found_agent=true found_agent=true
fi fi
if [[ -f "$BOB_FILE" ]]; then if [[ -f "$BOB_FILE" ]]; then
update_agent_file "$BOB_FILE" "IBM Bob" update_agent_file "$BOB_FILE" "IBM Bob"
found_agent=true found_agent=true
@@ -753,7 +755,7 @@ print_summary() {
echo echo
log_info "Usage: $0 [claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|codebuddy|shai|q|agy|bob|qoder]" log_info "Usage: $0 [claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|roo|codebuddy|amp|shai|q|agy|bob|qodercli]"
} }
#============================================================================== #==============================================================================

View File

@@ -9,7 +9,7 @@ Mirrors the behavior of scripts/bash/update-agent-context.sh:
2. Plan Data Extraction 2. Plan Data Extraction
3. Agent File Management (create from template or update existing) 3. Agent File Management (create from template or update existing)
4. Content Generation (technology stack, recent changes, timestamp) 4. Content Generation (technology stack, recent changes, timestamp)
5. Multi-Agent Support (claude, gemini, copilot, cursor-agent, qwen, opencode, codex, windsurf, kilocode, auggie, roo, codebuddy, amp, shai, q, agy, bob, qoder) 5. Multi-Agent Support (claude, gemini, copilot, cursor-agent, qwen, opencode, codex, windsurf, kilocode, auggie, roo, codebuddy, amp, shai, q, agy, bob, qodercli)
.PARAMETER AgentType .PARAMETER AgentType
Optional agent key to update a single agent. If omitted, updates all existing agent files (creating a default Claude file if none exist). Optional agent key to update a single agent. If omitted, updates all existing agent files (creating a default Claude file if none exist).
@@ -25,7 +25,7 @@ Relies on common helper functions in common.ps1
#> #>
param( param(
[Parameter(Position=0)] [Parameter(Position=0)]
[ValidateSet('claude','gemini','copilot','cursor-agent','qwen','opencode','codex','windsurf','kilocode','auggie','roo','codebuddy','amp','shai','q','agy','bob','qoder')] [ValidateSet('claude','gemini','copilot','cursor-agent','qwen','opencode','codex','windsurf','kilocode','auggie','roo','codebuddy','amp','shai','q','agy','bob','qodercli','generic')]
[string]$AgentType [string]$AgentType
) )
@@ -384,13 +384,14 @@ function Update-SpecificAgent {
'auggie' { Update-AgentFile -TargetFile $AUGGIE_FILE -AgentName 'Auggie CLI' } 'auggie' { Update-AgentFile -TargetFile $AUGGIE_FILE -AgentName 'Auggie CLI' }
'roo' { Update-AgentFile -TargetFile $ROO_FILE -AgentName 'Roo Code' } 'roo' { Update-AgentFile -TargetFile $ROO_FILE -AgentName 'Roo Code' }
'codebuddy' { Update-AgentFile -TargetFile $CODEBUDDY_FILE -AgentName 'CodeBuddy CLI' } 'codebuddy' { Update-AgentFile -TargetFile $CODEBUDDY_FILE -AgentName 'CodeBuddy CLI' }
'qoder' { Update-AgentFile -TargetFile $QODER_FILE -AgentName 'Qoder CLI' } 'qodercli' { Update-AgentFile -TargetFile $QODER_FILE -AgentName 'Qoder CLI' }
'amp' { Update-AgentFile -TargetFile $AMP_FILE -AgentName 'Amp' } 'amp' { Update-AgentFile -TargetFile $AMP_FILE -AgentName 'Amp' }
'shai' { Update-AgentFile -TargetFile $SHAI_FILE -AgentName 'SHAI' } 'shai' { Update-AgentFile -TargetFile $SHAI_FILE -AgentName 'SHAI' }
'q' { Update-AgentFile -TargetFile $Q_FILE -AgentName 'Amazon Q Developer CLI' } 'q' { Update-AgentFile -TargetFile $Q_FILE -AgentName 'Amazon Q Developer CLI' }
'agy' { Update-AgentFile -TargetFile $AGY_FILE -AgentName 'Antigravity' } 'agy' { Update-AgentFile -TargetFile $AGY_FILE -AgentName 'Antigravity' }
'bob' { Update-AgentFile -TargetFile $BOB_FILE -AgentName 'IBM Bob' } 'bob' { Update-AgentFile -TargetFile $BOB_FILE -AgentName 'IBM Bob' }
default { Write-Err "Unknown agent type '$Type'"; Write-Err 'Expected: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|roo|codebuddy|amp|shai|q|agy|bob|qoder'; return $false } 'generic' { Write-Info 'Generic agent: no predefined context file. Use the agent-specific update script for your agent.' }
default { Write-Err "Unknown agent type '$Type'"; Write-Err 'Expected: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|roo|codebuddy|amp|shai|q|agy|bob|qodercli|generic'; return $false }
} }
} }
@@ -427,7 +428,7 @@ function Print-Summary {
if ($NEW_FRAMEWORK) { Write-Host " - Added framework: $NEW_FRAMEWORK" } if ($NEW_FRAMEWORK) { Write-Host " - Added framework: $NEW_FRAMEWORK" }
if ($NEW_DB -and $NEW_DB -ne 'N/A') { Write-Host " - Added database: $NEW_DB" } if ($NEW_DB -and $NEW_DB -ne 'N/A') { Write-Host " - Added database: $NEW_DB" }
Write-Host '' Write-Host ''
Write-Info 'Usage: ./update-agent-context.ps1 [-AgentType claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|roo|codebuddy|amp|shai|q|agy|bob|qoder]' Write-Info 'Usage: ./update-agent-context.ps1 [-AgentType claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|roo|codebuddy|amp|shai|q|agy|bob|qodercli|generic]'
} }
function Main { function Main {

View File

@@ -32,6 +32,7 @@ import tempfile
import shutil import shutil
import shlex import shlex
import json import json
import yaml
from pathlib import Path from pathlib import Path
from typing import Optional, Tuple from typing import Optional, Tuple
@@ -122,116 +123,141 @@ def _format_rate_limit_error(status_code: int, headers: httpx.Headers, url: str)
return "\n".join(lines) return "\n".join(lines)
# Agent configuration with name, folder, install URL, and CLI tool requirement # Agent configuration with name, folder, install URL, CLI tool requirement, and commands subdirectory
AGENT_CONFIG = { AGENT_CONFIG = {
"copilot": { "copilot": {
"name": "GitHub Copilot", "name": "GitHub Copilot",
"folder": ".github/", "folder": ".github/",
"commands_subdir": "agents", # Special: uses agents/ not commands/
"install_url": None, # IDE-based, no CLI check needed "install_url": None, # IDE-based, no CLI check needed
"requires_cli": False, "requires_cli": False,
}, },
"claude": { "claude": {
"name": "Claude Code", "name": "Claude Code",
"folder": ".claude/", "folder": ".claude/",
"commands_subdir": "commands",
"install_url": "https://docs.anthropic.com/en/docs/claude-code/setup", "install_url": "https://docs.anthropic.com/en/docs/claude-code/setup",
"requires_cli": True, "requires_cli": True,
}, },
"gemini": { "gemini": {
"name": "Gemini CLI", "name": "Gemini CLI",
"folder": ".gemini/", "folder": ".gemini/",
"commands_subdir": "commands",
"install_url": "https://github.com/google-gemini/gemini-cli", "install_url": "https://github.com/google-gemini/gemini-cli",
"requires_cli": True, "requires_cli": True,
}, },
"cursor-agent": { "cursor-agent": {
"name": "Cursor", "name": "Cursor",
"folder": ".cursor/", "folder": ".cursor/",
"commands_subdir": "commands",
"install_url": None, # IDE-based "install_url": None, # IDE-based
"requires_cli": False, "requires_cli": False,
}, },
"qwen": { "qwen": {
"name": "Qwen Code", "name": "Qwen Code",
"folder": ".qwen/", "folder": ".qwen/",
"commands_subdir": "commands",
"install_url": "https://github.com/QwenLM/qwen-code", "install_url": "https://github.com/QwenLM/qwen-code",
"requires_cli": True, "requires_cli": True,
}, },
"opencode": { "opencode": {
"name": "opencode", "name": "opencode",
"folder": ".opencode/", "folder": ".opencode/",
"commands_subdir": "command", # Special: singular 'command' not 'commands'
"install_url": "https://opencode.ai", "install_url": "https://opencode.ai",
"requires_cli": True, "requires_cli": True,
}, },
"codex": { "codex": {
"name": "Codex CLI", "name": "Codex CLI",
"folder": ".codex/", "folder": ".codex/",
"commands_subdir": "prompts", # Special: uses prompts/ not commands/
"install_url": "https://github.com/openai/codex", "install_url": "https://github.com/openai/codex",
"requires_cli": True, "requires_cli": True,
}, },
"windsurf": { "windsurf": {
"name": "Windsurf", "name": "Windsurf",
"folder": ".windsurf/", "folder": ".windsurf/",
"commands_subdir": "workflows", # Special: uses workflows/ not commands/
"install_url": None, # IDE-based "install_url": None, # IDE-based
"requires_cli": False, "requires_cli": False,
}, },
"kilocode": { "kilocode": {
"name": "Kilo Code", "name": "Kilo Code",
"folder": ".kilocode/", "folder": ".kilocode/",
"commands_subdir": "workflows", # Special: uses workflows/ not commands/
"install_url": None, # IDE-based "install_url": None, # IDE-based
"requires_cli": False, "requires_cli": False,
}, },
"auggie": { "auggie": {
"name": "Auggie CLI", "name": "Auggie CLI",
"folder": ".augment/", "folder": ".augment/",
"commands_subdir": "commands",
"install_url": "https://docs.augmentcode.com/cli/setup-auggie/install-auggie-cli", "install_url": "https://docs.augmentcode.com/cli/setup-auggie/install-auggie-cli",
"requires_cli": True, "requires_cli": True,
}, },
"codebuddy": { "codebuddy": {
"name": "CodeBuddy", "name": "CodeBuddy",
"folder": ".codebuddy/", "folder": ".codebuddy/",
"commands_subdir": "commands",
"install_url": "https://www.codebuddy.ai/cli", "install_url": "https://www.codebuddy.ai/cli",
"requires_cli": True, "requires_cli": True,
}, },
"qoder": { "qodercli": {
"name": "Qoder CLI", "name": "Qoder CLI",
"folder": ".qoder/", "folder": ".qoder/",
"commands_subdir": "commands",
"install_url": "https://qoder.com/cli", "install_url": "https://qoder.com/cli",
"requires_cli": True, "requires_cli": True,
}, },
"roo": { "roo": {
"name": "Roo Code", "name": "Roo Code",
"folder": ".roo/", "folder": ".roo/",
"commands_subdir": "commands",
"install_url": None, # IDE-based "install_url": None, # IDE-based
"requires_cli": False, "requires_cli": False,
}, },
"q": { "q": {
"name": "Amazon Q Developer CLI", "name": "Amazon Q Developer CLI",
"folder": ".amazonq/", "folder": ".amazonq/",
"commands_subdir": "prompts", # Special: uses prompts/ not commands/
"install_url": "https://aws.amazon.com/developer/learning/q-developer-cli/", "install_url": "https://aws.amazon.com/developer/learning/q-developer-cli/",
"requires_cli": True, "requires_cli": True,
}, },
"amp": { "amp": {
"name": "Amp", "name": "Amp",
"folder": ".agents/", "folder": ".agents/",
"commands_subdir": "commands",
"install_url": "https://ampcode.com/manual#install", "install_url": "https://ampcode.com/manual#install",
"requires_cli": True, "requires_cli": True,
}, },
"shai": { "shai": {
"name": "SHAI", "name": "SHAI",
"folder": ".shai/", "folder": ".shai/",
"commands_subdir": "commands",
"install_url": "https://github.com/ovh/shai", "install_url": "https://github.com/ovh/shai",
"requires_cli": True, "requires_cli": True,
}, },
"agy": { "agy": {
"name": "Antigravity", "name": "Antigravity",
"folder": ".agent/", "folder": ".agent/",
"commands_subdir": "workflows", # Special: uses workflows/ not commands/
"install_url": None, # IDE-based "install_url": None, # IDE-based
"requires_cli": False, "requires_cli": False,
}, },
"bob": { "bob": {
"name": "IBM Bob", "name": "IBM Bob",
"folder": ".bob/", "folder": ".bob/",
"commands_subdir": "commands",
"install_url": None, # IDE-based "install_url": None, # IDE-based
"requires_cli": False, "requires_cli": False,
}, },
"generic": {
"name": "Generic (bring your own agent)",
"folder": None, # Set dynamically via --ai-commands-dir
"commands_subdir": "commands",
"install_url": None,
"requires_cli": False,
},
} }
SCRIPT_TYPE_CHOICES = {"sh": "POSIX Shell (bash/zsh)", "ps": "PowerShell"} SCRIPT_TYPE_CHOICES = {"sh": "POSIX Shell (bash/zsh)", "ps": "PowerShell"}
@@ -669,7 +695,7 @@ def download_template_from_github(ai_assistant: str, download_dir: Path, *, scri
except ValueError as je: except ValueError as je:
raise RuntimeError(f"Failed to parse release JSON: {je}\nRaw (truncated 400): {response.text[:400]}") raise RuntimeError(f"Failed to parse release JSON: {je}\nRaw (truncated 400): {response.text[:400]}")
except Exception as e: except Exception as e:
console.print(f"[red]Error fetching release information[/red]") console.print("[red]Error fetching release information[/red]")
console.print(Panel(str(e), title="Fetch Error", border_style="red")) console.print(Panel(str(e), title="Fetch Error", border_style="red"))
raise typer.Exit(1) raise typer.Exit(1)
@@ -699,7 +725,7 @@ def download_template_from_github(ai_assistant: str, download_dir: Path, *, scri
zip_path = download_dir / filename zip_path = download_dir / filename
if verbose: if verbose:
console.print(f"[cyan]Downloading template...[/cyan]") console.print("[cyan]Downloading template...[/cyan]")
try: try:
with client.stream( with client.stream(
@@ -738,7 +764,7 @@ def download_template_from_github(ai_assistant: str, download_dir: Path, *, scri
for chunk in response.iter_bytes(chunk_size=8192): for chunk in response.iter_bytes(chunk_size=8192):
f.write(chunk) f.write(chunk)
except Exception as e: except Exception as e:
console.print(f"[red]Error downloading template[/red]") console.print("[red]Error downloading template[/red]")
detail = str(e) detail = str(e)
if zip_path.exists(): if zip_path.exists():
zip_path.unlink() zip_path.unlink()
@@ -822,7 +848,7 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, script_
tracker.add("flatten", "Flatten nested directory") tracker.add("flatten", "Flatten nested directory")
tracker.complete("flatten") tracker.complete("flatten")
elif verbose: elif verbose:
console.print(f"[cyan]Found nested directory structure[/cyan]") console.print("[cyan]Found nested directory structure[/cyan]")
for item in source_dir.iterdir(): for item in source_dir.iterdir():
dest_path = project_path / item.name dest_path = project_path / item.name
@@ -847,7 +873,7 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, script_
console.print(f"[yellow]Overwriting file:[/yellow] {item.name}") console.print(f"[yellow]Overwriting file:[/yellow] {item.name}")
shutil.copy2(item, dest_path) shutil.copy2(item, dest_path)
if verbose and not tracker: if verbose and not tracker:
console.print(f"[cyan]Template files merged into current directory[/cyan]") console.print("[cyan]Template files merged into current directory[/cyan]")
else: else:
zip_ref.extractall(project_path) zip_ref.extractall(project_path)
@@ -873,7 +899,7 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, script_
tracker.add("flatten", "Flatten nested directory") tracker.add("flatten", "Flatten nested directory")
tracker.complete("flatten") tracker.complete("flatten")
elif verbose: elif verbose:
console.print(f"[cyan]Flattened nested directory structure[/cyan]") console.print("[cyan]Flattened nested directory structure[/cyan]")
except Exception as e: except Exception as e:
if tracker: if tracker:
@@ -923,13 +949,17 @@ def ensure_executable_scripts(project_path: Path, tracker: StepTracker | None =
continue continue
except Exception: except Exception:
continue continue
st = script.stat(); mode = st.st_mode st = script.stat()
mode = st.st_mode
if mode & 0o111: if mode & 0o111:
continue continue
new_mode = mode new_mode = mode
if mode & 0o400: new_mode |= 0o100 if mode & 0o400:
if mode & 0o040: new_mode |= 0o010 new_mode |= 0o100
if mode & 0o004: new_mode |= 0o001 if mode & 0o040:
new_mode |= 0o010
if mode & 0o004:
new_mode |= 0o001
if not (new_mode & 0o100): if not (new_mode & 0o100):
new_mode |= 0o100 new_mode |= 0o100
os.chmod(script, new_mode) os.chmod(script, new_mode)
@@ -975,7 +1005,7 @@ def ensure_constitution_from_template(project_path: Path, tracker: StepTracker |
tracker.add("constitution", "Constitution setup") tracker.add("constitution", "Constitution setup")
tracker.complete("constitution", "copied from template") tracker.complete("constitution", "copied from template")
else: else:
console.print(f"[cyan]Initialized constitution from template[/cyan]") console.print("[cyan]Initialized constitution from template[/cyan]")
except Exception as e: except Exception as e:
if tracker: if tracker:
tracker.add("constitution", "Constitution setup") tracker.add("constitution", "Constitution setup")
@@ -983,10 +1013,209 @@ def ensure_constitution_from_template(project_path: Path, tracker: StepTracker |
else: else:
console.print(f"[yellow]Warning: Could not initialize constitution: {e}[/yellow]") console.print(f"[yellow]Warning: Could not initialize constitution: {e}[/yellow]")
# Agent-specific skill directory overrides for agents whose skills directory
# doesn't follow the standard <agent_folder>/skills/ pattern
AGENT_SKILLS_DIR_OVERRIDES = {
"codex": ".agents/skills", # Codex agent layout override
}
# Default skills directory for agents not in AGENT_CONFIG
DEFAULT_SKILLS_DIR = ".agents/skills"
# Enhanced descriptions for each spec-kit command skill
SKILL_DESCRIPTIONS = {
"specify": "Create or update feature specifications from natural language descriptions. Use when starting new features or refining requirements. Generates spec.md with user stories, functional requirements, and acceptance criteria following spec-driven development methodology.",
"plan": "Generate technical implementation plans from feature specifications. Use after creating a spec to define architecture, tech stack, and implementation phases. Creates plan.md with detailed technical design.",
"tasks": "Break down implementation plans into actionable task lists. Use after planning to create a structured task breakdown. Generates tasks.md with ordered, dependency-aware tasks.",
"implement": "Execute all tasks from the task breakdown to build the feature. Use after task generation to systematically implement the planned solution following TDD approach where applicable.",
"analyze": "Perform cross-artifact consistency analysis across spec.md, plan.md, and tasks.md. Use after task generation to identify gaps, duplications, and inconsistencies before implementation.",
"clarify": "Structured clarification workflow for underspecified requirements. Use before planning to resolve ambiguities through coverage-based questioning. Records answers in spec clarifications section.",
"constitution": "Create or update project governing principles and development guidelines. Use at project start to establish code quality, testing standards, and architectural constraints that guide all development.",
"checklist": "Generate custom quality checklists for validating requirements completeness and clarity. Use to create unit tests for English that ensure spec quality before implementation.",
"taskstoissues": "Convert tasks from tasks.md into GitHub issues. Use after task breakdown to track work items in GitHub project management.",
}
def _get_skills_dir(project_path: Path, selected_ai: str) -> Path:
"""Resolve the agent-specific skills directory for the given AI assistant.
Uses ``AGENT_SKILLS_DIR_OVERRIDES`` first, then falls back to
``AGENT_CONFIG[agent]["folder"] + "skills"``, and finally to
``DEFAULT_SKILLS_DIR``.
"""
if selected_ai in AGENT_SKILLS_DIR_OVERRIDES:
return project_path / AGENT_SKILLS_DIR_OVERRIDES[selected_ai]
agent_config = AGENT_CONFIG.get(selected_ai, {})
agent_folder = agent_config.get("folder", "")
if agent_folder:
return project_path / agent_folder.rstrip("/") / "skills"
return project_path / DEFAULT_SKILLS_DIR
def install_ai_skills(project_path: Path, selected_ai: str, tracker: StepTracker | None = None) -> bool:
"""Install Prompt.MD files from templates/commands/ as agent skills.
Skills are written to the agent-specific skills directory following the
`agentskills.io <https://agentskills.io/specification>`_ specification.
Installation is additive — existing files are never removed and prompt
command files in the agent's commands directory are left untouched.
Args:
project_path: Target project directory.
selected_ai: AI assistant key from ``AGENT_CONFIG``.
tracker: Optional progress tracker.
Returns:
``True`` if at least one skill was installed or all skills were
already present (idempotent re-run), ``False`` otherwise.
"""
# Locate command templates in the agent's extracted commands directory.
# download_and_extract_template() already placed the .md files here.
agent_config = AGENT_CONFIG.get(selected_ai, {})
agent_folder = agent_config.get("folder", "")
commands_subdir = agent_config.get("commands_subdir", "commands")
if agent_folder:
templates_dir = project_path / agent_folder.rstrip("/") / commands_subdir
else:
templates_dir = project_path / commands_subdir
if not templates_dir.exists() or not any(templates_dir.glob("*.md")):
# Fallback: try the repo-relative path (for running from source checkout)
# This also covers agents whose extracted commands are in a different
# format (e.g. gemini uses .toml, not .md).
script_dir = Path(__file__).parent.parent.parent # up from src/specify_cli/
fallback_dir = script_dir / "templates" / "commands"
if fallback_dir.exists() and any(fallback_dir.glob("*.md")):
templates_dir = fallback_dir
if not templates_dir.exists() or not any(templates_dir.glob("*.md")):
if tracker:
tracker.error("ai-skills", "command templates not found")
else:
console.print("[yellow]Warning: command templates not found, skipping skills installation[/yellow]")
return False
command_files = sorted(templates_dir.glob("*.md"))
if not command_files:
if tracker:
tracker.skip("ai-skills", "no command templates found")
else:
console.print("[yellow]No command templates found to install[/yellow]")
return False
# Resolve the correct skills directory for this agent
skills_dir = _get_skills_dir(project_path, selected_ai)
skills_dir.mkdir(parents=True, exist_ok=True)
if tracker:
tracker.start("ai-skills")
installed_count = 0
skipped_count = 0
for command_file in command_files:
try:
content = command_file.read_text(encoding="utf-8")
# Parse YAML frontmatter
if content.startswith("---"):
parts = content.split("---", 2)
if len(parts) >= 3:
frontmatter = yaml.safe_load(parts[1])
if not isinstance(frontmatter, dict):
frontmatter = {}
body = parts[2].strip()
else:
# File starts with --- but has no closing ---
console.print(f"[yellow]Warning: {command_file.name} has malformed frontmatter (no closing ---), treating as plain content[/yellow]")
frontmatter = {}
body = content
else:
frontmatter = {}
body = content
command_name = command_file.stem
# Normalize: extracted commands may be named "speckit.<cmd>.md";
# strip the "speckit." prefix so skill names stay clean and
# SKILL_DESCRIPTIONS lookups work.
if command_name.startswith("speckit."):
command_name = command_name[len("speckit."):]
skill_name = f"speckit-{command_name}"
# Create skill directory (additive — never removes existing content)
skill_dir = skills_dir / skill_name
skill_dir.mkdir(parents=True, exist_ok=True)
# Select the best description available
original_desc = frontmatter.get("description", "")
enhanced_desc = SKILL_DESCRIPTIONS.get(command_name, original_desc or f"Spec-kit workflow command: {command_name}")
# Build SKILL.md following agentskills.io spec
# Use yaml.safe_dump to safely serialise the frontmatter and
# avoid YAML injection from descriptions containing colons,
# quotes, or newlines.
# Normalize source filename for metadata — strip speckit. prefix
# so it matches the canonical templates/commands/<cmd>.md path.
source_name = command_file.name
if source_name.startswith("speckit."):
source_name = source_name[len("speckit."):]
frontmatter_data = {
"name": skill_name,
"description": enhanced_desc,
"compatibility": "Requires spec-kit project structure with .specify/ directory",
"metadata": {
"author": "github-spec-kit",
"source": f"templates/commands/{source_name}",
},
}
frontmatter_text = yaml.safe_dump(frontmatter_data, sort_keys=False).strip()
skill_content = (
f"---\n"
f"{frontmatter_text}\n"
f"---\n\n"
f"# Speckit {command_name.title()} Skill\n\n"
f"{body}\n"
)
skill_file = skill_dir / "SKILL.md"
if skill_file.exists():
# Do not overwrite user-customized skills on re-runs
skipped_count += 1
continue
skill_file.write_text(skill_content, encoding="utf-8")
installed_count += 1
except Exception as e:
console.print(f"[yellow]Warning: Failed to install skill {command_file.stem}: {e}[/yellow]")
continue
if tracker:
if installed_count > 0 and skipped_count > 0:
tracker.complete("ai-skills", f"{installed_count} new + {skipped_count} existing skills in {skills_dir.relative_to(project_path)}")
elif installed_count > 0:
tracker.complete("ai-skills", f"{installed_count} skills → {skills_dir.relative_to(project_path)}")
elif skipped_count > 0:
tracker.complete("ai-skills", f"{skipped_count} skills already present")
else:
tracker.error("ai-skills", "no skills installed")
else:
if installed_count > 0:
console.print(f"[green]✓[/green] Installed {installed_count} agent skills to {skills_dir.relative_to(project_path)}/")
elif skipped_count > 0:
console.print(f"[green]✓[/green] {skipped_count} agent skills already present in {skills_dir.relative_to(project_path)}/")
else:
console.print("[yellow]No skills were installed[/yellow]")
return installed_count > 0 or skipped_count > 0
@app.command() @app.command()
def init( def init(
project_name: str = typer.Argument(None, help="Name for your new project directory (optional if using --here, or use '.' for current directory)"), project_name: str = typer.Argument(None, help="Name for your new project directory (optional if using --here, or use '.' for current directory)"),
ai_assistant: str = typer.Option(None, "--ai", help="AI assistant to use: claude, gemini, copilot, cursor-agent, qwen, opencode, codex, windsurf, kilocode, auggie, codebuddy, amp, shai, q, agy, bob, or qoder "), ai_assistant: str = typer.Option(None, "--ai", help="AI assistant to use: claude, gemini, copilot, cursor-agent, qwen, opencode, codex, windsurf, kilocode, auggie, codebuddy, amp, shai, q, agy, bob, qodercli, or generic (requires --ai-commands-dir)"),
ai_commands_dir: str = typer.Option(None, "--ai-commands-dir", help="Directory for agent command files (required with --ai generic, e.g. .myagent/commands/)"),
script_type: str = typer.Option(None, "--script", help="Script type to use: sh or ps"), script_type: str = typer.Option(None, "--script", help="Script type to use: sh or ps"),
ignore_agent_tools: bool = typer.Option(False, "--ignore-agent-tools", help="Skip checks for AI agent tools like Claude Code"), ignore_agent_tools: bool = typer.Option(False, "--ignore-agent-tools", help="Skip checks for AI agent tools like Claude Code"),
no_git: bool = typer.Option(False, "--no-git", help="Skip git repository initialization"), no_git: bool = typer.Option(False, "--no-git", help="Skip git repository initialization"),
@@ -995,6 +1224,7 @@ def init(
skip_tls: bool = typer.Option(False, "--skip-tls", help="Skip SSL/TLS verification (not recommended)"), skip_tls: bool = typer.Option(False, "--skip-tls", help="Skip SSL/TLS verification (not recommended)"),
debug: bool = typer.Option(False, "--debug", help="Show verbose diagnostic output for network and extraction failures"), debug: bool = typer.Option(False, "--debug", help="Show verbose diagnostic output for network and extraction failures"),
github_token: str = typer.Option(None, "--github-token", help="GitHub token to use for API requests (or set GH_TOKEN or GITHUB_TOKEN environment variable)"), github_token: str = typer.Option(None, "--github-token", help="GitHub token to use for API requests (or set GH_TOKEN or GITHUB_TOKEN environment variable)"),
ai_skills: bool = typer.Option(False, "--ai-skills", help="Install Prompt.MD templates as agent skills (requires --ai)"),
): ):
""" """
Initialize a new Specify project from the latest template. Initialize a new Specify project from the latest template.
@@ -1019,10 +1249,27 @@ def init(
specify init --here --ai codebuddy specify init --here --ai codebuddy
specify init --here specify init --here
specify init --here --force # Skip confirmation when current directory not empty specify init --here --force # Skip confirmation when current directory not empty
specify init my-project --ai claude --ai-skills # Install agent skills
specify init --here --ai gemini --ai-skills
specify init my-project --ai generic --ai-commands-dir .myagent/commands/ # Unsupported agent
""" """
show_banner() show_banner()
# Detect when option values are likely misinterpreted flags (parameter ordering issue)
if ai_assistant and ai_assistant.startswith("--"):
console.print(f"[red]Error:[/red] Invalid value for --ai: '{ai_assistant}'")
console.print("[yellow]Hint:[/yellow] Did you forget to provide a value for --ai?")
console.print("[yellow]Example:[/yellow] specify init --ai claude --here")
console.print(f"[yellow]Available agents:[/yellow] {', '.join(AGENT_CONFIG.keys())}")
raise typer.Exit(1)
if ai_commands_dir and ai_commands_dir.startswith("--"):
console.print(f"[red]Error:[/red] Invalid value for --ai-commands-dir: '{ai_commands_dir}'")
console.print("[yellow]Hint:[/yellow] Did you forget to provide a value for --ai-commands-dir?")
console.print("[yellow]Example:[/yellow] specify init --ai generic --ai-commands-dir .myagent/commands/")
raise typer.Exit(1)
if project_name == ".": if project_name == ".":
here = True here = True
project_name = None # Clear project_name to use existing validation logic project_name = None # Clear project_name to use existing validation logic
@@ -1035,6 +1282,11 @@ def init(
console.print("[red]Error:[/red] Must specify either a project name, use '.' for current directory, or use --here flag") console.print("[red]Error:[/red] Must specify either a project name, use '.' for current directory, or use --here flag")
raise typer.Exit(1) raise typer.Exit(1)
if ai_skills and not ai_assistant:
console.print("[red]Error:[/red] --ai-skills requires --ai to be specified")
console.print("[yellow]Usage:[/yellow] specify init <project> --ai <agent> --ai-skills")
raise typer.Exit(1)
if here: if here:
project_name = Path.cwd().name project_name = Path.cwd().name
project_path = Path.cwd() project_path = Path.cwd()
@@ -1098,6 +1350,16 @@ def init(
"copilot" "copilot"
) )
# Validate --ai-commands-dir usage
if selected_ai == "generic":
if not ai_commands_dir:
console.print("[red]Error:[/red] --ai-commands-dir is required when using --ai generic")
console.print("[dim]Example: specify init my-project --ai generic --ai-commands-dir .myagent/commands/[/dim]")
raise typer.Exit(1)
elif ai_commands_dir:
console.print(f"[red]Error:[/red] --ai-commands-dir can only be used with --ai generic (not '{selected_ai}')")
raise typer.Exit(1)
if not ignore_agent_tools: if not ignore_agent_tools:
agent_config = AGENT_CONFIG.get(selected_ai) agent_config = AGENT_CONFIG.get(selected_ai)
if agent_config and agent_config["requires_cli"]: if agent_config and agent_config["requires_cli"]:
@@ -1150,6 +1412,11 @@ def init(
("extracted-summary", "Extraction summary"), ("extracted-summary", "Extraction summary"),
("chmod", "Ensure scripts executable"), ("chmod", "Ensure scripts executable"),
("constitution", "Constitution setup"), ("constitution", "Constitution setup"),
]:
tracker.add(key, label)
if ai_skills:
tracker.add("ai-skills", "Install agent skills")
for key, label in [
("cleanup", "Cleanup"), ("cleanup", "Cleanup"),
("git", "Initialize git repository"), ("git", "Initialize git repository"),
("final", "Finalize") ("final", "Finalize")
@@ -1168,10 +1435,45 @@ def init(
download_and_extract_template(project_path, selected_ai, selected_script, here, verbose=False, tracker=tracker, client=local_client, debug=debug, github_token=github_token) download_and_extract_template(project_path, selected_ai, selected_script, here, verbose=False, tracker=tracker, client=local_client, debug=debug, github_token=github_token)
# For generic agent, rename placeholder directory to user-specified path
if selected_ai == "generic" and ai_commands_dir:
placeholder_dir = project_path / ".speckit" / "commands"
target_dir = project_path / ai_commands_dir
if placeholder_dir.is_dir():
target_dir.parent.mkdir(parents=True, exist_ok=True)
shutil.move(str(placeholder_dir), str(target_dir))
# Clean up empty .speckit dir if it's now empty
speckit_dir = project_path / ".speckit"
if speckit_dir.is_dir() and not any(speckit_dir.iterdir()):
speckit_dir.rmdir()
ensure_executable_scripts(project_path, tracker=tracker) ensure_executable_scripts(project_path, tracker=tracker)
ensure_constitution_from_template(project_path, tracker=tracker) ensure_constitution_from_template(project_path, tracker=tracker)
if ai_skills:
skills_ok = install_ai_skills(project_path, selected_ai, tracker=tracker)
# When --ai-skills is used on a NEW project and skills were
# successfully installed, remove the command files that the
# template archive just created. Skills replace commands, so
# keeping both would be confusing. For --here on an existing
# repo we leave pre-existing commands untouched to avoid a
# breaking change. We only delete AFTER skills succeed so the
# project always has at least one of {commands, skills}.
if skills_ok and not here:
agent_cfg = AGENT_CONFIG.get(selected_ai, {})
agent_folder = agent_cfg.get("folder", "")
if agent_folder:
cmds_dir = project_path / agent_folder.rstrip("/") / "commands"
if cmds_dir.exists():
try:
shutil.rmtree(cmds_dir)
except OSError:
# Best-effort cleanup: skills are already installed,
# so leaving stale commands is non-fatal.
console.print("[yellow]Warning: could not remove extracted commands directory[/yellow]")
if not no_git: if not no_git:
tracker.start("git") tracker.start("git")
if is_git_repo(project_path): if is_git_repo(project_path):
@@ -1230,16 +1532,17 @@ def init(
# Agent folder security notice # Agent folder security notice
agent_config = AGENT_CONFIG.get(selected_ai) agent_config = AGENT_CONFIG.get(selected_ai)
if agent_config: if agent_config:
agent_folder = agent_config["folder"] agent_folder = ai_commands_dir if selected_ai == "generic" else agent_config["folder"]
security_notice = Panel( if agent_folder:
f"Some agents may store credentials, auth tokens, or other identifying and private artifacts in the agent folder within your project.\n" security_notice = Panel(
f"Consider adding [cyan]{agent_folder}[/cyan] (or parts of it) to [cyan].gitignore[/cyan] to prevent accidental credential leakage.", f"Some agents may store credentials, auth tokens, or other identifying and private artifacts in the agent folder within your project.\n"
title="[yellow]Agent Folder Security[/yellow]", f"Consider adding [cyan]{agent_folder}[/cyan] (or parts of it) to [cyan].gitignore[/cyan] to prevent accidental credential leakage.",
border_style="yellow", title="[yellow]Agent Folder Security[/yellow]",
padding=(1, 2) border_style="yellow",
) padding=(1, 2)
console.print() )
console.print(security_notice) console.print()
console.print(security_notice)
steps_lines = [] steps_lines = []
if not here: if not here:
@@ -1276,9 +1579,9 @@ def init(
enhancement_lines = [ enhancement_lines = [
"Optional commands that you can use for your specs [bright_black](improve quality & confidence)[/bright_black]", "Optional commands that you can use for your specs [bright_black](improve quality & confidence)[/bright_black]",
"", "",
f"○ [cyan]/speckit.clarify[/] [bright_black](optional)[/bright_black] - Ask structured questions to de-risk ambiguous areas before planning (run before [cyan]/speckit.plan[/] if used)", "○ [cyan]/speckit.clarify[/] [bright_black](optional)[/bright_black] - Ask structured questions to de-risk ambiguous areas before planning (run before [cyan]/speckit.plan[/] if used)",
f"○ [cyan]/speckit.analyze[/] [bright_black](optional)[/bright_black] - Cross-artifact consistency & alignment report (after [cyan]/speckit.tasks[/], before [cyan]/speckit.implement[/])", "○ [cyan]/speckit.analyze[/] [bright_black](optional)[/bright_black] - Cross-artifact consistency & alignment report (after [cyan]/speckit.tasks[/], before [cyan]/speckit.implement[/])",
f"○ [cyan]/speckit.checklist[/] [bright_black](optional)[/bright_black] - Generate quality checklists to validate requirements completeness, clarity, and consistency (after [cyan]/speckit.plan[/])" "○ [cyan]/speckit.checklist[/] [bright_black](optional)[/bright_black] - Generate quality checklists to validate requirements completeness, clarity, and consistency (after [cyan]/speckit.plan[/])"
] ]
enhancements_panel = Panel("\n".join(enhancement_lines), title="Enhancement Commands", border_style="cyan", padding=(1,2)) enhancements_panel = Panel("\n".join(enhancement_lines), title="Enhancement Commands", border_style="cyan", padding=(1,2))
console.print() console.print()
@@ -1297,6 +1600,8 @@ def check():
agent_results = {} agent_results = {}
for agent_key, agent_config in AGENT_CONFIG.items(): for agent_key, agent_config in AGENT_CONFIG.items():
if agent_key == "generic":
continue # Generic is not a real agent to check
agent_name = agent_config["name"] agent_name = agent_config["name"]
requires_cli = agent_config["requires_cli"] requires_cli = agent_config["requires_cli"]
@@ -1311,10 +1616,10 @@ def check():
# Check VS Code variants (not in agent config) # Check VS Code variants (not in agent config)
tracker.add("code", "Visual Studio Code") tracker.add("code", "Visual Studio Code")
code_ok = check_tool("code", tracker=tracker) check_tool("code", tracker=tracker)
tracker.add("code-insiders", "Visual Studio Code Insiders") tracker.add("code-insiders", "Visual Studio Code Insiders")
code_insiders_ok = check_tool("code-insiders", tracker=tracker) check_tool("code-insiders", tracker=tracker)
console.print(tracker.render()) console.print(tracker.render())
@@ -1580,14 +1885,14 @@ def extension_add(
if zip_path.exists(): if zip_path.exists():
zip_path.unlink() zip_path.unlink()
console.print(f"\n[green]✓[/green] Extension installed successfully!") console.print("\n[green]✓[/green] Extension installed successfully!")
console.print(f"\n[bold]{manifest.name}[/bold] (v{manifest.version})") console.print(f"\n[bold]{manifest.name}[/bold] (v{manifest.version})")
console.print(f" {manifest.description}") console.print(f" {manifest.description}")
console.print(f"\n[bold cyan]Provided commands:[/bold cyan]") console.print("\n[bold cyan]Provided commands:[/bold cyan]")
for cmd in manifest.commands: for cmd in manifest.commands:
console.print(f"{cmd['name']} - {cmd.get('description', '')}") console.print(f"{cmd['name']} - {cmd.get('description', '')}")
console.print(f"\n[yellow]⚠[/yellow] Configuration may be required") console.print("\n[yellow]⚠[/yellow] Configuration may be required")
console.print(f" Check: .specify/extensions/{manifest.id}/") console.print(f" Check: .specify/extensions/{manifest.id}/")
except ValidationError as e: except ValidationError as e:
@@ -1637,11 +1942,11 @@ def extension_remove(
# Confirm removal # Confirm removal
if not force: if not force:
console.print(f"\n[yellow]⚠ This will remove:[/yellow]") console.print("\n[yellow]⚠ This will remove:[/yellow]")
console.print(f"{cmd_count} commands from AI agent") console.print(f"{cmd_count} commands from AI agent")
console.print(f" • Extension directory: .specify/extensions/{extension}/") console.print(f" • Extension directory: .specify/extensions/{extension}/")
if not keep_config: if not keep_config:
console.print(f" • Config files (will be backed up)") console.print(" • Config files (will be backed up)")
console.print() console.print()
confirm = typer.confirm("Continue?") confirm = typer.confirm("Continue?")
@@ -1660,7 +1965,7 @@ def extension_remove(
console.print(f"\nConfig files backed up to .specify/extensions/.backup/{extension}/") console.print(f"\nConfig files backed up to .specify/extensions/.backup/{extension}/")
console.print(f"\nTo reinstall: specify extension add {extension}") console.print(f"\nTo reinstall: specify extension add {extension}")
else: else:
console.print(f"[red]Error:[/red] Failed to remove extension") console.print("[red]Error:[/red] Failed to remove extension")
raise typer.Exit(1) raise typer.Exit(1)
@@ -1935,8 +2240,8 @@ def extension_update(
# TODO: Implement download and reinstall from URL # TODO: Implement download and reinstall from URL
# For now, just show message # For now, just show message
console.print( console.print(
f"[yellow]Note:[/yellow] Automatic update not yet implemented. " "[yellow]Note:[/yellow] Automatic update not yet implemented. "
f"Please update manually:" "Please update manually:"
) )
console.print(f" specify extension remove {ext_id} --keep-config") console.print(f" specify extension remove {ext_id} --keep-config")
console.print(f" specify extension add {ext_id}") console.print(f" specify extension add {ext_id}")
@@ -2036,7 +2341,7 @@ def extension_disable(
hook_executor.save_project_config(config) hook_executor.save_project_config(config)
console.print(f"[green]✓[/green] Extension '{extension}' disabled") console.print(f"[green]✓[/green] Extension '{extension}' disabled")
console.print(f"\nCommands will no longer be available. Hooks will not execute.") console.print("\nCommands will no longer be available. Hooks will not execute.")
console.print(f"To re-enable: specify extension enable {extension}") console.print(f"To re-enable: specify extension enable {extension}")

View File

@@ -647,7 +647,7 @@ class CommandRegistrar:
"args": "$ARGUMENTS", "args": "$ARGUMENTS",
"extension": ".md" "extension": ".md"
}, },
"qoder": { "qodercli": {
"dir": ".qoder/commands", "dir": ".qoder/commands",
"format": "markdown", "format": "markdown",
"args": "$ARGUMENTS", "args": "$ARGUMENTS",

View File

@@ -75,10 +75,11 @@ You **MUST** consider the user input before proceeding (if not empty).
- Validation rules from requirements - Validation rules from requirements
- State transitions if applicable - State transitions if applicable
2. **Generate API contracts** from functional requirements: 2. **Define interface contracts** (if project has external interfaces) → `/contracts/`:
- For each user action → endpoint - Identify what interfaces the project exposes to users or other systems
- Use standard REST/GraphQL patterns - Document the contract format appropriate for the project type
- Output OpenAPI/GraphQL schema to `/contracts/` - Examples: public APIs for libraries, command schemas for CLI tools, endpoints for web services, grammars for parsers, UI contracts for applications
- Skip if project is purely internal (build scripts, one-off tools, etc.)
3. **Agent context update**: 3. **Agent context update**:
- Run `{AGENT_SCRIPT}` - Run `{AGENT_SCRIPT}`

View File

@@ -235,7 +235,7 @@ When creating this spec from a user prompt:
- Performance targets: Standard web/mobile app expectations unless specified - Performance targets: Standard web/mobile app expectations unless specified
- Error handling: User-friendly messages with appropriate fallbacks - Error handling: User-friendly messages with appropriate fallbacks
- Authentication method: Standard session-based or OAuth2 for web apps - Authentication method: Standard session-based or OAuth2 for web apps
- Integration patterns: RESTful APIs unless specified otherwise - Integration patterns: Use project-appropriate patterns (REST/GraphQL for web services, function calls for libraries, CLI args for tools, etc.)
### Success Criteria Guidelines ### Success Criteria Guidelines

View File

@@ -28,14 +28,14 @@ You **MUST** consider the user input before proceeding (if not empty).
2. **Load design documents**: Read from FEATURE_DIR: 2. **Load design documents**: Read from FEATURE_DIR:
- **Required**: plan.md (tech stack, libraries, structure), spec.md (user stories with priorities) - **Required**: plan.md (tech stack, libraries, structure), spec.md (user stories with priorities)
- **Optional**: data-model.md (entities), contracts/ (API endpoints), research.md (decisions), quickstart.md (test scenarios) - **Optional**: data-model.md (entities), contracts/ (interface contracts), research.md (decisions), quickstart.md (test scenarios)
- Note: Not all projects have all documents. Generate tasks based on what's available. - Note: Not all projects have all documents. Generate tasks based on what's available.
3. **Execute task generation workflow**: 3. **Execute task generation workflow**:
- Load plan.md and extract tech stack, libraries, project structure - Load plan.md and extract tech stack, libraries, project structure
- Load spec.md and extract user stories with their priorities (P1, P2, P3, etc.) - Load spec.md and extract user stories with their priorities (P1, P2, P3, etc.)
- If data-model.md exists: Extract entities and map to user stories - If data-model.md exists: Extract entities and map to user stories
- If contracts/ exists: Map endpoints to user stories - If contracts/ exists: Map interface contracts to user stories
- If research.md exists: Extract decisions for setup tasks - If research.md exists: Extract decisions for setup tasks
- Generate tasks organized by user story (see Task Generation Rules below) - Generate tasks organized by user story (see Task Generation Rules below)
- Generate dependency graph showing user story completion order - Generate dependency graph showing user story completion order
@@ -112,13 +112,13 @@ Every task MUST strictly follow this format:
- Map all related components to their story: - Map all related components to their story:
- Models needed for that story - Models needed for that story
- Services needed for that story - Services needed for that story
- Endpoints/UI needed for that story - Interfaces/UI needed for that story
- If tests requested: Tests specific to that story - If tests requested: Tests specific to that story
- Mark story dependencies (most stories should be independent) - Mark story dependencies (most stories should be independent)
2. **From Contracts**: 2. **From Contracts**:
- Map each contract/endpoint → to the user story it serves - Map each interface contract → to the user story it serves
- If tests requested: Each contract → contract test task [P] before implementation in that story's phase - If tests requested: Each interface contract → contract test task [P] before implementation in that story's phase
3. **From Data Model**: 3. **From Data Model**:
- Map each entity to the user story(ies) that need it - Map each entity to the user story(ies) that need it

View File

@@ -22,7 +22,7 @@
**Storage**: [if applicable, e.g., PostgreSQL, CoreData, files or N/A] **Storage**: [if applicable, e.g., PostgreSQL, CoreData, files or N/A]
**Testing**: [e.g., pytest, XCTest, cargo test or NEEDS CLARIFICATION] **Testing**: [e.g., pytest, XCTest, cargo test or NEEDS CLARIFICATION]
**Target Platform**: [e.g., Linux server, iOS 15+, WASM or NEEDS CLARIFICATION] **Target Platform**: [e.g., Linux server, iOS 15+, WASM or NEEDS CLARIFICATION]
**Project Type**: [single/web/mobile - determines source structure] **Project Type**: [e.g., library/cli/web-service/mobile-app/compiler/desktop-app or NEEDS CLARIFICATION]
**Performance Goals**: [domain-specific, e.g., 1000 req/s, 10k lines/sec, 60 fps or NEEDS CLARIFICATION] **Performance Goals**: [domain-specific, e.g., 1000 req/s, 10k lines/sec, 60 fps or NEEDS CLARIFICATION]
**Constraints**: [domain-specific, e.g., <200ms p95, <100MB memory, offline-capable or NEEDS CLARIFICATION] **Constraints**: [domain-specific, e.g., <200ms p95, <100MB memory, offline-capable or NEEDS CLARIFICATION]
**Scale/Scope**: [domain-specific, e.g., 10k users, 1M LOC, 50 screens or NEEDS CLARIFICATION] **Scale/Scope**: [domain-specific, e.g., 10k users, 1M LOC, 50 screens or NEEDS CLARIFICATION]

694
tests/test_ai_skills.py Normal file
View File

@@ -0,0 +1,694 @@
"""
Unit tests for AI agent skills installation.
Tests cover:
- Skills directory resolution for different agents (_get_skills_dir)
- YAML frontmatter parsing and SKILL.md generation (install_ai_skills)
- Cleanup of duplicate command files when --ai-skills is used
- Missing templates directory handling
- Malformed template error handling
- CLI validation: --ai-skills requires --ai
"""
import re
import pytest
import tempfile
import shutil
import yaml
from pathlib import Path
from unittest.mock import patch
import specify_cli
from specify_cli import (
_get_skills_dir,
install_ai_skills,
AGENT_SKILLS_DIR_OVERRIDES,
DEFAULT_SKILLS_DIR,
SKILL_DESCRIPTIONS,
AGENT_CONFIG,
app,
)
# ===== Fixtures =====
@pytest.fixture
def temp_dir():
"""Create a temporary directory for tests."""
tmpdir = tempfile.mkdtemp()
yield Path(tmpdir)
shutil.rmtree(tmpdir)
@pytest.fixture
def project_dir(temp_dir):
"""Create a mock project directory."""
proj_dir = temp_dir / "test-project"
proj_dir.mkdir()
return proj_dir
@pytest.fixture
def templates_dir(project_dir):
"""Create mock command templates in the project's agent commands directory.
This simulates what download_and_extract_template() does: it places
command .md files into project_path/<agent_folder>/commands/.
install_ai_skills() now reads from here instead of from the repo
source tree.
"""
tpl_root = project_dir / ".claude" / "commands"
tpl_root.mkdir(parents=True, exist_ok=True)
# Template with valid YAML frontmatter
(tpl_root / "specify.md").write_text(
"---\n"
"description: Create or update the feature specification.\n"
"handoffs:\n"
" - label: Build Plan\n"
" agent: speckit.plan\n"
"scripts:\n"
" sh: scripts/bash/create-new-feature.sh\n"
"---\n"
"\n"
"# Specify Command\n"
"\n"
"Run this to create a spec.\n",
encoding="utf-8",
)
# Template with minimal frontmatter
(tpl_root / "plan.md").write_text(
"---\n"
"description: Generate implementation plan.\n"
"---\n"
"\n"
"# Plan Command\n"
"\n"
"Plan body content.\n",
encoding="utf-8",
)
# Template with no frontmatter
(tpl_root / "tasks.md").write_text(
"# Tasks Command\n"
"\n"
"Body without frontmatter.\n",
encoding="utf-8",
)
# Template with empty YAML frontmatter (yaml.safe_load returns None)
(tpl_root / "empty_fm.md").write_text(
"---\n"
"---\n"
"\n"
"# Empty Frontmatter Command\n"
"\n"
"Body with empty frontmatter.\n",
encoding="utf-8",
)
return tpl_root
@pytest.fixture
def commands_dir_claude(project_dir):
"""Create a populated .claude/commands directory simulating template extraction."""
cmd_dir = project_dir / ".claude" / "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
@pytest.fixture
def commands_dir_gemini(project_dir):
"""Create a populated .gemini/commands directory (TOML format)."""
cmd_dir = project_dir / ".gemini" / "commands"
cmd_dir.mkdir(parents=True)
for name in ["speckit.specify.toml", "speckit.plan.toml", "speckit.tasks.toml"]:
(cmd_dir / name).write_text(f'[command]\nname = "{name}"\n')
return cmd_dir
# ===== _get_skills_dir Tests =====
class TestGetSkillsDir:
"""Test the _get_skills_dir() helper function."""
def test_claude_skills_dir(self, project_dir):
"""Claude should use .claude/skills/."""
result = _get_skills_dir(project_dir, "claude")
assert result == project_dir / ".claude" / "skills"
def test_gemini_skills_dir(self, project_dir):
"""Gemini should use .gemini/skills/."""
result = _get_skills_dir(project_dir, "gemini")
assert result == project_dir / ".gemini" / "skills"
def test_copilot_skills_dir(self, project_dir):
"""Copilot should use .github/skills/."""
result = _get_skills_dir(project_dir, "copilot")
assert result == project_dir / ".github" / "skills"
def test_codex_uses_override(self, project_dir):
"""Codex should use the AGENT_SKILLS_DIR_OVERRIDES value."""
result = _get_skills_dir(project_dir, "codex")
assert result == project_dir / ".agents" / "skills"
def test_cursor_agent_skills_dir(self, project_dir):
"""Cursor should use .cursor/skills/."""
result = _get_skills_dir(project_dir, "cursor-agent")
assert result == project_dir / ".cursor" / "skills"
def test_unknown_agent_uses_default(self, project_dir):
"""Unknown agents should fall back to DEFAULT_SKILLS_DIR."""
result = _get_skills_dir(project_dir, "nonexistent-agent")
assert result == project_dir / DEFAULT_SKILLS_DIR
def test_all_configured_agents_resolve(self, project_dir):
"""Every agent in AGENT_CONFIG should resolve to a valid path."""
for agent_key in AGENT_CONFIG:
result = _get_skills_dir(project_dir, agent_key)
assert result is not None
assert str(result).startswith(str(project_dir))
# Should always end with "skills"
assert result.name == "skills"
def test_override_takes_precedence_over_config(self, project_dir):
"""AGENT_SKILLS_DIR_OVERRIDES should take precedence over AGENT_CONFIG."""
for agent_key in AGENT_SKILLS_DIR_OVERRIDES:
result = _get_skills_dir(project_dir, agent_key)
expected = project_dir / AGENT_SKILLS_DIR_OVERRIDES[agent_key]
assert result == expected
# ===== install_ai_skills Tests =====
class TestInstallAiSkills:
"""Test SKILL.md generation and installation logic."""
def test_skills_installed_with_correct_structure(self, project_dir, templates_dir):
"""Verify SKILL.md files have correct agentskills.io structure."""
result = install_ai_skills(project_dir, "claude")
assert result is True
skills_dir = project_dir / ".claude" / "skills"
assert skills_dir.exists()
# Check that skill directories were created
skill_dirs = sorted([d.name for d in skills_dir.iterdir() if d.is_dir()])
assert "speckit-plan" in skill_dirs
assert "speckit-specify" in skill_dirs
assert "speckit-tasks" in skill_dirs
assert "speckit-empty_fm" in skill_dirs
# Verify SKILL.md content for speckit-specify
skill_file = skills_dir / "speckit-specify" / "SKILL.md"
assert skill_file.exists()
content = skill_file.read_text()
# Check agentskills.io frontmatter
assert content.startswith("---\n")
assert "name: speckit-specify" in content
assert "description:" in content
assert "compatibility:" in content
assert "metadata:" in content
assert "author: github-spec-kit" in content
assert "source: templates/commands/specify.md" in content
# Check body content is included
assert "# Speckit Specify Skill" in content
assert "Run this to create a spec." in content
def test_generated_skill_has_parseable_yaml(self, project_dir, templates_dir):
"""Generated SKILL.md should contain valid, parseable YAML frontmatter."""
install_ai_skills(project_dir, "claude")
skill_file = project_dir / ".claude" / "skills" / "speckit-specify" / "SKILL.md"
content = skill_file.read_text()
# Extract and parse frontmatter
assert content.startswith("---\n")
parts = content.split("---", 2)
assert len(parts) >= 3
parsed = yaml.safe_load(parts[1])
assert isinstance(parsed, dict)
assert "name" in parsed
assert parsed["name"] == "speckit-specify"
assert "description" in parsed
def test_empty_yaml_frontmatter(self, project_dir, templates_dir):
"""Templates with empty YAML frontmatter (---\\n---) should not crash."""
result = install_ai_skills(project_dir, "claude")
assert result is True
skill_file = project_dir / ".claude" / "skills" / "speckit-empty_fm" / "SKILL.md"
assert skill_file.exists()
content = skill_file.read_text()
assert "name: speckit-empty_fm" in content
assert "Body with empty frontmatter." in content
def test_enhanced_descriptions_used_when_available(self, project_dir, templates_dir):
"""SKILL_DESCRIPTIONS take precedence over template frontmatter descriptions."""
install_ai_skills(project_dir, "claude")
skill_file = project_dir / ".claude" / "skills" / "speckit-specify" / "SKILL.md"
content = skill_file.read_text()
# Parse the generated YAML to compare the description value
# (yaml.safe_dump may wrap long strings across multiple lines)
parts = content.split("---", 2)
parsed = yaml.safe_load(parts[1])
if "specify" in SKILL_DESCRIPTIONS:
assert parsed["description"] == SKILL_DESCRIPTIONS["specify"]
def test_template_without_frontmatter(self, project_dir, templates_dir):
"""Templates without YAML frontmatter should still produce valid skills."""
install_ai_skills(project_dir, "claude")
skill_file = project_dir / ".claude" / "skills" / "speckit-tasks" / "SKILL.md"
assert skill_file.exists()
content = skill_file.read_text()
# Should still have valid SKILL.md structure
assert "name: speckit-tasks" in content
assert "Body without frontmatter." in content
def test_missing_templates_directory(self, project_dir):
"""Returns False when no command templates exist anywhere."""
# No .claude/commands/ exists, and __file__ fallback won't find anything
fake_init = project_dir / "nonexistent" / "src" / "specify_cli" / "__init__.py"
fake_init.parent.mkdir(parents=True, exist_ok=True)
fake_init.touch()
with patch.object(specify_cli, "__file__", str(fake_init)):
result = install_ai_skills(project_dir, "claude")
assert result is False
# Skills directory should not exist
skills_dir = project_dir / ".claude" / "skills"
assert not skills_dir.exists()
def test_empty_templates_directory(self, project_dir):
"""Returns False when commands directory has no .md files."""
# Create empty .claude/commands/
empty_cmds = project_dir / ".claude" / "commands"
empty_cmds.mkdir(parents=True)
# Block the __file__ fallback so it can't find real templates
fake_init = project_dir / "nowhere" / "src" / "specify_cli" / "__init__.py"
fake_init.parent.mkdir(parents=True, exist_ok=True)
fake_init.touch()
with patch.object(specify_cli, "__file__", str(fake_init)):
result = install_ai_skills(project_dir, "claude")
assert result is False
def test_malformed_yaml_frontmatter(self, project_dir):
"""Malformed YAML in a template should be handled gracefully, not crash."""
# Create .claude/commands/ with a broken template
cmds_dir = project_dir / ".claude" / "commands"
cmds_dir.mkdir(parents=True)
(cmds_dir / "broken.md").write_text(
"---\n"
"description: [unclosed bracket\n"
" invalid: yaml: content: here\n"
"---\n"
"\n"
"# Broken\n",
encoding="utf-8",
)
# Should not raise — errors are caught per-file
result = install_ai_skills(project_dir, "claude")
# The broken template should be skipped but not crash the process
assert result is False
def test_additive_does_not_overwrite_other_files(self, project_dir, templates_dir):
"""Installing skills should not remove non-speckit files in the skills dir."""
# Pre-create a custom skill
custom_dir = project_dir / ".claude" / "skills" / "my-custom-skill"
custom_dir.mkdir(parents=True)
custom_file = custom_dir / "SKILL.md"
custom_file.write_text("# My Custom Skill\n")
install_ai_skills(project_dir, "claude")
# Custom skill should still exist
assert custom_file.exists()
assert custom_file.read_text() == "# My Custom Skill\n"
def test_return_value(self, project_dir, templates_dir):
"""install_ai_skills returns True when skills installed, False otherwise."""
assert install_ai_skills(project_dir, "claude") is True
def test_return_false_when_no_templates(self, project_dir):
"""install_ai_skills returns False when no templates found."""
fake_init = project_dir / "missing" / "src" / "specify_cli" / "__init__.py"
fake_init.parent.mkdir(parents=True, exist_ok=True)
fake_init.touch()
with patch.object(specify_cli, "__file__", str(fake_init)):
assert install_ai_skills(project_dir, "claude") is False
def test_non_md_commands_dir_falls_back(self, project_dir):
"""When extracted commands are .toml (e.g. gemini), fall back to repo templates."""
# Simulate gemini template extraction: .gemini/commands/ with .toml files only
cmds_dir = project_dir / ".gemini" / "commands"
cmds_dir.mkdir(parents=True)
(cmds_dir / "speckit.specify.toml").write_text('[command]\nname = "specify"\n')
(cmds_dir / "speckit.plan.toml").write_text('[command]\nname = "plan"\n')
# The __file__ fallback should find the real repo templates/commands/*.md
result = install_ai_skills(project_dir, "gemini")
assert result is True
skills_dir = project_dir / ".gemini" / "skills"
assert skills_dir.exists()
# Should have installed skills from the fallback .md templates
skill_dirs = [d.name for d in skills_dir.iterdir() if d.is_dir()]
assert len(skill_dirs) >= 1
# .toml commands should be untouched
assert (cmds_dir / "speckit.specify.toml").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."""
proj = temp_dir / f"proj-{agent_key}"
proj.mkdir()
# Place .md templates in the agent's commands directory
agent_folder = AGENT_CONFIG[agent_key]["folder"]
cmds_dir = proj / agent_folder.rstrip("/") / "commands"
cmds_dir.mkdir(parents=True)
(cmds_dir / "specify.md").write_text(
"---\ndescription: Test command\n---\n\n# Test\n\nBody.\n"
)
result = install_ai_skills(proj, agent_key)
assert result is True
skills_dir = _get_skills_dir(proj, agent_key)
assert skills_dir.exists()
skill_dirs = [d.name for d in skills_dir.iterdir() if d.is_dir()]
assert "speckit-specify" in skill_dirs
assert (skills_dir / "speckit-specify" / "SKILL.md").exists()
class TestCommandCoexistence:
"""Verify install_ai_skills never touches command files.
Cleanup of freshly-extracted commands for NEW projects is handled
in init(), not in install_ai_skills(). These tests confirm that
install_ai_skills leaves existing commands intact.
"""
def test_existing_commands_preserved_claude(self, project_dir, templates_dir, commands_dir_claude):
"""install_ai_skills must NOT remove pre-existing .claude/commands files."""
# Verify commands exist before
assert len(list(commands_dir_claude.glob("speckit.*"))) == 3
install_ai_skills(project_dir, "claude")
# Commands must still be there — install_ai_skills never touches them
remaining = list(commands_dir_claude.glob("speckit.*"))
assert len(remaining) == 3
def test_existing_commands_preserved_gemini(self, project_dir, templates_dir, commands_dir_gemini):
"""install_ai_skills must NOT remove pre-existing .gemini/commands files."""
assert len(list(commands_dir_gemini.glob("speckit.*"))) == 3
install_ai_skills(project_dir, "gemini")
remaining = list(commands_dir_gemini.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")
assert commands_dir_claude.exists()
def test_no_commands_dir_no_error(self, project_dir, templates_dir):
"""No error when installing skills — commands dir has templates and is preserved."""
result = install_ai_skills(project_dir, "claude")
# Should succeed since templates are in .claude/commands/ via fixture
assert result is True
# ===== New-Project Command Skip Tests =====
class TestNewProjectCommandSkip:
"""Test that init() removes extracted commands for new projects only.
These tests run init() end-to-end via CliRunner with
download_and_extract_template patched to create local fixtures.
"""
def _fake_extract(self, agent, project_path, **_kwargs):
"""Simulate template extraction: create agent commands dir."""
agent_cfg = AGENT_CONFIG.get(agent, {})
agent_folder = agent_cfg.get("folder", "")
if agent_folder:
cmds_dir = project_path / agent_folder.rstrip("/") / "commands"
cmds_dir.mkdir(parents=True, exist_ok=True)
(cmds_dir / "speckit.specify.md").write_text("# spec")
def test_new_project_commands_removed_after_skills_succeed(self, tmp_path):
"""For new projects, commands should be removed when skills succeed."""
from typer.testing import CliRunner
runner = CliRunner()
target = tmp_path / "new-proj"
def fake_download(project_path, *args, **kwargs):
self._fake_extract("claude", project_path)
with patch("specify_cli.download_and_extract_template", side_effect=fake_download), \
patch("specify_cli.ensure_executable_scripts"), \
patch("specify_cli.ensure_constitution_from_template"), \
patch("specify_cli.install_ai_skills", return_value=True) as mock_skills, \
patch("specify_cli.is_git_repo", return_value=False), \
patch("specify_cli.shutil.which", return_value="/usr/bin/git"):
result = runner.invoke(app, ["init", str(target), "--ai", "claude", "--ai-skills", "--script", "sh", "--no-git"])
# Skills should have been called
mock_skills.assert_called_once()
# Commands dir should have been removed after skills succeeded
cmds_dir = target / ".claude" / "commands"
assert not cmds_dir.exists()
def test_commands_preserved_when_skills_fail(self, tmp_path):
"""If skills fail, commands should NOT be removed (safety net)."""
from typer.testing import CliRunner
runner = CliRunner()
target = tmp_path / "fail-proj"
def fake_download(project_path, *args, **kwargs):
self._fake_extract("claude", project_path)
with patch("specify_cli.download_and_extract_template", side_effect=fake_download), \
patch("specify_cli.ensure_executable_scripts"), \
patch("specify_cli.ensure_constitution_from_template"), \
patch("specify_cli.install_ai_skills", return_value=False), \
patch("specify_cli.is_git_repo", return_value=False), \
patch("specify_cli.shutil.which", return_value="/usr/bin/git"):
result = runner.invoke(app, ["init", str(target), "--ai", "claude", "--ai-skills", "--script", "sh", "--no-git"])
# Commands should still exist since skills failed
cmds_dir = target / ".claude" / "commands"
assert cmds_dir.exists()
assert (cmds_dir / "speckit.specify.md").exists()
def test_here_mode_commands_preserved(self, tmp_path, monkeypatch):
"""For --here on existing repos, commands must NOT be removed."""
from typer.testing import CliRunner
runner = CliRunner()
# Create a mock existing project with commands already present
target = tmp_path / "existing"
target.mkdir()
agent_folder = AGENT_CONFIG["claude"]["folder"]
cmds_dir = target / agent_folder.rstrip("/") / "commands"
cmds_dir.mkdir(parents=True)
(cmds_dir / "speckit.specify.md").write_text("# spec")
# --here uses CWD, so chdir into the target
monkeypatch.chdir(target)
def fake_download(project_path, *args, **kwargs):
pass # commands already exist, no need to re-create
with patch("specify_cli.download_and_extract_template", side_effect=fake_download), \
patch("specify_cli.ensure_executable_scripts"), \
patch("specify_cli.ensure_constitution_from_template"), \
patch("specify_cli.install_ai_skills", return_value=True), \
patch("specify_cli.is_git_repo", return_value=True), \
patch("specify_cli.shutil.which", return_value="/usr/bin/git"):
result = runner.invoke(app, ["init", "--here", "--ai", "claude", "--ai-skills", "--script", "sh", "--no-git"])
# Commands must remain for --here
assert cmds_dir.exists()
assert (cmds_dir / "speckit.specify.md").exists()
# ===== Skip-If-Exists Tests =====
class TestSkipIfExists:
"""Test that install_ai_skills does not overwrite existing SKILL.md files."""
def test_existing_skill_not_overwritten(self, project_dir, templates_dir):
"""Pre-existing SKILL.md should not be replaced on re-run."""
# Pre-create a custom SKILL.md for speckit-specify
skill_dir = project_dir / ".claude" / "skills" / "speckit-specify"
skill_dir.mkdir(parents=True)
custom_content = "# My Custom Specify Skill\nUser-modified content\n"
(skill_dir / "SKILL.md").write_text(custom_content)
result = install_ai_skills(project_dir, "claude")
# The custom SKILL.md should be untouched
assert (skill_dir / "SKILL.md").read_text() == custom_content
# But other skills should still be installed
assert result is True
assert (project_dir / ".claude" / "skills" / "speckit-plan" / "SKILL.md").exists()
assert (project_dir / ".claude" / "skills" / "speckit-tasks" / "SKILL.md").exists()
def test_fresh_install_writes_all_skills(self, project_dir, templates_dir):
"""On first install (no pre-existing skills), all should be written."""
result = install_ai_skills(project_dir, "claude")
assert result is True
skills_dir = project_dir / ".claude" / "skills"
skill_dirs = [d.name for d in skills_dir.iterdir() if d.is_dir()]
# All 4 templates should produce skills (specify, plan, tasks, empty_fm)
assert len(skill_dirs) == 4
# ===== SKILL_DESCRIPTIONS Coverage Tests =====
class TestSkillDescriptions:
"""Test SKILL_DESCRIPTIONS constants."""
def test_all_known_commands_have_descriptions(self):
"""All standard spec-kit commands should have enhanced descriptions."""
expected_commands = [
"specify", "plan", "tasks", "implement", "analyze",
"clarify", "constitution", "checklist", "taskstoissues",
]
for cmd in expected_commands:
assert cmd in SKILL_DESCRIPTIONS, f"Missing description for '{cmd}'"
assert len(SKILL_DESCRIPTIONS[cmd]) > 20, f"Description for '{cmd}' is too short"
# ===== CLI Validation Tests =====
class TestCliValidation:
"""Test --ai-skills CLI flag validation."""
def test_ai_skills_without_ai_fails(self):
"""--ai-skills without --ai should fail with exit code 1."""
from typer.testing import CliRunner
runner = CliRunner()
result = runner.invoke(app, ["init", "test-proj", "--ai-skills"])
assert result.exit_code == 1
assert "--ai-skills requires --ai" in result.output
def test_ai_skills_without_ai_shows_usage(self):
"""Error message should include usage hint."""
from typer.testing import CliRunner
runner = CliRunner()
result = runner.invoke(app, ["init", "test-proj", "--ai-skills"])
assert "Usage:" in result.output
assert "--ai" in result.output
def test_ai_skills_flag_appears_in_help(self):
"""--ai-skills should appear in init --help output."""
from typer.testing import CliRunner
runner = CliRunner()
result = runner.invoke(app, ["init", "--help"])
plain = re.sub(r'\x1b\[[0-9;]*m', '', result.output)
assert "--ai-skills" in plain
assert "agent skills" in plain.lower()
class TestParameterOrderingIssue:
"""Test fix for GitHub issue #1641: parameter ordering issues."""
def test_ai_flag_consuming_here_flag(self):
"""--ai without value should not consume --here flag (issue #1641)."""
from typer.testing import CliRunner
runner = CliRunner()
# This used to fail with "Must specify project name" because --here was consumed by --ai
result = runner.invoke(app, ["init", "--ai-skills", "--ai", "--here"])
assert result.exit_code == 1
assert "Invalid value for --ai" in result.output
assert "--here" in result.output # Should mention the invalid value
def test_ai_flag_consuming_ai_skills_flag(self):
"""--ai without value should not consume --ai-skills flag."""
from typer.testing import CliRunner
runner = CliRunner()
# This should fail with helpful error about missing --ai value
result = runner.invoke(app, ["init", "--here", "--ai", "--ai-skills"])
assert result.exit_code == 1
assert "Invalid value for --ai" in result.output
assert "--ai-skills" in result.output # Should mention the invalid value
def test_error_message_provides_hint(self):
"""Error message should provide helpful hint about missing value."""
from typer.testing import CliRunner
runner = CliRunner()
result = runner.invoke(app, ["init", "--ai", "--here"])
assert result.exit_code == 1
assert "Hint:" in result.output or "hint" in result.output.lower()
assert "forget to provide a value" in result.output.lower()
def test_error_message_lists_available_agents(self):
"""Error message should list available agents."""
from typer.testing import CliRunner
runner = CliRunner()
result = runner.invoke(app, ["init", "--ai", "--here"])
assert result.exit_code == 1
# Should mention some known agents
output_lower = result.output.lower()
assert any(agent in output_lower for agent in ["claude", "copilot", "gemini"])
def test_ai_commands_dir_consuming_flag(self):
"""--ai-commands-dir without value should not consume next flag."""
from typer.testing import CliRunner
runner = CliRunner()
result = runner.invoke(app, ["init", "myproject", "--ai", "generic", "--ai-commands-dir", "--here"])
assert result.exit_code == 1
assert "Invalid value for --ai-commands-dir" in result.output
assert "--here" in result.output