mirror of
https://github.com/github/spec-kit.git
synced 2026-03-21 04:43:08 +00:00
Compare commits
52 Commits
chore/rele
...
copilot/ag
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
978addc390 | ||
|
|
9b580a536b | ||
|
|
d6016ab9db | ||
|
|
c2227a7ffd | ||
|
|
c3efd1fb71 | ||
|
|
e190116d13 | ||
|
|
a63c248c80 | ||
|
|
b5a5e3fc35 | ||
|
|
ec5471af61 | ||
|
|
3212309e7c | ||
|
|
8b20d0b336 | ||
|
|
bf33980426 | ||
|
|
a7606c0f14 | ||
|
|
7d9361c716 | ||
|
|
191f33213c | ||
|
|
65ecd5321d | ||
|
|
d2559d7025 | ||
|
|
f85944aafe | ||
|
|
34171efcef | ||
|
|
c8af730b14 | ||
|
|
a4b60aca7f | ||
|
|
2f25e2d575 | ||
|
|
7484eb521a | ||
|
|
2bf655e261 | ||
|
|
f6794685b6 | ||
|
|
333a76535b | ||
|
|
6d0b84ab5b | ||
|
|
497b5885e1 | ||
|
|
33c83a6162 | ||
|
|
f97c8e95a6 | ||
|
|
cfd99ad499 | ||
|
|
96712e1cdf | ||
|
|
2e55bdd3f2 | ||
|
|
eecb723663 | ||
|
|
1a21bdef01 | ||
|
|
f21eb71990 | ||
|
|
b471b5e6f3 | ||
|
|
489ced56ba | ||
|
|
6644f69a96 | ||
|
|
a177a1a6d1 | ||
|
|
c12b8c1763 | ||
|
|
d2ecf6560d | ||
|
|
7a5762fe6a | ||
|
|
9c0c1446ec | ||
|
|
82b8ce4295 | ||
|
|
2cf332db1b | ||
|
|
b1650f884d | ||
|
|
23bd645054 | ||
|
|
bef9c2cb59 | ||
|
|
4f81fc298f | ||
|
|
4a3234496e | ||
|
|
f92d81bbec |
@@ -51,6 +51,14 @@ echo -e "\n🤖 Installing OpenCode CLI..."
|
|||||||
run_command "npm install -g opencode-ai@latest"
|
run_command "npm install -g opencode-ai@latest"
|
||||||
echo "✅ Done"
|
echo "✅ Done"
|
||||||
|
|
||||||
|
echo -e "\n🤖 Installing Junie CLI..."
|
||||||
|
run_command "npm install -g @jetbrains/junie-cli@latest"
|
||||||
|
echo "✅ Done"
|
||||||
|
|
||||||
|
echo -e "\n🤖 Installing Pi Coding Agent..."
|
||||||
|
run_command "npm install -g @mariozechner/pi-coding-agent@latest"
|
||||||
|
echo "✅ Done"
|
||||||
|
|
||||||
echo -e "\n🤖 Installing Kiro CLI..."
|
echo -e "\n🤖 Installing Kiro CLI..."
|
||||||
# https://kiro.dev/docs/cli/
|
# https://kiro.dev/docs/cli/
|
||||||
KIRO_INSTALLER_URL="https://kiro.dev/install.sh"
|
KIRO_INSTALLER_URL="https://kiro.dev/install.sh"
|
||||||
|
|||||||
2
.github/ISSUE_TEMPLATE/agent_request.yml
vendored
2
.github/ISSUE_TEMPLATE/agent_request.yml
vendored
@@ -8,7 +8,7 @@ body:
|
|||||||
value: |
|
value: |
|
||||||
Thanks for requesting a new agent! Before submitting, please check if the agent is already supported.
|
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, Kiro CLI, Amp, SHAI, IBM Bob, Antigravity
|
**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, Kiro CLI, Amp, SHAI, Tabnine CLI, Antigravity, IBM Bob, Mistral Vibe, Kimi Code, Trae, Pi Coding Agent, iFlow CLI
|
||||||
|
|
||||||
- type: input
|
- type: input
|
||||||
id: agent-name
|
id: agent-name
|
||||||
|
|||||||
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -7,7 +7,7 @@ contact_links:
|
|||||||
url: https://github.com/github/spec-kit/blob/main/README.md
|
url: https://github.com/github/spec-kit/blob/main/README.md
|
||||||
about: Read the Spec Kit documentation and guides
|
about: Read the Spec Kit documentation and guides
|
||||||
- name: 🛠️ Extension Development Guide
|
- name: 🛠️ Extension Development Guide
|
||||||
url: https://github.com/manfredseee/spec-kit/blob/main/extensions/EXTENSION-DEVELOPMENT-GUIDE.md
|
url: https://github.com/github/spec-kit/blob/main/extensions/EXTENSION-DEVELOPMENT-GUIDE.md
|
||||||
about: Learn how to develop and publish Spec Kit extensions
|
about: Learn how to develop and publish Spec Kit extensions
|
||||||
- name: 🤝 Contributing Guide
|
- name: 🤝 Contributing Guide
|
||||||
url: https://github.com/github/spec-kit/blob/main/CONTRIBUTING.md
|
url: https://github.com/github/spec-kit/blob/main/CONTRIBUTING.md
|
||||||
|
|||||||
169
.github/ISSUE_TEMPLATE/preset_submission.yml
vendored
Normal file
169
.github/ISSUE_TEMPLATE/preset_submission.yml
vendored
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
name: Preset Submission
|
||||||
|
description: Submit your preset to the Spec Kit preset catalog
|
||||||
|
title: "[Preset]: Add "
|
||||||
|
labels: ["preset-submission", "enhancement", "needs-triage"]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thanks for contributing a preset! This template helps you submit your preset to the community catalog.
|
||||||
|
|
||||||
|
**Before submitting:**
|
||||||
|
- Review the [Preset Publishing Guide](https://github.com/github/spec-kit/blob/main/presets/PUBLISHING.md)
|
||||||
|
- Ensure your preset has a valid `preset.yml` manifest
|
||||||
|
- Create a GitHub release with a version tag (e.g., v1.0.0)
|
||||||
|
- Test installation from the release archive: `specify preset add --from <download-url>`
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: preset-id
|
||||||
|
attributes:
|
||||||
|
label: Preset ID
|
||||||
|
description: Unique preset identifier (lowercase with hyphens only)
|
||||||
|
placeholder: "e.g., healthcare-compliance"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: preset-name
|
||||||
|
attributes:
|
||||||
|
label: Preset Name
|
||||||
|
description: Human-readable preset name
|
||||||
|
placeholder: "e.g., Healthcare Compliance"
|
||||||
|
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 preset does (under 200 characters)
|
||||||
|
placeholder: Enforces HIPAA-compliant spec workflows with audit templates and compliance checklists
|
||||||
|
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 preset
|
||||||
|
placeholder: "https://github.com/your-org/spec-kit-your-preset"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: download-url
|
||||||
|
attributes:
|
||||||
|
label: Download URL
|
||||||
|
description: URL to the GitHub release archive for your preset (e.g., https://github.com/your-org/spec-kit-preset-your-preset/archive/refs/tags/v1.0.0.zip)
|
||||||
|
placeholder: "https://github.com/your-org/spec-kit-preset-your-preset/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: speckit-version
|
||||||
|
attributes:
|
||||||
|
label: Required Spec Kit Version
|
||||||
|
description: Minimum Spec Kit version required
|
||||||
|
placeholder: "e.g., >=0.3.0"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: templates-provided
|
||||||
|
attributes:
|
||||||
|
label: Templates Provided
|
||||||
|
description: List the template overrides your preset provides
|
||||||
|
placeholder: |
|
||||||
|
- spec-template.md — adds compliance section
|
||||||
|
- plan-template.md — includes audit checkpoints
|
||||||
|
- checklist-template.md — HIPAA compliance checklist
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: commands-provided
|
||||||
|
attributes:
|
||||||
|
label: Commands Provided (optional)
|
||||||
|
description: List any command overrides your preset provides
|
||||||
|
placeholder: |
|
||||||
|
- speckit.specify.md — customized for compliance workflows
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: tags
|
||||||
|
attributes:
|
||||||
|
label: Tags
|
||||||
|
description: 2-5 relevant tags (lowercase, separated by commas)
|
||||||
|
placeholder: "compliance, healthcare, hipaa, audit"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: features
|
||||||
|
attributes:
|
||||||
|
label: Key Features
|
||||||
|
description: List the main features and capabilities of your preset
|
||||||
|
placeholder: |
|
||||||
|
- HIPAA-compliant spec templates
|
||||||
|
- Audit trail checklists
|
||||||
|
- Compliance review workflow
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
id: testing
|
||||||
|
attributes:
|
||||||
|
label: Testing Checklist
|
||||||
|
description: Confirm that your preset has been tested
|
||||||
|
options:
|
||||||
|
- label: Preset installs successfully via `specify preset add`
|
||||||
|
required: true
|
||||||
|
- label: Template resolution works correctly after installation
|
||||||
|
required: true
|
||||||
|
- label: Documentation is complete and accurate
|
||||||
|
required: true
|
||||||
|
- label: Tested on at least one real project
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
id: requirements
|
||||||
|
attributes:
|
||||||
|
label: Submission Requirements
|
||||||
|
description: Verify your preset meets all requirements
|
||||||
|
options:
|
||||||
|
- label: Valid `preset.yml` manifest included
|
||||||
|
required: true
|
||||||
|
- label: README.md with description and usage instructions
|
||||||
|
required: true
|
||||||
|
- label: LICENSE file included
|
||||||
|
required: true
|
||||||
|
- label: GitHub release created with version tag
|
||||||
|
required: true
|
||||||
|
- label: Preset ID follows naming conventions (lowercase-with-hyphens)
|
||||||
|
required: true
|
||||||
8
.github/workflows/release-trigger.yml
vendored
8
.github/workflows/release-trigger.yml
vendored
@@ -86,8 +86,10 @@ jobs:
|
|||||||
if [ -f "CHANGELOG.md" ]; then
|
if [ -f "CHANGELOG.md" ]; then
|
||||||
DATE=$(date +%Y-%m-%d)
|
DATE=$(date +%Y-%m-%d)
|
||||||
|
|
||||||
# Get the previous tag to compare commits
|
# Get the previous tag by sorting all version tags numerically
|
||||||
PREVIOUS_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
|
# (git describe --tags only finds tags reachable from HEAD,
|
||||||
|
# which misses tags on unmerged release branches)
|
||||||
|
PREVIOUS_TAG=$(git tag -l 'v*' --sort=-version:refname | head -n 1)
|
||||||
|
|
||||||
echo "Generating changelog from commits..."
|
echo "Generating changelog from commits..."
|
||||||
if [[ -n "$PREVIOUS_TAG" ]]; then
|
if [[ -n "$PREVIOUS_TAG" ]]; then
|
||||||
@@ -104,7 +106,7 @@ jobs:
|
|||||||
echo ""
|
echo ""
|
||||||
echo "## [${{ steps.version.outputs.version }}] - $DATE"
|
echo "## [${{ steps.version.outputs.version }}] - $DATE"
|
||||||
echo ""
|
echo ""
|
||||||
echo "### Changed"
|
echo "### Changes"
|
||||||
echo ""
|
echo ""
|
||||||
echo "$COMMITS"
|
echo "$COMMITS"
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ gh release create "$VERSION" \
|
|||||||
.genreleases/spec-kit-template-qwen-ps-"$VERSION".zip \
|
.genreleases/spec-kit-template-qwen-ps-"$VERSION".zip \
|
||||||
.genreleases/spec-kit-template-windsurf-sh-"$VERSION".zip \
|
.genreleases/spec-kit-template-windsurf-sh-"$VERSION".zip \
|
||||||
.genreleases/spec-kit-template-windsurf-ps-"$VERSION".zip \
|
.genreleases/spec-kit-template-windsurf-ps-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-junie-sh-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-junie-ps-"$VERSION".zip \
|
||||||
.genreleases/spec-kit-template-codex-sh-"$VERSION".zip \
|
.genreleases/spec-kit-template-codex-sh-"$VERSION".zip \
|
||||||
.genreleases/spec-kit-template-codex-ps-"$VERSION".zip \
|
.genreleases/spec-kit-template-codex-ps-"$VERSION".zip \
|
||||||
.genreleases/spec-kit-template-kilocode-sh-"$VERSION".zip \
|
.genreleases/spec-kit-template-kilocode-sh-"$VERSION".zip \
|
||||||
@@ -58,6 +60,12 @@ gh release create "$VERSION" \
|
|||||||
.genreleases/spec-kit-template-vibe-ps-"$VERSION".zip \
|
.genreleases/spec-kit-template-vibe-ps-"$VERSION".zip \
|
||||||
.genreleases/spec-kit-template-kimi-sh-"$VERSION".zip \
|
.genreleases/spec-kit-template-kimi-sh-"$VERSION".zip \
|
||||||
.genreleases/spec-kit-template-kimi-ps-"$VERSION".zip \
|
.genreleases/spec-kit-template-kimi-ps-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-trae-sh-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-trae-ps-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-pi-sh-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-pi-ps-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-iflow-sh-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-iflow-ps-"$VERSION".zip \
|
||||||
.genreleases/spec-kit-template-generic-sh-"$VERSION".zip \
|
.genreleases/spec-kit-template-generic-sh-"$VERSION".zip \
|
||||||
.genreleases/spec-kit-template-generic-ps-"$VERSION".zip \
|
.genreleases/spec-kit-template-generic-ps-"$VERSION".zip \
|
||||||
--title "Spec Kit Templates - $VERSION_NO_V" \
|
--title "Spec Kit Templates - $VERSION_NO_V" \
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
.PARAMETER Agents
|
.PARAMETER Agents
|
||||||
Comma or space separated subset of agents to build (default: all)
|
Comma or space separated subset of agents to build (default: all)
|
||||||
Valid agents: claude, gemini, copilot, cursor-agent, qwen, opencode, windsurf, codex, kilocode, auggie, roo, codebuddy, amp, kiro-cli, bob, qodercli, shai, tabnine, agy, vibe, kimi, generic
|
Valid agents: claude, gemini, copilot, cursor-agent, qwen, opencode, windsurf, junie, codex, kilocode, auggie, roo, codebuddy, amp, kiro-cli, bob, qodercli, shai, tabnine, agy, vibe, kimi, trae, pi, iflow, generic
|
||||||
|
|
||||||
.PARAMETER Scripts
|
.PARAMETER Scripts
|
||||||
Comma or space separated subset of script types to build (default: both)
|
Comma or space separated subset of script types to build (default: both)
|
||||||
@@ -201,20 +201,26 @@ agent: $basename
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Create Kimi Code skills in .kimi/skills/<name>/SKILL.md format.
|
# Create skills in <skills_dir>\<name>\SKILL.md format.
|
||||||
# Kimi CLI discovers skills as directories containing a SKILL.md file,
|
# Most agents use hyphenated names (e.g. speckit-plan); Kimi is the
|
||||||
# invoked with /skill:<name> (e.g. /skill:speckit.specify).
|
# current dotted-name exception (e.g. speckit.plan).
|
||||||
function New-KimiSkills {
|
#
|
||||||
|
# Technical debt note:
|
||||||
|
# Keep SKILL.md frontmatter aligned with `install_ai_skills()` and extension
|
||||||
|
# overrides (at minimum: name/description/compatibility/metadata.{author,source}).
|
||||||
|
function New-Skills {
|
||||||
param(
|
param(
|
||||||
[string]$SkillsDir,
|
[string]$SkillsDir,
|
||||||
[string]$ScriptVariant
|
[string]$ScriptVariant,
|
||||||
|
[string]$AgentName,
|
||||||
|
[string]$Separator = '-'
|
||||||
)
|
)
|
||||||
|
|
||||||
$templates = Get-ChildItem -Path "templates/commands/*.md" -File -ErrorAction SilentlyContinue
|
$templates = Get-ChildItem -Path "templates/commands/*.md" -File -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
foreach ($template in $templates) {
|
foreach ($template in $templates) {
|
||||||
$name = [System.IO.Path]::GetFileNameWithoutExtension($template.Name)
|
$name = [System.IO.Path]::GetFileNameWithoutExtension($template.Name)
|
||||||
$skillName = "speckit.$name"
|
$skillName = "speckit${Separator}$name"
|
||||||
$skillDir = Join-Path $SkillsDir $skillName
|
$skillDir = Join-Path $SkillsDir $skillName
|
||||||
New-Item -ItemType Directory -Force -Path $skillDir | Out-Null
|
New-Item -ItemType Directory -Force -Path $skillDir | Out-Null
|
||||||
|
|
||||||
@@ -267,7 +273,7 @@ function New-KimiSkills {
|
|||||||
|
|
||||||
$body = $outputLines -join "`n"
|
$body = $outputLines -join "`n"
|
||||||
$body = $body -replace '\{ARGS\}', '$ARGUMENTS'
|
$body = $body -replace '\{ARGS\}', '$ARGUMENTS'
|
||||||
$body = $body -replace '__AGENT__', 'kimi'
|
$body = $body -replace '__AGENT__', $AgentName
|
||||||
$body = Rewrite-Paths -Content $body
|
$body = Rewrite-Paths -Content $body
|
||||||
|
|
||||||
# Strip existing frontmatter, keep only body
|
# Strip existing frontmatter, keep only body
|
||||||
@@ -283,7 +289,7 @@ function New-KimiSkills {
|
|||||||
if ($inBody) { $templateBody += "$line`n" }
|
if ($inBody) { $templateBody += "$line`n" }
|
||||||
}
|
}
|
||||||
|
|
||||||
$skillContent = "---`nname: `"$skillName`"`ndescription: `"$description`"`n---`n`n$templateBody"
|
$skillContent = "---`nname: `"$skillName`"`ndescription: `"$description`"`ncompatibility: `"Requires spec-kit project structure with .specify/ directory`"`nmetadata:`n author: `"github-spec-kit`"`n source: `"templates/commands/$name.md`"`n---`n`n$templateBody"
|
||||||
Set-Content -Path (Join-Path $skillDir "SKILL.md") -Value $skillContent -NoNewline
|
Set-Content -Path (Join-Path $skillDir "SKILL.md") -Value $skillContent -NoNewline
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -395,9 +401,14 @@ function Build-Variant {
|
|||||||
$cmdDir = Join-Path $baseDir ".windsurf/workflows"
|
$cmdDir = Join-Path $baseDir ".windsurf/workflows"
|
||||||
Generate-Commands -Agent 'windsurf' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script
|
Generate-Commands -Agent 'windsurf' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script
|
||||||
}
|
}
|
||||||
|
'junie' {
|
||||||
|
$cmdDir = Join-Path $baseDir ".junie/commands"
|
||||||
|
Generate-Commands -Agent 'junie' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script
|
||||||
|
}
|
||||||
'codex' {
|
'codex' {
|
||||||
$cmdDir = Join-Path $baseDir ".codex/prompts"
|
$skillsDir = Join-Path $baseDir ".agents/skills"
|
||||||
Generate-Commands -Agent 'codex' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script
|
New-Item -ItemType Directory -Force -Path $skillsDir | Out-Null
|
||||||
|
New-Skills -SkillsDir $skillsDir -ScriptVariant $Script -AgentName 'codex' -Separator '-'
|
||||||
}
|
}
|
||||||
'kilocode' {
|
'kilocode' {
|
||||||
$cmdDir = Join-Path $baseDir ".kilocode/workflows"
|
$cmdDir = Join-Path $baseDir ".kilocode/workflows"
|
||||||
@@ -452,7 +463,20 @@ function Build-Variant {
|
|||||||
'kimi' {
|
'kimi' {
|
||||||
$skillsDir = Join-Path $baseDir ".kimi/skills"
|
$skillsDir = Join-Path $baseDir ".kimi/skills"
|
||||||
New-Item -ItemType Directory -Force -Path $skillsDir | Out-Null
|
New-Item -ItemType Directory -Force -Path $skillsDir | Out-Null
|
||||||
New-KimiSkills -SkillsDir $skillsDir -ScriptVariant $Script
|
New-Skills -SkillsDir $skillsDir -ScriptVariant $Script -AgentName 'kimi' -Separator '.'
|
||||||
|
}
|
||||||
|
'trae' {
|
||||||
|
$rulesDir = Join-Path $baseDir ".trae/rules"
|
||||||
|
New-Item -ItemType Directory -Force -Path $rulesDir | Out-Null
|
||||||
|
Generate-Commands -Agent 'trae' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $rulesDir -ScriptVariant $Script
|
||||||
|
}
|
||||||
|
'pi' {
|
||||||
|
$cmdDir = Join-Path $baseDir ".pi/prompts"
|
||||||
|
Generate-Commands -Agent 'pi' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script
|
||||||
|
}
|
||||||
|
'iflow' {
|
||||||
|
$cmdDir = Join-Path $baseDir ".iflow/commands"
|
||||||
|
Generate-Commands -Agent 'iflow' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script
|
||||||
}
|
}
|
||||||
'generic' {
|
'generic' {
|
||||||
$cmdDir = Join-Path $baseDir ".speckit/commands"
|
$cmdDir = Join-Path $baseDir ".speckit/commands"
|
||||||
@@ -470,7 +494,7 @@ function Build-Variant {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Define all agents and scripts
|
# Define all agents and scripts
|
||||||
$AllAgents = @('claude', 'gemini', 'copilot', 'cursor-agent', 'qwen', 'opencode', 'windsurf', 'codex', 'kilocode', 'auggie', 'roo', 'codebuddy', 'amp', 'kiro-cli', 'bob', 'qodercli', 'shai', 'tabnine', 'agy', 'vibe', 'kimi', 'generic')
|
$AllAgents = @('claude', 'gemini', 'copilot', 'cursor-agent', 'qwen', 'opencode', 'windsurf', 'junie', 'codex', 'kilocode', 'auggie', 'roo', 'codebuddy', 'amp', 'kiro-cli', 'bob', 'qodercli', 'shai', 'tabnine', 'agy', 'vibe', 'kimi', 'trae', 'pi', 'iflow', 'generic')
|
||||||
$AllScripts = @('sh', 'ps')
|
$AllScripts = @('sh', 'ps')
|
||||||
|
|
||||||
function Normalize-List {
|
function Normalize-List {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ set -euo pipefail
|
|||||||
# Usage: .github/workflows/scripts/create-release-packages.sh <version>
|
# Usage: .github/workflows/scripts/create-release-packages.sh <version>
|
||||||
# Version argument should include leading 'v'.
|
# Version argument should include leading 'v'.
|
||||||
# Optionally set AGENTS and/or SCRIPTS env vars to limit what gets built.
|
# Optionally set AGENTS and/or SCRIPTS env vars to limit what gets built.
|
||||||
# AGENTS : space or comma separated subset of: claude gemini copilot cursor-agent qwen opencode windsurf codex kilocode auggie roo codebuddy amp shai tabnine kiro-cli agy bob vibe qodercli kimi generic (default: all)
|
# AGENTS : space or comma separated subset of: claude gemini copilot cursor-agent qwen opencode windsurf junie codex kilocode auggie roo codebuddy amp shai tabnine kiro-cli agy bob vibe qodercli kimi trae pi iflow generic (default: all)
|
||||||
# SCRIPTS : space or comma separated subset of: sh ps (default: both)
|
# SCRIPTS : space or comma separated subset of: sh ps (default: both)
|
||||||
# Examples:
|
# Examples:
|
||||||
# AGENTS=claude SCRIPTS=sh $0 v0.2.0
|
# AGENTS=claude SCRIPTS=sh $0 v0.2.0
|
||||||
@@ -26,9 +26,27 @@ fi
|
|||||||
echo "Building release packages for $NEW_VERSION"
|
echo "Building release packages for $NEW_VERSION"
|
||||||
|
|
||||||
# Create and use .genreleases directory for all build artifacts
|
# Create and use .genreleases directory for all build artifacts
|
||||||
GENRELEASES_DIR=".genreleases"
|
# Override via GENRELEASES_DIR env var (e.g. for tests writing to a temp dir)
|
||||||
|
GENRELEASES_DIR="${GENRELEASES_DIR:-.genreleases}"
|
||||||
|
|
||||||
|
# Guard against unsafe GENRELEASES_DIR values before cleaning
|
||||||
|
if [[ -z "$GENRELEASES_DIR" ]]; then
|
||||||
|
echo "GENRELEASES_DIR must not be empty" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
case "$GENRELEASES_DIR" in
|
||||||
|
'/'|'.'|'..')
|
||||||
|
echo "Refusing to use unsafe GENRELEASES_DIR value: $GENRELEASES_DIR" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
if [[ "$GENRELEASES_DIR" == *".."* ]]; then
|
||||||
|
echo "Refusing to use GENRELEASES_DIR containing '..' path segments: $GENRELEASES_DIR" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
mkdir -p "$GENRELEASES_DIR"
|
mkdir -p "$GENRELEASES_DIR"
|
||||||
rm -rf "$GENRELEASES_DIR"/* || true
|
rm -rf "${GENRELEASES_DIR%/}/"* || true
|
||||||
|
|
||||||
rewrite_paths() {
|
rewrite_paths() {
|
||||||
sed -E \
|
sed -E \
|
||||||
@@ -121,18 +139,24 @@ EOF
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
# Create Kimi Code skills in .kimi/skills/<name>/SKILL.md format.
|
# Create skills in <skills_dir>/<name>/SKILL.md format.
|
||||||
# Kimi CLI discovers skills as directories containing a SKILL.md file,
|
# Most agents use hyphenated names (e.g. speckit-plan); Kimi is the
|
||||||
# invoked with /skill:<name> (e.g. /skill:speckit.specify).
|
# current dotted-name exception (e.g. speckit.plan).
|
||||||
create_kimi_skills() {
|
#
|
||||||
|
# Technical debt note:
|
||||||
|
# Keep SKILL.md frontmatter aligned with `install_ai_skills()` and extension
|
||||||
|
# overrides (at minimum: name/description/compatibility/metadata.{author,source}).
|
||||||
|
create_skills() {
|
||||||
local skills_dir="$1"
|
local skills_dir="$1"
|
||||||
local script_variant="$2"
|
local script_variant="$2"
|
||||||
|
local agent_name="$3"
|
||||||
|
local separator="${4:-"-"}"
|
||||||
|
|
||||||
for template in templates/commands/*.md; do
|
for template in templates/commands/*.md; do
|
||||||
[[ -f "$template" ]] || continue
|
[[ -f "$template" ]] || continue
|
||||||
local name
|
local name
|
||||||
name=$(basename "$template" .md)
|
name=$(basename "$template" .md)
|
||||||
local skill_name="speckit.${name}"
|
local skill_name="speckit${separator}${name}"
|
||||||
local skill_dir="${skills_dir}/${skill_name}"
|
local skill_dir="${skills_dir}/${skill_name}"
|
||||||
mkdir -p "$skill_dir"
|
mkdir -p "$skill_dir"
|
||||||
|
|
||||||
@@ -175,9 +199,9 @@ create_kimi_skills() {
|
|||||||
in_frontmatter && skip_scripts && /^[[:space:]]/ { next }
|
in_frontmatter && skip_scripts && /^[[:space:]]/ { next }
|
||||||
{ print }
|
{ print }
|
||||||
')
|
')
|
||||||
body=$(printf '%s\n' "$body" | sed 's/{ARGS}/\$ARGUMENTS/g' | sed 's/__AGENT__/kimi/g' | rewrite_paths)
|
body=$(printf '%s\n' "$body" | sed 's/{ARGS}/\$ARGUMENTS/g' | sed "s/__AGENT__/$agent_name/g" | rewrite_paths)
|
||||||
|
|
||||||
# Strip existing frontmatter and prepend Kimi frontmatter
|
# Strip existing frontmatter and prepend skills frontmatter.
|
||||||
local template_body
|
local template_body
|
||||||
template_body=$(printf '%s\n' "$body" | awk '/^---/{p++; if(p==2){found=1; next}} found')
|
template_body=$(printf '%s\n' "$body" | awk '/^---/{p++; if(p==2){found=1; next}} found')
|
||||||
|
|
||||||
@@ -185,6 +209,10 @@ create_kimi_skills() {
|
|||||||
printf -- '---\n'
|
printf -- '---\n'
|
||||||
printf 'name: "%s"\n' "$skill_name"
|
printf 'name: "%s"\n' "$skill_name"
|
||||||
printf 'description: "%s"\n' "$description"
|
printf 'description: "%s"\n' "$description"
|
||||||
|
printf 'compatibility: "%s"\n' "Requires spec-kit project structure with .specify/ directory"
|
||||||
|
printf -- 'metadata:\n'
|
||||||
|
printf ' author: "%s"\n' "github-spec-kit"
|
||||||
|
printf ' source: "%s"\n' "templates/commands/${name}.md"
|
||||||
printf -- '---\n\n'
|
printf -- '---\n\n'
|
||||||
printf '%s\n' "$template_body"
|
printf '%s\n' "$template_body"
|
||||||
} > "$skill_dir/SKILL.md"
|
} > "$skill_dir/SKILL.md"
|
||||||
@@ -218,7 +246,7 @@ build_variant() {
|
|||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
[[ -d templates ]] && { mkdir -p "$SPEC_DIR/templates"; find templates -type f -not -path "templates/commands/*" -not -name "vscode-settings.json" -exec cp --parents {} "$SPEC_DIR"/ \; ; echo "Copied templates -> .specify/templates"; }
|
[[ -d templates ]] && { mkdir -p "$SPEC_DIR/templates"; find templates -type f -not -path "templates/commands/*" -not -name "vscode-settings.json" | while IFS= read -r f; do d="$SPEC_DIR/$(dirname "$f")"; mkdir -p "$d"; cp "$f" "$d/"; done; echo "Copied templates -> .specify/templates"; }
|
||||||
|
|
||||||
case $agent in
|
case $agent in
|
||||||
claude)
|
claude)
|
||||||
@@ -248,9 +276,12 @@ build_variant() {
|
|||||||
windsurf)
|
windsurf)
|
||||||
mkdir -p "$base_dir/.windsurf/workflows"
|
mkdir -p "$base_dir/.windsurf/workflows"
|
||||||
generate_commands windsurf md "\$ARGUMENTS" "$base_dir/.windsurf/workflows" "$script" ;;
|
generate_commands windsurf md "\$ARGUMENTS" "$base_dir/.windsurf/workflows" "$script" ;;
|
||||||
|
junie)
|
||||||
|
mkdir -p "$base_dir/.junie/commands"
|
||||||
|
generate_commands junie md "\$ARGUMENTS" "$base_dir/.junie/commands" "$script" ;;
|
||||||
codex)
|
codex)
|
||||||
mkdir -p "$base_dir/.codex/prompts"
|
mkdir -p "$base_dir/.agents/skills"
|
||||||
generate_commands codex md "\$ARGUMENTS" "$base_dir/.codex/prompts" "$script" ;;
|
create_skills "$base_dir/.agents/skills" "$script" "codex" "-" ;;
|
||||||
kilocode)
|
kilocode)
|
||||||
mkdir -p "$base_dir/.kilocode/workflows"
|
mkdir -p "$base_dir/.kilocode/workflows"
|
||||||
generate_commands kilocode md "\$ARGUMENTS" "$base_dir/.kilocode/workflows" "$script" ;;
|
generate_commands kilocode md "\$ARGUMENTS" "$base_dir/.kilocode/workflows" "$script" ;;
|
||||||
@@ -290,7 +321,16 @@ build_variant() {
|
|||||||
generate_commands vibe md "\$ARGUMENTS" "$base_dir/.vibe/prompts" "$script" ;;
|
generate_commands vibe md "\$ARGUMENTS" "$base_dir/.vibe/prompts" "$script" ;;
|
||||||
kimi)
|
kimi)
|
||||||
mkdir -p "$base_dir/.kimi/skills"
|
mkdir -p "$base_dir/.kimi/skills"
|
||||||
create_kimi_skills "$base_dir/.kimi/skills" "$script" ;;
|
create_skills "$base_dir/.kimi/skills" "$script" "kimi" "." ;;
|
||||||
|
trae)
|
||||||
|
mkdir -p "$base_dir/.trae/rules"
|
||||||
|
generate_commands trae md "\$ARGUMENTS" "$base_dir/.trae/rules" "$script" ;;
|
||||||
|
pi)
|
||||||
|
mkdir -p "$base_dir/.pi/prompts"
|
||||||
|
generate_commands pi md "\$ARGUMENTS" "$base_dir/.pi/prompts" "$script" ;;
|
||||||
|
iflow)
|
||||||
|
mkdir -p "$base_dir/.iflow/commands"
|
||||||
|
generate_commands iflow md "\$ARGUMENTS" "$base_dir/.iflow/commands" "$script" ;;
|
||||||
generic)
|
generic)
|
||||||
mkdir -p "$base_dir/.speckit/commands"
|
mkdir -p "$base_dir/.speckit/commands"
|
||||||
generate_commands generic md "\$ARGUMENTS" "$base_dir/.speckit/commands" "$script" ;;
|
generate_commands generic md "\$ARGUMENTS" "$base_dir/.speckit/commands" "$script" ;;
|
||||||
@@ -300,37 +340,38 @@ build_variant() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Determine agent list
|
# Determine agent list
|
||||||
ALL_AGENTS=(claude gemini copilot cursor-agent qwen opencode windsurf codex kilocode auggie roo codebuddy amp shai tabnine kiro-cli agy bob vibe qodercli kimi generic)
|
ALL_AGENTS=(claude gemini copilot cursor-agent qwen opencode windsurf junie codex kilocode auggie roo codebuddy amp shai tabnine kiro-cli agy bob vibe qodercli kimi trae pi iflow generic)
|
||||||
ALL_SCRIPTS=(sh ps)
|
ALL_SCRIPTS=(sh ps)
|
||||||
|
|
||||||
norm_list() {
|
|
||||||
tr ',\n' ' ' | awk '{for(i=1;i<=NF;i++){if(!seen[$i]++){printf((out?"\n":"") $i);out=1}}}END{printf("\n")}'
|
|
||||||
}
|
|
||||||
|
|
||||||
validate_subset() {
|
validate_subset() {
|
||||||
local type=$1; shift; local -n allowed=$1; shift; local items=("$@")
|
local type=$1; shift
|
||||||
|
local allowed_str="$1"; shift
|
||||||
local invalid=0
|
local invalid=0
|
||||||
for it in "${items[@]}"; do
|
for it in "$@"; do
|
||||||
local found=0
|
local found=0
|
||||||
for a in "${allowed[@]}"; do [[ $it == "$a" ]] && { found=1; break; }; done
|
for a in $allowed_str; do
|
||||||
|
if [[ "$it" == "$a" ]]; then found=1; break; fi
|
||||||
|
done
|
||||||
if [[ $found -eq 0 ]]; then
|
if [[ $found -eq 0 ]]; then
|
||||||
echo "Error: unknown $type '$it' (allowed: ${allowed[*]})" >&2
|
echo "Error: unknown $type '$it' (allowed: $allowed_str)" >&2
|
||||||
invalid=1
|
invalid=1
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
return $invalid
|
return $invalid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
read_list() { tr ',\n' ' ' | awk '{for(i=1;i<=NF;i++){if(!seen[$i]++){printf((out?" ":"") $i);out=1}}}END{printf("\n")}'; }
|
||||||
|
|
||||||
if [[ -n ${AGENTS:-} ]]; then
|
if [[ -n ${AGENTS:-} ]]; then
|
||||||
mapfile -t AGENT_LIST < <(printf '%s' "$AGENTS" | norm_list)
|
read -ra AGENT_LIST <<< "$(printf '%s' "$AGENTS" | read_list)"
|
||||||
validate_subset agent ALL_AGENTS "${AGENT_LIST[@]}" || exit 1
|
validate_subset agent "${ALL_AGENTS[*]}" "${AGENT_LIST[@]}" || exit 1
|
||||||
else
|
else
|
||||||
AGENT_LIST=("${ALL_AGENTS[@]}")
|
AGENT_LIST=("${ALL_AGENTS[@]}")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -n ${SCRIPTS:-} ]]; then
|
if [[ -n ${SCRIPTS:-} ]]; then
|
||||||
mapfile -t SCRIPT_LIST < <(printf '%s' "$SCRIPTS" | norm_list)
|
read -ra SCRIPT_LIST <<< "$(printf '%s' "$SCRIPTS" | read_list)"
|
||||||
validate_subset script ALL_SCRIPTS "${SCRIPT_LIST[@]}" || exit 1
|
validate_subset script "${ALL_SCRIPTS[*]}" "${SCRIPT_LIST[@]}" || exit 1
|
||||||
else
|
else
|
||||||
SCRIPT_LIST=("${ALL_SCRIPTS[@]}")
|
SCRIPT_LIST=("${ALL_SCRIPTS[@]}")
|
||||||
fi
|
fi
|
||||||
|
|||||||
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@@ -39,4 +39,4 @@ jobs:
|
|||||||
any-of-labels: ''
|
any-of-labels: ''
|
||||||
|
|
||||||
# Operations per run (helps avoid rate limits)
|
# Operations per run (helps avoid rate limits)
|
||||||
operations-per-run: 100
|
operations-per-run: 250
|
||||||
|
|||||||
23
AGENTS.md
23
AGENTS.md
@@ -33,11 +33,12 @@ Specify supports multiple AI agents by generating agent-specific command files a
|
|||||||
| **Cursor** | `.cursor/commands/` | Markdown | `cursor-agent` | Cursor CLI |
|
| **Cursor** | `.cursor/commands/` | Markdown | `cursor-agent` | Cursor CLI |
|
||||||
| **Qwen Code** | `.qwen/commands/` | Markdown | `qwen` | Alibaba's Qwen Code CLI |
|
| **Qwen Code** | `.qwen/commands/` | Markdown | `qwen` | Alibaba's Qwen Code CLI |
|
||||||
| **opencode** | `.opencode/command/` | Markdown | `opencode` | opencode CLI |
|
| **opencode** | `.opencode/command/` | Markdown | `opencode` | opencode CLI |
|
||||||
| **Codex CLI** | `.codex/commands/` | Markdown | `codex` | Codex CLI |
|
| **Codex CLI** | `.agents/skills/` | Markdown | `codex` | Codex CLI (skills) |
|
||||||
| **Windsurf** | `.windsurf/workflows/` | Markdown | N/A (IDE-based) | Windsurf IDE workflows |
|
| **Windsurf** | `.windsurf/workflows/` | Markdown | N/A (IDE-based) | Windsurf IDE workflows |
|
||||||
| **Kilo Code** | `.kilocode/rules/` | Markdown | N/A (IDE-based) | Kilo Code IDE |
|
| **Junie** | `.junie/commands/` | Markdown | `junie` | Junie by JetBrains |
|
||||||
| **Auggie CLI** | `.augment/rules/` | Markdown | `auggie` | Auggie CLI |
|
| **Kilo Code** | `.kilocode/workflows/` | Markdown | N/A (IDE-based) | Kilo Code IDE |
|
||||||
| **Roo Code** | `.roo/rules/` | Markdown | N/A (IDE-based) | Roo Code IDE |
|
| **Auggie CLI** | `.augment/commands/` | Markdown | `auggie` | Auggie CLI |
|
||||||
|
| **Roo Code** | `.roo/commands/` | Markdown | N/A (IDE-based) | Roo Code IDE |
|
||||||
| **CodeBuddy CLI** | `.codebuddy/commands/` | Markdown | `codebuddy` | CodeBuddy CLI |
|
| **CodeBuddy CLI** | `.codebuddy/commands/` | Markdown | `codebuddy` | CodeBuddy CLI |
|
||||||
| **Qoder CLI** | `.qoder/commands/` | Markdown | `qodercli` | Qoder CLI |
|
| **Qoder CLI** | `.qoder/commands/` | Markdown | `qodercli` | Qoder CLI |
|
||||||
| **Kiro CLI** | `.kiro/prompts/` | Markdown | `kiro-cli` | Kiro CLI |
|
| **Kiro CLI** | `.kiro/prompts/` | Markdown | `kiro-cli` | Kiro CLI |
|
||||||
@@ -45,7 +46,10 @@ Specify supports multiple AI agents by generating agent-specific command files a
|
|||||||
| **SHAI** | `.shai/commands/` | Markdown | `shai` | SHAI CLI |
|
| **SHAI** | `.shai/commands/` | Markdown | `shai` | SHAI CLI |
|
||||||
| **Tabnine CLI** | `.tabnine/agent/commands/` | TOML | `tabnine` | Tabnine CLI |
|
| **Tabnine CLI** | `.tabnine/agent/commands/` | TOML | `tabnine` | Tabnine CLI |
|
||||||
| **Kimi Code** | `.kimi/skills/` | Markdown | `kimi` | Kimi Code CLI (Moonshot AI) |
|
| **Kimi Code** | `.kimi/skills/` | Markdown | `kimi` | Kimi Code CLI (Moonshot AI) |
|
||||||
|
| **Pi Coding Agent** | `.pi/prompts/` | Markdown | `pi` | Pi terminal coding agent |
|
||||||
|
| **iFlow CLI** | `.iflow/commands/` | Markdown | `iflow` | iFlow CLI (iflow-ai) |
|
||||||
| **IBM Bob** | `.bob/commands/` | Markdown | N/A (IDE-based) | IBM Bob IDE |
|
| **IBM Bob** | `.bob/commands/` | Markdown | N/A (IDE-based) | IBM Bob IDE |
|
||||||
|
| **Trae** | `.trae/rules/` | Markdown | N/A (IDE-based) | Trae IDE |
|
||||||
| **Generic** | User-specified via `--ai-commands-dir` | Markdown | N/A | Bring your own agent |
|
| **Generic** | User-specified via `--ai-commands-dir` | Markdown | N/A | Bring your own agent |
|
||||||
|
|
||||||
### Step-by-Step Integration Guide
|
### Step-by-Step Integration Guide
|
||||||
@@ -84,7 +88,7 @@ This eliminates the need for special-case mappings throughout the codebase.
|
|||||||
- `folder`: Directory where agent-specific files are stored (relative to project root)
|
- `folder`: Directory where agent-specific files are stored (relative to project root)
|
||||||
- `commands_subdir`: Subdirectory name within the agent folder where command/prompt files are stored (default: `"commands"`)
|
- `commands_subdir`: Subdirectory name within the agent folder where command/prompt files are stored (default: `"commands"`)
|
||||||
- Most agents use `"commands"` (e.g., `.claude/commands/`)
|
- Most agents use `"commands"` (e.g., `.claude/commands/`)
|
||||||
- Some agents use alternative names: `"agents"` (copilot), `"workflows"` (windsurf, kilocode), `"prompts"` (codex, kiro-cli), `"command"` (opencode - singular)
|
- Some agents use alternative names: `"agents"` (copilot), `"workflows"` (windsurf, kilocode), `"prompts"` (codex, kiro-cli, pi), `"command"` (opencode - singular)
|
||||||
- This field enables `--ai-skills` to locate command templates correctly for skill generation
|
- This field enables `--ai-skills` to locate command templates correctly for skill generation
|
||||||
- `install_url`: Installation documentation URL (set to `None` for IDE-based agents)
|
- `install_url`: Installation documentation URL (set to `None` for IDE-based agents)
|
||||||
- `requires_cli`: Whether the agent requires a CLI tool check during initialization
|
- `requires_cli`: Whether the agent requires a CLI tool check during initialization
|
||||||
@@ -315,6 +319,7 @@ Require a command-line tool to be installed:
|
|||||||
- **Cursor**: `cursor-agent` CLI
|
- **Cursor**: `cursor-agent` CLI
|
||||||
- **Qwen Code**: `qwen` CLI
|
- **Qwen Code**: `qwen` CLI
|
||||||
- **opencode**: `opencode` CLI
|
- **opencode**: `opencode` CLI
|
||||||
|
- **Junie**: `junie` CLI
|
||||||
- **Kiro CLI**: `kiro-cli` CLI
|
- **Kiro CLI**: `kiro-cli` CLI
|
||||||
- **CodeBuddy CLI**: `codebuddy` CLI
|
- **CodeBuddy CLI**: `codebuddy` CLI
|
||||||
- **Qoder CLI**: `qodercli` CLI
|
- **Qoder CLI**: `qodercli` CLI
|
||||||
@@ -322,6 +327,7 @@ Require a command-line tool to be installed:
|
|||||||
- **SHAI**: `shai` CLI
|
- **SHAI**: `shai` CLI
|
||||||
- **Tabnine CLI**: `tabnine` CLI
|
- **Tabnine CLI**: `tabnine` CLI
|
||||||
- **Kimi Code**: `kimi` CLI
|
- **Kimi Code**: `kimi` CLI
|
||||||
|
- **Pi Coding Agent**: `pi` CLI
|
||||||
|
|
||||||
### IDE-Based Agents
|
### IDE-Based Agents
|
||||||
|
|
||||||
@@ -335,7 +341,7 @@ Work within integrated development environments:
|
|||||||
|
|
||||||
### Markdown Format
|
### Markdown Format
|
||||||
|
|
||||||
Used by: Claude, Cursor, opencode, Windsurf, Kiro CLI, Amp, SHAI, IBM Bob, Kimi Code, Qwen
|
Used by: Claude, Cursor, opencode, Windsurf, Junie, Kiro CLI, Amp, SHAI, IBM Bob, Kimi Code, Qwen, Pi
|
||||||
|
|
||||||
**Standard format:**
|
**Standard format:**
|
||||||
|
|
||||||
@@ -373,6 +379,11 @@ Command content with {SCRIPT} and {{args}} placeholders.
|
|||||||
## Directory Conventions
|
## Directory Conventions
|
||||||
|
|
||||||
- **CLI agents**: Usually `.<agent-name>/commands/`
|
- **CLI agents**: Usually `.<agent-name>/commands/`
|
||||||
|
- **Skills-based exceptions**:
|
||||||
|
- Codex: `.agents/skills/` (skills, invoked as `$speckit-<command>`)
|
||||||
|
- **Prompt-based exceptions**:
|
||||||
|
- Kiro CLI: `.kiro/prompts/`
|
||||||
|
- Pi: `.pi/prompts/`
|
||||||
- **IDE agents**: Follow IDE-specific patterns:
|
- **IDE agents**: Follow IDE-specific patterns:
|
||||||
- Copilot: `.github/agents/`
|
- Copilot: `.github/agents/`
|
||||||
- Cursor: `.cursor/commands/`
|
- Cursor: `.cursor/commands/`
|
||||||
|
|||||||
118
CHANGELOG.md
118
CHANGELOG.md
@@ -1,56 +1,61 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
<!-- markdownlint-disable MD024 -->
|
## [0.3.2] - 2026-03-19
|
||||||
|
|
||||||
Recent changes to the Specify CLI and templates are documented here.
|
### Changes
|
||||||
|
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
- chore: bump version to 0.3.2
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
- Add conduct extension to community catalog (#1908)
|
||||||
|
- feat(extensions): add verify-tasks extension to community catalog (#1871)
|
||||||
|
- feat(presets): add enable/disable toggle and update semantics (#1891)
|
||||||
|
- feat: add iFlow CLI support (#1875)
|
||||||
|
- feat(commands): wire before/after hook events into specify and plan templates (#1886)
|
||||||
|
- docs(catalog): add speckit-utils to community catalog (#1896)
|
||||||
|
- docs: Add Extensions & Presets section to README (#1898)
|
||||||
|
- chore: update DocGuard extension to v0.9.11 (#1899)
|
||||||
|
- Update cognitive-squad catalog entry — Triadic Model, full lifecycle (#1884)
|
||||||
|
- feat: register spec-kit-iterate extension (#1887)
|
||||||
|
- fix(scripts): add explicit positional binding to PowerShell create-new-feature params (#1885)
|
||||||
|
- fix(scripts): encode residual JSON control chars as \uXXXX instead of stripping (#1872)
|
||||||
|
- chore: update DocGuard extension to v0.9.10 (#1890)
|
||||||
|
- Feature/spec kit add pi coding agent pullrequest (#1853)
|
||||||
|
- feat: register spec-kit-learn extension (#1883)
|
||||||
|
|
||||||
|
## [0.3.1] - 2026-03-17
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- chore: bump version to 0.3.1
|
||||||
|
- docs: add greenfield Spring Boot pirate-speak preset demo to README (#1878)
|
||||||
|
- fix(ai-skills): exclude non-speckit copilot agent markdown from skills (#1867)
|
||||||
|
- feat: add Trae IDE support as a new agent (#1817)
|
||||||
|
- feat(cli): polite deep merge for settings.json and support JSONC (#1874)
|
||||||
|
- feat(extensions,presets): add priority-based resolution ordering (#1855)
|
||||||
|
- fix(scripts): suppress stdout from git fetch in create-new-feature.sh (#1876)
|
||||||
|
- fix(scripts): harden bash scripts — escape, compat, and error handling (#1869)
|
||||||
|
- Add cognitive-squad to community extension catalog (#1870)
|
||||||
|
- docs: add Go / React brownfield walkthrough to community walkthroughs (#1868)
|
||||||
|
- chore: update DocGuard extension to v0.9.8 (#1859)
|
||||||
|
- Feature: add specify status command (#1837)
|
||||||
|
- fix(extensions): show extension ID in list output (#1843)
|
||||||
|
- feat(extensions): add Archive and Reconcile extensions to community catalog (#1844)
|
||||||
|
- feat: Add DocGuard CDD enforcement extension to community catalog (#1838)
|
||||||
|
|
||||||
## [0.3.0] - 2026-03-13
|
## [0.3.0] - 2026-03-13
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- No changes have been documented for this release yet.
|
- chore: bump version to 0.3.0
|
||||||
|
- feat(presets): Pluggable preset system with catalog, resolver, and skills propagation (#1787)
|
||||||
<!-- Entries for 0.2.x and earlier releases are documented in their respective sections below. -->
|
- fix: match 'Last updated' timestamp with or without bold markers (#1836)
|
||||||
- make c ignores consistent with c++ (#1747)
|
- Add specify doctor command for project health diagnostics (#1828)
|
||||||
- chore: bump version to 0.1.13 (#1746)
|
- fix: harden bash scripts against shell injection and improve robustness (#1809)
|
||||||
- feat: add kiro-cli and AGENT_CONFIG consistency coverage (#1690)
|
- fix: clean up command templates (specify, analyze) (#1810)
|
||||||
- feat: add verify extension to community catalog (#1726)
|
- fix: migrate Qwen Code CLI from TOML to Markdown format (#1589) (#1730)
|
||||||
- Add Retrospective Extension to community catalog README table (#1741)
|
- fix(cli): deprecate explicit command support for agy (#1798) (#1808)
|
||||||
- fix(scripts): add empty description validation and branch checkout error handling (#1559)
|
- Add /selftest.extension core extension to test other extensions (#1758)
|
||||||
- fix: correct Copilot extension command registration (#1724)
|
- feat(extensions): Quality of life improvements for RFC-aligned catalog integration (#1776)
|
||||||
- fix(implement): remove Makefile from C ignore patterns (#1558)
|
- Add Java brownfield walkthrough to community walkthroughs (#1820)
|
||||||
- Add sync extension to community catalog (#1728)
|
|
||||||
- fix(checklist): clarify file handling behavior for append vs create (#1556)
|
|
||||||
- fix(clarify): correct conflicting question limit from 10 to 5 (#1557)
|
|
||||||
- chore: bump version to 0.1.12 (#1737)
|
|
||||||
- fix: use RELEASE_PAT so tag push triggers release workflow (#1736)
|
|
||||||
- fix: release-trigger uses release branch + PR instead of direct push to main (#1733)
|
|
||||||
- fix: Split release process to sync pyproject.toml version with git tags (#1732)
|
|
||||||
|
|
||||||
|
|
||||||
## [Unreleased]
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- feat(presets): Pluggable preset system with preset catalog and template resolver
|
|
||||||
- Preset manifest (`preset.yml`) with validation for artifact, command, and script types
|
|
||||||
- `PresetManifest`, `PresetRegistry`, `PresetManager`, `PresetCatalog`, `PresetResolver` classes in `src/specify_cli/presets.py`
|
|
||||||
- CLI commands: `specify preset search`, `specify preset add`, `specify preset list`, `specify preset remove`, `specify preset resolve`, `specify preset info`
|
|
||||||
- CLI commands: `specify preset catalog list`, `specify preset catalog add`, `specify preset catalog remove` for multi-catalog management
|
|
||||||
- `PresetCatalogEntry` dataclass and multi-catalog support mirroring the extension catalog system
|
|
||||||
- `--preset` option for `specify init` to install presets during initialization
|
|
||||||
- Priority-based preset resolution: presets with lower priority number win (`--priority` flag)
|
|
||||||
- `resolve_template()` / `Resolve-Template` helpers in bash and PowerShell common scripts
|
|
||||||
- Template resolution priority stack: overrides → presets → extensions → core
|
|
||||||
- Preset catalog files (`presets/catalog.json`, `presets/catalog.community.json`)
|
|
||||||
- Preset scaffold directory (`presets/scaffold/`)
|
|
||||||
- Scripts updated to use template resolution instead of hardcoded paths
|
|
||||||
- feat(presets): Preset command overrides now propagate to agent skills when `--ai-skills` was used during init
|
|
||||||
- feat: `specify init` persists CLI options to `.specify/init-options.json` for downstream operations
|
|
||||||
- feat(extensions): support `.extensionignore` to exclude files/folders during `specify extension add` (#1781)
|
|
||||||
|
|
||||||
## [0.2.1] - 2026-03-11
|
## [0.2.1] - 2026-03-11
|
||||||
|
|
||||||
@@ -277,28 +282,3 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
- Add pytest and Python linting (ruff) to CI (#1637)
|
- Add pytest and Python linting (ruff) to CI (#1637)
|
||||||
- feat: add pull request template for better contribution guidelines (#1634)
|
- 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)
|
|
||||||
|
|||||||
123
README.md
123
README.md
@@ -25,6 +25,7 @@
|
|||||||
- [🚶 Community Walkthroughs](#-community-walkthroughs)
|
- [🚶 Community Walkthroughs](#-community-walkthroughs)
|
||||||
- [🤖 Supported AI Agents](#-supported-ai-agents)
|
- [🤖 Supported AI Agents](#-supported-ai-agents)
|
||||||
- [🔧 Specify CLI Reference](#-specify-cli-reference)
|
- [🔧 Specify CLI Reference](#-specify-cli-reference)
|
||||||
|
- [🧩 Making Spec Kit Your Own: Extensions & Presets](#-making-spec-kit-your-own-extensions--presets)
|
||||||
- [📚 Core Philosophy](#-core-philosophy)
|
- [📚 Core Philosophy](#-core-philosophy)
|
||||||
- [🌟 Development Phases](#-development-phases)
|
- [🌟 Development Phases](#-development-phases)
|
||||||
- [🎯 Experimental Goals](#-experimental-goals)
|
- [🎯 Experimental Goals](#-experimental-goals)
|
||||||
@@ -48,9 +49,13 @@ Choose your preferred installation method:
|
|||||||
|
|
||||||
#### Option 1: Persistent Installation (Recommended)
|
#### Option 1: Persistent Installation (Recommended)
|
||||||
|
|
||||||
Install once and use everywhere:
|
Install once and use everywhere. Pin a specific release tag for stability (check [Releases](https://github.com/github/spec-kit/releases) for the latest):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# Install a specific stable release (recommended — replace vX.Y.Z with the latest tag)
|
||||||
|
uv tool install specify-cli --from git+https://github.com/github/spec-kit.git@vX.Y.Z
|
||||||
|
|
||||||
|
# Or install latest from main (may include unreleased changes)
|
||||||
uv tool install specify-cli --from git+https://github.com/github/spec-kit.git
|
uv tool install specify-cli --from git+https://github.com/github/spec-kit.git
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -72,7 +77,7 @@ specify check
|
|||||||
To upgrade Specify, see the [Upgrade Guide](./docs/upgrade.md) for detailed instructions. Quick upgrade:
|
To upgrade Specify, see the [Upgrade Guide](./docs/upgrade.md) for detailed instructions. Quick upgrade:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uv tool install specify-cli --force --from git+https://github.com/github/spec-kit.git
|
uv tool install specify-cli --force --from git+https://github.com/github/spec-kit.git@vX.Y.Z
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Option 2: One-time Usage
|
#### Option 2: One-time Usage
|
||||||
@@ -80,13 +85,13 @@ uv tool install specify-cli --force --from git+https://github.com/github/spec-ki
|
|||||||
Run directly without installing:
|
Run directly without installing:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Create new project
|
# Create new project (pinned to a stable release — replace vX.Y.Z with the latest tag)
|
||||||
uvx --from git+https://github.com/github/spec-kit.git specify init <PROJECT_NAME>
|
uvx --from git+https://github.com/github/spec-kit.git@vX.Y.Z specify init <PROJECT_NAME>
|
||||||
|
|
||||||
# Or initialize in existing project
|
# Or initialize in existing project
|
||||||
uvx --from git+https://github.com/github/spec-kit.git specify init . --ai claude
|
uvx --from git+https://github.com/github/spec-kit.git@vX.Y.Z specify init . --ai claude
|
||||||
# or
|
# or
|
||||||
uvx --from git+https://github.com/github/spec-kit.git specify init --here --ai claude
|
uvx --from git+https://github.com/github/spec-kit.git@vX.Y.Z specify init --here --ai claude
|
||||||
```
|
```
|
||||||
|
|
||||||
**Benefits of persistent installation:**
|
**Benefits of persistent installation:**
|
||||||
@@ -96,9 +101,13 @@ uvx --from git+https://github.com/github/spec-kit.git specify init --here --ai c
|
|||||||
- Better tool management with `uv tool list`, `uv tool upgrade`, `uv tool uninstall`
|
- Better tool management with `uv tool list`, `uv tool upgrade`, `uv tool uninstall`
|
||||||
- Cleaner shell configuration
|
- Cleaner shell configuration
|
||||||
|
|
||||||
|
#### Option 3: Enterprise / Air-Gapped Installation
|
||||||
|
|
||||||
|
If your environment blocks access to PyPI or GitHub, see the [Enterprise / Air-Gapped Installation](./docs/installation.md#enterprise--air-gapped-installation) guide for step-by-step instructions on using `pip download` to create portable, OS-specific wheel bundles on a connected machine.
|
||||||
|
|
||||||
### 2. Establish project principles
|
### 2. Establish project principles
|
||||||
|
|
||||||
Launch your AI assistant in the project directory. The `/speckit.*` commands are available in the assistant.
|
Launch your AI assistant in the project directory. Most agents expose spec-kit as `/speckit.*` slash commands; Codex CLI in skills mode uses `$speckit-*` instead.
|
||||||
|
|
||||||
Use the **`/speckit.constitution`** command to create your project's governing principles and development guidelines that will guide all subsequent development.
|
Use the **`/speckit.constitution`** command to create your project's governing principles and development guidelines that will guide all subsequent development.
|
||||||
|
|
||||||
@@ -158,6 +167,10 @@ See Spec-Driven Development in action across different scenarios with these comm
|
|||||||
|
|
||||||
- **[Brownfield Java runtime extension](https://github.com/mnriem/spec-kit-java-brownfield-demo)** — Extends an existing open-source Jakarta EE runtime (Piranha, ~420,000 lines of Java, XML, JSP, HTML, and config files across 180 Maven modules) with a password-protected Server Admin Console, demonstrating spec-kit on a large multi-module Java project with no prior specs or constitution.
|
- **[Brownfield Java runtime extension](https://github.com/mnriem/spec-kit-java-brownfield-demo)** — Extends an existing open-source Jakarta EE runtime (Piranha, ~420,000 lines of Java, XML, JSP, HTML, and config files across 180 Maven modules) with a password-protected Server Admin Console, demonstrating spec-kit on a large multi-module Java project with no prior specs or constitution.
|
||||||
|
|
||||||
|
- **[Brownfield Go / React dashboard demo](https://github.com/mnriem/spec-kit-go-brownfield-demo)** — Demonstrates spec-kit driven entirely from the **terminal using GitHub Copilot CLI**. Extends NASA's open-source Hermes ground support system (Go) with a lightweight React-based web telemetry dashboard, showing that the full constitution → specify → plan → tasks → implement workflow works from the terminal.
|
||||||
|
|
||||||
|
- **[Greenfield Spring Boot MVC with a custom preset](https://github.com/mnriem/spec-kit-pirate-speak-preset-demo)** — Builds a Spring Boot MVC application from scratch using a custom pirate-speak preset, demonstrating how presets can reshape the entire spec-kit experience: specifications become "Voyage Manifests," plans become "Battle Plans," and tasks become "Crew Assignments" — all generated in full pirate vernacular without changing any tooling.
|
||||||
|
|
||||||
## 🤖 Supported AI Agents
|
## 🤖 Supported AI Agents
|
||||||
|
|
||||||
| Agent | Support | Notes |
|
| Agent | Support | Notes |
|
||||||
@@ -168,7 +181,7 @@ See Spec-Driven Development in action across different scenarios with these comm
|
|||||||
| [Auggie CLI](https://docs.augmentcode.com/cli/overview) | ✅ | |
|
| [Auggie CLI](https://docs.augmentcode.com/cli/overview) | ✅ | |
|
||||||
| [Claude Code](https://www.anthropic.com/claude-code) | ✅ | |
|
| [Claude Code](https://www.anthropic.com/claude-code) | ✅ | |
|
||||||
| [CodeBuddy CLI](https://www.codebuddy.ai/cli) | ✅ | |
|
| [CodeBuddy CLI](https://www.codebuddy.ai/cli) | ✅ | |
|
||||||
| [Codex CLI](https://github.com/openai/codex) | ✅ | |
|
| [Codex CLI](https://github.com/openai/codex) | ✅ | Requires `--ai-skills`. Codex recommends [skills](https://developers.openai.com/codex/skills) and treats [custom prompts](https://developers.openai.com/codex/custom-prompts) as deprecated. Spec-kit installs Codex skills into `.agents/skills` and invokes them as `$speckit-<command>`. |
|
||||||
| [Cursor](https://cursor.sh/) | ✅ | |
|
| [Cursor](https://cursor.sh/) | ✅ | |
|
||||||
| [Gemini CLI](https://github.com/google-gemini/gemini-cli) | ✅ | |
|
| [Gemini CLI](https://github.com/google-gemini/gemini-cli) | ✅ | |
|
||||||
| [GitHub Copilot](https://code.visualstudio.com/) | ✅ | |
|
| [GitHub Copilot](https://code.visualstudio.com/) | ✅ | |
|
||||||
@@ -176,14 +189,18 @@ See Spec-Driven Development in action across different scenarios with these comm
|
|||||||
| [Jules](https://jules.google.com/) | ✅ | |
|
| [Jules](https://jules.google.com/) | ✅ | |
|
||||||
| [Kilo Code](https://github.com/Kilo-Org/kilocode) | ✅ | |
|
| [Kilo Code](https://github.com/Kilo-Org/kilocode) | ✅ | |
|
||||||
| [opencode](https://opencode.ai/) | ✅ | |
|
| [opencode](https://opencode.ai/) | ✅ | |
|
||||||
|
| [Pi Coding Agent](https://pi.dev) | ✅ | Pi doesn't have MCP support out of the box, so `taskstoissues` won't work as intended. MCP support can be added via [extensions](https://github.com/badlogic/pi-mono/tree/main/packages/coding-agent#extensions) |
|
||||||
| [Qwen Code](https://github.com/QwenLM/qwen-code) | ✅ | |
|
| [Qwen Code](https://github.com/QwenLM/qwen-code) | ✅ | |
|
||||||
| [Roo Code](https://roocode.com/) | ✅ | |
|
| [Roo Code](https://roocode.com/) | ✅ | |
|
||||||
| [SHAI (OVHcloud)](https://github.com/ovh/shai) | ✅ | |
|
| [SHAI (OVHcloud)](https://github.com/ovh/shai) | ✅ | |
|
||||||
| [Tabnine CLI](https://docs.tabnine.com/main/getting-started/tabnine-cli) | ✅ | |
|
| [Tabnine CLI](https://docs.tabnine.com/main/getting-started/tabnine-cli) | ✅ | |
|
||||||
| [Mistral Vibe](https://github.com/mistralai/mistral-vibe) | ✅ | |
|
| [Mistral Vibe](https://github.com/mistralai/mistral-vibe) | ✅ | |
|
||||||
| [Kimi Code](https://code.kimi.com/) | ✅ | |
|
| [Kimi Code](https://code.kimi.com/) | ✅ | |
|
||||||
|
| [iFlow CLI](https://docs.iflow.cn/en/cli/quickstart) | ✅ | |
|
||||||
| [Windsurf](https://windsurf.com/) | ✅ | |
|
| [Windsurf](https://windsurf.com/) | ✅ | |
|
||||||
|
| [Junie](https://junie.jetbrains.com/) | ✅ | |
|
||||||
| [Antigravity (agy)](https://antigravity.google/) | ✅ | Requires `--ai-skills` |
|
| [Antigravity (agy)](https://antigravity.google/) | ✅ | Requires `--ai-skills` |
|
||||||
|
| [Trae](https://www.trae.ai/) | ✅ | |
|
||||||
| Generic | ✅ | Bring your own agent — use `--ai generic --ai-commands-dir <path>` for unsupported agents |
|
| Generic | ✅ | Bring your own agent — use `--ai generic --ai-commands-dir <path>` for unsupported agents |
|
||||||
|
|
||||||
## 🔧 Specify CLI Reference
|
## 🔧 Specify CLI Reference
|
||||||
@@ -193,16 +210,16 @@ The `specify` command supports the following options:
|
|||||||
### Commands
|
### Commands
|
||||||
|
|
||||||
| Command | Description |
|
| Command | Description |
|
||||||
| ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| ------- |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `init` | Initialize a new Specify project from the latest template |
|
| `init` | Initialize a new Specify project from the latest template |
|
||||||
| `check` | Check for installed tools (`git`, `claude`, `gemini`, `code`/`code-insiders`, `cursor-agent`, `windsurf`, `qwen`, `opencode`, `codex`, `kiro-cli`, `shai`, `qodercli`, `vibe`, `kimi`) |
|
| `check` | Check for installed tools: `git` plus all CLI-based agents configured in `AGENT_CONFIG` (for example: `claude`, `gemini`, `code`/`code-insiders`, `cursor-agent`, `windsurf`, `junie`, `qwen`, `opencode`, `codex`, `kiro-cli`, `shai`, `qodercli`, `vibe`, `kimi`, `iflow`, `pi`, etc.) |
|
||||||
|
|
||||||
### `specify init` Arguments & Options
|
### `specify init` Arguments & Options
|
||||||
|
|
||||||
| Argument/Option | Type | Description |
|
| Argument/Option | Type | Description |
|
||||||
| ---------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| ---------------------- | -------- |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `<project-name>` | Argument | Name for your new project directory (optional if using `--here`, or use `.` for current directory) |
|
| `<project-name>` | Argument | Name for your new project directory (optional if using `--here`, or use `.` for current directory) |
|
||||||
| `--ai` | Option | AI assistant to use: `claude`, `gemini`, `copilot`, `cursor-agent`, `qwen`, `opencode`, `codex`, `windsurf`, `kilocode`, `auggie`, `roo`, `codebuddy`, `amp`, `shai`, `kiro-cli` (`kiro` alias), `agy`, `bob`, `qodercli`, `vibe`, `kimi`, or `generic` (requires `--ai-commands-dir`) |
|
| `--ai` | Option | AI assistant to use (see `AGENT_CONFIG` for the full, up-to-date list). Common options include: `claude`, `gemini`, `copilot`, `cursor-agent`, `qwen`, `opencode`, `codex`, `windsurf`, `junie`, `kilocode`, `auggie`, `roo`, `codebuddy`, `amp`, `shai`, `kiro-cli` (`kiro` alias), `agy`, `bob`, `qodercli`, `vibe`, `kimi`, `iflow`, `pi`, or `generic` (requires `--ai-commands-dir`) |
|
||||||
| `--ai-commands-dir` | Option | Directory for agent command files (required with `--ai generic`, e.g. `.myagent/commands/`) |
|
| `--ai-commands-dir` | Option | Directory for agent command files (required with `--ai generic`, e.g. `.myagent/commands/`) |
|
||||||
| `--script` | Option | Script variant to use: `sh` (bash/zsh) or `ps` (PowerShell) |
|
| `--script` | Option | Script variant to use: `sh` (bash/zsh) or `ps` (PowerShell) |
|
||||||
| `--ignore-agent-tools` | Flag | Skip checks for AI agent tools like Claude Code |
|
| `--ignore-agent-tools` | Flag | Skip checks for AI agent tools like Claude Code |
|
||||||
@@ -213,6 +230,7 @@ The `specify` command supports the following options:
|
|||||||
| `--debug` | Flag | Enable detailed debug output for troubleshooting |
|
| `--debug` | Flag | Enable detailed debug output for troubleshooting |
|
||||||
| `--github-token` | Option | GitHub token for API requests (or set GH_TOKEN/GITHUB_TOKEN env variable) |
|
| `--github-token` | Option | GitHub token for API requests (or set GH_TOKEN/GITHUB_TOKEN env variable) |
|
||||||
| `--ai-skills` | Flag | Install Prompt.MD templates as agent skills in agent-specific `skills/` directory (requires `--ai`) |
|
| `--ai-skills` | Flag | Install Prompt.MD templates as agent skills in agent-specific `skills/` directory (requires `--ai`) |
|
||||||
|
| `--branch-numbering` | Option | Branch numbering strategy: `sequential` (default — `001`, `002`, `003`) or `timestamp` (`YYYYMMDD-HHMMSS`). Timestamp mode is useful for distributed teams to avoid numbering conflicts |
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
@@ -247,6 +265,12 @@ specify init my-project --ai vibe
|
|||||||
# Initialize with IBM Bob support
|
# Initialize with IBM Bob support
|
||||||
specify init my-project --ai bob
|
specify init my-project --ai bob
|
||||||
|
|
||||||
|
# Initialize with Pi Coding Agent support
|
||||||
|
specify init my-project --ai pi
|
||||||
|
|
||||||
|
# Initialize with Codex CLI support
|
||||||
|
specify init my-project --ai codex --ai-skills
|
||||||
|
|
||||||
# Initialize with Antigravity support
|
# Initialize with Antigravity support
|
||||||
specify init my-project --ai agy --ai-skills
|
specify init my-project --ai agy --ai-skills
|
||||||
|
|
||||||
@@ -281,13 +305,18 @@ specify init my-project --ai claude --ai-skills
|
|||||||
# Initialize in current directory with agent skills
|
# Initialize in current directory with agent skills
|
||||||
specify init --here --ai gemini --ai-skills
|
specify init --here --ai gemini --ai-skills
|
||||||
|
|
||||||
|
# Use timestamp-based branch numbering (useful for distributed teams)
|
||||||
|
specify init my-project --ai claude --branch-numbering timestamp
|
||||||
|
|
||||||
# Check system requirements
|
# Check system requirements
|
||||||
specify check
|
specify check
|
||||||
```
|
```
|
||||||
|
|
||||||
### Available Slash Commands
|
### Available Slash Commands
|
||||||
|
|
||||||
After running `specify init`, your AI coding agent will have access to these slash commands for structured development:
|
After running `specify init`, your AI coding agent will have access to these slash commands for structured development.
|
||||||
|
|
||||||
|
For Codex CLI, `--ai-skills` installs spec-kit as agent skills instead of slash-command prompt files. In Codex skills mode, invoke spec-kit as `$speckit-constitution`, `$speckit-specify`, `$speckit-plan`, `$speckit-tasks`, and `$speckit-implement`.
|
||||||
|
|
||||||
#### Core Commands
|
#### Core Commands
|
||||||
|
|
||||||
@@ -317,6 +346,68 @@ Additional commands for enhanced quality and validation:
|
|||||||
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||||
| `SPECIFY_FEATURE` | Override feature detection for non-Git repositories. Set to the feature directory name (e.g., `001-photo-albums`) to work on a specific feature when not using Git branches.<br/>\*\*Must be set in the context of the agent you're working with prior to using `/speckit.plan` or follow-up commands. |
|
| `SPECIFY_FEATURE` | Override feature detection for non-Git repositories. Set to the feature directory name (e.g., `001-photo-albums`) to work on a specific feature when not using Git branches.<br/>\*\*Must be set in the context of the agent you're working with prior to using `/speckit.plan` or follow-up commands. |
|
||||||
|
|
||||||
|
## 🧩 Making Spec Kit Your Own: Extensions & Presets
|
||||||
|
|
||||||
|
Spec Kit can be tailored to your needs through two complementary systems — **extensions** and **presets** — plus project-local overrides for one-off adjustments:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
block-beta
|
||||||
|
columns 1
|
||||||
|
overrides["⬆ Highest priority\nProject-Local Overrides\n.specify/templates/overrides/"]
|
||||||
|
presets["Presets — Customize core & extensions\n.specify/presets/<preset-id>/templates/"]
|
||||||
|
extensions["Extensions — Add new capabilities\n.specify/extensions/<ext-id>/templates/"]
|
||||||
|
core["Spec Kit Core — Built-in SDD commands & templates\n.specify/templates/\n⬇ Lowest priority"]
|
||||||
|
|
||||||
|
style overrides fill:transparent,stroke:#999
|
||||||
|
style presets fill:transparent,stroke:#4a9eda
|
||||||
|
style extensions fill:transparent,stroke:#4a9e4a
|
||||||
|
style core fill:transparent,stroke:#e6a817
|
||||||
|
```
|
||||||
|
|
||||||
|
**Templates** are resolved at **runtime** — Spec Kit walks the stack top-down and uses the first match. Project-local overrides (`.specify/templates/overrides/`) let you make one-off adjustments for a single project without creating a full preset. **Commands** are applied at **install time** — when you run `specify extension add` or `specify preset add`, command files are written into agent directories (e.g., `.claude/commands/`). If multiple presets or extensions provide the same command, the highest-priority version wins. On removal, the next-highest-priority version is restored automatically. If no overrides or customizations exist, Spec Kit uses its core defaults.
|
||||||
|
|
||||||
|
### Extensions — Add New Capabilities
|
||||||
|
|
||||||
|
Use **extensions** when you need functionality that goes beyond Spec Kit's core. Extensions introduce new commands and templates — for example, adding domain-specific workflows that are not covered by the built-in SDD commands, integrating with external tools, or adding entirely new development phases. They expand *what Spec Kit can do*.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Search available extensions
|
||||||
|
specify extension search
|
||||||
|
|
||||||
|
# Install an extension
|
||||||
|
specify extension add <extension-name>
|
||||||
|
```
|
||||||
|
|
||||||
|
For example, extensions could add Jira integration, post-implementation code review, V-Model test traceability, or project health diagnostics.
|
||||||
|
|
||||||
|
See the [Extensions README](./extensions/README.md) for the full guide, the complete community catalog, and how to build and publish your own.
|
||||||
|
|
||||||
|
### Presets — Customize Existing Workflows
|
||||||
|
|
||||||
|
Use **presets** when you want to change *how* Spec Kit works without adding new capabilities. Presets override the templates and commands that ship with the core *and* with installed extensions — for example, enforcing a compliance-oriented spec format, using domain-specific terminology, or applying organizational standards to plans and tasks. They customize the artifacts and instructions that Spec Kit and its extensions produce.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Search available presets
|
||||||
|
specify preset search
|
||||||
|
|
||||||
|
# Install a preset
|
||||||
|
specify preset add <preset-name>
|
||||||
|
```
|
||||||
|
|
||||||
|
For example, presets could restructure spec templates to require regulatory traceability, adapt the workflow to fit the methodology you use (e.g., Agile, Kanban, Waterfall, jobs-to-be-done, or domain-driven design), add mandatory security review gates to plans, enforce test-first task ordering, or localize the entire workflow to a different language. The [pirate-speak demo](https://github.com/mnriem/spec-kit-pirate-speak-preset-demo) shows just how deep the customization can go. Multiple presets can be stacked with priority ordering.
|
||||||
|
|
||||||
|
See the [Presets README](./presets/README.md) for the full guide, including resolution order, priority, and how to create your own.
|
||||||
|
|
||||||
|
### When to Use Which
|
||||||
|
|
||||||
|
| Goal | Use |
|
||||||
|
| --- | --- |
|
||||||
|
| Add a brand-new command or workflow | Extension |
|
||||||
|
| Customize the format of specs, plans, or tasks | Preset |
|
||||||
|
| Integrate an external tool or service | Extension |
|
||||||
|
| Enforce organizational or regulatory standards | Preset |
|
||||||
|
| Ship reusable domain-specific templates | Either — presets for template overrides, extensions for templates bundled with new commands |
|
||||||
|
|
||||||
## 📚 Core Philosophy
|
## 📚 Core Philosophy
|
||||||
|
|
||||||
Spec-Driven Development is a structured process that emphasizes:
|
Spec-Driven Development is a structured process that emphasizes:
|
||||||
@@ -411,11 +502,11 @@ specify init <project_name> --ai copilot
|
|||||||
|
|
||||||
# Or in current directory:
|
# Or in current directory:
|
||||||
specify init . --ai claude
|
specify init . --ai claude
|
||||||
specify init . --ai codex
|
specify init . --ai codex --ai-skills
|
||||||
|
|
||||||
# or use --here flag
|
# or use --here flag
|
||||||
specify init --here --ai claude
|
specify init --here --ai claude
|
||||||
specify init --here --ai codex
|
specify init --here --ai codex --ai-skills
|
||||||
|
|
||||||
# Force merge into a non-empty current directory
|
# Force merge into a non-empty current directory
|
||||||
specify init . --force --ai claude
|
specify init . --force --ai claude
|
||||||
@@ -424,7 +515,7 @@ specify init . --force --ai claude
|
|||||||
specify init --here --force --ai claude
|
specify init --here --force --ai claude
|
||||||
```
|
```
|
||||||
|
|
||||||
The CLI will check if you have Claude Code, Gemini CLI, Cursor CLI, Qwen CLI, opencode, Codex CLI, Qoder CLI, Tabnine CLI, Kiro CLI, or Mistral Vibe installed. If you do not, or you prefer to get the templates without checking for the right tools, use `--ignore-agent-tools` with your command:
|
The CLI will check if you have Claude Code, Gemini CLI, Cursor CLI, Qwen CLI, opencode, Codex CLI, Qoder CLI, Tabnine CLI, Kiro CLI, Pi, or Mistral Vibe installed. If you do not, or you prefer to get the templates without checking for the right tools, use `--ignore-agent-tools` with your command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
specify init <project_name> --ai claude --ignore-agent-tools
|
specify init <project_name> --ai claude --ignore-agent-tools
|
||||||
|
|||||||
13
SUPPORT.md
13
SUPPORT.md
@@ -1,18 +1,17 @@
|
|||||||
# Support
|
# Support
|
||||||
|
|
||||||
## How to file issues and get help
|
## How to get help
|
||||||
|
|
||||||
This project uses GitHub issues to track bugs and feature requests. Please search the existing issues before filing new issues to avoid duplicates. For new issues, file your bug or feature request as a new issue.
|
Please search existing [issues](https://github.com/github/spec-kit/issues) and [discussions](https://github.com/github/spec-kit/discussions) before creating new ones to avoid duplicates.
|
||||||
|
|
||||||
For help or questions about using this project, please:
|
|
||||||
|
|
||||||
- Open a [GitHub issue](https://github.com/github/spec-kit/issues/new) for bug reports, feature requests, or questions about the Spec-Driven Development methodology
|
|
||||||
- Check the [comprehensive guide](./spec-driven.md) for detailed documentation on the Spec-Driven Development process
|
|
||||||
- Review the [README](./README.md) for getting started instructions and troubleshooting tips
|
- Review the [README](./README.md) for getting started instructions and troubleshooting tips
|
||||||
|
- Check the [comprehensive guide](./spec-driven.md) for detailed documentation on the Spec-Driven Development process
|
||||||
|
- Ask in [GitHub Discussions](https://github.com/github/spec-kit/discussions) for questions about using Spec Kit or the Spec-Driven Development methodology
|
||||||
|
- Open a [GitHub issue](https://github.com/github/spec-kit/issues/new) for bug reports and feature requests
|
||||||
|
|
||||||
## Project Status
|
## Project Status
|
||||||
|
|
||||||
**Spec Kit** is under active development and maintained by GitHub staff **AND THE COMMUNITY**. We will do our best to respond to support, feature requests, and community questions in a timely manner.
|
**Spec Kit** is under active development and maintained by GitHub staff and the community. We will do our best to respond to support, feature requests, and community questions as time permits.
|
||||||
|
|
||||||
## GitHub Support Policy
|
## GitHub Support Policy
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- **Linux/macOS** (or Windows; PowerShell scripts now supported without WSL)
|
- **Linux/macOS** (or Windows; PowerShell scripts now supported without WSL)
|
||||||
- AI coding agent: [Claude Code](https://www.anthropic.com/claude-code), [GitHub Copilot](https://code.visualstudio.com/), [Codebuddy CLI](https://www.codebuddy.ai/cli) or [Gemini CLI](https://github.com/google-gemini/gemini-cli)
|
- AI coding agent: [Claude Code](https://www.anthropic.com/claude-code), [GitHub Copilot](https://code.visualstudio.com/), [Codebuddy CLI](https://www.codebuddy.ai/cli), [Gemini CLI](https://github.com/google-gemini/gemini-cli), or [Pi Coding Agent](https://pi.dev)
|
||||||
- [uv](https://docs.astral.sh/uv/) for package management
|
- [uv](https://docs.astral.sh/uv/) for package management
|
||||||
- [Python 3.11+](https://www.python.org/downloads/)
|
- [Python 3.11+](https://www.python.org/downloads/)
|
||||||
- [Git](https://git-scm.com/downloads)
|
- [Git](https://git-scm.com/downloads)
|
||||||
@@ -12,18 +12,22 @@
|
|||||||
|
|
||||||
### Initialize a New Project
|
### Initialize a New Project
|
||||||
|
|
||||||
The easiest way to get started is to initialize a new project:
|
The easiest way to get started is to initialize a new project. Pin a specific release tag for stability (check [Releases](https://github.com/github/spec-kit/releases) for the latest):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# Install from a specific stable release (recommended — replace vX.Y.Z with the latest tag)
|
||||||
|
uvx --from git+https://github.com/github/spec-kit.git@vX.Y.Z specify init <PROJECT_NAME>
|
||||||
|
|
||||||
|
# Or install latest from main (may include unreleased changes)
|
||||||
uvx --from git+https://github.com/github/spec-kit.git specify init <PROJECT_NAME>
|
uvx --from git+https://github.com/github/spec-kit.git specify init <PROJECT_NAME>
|
||||||
```
|
```
|
||||||
|
|
||||||
Or initialize in the current directory:
|
Or initialize in the current directory:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uvx --from git+https://github.com/github/spec-kit.git specify init .
|
uvx --from git+https://github.com/github/spec-kit.git@vX.Y.Z specify init .
|
||||||
# or use the --here flag
|
# or use the --here flag
|
||||||
uvx --from git+https://github.com/github/spec-kit.git specify init --here
|
uvx --from git+https://github.com/github/spec-kit.git@vX.Y.Z specify init --here
|
||||||
```
|
```
|
||||||
|
|
||||||
### Specify AI Agent
|
### Specify AI Agent
|
||||||
@@ -31,10 +35,11 @@ uvx --from git+https://github.com/github/spec-kit.git specify init --here
|
|||||||
You can proactively specify your AI agent during initialization:
|
You can proactively specify your AI agent during initialization:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uvx --from git+https://github.com/github/spec-kit.git specify init <project_name> --ai claude
|
uvx --from git+https://github.com/github/spec-kit.git@vX.Y.Z specify init <project_name> --ai claude
|
||||||
uvx --from git+https://github.com/github/spec-kit.git specify init <project_name> --ai gemini
|
uvx --from git+https://github.com/github/spec-kit.git@vX.Y.Z specify init <project_name> --ai gemini
|
||||||
uvx --from git+https://github.com/github/spec-kit.git specify init <project_name> --ai copilot
|
uvx --from git+https://github.com/github/spec-kit.git@vX.Y.Z specify init <project_name> --ai copilot
|
||||||
uvx --from git+https://github.com/github/spec-kit.git specify init <project_name> --ai codebuddy
|
uvx --from git+https://github.com/github/spec-kit.git@vX.Y.Z specify init <project_name> --ai codebuddy
|
||||||
|
uvx --from git+https://github.com/github/spec-kit.git@vX.Y.Z specify init <project_name> --ai pi
|
||||||
```
|
```
|
||||||
|
|
||||||
### Specify Script Type (Shell vs PowerShell)
|
### Specify Script Type (Shell vs PowerShell)
|
||||||
@@ -50,8 +55,8 @@ Auto behavior:
|
|||||||
Force a specific script type:
|
Force a specific script type:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uvx --from git+https://github.com/github/spec-kit.git specify init <project_name> --script sh
|
uvx --from git+https://github.com/github/spec-kit.git@vX.Y.Z specify init <project_name> --script sh
|
||||||
uvx --from git+https://github.com/github/spec-kit.git specify init <project_name> --script ps
|
uvx --from git+https://github.com/github/spec-kit.git@vX.Y.Z specify init <project_name> --script ps
|
||||||
```
|
```
|
||||||
|
|
||||||
### Ignore Agent Tools Check
|
### Ignore Agent Tools Check
|
||||||
@@ -59,7 +64,7 @@ uvx --from git+https://github.com/github/spec-kit.git specify init <project_name
|
|||||||
If you prefer to get the templates without checking for the right tools:
|
If you prefer to get the templates without checking for the right tools:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uvx --from git+https://github.com/github/spec-kit.git specify init <project_name> --ai claude --ignore-agent-tools
|
uvx --from git+https://github.com/github/spec-kit.git@vX.Y.Z specify init <project_name> --ai claude --ignore-agent-tools
|
||||||
```
|
```
|
||||||
|
|
||||||
## Verification
|
## Verification
|
||||||
@@ -74,6 +79,52 @@ The `.specify/scripts` directory will contain both `.sh` and `.ps1` scripts.
|
|||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Enterprise / Air-Gapped Installation
|
||||||
|
|
||||||
|
If your environment blocks access to PyPI (you see 403 errors when running `uv tool install` or `pip install`), you can create a portable wheel bundle on a connected machine and transfer it to the air-gapped target.
|
||||||
|
|
||||||
|
**Step 1: Build the wheel on a connected machine (same OS and Python version as the target)**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clone the repository
|
||||||
|
git clone https://github.com/github/spec-kit.git
|
||||||
|
cd spec-kit
|
||||||
|
|
||||||
|
# Build the wheel
|
||||||
|
pip install build
|
||||||
|
python -m build --wheel --outdir dist/
|
||||||
|
|
||||||
|
# Download the wheel and all its runtime dependencies
|
||||||
|
pip download -d dist/ dist/specify_cli-*.whl
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Important:** `pip download` resolves platform-specific wheels (e.g., PyYAML includes native extensions). You must run this step on a machine with the **same OS and Python version** as the air-gapped target. If you need to support multiple platforms, repeat this step on each target OS (Linux, macOS, Windows) and Python version.
|
||||||
|
|
||||||
|
**Step 2: Transfer the `dist/` directory to the air-gapped machine**
|
||||||
|
|
||||||
|
Copy the entire `dist/` directory (which contains the `specify-cli` wheel and all dependency wheels) to the target machine via USB, network share, or other approved transfer method.
|
||||||
|
|
||||||
|
**Step 3: Install on the air-gapped machine**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install --no-index --find-links=./dist specify-cli
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 4: Initialize a project (no network required)**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Initialize a project — no GitHub access needed
|
||||||
|
specify init my-project --ai claude --offline
|
||||||
|
```
|
||||||
|
|
||||||
|
The `--offline` flag tells the CLI to use the templates, commands, and scripts bundled inside the wheel instead of downloading from GitHub.
|
||||||
|
|
||||||
|
> **Deprecation notice:** Starting with v0.6.0, `specify init` will use bundled assets by default and the `--offline` flag will be removed. The GitHub download path will be retired because bundled assets eliminate the need for network access, avoid proxy/firewall issues, and guarantee that templates always match the installed CLI version. No action will be needed — `specify init` will simply work without network access out of the box.
|
||||||
|
|
||||||
|
> **Note:** Python 3.11+ is required.
|
||||||
|
|
||||||
|
> **Windows note:** Offline scaffolding requires PowerShell 7+ (`pwsh`), not Windows PowerShell 5.x (`powershell.exe`). Install from https://aka.ms/powershell.
|
||||||
|
|
||||||
### Git Credential Manager on Linux
|
### Git Credential Manager on Linux
|
||||||
|
|
||||||
If you're having issues with Git authentication on Linux, you can install Git Credential Manager:
|
If you're having issues with Git authentication on Linux, you can install Git Credential Manager:
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
| What to Upgrade | Command | When to Use |
|
| What to Upgrade | Command | When to Use |
|
||||||
|----------------|---------|-------------|
|
|----------------|---------|-------------|
|
||||||
| **CLI Tool Only** | `uv tool install specify-cli --force --from git+https://github.com/github/spec-kit.git` | Get latest CLI features without touching project files |
|
| **CLI Tool Only** | `uv tool install specify-cli --force --from git+https://github.com/github/spec-kit.git@vX.Y.Z` | Get latest CLI features without touching project files |
|
||||||
| **Project Files** | `specify init --here --force --ai <your-agent>` | Update slash commands, templates, and scripts in your project |
|
| **Project Files** | `specify init --here --force --ai <your-agent>` | Update slash commands, templates, and scripts in your project |
|
||||||
| **Both** | Run CLI upgrade, then project update | Recommended for major version updates |
|
| **Both** | Run CLI upgrade, then project update | Recommended for major version updates |
|
||||||
|
|
||||||
@@ -20,16 +20,18 @@ The CLI tool (`specify`) is separate from your project files. Upgrade it to get
|
|||||||
|
|
||||||
### If you installed with `uv tool install`
|
### If you installed with `uv tool install`
|
||||||
|
|
||||||
|
Upgrade to a specific release (check [Releases](https://github.com/github/spec-kit/releases) for the latest tag):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uv tool install specify-cli --force --from git+https://github.com/github/spec-kit.git
|
uv tool install specify-cli --force --from git+https://github.com/github/spec-kit.git@vX.Y.Z
|
||||||
```
|
```
|
||||||
|
|
||||||
### If you use one-shot `uvx` commands
|
### If you use one-shot `uvx` commands
|
||||||
|
|
||||||
No upgrade needed—`uvx` always fetches the latest version. Just run your commands as normal:
|
Specify the desired release tag:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uvx --from git+https://github.com/github/spec-kit.git specify init --here --ai copilot
|
uvx --from git+https://github.com/github/spec-kit.git@vX.Y.Z specify init --here --ai copilot
|
||||||
```
|
```
|
||||||
|
|
||||||
### Verify the upgrade
|
### Verify the upgrade
|
||||||
@@ -291,6 +293,7 @@ This tells Spec Kit which feature directory to use when creating specs, plans, a
|
|||||||
ls -la .claude/commands/ # Claude Code
|
ls -la .claude/commands/ # Claude Code
|
||||||
ls -la .gemini/commands/ # Gemini
|
ls -la .gemini/commands/ # Gemini
|
||||||
ls -la .cursor/commands/ # Cursor
|
ls -la .cursor/commands/ # Cursor
|
||||||
|
ls -la .pi/prompts/ # Pi Coding Agent
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Check agent-specific setup:**
|
3. **Check agent-specific setup:**
|
||||||
@@ -398,7 +401,7 @@ The `specify` CLI tool is used for:
|
|||||||
- **Upgrades:** `specify init --here --force` to update templates and commands
|
- **Upgrades:** `specify init --here --force` to update templates and commands
|
||||||
- **Diagnostics:** `specify check` to verify tool installation
|
- **Diagnostics:** `specify check` to verify tool installation
|
||||||
|
|
||||||
Once you've run `specify init`, the slash commands (like `/speckit.specify`, `/speckit.plan`, etc.) are **permanently installed** in your project's agent folder (`.claude/`, `.github/prompts/`, etc.). Your AI assistant reads these command files directly—no need to run `specify` again.
|
Once you've run `specify init`, the slash commands (like `/speckit.specify`, `/speckit.plan`, etc.) are **permanently installed** in your project's agent folder (`.claude/`, `.github/prompts/`, `.pi/prompts/`, etc.). Your AI assistant reads these command files directly—no need to run `specify` again.
|
||||||
|
|
||||||
**If your agent isn't recognizing slash commands:**
|
**If your agent isn't recognizing slash commands:**
|
||||||
|
|
||||||
@@ -410,6 +413,9 @@ Once you've run `specify init`, the slash commands (like `/speckit.specify`, `/s
|
|||||||
|
|
||||||
# For Claude
|
# For Claude
|
||||||
ls -la .claude/commands/
|
ls -la .claude/commands/
|
||||||
|
|
||||||
|
# For Pi
|
||||||
|
ls -la .pi/prompts/
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Restart your IDE/editor completely** (not just reload window)
|
2. **Restart your IDE/editor completely** (not just reload window)
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ provides:
|
|||||||
required: boolean # Default: false
|
required: boolean # Default: false
|
||||||
|
|
||||||
hooks: # Optional, event hooks
|
hooks: # Optional, event hooks
|
||||||
event_name: # e.g., "after_tasks", "after_implement"
|
event_name: # e.g., "after_specify", "after_plan", "after_tasks", "after_implement"
|
||||||
command: string # Command to execute
|
command: string # Command to execute
|
||||||
optional: boolean # Default: true
|
optional: boolean # Default: true
|
||||||
prompt: string # Prompt text for optional hooks
|
prompt: string # Prompt text for optional hooks
|
||||||
@@ -108,7 +108,7 @@ defaults: # Optional, default configuration values
|
|||||||
#### `hooks`
|
#### `hooks`
|
||||||
|
|
||||||
- **Type**: object
|
- **Type**: object
|
||||||
- **Keys**: Event names (e.g., `after_tasks`, `after_implement`, `before_commit`)
|
- **Keys**: Event names (e.g., `after_specify`, `after_plan`, `after_tasks`, `after_implement`, `before_commit`)
|
||||||
- **Description**: Hooks that execute at lifecycle events
|
- **Description**: Hooks that execute at lifecycle events
|
||||||
- **Events**: Defined by core spec-kit commands
|
- **Events**: Defined by core spec-kit commands
|
||||||
|
|
||||||
@@ -551,10 +551,16 @@ hooks:
|
|||||||
|
|
||||||
Standard events (defined by core):
|
Standard events (defined by core):
|
||||||
|
|
||||||
|
- `before_specify` - Before specification generation
|
||||||
|
- `after_specify` - After specification generation
|
||||||
|
- `before_plan` - Before implementation planning
|
||||||
|
- `after_plan` - After implementation planning
|
||||||
|
- `before_tasks` - Before task generation
|
||||||
- `after_tasks` - After task generation
|
- `after_tasks` - After task generation
|
||||||
|
- `before_implement` - Before implementation
|
||||||
- `after_implement` - After implementation
|
- `after_implement` - After implementation
|
||||||
- `before_commit` - Before git commit
|
- `before_commit` - Before git commit *(planned - not yet wired into core templates)*
|
||||||
- `after_commit` - After git commit
|
- `after_commit` - After git commit *(planned - not yet wired into core templates)*
|
||||||
|
|
||||||
### Hook Configuration
|
### Hook Configuration
|
||||||
|
|
||||||
|
|||||||
@@ -209,9 +209,22 @@ Edit `extensions/catalog.community.json` and add your extension:
|
|||||||
Add your extension to the Available Extensions table in `extensions/README.md`:
|
Add your extension to the Available Extensions table in `extensions/README.md`:
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
| Your Extension Name | Brief description of what it does | [repo-name](https://github.com/your-org/spec-kit-your-extension) |
|
| Your Extension Name | Brief description of what it does | `<category>` | <effect> | [repo-name](https://github.com/your-org/spec-kit-your-extension) |
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**(Table) Category** — pick the one that best fits your extension:
|
||||||
|
|
||||||
|
- `docs` — reads, validates, or generates spec artifacts
|
||||||
|
- `code` — reviews, validates, or modifies source code
|
||||||
|
- `process` — orchestrates workflow across phases
|
||||||
|
- `integration` — syncs with external platforms
|
||||||
|
- `visibility` — reports on project health or progress
|
||||||
|
|
||||||
|
**Effect** — choose one:
|
||||||
|
|
||||||
|
- Read-only — produces reports without modifying files
|
||||||
|
- Read+Write — modifies files, creates artifacts, or updates specs
|
||||||
|
|
||||||
Insert your extension in alphabetical order in the table.
|
Insert your extension in alphabetical order in the table.
|
||||||
|
|
||||||
### 4. Submit Pull Request
|
### 4. Submit Pull Request
|
||||||
|
|||||||
@@ -387,6 +387,9 @@ settings:
|
|||||||
auto_execute_hooks: true
|
auto_execute_hooks: true
|
||||||
|
|
||||||
# Hook configuration
|
# Hook configuration
|
||||||
|
# Available events: before_specify, after_specify, before_plan, after_plan,
|
||||||
|
# before_tasks, after_tasks, before_implement, after_implement
|
||||||
|
# Planned (not yet wired into core templates): before_commit, after_commit
|
||||||
hooks:
|
hooks:
|
||||||
after_tasks:
|
after_tasks:
|
||||||
- extension: jira
|
- extension: jira
|
||||||
|
|||||||
@@ -70,20 +70,34 @@ specify extension add --from https://github.com/org/spec-kit-ext/archive/refs/ta
|
|||||||
|
|
||||||
The following community-contributed extensions are available in [`catalog.community.json`](catalog.community.json):
|
The following community-contributed extensions are available in [`catalog.community.json`](catalog.community.json):
|
||||||
|
|
||||||
| Extension | Purpose | URL |
|
**Categories:** `docs` — reads, validates, or generates spec artifacts · `code` — reviews, validates, or modifies source code · `process` — orchestrates workflow across phases · `integration` — syncs with external platforms · `visibility` — reports on project health or progress
|
||||||
|-----------|---------|-----|
|
|
||||||
| Azure DevOps Integration | Sync user stories and tasks to Azure DevOps work items using OAuth authentication | [spec-kit-azure-devops](https://github.com/pragya247/spec-kit-azure-devops) |
|
**Effect:** `Read-only` — produces reports without modifying files · `Read+Write` — modifies files, creates artifacts, or updates specs
|
||||||
| 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) |
|
|
||||||
| Fleet Orchestrator | Orchestrate a full feature lifecycle with human-in-the-loop gates across all SpecKit phases | [spec-kit-fleet](https://github.com/sharathsatish/spec-kit-fleet) |
|
| Extension | Purpose | Category | Effect | URL |
|
||||||
| Jira Integration | Create Jira Epics, Stories, and Issues from spec-kit specifications and task breakdowns with configurable hierarchy and custom field support | [spec-kit-jira](https://github.com/mbachorik/spec-kit-jira) |
|
|-----------|---------|----------|--------|-----|
|
||||||
| Project Health Check | Diagnose a Spec Kit project and report health issues across structure, agents, features, scripts, extensions, and git | [spec-kit-doctor](https://github.com/KhawarHabibKhan/spec-kit-doctor) |
|
| Archive Extension | Archive merged features into main project memory. | `docs` | Read+Write | [spec-kit-archive](https://github.com/stn1slv/spec-kit-archive) |
|
||||||
| Ralph Loop | Autonomous implementation loop using AI agent CLI | [spec-kit-ralph](https://github.com/Rubiss/spec-kit-ralph) |
|
| Azure DevOps Integration | Sync user stories and tasks to Azure DevOps work items using OAuth authentication | `integration` | Read+Write | [spec-kit-azure-devops](https://github.com/pragya247/spec-kit-azure-devops) |
|
||||||
| Retrospective Extension | Post-implementation retrospective with spec adherence scoring, drift analysis, and human-gated spec updates | [spec-kit-retrospective](https://github.com/emi-dm/spec-kit-retrospective) |
|
| 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 | `code` | Read+Write | [spec-kit-cleanup](https://github.com/dsrednicki/spec-kit-cleanup) |
|
||||||
| Review Extension | Post-implementation comprehensive code review with specialized agents for code quality, comments, tests, error handling, type design, and simplification | [spec-kit-review](https://github.com/ismaelJimenez/spec-kit-review) |
|
| Cognitive Squad | Multi-agent cognitive system with Triadic Model: understanding, internalization, application — with quality gates, backpropagation verification, and self-healing | `docs` | Read+Write | [cognitive-squad](https://github.com/Testimonial/cognitive-squad) |
|
||||||
| Spec Sync | Detect and resolve drift between specs and implementation. AI-assisted resolution with human approval | [spec-kit-sync](https://github.com/bgervin/spec-kit-sync) |
|
| Conduct Extension | Orchestrates spec-kit phases via sub-agent delegation to reduce context pollution. | `process` | Read+Write | [spec-kit-conduct-ext](https://github.com/twbrandon7/spec-kit-conduct-ext) |
|
||||||
| Understanding | Automated requirements quality analysis — 31 deterministic metrics against IEEE/ISO standards with experimental energy-based ambiguity detection | [understanding](https://github.com/Testimonial/understanding) |
|
| DocGuard — CDD Enforcement | Canonical-Driven Development enforcement. Validates, scores, and traces project documentation with automated checks, AI-driven workflows, and spec-kit hooks. Zero NPM runtime dependencies. | `docs` | Read+Write | [spec-kit-docguard](https://github.com/raccioly/docguard) |
|
||||||
| 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) |
|
| Fleet Orchestrator | Orchestrate a full feature lifecycle with human-in-the-loop gates across all SpecKit phases | `process` | Read+Write | [spec-kit-fleet](https://github.com/sharathsatish/spec-kit-fleet) |
|
||||||
| Verify Extension | Post-implementation quality gate that validates implemented code against specification artifacts | [spec-kit-verify](https://github.com/ismaelJimenez/spec-kit-verify) |
|
| Iterate | Iterate on spec documents with a two-phase define-and-apply workflow — refine specs mid-implementation and go straight back to building | `docs` | Read+Write | [spec-kit-iterate](https://github.com/imviancagrace/spec-kit-iterate) |
|
||||||
|
| Jira Integration | Create Jira Epics, Stories, and Issues from spec-kit specifications and task breakdowns with configurable hierarchy and custom field support | `integration` | Read+Write | [spec-kit-jira](https://github.com/mbachorik/spec-kit-jira) |
|
||||||
|
| Learning Extension | Generate educational guides from implementations and enhance clarifications with mentoring context | `docs` | Read+Write | [spec-kit-learn](https://github.com/imviancagrace/spec-kit-learn) |
|
||||||
|
| Project Health Check | Diagnose a Spec Kit project and report health issues across structure, agents, features, scripts, extensions, and git | `visibility` | Read-only | [spec-kit-doctor](https://github.com/KhawarHabibKhan/spec-kit-doctor) |
|
||||||
|
| Project Status | Show current SDD workflow progress — active feature, artifact status, task completion, workflow phase, and extensions summary | `visibility` | Read-only | [spec-kit-status](https://github.com/KhawarHabibKhan/spec-kit-status) |
|
||||||
|
| Ralph Loop | Autonomous implementation loop using AI agent CLI | `code` | Read+Write | [spec-kit-ralph](https://github.com/Rubiss/spec-kit-ralph) |
|
||||||
|
| Reconcile Extension | Reconcile implementation drift by surgically updating feature artifacts. | `docs` | Read+Write | [spec-kit-reconcile](https://github.com/stn1slv/spec-kit-reconcile) |
|
||||||
|
| Retrospective Extension | Post-implementation retrospective with spec adherence scoring, drift analysis, and human-gated spec updates | `docs` | Read+Write | [spec-kit-retrospective](https://github.com/emi-dm/spec-kit-retrospective) |
|
||||||
|
| Review Extension | Post-implementation comprehensive code review with specialized agents for code quality, comments, tests, error handling, type design, and simplification | `code` | Read-only | [spec-kit-review](https://github.com/ismaelJimenez/spec-kit-review) |
|
||||||
|
| SDD Utilities | Resume interrupted workflows, validate project health, and verify spec-to-task traceability | `process` | Read+Write | [speckit-utils](https://github.com/mvanhorn/speckit-utils) |
|
||||||
|
| Spec Sync | Detect and resolve drift between specs and implementation. AI-assisted resolution with human approval | `docs` | Read+Write | [spec-kit-sync](https://github.com/bgervin/spec-kit-sync) |
|
||||||
|
| Understanding | Automated requirements quality analysis — 31 deterministic metrics against IEEE/ISO standards with experimental energy-based ambiguity detection | `docs` | Read-only | [understanding](https://github.com/Testimonial/understanding) |
|
||||||
|
| V-Model Extension Pack | Enforces V-Model paired generation of development specs and test specs with full traceability | `docs` | Read+Write | [spec-kit-v-model](https://github.com/leocamello/spec-kit-v-model) |
|
||||||
|
| Verify Extension | Post-implementation quality gate that validates implemented code against specification artifacts | `code` | Read-only | [spec-kit-verify](https://github.com/ismaelJimenez/spec-kit-verify) |
|
||||||
|
| Verify Tasks Extension | Detect phantom completions: tasks marked [X] in tasks.md with no real implementation | `code` | Read-only | [spec-kit-verify-tasks](https://github.com/datastone-inc/spec-kit-verify-tasks) |
|
||||||
|
|
||||||
|
|
||||||
## Adding Your Extension
|
## Adding Your Extension
|
||||||
|
|||||||
@@ -359,12 +359,15 @@ specify extension add jira
|
|||||||
"installed_at": "2026-01-28T14:30:00Z",
|
"installed_at": "2026-01-28T14:30:00Z",
|
||||||
"source": "catalog",
|
"source": "catalog",
|
||||||
"manifest_hash": "sha256:abc123...",
|
"manifest_hash": "sha256:abc123...",
|
||||||
"enabled": true
|
"enabled": true,
|
||||||
|
"priority": 10
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Priority Field**: Extensions are ordered by `priority` (lower = higher precedence). Default is 10. Used for template resolution when multiple extensions provide the same template.
|
||||||
|
|
||||||
### 3. Configuration
|
### 3. Configuration
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -1084,11 +1087,15 @@ List installed extensions in current project.
|
|||||||
$ specify extension list
|
$ specify extension list
|
||||||
|
|
||||||
Installed Extensions:
|
Installed Extensions:
|
||||||
✓ jira (v1.0.0) - Jira Integration
|
✓ Jira Integration (v1.0.0)
|
||||||
Commands: 3 | Hooks: 2 | Status: Enabled
|
jira
|
||||||
|
Create Jira issues from spec-kit artifacts
|
||||||
|
Commands: 3 | Hooks: 2 | Priority: 10 | Status: Enabled
|
||||||
|
|
||||||
✓ linear (v0.9.0) - Linear Integration
|
✓ Linear Integration (v0.9.0)
|
||||||
Commands: 1 | Hooks: 1 | Status: Enabled
|
linear
|
||||||
|
Create Linear issues from spec-kit artifacts
|
||||||
|
Commands: 1 | Hooks: 1 | Priority: 10 | Status: Enabled
|
||||||
```
|
```
|
||||||
|
|
||||||
**Options:**
|
**Options:**
|
||||||
@@ -1196,10 +1203,9 @@ Next steps:
|
|||||||
|
|
||||||
**Options:**
|
**Options:**
|
||||||
|
|
||||||
- `--from URL`: Install from custom URL or Git repo
|
- `--from URL`: Install from a remote URL (archive). Does not accept Git repositories directly.
|
||||||
- `--version VERSION`: Install specific version
|
- `--dev`: Install from a local path in development mode (the PATH is the positional `extension` argument).
|
||||||
- `--dev PATH`: Install from local path (development mode)
|
- `--priority NUMBER`: Set resolution priority (lower = higher precedence, default 10)
|
||||||
- `--no-register`: Skip command registration (manual setup)
|
|
||||||
|
|
||||||
#### `specify extension remove NAME`
|
#### `specify extension remove NAME`
|
||||||
|
|
||||||
@@ -1280,6 +1286,29 @@ $ specify extension disable jira
|
|||||||
To re-enable: specify extension enable jira
|
To re-enable: specify extension enable jira
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### `specify extension set-priority NAME PRIORITY`
|
||||||
|
|
||||||
|
Change the resolution priority of an installed extension.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ specify extension set-priority jira 5
|
||||||
|
|
||||||
|
✓ Extension 'Jira Integration' priority changed: 10 → 5
|
||||||
|
|
||||||
|
Lower priority = higher precedence in template resolution
|
||||||
|
```
|
||||||
|
|
||||||
|
**Priority Values:**
|
||||||
|
|
||||||
|
- Lower numbers = higher precedence (checked first in resolution)
|
||||||
|
- Default priority is 10
|
||||||
|
- Must be a positive integer (1 or higher)
|
||||||
|
|
||||||
|
**Use Cases:**
|
||||||
|
|
||||||
|
- Ensure a critical extension's templates take precedence
|
||||||
|
- Override default resolution order when multiple extensions provide similar templates
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Compatibility & Versioning
|
## Compatibility & Versioning
|
||||||
|
|||||||
@@ -1,8 +1,39 @@
|
|||||||
{
|
{
|
||||||
"schema_version": "1.0",
|
"schema_version": "1.0",
|
||||||
"updated_at": "2026-03-13T12:00:00Z",
|
"updated_at": "2026-03-19T12:08:20Z",
|
||||||
"catalog_url": "https://raw.githubusercontent.com/github/spec-kit/main/extensions/catalog.community.json",
|
"catalog_url": "https://raw.githubusercontent.com/github/spec-kit/main/extensions/catalog.community.json",
|
||||||
"extensions": {
|
"extensions": {
|
||||||
|
"archive": {
|
||||||
|
"name": "Archive Extension",
|
||||||
|
"id": "archive",
|
||||||
|
"description": "Archive merged features into main project memory, resolving gaps and conflicts.",
|
||||||
|
"author": "Stanislav Deviatov",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"download_url": "https://github.com/stn1slv/spec-kit-archive/archive/refs/tags/v1.0.0.zip",
|
||||||
|
"repository": "https://github.com/stn1slv/spec-kit-archive",
|
||||||
|
"homepage": "https://github.com/stn1slv/spec-kit-archive",
|
||||||
|
"documentation": "https://github.com/stn1slv/spec-kit-archive/blob/main/README.md",
|
||||||
|
"changelog": "https://github.com/stn1slv/spec-kit-archive/blob/main/CHANGELOG.md",
|
||||||
|
"license": "MIT",
|
||||||
|
"requires": {
|
||||||
|
"speckit_version": ">=0.1.0"
|
||||||
|
},
|
||||||
|
"provides": {
|
||||||
|
"commands": 1,
|
||||||
|
"hooks": 0
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"archive",
|
||||||
|
"memory",
|
||||||
|
"merge",
|
||||||
|
"changelog"
|
||||||
|
],
|
||||||
|
"verified": false,
|
||||||
|
"downloads": 0,
|
||||||
|
"stars": 0,
|
||||||
|
"created_at": "2026-03-14T00:00:00Z",
|
||||||
|
"updated_at": "2026-03-14T00:00:00Z"
|
||||||
|
},
|
||||||
"azure-devops": {
|
"azure-devops": {
|
||||||
"name": "Azure DevOps Integration",
|
"name": "Azure DevOps Integration",
|
||||||
"id": "azure-devops",
|
"id": "azure-devops",
|
||||||
@@ -74,6 +105,122 @@
|
|||||||
"created_at": "2026-02-22T00:00:00Z",
|
"created_at": "2026-02-22T00:00:00Z",
|
||||||
"updated_at": "2026-02-22T00:00:00Z"
|
"updated_at": "2026-02-22T00:00:00Z"
|
||||||
},
|
},
|
||||||
|
"cognitive-squad": {
|
||||||
|
"name": "Cognitive Squad",
|
||||||
|
"id": "cognitive-squad",
|
||||||
|
"description": "Multi-agent cognitive system with Triadic Model: understanding, internalization, application — with quality gates, backpropagation verification, and self-healing",
|
||||||
|
"author": "Testimonial",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"download_url": "https://github.com/Testimonial/cognitive-squad/archive/refs/tags/v0.1.0.zip",
|
||||||
|
"repository": "https://github.com/Testimonial/cognitive-squad",
|
||||||
|
"homepage": "https://github.com/Testimonial/cognitive-squad",
|
||||||
|
"documentation": "https://github.com/Testimonial/cognitive-squad/blob/main/README.md",
|
||||||
|
"changelog": "https://github.com/Testimonial/cognitive-squad/blob/main/CHANGELOG.md",
|
||||||
|
"license": "MIT",
|
||||||
|
"requires": {
|
||||||
|
"speckit_version": ">=0.3.0",
|
||||||
|
"tools": [
|
||||||
|
{
|
||||||
|
"name": "understanding",
|
||||||
|
"version": ">=3.4.0",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "spec-kit-reverse-eng",
|
||||||
|
"version": ">=1.0.0",
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"provides": {
|
||||||
|
"commands": 10,
|
||||||
|
"hooks": 1
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"ai-agents",
|
||||||
|
"cognitive",
|
||||||
|
"full-lifecycle",
|
||||||
|
"verification",
|
||||||
|
"multi-agent"
|
||||||
|
],
|
||||||
|
"verified": false,
|
||||||
|
"downloads": 0,
|
||||||
|
"stars": 0,
|
||||||
|
"created_at": "2026-03-16T00:00:00Z",
|
||||||
|
"updated_at": "2026-03-18T00:00:00Z"
|
||||||
|
},
|
||||||
|
"conduct": {
|
||||||
|
"name": "Conduct Extension",
|
||||||
|
"id": "conduct",
|
||||||
|
"description": "Executes a single spec-kit phase via sub-agent delegation to reduce context pollution.",
|
||||||
|
"author": "twbrandon7",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"download_url": "https://github.com/twbrandon7/spec-kit-conduct-ext/archive/refs/tags/v1.0.0.zip",
|
||||||
|
"repository": "https://github.com/twbrandon7/spec-kit-conduct-ext",
|
||||||
|
"homepage": "https://github.com/twbrandon7/spec-kit-conduct-ext",
|
||||||
|
"documentation": "https://github.com/twbrandon7/spec-kit-conduct-ext/blob/main/README.md",
|
||||||
|
"changelog": "https://github.com/twbrandon7/spec-kit-conduct-ext/blob/main/CHANGELOG.md",
|
||||||
|
"license": "MIT",
|
||||||
|
"requires": {
|
||||||
|
"speckit_version": ">=0.3.1"
|
||||||
|
},
|
||||||
|
"provides": {
|
||||||
|
"commands": 1,
|
||||||
|
"hooks": 0
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"conduct",
|
||||||
|
"workflow",
|
||||||
|
"automation"
|
||||||
|
],
|
||||||
|
"verified": false,
|
||||||
|
"downloads": 0,
|
||||||
|
"stars": 0,
|
||||||
|
"created_at": "2026-03-19T12:08:20Z",
|
||||||
|
"updated_at": "2026-03-19T12:08:20Z"
|
||||||
|
},
|
||||||
|
"docguard": {
|
||||||
|
"name": "DocGuard \u2014 CDD Enforcement",
|
||||||
|
"id": "docguard",
|
||||||
|
"description": "Canonical-Driven Development enforcement. Validates, scores, and traces project documentation with automated checks, AI-driven workflows, and spec-kit hooks. Zero NPM runtime dependencies.",
|
||||||
|
"author": "raccioly",
|
||||||
|
"version": "0.9.11",
|
||||||
|
"download_url": "https://github.com/raccioly/docguard/releases/download/v0.9.11/spec-kit-docguard-v0.9.11.zip",
|
||||||
|
"repository": "https://github.com/raccioly/docguard",
|
||||||
|
"homepage": "https://www.npmjs.com/package/docguard-cli",
|
||||||
|
"documentation": "https://github.com/raccioly/docguard/blob/main/extensions/spec-kit-docguard/README.md",
|
||||||
|
"changelog": "https://github.com/raccioly/docguard/blob/main/CHANGELOG.md",
|
||||||
|
"license": "MIT",
|
||||||
|
"requires": {
|
||||||
|
"speckit_version": ">=0.1.0",
|
||||||
|
"tools": [
|
||||||
|
{
|
||||||
|
"name": "node",
|
||||||
|
"version": ">=18.0.0",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"provides": {
|
||||||
|
"commands": 6,
|
||||||
|
"hooks": 3
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"documentation",
|
||||||
|
"validation",
|
||||||
|
"quality",
|
||||||
|
"cdd",
|
||||||
|
"traceability",
|
||||||
|
"ai-agents",
|
||||||
|
"enforcement",
|
||||||
|
"spec-kit"
|
||||||
|
],
|
||||||
|
"verified": false,
|
||||||
|
"downloads": 0,
|
||||||
|
"stars": 0,
|
||||||
|
"created_at": "2026-03-13T00:00:00Z",
|
||||||
|
"updated_at": "2026-03-18T18:53:31Z"
|
||||||
|
},
|
||||||
"doctor": {
|
"doctor": {
|
||||||
"name": "Project Health Check",
|
"name": "Project Health Check",
|
||||||
"id": "doctor",
|
"id": "doctor",
|
||||||
@@ -124,13 +271,48 @@
|
|||||||
"commands": 2,
|
"commands": 2,
|
||||||
"hooks": 1
|
"hooks": 1
|
||||||
},
|
},
|
||||||
"tags": ["orchestration", "workflow", "human-in-the-loop", "parallel"],
|
"tags": [
|
||||||
|
"orchestration",
|
||||||
|
"workflow",
|
||||||
|
"human-in-the-loop",
|
||||||
|
"parallel"
|
||||||
|
],
|
||||||
"verified": false,
|
"verified": false,
|
||||||
"downloads": 0,
|
"downloads": 0,
|
||||||
"stars": 0,
|
"stars": 0,
|
||||||
"created_at": "2026-03-06T00:00:00Z",
|
"created_at": "2026-03-06T00:00:00Z",
|
||||||
"updated_at": "2026-03-06T00:00:00Z"
|
"updated_at": "2026-03-06T00:00:00Z"
|
||||||
},
|
},
|
||||||
|
"iterate": {
|
||||||
|
"name": "Iterate",
|
||||||
|
"id": "iterate",
|
||||||
|
"description": "Iterate on spec documents with a two-phase define-and-apply workflow — refine specs mid-implementation and go straight back to building",
|
||||||
|
"author": "Vianca Martinez",
|
||||||
|
"version": "2.0.0",
|
||||||
|
"download_url": "https://github.com/imviancagrace/spec-kit-iterate/archive/refs/tags/v2.0.0.zip",
|
||||||
|
"repository": "https://github.com/imviancagrace/spec-kit-iterate",
|
||||||
|
"homepage": "https://github.com/imviancagrace/spec-kit-iterate",
|
||||||
|
"documentation": "https://github.com/imviancagrace/spec-kit-iterate/blob/main/README.md",
|
||||||
|
"changelog": "https://github.com/imviancagrace/spec-kit-iterate/blob/main/CHANGELOG.md",
|
||||||
|
"license": "MIT",
|
||||||
|
"requires": {
|
||||||
|
"speckit_version": ">=0.1.0"
|
||||||
|
},
|
||||||
|
"provides": {
|
||||||
|
"commands": 2,
|
||||||
|
"hooks": 0
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"iteration",
|
||||||
|
"change-management",
|
||||||
|
"spec-maintenance"
|
||||||
|
],
|
||||||
|
"verified": false,
|
||||||
|
"downloads": 0,
|
||||||
|
"stars": 0,
|
||||||
|
"created_at": "2026-03-17T00:00:00Z",
|
||||||
|
"updated_at": "2026-03-17T00:00:00Z"
|
||||||
|
},
|
||||||
"jira": {
|
"jira": {
|
||||||
"name": "Jira Integration",
|
"name": "Jira Integration",
|
||||||
"id": "jira",
|
"id": "jira",
|
||||||
@@ -191,13 +373,49 @@
|
|||||||
"commands": 2,
|
"commands": 2,
|
||||||
"hooks": 1
|
"hooks": 1
|
||||||
},
|
},
|
||||||
"tags": ["implementation", "automation", "loop", "copilot"],
|
"tags": [
|
||||||
|
"implementation",
|
||||||
|
"automation",
|
||||||
|
"loop",
|
||||||
|
"copilot"
|
||||||
|
],
|
||||||
"verified": false,
|
"verified": false,
|
||||||
"downloads": 0,
|
"downloads": 0,
|
||||||
"stars": 0,
|
"stars": 0,
|
||||||
"created_at": "2026-03-09T00:00:00Z",
|
"created_at": "2026-03-09T00:00:00Z",
|
||||||
"updated_at": "2026-03-09T00:00:00Z"
|
"updated_at": "2026-03-09T00:00:00Z"
|
||||||
},
|
},
|
||||||
|
"reconcile": {
|
||||||
|
"name": "Reconcile Extension",
|
||||||
|
"id": "reconcile",
|
||||||
|
"description": "Reconcile implementation drift by surgically updating the feature's own spec, plan, and tasks.",
|
||||||
|
"author": "Stanislav Deviatov",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"download_url": "https://github.com/stn1slv/spec-kit-reconcile/archive/refs/tags/v1.0.0.zip",
|
||||||
|
"repository": "https://github.com/stn1slv/spec-kit-reconcile",
|
||||||
|
"homepage": "https://github.com/stn1slv/spec-kit-reconcile",
|
||||||
|
"documentation": "https://github.com/stn1slv/spec-kit-reconcile/blob/main/README.md",
|
||||||
|
"changelog": "https://github.com/stn1slv/spec-kit-reconcile/blob/main/CHANGELOG.md",
|
||||||
|
"license": "MIT",
|
||||||
|
"requires": {
|
||||||
|
"speckit_version": ">=0.1.0"
|
||||||
|
},
|
||||||
|
"provides": {
|
||||||
|
"commands": 1,
|
||||||
|
"hooks": 0
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"reconcile",
|
||||||
|
"drift",
|
||||||
|
"tasks",
|
||||||
|
"remediation"
|
||||||
|
],
|
||||||
|
"verified": false,
|
||||||
|
"downloads": 0,
|
||||||
|
"stars": 0,
|
||||||
|
"created_at": "2026-03-14T00:00:00Z",
|
||||||
|
"updated_at": "2026-03-14T00:00:00Z"
|
||||||
|
},
|
||||||
"retrospective": {
|
"retrospective": {
|
||||||
"name": "Retrospective Extension",
|
"name": "Retrospective Extension",
|
||||||
"id": "retrospective",
|
"id": "retrospective",
|
||||||
@@ -249,13 +467,53 @@
|
|||||||
"commands": 7,
|
"commands": 7,
|
||||||
"hooks": 1
|
"hooks": 1
|
||||||
},
|
},
|
||||||
"tags": ["code-review", "quality", "review", "testing", "error-handling", "type-design", "simplification"],
|
"tags": [
|
||||||
|
"code-review",
|
||||||
|
"quality",
|
||||||
|
"review",
|
||||||
|
"testing",
|
||||||
|
"error-handling",
|
||||||
|
"type-design",
|
||||||
|
"simplification"
|
||||||
|
],
|
||||||
"verified": false,
|
"verified": false,
|
||||||
"downloads": 0,
|
"downloads": 0,
|
||||||
"stars": 0,
|
"stars": 0,
|
||||||
"created_at": "2026-03-06T00:00:00Z",
|
"created_at": "2026-03-06T00:00:00Z",
|
||||||
"updated_at": "2026-03-06T00:00:00Z"
|
"updated_at": "2026-03-06T00:00:00Z"
|
||||||
},
|
},
|
||||||
|
"speckit-utils": {
|
||||||
|
"name": "SDD Utilities",
|
||||||
|
"id": "speckit-utils",
|
||||||
|
"description": "Resume interrupted workflows, validate project health, and verify spec-to-task traceability.",
|
||||||
|
"author": "mvanhorn",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"download_url": "https://github.com/mvanhorn/speckit-utils/archive/refs/tags/v1.0.0.zip",
|
||||||
|
"repository": "https://github.com/mvanhorn/speckit-utils",
|
||||||
|
"homepage": "https://github.com/mvanhorn/speckit-utils",
|
||||||
|
"documentation": "https://github.com/mvanhorn/speckit-utils/blob/main/README.md",
|
||||||
|
"changelog": "https://github.com/mvanhorn/speckit-utils/blob/main/CHANGELOG.md",
|
||||||
|
"license": "MIT",
|
||||||
|
"requires": {
|
||||||
|
"speckit_version": ">=0.1.0"
|
||||||
|
},
|
||||||
|
"provides": {
|
||||||
|
"commands": 3,
|
||||||
|
"hooks": 2
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"resume",
|
||||||
|
"doctor",
|
||||||
|
"validate",
|
||||||
|
"workflow",
|
||||||
|
"health-check"
|
||||||
|
],
|
||||||
|
"verified": false,
|
||||||
|
"downloads": 0,
|
||||||
|
"stars": 0,
|
||||||
|
"created_at": "2026-03-18T00:00:00Z",
|
||||||
|
"updated_at": "2026-03-18T00:00:00Z"
|
||||||
|
},
|
||||||
"sync": {
|
"sync": {
|
||||||
"name": "Spec Sync",
|
"name": "Spec Sync",
|
||||||
"id": "sync",
|
"id": "sync",
|
||||||
@@ -291,7 +549,7 @@
|
|||||||
"understanding": {
|
"understanding": {
|
||||||
"name": "Understanding",
|
"name": "Understanding",
|
||||||
"id": "understanding",
|
"id": "understanding",
|
||||||
"description": "Automated requirements quality analysis — validates specs against IEEE/ISO standards using 31 deterministic metrics. Catches ambiguity, missing testability, and structural issues before they reach implementation. Includes experimental energy-based ambiguity detection using local LM token perplexity.",
|
"description": "Automated requirements quality analysis \u2014 validates specs against IEEE/ISO standards using 31 deterministic metrics. Catches ambiguity, missing testability, and structural issues before they reach implementation. Includes experimental energy-based ambiguity detection using local LM token perplexity.",
|
||||||
"author": "Ladislav Bihari",
|
"author": "Ladislav Bihari",
|
||||||
"version": "3.4.0",
|
"version": "3.4.0",
|
||||||
"download_url": "https://github.com/Testimonial/understanding/archive/refs/tags/v3.4.0.zip",
|
"download_url": "https://github.com/Testimonial/understanding/archive/refs/tags/v3.4.0.zip",
|
||||||
@@ -329,6 +587,38 @@
|
|||||||
"created_at": "2026-03-07T00:00:00Z",
|
"created_at": "2026-03-07T00:00:00Z",
|
||||||
"updated_at": "2026-03-07T00:00:00Z"
|
"updated_at": "2026-03-07T00:00:00Z"
|
||||||
},
|
},
|
||||||
|
"status": {
|
||||||
|
"name": "Project Status",
|
||||||
|
"id": "status",
|
||||||
|
"description": "Show current SDD workflow progress — active feature, artifact status, task completion, workflow phase, and extensions summary.",
|
||||||
|
"author": "KhawarHabibKhan",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"download_url": "https://github.com/KhawarHabibKhan/spec-kit-status/archive/refs/tags/v1.0.0.zip",
|
||||||
|
"repository": "https://github.com/KhawarHabibKhan/spec-kit-status",
|
||||||
|
"homepage": "https://github.com/KhawarHabibKhan/spec-kit-status",
|
||||||
|
"documentation": "https://github.com/KhawarHabibKhan/spec-kit-status/blob/main/README.md",
|
||||||
|
"changelog": "https://github.com/KhawarHabibKhan/spec-kit-status/blob/main/CHANGELOG.md",
|
||||||
|
"license": "MIT",
|
||||||
|
"requires": {
|
||||||
|
"speckit_version": ">=0.1.0"
|
||||||
|
},
|
||||||
|
"provides": {
|
||||||
|
"commands": 1,
|
||||||
|
"hooks": 0
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"status",
|
||||||
|
"workflow",
|
||||||
|
"progress",
|
||||||
|
"feature-tracking",
|
||||||
|
"task-progress"
|
||||||
|
],
|
||||||
|
"verified": false,
|
||||||
|
"downloads": 0,
|
||||||
|
"stars": 0,
|
||||||
|
"created_at": "2026-03-16T00:00:00Z",
|
||||||
|
"updated_at": "2026-03-16T00:00:00Z"
|
||||||
|
},
|
||||||
"v-model": {
|
"v-model": {
|
||||||
"name": "V-Model Extension Pack",
|
"name": "V-Model Extension Pack",
|
||||||
"id": "v-model",
|
"id": "v-model",
|
||||||
@@ -361,6 +651,37 @@
|
|||||||
"created_at": "2026-02-20T00:00:00Z",
|
"created_at": "2026-02-20T00:00:00Z",
|
||||||
"updated_at": "2026-02-22T00:00:00Z"
|
"updated_at": "2026-02-22T00:00:00Z"
|
||||||
},
|
},
|
||||||
|
"learn": {
|
||||||
|
"name": "Learning Extension",
|
||||||
|
"id": "learn",
|
||||||
|
"description": "Generate educational guides from implementations and enhance clarifications with mentoring context.",
|
||||||
|
"author": "Vianca Martinez",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"download_url": "https://github.com/imviancagrace/spec-kit-learn/archive/refs/tags/v1.0.0.zip",
|
||||||
|
"repository": "https://github.com/imviancagrace/spec-kit-learn",
|
||||||
|
"homepage": "https://github.com/imviancagrace/spec-kit-learn",
|
||||||
|
"documentation": "https://github.com/imviancagrace/spec-kit-learn/blob/main/README.md",
|
||||||
|
"changelog": "https://github.com/imviancagrace/spec-kit-learn/blob/main/CHANGELOG.md",
|
||||||
|
"license": "MIT",
|
||||||
|
"requires": {
|
||||||
|
"speckit_version": ">=0.1.0"
|
||||||
|
},
|
||||||
|
"provides": {
|
||||||
|
"commands": 2,
|
||||||
|
"hooks": 1
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"learning",
|
||||||
|
"education",
|
||||||
|
"mentoring",
|
||||||
|
"knowledge-transfer"
|
||||||
|
],
|
||||||
|
"verified": false,
|
||||||
|
"downloads": 0,
|
||||||
|
"stars": 0,
|
||||||
|
"created_at": "2026-03-17T00:00:00Z",
|
||||||
|
"updated_at": "2026-03-17T00:00:00Z"
|
||||||
|
},
|
||||||
"verify": {
|
"verify": {
|
||||||
"name": "Verify Extension",
|
"name": "Verify Extension",
|
||||||
"id": "verify",
|
"id": "verify",
|
||||||
@@ -392,6 +713,37 @@
|
|||||||
"stars": 0,
|
"stars": 0,
|
||||||
"created_at": "2026-03-03T00:00:00Z",
|
"created_at": "2026-03-03T00:00:00Z",
|
||||||
"updated_at": "2026-03-03T00:00:00Z"
|
"updated_at": "2026-03-03T00:00:00Z"
|
||||||
|
},
|
||||||
|
"verify-tasks": {
|
||||||
|
"name": "Verify Tasks Extension",
|
||||||
|
"id": "verify-tasks",
|
||||||
|
"description": "Detect phantom completions: tasks marked [X] in tasks.md with no real implementation.",
|
||||||
|
"author": "Dave Sharpe",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"download_url": "https://github.com/datastone-inc/spec-kit-verify-tasks/archive/refs/tags/v1.0.0.zip",
|
||||||
|
"repository": "https://github.com/datastone-inc/spec-kit-verify-tasks",
|
||||||
|
"homepage": "https://github.com/datastone-inc/spec-kit-verify-tasks",
|
||||||
|
"documentation": "https://github.com/datastone-inc/spec-kit-verify-tasks/blob/main/README.md",
|
||||||
|
"changelog": "https://github.com/datastone-inc/spec-kit-verify-tasks/blob/main/CHANGELOG.md",
|
||||||
|
"license": "MIT",
|
||||||
|
"requires": {
|
||||||
|
"speckit_version": ">=0.1.0"
|
||||||
|
},
|
||||||
|
"provides": {
|
||||||
|
"commands": 1,
|
||||||
|
"hooks": 1
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"verification",
|
||||||
|
"quality",
|
||||||
|
"phantom-completion",
|
||||||
|
"tasks"
|
||||||
|
],
|
||||||
|
"verified": false,
|
||||||
|
"downloads": 0,
|
||||||
|
"stars": 0,
|
||||||
|
"created_at": "2026-03-16T00:00:00Z",
|
||||||
|
"updated_at": "2026-03-16T00:00:00Z"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,13 +13,15 @@ When Spec Kit needs a template (e.g. `spec-template`), it walks a resolution sta
|
|||||||
|
|
||||||
If no preset is installed, core templates are used — exactly the same behavior as before presets existed.
|
If no preset is installed, core templates are used — exactly the same behavior as before presets existed.
|
||||||
|
|
||||||
|
Template resolution happens **at runtime** — although preset files are copied into `.specify/presets/<id>/` during installation, Spec Kit walks the resolution stack on every template lookup rather than merging templates into a single location.
|
||||||
|
|
||||||
For detailed resolution and command registration flows, see [ARCHITECTURE.md](ARCHITECTURE.md).
|
For detailed resolution and command registration flows, see [ARCHITECTURE.md](ARCHITECTURE.md).
|
||||||
|
|
||||||
## Command Overrides
|
## Command Overrides
|
||||||
|
|
||||||
Presets can also override the commands that guide the SDD workflow. Templates define *what* gets produced (specs, plans, constitutions); commands define *how* the LLM produces them (the step-by-step instructions).
|
Presets can also override the commands that guide the SDD workflow. Templates define *what* gets produced (specs, plans, constitutions); commands define *how* the LLM produces them (the step-by-step instructions).
|
||||||
|
|
||||||
When a preset includes `type: "command"` entries, the commands are automatically registered into all detected agent directories (`.claude/commands/`, `.gemini/commands/`, etc.) in the correct format (Markdown or TOML with appropriate argument placeholders). When the preset is removed, the registered commands are cleaned up.
|
Unlike templates, command overrides are applied **at install time**. When a preset includes `type: "command"` entries, the commands are registered into all detected agent directories (`.claude/commands/`, `.gemini/commands/`, etc.) in the correct format (Markdown or TOML with appropriate argument placeholders). When the preset is removed, the registered commands are cleaned up.
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "specify-cli"
|
name = "specify-cli"
|
||||||
version = "0.3.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 = [
|
||||||
@@ -14,6 +14,7 @@ dependencies = [
|
|||||||
"pyyaml>=6.0",
|
"pyyaml>=6.0",
|
||||||
"packaging>=23.0",
|
"packaging>=23.0",
|
||||||
"pathspec>=0.12.0",
|
"pathspec>=0.12.0",
|
||||||
|
"json5>=0.13.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
@@ -26,6 +27,25 @@ build-backend = "hatchling.build"
|
|||||||
[tool.hatch.build.targets.wheel]
|
[tool.hatch.build.targets.wheel]
|
||||||
packages = ["src/specify_cli"]
|
packages = ["src/specify_cli"]
|
||||||
|
|
||||||
|
[tool.hatch.build.targets.wheel.force-include]
|
||||||
|
# Bundle core assets so `specify init` works without network access (air-gapped / enterprise)
|
||||||
|
# Page templates (exclude commands/ — bundled separately below to avoid duplication)
|
||||||
|
"templates/agent-file-template.md" = "specify_cli/core_pack/templates/agent-file-template.md"
|
||||||
|
"templates/checklist-template.md" = "specify_cli/core_pack/templates/checklist-template.md"
|
||||||
|
"templates/constitution-template.md" = "specify_cli/core_pack/templates/constitution-template.md"
|
||||||
|
"templates/plan-template.md" = "specify_cli/core_pack/templates/plan-template.md"
|
||||||
|
"templates/spec-template.md" = "specify_cli/core_pack/templates/spec-template.md"
|
||||||
|
"templates/tasks-template.md" = "specify_cli/core_pack/templates/tasks-template.md"
|
||||||
|
"templates/vscode-settings.json" = "specify_cli/core_pack/templates/vscode-settings.json"
|
||||||
|
# Command templates
|
||||||
|
"templates/commands" = "specify_cli/core_pack/commands"
|
||||||
|
"scripts/bash" = "specify_cli/core_pack/scripts/bash"
|
||||||
|
"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.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 = [
|
||||||
"pytest>=7.0",
|
"pytest>=7.0",
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ if $JSON_MODE; then
|
|||||||
if [[ ${#docs[@]} -eq 0 ]]; then
|
if [[ ${#docs[@]} -eq 0 ]]; then
|
||||||
json_docs="[]"
|
json_docs="[]"
|
||||||
else
|
else
|
||||||
json_docs=$(printf '"%s",' "${docs[@]}")
|
json_docs=$(for d in "${docs[@]}"; do printf '"%s",' "$(json_escape "$d")"; done)
|
||||||
json_docs="[${json_docs%,}]"
|
json_docs="[${json_docs%,}]"
|
||||||
fi
|
fi
|
||||||
printf '{"FEATURE_DIR":"%s","AVAILABLE_DOCS":%s}\n' "$(json_escape "$FEATURE_DIR")" "$json_docs"
|
printf '{"FEATURE_DIR":"%s","AVAILABLE_DOCS":%s}\n' "$(json_escape "$FEATURE_DIR")" "$json_docs"
|
||||||
|
|||||||
@@ -33,19 +33,30 @@ get_current_branch() {
|
|||||||
if [[ -d "$specs_dir" ]]; then
|
if [[ -d "$specs_dir" ]]; then
|
||||||
local latest_feature=""
|
local latest_feature=""
|
||||||
local highest=0
|
local highest=0
|
||||||
|
local latest_timestamp=""
|
||||||
|
|
||||||
for dir in "$specs_dir"/*; do
|
for dir in "$specs_dir"/*; do
|
||||||
if [[ -d "$dir" ]]; then
|
if [[ -d "$dir" ]]; then
|
||||||
local dirname=$(basename "$dir")
|
local dirname=$(basename "$dir")
|
||||||
if [[ "$dirname" =~ ^([0-9]{3})- ]]; then
|
if [[ "$dirname" =~ ^([0-9]{8}-[0-9]{6})- ]]; then
|
||||||
|
# Timestamp-based branch: compare lexicographically
|
||||||
|
local ts="${BASH_REMATCH[1]}"
|
||||||
|
if [[ "$ts" > "$latest_timestamp" ]]; then
|
||||||
|
latest_timestamp="$ts"
|
||||||
|
latest_feature=$dirname
|
||||||
|
fi
|
||||||
|
elif [[ "$dirname" =~ ^([0-9]{3})- ]]; then
|
||||||
local number=${BASH_REMATCH[1]}
|
local number=${BASH_REMATCH[1]}
|
||||||
number=$((10#$number))
|
number=$((10#$number))
|
||||||
if [[ "$number" -gt "$highest" ]]; then
|
if [[ "$number" -gt "$highest" ]]; then
|
||||||
highest=$number
|
highest=$number
|
||||||
|
# Only update if no timestamp branch found yet
|
||||||
|
if [[ -z "$latest_timestamp" ]]; then
|
||||||
latest_feature=$dirname
|
latest_feature=$dirname
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ -n "$latest_feature" ]]; then
|
if [[ -n "$latest_feature" ]]; then
|
||||||
@@ -72,9 +83,9 @@ check_feature_branch() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ! "$branch" =~ ^[0-9]{3}- ]]; then
|
if [[ ! "$branch" =~ ^[0-9]{3}- ]] && [[ ! "$branch" =~ ^[0-9]{8}-[0-9]{6}- ]]; then
|
||||||
echo "ERROR: Not on a feature branch. Current branch: $branch" >&2
|
echo "ERROR: Not on a feature branch. Current branch: $branch" >&2
|
||||||
echo "Feature branches should be named like: 001-feature-name" >&2
|
echo "Feature branches should be named like: 001-feature-name or 20260319-143022-feature-name" >&2
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -90,15 +101,18 @@ find_feature_dir_by_prefix() {
|
|||||||
local branch_name="$2"
|
local branch_name="$2"
|
||||||
local specs_dir="$repo_root/specs"
|
local specs_dir="$repo_root/specs"
|
||||||
|
|
||||||
# Extract numeric prefix from branch (e.g., "004" from "004-whatever")
|
# Extract prefix from branch (e.g., "004" from "004-whatever" or "20260319-143022" from timestamp branches)
|
||||||
if [[ ! "$branch_name" =~ ^([0-9]{3})- ]]; then
|
local prefix=""
|
||||||
# If branch doesn't have numeric prefix, fall back to exact match
|
if [[ "$branch_name" =~ ^([0-9]{8}-[0-9]{6})- ]]; then
|
||||||
|
prefix="${BASH_REMATCH[1]}"
|
||||||
|
elif [[ "$branch_name" =~ ^([0-9]{3})- ]]; then
|
||||||
|
prefix="${BASH_REMATCH[1]}"
|
||||||
|
else
|
||||||
|
# If branch doesn't have a recognized prefix, fall back to exact match
|
||||||
echo "$specs_dir/$branch_name"
|
echo "$specs_dir/$branch_name"
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local prefix="${BASH_REMATCH[1]}"
|
|
||||||
|
|
||||||
# Search for directories in specs/ that start with this prefix
|
# Search for directories in specs/ that start with this prefix
|
||||||
local matches=()
|
local matches=()
|
||||||
if [[ -d "$specs_dir" ]]; then
|
if [[ -d "$specs_dir" ]]; then
|
||||||
@@ -119,7 +133,7 @@ find_feature_dir_by_prefix() {
|
|||||||
else
|
else
|
||||||
# Multiple matches - this shouldn't happen with proper naming convention
|
# Multiple matches - this shouldn't happen with proper naming convention
|
||||||
echo "ERROR: Multiple spec directories found with prefix '$prefix': ${matches[*]}" >&2
|
echo "ERROR: Multiple spec directories found with prefix '$prefix': ${matches[*]}" >&2
|
||||||
echo "Please ensure only one spec directory exists per numeric prefix." >&2
|
echo "Please ensure only one spec directory exists per prefix." >&2
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
@@ -161,7 +175,7 @@ has_jq() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Escape a string for safe embedding in a JSON value (fallback when jq is unavailable).
|
# Escape a string for safe embedding in a JSON value (fallback when jq is unavailable).
|
||||||
# Handles backslash, double-quote, and control characters (newline, tab, carriage return).
|
# Handles backslash, double-quote, and JSON-required control character escapes (RFC 8259).
|
||||||
json_escape() {
|
json_escape() {
|
||||||
local s="$1"
|
local s="$1"
|
||||||
s="${s//\\/\\\\}"
|
s="${s//\\/\\\\}"
|
||||||
@@ -169,7 +183,23 @@ json_escape() {
|
|||||||
s="${s//$'\n'/\\n}"
|
s="${s//$'\n'/\\n}"
|
||||||
s="${s//$'\t'/\\t}"
|
s="${s//$'\t'/\\t}"
|
||||||
s="${s//$'\r'/\\r}"
|
s="${s//$'\r'/\\r}"
|
||||||
printf '%s' "$s"
|
s="${s//$'\b'/\\b}"
|
||||||
|
s="${s//$'\f'/\\f}"
|
||||||
|
# Escape any remaining U+0001-U+001F control characters as \uXXXX.
|
||||||
|
# (U+0000/NUL cannot appear in bash strings and is excluded.)
|
||||||
|
# LC_ALL=C ensures ${#s} counts bytes and ${s:$i:1} yields single bytes,
|
||||||
|
# so multi-byte UTF-8 sequences (first byte >= 0xC0) pass through intact.
|
||||||
|
local LC_ALL=C
|
||||||
|
local i char code
|
||||||
|
for (( i=0; i<${#s}; i++ )); do
|
||||||
|
char="${s:$i:1}"
|
||||||
|
printf -v code '%d' "'$char" 2>/dev/null || code=256
|
||||||
|
if (( code >= 1 && code <= 31 )); then
|
||||||
|
printf '\\u%04x' "$code"
|
||||||
|
else
|
||||||
|
printf '%s' "$char"
|
||||||
|
fi
|
||||||
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
check_file() { [[ -f "$1" ]] && echo " ✓ $2" || echo " ✗ $2"; }
|
check_file() { [[ -f "$1" ]] && echo " ✓ $2" || echo " ✗ $2"; }
|
||||||
@@ -194,9 +224,11 @@ resolve_template() {
|
|||||||
if [ -d "$presets_dir" ]; then
|
if [ -d "$presets_dir" ]; then
|
||||||
local registry_file="$presets_dir/.registry"
|
local registry_file="$presets_dir/.registry"
|
||||||
if [ -f "$registry_file" ] && command -v python3 >/dev/null 2>&1; then
|
if [ -f "$registry_file" ] && command -v python3 >/dev/null 2>&1; then
|
||||||
# Read preset IDs sorted by priority (lower number = higher precedence)
|
# Read preset IDs sorted by priority (lower number = higher precedence).
|
||||||
local sorted_presets
|
# The python3 call is wrapped in an if-condition so that set -e does not
|
||||||
sorted_presets=$(SPECKIT_REGISTRY="$registry_file" python3 -c "
|
# abort the function when python3 exits non-zero (e.g. invalid JSON).
|
||||||
|
local sorted_presets=""
|
||||||
|
if sorted_presets=$(SPECKIT_REGISTRY="$registry_file" python3 -c "
|
||||||
import json, sys, os
|
import json, sys, os
|
||||||
try:
|
try:
|
||||||
with open(os.environ['SPECKIT_REGISTRY']) as f:
|
with open(os.environ['SPECKIT_REGISTRY']) as f:
|
||||||
@@ -206,14 +238,17 @@ try:
|
|||||||
print(pid)
|
print(pid)
|
||||||
except Exception:
|
except Exception:
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
" 2>/dev/null)
|
" 2>/dev/null); then
|
||||||
if [ $? -eq 0 ] && [ -n "$sorted_presets" ]; then
|
if [ -n "$sorted_presets" ]; then
|
||||||
|
# python3 succeeded and returned preset IDs — search in priority order
|
||||||
while IFS= read -r preset_id; do
|
while IFS= read -r preset_id; do
|
||||||
local candidate="$presets_dir/$preset_id/templates/${template_name}.md"
|
local candidate="$presets_dir/$preset_id/templates/${template_name}.md"
|
||||||
[ -f "$candidate" ] && echo "$candidate" && return 0
|
[ -f "$candidate" ] && echo "$candidate" && return 0
|
||||||
done <<< "$sorted_presets"
|
done <<< "$sorted_presets"
|
||||||
|
fi
|
||||||
|
# python3 succeeded but registry has no presets — nothing to search
|
||||||
else
|
else
|
||||||
# python3 returned empty list — fall through to directory scan
|
# python3 failed (missing, or registry parse error) — fall back to unordered directory scan
|
||||||
for preset in "$presets_dir"/*/; do
|
for preset in "$presets_dir"/*/; do
|
||||||
[ -d "$preset" ] || continue
|
[ -d "$preset" ] || continue
|
||||||
local candidate="$preset/templates/${template_name}.md"
|
local candidate="$preset/templates/${template_name}.md"
|
||||||
@@ -246,8 +281,9 @@ except Exception:
|
|||||||
local core="$base/${template_name}.md"
|
local core="$base/${template_name}.md"
|
||||||
[ -f "$core" ] && echo "$core" && return 0
|
[ -f "$core" ] && echo "$core" && return 0
|
||||||
|
|
||||||
# Return success with empty output so callers using set -e don't abort;
|
# Template not found in any location.
|
||||||
# callers check [ -n "$TEMPLATE" ] to detect "not found".
|
# Return 1 so callers can distinguish "not found" from "found".
|
||||||
return 0
|
# Callers running under set -e should use: TEMPLATE=$(resolve_template ...) || true
|
||||||
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ set -e
|
|||||||
JSON_MODE=false
|
JSON_MODE=false
|
||||||
SHORT_NAME=""
|
SHORT_NAME=""
|
||||||
BRANCH_NUMBER=""
|
BRANCH_NUMBER=""
|
||||||
|
USE_TIMESTAMP=false
|
||||||
ARGS=()
|
ARGS=()
|
||||||
i=1
|
i=1
|
||||||
while [ $i -le $# ]; do
|
while [ $i -le $# ]; do
|
||||||
@@ -40,18 +41,23 @@ while [ $i -le $# ]; do
|
|||||||
fi
|
fi
|
||||||
BRANCH_NUMBER="$next_arg"
|
BRANCH_NUMBER="$next_arg"
|
||||||
;;
|
;;
|
||||||
|
--timestamp)
|
||||||
|
USE_TIMESTAMP=true
|
||||||
|
;;
|
||||||
--help|-h)
|
--help|-h)
|
||||||
echo "Usage: $0 [--json] [--short-name <name>] [--number N] <feature_description>"
|
echo "Usage: $0 [--json] [--short-name <name>] [--number N] [--timestamp] <feature_description>"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Options:"
|
echo "Options:"
|
||||||
echo " --json Output in JSON format"
|
echo " --json Output in JSON format"
|
||||||
echo " --short-name <name> Provide a custom short name (2-4 words) for the branch"
|
echo " --short-name <name> Provide a custom short name (2-4 words) for the branch"
|
||||||
echo " --number N Specify branch number manually (overrides auto-detection)"
|
echo " --number N Specify branch number manually (overrides auto-detection)"
|
||||||
|
echo " --timestamp Use timestamp prefix (YYYYMMDD-HHMMSS) instead of sequential numbering"
|
||||||
echo " --help, -h Show this help message"
|
echo " --help, -h Show this help message"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Examples:"
|
echo "Examples:"
|
||||||
echo " $0 'Add user authentication system' --short-name 'user-auth'"
|
echo " $0 'Add user authentication system' --short-name 'user-auth'"
|
||||||
echo " $0 'Implement OAuth2 integration for API' --number 5"
|
echo " $0 'Implement OAuth2 integration for API' --number 5"
|
||||||
|
echo " $0 --timestamp --short-name 'user-auth' 'Add user authentication'"
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
@@ -63,7 +69,7 @@ done
|
|||||||
|
|
||||||
FEATURE_DESCRIPTION="${ARGS[*]}"
|
FEATURE_DESCRIPTION="${ARGS[*]}"
|
||||||
if [ -z "$FEATURE_DESCRIPTION" ]; then
|
if [ -z "$FEATURE_DESCRIPTION" ]; then
|
||||||
echo "Usage: $0 [--json] [--short-name <name>] [--number N] <feature_description>" >&2
|
echo "Usage: $0 [--json] [--short-name <name>] [--number N] [--timestamp] <feature_description>" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -96,11 +102,14 @@ get_highest_from_specs() {
|
|||||||
for dir in "$specs_dir"/*; do
|
for dir in "$specs_dir"/*; do
|
||||||
[ -d "$dir" ] || continue
|
[ -d "$dir" ] || continue
|
||||||
dirname=$(basename "$dir")
|
dirname=$(basename "$dir")
|
||||||
number=$(echo "$dirname" | grep -o '^[0-9]\+' || echo "0")
|
# Only match sequential prefixes (###-*), skip timestamp dirs
|
||||||
|
if echo "$dirname" | grep -q '^[0-9]\{3\}-'; then
|
||||||
|
number=$(echo "$dirname" | grep -o '^[0-9]\{3\}')
|
||||||
number=$((10#$number))
|
number=$((10#$number))
|
||||||
if [ "$number" -gt "$highest" ]; then
|
if [ "$number" -gt "$highest" ]; then
|
||||||
highest=$number
|
highest=$number
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -138,7 +147,7 @@ check_existing_branches() {
|
|||||||
local specs_dir="$1"
|
local specs_dir="$1"
|
||||||
|
|
||||||
# Fetch all remotes to get latest branch info (suppress errors if no remotes)
|
# Fetch all remotes to get latest branch info (suppress errors if no remotes)
|
||||||
git fetch --all --prune 2>/dev/null || true
|
git fetch --all --prune >/dev/null 2>&1 || true
|
||||||
|
|
||||||
# Get highest number from ALL branches (not just matching short name)
|
# Get highest number from ALL branches (not just matching short name)
|
||||||
local highest_branch=$(get_highest_from_branches)
|
local highest_branch=$(get_highest_from_branches)
|
||||||
@@ -162,17 +171,6 @@ clean_branch_name() {
|
|||||||
echo "$name" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/-\+/-/g' | sed 's/^-//' | sed 's/-$//'
|
echo "$name" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/-\+/-/g' | sed 's/^-//' | sed 's/-$//'
|
||||||
}
|
}
|
||||||
|
|
||||||
# Escape a string for safe embedding in a JSON value (fallback when jq is unavailable).
|
|
||||||
json_escape() {
|
|
||||||
local s="$1"
|
|
||||||
s="${s//\\/\\\\}"
|
|
||||||
s="${s//\"/\\\"}"
|
|
||||||
s="${s//$'\n'/\\n}"
|
|
||||||
s="${s//$'\t'/\\t}"
|
|
||||||
s="${s//$'\r'/\\r}"
|
|
||||||
printf '%s' "$s"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Resolve repository root. Prefer git information when available, but fall back
|
# Resolve repository root. Prefer git information when available, but fall back
|
||||||
# to searching for repository markers so the workflow still functions in repositories that
|
# to searching for repository markers so the workflow still functions in repositories that
|
||||||
# were initialised with --no-git.
|
# were initialised with --no-git.
|
||||||
@@ -253,8 +251,19 @@ else
|
|||||||
BRANCH_SUFFIX=$(generate_branch_name "$FEATURE_DESCRIPTION")
|
BRANCH_SUFFIX=$(generate_branch_name "$FEATURE_DESCRIPTION")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Determine branch number
|
# Warn if --number and --timestamp are both specified
|
||||||
if [ -z "$BRANCH_NUMBER" ]; then
|
if [ "$USE_TIMESTAMP" = true ] && [ -n "$BRANCH_NUMBER" ]; then
|
||||||
|
>&2 echo "[specify] Warning: --number is ignored when --timestamp is used"
|
||||||
|
BRANCH_NUMBER=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Determine branch prefix
|
||||||
|
if [ "$USE_TIMESTAMP" = true ]; then
|
||||||
|
FEATURE_NUM=$(date +%Y%m%d-%H%M%S)
|
||||||
|
BRANCH_NAME="${FEATURE_NUM}-${BRANCH_SUFFIX}"
|
||||||
|
else
|
||||||
|
# Determine branch number
|
||||||
|
if [ -z "$BRANCH_NUMBER" ]; then
|
||||||
if [ "$HAS_GIT" = true ]; then
|
if [ "$HAS_GIT" = true ]; then
|
||||||
# Check existing branches on remotes
|
# Check existing branches on remotes
|
||||||
BRANCH_NUMBER=$(check_existing_branches "$SPECS_DIR")
|
BRANCH_NUMBER=$(check_existing_branches "$SPECS_DIR")
|
||||||
@@ -263,19 +272,21 @@ if [ -z "$BRANCH_NUMBER" ]; then
|
|||||||
HIGHEST=$(get_highest_from_specs "$SPECS_DIR")
|
HIGHEST=$(get_highest_from_specs "$SPECS_DIR")
|
||||||
BRANCH_NUMBER=$((HIGHEST + 1))
|
BRANCH_NUMBER=$((HIGHEST + 1))
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Force base-10 interpretation to prevent octal conversion (e.g., 010 → 8 in octal, but should be 10 in decimal)
|
# Force base-10 interpretation to prevent octal conversion (e.g., 010 → 8 in octal, but should be 10 in decimal)
|
||||||
FEATURE_NUM=$(printf "%03d" "$((10#$BRANCH_NUMBER))")
|
FEATURE_NUM=$(printf "%03d" "$((10#$BRANCH_NUMBER))")
|
||||||
BRANCH_NAME="${FEATURE_NUM}-${BRANCH_SUFFIX}"
|
BRANCH_NAME="${FEATURE_NUM}-${BRANCH_SUFFIX}"
|
||||||
|
fi
|
||||||
|
|
||||||
# GitHub enforces a 244-byte limit on branch names
|
# GitHub enforces a 244-byte limit on branch names
|
||||||
# Validate and truncate if necessary
|
# Validate and truncate if necessary
|
||||||
MAX_BRANCH_LENGTH=244
|
MAX_BRANCH_LENGTH=244
|
||||||
if [ ${#BRANCH_NAME} -gt $MAX_BRANCH_LENGTH ]; then
|
if [ ${#BRANCH_NAME} -gt $MAX_BRANCH_LENGTH ]; then
|
||||||
# Calculate how much we need to trim from suffix
|
# Calculate how much we need to trim from suffix
|
||||||
# Account for: feature number (3) + hyphen (1) = 4 chars
|
# Account for prefix length: timestamp (15) + hyphen (1) = 16, or sequential (3) + hyphen (1) = 4
|
||||||
MAX_SUFFIX_LENGTH=$((MAX_BRANCH_LENGTH - 4))
|
PREFIX_LENGTH=$(( ${#FEATURE_NUM} + 1 ))
|
||||||
|
MAX_SUFFIX_LENGTH=$((MAX_BRANCH_LENGTH - PREFIX_LENGTH))
|
||||||
|
|
||||||
# Truncate suffix at word boundary if possible
|
# Truncate suffix at word boundary if possible
|
||||||
TRUNCATED_SUFFIX=$(echo "$BRANCH_SUFFIX" | cut -c1-$MAX_SUFFIX_LENGTH)
|
TRUNCATED_SUFFIX=$(echo "$BRANCH_SUFFIX" | cut -c1-$MAX_SUFFIX_LENGTH)
|
||||||
@@ -294,7 +305,11 @@ if [ "$HAS_GIT" = true ]; then
|
|||||||
if ! git checkout -b "$BRANCH_NAME" 2>/dev/null; then
|
if ! git checkout -b "$BRANCH_NAME" 2>/dev/null; then
|
||||||
# Check if branch already exists
|
# Check if branch already exists
|
||||||
if git branch --list "$BRANCH_NAME" | grep -q .; then
|
if git branch --list "$BRANCH_NAME" | grep -q .; then
|
||||||
|
if [ "$USE_TIMESTAMP" = true ]; then
|
||||||
|
>&2 echo "Error: Branch '$BRANCH_NAME' already exists. Rerun to get a new timestamp or use a different --short-name."
|
||||||
|
else
|
||||||
>&2 echo "Error: Branch '$BRANCH_NAME' already exists. Please use a different feature name or specify a different number with --number."
|
>&2 echo "Error: Branch '$BRANCH_NAME' already exists. Please use a different feature name or specify a different number with --number."
|
||||||
|
fi
|
||||||
exit 1
|
exit 1
|
||||||
else
|
else
|
||||||
>&2 echo "Error: Failed to create git branch '$BRANCH_NAME'. Please check your git configuration and try again."
|
>&2 echo "Error: Failed to create git branch '$BRANCH_NAME'. Please check your git configuration and try again."
|
||||||
@@ -308,9 +323,14 @@ fi
|
|||||||
FEATURE_DIR="$SPECS_DIR/$BRANCH_NAME"
|
FEATURE_DIR="$SPECS_DIR/$BRANCH_NAME"
|
||||||
mkdir -p "$FEATURE_DIR"
|
mkdir -p "$FEATURE_DIR"
|
||||||
|
|
||||||
TEMPLATE=$(resolve_template "spec-template" "$REPO_ROOT")
|
TEMPLATE=$(resolve_template "spec-template" "$REPO_ROOT") || true
|
||||||
SPEC_FILE="$FEATURE_DIR/spec.md"
|
SPEC_FILE="$FEATURE_DIR/spec.md"
|
||||||
if [ -n "$TEMPLATE" ] && [ -f "$TEMPLATE" ]; then cp "$TEMPLATE" "$SPEC_FILE"; else touch "$SPEC_FILE"; fi
|
if [ -n "$TEMPLATE" ] && [ -f "$TEMPLATE" ]; then
|
||||||
|
cp "$TEMPLATE" "$SPEC_FILE"
|
||||||
|
else
|
||||||
|
echo "Warning: Spec template not found; created empty spec file" >&2
|
||||||
|
touch "$SPEC_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
# Inform the user how to persist the feature variable in their own shell
|
# Inform the user how to persist the feature variable in their own shell
|
||||||
printf '# To persist: export SPECIFY_FEATURE=%q\n' "$BRANCH_NAME" >&2
|
printf '# To persist: export SPECIFY_FEATURE=%q\n' "$BRANCH_NAME" >&2
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ check_feature_branch "$CURRENT_BRANCH" "$HAS_GIT" || exit 1
|
|||||||
mkdir -p "$FEATURE_DIR"
|
mkdir -p "$FEATURE_DIR"
|
||||||
|
|
||||||
# Copy plan template if it exists
|
# Copy plan template if it exists
|
||||||
TEMPLATE=$(resolve_template "plan-template" "$REPO_ROOT")
|
TEMPLATE=$(resolve_template "plan-template" "$REPO_ROOT") || true
|
||||||
if [[ -n "$TEMPLATE" ]] && [[ -f "$TEMPLATE" ]]; then
|
if [[ -n "$TEMPLATE" ]] && [[ -f "$TEMPLATE" ]]; then
|
||||||
cp "$TEMPLATE" "$IMPL_PLAN"
|
cp "$TEMPLATE" "$IMPL_PLAN"
|
||||||
echo "Copied plan template to $IMPL_PLAN"
|
echo "Copied plan template to $IMPL_PLAN"
|
||||||
|
|||||||
@@ -30,12 +30,12 @@
|
|||||||
#
|
#
|
||||||
# 5. Multi-Agent Support
|
# 5. Multi-Agent Support
|
||||||
# - Handles agent-specific file paths and naming conventions
|
# - 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, Tabnine CLI, Kiro CLI, Mistral Vibe, Kimi Code, Antigravity or Generic
|
# - Supports: Claude, Gemini, Copilot, Cursor, Qwen, opencode, Codex, Windsurf, Junie, Kilo Code, Auggie CLI, Roo Code, CodeBuddy CLI, Qoder CLI, Amp, SHAI, Tabnine CLI, Kiro CLI, Mistral Vibe, Kimi Code, Pi Coding Agent, iFlow CLI, Antigravity or Generic
|
||||||
# - Can update single agents or all existing agent files
|
# - Can update single agents or all existing agent files
|
||||||
# - Creates default Claude file if no agent files exist
|
# - Creates default Claude file if no agent files exist
|
||||||
#
|
#
|
||||||
# Usage: ./update-agent-context.sh [agent_type]
|
# Usage: ./update-agent-context.sh [agent_type]
|
||||||
# Agent types: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|roo|codebuddy|amp|shai|tabnine|kiro-cli|agy|bob|vibe|qodercli|kimi|generic
|
# Agent types: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|junie|kilocode|auggie|roo|codebuddy|amp|shai|tabnine|kiro-cli|agy|bob|vibe|qodercli|kimi|trae|pi|iflow|generic
|
||||||
# Leave empty to update all existing agent files
|
# Leave empty to update all existing agent files
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
@@ -68,12 +68,13 @@ CURSOR_FILE="$REPO_ROOT/.cursor/rules/specify-rules.mdc"
|
|||||||
QWEN_FILE="$REPO_ROOT/QWEN.md"
|
QWEN_FILE="$REPO_ROOT/QWEN.md"
|
||||||
AGENTS_FILE="$REPO_ROOT/AGENTS.md"
|
AGENTS_FILE="$REPO_ROOT/AGENTS.md"
|
||||||
WINDSURF_FILE="$REPO_ROOT/.windsurf/rules/specify-rules.md"
|
WINDSURF_FILE="$REPO_ROOT/.windsurf/rules/specify-rules.md"
|
||||||
|
JUNIE_FILE="$REPO_ROOT/.junie/AGENTS.md"
|
||||||
KILOCODE_FILE="$REPO_ROOT/.kilocode/rules/specify-rules.md"
|
KILOCODE_FILE="$REPO_ROOT/.kilocode/rules/specify-rules.md"
|
||||||
AUGGIE_FILE="$REPO_ROOT/.augment/rules/specify-rules.md"
|
AUGGIE_FILE="$REPO_ROOT/.augment/rules/specify-rules.md"
|
||||||
ROO_FILE="$REPO_ROOT/.roo/rules/specify-rules.md"
|
ROO_FILE="$REPO_ROOT/.roo/rules/specify-rules.md"
|
||||||
CODEBUDDY_FILE="$REPO_ROOT/CODEBUDDY.md"
|
CODEBUDDY_FILE="$REPO_ROOT/CODEBUDDY.md"
|
||||||
QODER_FILE="$REPO_ROOT/QODER.md"
|
QODER_FILE="$REPO_ROOT/QODER.md"
|
||||||
# AMP, Kiro CLI, and IBM Bob all share AGENTS.md — use AGENTS_FILE to avoid
|
# Amp, Kiro CLI, IBM Bob, and Pi all share AGENTS.md — use AGENTS_FILE to avoid
|
||||||
# updating the same file multiple times.
|
# updating the same file multiple times.
|
||||||
AMP_FILE="$AGENTS_FILE"
|
AMP_FILE="$AGENTS_FILE"
|
||||||
SHAI_FILE="$REPO_ROOT/SHAI.md"
|
SHAI_FILE="$REPO_ROOT/SHAI.md"
|
||||||
@@ -83,6 +84,8 @@ AGY_FILE="$REPO_ROOT/.agent/rules/specify-rules.md"
|
|||||||
BOB_FILE="$AGENTS_FILE"
|
BOB_FILE="$AGENTS_FILE"
|
||||||
VIBE_FILE="$REPO_ROOT/.vibe/agents/specify-agents.md"
|
VIBE_FILE="$REPO_ROOT/.vibe/agents/specify-agents.md"
|
||||||
KIMI_FILE="$REPO_ROOT/KIMI.md"
|
KIMI_FILE="$REPO_ROOT/KIMI.md"
|
||||||
|
TRAE_FILE="$REPO_ROOT/.trae/rules/AGENTS.md"
|
||||||
|
IFLOW_FILE="$REPO_ROOT/IFLOW.md"
|
||||||
|
|
||||||
# Template file
|
# Template file
|
||||||
TEMPLATE_FILE="$REPO_ROOT/.specify/templates/agent-file-template.md"
|
TEMPLATE_FILE="$REPO_ROOT/.specify/templates/agent-file-template.md"
|
||||||
@@ -636,6 +639,9 @@ update_specific_agent() {
|
|||||||
windsurf)
|
windsurf)
|
||||||
update_agent_file "$WINDSURF_FILE" "Windsurf" || return 1
|
update_agent_file "$WINDSURF_FILE" "Windsurf" || return 1
|
||||||
;;
|
;;
|
||||||
|
junie)
|
||||||
|
update_agent_file "$JUNIE_FILE" "Junie" || return 1
|
||||||
|
;;
|
||||||
kilocode)
|
kilocode)
|
||||||
update_agent_file "$KILOCODE_FILE" "Kilo Code" || return 1
|
update_agent_file "$KILOCODE_FILE" "Kilo Code" || return 1
|
||||||
;;
|
;;
|
||||||
@@ -675,26 +681,36 @@ update_specific_agent() {
|
|||||||
kimi)
|
kimi)
|
||||||
update_agent_file "$KIMI_FILE" "Kimi Code" || return 1
|
update_agent_file "$KIMI_FILE" "Kimi Code" || return 1
|
||||||
;;
|
;;
|
||||||
|
trae)
|
||||||
|
update_agent_file "$TRAE_FILE" "Trae" || return 1
|
||||||
|
;;
|
||||||
|
pi)
|
||||||
|
update_agent_file "$AGENTS_FILE" "Pi Coding Agent" || return 1
|
||||||
|
;;
|
||||||
|
iflow)
|
||||||
|
update_agent_file "$IFLOW_FILE" "iFlow CLI" || return 1
|
||||||
|
;;
|
||||||
generic)
|
generic)
|
||||||
log_info "Generic agent: no predefined context file. Use the agent-specific update script for your agent."
|
log_info "Generic agent: no predefined context file. Use the agent-specific update script for your agent."
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
log_error "Unknown agent type '$agent_type'"
|
log_error "Unknown agent type '$agent_type'"
|
||||||
log_error "Expected: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|roo|codebuddy|amp|shai|tabnine|kiro-cli|agy|bob|vibe|qodercli|kimi|generic"
|
log_error "Expected: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|junie|kilocode|auggie|roo|codebuddy|amp|shai|tabnine|kiro-cli|agy|bob|vibe|qodercli|kimi|trae|pi|iflow|generic"
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
update_all_existing_agents() {
|
# Helper: skip non-existent files and files already updated (dedup by
|
||||||
local found_agent=false
|
# realpath so that variables pointing to the same file — e.g. AMP_FILE,
|
||||||
local _updated_paths=()
|
# KIRO_FILE, BOB_FILE all resolving to AGENTS_FILE — are only written once).
|
||||||
|
# Uses a linear array instead of associative array for bash 3.2 compatibility.
|
||||||
|
# Note: defined at top level because bash 3.2 does not support true
|
||||||
|
# nested/local functions. _updated_paths, _found_agent, and _all_ok are
|
||||||
|
# initialised exclusively inside update_all_existing_agents so that
|
||||||
|
# sourcing this script has no side effects on the caller's environment.
|
||||||
|
|
||||||
# Helper: skip non-existent files and files already updated (dedup by
|
_update_if_new() {
|
||||||
# realpath so that variables pointing to the same file — e.g. AMP_FILE,
|
|
||||||
# KIRO_FILE, BOB_FILE all resolving to AGENTS_FILE — are only written once).
|
|
||||||
# Uses a linear array instead of associative array for bash 3.2 compatibility.
|
|
||||||
update_if_new() {
|
|
||||||
local file="$1" name="$2"
|
local file="$1" name="$2"
|
||||||
[[ -f "$file" ]] || return 0
|
[[ -f "$file" ]] || return 0
|
||||||
local real_path
|
local real_path
|
||||||
@@ -705,37 +721,50 @@ update_all_existing_agents() {
|
|||||||
[[ "$p" == "$real_path" ]] && return 0
|
[[ "$p" == "$real_path" ]] && return 0
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
update_agent_file "$file" "$name" || return 1
|
# Record the file as seen before attempting the update so that:
|
||||||
|
# (a) aliases pointing to the same path are not retried on failure
|
||||||
|
# (b) _found_agent reflects file existence, not update success
|
||||||
_updated_paths+=("$real_path")
|
_updated_paths+=("$real_path")
|
||||||
found_agent=true
|
_found_agent=true
|
||||||
}
|
update_agent_file "$file" "$name"
|
||||||
|
}
|
||||||
|
|
||||||
update_if_new "$CLAUDE_FILE" "Claude Code"
|
update_all_existing_agents() {
|
||||||
update_if_new "$GEMINI_FILE" "Gemini CLI"
|
_found_agent=false
|
||||||
update_if_new "$COPILOT_FILE" "GitHub Copilot"
|
_updated_paths=()
|
||||||
update_if_new "$CURSOR_FILE" "Cursor IDE"
|
local _all_ok=true
|
||||||
update_if_new "$QWEN_FILE" "Qwen Code"
|
|
||||||
update_if_new "$AGENTS_FILE" "Codex/opencode"
|
_update_if_new "$CLAUDE_FILE" "Claude Code" || _all_ok=false
|
||||||
update_if_new "$AMP_FILE" "Amp"
|
_update_if_new "$GEMINI_FILE" "Gemini CLI" || _all_ok=false
|
||||||
update_if_new "$KIRO_FILE" "Kiro CLI"
|
_update_if_new "$COPILOT_FILE" "GitHub Copilot" || _all_ok=false
|
||||||
update_if_new "$BOB_FILE" "IBM Bob"
|
_update_if_new "$CURSOR_FILE" "Cursor IDE" || _all_ok=false
|
||||||
update_if_new "$WINDSURF_FILE" "Windsurf"
|
_update_if_new "$QWEN_FILE" "Qwen Code" || _all_ok=false
|
||||||
update_if_new "$KILOCODE_FILE" "Kilo Code"
|
_update_if_new "$AGENTS_FILE" "Codex/opencode" || _all_ok=false
|
||||||
update_if_new "$AUGGIE_FILE" "Auggie CLI"
|
_update_if_new "$AMP_FILE" "Amp" || _all_ok=false
|
||||||
update_if_new "$ROO_FILE" "Roo Code"
|
_update_if_new "$KIRO_FILE" "Kiro CLI" || _all_ok=false
|
||||||
update_if_new "$CODEBUDDY_FILE" "CodeBuddy CLI"
|
_update_if_new "$BOB_FILE" "IBM Bob" || _all_ok=false
|
||||||
update_if_new "$SHAI_FILE" "SHAI"
|
_update_if_new "$WINDSURF_FILE" "Windsurf" || _all_ok=false
|
||||||
update_if_new "$TABNINE_FILE" "Tabnine CLI"
|
_update_if_new "$JUNIE_FILE" "Junie" || _all_ok=false
|
||||||
update_if_new "$QODER_FILE" "Qoder CLI"
|
_update_if_new "$KILOCODE_FILE" "Kilo Code" || _all_ok=false
|
||||||
update_if_new "$AGY_FILE" "Antigravity"
|
_update_if_new "$AUGGIE_FILE" "Auggie CLI" || _all_ok=false
|
||||||
update_if_new "$VIBE_FILE" "Mistral Vibe"
|
_update_if_new "$ROO_FILE" "Roo Code" || _all_ok=false
|
||||||
update_if_new "$KIMI_FILE" "Kimi Code"
|
_update_if_new "$CODEBUDDY_FILE" "CodeBuddy CLI" || _all_ok=false
|
||||||
|
_update_if_new "$SHAI_FILE" "SHAI" || _all_ok=false
|
||||||
|
_update_if_new "$TABNINE_FILE" "Tabnine CLI" || _all_ok=false
|
||||||
|
_update_if_new "$QODER_FILE" "Qoder CLI" || _all_ok=false
|
||||||
|
_update_if_new "$AGY_FILE" "Antigravity" || _all_ok=false
|
||||||
|
_update_if_new "$VIBE_FILE" "Mistral Vibe" || _all_ok=false
|
||||||
|
_update_if_new "$KIMI_FILE" "Kimi Code" || _all_ok=false
|
||||||
|
_update_if_new "$TRAE_FILE" "Trae" || _all_ok=false
|
||||||
|
_update_if_new "$IFLOW_FILE" "iFlow CLI" || _all_ok=false
|
||||||
|
|
||||||
# If no agent files exist, create a default Claude file
|
# If no agent files exist, create a default Claude file
|
||||||
if [[ "$found_agent" == false ]]; then
|
if [[ "$_found_agent" == false ]]; then
|
||||||
log_info "No existing agent files found, creating default Claude file..."
|
log_info "No existing agent files found, creating default Claude file..."
|
||||||
update_agent_file "$CLAUDE_FILE" "Claude Code" || return 1
|
update_agent_file "$CLAUDE_FILE" "Claude Code" || return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
[[ "$_all_ok" == true ]]
|
||||||
}
|
}
|
||||||
print_summary() {
|
print_summary() {
|
||||||
echo
|
echo
|
||||||
@@ -754,7 +783,7 @@ print_summary() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo
|
echo
|
||||||
log_info "Usage: $0 [claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|roo|codebuddy|amp|shai|tabnine|kiro-cli|agy|bob|vibe|qodercli|kimi|generic]"
|
log_info "Usage: $0 [claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|junie|kilocode|auggie|roo|codebuddy|amp|shai|tabnine|kiro-cli|agy|bob|vibe|qodercli|kimi|trae|pi|iflow|generic]"
|
||||||
}
|
}
|
||||||
|
|
||||||
#==============================================================================
|
#==============================================================================
|
||||||
|
|||||||
@@ -38,16 +38,27 @@ function Get-CurrentBranch {
|
|||||||
if (Test-Path $specsDir) {
|
if (Test-Path $specsDir) {
|
||||||
$latestFeature = ""
|
$latestFeature = ""
|
||||||
$highest = 0
|
$highest = 0
|
||||||
|
$latestTimestamp = ""
|
||||||
|
|
||||||
Get-ChildItem -Path $specsDir -Directory | ForEach-Object {
|
Get-ChildItem -Path $specsDir -Directory | ForEach-Object {
|
||||||
if ($_.Name -match '^(\d{3})-') {
|
if ($_.Name -match '^(\d{8}-\d{6})-') {
|
||||||
|
# Timestamp-based branch: compare lexicographically
|
||||||
|
$ts = $matches[1]
|
||||||
|
if ($ts -gt $latestTimestamp) {
|
||||||
|
$latestTimestamp = $ts
|
||||||
|
$latestFeature = $_.Name
|
||||||
|
}
|
||||||
|
} elseif ($_.Name -match '^(\d{3})-') {
|
||||||
$num = [int]$matches[1]
|
$num = [int]$matches[1]
|
||||||
if ($num -gt $highest) {
|
if ($num -gt $highest) {
|
||||||
$highest = $num
|
$highest = $num
|
||||||
|
# Only update if no timestamp branch found yet
|
||||||
|
if (-not $latestTimestamp) {
|
||||||
$latestFeature = $_.Name
|
$latestFeature = $_.Name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ($latestFeature) {
|
if ($latestFeature) {
|
||||||
return $latestFeature
|
return $latestFeature
|
||||||
@@ -79,9 +90,9 @@ function Test-FeatureBranch {
|
|||||||
return $true
|
return $true
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($Branch -notmatch '^[0-9]{3}-') {
|
if ($Branch -notmatch '^[0-9]{3}-' -and $Branch -notmatch '^\d{8}-\d{6}-') {
|
||||||
Write-Output "ERROR: Not on a feature branch. Current branch: $Branch"
|
Write-Output "ERROR: Not on a feature branch. Current branch: $Branch"
|
||||||
Write-Output "Feature branches should be named like: 001-feature-name"
|
Write-Output "Feature branches should be named like: 001-feature-name or 20260319-143022-feature-name"
|
||||||
return $false
|
return $false
|
||||||
}
|
}
|
||||||
return $true
|
return $true
|
||||||
|
|||||||
@@ -4,32 +4,36 @@
|
|||||||
param(
|
param(
|
||||||
[switch]$Json,
|
[switch]$Json,
|
||||||
[string]$ShortName,
|
[string]$ShortName,
|
||||||
|
[Parameter()]
|
||||||
[int]$Number = 0,
|
[int]$Number = 0,
|
||||||
|
[switch]$Timestamp,
|
||||||
[switch]$Help,
|
[switch]$Help,
|
||||||
[Parameter(ValueFromRemainingArguments = $true)]
|
[Parameter(Position = 0, ValueFromRemainingArguments = $true)]
|
||||||
[string[]]$FeatureDescription
|
[string[]]$FeatureDescription
|
||||||
)
|
)
|
||||||
$ErrorActionPreference = 'Stop'
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
# Show help if requested
|
# Show help if requested
|
||||||
if ($Help) {
|
if ($Help) {
|
||||||
Write-Host "Usage: ./create-new-feature.ps1 [-Json] [-ShortName <name>] [-Number N] <feature description>"
|
Write-Host "Usage: ./create-new-feature.ps1 [-Json] [-ShortName <name>] [-Number N] [-Timestamp] <feature description>"
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
Write-Host "Options:"
|
Write-Host "Options:"
|
||||||
Write-Host " -Json Output in JSON format"
|
Write-Host " -Json Output in JSON format"
|
||||||
Write-Host " -ShortName <name> Provide a custom short name (2-4 words) for the branch"
|
Write-Host " -ShortName <name> Provide a custom short name (2-4 words) for the branch"
|
||||||
Write-Host " -Number N Specify branch number manually (overrides auto-detection)"
|
Write-Host " -Number N Specify branch number manually (overrides auto-detection)"
|
||||||
|
Write-Host " -Timestamp Use timestamp prefix (YYYYMMDD-HHMMSS) instead of sequential numbering"
|
||||||
Write-Host " -Help Show this help message"
|
Write-Host " -Help Show this help message"
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
Write-Host "Examples:"
|
Write-Host "Examples:"
|
||||||
Write-Host " ./create-new-feature.ps1 'Add user authentication system' -ShortName 'user-auth'"
|
Write-Host " ./create-new-feature.ps1 'Add user authentication system' -ShortName 'user-auth'"
|
||||||
Write-Host " ./create-new-feature.ps1 'Implement OAuth2 integration for API'"
|
Write-Host " ./create-new-feature.ps1 'Implement OAuth2 integration for API'"
|
||||||
|
Write-Host " ./create-new-feature.ps1 -Timestamp -ShortName 'user-auth' 'Add user authentication'"
|
||||||
exit 0
|
exit 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check if feature description provided
|
# Check if feature description provided
|
||||||
if (-not $FeatureDescription -or $FeatureDescription.Count -eq 0) {
|
if (-not $FeatureDescription -or $FeatureDescription.Count -eq 0) {
|
||||||
Write-Error "Usage: ./create-new-feature.ps1 [-Json] [-ShortName <name>] <feature description>"
|
Write-Error "Usage: ./create-new-feature.ps1 [-Json] [-ShortName <name>] [-Number N] [-Timestamp] <feature description>"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,7 +75,7 @@ function Get-HighestNumberFromSpecs {
|
|||||||
$highest = 0
|
$highest = 0
|
||||||
if (Test-Path $SpecsDir) {
|
if (Test-Path $SpecsDir) {
|
||||||
Get-ChildItem -Path $SpecsDir -Directory | ForEach-Object {
|
Get-ChildItem -Path $SpecsDir -Directory | ForEach-Object {
|
||||||
if ($_.Name -match '^(\d+)') {
|
if ($_.Name -match '^(\d{3})-') {
|
||||||
$num = [int]$matches[1]
|
$num = [int]$matches[1]
|
||||||
if ($num -gt $highest) { $highest = $num }
|
if ($num -gt $highest) { $highest = $num }
|
||||||
}
|
}
|
||||||
@@ -92,7 +96,7 @@ function Get-HighestNumberFromBranches {
|
|||||||
$cleanBranch = $branch.Trim() -replace '^\*?\s+', '' -replace '^remotes/[^/]+/', ''
|
$cleanBranch = $branch.Trim() -replace '^\*?\s+', '' -replace '^remotes/[^/]+/', ''
|
||||||
|
|
||||||
# Extract feature number if branch matches pattern ###-*
|
# Extract feature number if branch matches pattern ###-*
|
||||||
if ($cleanBranch -match '^(\d+)-') {
|
if ($cleanBranch -match '^(\d{3})-') {
|
||||||
$num = [int]$matches[1]
|
$num = [int]$matches[1]
|
||||||
if ($num -gt $highest) { $highest = $num }
|
if ($num -gt $highest) { $highest = $num }
|
||||||
}
|
}
|
||||||
@@ -215,8 +219,19 @@ if ($ShortName) {
|
|||||||
$branchSuffix = Get-BranchName -Description $featureDesc
|
$branchSuffix = Get-BranchName -Description $featureDesc
|
||||||
}
|
}
|
||||||
|
|
||||||
# Determine branch number
|
# Warn if -Number and -Timestamp are both specified
|
||||||
if ($Number -eq 0) {
|
if ($Timestamp -and $Number -ne 0) {
|
||||||
|
Write-Warning "[specify] Warning: -Number is ignored when -Timestamp is used"
|
||||||
|
$Number = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Determine branch prefix
|
||||||
|
if ($Timestamp) {
|
||||||
|
$featureNum = Get-Date -Format 'yyyyMMdd-HHmmss'
|
||||||
|
$branchName = "$featureNum-$branchSuffix"
|
||||||
|
} else {
|
||||||
|
# Determine branch number
|
||||||
|
if ($Number -eq 0) {
|
||||||
if ($hasGit) {
|
if ($hasGit) {
|
||||||
# Check existing branches on remotes
|
# Check existing branches on remotes
|
||||||
$Number = Get-NextBranchNumber -SpecsDir $specsDir
|
$Number = Get-NextBranchNumber -SpecsDir $specsDir
|
||||||
@@ -224,18 +239,20 @@ if ($Number -eq 0) {
|
|||||||
# Fall back to local directory check
|
# Fall back to local directory check
|
||||||
$Number = (Get-HighestNumberFromSpecs -SpecsDir $specsDir) + 1
|
$Number = (Get-HighestNumberFromSpecs -SpecsDir $specsDir) + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$featureNum = ('{0:000}' -f $Number)
|
$featureNum = ('{0:000}' -f $Number)
|
||||||
$branchName = "$featureNum-$branchSuffix"
|
$branchName = "$featureNum-$branchSuffix"
|
||||||
|
}
|
||||||
|
|
||||||
# GitHub enforces a 244-byte limit on branch names
|
# GitHub enforces a 244-byte limit on branch names
|
||||||
# Validate and truncate if necessary
|
# Validate and truncate if necessary
|
||||||
$maxBranchLength = 244
|
$maxBranchLength = 244
|
||||||
if ($branchName.Length -gt $maxBranchLength) {
|
if ($branchName.Length -gt $maxBranchLength) {
|
||||||
# Calculate how much we need to trim from suffix
|
# Calculate how much we need to trim from suffix
|
||||||
# Account for: feature number (3) + hyphen (1) = 4 chars
|
# Account for prefix length: timestamp (15) + hyphen (1) = 16, or sequential (3) + hyphen (1) = 4
|
||||||
$maxSuffixLength = $maxBranchLength - 4
|
$prefixLength = $featureNum.Length + 1
|
||||||
|
$maxSuffixLength = $maxBranchLength - $prefixLength
|
||||||
|
|
||||||
# Truncate suffix
|
# Truncate suffix
|
||||||
$truncatedSuffix = $branchSuffix.Substring(0, [Math]::Min($branchSuffix.Length, $maxSuffixLength))
|
$truncatedSuffix = $branchSuffix.Substring(0, [Math]::Min($branchSuffix.Length, $maxSuffixLength))
|
||||||
@@ -265,7 +282,11 @@ if ($hasGit) {
|
|||||||
# Check if branch already exists
|
# Check if branch already exists
|
||||||
$existingBranch = git branch --list $branchName 2>$null
|
$existingBranch = git branch --list $branchName 2>$null
|
||||||
if ($existingBranch) {
|
if ($existingBranch) {
|
||||||
|
if ($Timestamp) {
|
||||||
|
Write-Error "Error: Branch '$branchName' already exists. Rerun to get a new timestamp or use a different -ShortName."
|
||||||
|
} else {
|
||||||
Write-Error "Error: Branch '$branchName' already exists. Please use a different feature name or specify a different number with -Number."
|
Write-Error "Error: Branch '$branchName' already exists. Please use a different feature name or specify a different number with -Number."
|
||||||
|
}
|
||||||
exit 1
|
exit 1
|
||||||
} else {
|
} else {
|
||||||
Write-Error "Error: Failed to create git branch '$branchName'. Please check your git configuration and try again."
|
Write-Error "Error: Failed to create git branch '$branchName'. Please check your git configuration and try again."
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ Mirrors the behavior of scripts/bash/update-agent-context.sh:
|
|||||||
2. Plan Data Extraction
|
2. Plan Data Extraction
|
||||||
3. Agent File Management (create from template or update existing)
|
3. Agent File Management (create from template or update existing)
|
||||||
4. Content Generation (technology stack, recent changes, timestamp)
|
4. Content Generation (technology stack, recent changes, timestamp)
|
||||||
5. Multi-Agent Support (claude, gemini, copilot, cursor-agent, qwen, opencode, codex, windsurf, kilocode, auggie, roo, codebuddy, amp, shai, tabnine, kiro-cli, agy, bob, vibe, qodercli, kimi, generic)
|
5. Multi-Agent Support (claude, gemini, copilot, cursor-agent, qwen, opencode, codex, windsurf, junie, kilocode, auggie, roo, codebuddy, amp, shai, tabnine, kiro-cli, agy, bob, vibe, qodercli, kimi, trae, pi, iflow, generic)
|
||||||
|
|
||||||
.PARAMETER AgentType
|
.PARAMETER AgentType
|
||||||
Optional agent key to update a single agent. If omitted, updates all existing agent files (creating a default Claude file if none exist).
|
Optional agent key to update a single agent. If omitted, updates all existing agent files (creating a default Claude file if none exist).
|
||||||
@@ -25,7 +25,7 @@ Relies on common helper functions in common.ps1
|
|||||||
#>
|
#>
|
||||||
param(
|
param(
|
||||||
[Parameter(Position=0)]
|
[Parameter(Position=0)]
|
||||||
[ValidateSet('claude','gemini','copilot','cursor-agent','qwen','opencode','codex','windsurf','kilocode','auggie','roo','codebuddy','amp','shai','tabnine','kiro-cli','agy','bob','qodercli','vibe','kimi','generic')]
|
[ValidateSet('claude','gemini','copilot','cursor-agent','qwen','opencode','codex','windsurf','junie','kilocode','auggie','roo','codebuddy','amp','shai','tabnine','kiro-cli','agy','bob','qodercli','vibe','kimi','trae','pi','iflow','generic')]
|
||||||
[string]$AgentType
|
[string]$AgentType
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -51,6 +51,7 @@ $CURSOR_FILE = Join-Path $REPO_ROOT '.cursor/rules/specify-rules.mdc'
|
|||||||
$QWEN_FILE = Join-Path $REPO_ROOT 'QWEN.md'
|
$QWEN_FILE = Join-Path $REPO_ROOT 'QWEN.md'
|
||||||
$AGENTS_FILE = Join-Path $REPO_ROOT 'AGENTS.md'
|
$AGENTS_FILE = Join-Path $REPO_ROOT 'AGENTS.md'
|
||||||
$WINDSURF_FILE = Join-Path $REPO_ROOT '.windsurf/rules/specify-rules.md'
|
$WINDSURF_FILE = Join-Path $REPO_ROOT '.windsurf/rules/specify-rules.md'
|
||||||
|
$JUNIE_FILE = Join-Path $REPO_ROOT '.junie/AGENTS.md'
|
||||||
$KILOCODE_FILE = Join-Path $REPO_ROOT '.kilocode/rules/specify-rules.md'
|
$KILOCODE_FILE = Join-Path $REPO_ROOT '.kilocode/rules/specify-rules.md'
|
||||||
$AUGGIE_FILE = Join-Path $REPO_ROOT '.augment/rules/specify-rules.md'
|
$AUGGIE_FILE = Join-Path $REPO_ROOT '.augment/rules/specify-rules.md'
|
||||||
$ROO_FILE = Join-Path $REPO_ROOT '.roo/rules/specify-rules.md'
|
$ROO_FILE = Join-Path $REPO_ROOT '.roo/rules/specify-rules.md'
|
||||||
@@ -64,6 +65,8 @@ $AGY_FILE = Join-Path $REPO_ROOT '.agent/rules/specify-rules.md'
|
|||||||
$BOB_FILE = Join-Path $REPO_ROOT 'AGENTS.md'
|
$BOB_FILE = Join-Path $REPO_ROOT 'AGENTS.md'
|
||||||
$VIBE_FILE = Join-Path $REPO_ROOT '.vibe/agents/specify-agents.md'
|
$VIBE_FILE = Join-Path $REPO_ROOT '.vibe/agents/specify-agents.md'
|
||||||
$KIMI_FILE = Join-Path $REPO_ROOT 'KIMI.md'
|
$KIMI_FILE = Join-Path $REPO_ROOT 'KIMI.md'
|
||||||
|
$TRAE_FILE = Join-Path $REPO_ROOT '.trae/rules/AGENTS.md'
|
||||||
|
$IFLOW_FILE = Join-Path $REPO_ROOT 'IFLOW.md'
|
||||||
|
|
||||||
$TEMPLATE_FILE = Join-Path $REPO_ROOT '.specify/templates/agent-file-template.md'
|
$TEMPLATE_FILE = Join-Path $REPO_ROOT '.specify/templates/agent-file-template.md'
|
||||||
|
|
||||||
@@ -395,6 +398,7 @@ function Update-SpecificAgent {
|
|||||||
'opencode' { Update-AgentFile -TargetFile $AGENTS_FILE -AgentName 'opencode' }
|
'opencode' { Update-AgentFile -TargetFile $AGENTS_FILE -AgentName 'opencode' }
|
||||||
'codex' { Update-AgentFile -TargetFile $AGENTS_FILE -AgentName 'Codex CLI' }
|
'codex' { Update-AgentFile -TargetFile $AGENTS_FILE -AgentName 'Codex CLI' }
|
||||||
'windsurf' { Update-AgentFile -TargetFile $WINDSURF_FILE -AgentName 'Windsurf' }
|
'windsurf' { Update-AgentFile -TargetFile $WINDSURF_FILE -AgentName 'Windsurf' }
|
||||||
|
'junie' { Update-AgentFile -TargetFile $JUNIE_FILE -AgentName 'Junie' }
|
||||||
'kilocode' { Update-AgentFile -TargetFile $KILOCODE_FILE -AgentName 'Kilo Code' }
|
'kilocode' { Update-AgentFile -TargetFile $KILOCODE_FILE -AgentName 'Kilo Code' }
|
||||||
'auggie' { Update-AgentFile -TargetFile $AUGGIE_FILE -AgentName 'Auggie CLI' }
|
'auggie' { Update-AgentFile -TargetFile $AUGGIE_FILE -AgentName 'Auggie CLI' }
|
||||||
'roo' { Update-AgentFile -TargetFile $ROO_FILE -AgentName 'Roo Code' }
|
'roo' { Update-AgentFile -TargetFile $ROO_FILE -AgentName 'Roo Code' }
|
||||||
@@ -408,8 +412,11 @@ function Update-SpecificAgent {
|
|||||||
'bob' { Update-AgentFile -TargetFile $BOB_FILE -AgentName 'IBM Bob' }
|
'bob' { Update-AgentFile -TargetFile $BOB_FILE -AgentName 'IBM Bob' }
|
||||||
'vibe' { Update-AgentFile -TargetFile $VIBE_FILE -AgentName 'Mistral Vibe' }
|
'vibe' { Update-AgentFile -TargetFile $VIBE_FILE -AgentName 'Mistral Vibe' }
|
||||||
'kimi' { Update-AgentFile -TargetFile $KIMI_FILE -AgentName 'Kimi Code' }
|
'kimi' { Update-AgentFile -TargetFile $KIMI_FILE -AgentName 'Kimi Code' }
|
||||||
|
'trae' { Update-AgentFile -TargetFile $TRAE_FILE -AgentName 'Trae' }
|
||||||
|
'pi' { Update-AgentFile -TargetFile $AGENTS_FILE -AgentName 'Pi Coding Agent' }
|
||||||
|
'iflow' { Update-AgentFile -TargetFile $IFLOW_FILE -AgentName 'iFlow CLI' }
|
||||||
'generic' { Write-Info 'Generic agent: no predefined context file. Use the agent-specific update script for your agent.' }
|
'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|tabnine|kiro-cli|agy|bob|vibe|qodercli|kimi|generic'; return $false }
|
default { Write-Err "Unknown agent type '$Type'"; Write-Err 'Expected: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|junie|kilocode|auggie|roo|codebuddy|amp|shai|tabnine|kiro-cli|agy|bob|vibe|qodercli|kimi|trae|pi|iflow|generic'; return $false }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -423,6 +430,7 @@ function Update-AllExistingAgents {
|
|||||||
if (Test-Path $QWEN_FILE) { if (-not (Update-AgentFile -TargetFile $QWEN_FILE -AgentName 'Qwen Code')) { $ok = $false }; $found = $true }
|
if (Test-Path $QWEN_FILE) { if (-not (Update-AgentFile -TargetFile $QWEN_FILE -AgentName 'Qwen Code')) { $ok = $false }; $found = $true }
|
||||||
if (Test-Path $AGENTS_FILE) { if (-not (Update-AgentFile -TargetFile $AGENTS_FILE -AgentName 'Codex/opencode')) { $ok = $false }; $found = $true }
|
if (Test-Path $AGENTS_FILE) { if (-not (Update-AgentFile -TargetFile $AGENTS_FILE -AgentName 'Codex/opencode')) { $ok = $false }; $found = $true }
|
||||||
if (Test-Path $WINDSURF_FILE) { if (-not (Update-AgentFile -TargetFile $WINDSURF_FILE -AgentName 'Windsurf')) { $ok = $false }; $found = $true }
|
if (Test-Path $WINDSURF_FILE) { if (-not (Update-AgentFile -TargetFile $WINDSURF_FILE -AgentName 'Windsurf')) { $ok = $false }; $found = $true }
|
||||||
|
if (Test-Path $JUNIE_FILE) { if (-not (Update-AgentFile -TargetFile $JUNIE_FILE -AgentName 'Junie')) { $ok = $false }; $found = $true }
|
||||||
if (Test-Path $KILOCODE_FILE) { if (-not (Update-AgentFile -TargetFile $KILOCODE_FILE -AgentName 'Kilo Code')) { $ok = $false }; $found = $true }
|
if (Test-Path $KILOCODE_FILE) { if (-not (Update-AgentFile -TargetFile $KILOCODE_FILE -AgentName 'Kilo Code')) { $ok = $false }; $found = $true }
|
||||||
if (Test-Path $AUGGIE_FILE) { if (-not (Update-AgentFile -TargetFile $AUGGIE_FILE -AgentName 'Auggie CLI')) { $ok = $false }; $found = $true }
|
if (Test-Path $AUGGIE_FILE) { if (-not (Update-AgentFile -TargetFile $AUGGIE_FILE -AgentName 'Auggie CLI')) { $ok = $false }; $found = $true }
|
||||||
if (Test-Path $ROO_FILE) { if (-not (Update-AgentFile -TargetFile $ROO_FILE -AgentName 'Roo Code')) { $ok = $false }; $found = $true }
|
if (Test-Path $ROO_FILE) { if (-not (Update-AgentFile -TargetFile $ROO_FILE -AgentName 'Roo Code')) { $ok = $false }; $found = $true }
|
||||||
@@ -435,6 +443,8 @@ function Update-AllExistingAgents {
|
|||||||
if (Test-Path $BOB_FILE) { if (-not (Update-AgentFile -TargetFile $BOB_FILE -AgentName 'IBM Bob')) { $ok = $false }; $found = $true }
|
if (Test-Path $BOB_FILE) { if (-not (Update-AgentFile -TargetFile $BOB_FILE -AgentName 'IBM Bob')) { $ok = $false }; $found = $true }
|
||||||
if (Test-Path $VIBE_FILE) { if (-not (Update-AgentFile -TargetFile $VIBE_FILE -AgentName 'Mistral Vibe')) { $ok = $false }; $found = $true }
|
if (Test-Path $VIBE_FILE) { if (-not (Update-AgentFile -TargetFile $VIBE_FILE -AgentName 'Mistral Vibe')) { $ok = $false }; $found = $true }
|
||||||
if (Test-Path $KIMI_FILE) { if (-not (Update-AgentFile -TargetFile $KIMI_FILE -AgentName 'Kimi Code')) { $ok = $false }; $found = $true }
|
if (Test-Path $KIMI_FILE) { if (-not (Update-AgentFile -TargetFile $KIMI_FILE -AgentName 'Kimi Code')) { $ok = $false }; $found = $true }
|
||||||
|
if (Test-Path $TRAE_FILE) { if (-not (Update-AgentFile -TargetFile $TRAE_FILE -AgentName 'Trae')) { $ok = $false }; $found = $true }
|
||||||
|
if (Test-Path $IFLOW_FILE) { if (-not (Update-AgentFile -TargetFile $IFLOW_FILE -AgentName 'iFlow CLI')) { $ok = $false }; $found = $true }
|
||||||
if (-not $found) {
|
if (-not $found) {
|
||||||
Write-Info 'No existing agent files found, creating default Claude file...'
|
Write-Info 'No existing agent files found, creating default Claude file...'
|
||||||
if (-not (Update-AgentFile -TargetFile $CLAUDE_FILE -AgentName 'Claude Code')) { $ok = $false }
|
if (-not (Update-AgentFile -TargetFile $CLAUDE_FILE -AgentName 'Claude Code')) { $ok = $false }
|
||||||
@@ -449,7 +459,7 @@ function Print-Summary {
|
|||||||
if ($NEW_FRAMEWORK) { Write-Host " - Added framework: $NEW_FRAMEWORK" }
|
if ($NEW_FRAMEWORK) { Write-Host " - Added framework: $NEW_FRAMEWORK" }
|
||||||
if ($NEW_DB -and $NEW_DB -ne 'N/A') { Write-Host " - Added database: $NEW_DB" }
|
if ($NEW_DB -and $NEW_DB -ne 'N/A') { Write-Host " - Added database: $NEW_DB" }
|
||||||
Write-Host ''
|
Write-Host ''
|
||||||
Write-Info 'Usage: ./update-agent-context.ps1 [-AgentType claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|roo|codebuddy|amp|shai|tabnine|kiro-cli|agy|bob|vibe|qodercli|generic]'
|
Write-Info 'Usage: ./update-agent-context.ps1 [-AgentType claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|junie|kilocode|auggie|roo|codebuddy|amp|shai|tabnine|kiro-cli|agy|bob|vibe|qodercli|kimi|trae|pi|iflow|generic]'
|
||||||
}
|
}
|
||||||
|
|
||||||
function Main {
|
function Main {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
849
src/specify_cli/agent_pack.py
Normal file
849
src/specify_cli/agent_pack.py
Normal file
@@ -0,0 +1,849 @@
|
|||||||
|
"""
|
||||||
|
Agent Pack Manager for Spec Kit
|
||||||
|
|
||||||
|
Implements self-bootstrapping agent packs with declarative manifests
|
||||||
|
(speckit-agent.yml) and Python bootstrap modules (bootstrap.py).
|
||||||
|
|
||||||
|
Agent packs resolve by priority:
|
||||||
|
1. User-level (~/.specify/agents/<id>/)
|
||||||
|
2. Project-level (.specify/agents/<id>/)
|
||||||
|
3. Catalog-installed (downloaded via `specify agent add`)
|
||||||
|
4. Embedded in wheel (official packs under core_pack/agents/)
|
||||||
|
|
||||||
|
The embedded packs ship inside the pip wheel so that
|
||||||
|
`pip install specify-cli && specify init --ai claude` works offline.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import importlib.util
|
||||||
|
import json
|
||||||
|
import shutil
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
from platformdirs import user_data_path
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Manifest schema
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
MANIFEST_FILENAME = "speckit-agent.yml"
|
||||||
|
BOOTSTRAP_FILENAME = "bootstrap.py"
|
||||||
|
|
||||||
|
MANIFEST_SCHEMA_VERSION = "1.0"
|
||||||
|
|
||||||
|
# Required top-level keys
|
||||||
|
_REQUIRED_TOP_KEYS = {"schema_version", "agent"}
|
||||||
|
|
||||||
|
# Required keys within the ``agent`` block
|
||||||
|
_REQUIRED_AGENT_KEYS = {"id", "name", "version"}
|
||||||
|
|
||||||
|
|
||||||
|
class AgentPackError(Exception):
|
||||||
|
"""Base exception for agent-pack operations."""
|
||||||
|
|
||||||
|
|
||||||
|
class ManifestValidationError(AgentPackError):
|
||||||
|
"""Raised when a speckit-agent.yml file is invalid."""
|
||||||
|
|
||||||
|
|
||||||
|
class PackResolutionError(AgentPackError):
|
||||||
|
"""Raised when no pack can be found for the requested agent id."""
|
||||||
|
|
||||||
|
|
||||||
|
class AgentFileModifiedError(AgentPackError):
|
||||||
|
"""Raised when teardown finds user-modified files and ``--force`` is not set."""
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Manifest
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class AgentManifest:
|
||||||
|
"""Parsed and validated representation of a speckit-agent.yml file."""
|
||||||
|
|
||||||
|
# identity
|
||||||
|
id: str
|
||||||
|
name: str
|
||||||
|
version: str
|
||||||
|
description: str = ""
|
||||||
|
author: str = ""
|
||||||
|
license: str = ""
|
||||||
|
|
||||||
|
# runtime
|
||||||
|
requires_cli: bool = False
|
||||||
|
install_url: Optional[str] = None
|
||||||
|
cli_tool: Optional[str] = None
|
||||||
|
|
||||||
|
# compatibility
|
||||||
|
speckit_version: str = ">=0.1.0"
|
||||||
|
|
||||||
|
# discovery
|
||||||
|
tags: List[str] = field(default_factory=list)
|
||||||
|
|
||||||
|
# command registration metadata (used by CommandRegistrar / extensions)
|
||||||
|
commands_dir: str = ""
|
||||||
|
command_format: str = "markdown"
|
||||||
|
arg_placeholder: str = "$ARGUMENTS"
|
||||||
|
file_extension: str = ".md"
|
||||||
|
|
||||||
|
# raw data for anything else
|
||||||
|
raw: Dict[str, Any] = field(default_factory=dict, repr=False)
|
||||||
|
|
||||||
|
# filesystem path to the pack directory that produced this manifest
|
||||||
|
pack_path: Optional[Path] = field(default=None, repr=False)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_yaml(cls, path: Path) -> "AgentManifest":
|
||||||
|
"""Load and validate a manifest from *path*.
|
||||||
|
|
||||||
|
Raises ``ManifestValidationError`` on structural problems.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
text = path.read_text(encoding="utf-8")
|
||||||
|
data = yaml.safe_load(text) or {}
|
||||||
|
except yaml.YAMLError as exc:
|
||||||
|
raise ManifestValidationError(f"Invalid YAML in {path}: {exc}")
|
||||||
|
except FileNotFoundError:
|
||||||
|
raise ManifestValidationError(f"Manifest not found: {path}")
|
||||||
|
|
||||||
|
return cls.from_dict(data, pack_path=path.parent)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, data: dict, *, pack_path: Optional[Path] = None) -> "AgentManifest":
|
||||||
|
"""Build a manifest from a raw dictionary."""
|
||||||
|
if not isinstance(data, dict):
|
||||||
|
raise ManifestValidationError("Manifest must be a YAML mapping")
|
||||||
|
|
||||||
|
missing_top = _REQUIRED_TOP_KEYS - set(data)
|
||||||
|
if missing_top:
|
||||||
|
raise ManifestValidationError(
|
||||||
|
f"Missing required top-level key(s): {', '.join(sorted(missing_top))}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if data.get("schema_version") != MANIFEST_SCHEMA_VERSION:
|
||||||
|
raise ManifestValidationError(
|
||||||
|
f"Unsupported schema_version: {data.get('schema_version')!r} "
|
||||||
|
f"(expected {MANIFEST_SCHEMA_VERSION!r})"
|
||||||
|
)
|
||||||
|
|
||||||
|
agent_block = data.get("agent")
|
||||||
|
if not isinstance(agent_block, dict):
|
||||||
|
raise ManifestValidationError("'agent' must be a mapping")
|
||||||
|
|
||||||
|
missing_agent = _REQUIRED_AGENT_KEYS - set(agent_block)
|
||||||
|
if missing_agent:
|
||||||
|
raise ManifestValidationError(
|
||||||
|
f"Missing required agent key(s): {', '.join(sorted(missing_agent))}"
|
||||||
|
)
|
||||||
|
|
||||||
|
runtime = data.get("runtime") or {}
|
||||||
|
requires = data.get("requires") or {}
|
||||||
|
tags = data.get("tags") or []
|
||||||
|
cmd_reg = data.get("command_registration") or {}
|
||||||
|
|
||||||
|
return cls(
|
||||||
|
id=str(agent_block["id"]),
|
||||||
|
name=str(agent_block["name"]),
|
||||||
|
version=str(agent_block["version"]),
|
||||||
|
description=str(agent_block.get("description", "")),
|
||||||
|
author=str(agent_block.get("author", "")),
|
||||||
|
license=str(agent_block.get("license", "")),
|
||||||
|
requires_cli=bool(runtime.get("requires_cli", False)),
|
||||||
|
install_url=runtime.get("install_url"),
|
||||||
|
cli_tool=runtime.get("cli_tool"),
|
||||||
|
speckit_version=str(requires.get("speckit_version", ">=0.1.0")),
|
||||||
|
tags=[str(t) for t in tags] if isinstance(tags, list) else [],
|
||||||
|
commands_dir=str(cmd_reg.get("commands_dir", "")),
|
||||||
|
command_format=str(cmd_reg.get("format", "markdown")),
|
||||||
|
arg_placeholder=str(cmd_reg.get("arg_placeholder", "$ARGUMENTS")),
|
||||||
|
file_extension=str(cmd_reg.get("file_extension", ".md")),
|
||||||
|
raw=data,
|
||||||
|
pack_path=pack_path,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Bootstrap base class
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class AgentBootstrap:
|
||||||
|
"""Base class that every agent pack's ``bootstrap.py`` must subclass.
|
||||||
|
|
||||||
|
Subclasses override :meth:`setup` and :meth:`teardown` to define
|
||||||
|
agent-specific lifecycle operations.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, manifest: AgentManifest):
|
||||||
|
self.manifest = manifest
|
||||||
|
self.pack_path = manifest.pack_path
|
||||||
|
|
||||||
|
# -- lifecycle -----------------------------------------------------------
|
||||||
|
|
||||||
|
def setup(self, project_path: Path, script_type: str, options: Dict[str, Any]) -> List[Path]:
|
||||||
|
"""Install agent files into *project_path*.
|
||||||
|
|
||||||
|
This is invoked by ``specify init --ai <agent>`` and
|
||||||
|
``specify agent switch <agent>``.
|
||||||
|
|
||||||
|
Implementations **must** return every file they create so that the
|
||||||
|
CLI can record both agent-installed files and extension-installed
|
||||||
|
files in a single install manifest.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
project_path: Target project directory.
|
||||||
|
script_type: ``"sh"`` or ``"ps"``.
|
||||||
|
options: Arbitrary key/value options forwarded from the CLI.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of absolute paths of files created during setup.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def teardown(
|
||||||
|
self,
|
||||||
|
project_path: Path,
|
||||||
|
*,
|
||||||
|
force: bool = False,
|
||||||
|
files: Optional[Dict[str, str]] = None,
|
||||||
|
) -> List[str]:
|
||||||
|
"""Remove agent-specific files from *project_path*.
|
||||||
|
|
||||||
|
Invoked by ``specify agent switch`` (for the *old* agent) and
|
||||||
|
``specify agent remove`` when the user explicitly uninstalls.
|
||||||
|
Must preserve shared infrastructure (specs, plans, tasks, etc.).
|
||||||
|
|
||||||
|
Only individual files are removed — directories are **never**
|
||||||
|
deleted.
|
||||||
|
|
||||||
|
The caller (CLI) is expected to check for user-modified files
|
||||||
|
**before** invoking teardown and prompt for confirmation. If
|
||||||
|
*files* is provided, exactly those files are removed (values are
|
||||||
|
ignored but kept for forward compatibility). Otherwise the
|
||||||
|
install manifest is read.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
project_path: Project directory to clean up.
|
||||||
|
force: When ``True``, remove files even if they were modified
|
||||||
|
after installation.
|
||||||
|
files: Mapping of project-relative path → SHA-256 hash.
|
||||||
|
When supplied, only these files are removed and the
|
||||||
|
install manifest is not consulted.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of project-relative paths that were actually deleted.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
# -- helpers available to subclasses ------------------------------------
|
||||||
|
|
||||||
|
def agent_dir(self, project_path: Path) -> Path:
|
||||||
|
"""Return the agent's top-level directory inside the project."""
|
||||||
|
return project_path / self.manifest.commands_dir.split("/")[0]
|
||||||
|
|
||||||
|
def collect_installed_files(self, project_path: Path) -> List[Path]:
|
||||||
|
"""Return every file under the agent's directory tree.
|
||||||
|
|
||||||
|
Subclasses should call this at the end of :meth:`setup` to build
|
||||||
|
the return list. Any files present in the agent directory at
|
||||||
|
that point — whether created by ``setup()`` itself, by the
|
||||||
|
scaffold pipeline, or by a preceding step — are reported.
|
||||||
|
"""
|
||||||
|
root = self.agent_dir(project_path)
|
||||||
|
if not root.is_dir():
|
||||||
|
return []
|
||||||
|
return sorted(p for p in root.rglob("*") if p.is_file())
|
||||||
|
|
||||||
|
def _scaffold_project(
|
||||||
|
self,
|
||||||
|
project_path: Path,
|
||||||
|
script_type: str,
|
||||||
|
is_current_dir: bool = False,
|
||||||
|
) -> List[Path]:
|
||||||
|
"""Run the shared scaffolding pipeline and return new files.
|
||||||
|
|
||||||
|
Calls ``scaffold_from_core_pack`` for this agent and then
|
||||||
|
collects every file that was created. Subclasses should call
|
||||||
|
this from :meth:`setup` when they want to use the shared
|
||||||
|
scaffolding rather than creating files manually.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of absolute paths of **all** files created by the
|
||||||
|
scaffold (agent-specific commands, shared scripts,
|
||||||
|
templates, etc.).
|
||||||
|
"""
|
||||||
|
# Lazy import to avoid circular dependency (agent_pack is
|
||||||
|
# imported by specify_cli.__init__).
|
||||||
|
from specify_cli import scaffold_from_core_pack
|
||||||
|
|
||||||
|
# Snapshot existing files
|
||||||
|
before: set[Path] = set()
|
||||||
|
if project_path.exists():
|
||||||
|
before = {p for p in project_path.rglob("*") if p.is_file()}
|
||||||
|
|
||||||
|
ok = scaffold_from_core_pack(
|
||||||
|
project_path, self.manifest.id, script_type, is_current_dir,
|
||||||
|
)
|
||||||
|
if not ok:
|
||||||
|
raise AgentPackError(
|
||||||
|
f"Scaffolding failed for agent '{self.manifest.id}'")
|
||||||
|
|
||||||
|
# Collect every new file
|
||||||
|
after = {p for p in project_path.rglob("*") if p.is_file()}
|
||||||
|
return sorted(after - before)
|
||||||
|
|
||||||
|
def finalize_setup(
|
||||||
|
self,
|
||||||
|
project_path: Path,
|
||||||
|
agent_files: Optional[List[Path]] = None,
|
||||||
|
extension_files: Optional[List[Path]] = None,
|
||||||
|
) -> None:
|
||||||
|
"""Record installed files for tracked teardown.
|
||||||
|
|
||||||
|
This must be called **after** the full init pipeline has finished
|
||||||
|
writing files (commands, context files, extensions) into the
|
||||||
|
project. It combines the files reported by :meth:`setup` with
|
||||||
|
any extra files (e.g. from extension registration), scans the
|
||||||
|
agent's directory tree for anything additional, and writes the
|
||||||
|
install manifest.
|
||||||
|
|
||||||
|
``setup()`` may return *all* files created by the shared
|
||||||
|
scaffolding (including shared project files in ``.specify/``).
|
||||||
|
Only files under the agent's own directory tree are recorded as
|
||||||
|
``agent_files`` — shared project infrastructure is not tracked
|
||||||
|
per-agent and will not be removed during teardown.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
agent_files: Files reported by :meth:`setup`.
|
||||||
|
extension_files: Files created by extension registration.
|
||||||
|
"""
|
||||||
|
all_extension = list(extension_files or [])
|
||||||
|
|
||||||
|
# Filter agent_files: only keep files under the agent's directory
|
||||||
|
# tree. setup() returns *all* scaffolded files (including shared
|
||||||
|
# project infrastructure in .specify/) but only agent-owned files
|
||||||
|
# should be tracked per-agent — shared files are not removed
|
||||||
|
# during teardown/switch.
|
||||||
|
agent_root = self.agent_dir(project_path)
|
||||||
|
agent_root_resolved = agent_root.resolve()
|
||||||
|
all_agent: List[Path] = []
|
||||||
|
for p in (agent_files or []):
|
||||||
|
try:
|
||||||
|
p.resolve().relative_to(agent_root_resolved)
|
||||||
|
all_agent.append(p)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Scan the agent's directory tree for files created by later
|
||||||
|
# init pipeline steps (skills, presets, extensions) that
|
||||||
|
# setup() did not report. We scan the agent root directory
|
||||||
|
# (e.g. .claude/) so we catch both commands and skills
|
||||||
|
# directories (skills-migrated agents replace the commands
|
||||||
|
# directory with a sibling skills directory during init).
|
||||||
|
if self.manifest.commands_dir:
|
||||||
|
agent_root = self.agent_dir(project_path)
|
||||||
|
if agent_root.is_dir():
|
||||||
|
agent_set = {p.resolve() for p in all_agent}
|
||||||
|
for p in agent_root.rglob("*"):
|
||||||
|
if p.is_file() and p.resolve() not in agent_set:
|
||||||
|
all_agent.append(p)
|
||||||
|
agent_set.add(p.resolve())
|
||||||
|
|
||||||
|
record_installed_files(
|
||||||
|
project_path,
|
||||||
|
self.manifest.id,
|
||||||
|
agent_files=all_agent,
|
||||||
|
extension_files=all_extension,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Installed-file tracking
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _manifest_path(project_path: Path, agent_id: str) -> Path:
|
||||||
|
"""Return the path to the install manifest for *agent_id*."""
|
||||||
|
return project_path / ".specify" / f"agent-manifest-{agent_id}.json"
|
||||||
|
|
||||||
|
|
||||||
|
def _sha256(path: Path) -> str:
|
||||||
|
"""Return the hex SHA-256 of a file."""
|
||||||
|
h = hashlib.sha256()
|
||||||
|
with open(path, "rb") as f:
|
||||||
|
for chunk in iter(lambda: f.read(8192), b""):
|
||||||
|
h.update(chunk)
|
||||||
|
return h.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def _hash_file_list(
|
||||||
|
project_path: Path,
|
||||||
|
files: List[Path],
|
||||||
|
) -> Dict[str, str]:
|
||||||
|
"""Build a {relative_path: sha256} dict from a list of file paths."""
|
||||||
|
entries: Dict[str, str] = {}
|
||||||
|
for file_path in files:
|
||||||
|
abs_path = project_path / file_path if not file_path.is_absolute() else file_path
|
||||||
|
if abs_path.is_file():
|
||||||
|
rel = str(abs_path.relative_to(project_path))
|
||||||
|
entries[rel] = _sha256(abs_path)
|
||||||
|
return entries
|
||||||
|
|
||||||
|
|
||||||
|
def record_installed_files(
|
||||||
|
project_path: Path,
|
||||||
|
agent_id: str,
|
||||||
|
agent_files: Optional[List[Path]] = None,
|
||||||
|
extension_files: Optional[List[Path]] = None,
|
||||||
|
) -> Path:
|
||||||
|
"""Record the installed files and their SHA-256 hashes.
|
||||||
|
|
||||||
|
Writes ``.specify/agent-manifest-<agent_id>.json`` containing
|
||||||
|
categorised mappings of project-relative paths to SHA-256 digests.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
project_path: Project root directory.
|
||||||
|
agent_id: Agent identifier.
|
||||||
|
agent_files: Files created by the agent's ``setup()`` and the
|
||||||
|
init pipeline (core commands / templates).
|
||||||
|
extension_files: Files created by extension registration.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Path to the written manifest file.
|
||||||
|
"""
|
||||||
|
agent_entries = _hash_file_list(project_path, agent_files or [])
|
||||||
|
extension_entries = _hash_file_list(project_path, extension_files or [])
|
||||||
|
|
||||||
|
manifest_file = _manifest_path(project_path, agent_id)
|
||||||
|
manifest_file.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
manifest_file.write_text(
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"agent_id": agent_id,
|
||||||
|
"agent_files": agent_entries,
|
||||||
|
"extension_files": extension_entries,
|
||||||
|
},
|
||||||
|
indent=2,
|
||||||
|
),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
return manifest_file
|
||||||
|
|
||||||
|
|
||||||
|
def _all_tracked_entries(data: dict) -> Dict[str, str]:
|
||||||
|
"""Return the combined file → hash mapping from a manifest dict.
|
||||||
|
|
||||||
|
Supports both the new categorised layout (``agent_files`` +
|
||||||
|
``extension_files``) and the legacy flat ``files`` key.
|
||||||
|
"""
|
||||||
|
combined: Dict[str, str] = {}
|
||||||
|
# Legacy flat format
|
||||||
|
if "files" in data and isinstance(data["files"], dict):
|
||||||
|
combined.update(data["files"])
|
||||||
|
# New categorised format
|
||||||
|
if "agent_files" in data and isinstance(data["agent_files"], dict):
|
||||||
|
combined.update(data["agent_files"])
|
||||||
|
if "extension_files" in data and isinstance(data["extension_files"], dict):
|
||||||
|
combined.update(data["extension_files"])
|
||||||
|
return combined
|
||||||
|
|
||||||
|
|
||||||
|
def get_tracked_files(
|
||||||
|
project_path: Path,
|
||||||
|
agent_id: str,
|
||||||
|
) -> tuple[Dict[str, str], Dict[str, str]]:
|
||||||
|
"""Return the tracked file hashes split by source.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A tuple ``(agent_files, extension_files)`` where each is a
|
||||||
|
``{relative_path: sha256}`` dict. Returns two empty dicts
|
||||||
|
when no install manifest exists.
|
||||||
|
"""
|
||||||
|
manifest_file = _manifest_path(project_path, agent_id)
|
||||||
|
if not manifest_file.is_file():
|
||||||
|
return {}, {}
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = json.loads(manifest_file.read_text(encoding="utf-8"))
|
||||||
|
except (json.JSONDecodeError, OSError):
|
||||||
|
return {}, {}
|
||||||
|
|
||||||
|
# Support legacy flat format
|
||||||
|
if "files" in data and "agent_files" not in data:
|
||||||
|
return dict(data["files"]), {}
|
||||||
|
|
||||||
|
agent_entries = data.get("agent_files", {})
|
||||||
|
ext_entries = data.get("extension_files", {})
|
||||||
|
return dict(agent_entries), dict(ext_entries)
|
||||||
|
|
||||||
|
|
||||||
|
def check_modified_files(
|
||||||
|
project_path: Path,
|
||||||
|
agent_id: str,
|
||||||
|
) -> List[str]:
|
||||||
|
"""Return project-relative paths of files modified since installation.
|
||||||
|
|
||||||
|
Returns an empty list when no install manifest exists or when every
|
||||||
|
tracked file still has its original hash.
|
||||||
|
"""
|
||||||
|
manifest_file = _manifest_path(project_path, agent_id)
|
||||||
|
if not manifest_file.is_file():
|
||||||
|
return []
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = json.loads(manifest_file.read_text(encoding="utf-8"))
|
||||||
|
except (json.JSONDecodeError, OSError):
|
||||||
|
return []
|
||||||
|
|
||||||
|
entries = _all_tracked_entries(data)
|
||||||
|
|
||||||
|
modified: List[str] = []
|
||||||
|
for rel_path, original_hash in entries.items():
|
||||||
|
abs_path = project_path / rel_path
|
||||||
|
if abs_path.is_file():
|
||||||
|
if _sha256(abs_path) != original_hash:
|
||||||
|
modified.append(rel_path)
|
||||||
|
# If the file was deleted by the user, treat it as not needing
|
||||||
|
# removal — skip rather than flag as modified.
|
||||||
|
|
||||||
|
return modified
|
||||||
|
|
||||||
|
|
||||||
|
def remove_tracked_files(
|
||||||
|
project_path: Path,
|
||||||
|
agent_id: str,
|
||||||
|
*,
|
||||||
|
force: bool = False,
|
||||||
|
files: Optional[Dict[str, str]] = None,
|
||||||
|
) -> List[str]:
|
||||||
|
"""Remove individual tracked files.
|
||||||
|
|
||||||
|
If *files* is provided, exactly those files are removed (the values
|
||||||
|
are ignored but accepted for forward compatibility). Otherwise the
|
||||||
|
install manifest for *agent_id* is read.
|
||||||
|
|
||||||
|
Raises :class:`AgentFileModifiedError` if any tracked file was
|
||||||
|
modified and *force* is ``False`` (only when reading from the
|
||||||
|
manifest — callers that pass *files* are expected to have already
|
||||||
|
prompted the user).
|
||||||
|
|
||||||
|
Directories are **never** deleted — only individual files.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
project_path: Project root directory.
|
||||||
|
agent_id: Agent identifier.
|
||||||
|
force: When ``True``, delete even modified files.
|
||||||
|
files: Explicit mapping of project-relative path → hash. When
|
||||||
|
supplied, the install manifest is not consulted.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of project-relative paths that were removed.
|
||||||
|
"""
|
||||||
|
manifest_file = _manifest_path(project_path, agent_id)
|
||||||
|
|
||||||
|
if files is not None:
|
||||||
|
entries = files
|
||||||
|
else:
|
||||||
|
if not manifest_file.is_file():
|
||||||
|
return []
|
||||||
|
try:
|
||||||
|
data = json.loads(manifest_file.read_text(encoding="utf-8"))
|
||||||
|
except (json.JSONDecodeError, OSError):
|
||||||
|
return []
|
||||||
|
|
||||||
|
entries = _all_tracked_entries(data)
|
||||||
|
if not entries:
|
||||||
|
manifest_file.unlink(missing_ok=True)
|
||||||
|
return []
|
||||||
|
|
||||||
|
if not force:
|
||||||
|
modified = check_modified_files(project_path, agent_id)
|
||||||
|
if modified:
|
||||||
|
raise AgentFileModifiedError(
|
||||||
|
f"The following agent files have been modified since installation:\n"
|
||||||
|
+ "\n".join(f" {p}" for p in modified)
|
||||||
|
+ "\nUse --force to remove them anyway."
|
||||||
|
)
|
||||||
|
|
||||||
|
removed: List[str] = []
|
||||||
|
for rel_path in entries:
|
||||||
|
abs_path = project_path / rel_path
|
||||||
|
if abs_path.is_file():
|
||||||
|
abs_path.unlink()
|
||||||
|
removed.append(rel_path)
|
||||||
|
|
||||||
|
# Clean up the install manifest itself
|
||||||
|
if manifest_file.is_file():
|
||||||
|
manifest_file.unlink(missing_ok=True)
|
||||||
|
return removed
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Pack resolution
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _embedded_agents_dir() -> Path:
|
||||||
|
"""Return the path to the embedded agent packs inside the wheel."""
|
||||||
|
return Path(__file__).parent / "core_pack" / "agents"
|
||||||
|
|
||||||
|
|
||||||
|
def _user_agents_dir() -> Path:
|
||||||
|
"""Return the user-level agent overrides directory."""
|
||||||
|
return user_data_path("specify", "github") / "agents"
|
||||||
|
|
||||||
|
|
||||||
|
def _project_agents_dir(project_path: Path) -> Path:
|
||||||
|
"""Return the project-level agent overrides directory."""
|
||||||
|
return project_path / ".specify" / "agents"
|
||||||
|
|
||||||
|
|
||||||
|
def _catalog_agents_dir() -> Path:
|
||||||
|
"""Return the catalog-installed agent cache directory."""
|
||||||
|
return user_data_path("specify", "github") / "agent-cache"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ResolvedPack:
|
||||||
|
"""Result of resolving an agent pack through the priority stack."""
|
||||||
|
manifest: AgentManifest
|
||||||
|
source: str # "user", "project", "catalog", "embedded"
|
||||||
|
path: Path
|
||||||
|
overrides: Optional[str] = None # version of the pack being overridden
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_agent_pack(
|
||||||
|
agent_id: str,
|
||||||
|
project_path: Optional[Path] = None,
|
||||||
|
) -> ResolvedPack:
|
||||||
|
"""Resolve an agent pack through the priority stack.
|
||||||
|
|
||||||
|
Priority (highest first):
|
||||||
|
1. User-level ``~/.specify/agents/<id>/``
|
||||||
|
2. Project-level ``.specify/agents/<id>/``
|
||||||
|
3. Catalog-installed cache
|
||||||
|
4. Embedded in wheel
|
||||||
|
|
||||||
|
Raises ``PackResolutionError`` when no pack is found at any level.
|
||||||
|
"""
|
||||||
|
candidates: List[tuple[str, Path]] = []
|
||||||
|
|
||||||
|
# Priority 1 — user level
|
||||||
|
user_dir = _user_agents_dir() / agent_id
|
||||||
|
candidates.append(("user", user_dir))
|
||||||
|
|
||||||
|
# Priority 2 — project level
|
||||||
|
if project_path is not None:
|
||||||
|
proj_dir = _project_agents_dir(project_path) / agent_id
|
||||||
|
candidates.append(("project", proj_dir))
|
||||||
|
|
||||||
|
# Priority 3 — catalog cache
|
||||||
|
catalog_dir = _catalog_agents_dir() / agent_id
|
||||||
|
candidates.append(("catalog", catalog_dir))
|
||||||
|
|
||||||
|
# Priority 4 — embedded
|
||||||
|
embedded_dir = _embedded_agents_dir() / agent_id
|
||||||
|
candidates.append(("embedded", embedded_dir))
|
||||||
|
|
||||||
|
embedded_manifest: Optional[AgentManifest] = None
|
||||||
|
|
||||||
|
for source, pack_dir in candidates:
|
||||||
|
manifest_file = pack_dir / MANIFEST_FILENAME
|
||||||
|
if manifest_file.is_file():
|
||||||
|
manifest = AgentManifest.from_yaml(manifest_file)
|
||||||
|
if source == "embedded":
|
||||||
|
embedded_manifest = manifest
|
||||||
|
|
||||||
|
overrides = None
|
||||||
|
if source != "embedded" and embedded_manifest is None:
|
||||||
|
# Try loading embedded to record what it overrides
|
||||||
|
emb_file = _embedded_agents_dir() / agent_id / MANIFEST_FILENAME
|
||||||
|
if emb_file.is_file():
|
||||||
|
try:
|
||||||
|
emb = AgentManifest.from_yaml(emb_file)
|
||||||
|
overrides = f"embedded v{emb.version}"
|
||||||
|
except AgentPackError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return ResolvedPack(
|
||||||
|
manifest=manifest,
|
||||||
|
source=source,
|
||||||
|
path=pack_dir,
|
||||||
|
overrides=overrides,
|
||||||
|
)
|
||||||
|
|
||||||
|
raise PackResolutionError(
|
||||||
|
f"Agent '{agent_id}' not found locally or in any active catalog.\n"
|
||||||
|
f"Run 'specify agent search' to browse available agents, or\n"
|
||||||
|
f"'specify agent add {agent_id} --from <path>' for offline install."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Pack discovery helpers
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def list_embedded_agents() -> List[AgentManifest]:
|
||||||
|
"""Return manifests for all agent packs embedded in the wheel."""
|
||||||
|
agents_dir = _embedded_agents_dir()
|
||||||
|
if not agents_dir.is_dir():
|
||||||
|
return []
|
||||||
|
|
||||||
|
manifests: List[AgentManifest] = []
|
||||||
|
for child in sorted(agents_dir.iterdir()):
|
||||||
|
manifest_file = child / MANIFEST_FILENAME
|
||||||
|
if child.is_dir() and manifest_file.is_file():
|
||||||
|
try:
|
||||||
|
manifests.append(AgentManifest.from_yaml(manifest_file))
|
||||||
|
except AgentPackError:
|
||||||
|
continue
|
||||||
|
return manifests
|
||||||
|
|
||||||
|
|
||||||
|
def list_all_agents(project_path: Optional[Path] = None) -> List[ResolvedPack]:
|
||||||
|
"""List all available agents, resolved through the priority stack.
|
||||||
|
|
||||||
|
Each agent id appears at most once, at its highest-priority source.
|
||||||
|
"""
|
||||||
|
seen: dict[str, ResolvedPack] = {}
|
||||||
|
|
||||||
|
# Start from lowest priority (embedded) so higher priorities overwrite
|
||||||
|
for manifest in list_embedded_agents():
|
||||||
|
seen[manifest.id] = ResolvedPack(
|
||||||
|
manifest=manifest,
|
||||||
|
source="embedded",
|
||||||
|
path=manifest.pack_path or _embedded_agents_dir() / manifest.id,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Catalog cache
|
||||||
|
catalog_dir = _catalog_agents_dir()
|
||||||
|
if catalog_dir.is_dir():
|
||||||
|
for child in sorted(catalog_dir.iterdir()):
|
||||||
|
mf = child / MANIFEST_FILENAME
|
||||||
|
if child.is_dir() and mf.is_file():
|
||||||
|
try:
|
||||||
|
m = AgentManifest.from_yaml(mf)
|
||||||
|
overrides = f"embedded v{seen[m.id].manifest.version}" if m.id in seen else None
|
||||||
|
seen[m.id] = ResolvedPack(manifest=m, source="catalog", path=child, overrides=overrides)
|
||||||
|
except AgentPackError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Project-level
|
||||||
|
if project_path is not None:
|
||||||
|
proj_dir = _project_agents_dir(project_path)
|
||||||
|
if proj_dir.is_dir():
|
||||||
|
for child in sorted(proj_dir.iterdir()):
|
||||||
|
mf = child / MANIFEST_FILENAME
|
||||||
|
if child.is_dir() and mf.is_file():
|
||||||
|
try:
|
||||||
|
m = AgentManifest.from_yaml(mf)
|
||||||
|
overrides = f"embedded v{seen[m.id].manifest.version}" if m.id in seen else None
|
||||||
|
seen[m.id] = ResolvedPack(manifest=m, source="project", path=child, overrides=overrides)
|
||||||
|
except AgentPackError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# User-level
|
||||||
|
user_dir = _user_agents_dir()
|
||||||
|
if user_dir.is_dir():
|
||||||
|
for child in sorted(user_dir.iterdir()):
|
||||||
|
mf = child / MANIFEST_FILENAME
|
||||||
|
if child.is_dir() and mf.is_file():
|
||||||
|
try:
|
||||||
|
m = AgentManifest.from_yaml(mf)
|
||||||
|
overrides = f"embedded v{seen[m.id].manifest.version}" if m.id in seen else None
|
||||||
|
seen[m.id] = ResolvedPack(manifest=m, source="user", path=child, overrides=overrides)
|
||||||
|
except AgentPackError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
return sorted(seen.values(), key=lambda r: r.manifest.id)
|
||||||
|
|
||||||
|
|
||||||
|
def load_bootstrap(pack_path: Path, manifest: AgentManifest) -> AgentBootstrap:
|
||||||
|
"""Import ``bootstrap.py`` from *pack_path* and return the bootstrap instance.
|
||||||
|
|
||||||
|
The bootstrap module must define exactly one public subclass of
|
||||||
|
``AgentBootstrap``. That class is instantiated with *manifest* and
|
||||||
|
returned.
|
||||||
|
"""
|
||||||
|
bootstrap_file = pack_path / BOOTSTRAP_FILENAME
|
||||||
|
if not bootstrap_file.is_file():
|
||||||
|
raise AgentPackError(
|
||||||
|
f"Bootstrap module not found: {bootstrap_file}"
|
||||||
|
)
|
||||||
|
|
||||||
|
spec = importlib.util.spec_from_file_location(
|
||||||
|
f"speckit_agent_{manifest.id}_bootstrap", bootstrap_file
|
||||||
|
)
|
||||||
|
if spec is None or spec.loader is None:
|
||||||
|
raise AgentPackError(f"Cannot load bootstrap module: {bootstrap_file}")
|
||||||
|
|
||||||
|
module = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(module)
|
||||||
|
|
||||||
|
# Find the AgentBootstrap subclass
|
||||||
|
candidates = [
|
||||||
|
obj
|
||||||
|
for name, obj in vars(module).items()
|
||||||
|
if (
|
||||||
|
isinstance(obj, type)
|
||||||
|
and issubclass(obj, AgentBootstrap)
|
||||||
|
and obj is not AgentBootstrap
|
||||||
|
and not name.startswith("_")
|
||||||
|
)
|
||||||
|
]
|
||||||
|
if not candidates:
|
||||||
|
raise AgentPackError(
|
||||||
|
f"No AgentBootstrap subclass found in {bootstrap_file}"
|
||||||
|
)
|
||||||
|
if len(candidates) > 1:
|
||||||
|
raise AgentPackError(
|
||||||
|
f"Multiple AgentBootstrap subclasses in {bootstrap_file}: "
|
||||||
|
f"{[c.__name__ for c in candidates]}"
|
||||||
|
)
|
||||||
|
|
||||||
|
return candidates[0](manifest)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_pack(pack_path: Path) -> List[str]:
|
||||||
|
"""Validate a pack directory structure and return a list of warnings.
|
||||||
|
|
||||||
|
Returns an empty list when the pack is fully valid.
|
||||||
|
Raises ``ManifestValidationError`` on hard errors.
|
||||||
|
"""
|
||||||
|
warnings: List[str] = []
|
||||||
|
manifest_file = pack_path / MANIFEST_FILENAME
|
||||||
|
|
||||||
|
if not manifest_file.is_file():
|
||||||
|
raise ManifestValidationError(
|
||||||
|
f"Missing {MANIFEST_FILENAME} in {pack_path}"
|
||||||
|
)
|
||||||
|
|
||||||
|
manifest = AgentManifest.from_yaml(manifest_file)
|
||||||
|
|
||||||
|
bootstrap_file = pack_path / BOOTSTRAP_FILENAME
|
||||||
|
if not bootstrap_file.is_file():
|
||||||
|
warnings.append(f"Missing {BOOTSTRAP_FILENAME} (pack cannot be bootstrapped)")
|
||||||
|
|
||||||
|
if not manifest.commands_dir:
|
||||||
|
warnings.append("command_registration.commands_dir not set in manifest")
|
||||||
|
|
||||||
|
if not manifest.description:
|
||||||
|
warnings.append("agent.description is empty")
|
||||||
|
|
||||||
|
if not manifest.tags:
|
||||||
|
warnings.append("No tags specified (reduces discoverability)")
|
||||||
|
|
||||||
|
return warnings
|
||||||
|
|
||||||
|
|
||||||
|
def export_pack(agent_id: str, dest: Path, project_path: Optional[Path] = None) -> Path:
|
||||||
|
"""Export the active pack for *agent_id* to *dest*.
|
||||||
|
|
||||||
|
Returns the path to the exported pack directory.
|
||||||
|
"""
|
||||||
|
resolved = resolve_agent_pack(agent_id, project_path=project_path)
|
||||||
|
dest.mkdir(parents=True, exist_ok=True)
|
||||||
|
shutil.copytree(resolved.path, dest, dirs_exist_ok=True)
|
||||||
|
return dest
|
||||||
@@ -9,6 +9,7 @@ command files into agent-specific directories in the correct format.
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, List, Any
|
from typing import Dict, List, Any
|
||||||
|
|
||||||
|
import platform
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
|
|
||||||
@@ -59,13 +60,19 @@ class CommandRegistrar:
|
|||||||
"extension": ".md"
|
"extension": ".md"
|
||||||
},
|
},
|
||||||
"codex": {
|
"codex": {
|
||||||
"dir": ".codex/prompts",
|
"dir": ".agents/skills",
|
||||||
|
"format": "markdown",
|
||||||
|
"args": "$ARGUMENTS",
|
||||||
|
"extension": "/SKILL.md",
|
||||||
|
},
|
||||||
|
"windsurf": {
|
||||||
|
"dir": ".windsurf/workflows",
|
||||||
"format": "markdown",
|
"format": "markdown",
|
||||||
"args": "$ARGUMENTS",
|
"args": "$ARGUMENTS",
|
||||||
"extension": ".md"
|
"extension": ".md"
|
||||||
},
|
},
|
||||||
"windsurf": {
|
"junie": {
|
||||||
"dir": ".windsurf/workflows",
|
"dir": ".junie/commands",
|
||||||
"format": "markdown",
|
"format": "markdown",
|
||||||
"args": "$ARGUMENTS",
|
"args": "$ARGUMENTS",
|
||||||
"extension": ".md"
|
"extension": ".md"
|
||||||
@@ -106,6 +113,12 @@ class CommandRegistrar:
|
|||||||
"args": "$ARGUMENTS",
|
"args": "$ARGUMENTS",
|
||||||
"extension": ".md"
|
"extension": ".md"
|
||||||
},
|
},
|
||||||
|
"pi": {
|
||||||
|
"dir": ".pi/prompts",
|
||||||
|
"format": "markdown",
|
||||||
|
"args": "$ARGUMENTS",
|
||||||
|
"extension": ".md"
|
||||||
|
},
|
||||||
"amp": {
|
"amp": {
|
||||||
"dir": ".agents/commands",
|
"dir": ".agents/commands",
|
||||||
"format": "markdown",
|
"format": "markdown",
|
||||||
@@ -134,7 +147,19 @@ class CommandRegistrar:
|
|||||||
"dir": ".kimi/skills",
|
"dir": ".kimi/skills",
|
||||||
"format": "markdown",
|
"format": "markdown",
|
||||||
"args": "$ARGUMENTS",
|
"args": "$ARGUMENTS",
|
||||||
"extension": "/SKILL.md"
|
"extension": "/SKILL.md",
|
||||||
|
},
|
||||||
|
"trae": {
|
||||||
|
"dir": ".trae/rules",
|
||||||
|
"format": "markdown",
|
||||||
|
"args": "$ARGUMENTS",
|
||||||
|
"extension": ".md"
|
||||||
|
},
|
||||||
|
"iflow": {
|
||||||
|
"dir": ".iflow/commands",
|
||||||
|
"format": "markdown",
|
||||||
|
"args": "$ARGUMENTS",
|
||||||
|
"extension": ".md"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,6 +189,9 @@ class CommandRegistrar:
|
|||||||
except yaml.YAMLError:
|
except yaml.YAMLError:
|
||||||
frontmatter = {}
|
frontmatter = {}
|
||||||
|
|
||||||
|
if not isinstance(frontmatter, dict):
|
||||||
|
frontmatter = {}
|
||||||
|
|
||||||
return frontmatter, body
|
return frontmatter, body
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -191,11 +219,14 @@ class CommandRegistrar:
|
|||||||
Returns:
|
Returns:
|
||||||
Modified frontmatter with adjusted paths
|
Modified frontmatter with adjusted paths
|
||||||
"""
|
"""
|
||||||
if "scripts" in frontmatter:
|
for script_key in ("scripts", "agent_scripts"):
|
||||||
for key in frontmatter["scripts"]:
|
scripts = frontmatter.get(script_key)
|
||||||
script_path = frontmatter["scripts"][key]
|
if not isinstance(scripts, dict):
|
||||||
if script_path.startswith("../../scripts/"):
|
continue
|
||||||
frontmatter["scripts"][key] = f".specify/scripts/{script_path[14:]}"
|
|
||||||
|
for key, script_path in scripts.items():
|
||||||
|
if isinstance(script_path, str) and script_path.startswith("../../scripts/"):
|
||||||
|
scripts[key] = f".specify/scripts/{script_path[14:]}"
|
||||||
return frontmatter
|
return frontmatter
|
||||||
|
|
||||||
def render_markdown_command(
|
def render_markdown_command(
|
||||||
@@ -252,6 +283,101 @@ class CommandRegistrar:
|
|||||||
|
|
||||||
return "\n".join(toml_lines)
|
return "\n".join(toml_lines)
|
||||||
|
|
||||||
|
def render_skill_command(
|
||||||
|
self,
|
||||||
|
agent_name: str,
|
||||||
|
skill_name: str,
|
||||||
|
frontmatter: dict,
|
||||||
|
body: str,
|
||||||
|
source_id: str,
|
||||||
|
source_file: str,
|
||||||
|
project_root: Path,
|
||||||
|
) -> str:
|
||||||
|
"""Render a command override as a SKILL.md file.
|
||||||
|
|
||||||
|
SKILL-target agents should receive the same skills-oriented
|
||||||
|
frontmatter shape used elsewhere in the project instead of the
|
||||||
|
original command frontmatter.
|
||||||
|
|
||||||
|
Technical debt note:
|
||||||
|
Spec-kit currently has multiple SKILL.md generators (template packaging,
|
||||||
|
init-time conversion, and extension/preset overrides). Keep the skill
|
||||||
|
frontmatter keys aligned (name/description/compatibility/metadata, with
|
||||||
|
metadata.author and metadata.source subkeys) to avoid drift across agents.
|
||||||
|
"""
|
||||||
|
if not isinstance(frontmatter, dict):
|
||||||
|
frontmatter = {}
|
||||||
|
|
||||||
|
if agent_name == "codex":
|
||||||
|
body = self._resolve_codex_skill_placeholders(frontmatter, body, project_root)
|
||||||
|
|
||||||
|
description = frontmatter.get("description", f"Spec-kit workflow command: {skill_name}")
|
||||||
|
skill_frontmatter = {
|
||||||
|
"name": skill_name,
|
||||||
|
"description": description,
|
||||||
|
"compatibility": "Requires spec-kit project structure with .specify/ directory",
|
||||||
|
"metadata": {
|
||||||
|
"author": "github-spec-kit",
|
||||||
|
"source": f"{source_id}:{source_file}",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return self.render_frontmatter(skill_frontmatter) + "\n" + body
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _resolve_codex_skill_placeholders(frontmatter: dict, body: str, project_root: Path) -> str:
|
||||||
|
"""Resolve script placeholders for Codex skill overrides.
|
||||||
|
|
||||||
|
This intentionally scopes the fix to Codex, which is the newly
|
||||||
|
migrated runtime path in this PR. Existing Kimi behavior is left
|
||||||
|
unchanged for now.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
from . import load_init_options
|
||||||
|
except ImportError:
|
||||||
|
return body
|
||||||
|
|
||||||
|
if not isinstance(frontmatter, dict):
|
||||||
|
frontmatter = {}
|
||||||
|
|
||||||
|
scripts = frontmatter.get("scripts", {}) or {}
|
||||||
|
agent_scripts = frontmatter.get("agent_scripts", {}) or {}
|
||||||
|
if not isinstance(scripts, dict):
|
||||||
|
scripts = {}
|
||||||
|
if not isinstance(agent_scripts, dict):
|
||||||
|
agent_scripts = {}
|
||||||
|
|
||||||
|
script_variant = load_init_options(project_root).get("script")
|
||||||
|
if script_variant not in {"sh", "ps"}:
|
||||||
|
fallback_order = []
|
||||||
|
default_variant = "ps" if platform.system().lower().startswith("win") else "sh"
|
||||||
|
secondary_variant = "sh" if default_variant == "ps" else "ps"
|
||||||
|
|
||||||
|
if default_variant in scripts or default_variant in agent_scripts:
|
||||||
|
fallback_order.append(default_variant)
|
||||||
|
if secondary_variant in scripts or secondary_variant in agent_scripts:
|
||||||
|
fallback_order.append(secondary_variant)
|
||||||
|
|
||||||
|
for key in scripts:
|
||||||
|
if key not in fallback_order:
|
||||||
|
fallback_order.append(key)
|
||||||
|
for key in agent_scripts:
|
||||||
|
if key not in fallback_order:
|
||||||
|
fallback_order.append(key)
|
||||||
|
|
||||||
|
script_variant = fallback_order[0] if fallback_order else None
|
||||||
|
|
||||||
|
script_command = scripts.get(script_variant) if script_variant else None
|
||||||
|
if script_command:
|
||||||
|
script_command = script_command.replace("{ARGS}", "$ARGUMENTS")
|
||||||
|
body = body.replace("{SCRIPT}", script_command)
|
||||||
|
|
||||||
|
agent_script_command = agent_scripts.get(script_variant) if script_variant else None
|
||||||
|
if agent_script_command:
|
||||||
|
agent_script_command = agent_script_command.replace("{ARGS}", "$ARGUMENTS")
|
||||||
|
body = body.replace("{AGENT_SCRIPT}", agent_script_command)
|
||||||
|
|
||||||
|
return body.replace("{ARGS}", "$ARGUMENTS").replace("__AGENT__", "codex")
|
||||||
|
|
||||||
def _convert_argument_placeholder(self, content: str, from_placeholder: str, to_placeholder: str) -> str:
|
def _convert_argument_placeholder(self, content: str, from_placeholder: str, to_placeholder: str) -> str:
|
||||||
"""Convert argument placeholder format.
|
"""Convert argument placeholder format.
|
||||||
|
|
||||||
@@ -265,6 +391,18 @@ class CommandRegistrar:
|
|||||||
"""
|
"""
|
||||||
return content.replace(from_placeholder, to_placeholder)
|
return content.replace(from_placeholder, to_placeholder)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _compute_output_name(agent_name: str, cmd_name: str, agent_config: Dict[str, Any]) -> str:
|
||||||
|
"""Compute the on-disk command or skill name for an agent."""
|
||||||
|
if agent_config["extension"] != "/SKILL.md":
|
||||||
|
return cmd_name
|
||||||
|
|
||||||
|
short_name = cmd_name
|
||||||
|
if short_name.startswith("speckit."):
|
||||||
|
short_name = short_name[len("speckit."):]
|
||||||
|
|
||||||
|
return f"speckit.{short_name}" if agent_name == "kimi" else f"speckit-{short_name}"
|
||||||
|
|
||||||
def register_commands(
|
def register_commands(
|
||||||
self,
|
self,
|
||||||
agent_name: str,
|
agent_name: str,
|
||||||
@@ -316,14 +454,20 @@ class CommandRegistrar:
|
|||||||
body, "$ARGUMENTS", agent_config["args"]
|
body, "$ARGUMENTS", agent_config["args"]
|
||||||
)
|
)
|
||||||
|
|
||||||
if agent_config["format"] == "markdown":
|
output_name = self._compute_output_name(agent_name, cmd_name, agent_config)
|
||||||
|
|
||||||
|
if agent_config["extension"] == "/SKILL.md":
|
||||||
|
output = self.render_skill_command(
|
||||||
|
agent_name, output_name, frontmatter, body, source_id, cmd_file, project_root
|
||||||
|
)
|
||||||
|
elif agent_config["format"] == "markdown":
|
||||||
output = self.render_markdown_command(frontmatter, body, source_id, context_note)
|
output = self.render_markdown_command(frontmatter, body, source_id, context_note)
|
||||||
elif agent_config["format"] == "toml":
|
elif agent_config["format"] == "toml":
|
||||||
output = self.render_toml_command(frontmatter, body, source_id)
|
output = self.render_toml_command(frontmatter, body, source_id)
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Unsupported format: {agent_config['format']}")
|
raise ValueError(f"Unsupported format: {agent_config['format']}")
|
||||||
|
|
||||||
dest_file = commands_dir / f"{cmd_name}{agent_config['extension']}"
|
dest_file = commands_dir / f"{output_name}{agent_config['extension']}"
|
||||||
dest_file.parent.mkdir(parents=True, exist_ok=True)
|
dest_file.parent.mkdir(parents=True, exist_ok=True)
|
||||||
dest_file.write_text(output, encoding="utf-8")
|
dest_file.write_text(output, encoding="utf-8")
|
||||||
|
|
||||||
@@ -333,9 +477,15 @@ class CommandRegistrar:
|
|||||||
registered.append(cmd_name)
|
registered.append(cmd_name)
|
||||||
|
|
||||||
for alias in cmd_info.get("aliases", []):
|
for alias in cmd_info.get("aliases", []):
|
||||||
alias_file = commands_dir / f"{alias}{agent_config['extension']}"
|
alias_output_name = self._compute_output_name(agent_name, alias, agent_config)
|
||||||
|
alias_output = output
|
||||||
|
if agent_config["extension"] == "/SKILL.md":
|
||||||
|
alias_output = self.render_skill_command(
|
||||||
|
agent_name, alias_output_name, frontmatter, body, source_id, cmd_file, project_root
|
||||||
|
)
|
||||||
|
alias_file = commands_dir / f"{alias_output_name}{agent_config['extension']}"
|
||||||
alias_file.parent.mkdir(parents=True, exist_ok=True)
|
alias_file.parent.mkdir(parents=True, exist_ok=True)
|
||||||
alias_file.write_text(output, encoding="utf-8")
|
alias_file.write_text(alias_output, encoding="utf-8")
|
||||||
if agent_name == "copilot":
|
if agent_name == "copilot":
|
||||||
self.write_copilot_prompt(project_root, alias)
|
self.write_copilot_prompt(project_root, alias)
|
||||||
registered.append(alias)
|
registered.append(alias)
|
||||||
@@ -378,7 +528,7 @@ class CommandRegistrar:
|
|||||||
results = {}
|
results = {}
|
||||||
|
|
||||||
for agent_name, agent_config in self.AGENT_CONFIGS.items():
|
for agent_name, agent_config in self.AGENT_CONFIGS.items():
|
||||||
agent_dir = project_root / agent_config["dir"].split("/")[0]
|
agent_dir = project_root / agent_config["dir"]
|
||||||
|
|
||||||
if agent_dir.exists():
|
if agent_dir.exists():
|
||||||
try:
|
try:
|
||||||
@@ -412,7 +562,8 @@ class CommandRegistrar:
|
|||||||
commands_dir = project_root / agent_config["dir"]
|
commands_dir = project_root / agent_config["dir"]
|
||||||
|
|
||||||
for cmd_name in cmd_names:
|
for cmd_name in cmd_names:
|
||||||
cmd_file = commands_dir / f"{cmd_name}{agent_config['extension']}"
|
output_name = self._compute_output_name(agent_name, cmd_name, agent_config)
|
||||||
|
cmd_file = commands_dir / f"{output_name}{agent_config['extension']}"
|
||||||
if cmd_file.exists():
|
if cmd_file.exists():
|
||||||
cmd_file.unlink()
|
cmd_file.unlink()
|
||||||
|
|
||||||
|
|||||||
0
src/specify_cli/core_pack/agents/__init__.py
Normal file
0
src/specify_cli/core_pack/agents/__init__.py
Normal file
0
src/specify_cli/core_pack/agents/agy/__init__.py
Normal file
0
src/specify_cli/core_pack/agents/agy/__init__.py
Normal file
30
src/specify_cli/core_pack/agents/agy/bootstrap.py
Normal file
30
src/specify_cli/core_pack/agents/agy/bootstrap.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
"""Bootstrap module for Antigravity agent pack."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
|
from specify_cli.agent_pack import AgentBootstrap, remove_tracked_files
|
||||||
|
|
||||||
|
|
||||||
|
class Agy(AgentBootstrap):
|
||||||
|
"""Bootstrap for Antigravity."""
|
||||||
|
|
||||||
|
AGENT_DIR = ".agent"
|
||||||
|
COMMANDS_SUBDIR = "commands"
|
||||||
|
|
||||||
|
def setup(self, project_path: Path, script_type: str, options: Dict[str, Any]) -> List[Path]:
|
||||||
|
"""Install Antigravity agent files into the project."""
|
||||||
|
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||||
|
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
return self._scaffold_project(project_path, script_type)
|
||||||
|
|
||||||
|
def teardown(self, project_path: Path, *, force: bool = False, files: Optional[Dict[str, str]] = None) -> List[str]:
|
||||||
|
"""Remove Antigravity agent files from the project.
|
||||||
|
|
||||||
|
Only removes individual tracked files — directories are never
|
||||||
|
deleted. When *files* is provided, exactly those files are
|
||||||
|
removed. Otherwise the install manifest is consulted and
|
||||||
|
``AgentFileModifiedError`` is raised if any tracked file was
|
||||||
|
modified and *force* is ``False``.
|
||||||
|
"""
|
||||||
|
return remove_tracked_files(project_path, self.manifest.id, force=force, files=files)
|
||||||
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"
|
||||||
0
src/specify_cli/core_pack/agents/amp/__init__.py
Normal file
0
src/specify_cli/core_pack/agents/amp/__init__.py
Normal file
30
src/specify_cli/core_pack/agents/amp/bootstrap.py
Normal file
30
src/specify_cli/core_pack/agents/amp/bootstrap.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
"""Bootstrap module for Amp agent pack."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
|
from specify_cli.agent_pack import AgentBootstrap, remove_tracked_files
|
||||||
|
|
||||||
|
|
||||||
|
class Amp(AgentBootstrap):
|
||||||
|
"""Bootstrap for Amp."""
|
||||||
|
|
||||||
|
AGENT_DIR = ".agents"
|
||||||
|
COMMANDS_SUBDIR = "commands"
|
||||||
|
|
||||||
|
def setup(self, project_path: Path, script_type: str, options: Dict[str, Any]) -> List[Path]:
|
||||||
|
"""Install Amp agent files into the project."""
|
||||||
|
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||||
|
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
return self._scaffold_project(project_path, script_type)
|
||||||
|
|
||||||
|
def teardown(self, project_path: Path, *, force: bool = False, files: Optional[Dict[str, str]] = None) -> List[str]:
|
||||||
|
"""Remove Amp agent files from the project.
|
||||||
|
|
||||||
|
Only removes individual tracked files — directories are never
|
||||||
|
deleted. When *files* is provided, exactly those files are
|
||||||
|
removed. Otherwise the install manifest is consulted and
|
||||||
|
``AgentFileModifiedError`` is raised if any tracked file was
|
||||||
|
modified and *force* is ``False``.
|
||||||
|
"""
|
||||||
|
return remove_tracked_files(project_path, self.manifest.id, force=force, files=files)
|
||||||
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"
|
||||||
0
src/specify_cli/core_pack/agents/auggie/__init__.py
Normal file
0
src/specify_cli/core_pack/agents/auggie/__init__.py
Normal file
30
src/specify_cli/core_pack/agents/auggie/bootstrap.py
Normal file
30
src/specify_cli/core_pack/agents/auggie/bootstrap.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
"""Bootstrap module for Auggie CLI agent pack."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
|
from specify_cli.agent_pack import AgentBootstrap, remove_tracked_files
|
||||||
|
|
||||||
|
|
||||||
|
class Auggie(AgentBootstrap):
|
||||||
|
"""Bootstrap for Auggie CLI."""
|
||||||
|
|
||||||
|
AGENT_DIR = ".augment"
|
||||||
|
COMMANDS_SUBDIR = "commands"
|
||||||
|
|
||||||
|
def setup(self, project_path: Path, script_type: str, options: Dict[str, Any]) -> List[Path]:
|
||||||
|
"""Install Auggie CLI agent files into the project."""
|
||||||
|
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||||
|
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
return self._scaffold_project(project_path, script_type)
|
||||||
|
|
||||||
|
def teardown(self, project_path: Path, *, force: bool = False, files: Optional[Dict[str, str]] = None) -> List[str]:
|
||||||
|
"""Remove Auggie CLI agent files from the project.
|
||||||
|
|
||||||
|
Only removes individual tracked files — directories are never
|
||||||
|
deleted. When *files* is provided, exactly those files are
|
||||||
|
removed. Otherwise the install manifest is consulted and
|
||||||
|
``AgentFileModifiedError`` is raised if any tracked file was
|
||||||
|
modified and *force* is ``False``.
|
||||||
|
"""
|
||||||
|
return remove_tracked_files(project_path, self.manifest.id, force=force, files=files)
|
||||||
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"
|
||||||
0
src/specify_cli/core_pack/agents/bob/__init__.py
Normal file
0
src/specify_cli/core_pack/agents/bob/__init__.py
Normal file
30
src/specify_cli/core_pack/agents/bob/bootstrap.py
Normal file
30
src/specify_cli/core_pack/agents/bob/bootstrap.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
"""Bootstrap module for IBM Bob agent pack."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
|
from specify_cli.agent_pack import AgentBootstrap, remove_tracked_files
|
||||||
|
|
||||||
|
|
||||||
|
class Bob(AgentBootstrap):
|
||||||
|
"""Bootstrap for IBM Bob."""
|
||||||
|
|
||||||
|
AGENT_DIR = ".bob"
|
||||||
|
COMMANDS_SUBDIR = "commands"
|
||||||
|
|
||||||
|
def setup(self, project_path: Path, script_type: str, options: Dict[str, Any]) -> List[Path]:
|
||||||
|
"""Install IBM Bob agent files into the project."""
|
||||||
|
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||||
|
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
return self._scaffold_project(project_path, script_type)
|
||||||
|
|
||||||
|
def teardown(self, project_path: Path, *, force: bool = False, files: Optional[Dict[str, str]] = None) -> List[str]:
|
||||||
|
"""Remove IBM Bob agent files from the project.
|
||||||
|
|
||||||
|
Only removes individual tracked files — directories are never
|
||||||
|
deleted. When *files* is provided, exactly those files are
|
||||||
|
removed. Otherwise the install manifest is consulted and
|
||||||
|
``AgentFileModifiedError`` is raised if any tracked file was
|
||||||
|
modified and *force* is ``False``.
|
||||||
|
"""
|
||||||
|
return remove_tracked_files(project_path, self.manifest.id, force=force, files=files)
|
||||||
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"
|
||||||
0
src/specify_cli/core_pack/agents/claude/__init__.py
Normal file
0
src/specify_cli/core_pack/agents/claude/__init__.py
Normal file
30
src/specify_cli/core_pack/agents/claude/bootstrap.py
Normal file
30
src/specify_cli/core_pack/agents/claude/bootstrap.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
"""Bootstrap module for Claude Code agent pack."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
|
from specify_cli.agent_pack import AgentBootstrap, remove_tracked_files
|
||||||
|
|
||||||
|
|
||||||
|
class Claude(AgentBootstrap):
|
||||||
|
"""Bootstrap for Claude Code."""
|
||||||
|
|
||||||
|
AGENT_DIR = ".claude"
|
||||||
|
COMMANDS_SUBDIR = "commands"
|
||||||
|
|
||||||
|
def setup(self, project_path: Path, script_type: str, options: Dict[str, Any]) -> List[Path]:
|
||||||
|
"""Install Claude Code agent files into the project."""
|
||||||
|
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||||
|
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
return self._scaffold_project(project_path, script_type)
|
||||||
|
|
||||||
|
def teardown(self, project_path: Path, *, force: bool = False, files: Optional[Dict[str, str]] = None) -> List[str]:
|
||||||
|
"""Remove Claude Code agent files from the project.
|
||||||
|
|
||||||
|
Only removes individual tracked files — directories are never
|
||||||
|
deleted. When *files* is provided, exactly those files are
|
||||||
|
removed. Otherwise the install manifest is consulted and
|
||||||
|
``AgentFileModifiedError`` is raised if any tracked file was
|
||||||
|
modified and *force* is ``False``.
|
||||||
|
"""
|
||||||
|
return remove_tracked_files(project_path, self.manifest.id, force=force, files=files)
|
||||||
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"
|
||||||
30
src/specify_cli/core_pack/agents/codebuddy/bootstrap.py
Normal file
30
src/specify_cli/core_pack/agents/codebuddy/bootstrap.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
"""Bootstrap module for CodeBuddy agent pack."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
|
from specify_cli.agent_pack import AgentBootstrap, remove_tracked_files
|
||||||
|
|
||||||
|
|
||||||
|
class Codebuddy(AgentBootstrap):
|
||||||
|
"""Bootstrap for CodeBuddy."""
|
||||||
|
|
||||||
|
AGENT_DIR = ".codebuddy"
|
||||||
|
COMMANDS_SUBDIR = "commands"
|
||||||
|
|
||||||
|
def setup(self, project_path: Path, script_type: str, options: Dict[str, Any]) -> List[Path]:
|
||||||
|
"""Install CodeBuddy agent files into the project."""
|
||||||
|
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||||
|
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
return self._scaffold_project(project_path, script_type)
|
||||||
|
|
||||||
|
def teardown(self, project_path: Path, *, force: bool = False, files: Optional[Dict[str, str]] = None) -> List[str]:
|
||||||
|
"""Remove CodeBuddy agent files from the project.
|
||||||
|
|
||||||
|
Only removes individual tracked files — directories are never
|
||||||
|
deleted. When *files* is provided, exactly those files are
|
||||||
|
removed. Otherwise the install manifest is consulted and
|
||||||
|
``AgentFileModifiedError`` is raised if any tracked file was
|
||||||
|
modified and *force* is ``False``.
|
||||||
|
"""
|
||||||
|
return remove_tracked_files(project_path, self.manifest.id, force=force, files=files)
|
||||||
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"
|
||||||
0
src/specify_cli/core_pack/agents/codex/__init__.py
Normal file
0
src/specify_cli/core_pack/agents/codex/__init__.py
Normal file
30
src/specify_cli/core_pack/agents/codex/bootstrap.py
Normal file
30
src/specify_cli/core_pack/agents/codex/bootstrap.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
"""Bootstrap module for Codex CLI agent pack."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
|
from specify_cli.agent_pack import AgentBootstrap, remove_tracked_files
|
||||||
|
|
||||||
|
|
||||||
|
class Codex(AgentBootstrap):
|
||||||
|
"""Bootstrap for Codex CLI."""
|
||||||
|
|
||||||
|
AGENT_DIR = ".agents"
|
||||||
|
COMMANDS_SUBDIR = "skills"
|
||||||
|
|
||||||
|
def setup(self, project_path: Path, script_type: str, options: Dict[str, Any]) -> List[Path]:
|
||||||
|
"""Install Codex CLI agent files into the project."""
|
||||||
|
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||||
|
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
return self._scaffold_project(project_path, script_type)
|
||||||
|
|
||||||
|
def teardown(self, project_path: Path, *, force: bool = False, files: Optional[Dict[str, str]] = None) -> List[str]:
|
||||||
|
"""Remove Codex CLI agent files from the project.
|
||||||
|
|
||||||
|
Only removes individual tracked files — directories are never
|
||||||
|
deleted. When *files* is provided, exactly those files are
|
||||||
|
removed. Otherwise the install manifest is consulted and
|
||||||
|
``AgentFileModifiedError`` is raised if any tracked file was
|
||||||
|
modified and *force* is ``False``.
|
||||||
|
"""
|
||||||
|
return remove_tracked_files(project_path, self.manifest.id, force=force, files=files)
|
||||||
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"
|
||||||
30
src/specify_cli/core_pack/agents/copilot/bootstrap.py
Normal file
30
src/specify_cli/core_pack/agents/copilot/bootstrap.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
"""Bootstrap module for GitHub Copilot agent pack."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
|
from specify_cli.agent_pack import AgentBootstrap, remove_tracked_files
|
||||||
|
|
||||||
|
|
||||||
|
class Copilot(AgentBootstrap):
|
||||||
|
"""Bootstrap for GitHub Copilot."""
|
||||||
|
|
||||||
|
AGENT_DIR = ".github"
|
||||||
|
COMMANDS_SUBDIR = "agents"
|
||||||
|
|
||||||
|
def setup(self, project_path: Path, script_type: str, options: Dict[str, Any]) -> List[Path]:
|
||||||
|
"""Install GitHub Copilot agent files into the project."""
|
||||||
|
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||||
|
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
return self._scaffold_project(project_path, script_type)
|
||||||
|
|
||||||
|
def teardown(self, project_path: Path, *, force: bool = False, files: Optional[Dict[str, str]] = None) -> List[str]:
|
||||||
|
"""Remove GitHub Copilot agent files from the project.
|
||||||
|
|
||||||
|
Only removes individual tracked files — directories are never
|
||||||
|
deleted. When *files* is provided, exactly those files are
|
||||||
|
removed. Otherwise the install manifest is consulted and
|
||||||
|
``AgentFileModifiedError`` is raised if any tracked file was
|
||||||
|
modified and *force* is ``False``.
|
||||||
|
"""
|
||||||
|
return remove_tracked_files(project_path, self.manifest.id, force=force, files=files)
|
||||||
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"
|
||||||
30
src/specify_cli/core_pack/agents/cursor-agent/bootstrap.py
Normal file
30
src/specify_cli/core_pack/agents/cursor-agent/bootstrap.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
"""Bootstrap module for Cursor agent pack."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
|
from specify_cli.agent_pack import AgentBootstrap, remove_tracked_files
|
||||||
|
|
||||||
|
|
||||||
|
class CursorAgent(AgentBootstrap):
|
||||||
|
"""Bootstrap for Cursor."""
|
||||||
|
|
||||||
|
AGENT_DIR = ".cursor"
|
||||||
|
COMMANDS_SUBDIR = "commands"
|
||||||
|
|
||||||
|
def setup(self, project_path: Path, script_type: str, options: Dict[str, Any]) -> List[Path]:
|
||||||
|
"""Install Cursor agent files into the project."""
|
||||||
|
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||||
|
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
return self._scaffold_project(project_path, script_type)
|
||||||
|
|
||||||
|
def teardown(self, project_path: Path, *, force: bool = False, files: Optional[Dict[str, str]] = None) -> List[str]:
|
||||||
|
"""Remove Cursor agent files from the project.
|
||||||
|
|
||||||
|
Only removes individual tracked files — directories are never
|
||||||
|
deleted. When *files* is provided, exactly those files are
|
||||||
|
removed. Otherwise the install manifest is consulted and
|
||||||
|
``AgentFileModifiedError`` is raised if any tracked file was
|
||||||
|
modified and *force* is ``False``.
|
||||||
|
"""
|
||||||
|
return remove_tracked_files(project_path, self.manifest.id, force=force, files=files)
|
||||||
@@ -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"
|
||||||
0
src/specify_cli/core_pack/agents/gemini/__init__.py
Normal file
0
src/specify_cli/core_pack/agents/gemini/__init__.py
Normal file
30
src/specify_cli/core_pack/agents/gemini/bootstrap.py
Normal file
30
src/specify_cli/core_pack/agents/gemini/bootstrap.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
"""Bootstrap module for Gemini CLI agent pack."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
|
from specify_cli.agent_pack import AgentBootstrap, remove_tracked_files
|
||||||
|
|
||||||
|
|
||||||
|
class Gemini(AgentBootstrap):
|
||||||
|
"""Bootstrap for Gemini CLI."""
|
||||||
|
|
||||||
|
AGENT_DIR = ".gemini"
|
||||||
|
COMMANDS_SUBDIR = "commands"
|
||||||
|
|
||||||
|
def setup(self, project_path: Path, script_type: str, options: Dict[str, Any]) -> List[Path]:
|
||||||
|
"""Install Gemini CLI agent files into the project."""
|
||||||
|
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||||
|
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
return self._scaffold_project(project_path, script_type)
|
||||||
|
|
||||||
|
def teardown(self, project_path: Path, *, force: bool = False, files: Optional[Dict[str, str]] = None) -> List[str]:
|
||||||
|
"""Remove Gemini CLI agent files from the project.
|
||||||
|
|
||||||
|
Only removes individual tracked files — directories are never
|
||||||
|
deleted. When *files* is provided, exactly those files are
|
||||||
|
removed. Otherwise the install manifest is consulted and
|
||||||
|
``AgentFileModifiedError`` is raised if any tracked file was
|
||||||
|
modified and *force* is ``False``.
|
||||||
|
"""
|
||||||
|
return remove_tracked_files(project_path, self.manifest.id, force=force, files=files)
|
||||||
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"
|
||||||
0
src/specify_cli/core_pack/agents/iflow/__init__.py
Normal file
0
src/specify_cli/core_pack/agents/iflow/__init__.py
Normal file
30
src/specify_cli/core_pack/agents/iflow/bootstrap.py
Normal file
30
src/specify_cli/core_pack/agents/iflow/bootstrap.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
"""Bootstrap module for iFlow CLI agent pack."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
|
from specify_cli.agent_pack import AgentBootstrap, remove_tracked_files
|
||||||
|
|
||||||
|
|
||||||
|
class Iflow(AgentBootstrap):
|
||||||
|
"""Bootstrap for iFlow CLI."""
|
||||||
|
|
||||||
|
AGENT_DIR = ".iflow"
|
||||||
|
COMMANDS_SUBDIR = "commands"
|
||||||
|
|
||||||
|
def setup(self, project_path: Path, script_type: str, options: Dict[str, Any]) -> List[Path]:
|
||||||
|
"""Install iFlow CLI agent files into the project."""
|
||||||
|
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||||
|
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
return self._scaffold_project(project_path, script_type)
|
||||||
|
|
||||||
|
def teardown(self, project_path: Path, *, force: bool = False, files: Optional[Dict[str, str]] = None) -> List[str]:
|
||||||
|
"""Remove iFlow CLI agent files from the project.
|
||||||
|
|
||||||
|
Only removes individual tracked files — directories are never
|
||||||
|
deleted. When *files* is provided, exactly those files are
|
||||||
|
removed. Otherwise the install manifest is consulted and
|
||||||
|
``AgentFileModifiedError`` is raised if any tracked file was
|
||||||
|
modified and *force* is ``False``.
|
||||||
|
"""
|
||||||
|
return remove_tracked_files(project_path, self.manifest.id, force=force, files=files)
|
||||||
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"
|
||||||
0
src/specify_cli/core_pack/agents/junie/__init__.py
Normal file
0
src/specify_cli/core_pack/agents/junie/__init__.py
Normal file
30
src/specify_cli/core_pack/agents/junie/bootstrap.py
Normal file
30
src/specify_cli/core_pack/agents/junie/bootstrap.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
"""Bootstrap module for Junie agent pack."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
|
from specify_cli.agent_pack import AgentBootstrap, remove_tracked_files
|
||||||
|
|
||||||
|
|
||||||
|
class Junie(AgentBootstrap):
|
||||||
|
"""Bootstrap for Junie."""
|
||||||
|
|
||||||
|
AGENT_DIR = ".junie"
|
||||||
|
COMMANDS_SUBDIR = "commands"
|
||||||
|
|
||||||
|
def setup(self, project_path: Path, script_type: str, options: Dict[str, Any]) -> List[Path]:
|
||||||
|
"""Install Junie agent files into the project."""
|
||||||
|
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||||
|
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
return self._scaffold_project(project_path, script_type)
|
||||||
|
|
||||||
|
def teardown(self, project_path: Path, *, force: bool = False, files: Optional[Dict[str, str]] = None) -> List[str]:
|
||||||
|
"""Remove Junie agent files from the project.
|
||||||
|
|
||||||
|
Only removes individual tracked files — directories are never
|
||||||
|
deleted. When *files* is provided, exactly those files are
|
||||||
|
removed. Otherwise the install manifest is consulted and
|
||||||
|
``AgentFileModifiedError`` is raised if any tracked file was
|
||||||
|
modified and *force* is ``False``.
|
||||||
|
"""
|
||||||
|
return remove_tracked_files(project_path, self.manifest.id, force=force, files=files)
|
||||||
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"
|
||||||
30
src/specify_cli/core_pack/agents/kilocode/bootstrap.py
Normal file
30
src/specify_cli/core_pack/agents/kilocode/bootstrap.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
"""Bootstrap module for Kilo Code agent pack."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
|
from specify_cli.agent_pack import AgentBootstrap, remove_tracked_files
|
||||||
|
|
||||||
|
|
||||||
|
class Kilocode(AgentBootstrap):
|
||||||
|
"""Bootstrap for Kilo Code."""
|
||||||
|
|
||||||
|
AGENT_DIR = ".kilocode"
|
||||||
|
COMMANDS_SUBDIR = "workflows"
|
||||||
|
|
||||||
|
def setup(self, project_path: Path, script_type: str, options: Dict[str, Any]) -> List[Path]:
|
||||||
|
"""Install Kilo Code agent files into the project."""
|
||||||
|
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||||
|
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
return self._scaffold_project(project_path, script_type)
|
||||||
|
|
||||||
|
def teardown(self, project_path: Path, *, force: bool = False, files: Optional[Dict[str, str]] = None) -> List[str]:
|
||||||
|
"""Remove Kilo Code agent files from the project.
|
||||||
|
|
||||||
|
Only removes individual tracked files — directories are never
|
||||||
|
deleted. When *files* is provided, exactly those files are
|
||||||
|
removed. Otherwise the install manifest is consulted and
|
||||||
|
``AgentFileModifiedError`` is raised if any tracked file was
|
||||||
|
modified and *force* is ``False``.
|
||||||
|
"""
|
||||||
|
return remove_tracked_files(project_path, self.manifest.id, force=force, files=files)
|
||||||
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"
|
||||||
0
src/specify_cli/core_pack/agents/kimi/__init__.py
Normal file
0
src/specify_cli/core_pack/agents/kimi/__init__.py
Normal file
30
src/specify_cli/core_pack/agents/kimi/bootstrap.py
Normal file
30
src/specify_cli/core_pack/agents/kimi/bootstrap.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
"""Bootstrap module for Kimi Code agent pack."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
|
from specify_cli.agent_pack import AgentBootstrap, remove_tracked_files
|
||||||
|
|
||||||
|
|
||||||
|
class Kimi(AgentBootstrap):
|
||||||
|
"""Bootstrap for Kimi Code."""
|
||||||
|
|
||||||
|
AGENT_DIR = ".kimi"
|
||||||
|
COMMANDS_SUBDIR = "skills"
|
||||||
|
|
||||||
|
def setup(self, project_path: Path, script_type: str, options: Dict[str, Any]) -> List[Path]:
|
||||||
|
"""Install Kimi Code agent files into the project."""
|
||||||
|
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||||
|
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
return self._scaffold_project(project_path, script_type)
|
||||||
|
|
||||||
|
def teardown(self, project_path: Path, *, force: bool = False, files: Optional[Dict[str, str]] = None) -> List[str]:
|
||||||
|
"""Remove Kimi Code agent files from the project.
|
||||||
|
|
||||||
|
Only removes individual tracked files — directories are never
|
||||||
|
deleted. When *files* is provided, exactly those files are
|
||||||
|
removed. Otherwise the install manifest is consulted and
|
||||||
|
``AgentFileModifiedError`` is raised if any tracked file was
|
||||||
|
modified and *force* is ``False``.
|
||||||
|
"""
|
||||||
|
return remove_tracked_files(project_path, self.manifest.id, force=force, files=files)
|
||||||
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"
|
||||||
30
src/specify_cli/core_pack/agents/kiro-cli/bootstrap.py
Normal file
30
src/specify_cli/core_pack/agents/kiro-cli/bootstrap.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
"""Bootstrap module for Kiro CLI agent pack."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
|
from specify_cli.agent_pack import AgentBootstrap, remove_tracked_files
|
||||||
|
|
||||||
|
|
||||||
|
class KiroCli(AgentBootstrap):
|
||||||
|
"""Bootstrap for Kiro CLI."""
|
||||||
|
|
||||||
|
AGENT_DIR = ".kiro"
|
||||||
|
COMMANDS_SUBDIR = "prompts"
|
||||||
|
|
||||||
|
def setup(self, project_path: Path, script_type: str, options: Dict[str, Any]) -> List[Path]:
|
||||||
|
"""Install Kiro CLI agent files into the project."""
|
||||||
|
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||||
|
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
return self._scaffold_project(project_path, script_type)
|
||||||
|
|
||||||
|
def teardown(self, project_path: Path, *, force: bool = False, files: Optional[Dict[str, str]] = None) -> List[str]:
|
||||||
|
"""Remove Kiro CLI agent files from the project.
|
||||||
|
|
||||||
|
Only removes individual tracked files — directories are never
|
||||||
|
deleted. When *files* is provided, exactly those files are
|
||||||
|
removed. Otherwise the install manifest is consulted and
|
||||||
|
``AgentFileModifiedError`` is raised if any tracked file was
|
||||||
|
modified and *force* is ``False``.
|
||||||
|
"""
|
||||||
|
return remove_tracked_files(project_path, self.manifest.id, force=force, files=files)
|
||||||
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"
|
||||||
30
src/specify_cli/core_pack/agents/opencode/bootstrap.py
Normal file
30
src/specify_cli/core_pack/agents/opencode/bootstrap.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
"""Bootstrap module for opencode agent pack."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
|
from specify_cli.agent_pack import AgentBootstrap, remove_tracked_files
|
||||||
|
|
||||||
|
|
||||||
|
class Opencode(AgentBootstrap):
|
||||||
|
"""Bootstrap for opencode."""
|
||||||
|
|
||||||
|
AGENT_DIR = ".opencode"
|
||||||
|
COMMANDS_SUBDIR = "command"
|
||||||
|
|
||||||
|
def setup(self, project_path: Path, script_type: str, options: Dict[str, Any]) -> List[Path]:
|
||||||
|
"""Install opencode agent files into the project."""
|
||||||
|
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||||
|
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
return self._scaffold_project(project_path, script_type)
|
||||||
|
|
||||||
|
def teardown(self, project_path: Path, *, force: bool = False, files: Optional[Dict[str, str]] = None) -> List[str]:
|
||||||
|
"""Remove opencode agent files from the project.
|
||||||
|
|
||||||
|
Only removes individual tracked files — directories are never
|
||||||
|
deleted. When *files* is provided, exactly those files are
|
||||||
|
removed. Otherwise the install manifest is consulted and
|
||||||
|
``AgentFileModifiedError`` is raised if any tracked file was
|
||||||
|
modified and *force* is ``False``.
|
||||||
|
"""
|
||||||
|
return remove_tracked_files(project_path, self.manifest.id, force=force, files=files)
|
||||||
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"
|
||||||
0
src/specify_cli/core_pack/agents/pi/__init__.py
Normal file
0
src/specify_cli/core_pack/agents/pi/__init__.py
Normal file
30
src/specify_cli/core_pack/agents/pi/bootstrap.py
Normal file
30
src/specify_cli/core_pack/agents/pi/bootstrap.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
"""Bootstrap module for Pi Coding Agent agent pack."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
|
from specify_cli.agent_pack import AgentBootstrap, remove_tracked_files
|
||||||
|
|
||||||
|
|
||||||
|
class Pi(AgentBootstrap):
|
||||||
|
"""Bootstrap for Pi Coding Agent."""
|
||||||
|
|
||||||
|
AGENT_DIR = ".pi"
|
||||||
|
COMMANDS_SUBDIR = "prompts"
|
||||||
|
|
||||||
|
def setup(self, project_path: Path, script_type: str, options: Dict[str, Any]) -> List[Path]:
|
||||||
|
"""Install Pi Coding Agent agent files into the project."""
|
||||||
|
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||||
|
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
return self._scaffold_project(project_path, script_type)
|
||||||
|
|
||||||
|
def teardown(self, project_path: Path, *, force: bool = False, files: Optional[Dict[str, str]] = None) -> List[str]:
|
||||||
|
"""Remove Pi Coding Agent agent files from the project.
|
||||||
|
|
||||||
|
Only removes individual tracked files — directories are never
|
||||||
|
deleted. When *files* is provided, exactly those files are
|
||||||
|
removed. Otherwise the install manifest is consulted and
|
||||||
|
``AgentFileModifiedError`` is raised if any tracked file was
|
||||||
|
modified and *force* is ``False``.
|
||||||
|
"""
|
||||||
|
return remove_tracked_files(project_path, self.manifest.id, force=force, files=files)
|
||||||
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"
|
||||||
30
src/specify_cli/core_pack/agents/qodercli/bootstrap.py
Normal file
30
src/specify_cli/core_pack/agents/qodercli/bootstrap.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
"""Bootstrap module for Qoder CLI agent pack."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
|
from specify_cli.agent_pack import AgentBootstrap, remove_tracked_files
|
||||||
|
|
||||||
|
|
||||||
|
class Qodercli(AgentBootstrap):
|
||||||
|
"""Bootstrap for Qoder CLI."""
|
||||||
|
|
||||||
|
AGENT_DIR = ".qoder"
|
||||||
|
COMMANDS_SUBDIR = "commands"
|
||||||
|
|
||||||
|
def setup(self, project_path: Path, script_type: str, options: Dict[str, Any]) -> List[Path]:
|
||||||
|
"""Install Qoder CLI agent files into the project."""
|
||||||
|
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||||
|
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
return self._scaffold_project(project_path, script_type)
|
||||||
|
|
||||||
|
def teardown(self, project_path: Path, *, force: bool = False, files: Optional[Dict[str, str]] = None) -> List[str]:
|
||||||
|
"""Remove Qoder CLI agent files from the project.
|
||||||
|
|
||||||
|
Only removes individual tracked files — directories are never
|
||||||
|
deleted. When *files* is provided, exactly those files are
|
||||||
|
removed. Otherwise the install manifest is consulted and
|
||||||
|
``AgentFileModifiedError`` is raised if any tracked file was
|
||||||
|
modified and *force* is ``False``.
|
||||||
|
"""
|
||||||
|
return remove_tracked_files(project_path, self.manifest.id, force=force, files=files)
|
||||||
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"
|
||||||
0
src/specify_cli/core_pack/agents/qwen/__init__.py
Normal file
0
src/specify_cli/core_pack/agents/qwen/__init__.py
Normal file
30
src/specify_cli/core_pack/agents/qwen/bootstrap.py
Normal file
30
src/specify_cli/core_pack/agents/qwen/bootstrap.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
"""Bootstrap module for Qwen Code agent pack."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
|
from specify_cli.agent_pack import AgentBootstrap, remove_tracked_files
|
||||||
|
|
||||||
|
|
||||||
|
class Qwen(AgentBootstrap):
|
||||||
|
"""Bootstrap for Qwen Code."""
|
||||||
|
|
||||||
|
AGENT_DIR = ".qwen"
|
||||||
|
COMMANDS_SUBDIR = "commands"
|
||||||
|
|
||||||
|
def setup(self, project_path: Path, script_type: str, options: Dict[str, Any]) -> List[Path]:
|
||||||
|
"""Install Qwen Code agent files into the project."""
|
||||||
|
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||||
|
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
return self._scaffold_project(project_path, script_type)
|
||||||
|
|
||||||
|
def teardown(self, project_path: Path, *, force: bool = False, files: Optional[Dict[str, str]] = None) -> List[str]:
|
||||||
|
"""Remove Qwen Code agent files from the project.
|
||||||
|
|
||||||
|
Only removes individual tracked files — directories are never
|
||||||
|
deleted. When *files* is provided, exactly those files are
|
||||||
|
removed. Otherwise the install manifest is consulted and
|
||||||
|
``AgentFileModifiedError`` is raised if any tracked file was
|
||||||
|
modified and *force* is ``False``.
|
||||||
|
"""
|
||||||
|
return remove_tracked_files(project_path, self.manifest.id, force=force, files=files)
|
||||||
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"
|
||||||
0
src/specify_cli/core_pack/agents/roo/__init__.py
Normal file
0
src/specify_cli/core_pack/agents/roo/__init__.py
Normal file
30
src/specify_cli/core_pack/agents/roo/bootstrap.py
Normal file
30
src/specify_cli/core_pack/agents/roo/bootstrap.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
"""Bootstrap module for Roo Code agent pack."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
|
from specify_cli.agent_pack import AgentBootstrap, remove_tracked_files
|
||||||
|
|
||||||
|
|
||||||
|
class Roo(AgentBootstrap):
|
||||||
|
"""Bootstrap for Roo Code."""
|
||||||
|
|
||||||
|
AGENT_DIR = ".roo"
|
||||||
|
COMMANDS_SUBDIR = "commands"
|
||||||
|
|
||||||
|
def setup(self, project_path: Path, script_type: str, options: Dict[str, Any]) -> List[Path]:
|
||||||
|
"""Install Roo Code agent files into the project."""
|
||||||
|
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||||
|
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
return self._scaffold_project(project_path, script_type)
|
||||||
|
|
||||||
|
def teardown(self, project_path: Path, *, force: bool = False, files: Optional[Dict[str, str]] = None) -> List[str]:
|
||||||
|
"""Remove Roo Code agent files from the project.
|
||||||
|
|
||||||
|
Only removes individual tracked files — directories are never
|
||||||
|
deleted. When *files* is provided, exactly those files are
|
||||||
|
removed. Otherwise the install manifest is consulted and
|
||||||
|
``AgentFileModifiedError`` is raised if any tracked file was
|
||||||
|
modified and *force* is ``False``.
|
||||||
|
"""
|
||||||
|
return remove_tracked_files(project_path, self.manifest.id, force=force, files=files)
|
||||||
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"
|
||||||
0
src/specify_cli/core_pack/agents/shai/__init__.py
Normal file
0
src/specify_cli/core_pack/agents/shai/__init__.py
Normal file
30
src/specify_cli/core_pack/agents/shai/bootstrap.py
Normal file
30
src/specify_cli/core_pack/agents/shai/bootstrap.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
"""Bootstrap module for SHAI agent pack."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
|
from specify_cli.agent_pack import AgentBootstrap, remove_tracked_files
|
||||||
|
|
||||||
|
|
||||||
|
class Shai(AgentBootstrap):
|
||||||
|
"""Bootstrap for SHAI."""
|
||||||
|
|
||||||
|
AGENT_DIR = ".shai"
|
||||||
|
COMMANDS_SUBDIR = "commands"
|
||||||
|
|
||||||
|
def setup(self, project_path: Path, script_type: str, options: Dict[str, Any]) -> List[Path]:
|
||||||
|
"""Install SHAI agent files into the project."""
|
||||||
|
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||||
|
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
return self._scaffold_project(project_path, script_type)
|
||||||
|
|
||||||
|
def teardown(self, project_path: Path, *, force: bool = False, files: Optional[Dict[str, str]] = None) -> List[str]:
|
||||||
|
"""Remove SHAI agent files from the project.
|
||||||
|
|
||||||
|
Only removes individual tracked files — directories are never
|
||||||
|
deleted. When *files* is provided, exactly those files are
|
||||||
|
removed. Otherwise the install manifest is consulted and
|
||||||
|
``AgentFileModifiedError`` is raised if any tracked file was
|
||||||
|
modified and *force* is ``False``.
|
||||||
|
"""
|
||||||
|
return remove_tracked_files(project_path, self.manifest.id, force=force, files=files)
|
||||||
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"
|
||||||
30
src/specify_cli/core_pack/agents/tabnine/bootstrap.py
Normal file
30
src/specify_cli/core_pack/agents/tabnine/bootstrap.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
"""Bootstrap module for Tabnine CLI agent pack."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
|
from specify_cli.agent_pack import AgentBootstrap, remove_tracked_files
|
||||||
|
|
||||||
|
|
||||||
|
class Tabnine(AgentBootstrap):
|
||||||
|
"""Bootstrap for Tabnine CLI."""
|
||||||
|
|
||||||
|
AGENT_DIR = ".tabnine/agent"
|
||||||
|
COMMANDS_SUBDIR = "commands"
|
||||||
|
|
||||||
|
def setup(self, project_path: Path, script_type: str, options: Dict[str, Any]) -> List[Path]:
|
||||||
|
"""Install Tabnine CLI agent files into the project."""
|
||||||
|
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
|
||||||
|
commands_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
return self._scaffold_project(project_path, script_type)
|
||||||
|
|
||||||
|
def teardown(self, project_path: Path, *, force: bool = False, files: Optional[Dict[str, str]] = None) -> List[str]:
|
||||||
|
"""Remove Tabnine CLI agent files from the project.
|
||||||
|
|
||||||
|
Only removes individual tracked files — directories are never
|
||||||
|
deleted. When *files* is provided, exactly those files are
|
||||||
|
removed. Otherwise the install manifest is consulted and
|
||||||
|
``AgentFileModifiedError`` is raised if any tracked file was
|
||||||
|
modified and *force* is ``False``.
|
||||||
|
"""
|
||||||
|
return remove_tracked_files(project_path, self.manifest.id, force=force, files=files)
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user