mirror of
https://github.com/github/spec-kit.git
synced 2026-03-17 10:53:08 +00:00
Compare commits
28 Commits
v0.0.93
...
copilot/up
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f237803a2 | ||
|
|
525cdc17ec | ||
|
|
607760e72f | ||
|
|
c7ecdfb998 | ||
|
|
f444ccba3a | ||
|
|
3040d33c31 | ||
|
|
6cc61025cb | ||
|
|
c1034f1d9d | ||
|
|
cee4f26fac | ||
|
|
6f523ede22 | ||
|
|
68d1d3a0fc | ||
|
|
07077d0fc2 | ||
|
|
aeed11f735 | ||
|
|
12405c01e1 | ||
|
|
fc3b98ea09 | ||
|
|
6150f1e317 | ||
|
|
6fca5d83b2 | ||
|
|
465acd9024 | ||
|
|
04fc3fd1ba | ||
|
|
24d76b5d92 | ||
|
|
0f7d04b12b | ||
|
|
9402ebd00a | ||
|
|
d410d188fc | ||
|
|
686c91f94e | ||
|
|
22036732d8 | ||
|
|
c78f8423f6 | ||
|
|
76cca34293 | ||
|
|
9a1e3037b0 |
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@@ -1,3 +1,3 @@
|
||||
# Global code owner
|
||||
* @localden
|
||||
* @mnriem
|
||||
|
||||
|
||||
141
.github/ISSUE_TEMPLATE/agent_request.yml
vendored
Normal file
141
.github/ISSUE_TEMPLATE/agent_request.yml
vendored
Normal 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
118
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal 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
17
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal 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
|
||||
280
.github/ISSUE_TEMPLATE/extension_submission.yml
vendored
Normal file
280
.github/ISSUE_TEMPLATE/extension_submission.yml
vendored
Normal 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.
|
||||
104
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
104
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal 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
22
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal 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
11
.github/dependabot.yml
vendored
Normal 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
32
.github/workflows/codeql.yml
vendored
Normal 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 }}"
|
||||
9
.github/workflows/release.yml
vendored
9
.github/workflows/release.yml
vendored
@@ -6,6 +6,7 @@ on:
|
||||
paths:
|
||||
- 'memory/**'
|
||||
- 'scripts/**'
|
||||
- 'src/**'
|
||||
- 'templates/**'
|
||||
- '.github/workflows/**'
|
||||
workflow_dispatch:
|
||||
@@ -57,4 +58,12 @@ jobs:
|
||||
run: |
|
||||
chmod +x .github/workflows/scripts/update-version.sh
|
||||
.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
|
||||
|
||||
|
||||
@@ -40,15 +40,19 @@ gh release create "$VERSION" \
|
||||
.genreleases/spec-kit-template-roo-ps-"$VERSION".zip \
|
||||
.genreleases/spec-kit-template-codebuddy-sh-"$VERSION".zip \
|
||||
.genreleases/spec-kit-template-codebuddy-ps-"$VERSION".zip \
|
||||
.genreleases/spec-kit-template-qoder-sh-"$VERSION".zip \
|
||||
.genreleases/spec-kit-template-qoder-ps-"$VERSION".zip \
|
||||
.genreleases/spec-kit-template-qodercli-sh-"$VERSION".zip \
|
||||
.genreleases/spec-kit-template-qodercli-ps-"$VERSION".zip \
|
||||
.genreleases/spec-kit-template-amp-sh-"$VERSION".zip \
|
||||
.genreleases/spec-kit-template-amp-ps-"$VERSION".zip \
|
||||
.genreleases/spec-kit-template-shai-sh-"$VERSION".zip \
|
||||
.genreleases/spec-kit-template-shai-ps-"$VERSION".zip \
|
||||
.genreleases/spec-kit-template-q-sh-"$VERSION".zip \
|
||||
.genreleases/spec-kit-template-q-ps-"$VERSION".zip \
|
||||
.genreleases/spec-kit-template-agy-sh-"$VERSION".zip \
|
||||
.genreleases/spec-kit-template-agy-ps-"$VERSION".zip \
|
||||
.genreleases/spec-kit-template-bob-sh-"$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" \
|
||||
--notes-file release_notes.md
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
.PARAMETER Agents
|
||||
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
|
||||
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"
|
||||
Generate-Commands -Agent 'bob' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script
|
||||
}
|
||||
'qoder' {
|
||||
'qodercli' {
|
||||
$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
|
||||
$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')
|
||||
|
||||
function Normalize-List {
|
||||
|
||||
@@ -6,7 +6,7 @@ set -euo pipefail
|
||||
# Usage: .github/workflows/scripts/create-release-packages.sh <version>
|
||||
# Version argument should include leading 'v'.
|
||||
# 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)
|
||||
# Examples:
|
||||
# AGENTS=claude SCRIPTS=sh $0 v0.2.0
|
||||
@@ -203,9 +203,9 @@ build_variant() {
|
||||
codebuddy)
|
||||
mkdir -p "$base_dir/.codebuddy/commands"
|
||||
generate_commands codebuddy md "\$ARGUMENTS" "$base_dir/.codebuddy/commands" "$script" ;;
|
||||
qoder)
|
||||
qodercli)
|
||||
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)
|
||||
mkdir -p "$base_dir/.agents/commands"
|
||||
generate_commands amp md "\$ARGUMENTS" "$base_dir/.agents/commands" "$script" ;;
|
||||
@@ -215,16 +215,22 @@ build_variant() {
|
||||
q)
|
||||
mkdir -p "$base_dir/.amazonq/prompts"
|
||||
generate_commands q md "\$ARGUMENTS" "$base_dir/.amazonq/prompts" "$script" ;;
|
||||
agy)
|
||||
mkdir -p "$base_dir/.agent/workflows"
|
||||
generate_commands agy md "\$ARGUMENTS" "$base_dir/.agent/workflows" "$script" ;;
|
||||
bob)
|
||||
mkdir -p "$base_dir/.bob/commands"
|
||||
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
|
||||
( 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"
|
||||
}
|
||||
|
||||
# Determine agent list
|
||||
ALL_AGENTS=(claude gemini copilot cursor-agent qwen opencode windsurf codex kilocode auggie roo codebuddy amp shai q 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)
|
||||
|
||||
norm_list() {
|
||||
|
||||
42
.github/workflows/stale.yml
vendored
Normal file
42
.github/workflows/stale.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
name: 'Close stale issues and PRs'
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *' # Run daily at midnight UTC
|
||||
workflow_dispatch: # Allow manual triggering
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v10
|
||||
with:
|
||||
# Days of inactivity before an issue or PR becomes stale
|
||||
days-before-stale: 150
|
||||
# Days of inactivity before a stale issue or PR is closed (after being marked stale)
|
||||
days-before-close: 30
|
||||
|
||||
# Stale issue settings
|
||||
stale-issue-message: 'This issue has been automatically marked as stale because it has not had any activity for 150 days. It will be closed in 30 days if no further activity occurs.'
|
||||
close-issue-message: 'This issue has been automatically closed due to inactivity (180 days total). If you believe this issue is still relevant, please reopen it or create a new issue.'
|
||||
stale-issue-label: 'stale'
|
||||
|
||||
# Stale PR settings
|
||||
stale-pr-message: 'This pull request has been automatically marked as stale because it has not had any activity for 150 days. It will be closed in 30 days if no further activity occurs.'
|
||||
close-pr-message: 'This pull request has been automatically closed due to inactivity (180 days total). If you believe this PR is still relevant, please reopen it or create a new PR.'
|
||||
stale-pr-label: 'stale'
|
||||
|
||||
# Exempt issues and PRs with these labels from being marked as stale
|
||||
exempt-issue-labels: 'pinned,security'
|
||||
exempt-pr-labels: 'pinned,security'
|
||||
|
||||
# Only issues or PRs with all of these labels are checked
|
||||
# Leave empty to check all issues and PRs
|
||||
any-of-labels: ''
|
||||
|
||||
# Operations per run (helps avoid rate limits)
|
||||
operations-per-run: 100
|
||||
50
.github/workflows/test.yml
vendored
Normal file
50
.github/workflows/test.yml
vendored
Normal 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
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -44,10 +44,9 @@ env/
|
||||
.genreleases/
|
||||
*.zip
|
||||
sdd-*/
|
||||
|
||||
docs/dev
|
||||
|
||||
# Extension system
|
||||
.specify/extensions/.cache/
|
||||
.specify/extensions/.backup/
|
||||
.specify/extensions/*/local-config.yml
|
||||
.specify/extensions/*/local-config.yml
|
||||
|
||||
10
AGENTS.md
10
AGENTS.md
@@ -43,11 +43,12 @@ Specify supports multiple AI agents by generating agent-specific command files a
|
||||
| **Auggie CLI** | `.augment/rules/` | Markdown | `auggie` | Auggie CLI |
|
||||
| **Roo Code** | `.roo/rules/` | Markdown | N/A (IDE-based) | Roo Code IDE |
|
||||
| **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 |
|
||||
| **Amp** | `.agents/commands/` | Markdown | `amp` | Amp CLI |
|
||||
| **SHAI** | `.shai/commands/` | Markdown | `shai` | SHAI CLI |
|
||||
| **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
|
||||
|
||||
@@ -65,6 +66,7 @@ AGENT_CONFIG = {
|
||||
"new-agent-cli": { # Use the ACTUAL CLI tool name (what users type in terminal)
|
||||
"name": "New Agent Display Name",
|
||||
"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)
|
||||
"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
|
||||
- `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)
|
||||
- `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
|
||||
- **Amazon Q Developer CLI**: `q` CLI
|
||||
- **CodeBuddy CLI**: `codebuddy` CLI
|
||||
- **Qoder CLI**: `qoder` CLI
|
||||
- **Qoder CLI**: `qodercli` CLI
|
||||
- **Amp**: `amp` CLI
|
||||
- **SHAI**: `shai` CLI
|
||||
|
||||
|
||||
345
CHANGELOG.md
345
CHANGELOG.md
@@ -2,296 +2,81 @@
|
||||
|
||||
<!-- 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/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.1.0] - 2026-01-28
|
||||
|
||||
### 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
|
||||
## [0.1.6] - 2026-02-23
|
||||
|
||||
### 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
|
||||
|
||||
### 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
|
||||
## [0.1.5] - 2026-02-21
|
||||
|
||||
### 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)
|
||||
|
||||
25
README.md
25
README.md
@@ -31,7 +31,6 @@
|
||||
- [📖 Learn More](#-learn-more)
|
||||
- [📋 Detailed Process](#-detailed-process)
|
||||
- [🔍 Troubleshooting](#-troubleshooting)
|
||||
- [👥 Maintainers](#-maintainers)
|
||||
- [💬 Support](#-support)
|
||||
- [🙏 Acknowledgements](#-acknowledgements)
|
||||
- [📄 License](#-license)
|
||||
@@ -162,6 +161,8 @@ Want to see Spec Kit in action? Watch our [video overview](https://www.youtube.c
|
||||
| [Roo Code](https://roocode.com/) | ✅ | |
|
||||
| [SHAI (OVHcloud)](https://github.com/ovh/shai) | ✅ | |
|
||||
| [Windsurf](https://windsurf.com/) | ✅ | |
|
||||
| [Antigravity (agy)](https://agy.ai/) | ✅ | |
|
||||
| Generic | ✅ | Bring your own agent — use `--ai generic --ai-commands-dir <path>` for unsupported agents |
|
||||
|
||||
## 🔧 Specify CLI Reference
|
||||
|
||||
@@ -172,14 +173,15 @@ The `specify` command supports the following options:
|
||||
| Command | Description |
|
||||
| ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `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
|
||||
|
||||
| Argument/Option | Type | Description |
|
||||
| ---------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `<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`, `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) |
|
||||
| `--ignore-agent-tools` | Flag | Skip checks for AI agent tools like Claude Code |
|
||||
| `--no-git` | Flag | Skip git repository initialization |
|
||||
@@ -188,6 +190,7 @@ The `specify` command supports the following options:
|
||||
| `--skip-tls` | Flag | Skip SSL/TLS verification (not recommended) |
|
||||
| `--debug` | Flag | Enable detailed debug output for troubleshooting |
|
||||
| `--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
|
||||
|
||||
@@ -202,7 +205,7 @@ specify init my-project --ai claude
|
||||
specify init my-project --ai cursor-agent
|
||||
|
||||
# Initialize with Qoder support
|
||||
specify init my-project --ai qoder
|
||||
specify init my-project --ai qodercli
|
||||
|
||||
# Initialize with Windsurf support
|
||||
specify init my-project --ai windsurf
|
||||
@@ -216,6 +219,9 @@ specify init my-project --ai shai
|
||||
# Initialize with IBM Bob support
|
||||
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)
|
||||
specify init my-project --ai copilot --script ps
|
||||
|
||||
@@ -238,6 +244,12 @@ specify init my-project --ai claude --debug
|
||||
# Use GitHub token for API requests (helpful for corporate environments)
|
||||
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
|
||||
specify check
|
||||
```
|
||||
@@ -636,11 +648,6 @@ echo "Cleaning up..."
|
||||
rm gcm-linux_amd64.2.6.1.deb
|
||||
```
|
||||
|
||||
## 👥 Maintainers
|
||||
|
||||
- Den Delimarsky ([@localden](https://github.com/localden))
|
||||
- John Lam ([@jflam](https://github.com/jflam))
|
||||
|
||||
## 💬 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.
|
||||
|
||||
@@ -81,6 +81,9 @@ Then, use the `/speckit.implement` slash command to execute the plan.
|
||||
/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
|
||||
|
||||
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.
|
||||
```
|
||||
|
||||
### 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`:
|
||||
|
||||
@@ -149,6 +160,9 @@ Finally, implement the solution:
|
||||
/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
|
||||
|
||||
- **Be explicit** about what you're building and why
|
||||
|
||||
@@ -46,7 +46,7 @@ Extensions are modular packages that add new commands and functionality to Spec
|
||||
### Check Your Version
|
||||
|
||||
```bash
|
||||
specify --version
|
||||
specify version
|
||||
# Should show 0.1.0 or higher
|
||||
```
|
||||
|
||||
|
||||
14
extensions/README.md
Normal file
14
extensions/README.md
Normal 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.
|
||||
85
extensions/catalog.community.json
Normal file
85
extensions/catalog.community.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
[project]
|
||||
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)."
|
||||
requires-python = ">=3.11"
|
||||
dependencies = [
|
||||
"typer",
|
||||
"click>=8.1",
|
||||
"rich",
|
||||
"httpx[socks]",
|
||||
"platformdirs",
|
||||
|
||||
@@ -30,12 +30,12 @@
|
||||
#
|
||||
# 5. Multi-Agent Support
|
||||
# - Handles agent-specific file paths and naming conventions
|
||||
# - Supports: Claude, Gemini, Copilot, Cursor, Qwen, opencode, Codex, Windsurf, Kilo Code, Auggie CLI, Roo Code, CodeBuddy CLI, Qoder CLI, Amp, SHAI, or Amazon Q Developer CLI
|
||||
# - Supports: Claude, Gemini, Copilot, Cursor, Qwen, opencode, Codex, Windsurf, Kilo Code, Auggie CLI, Roo Code, CodeBuddy CLI, Qoder CLI, Amp, SHAI, Amazon Q Developer CLI, or Antigravity
|
||||
# - Can update single agents or all existing agent files
|
||||
# - Creates default Claude file if no agent files exist
|
||||
#
|
||||
# Usage: ./update-agent-context.sh [agent_type]
|
||||
# Agent types: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|shai|q|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
|
||||
|
||||
set -e
|
||||
@@ -74,6 +74,7 @@ QODER_FILE="$REPO_ROOT/QODER.md"
|
||||
AMP_FILE="$REPO_ROOT/AGENTS.md"
|
||||
SHAI_FILE="$REPO_ROOT/SHAI.md"
|
||||
Q_FILE="$REPO_ROOT/AGENTS.md"
|
||||
AGY_FILE="$REPO_ROOT/.agent/rules/specify-rules.md"
|
||||
BOB_FILE="$REPO_ROOT/AGENTS.md"
|
||||
|
||||
# Template file
|
||||
@@ -618,7 +619,7 @@ update_specific_agent() {
|
||||
codebuddy)
|
||||
update_agent_file "$CODEBUDDY_FILE" "CodeBuddy CLI"
|
||||
;;
|
||||
qoder)
|
||||
qodercli)
|
||||
update_agent_file "$QODER_FILE" "Qoder CLI"
|
||||
;;
|
||||
amp)
|
||||
@@ -630,12 +631,18 @@ update_specific_agent() {
|
||||
q)
|
||||
update_agent_file "$Q_FILE" "Amazon Q Developer CLI"
|
||||
;;
|
||||
agy)
|
||||
update_agent_file "$AGY_FILE" "Antigravity"
|
||||
;;
|
||||
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 "Expected: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|roo|amp|shai|q|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
|
||||
;;
|
||||
esac
|
||||
@@ -714,7 +721,11 @@ update_all_existing_agents() {
|
||||
update_agent_file "$Q_FILE" "Amazon Q Developer CLI"
|
||||
found_agent=true
|
||||
fi
|
||||
|
||||
|
||||
if [[ -f "$AGY_FILE" ]]; then
|
||||
update_agent_file "$AGY_FILE" "Antigravity"
|
||||
found_agent=true
|
||||
fi
|
||||
if [[ -f "$BOB_FILE" ]]; then
|
||||
update_agent_file "$BOB_FILE" "IBM Bob"
|
||||
found_agent=true
|
||||
@@ -744,7 +755,7 @@ print_summary() {
|
||||
|
||||
echo
|
||||
|
||||
log_info "Usage: $0 [claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|codebuddy|shai|q|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]"
|
||||
}
|
||||
|
||||
#==============================================================================
|
||||
|
||||
@@ -9,7 +9,7 @@ Mirrors the behavior of scripts/bash/update-agent-context.sh:
|
||||
2. Plan Data Extraction
|
||||
3. Agent File Management (create from template or update existing)
|
||||
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, 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
|
||||
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(
|
||||
[Parameter(Position=0)]
|
||||
[ValidateSet('claude','gemini','copilot','cursor-agent','qwen','opencode','codex','windsurf','kilocode','auggie','roo','codebuddy','amp','shai','q','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
|
||||
)
|
||||
|
||||
@@ -59,6 +59,7 @@ $QODER_FILE = Join-Path $REPO_ROOT 'QODER.md'
|
||||
$AMP_FILE = Join-Path $REPO_ROOT 'AGENTS.md'
|
||||
$SHAI_FILE = Join-Path $REPO_ROOT 'SHAI.md'
|
||||
$Q_FILE = Join-Path $REPO_ROOT 'AGENTS.md'
|
||||
$AGY_FILE = Join-Path $REPO_ROOT '.agent/rules/specify-rules.md'
|
||||
$BOB_FILE = Join-Path $REPO_ROOT 'AGENTS.md'
|
||||
|
||||
$TEMPLATE_FILE = Join-Path $REPO_ROOT '.specify/templates/agent-file-template.md'
|
||||
@@ -383,12 +384,14 @@ function Update-SpecificAgent {
|
||||
'auggie' { Update-AgentFile -TargetFile $AUGGIE_FILE -AgentName 'Auggie CLI' }
|
||||
'roo' { Update-AgentFile -TargetFile $ROO_FILE -AgentName 'Roo Code' }
|
||||
'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' }
|
||||
'shai' { Update-AgentFile -TargetFile $SHAI_FILE -AgentName 'SHAI' }
|
||||
'q' { Update-AgentFile -TargetFile $Q_FILE -AgentName 'Amazon Q Developer CLI' }
|
||||
'agy' { Update-AgentFile -TargetFile $AGY_FILE -AgentName 'Antigravity' }
|
||||
'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|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 }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -409,6 +412,7 @@ function Update-AllExistingAgents {
|
||||
if (Test-Path $QODER_FILE) { if (-not (Update-AgentFile -TargetFile $QODER_FILE -AgentName 'Qoder CLI')) { $ok = $false }; $found = $true }
|
||||
if (Test-Path $SHAI_FILE) { if (-not (Update-AgentFile -TargetFile $SHAI_FILE -AgentName 'SHAI')) { $ok = $false }; $found = $true }
|
||||
if (Test-Path $Q_FILE) { if (-not (Update-AgentFile -TargetFile $Q_FILE -AgentName 'Amazon Q Developer CLI')) { $ok = $false }; $found = $true }
|
||||
if (Test-Path $AGY_FILE) { if (-not (Update-AgentFile -TargetFile $AGY_FILE -AgentName 'Antigravity')) { $ok = $false }; $found = $true }
|
||||
if (Test-Path $BOB_FILE) { if (-not (Update-AgentFile -TargetFile $BOB_FILE -AgentName 'IBM Bob')) { $ok = $false }; $found = $true }
|
||||
if (-not $found) {
|
||||
Write-Info 'No existing agent files found, creating default Claude file...'
|
||||
@@ -424,7 +428,7 @@ function Print-Summary {
|
||||
if ($NEW_FRAMEWORK) { Write-Host " - Added framework: $NEW_FRAMEWORK" }
|
||||
if ($NEW_DB -and $NEW_DB -ne 'N/A') { Write-Host " - Added database: $NEW_DB" }
|
||||
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|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 {
|
||||
|
||||
@@ -32,6 +32,7 @@ import tempfile
|
||||
import shutil
|
||||
import shlex
|
||||
import json
|
||||
import yaml
|
||||
from pathlib import Path
|
||||
from typing import Optional, Tuple
|
||||
|
||||
@@ -122,110 +123,141 @@ def _format_rate_limit_error(status_code: int, headers: httpx.Headers, url: str)
|
||||
|
||||
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 = {
|
||||
"copilot": {
|
||||
"name": "GitHub Copilot",
|
||||
"folder": ".github/",
|
||||
"commands_subdir": "agents", # Special: uses agents/ not commands/
|
||||
"install_url": None, # IDE-based, no CLI check needed
|
||||
"requires_cli": False,
|
||||
},
|
||||
"claude": {
|
||||
"name": "Claude Code",
|
||||
"folder": ".claude/",
|
||||
"commands_subdir": "commands",
|
||||
"install_url": "https://docs.anthropic.com/en/docs/claude-code/setup",
|
||||
"requires_cli": True,
|
||||
},
|
||||
"gemini": {
|
||||
"name": "Gemini CLI",
|
||||
"folder": ".gemini/",
|
||||
"commands_subdir": "commands",
|
||||
"install_url": "https://github.com/google-gemini/gemini-cli",
|
||||
"requires_cli": True,
|
||||
},
|
||||
"cursor-agent": {
|
||||
"name": "Cursor",
|
||||
"folder": ".cursor/",
|
||||
"commands_subdir": "commands",
|
||||
"install_url": None, # IDE-based
|
||||
"requires_cli": False,
|
||||
},
|
||||
"qwen": {
|
||||
"name": "Qwen Code",
|
||||
"folder": ".qwen/",
|
||||
"commands_subdir": "commands",
|
||||
"install_url": "https://github.com/QwenLM/qwen-code",
|
||||
"requires_cli": True,
|
||||
},
|
||||
"opencode": {
|
||||
"name": "opencode",
|
||||
"folder": ".opencode/",
|
||||
"commands_subdir": "command", # Special: singular 'command' not 'commands'
|
||||
"install_url": "https://opencode.ai",
|
||||
"requires_cli": True,
|
||||
},
|
||||
"codex": {
|
||||
"name": "Codex CLI",
|
||||
"folder": ".codex/",
|
||||
"commands_subdir": "prompts", # Special: uses prompts/ not commands/
|
||||
"install_url": "https://github.com/openai/codex",
|
||||
"requires_cli": True,
|
||||
},
|
||||
"windsurf": {
|
||||
"name": "Windsurf",
|
||||
"folder": ".windsurf/",
|
||||
"commands_subdir": "workflows", # Special: uses workflows/ not commands/
|
||||
"install_url": None, # IDE-based
|
||||
"requires_cli": False,
|
||||
},
|
||||
"kilocode": {
|
||||
"name": "Kilo Code",
|
||||
"folder": ".kilocode/",
|
||||
"commands_subdir": "workflows", # Special: uses workflows/ not commands/
|
||||
"install_url": None, # IDE-based
|
||||
"requires_cli": False,
|
||||
},
|
||||
"auggie": {
|
||||
"name": "Auggie CLI",
|
||||
"folder": ".augment/",
|
||||
"commands_subdir": "commands",
|
||||
"install_url": "https://docs.augmentcode.com/cli/setup-auggie/install-auggie-cli",
|
||||
"requires_cli": True,
|
||||
},
|
||||
"codebuddy": {
|
||||
"name": "CodeBuddy",
|
||||
"folder": ".codebuddy/",
|
||||
"commands_subdir": "commands",
|
||||
"install_url": "https://www.codebuddy.ai/cli",
|
||||
"requires_cli": True,
|
||||
},
|
||||
"qoder": {
|
||||
"qodercli": {
|
||||
"name": "Qoder CLI",
|
||||
"folder": ".qoder/",
|
||||
"commands_subdir": "commands",
|
||||
"install_url": "https://qoder.com/cli",
|
||||
"requires_cli": True,
|
||||
},
|
||||
"roo": {
|
||||
"name": "Roo Code",
|
||||
"folder": ".roo/",
|
||||
"commands_subdir": "commands",
|
||||
"install_url": None, # IDE-based
|
||||
"requires_cli": False,
|
||||
},
|
||||
"q": {
|
||||
"name": "Amazon Q Developer CLI",
|
||||
"folder": ".amazonq/",
|
||||
"commands_subdir": "prompts", # Special: uses prompts/ not commands/
|
||||
"install_url": "https://aws.amazon.com/developer/learning/q-developer-cli/",
|
||||
"requires_cli": True,
|
||||
},
|
||||
"amp": {
|
||||
"name": "Amp",
|
||||
"folder": ".agents/",
|
||||
"commands_subdir": "commands",
|
||||
"install_url": "https://ampcode.com/manual#install",
|
||||
"requires_cli": True,
|
||||
},
|
||||
"shai": {
|
||||
"name": "SHAI",
|
||||
"folder": ".shai/",
|
||||
"commands_subdir": "commands",
|
||||
"install_url": "https://github.com/ovh/shai",
|
||||
"requires_cli": True,
|
||||
},
|
||||
"agy": {
|
||||
"name": "Antigravity",
|
||||
"folder": ".agent/",
|
||||
"commands_subdir": "workflows", # Special: uses workflows/ not commands/
|
||||
"install_url": None, # IDE-based
|
||||
"requires_cli": False,
|
||||
},
|
||||
"bob": {
|
||||
"name": "IBM Bob",
|
||||
"folder": ".bob/",
|
||||
"commands_subdir": "commands",
|
||||
"install_url": None, # IDE-based
|
||||
"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"}
|
||||
@@ -663,7 +695,7 @@ def download_template_from_github(ai_assistant: str, download_dir: Path, *, scri
|
||||
except ValueError as je:
|
||||
raise RuntimeError(f"Failed to parse release JSON: {je}\nRaw (truncated 400): {response.text[:400]}")
|
||||
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"))
|
||||
raise typer.Exit(1)
|
||||
|
||||
@@ -693,7 +725,7 @@ def download_template_from_github(ai_assistant: str, download_dir: Path, *, scri
|
||||
|
||||
zip_path = download_dir / filename
|
||||
if verbose:
|
||||
console.print(f"[cyan]Downloading template...[/cyan]")
|
||||
console.print("[cyan]Downloading template...[/cyan]")
|
||||
|
||||
try:
|
||||
with client.stream(
|
||||
@@ -732,7 +764,7 @@ def download_template_from_github(ai_assistant: str, download_dir: Path, *, scri
|
||||
for chunk in response.iter_bytes(chunk_size=8192):
|
||||
f.write(chunk)
|
||||
except Exception as e:
|
||||
console.print(f"[red]Error downloading template[/red]")
|
||||
console.print("[red]Error downloading template[/red]")
|
||||
detail = str(e)
|
||||
if zip_path.exists():
|
||||
zip_path.unlink()
|
||||
@@ -816,7 +848,7 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, script_
|
||||
tracker.add("flatten", "Flatten nested directory")
|
||||
tracker.complete("flatten")
|
||||
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():
|
||||
dest_path = project_path / item.name
|
||||
@@ -841,7 +873,7 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, script_
|
||||
console.print(f"[yellow]Overwriting file:[/yellow] {item.name}")
|
||||
shutil.copy2(item, dest_path)
|
||||
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:
|
||||
zip_ref.extractall(project_path)
|
||||
|
||||
@@ -867,7 +899,7 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, script_
|
||||
tracker.add("flatten", "Flatten nested directory")
|
||||
tracker.complete("flatten")
|
||||
elif verbose:
|
||||
console.print(f"[cyan]Flattened nested directory structure[/cyan]")
|
||||
console.print("[cyan]Flattened nested directory structure[/cyan]")
|
||||
|
||||
except Exception as e:
|
||||
if tracker:
|
||||
@@ -917,13 +949,17 @@ def ensure_executable_scripts(project_path: Path, tracker: StepTracker | None =
|
||||
continue
|
||||
except Exception:
|
||||
continue
|
||||
st = script.stat(); mode = st.st_mode
|
||||
st = script.stat()
|
||||
mode = st.st_mode
|
||||
if mode & 0o111:
|
||||
continue
|
||||
new_mode = mode
|
||||
if mode & 0o400: new_mode |= 0o100
|
||||
if mode & 0o040: new_mode |= 0o010
|
||||
if mode & 0o004: new_mode |= 0o001
|
||||
if mode & 0o400:
|
||||
new_mode |= 0o100
|
||||
if mode & 0o040:
|
||||
new_mode |= 0o010
|
||||
if mode & 0o004:
|
||||
new_mode |= 0o001
|
||||
if not (new_mode & 0o100):
|
||||
new_mode |= 0o100
|
||||
os.chmod(script, new_mode)
|
||||
@@ -969,7 +1005,7 @@ def ensure_constitution_from_template(project_path: Path, tracker: StepTracker |
|
||||
tracker.add("constitution", "Constitution setup")
|
||||
tracker.complete("constitution", "copied from template")
|
||||
else:
|
||||
console.print(f"[cyan]Initialized constitution from template[/cyan]")
|
||||
console.print("[cyan]Initialized constitution from template[/cyan]")
|
||||
except Exception as e:
|
||||
if tracker:
|
||||
tracker.add("constitution", "Constitution setup")
|
||||
@@ -977,10 +1013,209 @@ def ensure_constitution_from_template(project_path: Path, tracker: StepTracker |
|
||||
else:
|
||||
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()
|
||||
def init(
|
||||
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, 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"),
|
||||
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"),
|
||||
@@ -989,6 +1224,7 @@ def init(
|
||||
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"),
|
||||
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.
|
||||
@@ -1013,10 +1249,27 @@ def init(
|
||||
specify init --here --ai codebuddy
|
||||
specify init --here
|
||||
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()
|
||||
|
||||
# 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 == ".":
|
||||
here = True
|
||||
project_name = None # Clear project_name to use existing validation logic
|
||||
@@ -1029,6 +1282,11 @@ def init(
|
||||
console.print("[red]Error:[/red] Must specify either a project name, use '.' for current directory, or use --here flag")
|
||||
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:
|
||||
project_name = Path.cwd().name
|
||||
project_path = Path.cwd()
|
||||
@@ -1092,6 +1350,16 @@ def init(
|
||||
"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:
|
||||
agent_config = AGENT_CONFIG.get(selected_ai)
|
||||
if agent_config and agent_config["requires_cli"]:
|
||||
@@ -1144,6 +1412,11 @@ def init(
|
||||
("extracted-summary", "Extraction summary"),
|
||||
("chmod", "Ensure scripts executable"),
|
||||
("constitution", "Constitution setup"),
|
||||
]:
|
||||
tracker.add(key, label)
|
||||
if ai_skills:
|
||||
tracker.add("ai-skills", "Install agent skills")
|
||||
for key, label in [
|
||||
("cleanup", "Cleanup"),
|
||||
("git", "Initialize git repository"),
|
||||
("final", "Finalize")
|
||||
@@ -1162,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)
|
||||
|
||||
# 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_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:
|
||||
tracker.start("git")
|
||||
if is_git_repo(project_path):
|
||||
@@ -1224,16 +1532,17 @@ def init(
|
||||
# Agent folder security notice
|
||||
agent_config = AGENT_CONFIG.get(selected_ai)
|
||||
if agent_config:
|
||||
agent_folder = agent_config["folder"]
|
||||
security_notice = Panel(
|
||||
f"Some agents may store credentials, auth tokens, or other identifying and private artifacts in the agent folder within your project.\n"
|
||||
f"Consider adding [cyan]{agent_folder}[/cyan] (or parts of it) to [cyan].gitignore[/cyan] to prevent accidental credential leakage.",
|
||||
title="[yellow]Agent Folder Security[/yellow]",
|
||||
border_style="yellow",
|
||||
padding=(1, 2)
|
||||
)
|
||||
console.print()
|
||||
console.print(security_notice)
|
||||
agent_folder = ai_commands_dir if selected_ai == "generic" else agent_config["folder"]
|
||||
if agent_folder:
|
||||
security_notice = Panel(
|
||||
f"Some agents may store credentials, auth tokens, or other identifying and private artifacts in the agent folder within your project.\n"
|
||||
f"Consider adding [cyan]{agent_folder}[/cyan] (or parts of it) to [cyan].gitignore[/cyan] to prevent accidental credential leakage.",
|
||||
title="[yellow]Agent Folder Security[/yellow]",
|
||||
border_style="yellow",
|
||||
padding=(1, 2)
|
||||
)
|
||||
console.print()
|
||||
console.print(security_notice)
|
||||
|
||||
steps_lines = []
|
||||
if not here:
|
||||
@@ -1270,9 +1579,9 @@ def init(
|
||||
enhancement_lines = [
|
||||
"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)",
|
||||
f"○ [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.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.analyze[/] [bright_black](optional)[/bright_black] - Cross-artifact consistency & alignment report (after [cyan]/speckit.tasks[/], before [cyan]/speckit.implement[/])",
|
||||
"○ [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))
|
||||
console.print()
|
||||
@@ -1291,6 +1600,8 @@ def check():
|
||||
|
||||
agent_results = {}
|
||||
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"]
|
||||
requires_cli = agent_config["requires_cli"]
|
||||
|
||||
@@ -1305,10 +1616,10 @@ def check():
|
||||
|
||||
# Check VS Code variants (not in agent config)
|
||||
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")
|
||||
code_insiders_ok = check_tool("code-insiders", tracker=tracker)
|
||||
check_tool("code-insiders", tracker=tracker)
|
||||
|
||||
console.print(tracker.render())
|
||||
|
||||
@@ -1574,14 +1885,14 @@ def extension_add(
|
||||
if zip_path.exists():
|
||||
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" {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:
|
||||
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}/")
|
||||
|
||||
except ValidationError as e:
|
||||
@@ -1631,11 +1942,11 @@ def extension_remove(
|
||||
|
||||
# Confirm removal
|
||||
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" • Extension directory: .specify/extensions/{extension}/")
|
||||
if not keep_config:
|
||||
console.print(f" • Config files (will be backed up)")
|
||||
console.print(" • Config files (will be backed up)")
|
||||
console.print()
|
||||
|
||||
confirm = typer.confirm("Continue?")
|
||||
@@ -1654,7 +1965,7 @@ def extension_remove(
|
||||
console.print(f"\nConfig files backed up to .specify/extensions/.backup/{extension}/")
|
||||
console.print(f"\nTo reinstall: specify extension add {extension}")
|
||||
else:
|
||||
console.print(f"[red]Error:[/red] Failed to remove extension")
|
||||
console.print("[red]Error:[/red] Failed to remove extension")
|
||||
raise typer.Exit(1)
|
||||
|
||||
|
||||
@@ -1929,8 +2240,8 @@ def extension_update(
|
||||
# TODO: Implement download and reinstall from URL
|
||||
# For now, just show message
|
||||
console.print(
|
||||
f"[yellow]Note:[/yellow] Automatic update not yet implemented. "
|
||||
f"Please update manually:"
|
||||
"[yellow]Note:[/yellow] Automatic update not yet implemented. "
|
||||
"Please update manually:"
|
||||
)
|
||||
console.print(f" specify extension remove {ext_id} --keep-config")
|
||||
console.print(f" specify extension add {ext_id}")
|
||||
@@ -2030,7 +2341,7 @@ def extension_disable(
|
||||
hook_executor.save_project_config(config)
|
||||
|
||||
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}")
|
||||
|
||||
|
||||
|
||||
@@ -647,7 +647,7 @@ class CommandRegistrar:
|
||||
"args": "$ARGUMENTS",
|
||||
"extension": ".md"
|
||||
},
|
||||
"qoder": {
|
||||
"qodercli": {
|
||||
"dir": ".qoder/commands",
|
||||
"format": "markdown",
|
||||
"args": "$ARGUMENTS",
|
||||
|
||||
@@ -75,10 +75,11 @@ You **MUST** consider the user input before proceeding (if not empty).
|
||||
- Validation rules from requirements
|
||||
- State transitions if applicable
|
||||
|
||||
2. **Generate API contracts** from functional requirements:
|
||||
- For each user action → endpoint
|
||||
- Use standard REST/GraphQL patterns
|
||||
- Output OpenAPI/GraphQL schema to `/contracts/`
|
||||
2. **Define interface contracts** (if project has external interfaces) → `/contracts/`:
|
||||
- Identify what interfaces the project exposes to users or other systems
|
||||
- Document the contract format appropriate for the project type
|
||||
- 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**:
|
||||
- Run `{AGENT_SCRIPT}`
|
||||
|
||||
@@ -235,7 +235,7 @@ When creating this spec from a user prompt:
|
||||
- Performance targets: Standard web/mobile app expectations unless specified
|
||||
- Error handling: User-friendly messages with appropriate fallbacks
|
||||
- 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
|
||||
|
||||
|
||||
@@ -28,14 +28,14 @@ You **MUST** consider the user input before proceeding (if not empty).
|
||||
|
||||
2. **Load design documents**: Read from FEATURE_DIR:
|
||||
- **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.
|
||||
|
||||
3. **Execute task generation workflow**:
|
||||
- Load plan.md and extract tech stack, libraries, project structure
|
||||
- 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 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
|
||||
- Generate tasks organized by user story (see Task Generation Rules below)
|
||||
- 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:
|
||||
- Models 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
|
||||
- Mark story dependencies (most stories should be independent)
|
||||
|
||||
2. **From Contracts**:
|
||||
- Map each contract/endpoint → to the user story it serves
|
||||
- If tests requested: Each contract → contract test task [P] before implementation in that story's phase
|
||||
- Map each interface contract → to the user story it serves
|
||||
- If tests requested: Each interface contract → contract test task [P] before implementation in that story's phase
|
||||
|
||||
3. **From Data Model**:
|
||||
- Map each entity to the user story(ies) that need it
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
**Branch**: `[###-feature-name]` | **Date**: [DATE] | **Spec**: [link]
|
||||
**Input**: Feature specification from `/specs/[###-feature-name]/spec.md`
|
||||
|
||||
**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/templates/commands/plan.md` for the execution workflow.
|
||||
**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/templates/plan-template.md` for the execution workflow.
|
||||
|
||||
## Summary
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
**Storage**: [if applicable, e.g., PostgreSQL, CoreData, files or N/A]
|
||||
**Testing**: [e.g., pytest, XCTest, cargo test 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]
|
||||
**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]
|
||||
|
||||
694
tests/test_ai_skills.py
Normal file
694
tests/test_ai_skills.py
Normal 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
|
||||
Reference in New Issue
Block a user