mirror of
https://github.com/github/spec-kit.git
synced 2026-03-20 12:23:09 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2559d7025 | ||
|
|
f85944aafe | ||
|
|
34171efcef | ||
|
|
c8af730b14 | ||
|
|
a4b60aca7f |
@@ -51,6 +51,10 @@ 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..."
|
echo -e "\n🤖 Installing Pi Coding Agent..."
|
||||||
run_command "npm install -g @mariozechner/pi-coding-agent@latest"
|
run_command "npm install -g @mariozechner/pi-coding-agent@latest"
|
||||||
echo "✅ Done"
|
echo "✅ Done"
|
||||||
|
|||||||
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 \
|
||||||
|
|||||||
@@ -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, trae, pi, iflow, 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,22 @@ 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 {
|
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 +269,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
|
||||||
@@ -395,9 +397,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 +459,7 @@ 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' {
|
'trae' {
|
||||||
$rulesDir = Join-Path $baseDir ".trae/rules"
|
$rulesDir = Join-Path $baseDir ".trae/rules"
|
||||||
@@ -483,7 +490,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', 'trae', 'pi', 'iflow', '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 trae pi iflow 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
|
||||||
@@ -121,18 +121,20 @@ 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() {
|
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 +177,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')
|
||||||
|
|
||||||
@@ -248,9 +250,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 +295,7 @@ 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)
|
trae)
|
||||||
mkdir -p "$base_dir/.trae/rules"
|
mkdir -p "$base_dir/.trae/rules"
|
||||||
generate_commands trae md "\$ARGUMENTS" "$base_dir/.trae/rules" "$script" ;;
|
generate_commands trae md "\$ARGUMENTS" "$base_dir/.trae/rules" "$script" ;;
|
||||||
@@ -309,7 +314,7 @@ 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 trae pi iflow 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() {
|
norm_list() {
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ Specify supports multiple AI agents by generating agent-specific command files a
|
|||||||
| **opencode** | `.opencode/command/` | Markdown | `opencode` | opencode CLI |
|
| **opencode** | `.opencode/command/` | Markdown | `opencode` | opencode CLI |
|
||||||
| **Codex CLI** | `.codex/prompts/` | Markdown | `codex` | Codex CLI |
|
| **Codex CLI** | `.codex/prompts/` | Markdown | `codex` | Codex CLI |
|
||||||
| **Windsurf** | `.windsurf/workflows/` | Markdown | N/A (IDE-based) | Windsurf IDE workflows |
|
| **Windsurf** | `.windsurf/workflows/` | Markdown | N/A (IDE-based) | Windsurf IDE workflows |
|
||||||
|
| **Junie** | `.junie/commands/` | Markdown | `junie` | Junie by JetBrains |
|
||||||
| **Kilo Code** | `.kilocode/workflows/` | Markdown | N/A (IDE-based) | Kilo Code IDE |
|
| **Kilo Code** | `.kilocode/workflows/` | Markdown | N/A (IDE-based) | Kilo Code IDE |
|
||||||
| **Auggie CLI** | `.augment/commands/` | Markdown | `auggie` | Auggie CLI |
|
| **Auggie CLI** | `.augment/commands/` | Markdown | `auggie` | Auggie CLI |
|
||||||
| **Roo Code** | `.roo/commands/` | Markdown | N/A (IDE-based) | Roo Code IDE |
|
| **Roo Code** | `.roo/commands/` | Markdown | N/A (IDE-based) | Roo Code IDE |
|
||||||
@@ -318,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
|
||||||
@@ -339,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, Pi
|
Used by: Claude, Cursor, opencode, Windsurf, Junie, Kiro CLI, Amp, SHAI, IBM Bob, Kimi Code, Qwen, Pi
|
||||||
|
|
||||||
**Standard format:**
|
**Standard format:**
|
||||||
|
|
||||||
|
|||||||
38
CHANGELOG.md
38
CHANGELOG.md
@@ -1,11 +1,25 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
<!-- markdownlint-disable MD024 -->
|
## [0.3.2] - 2026-03-19
|
||||||
|
|
||||||
Recent changes to the Specify CLI and templates are documented here.
|
### Changes
|
||||||
|
|
||||||
|
- 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)
|
||||||
|
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
||||||
|
|
||||||
## [0.3.1] - 2026-03-17
|
## [0.3.1] - 2026-03-17
|
||||||
|
|
||||||
@@ -307,19 +321,3 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
- chore(deps): bump actions/stale from 9 to 10 (#1623)
|
- chore(deps): bump actions/stale from 9 to 10 (#1623)
|
||||||
- feat: add dependabot configuration for pip and GitHub Actions updates (#1622)
|
- 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)
|
|
||||||
|
|||||||
52
README.md
52
README.md
@@ -99,7 +99,7 @@ uvx --from git+https://github.com/github/spec-kit.git specify init --here --ai c
|
|||||||
|
|
||||||
### 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.
|
||||||
|
|
||||||
@@ -173,7 +173,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/) | ✅ | |
|
||||||
@@ -190,6 +190,7 @@ See Spec-Driven Development in action across different scenarios with these comm
|
|||||||
| [Kimi Code](https://code.kimi.com/) | ✅ | |
|
| [Kimi Code](https://code.kimi.com/) | ✅ | |
|
||||||
| [iFlow CLI](https://docs.iflow.cn/en/cli/quickstart) | ✅ | |
|
| [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/) | ✅ | |
|
| [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 |
|
||||||
@@ -200,27 +201,27 @@ 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` plus all CLI-based agents configured in `AGENT_CONFIG` (for example: `claude`, `gemini`, `code`/`code-insiders`, `cursor-agent`, `windsurf`, `qwen`, `opencode`, `codex`, `kiro-cli`, `shai`, `qodercli`, `vibe`, `kimi`, `iflow`, `pi`, etc.) |
|
| `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 (see `AGENT_CONFIG` for the full, up-to-date list). Common options include: `claude`, `gemini`, `copilot`, `cursor-agent`, `qwen`, `opencode`, `codex`, `windsurf`, `kilocode`, `auggie`, `roo`, `codebuddy`, `amp`, `shai`, `kiro-cli` (`kiro` alias), `agy`, `bob`, `qodercli`, `vibe`, `kimi`, `iflow`, `pi`, 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 |
|
||||||
| `--no-git` | Flag | Skip git repository initialization |
|
| `--no-git` | Flag | Skip git repository initialization |
|
||||||
| `--here` | Flag | Initialize project in the current directory instead of creating a new one |
|
| `--here` | Flag | Initialize project in the current directory instead of creating a new one |
|
||||||
| `--force` | Flag | Force merge/overwrite when initializing in current directory (skip confirmation) |
|
| `--force` | Flag | Force merge/overwrite when initializing in current directory (skip confirmation) |
|
||||||
| `--skip-tls` | Flag | Skip SSL/TLS verification (not recommended) |
|
| `--skip-tls` | Flag | Skip SSL/TLS verification (not recommended) |
|
||||||
| `--debug` | Flag | Enable detailed debug output for troubleshooting |
|
| `--debug` | Flag | Enable detailed debug output for troubleshooting |
|
||||||
| `--github-token` | Option | GitHub token for API requests (or set GH_TOKEN/GITHUB_TOKEN env variable) |
|
| `--github-token` | Option | GitHub token for API requests (or set GH_TOKEN/GITHUB_TOKEN env variable) |
|
||||||
| `--ai-skills` | Flag | Install Prompt.MD templates as agent skills in agent-specific `skills/` directory (requires `--ai`) |
|
| `--ai-skills` | Flag | Install Prompt.MD templates as agent skills in agent-specific `skills/` directory (requires `--ai`) |
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
@@ -258,6 +259,9 @@ specify init my-project --ai bob
|
|||||||
# Initialize with Pi Coding Agent support
|
# Initialize with Pi Coding Agent support
|
||||||
specify init my-project --ai pi
|
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
|
||||||
|
|
||||||
@@ -298,7 +302,9 @@ 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
|
||||||
|
|
||||||
@@ -484,11 +490,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
|
||||||
|
|||||||
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
|
||||||
|
|
||||||
|
|||||||
@@ -70,30 +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
|
||||||
|-----------|---------|-----|
|
|
||||||
| Archive Extension | Archive merged features into main project memory. | [spec-kit-archive](https://github.com/stn1slv/spec-kit-archive) |
|
**Effect:** `Read-only` — produces reports without modifying files · `Read+Write` — modifies files, creates artifacts, or updates specs
|
||||||
| 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) |
|
|
||||||
| 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) |
|
| Extension | Purpose | Category | Effect | URL |
|
||||||
| Cognitive Squad | Multi-agent cognitive system with Triadic Model: understanding, internalization, application — with quality gates, backpropagation verification, and self-healing | [cognitive-squad](https://github.com/Testimonial/cognitive-squad) |
|
|-----------|---------|----------|--------|-----|
|
||||||
| Conduct Extension | Orchestrates spec-kit phases via sub-agent delegation to reduce context pollution. | [spec-kit-conduct-ext](https://github.com/twbrandon7/spec-kit-conduct-ext) |
|
| Archive Extension | Archive merged features into main project memory. | `docs` | Read+Write | [spec-kit-archive](https://github.com/stn1slv/spec-kit-archive) |
|
||||||
| 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. | [spec-kit-docguard](https://github.com/raccioly/docguard) |
|
| 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) |
|
||||||
| 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) |
|
| 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) |
|
||||||
| Iterate | Iterate on spec documents with a two-phase define-and-apply workflow — refine specs mid-implementation and go straight back to building | [spec-kit-iterate](https://github.com/imviancagrace/spec-kit-iterate) |
|
| 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) |
|
||||||
| 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) |
|
| 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) |
|
||||||
| Learning Extension | Generate educational guides from implementations and enhance clarifications with mentoring context | [spec-kit-learn](https://github.com/imviancagrace/spec-kit-learn) |
|
| 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) |
|
||||||
| 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) |
|
| 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) |
|
||||||
| Project Status | Show current SDD workflow progress — active feature, artifact status, task completion, workflow phase, and extensions summary | [spec-kit-status](https://github.com/KhawarHabibKhan/spec-kit-status) |
|
| 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) |
|
||||||
| Ralph Loop | Autonomous implementation loop using AI agent CLI | [spec-kit-ralph](https://github.com/Rubiss/spec-kit-ralph) |
|
| 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) |
|
||||||
| Reconcile Extension | Reconcile implementation drift by surgically updating feature artifacts. | [spec-kit-reconcile](https://github.com/stn1slv/spec-kit-reconcile) |
|
| 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) |
|
||||||
| 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) |
|
| 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) |
|
||||||
| 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) |
|
| 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) |
|
||||||
| SDD Utilities | Resume interrupted workflows, validate project health, and verify spec-to-task traceability | [speckit-utils](https://github.com/mvanhorn/speckit-utils) |
|
| Ralph Loop | Autonomous implementation loop using AI agent CLI | `code` | Read+Write | [spec-kit-ralph](https://github.com/Rubiss/spec-kit-ralph) |
|
||||||
| 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) |
|
| Reconcile Extension | Reconcile implementation drift by surgically updating feature artifacts. | `docs` | Read+Write | [spec-kit-reconcile](https://github.com/stn1slv/spec-kit-reconcile) |
|
||||||
| Understanding | Automated requirements quality analysis — 31 deterministic metrics against IEEE/ISO standards with experimental energy-based ambiguity detection | [understanding](https://github.com/Testimonial/understanding) |
|
| 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) |
|
||||||
| 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) |
|
| 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) |
|
||||||
| Verify Extension | Post-implementation quality gate that validates implemented code against specification artifacts | [spec-kit-verify](https://github.com/ismaelJimenez/spec-kit-verify) |
|
| 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) |
|
||||||
| Verify Tasks Extension | Detect phantom completions: tasks marked [X] in tasks.md with no real implementation | [spec-kit-verify-tasks](https://github.com/datastone-inc/spec-kit-verify-tasks) |
|
| 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
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "specify-cli"
|
name = "specify-cli"
|
||||||
version = "0.3.1"
|
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 = [
|
||||||
|
|||||||
@@ -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, Pi Coding Agent, iFlow CLI, 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|trae|pi|iflow|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,6 +68,7 @@ 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"
|
||||||
@@ -638,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
|
||||||
;;
|
;;
|
||||||
@@ -691,7 +695,7 @@ update_specific_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|trae|pi|iflow|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
|
||||||
@@ -740,6 +744,7 @@ update_all_existing_agents() {
|
|||||||
_update_if_new "$KIRO_FILE" "Kiro CLI" || _all_ok=false
|
_update_if_new "$KIRO_FILE" "Kiro CLI" || _all_ok=false
|
||||||
_update_if_new "$BOB_FILE" "IBM Bob" || _all_ok=false
|
_update_if_new "$BOB_FILE" "IBM Bob" || _all_ok=false
|
||||||
_update_if_new "$WINDSURF_FILE" "Windsurf" || _all_ok=false
|
_update_if_new "$WINDSURF_FILE" "Windsurf" || _all_ok=false
|
||||||
|
_update_if_new "$JUNIE_FILE" "Junie" || _all_ok=false
|
||||||
_update_if_new "$KILOCODE_FILE" "Kilo Code" || _all_ok=false
|
_update_if_new "$KILOCODE_FILE" "Kilo Code" || _all_ok=false
|
||||||
_update_if_new "$AUGGIE_FILE" "Auggie CLI" || _all_ok=false
|
_update_if_new "$AUGGIE_FILE" "Auggie CLI" || _all_ok=false
|
||||||
_update_if_new "$ROO_FILE" "Roo Code" || _all_ok=false
|
_update_if_new "$ROO_FILE" "Roo Code" || _all_ok=false
|
||||||
@@ -778,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|trae|pi|iflow|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]"
|
||||||
}
|
}
|
||||||
|
|
||||||
#==============================================================================
|
#==============================================================================
|
||||||
|
|||||||
@@ -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, trae, pi, iflow, 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','trae','pi','iflow','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'
|
||||||
@@ -397,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' }
|
||||||
@@ -414,7 +416,7 @@ function Update-SpecificAgent {
|
|||||||
'pi' { Update-AgentFile -TargetFile $AGENTS_FILE -AgentName 'Pi Coding Agent' }
|
'pi' { Update-AgentFile -TargetFile $AGENTS_FILE -AgentName 'Pi Coding Agent' }
|
||||||
'iflow' { Update-AgentFile -TargetFile $IFLOW_FILE -AgentName 'iFlow CLI' }
|
'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|trae|pi|iflow|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 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -428,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 }
|
||||||
@@ -456,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|kimi|trae|pi|iflow|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 {
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ import sys
|
|||||||
import zipfile
|
import zipfile
|
||||||
import tempfile
|
import tempfile
|
||||||
import shutil
|
import shutil
|
||||||
import shlex
|
|
||||||
import json
|
import json
|
||||||
import json5
|
import json5
|
||||||
import stat
|
import stat
|
||||||
@@ -172,8 +171,8 @@ AGENT_CONFIG = {
|
|||||||
},
|
},
|
||||||
"codex": {
|
"codex": {
|
||||||
"name": "Codex CLI",
|
"name": "Codex CLI",
|
||||||
"folder": ".codex/",
|
"folder": ".agents/",
|
||||||
"commands_subdir": "prompts", # Special: uses prompts/ not commands/
|
"commands_subdir": "skills", # Codex now uses project skills directly
|
||||||
"install_url": "https://github.com/openai/codex",
|
"install_url": "https://github.com/openai/codex",
|
||||||
"requires_cli": True,
|
"requires_cli": True,
|
||||||
},
|
},
|
||||||
@@ -184,6 +183,13 @@ AGENT_CONFIG = {
|
|||||||
"install_url": None, # IDE-based
|
"install_url": None, # IDE-based
|
||||||
"requires_cli": False,
|
"requires_cli": False,
|
||||||
},
|
},
|
||||||
|
"junie": {
|
||||||
|
"name": "Junie",
|
||||||
|
"folder": ".junie/",
|
||||||
|
"commands_subdir": "commands",
|
||||||
|
"install_url": "https://junie.jetbrains.com/",
|
||||||
|
"requires_cli": True,
|
||||||
|
},
|
||||||
"kilocode": {
|
"kilocode": {
|
||||||
"name": "Kilo Code",
|
"name": "Kilo Code",
|
||||||
"folder": ".kilocode/",
|
"folder": ".kilocode/",
|
||||||
@@ -1211,6 +1217,9 @@ AGENT_SKILLS_DIR_OVERRIDES = {
|
|||||||
# Default skills directory for agents not in AGENT_CONFIG
|
# Default skills directory for agents not in AGENT_CONFIG
|
||||||
DEFAULT_SKILLS_DIR = ".agents/skills"
|
DEFAULT_SKILLS_DIR = ".agents/skills"
|
||||||
|
|
||||||
|
# Agents whose downloaded template already contains skills in the final layout.
|
||||||
|
NATIVE_SKILLS_AGENTS = {"codex", "kimi"}
|
||||||
|
|
||||||
# Enhanced descriptions for each spec-kit command skill
|
# Enhanced descriptions for each spec-kit command skill
|
||||||
SKILL_DESCRIPTIONS = {
|
SKILL_DESCRIPTIONS = {
|
||||||
"specify": "Create or update feature specifications from natural language descriptions. Use when starting new features or refining requirements. Generates spec.md with user stories, functional requirements, and acceptance criteria following spec-driven development methodology.",
|
"specify": "Create or update feature specifications from natural language descriptions. Use when starting new features or refining requirements. Generates spec.md with user stories, functional requirements, and acceptance criteria following spec-driven development methodology.",
|
||||||
@@ -1339,8 +1348,6 @@ def install_ai_skills(project_path: Path, selected_ai: str, tracker: StepTracker
|
|||||||
command_name = command_name[len("speckit."):]
|
command_name = command_name[len("speckit."):]
|
||||||
if command_name.endswith(".agent"):
|
if command_name.endswith(".agent"):
|
||||||
command_name = command_name[:-len(".agent")]
|
command_name = command_name[:-len(".agent")]
|
||||||
# Kimi CLI discovers skills by directory name and invokes them as
|
|
||||||
# /skill:<name> — use dot separator to match packaging convention.
|
|
||||||
if selected_ai == "kimi":
|
if selected_ai == "kimi":
|
||||||
skill_name = f"speckit.{command_name}"
|
skill_name = f"speckit.{command_name}"
|
||||||
else:
|
else:
|
||||||
@@ -1416,20 +1423,45 @@ def install_ai_skills(project_path: Path, selected_ai: str, tracker: StepTracker
|
|||||||
return installed_count > 0 or skipped_count > 0
|
return installed_count > 0 or skipped_count > 0
|
||||||
|
|
||||||
|
|
||||||
def _handle_agy_deprecation(console: Console) -> None:
|
def _has_bundled_skills(project_path: Path, selected_ai: str) -> bool:
|
||||||
"""
|
"""Return True when a native-skills agent has spec-kit bundled skills."""
|
||||||
Print the deprecation error for the Antigravity (agy) agent and exit.
|
skills_dir = _get_skills_dir(project_path, selected_ai)
|
||||||
|
if not skills_dir.is_dir():
|
||||||
|
return False
|
||||||
|
|
||||||
Context for future AI reviewers (e.g., GitHub Copilot):
|
pattern = "speckit.*/SKILL.md" if selected_ai == "kimi" else "speckit-*/SKILL.md"
|
||||||
- Prior to Antigravity v1.20.5, users could rely on explicit agent command definitions generated by this tool.
|
return any(skills_dir.glob(pattern))
|
||||||
- v1.20.5 removed this "command support" in favor of "Agent Skills".
|
|
||||||
- `specify_cli` handles this by throwing a fail-fast deprecation error if `--ai agy` is
|
|
||||||
invoked without the accompanying `--ai-skills` flag. Users are instructed to add
|
AGENT_SKILLS_MIGRATIONS = {
|
||||||
the skills flag to generate agent skills templates instead.
|
"agy": {
|
||||||
"""
|
"error": "Explicit command support was deprecated in Antigravity version 1.20.5.",
|
||||||
console.print("\n[red]Error:[/red] Explicit command support was deprecated in Antigravity version 1.20.5.")
|
"usage": "specify init <project> --ai agy --ai-skills",
|
||||||
|
"interactive_note": (
|
||||||
|
"'agy' was selected interactively; enabling [cyan]--ai-skills[/cyan] "
|
||||||
|
"automatically for compatibility (explicit .agent/commands usage is deprecated)."
|
||||||
|
),
|
||||||
|
},
|
||||||
|
"codex": {
|
||||||
|
"error": (
|
||||||
|
"Custom prompt-based spec-kit initialization is deprecated for Codex CLI; "
|
||||||
|
"use agent skills instead."
|
||||||
|
),
|
||||||
|
"usage": "specify init <project> --ai codex --ai-skills",
|
||||||
|
"interactive_note": (
|
||||||
|
"'codex' was selected interactively; enabling [cyan]--ai-skills[/cyan] "
|
||||||
|
"automatically for compatibility (.agents/skills is the recommended Codex layout)."
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _handle_agent_skills_migration(console: Console, agent_key: str) -> None:
|
||||||
|
"""Print a fail-fast migration error for agents that now require skills."""
|
||||||
|
migration = AGENT_SKILLS_MIGRATIONS[agent_key]
|
||||||
|
console.print(f"\n[red]Error:[/red] {migration['error']}")
|
||||||
console.print("Please use [cyan]--ai-skills[/cyan] when initializing to install templates as agent skills instead.")
|
console.print("Please use [cyan]--ai-skills[/cyan] when initializing to install templates as agent skills instead.")
|
||||||
console.print("[yellow]Usage:[/yellow] specify init <project> --ai agy --ai-skills")
|
console.print(f"[yellow]Usage:[/yellow] {migration['usage']}")
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
@@ -1467,7 +1499,7 @@ def init(
|
|||||||
specify init . --ai claude # Initialize in current directory
|
specify init . --ai claude # Initialize in current directory
|
||||||
specify init . # Initialize in current directory (interactive AI selection)
|
specify init . # Initialize in current directory (interactive AI selection)
|
||||||
specify init --here --ai claude # Alternative syntax for current directory
|
specify init --here --ai claude # Alternative syntax for current directory
|
||||||
specify init --here --ai codex
|
specify init --here --ai codex --ai-skills
|
||||||
specify init --here --ai codebuddy
|
specify init --here --ai codebuddy
|
||||||
specify init --here --ai vibe # Initialize with Mistral Vibe support
|
specify init --here --ai vibe # Initialize with Mistral Vibe support
|
||||||
specify init --here
|
specify init --here
|
||||||
@@ -1557,24 +1589,16 @@ def init(
|
|||||||
"copilot"
|
"copilot"
|
||||||
)
|
)
|
||||||
|
|
||||||
# [DEPRECATION NOTICE: Antigravity (agy)]
|
# Agents that have moved from explicit commands/prompts to agent skills.
|
||||||
# As of Antigravity v1.20.5, traditional CLI "command" support was fully removed
|
if selected_ai in AGENT_SKILLS_MIGRATIONS and not ai_skills:
|
||||||
# in favor of "Agent Skills" (SKILL.md files under <agent_folder>/skills/<skill_name>/).
|
# If selected interactively (no --ai provided), automatically enable
|
||||||
# Because 'specify_cli' historically populated .agent/commands/, we now must explicitly
|
|
||||||
# enforce the `--ai-skills` flag for `agy` to ensure valid template generation.
|
|
||||||
if selected_ai == "agy" and not ai_skills:
|
|
||||||
# If agy was selected interactively (no --ai provided), automatically enable
|
|
||||||
# ai_skills so the agent remains usable without requiring an extra flag.
|
# ai_skills so the agent remains usable without requiring an extra flag.
|
||||||
# Preserve deprecation behavior only for explicit '--ai agy' without skills.
|
# Preserve fail-fast behavior only for explicit '--ai <agent>' without skills.
|
||||||
if ai_assistant:
|
if ai_assistant:
|
||||||
_handle_agy_deprecation(console)
|
_handle_agent_skills_migration(console, selected_ai)
|
||||||
else:
|
else:
|
||||||
ai_skills = True
|
ai_skills = True
|
||||||
console.print(
|
console.print(f"\n[yellow]Note:[/yellow] {AGENT_SKILLS_MIGRATIONS[selected_ai]['interactive_note']}")
|
||||||
"\n[yellow]Note:[/yellow] 'agy' was selected interactively; "
|
|
||||||
"enabling [cyan]--ai-skills[/cyan] automatically for compatibility "
|
|
||||||
"(explicit .agent/commands usage is deprecated)."
|
|
||||||
)
|
|
||||||
|
|
||||||
# Validate --ai-commands-dir usage
|
# Validate --ai-commands-dir usage
|
||||||
if selected_ai == "generic":
|
if selected_ai == "generic":
|
||||||
@@ -1698,28 +1722,41 @@ def init(
|
|||||||
ensure_constitution_from_template(project_path, tracker=tracker)
|
ensure_constitution_from_template(project_path, tracker=tracker)
|
||||||
|
|
||||||
if ai_skills:
|
if ai_skills:
|
||||||
skills_ok = install_ai_skills(project_path, selected_ai, tracker=tracker)
|
if selected_ai in NATIVE_SKILLS_AGENTS:
|
||||||
|
skills_dir = _get_skills_dir(project_path, selected_ai)
|
||||||
|
if not _has_bundled_skills(project_path, selected_ai):
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Expected bundled agent skills in {skills_dir.relative_to(project_path)}, "
|
||||||
|
"but none were found. Re-run with an up-to-date template."
|
||||||
|
)
|
||||||
|
if tracker:
|
||||||
|
tracker.start("ai-skills")
|
||||||
|
tracker.complete("ai-skills", f"bundled skills → {skills_dir.relative_to(project_path)}")
|
||||||
|
else:
|
||||||
|
console.print(f"[green]✓[/green] Using bundled agent skills in {skills_dir.relative_to(project_path)}/")
|
||||||
|
else:
|
||||||
|
skills_ok = install_ai_skills(project_path, selected_ai, tracker=tracker)
|
||||||
|
|
||||||
# When --ai-skills is used on a NEW project and skills were
|
# When --ai-skills is used on a NEW project and skills were
|
||||||
# successfully installed, remove the command files that the
|
# successfully installed, remove the command files that the
|
||||||
# template archive just created. Skills replace commands, so
|
# template archive just created. Skills replace commands, so
|
||||||
# keeping both would be confusing. For --here on an existing
|
# keeping both would be confusing. For --here on an existing
|
||||||
# repo we leave pre-existing commands untouched to avoid a
|
# repo we leave pre-existing commands untouched to avoid a
|
||||||
# breaking change. We only delete AFTER skills succeed so the
|
# breaking change. We only delete AFTER skills succeed so the
|
||||||
# project always has at least one of {commands, skills}.
|
# project always has at least one of {commands, skills}.
|
||||||
if skills_ok and not here:
|
if skills_ok and not here:
|
||||||
agent_cfg = AGENT_CONFIG.get(selected_ai, {})
|
agent_cfg = AGENT_CONFIG.get(selected_ai, {})
|
||||||
agent_folder = agent_cfg.get("folder", "")
|
agent_folder = agent_cfg.get("folder", "")
|
||||||
commands_subdir = agent_cfg.get("commands_subdir", "commands")
|
commands_subdir = agent_cfg.get("commands_subdir", "commands")
|
||||||
if agent_folder:
|
if agent_folder:
|
||||||
cmds_dir = project_path / agent_folder.rstrip("/") / commands_subdir
|
cmds_dir = project_path / agent_folder.rstrip("/") / commands_subdir
|
||||||
if cmds_dir.exists():
|
if cmds_dir.exists():
|
||||||
try:
|
try:
|
||||||
shutil.rmtree(cmds_dir)
|
shutil.rmtree(cmds_dir)
|
||||||
except OSError:
|
except OSError:
|
||||||
# Best-effort cleanup: skills are already installed,
|
# Best-effort cleanup: skills are already installed,
|
||||||
# so leaving stale commands is non-fatal.
|
# so leaving stale commands is non-fatal.
|
||||||
console.print("[yellow]Warning: could not remove extracted commands directory[/yellow]")
|
console.print("[yellow]Warning: could not remove extracted commands directory[/yellow]")
|
||||||
|
|
||||||
if not no_git:
|
if not no_git:
|
||||||
tracker.start("git")
|
tracker.start("git")
|
||||||
@@ -1843,38 +1880,48 @@ def init(
|
|||||||
steps_lines.append("1. You're already in the project directory!")
|
steps_lines.append("1. You're already in the project directory!")
|
||||||
step_num = 2
|
step_num = 2
|
||||||
|
|
||||||
# Add Codex-specific setup step if needed
|
if selected_ai == "codex" and ai_skills:
|
||||||
if selected_ai == "codex":
|
steps_lines.append(f"{step_num}. Start Codex in this project directory; spec-kit skills were installed to [cyan].agents/skills[/cyan]")
|
||||||
codex_path = project_path / ".codex"
|
|
||||||
quoted_path = shlex.quote(str(codex_path))
|
|
||||||
if os.name == "nt": # Windows
|
|
||||||
cmd = f"setx CODEX_HOME {quoted_path}"
|
|
||||||
else: # Unix-like systems
|
|
||||||
cmd = f"export CODEX_HOME={quoted_path}"
|
|
||||||
|
|
||||||
steps_lines.append(f"{step_num}. Set [cyan]CODEX_HOME[/cyan] environment variable before running Codex: [cyan]{cmd}[/cyan]")
|
|
||||||
step_num += 1
|
step_num += 1
|
||||||
|
|
||||||
steps_lines.append(f"{step_num}. Start using slash commands with your AI agent:")
|
codex_skill_mode = selected_ai == "codex" and ai_skills
|
||||||
|
kimi_skill_mode = selected_ai == "kimi"
|
||||||
|
native_skill_mode = codex_skill_mode or kimi_skill_mode
|
||||||
|
usage_label = "skills" if native_skill_mode else "slash commands"
|
||||||
|
|
||||||
steps_lines.append(" 2.1 [cyan]/speckit.constitution[/] - Establish project principles")
|
def _display_cmd(name: str) -> str:
|
||||||
steps_lines.append(" 2.2 [cyan]/speckit.specify[/] - Create baseline specification")
|
if codex_skill_mode:
|
||||||
steps_lines.append(" 2.3 [cyan]/speckit.plan[/] - Create implementation plan")
|
return f"$speckit-{name}"
|
||||||
steps_lines.append(" 2.4 [cyan]/speckit.tasks[/] - Generate actionable tasks")
|
if kimi_skill_mode:
|
||||||
steps_lines.append(" 2.5 [cyan]/speckit.implement[/] - Execute implementation")
|
return f"/skill:speckit.{name}"
|
||||||
|
return f"/speckit.{name}"
|
||||||
|
|
||||||
|
steps_lines.append(f"{step_num}. Start using {usage_label} with your AI agent:")
|
||||||
|
|
||||||
|
steps_lines.append(f" {step_num}.1 [cyan]{_display_cmd('constitution')}[/] - Establish project principles")
|
||||||
|
steps_lines.append(f" {step_num}.2 [cyan]{_display_cmd('specify')}[/] - Create baseline specification")
|
||||||
|
steps_lines.append(f" {step_num}.3 [cyan]{_display_cmd('plan')}[/] - Create implementation plan")
|
||||||
|
steps_lines.append(f" {step_num}.4 [cyan]{_display_cmd('tasks')}[/] - Generate actionable tasks")
|
||||||
|
steps_lines.append(f" {step_num}.5 [cyan]{_display_cmd('implement')}[/] - Execute implementation")
|
||||||
|
|
||||||
steps_panel = Panel("\n".join(steps_lines), title="Next Steps", border_style="cyan", padding=(1,2))
|
steps_panel = Panel("\n".join(steps_lines), title="Next Steps", border_style="cyan", padding=(1,2))
|
||||||
console.print()
|
console.print()
|
||||||
console.print(steps_panel)
|
console.print(steps_panel)
|
||||||
|
|
||||||
|
enhancement_intro = (
|
||||||
|
"Optional skills that you can use for your specs [bright_black](improve quality & confidence)[/bright_black]"
|
||||||
|
if native_skill_mode
|
||||||
|
else "Optional commands that you can use for your specs [bright_black](improve quality & confidence)[/bright_black]"
|
||||||
|
)
|
||||||
enhancement_lines = [
|
enhancement_lines = [
|
||||||
"Optional commands that you can use for your specs [bright_black](improve quality & confidence)[/bright_black]",
|
enhancement_intro,
|
||||||
"",
|
"",
|
||||||
"○ [cyan]/speckit.clarify[/] [bright_black](optional)[/bright_black] - Ask structured questions to de-risk ambiguous areas before planning (run before [cyan]/speckit.plan[/] if used)",
|
f"○ [cyan]{_display_cmd('clarify')}[/] [bright_black](optional)[/bright_black] - Ask structured questions to de-risk ambiguous areas before planning (run before [cyan]{_display_cmd('plan')}[/] if used)",
|
||||||
"○ [cyan]/speckit.analyze[/] [bright_black](optional)[/bright_black] - Cross-artifact consistency & alignment report (after [cyan]/speckit.tasks[/], before [cyan]/speckit.implement[/])",
|
f"○ [cyan]{_display_cmd('analyze')}[/] [bright_black](optional)[/bright_black] - Cross-artifact consistency & alignment report (after [cyan]{_display_cmd('tasks')}[/], before [cyan]{_display_cmd('implement')}[/])",
|
||||||
"○ [cyan]/speckit.checklist[/] [bright_black](optional)[/bright_black] - Generate quality checklists to validate requirements completeness, clarity, and consistency (after [cyan]/speckit.plan[/])"
|
f"○ [cyan]{_display_cmd('checklist')}[/] [bright_black](optional)[/bright_black] - Generate quality checklists to validate requirements completeness, clarity, and consistency (after [cyan]{_display_cmd('plan')}[/])"
|
||||||
]
|
]
|
||||||
enhancements_panel = Panel("\n".join(enhancement_lines), title="Enhancement Commands", border_style="cyan", padding=(1,2))
|
enhancements_title = "Enhancement Skills" if native_skill_mode else "Enhancement Commands"
|
||||||
|
enhancements_panel = Panel("\n".join(enhancement_lines), title=enhancements_title, border_style="cyan", padding=(1,2))
|
||||||
console.print()
|
console.print()
|
||||||
console.print(enhancements_panel)
|
console.print(enhancements_panel)
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -140,7 +147,7 @@ class CommandRegistrar:
|
|||||||
"dir": ".kimi/skills",
|
"dir": ".kimi/skills",
|
||||||
"format": "markdown",
|
"format": "markdown",
|
||||||
"args": "$ARGUMENTS",
|
"args": "$ARGUMENTS",
|
||||||
"extension": "/SKILL.md"
|
"extension": "/SKILL.md",
|
||||||
},
|
},
|
||||||
"trae": {
|
"trae": {
|
||||||
"dir": ".trae/rules",
|
"dir": ".trae/rules",
|
||||||
@@ -182,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
|
||||||
@@ -209,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(
|
||||||
@@ -270,6 +283,95 @@ 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.
|
||||||
|
"""
|
||||||
|
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.
|
||||||
|
|
||||||
@@ -283,6 +385,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,
|
||||||
@@ -334,14 +448,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")
|
||||||
|
|
||||||
@@ -351,9 +471,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)
|
||||||
@@ -396,7 +522,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:
|
||||||
@@ -430,7 +556,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()
|
||||||
|
|
||||||
|
|||||||
@@ -646,8 +646,6 @@ class PresetManager:
|
|||||||
short_name = cmd_name
|
short_name = cmd_name
|
||||||
if short_name.startswith("speckit."):
|
if short_name.startswith("speckit."):
|
||||||
short_name = short_name[len("speckit."):]
|
short_name = short_name[len("speckit."):]
|
||||||
# Kimi CLI discovers skills by directory name and invokes them as
|
|
||||||
# /skill:<name> — use dot separator to match packaging convention.
|
|
||||||
if selected_ai == "kimi":
|
if selected_ai == "kimi":
|
||||||
skill_name = f"speckit.{short_name}"
|
skill_name = f"speckit.{short_name}"
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -29,11 +29,17 @@ class TestAgentConfigConsistency:
|
|||||||
assert "q" not in cfg
|
assert "q" not in cfg
|
||||||
|
|
||||||
def test_extension_registrar_includes_codex(self):
|
def test_extension_registrar_includes_codex(self):
|
||||||
"""Extension command registrar should include codex targeting .codex/prompts."""
|
"""Extension command registrar should include codex targeting .agents/skills."""
|
||||||
cfg = CommandRegistrar.AGENT_CONFIGS
|
cfg = CommandRegistrar.AGENT_CONFIGS
|
||||||
|
|
||||||
assert "codex" in cfg
|
assert "codex" in cfg
|
||||||
assert cfg["codex"]["dir"] == ".codex/prompts"
|
assert cfg["codex"]["dir"] == ".agents/skills"
|
||||||
|
assert cfg["codex"]["extension"] == "/SKILL.md"
|
||||||
|
|
||||||
|
def test_runtime_codex_uses_native_skills(self):
|
||||||
|
"""Codex runtime config should point at .agents/skills."""
|
||||||
|
assert AGENT_CONFIG["codex"]["folder"] == ".agents/"
|
||||||
|
assert AGENT_CONFIG["codex"]["commands_subdir"] == "skills"
|
||||||
|
|
||||||
def test_release_agent_lists_include_kiro_cli_and_exclude_q(self):
|
def test_release_agent_lists_include_kiro_cli_and_exclude_q(self):
|
||||||
"""Bash and PowerShell release scripts should agree on agent key set for Kiro."""
|
"""Bash and PowerShell release scripts should agree on agent key set for Kiro."""
|
||||||
@@ -71,6 +77,16 @@ class TestAgentConfigConsistency:
|
|||||||
assert re.search(r"shai\)\s*\n.*?\.shai/commands", sh_text, re.S) is not None
|
assert re.search(r"shai\)\s*\n.*?\.shai/commands", sh_text, re.S) is not None
|
||||||
assert re.search(r"agy\)\s*\n.*?\.agent/commands", sh_text, re.S) is not None
|
assert re.search(r"agy\)\s*\n.*?\.agent/commands", sh_text, re.S) is not None
|
||||||
|
|
||||||
|
def test_release_scripts_generate_codex_skills(self):
|
||||||
|
"""Release scripts should generate Codex skills in .agents/skills."""
|
||||||
|
sh_text = (REPO_ROOT / ".github" / "workflows" / "scripts" / "create-release-packages.sh").read_text(encoding="utf-8")
|
||||||
|
ps_text = (REPO_ROOT / ".github" / "workflows" / "scripts" / "create-release-packages.ps1").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
assert ".agents/skills" in sh_text
|
||||||
|
assert ".agents/skills" in ps_text
|
||||||
|
assert re.search(r"codex\)\s*\n.*?create_skills.*?\.agents/skills.*?\"-\"", sh_text, re.S) is not None
|
||||||
|
assert re.search(r"'codex'\s*\{.*?\.agents/skills.*?New-Skills.*?-Separator '-'", ps_text, re.S) is not None
|
||||||
|
|
||||||
def test_init_ai_help_includes_roo_and_kiro_alias(self):
|
def test_init_ai_help_includes_roo_and_kiro_alias(self):
|
||||||
"""CLI help text for --ai should stay in sync with agent config and alias guidance."""
|
"""CLI help text for --ai should stay in sync with agent config and alias guidance."""
|
||||||
assert "roo" in AI_ASSISTANT_HELP
|
assert "roo" in AI_ASSISTANT_HELP
|
||||||
|
|||||||
@@ -471,8 +471,7 @@ class TestInstallAiSkills:
|
|||||||
skills_dir = _get_skills_dir(proj, agent_key)
|
skills_dir = _get_skills_dir(proj, agent_key)
|
||||||
assert skills_dir.exists()
|
assert skills_dir.exists()
|
||||||
skill_dirs = [d.name for d in skills_dir.iterdir() if d.is_dir()]
|
skill_dirs = [d.name for d in skills_dir.iterdir() if d.is_dir()]
|
||||||
# Kimi uses dot-separator (speckit.specify) to match /skill:speckit.* invocation;
|
# Kimi uses dotted skill names; other agents use hyphen-separated names.
|
||||||
# all other agents use hyphen-separator (speckit-specify).
|
|
||||||
expected_skill_name = "speckit.specify" if agent_key == "kimi" else "speckit-specify"
|
expected_skill_name = "speckit.specify" if agent_key == "kimi" else "speckit-specify"
|
||||||
assert expected_skill_name in skill_dirs
|
assert expected_skill_name in skill_dirs
|
||||||
assert (skills_dir / expected_skill_name / "SKILL.md").exists()
|
assert (skills_dir / expected_skill_name / "SKILL.md").exists()
|
||||||
@@ -694,6 +693,82 @@ class TestNewProjectCommandSkip:
|
|||||||
prompts_dir = target / ".kiro" / "prompts"
|
prompts_dir = target / ".kiro" / "prompts"
|
||||||
assert not prompts_dir.exists()
|
assert not prompts_dir.exists()
|
||||||
|
|
||||||
|
def test_codex_native_skills_preserved_without_conversion(self, tmp_path):
|
||||||
|
"""Codex should keep bundled .agents/skills and skip install_ai_skills conversion."""
|
||||||
|
from typer.testing import CliRunner
|
||||||
|
|
||||||
|
runner = CliRunner()
|
||||||
|
target = tmp_path / "new-codex-proj"
|
||||||
|
|
||||||
|
def fake_download(project_path, *args, **kwargs):
|
||||||
|
skill_dir = project_path / ".agents" / "skills" / "speckit-specify"
|
||||||
|
skill_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
(skill_dir / "SKILL.md").write_text("---\ndescription: Test skill\n---\n\nBody.\n")
|
||||||
|
|
||||||
|
with patch("specify_cli.download_and_extract_template", side_effect=fake_download), \
|
||||||
|
patch("specify_cli.ensure_executable_scripts"), \
|
||||||
|
patch("specify_cli.ensure_constitution_from_template"), \
|
||||||
|
patch("specify_cli.install_ai_skills") as mock_skills, \
|
||||||
|
patch("specify_cli.is_git_repo", return_value=False), \
|
||||||
|
patch("specify_cli.shutil.which", return_value="/usr/bin/codex"):
|
||||||
|
result = runner.invoke(
|
||||||
|
app,
|
||||||
|
["init", str(target), "--ai", "codex", "--ai-skills", "--script", "sh", "--no-git"],
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result.exit_code == 0
|
||||||
|
mock_skills.assert_not_called()
|
||||||
|
assert (target / ".agents" / "skills" / "speckit-specify" / "SKILL.md").exists()
|
||||||
|
|
||||||
|
def test_codex_native_skills_missing_fails_clearly(self, tmp_path):
|
||||||
|
"""Codex native skills init should fail if bundled skills are missing."""
|
||||||
|
from typer.testing import CliRunner
|
||||||
|
|
||||||
|
runner = CliRunner()
|
||||||
|
target = tmp_path / "missing-codex-skills"
|
||||||
|
|
||||||
|
with patch("specify_cli.download_and_extract_template", lambda *args, **kwargs: None), \
|
||||||
|
patch("specify_cli.ensure_executable_scripts"), \
|
||||||
|
patch("specify_cli.ensure_constitution_from_template"), \
|
||||||
|
patch("specify_cli.install_ai_skills") as mock_skills, \
|
||||||
|
patch("specify_cli.is_git_repo", return_value=False), \
|
||||||
|
patch("specify_cli.shutil.which", return_value="/usr/bin/codex"):
|
||||||
|
result = runner.invoke(
|
||||||
|
app,
|
||||||
|
["init", str(target), "--ai", "codex", "--ai-skills", "--script", "sh", "--no-git"],
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result.exit_code == 1
|
||||||
|
mock_skills.assert_not_called()
|
||||||
|
assert "Expected bundled agent skills" in result.output
|
||||||
|
|
||||||
|
def test_codex_native_skills_ignores_non_speckit_skill_dirs(self, tmp_path):
|
||||||
|
"""Non-spec-kit SKILL.md files should not satisfy Codex bundled-skills validation."""
|
||||||
|
from typer.testing import CliRunner
|
||||||
|
|
||||||
|
runner = CliRunner()
|
||||||
|
target = tmp_path / "foreign-codex-skills"
|
||||||
|
|
||||||
|
def fake_download(project_path, *args, **kwargs):
|
||||||
|
skill_dir = project_path / ".agents" / "skills" / "other-tool"
|
||||||
|
skill_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
(skill_dir / "SKILL.md").write_text("---\ndescription: Foreign skill\n---\n\nBody.\n")
|
||||||
|
|
||||||
|
with patch("specify_cli.download_and_extract_template", side_effect=fake_download), \
|
||||||
|
patch("specify_cli.ensure_executable_scripts"), \
|
||||||
|
patch("specify_cli.ensure_constitution_from_template"), \
|
||||||
|
patch("specify_cli.install_ai_skills") as mock_skills, \
|
||||||
|
patch("specify_cli.is_git_repo", return_value=False), \
|
||||||
|
patch("specify_cli.shutil.which", return_value="/usr/bin/codex"):
|
||||||
|
result = runner.invoke(
|
||||||
|
app,
|
||||||
|
["init", str(target), "--ai", "codex", "--ai-skills", "--script", "sh", "--no-git"],
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result.exit_code == 1
|
||||||
|
mock_skills.assert_not_called()
|
||||||
|
assert "Expected bundled agent skills" in result.output
|
||||||
|
|
||||||
def test_commands_preserved_when_skills_fail(self, tmp_path):
|
def test_commands_preserved_when_skills_fail(self, tmp_path):
|
||||||
"""If skills fail, commands should NOT be removed (safety net)."""
|
"""If skills fail, commands should NOT be removed (safety net)."""
|
||||||
from typer.testing import CliRunner
|
from typer.testing import CliRunner
|
||||||
@@ -837,6 +912,17 @@ class TestCliValidation:
|
|||||||
assert "Explicit command support was deprecated in Antigravity version 1.20.5." in result.output
|
assert "Explicit command support was deprecated in Antigravity version 1.20.5." in result.output
|
||||||
assert "--ai-skills" in result.output
|
assert "--ai-skills" in result.output
|
||||||
|
|
||||||
|
def test_codex_without_ai_skills_fails(self):
|
||||||
|
"""--ai codex without --ai-skills should fail with exit code 1."""
|
||||||
|
from typer.testing import CliRunner
|
||||||
|
|
||||||
|
runner = CliRunner()
|
||||||
|
result = runner.invoke(app, ["init", "test-proj", "--ai", "codex"])
|
||||||
|
|
||||||
|
assert result.exit_code == 1
|
||||||
|
assert "Custom prompt-based spec-kit initialization is deprecated for Codex CLI" in result.output
|
||||||
|
assert "--ai-skills" in result.output
|
||||||
|
|
||||||
def test_interactive_agy_without_ai_skills_prompts_skills(self, monkeypatch):
|
def test_interactive_agy_without_ai_skills_prompts_skills(self, monkeypatch):
|
||||||
"""Interactive selector returning agy without --ai-skills should automatically enable --ai-skills."""
|
"""Interactive selector returning agy without --ai-skills should automatically enable --ai-skills."""
|
||||||
from typer.testing import CliRunner
|
from typer.testing import CliRunner
|
||||||
@@ -879,6 +965,72 @@ class TestCliValidation:
|
|||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
assert "Explicit command support was deprecated" not in result.output
|
assert "Explicit command support was deprecated" not in result.output
|
||||||
|
|
||||||
|
def test_interactive_codex_without_ai_skills_enables_skills(self, monkeypatch):
|
||||||
|
"""Interactive selector returning codex without --ai-skills should automatically enable --ai-skills."""
|
||||||
|
from typer.testing import CliRunner
|
||||||
|
|
||||||
|
def _fake_select_with_arrows(*args, **kwargs):
|
||||||
|
options = kwargs.get("options")
|
||||||
|
if options is None and len(args) >= 1:
|
||||||
|
options = args[0]
|
||||||
|
|
||||||
|
if isinstance(options, dict) and "codex" in options:
|
||||||
|
return "codex"
|
||||||
|
if isinstance(options, (list, tuple)) and "codex" in options:
|
||||||
|
return "codex"
|
||||||
|
|
||||||
|
if isinstance(options, dict) and options:
|
||||||
|
return next(iter(options.keys()))
|
||||||
|
if isinstance(options, (list, tuple)) and options:
|
||||||
|
return options[0]
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
monkeypatch.setattr("specify_cli.select_with_arrows", _fake_select_with_arrows)
|
||||||
|
|
||||||
|
def _fake_download(*args, **kwargs):
|
||||||
|
project_path = Path(args[0])
|
||||||
|
skill_dir = project_path / ".agents" / "skills" / "speckit-specify"
|
||||||
|
skill_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
(skill_dir / "SKILL.md").write_text("---\ndescription: Test skill\n---\n\nBody.\n")
|
||||||
|
|
||||||
|
monkeypatch.setattr("specify_cli.download_and_extract_template", _fake_download)
|
||||||
|
|
||||||
|
runner = CliRunner()
|
||||||
|
with runner.isolated_filesystem():
|
||||||
|
result = runner.invoke(app, ["init", "test-proj", "--no-git", "--ignore-agent-tools"])
|
||||||
|
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert "Custom prompt-based spec-kit initialization is deprecated for Codex CLI" not in result.output
|
||||||
|
assert ".agents/skills" in result.output
|
||||||
|
assert "$speckit-constitution" in result.output
|
||||||
|
assert "/speckit.constitution" not in result.output
|
||||||
|
assert "Optional skills that you can use for your specs" in result.output
|
||||||
|
|
||||||
|
def test_kimi_next_steps_show_skill_invocation(self, monkeypatch):
|
||||||
|
"""Kimi next-steps guidance should display /skill:speckit.* usage."""
|
||||||
|
from typer.testing import CliRunner
|
||||||
|
|
||||||
|
def _fake_download(*args, **kwargs):
|
||||||
|
project_path = Path(args[0])
|
||||||
|
skill_dir = project_path / ".kimi" / "skills" / "speckit.specify"
|
||||||
|
skill_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
(skill_dir / "SKILL.md").write_text("---\ndescription: Test skill\n---\n\nBody.\n")
|
||||||
|
|
||||||
|
monkeypatch.setattr("specify_cli.download_and_extract_template", _fake_download)
|
||||||
|
|
||||||
|
runner = CliRunner()
|
||||||
|
with runner.isolated_filesystem():
|
||||||
|
result = runner.invoke(
|
||||||
|
app,
|
||||||
|
["init", "test-proj", "--ai", "kimi", "--no-git", "--ignore-agent-tools"],
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert "/skill:speckit.constitution" in result.output
|
||||||
|
assert "/speckit.constitution" not in result.output
|
||||||
|
assert "Optional skills that you can use for your specs" in result.output
|
||||||
|
|
||||||
def test_ai_skills_flag_appears_in_help(self):
|
def test_ai_skills_flag_appears_in_help(self):
|
||||||
"""--ai-skills should appear in init --help output."""
|
"""--ai-skills should appear in init --help output."""
|
||||||
from typer.testing import CliRunner
|
from typer.testing import CliRunner
|
||||||
|
|||||||
@@ -665,9 +665,10 @@ class TestCommandRegistrar:
|
|||||||
assert "q" not in CommandRegistrar.AGENT_CONFIGS
|
assert "q" not in CommandRegistrar.AGENT_CONFIGS
|
||||||
|
|
||||||
def test_codex_agent_config_present(self):
|
def test_codex_agent_config_present(self):
|
||||||
"""Codex should be mapped to .codex/prompts."""
|
"""Codex should be mapped to .agents/skills."""
|
||||||
assert "codex" in CommandRegistrar.AGENT_CONFIGS
|
assert "codex" in CommandRegistrar.AGENT_CONFIGS
|
||||||
assert CommandRegistrar.AGENT_CONFIGS["codex"]["dir"] == ".codex/prompts"
|
assert CommandRegistrar.AGENT_CONFIGS["codex"]["dir"] == ".agents/skills"
|
||||||
|
assert CommandRegistrar.AGENT_CONFIGS["codex"]["extension"] == "/SKILL.md"
|
||||||
|
|
||||||
def test_pi_agent_config_present(self):
|
def test_pi_agent_config_present(self):
|
||||||
"""Pi should be mapped to .pi/prompts."""
|
"""Pi should be mapped to .pi/prompts."""
|
||||||
@@ -717,6 +718,21 @@ $ARGUMENTS
|
|||||||
assert frontmatter == {}
|
assert frontmatter == {}
|
||||||
assert body == content
|
assert body == content
|
||||||
|
|
||||||
|
def test_parse_frontmatter_non_mapping_returns_empty_dict(self):
|
||||||
|
"""Non-mapping YAML frontmatter should not crash downstream renderers."""
|
||||||
|
content = """---
|
||||||
|
- item1
|
||||||
|
- item2
|
||||||
|
---
|
||||||
|
|
||||||
|
# Command body
|
||||||
|
"""
|
||||||
|
registrar = CommandRegistrar()
|
||||||
|
frontmatter, body = registrar.parse_frontmatter(content)
|
||||||
|
|
||||||
|
assert frontmatter == {}
|
||||||
|
assert "Command body" in body
|
||||||
|
|
||||||
def test_render_frontmatter(self):
|
def test_render_frontmatter(self):
|
||||||
"""Test rendering frontmatter to YAML."""
|
"""Test rendering frontmatter to YAML."""
|
||||||
frontmatter = {
|
frontmatter = {
|
||||||
@@ -808,6 +824,299 @@ $ARGUMENTS
|
|||||||
assert (claude_dir / "speckit.alias.cmd.md").exists()
|
assert (claude_dir / "speckit.alias.cmd.md").exists()
|
||||||
assert (claude_dir / "speckit.shortcut.md").exists()
|
assert (claude_dir / "speckit.shortcut.md").exists()
|
||||||
|
|
||||||
|
def test_unregister_commands_for_codex_skills_uses_mapped_names(self, project_dir):
|
||||||
|
"""Codex skill cleanup should use the same mapped names as registration."""
|
||||||
|
skills_dir = project_dir / ".agents" / "skills"
|
||||||
|
(skills_dir / "speckit-specify").mkdir(parents=True)
|
||||||
|
(skills_dir / "speckit-specify" / "SKILL.md").write_text("body")
|
||||||
|
(skills_dir / "speckit-shortcut").mkdir(parents=True)
|
||||||
|
(skills_dir / "speckit-shortcut" / "SKILL.md").write_text("body")
|
||||||
|
|
||||||
|
registrar = CommandRegistrar()
|
||||||
|
registrar.unregister_commands(
|
||||||
|
{"codex": ["speckit.specify", "speckit.shortcut"]},
|
||||||
|
project_dir,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert not (skills_dir / "speckit-specify" / "SKILL.md").exists()
|
||||||
|
assert not (skills_dir / "speckit-shortcut" / "SKILL.md").exists()
|
||||||
|
|
||||||
|
def test_register_commands_for_all_agents_distinguishes_codex_from_amp(self, extension_dir, project_dir):
|
||||||
|
"""A Codex project under .agents/skills should not implicitly activate Amp."""
|
||||||
|
skills_dir = project_dir / ".agents" / "skills"
|
||||||
|
skills_dir.mkdir(parents=True)
|
||||||
|
|
||||||
|
manifest = ExtensionManifest(extension_dir / "extension.yml")
|
||||||
|
registrar = CommandRegistrar()
|
||||||
|
registered = registrar.register_commands_for_all_agents(manifest, extension_dir, project_dir)
|
||||||
|
|
||||||
|
assert "codex" in registered
|
||||||
|
assert "amp" not in registered
|
||||||
|
assert not (project_dir / ".agents" / "commands").exists()
|
||||||
|
|
||||||
|
def test_codex_skill_registration_writes_skill_frontmatter(self, extension_dir, project_dir):
|
||||||
|
"""Codex SKILL.md output should use skills-oriented frontmatter."""
|
||||||
|
skills_dir = project_dir / ".agents" / "skills"
|
||||||
|
skills_dir.mkdir(parents=True)
|
||||||
|
|
||||||
|
manifest = ExtensionManifest(extension_dir / "extension.yml")
|
||||||
|
registrar = CommandRegistrar()
|
||||||
|
registrar.register_commands_for_agent("codex", manifest, extension_dir, project_dir)
|
||||||
|
|
||||||
|
skill_file = skills_dir / "speckit-test.hello" / "SKILL.md"
|
||||||
|
assert skill_file.exists()
|
||||||
|
|
||||||
|
content = skill_file.read_text()
|
||||||
|
assert "name: speckit-test.hello" in content
|
||||||
|
assert "description: Test hello command" in content
|
||||||
|
assert "compatibility:" in content
|
||||||
|
assert "metadata:" in content
|
||||||
|
assert "source: test-ext:commands/hello.md" in content
|
||||||
|
assert "<!-- Extension:" not in content
|
||||||
|
|
||||||
|
def test_codex_skill_registration_resolves_script_placeholders(self, project_dir, temp_dir):
|
||||||
|
"""Codex SKILL.md overrides should resolve script placeholders."""
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
ext_dir = temp_dir / "ext-scripted"
|
||||||
|
ext_dir.mkdir()
|
||||||
|
(ext_dir / "commands").mkdir()
|
||||||
|
|
||||||
|
manifest_data = {
|
||||||
|
"schema_version": "1.0",
|
||||||
|
"extension": {
|
||||||
|
"id": "ext-scripted",
|
||||||
|
"name": "Scripted Extension",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Test",
|
||||||
|
},
|
||||||
|
"requires": {"speckit_version": ">=0.1.0"},
|
||||||
|
"provides": {
|
||||||
|
"commands": [
|
||||||
|
{
|
||||||
|
"name": "speckit.test.plan",
|
||||||
|
"file": "commands/plan.md",
|
||||||
|
"description": "Scripted command",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
with open(ext_dir / "extension.yml", "w") as f:
|
||||||
|
yaml.dump(manifest_data, f)
|
||||||
|
|
||||||
|
(ext_dir / "commands" / "plan.md").write_text(
|
||||||
|
"""---
|
||||||
|
description: "Scripted command"
|
||||||
|
scripts:
|
||||||
|
sh: ../../scripts/bash/setup-plan.sh --json "{ARGS}"
|
||||||
|
ps: ../../scripts/powershell/setup-plan.ps1 -Json
|
||||||
|
agent_scripts:
|
||||||
|
sh: ../../scripts/bash/update-agent-context.sh __AGENT__
|
||||||
|
ps: ../../scripts/powershell/update-agent-context.ps1 -AgentType __AGENT__
|
||||||
|
---
|
||||||
|
|
||||||
|
Run {SCRIPT}
|
||||||
|
Then {AGENT_SCRIPT}
|
||||||
|
Agent __AGENT__
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
init_options = project_dir / ".specify" / "init-options.json"
|
||||||
|
init_options.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
init_options.write_text('{"ai":"codex","ai_skills":true,"script":"sh"}')
|
||||||
|
|
||||||
|
skills_dir = project_dir / ".agents" / "skills"
|
||||||
|
skills_dir.mkdir(parents=True)
|
||||||
|
|
||||||
|
manifest = ExtensionManifest(ext_dir / "extension.yml")
|
||||||
|
registrar = CommandRegistrar()
|
||||||
|
registrar.register_commands_for_agent("codex", manifest, ext_dir, project_dir)
|
||||||
|
|
||||||
|
skill_file = skills_dir / "speckit-test.plan" / "SKILL.md"
|
||||||
|
assert skill_file.exists()
|
||||||
|
|
||||||
|
content = skill_file.read_text()
|
||||||
|
assert "{SCRIPT}" not in content
|
||||||
|
assert "{AGENT_SCRIPT}" not in content
|
||||||
|
assert "__AGENT__" not in content
|
||||||
|
assert "{ARGS}" not in content
|
||||||
|
assert '.specify/scripts/bash/setup-plan.sh --json "$ARGUMENTS"' in content
|
||||||
|
assert ".specify/scripts/bash/update-agent-context.sh codex" in content
|
||||||
|
|
||||||
|
def test_codex_skill_alias_frontmatter_matches_alias_name(self, project_dir, temp_dir):
|
||||||
|
"""Codex alias skills should render their own matching `name:` frontmatter."""
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
ext_dir = temp_dir / "ext-alias-skill"
|
||||||
|
ext_dir.mkdir()
|
||||||
|
(ext_dir / "commands").mkdir()
|
||||||
|
|
||||||
|
manifest_data = {
|
||||||
|
"schema_version": "1.0",
|
||||||
|
"extension": {
|
||||||
|
"id": "ext-alias-skill",
|
||||||
|
"name": "Alias Skill Extension",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Test",
|
||||||
|
},
|
||||||
|
"requires": {"speckit_version": ">=0.1.0"},
|
||||||
|
"provides": {
|
||||||
|
"commands": [
|
||||||
|
{
|
||||||
|
"name": "speckit.alias.cmd",
|
||||||
|
"file": "commands/cmd.md",
|
||||||
|
"aliases": ["speckit.shortcut"],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
with open(ext_dir / "extension.yml", "w") as f:
|
||||||
|
yaml.dump(manifest_data, f)
|
||||||
|
|
||||||
|
(ext_dir / "commands" / "cmd.md").write_text("---\ndescription: Alias skill\n---\n\nBody\n")
|
||||||
|
|
||||||
|
skills_dir = project_dir / ".agents" / "skills"
|
||||||
|
skills_dir.mkdir(parents=True)
|
||||||
|
|
||||||
|
manifest = ExtensionManifest(ext_dir / "extension.yml")
|
||||||
|
registrar = CommandRegistrar()
|
||||||
|
registrar.register_commands_for_agent("codex", manifest, ext_dir, project_dir)
|
||||||
|
|
||||||
|
primary = skills_dir / "speckit-alias.cmd" / "SKILL.md"
|
||||||
|
alias = skills_dir / "speckit-shortcut" / "SKILL.md"
|
||||||
|
|
||||||
|
assert primary.exists()
|
||||||
|
assert alias.exists()
|
||||||
|
assert "name: speckit-alias.cmd" in primary.read_text()
|
||||||
|
assert "name: speckit-shortcut" in alias.read_text()
|
||||||
|
|
||||||
|
def test_codex_skill_registration_uses_fallback_script_variant_without_init_options(
|
||||||
|
self, project_dir, temp_dir
|
||||||
|
):
|
||||||
|
"""Codex placeholder substitution should still work without init-options.json."""
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
ext_dir = temp_dir / "ext-script-fallback"
|
||||||
|
ext_dir.mkdir()
|
||||||
|
(ext_dir / "commands").mkdir()
|
||||||
|
|
||||||
|
manifest_data = {
|
||||||
|
"schema_version": "1.0",
|
||||||
|
"extension": {
|
||||||
|
"id": "ext-script-fallback",
|
||||||
|
"name": "Script fallback",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Test",
|
||||||
|
},
|
||||||
|
"requires": {"speckit_version": ">=0.1.0"},
|
||||||
|
"provides": {
|
||||||
|
"commands": [
|
||||||
|
{
|
||||||
|
"name": "speckit.fallback.plan",
|
||||||
|
"file": "commands/plan.md",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
with open(ext_dir / "extension.yml", "w") as f:
|
||||||
|
yaml.dump(manifest_data, f)
|
||||||
|
|
||||||
|
(ext_dir / "commands" / "plan.md").write_text(
|
||||||
|
"""---
|
||||||
|
description: "Fallback scripted command"
|
||||||
|
scripts:
|
||||||
|
sh: ../../scripts/bash/setup-plan.sh --json "{ARGS}"
|
||||||
|
ps: ../../scripts/powershell/setup-plan.ps1 -Json
|
||||||
|
agent_scripts:
|
||||||
|
sh: ../../scripts/bash/update-agent-context.sh __AGENT__
|
||||||
|
---
|
||||||
|
|
||||||
|
Run {SCRIPT}
|
||||||
|
Then {AGENT_SCRIPT}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
# Intentionally do NOT create .specify/init-options.json
|
||||||
|
skills_dir = project_dir / ".agents" / "skills"
|
||||||
|
skills_dir.mkdir(parents=True)
|
||||||
|
|
||||||
|
manifest = ExtensionManifest(ext_dir / "extension.yml")
|
||||||
|
registrar = CommandRegistrar()
|
||||||
|
registrar.register_commands_for_agent("codex", manifest, ext_dir, project_dir)
|
||||||
|
|
||||||
|
skill_file = skills_dir / "speckit-fallback.plan" / "SKILL.md"
|
||||||
|
assert skill_file.exists()
|
||||||
|
|
||||||
|
content = skill_file.read_text()
|
||||||
|
assert "{SCRIPT}" not in content
|
||||||
|
assert "{AGENT_SCRIPT}" not in content
|
||||||
|
assert '.specify/scripts/bash/setup-plan.sh --json "$ARGUMENTS"' in content
|
||||||
|
assert ".specify/scripts/bash/update-agent-context.sh codex" in content
|
||||||
|
|
||||||
|
def test_codex_skill_registration_fallback_prefers_powershell_on_windows(
|
||||||
|
self, project_dir, temp_dir, monkeypatch
|
||||||
|
):
|
||||||
|
"""Without init metadata, Windows fallback should prefer ps scripts over sh."""
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
monkeypatch.setattr("specify_cli.agents.platform.system", lambda: "Windows")
|
||||||
|
|
||||||
|
ext_dir = temp_dir / "ext-script-windows-fallback"
|
||||||
|
ext_dir.mkdir()
|
||||||
|
(ext_dir / "commands").mkdir()
|
||||||
|
|
||||||
|
manifest_data = {
|
||||||
|
"schema_version": "1.0",
|
||||||
|
"extension": {
|
||||||
|
"id": "ext-script-windows-fallback",
|
||||||
|
"name": "Script fallback windows",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Test",
|
||||||
|
},
|
||||||
|
"requires": {"speckit_version": ">=0.1.0"},
|
||||||
|
"provides": {
|
||||||
|
"commands": [
|
||||||
|
{
|
||||||
|
"name": "speckit.windows.plan",
|
||||||
|
"file": "commands/plan.md",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
with open(ext_dir / "extension.yml", "w") as f:
|
||||||
|
yaml.dump(manifest_data, f)
|
||||||
|
|
||||||
|
(ext_dir / "commands" / "plan.md").write_text(
|
||||||
|
"""---
|
||||||
|
description: "Windows fallback scripted command"
|
||||||
|
scripts:
|
||||||
|
sh: ../../scripts/bash/setup-plan.sh --json "{ARGS}"
|
||||||
|
ps: ../../scripts/powershell/setup-plan.ps1 -Json
|
||||||
|
agent_scripts:
|
||||||
|
sh: ../../scripts/bash/update-agent-context.sh __AGENT__
|
||||||
|
ps: ../../scripts/powershell/update-agent-context.ps1 -AgentType __AGENT__
|
||||||
|
---
|
||||||
|
|
||||||
|
Run {SCRIPT}
|
||||||
|
Then {AGENT_SCRIPT}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
skills_dir = project_dir / ".agents" / "skills"
|
||||||
|
skills_dir.mkdir(parents=True)
|
||||||
|
|
||||||
|
manifest = ExtensionManifest(ext_dir / "extension.yml")
|
||||||
|
registrar = CommandRegistrar()
|
||||||
|
registrar.register_commands_for_agent("codex", manifest, ext_dir, project_dir)
|
||||||
|
|
||||||
|
skill_file = skills_dir / "speckit-windows.plan" / "SKILL.md"
|
||||||
|
assert skill_file.exists()
|
||||||
|
|
||||||
|
content = skill_file.read_text()
|
||||||
|
assert ".specify/scripts/powershell/setup-plan.ps1 -Json" in content
|
||||||
|
assert ".specify/scripts/powershell/update-agent-context.ps1 -AgentType codex" in content
|
||||||
|
assert ".specify/scripts/bash/setup-plan.sh" not in content
|
||||||
|
|
||||||
def test_register_commands_for_copilot(self, extension_dir, project_dir):
|
def test_register_commands_for_copilot(self, extension_dir, project_dir):
|
||||||
"""Test registering commands for Copilot agent with .agent.md extension."""
|
"""Test registering commands for Copilot agent with .agent.md extension."""
|
||||||
# Create .github/agents directory (Copilot project)
|
# Create .github/agents directory (Copilot project)
|
||||||
|
|||||||
Reference in New Issue
Block a user