mirror of
https://github.com/github/spec-kit.git
synced 2026-03-24 14:23:09 +00:00
Compare commits
24 Commits
copilot/su
...
copilot/ag
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b59b82813c | ||
|
|
38ae759568 | ||
|
|
ca9c73da0f | ||
|
|
433502b72d | ||
|
|
720ac509d2 | ||
|
|
48392ea865 | ||
|
|
34fa61e1cc | ||
|
|
790448294e | ||
|
|
b94e541234 | ||
|
|
ab8c58ff23 | ||
|
|
00117c5074 | ||
|
|
795f1e7703 | ||
|
|
55bcbd3977 | ||
|
|
978addc390 | ||
|
|
9b580a536b | ||
|
|
d6016ab9db | ||
|
|
c2227a7ffd | ||
|
|
c3efd1fb71 | ||
|
|
e190116d13 | ||
|
|
a63c248c80 | ||
|
|
b5a5e3fc35 | ||
|
|
ec5471af61 | ||
|
|
3212309e7c | ||
|
|
8b20d0b336 |
52
AGENTS.md
52
AGENTS.md
@@ -427,4 +427,56 @@ When adding new agents:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Agent Pack System (new)
|
||||||
|
|
||||||
|
The agent pack system is a declarative, self-contained replacement for the legacy `AGENT_CONFIG` + case/switch architecture. Each agent is defined by a `speckit-agent.yml` manifest and an optional `bootstrap.py` module. When `bootstrap.py` is absent, the built-in `DefaultBootstrap` class derives its directory layout from the manifest's `commands_dir` field.
|
||||||
|
|
||||||
|
### `--agent` flag on `specify init`
|
||||||
|
|
||||||
|
`specify init --agent <id>` uses the pack-based init flow instead of the legacy `--ai` flow. Both accept the same agent IDs, but `--agent` additionally enables installed-file tracking so that `specify agent switch` can cleanly tear down agent files later.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
specify init my-project --agent claude # Pack-based flow (with file tracking)
|
||||||
|
specify init --here --agent gemini --ai-skills # With skills
|
||||||
|
```
|
||||||
|
|
||||||
|
`--agent` and `--ai` are mutually exclusive. When `--agent` is used, `init-options.json` gains `"agent_pack": true`. The `generic` agent (which requires `--ai-commands-dir`) falls through to the legacy flow since it has no embedded pack.
|
||||||
|
|
||||||
|
### `specify agent` subcommands
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
| ------------------------------- | ----------- |
|
||||||
|
| `specify agent list` | List all available agent packs |
|
||||||
|
| `specify agent list --installed`| List only agents installed in the current project |
|
||||||
|
| `specify agent info <id>` | Show detailed information about an agent pack |
|
||||||
|
| `specify agent switch <id>` | Switch the active agent (tears down old, sets up new) |
|
||||||
|
| `specify agent search [query]` | Search agents by name, ID, description, or tags |
|
||||||
|
| `specify agent validate <path>` | Validate an agent pack directory |
|
||||||
|
| `specify agent export <id>` | Export an agent pack for editing |
|
||||||
|
| `specify agent add <id> --from <path>` | Install an agent pack from a local directory |
|
||||||
|
| `specify agent remove <id>` | Remove a cached/override agent pack |
|
||||||
|
|
||||||
|
> **Note:** `specify agent add <id>` without `--from <path>` is reserved for future catalog-based installation, which is not yet implemented.
|
||||||
|
|
||||||
|
### Pack resolution order
|
||||||
|
|
||||||
|
Agent packs resolve by priority (highest first):
|
||||||
|
1. **User-level** (`~/.specify/agents/<id>/`) — applies to all projects
|
||||||
|
2. **Project-level** (`.specify/agents/<id>/`) — project-specific override
|
||||||
|
3. **Catalog cache** (downloaded via `specify agent add`)
|
||||||
|
4. **Embedded** (bundled in the specify-cli wheel)
|
||||||
|
|
||||||
|
### Trust boundary
|
||||||
|
|
||||||
|
Agent packs can include a `bootstrap.py` module that is dynamically imported and executed. Pack authors can run arbitrary code through this mechanism. Only install packs from trusted sources. The 4-level resolution stack means that placing a pack in any of the resolution directories causes its code to run when the agent is loaded.
|
||||||
|
|
||||||
|
### Installed-file tracking
|
||||||
|
|
||||||
|
When using `--agent`, all installed files are recorded in `.specify/agent-manifest-<id>.json` with SHA-256 hashes. During `specify agent switch`, the CLI:
|
||||||
|
1. Checks for user-modified files before teardown
|
||||||
|
2. Prompts for confirmation if files were changed
|
||||||
|
3. Feeds tracked file lists into teardown for precise, file-level removal (directories are never deleted)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
*This documentation should be updated whenever new agents are added to maintain accuracy and completeness.*
|
*This documentation should be updated whenever new agents are added to maintain accuracy and completeness.*
|
||||||
|
|||||||
24
CHANGELOG.md
24
CHANGELOG.md
@@ -1,22 +1,11 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## [0.4.0] - 2026-03-23
|
## [0.3.2] - 2026-03-19
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
|
||||||
- fix(cli): add allow_unicode=True and encoding="utf-8" to YAML I/O (#1936)
|
- chore: bump version to 0.3.2
|
||||||
- fix(codex): native skills fallback refresh + legacy prompt suppression (#1930)
|
- Add conduct extension to community catalog (#1908)
|
||||||
- feat(cli): embed core pack in wheel for offline/air-gapped deployment (#1803)
|
|
||||||
- ci: increase stale workflow operations-per-run to 250 (#1922)
|
|
||||||
- docs: update publishing guide with Category and Effect columns (#1913)
|
|
||||||
- fix: Align native skills frontmatter with install_ai_skills (#1920)
|
|
||||||
- feat: add timestamp-based branch naming option for `specify init` (#1911)
|
|
||||||
- docs: add Extension Comparison Guide for community extensions (#1897)
|
|
||||||
- docs: update SUPPORT.md, fix issue templates, add preset submission template (#1910)
|
|
||||||
- Add support for Junie (#1831)
|
|
||||||
- feat: migrate Codex/agy init to native skills workflow (#1906)
|
|
||||||
- chore: bump version to 0.3.2 (#1909)
|
|
||||||
|
|
||||||
- feat(extensions): add verify-tasks extension to community catalog (#1871)
|
- feat(extensions): add verify-tasks extension to community catalog (#1871)
|
||||||
- feat(presets): add enable/disable toggle and update semantics (#1891)
|
- feat(presets): add enable/disable toggle and update semantics (#1891)
|
||||||
- feat: add iFlow CLI support (#1875)
|
- feat: add iFlow CLI support (#1875)
|
||||||
@@ -32,13 +21,6 @@
|
|||||||
- Feature/spec kit add pi coding agent pullrequest (#1853)
|
- Feature/spec kit add pi coding agent pullrequest (#1853)
|
||||||
- feat: register spec-kit-learn extension (#1883)
|
- feat: register spec-kit-learn extension (#1883)
|
||||||
|
|
||||||
## [0.3.2] - 2026-03-19
|
|
||||||
|
|
||||||
### Changes
|
|
||||||
|
|
||||||
- chore: bump version to 0.3.2
|
|
||||||
- Add conduct extension to community catalog (#1908)
|
|
||||||
|
|
||||||
## [0.3.1] - 2026-03-17
|
## [0.3.1] - 2026-03-17
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "specify-cli"
|
name = "specify-cli"
|
||||||
version = "0.4.0"
|
version = "0.3.2"
|
||||||
description = "Specify CLI, part of GitHub Spec Kit. A tool to bootstrap your projects for Spec-Driven Development (SDD)."
|
description = "Specify CLI, part of GitHub Spec Kit. A tool to bootstrap your projects for Spec-Driven Development (SDD)."
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
@@ -43,6 +43,8 @@ packages = ["src/specify_cli"]
|
|||||||
"scripts/powershell" = "specify_cli/core_pack/scripts/powershell"
|
"scripts/powershell" = "specify_cli/core_pack/scripts/powershell"
|
||||||
".github/workflows/scripts/create-release-packages.sh" = "specify_cli/core_pack/release_scripts/create-release-packages.sh"
|
".github/workflows/scripts/create-release-packages.sh" = "specify_cli/core_pack/release_scripts/create-release-packages.sh"
|
||||||
".github/workflows/scripts/create-release-packages.ps1" = "specify_cli/core_pack/release_scripts/create-release-packages.ps1"
|
".github/workflows/scripts/create-release-packages.ps1" = "specify_cli/core_pack/release_scripts/create-release-packages.ps1"
|
||||||
|
# Official agent packs (embedded in wheel for zero-config offline operation)
|
||||||
|
"src/specify_cli/core_pack/agents" = "specify_cli/core_pack/agents"
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
test = [
|
test = [
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
1005
src/specify_cli/agent_pack.py
Normal file
1005
src/specify_cli/agent_pack.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -47,6 +47,12 @@ class CommandRegistrar:
|
|||||||
"args": "$ARGUMENTS",
|
"args": "$ARGUMENTS",
|
||||||
"extension": ".md"
|
"extension": ".md"
|
||||||
},
|
},
|
||||||
|
"cursor-agent": {
|
||||||
|
"dir": ".cursor/commands",
|
||||||
|
"format": "markdown",
|
||||||
|
"args": "$ARGUMENTS",
|
||||||
|
"extension": ".md"
|
||||||
|
},
|
||||||
"qwen": {
|
"qwen": {
|
||||||
"dir": ".qwen/commands",
|
"dir": ".qwen/commands",
|
||||||
"format": "markdown",
|
"format": "markdown",
|
||||||
@@ -207,7 +213,7 @@ class CommandRegistrar:
|
|||||||
if not fm:
|
if not fm:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
yaml_str = yaml.dump(fm, default_flow_style=False, sort_keys=False, allow_unicode=True)
|
yaml_str = yaml.dump(fm, default_flow_style=False, sort_keys=False)
|
||||||
return f"---\n{yaml_str}---\n"
|
return f"---\n{yaml_str}---\n"
|
||||||
|
|
||||||
def _adjust_script_paths(self, frontmatter: dict) -> dict:
|
def _adjust_script_paths(self, frontmatter: dict) -> dict:
|
||||||
|
|||||||
23
src/specify_cli/core_pack/agents/agy/speckit-agent.yml
Normal file
23
src/specify_cli/core_pack/agents/agy/speckit-agent.yml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
schema_version: "1.0"
|
||||||
|
|
||||||
|
agent:
|
||||||
|
id: "agy"
|
||||||
|
name: "Antigravity"
|
||||||
|
version: "1.0.0"
|
||||||
|
description: "Antigravity IDE for AI-assisted development"
|
||||||
|
author: "github"
|
||||||
|
license: "MIT"
|
||||||
|
|
||||||
|
runtime:
|
||||||
|
requires_cli: false
|
||||||
|
|
||||||
|
requires:
|
||||||
|
speckit_version: ">=0.1.0"
|
||||||
|
|
||||||
|
tags: ['ide', 'antigravity']
|
||||||
|
|
||||||
|
command_registration:
|
||||||
|
commands_dir: ".agent/commands"
|
||||||
|
format: "markdown"
|
||||||
|
arg_placeholder: "$ARGUMENTS"
|
||||||
|
file_extension: ".md"
|
||||||
25
src/specify_cli/core_pack/agents/amp/speckit-agent.yml
Normal file
25
src/specify_cli/core_pack/agents/amp/speckit-agent.yml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
schema_version: "1.0"
|
||||||
|
|
||||||
|
agent:
|
||||||
|
id: "amp"
|
||||||
|
name: "Amp"
|
||||||
|
version: "1.0.0"
|
||||||
|
description: "Amp CLI for AI-assisted development"
|
||||||
|
author: "github"
|
||||||
|
license: "MIT"
|
||||||
|
|
||||||
|
runtime:
|
||||||
|
requires_cli: true
|
||||||
|
install_url: "https://ampcode.com/manual#install"
|
||||||
|
cli_tool: "amp"
|
||||||
|
|
||||||
|
requires:
|
||||||
|
speckit_version: ">=0.1.0"
|
||||||
|
|
||||||
|
tags: ['cli', 'amp']
|
||||||
|
|
||||||
|
command_registration:
|
||||||
|
commands_dir: ".agents/commands"
|
||||||
|
format: "markdown"
|
||||||
|
arg_placeholder: "$ARGUMENTS"
|
||||||
|
file_extension: ".md"
|
||||||
25
src/specify_cli/core_pack/agents/auggie/speckit-agent.yml
Normal file
25
src/specify_cli/core_pack/agents/auggie/speckit-agent.yml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
schema_version: "1.0"
|
||||||
|
|
||||||
|
agent:
|
||||||
|
id: "auggie"
|
||||||
|
name: "Auggie CLI"
|
||||||
|
version: "1.0.0"
|
||||||
|
description: "Auggie CLI for AI-assisted development"
|
||||||
|
author: "github"
|
||||||
|
license: "MIT"
|
||||||
|
|
||||||
|
runtime:
|
||||||
|
requires_cli: true
|
||||||
|
install_url: "https://docs.augmentcode.com/cli/setup-auggie/install-auggie-cli"
|
||||||
|
cli_tool: "auggie"
|
||||||
|
|
||||||
|
requires:
|
||||||
|
speckit_version: ">=0.1.0"
|
||||||
|
|
||||||
|
tags: ['cli', 'augment', 'auggie']
|
||||||
|
|
||||||
|
command_registration:
|
||||||
|
commands_dir: ".augment/commands"
|
||||||
|
format: "markdown"
|
||||||
|
arg_placeholder: "$ARGUMENTS"
|
||||||
|
file_extension: ".md"
|
||||||
23
src/specify_cli/core_pack/agents/bob/speckit-agent.yml
Normal file
23
src/specify_cli/core_pack/agents/bob/speckit-agent.yml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
schema_version: "1.0"
|
||||||
|
|
||||||
|
agent:
|
||||||
|
id: "bob"
|
||||||
|
name: "IBM Bob"
|
||||||
|
version: "1.0.0"
|
||||||
|
description: "IBM Bob IDE for AI-assisted development"
|
||||||
|
author: "github"
|
||||||
|
license: "MIT"
|
||||||
|
|
||||||
|
runtime:
|
||||||
|
requires_cli: false
|
||||||
|
|
||||||
|
requires:
|
||||||
|
speckit_version: ">=0.1.0"
|
||||||
|
|
||||||
|
tags: ['ide', 'ibm', 'bob']
|
||||||
|
|
||||||
|
command_registration:
|
||||||
|
commands_dir: ".bob/commands"
|
||||||
|
format: "markdown"
|
||||||
|
arg_placeholder: "$ARGUMENTS"
|
||||||
|
file_extension: ".md"
|
||||||
25
src/specify_cli/core_pack/agents/claude/speckit-agent.yml
Normal file
25
src/specify_cli/core_pack/agents/claude/speckit-agent.yml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
schema_version: "1.0"
|
||||||
|
|
||||||
|
agent:
|
||||||
|
id: "claude"
|
||||||
|
name: "Claude Code"
|
||||||
|
version: "1.0.0"
|
||||||
|
description: "Anthropic's Claude Code CLI for AI-assisted development"
|
||||||
|
author: "github"
|
||||||
|
license: "MIT"
|
||||||
|
|
||||||
|
runtime:
|
||||||
|
requires_cli: true
|
||||||
|
install_url: "https://docs.anthropic.com/en/docs/claude-code/setup"
|
||||||
|
cli_tool: "claude"
|
||||||
|
|
||||||
|
requires:
|
||||||
|
speckit_version: ">=0.1.0"
|
||||||
|
|
||||||
|
tags: ['cli', 'anthropic', 'claude']
|
||||||
|
|
||||||
|
command_registration:
|
||||||
|
commands_dir: ".claude/commands"
|
||||||
|
format: "markdown"
|
||||||
|
arg_placeholder: "$ARGUMENTS"
|
||||||
|
file_extension: ".md"
|
||||||
25
src/specify_cli/core_pack/agents/codebuddy/speckit-agent.yml
Normal file
25
src/specify_cli/core_pack/agents/codebuddy/speckit-agent.yml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
schema_version: "1.0"
|
||||||
|
|
||||||
|
agent:
|
||||||
|
id: "codebuddy"
|
||||||
|
name: "CodeBuddy"
|
||||||
|
version: "1.0.0"
|
||||||
|
description: "CodeBuddy CLI for AI-assisted development"
|
||||||
|
author: "github"
|
||||||
|
license: "MIT"
|
||||||
|
|
||||||
|
runtime:
|
||||||
|
requires_cli: true
|
||||||
|
install_url: "https://www.codebuddy.ai/cli"
|
||||||
|
cli_tool: "codebuddy"
|
||||||
|
|
||||||
|
requires:
|
||||||
|
speckit_version: ">=0.1.0"
|
||||||
|
|
||||||
|
tags: ['cli', 'codebuddy']
|
||||||
|
|
||||||
|
command_registration:
|
||||||
|
commands_dir: ".codebuddy/commands"
|
||||||
|
format: "markdown"
|
||||||
|
arg_placeholder: "$ARGUMENTS"
|
||||||
|
file_extension: ".md"
|
||||||
25
src/specify_cli/core_pack/agents/codex/speckit-agent.yml
Normal file
25
src/specify_cli/core_pack/agents/codex/speckit-agent.yml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
schema_version: "1.0"
|
||||||
|
|
||||||
|
agent:
|
||||||
|
id: "codex"
|
||||||
|
name: "Codex CLI"
|
||||||
|
version: "1.0.0"
|
||||||
|
description: "OpenAI Codex CLI with project skills support"
|
||||||
|
author: "github"
|
||||||
|
license: "MIT"
|
||||||
|
|
||||||
|
runtime:
|
||||||
|
requires_cli: true
|
||||||
|
install_url: "https://github.com/openai/codex"
|
||||||
|
cli_tool: "codex"
|
||||||
|
|
||||||
|
requires:
|
||||||
|
speckit_version: ">=0.1.0"
|
||||||
|
|
||||||
|
tags: ['cli', 'openai', 'codex', 'skills']
|
||||||
|
|
||||||
|
command_registration:
|
||||||
|
commands_dir: ".agents/skills"
|
||||||
|
format: "markdown"
|
||||||
|
arg_placeholder: "$ARGUMENTS"
|
||||||
|
file_extension: "/SKILL.md"
|
||||||
23
src/specify_cli/core_pack/agents/copilot/speckit-agent.yml
Normal file
23
src/specify_cli/core_pack/agents/copilot/speckit-agent.yml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
schema_version: "1.0"
|
||||||
|
|
||||||
|
agent:
|
||||||
|
id: "copilot"
|
||||||
|
name: "GitHub Copilot"
|
||||||
|
version: "1.0.0"
|
||||||
|
description: "GitHub Copilot for AI-assisted development in VS Code"
|
||||||
|
author: "github"
|
||||||
|
license: "MIT"
|
||||||
|
|
||||||
|
runtime:
|
||||||
|
requires_cli: false
|
||||||
|
|
||||||
|
requires:
|
||||||
|
speckit_version: ">=0.1.0"
|
||||||
|
|
||||||
|
tags: ['ide', 'github', 'copilot']
|
||||||
|
|
||||||
|
command_registration:
|
||||||
|
commands_dir: ".github/agents"
|
||||||
|
format: "markdown"
|
||||||
|
arg_placeholder: "$ARGUMENTS"
|
||||||
|
file_extension: ".agent.md"
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
schema_version: "1.0"
|
||||||
|
|
||||||
|
agent:
|
||||||
|
id: "cursor-agent"
|
||||||
|
name: "Cursor"
|
||||||
|
version: "1.0.0"
|
||||||
|
description: "Cursor IDE for AI-assisted development"
|
||||||
|
author: "github"
|
||||||
|
license: "MIT"
|
||||||
|
|
||||||
|
runtime:
|
||||||
|
requires_cli: false
|
||||||
|
|
||||||
|
requires:
|
||||||
|
speckit_version: ">=0.1.0"
|
||||||
|
|
||||||
|
tags: ['ide', 'cursor']
|
||||||
|
|
||||||
|
command_registration:
|
||||||
|
commands_dir: ".cursor/commands"
|
||||||
|
format: "markdown"
|
||||||
|
arg_placeholder: "$ARGUMENTS"
|
||||||
|
file_extension: ".md"
|
||||||
25
src/specify_cli/core_pack/agents/gemini/speckit-agent.yml
Normal file
25
src/specify_cli/core_pack/agents/gemini/speckit-agent.yml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
schema_version: "1.0"
|
||||||
|
|
||||||
|
agent:
|
||||||
|
id: "gemini"
|
||||||
|
name: "Gemini CLI"
|
||||||
|
version: "1.0.0"
|
||||||
|
description: "Google's Gemini CLI for AI-assisted development"
|
||||||
|
author: "github"
|
||||||
|
license: "MIT"
|
||||||
|
|
||||||
|
runtime:
|
||||||
|
requires_cli: true
|
||||||
|
install_url: "https://github.com/google-gemini/gemini-cli"
|
||||||
|
cli_tool: "gemini"
|
||||||
|
|
||||||
|
requires:
|
||||||
|
speckit_version: ">=0.1.0"
|
||||||
|
|
||||||
|
tags: ['cli', 'google', 'gemini']
|
||||||
|
|
||||||
|
command_registration:
|
||||||
|
commands_dir: ".gemini/commands"
|
||||||
|
format: "toml"
|
||||||
|
arg_placeholder: "{{args}}"
|
||||||
|
file_extension: ".toml"
|
||||||
25
src/specify_cli/core_pack/agents/iflow/speckit-agent.yml
Normal file
25
src/specify_cli/core_pack/agents/iflow/speckit-agent.yml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
schema_version: "1.0"
|
||||||
|
|
||||||
|
agent:
|
||||||
|
id: "iflow"
|
||||||
|
name: "iFlow CLI"
|
||||||
|
version: "1.0.0"
|
||||||
|
description: "iFlow CLI by iflow-ai for AI-assisted development"
|
||||||
|
author: "github"
|
||||||
|
license: "MIT"
|
||||||
|
|
||||||
|
runtime:
|
||||||
|
requires_cli: true
|
||||||
|
install_url: "https://docs.iflow.cn/en/cli/quickstart"
|
||||||
|
cli_tool: "iflow"
|
||||||
|
|
||||||
|
requires:
|
||||||
|
speckit_version: ">=0.1.0"
|
||||||
|
|
||||||
|
tags: ['cli', 'iflow']
|
||||||
|
|
||||||
|
command_registration:
|
||||||
|
commands_dir: ".iflow/commands"
|
||||||
|
format: "markdown"
|
||||||
|
arg_placeholder: "$ARGUMENTS"
|
||||||
|
file_extension: ".md"
|
||||||
25
src/specify_cli/core_pack/agents/junie/speckit-agent.yml
Normal file
25
src/specify_cli/core_pack/agents/junie/speckit-agent.yml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
schema_version: "1.0"
|
||||||
|
|
||||||
|
agent:
|
||||||
|
id: "junie"
|
||||||
|
name: "Junie"
|
||||||
|
version: "1.0.0"
|
||||||
|
description: "Junie by JetBrains for AI-assisted development"
|
||||||
|
author: "github"
|
||||||
|
license: "MIT"
|
||||||
|
|
||||||
|
runtime:
|
||||||
|
requires_cli: true
|
||||||
|
install_url: "https://junie.jetbrains.com/"
|
||||||
|
cli_tool: "junie"
|
||||||
|
|
||||||
|
requires:
|
||||||
|
speckit_version: ">=0.1.0"
|
||||||
|
|
||||||
|
tags: ['cli', 'jetbrains', 'junie']
|
||||||
|
|
||||||
|
command_registration:
|
||||||
|
commands_dir: ".junie/commands"
|
||||||
|
format: "markdown"
|
||||||
|
arg_placeholder: "$ARGUMENTS"
|
||||||
|
file_extension: ".md"
|
||||||
23
src/specify_cli/core_pack/agents/kilocode/speckit-agent.yml
Normal file
23
src/specify_cli/core_pack/agents/kilocode/speckit-agent.yml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
schema_version: "1.0"
|
||||||
|
|
||||||
|
agent:
|
||||||
|
id: "kilocode"
|
||||||
|
name: "Kilo Code"
|
||||||
|
version: "1.0.0"
|
||||||
|
description: "Kilo Code IDE for AI-assisted development"
|
||||||
|
author: "github"
|
||||||
|
license: "MIT"
|
||||||
|
|
||||||
|
runtime:
|
||||||
|
requires_cli: false
|
||||||
|
|
||||||
|
requires:
|
||||||
|
speckit_version: ">=0.1.0"
|
||||||
|
|
||||||
|
tags: ['ide', 'kilocode']
|
||||||
|
|
||||||
|
command_registration:
|
||||||
|
commands_dir: ".kilocode/workflows"
|
||||||
|
format: "markdown"
|
||||||
|
arg_placeholder: "$ARGUMENTS"
|
||||||
|
file_extension: ".md"
|
||||||
25
src/specify_cli/core_pack/agents/kimi/speckit-agent.yml
Normal file
25
src/specify_cli/core_pack/agents/kimi/speckit-agent.yml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
schema_version: "1.0"
|
||||||
|
|
||||||
|
agent:
|
||||||
|
id: "kimi"
|
||||||
|
name: "Kimi Code"
|
||||||
|
version: "1.0.0"
|
||||||
|
description: "Kimi Code CLI by Moonshot AI with skills support"
|
||||||
|
author: "github"
|
||||||
|
license: "MIT"
|
||||||
|
|
||||||
|
runtime:
|
||||||
|
requires_cli: true
|
||||||
|
install_url: "https://code.kimi.com/"
|
||||||
|
cli_tool: "kimi"
|
||||||
|
|
||||||
|
requires:
|
||||||
|
speckit_version: ">=0.1.0"
|
||||||
|
|
||||||
|
tags: ['cli', 'moonshot', 'kimi', 'skills']
|
||||||
|
|
||||||
|
command_registration:
|
||||||
|
commands_dir: ".kimi/skills"
|
||||||
|
format: "markdown"
|
||||||
|
arg_placeholder: "$ARGUMENTS"
|
||||||
|
file_extension: "/SKILL.md"
|
||||||
25
src/specify_cli/core_pack/agents/kiro-cli/speckit-agent.yml
Normal file
25
src/specify_cli/core_pack/agents/kiro-cli/speckit-agent.yml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
schema_version: "1.0"
|
||||||
|
|
||||||
|
agent:
|
||||||
|
id: "kiro-cli"
|
||||||
|
name: "Kiro CLI"
|
||||||
|
version: "1.0.0"
|
||||||
|
description: "Kiro CLI by Amazon for AI-assisted development"
|
||||||
|
author: "github"
|
||||||
|
license: "MIT"
|
||||||
|
|
||||||
|
runtime:
|
||||||
|
requires_cli: true
|
||||||
|
install_url: "https://kiro.dev/docs/cli/"
|
||||||
|
cli_tool: "kiro-cli"
|
||||||
|
|
||||||
|
requires:
|
||||||
|
speckit_version: ">=0.1.0"
|
||||||
|
|
||||||
|
tags: ['cli', 'amazon', 'kiro']
|
||||||
|
|
||||||
|
command_registration:
|
||||||
|
commands_dir: ".kiro/prompts"
|
||||||
|
format: "markdown"
|
||||||
|
arg_placeholder: "$ARGUMENTS"
|
||||||
|
file_extension: ".md"
|
||||||
25
src/specify_cli/core_pack/agents/opencode/speckit-agent.yml
Normal file
25
src/specify_cli/core_pack/agents/opencode/speckit-agent.yml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
schema_version: "1.0"
|
||||||
|
|
||||||
|
agent:
|
||||||
|
id: "opencode"
|
||||||
|
name: "opencode"
|
||||||
|
version: "1.0.0"
|
||||||
|
description: "opencode CLI for AI-assisted development"
|
||||||
|
author: "github"
|
||||||
|
license: "MIT"
|
||||||
|
|
||||||
|
runtime:
|
||||||
|
requires_cli: true
|
||||||
|
install_url: "https://opencode.ai"
|
||||||
|
cli_tool: "opencode"
|
||||||
|
|
||||||
|
requires:
|
||||||
|
speckit_version: ">=0.1.0"
|
||||||
|
|
||||||
|
tags: ['cli', 'opencode']
|
||||||
|
|
||||||
|
command_registration:
|
||||||
|
commands_dir: ".opencode/command"
|
||||||
|
format: "markdown"
|
||||||
|
arg_placeholder: "$ARGUMENTS"
|
||||||
|
file_extension: ".md"
|
||||||
25
src/specify_cli/core_pack/agents/pi/speckit-agent.yml
Normal file
25
src/specify_cli/core_pack/agents/pi/speckit-agent.yml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
schema_version: "1.0"
|
||||||
|
|
||||||
|
agent:
|
||||||
|
id: "pi"
|
||||||
|
name: "Pi Coding Agent"
|
||||||
|
version: "1.0.0"
|
||||||
|
description: "Pi terminal coding agent for AI-assisted development"
|
||||||
|
author: "github"
|
||||||
|
license: "MIT"
|
||||||
|
|
||||||
|
runtime:
|
||||||
|
requires_cli: true
|
||||||
|
install_url: "https://www.npmjs.com/package/@mariozechner/pi-coding-agent"
|
||||||
|
cli_tool: "pi"
|
||||||
|
|
||||||
|
requires:
|
||||||
|
speckit_version: ">=0.1.0"
|
||||||
|
|
||||||
|
tags: ['cli', 'pi']
|
||||||
|
|
||||||
|
command_registration:
|
||||||
|
commands_dir: ".pi/prompts"
|
||||||
|
format: "markdown"
|
||||||
|
arg_placeholder: "$ARGUMENTS"
|
||||||
|
file_extension: ".md"
|
||||||
25
src/specify_cli/core_pack/agents/qodercli/speckit-agent.yml
Normal file
25
src/specify_cli/core_pack/agents/qodercli/speckit-agent.yml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
schema_version: "1.0"
|
||||||
|
|
||||||
|
agent:
|
||||||
|
id: "qodercli"
|
||||||
|
name: "Qoder CLI"
|
||||||
|
version: "1.0.0"
|
||||||
|
description: "Qoder CLI for AI-assisted development"
|
||||||
|
author: "github"
|
||||||
|
license: "MIT"
|
||||||
|
|
||||||
|
runtime:
|
||||||
|
requires_cli: true
|
||||||
|
install_url: "https://qoder.com/cli"
|
||||||
|
cli_tool: "qodercli"
|
||||||
|
|
||||||
|
requires:
|
||||||
|
speckit_version: ">=0.1.0"
|
||||||
|
|
||||||
|
tags: ['cli', 'qoder']
|
||||||
|
|
||||||
|
command_registration:
|
||||||
|
commands_dir: ".qoder/commands"
|
||||||
|
format: "markdown"
|
||||||
|
arg_placeholder: "$ARGUMENTS"
|
||||||
|
file_extension: ".md"
|
||||||
25
src/specify_cli/core_pack/agents/qwen/speckit-agent.yml
Normal file
25
src/specify_cli/core_pack/agents/qwen/speckit-agent.yml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
schema_version: "1.0"
|
||||||
|
|
||||||
|
agent:
|
||||||
|
id: "qwen"
|
||||||
|
name: "Qwen Code"
|
||||||
|
version: "1.0.0"
|
||||||
|
description: "Alibaba's Qwen Code CLI for AI-assisted development"
|
||||||
|
author: "github"
|
||||||
|
license: "MIT"
|
||||||
|
|
||||||
|
runtime:
|
||||||
|
requires_cli: true
|
||||||
|
install_url: "https://github.com/QwenLM/qwen-code"
|
||||||
|
cli_tool: "qwen"
|
||||||
|
|
||||||
|
requires:
|
||||||
|
speckit_version: ">=0.1.0"
|
||||||
|
|
||||||
|
tags: ['cli', 'alibaba', 'qwen']
|
||||||
|
|
||||||
|
command_registration:
|
||||||
|
commands_dir: ".qwen/commands"
|
||||||
|
format: "markdown"
|
||||||
|
arg_placeholder: "$ARGUMENTS"
|
||||||
|
file_extension: ".md"
|
||||||
23
src/specify_cli/core_pack/agents/roo/speckit-agent.yml
Normal file
23
src/specify_cli/core_pack/agents/roo/speckit-agent.yml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
schema_version: "1.0"
|
||||||
|
|
||||||
|
agent:
|
||||||
|
id: "roo"
|
||||||
|
name: "Roo Code"
|
||||||
|
version: "1.0.0"
|
||||||
|
description: "Roo Code IDE for AI-assisted development"
|
||||||
|
author: "github"
|
||||||
|
license: "MIT"
|
||||||
|
|
||||||
|
runtime:
|
||||||
|
requires_cli: false
|
||||||
|
|
||||||
|
requires:
|
||||||
|
speckit_version: ">=0.1.0"
|
||||||
|
|
||||||
|
tags: ['ide', 'roo']
|
||||||
|
|
||||||
|
command_registration:
|
||||||
|
commands_dir: ".roo/commands"
|
||||||
|
format: "markdown"
|
||||||
|
arg_placeholder: "$ARGUMENTS"
|
||||||
|
file_extension: ".md"
|
||||||
25
src/specify_cli/core_pack/agents/shai/speckit-agent.yml
Normal file
25
src/specify_cli/core_pack/agents/shai/speckit-agent.yml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
schema_version: "1.0"
|
||||||
|
|
||||||
|
agent:
|
||||||
|
id: "shai"
|
||||||
|
name: "SHAI"
|
||||||
|
version: "1.0.0"
|
||||||
|
description: "SHAI CLI for AI-assisted development"
|
||||||
|
author: "github"
|
||||||
|
license: "MIT"
|
||||||
|
|
||||||
|
runtime:
|
||||||
|
requires_cli: true
|
||||||
|
install_url: "https://github.com/ovh/shai"
|
||||||
|
cli_tool: "shai"
|
||||||
|
|
||||||
|
requires:
|
||||||
|
speckit_version: ">=0.1.0"
|
||||||
|
|
||||||
|
tags: ['cli', 'ovh', 'shai']
|
||||||
|
|
||||||
|
command_registration:
|
||||||
|
commands_dir: ".shai/commands"
|
||||||
|
format: "markdown"
|
||||||
|
arg_placeholder: "$ARGUMENTS"
|
||||||
|
file_extension: ".md"
|
||||||
25
src/specify_cli/core_pack/agents/tabnine/speckit-agent.yml
Normal file
25
src/specify_cli/core_pack/agents/tabnine/speckit-agent.yml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
schema_version: "1.0"
|
||||||
|
|
||||||
|
agent:
|
||||||
|
id: "tabnine"
|
||||||
|
name: "Tabnine CLI"
|
||||||
|
version: "1.0.0"
|
||||||
|
description: "Tabnine CLI for AI-assisted development"
|
||||||
|
author: "github"
|
||||||
|
license: "MIT"
|
||||||
|
|
||||||
|
runtime:
|
||||||
|
requires_cli: true
|
||||||
|
install_url: "https://docs.tabnine.com/main/getting-started/tabnine-cli"
|
||||||
|
cli_tool: "tabnine"
|
||||||
|
|
||||||
|
requires:
|
||||||
|
speckit_version: ">=0.1.0"
|
||||||
|
|
||||||
|
tags: ['cli', 'tabnine']
|
||||||
|
|
||||||
|
command_registration:
|
||||||
|
commands_dir: ".tabnine/agent/commands"
|
||||||
|
format: "toml"
|
||||||
|
arg_placeholder: "{{args}}"
|
||||||
|
file_extension: ".toml"
|
||||||
23
src/specify_cli/core_pack/agents/trae/speckit-agent.yml
Normal file
23
src/specify_cli/core_pack/agents/trae/speckit-agent.yml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
schema_version: "1.0"
|
||||||
|
|
||||||
|
agent:
|
||||||
|
id: "trae"
|
||||||
|
name: "Trae"
|
||||||
|
version: "1.0.0"
|
||||||
|
description: "Trae IDE for AI-assisted development"
|
||||||
|
author: "github"
|
||||||
|
license: "MIT"
|
||||||
|
|
||||||
|
runtime:
|
||||||
|
requires_cli: false
|
||||||
|
|
||||||
|
requires:
|
||||||
|
speckit_version: ">=0.1.0"
|
||||||
|
|
||||||
|
tags: ['ide', 'trae']
|
||||||
|
|
||||||
|
command_registration:
|
||||||
|
commands_dir: ".trae/rules"
|
||||||
|
format: "markdown"
|
||||||
|
arg_placeholder: "$ARGUMENTS"
|
||||||
|
file_extension: ".md"
|
||||||
25
src/specify_cli/core_pack/agents/vibe/speckit-agent.yml
Normal file
25
src/specify_cli/core_pack/agents/vibe/speckit-agent.yml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
schema_version: "1.0"
|
||||||
|
|
||||||
|
agent:
|
||||||
|
id: "vibe"
|
||||||
|
name: "Mistral Vibe"
|
||||||
|
version: "1.0.0"
|
||||||
|
description: "Mistral Vibe CLI for AI-assisted development"
|
||||||
|
author: "github"
|
||||||
|
license: "MIT"
|
||||||
|
|
||||||
|
runtime:
|
||||||
|
requires_cli: true
|
||||||
|
install_url: "https://github.com/mistralai/mistral-vibe"
|
||||||
|
cli_tool: "vibe"
|
||||||
|
|
||||||
|
requires:
|
||||||
|
speckit_version: ">=0.1.0"
|
||||||
|
|
||||||
|
tags: ['cli', 'mistral', 'vibe']
|
||||||
|
|
||||||
|
command_registration:
|
||||||
|
commands_dir: ".vibe/prompts"
|
||||||
|
format: "markdown"
|
||||||
|
arg_placeholder: "$ARGUMENTS"
|
||||||
|
file_extension: ".md"
|
||||||
23
src/specify_cli/core_pack/agents/windsurf/speckit-agent.yml
Normal file
23
src/specify_cli/core_pack/agents/windsurf/speckit-agent.yml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
schema_version: "1.0"
|
||||||
|
|
||||||
|
agent:
|
||||||
|
id: "windsurf"
|
||||||
|
name: "Windsurf"
|
||||||
|
version: "1.0.0"
|
||||||
|
description: "Windsurf IDE for AI-assisted development"
|
||||||
|
author: "github"
|
||||||
|
license: "MIT"
|
||||||
|
|
||||||
|
runtime:
|
||||||
|
requires_cli: false
|
||||||
|
|
||||||
|
requires:
|
||||||
|
speckit_version: ">=0.1.0"
|
||||||
|
|
||||||
|
tags: ['ide', 'windsurf']
|
||||||
|
|
||||||
|
command_registration:
|
||||||
|
commands_dir: ".windsurf/workflows"
|
||||||
|
format: "markdown"
|
||||||
|
arg_placeholder: "$ARGUMENTS"
|
||||||
|
file_extension: ".md"
|
||||||
@@ -975,8 +975,8 @@ class ExtensionCatalog:
|
|||||||
if not config_path.exists():
|
if not config_path.exists():
|
||||||
return None
|
return None
|
||||||
try:
|
try:
|
||||||
data = yaml.safe_load(config_path.read_text(encoding="utf-8")) or {}
|
data = yaml.safe_load(config_path.read_text()) or {}
|
||||||
except (yaml.YAMLError, OSError, UnicodeError) as e:
|
except (yaml.YAMLError, OSError) as e:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
f"Failed to read catalog config {config_path}: {e}"
|
f"Failed to read catalog config {config_path}: {e}"
|
||||||
)
|
)
|
||||||
@@ -1467,8 +1467,8 @@ class ConfigManager:
|
|||||||
return {}
|
return {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return yaml.safe_load(file_path.read_text(encoding="utf-8")) or {}
|
return yaml.safe_load(file_path.read_text()) or {}
|
||||||
except (yaml.YAMLError, OSError, UnicodeError):
|
except (yaml.YAMLError, OSError):
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def _get_extension_defaults(self) -> Dict[str, Any]:
|
def _get_extension_defaults(self) -> Dict[str, Any]:
|
||||||
@@ -1659,8 +1659,8 @@ class HookExecutor:
|
|||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return yaml.safe_load(self.config_file.read_text(encoding="utf-8")) or {}
|
return yaml.safe_load(self.config_file.read_text()) or {}
|
||||||
except (yaml.YAMLError, OSError, UnicodeError):
|
except (yaml.YAMLError, OSError):
|
||||||
return {
|
return {
|
||||||
"installed": [],
|
"installed": [],
|
||||||
"settings": {"auto_execute_hooks": True},
|
"settings": {"auto_execute_hooks": True},
|
||||||
@@ -1675,8 +1675,7 @@ class HookExecutor:
|
|||||||
"""
|
"""
|
||||||
self.config_file.parent.mkdir(parents=True, exist_ok=True)
|
self.config_file.parent.mkdir(parents=True, exist_ok=True)
|
||||||
self.config_file.write_text(
|
self.config_file.write_text(
|
||||||
yaml.dump(config, default_flow_style=False, sort_keys=False, allow_unicode=True),
|
yaml.dump(config, default_flow_style=False, sort_keys=False)
|
||||||
encoding="utf-8",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def register_hooks(self, manifest: ExtensionManifest):
|
def register_hooks(self, manifest: ExtensionManifest):
|
||||||
|
|||||||
@@ -1062,8 +1062,8 @@ class PresetCatalog:
|
|||||||
if not config_path.exists():
|
if not config_path.exists():
|
||||||
return None
|
return None
|
||||||
try:
|
try:
|
||||||
data = yaml.safe_load(config_path.read_text(encoding="utf-8")) or {}
|
data = yaml.safe_load(config_path.read_text()) or {}
|
||||||
except (yaml.YAMLError, OSError, UnicodeError) as e:
|
except (yaml.YAMLError, OSError) as e:
|
||||||
raise PresetValidationError(
|
raise PresetValidationError(
|
||||||
f"Failed to read catalog config {config_path}: {e}"
|
f"Failed to read catalog config {config_path}: {e}"
|
||||||
)
|
)
|
||||||
|
|||||||
1353
tests/test_agent_pack.py
Normal file
1353
tests/test_agent_pack.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -11,12 +11,10 @@ Tests cover:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import zipfile
|
|
||||||
import pytest
|
import pytest
|
||||||
import tempfile
|
import tempfile
|
||||||
import shutil
|
import shutil
|
||||||
import yaml
|
import yaml
|
||||||
import typer
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
@@ -722,8 +720,8 @@ class TestNewProjectCommandSkip:
|
|||||||
mock_skills.assert_not_called()
|
mock_skills.assert_not_called()
|
||||||
assert (target / ".agents" / "skills" / "speckit-specify" / "SKILL.md").exists()
|
assert (target / ".agents" / "skills" / "speckit-specify" / "SKILL.md").exists()
|
||||||
|
|
||||||
def test_codex_native_skills_missing_falls_back_then_fails_cleanly(self, tmp_path):
|
def test_codex_native_skills_missing_fails_clearly(self, tmp_path):
|
||||||
"""Codex should attempt fallback conversion when bundled skills are missing."""
|
"""Codex native skills init should fail if bundled skills are missing."""
|
||||||
from typer.testing import CliRunner
|
from typer.testing import CliRunner
|
||||||
|
|
||||||
runner = CliRunner()
|
runner = CliRunner()
|
||||||
@@ -732,7 +730,7 @@ class TestNewProjectCommandSkip:
|
|||||||
with patch("specify_cli.download_and_extract_template", lambda *args, **kwargs: None), \
|
with patch("specify_cli.download_and_extract_template", lambda *args, **kwargs: None), \
|
||||||
patch("specify_cli.ensure_executable_scripts"), \
|
patch("specify_cli.ensure_executable_scripts"), \
|
||||||
patch("specify_cli.ensure_constitution_from_template"), \
|
patch("specify_cli.ensure_constitution_from_template"), \
|
||||||
patch("specify_cli.install_ai_skills", return_value=False) as mock_skills, \
|
patch("specify_cli.install_ai_skills") as mock_skills, \
|
||||||
patch("specify_cli.is_git_repo", return_value=False), \
|
patch("specify_cli.is_git_repo", return_value=False), \
|
||||||
patch("specify_cli.shutil.which", return_value="/usr/bin/codex"):
|
patch("specify_cli.shutil.which", return_value="/usr/bin/codex"):
|
||||||
result = runner.invoke(
|
result = runner.invoke(
|
||||||
@@ -741,13 +739,11 @@ class TestNewProjectCommandSkip:
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert result.exit_code == 1
|
assert result.exit_code == 1
|
||||||
mock_skills.assert_called_once()
|
mock_skills.assert_not_called()
|
||||||
assert mock_skills.call_args.kwargs.get("overwrite_existing") is True
|
|
||||||
assert "Expected bundled agent skills" in result.output
|
assert "Expected bundled agent skills" in result.output
|
||||||
assert "fallback conversion failed" in result.output
|
|
||||||
|
|
||||||
def test_codex_native_skills_ignores_non_speckit_skill_dirs(self, tmp_path):
|
def test_codex_native_skills_ignores_non_speckit_skill_dirs(self, tmp_path):
|
||||||
"""Non-spec-kit SKILL.md files should trigger fallback conversion, not hard-fail."""
|
"""Non-spec-kit SKILL.md files should not satisfy Codex bundled-skills validation."""
|
||||||
from typer.testing import CliRunner
|
from typer.testing import CliRunner
|
||||||
|
|
||||||
runner = CliRunner()
|
runner = CliRunner()
|
||||||
@@ -761,7 +757,7 @@ class TestNewProjectCommandSkip:
|
|||||||
with patch("specify_cli.download_and_extract_template", side_effect=fake_download), \
|
with patch("specify_cli.download_and_extract_template", side_effect=fake_download), \
|
||||||
patch("specify_cli.ensure_executable_scripts"), \
|
patch("specify_cli.ensure_executable_scripts"), \
|
||||||
patch("specify_cli.ensure_constitution_from_template"), \
|
patch("specify_cli.ensure_constitution_from_template"), \
|
||||||
patch("specify_cli.install_ai_skills", return_value=True) as mock_skills, \
|
patch("specify_cli.install_ai_skills") as mock_skills, \
|
||||||
patch("specify_cli.is_git_repo", return_value=False), \
|
patch("specify_cli.is_git_repo", return_value=False), \
|
||||||
patch("specify_cli.shutil.which", return_value="/usr/bin/codex"):
|
patch("specify_cli.shutil.which", return_value="/usr/bin/codex"):
|
||||||
result = runner.invoke(
|
result = runner.invoke(
|
||||||
@@ -769,100 +765,9 @@ class TestNewProjectCommandSkip:
|
|||||||
["init", str(target), "--ai", "codex", "--ai-skills", "--script", "sh", "--no-git"],
|
["init", str(target), "--ai", "codex", "--ai-skills", "--script", "sh", "--no-git"],
|
||||||
)
|
)
|
||||||
|
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 1
|
||||||
mock_skills.assert_called_once()
|
mock_skills.assert_not_called()
|
||||||
assert mock_skills.call_args.kwargs.get("overwrite_existing") is True
|
assert "Expected bundled agent skills" in result.output
|
||||||
|
|
||||||
def test_codex_ai_skills_here_mode_preserves_existing_codex_dir(self, tmp_path, monkeypatch):
|
|
||||||
"""Codex --here skills init should not delete a pre-existing .codex directory."""
|
|
||||||
from typer.testing import CliRunner
|
|
||||||
|
|
||||||
runner = CliRunner()
|
|
||||||
target = tmp_path / "codex-preserve-here"
|
|
||||||
target.mkdir()
|
|
||||||
existing_prompts = target / ".codex" / "prompts"
|
|
||||||
existing_prompts.mkdir(parents=True)
|
|
||||||
(existing_prompts / "custom.md").write_text("custom")
|
|
||||||
monkeypatch.chdir(target)
|
|
||||||
|
|
||||||
with patch("specify_cli.download_and_extract_template", return_value=target), \
|
|
||||||
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/codex"):
|
|
||||||
result = runner.invoke(
|
|
||||||
app,
|
|
||||||
["init", "--here", "--ai", "codex", "--ai-skills", "--script", "sh", "--no-git"],
|
|
||||||
input="y\n",
|
|
||||||
)
|
|
||||||
|
|
||||||
assert result.exit_code == 0
|
|
||||||
assert (target / ".codex").exists()
|
|
||||||
assert (existing_prompts / "custom.md").exists()
|
|
||||||
|
|
||||||
def test_codex_ai_skills_fresh_dir_does_not_create_codex_dir(self, tmp_path):
|
|
||||||
"""Fresh-directory Codex skills init should not leave legacy .codex from archive."""
|
|
||||||
target = tmp_path / "fresh-codex-proj"
|
|
||||||
archive = tmp_path / "codex-template.zip"
|
|
||||||
|
|
||||||
with zipfile.ZipFile(archive, "w") as zf:
|
|
||||||
zf.writestr("template-root/.codex/prompts/speckit.specify.md", "legacy")
|
|
||||||
zf.writestr("template-root/.specify/templates/constitution-template.md", "constitution")
|
|
||||||
|
|
||||||
fake_meta = {
|
|
||||||
"filename": archive.name,
|
|
||||||
"size": archive.stat().st_size,
|
|
||||||
"release": "vtest",
|
|
||||||
"asset_url": "https://example.invalid/template.zip",
|
|
||||||
}
|
|
||||||
|
|
||||||
with patch("specify_cli.download_template_from_github", return_value=(archive, fake_meta)):
|
|
||||||
specify_cli.download_and_extract_template(
|
|
||||||
target,
|
|
||||||
"codex",
|
|
||||||
"sh",
|
|
||||||
is_current_dir=False,
|
|
||||||
skip_legacy_codex_prompts=True,
|
|
||||||
verbose=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
assert target.exists()
|
|
||||||
assert (target / ".specify").exists()
|
|
||||||
assert not (target / ".codex").exists()
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("is_current_dir", [False, True])
|
|
||||||
def test_download_and_extract_template_blocks_zip_path_traversal(self, tmp_path, monkeypatch, is_current_dir):
|
|
||||||
"""Extraction should reject ZIP members escaping the target directory."""
|
|
||||||
target = (tmp_path / "here-proj") if is_current_dir else (tmp_path / "new-proj")
|
|
||||||
if is_current_dir:
|
|
||||||
target.mkdir()
|
|
||||||
monkeypatch.chdir(target)
|
|
||||||
|
|
||||||
archive = tmp_path / "malicious-template.zip"
|
|
||||||
with zipfile.ZipFile(archive, "w") as zf:
|
|
||||||
zf.writestr("../evil.txt", "pwned")
|
|
||||||
zf.writestr("template-root/.specify/templates/constitution-template.md", "constitution")
|
|
||||||
|
|
||||||
fake_meta = {
|
|
||||||
"filename": archive.name,
|
|
||||||
"size": archive.stat().st_size,
|
|
||||||
"release": "vtest",
|
|
||||||
"asset_url": "https://example.invalid/template.zip",
|
|
||||||
}
|
|
||||||
|
|
||||||
with patch("specify_cli.download_template_from_github", return_value=(archive, fake_meta)):
|
|
||||||
with pytest.raises(typer.Exit):
|
|
||||||
specify_cli.download_and_extract_template(
|
|
||||||
target,
|
|
||||||
"codex",
|
|
||||||
"sh",
|
|
||||||
is_current_dir=is_current_dir,
|
|
||||||
skip_legacy_codex_prompts=True,
|
|
||||||
verbose=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
assert not (tmp_path / "evil.txt").exists()
|
|
||||||
|
|
||||||
def test_commands_preserved_when_skills_fail(self, tmp_path):
|
def test_commands_preserved_when_skills_fail(self, tmp_path):
|
||||||
"""If skills fail, commands should NOT be removed (safety net)."""
|
"""If skills fail, commands should NOT be removed (safety net)."""
|
||||||
@@ -954,21 +859,6 @@ class TestSkipIfExists:
|
|||||||
# All 4 templates should produce skills (specify, plan, tasks, empty_fm)
|
# All 4 templates should produce skills (specify, plan, tasks, empty_fm)
|
||||||
assert len(skill_dirs) == 4
|
assert len(skill_dirs) == 4
|
||||||
|
|
||||||
def test_existing_skill_overwritten_when_enabled(self, project_dir, templates_dir):
|
|
||||||
"""When overwrite_existing=True, pre-existing SKILL.md should be replaced."""
|
|
||||||
skill_dir = project_dir / ".claude" / "skills" / "speckit-specify"
|
|
||||||
skill_dir.mkdir(parents=True)
|
|
||||||
custom_content = "# My Custom Specify Skill\nUser-modified content\n"
|
|
||||||
skill_file = skill_dir / "SKILL.md"
|
|
||||||
skill_file.write_text(custom_content)
|
|
||||||
|
|
||||||
result = install_ai_skills(project_dir, "claude", overwrite_existing=True)
|
|
||||||
|
|
||||||
assert result is True
|
|
||||||
updated_content = skill_file.read_text()
|
|
||||||
assert updated_content != custom_content
|
|
||||||
assert "name: speckit-specify" in updated_content
|
|
||||||
|
|
||||||
|
|
||||||
# ===== SKILL_DESCRIPTIONS Coverage Tests =====
|
# ===== SKILL_DESCRIPTIONS Coverage Tests =====
|
||||||
|
|
||||||
|
|||||||
@@ -747,18 +747,6 @@ $ARGUMENTS
|
|||||||
assert output.endswith("---\n")
|
assert output.endswith("---\n")
|
||||||
assert "description: Test command" in output
|
assert "description: Test command" in output
|
||||||
|
|
||||||
def test_render_frontmatter_unicode(self):
|
|
||||||
"""Test rendering frontmatter preserves non-ASCII characters."""
|
|
||||||
frontmatter = {
|
|
||||||
"description": "Prüfe Konformität der Implementierung"
|
|
||||||
}
|
|
||||||
|
|
||||||
registrar = CommandRegistrar()
|
|
||||||
output = registrar.render_frontmatter(frontmatter)
|
|
||||||
|
|
||||||
assert "Prüfe Konformität" in output
|
|
||||||
assert "\\u" not in output
|
|
||||||
|
|
||||||
def test_register_commands_for_claude(self, extension_dir, project_dir):
|
def test_register_commands_for_claude(self, extension_dir, project_dir):
|
||||||
"""Test registering commands for Claude agent."""
|
"""Test registering commands for Claude agent."""
|
||||||
# Create .claude directory
|
# Create .claude directory
|
||||||
|
|||||||
Reference in New Issue
Block a user