mirror of
https://github.com/github/spec-kit.git
synced 2026-07-03 20:36:23 +08:00
Compare commits
1 Commits
copilot/do
...
v0.7.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b397b02f1 |
5
.github/CODEOWNERS
vendored
5
.github/CODEOWNERS
vendored
@@ -1,8 +1,3 @@
|
||||
# Global code owner
|
||||
* @mnriem
|
||||
|
||||
# Community catalog files — explicit ownership for when global ownership expands
|
||||
/extensions/catalog.community.json @mnriem
|
||||
/integrations/catalog.community.json @mnriem
|
||||
/presets/catalog.community.json @mnriem
|
||||
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/agent_request.yml
vendored
2
.github/ISSUE_TEMPLATE/agent_request.yml
vendored
@@ -8,7 +8,7 @@ body:
|
||||
value: |
|
||||
Thanks for requesting a new agent! Before submitting, please check if the agent is already supported.
|
||||
|
||||
**Currently supported agents**: Claude Code, Gemini CLI, GitHub Copilot, Cursor, Qwen Code, opencode, Codex CLI, Windsurf, Kilo Code, Auggie CLI, Roo Code, CodeBuddy, Qoder CLI, Kiro CLI, Amp, SHAI, Tabnine CLI, Antigravity, IBM Bob, Mistral Vibe, Kimi Code, Trae, Pi Coding Agent, iFlow CLI, Devin for Terminal
|
||||
**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
|
||||
id: agent-name
|
||||
|
||||
22
.github/ISSUE_TEMPLATE/preset_submission.yml
vendored
22
.github/ISSUE_TEMPLATE/preset_submission.yml
vendored
@@ -95,18 +95,11 @@ body:
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: required-extensions
|
||||
attributes:
|
||||
label: Required Extensions (optional)
|
||||
description: Comma-separated list of required extension IDs (e.g., aide)
|
||||
placeholder: "e.g., aide, canon"
|
||||
|
||||
- type: textarea
|
||||
id: templates-provided
|
||||
attributes:
|
||||
label: Templates Provided
|
||||
description: List the template overrides your preset provides (enter "None" if command-only)
|
||||
description: List the template overrides your preset provides
|
||||
placeholder: |
|
||||
- spec-template.md — adds compliance section
|
||||
- plan-template.md — includes audit checkpoints
|
||||
@@ -117,19 +110,10 @@ body:
|
||||
- type: textarea
|
||||
id: commands-provided
|
||||
attributes:
|
||||
label: Commands Provided
|
||||
description: List the command overrides your preset provides (enter "None" if template-only)
|
||||
label: Commands Provided (optional)
|
||||
description: List any command overrides your preset provides
|
||||
placeholder: |
|
||||
- speckit.specify.md — customized for compliance workflows
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: scripts-count
|
||||
attributes:
|
||||
label: Number of Scripts (optional)
|
||||
description: How many scripts does your preset provide? (leave empty if none)
|
||||
placeholder: "e.g., 1"
|
||||
|
||||
- type: textarea
|
||||
id: tags
|
||||
|
||||
59
.github/workflows/catalog-assign.yml
vendored
59
.github/workflows/catalog-assign.yml
vendored
@@ -1,59 +0,0 @@
|
||||
name: "Catalog: Auto-assign submission"
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened, labeled]
|
||||
|
||||
jobs:
|
||||
assign:
|
||||
if: >
|
||||
(github.event.action == 'opened' && (
|
||||
contains(github.event.issue.labels.*.name, 'extension-submission') ||
|
||||
contains(github.event.issue.labels.*.name, 'preset-submission')
|
||||
)) ||
|
||||
(github.event.action == 'labeled' && (
|
||||
github.event.label.name == 'extension-submission' ||
|
||||
github.event.label.name == 'preset-submission'
|
||||
))
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const issue = context.payload.issue;
|
||||
const assigned = (issue.assignees || []).map(a => a.login);
|
||||
const marker = '<!-- catalog-assign-bot -->';
|
||||
|
||||
// Assign mnriem if not already assigned
|
||||
if (!assigned.includes('mnriem')) {
|
||||
try {
|
||||
await github.rest.issues.addAssignees({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
assignees: ['mnriem'],
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(`Warning: could not assign mnriem: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Post team notification if not already posted
|
||||
const comments = await github.paginate(
|
||||
github.rest.issues.listComments,
|
||||
{
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
}
|
||||
);
|
||||
if (!comments.some(c => c.body && c.body.includes(marker))) {
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
body: marker + '\ncc @github/spec-kit-maintainers — new catalog submission for review.',
|
||||
});
|
||||
}
|
||||
6
.github/workflows/codeql.yml
vendored
6
.github/workflows/codeql.yml
vendored
@@ -19,14 +19,14 @@ jobs:
|
||||
language: [ 'actions', 'python' ]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@e46ed2cbd01164d986452f91f178727624ae40d7 # v4
|
||||
uses: github/codeql-action/init@v4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@e46ed2cbd01164d986452f91f178727624ae40d7 # v4
|
||||
uses: github/codeql-action/analyze@v4
|
||||
with:
|
||||
category: "/language:${{ matrix.language }}"
|
||||
|
||||
11
.github/workflows/docs.yml
vendored
11
.github/workflows/docs.yml
vendored
@@ -30,12 +30,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0 # Fetch all history for git info
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: '8.x'
|
||||
|
||||
@@ -48,10 +48,10 @@ jobs:
|
||||
docfx docfx.json
|
||||
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6
|
||||
uses: actions/configure-pages@v6
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: 'docs/_site'
|
||||
|
||||
@@ -66,4 +66,5 @@ jobs:
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5
|
||||
uses: actions/deploy-pages@v5
|
||||
|
||||
|
||||
4
.github/workflows/lint.yml
vendored
4
.github/workflows/lint.yml
vendored
@@ -12,10 +12,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Run markdownlint-cli2
|
||||
uses: DavidAnson/markdownlint-cli2-action@6b51ade7a9e4a75a7ad929842dd298a3804ebe8b # v23
|
||||
uses: DavidAnson/markdownlint-cli2-action@ce4853d43830c74c1753b39f3cf40f71c2031eb9 # v23
|
||||
with:
|
||||
globs: |
|
||||
'**/*.md'
|
||||
|
||||
2
.github/workflows/release-trigger.yml
vendored
2
.github/workflows/release-trigger.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.RELEASE_PAT }}
|
||||
|
||||
3
.github/workflows/release.yml
vendored
3
.github/workflows/release.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -86,3 +86,4 @@ jobs:
|
||||
--notes-file release_notes.md
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
||||
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10
|
||||
- uses: actions/stale@v10
|
||||
with:
|
||||
# Days of inactivity before an issue or PR becomes stale
|
||||
days-before-stale: 150
|
||||
|
||||
12
.github/workflows/test.yml
vendored
12
.github/workflows/test.yml
vendored
@@ -13,13 +13,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
||||
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.13"
|
||||
|
||||
@@ -34,13 +34,13 @@ jobs:
|
||||
python-version: ["3.11", "3.12", "3.13"]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
||||
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
|
||||
29
.zenodo.json
29
.zenodo.json
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"title": "Spec Kit",
|
||||
"description": "Spec Kit is an open source toolkit for Spec-Driven Development (SDD) — a methodology that helps software teams build high-quality software faster by focusing on product scenarios and predictable outcomes. It provides the Specify CLI, slash-command templates, extensions, presets, workflows, and integrations for popular AI coding agents.",
|
||||
"creators": [
|
||||
{
|
||||
"name": "Delimarsky, Den"
|
||||
},
|
||||
{
|
||||
"name": "Riem, Manfred"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"upload_type": "software",
|
||||
"keywords": [
|
||||
"spec-driven development",
|
||||
"ai coding agents",
|
||||
"software engineering",
|
||||
"cli",
|
||||
"copilot",
|
||||
"specification"
|
||||
],
|
||||
"related_identifiers": [
|
||||
{
|
||||
"identifier": "https://github.com/github/spec-kit",
|
||||
"relation": "isSupplementTo",
|
||||
"scheme": "url"
|
||||
}
|
||||
]
|
||||
}
|
||||
101
AGENTS.md
101
AGENTS.md
@@ -20,17 +20,23 @@ src/specify_cli/integrations/
|
||||
├── base.py # IntegrationBase, MarkdownIntegration, TomlIntegration, YamlIntegration, SkillsIntegration
|
||||
├── manifest.py # IntegrationManifest (file tracking)
|
||||
├── claude/ # Example: SkillsIntegration subclass
|
||||
│ └── __init__.py # ClaudeIntegration class
|
||||
│ ├── __init__.py # ClaudeIntegration class
|
||||
│ └── scripts/ # Thin wrapper scripts
|
||||
│ ├── update-context.sh
|
||||
│ └── update-context.ps1
|
||||
├── gemini/ # Example: TomlIntegration subclass
|
||||
│ └── __init__.py
|
||||
│ ├── __init__.py
|
||||
│ └── scripts/
|
||||
├── windsurf/ # Example: MarkdownIntegration subclass
|
||||
│ └── __init__.py
|
||||
│ ├── __init__.py
|
||||
│ └── scripts/
|
||||
├── copilot/ # Example: IntegrationBase subclass (custom setup)
|
||||
│ └── __init__.py
|
||||
│ ├── __init__.py
|
||||
│ └── scripts/
|
||||
└── ... # One subpackage per supported agent
|
||||
```
|
||||
|
||||
The registry is the **single source of truth for Python integration metadata**. Supported agents, their directories, formats, capabilities, and context files are derived from the integration classes for the Python integration layer.
|
||||
The registry is the **single source of truth for Python integration metadata**. Supported agents, their directories, formats, and capabilities are derived from the integration classes for the Python integration layer. However, context-update behavior still requires explicit cases in the shared dispatcher scripts (`scripts/bash/update-agent-context.sh` and `scripts/powershell/update-agent-context.ps1`), which currently maintain their own supported-agent lists and agent-key→context-file mappings until they are migrated to registry-based dispatch.
|
||||
|
||||
---
|
||||
|
||||
@@ -173,11 +179,63 @@ def _register_builtins() -> None:
|
||||
# ...
|
||||
```
|
||||
|
||||
### 4. Context file behavior
|
||||
### 4. Add scripts
|
||||
|
||||
Set `context_file` on the integration class. The base integration setup creates or updates the managed Spec Kit section in that file, and uninstall removes the managed section when appropriate.
|
||||
Create two thin wrapper scripts in `src/specify_cli/integrations/<package_dir>/scripts/` that delegate to the shared context-update scripts. Each is ~25 lines of boilerplate.
|
||||
|
||||
Only add custom setup logic when the agent needs non-standard behavior. Most integrations do not need wrapper scripts or separate context-update dispatch code.
|
||||
> **Note on `<package_dir>` vs `<key>`:** `<package_dir>` is the Python-safe directory name for your integration — it matches `<key>` exactly when the key contains no hyphens (e.g., key `"gemini"` → `gemini/`), but uses underscores when it does (e.g., key `"kiro-cli"` → `kiro_cli/`). The `IntegrationBase.key` class attribute always retains the original hyphenated value (e.g., `key = "kiro-cli"`), since that is what the CLI and registry use.
|
||||
|
||||
**`update-context.sh`:**
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
# update-context.sh — <Agent Name> integration: create/update <context_file>
|
||||
set -euo pipefail
|
||||
|
||||
_script_dir="$(cd "$(dirname "$0")" && pwd)"
|
||||
_root="$_script_dir"
|
||||
while [ "$_root" != "/" ] && [ ! -d "$_root/.specify" ]; do _root="$(dirname "$_root")"; done
|
||||
if [ -z "${REPO_ROOT:-}" ]; then
|
||||
if [ -d "$_root/.specify" ]; then
|
||||
REPO_ROOT="$_root"
|
||||
else
|
||||
git_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
||||
if [ -n "$git_root" ] && [ -d "$git_root/.specify" ]; then
|
||||
REPO_ROOT="$git_root"
|
||||
else
|
||||
REPO_ROOT="$_root"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
exec "$REPO_ROOT/.specify/scripts/bash/update-agent-context.sh" <key>
|
||||
```
|
||||
|
||||
**`update-context.ps1`:**
|
||||
|
||||
```powershell
|
||||
# update-context.ps1 — <Agent Name> integration: create/update <context_file>
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$repoRoot = try { git rev-parse --show-toplevel 2>$null } catch { $null }
|
||||
if (-not $repoRoot -or -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = $scriptDir
|
||||
$fsRoot = [System.IO.Path]::GetPathRoot($repoRoot)
|
||||
while ($repoRoot -and $repoRoot -ne $fsRoot -and -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = Split-Path -Parent $repoRoot
|
||||
}
|
||||
}
|
||||
|
||||
& "$repoRoot/.specify/scripts/powershell/update-agent-context.ps1" -AgentType <key>
|
||||
```
|
||||
|
||||
Replace `<key>` with your integration key and `<Agent Name>` / `<context_file>` with the appropriate values.
|
||||
|
||||
You must also add the agent to the shared context-update scripts so the shared dispatcher recognises the new key:
|
||||
|
||||
- **`scripts/bash/update-agent-context.sh`** — add a file-path variable and a case in `update_specific_agent()`.
|
||||
- **`scripts/powershell/update-agent-context.ps1`** — add a file-path variable, add the new key to the `AgentType` parameter's `[ValidateSet(...)]`, add a switch case in `Update-SpecificAgent`, and add an entry in `Update-AllExistingAgents`.
|
||||
|
||||
### 5. Test it
|
||||
|
||||
@@ -206,13 +264,13 @@ The base classes handle most work automatically. Override only when the agent de
|
||||
| Override | When to use | Example |
|
||||
|---|---|---|
|
||||
| `command_filename(template_name)` | Custom file naming or extension | Copilot → `speckit.{name}.agent.md` |
|
||||
| `options()` | Integration-specific CLI flags via `--integration-options` | Codex → `--skills` flag, Copilot → `--skills` flag |
|
||||
| `setup()` | Custom install logic (companion files, settings merge) | Copilot → `.agent.md` + `.prompt.md` + `.vscode/settings.json` (default) or `speckit-<name>/SKILL.md` (skills mode) |
|
||||
| `options()` | Integration-specific CLI flags via `--integration-options` | Codex → `--skills` flag |
|
||||
| `setup()` | Custom install logic (companion files, settings merge) | Copilot → `.agent.md` + `.prompt.md` + `.vscode/settings.json` |
|
||||
| `teardown()` | Custom uninstall logic | Rarely needed; base handles manifest-tracked files |
|
||||
|
||||
**Example — Copilot (fully custom `setup`):**
|
||||
|
||||
Copilot extends `IntegrationBase` directly because it creates `.agent.md` commands, companion `.prompt.md` files, and merges `.vscode/settings.json`. It also supports a `--skills` mode that scaffolds `speckit-<name>/SKILL.md` under `.github/skills/` using composition with an internal `_CopilotSkillsHelper`. See `src/specify_cli/integrations/copilot/__init__.py` for the full implementation.
|
||||
Copilot extends `IntegrationBase` directly because it creates `.agent.md` commands, companion `.prompt.md` files, and merges `.vscode/settings.json`. See `src/specify_cli/integrations/copilot/__init__.py` for the full implementation.
|
||||
|
||||
### 7. Update Devcontainer files (Optional)
|
||||
|
||||
@@ -333,24 +391,6 @@ Implementation: Extends `IntegrationBase` with custom `setup()` method that:
|
||||
2. Generates companion `.prompt.md` files
|
||||
3. Merges VS Code settings
|
||||
|
||||
**Skills mode (`--skills`):** Copilot also supports an alternative skills-based layout
|
||||
via `--integration-options="--skills"`. When enabled:
|
||||
- Commands are scaffolded as `speckit-<name>/SKILL.md` under `.github/skills/`
|
||||
- No companion `.prompt.md` files are generated
|
||||
- No `.vscode/settings.json` merge
|
||||
- `post_process_skill_content()` injects a `mode: speckit.<stem>` frontmatter field
|
||||
- `build_command_invocation()` returns `/speckit-<stem>` instead of bare args
|
||||
|
||||
The two modes are mutually exclusive — a project uses one or the other:
|
||||
|
||||
```bash
|
||||
# Default mode: .agent.md agents + .prompt.md companions + settings merge
|
||||
specify init my-project --integration copilot
|
||||
|
||||
# Skills mode: speckit-<name>/SKILL.md under .github/skills/
|
||||
specify init my-project --integration copilot --integration-options="--skills"
|
||||
```
|
||||
|
||||
### Forge Integration
|
||||
|
||||
Forge has special frontmatter and argument requirements:
|
||||
@@ -364,6 +404,7 @@ Implementation: Extends `MarkdownIntegration` with custom `setup()` method that:
|
||||
3. Applies Forge-specific transformations via `_apply_forge_transformations()`
|
||||
4. Strips `handoffs` frontmatter key
|
||||
5. Injects missing `name` fields
|
||||
6. Ensures the shared `update-agent-context.*` scripts include a `forge` case that maps context updates to `AGENTS.md` and lists `forge` in their usage/help text
|
||||
|
||||
### Goose Integration
|
||||
|
||||
@@ -377,7 +418,7 @@ Implementation: Extends `YamlIntegration` (parallel to `TomlIntegration`):
|
||||
2. Extracts title and description from frontmatter
|
||||
3. Renders output as Goose recipe YAML (version, title, description, author, extensions, activities, prompt)
|
||||
4. Uses `yaml.safe_dump()` for header fields to ensure proper escaping
|
||||
5. Sets `context_file = "AGENTS.md"` so the base setup manages the Spec Kit context section there
|
||||
5. Context updates map to `AGENTS.md` (shared with opencode/codex/pi/forge)
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
|
||||
151
CHANGELOG.md
151
CHANGELOG.md
@@ -2,157 +2,6 @@
|
||||
|
||||
<!-- insert new changelog below this comment -->
|
||||
|
||||
## [0.8.6] - 2026-05-06
|
||||
|
||||
### Changed
|
||||
|
||||
- Load constitution context in `/speckit.implement` to enforce governance during implementation (#2460)
|
||||
- feat: improve catalog submission templates and CODEOWNERS (#2401)
|
||||
- fix: validate URL scheme in build_github_request (#2449)
|
||||
- Add Architecture Guard to community catalog (#2430)
|
||||
- Add multi-model-review extension to community catalog (#2446)
|
||||
- Update Ralph Loop to v1.0.2 (#2435)
|
||||
- Pin GitHub Actions by SHA (#2441)
|
||||
- fix(workflows): require project for catalog list (#2436)
|
||||
- Add agent-parity-governance to community catalog (#2382)
|
||||
- chore: release 0.8.5, begin 0.8.6.dev0 development (#2447)
|
||||
|
||||
## [0.8.5] - 2026-05-04
|
||||
|
||||
### Changed
|
||||
|
||||
- feat(presets): add Spec2Cloud preset for Azure deployment workflow (#2413)
|
||||
- update security-review and memory-md extensions to latest versions (#2445)
|
||||
- fix: honor template overrides for tasks-template (#2278) (#2292)
|
||||
- Add token-analyzer to community catalog (#2433)
|
||||
- docs: add April 2026 newsletter (#2434)
|
||||
- feat: emit init-time notice for git extension default change (#2165) (#2432)
|
||||
- Update DyanGalih(Memory Hub and Security Review) community extensions (#2429)
|
||||
- Support controlled multi-install for safe AI agent integrations (#2389)
|
||||
- chore(integrations): clean up docs and project guard (#2428)
|
||||
- chore: release 0.8.4, begin 0.8.5.dev0 development (#2431)
|
||||
|
||||
## [0.8.4] - 2026-05-01
|
||||
|
||||
### Changed
|
||||
|
||||
- fix(specify): correct self-referencing step number in validation flow (#2152)
|
||||
- chore(deps): bump DavidAnson/markdownlint-cli2-action (#2425)
|
||||
- Add security-governance to community catalog (#2386)
|
||||
- Add cross-platform-governance to community catalog (#2384)
|
||||
- Add architecture-governance to community catalog (#2383)
|
||||
- Add a11y-governance to community catalog (#2381)
|
||||
- feat(extensions): add Spec2Cloud extension for Azure deployment workflow (#2412)
|
||||
- fix: migrate extension commands on integration switch (#2404)
|
||||
- feat: add Squad Bridge extension to community catalog (#2417)
|
||||
- chore: release 0.8.3, begin 0.8.4.dev0 development (#2418)
|
||||
|
||||
## [0.8.3] - 2026-04-29
|
||||
|
||||
### Changed
|
||||
|
||||
- Add Work IQ extension to community catalog (#2415)
|
||||
- feat(integrations): add Devin for Terminal skills-based integration (#2364)
|
||||
- fix: include --from git+... in upgrade hint to avoid PyPI squat package (#2411)
|
||||
- fix: dispatch opencode commands via run (#2410)
|
||||
- feat: add catalog discovery CLI commands (#2360)
|
||||
- update security review extension catalog to v1.3.0 (#2374)
|
||||
- chore(catalog): bump v-model extension to v0.6.0 (#2399)
|
||||
- feat: add threatmodel extension to community catalog (#2369)
|
||||
- Add isaqb-architecture-governance to community catalog (#2385)
|
||||
- chore: release 0.8.2, begin 0.8.3.dev0 development (#2397)
|
||||
|
||||
## [0.8.2] - 2026-04-28
|
||||
|
||||
### Changed
|
||||
|
||||
- Add MarkItDown Document Converter extension to community catalog (#2390)
|
||||
- feat: Speckit preset fiction book v1.7 - Support for RAG (Chroma DB) offline semantic search (#2367)
|
||||
- fix(extensions): use explicit UTF-8 encoding when reading manifest YAML (#2370)
|
||||
- catalog: add m365 community extension
|
||||
- docs: replace deprecated --ai flag with --integration in all documentation (#2359)
|
||||
- feat(extensions,presets): authenticate GitHub-hosted catalog and download requests with GITHUB_TOKEN/GH_TOKEN (#2331)
|
||||
- Update extensify to v1.1.0 in community catalog (#2337)
|
||||
- feat(init): deprecate --no-git flag, gate deprecations at v0.10.0 (#2357)
|
||||
- Add Spec Orchestrator extension to community catalog (#2350)
|
||||
- chore: release 0.8.1, begin 0.8.2.dev0 development (#2356)
|
||||
|
||||
## [0.8.1] - 2026-04-24
|
||||
|
||||
### Changed
|
||||
|
||||
- fix(plan): use .specify/feature.json to allow /speckit.plan on custom git branches (#2305) (#2349)
|
||||
- feat(vibe): migrate to SkillsIntegration from the old prompts-based MarkdownIntegration (#2336)
|
||||
- docs: move community presets table to docs site, add missing entries (#2341)
|
||||
- docs(presets): add lean preset README and enrich catalog metadata (#2340)
|
||||
- fix: resolve command references per integration type (dot vs hyphen) (#2354)
|
||||
- Update product-forge to v1.5.1 in community catalog (#2352)
|
||||
- chore(deps): bump astral-sh/setup-uv from 8.0.0 to 8.1.0 (#2345)
|
||||
- fix: replace xargs trim with sed to handle quotes in descriptions (#2351)
|
||||
- feat: register jira preset in community catalog (#2224)
|
||||
- feat: Preset screenwriting (#2332)
|
||||
- chore: release 0.8.0, begin 0.8.1.dev0 development (#2333)
|
||||
|
||||
## [0.8.0] - 2026-04-23
|
||||
|
||||
### Changed
|
||||
|
||||
- feat(presets): Composition strategies (prepend, append, wrap) for templates, commands, and scripts (#2133)
|
||||
- feat(copilot): support `--integration-options="--skills"` for skills-based scaffolding (#2324)
|
||||
- docs(install): add pipx as alternative installation method (#2288)
|
||||
- Add Memory MD community extension (#2327)
|
||||
- Update version-guard to v1.2.0 (#2321)
|
||||
- fix: `--force` now overwrites shared infra files during init and upgrade (#2320)
|
||||
- chore: release 0.7.5, begin 0.7.6.dev0 development (#2322)
|
||||
|
||||
## [0.7.5] - 2026-04-22
|
||||
|
||||
### Changed
|
||||
|
||||
- fix: resolve skill placeholders for all SKILL.md agents, not just codex/kimi (#2313)
|
||||
- feat(cli): add specify self check and self upgrade stub (#2316)
|
||||
- Update version-guard to v1.1.0 (#2318)
|
||||
- docs: move community presets from README to docs/community (#2314)
|
||||
- catalog: add wireframe extension (v0.1.1) (#2262)
|
||||
- Move community walkthroughs from README to docs/community (#2312)
|
||||
- docs(readme): list red-team in community-extensions table (#2311)
|
||||
- feat(catalog): add red-team extension to community catalog (#2306)
|
||||
- Add superpowers-bridge community extension (#2309)
|
||||
- feat: implement preset wrap strategy (#2189)
|
||||
- fix(agents): block directory traversal in command write paths (#2229) (#2296)
|
||||
- chore: release 0.7.4, begin 0.7.5.dev0 development (#2299)
|
||||
|
||||
## [0.7.4] - 2026-04-21
|
||||
|
||||
### Changed
|
||||
|
||||
- fix(copilot): use --yolo to grant all permissions in non-interactive mode (#2298)
|
||||
- feat: add CITATION.cff and .zenodo.json for academic citation support (#2291)
|
||||
- Add spec-validate to community catalog (#2274)
|
||||
- feat: register Ripple in community catalog (#2272)
|
||||
- Add version-guard to community catalog (#2286)
|
||||
- Add spec-reference-loader to community catalog (#2285)
|
||||
- Add memory-loader to community catalog (#2284)
|
||||
- fix(integrations): strip UTF-8 BOM when reading agent context files (#2283)
|
||||
- Preset fiction book writing1.6 (#2270)
|
||||
- fix(integrations): migrate Antigravity (agy) layout to .agents/ and deprecate --skills (#2276)
|
||||
- chore: release 0.7.3, begin 0.7.4.dev0 development (#2263)
|
||||
|
||||
## [0.7.3] - 2026-04-17
|
||||
|
||||
### Changed
|
||||
|
||||
- fix: replace shell-based context updates with marker-based upsert (#2259)
|
||||
- Add Community Friends page to docs site (#2261)
|
||||
- Add Spec Scope extension to community catalog (#2172)
|
||||
- docs: add Community-maintained plugin for Claude Code and GitHub Copilot CLI that installs Spec Kit skills via the plugin marketplace to README (#2250)
|
||||
- fix: suppress CRLF warnings in auto-commit.ps1 (#2258)
|
||||
- feat: register Blueprint in community catalog (#2252)
|
||||
- preset: Update preset-fiction-book-writing to community catalog -> v1.5.0 (#2256)
|
||||
- chore(deps): bump actions/upload-pages-artifact from 3 to 5 (#2251)
|
||||
- fix: add reference/*.md to docfx content glob (#2248)
|
||||
- chore: release 0.7.2, begin 0.7.3.dev0 development (#2247)
|
||||
|
||||
## [0.7.2] - 2026-04-16
|
||||
|
||||
### Changed
|
||||
|
||||
31
CITATION.cff
31
CITATION.cff
@@ -1,31 +0,0 @@
|
||||
cff-version: 1.2.0
|
||||
message: >-
|
||||
If you use Spec Kit in your research or reference it in a paper,
|
||||
please cite it using the metadata below.
|
||||
type: software
|
||||
title: "Spec Kit"
|
||||
abstract: >-
|
||||
Spec Kit is an open source toolkit for Spec-Driven Development (SDD) —
|
||||
a methodology that helps software teams build high-quality software faster
|
||||
by focusing on product scenarios and predictable outcomes. It provides the
|
||||
Specify CLI, slash-command templates, extensions, presets, workflows, and
|
||||
integrations for popular AI coding agents.
|
||||
authors:
|
||||
- given-names: Den
|
||||
family-names: Delimarsky
|
||||
alias: localden
|
||||
- given-names: Manfred
|
||||
family-names: Riem
|
||||
alias: mnriem
|
||||
repository-code: "https://github.com/github/spec-kit"
|
||||
url: "https://github.github.io/spec-kit/"
|
||||
license: MIT
|
||||
version: "0.7.3"
|
||||
date-released: "2026-04-17"
|
||||
keywords:
|
||||
- spec-driven development
|
||||
- ai coding agents
|
||||
- software engineering
|
||||
- cli
|
||||
- copilot
|
||||
- specification
|
||||
@@ -94,7 +94,7 @@ uv pip install -e .
|
||||
# Ensure the `specify` binary in this environment points at your working tree so the agent runs the branch you're testing.
|
||||
|
||||
# Initialize a test project using your local changes
|
||||
uv run specify init <temp-dir>/speckit-test --integration <agent>
|
||||
uv run specify init <temp-dir>/speckit-test --ai <agent> --offline
|
||||
cd <temp-dir>/speckit-test
|
||||
|
||||
# Open in your agent
|
||||
@@ -102,7 +102,7 @@ cd <temp-dir>/speckit-test
|
||||
|
||||
#### Manual testing process
|
||||
|
||||
Any change that affects a slash command's behavior requires manually testing that command through a coding agent and submitting results with the PR.
|
||||
Any change that affects a slash command's behavior requires manually testing that command through an AI agent and submitting results with the PR.
|
||||
|
||||
1. **Identify affected commands** — use the [prompt below](#determining-which-tests-to-run) to have your agent analyze your changed files and determine which commands need testing.
|
||||
2. **Set up a test project** — scaffold from your local branch (see [Testing setup](#testing-setup)).
|
||||
|
||||
127
README.md
127
README.md
@@ -62,10 +62,6 @@ uv tool install specify-cli --from git+https://github.com/github/spec-kit.git@vX
|
||||
|
||||
# Or install latest from main (may include unreleased changes)
|
||||
uv tool install specify-cli --from git+https://github.com/github/spec-kit.git
|
||||
|
||||
# Alternative: using pipx (also works)
|
||||
pipx install git+https://github.com/github/spec-kit.git@vX.Y.Z
|
||||
pipx install git+https://github.com/github/spec-kit.git
|
||||
```
|
||||
|
||||
Then verify the correct version is installed:
|
||||
@@ -81,9 +77,9 @@ And use the tool directly:
|
||||
specify init <PROJECT_NAME>
|
||||
|
||||
# Or initialize in existing project
|
||||
specify init . --integration copilot
|
||||
specify init . --ai copilot
|
||||
# or
|
||||
specify init --here --integration copilot
|
||||
specify init --here --ai copilot
|
||||
|
||||
# Check installed tools
|
||||
specify check
|
||||
@@ -93,7 +89,6 @@ To upgrade Specify, see the [Upgrade Guide](./docs/upgrade.md) for detailed inst
|
||||
|
||||
```bash
|
||||
uv tool install specify-cli --force --from git+https://github.com/github/spec-kit.git@vX.Y.Z
|
||||
# pipx users: pipx install --force git+https://github.com/github/spec-kit.git@vX.Y.Z
|
||||
```
|
||||
|
||||
#### Option 2: One-time Usage
|
||||
@@ -105,9 +100,9 @@ Run directly without installing:
|
||||
uvx --from git+https://github.com/github/spec-kit.git@vX.Y.Z specify init <PROJECT_NAME>
|
||||
|
||||
# Or initialize in existing project
|
||||
uvx --from git+https://github.com/github/spec-kit.git@vX.Y.Z specify init . --integration copilot
|
||||
uvx --from git+https://github.com/github/spec-kit.git@vX.Y.Z specify init . --ai copilot
|
||||
# or
|
||||
uvx --from git+https://github.com/github/spec-kit.git@vX.Y.Z specify init --here --integration copilot
|
||||
uvx --from git+https://github.com/github/spec-kit.git@vX.Y.Z specify init --here --ai copilot
|
||||
```
|
||||
|
||||
**Benefits of persistent installation:**
|
||||
@@ -123,7 +118,7 @@ If your environment blocks access to PyPI or GitHub, see the [Enterprise / Air-G
|
||||
|
||||
### 2. Establish project principles
|
||||
|
||||
Launch your coding agent in the project directory. Most agents expose spec-kit as `/speckit.*` slash commands; Codex CLI in skills mode uses `$speckit-*` instead.
|
||||
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.
|
||||
|
||||
@@ -174,7 +169,7 @@ Want to see Spec Kit in action? Watch our [video overview](https://www.youtube.c
|
||||
## 🧩 Community Extensions
|
||||
|
||||
> [!NOTE]
|
||||
> Community extensions are independently created and maintained by their respective authors. Maintainers only verify that catalog entries are complete and correctly formatted — they do **not review, audit, endorse, or support the extension code itself**. The Community Extensions website is also a third-party resource. Review extension source code before installation and use at your own discretion.
|
||||
> Community extensions are independently created and maintained by their respective authors. GitHub and the Spec Kit maintainers may review pull requests that add entries to the community catalog for formatting, catalog structure, or policy compliance, but they do **not review, audit, endorse, or support the extension code itself**. The Community Extensions website is also a third-party resource. Review extension source code before installation and use at your own discretion.
|
||||
|
||||
🔍 **Browse and search community extensions on the [Community Extensions website](https://speckit-community.github.io/extensions/).**
|
||||
|
||||
@@ -198,10 +193,8 @@ The following community-contributed extensions are available in [`catalog.commun
|
||||
| Agent Assign | Assign specialized Claude Code agents to spec-kit tasks for targeted execution | `process` | Read+Write | [spec-kit-agent-assign](https://github.com/xymelon/spec-kit-agent-assign) |
|
||||
| AI-Driven Engineering (AIDE) | A structured 7-step workflow for building new projects from scratch with AI assistants — from vision through implementation | `process` | Read+Write | [aide](https://github.com/mnriem/spec-kit-extensions/tree/main/aide) |
|
||||
| Architect Impact Previewer | Predicts architectural impact, complexity, and risks of proposed changes before implementation. | `visibility` | Read-only | [spec-kit-architect-preview](https://github.com/UmmeHabiba1312/spec-kit-architect-preview) |
|
||||
| Architecture Guard | Continuous architecture governance for AI-assisted development. Reviews specs, plans, and code for architecture drift, producing structured refactor tasks and evolution proposals. | `process` | Read+Write | [spec-kit-architecture-guard](https://github.com/DyanGalih/spec-kit-architecture-guard) |
|
||||
| Archive Extension | Archive merged features into main project memory. | `docs` | Read+Write | [spec-kit-archive](https://github.com/stn1slv/spec-kit-archive) |
|
||||
| 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) |
|
||||
| Blueprint | Stay code-literate in AI-driven development: review a complete code blueprint for every task from spec artifacts before /speckit.implement runs | `docs` | Read+Write | [spec-kit-blueprint](https://github.com/chordpli/spec-kit-blueprint) |
|
||||
| Branch Convention | Configurable branch and folder naming conventions for /specify with presets and custom patterns | `process` | Read+Write | [spec-kit-branch-convention](https://github.com/Quratulain-bilal/spec-kit-branch-convention) |
|
||||
| Brownfield Bootstrap | Bootstrap spec-kit for existing codebases — auto-discover architecture and adopt SDD incrementally | `process` | Read+Write | [spec-kit-brownfield](https://github.com/Quratulain-bilal/spec-kit-brownfield) |
|
||||
| Bugfix Workflow | Structured bugfix workflow — capture bugs, trace to spec artifacts, and patch specs surgically | `process` | Read+Write | [spec-kit-bugfix](https://github.com/Quratulain-bilal/spec-kit-bugfix) |
|
||||
@@ -229,58 +222,39 @@ The following community-contributed extensions are available in [`catalog.commun
|
||||
| MAQA Jira Integration | Jira integration for MAQA — syncs Stories and Subtasks as features progress through the board | `integration` | Read+Write | [spec-kit-maqa-jira](https://github.com/GenieRobot/spec-kit-maqa-jira) |
|
||||
| MAQA Linear Integration | Linear integration for MAQA — syncs issues and sub-issues across workflow states as features progress | `integration` | Read+Write | [spec-kit-maqa-linear](https://github.com/GenieRobot/spec-kit-maqa-linear) |
|
||||
| MAQA Trello Integration | Trello board integration for MAQA — populates board from specs, moves cards, real-time checklist ticking | `integration` | Read+Write | [spec-kit-maqa-trello](https://github.com/GenieRobot/spec-kit-maqa-trello) |
|
||||
| MarkItDown Document Converter | Convert documents (PDF, Word, PowerPoint, Excel, and more) to Markdown for use as spec reference material | `docs` | Read+Write | [spec-kit-markitdown](https://github.com/BenBtg/spec-kit-markitdown) |
|
||||
| Memory Loader | Loads .specify/memory/ files before lifecycle commands so LLM agents have project governance context | `docs` | Read-only | [spec-kit-memory-loader](https://github.com/KevinBrown5280/spec-kit-memory-loader) |
|
||||
| Memory MD | Spec Kit extension for repository-native Markdown memory that captures durable decisions, bugs, and project context | `docs` | Read+Write | [spec-kit-memory-hub](https://github.com/DyanGalih/spec-kit-memory-hub) |
|
||||
| MemoryLint | Agent memory governance tool: Automatically audits and fixes boundary conflicts between AGENTS.md and the constitution. | `process` | Read+Write | [memorylint](https://github.com/RbBtSn0w/spec-kit-extensions/tree/main/memorylint) |
|
||||
| Microsoft 365 Integration | Fetch Teams messages, meeting transcripts, and SharePoint/OneDrive files as local Markdown for spec generation | `integration` | Read+Write | [spec-kit-m365](https://github.com/BenBtg/spec-kit-m365) |
|
||||
| Multi-Model Review | Cross-model Spec Kit handoffs for spec authoring, implementation routing, and review. | `process` | Read+Write | [multi-model-review](https://github.com/formin/multi-model-review) |
|
||||
| Onboard | Contextual onboarding and progressive growth for developers new to spec-kit projects. Explains specs, maps dependencies, validates understanding, and guides the next step | `process` | Read+Write | [spec-kit-onboard](https://github.com/dmux/spec-kit-onboard) |
|
||||
| Optimize | Audit and optimize AI governance for context efficiency — token budgets, rule health, interpretability, compression, coherence, and echo detection | `process` | Read+Write | [spec-kit-optimize](https://github.com/sakitA/spec-kit-optimize) |
|
||||
| OWASP LLM Threat Model | OWASP Top 10 for LLM Applications 2025 threat analysis on agent artifacts | `code` | Read-only | [spec-kit-threatmodel](https://github.com/NaviaSamal/spec-kit-threatmodel) |
|
||||
| Plan Review Gate | Require spec.md and plan.md to be merged via MR/PR before allowing task generation | `process` | Read-only | [spec-kit-plan-review-gate](https://github.com/luno/spec-kit-plan-review-gate) |
|
||||
| PR Bridge | Auto-generate pull request descriptions, checklists, and summaries from spec artifacts | `process` | Read-only | [spec-kit-pr-bridge-](https://github.com/Quratulain-bilal/spec-kit-pr-bridge-) |
|
||||
| Presetify | Create and validate presets and preset catalogs | `process` | Read+Write | [presetify](https://github.com/mnriem/spec-kit-extensions/tree/main/presetify) |
|
||||
| Product Forge | Full product lifecycle from research to release — portfolio, lite mode, monorepo, optional V-Model | `process` | Read+Write | [speckit-product-forge](https://github.com/VaiYav/speckit-product-forge) |
|
||||
| Product Forge | Full product lifecycle: research → product spec → SpecKit → implement → verify → test | `process` | Read+Write | [speckit-product-forge](https://github.com/VaiYav/speckit-product-forge) |
|
||||
| Project Health Check | Diagnose a Spec Kit project and report health issues across structure, agents, features, scripts, extensions, and git | `visibility` | Read-only | [spec-kit-doctor](https://github.com/KhawarHabibKhan/spec-kit-doctor) |
|
||||
| Project Status | Show current SDD workflow progress — active feature, artifact status, task completion, workflow phase, and extensions summary | `visibility` | Read-only | [spec-kit-status](https://github.com/KhawarHabibKhan/spec-kit-status) |
|
||||
| QA Testing Extension | Systematic QA testing with browser-driven or CLI-based validation of acceptance criteria from spec | `code` | Read-only | [spec-kit-qa](https://github.com/arunt14/spec-kit-qa) |
|
||||
| Ralph Loop | Autonomous implementation loop using AI agent CLI | `code` | Read+Write | [spec-kit-ralph](https://github.com/Rubiss-Projects/spec-kit-ralph) |
|
||||
| Ralph Loop | Autonomous implementation loop using AI agent CLI | `code` | Read+Write | [spec-kit-ralph](https://github.com/Rubiss/spec-kit-ralph) |
|
||||
| Reconcile Extension | Reconcile implementation drift by surgically updating feature artifacts. | `docs` | Read+Write | [spec-kit-reconcile](https://github.com/stn1slv/spec-kit-reconcile) |
|
||||
| Red Team | Adversarial review of specs before /speckit.plan — parallel lens agents surface risks that clarify/analyze structurally can't (prompt injection, integrity gaps, cross-spec drift, silent failures). Produces a structured findings report; no auto-edits to specs. | `docs` | Read+Write | [spec-kit-red-team](https://github.com/ashbrener/spec-kit-red-team) |
|
||||
| Repository Index | Generate index for existing repo for overview, architecture and module level. | `docs` | Read-only | [spec-kit-repoindex](https://github.com/liuyiyu/spec-kit-repoindex) |
|
||||
| Retro Extension | Sprint retrospective analysis with metrics, spec accuracy assessment, and improvement suggestions | `process` | Read+Write | [spec-kit-retro](https://github.com/arunt14/spec-kit-retro) |
|
||||
| Retrospective Extension | Post-implementation retrospective with spec adherence scoring, drift analysis, and human-gated spec updates | `docs` | Read+Write | [spec-kit-retrospective](https://github.com/emi-dm/spec-kit-retrospective) |
|
||||
| Review Extension | Post-implementation comprehensive code review with specialized agents for code quality, comments, tests, error handling, type design, and simplification | `code` | Read-only | [spec-kit-review](https://github.com/ismaelJimenez/spec-kit-review) |
|
||||
| Ripple | Detect side effects that tests can't catch after implementation — delta-anchored analysis across 9 domain-agnostic categories | `code` | Read+Write | [spec-kit-ripple](https://github.com/chordpli/spec-kit-ripple) |
|
||||
| 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) |
|
||||
| Security Review | Full-project secure-by-design security audits plus staged, branch/PR, plan, task, follow-up, and apply reviews | `code` | Read+Write | [spec-kit-security-review](https://github.com/DyanGalih/spec-kit-security-review) |
|
||||
| Security Review | Comprehensive security audit of codebases using AI-powered DevSecOps analysis | `code` | Read-only | [spec-kit-security-review](https://github.com/DyanGalih/spec-kit-security-review) |
|
||||
| SFSpeckit | Enterprise Salesforce SDLC with 18 commands for the full SDD lifecycle. | `process` | Read+Write | [spec-kit-sf](https://github.com/ysumanth06/spec-kit-sf) |
|
||||
| Ship Release Extension | Automates release pipeline: pre-flight checks, branch sync, changelog generation, CI verification, and PR creation | `process` | Read+Write | [spec-kit-ship](https://github.com/arunt14/spec-kit-ship) |
|
||||
| Spec Reference Loader | Reads the ## References section from the feature spec and loads only the listed docs into context | `docs` | Read-only | [spec-kit-spec-reference-loader](https://github.com/KevinBrown5280/spec-kit-spec-reference-loader) |
|
||||
| Spec Critique Extension | Dual-lens critical review of spec and plan from product strategy and engineering risk perspectives | `docs` | Read-only | [spec-kit-critique](https://github.com/arunt14/spec-kit-critique) |
|
||||
| Spec Diagram | Auto-generate Mermaid diagrams of SDD workflow state, feature progress, and task dependencies | `visibility` | Read-only | [spec-kit-diagram-](https://github.com/Quratulain-bilal/spec-kit-diagram-) |
|
||||
| Spec Orchestrator | Cross-feature orchestration — track state, select tasks, and detect conflicts across parallel specs | `process` | Read-only | [spec-kit-orchestrator](https://github.com/Quratulain-bilal/spec-kit-orchestrator) |
|
||||
| Spec Refine | Update specs in-place, propagate changes to plan and tasks, and diff impact across artifacts | `process` | Read+Write | [spec-kit-refine](https://github.com/Quratulain-bilal/spec-kit-refine) |
|
||||
| Spec Scope | Effort estimation and scope tracking — estimate work, detect creep, and budget time per phase | `process` | Read-only | [spec-kit-scope-](https://github.com/Quratulain-bilal/spec-kit-scope-) |
|
||||
| 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) |
|
||||
| Spec Validate | Comprehension validation, review gating, and approval state for spec-kit artifacts — staged quizzes, peer review SLA, and a hard gate before /speckit.implement | `process` | Read+Write | [spec-kit-spec-validate](https://github.com/aeltayeb/spec-kit-spec-validate) |
|
||||
| Spec2Cloud | Spec-driven workflow tuned for shipping to Azure | `process` | Read+Write | [spec2cloud](https://github.com/Azure-Samples/Spec2Cloud) |
|
||||
| SpecTest | Auto-generate test scaffolds from spec criteria, map coverage, and find untested requirements | `code` | Read+Write | [spec-kit-spectest](https://github.com/Quratulain-bilal/spec-kit-spectest) |
|
||||
| Squad Bridge | Bootstrap and synchronize a Squad agent team from your Speckit spec and tasks | `process` | Read+Write | [spec-kit-squad](https://github.com/jwill824/spec-kit-squad) |
|
||||
| Staff Review Extension | Staff-engineer-level code review that validates implementation against spec, checks security, performance, and test coverage | `code` | Read-only | [spec-kit-staff-review](https://github.com/arunt14/spec-kit-staff-review) |
|
||||
| Status Report | Project status, feature progress, and next-action recommendations for spec-driven workflows | `visibility` | Read-only | [Open-Agent-Tools/spec-kit-status](https://github.com/Open-Agent-Tools/spec-kit-status) |
|
||||
| Superpowers Bridge | Orchestrates obra/superpowers skills within the spec-kit SDD workflow across the full lifecycle (clarification, TDD, review, verification, critique, debugging, branch completion) | `process` | Read+Write | [superpowers-bridge](https://github.com/RbBtSn0w/spec-kit-extensions/tree/main/superpowers-bridge) |
|
||||
| Superpowers Bridge (WangX0111) | Bridges spec-kit with obra/superpowers (brainstorming, TDD, subagent, code-review) into a unified, resumable workflow with graceful degradation and session progress tracking | `process` | Read+Write | [superspec](https://github.com/WangX0111/superspec) |
|
||||
| TinySpec | Lightweight single-file workflow for small tasks — skip the heavy multi-step SDD process | `process` | Read+Write | [spec-kit-tinyspec](https://github.com/Quratulain-bilal/spec-kit-tinyspec) |
|
||||
| Token Consumption Analyzer | Captures, analyzes, and compares token consumption across SDD workflows | `visibility` | Read-only | [spec-kit-token-analyzer](https://github.com/coderandhiker/spec-kit-token-analyzer) |
|
||||
| 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) |
|
||||
| Version Guard | Verify tech stack versions against live npm registries before planning and implementation | `process` | Read-only | [spec-kit-version-guard](https://github.com/KevinBrown5280/spec-kit-version-guard) |
|
||||
| What-if Analysis | Preview the downstream impact (complexity, effort, tasks, risks) of requirement changes before committing to them | `visibility` | Read-only | [spec-kit-whatif](https://github.com/DevAbdullah90/spec-kit-whatif) |
|
||||
| Wireframe Visual Feedback Loop | SVG wireframe generation, review, and sign-off for spec-driven development. Approved wireframes become spec constraints honored by /speckit.plan, /speckit.tasks, and /speckit.implement | `visibility` | Read+Write | [spec-kit-extension-wireframe](https://github.com/TortoiseWolfe/spec-kit-extension-wireframe) |
|
||||
| Work IQ | Integrate Microsoft 365 organizational knowledge into spec-driven development workflows | `integration` | Read-only | [spec-kit-workiq](https://github.com/sakitA/spec-kit-workiq) |
|
||||
| Worktree Isolation | Spawn isolated git worktrees for parallel feature development without checkout switching | `process` | Read+Write | [spec-kit-worktree](https://github.com/Quratulain-bilal/spec-kit-worktree) |
|
||||
| Worktrees | Default-on worktree isolation for parallel agents — sibling or nested layout | `process` | Read+Write | [spec-kit-worktree-parallel](https://github.com/dango85/spec-kit-worktree-parallel) |
|
||||
|
||||
@@ -288,20 +262,57 @@ To submit your own extension, see the [Extension Publishing Guide](extensions/EX
|
||||
|
||||
## 🎨 Community Presets
|
||||
|
||||
Community-contributed presets customize how Spec Kit behaves — overriding templates, commands, and terminology without changing any tooling. See the full list on the [Community Presets](https://github.github.io/spec-kit/community/presets.html) page.
|
||||
|
||||
> [!NOTE]
|
||||
> Community presets are third-party contributions and are not maintained by the Spec Kit team. Review them carefully before use, and see the docs page above for the full disclaimer.
|
||||
> Community presets are independently created and maintained by their respective authors. GitHub and the Spec Kit maintainers may review pull requests that add entries to the community catalog for formatting, catalog structure, or policy compliance, but they do **not review, audit, endorse, or support the preset code itself**. Review preset source code before installation and use at your own discretion.
|
||||
|
||||
To submit your own preset, see the [Presets Publishing Guide](presets/PUBLISHING.md).
|
||||
The following community-contributed presets customize how Spec Kit behaves — overriding templates, commands, and terminology without changing any tooling. Presets are available in [`catalog.community.json`](presets/catalog.community.json):
|
||||
|
||||
| Preset | Purpose | Provides | Requires | URL |
|
||||
|--------|---------|----------|----------|-----|
|
||||
| AIDE In-Place Migration | Adapts the AIDE extension workflow for in-place technology migrations (X → Y pattern) — adds migration objectives, verification gates, knowledge documents, and behavioral equivalence criteria | 2 templates, 8 commands | AIDE extension | [spec-kit-presets](https://github.com/mnriem/spec-kit-presets) |
|
||||
| Canon Core | Adapts original Spec Kit workflow to work together with Canon extension | 2 templates, 8 commands | — | [spec-kit-canon](https://github.com/maximiliamus/spec-kit-canon) |
|
||||
| Explicit Task Dependencies | Adds explicit `(depends on T###)` dependency declarations and an Execution Wave DAG to tasks.md for parallel scheduling | 1 template, 1 command | — | [spec-kit-preset-explicit-task-dependencies](https://github.com/Quratulain-bilal/spec-kit-preset-explicit-task-dependencies) |
|
||||
| Fiction Book Writing | It adapts the Spec-Driven Development workflow for storytelling: features become story elements, specs become story briefs, plans become story structures, and tasks become scene-by-scene writing tasks. Supports single and multi-POV, all major plot structure frameworks, and two style modes, author voice sample or humanized AI prose. | 21 templates, 17 commands | — | [spec-kit-preset-fiction-book-writing](https://github.com/adaumann/speckit-preset-fiction-book-writing) |
|
||||
| Multi-Repo Branching | Coordinates feature branch creation across multiple git repositories (independent repos and submodules) during plan and tasks phases | 2 commands | — | [spec-kit-preset-multi-repo-branching](https://github.com/sakitA/spec-kit-preset-multi-repo-branching) |
|
||||
| Pirate Speak (Full) | Transforms all Spec Kit output into pirate speak — specs become "Voyage Manifests", plans become "Battle Plans", tasks become "Crew Assignments" | 6 templates, 9 commands | — | [spec-kit-presets](https://github.com/mnriem/spec-kit-presets) |
|
||||
| Table of Contents Navigation | Adds a navigable Table of Contents to generated spec.md, plan.md, and tasks.md documents | 3 templates, 3 commands | — | [spec-kit-preset-toc-navigation](https://github.com/Quratulain-bilal/spec-kit-preset-toc-navigation) |
|
||||
| VS Code Ask Questions | Enhances the clarify command to use `vscode/askQuestions` for batched interactive questioning. | 1 command | — | [spec-kit-presets](https://github.com/fdcastel/spec-kit-presets) |
|
||||
|
||||
To build and publish your own preset, see the [Presets Publishing Guide](presets/PUBLISHING.md).
|
||||
|
||||
## 🚶 Community Walkthroughs
|
||||
|
||||
See Spec-Driven Development in action across different scenarios with community-contributed walkthroughs; find the full list on the [Community Walkthroughs](https://github.github.io/spec-kit/community/walkthroughs.html) page.
|
||||
> [!NOTE]
|
||||
> Community walkthroughs are independently created and maintained by their respective authors. They are **not reviewed, nor endorsed, nor supported by GitHub**. Review their content before following along and use at your own discretion.
|
||||
|
||||
See Spec-Driven Development in action across different scenarios with these community-contributed walkthroughs:
|
||||
|
||||
- **[Greenfield .NET CLI tool](https://github.com/mnriem/spec-kit-dotnet-cli-demo)** — Builds a Timezone Utility as a .NET single-binary CLI tool from a blank directory, covering the full spec-kit workflow: constitution, specify, plan, tasks, and multi-pass implement using GitHub Copilot agents.
|
||||
|
||||
- **[Greenfield Spring Boot + React platform](https://github.com/mnriem/spec-kit-spring-react-demo)** — Builds an LLM performance analytics platform (REST API, graphs, iteration tracking) from scratch using Spring Boot, embedded React, PostgreSQL, and Docker Compose, with a clarify step and a cross-artifact consistency analysis pass included.
|
||||
|
||||
- **[Brownfield ASP.NET CMS extension](https://github.com/mnriem/spec-kit-aspnet-brownfield-demo)** — Extends an existing open-source .NET CMS (CarrotCakeCMS-Core, ~307,000 lines of C#, Razor, SQL, JavaScript, and config files) with two new features — cross-platform Docker Compose infrastructure and a token-authenticated headless REST API — demonstrating how spec-kit fits into existing codebases without prior specs or a constitution.
|
||||
|
||||
- **[Brownfield Java runtime extension](https://github.com/mnriem/spec-kit-java-brownfield-demo)** — Extends an existing open-source Jakarta EE runtime (Piranha, ~420,000 lines of Java, XML, JSP, HTML, and config files across 180 Maven modules) with a password-protected Server Admin Console, demonstrating spec-kit on a large multi-module Java project with no prior specs or constitution.
|
||||
|
||||
- **[Brownfield Go / React dashboard demo](https://github.com/mnriem/spec-kit-go-brownfield-demo)** — Demonstrates spec-kit driven entirely from the **terminal using GitHub Copilot CLI**. Extends NASA's open-source Hermes ground support system (Go) with a lightweight React-based web telemetry dashboard, showing that the full constitution → specify → plan → tasks → implement workflow works from the terminal.
|
||||
|
||||
- **[Greenfield Spring Boot MVC with a custom preset](https://github.com/mnriem/spec-kit-pirate-speak-preset-demo)** — Builds a Spring Boot MVC application from scratch using a custom pirate-speak preset, demonstrating how presets can reshape the entire spec-kit experience: specifications become "Voyage Manifests," plans become "Battle Plans," and tasks become "Crew Assignments" — all generated in full pirate vernacular without changing any tooling.
|
||||
|
||||
- **[Greenfield Spring Boot + React with a custom extension](https://github.com/mnriem/spec-kit-aide-extension-demo)** — Walks through the **AIDE extension**, a community extension that adds an alternative spec-driven workflow to spec-kit with high-level specs (vision) and low-level specs (work items) organized in a 7-step iterative lifecycle: vision → roadmap → progress tracking → work queue → work items → execution → feedback loops. Uses a family trading platform (Spring Boot 4, React 19, PostgreSQL, Docker Compose) as the scenario to illustrate how the extension mechanism lets you plug in a different style of spec-driven development without changing any core tooling — truly utilizing the "Kit" in Spec Kit.
|
||||
|
||||
## 🛠️ Community Friends
|
||||
|
||||
Community projects that extend, visualize, or build on Spec Kit. See the full list on the [Community Friends](https://github.github.io/spec-kit/community/friends.html) page.
|
||||
> [!NOTE]
|
||||
> Community projects listed here are independently created and maintained by their respective authors. They are **not reviewed, nor endorsed, nor supported by GitHub**. Review their source code before installation and use at your own discretion.
|
||||
|
||||
Community projects that extend, visualize, or build on Spec Kit:
|
||||
|
||||
- **[cc-spex](https://github.com/rhuss/cc-spex)** - A Claude Code plugin that adds composable traits on top of Spec Kit with [Superpowers](https://github.com/obra/superpowers)-based quality gates, spec/code review, git worktree isolation, and parallel implementation via agent teams.
|
||||
|
||||
- **[Spec Kit Assistant](https://marketplace.visualstudio.com/items?itemName=rfsales.speckit-assistant)** — A VS Code extension that provides a visual orchestrator for the full SDD workflow (constitution → specification → planning → tasks → implementation) with phase status visualization, an interactive task checklist, DAG visualization, and support for Claude, Gemini, GitHub Copilot, and OpenAI backends. Requires the `specify` CLI in your PATH.
|
||||
|
||||
- **[SpecKit Companion](https://marketplace.visualstudio.com/items?itemName=alfredoperez.speckit-companion)** — A VS Code extension that brings a visual GUI to Spec Kit. Browse specs in a rich markdown viewer with clickable file references, create specifications with image attachments, comment and refine each step inline (GitHub-style review), track your progress through the SDD workflow with a visual phase stepper, and manage steering documents like constitutions and templates.
|
||||
|
||||
## 🤖 Supported AI Coding Agent Integrations
|
||||
|
||||
@@ -311,7 +322,7 @@ Run `specify integration list` to see all available integrations in your install
|
||||
|
||||
## Available Slash Commands
|
||||
|
||||
After running `specify init`, your AI coding agent will have access to these slash commands for structured development. For integrations that support skills mode, passing `--integration <agent> --integration-options="--skills"` installs agent skills instead of slash-command prompt files.
|
||||
After running `specify init`, your AI coding agent will have access to these slash commands for structured development. If you pass `--ai <agent> --ai-skills`, Spec Kit installs agent skills instead of slash-command prompt files; `--ai-skills` requires `--ai`.
|
||||
|
||||
#### Core Commands
|
||||
|
||||
@@ -446,7 +457,7 @@ Our research and experimentation focus on:
|
||||
|
||||
- **Linux/macOS/Windows**
|
||||
- [Supported](#-supported-ai-coding-agent-integrations) AI coding agent.
|
||||
- [uv](https://docs.astral.sh/uv/) for package management (recommended) or [pipx](https://pypa.github.io/pipx/) for persistent installation
|
||||
- [uv](https://docs.astral.sh/uv/) for package management
|
||||
- [Python 3.11+](https://www.python.org/downloads/)
|
||||
- [Git](https://git-scm.com/downloads)
|
||||
|
||||
@@ -484,37 +495,37 @@ specify init --here --force
|
||||
|
||||

|
||||
|
||||
You will be prompted to select the coding agent integration you are using. You can also proactively specify it directly in the terminal:
|
||||
You will be prompted to select the AI agent you are using. You can also proactively specify it directly in the terminal:
|
||||
|
||||
```bash
|
||||
specify init <project_name> --integration copilot
|
||||
specify init <project_name> --integration gemini
|
||||
specify init <project_name> --integration codex
|
||||
specify init <project_name> --ai copilot
|
||||
specify init <project_name> --ai gemini
|
||||
specify init <project_name> --ai copilot
|
||||
|
||||
# Or in current directory:
|
||||
specify init . --integration copilot
|
||||
specify init . --integration codex --integration-options="--skills"
|
||||
specify init . --ai copilot
|
||||
specify init . --ai codex --ai-skills
|
||||
|
||||
# or use --here flag
|
||||
specify init --here --integration copilot
|
||||
specify init --here --integration codex --integration-options="--skills"
|
||||
specify init --here --ai copilot
|
||||
specify init --here --ai codex --ai-skills
|
||||
|
||||
# Force merge into a non-empty current directory
|
||||
specify init . --force --integration copilot
|
||||
specify init . --force --ai copilot
|
||||
|
||||
# or
|
||||
specify init --here --force --integration copilot
|
||||
specify init --here --force --ai copilot
|
||||
```
|
||||
|
||||
The CLI will check if you have Claude Code, Gemini CLI, Cursor CLI, Qwen CLI, opencode, Codex CLI, Qoder CLI, Tabnine CLI, Kiro CLI, Pi, Forge, Goose, or Mistral Vibe installed. If you do not, or you prefer to get the templates without checking for the right tools, use `--ignore-agent-tools` with your command:
|
||||
|
||||
```bash
|
||||
specify init <project_name> --integration copilot --ignore-agent-tools
|
||||
specify init <project_name> --ai copilot --ignore-agent-tools
|
||||
```
|
||||
|
||||
### **STEP 1:** Establish project principles
|
||||
|
||||
Go to the project folder and run your coding agent. In our example, we're using `claude`.
|
||||
Go to the project folder and run your AI agent. In our example, we're using `claude`.
|
||||
|
||||

|
||||
|
||||
@@ -526,7 +537,7 @@ The first step should be establishing your project's governing principles using
|
||||
/speckit.constitution Create principles focused on code quality, testing standards, user experience consistency, and performance requirements. Include governance for how these principles should guide technical decisions and implementation choices.
|
||||
```
|
||||
|
||||
This step creates or updates the `.specify/memory/constitution.md` file with your project's foundational guidelines that the coding agent will reference during specification, planning, and implementation phases.
|
||||
This step creates or updates the `.specify/memory/constitution.md` file with your project's foundational guidelines that the AI agent will reference during specification, planning, and implementation phases.
|
||||
|
||||
### **STEP 2:** Create project specifications
|
||||
|
||||
@@ -734,9 +745,9 @@ The `/speckit.implement` command will:
|
||||
- Provide progress updates and handle errors appropriately
|
||||
|
||||
> [!IMPORTANT]
|
||||
> The coding agent will execute local CLI commands (such as `dotnet`, `npm`, etc.) - make sure you have the required tools installed on your machine.
|
||||
> The AI agent will execute local CLI commands (such as `dotnet`, `npm`, etc.) - make sure you have the required tools installed on your machine.
|
||||
|
||||
Once the implementation is complete, test the application and resolve any runtime errors that may not be visible in CLI logs (e.g., browser console errors). You can copy and paste such errors back to your coding agent for resolution.
|
||||
Once the implementation is complete, test the application and resolve any runtime errors that may not be visible in CLI logs (e.g., browser console errors). You can copy and paste such errors back to your AI agent for resolution.
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
# Community Friends
|
||||
|
||||
> [!NOTE]
|
||||
> Community projects listed here are independently created and maintained by their respective authors. They are **not reviewed, nor endorsed, nor supported by GitHub**. Review their source code before installation and use at your own discretion.
|
||||
|
||||
Community projects that extend, visualize, or build on Spec Kit:
|
||||
|
||||
- **[cc-spex](https://github.com/rhuss/cc-spex)** — A Claude Code plugin that adds composable traits on top of Spec Kit with [Superpowers](https://github.com/obra/superpowers)-based quality gates, spec/code review, git worktree isolation, and parallel implementation via agent teams.
|
||||
|
||||
- **[Spec Kit Assistant](https://marketplace.visualstudio.com/items?itemName=rfsales.speckit-assistant)** — A VS Code extension that provides a visual orchestrator for the full SDD workflow (constitution → specification → planning → tasks → implementation) with phase status visualization, an interactive task checklist, DAG visualization, and support for Claude, Gemini, GitHub Copilot, and OpenAI backends. Requires the `specify` CLI in your PATH.
|
||||
|
||||
- **[SpecKit Companion](https://marketplace.visualstudio.com/items?itemName=alfredoperez.speckit-companion)** — A VS Code extension that brings a visual GUI to Spec Kit. Browse specs in a rich markdown viewer with clickable file references, create specifications with image attachments, comment and refine each step inline (GitHub-style review), track your progress through the SDD workflow with a visual phase stepper, and manage steering documents like constitutions and templates.
|
||||
|
||||
- **[cc-spec-kit](https://github.com/speckit-community/cc-spec-kit)** — Community-maintained plugin for Claude Code and GitHub Copilot CLI that installs Spec Kit skills via the plugin marketplace.
|
||||
@@ -1,29 +0,0 @@
|
||||
# Community Presets
|
||||
|
||||
> [!NOTE]
|
||||
> Community presets are independently created and maintained by their respective authors. Maintainers only verify that catalog entries are complete and correctly formatted — they do **not review, audit, endorse, or support the preset code itself**. Review preset source code before installation and use at your own discretion.
|
||||
|
||||
The following community-contributed presets customize how Spec Kit behaves — overriding templates, commands, and terminology without changing any tooling. Presets are available in [`catalog.community.json`](https://github.com/github/spec-kit/blob/main/presets/catalog.community.json):
|
||||
|
||||
| Preset | Purpose | Provides | Requires | URL |
|
||||
|--------|---------|----------|----------|-----|
|
||||
| A11Y Governance | Adds WCAG 2.2 AA accessibility checks, bilingual DE/EN delivery, CEFR-B2 readability, CLI accessibility, and inclusive-content guidance | 9 templates, 3 commands | — | [spec-kit-preset-a11y-governance](https://github.com/hindermath/spec-kit-preset-a11y-governance) |
|
||||
| Agent Parity Governance | Keeps shared AI-agent instructions aligned across project-defined agent guidance surfaces and documents intentional deviations | 6 templates, 3 commands | — | [spec-kit-preset-agent-parity-governance](https://github.com/hindermath/spec-kit-preset-agent-parity-governance) |
|
||||
| AIDE In-Place Migration | Adapts the AIDE extension workflow for in-place technology migrations (X → Y pattern) — adds migration objectives, verification gates, knowledge documents, and behavioral equivalence criteria | 2 templates, 8 commands | AIDE extension | [spec-kit-presets](https://github.com/mnriem/spec-kit-presets) |
|
||||
| Architecture Governance | Adds secure architecture governance: trust boundaries, threat modeling, STRIDE/CAPEC, S-ADRs, Zero Trust applicability, and OWASP SAMM | 11 templates, 3 commands | — | [spec-kit-preset-architecture-governance](https://github.com/hindermath/spec-kit-preset-architecture-governance) |
|
||||
| Canon Core | Adapts original Spec Kit workflow to work together with Canon extension | 2 templates, 8 commands | — | [spec-kit-canon](https://github.com/maximiliamus/spec-kit-canon) |
|
||||
| Claude AskUserQuestion | Upgrades `/speckit.clarify` and `/speckit.checklist` on Claude Code from Markdown-table prompts to the native AskUserQuestion picker, with a recommended option and reasoning on every question | 2 commands | — | [spec-kit-preset-claude-ask-questions](https://github.com/0xrafasec/spec-kit-preset-claude-ask-questions) |
|
||||
| Cross-Platform Governance | Adds Bash/PowerShell parity, dry-run/WhatIf parity, Unix man-page expectations, PowerShell comment-based help, and Verb-Noun Cmdlet discipline | 8 templates, 3 commands | — | [spec-kit-preset-cross-platform-governance](https://github.com/hindermath/spec-kit-preset-cross-platform-governance) |
|
||||
| Explicit Task Dependencies | Adds explicit `(depends on T###)` dependency declarations and an Execution Wave DAG to tasks.md for parallel scheduling | 1 template, 1 command | — | [spec-kit-preset-explicit-task-dependencies](https://github.com/Quratulain-bilal/spec-kit-preset-explicit-task-dependencies) |
|
||||
| Fiction Book Writing | It adapts the Spec-Driven Development workflow for storytelling to create books or audiobooks (with annotations) in 12 languages: features become story elements, specs become story briefs, plans become story structures, and tasks become scene-by-scene writing tasks. Supports single and multi-POV, all major plot structure frameworks, and two style modes: an author voice sample or humanized AI prose. Supports interactive elements like brainstorming, interview, roleplay and extras like statistics, cover builder and bio command. Export with templates for KDP, D2D etc. | 22 templates, 27 commands, 2 scripts | — | [speckit-preset-fiction-book-writing](https://github.com/adaumann/speckit-preset-fiction-book-writing) |
|
||||
| iSAQB Architecture Governance | Adds general iSAQB/CPSA-F and arc42 architecture governance: goals, context, building blocks, runtime and deployment views, quality scenarios, ADRs, risks, and technical debt | 13 templates, 3 commands | — | [spec-kit-preset-isaqb-architecture-governance](https://github.com/hindermath/spec-kit-preset-isaqb-architecture-governance) |
|
||||
| Jira Issue Tracking | Overrides `speckit.taskstoissues` to create Jira epics, stories, and tasks instead of GitHub Issues via Atlassian MCP tools | 1 command | — | [spec-kit-preset-jira](https://github.com/luno/spec-kit-preset-jira) |
|
||||
| Multi-Repo Branching | Coordinates feature branch creation across multiple git repositories (independent repos and submodules) during plan and tasks phases | 2 commands | — | [spec-kit-preset-multi-repo-branching](https://github.com/sakitA/spec-kit-preset-multi-repo-branching) |
|
||||
| Pirate Speak (Full) | Transforms all Spec Kit output into pirate speak — specs become "Voyage Manifests", plans become "Battle Plans", tasks become "Crew Assignments" | 6 templates, 9 commands | — | [spec-kit-presets](https://github.com/mnriem/spec-kit-presets) |
|
||||
| Screenwriting | Spec-Driven Development for screenwriting/scriptwriting/tutorials: feature films, television (pilot, episode, limited series), and stage plays. Adapts the Spec Kit workflow to screenplay craft — slug lines, action lines, act breaks, beat sheets, and industry-standard pitch documents. Supports three-act, Save the Cat, TV pilot, network episode, cable/streaming episode, and stage-play structural frameworks. Export to Fountain, FTX, PDF | 26 templates, 32 commands, 1 script | — | [speckit-preset-screenwriting](https://github.com/adaumann/speckit-preset-screenwriting) |
|
||||
| Security Governance | Adds secure development governance: memory-safe-language preference, secure code generation, NIST SSDF, CWE Top 25, OWASP ASVS, SBOM/VEX/SLSA, OpenSSF Scorecard, and EU CRA applicability | 12 templates, 3 commands | — | [spec-kit-preset-security-governance](https://github.com/hindermath/spec-kit-preset-security-governance) |
|
||||
| Spec2Cloud | Spec-driven workflow tuned for shipping to Azure: spec → plan → tasks → implement → deploy | 5 templates, 8 commands | — | [spec2cloud](https://github.com/Azure-Samples/Spec2Cloud) |
|
||||
| Table of Contents Navigation | Adds a navigable Table of Contents to generated spec.md, plan.md, and tasks.md documents | 3 templates, 3 commands | — | [spec-kit-preset-toc-navigation](https://github.com/Quratulain-bilal/spec-kit-preset-toc-navigation) |
|
||||
| VS Code Ask Questions | Enhances the clarify command to use `vscode/askQuestions` for batched interactive questioning. | 1 command | — | [spec-kit-presets](https://github.com/fdcastel/spec-kit-presets) |
|
||||
|
||||
To build and publish your own preset, see the [Presets Publishing Guide](https://github.com/github/spec-kit/blob/main/presets/PUBLISHING.md).
|
||||
@@ -1,20 +0,0 @@
|
||||
# Community Walkthroughs
|
||||
|
||||
> [!NOTE]
|
||||
> Community walkthroughs are independently created and maintained by their respective authors. They are **not reviewed, nor endorsed, nor supported by GitHub**. Review their content before following along and use at your own discretion.
|
||||
|
||||
See Spec-Driven Development in action across different scenarios with these community-contributed walkthroughs:
|
||||
|
||||
- **[Greenfield .NET CLI tool](https://github.com/mnriem/spec-kit-dotnet-cli-demo)** — Builds a Timezone Utility as a .NET single-binary CLI tool from a blank directory, covering the full spec-kit workflow: constitution, specify, plan, tasks, and multi-pass implement using GitHub Copilot agents.
|
||||
|
||||
- **[Greenfield Spring Boot + React platform](https://github.com/mnriem/spec-kit-spring-react-demo)** — Builds an LLM performance analytics platform (REST API, graphs, iteration tracking) from scratch using Spring Boot, embedded React, PostgreSQL, and Docker Compose, with a clarify step and a cross-artifact consistency analysis pass included.
|
||||
|
||||
- **[Brownfield ASP.NET CMS extension](https://github.com/mnriem/spec-kit-aspnet-brownfield-demo)** — Extends an existing open-source .NET CMS (CarrotCakeCMS-Core, ~307,000 lines of C#, Razor, SQL, JavaScript, and config files) with two new features — cross-platform Docker Compose infrastructure and a token-authenticated headless REST API — demonstrating how spec-kit fits into existing codebases without prior specs or a constitution.
|
||||
|
||||
- **[Brownfield Java runtime extension](https://github.com/mnriem/spec-kit-java-brownfield-demo)** — Extends an existing open-source Jakarta EE runtime (Piranha, ~420,000 lines of Java, XML, JSP, HTML, and config files across 180 Maven modules) with a password-protected Server Admin Console, demonstrating spec-kit on a large multi-module Java project with no prior specs or constitution.
|
||||
|
||||
- **[Brownfield Go / React dashboard demo](https://github.com/mnriem/spec-kit-go-brownfield-demo)** — Demonstrates spec-kit driven entirely from the **terminal using GitHub Copilot CLI**. Extends NASA's open-source Hermes ground support system (Go) with a lightweight React-based web telemetry dashboard, showing that the full constitution → specify → plan → tasks → implement workflow works from the terminal.
|
||||
|
||||
- **[Greenfield Spring Boot MVC with a custom preset](https://github.com/mnriem/spec-kit-pirate-speak-preset-demo)** — Builds a Spring Boot MVC application from scratch using a custom pirate-speak preset, demonstrating how presets can reshape the entire spec-kit experience: specifications become "Voyage Manifests," plans become "Battle Plans," and tasks become "Crew Assignments" — all generated in full pirate vernacular without changing any tooling.
|
||||
|
||||
- **[Greenfield Spring Boot + React with a custom extension](https://github.com/mnriem/spec-kit-aide-extension-demo)** — Walks through the **AIDE extension**, a community extension that adds an alternative spec-driven workflow to spec-kit with high-level specs (vision) and low-level specs (work items) organized in a 7-step iterative lifecycle: vision → roadmap → progress tracking → work queue → work items → execution → feedback loops. Uses a family trading platform (Spring Boot 4, React 19, PostgreSQL, Docker Compose) as the scenario to illustrate how the extension mechanism lets you plug in a different style of spec-driven development without changing any core tooling — truly utilizing the "Kit" in Spec Kit.
|
||||
@@ -4,9 +4,7 @@
|
||||
{
|
||||
"files": [
|
||||
"*.md",
|
||||
"toc.yml",
|
||||
"community/*.md",
|
||||
"reference/*.md"
|
||||
"toc.yml"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
- **Linux/macOS** (or Windows; PowerShell scripts now supported without WSL)
|
||||
- AI coding agent: [Claude Code](https://www.anthropic.com/claude-code), [GitHub Copilot](https://code.visualstudio.com/), [Codebuddy CLI](https://www.codebuddy.ai/cli), [Gemini CLI](https://github.com/google-gemini/gemini-cli), or [Pi Coding Agent](https://pi.dev)
|
||||
- [uv](https://docs.astral.sh/uv/) for package management (recommended) or [pipx](https://pypa.github.io/pipx/) for persistent installation
|
||||
- [uv](https://docs.astral.sh/uv/) for package management
|
||||
- [Python 3.11+](https://www.python.org/downloads/)
|
||||
- [Git](https://git-scm.com/downloads)
|
||||
|
||||
@@ -24,13 +24,6 @@ uvx --from git+https://github.com/github/spec-kit.git@vX.Y.Z specify init <PROJE
|
||||
uvx --from git+https://github.com/github/spec-kit.git specify init <PROJECT_NAME>
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> For a persistent installation, `pipx` works equally well:
|
||||
> ```bash
|
||||
> pipx install git+https://github.com/github/spec-kit.git@vX.Y.Z
|
||||
> ```
|
||||
> The project uses a standard `hatchling` build backend and has no uv-specific dependencies.
|
||||
|
||||
Or initialize in the current directory:
|
||||
|
||||
```bash
|
||||
@@ -39,16 +32,16 @@ uvx --from git+https://github.com/github/spec-kit.git@vX.Y.Z specify init .
|
||||
uvx --from git+https://github.com/github/spec-kit.git@vX.Y.Z specify init --here
|
||||
```
|
||||
|
||||
### Specify Integration
|
||||
### Specify AI Agent
|
||||
|
||||
You can proactively specify your coding agent integration during initialization:
|
||||
You can proactively specify your AI agent during initialization:
|
||||
|
||||
```bash
|
||||
uvx --from git+https://github.com/github/spec-kit.git@vX.Y.Z specify init <project_name> --integration claude
|
||||
uvx --from git+https://github.com/github/spec-kit.git@vX.Y.Z specify init <project_name> --integration gemini
|
||||
uvx --from git+https://github.com/github/spec-kit.git@vX.Y.Z specify init <project_name> --integration copilot
|
||||
uvx --from git+https://github.com/github/spec-kit.git@vX.Y.Z specify init <project_name> --integration codebuddy
|
||||
uvx --from git+https://github.com/github/spec-kit.git@vX.Y.Z specify init <project_name> --integration pi
|
||||
uvx --from git+https://github.com/github/spec-kit.git@vX.Y.Z specify init <project_name> --ai claude
|
||||
uvx --from git+https://github.com/github/spec-kit.git@vX.Y.Z specify init <project_name> --ai gemini
|
||||
uvx --from git+https://github.com/github/spec-kit.git@vX.Y.Z specify init <project_name> --ai copilot
|
||||
uvx --from git+https://github.com/github/spec-kit.git@vX.Y.Z specify init <project_name> --ai codebuddy
|
||||
uvx --from git+https://github.com/github/spec-kit.git@vX.Y.Z specify init <project_name> --ai pi
|
||||
```
|
||||
|
||||
### Specify Script Type (Shell vs PowerShell)
|
||||
@@ -73,7 +66,7 @@ uvx --from git+https://github.com/github/spec-kit.git@vX.Y.Z specify init <proje
|
||||
If you prefer to get the templates without checking for the right tools:
|
||||
|
||||
```bash
|
||||
uvx --from git+https://github.com/github/spec-kit.git@vX.Y.Z specify init <project_name> --integration claude --ignore-agent-tools
|
||||
uvx --from git+https://github.com/github/spec-kit.git@vX.Y.Z specify init <project_name> --ai claude --ignore-agent-tools
|
||||
```
|
||||
|
||||
## Verification
|
||||
@@ -86,7 +79,7 @@ specify version
|
||||
|
||||
This helps verify you are running the official Spec Kit build from GitHub, not an unrelated package with the same name.
|
||||
|
||||
After initialization, you should see the following commands available in your coding agent:
|
||||
After initialization, you should see the following commands available in your AI agent:
|
||||
|
||||
- `/speckit.specify` - Create specifications
|
||||
- `/speckit.plan` - Generate implementation plans
|
||||
@@ -131,10 +124,12 @@ pip install --no-index --find-links=./dist specify-cli
|
||||
|
||||
```bash
|
||||
# Initialize a project — no GitHub access needed
|
||||
specify init my-project --integration claude
|
||||
specify init my-project --ai claude --offline
|
||||
```
|
||||
|
||||
Bundled assets are used by default — no network access is required.
|
||||
The `--offline` flag tells the CLI to use the templates, commands, and scripts bundled inside the wheel instead of downloading from GitHub.
|
||||
|
||||
> **Deprecation notice:** Starting with v0.6.0, `specify init` will use bundled assets by default and the `--offline` flag will be removed. The GitHub download path will be retired because bundled assets eliminate the need for network access, avoid proxy/firewall issues, and guarantee that templates always match the installed CLI version. No action will be needed — `specify init` will simply work without network access out of the box.
|
||||
|
||||
> **Note:** Python 3.11+ is required.
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ You can execute the CLI via the module entrypoint without installing anything:
|
||||
```bash
|
||||
# From repo root
|
||||
python -m src.specify_cli --help
|
||||
python -m src.specify_cli init demo-project --integration claude --ignore-agent-tools --script sh
|
||||
python -m src.specify_cli init demo-project --ai claude --ignore-agent-tools --script sh
|
||||
```
|
||||
|
||||
If you prefer invoking the script file style (uses shebang):
|
||||
@@ -52,7 +52,7 @@ Re-running after code edits requires no reinstall because of editable mode.
|
||||
`uvx` can run from a local path (or a Git ref) to simulate user flows:
|
||||
|
||||
```bash
|
||||
uvx --from . specify init demo-uvx --integration copilot --ignore-agent-tools --script sh
|
||||
uvx --from . specify init demo-uvx --ai copilot --ignore-agent-tools --script sh
|
||||
```
|
||||
|
||||
You can also point uvx at a specific branch without merging:
|
||||
@@ -69,14 +69,14 @@ If you're in another directory, use an absolute path instead of `.`:
|
||||
|
||||
```bash
|
||||
uvx --from /mnt/c/GitHub/spec-kit specify --help
|
||||
uvx --from /mnt/c/GitHub/spec-kit specify init demo-anywhere --integration copilot --ignore-agent-tools --script sh
|
||||
uvx --from /mnt/c/GitHub/spec-kit specify init demo-anywhere --ai copilot --ignore-agent-tools --script sh
|
||||
```
|
||||
|
||||
Set an environment variable for convenience:
|
||||
|
||||
```bash
|
||||
export SPEC_KIT_SRC=/mnt/c/GitHub/spec-kit
|
||||
uvx --from "$SPEC_KIT_SRC" specify init demo-env --integration copilot --ignore-agent-tools --script ps
|
||||
uvx --from "$SPEC_KIT_SRC" specify init demo-env --ai copilot --ignore-agent-tools --script ps
|
||||
```
|
||||
|
||||
(Optional) Define a shell function:
|
||||
@@ -123,7 +123,7 @@ When testing `init --here` in a dirty directory, create a temp workspace:
|
||||
|
||||
```bash
|
||||
mkdir /tmp/spec-test && cd /tmp/spec-test
|
||||
python -m src.specify_cli init --here --integration claude --ignore-agent-tools --script sh # if repo copied here
|
||||
python -m src.specify_cli init --here --ai claude --ignore-agent-tools --script sh # if repo copied here
|
||||
```
|
||||
|
||||
Or copy only the modified CLI portion if you want a lighter sandbox.
|
||||
|
||||
@@ -22,17 +22,6 @@ uvx --from git+https://github.com/github/spec-kit.git specify init <PROJECT_NAME
|
||||
uvx --from git+https://github.com/github/spec-kit.git specify init .
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> You can also install the CLI persistently with `pipx`:
|
||||
> ```bash
|
||||
> pipx install git+https://github.com/github/spec-kit.git
|
||||
> ```
|
||||
> After installing with `pipx`, run `specify` directly instead of `uvx --from ... specify`, for example:
|
||||
> ```bash
|
||||
> specify init <PROJECT_NAME>
|
||||
> specify init .
|
||||
> ```
|
||||
|
||||
Pick script type explicitly (optional):
|
||||
|
||||
```bash
|
||||
@@ -42,7 +31,7 @@ uvx --from git+https://github.com/github/spec-kit.git specify init <PROJECT_NAME
|
||||
|
||||
### Step 2: Define Your Constitution
|
||||
|
||||
**In your coding agent's chat interface**, use the `/speckit.constitution` slash command to establish the core rules and principles for your project. You should provide your project's specific principles as arguments.
|
||||
**In your AI Agent's chat interface**, use the `/speckit.constitution` slash command to establish the core rules and principles for your project. You should provide your project's specific principles as arguments.
|
||||
|
||||
```markdown
|
||||
/speckit.constitution This project follows a "Library-First" approach. All features must be implemented as standalone libraries first. We use TDD strictly. We prefer functional programming patterns.
|
||||
@@ -159,7 +148,7 @@ Generate an actionable task list using the `/speckit.tasks` command:
|
||||
|
||||
### Step 7: Validate and Implement
|
||||
|
||||
Have your coding agent audit the implementation plan using `/speckit.analyze`:
|
||||
Have your AI agent audit the implementation plan using `/speckit.analyze`:
|
||||
|
||||
```bash
|
||||
/speckit.analyze
|
||||
@@ -180,7 +169,7 @@ Finally, implement the solution:
|
||||
- **Don't focus on tech stack** during specification phase
|
||||
- **Iterate and refine** your specifications before implementation
|
||||
- **Validate** the plan before coding begins
|
||||
- **Let the coding agent handle** the implementation details
|
||||
- **Let the AI agent handle** the implementation details
|
||||
|
||||
## Next Steps
|
||||
|
||||
|
||||
@@ -22,10 +22,6 @@ specify init [<project_name>]
|
||||
|
||||
Creates a new Spec Kit project with the necessary directory structure, templates, scripts, and AI coding agent integration files.
|
||||
|
||||
> [!NOTE]
|
||||
> The git extension is currently enabled by default during `specify init`.
|
||||
> Starting in `v0.10.0`, it will require explicit opt-in. To add it after init, run `specify extension add git`.
|
||||
|
||||
Use `<project_name>` to create a new directory, or `--here` (or `.`) to initialize in the current directory. If the directory already has files, use `--force` to merge without confirmation.
|
||||
|
||||
### Examples
|
||||
|
||||
@@ -13,7 +13,6 @@ The Specify CLI supports a wide range of AI coding agents. When you run `specify
|
||||
| [CodeBuddy CLI](https://www.codebuddy.ai/cli) | `codebuddy` | |
|
||||
| [Codex CLI](https://github.com/openai/codex) | `codex` | Skills-based integration; installs skills into `.agents/skills` and invokes them as `$speckit-<command>` |
|
||||
| [Cursor](https://cursor.sh/) | `cursor-agent` | |
|
||||
| [Devin for Terminal](https://cli.devin.ai/docs) | `devin` | Skills-based integration; installs skills into `.devin/skills/` and invokes them as `/speckit-<command>` |
|
||||
| [Forge](https://forgecode.dev/) | `forge` | |
|
||||
| [Gemini CLI](https://github.com/google-gemini/gemini-cli) | `gemini` | |
|
||||
| [GitHub Copilot](https://code.visualstudio.com/) | `copilot` | |
|
||||
@@ -43,8 +42,6 @@ specify integration list
|
||||
```
|
||||
|
||||
Shows all available integrations, which one is currently installed, and whether each requires a CLI tool or is IDE-based.
|
||||
When multiple integrations are installed, the list marks the default integration separately from the other installed integrations.
|
||||
The list also shows whether each built-in integration is declared multi-install safe.
|
||||
|
||||
## Install an Integration
|
||||
|
||||
@@ -55,12 +52,9 @@ specify integration install <key>
|
||||
| Option | Description |
|
||||
| ------------------------ | ------------------------------------------------------------------------ |
|
||||
| `--script sh\|ps` | Script type: `sh` (bash/zsh) or `ps` (PowerShell) |
|
||||
| `--force` | Opt in to installing alongside integrations that are not declared multi-install safe |
|
||||
| `--integration-options` | Integration-specific options (e.g. `--integration-options="--commands-dir .myagent/cmds"`) |
|
||||
|
||||
Installs the specified integration into the current project. If another integration is already installed, the command only proceeds automatically when all involved integrations are declared multi-install safe. Otherwise, use `switch` to replace the default integration or pass `--force` to explicitly opt in to multi-install. If the installation fails partway through, it automatically rolls back to a clean state.
|
||||
|
||||
Installing an additional integration does not change the default integration. Use `specify integration use <key>` to change the default.
|
||||
Installs the specified integration into the current project. Fails if another integration is already installed — use `switch` instead. If the installation fails partway through, it automatically rolls back to a clean state.
|
||||
|
||||
> **Note:** All integration management commands require a project already initialized with `specify init`. To start a new project with a specific agent, use `specify init <project> --integration <key>` instead.
|
||||
|
||||
@@ -89,22 +83,10 @@ specify integration switch <key>
|
||||
| Option | Description |
|
||||
| ------------------------ | ------------------------------------------------------------------------ |
|
||||
| `--script sh\|ps` | Script type: `sh` (bash/zsh) or `ps` (PowerShell) |
|
||||
| `--force` | Force removal of modified files during uninstall; when the target is already installed, overwrite managed shared templates while changing the default |
|
||||
| `--integration-options` | Options for the target integration when it is not already installed |
|
||||
| `--force` | Force removal of modified files during uninstall |
|
||||
| `--integration-options` | Options for the target integration |
|
||||
|
||||
If the target integration is not already installed, equivalent to running `uninstall` followed by `install` in a single step. In this mode, `--force` controls whether modified files from the removed integration are deleted. If the target integration is already installed, `switch` only changes the default integration, like `use`; in this mode, `--force` controls whether managed shared templates are overwritten while the default changes. `--integration-options` is rejected for already-installed targets because changing integration options requires reinstalling managed files; run `upgrade <key> --integration-options ...` first, then `use <key>`.
|
||||
|
||||
## Use an Installed Integration
|
||||
|
||||
```bash
|
||||
specify integration use <key>
|
||||
```
|
||||
|
||||
| Option | Description |
|
||||
| --------- | --------------------------------------------------- |
|
||||
| `--force` | Overwrite managed shared templates while changing the default |
|
||||
|
||||
Sets the default integration without uninstalling any other installed integrations. This also refreshes managed shared templates so command references match the new default integration's invocation style. Modified or untracked shared templates are preserved unless `--force` is used.
|
||||
Equivalent to running `uninstall` followed by `install` in a single step.
|
||||
|
||||
## Upgrade an Integration
|
||||
|
||||
@@ -118,7 +100,7 @@ specify integration upgrade [<key>]
|
||||
| `--script sh\|ps` | Script type: `sh` (bash/zsh) or `ps` (PowerShell) |
|
||||
| `--integration-options` | Options for the integration |
|
||||
|
||||
Reinstalls an installed integration with updated templates and commands (e.g., after upgrading Spec Kit). Defaults to the default integration; if a key is provided, it must be one of the installed integrations. Detects locally modified files and blocks the upgrade unless `--force` is used. Stale files from the previous install that are no longer needed are removed automatically. Shared templates stay aligned with the default integration even when upgrading a non-default integration.
|
||||
Reinstalls the current integration with updated templates and commands (e.g., after upgrading Spec Kit). Defaults to the currently installed integration; if a key is provided, it must match the installed one — otherwise the command fails and suggests using `switch` instead. Detects locally modified files and blocks the upgrade unless `--force` is used. Stale files from the previous install that are no longer needed are removed automatically.
|
||||
|
||||
## Integration-Specific Options
|
||||
|
||||
@@ -137,39 +119,9 @@ specify integration install generic --integration-options="--commands-dir .myage
|
||||
|
||||
## FAQ
|
||||
|
||||
### Can I install multiple integrations in the same project?
|
||||
### Can I use multiple integrations at the same time?
|
||||
|
||||
Yes, but it is intended for team portability rather than the default workflow. Multiple integrations are allowed automatically only when the installed integration and the new integration are declared multi-install safe by Spec Kit. For other combinations, pass `--force` to acknowledge that multiple agents may see unrelated agent-specific instructions or commands.
|
||||
|
||||
Spec Kit tracks one default integration in `.specify/integration.json` with `default_integration`, all installed integrations with `installed_integrations`, per-integration runtime settings with `integration_settings`, and a dedicated `integration_state_schema` for future state migrations. The legacy `integration` field remains as an alias for the default integration.
|
||||
|
||||
### Which integrations are multi-install safe?
|
||||
|
||||
An integration is multi-install safe when it uses isolated agent directories, a dedicated context file that does not collide with another safe integration, stable command invocation settings, and a separate install manifest. Shared Spec Kit templates remain aligned to the single default integration.
|
||||
|
||||
The currently declared multi-install safe integrations are:
|
||||
|
||||
| Key | Isolation |
|
||||
| --- | --------- |
|
||||
| `auggie` | `.augment/commands`, `.augment/rules/specify-rules.md` |
|
||||
| `claude` | `.claude/skills`, `CLAUDE.md` |
|
||||
| `codebuddy` | `.codebuddy/commands`, `CODEBUDDY.md` |
|
||||
| `codex` | `.agents/skills`, `AGENTS.md` |
|
||||
| `cursor-agent` | `.cursor/skills`, `.cursor/rules/specify-rules.mdc` |
|
||||
| `gemini` | `.gemini/commands`, `GEMINI.md` |
|
||||
| `iflow` | `.iflow/commands`, `IFLOW.md` |
|
||||
| `junie` | `.junie/commands`, `.junie/AGENTS.md` |
|
||||
| `kilocode` | `.kilocode/workflows`, `.kilocode/rules/specify-rules.md` |
|
||||
| `kimi` | `.kimi/skills`, `KIMI.md` |
|
||||
| `qodercli` | `.qoder/commands`, `QODER.md` |
|
||||
| `qwen` | `.qwen/commands`, `QWEN.md` |
|
||||
| `roo` | `.roo/commands`, `.roo/rules/specify-rules.md` |
|
||||
| `shai` | `.shai/commands`, `SHAI.md` |
|
||||
| `tabnine` | `.tabnine/agent/commands`, `TABNINE.md` |
|
||||
| `trae` | `.trae/skills`, `.trae/rules/project_rules.md` |
|
||||
| `windsurf` | `.windsurf/workflows`, `.windsurf/rules/specify-rules.md` |
|
||||
|
||||
Integrations that share a context file or command directory with another integration, require dynamic install paths such as `--commands-dir`, or merge shared tool settings are not declared safe by default. They can still be installed alongside another integration with `--force`.
|
||||
No. Only one AI coding agent integration can be installed per project. Use `specify integration switch <key>` to change to a different AI coding agent.
|
||||
|
||||
### What happens to my changes when I uninstall or switch?
|
||||
|
||||
@@ -185,4 +137,4 @@ CLI-based integrations (like Claude Code, Gemini CLI) require the tool to be ins
|
||||
|
||||
### When should I use `upgrade` vs `switch`?
|
||||
|
||||
Use `upgrade` when you've upgraded Spec Kit and want to refresh an installed integration's managed files. Use `switch` when you want to replace the current default with another integration; if the target is already installed, `switch` behaves like `use`.
|
||||
Use `upgrade` when you've upgraded Spec Kit and want to refresh the same integration's templates. Use `switch` when you want to change to a different AI coding agent.
|
||||
|
||||
10
docs/toc.yml
10
docs/toc.yml
@@ -33,13 +33,3 @@
|
||||
items:
|
||||
- name: Local Development
|
||||
href: local-development.md
|
||||
|
||||
# Community
|
||||
- name: Community
|
||||
items:
|
||||
- name: Presets
|
||||
href: community/presets.md
|
||||
- name: Walkthroughs
|
||||
href: community/walkthroughs.md
|
||||
- name: Friends
|
||||
href: community/friends.md
|
||||
|
||||
@@ -9,8 +9,7 @@
|
||||
| What to Upgrade | Command | When to Use |
|
||||
|----------------|---------|-------------|
|
||||
| **CLI Tool Only** | `uv tool install specify-cli --force --from git+https://github.com/github/spec-kit.git@vX.Y.Z` | Get latest CLI features without touching project files |
|
||||
| **CLI Tool Only (pipx)** | `pipx install --force git+https://github.com/github/spec-kit.git@vX.Y.Z` | Reinstall/upgrade a pipx-installed CLI to a specific release |
|
||||
| **Project Files** | `specify init --here --force --integration <your-agent>` | Update slash commands, templates, and scripts in your project |
|
||||
| **Project Files** | `specify init --here --force --ai <your-agent>` | Update slash commands, templates, and scripts in your project |
|
||||
| **Both** | Run CLI upgrade, then project update | Recommended for major version updates |
|
||||
|
||||
---
|
||||
@@ -32,15 +31,7 @@ uv tool install specify-cli --force --from git+https://github.com/github/spec-ki
|
||||
Specify the desired release tag:
|
||||
|
||||
```bash
|
||||
uvx --from git+https://github.com/github/spec-kit.git@vX.Y.Z specify init --here --integration copilot
|
||||
```
|
||||
|
||||
### If you installed with `pipx`
|
||||
|
||||
Upgrade to a specific release:
|
||||
|
||||
```bash
|
||||
pipx install --force git+https://github.com/github/spec-kit.git@vX.Y.Z
|
||||
uvx --from git+https://github.com/github/spec-kit.git@vX.Y.Z specify init --here --ai copilot
|
||||
```
|
||||
|
||||
### Verify the upgrade
|
||||
@@ -62,8 +53,8 @@ When Spec Kit releases new features (like new slash commands or updated template
|
||||
Running `specify init --here --force` will update:
|
||||
|
||||
- ✅ **Slash command files** (`.claude/commands/`, `.github/prompts/`, etc.)
|
||||
- ✅ **Script files** (`.specify/scripts/`) — **only with `--force`**; without it, only missing files are added
|
||||
- ✅ **Template files** (`.specify/templates/`) — **only with `--force`**; without it, only missing files are added
|
||||
- ✅ **Script files** (`.specify/scripts/`)
|
||||
- ✅ **Template files** (`.specify/templates/`)
|
||||
- ✅ **Shared memory files** (`.specify/memory/`) - **⚠️ See warnings below**
|
||||
|
||||
### What stays safe?
|
||||
@@ -82,7 +73,7 @@ The `specs/` directory is completely excluded from template packages and will ne
|
||||
Run this inside your project directory:
|
||||
|
||||
```bash
|
||||
specify init --here --force --integration <your-agent>
|
||||
specify init --here --force --ai <your-agent>
|
||||
```
|
||||
|
||||
Replace `<your-agent>` with your AI coding agent. Refer to this list of [Supported AI Coding Agent Integrations](reference/integrations.md)
|
||||
@@ -90,7 +81,7 @@ Replace `<your-agent>` with your AI coding agent. Refer to this list of [Support
|
||||
**Example:**
|
||||
|
||||
```bash
|
||||
specify init --here --force --integration copilot
|
||||
specify init --here --force --ai copilot
|
||||
```
|
||||
|
||||
### Understanding the `--force` flag
|
||||
@@ -103,9 +94,7 @@ Template files will be merged with existing content and may overwrite existing f
|
||||
Proceed? [y/N]
|
||||
```
|
||||
|
||||
With `--force`, it skips the confirmation and proceeds immediately. It also **overwrites shared infrastructure files** (`.specify/scripts/` and `.specify/templates/`) with the latest versions from the installed Spec Kit release.
|
||||
|
||||
Without `--force`, shared infrastructure files that already exist are skipped — the CLI will print a warning listing the skipped files so you know which ones were not updated.
|
||||
With `--force`, it skips the confirmation and proceeds immediately.
|
||||
|
||||
**Important: Your `specs/` directory is always safe.** The `--force` flag only affects template files (commands, scripts, templates, memory). Your feature specifications, plans, and tasks in `specs/` are never included in upgrade packages and cannot be overwritten.
|
||||
|
||||
@@ -124,7 +113,7 @@ Without `--force`, shared infrastructure files that already exist are skipped
|
||||
cp .specify/memory/constitution.md .specify/memory/constitution-backup.md
|
||||
|
||||
# 2. Run the upgrade
|
||||
specify init --here --force --integration copilot
|
||||
specify init --here --force --ai copilot
|
||||
|
||||
# 3. Restore your customized constitution
|
||||
mv .specify/memory/constitution-backup.md .specify/memory/constitution.md
|
||||
@@ -137,14 +126,13 @@ Or use git to restore it:
|
||||
git restore .specify/memory/constitution.md
|
||||
```
|
||||
|
||||
### 2. Custom script or template modifications
|
||||
### 2. Custom template modifications
|
||||
|
||||
If you customized files in `.specify/scripts/` or `.specify/templates/`, the `--force` flag will overwrite them. Back them up first:
|
||||
If you customized any templates in `.specify/templates/`, the upgrade will overwrite them. Back them up first:
|
||||
|
||||
```bash
|
||||
# Back up custom templates and scripts
|
||||
# Back up custom templates
|
||||
cp -r .specify/templates .specify/templates-backup
|
||||
cp -r .specify/scripts .specify/scripts-backup
|
||||
|
||||
# After upgrade, merge your changes back manually
|
||||
```
|
||||
@@ -182,7 +170,7 @@ Restart your IDE to refresh the command list.
|
||||
uv tool install specify-cli --force --from git+https://github.com/github/spec-kit.git
|
||||
|
||||
# Update project files to get new commands
|
||||
specify init --here --force --integration copilot
|
||||
specify init --here --force --ai copilot
|
||||
|
||||
# Restore your constitution if customized
|
||||
git restore .specify/memory/constitution.md
|
||||
@@ -199,7 +187,7 @@ cp -r .specify/templates /tmp/templates-backup
|
||||
uv tool install specify-cli --force --from git+https://github.com/github/spec-kit.git
|
||||
|
||||
# 3. Update project
|
||||
specify init --here --force --integration copilot
|
||||
specify init --here --force --ai copilot
|
||||
|
||||
# 4. Restore customizations
|
||||
mv /tmp/constitution-backup.md .specify/memory/constitution.md
|
||||
@@ -232,7 +220,7 @@ If you initialized your project with `--no-git`, you can still upgrade:
|
||||
cp .specify/memory/constitution.md /tmp/constitution-backup.md
|
||||
|
||||
# Run upgrade
|
||||
specify init --here --force --integration copilot --no-git
|
||||
specify init --here --force --ai copilot --no-git
|
||||
|
||||
# Restore customizations
|
||||
mv /tmp/constitution-backup.md .specify/memory/constitution.md
|
||||
@@ -253,13 +241,13 @@ The `--no-git` flag tells Spec Kit to **skip git repository initialization**. Th
|
||||
**During initial setup:**
|
||||
|
||||
```bash
|
||||
specify init my-project --integration copilot --no-git
|
||||
specify init my-project --ai copilot --no-git
|
||||
```
|
||||
|
||||
**During upgrade:**
|
||||
|
||||
```bash
|
||||
specify init --here --force --integration copilot --no-git
|
||||
specify init --here --force --ai copilot --no-git
|
||||
```
|
||||
|
||||
### What `--no-git` does NOT do
|
||||
@@ -367,7 +355,7 @@ Only Spec Kit infrastructure files:
|
||||
- **Use `--force` flag** - Skip this confirmation entirely:
|
||||
|
||||
```bash
|
||||
specify init --here --force --integration copilot
|
||||
specify init --here --force --ai copilot
|
||||
```
|
||||
|
||||
**When you see this warning:**
|
||||
|
||||
@@ -528,9 +528,11 @@ specify extension add <extension-name> --from https://github.com/.../spec-kit-my
|
||||
|
||||
Submit to the community catalog for public discovery:
|
||||
|
||||
1. **Create a GitHub release** for your extension
|
||||
2. **File an issue** using the [Extension Submission](https://github.com/github/spec-kit/issues/new?template=extension_submission.yml) template
|
||||
3. **After review**, a maintainer updates the catalog and your extension becomes available:
|
||||
1. **Fork** spec-kit repository
|
||||
2. **Add entry** to `extensions/catalog.community.json`
|
||||
3. **Update** the Community Extensions table in `README.md` with your extension
|
||||
4. **Create PR** following the [Extension Publishing Guide](EXTENSION-PUBLISHING-GUIDE.md)
|
||||
5. **After merge**, your extension becomes available:
|
||||
- Users can browse `catalog.community.json` to discover your extension
|
||||
- Users copy the entry to their own `catalog.json`
|
||||
- Users install with: `specify extension add my-ext` (from their catalog)
|
||||
@@ -667,7 +669,7 @@ hooks:
|
||||
|
||||
**Error**: `Extension requires spec-kit >=0.2.0`
|
||||
|
||||
- **Fix**: Update spec-kit with `uv tool install specify-cli --force --from git+https://github.com/github/spec-kit.git`. The bare `specify-cli` package on PyPI is a different, unrelated project — installing it without `--from git+...` will give you a stub CLI that does not include `extension`, `preset`, or other spec-kit commands.
|
||||
- **Fix**: Update spec-kit with `uv tool install specify-cli --force`
|
||||
|
||||
**Error**: `Command file not found`
|
||||
|
||||
|
||||
@@ -7,8 +7,9 @@ This guide explains how to publish your extension to the Spec Kit extension cata
|
||||
1. [Prerequisites](#prerequisites)
|
||||
2. [Prepare Your Extension](#prepare-your-extension)
|
||||
3. [Submit to Catalog](#submit-to-catalog)
|
||||
4. [Release Workflow](#release-workflow)
|
||||
5. [Best Practices](#best-practices)
|
||||
4. [Verification Process](#verification-process)
|
||||
5. [Release Workflow](#release-workflow)
|
||||
6. [Best Practices](#best-practices)
|
||||
|
||||
---
|
||||
|
||||
@@ -132,46 +133,222 @@ specify extension add <extension-name> --from https://github.com/your-org/spec-k
|
||||
|
||||
Spec Kit uses a dual-catalog system. For details about how catalogs work, see the main [Extensions README](README.md#extension-catalogs).
|
||||
|
||||
**For extension publishing**: All community extensions are listed in `extensions/catalog.community.json`. Users browse this catalog and copy extensions they trust into their own `catalog.json`.
|
||||
**For extension publishing**: All community extensions should be added to `catalog.community.json`. Users browse this catalog and copy extensions they trust into their own `catalog.json`.
|
||||
|
||||
### How to Submit
|
||||
### 1. Fork the spec-kit Repository
|
||||
|
||||
To submit your extension to the community catalog, file a new issue using the **[Extension Submission](https://github.com/github/spec-kit/issues/new?template=extension_submission.yml)** template. The template collects all required metadata, including:
|
||||
```bash
|
||||
# Fork on GitHub
|
||||
# https://github.com/github/spec-kit/fork
|
||||
|
||||
- Extension ID, name, and version
|
||||
- Description, author, and license
|
||||
- Repository, download URL, and documentation links
|
||||
- Required Spec Kit version and any tool dependencies
|
||||
- Number of commands and hooks
|
||||
- Tags and key features
|
||||
- Testing confirmation
|
||||
# Clone your fork
|
||||
git clone https://github.com/YOUR-USERNAME/spec-kit.git
|
||||
cd spec-kit
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Do **not** open a pull request directly to edit `extensions/catalog.community.json`. All community extension submissions must go through the issue template so a maintainer can review the entry and update the catalog.
|
||||
### 2. Add Extension to Community Catalog
|
||||
|
||||
### What Happens After You Submit
|
||||
Edit `extensions/catalog.community.json` and add your extension:
|
||||
|
||||
1. Your issue is automatically labeled and assigned to a maintainer for review
|
||||
2. A maintainer verifies that the catalog entry is complete and correctly formatted
|
||||
3. Once approved, the maintainer adds your extension to `extensions/catalog.community.json` and the Community Extensions table in the README
|
||||
4. Your extension becomes discoverable via `specify extension search`
|
||||
```json
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"updated_at": "2026-01-28T15:54:00Z",
|
||||
"catalog_url": "https://raw.githubusercontent.com/github/spec-kit/main/extensions/catalog.community.json",
|
||||
"extensions": {
|
||||
"your-extension": {
|
||||
"name": "Your Extension Name",
|
||||
"id": "your-extension",
|
||||
"description": "Brief description of your extension",
|
||||
"author": "Your Name",
|
||||
"version": "1.0.0",
|
||||
"download_url": "https://github.com/your-org/spec-kit-your-extension/archive/refs/tags/v1.0.0.zip",
|
||||
"repository": "https://github.com/your-org/spec-kit-your-extension",
|
||||
"homepage": "https://github.com/your-org/spec-kit-your-extension",
|
||||
"documentation": "https://github.com/your-org/spec-kit-your-extension/blob/main/docs/",
|
||||
"changelog": "https://github.com/your-org/spec-kit-your-extension/blob/main/CHANGELOG.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.1.0",
|
||||
"tools": [
|
||||
{
|
||||
"name": "required-mcp-tool",
|
||||
"version": ">=1.0.0",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"provides": {
|
||||
"commands": 3,
|
||||
"hooks": 1
|
||||
},
|
||||
"tags": [
|
||||
"category",
|
||||
"tool-name",
|
||||
"feature"
|
||||
],
|
||||
"verified": false,
|
||||
"downloads": 0,
|
||||
"stars": 0,
|
||||
"created_at": "2026-01-28T00:00:00Z",
|
||||
"updated_at": "2026-01-28T00:00:00Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### What Maintainers Check
|
||||
**Important**:
|
||||
|
||||
- The catalog entry fields are complete and correctly formatted
|
||||
- The download URL is accessible
|
||||
- The repository exists and contains an `extension.yml` manifest
|
||||
- Set `verified: false` (maintainers will verify)
|
||||
- Set `downloads: 0` and `stars: 0` (auto-updated later)
|
||||
- Use current timestamp for `created_at` and `updated_at`
|
||||
- Update the top-level `updated_at` to current time
|
||||
|
||||
> [!NOTE]
|
||||
> Maintainers do **not** review, audit, or test the extension code itself.
|
||||
### 3. Update Community Extensions Table
|
||||
|
||||
Add your extension to the Community Extensions table in the project root `README.md`:
|
||||
|
||||
```markdown
|
||||
| Your Extension Name | Brief description of what it does | `<category>` | <effect> | [repo-name](https://github.com/your-org/spec-kit-your-extension) |
|
||||
```
|
||||
|
||||
**(Table) Category** — pick the one that best fits your extension:
|
||||
|
||||
- `docs` — reads, validates, or generates spec artifacts
|
||||
- `code` — reviews, validates, or modifies source code
|
||||
- `process` — orchestrates workflow across phases
|
||||
- `integration` — syncs with external platforms
|
||||
- `visibility` — reports on project health or progress
|
||||
|
||||
**Effect** — choose one:
|
||||
|
||||
- Read-only — produces reports without modifying files
|
||||
- Read+Write — modifies files, creates artifacts, or updates specs
|
||||
|
||||
Insert your extension in alphabetical order in the table.
|
||||
|
||||
### 4. Submit Pull Request
|
||||
|
||||
```bash
|
||||
# Create a branch
|
||||
git checkout -b add-your-extension
|
||||
|
||||
# Commit your changes
|
||||
git add extensions/catalog.community.json README.md
|
||||
git commit -m "Add your-extension to community catalog
|
||||
|
||||
- Extension ID: your-extension
|
||||
- Version: 1.0.0
|
||||
- Author: Your Name
|
||||
- Description: Brief description
|
||||
"
|
||||
|
||||
# Push to your fork
|
||||
git push origin add-your-extension
|
||||
|
||||
# Create Pull Request on GitHub
|
||||
# https://github.com/github/spec-kit/compare
|
||||
```
|
||||
|
||||
**Pull Request Template**:
|
||||
|
||||
```markdown
|
||||
## Extension Submission
|
||||
|
||||
**Extension Name**: Your Extension Name
|
||||
**Extension ID**: your-extension
|
||||
**Version**: 1.0.0
|
||||
**Author**: Your Name
|
||||
**Repository**: https://github.com/your-org/spec-kit-your-extension
|
||||
|
||||
### Description
|
||||
Brief description of what your extension does.
|
||||
|
||||
### Checklist
|
||||
- [x] Valid extension.yml manifest
|
||||
- [x] README.md with installation and usage docs
|
||||
- [x] LICENSE file included
|
||||
- [x] GitHub release created (v1.0.0)
|
||||
- [x] Extension tested on real project
|
||||
- [x] All commands working
|
||||
- [x] No security vulnerabilities
|
||||
- [x] Added to extensions/catalog.community.json
|
||||
- [x] Added to Community Extensions table in README.md
|
||||
|
||||
### Testing
|
||||
Tested on:
|
||||
- macOS 13.0+ with spec-kit 0.1.0
|
||||
- Project: [Your test project]
|
||||
|
||||
### Additional Notes
|
||||
Any additional context or notes for reviewers.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Verification Process
|
||||
|
||||
### What Happens After Submission
|
||||
|
||||
1. **Automated Checks** (if available):
|
||||
- Manifest validation
|
||||
- Download URL accessibility
|
||||
- Repository existence
|
||||
- License file presence
|
||||
|
||||
2. **Manual Review**:
|
||||
- Code quality review
|
||||
- Security audit
|
||||
- Functionality testing
|
||||
- Documentation review
|
||||
|
||||
3. **Verification**:
|
||||
- If approved, `verified: true` is set
|
||||
- Extension appears in `specify extension search --verified`
|
||||
|
||||
### Verification Criteria
|
||||
|
||||
To be verified, your extension must:
|
||||
|
||||
✅ **Functionality**:
|
||||
|
||||
- Works as described in documentation
|
||||
- All commands execute without errors
|
||||
- No breaking changes to user workflows
|
||||
|
||||
✅ **Security**:
|
||||
|
||||
- No known vulnerabilities
|
||||
- No malicious code
|
||||
- Safe handling of user data
|
||||
- Proper validation of inputs
|
||||
|
||||
✅ **Code Quality**:
|
||||
|
||||
- Clean, readable code
|
||||
- Follows extension best practices
|
||||
- Proper error handling
|
||||
- Helpful error messages
|
||||
|
||||
✅ **Documentation**:
|
||||
|
||||
- Clear installation instructions
|
||||
- Usage examples
|
||||
- Troubleshooting section
|
||||
- Accurate description
|
||||
|
||||
✅ **Maintenance**:
|
||||
|
||||
- Active repository
|
||||
- Responsive to issues
|
||||
- Regular updates
|
||||
- Semantic versioning followed
|
||||
|
||||
### Typical Review Timeline
|
||||
|
||||
- **Review**: 3-7 business days
|
||||
|
||||
### Updating an Existing Extension
|
||||
|
||||
To update an extension that is already in the catalog (e.g., for a new version), file a new **[Extension Submission](https://github.com/github/spec-kit/issues/new?template=extension_submission.yml)** issue with the updated version, download URL, and any other changed fields. Mention in the issue that this is an update to an existing entry.
|
||||
- **Automated checks**: Immediate (if implemented)
|
||||
- **Manual review**: 3-7 business days
|
||||
- **Verification**: After successful review
|
||||
|
||||
---
|
||||
|
||||
@@ -208,7 +385,26 @@ When releasing a new version:
|
||||
# Create release on GitHub
|
||||
```
|
||||
|
||||
4. **File an update submission** using the [Extension Submission](https://github.com/github/spec-kit/issues/new?template=extension_submission.yml) template with the new version and download URL. Mention in the issue that this is an update to an existing entry.
|
||||
4. **Update catalog**:
|
||||
|
||||
```bash
|
||||
# Fork spec-kit repo (or update existing fork)
|
||||
cd spec-kit
|
||||
|
||||
# Update extensions/catalog.json
|
||||
jq '.extensions["your-extension"].version = "1.1.0"' extensions/catalog.json > tmp.json && mv tmp.json extensions/catalog.json
|
||||
jq '.extensions["your-extension"].download_url = "https://github.com/your-org/spec-kit-your-extension/archive/refs/tags/v1.1.0.zip"' extensions/catalog.json > tmp.json && mv tmp.json extensions/catalog.json
|
||||
jq '.extensions["your-extension"].updated_at = "2026-02-15T00:00:00Z"' extensions/catalog.json > tmp.json && mv tmp.json extensions/catalog.json
|
||||
jq '.updated_at = "2026-02-15T00:00:00Z"' extensions/catalog.json > tmp.json && mv tmp.json extensions/catalog.json
|
||||
|
||||
# Submit PR
|
||||
git checkout -b update-your-extension-v1.1.0
|
||||
git add extensions/catalog.json
|
||||
git commit -m "Update your-extension to v1.1.0"
|
||||
git push origin update-your-extension-v1.1.0
|
||||
```
|
||||
|
||||
5. **Submit update PR** with changelog in description
|
||||
|
||||
---
|
||||
|
||||
@@ -277,9 +473,9 @@ A: The main catalog is for public extensions only. For private extensions:
|
||||
- Users add your catalog: `specify extension add-catalog https://your-domain.com/catalog.json`
|
||||
- Not yet implemented - coming in Phase 4
|
||||
|
||||
### Q: How long does review take?
|
||||
### Q: How long does verification take?
|
||||
|
||||
A: Typically 3-7 business days. Updates to existing extensions are usually faster.
|
||||
A: Typically 3-7 business days for initial review. Updates to verified extensions are usually faster.
|
||||
|
||||
### Q: What if my extension is rejected?
|
||||
|
||||
@@ -287,11 +483,11 @@ A: You'll receive feedback on what needs to be fixed. Make the changes and resub
|
||||
|
||||
### Q: Can I update my extension anytime?
|
||||
|
||||
A: Yes, file a new [Extension Submission](https://github.com/github/spec-kit/issues/new?template=extension_submission.yml) issue with the updated version and download URL. Mention that it is an update to an existing entry.
|
||||
A: Yes, submit a PR to update the catalog with your new version. Verified status may be re-evaluated for major changes.
|
||||
|
||||
### Q: Do I need to be verified to be in the catalog?
|
||||
|
||||
A: No. All community extensions are listed in the catalog once their submission is reviewed and accepted.
|
||||
A: No, unverified extensions are still searchable. Verification just adds trust and visibility.
|
||||
|
||||
### Q: Can extensions have paid features?
|
||||
|
||||
@@ -340,7 +536,7 @@ A: Extensions should be free and open-source. Commercial support/services are al
|
||||
"hooks": "integer (optional)"
|
||||
},
|
||||
"tags": ["array of strings (2-10 tags)"],
|
||||
"verified": "boolean (default: false, set by maintainers)",
|
||||
"verified": "boolean (default: false)",
|
||||
"downloads": "integer (auto-updated)",
|
||||
"stars": "integer (auto-updated)",
|
||||
"created_at": "string (ISO 8601 datetime)",
|
||||
|
||||
@@ -153,7 +153,7 @@ This will:
|
||||
2. Validate the manifest
|
||||
3. Check compatibility with your spec-kit version
|
||||
4. Install to `.specify/extensions/jira/`
|
||||
5. Register commands with your coding agent
|
||||
5. Register commands with your AI agent
|
||||
6. Create config template
|
||||
|
||||
### Install from URL
|
||||
@@ -189,7 +189,7 @@ Provided commands:
|
||||
|
||||
### Automatic Agent Skill Registration
|
||||
|
||||
If your project uses a skills-based integration (e.g., `--integration claude`, `--integration codex`) or was initialized with `--integration-options="--skills"`, extension commands are **automatically registered as agent skills** during installation. This ensures that extensions are discoverable by agents that use the [agentskills.io](https://agentskills.io) skill specification.
|
||||
If your project was initialized with `--ai-skills`, extension commands are **automatically registered as agent skills** during installation. This ensures that extensions are discoverable by agents that use the [agentskills.io](https://agentskills.io) skill specification.
|
||||
|
||||
```text
|
||||
✓ Extension installed successfully!
|
||||
@@ -208,7 +208,7 @@ When an extension is removed, its corresponding skills are also cleaned up autom
|
||||
|
||||
### Using Extension Commands
|
||||
|
||||
Extensions add commands that appear in your coding agent (Claude Code):
|
||||
Extensions add commands that appear in your AI agent (Claude Code):
|
||||
|
||||
```text
|
||||
# In Claude Code
|
||||
@@ -423,7 +423,7 @@ In addition to extension-specific environment variables (`SPECKIT_{EXT_ID}_*`),
|
||||
| Variable | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `SPECKIT_CATALOG_URL` | Override the full catalog stack with a single URL (backward compat) | Built-in default stack |
|
||||
| `GH_TOKEN` / `GITHUB_TOKEN` | GitHub token for authenticated requests to GitHub-hosted URLs (`raw.githubusercontent.com`, `github.com`, `api.github.com`, `codeload.github.com`). Required when your catalog JSON or extension ZIPs are hosted in a private GitHub repository. | None |
|
||||
| `GH_TOKEN` / `GITHUB_TOKEN` | GitHub API token for downloads | None |
|
||||
|
||||
#### Example: Using a custom catalog for testing
|
||||
|
||||
@@ -435,21 +435,6 @@ export SPECKIT_CATALOG_URL="http://localhost:8000/catalog.json"
|
||||
export SPECKIT_CATALOG_URL="https://example.com/staging/catalog.json"
|
||||
```
|
||||
|
||||
#### Example: Using a private GitHub-hosted catalog
|
||||
|
||||
```bash
|
||||
# Authenticate with a token (gh CLI, PAT, or GITHUB_TOKEN in CI)
|
||||
export GITHUB_TOKEN=$(gh auth token)
|
||||
|
||||
# Search a private catalog added via `specify extension catalog add`
|
||||
specify extension search jira
|
||||
|
||||
# Install from a private catalog
|
||||
specify extension add jira-sync
|
||||
```
|
||||
|
||||
The token is attached automatically to requests targeting GitHub domains. Non-GitHub catalog URLs are always fetched without credentials.
|
||||
|
||||
---
|
||||
|
||||
## Extension Catalogs
|
||||
@@ -795,12 +780,12 @@ specify extension add --dev /path/to/extension
|
||||
|
||||
### Command Not Available
|
||||
|
||||
**Issue**: Extension command not appearing in coding agent
|
||||
**Issue**: Extension command not appearing in AI agent
|
||||
|
||||
**Solutions**:
|
||||
|
||||
1. Check extension is enabled: `specify extension list`
|
||||
2. Restart coding agent (Claude Code)
|
||||
2. Restart AI agent (Claude Code)
|
||||
3. Check command file exists:
|
||||
|
||||
```bash
|
||||
@@ -834,8 +819,8 @@ specify extension add --dev /path/to/extension
|
||||
**Solutions**:
|
||||
|
||||
1. Check MCP server is installed
|
||||
2. Check coding agent MCP configuration
|
||||
3. Restart coding agent
|
||||
2. Check AI agent MCP configuration
|
||||
3. Restart AI agent
|
||||
4. Check extension requirements: `specify extension info jira`
|
||||
|
||||
### Permission Denied
|
||||
|
||||
@@ -25,13 +25,13 @@ specify extension search # Now uses your organization's catalog instead of the
|
||||
### Community Reference Catalog (`catalog.community.json`)
|
||||
|
||||
> [!NOTE]
|
||||
> Community extensions are independently created and maintained by their respective authors. Maintainers only verify that catalog entries are complete and correctly formatted — they do **not review, audit, endorse, or support the extension code itself**. Review extension source code before installation and use at your own discretion.
|
||||
> Community extensions are independently created and maintained by their respective authors. GitHub and the Spec Kit maintainers may review pull requests that add entries to the community catalog for formatting, catalog structure, or policy compliance, but they do **not review, audit, endorse, or support the extension code itself**. Review extension source code before installation and use at your own discretion.
|
||||
|
||||
- **Purpose**: Browse available community-contributed extensions
|
||||
- **Status**: Active - contains extensions submitted by the community
|
||||
- **Location**: `extensions/catalog.community.json`
|
||||
- **Usage**: Reference catalog for discovering available extensions
|
||||
- **Submission**: Open to community contributions via [issue template](https://github.com/github/spec-kit/issues/new?template=extension_submission.yml)
|
||||
- **Submission**: Open to community contributions via Pull Request
|
||||
|
||||
**How It Works:**
|
||||
|
||||
@@ -72,7 +72,7 @@ specify extension add <extension-name> --from https://github.com/org/spec-kit-ex
|
||||
## Available Community Extensions
|
||||
|
||||
> [!NOTE]
|
||||
> Community extensions are independently created and maintained by their respective authors. Maintainers only verify that catalog entries are complete and correctly formatted — they do **not review, audit, endorse, or support the extension code itself**. The Community Extensions website is also a third-party resource. Review extension source code before installation and use at your own discretion.
|
||||
> Community extensions are independently created and maintained by their respective authors. GitHub and the Spec Kit maintainers may review pull requests that add entries to the community catalog for formatting, catalog structure, or policy compliance, but they do **not review, audit, endorse, or support the extension code itself**. The Community Extensions website is also a third-party resource. Review extension source code before installation and use at your own discretion.
|
||||
|
||||
🔍 **Browse and search community extensions on the [Community Extensions website](https://speckit-community.github.io/extensions/).**
|
||||
|
||||
@@ -89,8 +89,10 @@ To add your extension to the community catalog:
|
||||
|
||||
1. **Prepare your extension** following the [Extension Development Guide](EXTENSION-DEVELOPMENT-GUIDE.md)
|
||||
2. **Create a GitHub release** for your extension
|
||||
3. **File an issue** using the [Extension Submission](https://github.com/github/spec-kit/issues/new?template=extension_submission.yml) template with all required metadata
|
||||
4. **Wait for review** — a maintainer will review the submission, update the catalog, and close the issue
|
||||
3. **Submit a Pull Request** that:
|
||||
- Adds your extension to `extensions/catalog.community.json`
|
||||
- Updates this README with your extension in the Available Extensions table
|
||||
4. **Wait for review** - maintainers will review and merge if criteria are met
|
||||
|
||||
See the [Extension Publishing Guide](EXTENSION-PUBLISHING-GUIDE.md) for detailed step-by-step instructions.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"updated_at": "2026-05-05T07:26:00Z",
|
||||
"updated_at": "2026-04-16T18:00:00Z",
|
||||
"catalog_url": "https://raw.githubusercontent.com/github/spec-kit/main/extensions/catalog.community.json",
|
||||
"extensions": {
|
||||
"aide": {
|
||||
@@ -100,39 +100,6 @@
|
||||
"created_at": "2026-04-14T00:00:00Z",
|
||||
"updated_at": "2026-04-14T00:00:00Z"
|
||||
},
|
||||
"architecture-guard": {
|
||||
"name": "Architecture Guard",
|
||||
"id": "architecture-guard",
|
||||
"description": "Continuous architecture governance for AI-assisted development. Reviews specs, plans, and code for architecture drift, producing structured refactor tasks and evolution proposals.",
|
||||
"author": "DyanGalih",
|
||||
"version": "1.4.0",
|
||||
"download_url": "https://github.com/DyanGalih/spec-kit-architecture-guard/archive/refs/tags/v1.4.0.zip",
|
||||
"repository": "https://github.com/DyanGalih/spec-kit-architecture-guard",
|
||||
"homepage": "https://github.com/DyanGalih/spec-kit-architecture-guard",
|
||||
"documentation": "https://github.com/DyanGalih/spec-kit-architecture-guard/blob/main/README.md",
|
||||
"changelog": "https://github.com/DyanGalih/spec-kit-architecture-guard/releases",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.1.0"
|
||||
},
|
||||
"provides": {
|
||||
"commands": 6,
|
||||
"hooks": 0
|
||||
},
|
||||
"tags": [
|
||||
"architecture",
|
||||
"governance",
|
||||
"drift-detection",
|
||||
"refactor",
|
||||
"monolithic",
|
||||
"microservices"
|
||||
],
|
||||
"verified": false,
|
||||
"downloads": 0,
|
||||
"stars": 0,
|
||||
"created_at": "2026-05-05T07:26:00Z",
|
||||
"updated_at": "2026-05-05T07:26:00Z"
|
||||
},
|
||||
"archive": {
|
||||
"name": "Archive Extension",
|
||||
"id": "archive",
|
||||
@@ -203,38 +170,6 @@
|
||||
"created_at": "2026-03-03T00:00:00Z",
|
||||
"updated_at": "2026-03-03T00:00:00Z"
|
||||
},
|
||||
"blueprint": {
|
||||
"name": "Blueprint",
|
||||
"id": "blueprint",
|
||||
"description": "Stay code-literate in AI-driven development: review a complete code blueprint for every task from spec artifacts before /speckit.implement runs",
|
||||
"author": "chordpli",
|
||||
"version": "1.0.0",
|
||||
"download_url": "https://github.com/chordpli/spec-kit-blueprint/archive/refs/tags/v1.0.0.zip",
|
||||
"repository": "https://github.com/chordpli/spec-kit-blueprint",
|
||||
"homepage": "https://github.com/chordpli/spec-kit-blueprint",
|
||||
"documentation": "https://github.com/chordpli/spec-kit-blueprint/blob/main/README.md",
|
||||
"changelog": "https://github.com/chordpli/spec-kit-blueprint/blob/main/CHANGELOG.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.2.0"
|
||||
},
|
||||
"provides": {
|
||||
"commands": 2,
|
||||
"hooks": 1
|
||||
},
|
||||
"tags": [
|
||||
"blueprint",
|
||||
"pre-implementation",
|
||||
"review",
|
||||
"scaffolding",
|
||||
"code-literacy"
|
||||
],
|
||||
"verified": false,
|
||||
"downloads": 0,
|
||||
"stars": 0,
|
||||
"created_at": "2026-04-17T00:00:00Z",
|
||||
"updated_at": "2026-04-17T00:00:00Z"
|
||||
},
|
||||
"branch-convention": {
|
||||
"name": "Branch Convention",
|
||||
"id": "branch-convention",
|
||||
@@ -690,18 +625,18 @@
|
||||
"id": "extensify",
|
||||
"description": "Create and validate extensions and extension catalogs.",
|
||||
"author": "mnriem",
|
||||
"version": "1.1.0",
|
||||
"download_url": "https://github.com/mnriem/spec-kit-extensions/releases/download/extensify-v1.1.0/extensify.zip",
|
||||
"version": "1.0.0",
|
||||
"download_url": "https://github.com/mnriem/spec-kit-extensions/releases/download/extensify-v1.0.0/extensify.zip",
|
||||
"repository": "https://github.com/mnriem/spec-kit-extensions",
|
||||
"homepage": "https://github.com/mnriem/spec-kit-extensions",
|
||||
"documentation": "https://github.com/mnriem/spec-kit-extensions/blob/main/extensify/README.md",
|
||||
"changelog": "https://github.com/mnriem/spec-kit-extensions/blob/main/extensify/CHANGELOG.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.8.0"
|
||||
"speckit_version": ">=0.2.0"
|
||||
},
|
||||
"provides": {
|
||||
"commands": 5,
|
||||
"commands": 4,
|
||||
"hooks": 0
|
||||
},
|
||||
"tags": [
|
||||
@@ -714,7 +649,7 @@
|
||||
"downloads": 0,
|
||||
"stars": 0,
|
||||
"created_at": "2026-03-18T00:00:00Z",
|
||||
"updated_at": "2026-04-23T00:00:00Z"
|
||||
"updated_at": "2026-03-18T00:00:00Z"
|
||||
},
|
||||
"fix-findings": {
|
||||
"name": "Fix Findings",
|
||||
@@ -974,44 +909,6 @@
|
||||
"created_at": "2026-03-17T00:00:00Z",
|
||||
"updated_at": "2026-03-17T00:00:00Z"
|
||||
},
|
||||
"m365": {
|
||||
"name": "Microsoft 365 Integration",
|
||||
"id": "m365",
|
||||
"description": "Fetch Teams messages, meeting transcripts, and SharePoint/OneDrive files as local Markdown for spec generation.",
|
||||
"author": "BenBtg",
|
||||
"version": "1.0.0",
|
||||
"download_url": "https://github.com/BenBtg/spec-kit-m365/archive/refs/tags/v1.0.0.zip",
|
||||
"repository": "https://github.com/BenBtg/spec-kit-m365",
|
||||
"homepage": "https://github.com/BenBtg/spec-kit-m365",
|
||||
"documentation": "https://github.com/BenBtg/spec-kit-m365/blob/main/README.md",
|
||||
"changelog": "https://github.com/BenBtg/spec-kit-m365/blob/main/CHANGELOG.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.1.0",
|
||||
"tools": [
|
||||
{
|
||||
"name": "m365",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"provides": {
|
||||
"commands": 3,
|
||||
"hooks": 0
|
||||
},
|
||||
"tags": [
|
||||
"microsoft-365",
|
||||
"teams",
|
||||
"transcripts",
|
||||
"collaboration",
|
||||
"summarization"
|
||||
],
|
||||
"verified": false,
|
||||
"downloads": 0,
|
||||
"stars": 0,
|
||||
"created_at": "2026-04-28T00:00:00Z",
|
||||
"updated_at": "2026-04-28T00:00:00Z"
|
||||
},
|
||||
"maqa": {
|
||||
"name": "MAQA — Multi-Agent & Quality Assurance",
|
||||
"id": "maqa",
|
||||
@@ -1238,109 +1135,6 @@
|
||||
"created_at": "2026-03-26T00:00:00Z",
|
||||
"updated_at": "2026-03-26T00:00:00Z"
|
||||
},
|
||||
"markitdown": {
|
||||
"name": "MarkItDown Document Converter",
|
||||
"id": "markitdown",
|
||||
"description": "Convert documents (PDF, Word, PowerPoint, Excel, and more) to Markdown for use as spec reference material in Spec Kit workflows.",
|
||||
"author": "BenBtg",
|
||||
"version": "1.0.0",
|
||||
"download_url": "https://github.com/BenBtg/spec-kit-markitdown/archive/refs/tags/v1.0.0.zip",
|
||||
"repository": "https://github.com/BenBtg/spec-kit-markitdown",
|
||||
"homepage": "https://github.com/BenBtg/spec-kit-markitdown",
|
||||
"documentation": "https://github.com/BenBtg/spec-kit-markitdown/blob/main/README.md",
|
||||
"changelog": "https://github.com/BenBtg/spec-kit-markitdown/blob/main/CHANGELOG.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.1.0",
|
||||
"tools": [
|
||||
{
|
||||
"name": "markitdown",
|
||||
"version": ">=0.1.0",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"provides": {
|
||||
"commands": 1,
|
||||
"hooks": 0
|
||||
},
|
||||
"tags": [
|
||||
"markdown",
|
||||
"pdf",
|
||||
"document-conversion",
|
||||
"reference-material",
|
||||
"extraction"
|
||||
],
|
||||
"verified": false,
|
||||
"downloads": 0,
|
||||
"stars": 0,
|
||||
"created_at": "2026-04-28T00:00:00Z",
|
||||
"updated_at": "2026-04-28T00:00:00Z"
|
||||
},
|
||||
"memory-loader": {
|
||||
"name": "Memory Loader",
|
||||
"id": "memory-loader",
|
||||
"description": "Loads .specify/memory/ files before spec-kit lifecycle commands so LLM agents have project governance context",
|
||||
"author": "KevinBrown5280",
|
||||
"version": "1.0.0",
|
||||
"download_url": "https://github.com/KevinBrown5280/spec-kit-memory-loader/archive/refs/tags/v1.0.0.zip",
|
||||
"repository": "https://github.com/KevinBrown5280/spec-kit-memory-loader",
|
||||
"homepage": "https://github.com/KevinBrown5280/spec-kit-memory-loader",
|
||||
"documentation": "https://github.com/KevinBrown5280/spec-kit-memory-loader/blob/main/README.md",
|
||||
"changelog": "https://github.com/KevinBrown5280/spec-kit-memory-loader/blob/main/CHANGELOG.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.6.0"
|
||||
},
|
||||
"provides": {
|
||||
"commands": 1,
|
||||
"hooks": 7
|
||||
},
|
||||
"tags": [
|
||||
"context",
|
||||
"memory",
|
||||
"governance",
|
||||
"hooks"
|
||||
],
|
||||
"verified": false,
|
||||
"downloads": 0,
|
||||
"stars": 0,
|
||||
"created_at": "2026-04-20T00:00:00Z",
|
||||
"updated_at": "2026-04-20T00:00:00Z"
|
||||
},
|
||||
"memory-md": {
|
||||
"name": "Memory MD",
|
||||
"id": "memory-md",
|
||||
"description": "Spec Kit extension for repository-native Markdown memory that captures durable decisions, bugs, and project context",
|
||||
"author": "DyanGalih",
|
||||
"version": "0.7.5",
|
||||
"download_url": "https://github.com/DyanGalih/spec-kit-memory-hub/archive/refs/tags/v0.7.5.zip",
|
||||
"repository": "https://github.com/DyanGalih/spec-kit-memory-hub",
|
||||
"homepage": "https://github.com/DyanGalih/spec-kit-memory-hub",
|
||||
"documentation": "https://github.com/DyanGalih/spec-kit-memory-hub/blob/main/README.md",
|
||||
"changelog": "https://github.com/DyanGalih/spec-kit-memory-hub/blob/main/CHANGELOG.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.2.0"
|
||||
},
|
||||
"provides": {
|
||||
"commands": 6,
|
||||
"hooks": 0
|
||||
},
|
||||
"tags": [
|
||||
"memory",
|
||||
"workflow",
|
||||
"docs",
|
||||
"copilot",
|
||||
"markdown",
|
||||
"ai-context"
|
||||
],
|
||||
"verified": false,
|
||||
"downloads": 0,
|
||||
"stars": 0,
|
||||
"created_at": "2026-04-23T00:00:00Z",
|
||||
"updated_at": "2026-05-03T00:00:00Z"
|
||||
},
|
||||
"memorylint": {
|
||||
"name": "MemoryLint",
|
||||
"id": "memorylint",
|
||||
@@ -1373,56 +1167,6 @@
|
||||
"created_at": "2026-04-09T00:00:00Z",
|
||||
"updated_at": "2026-04-16T13:10:26Z"
|
||||
},
|
||||
"multi-model-review": {
|
||||
"name": "Multi-Model Review",
|
||||
"id": "multi-model-review",
|
||||
"description": "Cross-model Spec Kit handoffs for spec authoring, implementation routing, and review.",
|
||||
"author": "formin",
|
||||
"version": "0.1.0",
|
||||
"download_url": "https://github.com/formin/multi-model-review/archive/refs/tags/v0.1.0.zip",
|
||||
"repository": "https://github.com/formin/multi-model-review",
|
||||
"homepage": "https://github.com/formin/multi-model-review",
|
||||
"documentation": "https://github.com/formin/multi-model-review/blob/main/README.md",
|
||||
"changelog": "https://github.com/formin/multi-model-review/blob/main/CHANGELOG.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.2.0",
|
||||
"tools": [
|
||||
{
|
||||
"name": "git",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "codex",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"name": "gemini",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"name": "claude",
|
||||
"required": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"provides": {
|
||||
"commands": 4,
|
||||
"hooks": 0
|
||||
},
|
||||
"tags": [
|
||||
"review",
|
||||
"workflow",
|
||||
"multi-model",
|
||||
"spec-driven-development",
|
||||
"code"
|
||||
],
|
||||
"verified": false,
|
||||
"downloads": 0,
|
||||
"stars": 0,
|
||||
"created_at": "2026-05-04T02:51:52Z",
|
||||
"updated_at": "2026-05-04T02:51:52Z"
|
||||
},
|
||||
"onboard": {
|
||||
"name": "Onboard",
|
||||
"id": "onboard",
|
||||
@@ -1488,38 +1232,6 @@
|
||||
"created_at": "2026-04-03T00:00:00Z",
|
||||
"updated_at": "2026-04-03T00:00:00Z"
|
||||
},
|
||||
"orchestrator": {
|
||||
"name": "Spec Orchestrator",
|
||||
"id": "orchestrator",
|
||||
"description": "Cross-feature orchestration — track state, select tasks, and detect conflicts across parallel specs.",
|
||||
"author": "Quratulain-bilal",
|
||||
"version": "1.0.0",
|
||||
"download_url": "https://github.com/Quratulain-bilal/spec-kit-orchestrator/archive/refs/tags/v1.0.0.zip",
|
||||
"repository": "https://github.com/Quratulain-bilal/spec-kit-orchestrator",
|
||||
"homepage": "https://github.com/Quratulain-bilal/spec-kit-orchestrator",
|
||||
"documentation": "https://github.com/Quratulain-bilal/spec-kit-orchestrator/blob/main/README.md",
|
||||
"changelog": "https://github.com/Quratulain-bilal/spec-kit-orchestrator/releases",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.4.0"
|
||||
},
|
||||
"provides": {
|
||||
"commands": 4,
|
||||
"hooks": 0
|
||||
},
|
||||
"tags": [
|
||||
"orchestration",
|
||||
"multi-feature",
|
||||
"coordination",
|
||||
"workflow",
|
||||
"parallel"
|
||||
],
|
||||
"verified": false,
|
||||
"downloads": 0,
|
||||
"stars": 0,
|
||||
"created_at": "2026-04-24T14:00:00Z",
|
||||
"updated_at": "2026-04-24T14:00:00Z"
|
||||
},
|
||||
"plan-review-gate": {
|
||||
"name": "Plan Review Gate",
|
||||
"id": "plan-review-gate",
|
||||
@@ -1617,10 +1329,10 @@
|
||||
"product-forge": {
|
||||
"name": "Product Forge",
|
||||
"id": "product-forge",
|
||||
"description": "Full product lifecycle from research to release — portfolio, lite mode, monorepo, optional V-Model",
|
||||
"description": "Full product lifecycle: research → product spec → SpecKit → implement → verify → test",
|
||||
"author": "VaiYav",
|
||||
"version": "1.5.1",
|
||||
"download_url": "https://github.com/VaiYav/speckit-product-forge/archive/refs/tags/v1.5.1.zip",
|
||||
"version": "1.1.1",
|
||||
"download_url": "https://github.com/VaiYav/speckit-product-forge/archive/refs/tags/v1.1.1.zip",
|
||||
"repository": "https://github.com/VaiYav/speckit-product-forge",
|
||||
"homepage": "https://github.com/VaiYav/speckit-product-forge",
|
||||
"documentation": "https://github.com/VaiYav/speckit-product-forge/blob/main/README.md",
|
||||
@@ -1630,21 +1342,21 @@
|
||||
"speckit_version": ">=0.1.0"
|
||||
},
|
||||
"provides": {
|
||||
"commands": 29,
|
||||
"commands": 10,
|
||||
"hooks": 0
|
||||
},
|
||||
"tags": [
|
||||
"process",
|
||||
"research",
|
||||
"product-spec",
|
||||
"lifecycle",
|
||||
"monorepo",
|
||||
"v-model",
|
||||
"portfolio"
|
||||
"testing"
|
||||
],
|
||||
"verified": false,
|
||||
"downloads": 0,
|
||||
"stars": 0,
|
||||
"created_at": "2026-03-28T00:00:00Z",
|
||||
"updated_at": "2026-04-24T15:52:00Z"
|
||||
"updated_at": "2026-03-28T00:00:00Z"
|
||||
},
|
||||
"qa": {
|
||||
"name": "QA Testing Extension",
|
||||
@@ -1681,12 +1393,12 @@
|
||||
"id": "ralph",
|
||||
"description": "Autonomous implementation loop using AI agent CLI.",
|
||||
"author": "Rubiss",
|
||||
"version": "1.0.2",
|
||||
"download_url": "https://github.com/Rubiss-Projects/spec-kit-ralph/archive/refs/tags/v1.0.2.zip",
|
||||
"repository": "https://github.com/Rubiss-Projects/spec-kit-ralph",
|
||||
"homepage": "https://github.com/Rubiss-Projects/spec-kit-ralph",
|
||||
"documentation": "https://github.com/Rubiss-Projects/spec-kit-ralph/blob/main/README.md",
|
||||
"changelog": "https://github.com/Rubiss-Projects/spec-kit-ralph/blob/main/CHANGELOG.md",
|
||||
"version": "1.0.1",
|
||||
"download_url": "https://github.com/Rubiss/spec-kit-ralph/archive/refs/tags/v1.0.1.zip",
|
||||
"repository": "https://github.com/Rubiss/spec-kit-ralph",
|
||||
"homepage": "https://github.com/Rubiss/spec-kit-ralph",
|
||||
"documentation": "https://github.com/Rubiss/spec-kit-ralph/blob/main/README.md",
|
||||
"changelog": "https://github.com/Rubiss/spec-kit-ralph/blob/main/CHANGELOG.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.1.0",
|
||||
@@ -1715,7 +1427,7 @@
|
||||
"downloads": 0,
|
||||
"stars": 0,
|
||||
"created_at": "2026-03-09T00:00:00Z",
|
||||
"updated_at": "2026-05-04T17:02:08Z"
|
||||
"updated_at": "2026-04-12T19:00:00Z"
|
||||
},
|
||||
"reconcile": {
|
||||
"name": "Reconcile Extension",
|
||||
@@ -1748,38 +1460,6 @@
|
||||
"created_at": "2026-03-14T00:00:00Z",
|
||||
"updated_at": "2026-03-14T00:00:00Z"
|
||||
},
|
||||
"red-team": {
|
||||
"name": "Red Team",
|
||||
"id": "red-team",
|
||||
"description": "Adversarial review of functional specs before /speckit.plan. Parallel adversarial lens agents catch hostile actors, silent failures, and regulatory blind spots that clarify/analyze cannot.",
|
||||
"author": "Ash Brener",
|
||||
"version": "1.0.2",
|
||||
"download_url": "https://github.com/ashbrener/spec-kit-red-team/releases/download/v1.0.2/red-team-v1.0.2.zip",
|
||||
"repository": "https://github.com/ashbrener/spec-kit-red-team",
|
||||
"homepage": "https://github.com/ashbrener/spec-kit-red-team",
|
||||
"documentation": "https://github.com/ashbrener/spec-kit-red-team/blob/main/README.md",
|
||||
"changelog": "https://github.com/ashbrener/spec-kit-red-team/blob/main/CHANGELOG.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.1.0"
|
||||
},
|
||||
"provides": {
|
||||
"commands": 2,
|
||||
"hooks": 1
|
||||
},
|
||||
"tags": [
|
||||
"adversarial-review",
|
||||
"quality-gate",
|
||||
"spec-hardening",
|
||||
"pre-plan",
|
||||
"audit"
|
||||
],
|
||||
"verified": false,
|
||||
"downloads": 0,
|
||||
"stars": 0,
|
||||
"created_at": "2026-04-22T00:00:00Z",
|
||||
"updated_at": "2026-04-22T00:00:00Z"
|
||||
},
|
||||
"refine": {
|
||||
"name": "Spec Refine",
|
||||
"id": "refine",
|
||||
@@ -1945,78 +1625,13 @@
|
||||
"created_at": "2026-03-06T00:00:00Z",
|
||||
"updated_at": "2026-04-09T00:00:00Z"
|
||||
},
|
||||
"ripple": {
|
||||
"name": "Ripple",
|
||||
"id": "ripple",
|
||||
"description": "Detect side effects that tests can't catch after implementation — delta-anchored analysis across 9 domain-agnostic categories with fix-induced side effect detection",
|
||||
"author": "chordpli",
|
||||
"version": "1.0.0",
|
||||
"download_url": "https://github.com/chordpli/spec-kit-ripple/archive/refs/tags/v1.0.0.zip",
|
||||
"repository": "https://github.com/chordpli/spec-kit-ripple",
|
||||
"homepage": "https://github.com/chordpli/spec-kit-ripple",
|
||||
"documentation": "https://github.com/chordpli/spec-kit-ripple/blob/main/README.md",
|
||||
"changelog": "https://github.com/chordpli/spec-kit-ripple/blob/main/CHANGELOG.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.2.0"
|
||||
},
|
||||
"provides": {
|
||||
"commands": 3,
|
||||
"hooks": 1
|
||||
},
|
||||
"tags": [
|
||||
"side-effects",
|
||||
"post-implementation",
|
||||
"analysis",
|
||||
"quality",
|
||||
"risk-detection"
|
||||
],
|
||||
"verified": false,
|
||||
"downloads": 0,
|
||||
"stars": 0,
|
||||
"created_at": "2026-04-20T00:00:00Z",
|
||||
"updated_at": "2026-04-20T00:00:00Z"
|
||||
},
|
||||
"scope": {
|
||||
"name": "Spec Scope",
|
||||
"id": "scope",
|
||||
"description": "Effort estimation and scope tracking — estimate work, detect creep, and budget time per phase.",
|
||||
"author": "Quratulain-bilal",
|
||||
"version": "1.0.0",
|
||||
"download_url": "https://github.com/Quratulain-bilal/spec-kit-scope-/archive/refs/tags/v1.0.0.zip",
|
||||
"repository": "https://github.com/Quratulain-bilal/spec-kit-scope-",
|
||||
"homepage": "https://github.com/Quratulain-bilal/spec-kit-scope-",
|
||||
"documentation": "https://github.com/Quratulain-bilal/spec-kit-scope-/blob/main/README.md",
|
||||
"changelog": "https://github.com/Quratulain-bilal/spec-kit-scope-/blob/main/CHANGELOG.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.4.0"
|
||||
},
|
||||
"provides": {
|
||||
"commands": 4,
|
||||
"hooks": 1
|
||||
},
|
||||
"tags": [
|
||||
"estimation",
|
||||
"scope",
|
||||
"effort",
|
||||
"planning",
|
||||
"project-management",
|
||||
"tracking"
|
||||
],
|
||||
"verified": false,
|
||||
"downloads": 0,
|
||||
"stars": 0,
|
||||
"created_at": "2026-04-17T02:00:00Z",
|
||||
"updated_at": "2026-04-17T02:00:00Z"
|
||||
},
|
||||
"security-review": {
|
||||
"name": "Security Review",
|
||||
"id": "security-review",
|
||||
"description": "Full-project secure-by-design security audits plus staged, branch/PR, plan, task, follow-up, and apply reviews",
|
||||
"description": "Comprehensive security audit of codebases using AI-powered DevSecOps analysis",
|
||||
"author": "DyanGalih",
|
||||
"version": "1.4.2",
|
||||
"download_url": "https://github.com/DyanGalih/spec-kit-security-review/archive/refs/tags/v1.4.2.zip",
|
||||
"version": "1.1.1",
|
||||
"download_url": "https://github.com/DyanGalih/spec-kit-security-review/archive/refs/tags/v1.1.1.zip",
|
||||
"repository": "https://github.com/DyanGalih/spec-kit-security-review",
|
||||
"homepage": "https://github.com/DyanGalih/spec-kit-security-review",
|
||||
"documentation": "https://github.com/DyanGalih/spec-kit-security-review/blob/main/README.md",
|
||||
@@ -2026,7 +1641,7 @@
|
||||
"speckit_version": ">=0.1.0"
|
||||
},
|
||||
"provides": {
|
||||
"commands": 7,
|
||||
"commands": 3,
|
||||
"hooks": 0
|
||||
},
|
||||
"tags": [
|
||||
@@ -2040,7 +1655,7 @@
|
||||
"downloads": 0,
|
||||
"stars": 0,
|
||||
"created_at": "2026-04-03T03:24:03Z",
|
||||
"updated_at": "2026-05-03T00:00:00Z"
|
||||
"updated_at": "2026-04-03T04:15:00Z"
|
||||
},
|
||||
"sf": {
|
||||
"name": "SFSpeckit — Salesforce Spec-Driven Development",
|
||||
@@ -2116,101 +1731,6 @@
|
||||
"created_at": "2026-04-01T00:00:00Z",
|
||||
"updated_at": "2026-04-01T00:00:00Z"
|
||||
},
|
||||
"spec-reference-loader": {
|
||||
"name": "Spec Reference Loader",
|
||||
"id": "spec-reference-loader",
|
||||
"description": "Reads the ## References section from the current feature spec and loads the listed files into context",
|
||||
"author": "KevinBrown5280",
|
||||
"version": "1.0.0",
|
||||
"download_url": "https://github.com/KevinBrown5280/spec-kit-spec-reference-loader/archive/refs/tags/v1.0.0.zip",
|
||||
"repository": "https://github.com/KevinBrown5280/spec-kit-spec-reference-loader",
|
||||
"homepage": "https://github.com/KevinBrown5280/spec-kit-spec-reference-loader",
|
||||
"documentation": "https://github.com/KevinBrown5280/spec-kit-spec-reference-loader/blob/main/README.md",
|
||||
"changelog": "https://github.com/KevinBrown5280/spec-kit-spec-reference-loader/blob/main/CHANGELOG.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.6.0"
|
||||
},
|
||||
"provides": {
|
||||
"commands": 1,
|
||||
"hooks": 6
|
||||
},
|
||||
"tags": [
|
||||
"context",
|
||||
"references",
|
||||
"docs",
|
||||
"hooks"
|
||||
],
|
||||
"verified": false,
|
||||
"downloads": 0,
|
||||
"stars": 0,
|
||||
"created_at": "2026-04-20T00:00:00Z",
|
||||
"updated_at": "2026-04-20T00:00:00Z"
|
||||
},
|
||||
"spec-validate": {
|
||||
"name": "Spec Validate",
|
||||
"id": "spec-validate",
|
||||
"description": "Comprehension validation, review gating, and approval state for spec-kit artifacts — staged-reveal quizzes, peer review SLA, and a hard gate before /speckit.implement.",
|
||||
"author": "Ahmed Eltayeb",
|
||||
"version": "1.0.1",
|
||||
"download_url": "https://github.com/aeltayeb/spec-kit-spec-validate/archive/refs/tags/v1.0.1.zip",
|
||||
"repository": "https://github.com/aeltayeb/spec-kit-spec-validate",
|
||||
"homepage": "https://github.com/aeltayeb/spec-kit-spec-validate",
|
||||
"documentation": "https://github.com/aeltayeb/spec-kit-spec-validate/blob/main/README.md",
|
||||
"changelog": "https://github.com/aeltayeb/spec-kit-spec-validate/blob/main/CHANGELOG.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.5.0"
|
||||
},
|
||||
"provides": {
|
||||
"commands": 6,
|
||||
"hooks": 3
|
||||
},
|
||||
"tags": [
|
||||
"validation",
|
||||
"review",
|
||||
"quality",
|
||||
"workflow",
|
||||
"process"
|
||||
],
|
||||
"verified": false,
|
||||
"downloads": 0,
|
||||
"stars": 0,
|
||||
"created_at": "2026-04-20T00:00:00Z",
|
||||
"updated_at": "2026-04-21T00:00:00Z"
|
||||
},
|
||||
"spec2cloud": {
|
||||
"name": "Spec2Cloud",
|
||||
"id": "spec2cloud",
|
||||
"description": "Spec-driven workflow tuned for shipping to Azure: spec → plan → tasks → implement → deploy.",
|
||||
"author": "Azure Samples",
|
||||
"version": "1.1.0",
|
||||
"download_url": "https://github.com/Azure-Samples/Spec2Cloud/releases/download/spec-kit-spec2cloud-v1.1.0/extension.zip",
|
||||
"repository": "https://github.com/Azure-Samples/Spec2Cloud",
|
||||
"homepage": "https://aka.ms/spec2cloud",
|
||||
"documentation": "https://github.com/Azure-Samples/Spec2Cloud/blob/main/spec-kit/README.md",
|
||||
"changelog": "https://github.com/Azure-Samples/Spec2Cloud/blob/main/spec-kit/CHANGELOG.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.4.0"
|
||||
},
|
||||
"provides": {
|
||||
"commands": 2,
|
||||
"hooks": 0
|
||||
},
|
||||
"tags": [
|
||||
"spec2cloud",
|
||||
"azure",
|
||||
"cloud",
|
||||
"deploy",
|
||||
"workflow"
|
||||
],
|
||||
"verified": false,
|
||||
"downloads": 0,
|
||||
"stars": 0,
|
||||
"created_at": "2026-04-30T00:00:00Z",
|
||||
"updated_at": "2026-04-30T00:00:00Z"
|
||||
},
|
||||
"speckit-utils": {
|
||||
"name": "SDD Utilities",
|
||||
"id": "speckit-utils",
|
||||
@@ -2276,45 +1796,6 @@
|
||||
"created_at": "2026-04-10T16:00:00Z",
|
||||
"updated_at": "2026-04-10T16:00:00Z"
|
||||
},
|
||||
"squad": {
|
||||
"name": "Squad Bridge",
|
||||
"id": "squad",
|
||||
"description": "Bootstrap and synchronize a Squad agent team from your Spec Kit spec and tasks.",
|
||||
"author": "jwill824",
|
||||
"version": "1.1.0",
|
||||
"download_url": "https://github.com/jwill824/spec-kit-squad/archive/refs/tags/v1.1.0.zip",
|
||||
"repository": "https://github.com/jwill824/spec-kit-squad",
|
||||
"homepage": "https://github.com/jwill824/spec-kit-squad",
|
||||
"documentation": "https://github.com/jwill824/spec-kit-squad/blob/main/README.md",
|
||||
"changelog": "https://github.com/jwill824/spec-kit-squad/blob/main/docs/CHANGELOG.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.1.0",
|
||||
"tools": [
|
||||
{
|
||||
"name": "@bradygaster/squad-cli",
|
||||
"version": ">=0.1.0",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"provides": {
|
||||
"commands": 4,
|
||||
"hooks": 2
|
||||
},
|
||||
"tags": [
|
||||
"multi-agent",
|
||||
"agents",
|
||||
"orchestration",
|
||||
"process",
|
||||
"integration"
|
||||
],
|
||||
"verified": false,
|
||||
"downloads": 0,
|
||||
"stars": 0,
|
||||
"created_at": "2026-04-29T00:00:00Z",
|
||||
"updated_at": "2026-04-29T00:00:00Z"
|
||||
},
|
||||
"staff-review": {
|
||||
"name": "Staff Review Extension",
|
||||
"id": "staff-review",
|
||||
@@ -2450,39 +1931,6 @@
|
||||
"created_at": "2026-03-30T00:00:00Z",
|
||||
"updated_at": "2026-04-16T14:08:23Z"
|
||||
},
|
||||
"superpowers-bridge": {
|
||||
"name": "Superpowers Bridge",
|
||||
"id": "superpowers-bridge",
|
||||
"description": "Bridges spec-kit workflows with obra/superpowers capabilities for brainstorming, TDD, code review, and resumable execution.",
|
||||
"author": "WangX0111",
|
||||
"version": "1.0.0",
|
||||
"download_url": "https://github.com/WangX0111/superspec/archive/refs/tags/v1.0.0.zip",
|
||||
"repository": "https://github.com/WangX0111/superspec",
|
||||
"homepage": "https://github.com/WangX0111/superspec",
|
||||
"documentation": "https://github.com/WangX0111/superspec/blob/main/README.md",
|
||||
"changelog": "https://github.com/WangX0111/superspec/blob/main/CHANGELOG.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.1.0"
|
||||
},
|
||||
"provides": {
|
||||
"commands": 5,
|
||||
"hooks": 3
|
||||
},
|
||||
"tags": [
|
||||
"superpowers",
|
||||
"brainstorming",
|
||||
"tdd",
|
||||
"code-review",
|
||||
"subagent",
|
||||
"workflow"
|
||||
],
|
||||
"verified": false,
|
||||
"downloads": 0,
|
||||
"stars": 0,
|
||||
"created_at": "2026-04-22T00:00:00Z",
|
||||
"updated_at": "2026-04-22T00:00:00Z"
|
||||
},
|
||||
"sync": {
|
||||
"name": "Spec Sync",
|
||||
"id": "sync",
|
||||
@@ -2547,76 +1995,13 @@
|
||||
"created_at": "2026-04-10T00:00:00Z",
|
||||
"updated_at": "2026-04-10T00:00:00Z"
|
||||
},
|
||||
"threatmodel": {
|
||||
"name": "OWASP LLM Threat Model",
|
||||
"id": "threatmodel",
|
||||
"description": "OWASP Top 10 for LLM Applications 2025 threat analysis on agent artifacts",
|
||||
"author": "NaviaSamal",
|
||||
"version": "1.0.0",
|
||||
"download_url": "https://github.com/NaviaSamal/spec-kit-threatmodel/archive/refs/tags/v1.0.0.zip",
|
||||
"repository": "https://github.com/NaviaSamal/spec-kit-threatmodel",
|
||||
"homepage": "https://github.com/NaviaSamal/spec-kit-threatmodel",
|
||||
"documentation": "https://github.com/NaviaSamal/spec-kit-threatmodel/blob/main/README.md",
|
||||
"changelog": "https://github.com/NaviaSamal/spec-kit-threatmodel/blob/main/CHANGELOG.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.6.0"
|
||||
},
|
||||
"provides": {
|
||||
"commands": 1,
|
||||
"hooks": 1
|
||||
},
|
||||
"tags": [
|
||||
"security",
|
||||
"owasp",
|
||||
"threat-model",
|
||||
"llm",
|
||||
"analysis"
|
||||
],
|
||||
"verified": false,
|
||||
"downloads": 0,
|
||||
"stars": 0,
|
||||
"created_at": "2026-04-25T00:00:00Z",
|
||||
"updated_at": "2026-04-25T00:00:00Z"
|
||||
},
|
||||
"token-analyzer": {
|
||||
"name": "Token Consumption Analyzer",
|
||||
"id": "token-analyzer",
|
||||
"description": "Captures, analyzes, and compares token consumption across SDD workflows",
|
||||
"author": "Chris Roberts | coderandhiker",
|
||||
"version": "0.1.0",
|
||||
"download_url": "https://github.com/coderandhiker/spec-kit-token-analyzer/archive/refs/tags/v0.1.0.zip",
|
||||
"repository": "https://github.com/coderandhiker/spec-kit-token-analyzer",
|
||||
"homepage": "https://github.com/coderandhiker/spec-kit-token-analyzer",
|
||||
"documentation": "https://github.com/coderandhiker/spec-kit-token-analyzer/blob/main/README.md",
|
||||
"changelog": "https://github.com/coderandhiker/spec-kit-token-analyzer/blob/main/CHANGELOG.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.2.0"
|
||||
},
|
||||
"provides": {
|
||||
"commands": 3,
|
||||
"hooks": 4
|
||||
},
|
||||
"tags": [
|
||||
"tokens",
|
||||
"measurement",
|
||||
"optimization",
|
||||
"analysis"
|
||||
],
|
||||
"verified": false,
|
||||
"downloads": 0,
|
||||
"stars": 0,
|
||||
"created_at": "2026-05-01T00:00:00Z",
|
||||
"updated_at": "2026-05-01T00:00:00Z"
|
||||
},
|
||||
"v-model": {
|
||||
"name": "V-Model Extension Pack",
|
||||
"id": "v-model",
|
||||
"description": "Enforces V-Model paired generation of development specs and test specs with full traceability.",
|
||||
"author": "leocamello",
|
||||
"version": "0.6.0",
|
||||
"download_url": "https://github.com/leocamello/spec-kit-v-model/archive/refs/tags/v0.6.0.zip",
|
||||
"version": "0.5.0",
|
||||
"download_url": "https://github.com/leocamello/spec-kit-v-model/archive/refs/tags/v0.5.0.zip",
|
||||
"repository": "https://github.com/leocamello/spec-kit-v-model",
|
||||
"homepage": "https://github.com/leocamello/spec-kit-v-model",
|
||||
"documentation": "https://github.com/leocamello/spec-kit-v-model/blob/main/README.md",
|
||||
@@ -2638,9 +2023,9 @@
|
||||
],
|
||||
"verified": false,
|
||||
"downloads": 0,
|
||||
"stars": 21,
|
||||
"stars": 0,
|
||||
"created_at": "2026-02-20T00:00:00Z",
|
||||
"updated_at": "2026-04-25T00:00:00Z"
|
||||
"updated_at": "2026-04-06T00:00:00Z"
|
||||
},
|
||||
"verify": {
|
||||
"name": "Verify Extension",
|
||||
@@ -2705,37 +2090,6 @@
|
||||
"created_at": "2026-03-16T00:00:00Z",
|
||||
"updated_at": "2026-03-16T00:00:00Z"
|
||||
},
|
||||
"version-guard": {
|
||||
"name": "Version Guard",
|
||||
"id": "version-guard",
|
||||
"description": "Verify tech stack versions against live registries before planning and implementation",
|
||||
"author": "KevinBrown5280",
|
||||
"version": "1.2.0",
|
||||
"download_url": "https://github.com/KevinBrown5280/spec-kit-version-guard/archive/refs/tags/v1.2.0.zip",
|
||||
"repository": "https://github.com/KevinBrown5280/spec-kit-version-guard",
|
||||
"homepage": "https://github.com/KevinBrown5280/spec-kit-version-guard",
|
||||
"documentation": "https://github.com/KevinBrown5280/spec-kit-version-guard/blob/main/README.md",
|
||||
"changelog": "https://github.com/KevinBrown5280/spec-kit-version-guard/blob/main/CHANGELOG.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.2.0"
|
||||
},
|
||||
"provides": {
|
||||
"commands": 3,
|
||||
"hooks": 4
|
||||
},
|
||||
"tags": [
|
||||
"versioning",
|
||||
"npm",
|
||||
"validation",
|
||||
"hooks"
|
||||
],
|
||||
"verified": false,
|
||||
"downloads": 0,
|
||||
"stars": 0,
|
||||
"created_at": "2026-04-20T00:00:00Z",
|
||||
"updated_at": "2026-04-22T21:10:00Z"
|
||||
},
|
||||
"whatif": {
|
||||
"name": "What-if Analysis",
|
||||
"id": "whatif",
|
||||
@@ -2764,85 +2118,6 @@
|
||||
"created_at": "2026-04-13T00:00:00Z",
|
||||
"updated_at": "2026-04-13T00:00:00Z"
|
||||
},
|
||||
"wireframe": {
|
||||
"name": "Wireframe Visual Feedback Loop",
|
||||
"id": "wireframe",
|
||||
"description": "SVG wireframe generation, review, and sign-off for spec-driven development. Approved wireframes become spec constraints honored by /speckit.plan, /speckit.tasks, and /speckit.implement.",
|
||||
"author": "TortoiseWolfe",
|
||||
"version": "0.1.1",
|
||||
"download_url": "https://github.com/TortoiseWolfe/spec-kit-extension-wireframe/archive/refs/tags/v0.1.1.zip",
|
||||
"repository": "https://github.com/TortoiseWolfe/spec-kit-extension-wireframe",
|
||||
"homepage": "https://github.com/TortoiseWolfe/spec-kit-extension-wireframe",
|
||||
"documentation": "https://github.com/TortoiseWolfe/spec-kit-extension-wireframe/blob/main/README.md",
|
||||
"changelog": "https://github.com/TortoiseWolfe/spec-kit-extension-wireframe/blob/main/CHANGELOG.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.6.0"
|
||||
},
|
||||
"provides": {
|
||||
"commands": 6,
|
||||
"hooks": 3
|
||||
},
|
||||
"tags": [
|
||||
"wireframe",
|
||||
"visual",
|
||||
"design",
|
||||
"ui",
|
||||
"mockup",
|
||||
"svg",
|
||||
"feedback-loop",
|
||||
"sign-off"
|
||||
],
|
||||
"verified": false,
|
||||
"downloads": 0,
|
||||
"stars": 0,
|
||||
"created_at": "2026-04-22T00:00:00Z",
|
||||
"updated_at": "2026-04-22T00:00:00Z"
|
||||
},
|
||||
"workiq": {
|
||||
"name": "Work IQ",
|
||||
"id": "workiq",
|
||||
"description": "Integrate Microsoft 365 organizational knowledge into spec-driven development workflows",
|
||||
"author": "sakitA",
|
||||
"version": "1.0.0",
|
||||
"download_url": "https://github.com/sakitA/spec-kit-workiq/archive/refs/tags/v1.0.0.zip",
|
||||
"repository": "https://github.com/sakitA/spec-kit-workiq",
|
||||
"homepage": "https://github.com/sakitA/spec-kit-workiq",
|
||||
"documentation": "https://github.com/sakitA/spec-kit-workiq/blob/main/README.md",
|
||||
"changelog": "https://github.com/sakitA/spec-kit-workiq/blob/main/CHANGELOG.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.1.0",
|
||||
"tools": [
|
||||
{
|
||||
"name": "workiq",
|
||||
"version": ">=1.0.0",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "node",
|
||||
"version": ">=18.0.0",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"provides": {
|
||||
"commands": 4,
|
||||
"hooks": 2
|
||||
},
|
||||
"tags": [
|
||||
"microsoft-365",
|
||||
"work-iq",
|
||||
"context",
|
||||
"integration",
|
||||
"productivity"
|
||||
],
|
||||
"verified": false,
|
||||
"downloads": 0,
|
||||
"stars": 0,
|
||||
"created_at": "2026-04-29T00:00:00Z",
|
||||
"updated_at": "2026-04-29T00:00:00Z"
|
||||
},
|
||||
"worktree": {
|
||||
"name": "Worktree Isolation",
|
||||
"id": "worktree",
|
||||
@@ -2905,7 +2180,7 @@
|
||||
"downloads": 0,
|
||||
"stars": 0,
|
||||
"created_at": "2026-04-13T00:00:00Z",
|
||||
"updated_at": "2026-04-13T00:00:00Z"
|
||||
"updated_at": "2026-04-13T00:00:00Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ if [ -z "$FEATURE_DESCRIPTION" ]; then
|
||||
fi
|
||||
|
||||
# Trim whitespace and validate description is not empty
|
||||
FEATURE_DESCRIPTION=$(echo "$FEATURE_DESCRIPTION" | sed -E 's/^[[:space:]]+|[[:space:]]+$//g')
|
||||
FEATURE_DESCRIPTION=$(echo "$FEATURE_DESCRIPTION" | xargs)
|
||||
if [ -z "$FEATURE_DESCRIPTION" ]; then
|
||||
echo "Error: Feature description cannot be empty or contain only whitespace" >&2
|
||||
exit 1
|
||||
|
||||
@@ -36,17 +36,10 @@ if (-not (Get-Command git -ErrorAction SilentlyContinue)) {
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Temporarily relax ErrorActionPreference so git stderr warnings
|
||||
# (e.g. CRLF notices on Windows) do not become terminating errors.
|
||||
$savedEAP = $ErrorActionPreference
|
||||
$ErrorActionPreference = 'Continue'
|
||||
try {
|
||||
git rev-parse --is-inside-work-tree 2>$null | Out-Null
|
||||
$isRepo = $LASTEXITCODE -eq 0
|
||||
} finally {
|
||||
$ErrorActionPreference = $savedEAP
|
||||
}
|
||||
if (-not $isRepo) {
|
||||
if ($LASTEXITCODE -ne 0) { throw "not a repo" }
|
||||
} catch {
|
||||
Write-Warning "[specify] Warning: Not a Git repository; skipped auto-commit"
|
||||
exit 0
|
||||
}
|
||||
@@ -124,16 +117,9 @@ if (-not $enabled) {
|
||||
}
|
||||
|
||||
# Check if there are changes to commit
|
||||
# Relax ErrorActionPreference so CRLF warnings on stderr do not terminate.
|
||||
$savedEAP = $ErrorActionPreference
|
||||
$ErrorActionPreference = 'Continue'
|
||||
try {
|
||||
git diff --quiet HEAD 2>$null; $d1 = $LASTEXITCODE
|
||||
git diff --cached --quiet 2>$null; $d2 = $LASTEXITCODE
|
||||
$untracked = git ls-files --others --exclude-standard 2>$null
|
||||
} finally {
|
||||
$ErrorActionPreference = $savedEAP
|
||||
}
|
||||
$diffHead = git diff --quiet HEAD 2>$null; $d1 = $LASTEXITCODE
|
||||
$diffCached = git diff --cached --quiet 2>$null; $d2 = $LASTEXITCODE
|
||||
$untracked = git ls-files --others --exclude-standard 2>$null
|
||||
|
||||
if ($d1 -eq 0 -and $d2 -eq 0 -and -not $untracked) {
|
||||
Write-Host "[specify] No changes to commit after $EventName" -ForegroundColor DarkGray
|
||||
@@ -150,10 +136,6 @@ if (-not $commitMsg) {
|
||||
}
|
||||
|
||||
# Stage and commit
|
||||
# Relax ErrorActionPreference so CRLF warnings on stderr do not terminate,
|
||||
# while still allowing redirected error output to be captured for diagnostics.
|
||||
$savedEAP = $ErrorActionPreference
|
||||
$ErrorActionPreference = 'Continue'
|
||||
try {
|
||||
$out = git add . 2>&1 | Out-String
|
||||
if ($LASTEXITCODE -ne 0) { throw "git add failed: $out" }
|
||||
@@ -162,8 +144,6 @@ try {
|
||||
} catch {
|
||||
Write-Warning "[specify] Error: $_"
|
||||
exit 1
|
||||
} finally {
|
||||
$ErrorActionPreference = $savedEAP
|
||||
}
|
||||
|
||||
Write-Host "[OK] Changes committed $phase $commandName"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"updated_at": "2026-04-28T00:00:00Z",
|
||||
"updated_at": "2026-04-08T00:00:00Z",
|
||||
"catalog_url": "https://raw.githubusercontent.com/github/spec-kit/main/integrations/catalog.json",
|
||||
"integrations": {
|
||||
"claude": {
|
||||
@@ -66,15 +66,6 @@
|
||||
"repository": "https://github.com/github/spec-kit",
|
||||
"tags": ["cli", "skills"]
|
||||
},
|
||||
"devin": {
|
||||
"id": "devin",
|
||||
"name": "Devin for Terminal",
|
||||
"version": "1.0.0",
|
||||
"description": "Devin for Terminal CLI skills-based integration",
|
||||
"author": "spec-kit-core",
|
||||
"repository": "https://github.com/github/spec-kit",
|
||||
"tags": ["cli", "skills"]
|
||||
},
|
||||
"qwen": {
|
||||
"id": "qwen",
|
||||
"name": "Qwen Code",
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
# Spec Kit - April 2026 Newsletter
|
||||
|
||||
This edition covers Spec Kit activity in April 2026. Seventeen releases shipped (v0.4.4 through v0.8.3), delivering a full integration plugin architecture, a workflow engine, preset composition strategies, an integration catalog, and comprehensive documentation. The community extension catalog tripled from 26 to 83 entries, community presets grew from 2 to 12, and Spec Kit appeared on the Thoughtworks Technology Radar. A summary is in the table below, followed by details.
|
||||
|
||||
| **Spec Kit Core (Apr 2026)** | **Community & Content** | **SDD Ecosystem & Next** |
|
||||
| --- | --- | --- |
|
||||
| Seventeen releases shipped with major features: integration plugin architecture, workflow engine, preset composition, integration catalog, bundled lean preset, documentation site, and academic citation support. Three new agents added (Forgecode, Goose, Devin for Terminal). The repo grew from ~82k to **92,038 stars**. [\[github.com\]](https://github.com/github/spec-kit/releases) | Thoughtworks Technology Radar placed Spec Kit in the "Assess" ring. Community catalog grew from 26 to **83 extensions** and from 2 to **12 presets**. 12 substantive external articles published. XB Software documented a real legacy project. Fabián Silva shipped the Caramelo VS Code extension. | Matt Rickard argued for "smaller specs, harder checks." Will Torber's three-framework comparison recommended OpenSpec for most teams. The "Spec Layer" debate emerged: specs as constraint surfaces for AI agents. Spec Kit leads in breadth and portability; competitors differentiate on drift detection and orchestration depth. |
|
||||
|
||||
***
|
||||
|
||||
> **Important:** April's release pace outran external coverage. Most analyses published during the month (Rickard on April 1, Thoughtworks Radar on April 15, XB Software on April 17, Torber on April 23) were evaluating versions that predated the workflow engine (v0.7.0), integration catalog (v0.7.2), preset composition (v0.8.0), and catalog discovery CLI (v0.8.3). The ceremony and flexibility concerns they raised are precisely what these features address — the lean preset, pluggable workflows, composable presets, and community extensions like Conduct, MAQA, and Fleet Orchestrator already deliver alternative workflows beyond the default SDD process. We look forward to seeing how upcoming reviews account for these capabilities.
|
||||
|
||||
## Spec Kit Project Updates
|
||||
|
||||
### Releases Overview
|
||||
|
||||
**v0.4.4** (April 1) delivered the first stage of the **integration plugin architecture** — base classes, a manifest system, and a registry that replaced the hard-coded agent scaffolding. It also added the Product Forge, Superpowers Bridge, MAQA suite (7 extensions), Spec Kit Onboard, and Plan Review Gate to the community catalog, fixed Claude Code CLI detection for npm-local installs, and added `--allow-existing-branch` to `create-new-feature`. [\[github.com\]](https://github.com/github/spec-kit/releases/tag/v0.4.4)
|
||||
|
||||
**v0.4.5** (April 2) completed the integration migration in five stages: standard markdown integrations for 19 agents, TOML integrations (Gemini, Tabnine), skills and generic integrations, and removal of the legacy scaffold path. It also installed Claude Code as native skills, added a `--dry-run` flag for `create-new-feature`, support for 4+ digit feature branch numbers, the Fix Findings extension, and five lifecycle extensions to the community catalog. [\[github.com\]](https://github.com/github/spec-kit/releases/tag/v0.4.5)
|
||||
|
||||
**v0.5.0** (April 2) was a significant packaging change: **template zip bundles were removed from releases**, with the CLI itself now handling all scaffolding. This ensured CLI and templates stay in sync. It also introduced `DEVELOPMENT.md` for contributor onboarding. [\[github.com\]](https://github.com/github/spec-kit/releases/tag/v0.5.0)
|
||||
|
||||
**v0.5.1** (April 8) was a large patch release. It added the **bundled Git extension** (stages 1 and 2) with hooks on all core commands and `GIT_BRANCH_NAME` override support, **Forgecode** agent support, and the `specify integration` subcommand for post-init integration management. Argument hints were added to Claude Code commands. Numerous community extensions joined the catalog (Confluence, Canon, Spec Diagram, Branch Convention, Spec Refine, FixIt, Optimize, Security Review) along with presets (explicit-task-dependencies, toc-navigation, VS Code Ask Questions). Bug fixes included pinning typer≥0.24.0/click≥8.2.1 to fix an import crash, BSD-portable sed escaping, Trae agent fix, TOML frontmatter stripping, and preventing ambiguous TOML closing quotes. [\[github.com\]](https://github.com/github/spec-kit/releases/tag/v0.5.1)
|
||||
|
||||
**v0.6.0** (April 9) rewrote **AGENTS.md for the new integration architecture**, added the SpecKit Companion to Community Friends, and brought Bugfix Workflow, Worktree Isolation, and MemoryLint to the community catalog. A new multi-repo-branching preset arrived. [\[github.com\]](https://github.com/github/spec-kit/releases/tag/v0.6.0)
|
||||
|
||||
**v0.6.1** (April 10) added the **bundled lean preset** with a minimal workflow command set — a lighter-weight alternative to the full SDD ceremony. It also migrated **Cursor** from `.cursor/commands` to `.cursor/skills` and added Brownfield Bootstrap, CI Guard, SpecTest, PR Bridge, TinySpec, and Status Report to the community catalog. [\[github.com\]](https://github.com/github/spec-kit/releases/tag/v0.6.1)
|
||||
|
||||
**v0.6.2** (April 13) added **Goose AI agent** support (YAML-based recipe format), the GitHub Issues Integration extension, and the What-if Analysis extension. [\[github.com\]](https://github.com/github/spec-kit/releases/tag/v0.6.2)
|
||||
|
||||
**v0.7.0** (April 14) delivered the **workflow engine with catalog system**, enabling pluggable, multi-step workflow definitions. It added SFSpeckit (Salesforce SDD), the Worktrees extension, optional single-segment branch prefix for gitflow compatibility, and the claude-ask-questions and fiction-book-writing presets. [\[github.com\]](https://github.com/github/spec-kit/releases/tag/v0.7.0)
|
||||
|
||||
**v0.7.1** (April 15) deprecated the `--ai` flag in favor of `--integration` on `specify init`, added Windows to the CI test matrix, fixed Claude skill chaining for hook execution, merged TESTING.md into CONTRIBUTING.md, and added the Agent Assign and Architect Preview extensions. [\[github.com\]](https://github.com/github/spec-kit/releases/tag/v0.7.1)
|
||||
|
||||
**v0.7.2** (April 16) delivered the **integration catalog** for discovery, versioning, and community distribution of agent integrations. It also produced a major **documentation overhaul**: reference pages for core commands, extensions, presets, workflows, and integrations were added to `docs/reference/`, and the README CLI section was simplified. The Issues extension and Catalog CI extension joined the community catalog. [\[github.com\]](https://github.com/github/spec-kit/releases/tag/v0.7.2)
|
||||
|
||||
**v0.7.3** (April 17) replaced shell-based context updates with a **marker-based upsert** mechanism, eliminating accidental context file bloat. It added a **Community Friends page** to the docs site, the Spec Scope and Blueprint extensions, and a Claude Code/Copilot CLI plugin marketplace reference in the README. [\[github.com\]](https://github.com/github/spec-kit/releases/tag/v0.7.3)
|
||||
|
||||
**v0.7.4** (April 21) added **CITATION.cff and .zenodo.json** for academic citation support. It introduced Ripple (side-effect detection), Spec Validate, Version Guard, Spec Reference Loader, and Memory Loader extensions. A fix stripped UTF-8 BOM from agent context files, and the Antigravity (agy) agent layout was migrated to `.agents/` with `--skills` deprecated. [\[github.com\]](https://github.com/github/spec-kit/releases/tag/v0.7.4)
|
||||
|
||||
**v0.7.5** (April 22) added `specify self check` and `self upgrade` stubs, the **preset wrap strategy** (completing the composition trifecta alongside prepend and append), the Red Team adversarial review extension, the Wireframe extension, and a **directory traversal security fix** in command write paths. Skill placeholder resolution was expanded to all SKILL.md agents. Community content (walkthroughs and presets) was moved from the README to the docs site. [\[github.com\]](https://github.com/github/spec-kit/releases/tag/v0.7.5)
|
||||
|
||||
**v0.8.0** (April 23) delivered **preset composition strategies** (prepend, append, wrap) for templates, commands, and scripts — enabling presets to layer content around existing artifacts. It also added Copilot `--integration-options="--skills"` for skills-based scaffolding, `pipx` as an alternative installation method, and the Memory MD extension. [\[github.com\]](https://github.com/github/spec-kit/releases/tag/v0.8.0)
|
||||
|
||||
**v0.8.1** (April 24) fixed `/speckit.plan` on custom git branches via `.specify/feature.json`, migrated the **Mistral Vibe** integration to SkillsIntegration, added the **Screenwriting** and **Jira** presets, and resolved command reference formats per integration type (dot vs. hyphen notation). [\[github.com\]](https://github.com/github/spec-kit/releases/tag/v0.8.1)
|
||||
|
||||
**v0.8.2** (April 28) introduced **GITHUB_TOKEN/GH_TOKEN authentication** for private catalog and extension downloads, deprecated the `--no-git` flag (removal gated at v0.10.0), replaced all deprecated `--ai` references with `--integration` in documentation, and added MarkItDown Document Converter, Microsoft 365 Integration, Spec Orchestrator, and the Fiction Book Writing v1.7 preset with RAG (Chroma DB) offline semantic search. [\[github.com\]](https://github.com/github/spec-kit/releases/tag/v0.8.2)
|
||||
|
||||
**v0.8.3** (April 29) closed the month with **catalog discovery CLI commands** (search, info, catalog list/add/remove), support for **Devin for Terminal** as a skills-based integration, a fix for the opencode command dispatch, and the OWASP LLM Threat Model, iSAQB Architecture Governance, and Work IQ extensions. A fix was also added to the upgrade hint to prevent users from accidentally installing a PyPI squat package. [\[github.com\]](https://github.com/github/spec-kit/releases/tag/v0.8.3)
|
||||
|
||||
### Architecture & Infrastructure Highlights
|
||||
|
||||
The most significant architectural change in April was the **integration plugin architecture** (v0.4.4–v0.4.5), which replaced hard-coded agent scaffolding with a registry of self-describing integration classes. Each agent is now a self-contained subpackage under `src/specify_cli/integrations/<key>/` with base classes for Markdown, TOML, YAML, and Skills formats. This six-stage migration touched all 28 supported agents and laid the groundwork for the integration catalog (v0.7.2) and community-distributed integrations.
|
||||
|
||||
The **workflow engine** (v0.7.0) introduced a catalog-based system for pluggable, multi-step workflow definitions — moving beyond the fixed seven-step SDD sequence.
|
||||
|
||||
**Preset composition strategies** (v0.7.5/v0.8.0) completed the preset system with prepend, append, and wrap modes. Presets can now layer content around existing templates, commands, and scripts rather than only replacing them.
|
||||
|
||||
The **marker-based context upsert** (v0.7.3) replaced fragile shell-based sed operations for updating agent context files, eliminating a class of bugs around context bloat and encoding issues.
|
||||
|
||||
**Template zip bundles were removed** (v0.5.0), coupling the CLI and templates into a single distributable artifact.
|
||||
|
||||
### Bug Fixes and Security
|
||||
|
||||
The most critical fix was **blocking directory traversal in command write paths** (#2229, v0.7.5), which prevented a potential path traversal vulnerability in the CommandRegistrar. Other security-adjacent fixes included hardening against a **PyPI squat package** in upgrade hints (v0.8.3) and adding **GITHUB_TOKEN authentication** for private catalog downloads (v0.8.2).
|
||||
|
||||
Notable bug fixes: typer/click import crash (v0.5.1), BSD-portable sed escaping (v0.5.1), UTF-8 BOM stripping from context files (v0.7.4), CRLF warning suppression in PowerShell auto-commit (v0.7.3), Claude skill chaining for hooks (v0.7.1), TOML ambiguous closing quotes (v0.5.1), and custom branch support for `/speckit.plan` (v0.8.1). [\[github.com\]](https://github.com/github/spec-kit/releases)
|
||||
|
||||
### The Extension & Preset Ecosystem
|
||||
|
||||
The community extension catalog **tripled** during April, growing from 26 to **83 entries**. 59 new extensions were added and 2 were removed (Cognitive Squad and Understanding, whose repositories were no longer available). Community presets grew from 2 to **12 entries**, with 10 new presets added.
|
||||
|
||||
Notable new extensions by category:
|
||||
|
||||
- **Project management**: GitHub Issues Integration (Fatima367, aaronrsun), Spec Orchestrator (Quratulain-bilal), Agent Assign (xuyang), Status Report (Open-Agent-Tools)
|
||||
- **Quality & security**: Red Team adversarial review (Ash Brener), Security Review (DyanGalih), Ripple side-effect detection (chordpli), Spec Validate (Ahmed Eltayeb), CI Guard (Quratulain-bilal), OWASP LLM Threat Model (NaviaSamal)
|
||||
- **Multi-agent & orchestration**: MAQA suite with 7 extensions covering multi-agent QA, Jira, Azure DevOps, GitHub Projects, Linear, and Trello integrations (GenieRobot), Product Forge (VaiYav)
|
||||
- **Spec lifecycle**: Spec Refine (Quratulain-bilal), Bugfix Workflow (Quratulain-bilal), Fix Findings (Quratulain-bilal), Brownfield Bootstrap (Quratulain-bilal), TinySpec (Quratulain-bilal)
|
||||
- **Developer experience**: Blueprint code review (chordpli), Confluence (aaronrsun), MarkItDown Document Converter (BenBtg), Microsoft 365 Integration (BenBtg), Memory MD (DyanGalih), Memory Loader (KevinBrown5280), MemoryLint (RbBtSn0w)
|
||||
- **Domain-specific**: SFSpeckit for Salesforce (Sumanth Yanamala), iSAQB Architecture Governance preset (Thorsten Hindermann), Canon baseline-driven workflows (Maxim Stupakov)
|
||||
- **Creative**: Fiction Book Writing preset v1.7 with RAG/Chroma DB support (Andreas Daumann), Screenwriting preset (Andreas Daumann)
|
||||
|
||||
Notable contributor **Quratulain-bilal** contributed 15 extensions during the month, spanning spec lifecycle, workflow management, and CI/CD integration. **GenieRobot** contributed the 7-extension MAQA suite. **BenBtg** contributed both MarkItDown and Microsoft 365 integrations. [\[github.com\]](https://github.com/github/spec-kit/releases)
|
||||
|
||||
### Documentation Overhaul
|
||||
|
||||
April saw a comprehensive documentation effort. Reference pages for **core commands, extensions, presets, workflows, and integrations** were created under `docs/reference/`. Community content — **walkthroughs, presets, and a Community Friends page** — was moved from the README to `docs/community/`, reducing README length while improving discoverability. The deprecated `--ai` flag references were replaced with `--integration` across all documentation. TESTING.md was merged into CONTRIBUTING.md, and `DEVELOPMENT.md` was introduced for contributor onboarding. [\[github.com\]](https://github.com/github/spec-kit/releases)
|
||||
|
||||
## Community & Content
|
||||
|
||||
### Thoughtworks Technology Radar
|
||||
|
||||
On **April 15**, the **Thoughtworks Technology Radar Volume 34** placed GitHub Spec Kit in the **"Assess" ring** under Languages & Frameworks. The blip noted that teams report value in brownfield projects, that the constitution captures project scope and architecture, but flagged potential **instruction bloat, context rot, and verbose markdown output** as concerns to watch. This is the first appearance of any SDD-specific tool on the Radar. [\[thoughtworks.com\]](https://www.thoughtworks.com/radar/languages-and-frameworks/github-spec-kit)
|
||||
|
||||
### Developer Articles and Blog Posts
|
||||
|
||||
April produced 12 substantive external articles (plus one excluded as AI-generated SEO spam).
|
||||
|
||||
**Matt Rickard** published *"The Spec Layer: Why Spec-Driven Development (SDD) Works"* on April 1. His thesis: specs reduce execution freedom for AI agents, functioning as constraint surfaces. He compared Spec Kit, Kiro, OpenSpec, Tessl, Intent, and Symphony, and advocated for **"smaller specs, harder checks, less guessing."** [\[blog.matt-rickard.com\]](https://blog.matt-rickard.com/p/the-spec-layer)
|
||||
|
||||
**Fabián Silva** published *"I Built a Visual Spec-Driven Development Extension for VS Code That Works With Any LLM"* on April 3 on DEV Community. His **Caramelo** VS Code extension adds a visual UI, approval gates, Jira integration, and multi-LLM support on top of Spec Kit's workflow, reading and writing the standard `specs/` directory. [\[dev.to\]](https://dev.to/fabian_silva_/i-built-a-visual-spec-driven-development-extension-for-vs-code-that-works-with-any-llm-36ok)
|
||||
|
||||
**James M** published *"GitHub Spec Kit in 2026: SDD Goes Mainstream"* on April 4, calling the transition "from framework to platform" and highlighting Claude Code native skills, multi-agent support, and the massive ecosystem growth. [\[jamesm.blog\]](https://jamesm.blog/ai/github-spec-kit-2026-update/)
|
||||
|
||||
**Peter Saktor** published a detailed tutorial on DEV Community on April 6: *"GitHub Spec-Kit: From Vibe Coding to Spec-Driven Development,"* walking through a full 7-step SDD workflow refactoring an Azure Container App with 33 tasks across 6 phases. [\[dev.to\]](https://dev.to/petersaktor/github-spec-kit-from-vibe-coding-to-spec-driven-development-1pgd)
|
||||
|
||||
**Codexplorer** published *"Spec Kit: GitHub's Answer to 'The AI Built the Wrong Thing Again'"* on Medium (April 11), framing Spec Kit as flipping the spec-code relationship, with Go code examples covering the seven slash commands. [\[medium.com\]](https://codexplorer.medium.com/spec-kit-githubs-answer-to-the-ai-built-the-wrong-thing-again-22f122f142fb)
|
||||
|
||||
**XB Software** published *"Spec Kit on a Real Project: Implementation Experience in Large Legacy Code"* on April 17 — a field report from applying SDD to legacy systems. A week-long task was completed in half the time. The AI surfaced hidden requirements gaps. They noted API integration weakness, that SDD is overkill for small tasks, and that an experienced reviewer is still essential. [\[xbsoftware.com\]](https://xbsoftware.com/blog/ai-in-legacy-systems-spec-driven-development/)
|
||||
|
||||
**What IT Is** published *"Perspectives in Spec Driven Development"* on April 21, surveying the SDD landscape (Spec Kit, Kiro, Tessl) and calling Spec Kit "a good entry point." [\[theitsolutionist.com\]](https://theitsolutionist.com/2026/04/21/perspectives-in-spec-driven-development/)
|
||||
|
||||
**Will Torber** published *"Spec Kit vs BMAD vs OpenSpec: Choosing an SDD Framework in 2026"* on DEV Community on April 23. He recommended Spec Kit for greenfield but flagged brownfield friction and the branch-per-spec limitation, ultimately **recommending OpenSpec for most teams**. [\[dev.to\]](https://dev.to/willtorber/spec-kit-vs-bmad-vs-openspec-choosing-an-sdd-framework-in-2026-d3j)
|
||||
|
||||
**Truong Phung** published *"Spec Kit vs. Superpowers: A Comprehensive Comparison & Practical Guide to Combining Both"* on DEV Community on April 25 — an 11-section comparison proposing a hybrid workflow: "Spec Kit plans WHAT, Superpowers controls HOW," with a step-by-step playbook. [\[dev.to\]](https://dev.to/truongpx396/spec-kit-vs-superpowers-a-comprehensive-comparison-practical-guide-to-combining-both-52jj)
|
||||
|
||||
**Markus Wondrak** published *"Re-evaluating GitHub's Spec Kit: Structured SDLC Automation"* on LinkedIn on April 26, examining Spec Kit as a structured SDLC automation approach requiring human review at phase boundaries. [\[linkedin.com\]](https://www.linkedin.com/pulse/re-evaluating-githubs-spec-kit-structured-sdlc-markus-wondrak-eewqf/)
|
||||
|
||||
**FintechExtra** published a factual release-notes summary of v0.8.2 on April 28, highlighting authenticated catalog downloads, the UTF-8 manifest fix, and the Chroma DB semantic search in the fiction writing preset. [\[fintechextra.com\]](https://www.fintechextra.com/news/github-spec-kit-v082-expands-catalog-support-and-tightens-cli-behavior-331)
|
||||
|
||||
### Community Friends and Tools
|
||||
|
||||
The **SpecKit Companion** VS Code extension was added to the Community Friends section (v0.6.0). A community-maintained plugin for **Claude Code and GitHub Copilot CLI** that installs Spec Kit skills via the plugin marketplace was referenced in the README (v0.7.3). Fabián Silva's **Caramelo** VS Code extension demonstrated a visual UI approach to SDD. [\[github.com\]](https://github.com/github/spec-kit)
|
||||
|
||||
## SDD Ecosystem & Industry Trends
|
||||
|
||||
### The "Spec Layer" Debate
|
||||
|
||||
Matt Rickard's "The Spec Layer" essay established a new framing for SDD: specifications as **constraint surfaces** that reduce execution freedom for AI agents. His comparison of six SDD tools argued for smaller, more focused specs with harder verification checks — a departure from comprehensive specification documents. This framing resonated across the community, with the Thoughtworks Radar entry and multiple comparison articles echoing the tension between spec depth and practical overhead.
|
||||
|
||||
### Competitive Landscape
|
||||
|
||||
**Will Torber's** three-framework comparison (Spec Kit, BMAD, OpenSpec) recommended **OpenSpec for most teams**, citing lower ceremony and better brownfield support. **Truong Phung** proposed combining Spec Kit with **Superpowers** (Jesse Vincent) for a "plan WHAT + control HOW" hybrid. These comparisons reflected a maturing market where practitioners combine tools rather than picking one.
|
||||
|
||||
The **Thoughtworks Radar** placement validated SDD as a category worth tracking but flagged instruction bloat and context rot as open concerns — the same issues the Augment Code comparison raised in March. XB Software's field report confirmed these in practice: SDD adds value for complex legacy work but creates unnecessary overhead for small tasks.
|
||||
|
||||
Spec Kit continued to lead in **GitHub popularity** (92k stars) and **agent breadth** (29 integrations). The market continued to differentiate along several axes: Spec Kit on portability and ecosystem breadth, Intent on living specs and drift detection, BMAD-METHOD on multi-agent orchestration, and OpenSpec on simplicity. [\[dev.to\]](https://dev.to/willtorber/spec-kit-vs-bmad-vs-openspec-choosing-an-sdd-framework-in-2026-d3j) [\[thoughtworks.com\]](https://www.thoughtworks.com/radar/languages-and-frameworks/github-spec-kit)
|
||||
|
||||
## Roadmap
|
||||
|
||||
Areas under discussion or in progress for future development:
|
||||
|
||||
- **Spec lifecycle management** — context rot and spec drift remained the most cited concern across articles (Thoughtworks Radar, XB Software, Will Torber). The marker-based upsert (v0.7.3) addressed context file drift; spec-level drift detection remains an open area. The Reconcile and Archive extensions are community steps toward this. [\[thoughtworks.com\]](https://www.thoughtworks.com/radar/languages-and-frameworks/github-spec-kit)
|
||||
- **Workflow customization** — the workflow engine (v0.7.0) and preset composition strategies (v0.8.0) provide the foundation. Community presets for fiction writing, screenwriting, Jira tracking, and architecture governance demonstrate the breadth of possible workflows beyond standard SDD. [\[github.com\]](https://github.com/github/spec-kit/releases)
|
||||
- **Catalog discovery and distribution** — the integration catalog (v0.7.2) and catalog discovery CLI (v0.8.3) bring `specify` closer to a package-manager experience for extensions, presets, and integrations. Private catalog authentication (v0.8.2) supports enterprise distribution. [\[github.com\]](https://github.com/github/spec-kit/releases)
|
||||
- **Experience simplification** — the bundled lean preset (v0.6.1), `specify self check` (v0.7.5), and the deprecation of `--ai` in favor of `--integration` (v0.7.1) reflect ongoing work to reduce ceremony and improve the onboarding experience. Multiple external articles (Torber, XB Software) noted SDD overhead as a barrier. [\[dev.to\]](https://dev.to/willtorber/spec-kit-vs-bmad-vs-openspec-choosing-an-sdd-framework-in-2026-d3j)
|
||||
- **Cross-platform and enterprise** — Windows CI (v0.7.1), GITHUB_TOKEN authentication (v0.8.2), Salesforce-specific extensions, and the iSAQB architecture governance preset indicate growing enterprise adoption. [\[github.com\]](https://github.com/github/spec-kit)
|
||||
@@ -41,24 +41,6 @@ The resolution is implemented three times to ensure consistency:
|
||||
- **Bash**: `resolve_template()` in `scripts/bash/common.sh`
|
||||
- **PowerShell**: `Resolve-Template` in `scripts/powershell/common.ps1`
|
||||
|
||||
### Composition Strategies
|
||||
|
||||
Templates, commands, and scripts support a `strategy` field that controls how a preset's content is combined with lower-priority content instead of fully replacing it:
|
||||
|
||||
| Strategy | Description | Templates | Commands | Scripts |
|
||||
|----------|-------------|-----------|----------|---------|
|
||||
| `replace` (default) | Fully replaces lower-priority content | ✓ | ✓ | ✓ |
|
||||
| `prepend` | Places content before lower-priority content (separated by a blank line) | ✓ | ✓ | — |
|
||||
| `append` | Places content after lower-priority content (separated by a blank line) | ✓ | ✓ | — |
|
||||
| `wrap` | Content contains `{CORE_TEMPLATE}` (templates/commands) or `$CORE_SCRIPT` (scripts) placeholder replaced with lower-priority content | ✓ | ✓ | ✓ |
|
||||
|
||||
Composition is recursive — multiple composing presets chain. The `PresetResolver.resolve_content()` method walks the full priority stack bottom-up and applies each layer's strategy.
|
||||
|
||||
Content resolution functions for composition:
|
||||
- **Python**: `PresetResolver.resolve_content()` in `src/specify_cli/presets.py` (templates, commands, and scripts)
|
||||
- **Bash**: `resolve_template_content()` in `scripts/bash/common.sh` (templates only; command/script composition is handled by the Python resolver)
|
||||
- **PowerShell**: `Resolve-TemplateContent` in `scripts/powershell/common.ps1` (templates only; command/script composition is handled by the Python resolver)
|
||||
|
||||
## Command Registration
|
||||
|
||||
When a preset is installed with `type: "command"` entries, the `PresetManager` registers them into all detected agent directories using the shared `CommandRegistrar` from `src/specify_cli/agents.py`.
|
||||
|
||||
@@ -205,21 +205,11 @@ Edit `presets/catalog.community.json` and add your preset.
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Update Community Presets Table
|
||||
|
||||
Add your preset to the Community Presets table on the docs site at `docs/community/presets.md`:
|
||||
|
||||
```markdown
|
||||
| Your Preset Name | Brief description of what your preset does | N templates, M commands[, P scripts] | — | [repo-name](https://github.com/your-org/spec-kit-preset-your-preset) |
|
||||
```
|
||||
|
||||
Insert your row in alphabetical order by preset **name** (the first column of the table).
|
||||
|
||||
### 4. Submit Pull Request
|
||||
### 3. Submit Pull Request
|
||||
|
||||
```bash
|
||||
git checkout -b add-your-preset
|
||||
git add presets/catalog.community.json docs/community/presets.md
|
||||
git add presets/catalog.community.json
|
||||
git commit -m "Add your-preset to community catalog
|
||||
|
||||
- Preset ID: your-preset
|
||||
@@ -250,7 +240,6 @@ git push origin add-your-preset
|
||||
- [ ] Commands register to agent directories (if applicable)
|
||||
- [ ] Commands match template sections (command + template are coherent)
|
||||
- [ ] Added to presets/catalog.community.json
|
||||
- [ ] Added row to docs/community/presets.md table
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -61,44 +61,14 @@ specify preset add healthcare-compliance --priority 5 # overrides enterprise-sa
|
||||
specify preset add pm-workflow --priority 1 # overrides everything
|
||||
```
|
||||
|
||||
Presets **override by default**, they don't merge. If two presets both provide `spec-template` with the default `replace` strategy, the one with the lowest priority number wins entirely. However, presets can use **composition strategies** to augment rather than replace content.
|
||||
|
||||
### Composition Strategies
|
||||
|
||||
Presets can declare a `strategy` per template to control how content is combined. The `name` field identifies which template to compose with in the priority stack, while `file` points to the actual content file (which can differ from the convention path `templates/<name>.md`):
|
||||
|
||||
```yaml
|
||||
provides:
|
||||
templates:
|
||||
- type: "template"
|
||||
name: "spec-template"
|
||||
file: "templates/spec-addendum.md"
|
||||
strategy: "append" # adds content after the core template
|
||||
```
|
||||
|
||||
| Strategy | Description |
|
||||
|----------|-------------|
|
||||
| `replace` (default) | Fully replaces the lower-priority template |
|
||||
| `prepend` | Places content **before** the resolved lower-priority template, separated by a blank line |
|
||||
| `append` | Places content **after** the resolved lower-priority template, separated by a blank line |
|
||||
| `wrap` | Content contains `{CORE_TEMPLATE}` placeholder (or `$CORE_SCRIPT` for scripts) replaced with the lower-priority content |
|
||||
|
||||
**Supported combinations:**
|
||||
|
||||
| Type | `replace` | `prepend` | `append` | `wrap` |
|
||||
|------|-----------|-----------|----------|--------|
|
||||
| **template** | ✓ (default) | ✓ | ✓ | ✓ |
|
||||
| **command** | ✓ (default) | ✓ | ✓ | ✓ |
|
||||
| **script** | ✓ (default) | — | — | ✓ |
|
||||
|
||||
Multiple composing presets chain recursively. For example, a security preset with `prepend` and a compliance preset with `append` will produce: security header + core content + compliance footer.
|
||||
Presets **override**, they don't merge. If two presets both provide `spec-template`, the one with the lowest priority number wins entirely.
|
||||
|
||||
## Catalog Management
|
||||
|
||||
Presets are discovered through catalogs. By default, Spec Kit uses the official and community catalogs:
|
||||
|
||||
> [!NOTE]
|
||||
> Community presets are independently created and maintained by their respective authors. Maintainers only verify that catalog entries are complete and correctly formatted — they do **not review, audit, endorse, or support the preset code itself**. Review preset source code before installation and use at your own discretion.
|
||||
> Community presets are independently created and maintained by their respective authors. GitHub and the Spec Kit maintainers may review pull requests that add entries to the community catalog for formatting, catalog structure, or policy compliance, but they do **not review, audit, endorse, or support the preset code itself**. Review preset source code before installation and use at your own discretion.
|
||||
|
||||
```bash
|
||||
# List active catalogs
|
||||
@@ -123,25 +93,9 @@ See [scaffold/](scaffold/) for a scaffold you can copy to create your own preset
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `SPECKIT_PRESET_CATALOG_URL` | Override the full catalog stack with a single URL (replaces all defaults) | Built-in default stack |
|
||||
| `GH_TOKEN` / `GITHUB_TOKEN` | GitHub token for authenticated requests to GitHub-hosted URLs (`raw.githubusercontent.com`, `github.com`, `api.github.com`, `codeload.github.com`). Required when your catalog JSON or preset ZIPs are hosted in a private GitHub repository. | None |
|
||||
|
||||
#### Example: Using a private GitHub-hosted catalog
|
||||
|
||||
```bash
|
||||
# Authenticate with a token (gh CLI, PAT, or GITHUB_TOKEN in CI)
|
||||
export GITHUB_TOKEN=$(gh auth token)
|
||||
|
||||
# Search a private catalog added via `specify preset catalog add`
|
||||
specify preset search my-template
|
||||
|
||||
# Install from a private catalog
|
||||
specify preset add my-template
|
||||
```
|
||||
|
||||
The token is attached automatically to requests targeting GitHub domains. Non-GitHub catalog URLs are always fetched without credentials.
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
| `SPECKIT_PRESET_CATALOG_URL` | Override the catalog URL (replaces all defaults) |
|
||||
|
||||
## Configuration Files
|
||||
|
||||
@@ -154,5 +108,13 @@ The token is attached automatically to requests targeting GitHub domains. Non-Gi
|
||||
|
||||
The following enhancements are under consideration for future releases:
|
||||
|
||||
- **Structural merge strategies** — Parsing Markdown sections for per-section granularity (e.g., "replace only ## Security").
|
||||
- **Conflict detection** — `specify preset lint` / `specify preset doctor` for detecting composition conflicts.
|
||||
- **Composition strategies** — Allow presets to declare a `strategy` per template instead of the default `replace`:
|
||||
|
||||
| Type | `replace` | `prepend` | `append` | `wrap` |
|
||||
|------|-----------|-----------|----------|--------|
|
||||
| **template** | ✓ (default) | ✓ | ✓ | ✓ |
|
||||
| **command** | ✓ (default) | ✓ | ✓ | ✓ |
|
||||
| **script** | ✓ (default) | — | — | ✓ |
|
||||
|
||||
For artifacts and commands (which are LLM directives), `wrap` would inject preset content before and after the core template using a `{CORE_TEMPLATE}` placeholder. For scripts, `wrap` would run custom logic before/after the core script via a `$CORE_SCRIPT` variable.
|
||||
- **Script overrides** — Enable presets to provide alternative versions of core scripts (e.g. `create-new-feature.sh`) for workflow customization. A `strategy: "wrap"` option could allow presets to run custom logic before/after the core script without fully replacing it.
|
||||
|
||||
@@ -1,64 +1,8 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"updated_at": "2026-05-05T10:00:00Z",
|
||||
"updated_at": "2026-04-13T00:00:00Z",
|
||||
"catalog_url": "https://raw.githubusercontent.com/github/spec-kit/main/presets/catalog.community.json",
|
||||
"presets": {
|
||||
"a11y-governance": {
|
||||
"name": "A11Y Governance",
|
||||
"id": "a11y-governance",
|
||||
"version": "0.2.0",
|
||||
"description": "Adds accessibility, bilingual DE/EN delivery, CEFR-B2 readability, and inclusive-content governance to Spec Kit.",
|
||||
"author": "Thorsten Hindermann",
|
||||
"repository": "https://github.com/hindermath/spec-kit-preset-a11y-governance",
|
||||
"download_url": "https://github.com/hindermath/spec-kit-preset-a11y-governance/archive/refs/tags/v0.2.0.zip",
|
||||
"homepage": "https://github.com/hindermath/spec-kit-preset-a11y-governance",
|
||||
"documentation": "https://github.com/hindermath/spec-kit-preset-a11y-governance/blob/main/README.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.8.0"
|
||||
},
|
||||
"provides": {
|
||||
"templates": 9,
|
||||
"commands": 3
|
||||
},
|
||||
"tags": [
|
||||
"a11y",
|
||||
"accessibility",
|
||||
"bilingual",
|
||||
"wcag",
|
||||
"inclusion"
|
||||
],
|
||||
"created_at": "2026-04-27T00:00:00Z",
|
||||
"updated_at": "2026-04-27T00:00:00Z"
|
||||
},
|
||||
"agent-parity-governance": {
|
||||
"name": "Agent Parity Governance",
|
||||
"id": "agent-parity-governance",
|
||||
"version": "0.1.0",
|
||||
"description": "Keeps shared AI-agent guidance aligned across a project-defined set of agent instruction surfaces.",
|
||||
"author": "Thorsten Hindermann",
|
||||
"repository": "https://github.com/hindermath/spec-kit-preset-agent-parity-governance",
|
||||
"download_url": "https://github.com/hindermath/spec-kit-preset-agent-parity-governance/archive/refs/tags/v0.1.0.zip",
|
||||
"homepage": "https://github.com/hindermath/spec-kit-preset-agent-parity-governance",
|
||||
"documentation": "https://github.com/hindermath/spec-kit-preset-agent-parity-governance/blob/main/README.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.8.0"
|
||||
},
|
||||
"provides": {
|
||||
"templates": 6,
|
||||
"commands": 3
|
||||
},
|
||||
"tags": [
|
||||
"agents",
|
||||
"governance",
|
||||
"parity",
|
||||
"agent-guidance",
|
||||
"multi-agent"
|
||||
],
|
||||
"created_at": "2026-04-27T00:00:00Z",
|
||||
"updated_at": "2026-04-27T00:00:00Z"
|
||||
},
|
||||
"aide-in-place": {
|
||||
"name": "AIDE In-Place Migration",
|
||||
"id": "aide-in-place",
|
||||
@@ -72,9 +16,7 @@
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.2.0",
|
||||
"extensions": [
|
||||
"aide"
|
||||
]
|
||||
"extensions": ["aide"]
|
||||
},
|
||||
"provides": {
|
||||
"templates": 2,
|
||||
@@ -87,34 +29,6 @@
|
||||
"aide"
|
||||
]
|
||||
},
|
||||
"architecture-governance": {
|
||||
"name": "Architecture Governance",
|
||||
"id": "architecture-governance",
|
||||
"version": "0.2.0",
|
||||
"description": "Adds secure architecture governance, threat modeling, STRIDE/CAPEC, Zero Trust, S-ADRs, and OWASP SAMM to Spec Kit.",
|
||||
"author": "Thorsten Hindermann",
|
||||
"repository": "https://github.com/hindermath/spec-kit-preset-architecture-governance",
|
||||
"download_url": "https://github.com/hindermath/spec-kit-preset-architecture-governance/archive/refs/tags/v0.2.0.zip",
|
||||
"homepage": "https://github.com/hindermath/spec-kit-preset-architecture-governance",
|
||||
"documentation": "https://github.com/hindermath/spec-kit-preset-architecture-governance/blob/main/README.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.8.0"
|
||||
},
|
||||
"provides": {
|
||||
"templates": 11,
|
||||
"commands": 3
|
||||
},
|
||||
"tags": [
|
||||
"architecture",
|
||||
"governance",
|
||||
"threat-modeling",
|
||||
"stride",
|
||||
"zero-trust"
|
||||
],
|
||||
"created_at": "2026-04-27T00:00:00Z",
|
||||
"updated_at": "2026-04-27T00:00:00Z"
|
||||
},
|
||||
"canon-core": {
|
||||
"name": "Canon Core",
|
||||
"id": "canon-core",
|
||||
@@ -166,34 +80,6 @@
|
||||
"created_at": "2026-04-13T00:00:00Z",
|
||||
"updated_at": "2026-04-13T00:00:00Z"
|
||||
},
|
||||
"cross-platform-governance": {
|
||||
"name": "Cross-Platform Governance",
|
||||
"id": "cross-platform-governance",
|
||||
"version": "0.1.0",
|
||||
"description": "Adds Bash and PowerShell parity, dry-run/WhatIf parity, man-page expectations, and Verb-Noun Cmdlet discipline.",
|
||||
"author": "Thorsten Hindermann",
|
||||
"repository": "https://github.com/hindermath/spec-kit-preset-cross-platform-governance",
|
||||
"download_url": "https://github.com/hindermath/spec-kit-preset-cross-platform-governance/archive/refs/tags/v0.1.0.zip",
|
||||
"homepage": "https://github.com/hindermath/spec-kit-preset-cross-platform-governance",
|
||||
"documentation": "https://github.com/hindermath/spec-kit-preset-cross-platform-governance/blob/main/README.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.8.0"
|
||||
},
|
||||
"provides": {
|
||||
"templates": 8,
|
||||
"commands": 3
|
||||
},
|
||||
"tags": [
|
||||
"cross-platform",
|
||||
"bash",
|
||||
"powershell",
|
||||
"man-page",
|
||||
"cmdlet"
|
||||
],
|
||||
"created_at": "2026-04-27T00:00:00Z",
|
||||
"updated_at": "2026-04-27T00:00:00Z"
|
||||
},
|
||||
"explicit-task-dependencies": {
|
||||
"name": "Explicit Task Dependencies",
|
||||
"id": "explicit-task-dependencies",
|
||||
@@ -222,11 +108,11 @@
|
||||
"fiction-book-writing": {
|
||||
"name": "Fiction Book Writing",
|
||||
"id": "fiction-book-writing",
|
||||
"version": "1.7.0",
|
||||
"description": "Spec-Driven Development for novel and long-form fiction. 27 AI commands from idea to submission: story bible governance, 9 POV modes, all major plot structure frameworks, scene-by-scene drafting with quality gates, audiobook pipeline (SSML/ElevenLabs), cover design, sensitivity review, pacing and prose statistics, and pandoc-based export to DOCX/EPUB/LaTeX. Two style modes: author voice sample extraction or humanized-AI prose with 5 craft profiles. 12 languages supported. Support for offline semantic search.",
|
||||
"version": "1.3.0",
|
||||
"description": "Spec-Driven Development for novel and long-form fiction. Replaces software engineering terminology with storytelling craft: specs become story briefs, plans become story structures, and tasks become scene-by-scene writing tasks. Supports 8 POV modes, all major plot structure frameworks, 5 humanized-AI prose profiles, and exports to DOCX/EPUB/LaTeX via pandoc.",
|
||||
"author": "Andreas Daumann",
|
||||
"repository": "https://github.com/adaumann/speckit-preset-fiction-book-writing",
|
||||
"download_url": "https://github.com/adaumann/speckit-preset-fiction-book-writing/archive/refs/tags/v1.7.0.zip",
|
||||
"download_url": "https://github.com/adaumann/speckit-preset-fiction-book-writing/archive/refs/tags/v1.3.0.zip",
|
||||
"homepage": "https://github.com/adaumann/speckit-preset-fiction-book-writing",
|
||||
"documentation": "https://github.com/adaumann/speckit-preset-fiction-book-writing/blob/main/fiction-book-writing/README.md",
|
||||
"license": "MIT",
|
||||
@@ -234,82 +120,24 @@
|
||||
"speckit_version": ">=0.5.0"
|
||||
},
|
||||
"provides": {
|
||||
"templates": 22,
|
||||
"commands": 27,
|
||||
"scripts": 2
|
||||
"templates": 21,
|
||||
"commands": 17,
|
||||
"scripts": 1
|
||||
},
|
||||
"tags": [
|
||||
"writing",
|
||||
"novel",
|
||||
"book",
|
||||
"fiction",
|
||||
"storytelling",
|
||||
"creative-writing",
|
||||
"kdp",
|
||||
"single-pov",
|
||||
"multi-pov",
|
||||
"export",
|
||||
"book",
|
||||
"brainstorming",
|
||||
"roleplay",
|
||||
"audiobook",
|
||||
"language-support"
|
||||
"export"
|
||||
],
|
||||
"created_at": "2026-04-09T08:00:00Z",
|
||||
"updated_at": "2026-04-27T08:00:00Z"
|
||||
},
|
||||
"isaqb-architecture-governance": {
|
||||
"name": "iSAQB Architecture Governance",
|
||||
"id": "isaqb-architecture-governance",
|
||||
"version": "0.1.0",
|
||||
"description": "Adds general iSAQB/CPSA-F and arc42 architecture governance, including views, quality scenarios, ADRs, risks, and technical debt.",
|
||||
"author": "Thorsten Hindermann",
|
||||
"repository": "https://github.com/hindermath/spec-kit-preset-isaqb-architecture-governance",
|
||||
"download_url": "https://github.com/hindermath/spec-kit-preset-isaqb-architecture-governance/archive/refs/tags/v0.1.0.zip",
|
||||
"homepage": "https://github.com/hindermath/spec-kit-preset-isaqb-architecture-governance",
|
||||
"documentation": "https://github.com/hindermath/spec-kit-preset-isaqb-architecture-governance/blob/main/README.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.8.0"
|
||||
},
|
||||
"provides": {
|
||||
"templates": 13,
|
||||
"commands": 3
|
||||
},
|
||||
"tags": [
|
||||
"architecture",
|
||||
"governance",
|
||||
"isaqb",
|
||||
"arc42",
|
||||
"adr"
|
||||
],
|
||||
"created_at": "2026-04-27T00:00:00Z",
|
||||
"updated_at": "2026-04-27T00:00:00Z"
|
||||
},
|
||||
"jira": {
|
||||
"name": "Jira Issue Tracking",
|
||||
"id": "jira",
|
||||
"version": "1.0.0",
|
||||
"description": "Overrides speckit.taskstoissues to create Jira epics, stories, and tasks instead of GitHub Issues via Atlassian MCP tools.",
|
||||
"author": "luno",
|
||||
"repository": "https://github.com/luno/spec-kit-preset-jira",
|
||||
"download_url": "https://github.com/luno/spec-kit-preset-jira/archive/refs/tags/v1.0.0.zip",
|
||||
"homepage": "https://github.com/luno/spec-kit-preset-jira",
|
||||
"documentation": "https://github.com/luno/spec-kit-preset-jira/blob/main/README.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.1.0"
|
||||
},
|
||||
"provides": {
|
||||
"templates": 0,
|
||||
"commands": 1
|
||||
},
|
||||
"tags": [
|
||||
"jira",
|
||||
"atlassian",
|
||||
"issue-tracking",
|
||||
"preset"
|
||||
],
|
||||
"created_at": "2026-04-15T00:00:00Z",
|
||||
"updated_at": "2026-04-15T00:00:00Z"
|
||||
"updated_at": "2026-04-09T08:00:00Z"
|
||||
},
|
||||
"multi-repo-branching": {
|
||||
"name": "Multi-Repo Branching",
|
||||
@@ -363,99 +191,6 @@
|
||||
"experimental"
|
||||
]
|
||||
},
|
||||
"screenwriting": {
|
||||
"name": "Screenwriting",
|
||||
"id": "screenwriting",
|
||||
"version": "1.0.0",
|
||||
"description": "Spec-Driven Development for screenwriting/scriptwriting/tutorials: feature films, television (pilot, episode, limited series), and stage plays. Adapts the Spec Kit workflow to screenplay craft — slug lines, action lines, act breaks, beat sheets, and industry-standard pitch documents replace prose fiction conventions. Supports three-act, Save the Cat, TV pilot, network episode, cable/streaming episode, and stage-play structural frameworks.",
|
||||
"author": "Andreas Daumann",
|
||||
"repository": "https://github.com/adaumann/speckit-preset-screenwriting",
|
||||
"download_url": "https://github.com/adaumann/speckit-preset-screenwriting/archive/refs/tags/v1.0.0.zip",
|
||||
"homepage": "https://github.com/adaumann/speckit-preset-screenwriting",
|
||||
"documentation": "https://github.com/adaumann/speckit-preset-screenwriting/blob/main/screenwriting/README.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.5.0"
|
||||
},
|
||||
"provides": {
|
||||
"templates": 26,
|
||||
"commands": 32,
|
||||
"scripts": 1
|
||||
},
|
||||
"tags": [
|
||||
"writing",
|
||||
"screenplay",
|
||||
"scriptwriting",
|
||||
"film",
|
||||
"tv",
|
||||
"fountain",
|
||||
"fountain-format",
|
||||
"beat-sheet",
|
||||
"teleplay",
|
||||
"drama",
|
||||
"comedy",
|
||||
"storytelling",
|
||||
"tutorial",
|
||||
"education"
|
||||
],
|
||||
"created_at": "2026-04-23T08:00:00Z",
|
||||
"updated_at": "2026-04-23T08:00:00Z"
|
||||
},
|
||||
"security-governance": {
|
||||
"name": "Security Governance",
|
||||
"id": "security-governance",
|
||||
"version": "0.2.0",
|
||||
"description": "Adds secure development governance, MSL preference, ASVS verification, supply-chain transparency, and EU CRA awareness.",
|
||||
"author": "Thorsten Hindermann",
|
||||
"repository": "https://github.com/hindermath/spec-kit-preset-security-governance",
|
||||
"download_url": "https://github.com/hindermath/spec-kit-preset-security-governance/archive/refs/tags/v0.2.0.zip",
|
||||
"homepage": "https://github.com/hindermath/spec-kit-preset-security-governance",
|
||||
"documentation": "https://github.com/hindermath/spec-kit-preset-security-governance/blob/main/README.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.8.0"
|
||||
},
|
||||
"provides": {
|
||||
"templates": 12,
|
||||
"commands": 3
|
||||
},
|
||||
"tags": [
|
||||
"security",
|
||||
"governance",
|
||||
"msl",
|
||||
"asvs",
|
||||
"supply-chain"
|
||||
],
|
||||
"created_at": "2026-04-27T00:00:00Z",
|
||||
"updated_at": "2026-04-27T00:00:00Z"
|
||||
},
|
||||
"spec2cloud": {
|
||||
"name": "Spec2Cloud",
|
||||
"id": "spec2cloud",
|
||||
"version": "1.1.0",
|
||||
"description": "Spec-driven workflow tuned for shipping to Azure: spec → plan → tasks → implement → deploy.",
|
||||
"author": "Azure Samples",
|
||||
"repository": "https://github.com/Azure-Samples/Spec2Cloud",
|
||||
"download_url": "https://github.com/Azure-Samples/Spec2Cloud/releases/download/spec-kit-spec2cloud-v1.1.0/preset.zip",
|
||||
"homepage": "https://aka.ms/spec2cloud",
|
||||
"documentation": "https://github.com/Azure-Samples/Spec2Cloud/blob/main/spec-kit/README.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.1.0"
|
||||
},
|
||||
"provides": {
|
||||
"templates": 5,
|
||||
"commands": 8
|
||||
},
|
||||
"tags": [
|
||||
"azure",
|
||||
"spec2cloud",
|
||||
"workflow",
|
||||
"deployment"
|
||||
],
|
||||
"created_at": "2026-04-30T00:00:00Z",
|
||||
"updated_at": "2026-04-30T00:00:00Z"
|
||||
},
|
||||
"toc-navigation": {
|
||||
"name": "Table of Contents Navigation",
|
||||
"id": "toc-navigation",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"updated_at": "2026-04-24T00:00:00Z",
|
||||
"updated_at": "2026-04-10T00:00:00Z",
|
||||
"catalog_url": "https://raw.githubusercontent.com/github/spec-kit/main/presets/catalog.json",
|
||||
"presets": {
|
||||
"lean": {
|
||||
@@ -10,15 +10,7 @@
|
||||
"description": "Minimal core workflow commands - just the prompt, just the artifact",
|
||||
"author": "github",
|
||||
"repository": "https://github.com/github/spec-kit",
|
||||
"license": "MIT",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"speckit_version": ">=0.6.0"
|
||||
},
|
||||
"provides": {
|
||||
"commands": 5,
|
||||
"templates": 0
|
||||
},
|
||||
"tags": [
|
||||
"lean",
|
||||
"minimal",
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
# Lean Workflow
|
||||
|
||||
A minimal preset that strips the Spec Kit workflow down to its essentials — just the prompt, just the artifact.
|
||||
|
||||
## When to Use
|
||||
|
||||
Use Lean when you want the structured specify → plan → tasks → implement pipeline without the ceremony of the full templates. Each command produces a single focused Markdown file with no boilerplate sections to fill in.
|
||||
|
||||
## Commands Included
|
||||
|
||||
| Command | Output | Description |
|
||||
|---------|--------|-------------|
|
||||
| `speckit.specify` | `spec.md` | Create a specification from a feature description |
|
||||
| `speckit.plan` | `plan.md` | Create an implementation plan from the spec |
|
||||
| `speckit.tasks` | `tasks.md` | Create dependency-ordered tasks from spec and plan |
|
||||
| `speckit.implement` | *(code)* | Execute all tasks in order, marking progress |
|
||||
| `speckit.constitution` | `constitution.md` | Create or update the project constitution |
|
||||
|
||||
## What It Replaces
|
||||
|
||||
Lean overrides the five core workflow commands with self-contained prompts that produce each artifact directly — no separate template files involved. The result is a shorter, more direct workflow.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# Lean is a bundled preset — no download needed
|
||||
specify preset add lean
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
# Test from local directory
|
||||
specify preset add --dev ./presets/lean
|
||||
|
||||
# Verify commands resolve
|
||||
specify preset resolve speckit.specify
|
||||
|
||||
# Remove when done
|
||||
specify preset remove lean
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
@@ -48,4 +48,3 @@ tags:
|
||||
- "lean"
|
||||
- "minimal"
|
||||
- "workflow"
|
||||
- "core"
|
||||
|
||||
@@ -32,15 +32,6 @@ provides:
|
||||
templates:
|
||||
# CUSTOMIZE: Define your template overrides
|
||||
# Templates are document scaffolds (spec-template.md, plan-template.md, etc.)
|
||||
#
|
||||
# Strategy options (optional, defaults to "replace"):
|
||||
# replace - Fully replaces the lower-priority template (default)
|
||||
# prepend - Places this content BEFORE the lower-priority template
|
||||
# append - Places this content AFTER the lower-priority template
|
||||
# wrap - Uses {CORE_TEMPLATE} placeholder (templates/commands) or
|
||||
# $CORE_SCRIPT placeholder (scripts), replaced with lower-priority content
|
||||
#
|
||||
# Note: Scripts only support "replace" and "wrap" strategies.
|
||||
- type: "template"
|
||||
name: "spec-template"
|
||||
file: "templates/spec-template.md"
|
||||
@@ -54,26 +45,6 @@ provides:
|
||||
# description: "Custom plan template"
|
||||
# replaces: "plan-template"
|
||||
|
||||
# COMPOSITION EXAMPLES:
|
||||
# The `file` field points to the content file (can differ from the
|
||||
# convention path `templates/<name>.md`). The `name` field identifies
|
||||
# which template to compose with in the priority stack.
|
||||
#
|
||||
# Append additional sections to an existing template:
|
||||
# - type: "template"
|
||||
# name: "spec-template"
|
||||
# file: "templates/spec-addendum.md"
|
||||
# description: "Add compliance section to spec template"
|
||||
# strategy: "append"
|
||||
#
|
||||
# Wrap a command with preamble/sign-off:
|
||||
# - type: "command"
|
||||
# name: "speckit.specify"
|
||||
# file: "commands/specify-wrapper.md"
|
||||
# description: "Wrap specify command with compliance checks"
|
||||
# strategy: "wrap"
|
||||
# # In the wrapper file, use {CORE_TEMPLATE} where the original content goes
|
||||
|
||||
# OVERRIDE EXTENSION TEMPLATES:
|
||||
# Presets sit above extensions in the resolution stack, so you can
|
||||
# override templates provided by any installed extension.
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
---
|
||||
description: "Self-test wrap command — pre/post around core"
|
||||
strategy: wrap
|
||||
---
|
||||
|
||||
## Preset Pre-Logic
|
||||
|
||||
preset:self-test wrap-pre
|
||||
|
||||
{CORE_TEMPLATE}
|
||||
|
||||
## Preset Post-Logic
|
||||
|
||||
preset:self-test wrap-post
|
||||
@@ -56,11 +56,6 @@ provides:
|
||||
description: "Self-test override of the specify command"
|
||||
replaces: "speckit.specify"
|
||||
|
||||
- type: "command"
|
||||
name: "speckit.wrap-test"
|
||||
file: "commands/speckit.wrap-test.md"
|
||||
description: "Self-test wrap strategy command"
|
||||
|
||||
tags:
|
||||
- "testing"
|
||||
- "self-test"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "specify-cli"
|
||||
version = "0.8.7.dev0"
|
||||
version = "0.7.2"
|
||||
description = "Specify CLI, part of GitHub Spec Kit. A tool to bootstrap your projects for Spec-Driven Development (SDD)."
|
||||
requires-python = ">=3.11"
|
||||
dependencies = [
|
||||
@@ -28,6 +28,7 @@ packages = ["src/specify_cli"]
|
||||
[tool.hatch.build.targets.wheel.force-include]
|
||||
# Bundle core assets so `specify init` works without network access (air-gapped / enterprise)
|
||||
# Page templates (exclude commands/ — bundled separately below to avoid duplication)
|
||||
"templates/agent-file-template.md" = "specify_cli/core_pack/templates/agent-file-template.md"
|
||||
"templates/checklist-template.md" = "specify_cli/core_pack/templates/checklist-template.md"
|
||||
"templates/constitution-template.md" = "specify_cli/core_pack/templates/constitution-template.md"
|
||||
"templates/plan-template.md" = "specify_cli/core_pack/templates/plan-template.md"
|
||||
|
||||
@@ -153,59 +153,6 @@ check_feature_branch() {
|
||||
return 0
|
||||
}
|
||||
|
||||
# Safely read .specify/feature.json's "feature_directory" value.
|
||||
# Prints the raw value (possibly relative) to stdout, or empty string if the file
|
||||
# is missing, unparseable, or does not contain the key. Always returns 0 so callers
|
||||
# under `set -e` cannot be aborted by parser failure.
|
||||
# Parser order mirrors the historical get_feature_paths behavior: jq -> python3 -> grep/sed.
|
||||
read_feature_json_feature_directory() {
|
||||
local repo_root="$1"
|
||||
local fj="$repo_root/.specify/feature.json"
|
||||
[[ -f "$fj" ]] || { printf '%s' ''; return 0; }
|
||||
|
||||
local _fd=''
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
if ! _fd=$(jq -r '.feature_directory // empty' "$fj" 2>/dev/null); then
|
||||
_fd=''
|
||||
fi
|
||||
elif command -v python3 >/dev/null 2>&1; then
|
||||
# Use Python so pretty-printed/multi-line JSON still parses correctly.
|
||||
if ! _fd=$(python3 -c "import json,sys; d=json.load(open(sys.argv[1])); v=d.get('feature_directory'); print(v if v else '')" "$fj" 2>/dev/null); then
|
||||
_fd=''
|
||||
fi
|
||||
else
|
||||
# Last-resort single-line grep/sed fallback. The `|| true` guards against
|
||||
# grep returning 1 (no match) aborting under `set -e` / `pipefail`.
|
||||
_fd=$( { grep -E '"feature_directory"[[:space:]]*:' "$fj" 2>/dev/null || true; } \
|
||||
| head -n 1 \
|
||||
| sed -E 's/^[^:]*:[[:space:]]*"([^"]*)".*$/\1/' )
|
||||
fi
|
||||
|
||||
printf '%s' "$_fd"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Returns 0 when .specify/feature.json lists feature_directory that exists as a directory
|
||||
# and matches the resolved active FEATURE_DIR (so /speckit.plan can skip git branch pattern checks).
|
||||
# Delegates parsing to read_feature_json_feature_directory, which is safe under `set -e`.
|
||||
feature_json_matches_feature_dir() {
|
||||
local repo_root="$1"
|
||||
local active_feature_dir="$2"
|
||||
|
||||
local _fd
|
||||
_fd=$(read_feature_json_feature_directory "$repo_root")
|
||||
|
||||
[[ -n "$_fd" ]] || return 1
|
||||
[[ "$_fd" != /* ]] && _fd="$repo_root/$_fd"
|
||||
[[ -d "$_fd" ]] || return 1
|
||||
|
||||
local norm_json norm_active
|
||||
norm_json="$(cd -- "$_fd" 2>/dev/null && pwd -P)" || return 1
|
||||
norm_active="$(cd -- "$active_feature_dir" 2>/dev/null && pwd -P)" || return 1
|
||||
|
||||
[[ "$norm_json" == "$norm_active" ]]
|
||||
}
|
||||
|
||||
# Find feature directory by numeric prefix instead of exact branch match
|
||||
# This allows multiple branches to work on the same spec (e.g., 004-fix-bug, 004-add-feature)
|
||||
find_feature_dir_by_prefix() {
|
||||
@@ -270,10 +217,16 @@ get_feature_paths() {
|
||||
# Normalize relative paths to absolute under repo root
|
||||
[[ "$feature_dir" != /* ]] && feature_dir="$repo_root/$feature_dir"
|
||||
elif [[ -f "$repo_root/.specify/feature.json" ]]; then
|
||||
# Shared, set -e-safe parser: jq -> python3 -> grep/sed. Returns empty on
|
||||
# missing/unparseable/unset so we fall through to the branch-prefix lookup.
|
||||
local _fd
|
||||
_fd=$(read_feature_json_feature_directory "$repo_root")
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
_fd=$(jq -r '.feature_directory // empty' "$repo_root/.specify/feature.json" 2>/dev/null)
|
||||
elif command -v python3 >/dev/null 2>&1; then
|
||||
# Fallback: use Python to parse JSON so pretty-printed/multi-line files work
|
||||
_fd=$(python3 -c "import json,sys; d=json.load(open(sys.argv[1])); print(d.get('feature_directory',''))" "$repo_root/.specify/feature.json" 2>/dev/null)
|
||||
else
|
||||
# Last resort: single-line grep fallback (won't work on multi-line JSON)
|
||||
_fd=$(grep -o '"feature_directory"[[:space:]]*:[[:space:]]*"[^"]*"' "$repo_root/.specify/feature.json" 2>/dev/null | sed 's/.*"\([^"]*\)"$/\1/')
|
||||
fi
|
||||
if [[ -n "$_fd" ]]; then
|
||||
feature_dir="$_fd"
|
||||
# Normalize relative paths to absolute under repo root
|
||||
@@ -367,9 +320,8 @@ try:
|
||||
with open(os.environ['SPECKIT_REGISTRY']) as f:
|
||||
data = json.load(f)
|
||||
presets = data.get('presets', {})
|
||||
for pid, meta in sorted(presets.items(), key=lambda x: x[1].get('priority', 10) if isinstance(x[1], dict) else 10):
|
||||
if isinstance(meta, dict) and meta.get('enabled', True) is not False:
|
||||
print(pid)
|
||||
for pid, meta in sorted(presets.items(), key=lambda x: x[1].get('priority', 10)):
|
||||
print(pid)
|
||||
except Exception:
|
||||
sys.exit(1)
|
||||
" 2>/dev/null); then
|
||||
@@ -421,225 +373,3 @@ except Exception:
|
||||
return 1
|
||||
}
|
||||
|
||||
# Resolve a template name to composed content using composition strategies.
|
||||
# Reads strategy metadata from preset manifests and composes content
|
||||
# from multiple layers using prepend, append, or wrap strategies.
|
||||
#
|
||||
# Usage: CONTENT=$(resolve_template_content "template-name" "$REPO_ROOT")
|
||||
# Returns composed content string on stdout; exit code 1 if not found.
|
||||
resolve_template_content() {
|
||||
local template_name="$1"
|
||||
local repo_root="$2"
|
||||
local base="$repo_root/.specify/templates"
|
||||
|
||||
# Collect all layers (highest priority first)
|
||||
local -a layer_paths=()
|
||||
local -a layer_strategies=()
|
||||
|
||||
# Priority 1: Project overrides (always "replace")
|
||||
local override="$base/overrides/${template_name}.md"
|
||||
if [ -f "$override" ]; then
|
||||
layer_paths+=("$override")
|
||||
layer_strategies+=("replace")
|
||||
fi
|
||||
|
||||
# Priority 2: Installed presets (sorted by priority from .registry)
|
||||
local presets_dir="$repo_root/.specify/presets"
|
||||
if [ -d "$presets_dir" ]; then
|
||||
local registry_file="$presets_dir/.registry"
|
||||
local sorted_presets=""
|
||||
if [ -f "$registry_file" ] && command -v python3 >/dev/null 2>&1; then
|
||||
if sorted_presets=$(SPECKIT_REGISTRY="$registry_file" python3 -c "
|
||||
import json, sys, os
|
||||
try:
|
||||
with open(os.environ['SPECKIT_REGISTRY']) as f:
|
||||
data = json.load(f)
|
||||
presets = data.get('presets', {})
|
||||
for pid, meta in sorted(presets.items(), key=lambda x: x[1].get('priority', 10) if isinstance(x[1], dict) else 10):
|
||||
if isinstance(meta, dict) and meta.get('enabled', True) is not False:
|
||||
print(pid)
|
||||
except Exception:
|
||||
sys.exit(1)
|
||||
" 2>/dev/null); then
|
||||
if [ -n "$sorted_presets" ]; then
|
||||
local yaml_warned=false
|
||||
while IFS= read -r preset_id; do
|
||||
# Read strategy and file path from preset manifest
|
||||
local strategy="replace"
|
||||
local manifest_file=""
|
||||
local manifest="$presets_dir/$preset_id/preset.yml"
|
||||
if [ -f "$manifest" ] && command -v python3 >/dev/null 2>&1; then
|
||||
# Requires PyYAML; falls back to replace/convention if unavailable
|
||||
local result
|
||||
local py_stderr
|
||||
py_stderr=$(mktemp)
|
||||
result=$(SPECKIT_MANIFEST="$manifest" SPECKIT_TMPL="$template_name" python3 -c "
|
||||
import sys, os
|
||||
try:
|
||||
import yaml
|
||||
except ImportError:
|
||||
print('yaml_missing', file=sys.stderr)
|
||||
print('replace\t')
|
||||
sys.exit(0)
|
||||
try:
|
||||
with open(os.environ['SPECKIT_MANIFEST']) as f:
|
||||
data = yaml.safe_load(f)
|
||||
for t in data.get('provides', {}).get('templates', []):
|
||||
if t.get('name') == os.environ['SPECKIT_TMPL'] and t.get('type', 'template') == 'template':
|
||||
print(t.get('strategy', 'replace') + '\t' + t.get('file', ''))
|
||||
sys.exit(0)
|
||||
print('replace\t')
|
||||
except Exception:
|
||||
print('replace\t')
|
||||
" 2>"$py_stderr")
|
||||
local parse_status=$?
|
||||
if [ $parse_status -eq 0 ] && [ -n "$result" ]; then
|
||||
IFS=$'\t' read -r strategy manifest_file <<< "$result"
|
||||
strategy=$(printf '%s' "$strategy" | tr '[:upper:]' '[:lower:]')
|
||||
fi
|
||||
if [ "$yaml_warned" = false ] && grep -q 'yaml_missing' "$py_stderr" 2>/dev/null; then
|
||||
echo "Warning: PyYAML not available; composition strategies may be ignored" >&2
|
||||
yaml_warned=true
|
||||
fi
|
||||
rm -f "$py_stderr"
|
||||
fi
|
||||
# Try manifest file path first, then convention path
|
||||
local candidate=""
|
||||
if [ -n "$manifest_file" ]; then
|
||||
# Reject absolute paths and parent traversal
|
||||
case "$manifest_file" in
|
||||
/*|*../*|../*) manifest_file="" ;;
|
||||
esac
|
||||
fi
|
||||
if [ -n "$manifest_file" ]; then
|
||||
local mf="$presets_dir/$preset_id/$manifest_file"
|
||||
[ -f "$mf" ] && candidate="$mf"
|
||||
fi
|
||||
if [ -z "$candidate" ]; then
|
||||
local cf="$presets_dir/$preset_id/templates/${template_name}.md"
|
||||
[ -f "$cf" ] && candidate="$cf"
|
||||
fi
|
||||
if [ -n "$candidate" ]; then
|
||||
layer_paths+=("$candidate")
|
||||
layer_strategies+=("$strategy")
|
||||
fi
|
||||
done <<< "$sorted_presets"
|
||||
fi
|
||||
else
|
||||
# python3 failed — fall back to unordered directory scan (replace only)
|
||||
for preset in "$presets_dir"/*/; do
|
||||
[ -d "$preset" ] || continue
|
||||
local candidate="$preset/templates/${template_name}.md"
|
||||
if [ -f "$candidate" ]; then
|
||||
layer_paths+=("$candidate")
|
||||
layer_strategies+=("replace")
|
||||
fi
|
||||
done
|
||||
fi
|
||||
else
|
||||
# No python3 or registry — fall back to unordered directory scan (replace only)
|
||||
for preset in "$presets_dir"/*/; do
|
||||
[ -d "$preset" ] || continue
|
||||
local candidate="$preset/templates/${template_name}.md"
|
||||
if [ -f "$candidate" ]; then
|
||||
layer_paths+=("$candidate")
|
||||
layer_strategies+=("replace")
|
||||
fi
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
# Priority 3: Extension-provided templates (always "replace")
|
||||
local ext_dir="$repo_root/.specify/extensions"
|
||||
if [ -d "$ext_dir" ]; then
|
||||
for ext in "$ext_dir"/*/; do
|
||||
[ -d "$ext" ] || continue
|
||||
case "$(basename "$ext")" in .*) continue;; esac
|
||||
local candidate="$ext/templates/${template_name}.md"
|
||||
if [ -f "$candidate" ]; then
|
||||
layer_paths+=("$candidate")
|
||||
layer_strategies+=("replace")
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Priority 4: Core templates (always "replace")
|
||||
local core="$base/${template_name}.md"
|
||||
if [ -f "$core" ]; then
|
||||
layer_paths+=("$core")
|
||||
layer_strategies+=("replace")
|
||||
fi
|
||||
|
||||
local count=${#layer_paths[@]}
|
||||
[ "$count" -eq 0 ] && return 1
|
||||
|
||||
# Check if any layer uses a non-replace strategy
|
||||
local has_composition=false
|
||||
for s in "${layer_strategies[@]}"; do
|
||||
[ "$s" != "replace" ] && has_composition=true && break
|
||||
done
|
||||
|
||||
# If the top (highest-priority) layer is replace, it wins entirely —
|
||||
# lower layers are irrelevant regardless of their strategies.
|
||||
if [ "${layer_strategies[0]}" = "replace" ]; then
|
||||
cat "${layer_paths[0]}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ "$has_composition" = false ]; then
|
||||
cat "${layer_paths[0]}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Find the effective base: scan from highest priority (index 0) downward
|
||||
# to find the nearest replace layer. Only compose layers above that base.
|
||||
local base_idx=-1
|
||||
local i
|
||||
for (( i=0; i<count; i++ )); do
|
||||
if [ "${layer_strategies[$i]}" = "replace" ]; then
|
||||
base_idx=$i
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $base_idx -lt 0 ]; then
|
||||
return 1 # no base layer found
|
||||
fi
|
||||
|
||||
# Read the base content; compose layers above the base (higher priority)
|
||||
local content
|
||||
content=$(cat "${layer_paths[$base_idx]}"; printf x)
|
||||
content="${content%x}"
|
||||
|
||||
for (( i=base_idx-1; i>=0; i-- )); do
|
||||
local path="${layer_paths[$i]}"
|
||||
local strat="${layer_strategies[$i]}"
|
||||
local layer_content
|
||||
# Preserve trailing newlines
|
||||
layer_content=$(cat "$path"; printf x)
|
||||
layer_content="${layer_content%x}"
|
||||
|
||||
case "$strat" in
|
||||
replace) content="$layer_content" ;;
|
||||
prepend) content="$(printf '%s\n\n%s' "$layer_content" "$content")" ;;
|
||||
append) content="$(printf '%s\n\n%s' "$content" "$layer_content")" ;;
|
||||
wrap)
|
||||
case "$layer_content" in
|
||||
*'{CORE_TEMPLATE}'*) ;;
|
||||
*) echo "Error: wrap strategy missing {CORE_TEMPLATE} placeholder" >&2; return 1 ;;
|
||||
esac
|
||||
while [[ "$layer_content" == *'{CORE_TEMPLATE}'* ]]; do
|
||||
local before="${layer_content%%\{CORE_TEMPLATE\}*}"
|
||||
local after="${layer_content#*\{CORE_TEMPLATE\}}"
|
||||
layer_content="${before}${content}${after}"
|
||||
done
|
||||
content="$layer_content"
|
||||
;;
|
||||
*) echo "Error: unknown strategy '$strat'" >&2; return 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
printf '%s' "$content"
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ if [ -z "$FEATURE_DESCRIPTION" ]; then
|
||||
fi
|
||||
|
||||
# Trim whitespace and validate description is not empty (e.g., user passed only whitespace)
|
||||
FEATURE_DESCRIPTION=$(echo "$FEATURE_DESCRIPTION" | sed -E 's/^[[:space:]]+|[[:space:]]+$//g')
|
||||
FEATURE_DESCRIPTION=$(echo "$FEATURE_DESCRIPTION" | xargs)
|
||||
if [ -z "$FEATURE_DESCRIPTION" ]; then
|
||||
echo "Error: Feature description cannot be empty or contain only whitespace" >&2
|
||||
exit 1
|
||||
|
||||
@@ -32,10 +32,8 @@ _paths_output=$(get_feature_paths) || { echo "ERROR: Failed to resolve feature p
|
||||
eval "$_paths_output"
|
||||
unset _paths_output
|
||||
|
||||
# If feature.json pins an existing feature directory, branch naming is not required.
|
||||
if ! feature_json_matches_feature_dir "$REPO_ROOT" "$FEATURE_DIR"; then
|
||||
check_feature_branch "$CURRENT_BRANCH" "$HAS_GIT" || exit 1
|
||||
fi
|
||||
# Check if we're on a proper feature branch (only for git repos)
|
||||
check_feature_branch "$CURRENT_BRANCH" "$HAS_GIT" || exit 1
|
||||
|
||||
# Ensure the feature directory exists
|
||||
mkdir -p "$FEATURE_DIR"
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
# Parse command line arguments
|
||||
JSON_MODE=false
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--json) JSON_MODE=true ;;
|
||||
--help|-h)
|
||||
echo "Usage: $0 [--json]"
|
||||
echo " --json Output results in JSON format"
|
||||
echo " --help Show this help message"
|
||||
exit 0
|
||||
;;
|
||||
*) echo "ERROR: Unknown option '$arg'" >&2; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Source common functions
|
||||
SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/common.sh"
|
||||
|
||||
# Get feature paths
|
||||
_paths_output=$(get_feature_paths) || { echo "ERROR: Failed to resolve feature paths" >&2; exit 1; }
|
||||
eval "$_paths_output"
|
||||
unset _paths_output
|
||||
|
||||
# Validate branch
|
||||
# If feature.json pins an existing feature directory, branch naming is not required.
|
||||
if ! feature_json_matches_feature_dir "$REPO_ROOT" "$FEATURE_DIR"; then
|
||||
check_feature_branch "$CURRENT_BRANCH" "$HAS_GIT" || exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "$IMPL_PLAN" ]]; then
|
||||
echo "ERROR: plan.md not found in $FEATURE_DIR" >&2
|
||||
echo "Run /speckit.plan first to create the implementation plan." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "$FEATURE_SPEC" ]]; then
|
||||
echo "ERROR: spec.md not found in $FEATURE_DIR" >&2
|
||||
echo "Run /speckit.specify first to create the feature structure." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Build available docs list
|
||||
docs=()
|
||||
[[ -f "$RESEARCH" ]] && docs+=("research.md")
|
||||
[[ -f "$DATA_MODEL" ]] && docs+=("data-model.md")
|
||||
if [[ -d "$CONTRACTS_DIR" ]] && [[ -n "$(ls -A "$CONTRACTS_DIR" 2>/dev/null)" ]]; then
|
||||
docs+=("contracts/")
|
||||
fi
|
||||
[[ -f "$QUICKSTART" ]] && docs+=("quickstart.md")
|
||||
|
||||
# Resolve tasks template through override stack
|
||||
TASKS_TEMPLATE=$(resolve_template "tasks-template" "$REPO_ROOT") || true
|
||||
if [[ -z "$TASKS_TEMPLATE" ]] || [[ ! -f "$TASKS_TEMPLATE" ]]; then
|
||||
echo "ERROR: Could not resolve required tasks-template from the template override stack for $REPO_ROOT" >&2
|
||||
echo "Template 'tasks-template' was not found in any supported location (overrides, presets, extensions, or shared core). Add an override at .specify/templates/overrides/tasks-template.md, or run 'specify init' / reinstall shared infra to restore the core .specify/templates/tasks-template.md template." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Output results
|
||||
if $JSON_MODE; then
|
||||
if has_jq; then
|
||||
if [[ ${#docs[@]} -eq 0 ]]; then
|
||||
json_docs="[]"
|
||||
else
|
||||
json_docs=$(printf '%s\n' "${docs[@]}" | jq -R . | jq -s .)
|
||||
fi
|
||||
jq -cn \
|
||||
--arg feature_dir "$FEATURE_DIR" \
|
||||
--argjson docs "$json_docs" \
|
||||
--arg tasks_template "${TASKS_TEMPLATE:-}" \
|
||||
'{FEATURE_DIR:$feature_dir,AVAILABLE_DOCS:$docs,TASKS_TEMPLATE:$tasks_template}'
|
||||
else
|
||||
if [[ ${#docs[@]} -eq 0 ]]; then
|
||||
json_docs="[]"
|
||||
else
|
||||
json_docs=$(for d in "${docs[@]}"; do printf '"%s",' "$(json_escape "$d")"; done)
|
||||
json_docs="[${json_docs%,}]"
|
||||
fi
|
||||
printf '{"FEATURE_DIR":"%s","AVAILABLE_DOCS":%s,"TASKS_TEMPLATE":"%s"}\n' \
|
||||
"$(json_escape "$FEATURE_DIR")" "$json_docs" "$(json_escape "${TASKS_TEMPLATE:-}")"
|
||||
fi
|
||||
else
|
||||
echo "FEATURE_DIR: $FEATURE_DIR"
|
||||
echo "TASKS_TEMPLATE: ${TASKS_TEMPLATE:-not found}"
|
||||
echo "AVAILABLE_DOCS:"
|
||||
check_file "$RESEARCH" "research.md"
|
||||
check_file "$DATA_MODEL" "data-model.md"
|
||||
check_dir "$CONTRACTS_DIR" "contracts/"
|
||||
check_file "$QUICKSTART" "quickstart.md"
|
||||
fi
|
||||
857
scripts/bash/update-agent-context.sh
Normal file
857
scripts/bash/update-agent-context.sh
Normal file
@@ -0,0 +1,857 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Update agent context files with information from plan.md
|
||||
#
|
||||
# This script maintains AI agent context files by parsing feature specifications
|
||||
# and updating agent-specific configuration files with project information.
|
||||
#
|
||||
# MAIN FUNCTIONS:
|
||||
# 1. Environment Validation
|
||||
# - Verifies git repository structure and branch information
|
||||
# - Checks for required plan.md files and templates
|
||||
# - Validates file permissions and accessibility
|
||||
#
|
||||
# 2. Plan Data Extraction
|
||||
# - Parses plan.md files to extract project metadata
|
||||
# - Identifies language/version, frameworks, databases, and project types
|
||||
# - Handles missing or incomplete specification data gracefully
|
||||
#
|
||||
# 3. Agent File Management
|
||||
# - Creates new agent context files from templates when needed
|
||||
# - Updates existing agent files with new project information
|
||||
# - Preserves manual additions and custom configurations
|
||||
# - Supports multiple AI agent formats and directory structures
|
||||
#
|
||||
# 4. Content Generation
|
||||
# - Generates language-specific build/test commands
|
||||
# - Creates appropriate project directory structures
|
||||
# - Updates technology stacks and recent changes sections
|
||||
# - Maintains consistent formatting and timestamps
|
||||
#
|
||||
# 5. Multi-Agent Support
|
||||
# - Handles agent-specific file paths and naming conventions
|
||||
# - 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, Forge, Goose, Antigravity or Generic
|
||||
# - Can update single agents or all existing agent files
|
||||
# - Creates default Claude file if no agent files exist
|
||||
#
|
||||
# Usage: ./update-agent-context.sh [agent_type]
|
||||
# Agent types: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|junie|kilocode|auggie|roo|codebuddy|amp|shai|tabnine|kiro-cli|agy|bob|vibe|qodercli|kimi|trae|pi|iflow|forge|goose|generic
|
||||
# Leave empty to update all existing agent files
|
||||
|
||||
set -e
|
||||
|
||||
# Enable strict error handling
|
||||
set -u
|
||||
set -o pipefail
|
||||
|
||||
#==============================================================================
|
||||
# Configuration and Global Variables
|
||||
#==============================================================================
|
||||
|
||||
# Get script directory and load common functions
|
||||
SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/common.sh"
|
||||
|
||||
# Get all paths and variables from common functions
|
||||
_paths_output=$(get_feature_paths) || { echo "ERROR: Failed to resolve feature paths" >&2; exit 1; }
|
||||
eval "$_paths_output"
|
||||
unset _paths_output
|
||||
|
||||
NEW_PLAN="$IMPL_PLAN" # Alias for compatibility with existing code
|
||||
AGENT_TYPE="${1:-}"
|
||||
|
||||
# Agent-specific file paths
|
||||
CLAUDE_FILE="$REPO_ROOT/CLAUDE.md"
|
||||
GEMINI_FILE="$REPO_ROOT/GEMINI.md"
|
||||
COPILOT_FILE="$REPO_ROOT/.github/copilot-instructions.md"
|
||||
CURSOR_FILE="$REPO_ROOT/.cursor/rules/specify-rules.mdc"
|
||||
QWEN_FILE="$REPO_ROOT/QWEN.md"
|
||||
AGENTS_FILE="$REPO_ROOT/AGENTS.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"
|
||||
AUGGIE_FILE="$REPO_ROOT/.augment/rules/specify-rules.md"
|
||||
ROO_FILE="$REPO_ROOT/.roo/rules/specify-rules.md"
|
||||
CODEBUDDY_FILE="$REPO_ROOT/CODEBUDDY.md"
|
||||
QODER_FILE="$REPO_ROOT/QODER.md"
|
||||
# Amp, Kiro CLI, IBM Bob, Pi, Forge, and Goose all share AGENTS.md — use AGENTS_FILE to avoid
|
||||
# updating the same file multiple times.
|
||||
AMP_FILE="$AGENTS_FILE"
|
||||
SHAI_FILE="$REPO_ROOT/SHAI.md"
|
||||
TABNINE_FILE="$REPO_ROOT/TABNINE.md"
|
||||
KIRO_FILE="$AGENTS_FILE"
|
||||
AGY_FILE="$REPO_ROOT/.agent/rules/specify-rules.md"
|
||||
BOB_FILE="$AGENTS_FILE"
|
||||
VIBE_FILE="$REPO_ROOT/.vibe/agents/specify-agents.md"
|
||||
KIMI_FILE="$REPO_ROOT/KIMI.md"
|
||||
TRAE_FILE="$REPO_ROOT/.trae/rules/project_rules.md"
|
||||
IFLOW_FILE="$REPO_ROOT/IFLOW.md"
|
||||
FORGE_FILE="$AGENTS_FILE"
|
||||
|
||||
# Template file
|
||||
TEMPLATE_FILE="$REPO_ROOT/.specify/templates/agent-file-template.md"
|
||||
|
||||
# Global variables for parsed plan data
|
||||
NEW_LANG=""
|
||||
NEW_FRAMEWORK=""
|
||||
NEW_DB=""
|
||||
NEW_PROJECT_TYPE=""
|
||||
|
||||
#==============================================================================
|
||||
# Utility Functions
|
||||
#==============================================================================
|
||||
|
||||
log_info() {
|
||||
echo "INFO: $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo "✓ $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo "ERROR: $1" >&2
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo "WARNING: $1" >&2
|
||||
}
|
||||
|
||||
# Track temporary files for cleanup on interrupt
|
||||
_CLEANUP_FILES=()
|
||||
|
||||
# Cleanup function for temporary files
|
||||
cleanup() {
|
||||
local exit_code=$?
|
||||
# Disarm traps to prevent re-entrant loop
|
||||
trap - EXIT INT TERM
|
||||
if [ ${#_CLEANUP_FILES[@]} -gt 0 ]; then
|
||||
for f in "${_CLEANUP_FILES[@]}"; do
|
||||
rm -f "$f" "$f.bak" "$f.tmp"
|
||||
done
|
||||
fi
|
||||
exit $exit_code
|
||||
}
|
||||
|
||||
# Set up cleanup trap
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
#==============================================================================
|
||||
# Validation Functions
|
||||
#==============================================================================
|
||||
|
||||
validate_environment() {
|
||||
# Check if we have a current branch/feature (git or non-git)
|
||||
if [[ -z "$CURRENT_BRANCH" ]]; then
|
||||
log_error "Unable to determine current feature"
|
||||
if [[ "$HAS_GIT" == "true" ]]; then
|
||||
log_info "Make sure you're on a feature branch"
|
||||
else
|
||||
log_info "Set SPECIFY_FEATURE environment variable or create a feature first"
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if plan.md exists
|
||||
if [[ ! -f "$NEW_PLAN" ]]; then
|
||||
log_error "No plan.md found at $NEW_PLAN"
|
||||
log_info "Make sure you're working on a feature with a corresponding spec directory"
|
||||
if [[ "$HAS_GIT" != "true" ]]; then
|
||||
log_info "Use: export SPECIFY_FEATURE=your-feature-name or create a new feature first"
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if template exists (needed for new files)
|
||||
if [[ ! -f "$TEMPLATE_FILE" ]]; then
|
||||
log_warning "Template file not found at $TEMPLATE_FILE"
|
||||
log_warning "Creating new agent files will fail"
|
||||
fi
|
||||
}
|
||||
|
||||
#==============================================================================
|
||||
# Plan Parsing Functions
|
||||
#==============================================================================
|
||||
|
||||
extract_plan_field() {
|
||||
local field_pattern="$1"
|
||||
local plan_file="$2"
|
||||
|
||||
grep "^\*\*${field_pattern}\*\*: " "$plan_file" 2>/dev/null | \
|
||||
head -1 | \
|
||||
sed "s|^\*\*${field_pattern}\*\*: ||" | \
|
||||
sed 's/^[ \t]*//;s/[ \t]*$//' | \
|
||||
grep -v "NEEDS CLARIFICATION" | \
|
||||
grep -v "^N/A$" || echo ""
|
||||
}
|
||||
|
||||
parse_plan_data() {
|
||||
local plan_file="$1"
|
||||
|
||||
if [[ ! -f "$plan_file" ]]; then
|
||||
log_error "Plan file not found: $plan_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ ! -r "$plan_file" ]]; then
|
||||
log_error "Plan file is not readable: $plan_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "Parsing plan data from $plan_file"
|
||||
|
||||
NEW_LANG=$(extract_plan_field "Language/Version" "$plan_file")
|
||||
NEW_FRAMEWORK=$(extract_plan_field "Primary Dependencies" "$plan_file")
|
||||
NEW_DB=$(extract_plan_field "Storage" "$plan_file")
|
||||
NEW_PROJECT_TYPE=$(extract_plan_field "Project Type" "$plan_file")
|
||||
|
||||
# Log what we found
|
||||
if [[ -n "$NEW_LANG" ]]; then
|
||||
log_info "Found language: $NEW_LANG"
|
||||
else
|
||||
log_warning "No language information found in plan"
|
||||
fi
|
||||
|
||||
if [[ -n "$NEW_FRAMEWORK" ]]; then
|
||||
log_info "Found framework: $NEW_FRAMEWORK"
|
||||
fi
|
||||
|
||||
if [[ -n "$NEW_DB" ]] && [[ "$NEW_DB" != "N/A" ]]; then
|
||||
log_info "Found database: $NEW_DB"
|
||||
fi
|
||||
|
||||
if [[ -n "$NEW_PROJECT_TYPE" ]]; then
|
||||
log_info "Found project type: $NEW_PROJECT_TYPE"
|
||||
fi
|
||||
}
|
||||
|
||||
format_technology_stack() {
|
||||
local lang="$1"
|
||||
local framework="$2"
|
||||
local parts=()
|
||||
|
||||
# Add non-empty parts
|
||||
[[ -n "$lang" && "$lang" != "NEEDS CLARIFICATION" ]] && parts+=("$lang")
|
||||
[[ -n "$framework" && "$framework" != "NEEDS CLARIFICATION" && "$framework" != "N/A" ]] && parts+=("$framework")
|
||||
|
||||
# Join with proper formatting
|
||||
if [[ ${#parts[@]} -eq 0 ]]; then
|
||||
echo ""
|
||||
elif [[ ${#parts[@]} -eq 1 ]]; then
|
||||
echo "${parts[0]}"
|
||||
else
|
||||
# Join multiple parts with " + "
|
||||
local result="${parts[0]}"
|
||||
for ((i=1; i<${#parts[@]}; i++)); do
|
||||
result="$result + ${parts[i]}"
|
||||
done
|
||||
echo "$result"
|
||||
fi
|
||||
}
|
||||
|
||||
#==============================================================================
|
||||
# Template and Content Generation Functions
|
||||
#==============================================================================
|
||||
|
||||
get_project_structure() {
|
||||
local project_type="$1"
|
||||
|
||||
if [[ "$project_type" == *"web"* ]]; then
|
||||
echo "backend/\\nfrontend/\\ntests/"
|
||||
else
|
||||
echo "src/\\ntests/"
|
||||
fi
|
||||
}
|
||||
|
||||
get_commands_for_language() {
|
||||
local lang="$1"
|
||||
|
||||
case "$lang" in
|
||||
*"Python"*)
|
||||
echo "cd src && pytest && ruff check ."
|
||||
;;
|
||||
*"Rust"*)
|
||||
echo "cargo test && cargo clippy"
|
||||
;;
|
||||
*"JavaScript"*|*"TypeScript"*)
|
||||
echo "npm test && npm run lint"
|
||||
;;
|
||||
*)
|
||||
echo "# Add commands for $lang"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
get_language_conventions() {
|
||||
local lang="$1"
|
||||
echo "$lang: Follow standard conventions"
|
||||
}
|
||||
|
||||
# Escape sed replacement-side specials for | delimiter.
|
||||
# & and \ are replacement-side specials; | is our sed delimiter.
|
||||
_esc_sed() { printf '%s\n' "$1" | sed 's/[\\&|]/\\&/g'; }
|
||||
|
||||
create_new_agent_file() {
|
||||
local target_file="$1"
|
||||
local temp_file="$2"
|
||||
local project_name
|
||||
project_name=$(_esc_sed "$3")
|
||||
local current_date="$4"
|
||||
|
||||
if [[ ! -f "$TEMPLATE_FILE" ]]; then
|
||||
log_error "Template not found at $TEMPLATE_FILE"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ ! -r "$TEMPLATE_FILE" ]]; then
|
||||
log_error "Template file is not readable: $TEMPLATE_FILE"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "Creating new agent context file from template..."
|
||||
|
||||
if ! cp "$TEMPLATE_FILE" "$temp_file"; then
|
||||
log_error "Failed to copy template file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Replace template placeholders
|
||||
local project_structure
|
||||
project_structure=$(get_project_structure "$NEW_PROJECT_TYPE")
|
||||
project_structure=$(_esc_sed "$project_structure")
|
||||
|
||||
local commands
|
||||
commands=$(get_commands_for_language "$NEW_LANG")
|
||||
|
||||
local language_conventions
|
||||
language_conventions=$(get_language_conventions "$NEW_LANG")
|
||||
|
||||
local escaped_lang=$(_esc_sed "$NEW_LANG")
|
||||
local escaped_framework=$(_esc_sed "$NEW_FRAMEWORK")
|
||||
commands=$(_esc_sed "$commands")
|
||||
language_conventions=$(_esc_sed "$language_conventions")
|
||||
local escaped_branch=$(_esc_sed "$CURRENT_BRANCH")
|
||||
|
||||
# Build technology stack and recent change strings conditionally
|
||||
local tech_stack
|
||||
if [[ -n "$escaped_lang" && -n "$escaped_framework" ]]; then
|
||||
tech_stack="- $escaped_lang + $escaped_framework ($escaped_branch)"
|
||||
elif [[ -n "$escaped_lang" ]]; then
|
||||
tech_stack="- $escaped_lang ($escaped_branch)"
|
||||
elif [[ -n "$escaped_framework" ]]; then
|
||||
tech_stack="- $escaped_framework ($escaped_branch)"
|
||||
else
|
||||
tech_stack="- ($escaped_branch)"
|
||||
fi
|
||||
|
||||
local recent_change
|
||||
if [[ -n "$escaped_lang" && -n "$escaped_framework" ]]; then
|
||||
recent_change="- $escaped_branch: Added $escaped_lang + $escaped_framework"
|
||||
elif [[ -n "$escaped_lang" ]]; then
|
||||
recent_change="- $escaped_branch: Added $escaped_lang"
|
||||
elif [[ -n "$escaped_framework" ]]; then
|
||||
recent_change="- $escaped_branch: Added $escaped_framework"
|
||||
else
|
||||
recent_change="- $escaped_branch: Added"
|
||||
fi
|
||||
|
||||
local substitutions=(
|
||||
"s|\[PROJECT NAME\]|$project_name|"
|
||||
"s|\[DATE\]|$current_date|"
|
||||
"s|\[EXTRACTED FROM ALL PLAN.MD FILES\]|$tech_stack|"
|
||||
"s|\[ACTUAL STRUCTURE FROM PLANS\]|$project_structure|g"
|
||||
"s|\[ONLY COMMANDS FOR ACTIVE TECHNOLOGIES\]|$commands|"
|
||||
"s|\[LANGUAGE-SPECIFIC, ONLY FOR LANGUAGES IN USE\]|$language_conventions|"
|
||||
"s|\[LAST 3 FEATURES AND WHAT THEY ADDED\]|$recent_change|"
|
||||
)
|
||||
|
||||
for substitution in "${substitutions[@]}"; do
|
||||
if ! sed -i.bak -e "$substitution" "$temp_file"; then
|
||||
log_error "Failed to perform substitution: $substitution"
|
||||
rm -f "$temp_file" "$temp_file.bak"
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Convert literal \n sequences to actual newlines (portable — works on BSD + GNU)
|
||||
awk '{gsub(/\\n/,"\n")}1' "$temp_file" > "$temp_file.tmp"
|
||||
mv "$temp_file.tmp" "$temp_file"
|
||||
|
||||
# Clean up backup files from sed -i.bak
|
||||
rm -f "$temp_file.bak"
|
||||
|
||||
# Prepend Cursor frontmatter for .mdc files so rules are auto-included
|
||||
if [[ "$target_file" == *.mdc ]]; then
|
||||
local frontmatter_file
|
||||
frontmatter_file=$(mktemp) || return 1
|
||||
_CLEANUP_FILES+=("$frontmatter_file")
|
||||
printf '%s\n' "---" "description: Project Development Guidelines" "globs: [\"**/*\"]" "alwaysApply: true" "---" "" > "$frontmatter_file"
|
||||
cat "$temp_file" >> "$frontmatter_file"
|
||||
mv "$frontmatter_file" "$temp_file"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
update_existing_agent_file() {
|
||||
local target_file="$1"
|
||||
local current_date="$2"
|
||||
|
||||
log_info "Updating existing agent context file..."
|
||||
|
||||
# Use a single temporary file for atomic update
|
||||
local temp_file
|
||||
temp_file=$(mktemp) || {
|
||||
log_error "Failed to create temporary file"
|
||||
return 1
|
||||
}
|
||||
_CLEANUP_FILES+=("$temp_file")
|
||||
|
||||
# Process the file in one pass
|
||||
local tech_stack=$(format_technology_stack "$NEW_LANG" "$NEW_FRAMEWORK")
|
||||
local new_tech_entries=()
|
||||
local new_change_entry=""
|
||||
|
||||
# Prepare new technology entries
|
||||
if [[ -n "$tech_stack" ]] && ! grep -q "$tech_stack" "$target_file"; then
|
||||
new_tech_entries+=("- $tech_stack ($CURRENT_BRANCH)")
|
||||
fi
|
||||
|
||||
if [[ -n "$NEW_DB" ]] && [[ "$NEW_DB" != "N/A" ]] && [[ "$NEW_DB" != "NEEDS CLARIFICATION" ]] && ! grep -q "$NEW_DB" "$target_file"; then
|
||||
new_tech_entries+=("- $NEW_DB ($CURRENT_BRANCH)")
|
||||
fi
|
||||
|
||||
# Prepare new change entry
|
||||
if [[ -n "$tech_stack" ]]; then
|
||||
new_change_entry="- $CURRENT_BRANCH: Added $tech_stack"
|
||||
elif [[ -n "$NEW_DB" ]] && [[ "$NEW_DB" != "N/A" ]] && [[ "$NEW_DB" != "NEEDS CLARIFICATION" ]]; then
|
||||
new_change_entry="- $CURRENT_BRANCH: Added $NEW_DB"
|
||||
fi
|
||||
|
||||
# Check if sections exist in the file
|
||||
local has_active_technologies=0
|
||||
local has_recent_changes=0
|
||||
|
||||
if grep -q "^## Active Technologies" "$target_file" 2>/dev/null; then
|
||||
has_active_technologies=1
|
||||
fi
|
||||
|
||||
if grep -q "^## Recent Changes" "$target_file" 2>/dev/null; then
|
||||
has_recent_changes=1
|
||||
fi
|
||||
|
||||
# Process file line by line
|
||||
local in_tech_section=false
|
||||
local in_changes_section=false
|
||||
local tech_entries_added=false
|
||||
local changes_entries_added=false
|
||||
local existing_changes_count=0
|
||||
local file_ended=false
|
||||
|
||||
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||
# Handle Active Technologies section
|
||||
if [[ "$line" == "## Active Technologies" ]]; then
|
||||
echo "$line" >> "$temp_file"
|
||||
in_tech_section=true
|
||||
continue
|
||||
elif [[ $in_tech_section == true ]] && [[ "$line" =~ ^##[[:space:]] ]]; then
|
||||
# Add new tech entries before closing the section
|
||||
if [[ $tech_entries_added == false ]] && [[ ${#new_tech_entries[@]} -gt 0 ]]; then
|
||||
printf '%s\n' "${new_tech_entries[@]}" >> "$temp_file"
|
||||
tech_entries_added=true
|
||||
fi
|
||||
echo "$line" >> "$temp_file"
|
||||
in_tech_section=false
|
||||
continue
|
||||
elif [[ $in_tech_section == true ]] && [[ -z "$line" ]]; then
|
||||
# Add new tech entries before empty line in tech section
|
||||
if [[ $tech_entries_added == false ]] && [[ ${#new_tech_entries[@]} -gt 0 ]]; then
|
||||
printf '%s\n' "${new_tech_entries[@]}" >> "$temp_file"
|
||||
tech_entries_added=true
|
||||
fi
|
||||
echo "$line" >> "$temp_file"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Handle Recent Changes section
|
||||
if [[ "$line" == "## Recent Changes" ]]; then
|
||||
echo "$line" >> "$temp_file"
|
||||
# Add new change entry right after the heading
|
||||
if [[ -n "$new_change_entry" ]]; then
|
||||
echo "$new_change_entry" >> "$temp_file"
|
||||
fi
|
||||
in_changes_section=true
|
||||
changes_entries_added=true
|
||||
continue
|
||||
elif [[ $in_changes_section == true ]] && [[ "$line" =~ ^##[[:space:]] ]]; then
|
||||
echo "$line" >> "$temp_file"
|
||||
in_changes_section=false
|
||||
continue
|
||||
elif [[ $in_changes_section == true ]] && [[ "$line" == "- "* ]]; then
|
||||
# Keep only first 2 existing changes
|
||||
if [[ $existing_changes_count -lt 2 ]]; then
|
||||
echo "$line" >> "$temp_file"
|
||||
((existing_changes_count++))
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
|
||||
# Update timestamp
|
||||
if [[ "$line" =~ (\*\*)?Last\ updated(\*\*)?:.*[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] ]]; then
|
||||
echo "$line" | sed "s/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/$current_date/" >> "$temp_file"
|
||||
else
|
||||
echo "$line" >> "$temp_file"
|
||||
fi
|
||||
done < "$target_file"
|
||||
|
||||
# Post-loop check: if we're still in the Active Technologies section and haven't added new entries
|
||||
if [[ $in_tech_section == true ]] && [[ $tech_entries_added == false ]] && [[ ${#new_tech_entries[@]} -gt 0 ]]; then
|
||||
printf '%s\n' "${new_tech_entries[@]}" >> "$temp_file"
|
||||
tech_entries_added=true
|
||||
fi
|
||||
|
||||
# If sections don't exist, add them at the end of the file
|
||||
if [[ $has_active_technologies -eq 0 ]] && [[ ${#new_tech_entries[@]} -gt 0 ]]; then
|
||||
echo "" >> "$temp_file"
|
||||
echo "## Active Technologies" >> "$temp_file"
|
||||
printf '%s\n' "${new_tech_entries[@]}" >> "$temp_file"
|
||||
tech_entries_added=true
|
||||
fi
|
||||
|
||||
if [[ $has_recent_changes -eq 0 ]] && [[ -n "$new_change_entry" ]]; then
|
||||
echo "" >> "$temp_file"
|
||||
echo "## Recent Changes" >> "$temp_file"
|
||||
echo "$new_change_entry" >> "$temp_file"
|
||||
changes_entries_added=true
|
||||
fi
|
||||
|
||||
# Ensure Cursor .mdc files have YAML frontmatter for auto-inclusion
|
||||
if [[ "$target_file" == *.mdc ]]; then
|
||||
if ! head -1 "$temp_file" | grep -q '^---'; then
|
||||
local frontmatter_file
|
||||
frontmatter_file=$(mktemp) || { rm -f "$temp_file"; return 1; }
|
||||
_CLEANUP_FILES+=("$frontmatter_file")
|
||||
printf '%s\n' "---" "description: Project Development Guidelines" "globs: [\"**/*\"]" "alwaysApply: true" "---" "" > "$frontmatter_file"
|
||||
cat "$temp_file" >> "$frontmatter_file"
|
||||
mv "$frontmatter_file" "$temp_file"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Move temp file to target atomically
|
||||
if ! mv "$temp_file" "$target_file"; then
|
||||
log_error "Failed to update target file"
|
||||
rm -f "$temp_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
#==============================================================================
|
||||
# Main Agent File Update Function
|
||||
#==============================================================================
|
||||
|
||||
update_agent_file() {
|
||||
local target_file="$1"
|
||||
local agent_name="$2"
|
||||
|
||||
if [[ -z "$target_file" ]] || [[ -z "$agent_name" ]]; then
|
||||
log_error "update_agent_file requires target_file and agent_name parameters"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "Updating $agent_name context file: $target_file"
|
||||
|
||||
local project_name
|
||||
project_name=$(basename "$REPO_ROOT")
|
||||
local current_date
|
||||
current_date=$(date +%Y-%m-%d)
|
||||
|
||||
# Create directory if it doesn't exist
|
||||
local target_dir
|
||||
target_dir=$(dirname "$target_file")
|
||||
if [[ ! -d "$target_dir" ]]; then
|
||||
if ! mkdir -p "$target_dir"; then
|
||||
log_error "Failed to create directory: $target_dir"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ ! -f "$target_file" ]]; then
|
||||
# Create new file from template
|
||||
local temp_file
|
||||
temp_file=$(mktemp) || {
|
||||
log_error "Failed to create temporary file"
|
||||
return 1
|
||||
}
|
||||
_CLEANUP_FILES+=("$temp_file")
|
||||
|
||||
if create_new_agent_file "$target_file" "$temp_file" "$project_name" "$current_date"; then
|
||||
if mv "$temp_file" "$target_file"; then
|
||||
log_success "Created new $agent_name context file"
|
||||
else
|
||||
log_error "Failed to move temporary file to $target_file"
|
||||
rm -f "$temp_file"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
log_error "Failed to create new agent file"
|
||||
rm -f "$temp_file"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
# Update existing file
|
||||
if [[ ! -r "$target_file" ]]; then
|
||||
log_error "Cannot read existing file: $target_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ ! -w "$target_file" ]]; then
|
||||
log_error "Cannot write to existing file: $target_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if update_existing_agent_file "$target_file" "$current_date"; then
|
||||
log_success "Updated existing $agent_name context file"
|
||||
else
|
||||
log_error "Failed to update existing agent file"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
#==============================================================================
|
||||
# Agent Selection and Processing
|
||||
#==============================================================================
|
||||
|
||||
update_specific_agent() {
|
||||
local agent_type="$1"
|
||||
|
||||
case "$agent_type" in
|
||||
claude)
|
||||
update_agent_file "$CLAUDE_FILE" "Claude Code" || return 1
|
||||
;;
|
||||
gemini)
|
||||
update_agent_file "$GEMINI_FILE" "Gemini CLI" || return 1
|
||||
;;
|
||||
copilot)
|
||||
update_agent_file "$COPILOT_FILE" "GitHub Copilot" || return 1
|
||||
;;
|
||||
cursor-agent)
|
||||
update_agent_file "$CURSOR_FILE" "Cursor IDE" || return 1
|
||||
;;
|
||||
qwen)
|
||||
update_agent_file "$QWEN_FILE" "Qwen Code" || return 1
|
||||
;;
|
||||
opencode)
|
||||
update_agent_file "$AGENTS_FILE" "opencode" || return 1
|
||||
;;
|
||||
codex)
|
||||
update_agent_file "$AGENTS_FILE" "Codex CLI" || return 1
|
||||
;;
|
||||
windsurf)
|
||||
update_agent_file "$WINDSURF_FILE" "Windsurf" || return 1
|
||||
;;
|
||||
junie)
|
||||
update_agent_file "$JUNIE_FILE" "Junie" || return 1
|
||||
;;
|
||||
kilocode)
|
||||
update_agent_file "$KILOCODE_FILE" "Kilo Code" || return 1
|
||||
;;
|
||||
auggie)
|
||||
update_agent_file "$AUGGIE_FILE" "Auggie CLI" || return 1
|
||||
;;
|
||||
roo)
|
||||
update_agent_file "$ROO_FILE" "Roo Code" || return 1
|
||||
;;
|
||||
codebuddy)
|
||||
update_agent_file "$CODEBUDDY_FILE" "CodeBuddy CLI" || return 1
|
||||
;;
|
||||
qodercli)
|
||||
update_agent_file "$QODER_FILE" "Qoder CLI" || return 1
|
||||
;;
|
||||
amp)
|
||||
update_agent_file "$AMP_FILE" "Amp" || return 1
|
||||
;;
|
||||
shai)
|
||||
update_agent_file "$SHAI_FILE" "SHAI" || return 1
|
||||
;;
|
||||
tabnine)
|
||||
update_agent_file "$TABNINE_FILE" "Tabnine CLI" || return 1
|
||||
;;
|
||||
kiro-cli)
|
||||
update_agent_file "$KIRO_FILE" "Kiro CLI" || return 1
|
||||
;;
|
||||
agy)
|
||||
update_agent_file "$AGY_FILE" "Antigravity" || return 1
|
||||
;;
|
||||
bob)
|
||||
update_agent_file "$BOB_FILE" "IBM Bob" || return 1
|
||||
;;
|
||||
vibe)
|
||||
update_agent_file "$VIBE_FILE" "Mistral Vibe" || return 1
|
||||
;;
|
||||
kimi)
|
||||
update_agent_file "$KIMI_FILE" "Kimi Code" || return 1
|
||||
;;
|
||||
trae)
|
||||
update_agent_file "$TRAE_FILE" "Trae" || return 1
|
||||
;;
|
||||
pi)
|
||||
update_agent_file "$AGENTS_FILE" "Pi Coding Agent" || return 1
|
||||
;;
|
||||
iflow)
|
||||
update_agent_file "$IFLOW_FILE" "iFlow CLI" || return 1
|
||||
;;
|
||||
forge)
|
||||
update_agent_file "$AGENTS_FILE" "Forge" || return 1
|
||||
;;
|
||||
goose)
|
||||
update_agent_file "$AGENTS_FILE" "Goose" || return 1
|
||||
;;
|
||||
generic)
|
||||
log_info "Generic agent: no predefined context file. Use the agent-specific update script for your agent."
|
||||
;;
|
||||
*)
|
||||
log_error "Unknown agent type '$agent_type'"
|
||||
log_error "Expected: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|junie|kilocode|auggie|roo|codebuddy|amp|shai|tabnine|kiro-cli|agy|bob|vibe|qodercli|kimi|trae|pi|iflow|forge|goose|generic"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Helper: skip non-existent files and files already updated (dedup by
|
||||
# realpath so that variables pointing to the same file — e.g. AMP_FILE,
|
||||
# KIRO_FILE, BOB_FILE all resolving to AGENTS_FILE — are only written once).
|
||||
# Uses a linear array instead of associative array for bash 3.2 compatibility.
|
||||
# Note: defined at top level because bash 3.2 does not support true
|
||||
# nested/local functions. _updated_paths, _found_agent, and _all_ok are
|
||||
# initialised exclusively inside update_all_existing_agents so that
|
||||
# sourcing this script has no side effects on the caller's environment.
|
||||
|
||||
_update_if_new() {
|
||||
local file="$1" name="$2"
|
||||
[[ -f "$file" ]] || return 0
|
||||
local real_path
|
||||
real_path=$(realpath "$file" 2>/dev/null || echo "$file")
|
||||
local p
|
||||
if [[ ${#_updated_paths[@]} -gt 0 ]]; then
|
||||
for p in "${_updated_paths[@]}"; do
|
||||
[[ "$p" == "$real_path" ]] && return 0
|
||||
done
|
||||
fi
|
||||
# Record the file as seen before attempting the update so that:
|
||||
# (a) aliases pointing to the same path are not retried on failure
|
||||
# (b) _found_agent reflects file existence, not update success
|
||||
_updated_paths+=("$real_path")
|
||||
_found_agent=true
|
||||
update_agent_file "$file" "$name"
|
||||
}
|
||||
|
||||
update_all_existing_agents() {
|
||||
_found_agent=false
|
||||
_updated_paths=()
|
||||
local _all_ok=true
|
||||
|
||||
_update_if_new "$CLAUDE_FILE" "Claude Code" || _all_ok=false
|
||||
_update_if_new "$GEMINI_FILE" "Gemini CLI" || _all_ok=false
|
||||
_update_if_new "$COPILOT_FILE" "GitHub Copilot" || _all_ok=false
|
||||
_update_if_new "$CURSOR_FILE" "Cursor IDE" || _all_ok=false
|
||||
_update_if_new "$QWEN_FILE" "Qwen Code" || _all_ok=false
|
||||
_update_if_new "$AGENTS_FILE" "Codex/opencode/Amp/Kiro/Bob/Pi/Forge/Goose" || _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 "$AUGGIE_FILE" "Auggie CLI" || _all_ok=false
|
||||
_update_if_new "$ROO_FILE" "Roo Code" || _all_ok=false
|
||||
_update_if_new "$CODEBUDDY_FILE" "CodeBuddy CLI" || _all_ok=false
|
||||
_update_if_new "$SHAI_FILE" "SHAI" || _all_ok=false
|
||||
_update_if_new "$TABNINE_FILE" "Tabnine CLI" || _all_ok=false
|
||||
_update_if_new "$QODER_FILE" "Qoder CLI" || _all_ok=false
|
||||
_update_if_new "$AGY_FILE" "Antigravity" || _all_ok=false
|
||||
_update_if_new "$VIBE_FILE" "Mistral Vibe" || _all_ok=false
|
||||
_update_if_new "$KIMI_FILE" "Kimi Code" || _all_ok=false
|
||||
_update_if_new "$TRAE_FILE" "Trae" || _all_ok=false
|
||||
_update_if_new "$IFLOW_FILE" "iFlow CLI" || _all_ok=false
|
||||
|
||||
# If no agent files exist, create a default Claude file
|
||||
if [[ "$_found_agent" == false ]]; then
|
||||
log_info "No existing agent files found, creating default Claude file..."
|
||||
update_agent_file "$CLAUDE_FILE" "Claude Code" || return 1
|
||||
fi
|
||||
|
||||
[[ "$_all_ok" == true ]]
|
||||
}
|
||||
print_summary() {
|
||||
echo
|
||||
log_info "Summary of changes:"
|
||||
|
||||
if [[ -n "$NEW_LANG" ]]; then
|
||||
echo " - Added language: $NEW_LANG"
|
||||
fi
|
||||
|
||||
if [[ -n "$NEW_FRAMEWORK" ]]; then
|
||||
echo " - Added framework: $NEW_FRAMEWORK"
|
||||
fi
|
||||
|
||||
if [[ -n "$NEW_DB" ]] && [[ "$NEW_DB" != "N/A" ]]; then
|
||||
echo " - Added database: $NEW_DB"
|
||||
fi
|
||||
|
||||
echo
|
||||
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|forge|goose|generic]"
|
||||
}
|
||||
|
||||
#==============================================================================
|
||||
# Main Execution
|
||||
#==============================================================================
|
||||
|
||||
main() {
|
||||
# Validate environment before proceeding
|
||||
validate_environment
|
||||
|
||||
log_info "=== Updating agent context files for feature $CURRENT_BRANCH ==="
|
||||
|
||||
# Parse the plan file to extract project information
|
||||
if ! parse_plan_data "$NEW_PLAN"; then
|
||||
log_error "Failed to parse plan data"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Process based on agent type argument
|
||||
local success=true
|
||||
|
||||
if [[ -z "$AGENT_TYPE" ]]; then
|
||||
# No specific agent provided - update all existing agent files
|
||||
log_info "No agent specified, updating all existing agent files..."
|
||||
if ! update_all_existing_agents; then
|
||||
success=false
|
||||
fi
|
||||
else
|
||||
# Specific agent provided - update only that agent
|
||||
log_info "Updating specific agent: $AGENT_TYPE"
|
||||
if ! update_specific_agent "$AGENT_TYPE"; then
|
||||
success=false
|
||||
fi
|
||||
fi
|
||||
|
||||
# Print summary
|
||||
print_summary
|
||||
|
||||
if [[ "$success" == true ]]; then
|
||||
log_success "Agent context update completed successfully"
|
||||
exit 0
|
||||
else
|
||||
log_error "Agent context update completed with errors"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Execute main function if script is run directly
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
main "$@"
|
||||
fi
|
||||
@@ -164,74 +164,6 @@ function Test-FeatureBranch {
|
||||
return $true
|
||||
}
|
||||
|
||||
# True when .specify/feature.json pins an existing feature directory that matches the
|
||||
# active FEATURE_DIR from Get-FeaturePathsEnv (so /speckit.plan can skip git branch pattern checks).
|
||||
function Test-FeatureJsonMatchesFeatureDir {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$RepoRoot,
|
||||
[Parameter(Mandatory = $true)][string]$ActiveFeatureDir
|
||||
)
|
||||
|
||||
$featureJson = Join-Path (Join-Path $RepoRoot '.specify') 'feature.json'
|
||||
if (-not (Test-Path -LiteralPath $featureJson -PathType Leaf)) {
|
||||
return $false
|
||||
}
|
||||
|
||||
try {
|
||||
$raw = Get-Content -LiteralPath $featureJson -Raw
|
||||
$cfg = $raw | ConvertFrom-Json
|
||||
} catch {
|
||||
return $false
|
||||
}
|
||||
|
||||
$fd = $cfg.feature_directory
|
||||
if ([string]::IsNullOrWhiteSpace([string]$fd)) {
|
||||
return $false
|
||||
}
|
||||
|
||||
if (-not [System.IO.Path]::IsPathRooted($fd)) {
|
||||
$fd = Join-Path $RepoRoot $fd
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $fd -PathType Container)) {
|
||||
return $false
|
||||
}
|
||||
|
||||
# Resolve both paths to canonical absolute form. Prefer Resolve-Path (follows
|
||||
# symlinks and is the canonical PS way); fall back to [Path]::GetFullPath when
|
||||
# Resolve-Path can't produce a value. Mirrors the pattern used by Find-SpecifyRoot.
|
||||
$resolvedJson = Resolve-Path -LiteralPath $fd -ErrorAction SilentlyContinue
|
||||
if ($resolvedJson) {
|
||||
$normJson = $resolvedJson.Path
|
||||
} else {
|
||||
$normJson = [System.IO.Path]::GetFullPath($fd)
|
||||
}
|
||||
|
||||
$resolvedActive = Resolve-Path -LiteralPath $ActiveFeatureDir -ErrorAction SilentlyContinue
|
||||
if ($resolvedActive) {
|
||||
$normActive = $resolvedActive.Path
|
||||
} else {
|
||||
$normActive = [System.IO.Path]::GetFullPath($ActiveFeatureDir)
|
||||
}
|
||||
|
||||
# Use case-insensitive compare only on Windows; POSIX filesystems are case-sensitive.
|
||||
# PowerShell 5.1 is Windows-only and does not define $IsWindows, so treat its
|
||||
# absence as "we're on Windows".
|
||||
if ($null -ne $IsWindows) {
|
||||
$onWindows = $IsWindows
|
||||
} else {
|
||||
$onWindows = $true
|
||||
}
|
||||
|
||||
if ($onWindows) {
|
||||
$comparison = [System.StringComparison]::OrdinalIgnoreCase
|
||||
} else {
|
||||
$comparison = [System.StringComparison]::Ordinal
|
||||
}
|
||||
|
||||
return [string]::Equals($normJson, $normActive, $comparison)
|
||||
}
|
||||
|
||||
# Resolve specs/<feature-dir> by numeric/timestamp prefix (mirrors scripts/bash/common.sh find_feature_dir_by_prefix).
|
||||
function Find-FeatureDirByPrefix {
|
||||
param(
|
||||
@@ -355,21 +287,6 @@ function Test-DirHasFiles {
|
||||
}
|
||||
}
|
||||
|
||||
# Find a usable Python 3 executable (python3, python, or py -3).
|
||||
# Returns the command/arguments as an array, or $null if none found.
|
||||
function Get-Python3Command {
|
||||
if (Get-Command python3 -ErrorAction SilentlyContinue) { return @('python3') }
|
||||
if (Get-Command python -ErrorAction SilentlyContinue) {
|
||||
$ver = & python --version 2>&1
|
||||
if ($ver -match 'Python 3') { return @('python') }
|
||||
}
|
||||
if (Get-Command py -ErrorAction SilentlyContinue) {
|
||||
$ver = & py -3 --version 2>&1
|
||||
if ($ver -match 'Python 3') { return @('py', '-3') }
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
# Resolve a template name to a file path using the priority stack:
|
||||
# 1. .specify/templates/overrides/
|
||||
# 2. .specify/presets/<preset-id>/templates/ (sorted by priority from .registry)
|
||||
@@ -398,7 +315,6 @@ function Resolve-Template {
|
||||
$presets = $registryData.presets
|
||||
if ($presets) {
|
||||
$sortedPresets = $presets.PSObject.Properties |
|
||||
Where-Object { $null -eq $_.Value.enabled -or $_.Value.enabled -ne $false } |
|
||||
Sort-Object { if ($null -ne $_.Value.priority) { $_.Value.priority } else { 10 } } |
|
||||
ForEach-Object { $_.Name }
|
||||
}
|
||||
@@ -438,206 +354,3 @@ function Resolve-Template {
|
||||
return $null
|
||||
}
|
||||
|
||||
# Resolve a template name to composed content using composition strategies.
|
||||
# Reads strategy metadata from preset manifests and composes content
|
||||
# from multiple layers using prepend, append, or wrap strategies.
|
||||
function Resolve-TemplateContent {
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][string]$TemplateName,
|
||||
[Parameter(Mandatory=$true)][string]$RepoRoot
|
||||
)
|
||||
|
||||
$base = Join-Path $RepoRoot '.specify/templates'
|
||||
|
||||
# Collect all layers (highest priority first)
|
||||
$layerPaths = @()
|
||||
$layerStrategies = @()
|
||||
|
||||
# Priority 1: Project overrides (always "replace")
|
||||
$override = Join-Path $base "overrides/$TemplateName.md"
|
||||
if (Test-Path $override) {
|
||||
$layerPaths += $override
|
||||
$layerStrategies += 'replace'
|
||||
}
|
||||
|
||||
# Priority 2: Installed presets (sorted by priority from .registry)
|
||||
$presetsDir = Join-Path $RepoRoot '.specify/presets'
|
||||
if (Test-Path $presetsDir) {
|
||||
$registryFile = Join-Path $presetsDir '.registry'
|
||||
$sortedPresets = @()
|
||||
if (Test-Path $registryFile) {
|
||||
try {
|
||||
$registryData = Get-Content $registryFile -Raw | ConvertFrom-Json
|
||||
$presets = $registryData.presets
|
||||
if ($presets) {
|
||||
$sortedPresets = $presets.PSObject.Properties |
|
||||
Where-Object { $null -eq $_.Value.enabled -or $_.Value.enabled -ne $false } |
|
||||
Sort-Object { if ($null -ne $_.Value.priority) { $_.Value.priority } else { 10 } } |
|
||||
ForEach-Object { $_.Name }
|
||||
}
|
||||
} catch {
|
||||
$sortedPresets = @()
|
||||
}
|
||||
}
|
||||
|
||||
if ($sortedPresets.Count -gt 0) {
|
||||
$pyCmd = Get-Python3Command
|
||||
if (-not $pyCmd) {
|
||||
# Check if any preset has strategy fields that would be ignored
|
||||
foreach ($pid in $sortedPresets) {
|
||||
$mf = Join-Path $presetsDir "$pid/preset.yml"
|
||||
if ((Test-Path $mf) -and (Select-String -Path $mf -Pattern 'strategy:' -Quiet -ErrorAction SilentlyContinue)) {
|
||||
Write-Warning "No Python 3 found; preset composition strategies will be ignored"
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
$yamlWarned = $false
|
||||
foreach ($presetId in $sortedPresets) {
|
||||
# Read strategy and file path from preset manifest
|
||||
$strategy = 'replace'
|
||||
$manifestFilePath = ''
|
||||
$manifest = Join-Path $presetsDir "$presetId/preset.yml"
|
||||
if ((Test-Path $manifest) -and $pyCmd) {
|
||||
try {
|
||||
# Use Python to parse YAML manifest for strategy and file path
|
||||
$pyArgs = if ($pyCmd.Count -gt 1) { $pyCmd[1..($pyCmd.Count-1)] } else { @() }
|
||||
$pyStderrFile = [System.IO.Path]::GetTempFileName()
|
||||
$stratResult = & $pyCmd[0] @pyArgs -c @"
|
||||
import sys
|
||||
try:
|
||||
import yaml
|
||||
except ImportError:
|
||||
print('yaml_missing', file=sys.stderr)
|
||||
print('replace\t')
|
||||
sys.exit(0)
|
||||
try:
|
||||
with open(sys.argv[1]) as f:
|
||||
data = yaml.safe_load(f)
|
||||
for t in data.get('provides', {}).get('templates', []):
|
||||
if t.get('name') == sys.argv[2] and t.get('type', 'template') == 'template':
|
||||
print(t.get('strategy', 'replace') + '\t' + t.get('file', ''))
|
||||
sys.exit(0)
|
||||
print('replace\t')
|
||||
except Exception:
|
||||
print('replace\t')
|
||||
"@ $manifest $TemplateName 2>$pyStderrFile
|
||||
if ($stratResult) {
|
||||
$parts = $stratResult.Trim() -split "`t", 2
|
||||
$strategy = $parts[0].ToLowerInvariant()
|
||||
if ($parts.Count -gt 1 -and $parts[1]) { $manifestFilePath = $parts[1] }
|
||||
}
|
||||
if (-not $yamlWarned -and (Test-Path $pyStderrFile) -and (Get-Content $pyStderrFile -Raw -ErrorAction SilentlyContinue) -match 'yaml_missing') {
|
||||
Write-Warning "PyYAML not available; composition strategies may be ignored"
|
||||
$yamlWarned = $true
|
||||
}
|
||||
Remove-Item $pyStderrFile -Force -ErrorAction SilentlyContinue
|
||||
} catch {
|
||||
$strategy = 'replace'
|
||||
if ($pyStderrFile) { Remove-Item $pyStderrFile -Force -ErrorAction SilentlyContinue }
|
||||
}
|
||||
}
|
||||
# Try manifest file path first, then convention path
|
||||
$candidate = $null
|
||||
if ($manifestFilePath) {
|
||||
# Reject absolute paths and parent traversal
|
||||
if ([System.IO.Path]::IsPathRooted($manifestFilePath) -or $manifestFilePath -match '\.\.[\\/]') {
|
||||
$manifestFilePath = ''
|
||||
}
|
||||
}
|
||||
if ($manifestFilePath) {
|
||||
$mf = Join-Path $presetsDir "$presetId/$manifestFilePath"
|
||||
if (Test-Path $mf) { $candidate = $mf }
|
||||
}
|
||||
if (-not $candidate) {
|
||||
$cf = Join-Path $presetsDir "$presetId/templates/$TemplateName.md"
|
||||
if (Test-Path $cf) { $candidate = $cf }
|
||||
}
|
||||
if ($candidate) {
|
||||
$layerPaths += $candidate
|
||||
$layerStrategies += $strategy
|
||||
}
|
||||
}
|
||||
} else {
|
||||
# Fallback: alphabetical directory order (no registry or parse failure)
|
||||
foreach ($preset in Get-ChildItem -Path $presetsDir -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -notlike '.*' }) {
|
||||
$candidate = Join-Path $preset.FullName "templates/$TemplateName.md"
|
||||
if (Test-Path $candidate) {
|
||||
$layerPaths += $candidate
|
||||
$layerStrategies += 'replace'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Priority 3: Extension-provided templates (always "replace")
|
||||
$extDir = Join-Path $RepoRoot '.specify/extensions'
|
||||
if (Test-Path $extDir) {
|
||||
foreach ($ext in Get-ChildItem -Path $extDir -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -notlike '.*' } | Sort-Object Name) {
|
||||
$candidate = Join-Path $ext.FullName "templates/$TemplateName.md"
|
||||
if (Test-Path $candidate) {
|
||||
$layerPaths += $candidate
|
||||
$layerStrategies += 'replace'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Priority 4: Core templates (always "replace")
|
||||
$core = Join-Path $base "$TemplateName.md"
|
||||
if (Test-Path $core) {
|
||||
$layerPaths += $core
|
||||
$layerStrategies += 'replace'
|
||||
}
|
||||
|
||||
if ($layerPaths.Count -eq 0) { return $null }
|
||||
|
||||
# If the top (highest-priority) layer is replace, it wins entirely —
|
||||
# lower layers are irrelevant regardless of their strategies.
|
||||
if ($layerStrategies[0] -eq 'replace') {
|
||||
return (Get-Content $layerPaths[0] -Raw)
|
||||
}
|
||||
|
||||
# Check if any layer uses a non-replace strategy
|
||||
$hasComposition = $false
|
||||
foreach ($s in $layerStrategies) {
|
||||
if ($s -ne 'replace') { $hasComposition = $true; break }
|
||||
}
|
||||
|
||||
if (-not $hasComposition) {
|
||||
return (Get-Content $layerPaths[0] -Raw)
|
||||
}
|
||||
|
||||
# Find the effective base: scan from highest priority (index 0) downward
|
||||
# to find the nearest replace layer. Only compose layers above that base.
|
||||
$baseIdx = -1
|
||||
for ($i = 0; $i -lt $layerPaths.Count; $i++) {
|
||||
if ($layerStrategies[$i] -eq 'replace') {
|
||||
$baseIdx = $i
|
||||
break
|
||||
}
|
||||
}
|
||||
if ($baseIdx -lt 0) { return $null }
|
||||
|
||||
$content = Get-Content $layerPaths[$baseIdx] -Raw
|
||||
|
||||
for ($i = $baseIdx - 1; $i -ge 0; $i--) {
|
||||
$path = $layerPaths[$i]
|
||||
$strat = $layerStrategies[$i]
|
||||
$layerContent = Get-Content $path -Raw
|
||||
|
||||
switch ($strat) {
|
||||
'replace' { $content = $layerContent }
|
||||
'prepend' { $content = "$layerContent`n`n$content" }
|
||||
'append' { $content = "$content`n`n$layerContent" }
|
||||
'wrap' {
|
||||
if (-not $layerContent.Contains('{CORE_TEMPLATE}')) {
|
||||
throw "Wrap strategy missing {CORE_TEMPLATE} placeholder"
|
||||
}
|
||||
$content = $layerContent.Replace('{CORE_TEMPLATE}', $content)
|
||||
}
|
||||
default { throw "Unknown strategy: $strat" }
|
||||
}
|
||||
}
|
||||
|
||||
return $content
|
||||
}
|
||||
@@ -23,11 +23,9 @@ if ($Help) {
|
||||
# Get all paths and variables from common functions
|
||||
$paths = Get-FeaturePathsEnv
|
||||
|
||||
# If feature.json pins an existing feature directory, branch naming is not required.
|
||||
if (-not (Test-FeatureJsonMatchesFeatureDir -RepoRoot $paths.REPO_ROOT -ActiveFeatureDir $paths.FEATURE_DIR)) {
|
||||
if (-not (Test-FeatureBranch -Branch $paths.CURRENT_BRANCH -HasGit $paths.HAS_GIT)) {
|
||||
exit 1
|
||||
}
|
||||
# Check if we're on a proper feature branch (only for git repos)
|
||||
if (-not (Test-FeatureBranch -Branch $paths.CURRENT_BRANCH -HasGit $paths.HAS_GIT)) {
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Ensure the feature directory exists
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
#!/usr/bin/env pwsh
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[switch]$Json,
|
||||
[switch]$Help
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
if ($Help) {
|
||||
Write-Output "Usage: setup-tasks.ps1 [-Json] [-Help]"
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Source common functions
|
||||
. "$PSScriptRoot/common.ps1"
|
||||
|
||||
# Get feature paths and validate branch
|
||||
$paths = Get-FeaturePathsEnv
|
||||
|
||||
# If feature.json pins an existing feature directory, branch naming is not required.
|
||||
if (-not (Test-FeatureJsonMatchesFeatureDir -RepoRoot $paths.REPO_ROOT -ActiveFeatureDir $paths.FEATURE_DIR)) {
|
||||
if (-not (Test-FeatureBranch -Branch $paths.CURRENT_BRANCH -HasGit $paths.HAS_GIT)) {
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
if (-not (Test-Path $paths.IMPL_PLAN -PathType Leaf)) {
|
||||
[Console]::Error.WriteLine("ERROR: plan.md not found in $($paths.FEATURE_DIR)")
|
||||
[Console]::Error.WriteLine("Run /speckit.plan first to create the implementation plan.")
|
||||
exit 1
|
||||
}
|
||||
|
||||
if (-not (Test-Path $paths.FEATURE_SPEC -PathType Leaf)) {
|
||||
[Console]::Error.WriteLine("ERROR: spec.md not found in $($paths.FEATURE_DIR)")
|
||||
[Console]::Error.WriteLine("Run /speckit.specify first to create the feature structure.")
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Build available docs list
|
||||
$docs = @()
|
||||
if (Test-Path $paths.RESEARCH) { $docs += 'research.md' }
|
||||
if (Test-Path $paths.DATA_MODEL) { $docs += 'data-model.md' }
|
||||
if ((Test-Path $paths.CONTRACTS_DIR) -and (Get-ChildItem -Path $paths.CONTRACTS_DIR -ErrorAction SilentlyContinue | Select-Object -First 1)) {
|
||||
$docs += 'contracts/'
|
||||
}
|
||||
if (Test-Path $paths.QUICKSTART) { $docs += 'quickstart.md' }
|
||||
|
||||
# Resolve tasks template through override stack
|
||||
$tasksTemplate = Resolve-Template -TemplateName 'tasks-template' -RepoRoot $paths.REPO_ROOT
|
||||
if (-not $tasksTemplate -or -not (Test-Path -LiteralPath $tasksTemplate -PathType Leaf)) {
|
||||
$expectedCoreTemplate = Join-Path $paths.REPO_ROOT '.specify/templates/tasks-template.md'
|
||||
[Console]::Error.WriteLine("ERROR: Tasks template not found for repository root: $($paths.REPO_ROOT)`nTemplate resolution order: overrides -> presets -> extensions -> core.`nExpected shared/core template location: $expectedCoreTemplate`nTo continue, verify whether 'tasks-template.md' is available in '.specify/templates/overrides/', preset templates, extension templates, or restore the shared/core templates (for example by re-running 'specify init') so that '.specify/templates/tasks-template.md' exists.")
|
||||
exit 1
|
||||
}
|
||||
$tasksTemplate = (Resolve-Path -LiteralPath $tasksTemplate).Path
|
||||
|
||||
# Output results
|
||||
if ($Json) {
|
||||
[PSCustomObject]@{
|
||||
FEATURE_DIR = $paths.FEATURE_DIR
|
||||
AVAILABLE_DOCS = $docs
|
||||
TASKS_TEMPLATE = $tasksTemplate
|
||||
} | ConvertTo-Json -Compress
|
||||
} else {
|
||||
Write-Output "FEATURE_DIR: $($paths.FEATURE_DIR)"
|
||||
Write-Output "TASKS_TEMPLATE: $(if ($tasksTemplate) { $tasksTemplate } else { 'not found' })"
|
||||
Write-Output "AVAILABLE_DOCS:"
|
||||
Test-FileExists -Path $paths.RESEARCH -Description 'research.md' | Out-Null
|
||||
Test-FileExists -Path $paths.DATA_MODEL -Description 'data-model.md' | Out-Null
|
||||
Test-DirHasFiles -Path $paths.CONTRACTS_DIR -Description 'contracts/' | Out-Null
|
||||
Test-FileExists -Path $paths.QUICKSTART -Description 'quickstart.md' | Out-Null
|
||||
}
|
||||
515
scripts/powershell/update-agent-context.ps1
Normal file
515
scripts/powershell/update-agent-context.ps1
Normal file
@@ -0,0 +1,515 @@
|
||||
#!/usr/bin/env pwsh
|
||||
<#!
|
||||
.SYNOPSIS
|
||||
Update agent context files with information from plan.md (PowerShell version)
|
||||
|
||||
.DESCRIPTION
|
||||
Mirrors the behavior of scripts/bash/update-agent-context.sh:
|
||||
1. Environment Validation
|
||||
2. Plan Data Extraction
|
||||
3. Agent File Management (create from template or update existing)
|
||||
4. Content Generation (technology stack, recent changes, timestamp)
|
||||
5. Multi-Agent Support (claude, gemini, copilot, cursor-agent, qwen, opencode, codex, windsurf, junie, kilocode, auggie, roo, codebuddy, amp, shai, tabnine, kiro-cli, agy, bob, vibe, qodercli, kimi, trae, pi, iflow, forge, goose, generic)
|
||||
|
||||
.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).
|
||||
|
||||
.EXAMPLE
|
||||
./update-agent-context.ps1 -AgentType claude
|
||||
|
||||
.EXAMPLE
|
||||
./update-agent-context.ps1 # Updates all existing agent files
|
||||
|
||||
.NOTES
|
||||
Relies on common helper functions in common.ps1
|
||||
#>
|
||||
param(
|
||||
[Parameter(Position=0)]
|
||||
[ValidateSet('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','forge','goose','generic')]
|
||||
[string]$AgentType
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Import common helpers
|
||||
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
. (Join-Path $ScriptDir 'common.ps1')
|
||||
|
||||
# Acquire environment paths
|
||||
$envData = Get-FeaturePathsEnv
|
||||
$REPO_ROOT = $envData.REPO_ROOT
|
||||
$CURRENT_BRANCH = $envData.CURRENT_BRANCH
|
||||
$HAS_GIT = $envData.HAS_GIT
|
||||
$IMPL_PLAN = $envData.IMPL_PLAN
|
||||
$NEW_PLAN = $IMPL_PLAN
|
||||
|
||||
# Agent file paths
|
||||
$CLAUDE_FILE = Join-Path $REPO_ROOT 'CLAUDE.md'
|
||||
$GEMINI_FILE = Join-Path $REPO_ROOT 'GEMINI.md'
|
||||
$COPILOT_FILE = Join-Path $REPO_ROOT '.github/copilot-instructions.md'
|
||||
$CURSOR_FILE = Join-Path $REPO_ROOT '.cursor/rules/specify-rules.mdc'
|
||||
$QWEN_FILE = Join-Path $REPO_ROOT 'QWEN.md'
|
||||
$AGENTS_FILE = Join-Path $REPO_ROOT 'AGENTS.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'
|
||||
$AUGGIE_FILE = Join-Path $REPO_ROOT '.augment/rules/specify-rules.md'
|
||||
$ROO_FILE = Join-Path $REPO_ROOT '.roo/rules/specify-rules.md'
|
||||
$CODEBUDDY_FILE = Join-Path $REPO_ROOT 'CODEBUDDY.md'
|
||||
$QODER_FILE = Join-Path $REPO_ROOT 'QODER.md'
|
||||
$AMP_FILE = Join-Path $REPO_ROOT 'AGENTS.md'
|
||||
$SHAI_FILE = Join-Path $REPO_ROOT 'SHAI.md'
|
||||
$TABNINE_FILE = Join-Path $REPO_ROOT 'TABNINE.md'
|
||||
$KIRO_FILE = Join-Path $REPO_ROOT 'AGENTS.md'
|
||||
$AGY_FILE = Join-Path $REPO_ROOT '.agent/rules/specify-rules.md'
|
||||
$BOB_FILE = Join-Path $REPO_ROOT 'AGENTS.md'
|
||||
$VIBE_FILE = Join-Path $REPO_ROOT '.vibe/agents/specify-agents.md'
|
||||
$KIMI_FILE = Join-Path $REPO_ROOT 'KIMI.md'
|
||||
$TRAE_FILE = Join-Path $REPO_ROOT '.trae/rules/project_rules.md'
|
||||
$IFLOW_FILE = Join-Path $REPO_ROOT 'IFLOW.md'
|
||||
$FORGE_FILE = Join-Path $REPO_ROOT 'AGENTS.md'
|
||||
$GOOSE_FILE = Join-Path $REPO_ROOT 'AGENTS.md'
|
||||
|
||||
$TEMPLATE_FILE = Join-Path $REPO_ROOT '.specify/templates/agent-file-template.md'
|
||||
|
||||
# Parsed plan data placeholders
|
||||
$script:NEW_LANG = ''
|
||||
$script:NEW_FRAMEWORK = ''
|
||||
$script:NEW_DB = ''
|
||||
$script:NEW_PROJECT_TYPE = ''
|
||||
|
||||
function Write-Info {
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$Message
|
||||
)
|
||||
Write-Host "INFO: $Message"
|
||||
}
|
||||
|
||||
function Write-Success {
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$Message
|
||||
)
|
||||
Write-Host "$([char]0x2713) $Message"
|
||||
}
|
||||
|
||||
function Write-WarningMsg {
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$Message
|
||||
)
|
||||
Write-Warning $Message
|
||||
}
|
||||
|
||||
function Write-Err {
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$Message
|
||||
)
|
||||
Write-Host "ERROR: $Message" -ForegroundColor Red
|
||||
}
|
||||
|
||||
function Validate-Environment {
|
||||
if (-not $CURRENT_BRANCH) {
|
||||
Write-Err 'Unable to determine current feature'
|
||||
if ($HAS_GIT) { Write-Info "Make sure you're on a feature branch" } else { Write-Info 'Set SPECIFY_FEATURE environment variable or create a feature first' }
|
||||
exit 1
|
||||
}
|
||||
if (-not (Test-Path $NEW_PLAN)) {
|
||||
Write-Err "No plan.md found at $NEW_PLAN"
|
||||
Write-Info 'Ensure you are working on a feature with a corresponding spec directory'
|
||||
if (-not $HAS_GIT) { Write-Info 'Use: $env:SPECIFY_FEATURE=your-feature-name or create a new feature first' }
|
||||
exit 1
|
||||
}
|
||||
if (-not (Test-Path $TEMPLATE_FILE)) {
|
||||
Write-Err "Template file not found at $TEMPLATE_FILE"
|
||||
Write-Info 'Run specify init to scaffold .specify/templates, or add agent-file-template.md there.'
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
function Extract-PlanField {
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$FieldPattern,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$PlanFile
|
||||
)
|
||||
if (-not (Test-Path $PlanFile)) { return '' }
|
||||
# Lines like **Language/Version**: Python 3.12
|
||||
$regex = "^\*\*$([Regex]::Escape($FieldPattern))\*\*: (.+)$"
|
||||
Get-Content -LiteralPath $PlanFile -Encoding utf8 | ForEach-Object {
|
||||
if ($_ -match $regex) {
|
||||
$val = $Matches[1].Trim()
|
||||
if ($val -notin @('NEEDS CLARIFICATION','N/A')) { return $val }
|
||||
}
|
||||
} | Select-Object -First 1
|
||||
}
|
||||
|
||||
function Parse-PlanData {
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$PlanFile
|
||||
)
|
||||
if (-not (Test-Path $PlanFile)) { Write-Err "Plan file not found: $PlanFile"; return $false }
|
||||
Write-Info "Parsing plan data from $PlanFile"
|
||||
$script:NEW_LANG = Extract-PlanField -FieldPattern 'Language/Version' -PlanFile $PlanFile
|
||||
$script:NEW_FRAMEWORK = Extract-PlanField -FieldPattern 'Primary Dependencies' -PlanFile $PlanFile
|
||||
$script:NEW_DB = Extract-PlanField -FieldPattern 'Storage' -PlanFile $PlanFile
|
||||
$script:NEW_PROJECT_TYPE = Extract-PlanField -FieldPattern 'Project Type' -PlanFile $PlanFile
|
||||
|
||||
if ($NEW_LANG) { Write-Info "Found language: $NEW_LANG" } else { Write-WarningMsg 'No language information found in plan' }
|
||||
if ($NEW_FRAMEWORK) { Write-Info "Found framework: $NEW_FRAMEWORK" }
|
||||
if ($NEW_DB -and $NEW_DB -ne 'N/A') { Write-Info "Found database: $NEW_DB" }
|
||||
if ($NEW_PROJECT_TYPE) { Write-Info "Found project type: $NEW_PROJECT_TYPE" }
|
||||
return $true
|
||||
}
|
||||
|
||||
function Format-TechnologyStack {
|
||||
param(
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$Lang,
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$Framework
|
||||
)
|
||||
$parts = @()
|
||||
if ($Lang -and $Lang -ne 'NEEDS CLARIFICATION') { $parts += $Lang }
|
||||
if ($Framework -and $Framework -notin @('NEEDS CLARIFICATION','N/A')) { $parts += $Framework }
|
||||
if (-not $parts) { return '' }
|
||||
return ($parts -join ' + ')
|
||||
}
|
||||
|
||||
function Get-ProjectStructure {
|
||||
param(
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$ProjectType
|
||||
)
|
||||
if ($ProjectType -match 'web') { return "backend/`nfrontend/`ntests/" } else { return "src/`ntests/" }
|
||||
}
|
||||
|
||||
function Get-CommandsForLanguage {
|
||||
param(
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$Lang
|
||||
)
|
||||
switch -Regex ($Lang) {
|
||||
'Python' { return "cd src; pytest; ruff check ." }
|
||||
'Rust' { return "cargo test; cargo clippy" }
|
||||
'JavaScript|TypeScript' { return "npm test; npm run lint" }
|
||||
default { return "# Add commands for $Lang" }
|
||||
}
|
||||
}
|
||||
|
||||
function Get-LanguageConventions {
|
||||
param(
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$Lang
|
||||
)
|
||||
if ($Lang) { "${Lang}: Follow standard conventions" } else { 'General: Follow standard conventions' }
|
||||
}
|
||||
|
||||
function New-AgentFile {
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$TargetFile,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$ProjectName,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[datetime]$Date
|
||||
)
|
||||
if (-not (Test-Path $TEMPLATE_FILE)) { Write-Err "Template not found at $TEMPLATE_FILE"; return $false }
|
||||
$temp = New-TemporaryFile
|
||||
Copy-Item -LiteralPath $TEMPLATE_FILE -Destination $temp -Force
|
||||
|
||||
$projectStructure = Get-ProjectStructure -ProjectType $NEW_PROJECT_TYPE
|
||||
$commands = Get-CommandsForLanguage -Lang $NEW_LANG
|
||||
$languageConventions = Get-LanguageConventions -Lang $NEW_LANG
|
||||
|
||||
$escaped_lang = $NEW_LANG
|
||||
$escaped_framework = $NEW_FRAMEWORK
|
||||
$escaped_branch = $CURRENT_BRANCH
|
||||
|
||||
$content = Get-Content -LiteralPath $temp -Raw -Encoding utf8
|
||||
$content = $content -replace '\[PROJECT NAME\]',$ProjectName
|
||||
$content = $content -replace '\[DATE\]',$Date.ToString('yyyy-MM-dd')
|
||||
|
||||
# Build the technology stack string safely
|
||||
$techStackForTemplate = ""
|
||||
if ($escaped_lang -and $escaped_framework) {
|
||||
$techStackForTemplate = "- $escaped_lang + $escaped_framework ($escaped_branch)"
|
||||
} elseif ($escaped_lang) {
|
||||
$techStackForTemplate = "- $escaped_lang ($escaped_branch)"
|
||||
} elseif ($escaped_framework) {
|
||||
$techStackForTemplate = "- $escaped_framework ($escaped_branch)"
|
||||
}
|
||||
|
||||
$content = $content -replace '\[EXTRACTED FROM ALL PLAN.MD FILES\]',$techStackForTemplate
|
||||
# For project structure we manually embed (keep newlines)
|
||||
$escapedStructure = [Regex]::Escape($projectStructure)
|
||||
$content = $content -replace '\[ACTUAL STRUCTURE FROM PLANS\]',$escapedStructure
|
||||
# Replace escaped newlines placeholder after all replacements
|
||||
$content = $content -replace '\[ONLY COMMANDS FOR ACTIVE TECHNOLOGIES\]',$commands
|
||||
$content = $content -replace '\[LANGUAGE-SPECIFIC, ONLY FOR LANGUAGES IN USE\]',$languageConventions
|
||||
|
||||
# Build the recent changes string safely
|
||||
$recentChangesForTemplate = ""
|
||||
if ($escaped_lang -and $escaped_framework) {
|
||||
$recentChangesForTemplate = "- ${escaped_branch}: Added ${escaped_lang} + ${escaped_framework}"
|
||||
} elseif ($escaped_lang) {
|
||||
$recentChangesForTemplate = "- ${escaped_branch}: Added ${escaped_lang}"
|
||||
} elseif ($escaped_framework) {
|
||||
$recentChangesForTemplate = "- ${escaped_branch}: Added ${escaped_framework}"
|
||||
}
|
||||
|
||||
$content = $content -replace '\[LAST 3 FEATURES AND WHAT THEY ADDED\]',$recentChangesForTemplate
|
||||
# Convert literal \n sequences introduced by Escape to real newlines
|
||||
$content = $content -replace '\\n',[Environment]::NewLine
|
||||
|
||||
# Prepend Cursor frontmatter for .mdc files so rules are auto-included
|
||||
if ($TargetFile -match '\.mdc$') {
|
||||
$frontmatter = @('---','description: Project Development Guidelines','globs: ["**/*"]','alwaysApply: true','---','') -join [Environment]::NewLine
|
||||
$content = $frontmatter + $content
|
||||
}
|
||||
|
||||
$parent = Split-Path -Parent $TargetFile
|
||||
if (-not (Test-Path $parent)) { New-Item -ItemType Directory -Path $parent | Out-Null }
|
||||
Set-Content -LiteralPath $TargetFile -Value $content -NoNewline -Encoding utf8
|
||||
Remove-Item $temp -Force
|
||||
return $true
|
||||
}
|
||||
|
||||
function Update-ExistingAgentFile {
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$TargetFile,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[datetime]$Date
|
||||
)
|
||||
if (-not (Test-Path $TargetFile)) { return (New-AgentFile -TargetFile $TargetFile -ProjectName (Split-Path $REPO_ROOT -Leaf) -Date $Date) }
|
||||
|
||||
$techStack = Format-TechnologyStack -Lang $NEW_LANG -Framework $NEW_FRAMEWORK
|
||||
$newTechEntries = @()
|
||||
if ($techStack) {
|
||||
$escapedTechStack = [Regex]::Escape($techStack)
|
||||
if (-not (Select-String -Pattern $escapedTechStack -Path $TargetFile -Quiet)) {
|
||||
$newTechEntries += "- $techStack ($CURRENT_BRANCH)"
|
||||
}
|
||||
}
|
||||
if ($NEW_DB -and $NEW_DB -notin @('N/A','NEEDS CLARIFICATION')) {
|
||||
$escapedDB = [Regex]::Escape($NEW_DB)
|
||||
if (-not (Select-String -Pattern $escapedDB -Path $TargetFile -Quiet)) {
|
||||
$newTechEntries += "- $NEW_DB ($CURRENT_BRANCH)"
|
||||
}
|
||||
}
|
||||
$newChangeEntry = ''
|
||||
if ($techStack) { $newChangeEntry = "- ${CURRENT_BRANCH}: Added ${techStack}" }
|
||||
elseif ($NEW_DB -and $NEW_DB -notin @('N/A','NEEDS CLARIFICATION')) { $newChangeEntry = "- ${CURRENT_BRANCH}: Added ${NEW_DB}" }
|
||||
|
||||
$lines = Get-Content -LiteralPath $TargetFile -Encoding utf8
|
||||
$output = New-Object System.Collections.Generic.List[string]
|
||||
$inTech = $false; $inChanges = $false; $techAdded = $false; $changeAdded = $false; $existingChanges = 0
|
||||
|
||||
for ($i=0; $i -lt $lines.Count; $i++) {
|
||||
$line = $lines[$i]
|
||||
if ($line -eq '## Active Technologies') {
|
||||
$output.Add($line)
|
||||
$inTech = $true
|
||||
continue
|
||||
}
|
||||
if ($inTech -and $line -match '^##\s') {
|
||||
if (-not $techAdded -and $newTechEntries.Count -gt 0) { $newTechEntries | ForEach-Object { $output.Add($_) }; $techAdded = $true }
|
||||
$output.Add($line); $inTech = $false; continue
|
||||
}
|
||||
if ($inTech -and [string]::IsNullOrWhiteSpace($line)) {
|
||||
if (-not $techAdded -and $newTechEntries.Count -gt 0) { $newTechEntries | ForEach-Object { $output.Add($_) }; $techAdded = $true }
|
||||
$output.Add($line); continue
|
||||
}
|
||||
if ($line -eq '## Recent Changes') {
|
||||
$output.Add($line)
|
||||
if ($newChangeEntry) { $output.Add($newChangeEntry); $changeAdded = $true }
|
||||
$inChanges = $true
|
||||
continue
|
||||
}
|
||||
if ($inChanges -and $line -match '^##\s') { $output.Add($line); $inChanges = $false; continue }
|
||||
if ($inChanges -and $line -match '^- ') {
|
||||
if ($existingChanges -lt 2) { $output.Add($line); $existingChanges++ }
|
||||
continue
|
||||
}
|
||||
if ($line -match '(\*\*)?Last updated(\*\*)?: .*\d{4}-\d{2}-\d{2}') {
|
||||
$output.Add(($line -replace '\d{4}-\d{2}-\d{2}',$Date.ToString('yyyy-MM-dd')))
|
||||
continue
|
||||
}
|
||||
$output.Add($line)
|
||||
}
|
||||
|
||||
# Post-loop check: if we're still in the Active Technologies section and haven't added new entries
|
||||
if ($inTech -and -not $techAdded -and $newTechEntries.Count -gt 0) {
|
||||
$newTechEntries | ForEach-Object { $output.Add($_) }
|
||||
}
|
||||
|
||||
# Ensure Cursor .mdc files have YAML frontmatter for auto-inclusion
|
||||
if ($TargetFile -match '\.mdc$' -and $output.Count -gt 0 -and $output[0] -ne '---') {
|
||||
$frontmatter = @('---','description: Project Development Guidelines','globs: ["**/*"]','alwaysApply: true','---','')
|
||||
$output.InsertRange(0, $frontmatter)
|
||||
}
|
||||
|
||||
Set-Content -LiteralPath $TargetFile -Value ($output -join [Environment]::NewLine) -Encoding utf8
|
||||
return $true
|
||||
}
|
||||
|
||||
function Update-AgentFile {
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$TargetFile,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$AgentName
|
||||
)
|
||||
if (-not $TargetFile -or -not $AgentName) { Write-Err 'Update-AgentFile requires TargetFile and AgentName'; return $false }
|
||||
Write-Info "Updating $AgentName context file: $TargetFile"
|
||||
$projectName = Split-Path $REPO_ROOT -Leaf
|
||||
$date = Get-Date
|
||||
|
||||
$dir = Split-Path -Parent $TargetFile
|
||||
if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir | Out-Null }
|
||||
|
||||
if (-not (Test-Path $TargetFile)) {
|
||||
if (New-AgentFile -TargetFile $TargetFile -ProjectName $projectName -Date $date) { Write-Success "Created new $AgentName context file" } else { Write-Err 'Failed to create new agent file'; return $false }
|
||||
} else {
|
||||
try {
|
||||
if (Update-ExistingAgentFile -TargetFile $TargetFile -Date $date) { Write-Success "Updated existing $AgentName context file" } else { Write-Err 'Failed to update agent file'; return $false }
|
||||
} catch {
|
||||
Write-Err "Cannot access or update existing file: $TargetFile. $_"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
function Update-SpecificAgent {
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$Type
|
||||
)
|
||||
switch ($Type) {
|
||||
'claude' { Update-AgentFile -TargetFile $CLAUDE_FILE -AgentName 'Claude Code' }
|
||||
'gemini' { Update-AgentFile -TargetFile $GEMINI_FILE -AgentName 'Gemini CLI' }
|
||||
'copilot' { Update-AgentFile -TargetFile $COPILOT_FILE -AgentName 'GitHub Copilot' }
|
||||
'cursor-agent' { Update-AgentFile -TargetFile $CURSOR_FILE -AgentName 'Cursor IDE' }
|
||||
'qwen' { Update-AgentFile -TargetFile $QWEN_FILE -AgentName 'Qwen Code' }
|
||||
'opencode' { Update-AgentFile -TargetFile $AGENTS_FILE -AgentName 'opencode' }
|
||||
'codex' { Update-AgentFile -TargetFile $AGENTS_FILE -AgentName 'Codex CLI' }
|
||||
'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' }
|
||||
'auggie' { Update-AgentFile -TargetFile $AUGGIE_FILE -AgentName 'Auggie CLI' }
|
||||
'roo' { Update-AgentFile -TargetFile $ROO_FILE -AgentName 'Roo Code' }
|
||||
'codebuddy' { Update-AgentFile -TargetFile $CODEBUDDY_FILE -AgentName 'CodeBuddy CLI' }
|
||||
'qodercli' { Update-AgentFile -TargetFile $QODER_FILE -AgentName 'Qoder CLI' }
|
||||
'amp' { Update-AgentFile -TargetFile $AMP_FILE -AgentName 'Amp' }
|
||||
'shai' { Update-AgentFile -TargetFile $SHAI_FILE -AgentName 'SHAI' }
|
||||
'tabnine' { Update-AgentFile -TargetFile $TABNINE_FILE -AgentName 'Tabnine CLI' }
|
||||
'kiro-cli' { Update-AgentFile -TargetFile $KIRO_FILE -AgentName 'Kiro CLI' }
|
||||
'agy' { Update-AgentFile -TargetFile $AGY_FILE -AgentName 'Antigravity' }
|
||||
'bob' { Update-AgentFile -TargetFile $BOB_FILE -AgentName 'IBM Bob' }
|
||||
'vibe' { Update-AgentFile -TargetFile $VIBE_FILE -AgentName 'Mistral Vibe' }
|
||||
'kimi' { Update-AgentFile -TargetFile $KIMI_FILE -AgentName 'Kimi Code' }
|
||||
'trae' { Update-AgentFile -TargetFile $TRAE_FILE -AgentName 'Trae' }
|
||||
'pi' { Update-AgentFile -TargetFile $AGENTS_FILE -AgentName 'Pi Coding Agent' }
|
||||
'iflow' { Update-AgentFile -TargetFile $IFLOW_FILE -AgentName 'iFlow CLI' }
|
||||
'forge' { Update-AgentFile -TargetFile $FORGE_FILE -AgentName 'Forge' }
|
||||
'goose' { Update-AgentFile -TargetFile $GOOSE_FILE -AgentName 'Goose' }
|
||||
'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|junie|kilocode|auggie|roo|codebuddy|amp|shai|tabnine|kiro-cli|agy|bob|vibe|qodercli|kimi|trae|pi|iflow|forge|goose|generic'; return $false }
|
||||
}
|
||||
}
|
||||
|
||||
function Update-AllExistingAgents {
|
||||
$found = $false
|
||||
$ok = $true
|
||||
$updatedPaths = @()
|
||||
|
||||
# Helper function to update only if file exists and hasn't been updated yet
|
||||
function Update-IfNew {
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$FilePath,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$AgentName
|
||||
)
|
||||
|
||||
if (-not (Test-Path $FilePath)) { return $true }
|
||||
|
||||
# Get the real path to detect duplicates (e.g., AMP_FILE, KIRO_FILE, BOB_FILE all point to AGENTS.md)
|
||||
$realPath = (Get-Item -LiteralPath $FilePath).FullName
|
||||
|
||||
# Check if we've already updated this file
|
||||
if ($updatedPaths -contains $realPath) {
|
||||
return $true
|
||||
}
|
||||
|
||||
# Record the file as seen before attempting the update
|
||||
# Use parent scope (1) to modify Update-AllExistingAgents' local variables
|
||||
Set-Variable -Name updatedPaths -Value ($updatedPaths + $realPath) -Scope 1
|
||||
Set-Variable -Name found -Value $true -Scope 1
|
||||
|
||||
# Perform the update
|
||||
return (Update-AgentFile -TargetFile $FilePath -AgentName $AgentName)
|
||||
}
|
||||
|
||||
if (-not (Update-IfNew -FilePath $CLAUDE_FILE -AgentName 'Claude Code')) { $ok = $false }
|
||||
if (-not (Update-IfNew -FilePath $GEMINI_FILE -AgentName 'Gemini CLI')) { $ok = $false }
|
||||
if (-not (Update-IfNew -FilePath $COPILOT_FILE -AgentName 'GitHub Copilot')) { $ok = $false }
|
||||
if (-not (Update-IfNew -FilePath $CURSOR_FILE -AgentName 'Cursor IDE')) { $ok = $false }
|
||||
if (-not (Update-IfNew -FilePath $QWEN_FILE -AgentName 'Qwen Code')) { $ok = $false }
|
||||
if (-not (Update-IfNew -FilePath $AGENTS_FILE -AgentName 'Codex/opencode/Amp/Kiro/Bob/Pi/Forge/Goose')) { $ok = $false }
|
||||
if (-not (Update-IfNew -FilePath $WINDSURF_FILE -AgentName 'Windsurf')) { $ok = $false }
|
||||
if (-not (Update-IfNew -FilePath $JUNIE_FILE -AgentName 'Junie')) { $ok = $false }
|
||||
if (-not (Update-IfNew -FilePath $KILOCODE_FILE -AgentName 'Kilo Code')) { $ok = $false }
|
||||
if (-not (Update-IfNew -FilePath $AUGGIE_FILE -AgentName 'Auggie CLI')) { $ok = $false }
|
||||
if (-not (Update-IfNew -FilePath $ROO_FILE -AgentName 'Roo Code')) { $ok = $false }
|
||||
if (-not (Update-IfNew -FilePath $CODEBUDDY_FILE -AgentName 'CodeBuddy CLI')) { $ok = $false }
|
||||
if (-not (Update-IfNew -FilePath $QODER_FILE -AgentName 'Qoder CLI')) { $ok = $false }
|
||||
if (-not (Update-IfNew -FilePath $SHAI_FILE -AgentName 'SHAI')) { $ok = $false }
|
||||
if (-not (Update-IfNew -FilePath $TABNINE_FILE -AgentName 'Tabnine CLI')) { $ok = $false }
|
||||
if (-not (Update-IfNew -FilePath $AGY_FILE -AgentName 'Antigravity')) { $ok = $false }
|
||||
if (-not (Update-IfNew -FilePath $VIBE_FILE -AgentName 'Mistral Vibe')) { $ok = $false }
|
||||
if (-not (Update-IfNew -FilePath $KIMI_FILE -AgentName 'Kimi Code')) { $ok = $false }
|
||||
if (-not (Update-IfNew -FilePath $TRAE_FILE -AgentName 'Trae')) { $ok = $false }
|
||||
if (-not (Update-IfNew -FilePath $IFLOW_FILE -AgentName 'iFlow CLI')) { $ok = $false }
|
||||
|
||||
if (-not $found) {
|
||||
Write-Info 'No existing agent files found, creating default Claude file...'
|
||||
if (-not (Update-AgentFile -TargetFile $CLAUDE_FILE -AgentName 'Claude Code')) { $ok = $false }
|
||||
}
|
||||
return $ok
|
||||
}
|
||||
|
||||
function Print-Summary {
|
||||
Write-Host ''
|
||||
Write-Info 'Summary of changes:'
|
||||
if ($NEW_LANG) { Write-Host " - Added language: $NEW_LANG" }
|
||||
if ($NEW_FRAMEWORK) { Write-Host " - Added framework: $NEW_FRAMEWORK" }
|
||||
if ($NEW_DB -and $NEW_DB -ne 'N/A') { Write-Host " - Added database: $NEW_DB" }
|
||||
Write-Host ''
|
||||
Write-Info 'Usage: ./update-agent-context.ps1 [-AgentType claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|junie|kilocode|auggie|roo|codebuddy|amp|shai|tabnine|kiro-cli|agy|bob|vibe|qodercli|kimi|trae|pi|iflow|forge|goose|generic]'
|
||||
}
|
||||
|
||||
function Main {
|
||||
Validate-Environment
|
||||
Write-Info "=== Updating agent context files for feature $CURRENT_BRANCH ==="
|
||||
if (-not (Parse-PlanData -PlanFile $NEW_PLAN)) { Write-Err 'Failed to parse plan data'; exit 1 }
|
||||
$success = $true
|
||||
if ($AgentType) {
|
||||
Write-Info "Updating specific agent: $AgentType"
|
||||
if (-not (Update-SpecificAgent -Type $AgentType)) { $success = $false }
|
||||
}
|
||||
else {
|
||||
Write-Info 'No agent specified, updating all existing agent files...'
|
||||
if (-not (Update-AllExistingAgents)) { $success = $false }
|
||||
}
|
||||
Print-Summary
|
||||
if ($success) { Write-Success 'Agent context update completed successfully'; exit 0 } else { Write-Err 'Agent context update completed with errors'; exit 1 }
|
||||
}
|
||||
|
||||
Main
|
||||
@@ -12,13 +12,13 @@ This transformation is now possible because AI can understand and implement comp
|
||||
|
||||
In this new world, maintaining software means evolving specifications. The intent of the development team is expressed in natural language ("**intent-driven development**"), design assets, core principles and other guidelines. The **lingua franca** of development moves to a higher level, and code is the last-mile approach.
|
||||
|
||||
Debugging means fixing specifications and their implementation plans that generate incorrect code. Refactoring means restructuring for clarity. The entire development process reorganizes around specifications as the central source of truth, with implementation plans and code as the continuously regenerated output. Updating apps with new features or creating a new parallel implementation because we are creative beings, means revisiting the specification and creating new implementation plans. This process is therefore a 0 -> 1, (1', ..), 2, 3, N.
|
||||
Debugging means fixing specifications and their implementation plans that generate incorrect code. Refactoring means restructuring for clarity. The entire development workflow reorganizes around specifications as the central source of truth, with implementation plans and code as the continuously regenerated output. Updating apps with new features or creating a new parallel implementation because we are creative beings, means revisiting the specification and creating new implementation plans. This process is therefore a 0 -> 1, (1', ..), 2, 3, N.
|
||||
|
||||
The development team focuses in on their creativity, experimentation, their critical thinking.
|
||||
|
||||
## The SDD Process in Practice
|
||||
## The SDD Workflow in Practice
|
||||
|
||||
The process begins with an idea—often vague and incomplete. Through iterative dialogue with AI, this idea becomes a comprehensive PRD. The AI asks clarifying questions, identifies edge cases, and helps define precise acceptance criteria. What might take days of meetings and documentation in traditional development happens in hours of focused specification work. This transforms the traditional SDLC—requirements and design become continuous activities rather than discrete phases. This is supportive of a **team process**, where team-reviewed specifications are expressed, versioned, reviewed, and accepted.
|
||||
The workflow begins with an idea—often vague and incomplete. Through iterative dialogue with AI, this idea becomes a comprehensive PRD. The AI asks clarifying questions, identifies edge cases, and helps define precise acceptance criteria. What might take days of meetings and documentation in traditional development happens in hours of focused specification work. This transforms the traditional SDLC—requirements and design become continuous activities rather than discrete phases. This is supportive of a **team process**, where team-reviewed specifications are expressed and versioned, created in branches, and merged.
|
||||
|
||||
When a product manager updates acceptance criteria, implementation plans automatically flag affected technical decisions. When an architect discovers a better pattern, the PRD updates to reflect new possibilities.
|
||||
|
||||
@@ -42,7 +42,7 @@ Third, the pace of change accelerates. Requirements change far more rapidly toda
|
||||
|
||||
SDD can support what-if/simulation experiments: "If we need to re-implement or change the application to promote a business need to sell more T-shirts, how would we implement and experiment for that?"
|
||||
|
||||
SDD transforms requirement changes from obstacles into normal process. When specifications drive implementation, pivots become systematic regenerations rather than manual rewrites. Change a core requirement in the PRD, and affected implementation plans update automatically. Modify a user story, and corresponding API endpoints regenerate. This isn't just about initial development—it's about maintaining engineering velocity through inevitable changes.
|
||||
SDD transforms requirement changes from obstacles into normal workflow. When specifications drive implementation, pivots become systematic regenerations rather than manual rewrites. Change a core requirement in the PRD, and affected implementation plans update automatically. Modify a user story, and corresponding API endpoints regenerate. This isn't just about initial development—it's about maintaining engineering velocity through inevitable changes.
|
||||
|
||||
## Core Principles
|
||||
|
||||
@@ -65,14 +65,14 @@ Today, practicing SDD requires assembling existing tools and maintaining discipl
|
||||
- AI assistants for iterative specification development
|
||||
- Research agents for gathering technical context
|
||||
- Code generation tools for translating specifications to implementation
|
||||
- Version control systems adapted for specification-first processes
|
||||
- Version control systems adapted for specification-first workflows
|
||||
- Consistency checking through AI analysis of specification documents
|
||||
|
||||
The key is treating specifications as the source of truth, with code as the generated output that serves the specification rather than the other way around.
|
||||
|
||||
## Streamlining SDD with Commands
|
||||
|
||||
The SDD methodology is significantly enhanced through three powerful commands that automate the specification → planning → tasking process:
|
||||
The SDD methodology is significantly enhanced through three powerful commands that automate the specification → planning → tasking workflow:
|
||||
|
||||
### The `/speckit.specify` Command
|
||||
|
||||
@@ -104,7 +104,7 @@ After a plan is created, this command analyzes the plan and related design docum
|
||||
|
||||
### Example: Building a Chat Feature
|
||||
|
||||
Here's how these commands transform the traditional development process:
|
||||
Here's how these commands transform the traditional development workflow:
|
||||
|
||||
**Traditional Approach:**
|
||||
|
||||
@@ -149,7 +149,7 @@ In 15 minutes, you have:
|
||||
- A detailed implementation plan with technology choices and rationale
|
||||
- API contracts and data models ready for code generation
|
||||
- Comprehensive test scenarios for both automated and manual testing
|
||||
- All documents properly versioned and reviewable
|
||||
- All documents properly versioned in a feature branch
|
||||
|
||||
### The Power of Structured Automation
|
||||
|
||||
@@ -273,7 +273,7 @@ The templates transform the LLM from a creative writer into a disciplined specif
|
||||
|
||||
## The Constitutional Foundation: Enforcing Architectural Discipline
|
||||
|
||||
At the heart of SDD lies a constitution—a set of governing principles that define how specifications become code. The constitution acts as the architectural DNA of the system, ensuring that every generated implementation maintains consistency, simplicity, and quality. Run `/speckit.constitution` to create or update your project's constitution; the resulting file is stored at `.specify/memory/constitution.md`.
|
||||
At the heart of SDD lies a constitution—a set of immutable principles that govern how specifications become code. The constitution (`memory/constitution.md`) acts as the architectural DNA of the system, ensuring that every generated implementation maintains consistency, simplicity, and quality.
|
||||
|
||||
### The Nine Articles of Development
|
||||
|
||||
@@ -318,18 +318,6 @@ No implementation code shall be written before:
|
||||
|
||||
This completely inverts traditional AI code generation. Instead of generating code and hoping it works, the LLM must first generate comprehensive tests that define behavior, get them approved, and only then generate implementation.
|
||||
|
||||
#### Articles IV, V & VI: Project-Specific Standards
|
||||
|
||||
Articles IV, V, and VI are the primary customization points in the constitution. In the constitution file (`.specify/memory/constitution.md`), these slots are expressed as named principles—not as fixed numbered headings. Teams use `/speckit.constitution` to define them, and `/speckit.analyze` validates spec, plan, and task artifacts against every principle in the project constitution.
|
||||
|
||||
Common areas that projects define for these principles include:
|
||||
|
||||
- **Requirement quality and coverage** (Article IV): for example, rules about testability, unambiguity, and traceability that specifications must satisfy for that project.
|
||||
- **Operational concerns** (Article V): for example, explicit thresholds for performance, security, observability, and accessibility that the project requires.
|
||||
- **Lifecycle concerns** (Article VI): for example, how the constitution and specifications are versioned, how derived artifacts stay consistent with their source, and how implementation gaps are resolved.
|
||||
|
||||
Teams are free to name and scope these principles in whatever way best fits their project. The number of principles is also flexible—`/speckit.constitution` supports fewer or more than the template default.
|
||||
|
||||
#### Articles VII & VIII: Simplicity and Anti-Abstraction
|
||||
|
||||
These paired articles combat over-engineering:
|
||||
@@ -383,9 +371,9 @@ The implementation plan template operationalizes these articles through concrete
|
||||
|
||||
These gates act as compile-time checks for architectural principles. The LLM cannot proceed without either passing the gates or documenting justified exceptions in the "Complexity Tracking" section.
|
||||
|
||||
### The Power of Governing Principles
|
||||
### The Power of Immutable Principles
|
||||
|
||||
The constitution's power lies in its stability. While implementation details can evolve freely, changes to core principles require deliberate governance—explicit rationale, review, and versioning. This provides:
|
||||
The constitution's power lies in its immutability. While implementation details can evolve, the core principles remain constant. This provides:
|
||||
|
||||
1. **Consistency Across Time**: Code generated today follows the same principles as code generated next year
|
||||
2. **Consistency Across LLMs**: Different AI models produce architecturally compatible code
|
||||
@@ -394,7 +382,7 @@ The constitution's power lies in its stability. While implementation details can
|
||||
|
||||
### Constitutional Evolution
|
||||
|
||||
While principles are stable, both their application and the principles themselves can evolve through a governed process:
|
||||
While principles are immutable, their application can evolve:
|
||||
|
||||
```text
|
||||
Section 4.2: Amendment Process
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,93 +0,0 @@
|
||||
"""Shared GitHub-authenticated HTTP helpers.
|
||||
|
||||
Used by both ExtensionCatalog and PresetCatalog to attach
|
||||
GITHUB_TOKEN / GH_TOKEN credentials to requests targeting
|
||||
GitHub-hosted domains, while preventing token leakage to
|
||||
third-party hosts on redirects.
|
||||
"""
|
||||
|
||||
import os
|
||||
import urllib.request
|
||||
from typing import Dict
|
||||
from urllib.parse import urlparse
|
||||
|
||||
# GitHub-owned hostnames that should receive the Authorization header.
|
||||
# Includes codeload.github.com because GitHub archive URL downloads
|
||||
# (e.g. /archive/refs/tags/<tag>.zip) redirect there and require auth
|
||||
# for private repositories.
|
||||
GITHUB_HOSTS = frozenset({
|
||||
"raw.githubusercontent.com",
|
||||
"github.com",
|
||||
"api.github.com",
|
||||
"codeload.github.com",
|
||||
})
|
||||
|
||||
|
||||
def build_github_request(url: str) -> urllib.request.Request:
|
||||
"""Build a urllib Request, adding a GitHub auth header when available.
|
||||
|
||||
Reads GITHUB_TOKEN or GH_TOKEN from the environment and attaches an
|
||||
``Authorization: Bearer <value>`` header when the target hostname is one
|
||||
of the known GitHub-owned domains. Non-GitHub URLs are returned as plain
|
||||
requests so credentials are never leaked to third-party hosts.
|
||||
|
||||
Raises:
|
||||
ValueError: If ``url`` is empty or whitespace-only.
|
||||
ValueError: If ``url`` does not use the ``http`` or ``https`` scheme.
|
||||
ValueError: If ``url`` does not include a hostname.
|
||||
"""
|
||||
headers: Dict[str, str] = {}
|
||||
url = url.strip()
|
||||
if not url:
|
||||
raise ValueError("url must not be empty")
|
||||
parsed = urlparse(url)
|
||||
if parsed.scheme not in {"http", "https"}:
|
||||
raise ValueError(f"url must start with http:// or https://, got: {url!r}")
|
||||
if not parsed.hostname:
|
||||
raise ValueError(f"url must include a hostname, got: {url!r}")
|
||||
github_token = (os.environ.get("GITHUB_TOKEN") or "").strip()
|
||||
gh_token = (os.environ.get("GH_TOKEN") or "").strip()
|
||||
token = github_token or gh_token or None
|
||||
hostname = parsed.hostname.lower()
|
||||
if token and hostname in GITHUB_HOSTS:
|
||||
headers["Authorization"] = f"Bearer {token}"
|
||||
return urllib.request.Request(url, headers=headers)
|
||||
|
||||
|
||||
class _StripAuthOnRedirect(urllib.request.HTTPRedirectHandler):
|
||||
"""Redirect handler that drops the Authorization header when leaving GitHub.
|
||||
|
||||
Prevents token leakage to CDNs or other third-party hosts that GitHub
|
||||
may redirect to (e.g. S3 for release asset downloads, objects.githubusercontent.com).
|
||||
Auth is preserved as long as the redirect target remains within GITHUB_HOSTS.
|
||||
"""
|
||||
|
||||
def redirect_request(self, req, fp, code, msg, headers, newurl):
|
||||
original_auth = req.get_header("Authorization")
|
||||
new_req = super().redirect_request(req, fp, code, msg, headers, newurl)
|
||||
if new_req is not None:
|
||||
hostname = (urlparse(newurl).hostname or "").lower()
|
||||
if hostname in GITHUB_HOSTS:
|
||||
if original_auth:
|
||||
new_req.add_unredirected_header("Authorization", original_auth)
|
||||
else:
|
||||
new_req.headers.pop("Authorization", None)
|
||||
new_req.unredirected_hdrs.pop("Authorization", None)
|
||||
return new_req
|
||||
|
||||
|
||||
def open_github_url(url: str, timeout: int = 10):
|
||||
"""Open a URL with GitHub auth, stripping the header on cross-host redirects.
|
||||
|
||||
When the request carries an Authorization header, a custom redirect
|
||||
handler drops that header if the redirect target is not a GitHub-owned
|
||||
domain, preventing token leakage to CDNs or other third-party hosts
|
||||
that GitHub may redirect to (e.g. S3 for release asset downloads).
|
||||
"""
|
||||
req = build_github_request(url)
|
||||
|
||||
if not req.get_header("Authorization"):
|
||||
return urllib.request.urlopen(req, timeout=timeout)
|
||||
|
||||
opener = urllib.request.build_opener(_StripAuthOnRedirect)
|
||||
return opener.open(req, timeout=timeout)
|
||||
@@ -6,9 +6,8 @@ Used by both the extension system and the preset system to write
|
||||
command files into agent-specific directories in the correct format.
|
||||
"""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Any, Optional
|
||||
from typing import Dict, List, Any
|
||||
|
||||
import platform
|
||||
import re
|
||||
@@ -111,9 +110,9 @@ class CommandRegistrar:
|
||||
"""Normalize script paths in frontmatter to generated project locations.
|
||||
|
||||
Rewrites known repo-relative and top-level script paths under the
|
||||
``scripts`` key (for example ``../../scripts/``,
|
||||
``../../templates/``, ``../../memory/``, ``scripts/``, ``templates/``, and
|
||||
``memory/``) to the ``.specify/...`` paths used in generated projects.
|
||||
`scripts` and `agent_scripts` keys (for example `../../scripts/`,
|
||||
`../../templates/`, `../../memory/`, `scripts/`, `templates/`, and
|
||||
`memory/`) to the `.specify/...` paths used in generated projects.
|
||||
|
||||
Args:
|
||||
frontmatter: Frontmatter dictionary
|
||||
@@ -123,8 +122,11 @@ class CommandRegistrar:
|
||||
"""
|
||||
frontmatter = deepcopy(frontmatter)
|
||||
|
||||
scripts = frontmatter.get("scripts")
|
||||
if isinstance(scripts, dict):
|
||||
for script_key in ("scripts", "agent_scripts"):
|
||||
scripts = frontmatter.get(script_key)
|
||||
if not isinstance(scripts, dict):
|
||||
continue
|
||||
|
||||
for key, script_path in scripts.items():
|
||||
if isinstance(script_path, str):
|
||||
scripts[key] = self.rewrite_project_relative_paths(script_path)
|
||||
@@ -282,8 +284,7 @@ class CommandRegistrar:
|
||||
if not isinstance(frontmatter, dict):
|
||||
frontmatter = {}
|
||||
|
||||
agent_config = self.AGENT_CONFIGS.get(agent_name, {})
|
||||
if agent_config.get("extension") == "/SKILL.md":
|
||||
if agent_name in {"codex", "kimi"}:
|
||||
body = self.resolve_skill_placeholders(
|
||||
agent_name, frontmatter, body, project_root
|
||||
)
|
||||
@@ -332,8 +333,11 @@ class CommandRegistrar:
|
||||
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 = {}
|
||||
|
||||
init_opts = load_init_options(project_root)
|
||||
if not isinstance(init_opts, dict):
|
||||
@@ -347,14 +351,17 @@ class CommandRegistrar:
|
||||
)
|
||||
secondary_variant = "sh" if default_variant == "ps" else "ps"
|
||||
|
||||
if default_variant in scripts:
|
||||
if default_variant in scripts or default_variant in agent_scripts:
|
||||
fallback_order.append(default_variant)
|
||||
if secondary_variant in scripts:
|
||||
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
|
||||
|
||||
@@ -363,12 +370,14 @@ class CommandRegistrar:
|
||||
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)
|
||||
|
||||
body = body.replace("{ARGS}", "$ARGUMENTS").replace("__AGENT__", agent_name)
|
||||
|
||||
# Resolve __CONTEXT_FILE__ from init-options
|
||||
context_file = init_opts.get("context_file") or ""
|
||||
body = body.replace("__CONTEXT_FILE__", context_file)
|
||||
|
||||
return CommandRegistrar.rewrite_project_relative_paths(body)
|
||||
|
||||
def _convert_argument_placeholder(
|
||||
@@ -401,28 +410,6 @@ class CommandRegistrar:
|
||||
|
||||
return f"speckit-{short_name}"
|
||||
|
||||
@staticmethod
|
||||
def _ensure_inside(candidate: Path, base: Path) -> None:
|
||||
"""Validate that a write target stays within the expected base directory.
|
||||
|
||||
Uses lexical normalization so traversal via ``..`` or absolute paths is
|
||||
rejected while intentionally symlinked sub-directories remain
|
||||
supported.
|
||||
|
||||
Args:
|
||||
candidate: Path that will be written.
|
||||
base: Directory the write must remain within.
|
||||
|
||||
Raises:
|
||||
ValueError: If the normalized candidate path escapes ``base``.
|
||||
"""
|
||||
normalized = Path(os.path.normpath(candidate))
|
||||
base_normalized = Path(os.path.normpath(base))
|
||||
if not normalized.is_relative_to(base_normalized):
|
||||
raise ValueError(
|
||||
f"Output path {candidate!r} escapes directory {base!r}"
|
||||
)
|
||||
|
||||
def register_commands(
|
||||
self,
|
||||
agent_name: str,
|
||||
@@ -469,15 +456,6 @@ class CommandRegistrar:
|
||||
content = source_file.read_text(encoding="utf-8")
|
||||
frontmatter, body = self.parse_frontmatter(content)
|
||||
|
||||
if frontmatter.get("strategy") == "wrap":
|
||||
from .presets import _substitute_core_template
|
||||
body, core_frontmatter = _substitute_core_template(body, cmd_name, project_root, self)
|
||||
frontmatter = dict(frontmatter)
|
||||
for key in ("scripts", "agent_scripts"):
|
||||
if key not in frontmatter and key in core_frontmatter:
|
||||
frontmatter[key] = core_frontmatter[key]
|
||||
frontmatter.pop("strategy", None)
|
||||
|
||||
frontmatter = self._adjust_script_paths(frontmatter)
|
||||
|
||||
for key in agent_config.get("strip_frontmatter_keys", []):
|
||||
@@ -505,12 +483,10 @@ class CommandRegistrar:
|
||||
project_root,
|
||||
)
|
||||
elif agent_config["format"] == "markdown":
|
||||
body = self.resolve_skill_placeholders(agent_name, frontmatter, body, project_root)
|
||||
body = self._convert_argument_placeholder(body, "$ARGUMENTS", agent_config["args"])
|
||||
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":
|
||||
body = self.resolve_skill_placeholders(agent_name, frontmatter, body, project_root)
|
||||
body = self._convert_argument_placeholder(body, "$ARGUMENTS", agent_config["args"])
|
||||
output = self.render_toml_command(frontmatter, body, source_id)
|
||||
elif agent_config["format"] == "yaml":
|
||||
output = self.render_yaml_command(
|
||||
@@ -520,7 +496,6 @@ class CommandRegistrar:
|
||||
raise ValueError(f"Unsupported format: {agent_config['format']}")
|
||||
|
||||
dest_file = commands_dir / f"{output_name}{agent_config['extension']}"
|
||||
self._ensure_inside(dest_file, commands_dir)
|
||||
dest_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
dest_file.write_text(output, encoding="utf-8")
|
||||
|
||||
@@ -586,7 +561,12 @@ class CommandRegistrar:
|
||||
alias_file = (
|
||||
commands_dir / f"{alias_output_name}{agent_config['extension']}"
|
||||
)
|
||||
self._ensure_inside(alias_file, commands_dir)
|
||||
try:
|
||||
alias_file.resolve().relative_to(commands_dir.resolve())
|
||||
except ValueError:
|
||||
raise ValueError(
|
||||
f"Alias output path escapes commands directory: {alias_file!r}"
|
||||
)
|
||||
alias_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
alias_file.write_text(alias_output, encoding="utf-8")
|
||||
if agent_name == "copilot":
|
||||
@@ -606,7 +586,6 @@ class CommandRegistrar:
|
||||
prompts_dir = project_root / ".github" / "prompts"
|
||||
prompts_dir.mkdir(parents=True, exist_ok=True)
|
||||
prompt_file = prompts_dir / f"{cmd_name}.prompt.md"
|
||||
CommandRegistrar._ensure_inside(prompt_file, prompts_dir)
|
||||
prompt_file.write_text(f"---\nagent: {cmd_name}\n---\n", encoding="utf-8")
|
||||
|
||||
def register_commands_for_all_agents(
|
||||
@@ -652,49 +631,6 @@ class CommandRegistrar:
|
||||
|
||||
return results
|
||||
|
||||
def register_commands_for_non_skill_agents(
|
||||
self,
|
||||
commands: List[Dict[str, Any]],
|
||||
source_id: str,
|
||||
source_dir: Path,
|
||||
project_root: Path,
|
||||
context_note: Optional[str] = None,
|
||||
) -> Dict[str, List[str]]:
|
||||
"""Register commands for all non-skill agents in the project.
|
||||
|
||||
Like register_commands_for_all_agents but skips skill-based agents
|
||||
(those with extension '/SKILL.md'). Used by reconciliation to avoid
|
||||
overwriting properly formatted SKILL.md files.
|
||||
|
||||
Args:
|
||||
commands: List of command info dicts
|
||||
source_id: Identifier of the source
|
||||
source_dir: Directory containing command source files
|
||||
project_root: Path to project root
|
||||
context_note: Custom context comment for markdown output
|
||||
|
||||
Returns:
|
||||
Dictionary mapping agent names to list of registered commands
|
||||
"""
|
||||
results = {}
|
||||
self._ensure_configs()
|
||||
for agent_name, agent_config in self.AGENT_CONFIGS.items():
|
||||
if agent_config.get("extension") == "/SKILL.md":
|
||||
continue
|
||||
agent_dir = project_root / agent_config["dir"]
|
||||
if agent_dir.exists():
|
||||
try:
|
||||
registered = self.register_commands(
|
||||
agent_name, commands, source_id,
|
||||
source_dir, project_root,
|
||||
context_note=context_note,
|
||||
)
|
||||
if registered:
|
||||
results[agent_name] = registered
|
||||
except ValueError:
|
||||
continue
|
||||
return results
|
||||
|
||||
def unregister_commands(
|
||||
self, registered_commands: Dict[str, List[str]], project_root: Path
|
||||
) -> None:
|
||||
|
||||
@@ -139,23 +139,12 @@ class ExtensionManifest:
|
||||
def _load_yaml(self, path: Path) -> dict:
|
||||
"""Load YAML file safely."""
|
||||
try:
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
data = yaml.safe_load(f)
|
||||
with open(path, 'r') as f:
|
||||
return yaml.safe_load(f) or {}
|
||||
except yaml.YAMLError as e:
|
||||
raise ValidationError(f"Invalid YAML in {path}: {e}")
|
||||
except FileNotFoundError:
|
||||
raise ValidationError(f"Manifest not found: {path}")
|
||||
except UnicodeDecodeError as e:
|
||||
raise ValidationError(
|
||||
f"Manifest is not valid UTF-8: {path} ({e.reason} at byte {e.start})"
|
||||
)
|
||||
except OSError as e:
|
||||
raise ValidationError(f"Could not read manifest {path}: {e}")
|
||||
if not isinstance(data, dict):
|
||||
raise ValidationError(
|
||||
f"Manifest must be a YAML mapping, got {type(data).__name__}: {path}"
|
||||
)
|
||||
return data
|
||||
|
||||
def _validate(self):
|
||||
"""Validate manifest structure and required fields."""
|
||||
@@ -962,40 +951,29 @@ class ExtensionManager:
|
||||
|
||||
return written
|
||||
|
||||
def _unregister_extension_skills(
|
||||
self,
|
||||
skill_names: List[str],
|
||||
extension_id: str,
|
||||
skills_dir: Optional[Path] = None,
|
||||
) -> None:
|
||||
def _unregister_extension_skills(self, skill_names: List[str], extension_id: str) -> None:
|
||||
"""Remove SKILL.md directories for extension skills.
|
||||
|
||||
Called during extension removal to clean up skill files that
|
||||
were created by ``_register_extension_skills()``.
|
||||
|
||||
If *skills_dir* is not provided and ``_get_skills_dir()`` returns
|
||||
``None`` (e.g. the user removed init-options.json or toggled
|
||||
ai_skills after installation), we fall back to scanning all known
|
||||
agent skills directories so that orphaned skill directories are
|
||||
still cleaned up. In that case each candidate directory is
|
||||
verified against the SKILL.md ``metadata.source`` field before
|
||||
removal to avoid accidentally deleting user-created skills with
|
||||
the same name.
|
||||
If ``_get_skills_dir()`` returns ``None`` (e.g. the user removed
|
||||
init-options.json or toggled ai_skills after installation), we
|
||||
fall back to scanning all known agent skills directories so that
|
||||
orphaned skill directories are still cleaned up. In that case
|
||||
each candidate directory is verified against the SKILL.md
|
||||
``metadata.source`` field before removal to avoid accidentally
|
||||
deleting user-created skills with the same name.
|
||||
|
||||
Args:
|
||||
skill_names: List of skill names to remove.
|
||||
extension_id: Extension ID used to verify ownership during
|
||||
fallback candidate scanning.
|
||||
skills_dir: Optional explicit skills directory to use instead
|
||||
of resolving via ``_get_skills_dir()``. Useful when the
|
||||
caller needs to target a specific agent's skills directory
|
||||
regardless of the currently-active agent in init-options.
|
||||
"""
|
||||
if not skill_names:
|
||||
return
|
||||
|
||||
if skills_dir is None:
|
||||
skills_dir = self._get_skills_dir()
|
||||
skills_dir = self._get_skills_dir()
|
||||
|
||||
if skills_dir:
|
||||
# Fast path: we know the exact skills directory
|
||||
@@ -1119,7 +1097,7 @@ class ExtensionManager:
|
||||
raise CompatibilityError(
|
||||
f"Extension requires spec-kit {required}, "
|
||||
f"but {speckit_version} is installed.\n"
|
||||
f"Upgrade spec-kit with: {REINSTALL_COMMAND}"
|
||||
f"Upgrade spec-kit with: uv tool install specify-cli --force"
|
||||
)
|
||||
except InvalidSpecifier:
|
||||
raise CompatibilityError(f"Invalid version specifier: {required}")
|
||||
@@ -1343,156 +1321,6 @@ class ExtensionManager:
|
||||
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def _valid_name_list(value: Any) -> List[str]:
|
||||
"""Return string entries from a registry list, ignoring corrupt values."""
|
||||
if not isinstance(value, list):
|
||||
return []
|
||||
return [item for item in value if isinstance(item, str)]
|
||||
|
||||
def unregister_agent_artifacts(self, agent_name: str) -> None:
|
||||
"""Remove extension files registered for a specific agent.
|
||||
|
||||
Extension command files are tracked per agent in ``registered_commands``.
|
||||
Extension skills are scoped to the provided *agent_name*; they are removed
|
||||
from that agent's skills directory (resolved via its integration config)
|
||||
and the registry field is cleared.
|
||||
|
||||
Skips cleanup when *agent_name* is not a supported agent to avoid
|
||||
losing registry entries while leaving orphaned files on disk.
|
||||
"""
|
||||
if not agent_name:
|
||||
return
|
||||
|
||||
registrar = CommandRegistrar()
|
||||
if agent_name not in registrar.AGENT_CONFIGS:
|
||||
return
|
||||
|
||||
# Resolve the skills directory for the specific agent so cleanup is
|
||||
# agent-scoped and does not depend on the currently-active agent in
|
||||
# init-options. Use the same helper that extension install uses.
|
||||
from . import _get_skills_dir as resolve_skills_dir
|
||||
|
||||
agent_skills_dir = resolve_skills_dir(self.project_root, agent_name)
|
||||
|
||||
for ext_id, metadata in self.registry.list().items():
|
||||
updates: Dict[str, Any] = {}
|
||||
|
||||
registered_commands = metadata.get("registered_commands", {})
|
||||
if isinstance(registered_commands, dict) and agent_name in registered_commands:
|
||||
command_names = self._valid_name_list(registered_commands.get(agent_name))
|
||||
if command_names:
|
||||
registrar.unregister_commands({agent_name: command_names}, self.project_root)
|
||||
|
||||
new_registered = copy.deepcopy(registered_commands)
|
||||
new_registered.pop(agent_name, None)
|
||||
updates["registered_commands"] = new_registered
|
||||
|
||||
registered_skills = self._valid_name_list(metadata.get("registered_skills", []))
|
||||
if registered_skills:
|
||||
# Only pass the resolved skills_dir when it actually exists.
|
||||
# Otherwise let _unregister_extension_skills fall back to
|
||||
# scanning all known agent skills directories, which is useful
|
||||
# for cleaning up stale entries created by earlier installs.
|
||||
skills_dir = agent_skills_dir if agent_skills_dir.is_dir() else None
|
||||
self._unregister_extension_skills(
|
||||
registered_skills, ext_id, skills_dir=skills_dir
|
||||
)
|
||||
|
||||
# Only reconcile registry state when cleanup was scoped to a
|
||||
# specific existing directory. When skills_dir is None,
|
||||
# _unregister_extension_skills falls back to scanning multiple
|
||||
# candidate directories, so agent_skills_dir cannot be used to
|
||||
# infer what was removed. When skills_dir is set,
|
||||
# _unregister_extension_skills may intentionally skip deletion
|
||||
# when ownership cannot be verified (e.g., corrupted/missing
|
||||
# SKILL.md or mismatching metadata.source). Only drop registry
|
||||
# entries for skill directories that were actually removed so
|
||||
# future cleanup attempts can still find skipped ones.
|
||||
if skills_dir is not None:
|
||||
remaining_skills = [
|
||||
skill_name
|
||||
for skill_name in registered_skills
|
||||
if (skills_dir / skill_name).is_dir()
|
||||
]
|
||||
if remaining_skills != registered_skills:
|
||||
updates["registered_skills"] = remaining_skills
|
||||
|
||||
if updates:
|
||||
self.registry.update(ext_id, updates)
|
||||
|
||||
def register_enabled_extensions_for_agent(self, agent_name: str) -> None:
|
||||
"""Register installed, enabled extensions for ``agent_name``.
|
||||
|
||||
This is intended to be called after switching integrations. Command
|
||||
registration is scoped to the explicit ``agent_name`` argument, but some
|
||||
behavior still depends on the current init-options state (for example,
|
||||
skills-mode handling uses the active ``ai`` / ``ai_skills`` settings).
|
||||
|
||||
Callers should therefore pass the agent that has just been made active
|
||||
in init-options; in normal use, ``agent_name`` is expected to match the
|
||||
current ``ai`` value. This mirrors extension install behavior while
|
||||
avoiding stale default-mode command directories when that active agent
|
||||
is running in skills mode (notably Copilot ``--skills``).
|
||||
"""
|
||||
if not agent_name:
|
||||
return
|
||||
|
||||
from . import load_init_options
|
||||
|
||||
registrar = CommandRegistrar()
|
||||
agent_config = registrar.AGENT_CONFIGS.get(agent_name)
|
||||
init_options = load_init_options(self.project_root)
|
||||
if not isinstance(init_options, dict):
|
||||
init_options = {}
|
||||
|
||||
active_agent = init_options.get("ai")
|
||||
skills_mode_active = (
|
||||
active_agent == agent_name
|
||||
and bool(init_options.get("ai_skills"))
|
||||
and bool(agent_config)
|
||||
and agent_config.get("extension") != "/SKILL.md"
|
||||
)
|
||||
|
||||
for ext_id, metadata in self.registry.list().items():
|
||||
if not metadata.get("enabled", True):
|
||||
continue
|
||||
|
||||
manifest = self.get_extension(ext_id)
|
||||
if manifest is None:
|
||||
continue
|
||||
|
||||
ext_dir = self.extensions_dir / ext_id
|
||||
updates: Dict[str, Any] = {}
|
||||
|
||||
if agent_config and not skills_mode_active:
|
||||
registered = registrar.register_commands_for_agent(
|
||||
agent_name, manifest, ext_dir, self.project_root
|
||||
)
|
||||
registered_commands = metadata.get("registered_commands", {})
|
||||
if not isinstance(registered_commands, dict):
|
||||
registered_commands = {}
|
||||
new_registered = copy.deepcopy(registered_commands)
|
||||
if registered:
|
||||
new_registered[agent_name] = registered
|
||||
else:
|
||||
# Registration returned empty list (e.g., corrupted
|
||||
# manifest pointing at missing command files). Clear
|
||||
# stale entry so later cleanup doesn't try to remove
|
||||
# files that were never written.
|
||||
new_registered.pop(agent_name, None)
|
||||
if new_registered != registered_commands:
|
||||
updates["registered_commands"] = new_registered
|
||||
|
||||
registered_skills = self._register_extension_skills(manifest, ext_dir)
|
||||
if registered_skills:
|
||||
existing_skills = self._valid_name_list(metadata.get("registered_skills", []))
|
||||
merged_skills = list(dict.fromkeys(existing_skills + registered_skills))
|
||||
updates["registered_skills"] = merged_skills
|
||||
|
||||
if updates:
|
||||
self.registry.update(ext_id, updates)
|
||||
|
||||
def list_installed(self) -> List[Dict[str, Any]]:
|
||||
"""List all installed extensions with metadata.
|
||||
|
||||
@@ -1706,22 +1534,6 @@ class ExtensionCatalog:
|
||||
if not parsed.netloc:
|
||||
raise ValidationError("Catalog URL must be a valid URL with a host.")
|
||||
|
||||
def _make_request(self, url: str):
|
||||
"""Build a urllib Request, adding a GitHub auth header when available.
|
||||
|
||||
Delegates to :func:`specify_cli._github_http.build_github_request`.
|
||||
"""
|
||||
from specify_cli._github_http import build_github_request
|
||||
return build_github_request(url)
|
||||
|
||||
def _open_url(self, url: str, timeout: int = 10):
|
||||
"""Open a URL with GitHub auth, stripping the header on cross-host redirects.
|
||||
|
||||
Delegates to :func:`specify_cli._github_http.open_github_url`.
|
||||
"""
|
||||
from specify_cli._github_http import open_github_url
|
||||
return open_github_url(url, timeout)
|
||||
|
||||
def _load_catalog_config(self, config_path: Path) -> Optional[List[CatalogEntry]]:
|
||||
"""Load catalog stack configuration from a YAML file.
|
||||
|
||||
@@ -1878,6 +1690,7 @@ class ExtensionCatalog:
|
||||
Raises:
|
||||
ExtensionError: If catalog cannot be fetched or has invalid format
|
||||
"""
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
|
||||
# Determine cache file paths (backward compat for default catalog)
|
||||
@@ -1911,7 +1724,7 @@ class ExtensionCatalog:
|
||||
|
||||
# Fetch from network
|
||||
try:
|
||||
with self._open_url(entry.url, timeout=10) as response:
|
||||
with urllib.request.urlopen(entry.url, timeout=10) as response:
|
||||
catalog_data = json.loads(response.read())
|
||||
|
||||
if "schema_version" not in catalog_data or "extensions" not in catalog_data:
|
||||
@@ -2025,9 +1838,10 @@ class ExtensionCatalog:
|
||||
catalog_url = self.get_catalog_url()
|
||||
|
||||
try:
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
|
||||
with self._open_url(catalog_url, timeout=10) as response:
|
||||
with urllib.request.urlopen(catalog_url, timeout=10) as response:
|
||||
catalog_data = json.loads(response.read())
|
||||
|
||||
# Validate catalog structure
|
||||
@@ -2138,6 +1952,7 @@ class ExtensionCatalog:
|
||||
Raises:
|
||||
ExtensionError: If extension not found or download fails
|
||||
"""
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
|
||||
# Get extension info from catalog
|
||||
@@ -2177,7 +1992,7 @@ class ExtensionCatalog:
|
||||
|
||||
# Download the ZIP file
|
||||
try:
|
||||
with self._open_url(download_url, timeout=60) as response:
|
||||
with urllib.request.urlopen(download_url, timeout=60) as response:
|
||||
zip_data = response.read()
|
||||
|
||||
zip_path.write_bytes(zip_data)
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
"""Runtime helpers for integration commands."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
|
||||
from .integration_state import integration_setting, integration_settings
|
||||
|
||||
|
||||
ParseOptions = Callable[[Any, str], dict[str, Any] | None]
|
||||
|
||||
|
||||
def resolve_integration_options(
|
||||
integration: Any,
|
||||
state: dict[str, Any],
|
||||
key: str,
|
||||
raw_options: str | None,
|
||||
*,
|
||||
parse_options: ParseOptions,
|
||||
) -> tuple[str | None, dict[str, Any] | None]:
|
||||
"""Resolve raw and parsed options for an integration operation."""
|
||||
if raw_options is not None:
|
||||
return raw_options, parse_options(integration, raw_options)
|
||||
|
||||
setting = integration_setting(state, key)
|
||||
stored_raw = setting.get("raw_options")
|
||||
if not isinstance(stored_raw, str):
|
||||
stored_raw = None
|
||||
|
||||
stored_parsed = setting.get("parsed_options")
|
||||
if isinstance(stored_parsed, dict):
|
||||
return stored_raw, stored_parsed or None
|
||||
|
||||
if stored_raw:
|
||||
return stored_raw, parse_options(integration, stored_raw)
|
||||
|
||||
return None, None
|
||||
|
||||
|
||||
def with_integration_setting(
|
||||
state: dict[str, Any],
|
||||
key: str,
|
||||
integration: Any,
|
||||
*,
|
||||
script_type: str | None = None,
|
||||
raw_options: str | None = None,
|
||||
parsed_options: dict[str, Any] | None = None,
|
||||
) -> dict[str, dict[str, Any]]:
|
||||
"""Return integration settings with *key* updated."""
|
||||
settings = integration_settings(state)
|
||||
current = dict(settings.get(key, {}))
|
||||
|
||||
if script_type:
|
||||
current["script"] = script_type
|
||||
if raw_options is not None:
|
||||
current["raw_options"] = raw_options
|
||||
elif "raw_options" in current and not current.get("raw_options"):
|
||||
current.pop("raw_options", None)
|
||||
|
||||
if parsed_options is not None:
|
||||
current["parsed_options"] = parsed_options
|
||||
elif raw_options is not None:
|
||||
current.pop("parsed_options", None)
|
||||
|
||||
current["invoke_separator"] = integration.effective_invoke_separator(parsed_options)
|
||||
settings[key] = current
|
||||
return settings
|
||||
|
||||
|
||||
def invoke_separator_for_integration(
|
||||
integration: Any,
|
||||
state: dict[str, Any],
|
||||
key: str,
|
||||
parsed_options: dict[str, Any] | None = None,
|
||||
) -> str:
|
||||
"""Resolve the invocation separator for stored/default integration state."""
|
||||
if parsed_options is not None:
|
||||
return integration.effective_invoke_separator(parsed_options)
|
||||
|
||||
setting = integration_setting(state, key)
|
||||
stored_separator = setting.get("invoke_separator")
|
||||
if isinstance(stored_separator, str) and stored_separator:
|
||||
return stored_separator
|
||||
|
||||
stored_parsed = setting.get("parsed_options")
|
||||
if isinstance(stored_parsed, dict):
|
||||
return integration.effective_invoke_separator(stored_parsed)
|
||||
|
||||
return integration.effective_invoke_separator(None)
|
||||
@@ -1,161 +0,0 @@
|
||||
"""State helpers for installed AI agent integrations."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
INTEGRATION_JSON = ".specify/integration.json"
|
||||
INTEGRATION_STATE_SCHEMA = 1
|
||||
|
||||
|
||||
def clean_integration_key(key: Any) -> str | None:
|
||||
"""Return a stripped integration key, or None for empty/non-string values."""
|
||||
if not isinstance(key, str) or not key.strip():
|
||||
return None
|
||||
return key.strip()
|
||||
|
||||
|
||||
def dedupe_integration_keys(keys: list[Any]) -> list[str]:
|
||||
"""Return a de-duplicated list of non-empty integration keys."""
|
||||
seen: set[str] = set()
|
||||
deduped: list[str] = []
|
||||
for key in keys:
|
||||
clean = clean_integration_key(key)
|
||||
if clean is None:
|
||||
continue
|
||||
if clean in seen:
|
||||
continue
|
||||
seen.add(clean)
|
||||
deduped.append(clean)
|
||||
return deduped
|
||||
|
||||
|
||||
def normalize_integration_settings(settings: Any) -> dict[str, dict[str, Any]]:
|
||||
"""Return JSON-safe per-integration runtime settings."""
|
||||
if not isinstance(settings, dict):
|
||||
return {}
|
||||
|
||||
normalized: dict[str, dict[str, Any]] = {}
|
||||
for key, value in settings.items():
|
||||
if not isinstance(key, str) or not key.strip() or not isinstance(value, dict):
|
||||
continue
|
||||
|
||||
clean: dict[str, Any] = {}
|
||||
script = value.get("script")
|
||||
if isinstance(script, str) and script.strip():
|
||||
clean["script"] = script.strip()
|
||||
|
||||
raw_options = value.get("raw_options")
|
||||
if isinstance(raw_options, str):
|
||||
clean["raw_options"] = raw_options
|
||||
|
||||
parsed_options = value.get("parsed_options")
|
||||
if isinstance(parsed_options, dict):
|
||||
clean["parsed_options"] = parsed_options
|
||||
|
||||
invoke_separator = value.get("invoke_separator")
|
||||
if isinstance(invoke_separator, str) and invoke_separator.strip():
|
||||
clean["invoke_separator"] = invoke_separator.strip()
|
||||
|
||||
if clean:
|
||||
normalized[key.strip()] = clean
|
||||
|
||||
return normalized
|
||||
|
||||
|
||||
def _normalized_integration_state_schema(value: Any) -> int:
|
||||
if isinstance(value, int) and not isinstance(value, bool) and value > INTEGRATION_STATE_SCHEMA:
|
||||
return value
|
||||
return INTEGRATION_STATE_SCHEMA
|
||||
|
||||
|
||||
def normalize_integration_state(data: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Normalize legacy and multi-install integration metadata."""
|
||||
legacy_key = clean_integration_key(data.get("integration"))
|
||||
default_key = clean_integration_key(data.get("default_integration")) or legacy_key
|
||||
|
||||
installed = data.get("installed_integrations")
|
||||
installed_keys = dedupe_integration_keys(installed if isinstance(installed, list) else [])
|
||||
if not default_key and installed_keys:
|
||||
default_key = installed_keys[0]
|
||||
if default_key and default_key not in installed_keys:
|
||||
installed_keys.insert(0, default_key)
|
||||
|
||||
settings = normalize_integration_settings(data.get("integration_settings"))
|
||||
|
||||
normalized = dict(data)
|
||||
normalized["integration_state_schema"] = _normalized_integration_state_schema(
|
||||
data.get("integration_state_schema")
|
||||
)
|
||||
if default_key:
|
||||
normalized["integration"] = default_key
|
||||
normalized["default_integration"] = default_key
|
||||
else:
|
||||
normalized.pop("integration", None)
|
||||
normalized.pop("default_integration", None)
|
||||
normalized["installed_integrations"] = installed_keys
|
||||
normalized["integration_settings"] = {
|
||||
key: settings[key] for key in installed_keys if key in settings
|
||||
}
|
||||
return normalized
|
||||
|
||||
|
||||
def default_integration_key(state: dict[str, Any]) -> str | None:
|
||||
"""Return the default integration key from normalized state."""
|
||||
key = state.get("default_integration") or state.get("integration")
|
||||
return clean_integration_key(key)
|
||||
|
||||
|
||||
def installed_integration_keys(state: dict[str, Any]) -> list[str]:
|
||||
"""Return installed integration keys from normalized state."""
|
||||
return dedupe_integration_keys(state.get("installed_integrations", []))
|
||||
|
||||
|
||||
def integration_settings(state: dict[str, Any]) -> dict[str, dict[str, Any]]:
|
||||
"""Return normalized per-integration settings from state."""
|
||||
return normalize_integration_settings(state.get("integration_settings"))
|
||||
|
||||
|
||||
def integration_setting(state: dict[str, Any], key: str) -> dict[str, Any]:
|
||||
"""Return stored runtime settings for *key*."""
|
||||
return dict(integration_settings(state).get(key, {}))
|
||||
|
||||
|
||||
def write_integration_json(
|
||||
project_root: Path,
|
||||
*,
|
||||
version: str,
|
||||
integration_key: str | None,
|
||||
installed_integrations: list[str] | None = None,
|
||||
settings: dict[str, dict[str, Any]] | None = None,
|
||||
) -> None:
|
||||
"""Write ``.specify/integration.json`` with legacy-compatible state."""
|
||||
dest = project_root / INTEGRATION_JSON
|
||||
dest.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
integration_key = clean_integration_key(integration_key)
|
||||
installed = dedupe_integration_keys(installed_integrations or [])
|
||||
if integration_key and integration_key not in installed:
|
||||
installed.insert(0, integration_key)
|
||||
if not integration_key and installed:
|
||||
integration_key = installed[0]
|
||||
|
||||
normalized_settings = normalize_integration_settings(settings or {})
|
||||
normalized_settings = {
|
||||
key: normalized_settings[key] for key in installed if key in normalized_settings
|
||||
}
|
||||
|
||||
data: dict[str, Any] = {
|
||||
"version": version,
|
||||
"integration_state_schema": INTEGRATION_STATE_SCHEMA,
|
||||
"installed_integrations": installed,
|
||||
"integration_settings": normalized_settings,
|
||||
}
|
||||
if integration_key:
|
||||
data["integration"] = integration_key
|
||||
data["default_integration"] = integration_key
|
||||
|
||||
dest.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
|
||||
@@ -56,7 +56,6 @@ def _register_builtins() -> None:
|
||||
from .codex import CodexIntegration
|
||||
from .copilot import CopilotIntegration
|
||||
from .cursor_agent import CursorAgentIntegration
|
||||
from .devin import DevinIntegration
|
||||
from .forge import ForgeIntegration
|
||||
from .gemini import GeminiIntegration
|
||||
from .generic import GenericIntegration
|
||||
@@ -87,7 +86,6 @@ def _register_builtins() -> None:
|
||||
_register(CodexIntegration())
|
||||
_register(CopilotIntegration())
|
||||
_register(CursorAgentIntegration())
|
||||
_register(DevinIntegration())
|
||||
_register(ForgeIntegration())
|
||||
_register(GeminiIntegration())
|
||||
_register(GenericIntegration())
|
||||
|
||||
@@ -1,18 +1,13 @@
|
||||
"""Antigravity (agy) integration — skills-based agent.
|
||||
|
||||
Antigravity uses ``.agents/skills/speckit-<name>/SKILL.md`` layout (enforced since v1.20.5).
|
||||
Antigravity uses ``.agent/skills/speckit-<name>/SKILL.md`` layout.
|
||||
Explicit command support was deprecated in version 1.20.5;
|
||||
``--skills`` defaults to ``True``.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from ..base import SkillsIntegration
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..manifest import IntegrationManifest
|
||||
|
||||
from ..base import IntegrationOption, SkillsIntegration
|
||||
|
||||
|
||||
class AgyIntegration(SkillsIntegration):
|
||||
@@ -21,32 +16,26 @@ class AgyIntegration(SkillsIntegration):
|
||||
key = "agy"
|
||||
config = {
|
||||
"name": "Antigravity",
|
||||
"folder": ".agents/",
|
||||
"folder": ".agent/",
|
||||
"commands_subdir": "skills",
|
||||
"install_url": None,
|
||||
"requires_cli": False,
|
||||
}
|
||||
registrar_config = {
|
||||
"dir": ".agents/skills",
|
||||
"dir": ".agent/skills",
|
||||
"format": "markdown",
|
||||
"args": "$ARGUMENTS",
|
||||
"extension": "/SKILL.md",
|
||||
}
|
||||
context_file = "AGENTS.md"
|
||||
|
||||
def setup(
|
||||
self,
|
||||
project_root: Path,
|
||||
manifest: IntegrationManifest,
|
||||
parsed_options: dict[str, Any] | None = None,
|
||||
**opts: Any,
|
||||
) -> list[Path]:
|
||||
import click
|
||||
|
||||
click.secho(
|
||||
"Warning: The .agents/ layout requires Antigravity v1.20.5 or newer. "
|
||||
"Please ensure your agy installation is up to date.",
|
||||
fg="yellow",
|
||||
err=True,
|
||||
)
|
||||
return super().setup(project_root, manifest, parsed_options=parsed_options, **opts)
|
||||
@classmethod
|
||||
def options(cls) -> list[IntegrationOption]:
|
||||
return [
|
||||
IntegrationOption(
|
||||
"--skills",
|
||||
is_flag=True,
|
||||
default=True,
|
||||
help="Install as agent skills (default for Antigravity since v1.20.5)",
|
||||
),
|
||||
]
|
||||
|
||||
17
src/specify_cli/integrations/agy/scripts/update-context.ps1
Normal file
17
src/specify_cli/integrations/agy/scripts/update-context.ps1
Normal file
@@ -0,0 +1,17 @@
|
||||
# update-context.ps1 — Antigravity (agy) integration: create/update AGENTS.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$repoRoot = try { git rev-parse --show-toplevel 2>$null } catch { $null }
|
||||
if (-not $repoRoot -or -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = $scriptDir
|
||||
$fsRoot = [System.IO.Path]::GetPathRoot($repoRoot)
|
||||
while ($repoRoot -and $repoRoot -ne $fsRoot -and -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = Split-Path -Parent $repoRoot
|
||||
}
|
||||
}
|
||||
|
||||
& "$repoRoot/.specify/scripts/powershell/update-agent-context.ps1" -AgentType agy
|
||||
24
src/specify_cli/integrations/agy/scripts/update-context.sh
Executable file
24
src/specify_cli/integrations/agy/scripts/update-context.sh
Executable file
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env bash
|
||||
# update-context.sh — Antigravity (agy) integration: create/update AGENTS.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
_script_dir="$(cd "$(dirname "$0")" && pwd)"
|
||||
_root="$_script_dir"
|
||||
while [ "$_root" != "/" ] && [ ! -d "$_root/.specify" ]; do _root="$(dirname "$_root")"; done
|
||||
if [ -z "${REPO_ROOT:-}" ]; then
|
||||
if [ -d "$_root/.specify" ]; then
|
||||
REPO_ROOT="$_root"
|
||||
else
|
||||
git_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
||||
if [ -n "$git_root" ] && [ -d "$git_root/.specify" ]; then
|
||||
REPO_ROOT="$git_root"
|
||||
else
|
||||
REPO_ROOT="$_root"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
exec "$REPO_ROOT/.specify/scripts/bash/update-agent-context.sh" agy
|
||||
23
src/specify_cli/integrations/amp/scripts/update-context.ps1
Normal file
23
src/specify_cli/integrations/amp/scripts/update-context.ps1
Normal file
@@ -0,0 +1,23 @@
|
||||
# update-context.ps1 — Amp integration: create/update AGENTS.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$repoRoot = try { git rev-parse --show-toplevel 2>$null } catch { $null }
|
||||
# If git did not return a repo root, or the git root does not contain .specify,
|
||||
# fall back to walking up from the script directory to find the initialized project root.
|
||||
if (-not $repoRoot -or -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = $scriptDir
|
||||
$fsRoot = [System.IO.Path]::GetPathRoot($repoRoot)
|
||||
while ($repoRoot -and $repoRoot -ne $fsRoot -and -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = Split-Path -Parent $repoRoot
|
||||
}
|
||||
}
|
||||
|
||||
& "$repoRoot/.specify/scripts/powershell/update-agent-context.ps1" -AgentType amp
|
||||
28
src/specify_cli/integrations/amp/scripts/update-context.sh
Executable file
28
src/specify_cli/integrations/amp/scripts/update-context.sh
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
# update-context.sh — Amp integration: create/update AGENTS.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
_script_dir="$(cd "$(dirname "$0")" && pwd)"
|
||||
_root="$_script_dir"
|
||||
while [ "$_root" != "/" ] && [ ! -d "$_root/.specify" ]; do _root="$(dirname "$_root")"; done
|
||||
if [ -z "${REPO_ROOT:-}" ]; then
|
||||
if [ -d "$_root/.specify" ]; then
|
||||
REPO_ROOT="$_root"
|
||||
else
|
||||
git_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
||||
if [ -n "$git_root" ] && [ -d "$git_root/.specify" ]; then
|
||||
REPO_ROOT="$git_root"
|
||||
else
|
||||
REPO_ROOT="$_root"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
exec "$REPO_ROOT/.specify/scripts/bash/update-agent-context.sh" amp
|
||||
@@ -19,4 +19,3 @@ class AuggieIntegration(MarkdownIntegration):
|
||||
"extension": ".md",
|
||||
}
|
||||
context_file = ".augment/rules/specify-rules.md"
|
||||
multi_install_safe = True
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# update-context.ps1 — Auggie CLI integration: create/update .augment/rules/specify-rules.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$repoRoot = try { git rev-parse --show-toplevel 2>$null } catch { $null }
|
||||
# If git did not return a repo root, or the git root does not contain .specify,
|
||||
# fall back to walking up from the script directory to find the initialized project root.
|
||||
if (-not $repoRoot -or -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = $scriptDir
|
||||
$fsRoot = [System.IO.Path]::GetPathRoot($repoRoot)
|
||||
while ($repoRoot -and $repoRoot -ne $fsRoot -and -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = Split-Path -Parent $repoRoot
|
||||
}
|
||||
}
|
||||
|
||||
& "$repoRoot/.specify/scripts/powershell/update-agent-context.ps1" -AgentType auggie
|
||||
28
src/specify_cli/integrations/auggie/scripts/update-context.sh
Executable file
28
src/specify_cli/integrations/auggie/scripts/update-context.sh
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
# update-context.sh — Auggie CLI integration: create/update .augment/rules/specify-rules.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
_script_dir="$(cd "$(dirname "$0")" && pwd)"
|
||||
_root="$_script_dir"
|
||||
while [ "$_root" != "/" ] && [ ! -d "$_root/.specify" ]; do _root="$(dirname "$_root")"; done
|
||||
if [ -z "${REPO_ROOT:-}" ]; then
|
||||
if [ -d "$_root/.specify" ]; then
|
||||
REPO_ROOT="$_root"
|
||||
else
|
||||
git_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
||||
if [ -n "$git_root" ] && [ -d "$git_root/.specify" ]; then
|
||||
REPO_ROOT="$git_root"
|
||||
else
|
||||
REPO_ROOT="$_root"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
exec "$REPO_ROOT/.specify/scripts/bash/update-agent-context.sh" auggie
|
||||
@@ -84,22 +84,6 @@ class IntegrationBase(ABC):
|
||||
context_file: str | None = None
|
||||
"""Relative path to the agent context file (e.g. ``CLAUDE.md``)."""
|
||||
|
||||
invoke_separator: str = "."
|
||||
"""Separator used in slash-command invocations (``"."`` → ``/speckit.plan``)."""
|
||||
|
||||
multi_install_safe: bool = False
|
||||
"""Whether this integration is declared safe to install alongside others.
|
||||
|
||||
Safe integrations must use a static, unique agent root, command directory,
|
||||
and context file. Registry tests enforce those invariants for every
|
||||
integration that sets this flag.
|
||||
"""
|
||||
|
||||
# -- Markers for managed context section ------------------------------
|
||||
|
||||
CONTEXT_MARKER_START = "<!-- SPECKIT START -->"
|
||||
CONTEXT_MARKER_END = "<!-- SPECKIT END -->"
|
||||
|
||||
# -- Public API -------------------------------------------------------
|
||||
|
||||
@classmethod
|
||||
@@ -107,18 +91,6 @@ class IntegrationBase(ABC):
|
||||
"""Return options this integration accepts. Default: none."""
|
||||
return []
|
||||
|
||||
def effective_invoke_separator(
|
||||
self, parsed_options: dict[str, Any] | None = None
|
||||
) -> str:
|
||||
"""Return the invoke separator for the given options.
|
||||
|
||||
Subclasses whose separator depends on runtime options (e.g.
|
||||
Copilot in ``--skills`` mode) should override this method.
|
||||
The default implementation ignores *parsed_options* and returns
|
||||
the class-level ``invoke_separator``.
|
||||
"""
|
||||
return self.invoke_separator
|
||||
|
||||
def build_exec_args(
|
||||
self,
|
||||
prompt: str,
|
||||
@@ -145,12 +117,11 @@ class IntegrationBase(ABC):
|
||||
agents or ``"/speckit-specify my-feature"`` for skills agents.
|
||||
|
||||
*command_name* may be a full dotted name like
|
||||
``"speckit.specify"``, an extension command like
|
||||
``"speckit.git.commit"``, or a bare stem like ``"specify"``.
|
||||
``"speckit.specify"`` or a bare stem like ``"specify"``.
|
||||
"""
|
||||
stem = command_name
|
||||
if stem.startswith("speckit."):
|
||||
stem = stem[len("speckit."):]
|
||||
if "." in stem:
|
||||
stem = stem.rsplit(".", 1)[-1]
|
||||
|
||||
invocation = f"/speckit.{stem}"
|
||||
if args:
|
||||
@@ -409,256 +380,23 @@ class IntegrationBase(ABC):
|
||||
|
||||
return created
|
||||
|
||||
# -- Agent context file management ------------------------------------
|
||||
|
||||
@staticmethod
|
||||
def _ensure_mdc_frontmatter(content: str) -> str:
|
||||
"""Ensure ``.mdc`` content has YAML frontmatter with ``alwaysApply: true``.
|
||||
|
||||
If frontmatter is missing, prepend it. If frontmatter exists but
|
||||
``alwaysApply`` is absent or not ``true``, inject/fix it.
|
||||
|
||||
Uses string/regex manipulation to preserve comments and formatting
|
||||
in existing frontmatter.
|
||||
"""
|
||||
import re as _re
|
||||
|
||||
leading_ws = len(content) - len(content.lstrip())
|
||||
leading = content[:leading_ws]
|
||||
stripped = content[leading_ws:]
|
||||
|
||||
if not stripped.startswith("---"):
|
||||
return "---\nalwaysApply: true\n---\n\n" + content
|
||||
|
||||
# Match frontmatter block: ---\n...\n---
|
||||
match = _re.match(
|
||||
r"^(---[ \t]*\r?\n)(.*?)(\r?\n---[ \t]*)(\r?\n|$)(.*)",
|
||||
stripped,
|
||||
_re.DOTALL,
|
||||
)
|
||||
if not match:
|
||||
return "---\nalwaysApply: true\n---\n\n" + content
|
||||
|
||||
opening, fm_text, closing, sep, rest = match.groups()
|
||||
newline = "\r\n" if "\r\n" in opening else "\n"
|
||||
|
||||
# Already correct?
|
||||
if _re.search(
|
||||
r"(?m)^[ \t]*alwaysApply[ \t]*:[ \t]*true[ \t]*(?:#.*)?$", fm_text
|
||||
):
|
||||
return content
|
||||
|
||||
# alwaysApply exists but wrong value — fix in place while preserving
|
||||
# indentation and any trailing inline comment.
|
||||
if _re.search(r"(?m)^[ \t]*alwaysApply[ \t]*:", fm_text):
|
||||
fm_text = _re.sub(
|
||||
r"(?m)^([ \t]*)alwaysApply[ \t]*:.*?([ \t]*(?:#.*)?)$",
|
||||
r"\1alwaysApply: true\2",
|
||||
fm_text,
|
||||
count=1,
|
||||
)
|
||||
elif fm_text.strip():
|
||||
fm_text = fm_text + newline + "alwaysApply: true"
|
||||
else:
|
||||
fm_text = "alwaysApply: true"
|
||||
|
||||
return f"{leading}{opening}{fm_text}{closing}{sep}{rest}"
|
||||
|
||||
@staticmethod
|
||||
def _build_context_section(plan_path: str = "") -> str:
|
||||
"""Build the content for the managed section between markers.
|
||||
|
||||
*plan_path* is the project-relative path to the current plan
|
||||
(e.g. ``"specs/<feature>/plan.md"``). When empty, the section
|
||||
contains only the generic directive without a concrete path.
|
||||
"""
|
||||
lines = [
|
||||
"For additional context about technologies to be used, project structure,",
|
||||
"shell commands, and other important information, read the current plan",
|
||||
]
|
||||
if plan_path:
|
||||
lines.append(f"at {plan_path}")
|
||||
return "\n".join(lines)
|
||||
|
||||
def upsert_context_section(
|
||||
self,
|
||||
project_root: Path,
|
||||
plan_path: str = "",
|
||||
) -> Path | None:
|
||||
"""Create or update the managed section in the agent context file.
|
||||
|
||||
If the context file does not exist it is created with just the
|
||||
managed section. If it exists, the content between
|
||||
``<!-- SPECKIT START -->`` and ``<!-- SPECKIT END -->`` markers
|
||||
is replaced (or appended when no markers are found).
|
||||
|
||||
Returns the path to the context file, or ``None`` when
|
||||
``context_file`` is not set.
|
||||
"""
|
||||
if not self.context_file:
|
||||
return None
|
||||
|
||||
ctx_path = project_root / self.context_file
|
||||
section = (
|
||||
f"{self.CONTEXT_MARKER_START}\n"
|
||||
f"{self._build_context_section(plan_path)}\n"
|
||||
f"{self.CONTEXT_MARKER_END}\n"
|
||||
)
|
||||
|
||||
if ctx_path.exists():
|
||||
content = ctx_path.read_text(encoding="utf-8-sig")
|
||||
start_idx = content.find(self.CONTEXT_MARKER_START)
|
||||
end_idx = content.find(
|
||||
self.CONTEXT_MARKER_END,
|
||||
start_idx if start_idx != -1 else 0,
|
||||
)
|
||||
|
||||
if start_idx != -1 and end_idx != -1 and end_idx > start_idx:
|
||||
# Replace existing section (include the end marker + newline)
|
||||
end_of_marker = end_idx + len(self.CONTEXT_MARKER_END)
|
||||
# Consume trailing line ending (CRLF or LF)
|
||||
if end_of_marker < len(content) and content[end_of_marker] == "\r":
|
||||
end_of_marker += 1
|
||||
if end_of_marker < len(content) and content[end_of_marker] == "\n":
|
||||
end_of_marker += 1
|
||||
new_content = content[:start_idx] + section + content[end_of_marker:]
|
||||
elif start_idx != -1:
|
||||
# Corrupted: start marker without end — replace from start through EOF
|
||||
new_content = content[:start_idx] + section
|
||||
elif end_idx != -1:
|
||||
# Corrupted: end marker without start — replace BOF through end marker
|
||||
end_of_marker = end_idx + len(self.CONTEXT_MARKER_END)
|
||||
if end_of_marker < len(content) and content[end_of_marker] == "\r":
|
||||
end_of_marker += 1
|
||||
if end_of_marker < len(content) and content[end_of_marker] == "\n":
|
||||
end_of_marker += 1
|
||||
new_content = section + content[end_of_marker:]
|
||||
else:
|
||||
# No markers found — append
|
||||
if content:
|
||||
if not content.endswith("\n"):
|
||||
content += "\n"
|
||||
new_content = content + "\n" + section
|
||||
else:
|
||||
new_content = section
|
||||
|
||||
# Ensure .mdc files have required YAML frontmatter
|
||||
if ctx_path.suffix == ".mdc":
|
||||
new_content = self._ensure_mdc_frontmatter(new_content)
|
||||
else:
|
||||
ctx_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
# Cursor .mdc files require YAML frontmatter to be loaded
|
||||
if ctx_path.suffix == ".mdc":
|
||||
new_content = self._ensure_mdc_frontmatter(section)
|
||||
else:
|
||||
new_content = section
|
||||
|
||||
normalized = new_content.replace("\r\n", "\n").replace("\r", "\n")
|
||||
ctx_path.write_bytes(normalized.encode("utf-8"))
|
||||
return ctx_path
|
||||
|
||||
def remove_context_section(self, project_root: Path) -> bool:
|
||||
"""Remove the managed section from the agent context file.
|
||||
|
||||
Returns ``True`` if the section was found and removed. If the
|
||||
file becomes empty (or whitespace-only) after removal it is
|
||||
deleted.
|
||||
"""
|
||||
if not self.context_file:
|
||||
return False
|
||||
|
||||
ctx_path = project_root / self.context_file
|
||||
if not ctx_path.exists():
|
||||
return False
|
||||
|
||||
content = ctx_path.read_text(encoding="utf-8-sig")
|
||||
start_idx = content.find(self.CONTEXT_MARKER_START)
|
||||
end_idx = content.find(
|
||||
self.CONTEXT_MARKER_END,
|
||||
start_idx if start_idx != -1 else 0,
|
||||
)
|
||||
|
||||
# Only remove a complete, well-ordered managed section. If either
|
||||
# marker is missing, leave the file unchanged to avoid deleting
|
||||
# unrelated user-authored content.
|
||||
if start_idx == -1 or end_idx == -1 or end_idx <= start_idx:
|
||||
return False
|
||||
|
||||
removal_start = start_idx
|
||||
removal_end = end_idx + len(self.CONTEXT_MARKER_END)
|
||||
|
||||
# Consume trailing line ending (CRLF or LF)
|
||||
if removal_end < len(content) and content[removal_end] == "\r":
|
||||
removal_end += 1
|
||||
if removal_end < len(content) and content[removal_end] == "\n":
|
||||
removal_end += 1
|
||||
|
||||
# Also strip a blank line before the section if present
|
||||
if removal_start > 0 and content[removal_start - 1] == "\n":
|
||||
if removal_start > 1 and content[removal_start - 2] == "\n":
|
||||
removal_start -= 1
|
||||
|
||||
new_content = content[:removal_start] + content[removal_end:]
|
||||
|
||||
# Normalize line endings before comparisons
|
||||
normalized = new_content.replace("\r\n", "\n").replace("\r", "\n")
|
||||
|
||||
# For .mdc files, treat Speckit-generated frontmatter-only content as empty
|
||||
if ctx_path.suffix == ".mdc":
|
||||
import re
|
||||
# Delete the file if only YAML frontmatter remains (no body content)
|
||||
frontmatter_only = re.match(
|
||||
r"^---\n.*?\n---\s*$", normalized, re.DOTALL
|
||||
)
|
||||
if not normalized.strip() or frontmatter_only:
|
||||
ctx_path.unlink()
|
||||
return True
|
||||
|
||||
if not normalized.strip():
|
||||
ctx_path.unlink()
|
||||
else:
|
||||
ctx_path.write_bytes(normalized.encode("utf-8"))
|
||||
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def resolve_command_refs(content: str, separator: str = ".") -> str:
|
||||
"""Replace ``__SPECKIT_COMMAND_<NAME>__`` placeholders with invocations.
|
||||
|
||||
Each placeholder encodes a command name in upper-case with
|
||||
underscores (e.g. ``__SPECKIT_COMMAND_PLAN__``,
|
||||
``__SPECKIT_COMMAND_GIT_COMMIT__``). The replacement uses
|
||||
*separator* to join the segments:
|
||||
|
||||
* ``separator="."`` → ``/speckit.plan``, ``/speckit.git.commit``
|
||||
* ``separator="-"`` → ``/speckit-plan``, ``/speckit-git-commit``
|
||||
"""
|
||||
return re.sub(
|
||||
r"__SPECKIT_COMMAND_([A-Z][A-Z0-9_]*)__",
|
||||
lambda m: "/speckit" + separator + m.group(1).lower().replace("_", separator),
|
||||
content,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def process_template(
|
||||
content: str,
|
||||
agent_name: str,
|
||||
script_type: str,
|
||||
arg_placeholder: str = "$ARGUMENTS",
|
||||
context_file: str = "",
|
||||
invoke_separator: str = ".",
|
||||
) -> str:
|
||||
"""Process a raw command template into agent-ready content.
|
||||
|
||||
Performs the same transformations as the release script:
|
||||
1. Extract ``scripts.<script_type>`` value from YAML frontmatter
|
||||
2. Replace ``{SCRIPT}`` with the extracted script command
|
||||
3. Strip ``scripts:`` section from frontmatter
|
||||
4. Replace ``{ARGS}`` and ``$ARGUMENTS`` with *arg_placeholder*
|
||||
5. Replace ``__AGENT__`` with *agent_name*
|
||||
6. Replace ``__CONTEXT_FILE__`` with *context_file*
|
||||
3. Extract ``agent_scripts.<script_type>`` and replace ``{AGENT_SCRIPT}``
|
||||
4. Strip ``scripts:`` and ``agent_scripts:`` sections from frontmatter
|
||||
5. Replace ``{ARGS}`` and ``$ARGUMENTS`` with *arg_placeholder*
|
||||
6. Replace ``__AGENT__`` with *agent_name*
|
||||
7. Rewrite paths: ``scripts/`` → ``.specify/scripts/`` etc.
|
||||
8. Replace ``__SPECKIT_COMMAND_<NAME>__`` with invocation strings
|
||||
"""
|
||||
# 1. Extract script command from frontmatter
|
||||
script_command = ""
|
||||
@@ -683,7 +421,25 @@ class IntegrationBase(ABC):
|
||||
if script_command:
|
||||
content = content.replace("{SCRIPT}", script_command)
|
||||
|
||||
# 3. Strip scripts: section from frontmatter
|
||||
# 3. Extract agent_script command
|
||||
agent_script_command = ""
|
||||
in_agent_scripts = False
|
||||
for line in content.splitlines():
|
||||
if line.strip() == "agent_scripts:":
|
||||
in_agent_scripts = True
|
||||
continue
|
||||
if in_agent_scripts and line and not line[0].isspace():
|
||||
in_agent_scripts = False
|
||||
if in_agent_scripts:
|
||||
m = script_pattern.match(line)
|
||||
if m:
|
||||
agent_script_command = m.group(1).strip()
|
||||
break
|
||||
|
||||
if agent_script_command:
|
||||
content = content.replace("{AGENT_SCRIPT}", agent_script_command)
|
||||
|
||||
# 4. Strip scripts: and agent_scripts: sections from frontmatter
|
||||
lines = content.splitlines(keepends=True)
|
||||
output_lines: list[str] = []
|
||||
in_frontmatter = False
|
||||
@@ -701,26 +457,23 @@ class IntegrationBase(ABC):
|
||||
output_lines.append(line)
|
||||
continue
|
||||
if in_frontmatter:
|
||||
if stripped == "scripts:":
|
||||
if stripped in ("scripts:", "agent_scripts:"):
|
||||
skip_section = True
|
||||
continue
|
||||
if skip_section:
|
||||
if line[0:1].isspace():
|
||||
continue # skip indented content under scripts
|
||||
continue # skip indented content under scripts/agent_scripts
|
||||
skip_section = False
|
||||
output_lines.append(line)
|
||||
content = "".join(output_lines)
|
||||
|
||||
# 4. Replace {ARGS} and $ARGUMENTS
|
||||
# 5. Replace {ARGS} and $ARGUMENTS
|
||||
content = content.replace("{ARGS}", arg_placeholder)
|
||||
content = content.replace("$ARGUMENTS", arg_placeholder)
|
||||
|
||||
# 5. Replace __AGENT__
|
||||
# 6. Replace __AGENT__
|
||||
content = content.replace("__AGENT__", agent_name)
|
||||
|
||||
# 6. Replace __CONTEXT_FILE__
|
||||
content = content.replace("__CONTEXT_FILE__", context_file)
|
||||
|
||||
# 7. Rewrite paths — delegate to the shared implementation in
|
||||
# CommandRegistrar so extension-local paths are preserved and
|
||||
# boundary rules stay consistent across the codebase.
|
||||
@@ -728,9 +481,6 @@ class IntegrationBase(ABC):
|
||||
|
||||
content = CommandRegistrar.rewrite_project_relative_paths(content)
|
||||
|
||||
# 8. Replace __SPECKIT_COMMAND_<NAME>__ with invocation strings
|
||||
content = IntegrationBase.resolve_command_refs(content, invoke_separator)
|
||||
|
||||
return content
|
||||
|
||||
def setup(
|
||||
@@ -776,9 +526,6 @@ class IntegrationBase(ABC):
|
||||
self.record_file_in_manifest(dst_file, project_root, manifest)
|
||||
created.append(dst_file)
|
||||
|
||||
# Upsert managed context section into the agent context file
|
||||
self.upsert_context_section(project_root)
|
||||
|
||||
return created
|
||||
|
||||
def teardown(
|
||||
@@ -792,11 +539,9 @@ class IntegrationBase(ABC):
|
||||
|
||||
Delegates to ``manifest.uninstall()`` which only removes files
|
||||
whose hash still matches the recorded value (unless *force*).
|
||||
Also removes the managed context section from the agent file.
|
||||
|
||||
Returns ``(removed, skipped)`` file lists.
|
||||
"""
|
||||
self.remove_context_section(project_root)
|
||||
return manifest.uninstall(project_root, force=force)
|
||||
|
||||
# -- Convenience helpers for subclasses -------------------------------
|
||||
@@ -834,8 +579,8 @@ class MarkdownIntegration(IntegrationBase):
|
||||
(and optionally ``context_file``). Everything else is inherited.
|
||||
|
||||
``setup()`` processes command templates (replacing ``{SCRIPT}``,
|
||||
``{ARGS}``, ``__AGENT__``, rewriting paths) and upserts the
|
||||
managed context section into the agent context file.
|
||||
``{ARGS}``, ``__AGENT__``, rewriting paths) and installs
|
||||
integration-specific scripts (``update-context.sh`` / ``.ps1``).
|
||||
"""
|
||||
|
||||
def build_exec_args(
|
||||
@@ -893,8 +638,7 @@ class MarkdownIntegration(IntegrationBase):
|
||||
for src_file in templates:
|
||||
raw = src_file.read_text(encoding="utf-8")
|
||||
processed = self.process_template(
|
||||
raw, self.key, script_type, arg_placeholder,
|
||||
context_file=self.context_file or "",
|
||||
raw, self.key, script_type, arg_placeholder
|
||||
)
|
||||
dst_name = self.command_filename(src_file.stem)
|
||||
dst_file = self.write_file_and_record(
|
||||
@@ -902,9 +646,7 @@ class MarkdownIntegration(IntegrationBase):
|
||||
)
|
||||
created.append(dst_file)
|
||||
|
||||
# Upsert managed context section into the agent context file
|
||||
self.upsert_context_section(project_root)
|
||||
|
||||
created.extend(self.install_scripts(project_root, manifest))
|
||||
return created
|
||||
|
||||
|
||||
@@ -1099,8 +841,7 @@ class TomlIntegration(IntegrationBase):
|
||||
raw = src_file.read_text(encoding="utf-8")
|
||||
description = self._extract_description(raw)
|
||||
processed = self.process_template(
|
||||
raw, self.key, script_type, arg_placeholder,
|
||||
context_file=self.context_file or "",
|
||||
raw, self.key, script_type, arg_placeholder
|
||||
)
|
||||
_, body = self._split_frontmatter(processed)
|
||||
toml_content = self._render_toml(description, body)
|
||||
@@ -1110,9 +851,7 @@ class TomlIntegration(IntegrationBase):
|
||||
)
|
||||
created.append(dst_file)
|
||||
|
||||
# Upsert managed context section into the agent context file
|
||||
self.upsert_context_section(project_root)
|
||||
|
||||
created.extend(self.install_scripts(project_root, manifest))
|
||||
return created
|
||||
|
||||
|
||||
@@ -1282,8 +1021,7 @@ class YamlIntegration(IntegrationBase):
|
||||
title = self._human_title(src_file.stem)
|
||||
|
||||
processed = self.process_template(
|
||||
raw, self.key, script_type, arg_placeholder,
|
||||
context_file=self.context_file or "",
|
||||
raw, self.key, script_type, arg_placeholder
|
||||
)
|
||||
_, body = self._split_frontmatter(processed)
|
||||
yaml_content = self._render_yaml(
|
||||
@@ -1295,9 +1033,7 @@ class YamlIntegration(IntegrationBase):
|
||||
)
|
||||
created.append(dst_file)
|
||||
|
||||
# Upsert managed context section into the agent context file
|
||||
self.upsert_context_section(project_root)
|
||||
|
||||
created.extend(self.install_scripts(project_root, manifest))
|
||||
return created
|
||||
|
||||
|
||||
@@ -1321,8 +1057,6 @@ class SkillsIntegration(IntegrationBase):
|
||||
``speckit-<name>/SKILL.md`` file with skills-oriented frontmatter.
|
||||
"""
|
||||
|
||||
invoke_separator = "-"
|
||||
|
||||
def build_exec_args(
|
||||
self,
|
||||
prompt: str,
|
||||
@@ -1360,10 +1094,10 @@ class SkillsIntegration(IntegrationBase):
|
||||
def build_command_invocation(self, command_name: str, args: str = "") -> str:
|
||||
"""Skills use ``/speckit-<stem>`` (hyphenated directory name)."""
|
||||
stem = command_name
|
||||
if stem.startswith("speckit."):
|
||||
stem = stem[len("speckit."):]
|
||||
if "." in stem:
|
||||
stem = stem.rsplit(".", 1)[-1]
|
||||
|
||||
invocation = "/speckit-" + stem.replace(".", "-")
|
||||
invocation = f"/speckit-{stem}"
|
||||
if args:
|
||||
invocation = f"{invocation} {args}"
|
||||
return invocation
|
||||
@@ -1442,9 +1176,7 @@ class SkillsIntegration(IntegrationBase):
|
||||
|
||||
# Process body through the standard template pipeline
|
||||
processed_body = self.process_template(
|
||||
raw, self.key, script_type, arg_placeholder,
|
||||
context_file=self.context_file or "",
|
||||
invoke_separator=self.invoke_separator,
|
||||
raw, self.key, script_type, arg_placeholder
|
||||
)
|
||||
# Strip the processed frontmatter — we rebuild it for skills.
|
||||
# Preserve leading whitespace in the body to match release ZIP
|
||||
@@ -1488,7 +1220,5 @@ class SkillsIntegration(IntegrationBase):
|
||||
)
|
||||
created.append(dst)
|
||||
|
||||
# Upsert managed context section into the agent context file
|
||||
self.upsert_context_section(project_root)
|
||||
|
||||
created.extend(self.install_scripts(project_root, manifest))
|
||||
return created
|
||||
|
||||
23
src/specify_cli/integrations/bob/scripts/update-context.ps1
Normal file
23
src/specify_cli/integrations/bob/scripts/update-context.ps1
Normal file
@@ -0,0 +1,23 @@
|
||||
# update-context.ps1 — IBM Bob integration: create/update AGENTS.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$repoRoot = try { git rev-parse --show-toplevel 2>$null } catch { $null }
|
||||
# If git did not return a repo root, or the git root does not contain .specify,
|
||||
# fall back to walking up from the script directory to find the initialized project root.
|
||||
if (-not $repoRoot -or -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = $scriptDir
|
||||
$fsRoot = [System.IO.Path]::GetPathRoot($repoRoot)
|
||||
while ($repoRoot -and $repoRoot -ne $fsRoot -and -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = Split-Path -Parent $repoRoot
|
||||
}
|
||||
}
|
||||
|
||||
& "$repoRoot/.specify/scripts/powershell/update-agent-context.ps1" -AgentType bob
|
||||
28
src/specify_cli/integrations/bob/scripts/update-context.sh
Executable file
28
src/specify_cli/integrations/bob/scripts/update-context.sh
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
# update-context.sh — IBM Bob integration: create/update AGENTS.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
_script_dir="$(cd "$(dirname "$0")" && pwd)"
|
||||
_root="$_script_dir"
|
||||
while [ "$_root" != "/" ] && [ ! -d "$_root/.specify" ]; do _root="$(dirname "$_root")"; done
|
||||
if [ -z "${REPO_ROOT:-}" ]; then
|
||||
if [ -d "$_root/.specify" ]; then
|
||||
REPO_ROOT="$_root"
|
||||
else
|
||||
git_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
||||
if [ -n "$git_root" ] && [ -d "$git_root/.specify" ]; then
|
||||
REPO_ROOT="$git_root"
|
||||
else
|
||||
REPO_ROOT="$_root"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
exec "$REPO_ROOT/.specify/scripts/bash/update-agent-context.sh" bob
|
||||
@@ -16,7 +16,7 @@ import re
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import yaml
|
||||
from packaging import version as pkg_version
|
||||
@@ -30,10 +30,6 @@ class IntegrationCatalogError(Exception):
|
||||
"""Raised when a catalog operation fails."""
|
||||
|
||||
|
||||
class IntegrationValidationError(IntegrationCatalogError):
|
||||
"""Validation error for catalog config or catalog management operations."""
|
||||
|
||||
|
||||
class IntegrationDescriptorError(Exception):
|
||||
"""Raised when an integration.yml descriptor is invalid."""
|
||||
|
||||
@@ -100,36 +96,28 @@ class IntegrationCatalog:
|
||||
Returns None when the file does not exist.
|
||||
|
||||
Raises:
|
||||
IntegrationValidationError: on any local-config / YAML problem
|
||||
(parse failures, wrong shape, missing/invalid fields,
|
||||
invalid catalog URLs, etc.). This is a subclass of
|
||||
:class:`IntegrationCatalogError`, so any caller that already
|
||||
catches ``IntegrationCatalogError`` keeps working — but
|
||||
callers that want to distinguish *local config* problems
|
||||
from *remote/network* problems can match the subclass.
|
||||
IntegrationCatalogError: on invalid content
|
||||
"""
|
||||
if not config_path.exists():
|
||||
return None
|
||||
try:
|
||||
data = yaml.safe_load(config_path.read_text(encoding="utf-8"))
|
||||
data = yaml.safe_load(config_path.read_text(encoding="utf-8")) or {}
|
||||
except (yaml.YAMLError, OSError, UnicodeError) as exc:
|
||||
raise IntegrationValidationError(
|
||||
raise IntegrationCatalogError(
|
||||
f"Failed to read catalog config {config_path}: {exc}"
|
||||
) from exc
|
||||
if data is None:
|
||||
data = {}
|
||||
)
|
||||
if not isinstance(data, dict):
|
||||
raise IntegrationValidationError(
|
||||
raise IntegrationCatalogError(
|
||||
f"Invalid catalog config {config_path}: expected a YAML mapping at the root"
|
||||
)
|
||||
catalogs_data = data.get("catalogs", [])
|
||||
if not isinstance(catalogs_data, list):
|
||||
raise IntegrationValidationError(
|
||||
f"Invalid catalog config {config_path}: 'catalogs' must be a list, "
|
||||
raise IntegrationCatalogError(
|
||||
f"Invalid catalog config: 'catalogs' must be a list, "
|
||||
f"got {type(catalogs_data).__name__}"
|
||||
)
|
||||
if not catalogs_data:
|
||||
raise IntegrationValidationError(
|
||||
raise IntegrationCatalogError(
|
||||
f"Catalog config {config_path} exists but contains no 'catalogs' entries. "
|
||||
f"Remove the file to use built-in defaults, or add valid catalog entries."
|
||||
)
|
||||
@@ -137,52 +125,31 @@ class IntegrationCatalog:
|
||||
skipped: List[int] = []
|
||||
for idx, item in enumerate(catalogs_data):
|
||||
if not isinstance(item, dict):
|
||||
raise IntegrationValidationError(
|
||||
f"Invalid catalog config {config_path}: catalog entry at index {idx}: "
|
||||
raise IntegrationCatalogError(
|
||||
f"Invalid catalog entry at index {idx}: "
|
||||
f"expected a mapping, got {type(item).__name__}"
|
||||
)
|
||||
url = str(item.get("url", "")).strip()
|
||||
if not url:
|
||||
skipped.append(idx)
|
||||
continue
|
||||
self._validate_catalog_url(url)
|
||||
try:
|
||||
self._validate_catalog_url(url)
|
||||
except IntegrationCatalogError as exc:
|
||||
# ``_validate_catalog_url`` raises the base class for direct
|
||||
# callers (e.g. ``add_catalog`` validating user input); when
|
||||
# the bad URL came from a local config file, surface it as a
|
||||
# validation error so CLI handlers can route it accordingly.
|
||||
raise IntegrationValidationError(
|
||||
f"Invalid catalog URL in {config_path} at index {idx}: {exc}"
|
||||
) from exc
|
||||
raw_priority = item.get("priority", idx + 1)
|
||||
if isinstance(raw_priority, bool):
|
||||
raise IntegrationValidationError(
|
||||
f"Invalid catalog config {config_path}: "
|
||||
f"Invalid priority for catalog '{item.get('name', idx + 1)}': "
|
||||
f"expected integer, got {raw_priority!r}"
|
||||
)
|
||||
try:
|
||||
priority = int(raw_priority)
|
||||
priority = int(item.get("priority", idx + 1))
|
||||
except (TypeError, ValueError):
|
||||
raise IntegrationValidationError(
|
||||
f"Invalid catalog config {config_path}: "
|
||||
raise IntegrationCatalogError(
|
||||
f"Invalid priority for catalog '{item.get('name', idx + 1)}': "
|
||||
f"expected integer, got {raw_priority!r}"
|
||||
f"expected integer, got {item.get('priority')!r}"
|
||||
)
|
||||
raw_install = item.get("install_allowed", False)
|
||||
if isinstance(raw_install, str):
|
||||
install_allowed = raw_install.strip().lower() in ("true", "yes", "1")
|
||||
else:
|
||||
install_allowed = bool(raw_install)
|
||||
raw_name = item.get("name")
|
||||
name = str(raw_name).strip() if raw_name is not None else ""
|
||||
if not name:
|
||||
name = f"catalog-{len(entries) + 1}"
|
||||
entries.append(
|
||||
IntegrationCatalogEntry(
|
||||
url=url,
|
||||
name=name,
|
||||
name=str(item.get("name", f"catalog-{idx + 1}")),
|
||||
priority=priority,
|
||||
install_allowed=install_allowed,
|
||||
description=str(item.get("description", "")),
|
||||
@@ -190,7 +157,7 @@ class IntegrationCatalog:
|
||||
)
|
||||
entries.sort(key=lambda e: e.priority)
|
||||
if not entries:
|
||||
raise IntegrationValidationError(
|
||||
raise IntegrationCatalogError(
|
||||
f"Catalog config {config_path} contains {len(catalogs_data)} "
|
||||
f"entries but none have valid URLs (entries at indices {skipped} "
|
||||
f"were skipped). Each catalog entry must have a 'url' field."
|
||||
@@ -229,12 +196,12 @@ class IntegrationCatalog:
|
||||
)
|
||||
]
|
||||
|
||||
project_cfg = self.project_root / ".specify" / self.CONFIG_FILENAME
|
||||
project_cfg = self.project_root / ".specify" / "integration-catalogs.yml"
|
||||
catalogs = self._load_catalog_config(project_cfg)
|
||||
if catalogs is not None:
|
||||
return catalogs
|
||||
|
||||
user_cfg = Path.home() / ".specify" / self.CONFIG_FILENAME
|
||||
user_cfg = Path.home() / ".specify" / "integration-catalogs.yml"
|
||||
catalogs = self._load_catalog_config(user_cfg)
|
||||
if catalogs is not None:
|
||||
return catalogs
|
||||
@@ -441,288 +408,6 @@ class IntegrationCatalog:
|
||||
for f in self.cache_dir.glob(pattern):
|
||||
f.unlink(missing_ok=True)
|
||||
|
||||
# -- Catalog-source management ----------------------------------------
|
||||
|
||||
CONFIG_FILENAME = "integration-catalogs.yml"
|
||||
|
||||
def get_catalog_configs(self) -> List[Dict[str, Any]]:
|
||||
"""Return the active catalog stack as a list of dicts.
|
||||
|
||||
Thin adapter over :meth:`get_active_catalogs` that yields plain dicts
|
||||
suitable for CLI rendering and JSON-like consumers.
|
||||
"""
|
||||
return [
|
||||
{
|
||||
"name": e.name,
|
||||
"url": e.url,
|
||||
"priority": e.priority,
|
||||
"install_allowed": e.install_allowed,
|
||||
"description": e.description,
|
||||
}
|
||||
for e in self.get_active_catalogs()
|
||||
]
|
||||
|
||||
def get_project_catalog_configs(self) -> Optional[List[Dict[str, Any]]]:
|
||||
"""Return removable project-level catalog config entries, if configured."""
|
||||
config_path = self.project_root / ".specify" / self.CONFIG_FILENAME
|
||||
entries = self._load_catalog_config(config_path)
|
||||
if entries is None:
|
||||
return None
|
||||
return [
|
||||
{
|
||||
"name": e.name,
|
||||
"url": e.url,
|
||||
"priority": e.priority,
|
||||
"install_allowed": e.install_allowed,
|
||||
"description": e.description,
|
||||
}
|
||||
for e in entries
|
||||
]
|
||||
|
||||
def add_catalog(self, url: str, name: Optional[str] = None) -> None:
|
||||
"""Add a catalog source to the project-level config file.
|
||||
|
||||
The URL is normalized (whitespace stripped) and validated before being
|
||||
written. Duplicate URLs are rejected, including near-duplicates that
|
||||
differ only by surrounding whitespace. Priority is derived as
|
||||
``max(existing) + 1`` so the new entry sorts last in the resolution
|
||||
order unless the user edits the file manually.
|
||||
"""
|
||||
url = url.strip()
|
||||
if not url:
|
||||
raise IntegrationValidationError("Catalog URL must be non-empty.")
|
||||
self._validate_catalog_url(url)
|
||||
config_path = self.project_root / ".specify" / self.CONFIG_FILENAME
|
||||
|
||||
data: Dict[str, Any] = {"catalogs": []}
|
||||
if config_path.exists():
|
||||
try:
|
||||
raw = yaml.safe_load(config_path.read_text(encoding="utf-8"))
|
||||
except (yaml.YAMLError, OSError, UnicodeError) as exc:
|
||||
raise IntegrationValidationError(
|
||||
f"Failed to read catalog config {config_path}: {exc}"
|
||||
) from exc
|
||||
if raw is None:
|
||||
raw = {}
|
||||
if not isinstance(raw, dict):
|
||||
raise IntegrationValidationError(
|
||||
f"Catalog config file {config_path} is corrupted "
|
||||
"(expected a mapping)."
|
||||
)
|
||||
data = raw
|
||||
|
||||
catalogs = data.get("catalogs", [])
|
||||
if not isinstance(catalogs, list):
|
||||
raise IntegrationValidationError(
|
||||
f"Catalog config {config_path} has invalid 'catalogs' value: "
|
||||
"must be a list."
|
||||
)
|
||||
|
||||
# Validate each existing entry before mutating anything. Fail fast so
|
||||
# we don't silently preserve a corrupt sibling entry or derive a new
|
||||
# priority from a bogus value.
|
||||
existing_priorities: List[int] = []
|
||||
valid_catalog_count = 0
|
||||
for idx, cat in enumerate(catalogs):
|
||||
if not isinstance(cat, dict):
|
||||
raise IntegrationValidationError(
|
||||
f"Invalid catalog entry at index {idx} in {config_path}: "
|
||||
f"expected a mapping, got {type(cat).__name__}."
|
||||
)
|
||||
existing_url = str(cat.get("url", "")).strip()
|
||||
if not existing_url:
|
||||
continue
|
||||
# Re-run the same URL validation used when loading, so a corrupt
|
||||
# entry surfaces here instead of at the next `integration` call.
|
||||
try:
|
||||
self._validate_catalog_url(existing_url)
|
||||
except IntegrationCatalogError as exc:
|
||||
raise IntegrationValidationError(
|
||||
f"Invalid catalog entry at index {idx} in {config_path}: {exc}"
|
||||
) from exc
|
||||
if existing_url == url:
|
||||
raise IntegrationValidationError(
|
||||
f"Catalog URL already configured: {url}"
|
||||
)
|
||||
valid_catalog_count += 1
|
||||
if "priority" in cat:
|
||||
raw_priority = cat.get("priority")
|
||||
if isinstance(raw_priority, bool):
|
||||
raise IntegrationValidationError(
|
||||
f"Invalid catalog entry at index {idx} in {config_path}: "
|
||||
f"'priority' must be an integer, got "
|
||||
f"{type(raw_priority).__name__}."
|
||||
)
|
||||
try:
|
||||
normalized_priority = int(raw_priority)
|
||||
except (TypeError, ValueError):
|
||||
raise IntegrationValidationError(
|
||||
f"Invalid catalog entry at index {idx} in {config_path}: "
|
||||
f"'priority' must be an integer, got "
|
||||
f"{raw_priority!r}."
|
||||
) from None
|
||||
existing_priorities.append(normalized_priority)
|
||||
else:
|
||||
# Match `_load_catalog_config()`'s defaulting rule so the new
|
||||
# entry still sorts after implicit-priority siblings.
|
||||
existing_priorities.append(idx + 1)
|
||||
|
||||
max_priority = max(existing_priorities, default=0)
|
||||
normalized_name = str(name).strip() if name is not None else ""
|
||||
generated_name = f"catalog-{valid_catalog_count + 1}"
|
||||
catalogs.append(
|
||||
{
|
||||
"name": normalized_name or generated_name,
|
||||
"url": url,
|
||||
"priority": max_priority + 1,
|
||||
"install_allowed": True,
|
||||
"description": "",
|
||||
}
|
||||
)
|
||||
data["catalogs"] = catalogs
|
||||
|
||||
config_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(config_path, "w", encoding="utf-8") as f:
|
||||
yaml.dump(
|
||||
data,
|
||||
f,
|
||||
default_flow_style=False,
|
||||
sort_keys=False,
|
||||
allow_unicode=True,
|
||||
)
|
||||
|
||||
def remove_catalog(self, index: int) -> str:
|
||||
"""Remove a catalog source by 0-based index.
|
||||
|
||||
``index`` is interpreted in the same display order shown by
|
||||
``integration catalog list`` (i.e. sorted ascending by priority,
|
||||
with missing priority defaulting to ``yaml_index + 1``, matching
|
||||
``_load_catalog_config()``). This way, the index a user sees in
|
||||
``catalog list`` is the index they pass to ``catalog remove``,
|
||||
even if the underlying YAML lists entries in a different order
|
||||
from how they sort by priority.
|
||||
|
||||
Returns the removed catalog's name.
|
||||
"""
|
||||
config_path = self.project_root / ".specify" / self.CONFIG_FILENAME
|
||||
if not config_path.exists():
|
||||
raise IntegrationValidationError("No catalog config file found.")
|
||||
|
||||
try:
|
||||
data = yaml.safe_load(config_path.read_text(encoding="utf-8"))
|
||||
except (yaml.YAMLError, OSError, UnicodeError) as exc:
|
||||
raise IntegrationValidationError(
|
||||
f"Failed to read catalog config {config_path}: {exc}"
|
||||
) from exc
|
||||
if data is None:
|
||||
data = {}
|
||||
if not isinstance(data, dict):
|
||||
raise IntegrationValidationError(
|
||||
f"Catalog config file {config_path} is corrupted "
|
||||
"(expected a mapping)."
|
||||
)
|
||||
|
||||
catalogs = data.get("catalogs", [])
|
||||
if not isinstance(catalogs, list):
|
||||
raise IntegrationValidationError(
|
||||
f"Catalog config {config_path} has invalid 'catalogs' value: "
|
||||
"must be a list."
|
||||
)
|
||||
|
||||
if not catalogs:
|
||||
# An empty list is the kind of state that only happens if the
|
||||
# user hand-edited the file; our own `remove_catalog` deletes
|
||||
# the file when the last entry is popped. Surface a clear
|
||||
# message instead of `out of range (0--1)`.
|
||||
raise IntegrationValidationError(
|
||||
"Catalog config contains no catalog entries."
|
||||
)
|
||||
|
||||
# Map displayed index -> raw YAML index using the same priority
|
||||
# defaulting as ``_load_catalog_config``. We deliberately stay
|
||||
# tolerant here (no new validation errors) because the goal is
|
||||
# only to mirror the order shown by ``catalog list``; entries
|
||||
# that ``_load_catalog_config`` would have rejected outright
|
||||
# would have failed ``catalog list`` already.
|
||||
def _is_removable_catalog_entry(item: Any) -> bool:
|
||||
if not isinstance(item, dict):
|
||||
return False
|
||||
raw_url = item.get("url")
|
||||
if raw_url is None:
|
||||
return False
|
||||
return bool(str(raw_url).strip())
|
||||
|
||||
priority_pairs: List[Tuple[int, int]] = []
|
||||
for yaml_idx, item in enumerate(catalogs):
|
||||
if not _is_removable_catalog_entry(item):
|
||||
continue
|
||||
|
||||
raw_priority = item.get("priority", yaml_idx + 1)
|
||||
if isinstance(raw_priority, bool):
|
||||
priority = yaml_idx + 1
|
||||
else:
|
||||
try:
|
||||
priority = int(raw_priority)
|
||||
except (TypeError, ValueError):
|
||||
priority = yaml_idx + 1
|
||||
priority_pairs.append((priority, yaml_idx))
|
||||
if not priority_pairs:
|
||||
raise IntegrationValidationError(
|
||||
"Catalog config contains no removable catalog entries."
|
||||
)
|
||||
# Stable sort: ties keep their YAML order, matching list-view ordering.
|
||||
priority_pairs.sort(key=lambda p: p[0])
|
||||
display_order: List[int] = [yaml_idx for _, yaml_idx in priority_pairs]
|
||||
|
||||
if index < 0 or index >= len(display_order):
|
||||
raise IntegrationValidationError(
|
||||
f"Catalog index {index} out of range (0-{len(display_order) - 1})."
|
||||
)
|
||||
|
||||
target_yaml_idx = display_order[index]
|
||||
removed = catalogs.pop(target_yaml_idx)
|
||||
|
||||
if any(_is_removable_catalog_entry(item) for item in catalogs):
|
||||
data["catalogs"] = catalogs
|
||||
with open(config_path, "w", encoding="utf-8") as f:
|
||||
yaml.dump(
|
||||
data,
|
||||
f,
|
||||
default_flow_style=False,
|
||||
sort_keys=False,
|
||||
allow_unicode=True,
|
||||
)
|
||||
else:
|
||||
# Removing the final entry: delete the config file rather than
|
||||
# leaving behind an empty `catalogs:` list. `_load_catalog_config`
|
||||
# treats an empty list as an error, so leaving the file would
|
||||
# break every subsequent `integration` command until the user
|
||||
# manually deletes `.specify/integration-catalogs.yml`.
|
||||
# Deleting the file lets the project fall back to built-in
|
||||
# defaults, which matches the behavior before any
|
||||
# `catalog add` was ever run.
|
||||
try:
|
||||
config_path.unlink(missing_ok=True)
|
||||
except OSError as exc:
|
||||
raise IntegrationValidationError(
|
||||
f"Failed to delete catalog config {config_path}: {exc}"
|
||||
) from exc
|
||||
|
||||
fallback_name = f"catalog-{index + 1}"
|
||||
if isinstance(removed, dict):
|
||||
removed_name = removed.get("name")
|
||||
if removed_name is not None:
|
||||
normalized_name = str(removed_name).strip()
|
||||
if normalized_name:
|
||||
return normalized_name
|
||||
|
||||
removed_url = removed.get("url")
|
||||
if removed_url is not None:
|
||||
normalized_url = str(removed_url).strip()
|
||||
if normalized_url:
|
||||
return normalized_url
|
||||
return fallback_name
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# IntegrationDescriptor (integration.yml)
|
||||
|
||||
@@ -53,7 +53,6 @@ class ClaudeIntegration(SkillsIntegration):
|
||||
"extension": "/SKILL.md",
|
||||
}
|
||||
context_file = "CLAUDE.md"
|
||||
multi_install_safe = True
|
||||
|
||||
@staticmethod
|
||||
def inject_argument_hint(content: str, hint: str) -> str:
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# update-context.ps1 — Claude Code integration: create/update CLAUDE.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$repoRoot = try { git rev-parse --show-toplevel 2>$null } catch { $null }
|
||||
# If git did not return a repo root, or the git root does not contain .specify,
|
||||
# fall back to walking up from the script directory to find the initialized project root.
|
||||
if (-not $repoRoot -or -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = $scriptDir
|
||||
$fsRoot = [System.IO.Path]::GetPathRoot($repoRoot)
|
||||
while ($repoRoot -and $repoRoot -ne $fsRoot -and -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = Split-Path -Parent $repoRoot
|
||||
}
|
||||
}
|
||||
|
||||
& "$repoRoot/.specify/scripts/powershell/update-agent-context.ps1" -AgentType claude
|
||||
28
src/specify_cli/integrations/claude/scripts/update-context.sh
Executable file
28
src/specify_cli/integrations/claude/scripts/update-context.sh
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
# update-context.sh — Claude Code integration: create/update CLAUDE.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
_script_dir="$(cd "$(dirname "$0")" && pwd)"
|
||||
_root="$_script_dir"
|
||||
while [ "$_root" != "/" ] && [ ! -d "$_root/.specify" ]; do _root="$(dirname "$_root")"; done
|
||||
if [ -z "${REPO_ROOT:-}" ]; then
|
||||
if [ -d "$_root/.specify" ]; then
|
||||
REPO_ROOT="$_root"
|
||||
else
|
||||
git_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
||||
if [ -n "$git_root" ] && [ -d "$git_root/.specify" ]; then
|
||||
REPO_ROOT="$git_root"
|
||||
else
|
||||
REPO_ROOT="$_root"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
exec "$REPO_ROOT/.specify/scripts/bash/update-agent-context.sh" claude
|
||||
@@ -19,4 +19,3 @@ class CodebuddyIntegration(MarkdownIntegration):
|
||||
"extension": ".md",
|
||||
}
|
||||
context_file = "CODEBUDDY.md"
|
||||
multi_install_safe = True
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# update-context.ps1 — CodeBuddy integration: create/update CODEBUDDY.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$repoRoot = try { git rev-parse --show-toplevel 2>$null } catch { $null }
|
||||
# If git did not return a repo root, or the git root does not contain .specify,
|
||||
# fall back to walking up from the script directory to find the initialized project root.
|
||||
if (-not $repoRoot -or -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = $scriptDir
|
||||
$fsRoot = [System.IO.Path]::GetPathRoot($repoRoot)
|
||||
while ($repoRoot -and $repoRoot -ne $fsRoot -and -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = Split-Path -Parent $repoRoot
|
||||
}
|
||||
}
|
||||
|
||||
& "$repoRoot/.specify/scripts/powershell/update-agent-context.ps1" -AgentType codebuddy
|
||||
28
src/specify_cli/integrations/codebuddy/scripts/update-context.sh
Executable file
28
src/specify_cli/integrations/codebuddy/scripts/update-context.sh
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
# update-context.sh — CodeBuddy integration: create/update CODEBUDDY.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
_script_dir="$(cd "$(dirname "$0")" && pwd)"
|
||||
_root="$_script_dir"
|
||||
while [ "$_root" != "/" ] && [ ! -d "$_root/.specify" ]; do _root="$(dirname "$_root")"; done
|
||||
if [ -z "${REPO_ROOT:-}" ]; then
|
||||
if [ -d "$_root/.specify" ]; then
|
||||
REPO_ROOT="$_root"
|
||||
else
|
||||
git_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
||||
if [ -n "$git_root" ] && [ -d "$git_root/.specify" ]; then
|
||||
REPO_ROOT="$git_root"
|
||||
else
|
||||
REPO_ROOT="$_root"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
exec "$REPO_ROOT/.specify/scripts/bash/update-agent-context.sh" codebuddy
|
||||
@@ -27,7 +27,6 @@ class CodexIntegration(SkillsIntegration):
|
||||
"extension": "/SKILL.md",
|
||||
}
|
||||
context_file = "AGENTS.md"
|
||||
multi_install_safe = True
|
||||
|
||||
def build_exec_args(
|
||||
self,
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# update-context.ps1 — Codex CLI integration: create/update AGENTS.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$repoRoot = try { git rev-parse --show-toplevel 2>$null } catch { $null }
|
||||
if (-not $repoRoot -or -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = $scriptDir
|
||||
$fsRoot = [System.IO.Path]::GetPathRoot($repoRoot)
|
||||
while ($repoRoot -and $repoRoot -ne $fsRoot -and -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = Split-Path -Parent $repoRoot
|
||||
}
|
||||
}
|
||||
|
||||
& "$repoRoot/.specify/scripts/powershell/update-agent-context.ps1" -AgentType codex
|
||||
24
src/specify_cli/integrations/codex/scripts/update-context.sh
Executable file
24
src/specify_cli/integrations/codex/scripts/update-context.sh
Executable file
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env bash
|
||||
# update-context.sh — Codex CLI integration: create/update AGENTS.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
_script_dir="$(cd "$(dirname "$0")" && pwd)"
|
||||
_root="$_script_dir"
|
||||
while [ "$_root" != "/" ] && [ ! -d "$_root/.specify" ]; do _root="$(dirname "$_root")"; done
|
||||
if [ -z "${REPO_ROOT:-}" ]; then
|
||||
if [ -d "$_root/.specify" ]; then
|
||||
REPO_ROOT="$_root"
|
||||
else
|
||||
git_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
||||
if [ -n "$git_root" ] && [ -d "$git_root/.specify" ]; then
|
||||
REPO_ROOT="$git_root"
|
||||
else
|
||||
REPO_ROOT="$_root"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
exec "$REPO_ROOT/.specify/scripts/bash/update-agent-context.sh" codex
|
||||
@@ -5,83 +5,25 @@ Copilot has several unique behaviors compared to standard markdown agents:
|
||||
- Each command gets a companion ``.prompt.md`` file in ``.github/prompts/``
|
||||
- Installs ``.vscode/settings.json`` with prompt file recommendations
|
||||
- Context file lives at ``.github/copilot-instructions.md``
|
||||
|
||||
When ``--skills`` is passed via ``--integration-options``, Copilot scaffolds
|
||||
commands as ``speckit-<name>/SKILL.md`` directories under ``.github/skills/``
|
||||
instead. The two modes are mutually exclusive.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import warnings
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from ..base import IntegrationBase, IntegrationOption, SkillsIntegration
|
||||
from ..base import IntegrationBase
|
||||
from ..manifest import IntegrationManifest
|
||||
|
||||
|
||||
def _allow_all() -> bool:
|
||||
"""Return True if the Copilot CLI should run with full permissions.
|
||||
|
||||
Checks ``SPECKIT_COPILOT_ALLOW_ALL_TOOLS`` first (new canonical name).
|
||||
Falls back to the deprecated ``SPECKIT_ALLOW_ALL_TOOLS`` if set,
|
||||
emitting a deprecation warning. Default when neither is set: enabled.
|
||||
"""
|
||||
new_var = os.environ.get("SPECKIT_COPILOT_ALLOW_ALL_TOOLS")
|
||||
if new_var is not None:
|
||||
return new_var != "0"
|
||||
|
||||
old_var = os.environ.get("SPECKIT_ALLOW_ALL_TOOLS")
|
||||
if old_var is not None:
|
||||
warnings.warn(
|
||||
"SPECKIT_ALLOW_ALL_TOOLS is deprecated; "
|
||||
"use SPECKIT_COPILOT_ALLOW_ALL_TOOLS instead.",
|
||||
UserWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return old_var != "0"
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class _CopilotSkillsHelper(SkillsIntegration):
|
||||
"""Internal helper used when Copilot is scaffolded in skills mode.
|
||||
|
||||
Not registered in the integration registry — only used as a delegate
|
||||
by ``CopilotIntegration`` when ``--skills`` is passed.
|
||||
"""
|
||||
|
||||
key = "copilot"
|
||||
config = {
|
||||
"name": "GitHub Copilot",
|
||||
"folder": ".github/",
|
||||
"commands_subdir": "skills",
|
||||
"install_url": "https://docs.github.com/en/copilot/concepts/agents/copilot-cli/about-copilot-cli",
|
||||
"requires_cli": False,
|
||||
}
|
||||
registrar_config = {
|
||||
"dir": ".github/skills",
|
||||
"format": "markdown",
|
||||
"args": "$ARGUMENTS",
|
||||
"extension": "/SKILL.md",
|
||||
}
|
||||
context_file = ".github/copilot-instructions.md"
|
||||
|
||||
|
||||
class CopilotIntegration(IntegrationBase):
|
||||
"""Integration for GitHub Copilot (VS Code IDE + CLI).
|
||||
|
||||
The IDE integration (``requires_cli: False``) installs ``.agent.md``
|
||||
command files. Workflow dispatch additionally requires the
|
||||
``copilot`` CLI to be installed separately.
|
||||
|
||||
When ``--skills`` is passed via ``--integration-options``, commands
|
||||
are scaffolded as ``speckit-<name>/SKILL.md`` under ``.github/skills/``
|
||||
instead of the default ``.agent.md`` + ``.prompt.md`` layout.
|
||||
"""
|
||||
|
||||
key = "copilot"
|
||||
@@ -100,30 +42,6 @@ class CopilotIntegration(IntegrationBase):
|
||||
}
|
||||
context_file = ".github/copilot-instructions.md"
|
||||
|
||||
# Mutable flag set by setup() — indicates the active scaffolding mode.
|
||||
_skills_mode: bool = False
|
||||
|
||||
def effective_invoke_separator(
|
||||
self, parsed_options: dict[str, Any] | None = None
|
||||
) -> str:
|
||||
"""Return ``"-"`` when skills mode is requested, ``"."`` otherwise."""
|
||||
if parsed_options and parsed_options.get("skills"):
|
||||
return "-"
|
||||
if self._skills_mode:
|
||||
return "-"
|
||||
return self.invoke_separator
|
||||
|
||||
@classmethod
|
||||
def options(cls) -> list[IntegrationOption]:
|
||||
return [
|
||||
IntegrationOption(
|
||||
"--skills",
|
||||
is_flag=True,
|
||||
default=False,
|
||||
help="Scaffold commands as agent skills (speckit-<name>/SKILL.md) instead of .agent.md files",
|
||||
),
|
||||
]
|
||||
|
||||
def build_exec_args(
|
||||
self,
|
||||
prompt: str,
|
||||
@@ -132,15 +50,13 @@ class CopilotIntegration(IntegrationBase):
|
||||
output_json: bool = True,
|
||||
) -> list[str] | None:
|
||||
# GitHub Copilot CLI uses ``copilot -p "prompt"`` for
|
||||
# non-interactive mode. --yolo enables all permissions
|
||||
# (tools, paths, and URLs) so the agent can perform file
|
||||
# edits and shell commands without interactive prompts.
|
||||
# Controlled by SPECKIT_COPILOT_ALLOW_ALL_TOOLS env var
|
||||
# (default: enabled). The deprecated SPECKIT_ALLOW_ALL_TOOLS
|
||||
# is also honoured as a fallback.
|
||||
# non-interactive mode. --allow-all-tools is required for the
|
||||
# agent to perform file edits and shell commands. Controlled
|
||||
# by SPECKIT_ALLOW_ALL_TOOLS env var (default: enabled).
|
||||
import os
|
||||
args = ["copilot", "-p", prompt]
|
||||
if _allow_all():
|
||||
args.append("--yolo")
|
||||
if os.environ.get("SPECKIT_ALLOW_ALL_TOOLS", "1") != "0":
|
||||
args.append("--allow-all-tools")
|
||||
if model:
|
||||
args.extend(["--model", model])
|
||||
if output_json:
|
||||
@@ -148,19 +64,7 @@ class CopilotIntegration(IntegrationBase):
|
||||
return args
|
||||
|
||||
def build_command_invocation(self, command_name: str, args: str = "") -> str:
|
||||
"""Build the native invocation for a Copilot command.
|
||||
|
||||
Default mode: agents are not slash-commands — return args as prompt.
|
||||
Skills mode: ``/speckit-<stem>`` slash-command dispatch.
|
||||
"""
|
||||
if self._skills_mode:
|
||||
stem = command_name
|
||||
if stem.startswith("speckit."):
|
||||
stem = stem[len("speckit."):]
|
||||
invocation = "/speckit-" + stem.replace(".", "-")
|
||||
if args:
|
||||
invocation = f"{invocation} {args}"
|
||||
return invocation
|
||||
"""Copilot agents are not slash-commands — just return the args as prompt."""
|
||||
return args or ""
|
||||
|
||||
def dispatch_command(
|
||||
@@ -178,39 +82,22 @@ class CopilotIntegration(IntegrationBase):
|
||||
Copilot ``.agent.md`` files are agents, not skills. The CLI
|
||||
selects them with ``--agent <name>`` and the prompt is just
|
||||
the user's arguments.
|
||||
|
||||
In skills mode, the prompt includes the skill invocation
|
||||
(``/speckit-<stem>``).
|
||||
"""
|
||||
import subprocess
|
||||
|
||||
stem = command_name
|
||||
if stem.startswith("speckit."):
|
||||
stem = stem[len("speckit."):]
|
||||
if "." in stem:
|
||||
stem = stem.rsplit(".", 1)[-1]
|
||||
agent_name = f"speckit.{stem}"
|
||||
|
||||
# Detect skills mode from project layout when not set via setup()
|
||||
skills_mode = self._skills_mode
|
||||
if not skills_mode and project_root:
|
||||
skills_dir = project_root / ".github" / "skills"
|
||||
if skills_dir.is_dir():
|
||||
skills_mode = any(
|
||||
d.is_dir() and (d / "SKILL.md").is_file()
|
||||
for d in skills_dir.glob("speckit-*")
|
||||
)
|
||||
|
||||
if skills_mode:
|
||||
prompt = "/speckit-" + stem.replace(".", "-")
|
||||
if args:
|
||||
prompt = f"{prompt} {args}"
|
||||
else:
|
||||
agent_name = f"speckit.{stem}"
|
||||
prompt = args or ""
|
||||
|
||||
cli_args = ["copilot", "-p", prompt]
|
||||
if not skills_mode:
|
||||
cli_args.extend(["--agent", agent_name])
|
||||
if _allow_all():
|
||||
cli_args.append("--yolo")
|
||||
prompt = args or ""
|
||||
import os
|
||||
cli_args = [
|
||||
"copilot", "-p", prompt,
|
||||
"--agent", agent_name,
|
||||
]
|
||||
if os.environ.get("SPECKIT_ALLOW_ALL_TOOLS", "1") != "0":
|
||||
cli_args.append("--allow-all-tools")
|
||||
if model:
|
||||
cli_args.extend(["--model", model])
|
||||
if not stream:
|
||||
@@ -254,59 +141,6 @@ class CopilotIntegration(IntegrationBase):
|
||||
"""Copilot commands use ``.agent.md`` extension."""
|
||||
return f"speckit.{template_name}.agent.md"
|
||||
|
||||
def post_process_skill_content(self, content: str) -> str:
|
||||
"""Inject Copilot-specific ``mode:`` field into SKILL.md frontmatter.
|
||||
|
||||
Inserts ``mode: speckit.<stem>`` before the closing ``---`` so
|
||||
Copilot can associate the skill with its agent mode.
|
||||
"""
|
||||
lines = content.splitlines(keepends=True)
|
||||
|
||||
# Extract skill name from frontmatter to derive the mode value
|
||||
dash_count = 0
|
||||
skill_name = ""
|
||||
for line in lines:
|
||||
stripped = line.rstrip("\n\r")
|
||||
if stripped == "---":
|
||||
dash_count += 1
|
||||
if dash_count == 2:
|
||||
break
|
||||
continue
|
||||
if dash_count == 1:
|
||||
if stripped.startswith("mode:"):
|
||||
return content # already present
|
||||
if stripped.startswith("name:"):
|
||||
# Parse: name: "speckit-plan" → speckit.plan
|
||||
val = stripped.split(":", 1)[1].strip().strip('"').strip("'")
|
||||
# Convert speckit-plan → speckit.plan
|
||||
if val.startswith("speckit-"):
|
||||
skill_name = "speckit." + val[len("speckit-"):]
|
||||
else:
|
||||
skill_name = val
|
||||
|
||||
if not skill_name:
|
||||
return content
|
||||
|
||||
# Inject mode: before the closing --- of frontmatter
|
||||
out: list[str] = []
|
||||
dash_count = 0
|
||||
injected = False
|
||||
for line in lines:
|
||||
stripped = line.rstrip("\n\r")
|
||||
if stripped == "---":
|
||||
dash_count += 1
|
||||
if dash_count == 2 and not injected:
|
||||
if line.endswith("\r\n"):
|
||||
eol = "\r\n"
|
||||
elif line.endswith("\n"):
|
||||
eol = "\n"
|
||||
else:
|
||||
eol = ""
|
||||
out.append(f"mode: {skill_name}{eol}")
|
||||
injected = True
|
||||
out.append(line)
|
||||
return "".join(out)
|
||||
|
||||
def setup(
|
||||
self,
|
||||
project_root: Path,
|
||||
@@ -316,24 +150,10 @@ class CopilotIntegration(IntegrationBase):
|
||||
) -> list[Path]:
|
||||
"""Install copilot commands, companion prompts, and VS Code settings.
|
||||
|
||||
When ``parsed_options["skills"]`` is truthy, delegates to skills
|
||||
scaffolding (``speckit-<name>/SKILL.md`` under ``.github/skills/``).
|
||||
Otherwise uses the default ``.agent.md`` + ``.prompt.md`` layout.
|
||||
Uses base class primitives to: read templates, process them
|
||||
(replace placeholders, strip script blocks, rewrite paths),
|
||||
write as ``.agent.md``, then add companion prompts and VS Code settings.
|
||||
"""
|
||||
parsed_options = parsed_options or {}
|
||||
self._skills_mode = bool(parsed_options.get("skills"))
|
||||
if self._skills_mode:
|
||||
return self._setup_skills(project_root, manifest, parsed_options, **opts)
|
||||
return self._setup_default(project_root, manifest, parsed_options, **opts)
|
||||
|
||||
def _setup_default(
|
||||
self,
|
||||
project_root: Path,
|
||||
manifest: IntegrationManifest,
|
||||
parsed_options: dict[str, Any] | None = None,
|
||||
**opts: Any,
|
||||
) -> list[Path]:
|
||||
"""Default mode: .agent.md + .prompt.md + VS Code settings merge."""
|
||||
project_root_resolved = project_root.resolve()
|
||||
if manifest.project_root != project_root_resolved:
|
||||
raise ValueError(
|
||||
@@ -363,10 +183,7 @@ class CopilotIntegration(IntegrationBase):
|
||||
# 1. Process and write command files as .agent.md
|
||||
for src_file in templates:
|
||||
raw = src_file.read_text(encoding="utf-8")
|
||||
processed = self.process_template(
|
||||
raw, self.key, script_type, arg_placeholder,
|
||||
context_file=self.context_file or "",
|
||||
)
|
||||
processed = self.process_template(raw, self.key, script_type, arg_placeholder)
|
||||
dst_name = self.command_filename(src_file.stem)
|
||||
dst_file = self.write_file_and_record(
|
||||
processed, dest / dst_name, project_root, manifest
|
||||
@@ -400,39 +217,8 @@ class CopilotIntegration(IntegrationBase):
|
||||
self.record_file_in_manifest(dst_settings, project_root, manifest)
|
||||
created.append(dst_settings)
|
||||
|
||||
# 4. Upsert managed context section into the agent context file
|
||||
self.upsert_context_section(project_root)
|
||||
|
||||
return created
|
||||
|
||||
def _setup_skills(
|
||||
self,
|
||||
project_root: Path,
|
||||
manifest: IntegrationManifest,
|
||||
parsed_options: dict[str, Any] | None = None,
|
||||
**opts: Any,
|
||||
) -> list[Path]:
|
||||
"""Skills mode: delegate to ``_CopilotSkillsHelper`` then post-process."""
|
||||
helper = _CopilotSkillsHelper()
|
||||
created = SkillsIntegration.setup(
|
||||
helper, project_root, manifest, parsed_options, **opts
|
||||
)
|
||||
|
||||
# Post-process generated skill files with Copilot-specific frontmatter
|
||||
skills_dir = helper.skills_dest(project_root).resolve()
|
||||
for path in created:
|
||||
try:
|
||||
path.resolve().relative_to(skills_dir)
|
||||
except ValueError:
|
||||
continue
|
||||
if path.name != "SKILL.md":
|
||||
continue
|
||||
|
||||
content = path.read_text(encoding="utf-8")
|
||||
updated = self.post_process_skill_content(content)
|
||||
if updated != content:
|
||||
path.write_bytes(updated.encode("utf-8"))
|
||||
self.record_file_in_manifest(path, project_root, manifest)
|
||||
# 4. Install integration-specific update-context scripts
|
||||
created.extend(self.install_scripts(project_root, manifest))
|
||||
|
||||
return created
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
# update-context.ps1 — Copilot integration: create/update .github/copilot-instructions.md
|
||||
#
|
||||
# This is the copilot-specific implementation that produces the GitHub
|
||||
# Copilot instructions file. The shared dispatcher reads
|
||||
# .specify/integration.json and calls this script.
|
||||
#
|
||||
# NOTE: This script is not yet active. It will be activated in Stage 7
|
||||
# when the shared update-agent-context.ps1 replaces its switch statement
|
||||
# with integration.json-based dispatch. The shared script must also be
|
||||
# refactored to support SPECKIT_SOURCE_ONLY (guard the Main call) before
|
||||
# dot-sourcing will work.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$repoRoot = try { git rev-parse --show-toplevel 2>$null } catch { $null }
|
||||
# If git did not return a repo root, or the git root does not contain .specify,
|
||||
# fall back to walking up from the script directory to find the initialized project root.
|
||||
if (-not $repoRoot -or -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = $scriptDir
|
||||
$fsRoot = [System.IO.Path]::GetPathRoot($repoRoot)
|
||||
while ($repoRoot -and $repoRoot -ne $fsRoot -and -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = Split-Path -Parent $repoRoot
|
||||
}
|
||||
}
|
||||
|
||||
# Invoke shared update-agent-context script as a separate process.
|
||||
# Dot-sourcing is unsafe until that script guards its Main call.
|
||||
& "$repoRoot/.specify/scripts/powershell/update-agent-context.ps1" -AgentType copilot
|
||||
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env bash
|
||||
# update-context.sh — Copilot integration: create/update .github/copilot-instructions.md
|
||||
#
|
||||
# This is the copilot-specific implementation that produces the GitHub
|
||||
# Copilot instructions file. The shared dispatcher reads
|
||||
# .specify/integration.json and calls this script.
|
||||
#
|
||||
# NOTE: This script is not yet active. It will be activated in Stage 7
|
||||
# when the shared update-agent-context.sh replaces its case statement
|
||||
# with integration.json-based dispatch. The shared script must also be
|
||||
# refactored to support SPECKIT_SOURCE_ONLY (guard the main logic)
|
||||
# before sourcing will work.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
_script_dir="$(cd "$(dirname "$0")" && pwd)"
|
||||
_root="$_script_dir"
|
||||
while [ "$_root" != "/" ] && [ ! -d "$_root/.specify" ]; do _root="$(dirname "$_root")"; done
|
||||
if [ -z "${REPO_ROOT:-}" ]; then
|
||||
if [ -d "$_root/.specify" ]; then
|
||||
REPO_ROOT="$_root"
|
||||
else
|
||||
git_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
||||
if [ -n "$git_root" ] && [ -d "$git_root/.specify" ]; then
|
||||
REPO_ROOT="$git_root"
|
||||
else
|
||||
REPO_ROOT="$_root"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Invoke shared update-agent-context script as a separate process.
|
||||
# Sourcing is unsafe until that script guards its main logic.
|
||||
exec "$REPO_ROOT/.specify/scripts/bash/update-agent-context.sh" copilot
|
||||
@@ -26,7 +26,6 @@ class CursorAgentIntegration(SkillsIntegration):
|
||||
}
|
||||
|
||||
context_file = ".cursor/rules/specify-rules.mdc"
|
||||
multi_install_safe = True
|
||||
|
||||
@classmethod
|
||||
def options(cls) -> list[IntegrationOption]:
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# update-context.ps1 — Cursor integration: create/update .cursor/rules/specify-rules.mdc
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$repoRoot = try { git rev-parse --show-toplevel 2>$null } catch { $null }
|
||||
# If git did not return a repo root, or the git root does not contain .specify,
|
||||
# fall back to walking up from the script directory to find the initialized project root.
|
||||
if (-not $repoRoot -or -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = $scriptDir
|
||||
$fsRoot = [System.IO.Path]::GetPathRoot($repoRoot)
|
||||
while ($repoRoot -and $repoRoot -ne $fsRoot -and -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = Split-Path -Parent $repoRoot
|
||||
}
|
||||
}
|
||||
|
||||
& "$repoRoot/.specify/scripts/powershell/update-agent-context.ps1" -AgentType cursor-agent
|
||||
28
src/specify_cli/integrations/cursor_agent/scripts/update-context.sh
Executable file
28
src/specify_cli/integrations/cursor_agent/scripts/update-context.sh
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
# update-context.sh — Cursor integration: create/update .cursor/rules/specify-rules.mdc
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
_script_dir="$(cd "$(dirname "$0")" && pwd)"
|
||||
_root="$_script_dir"
|
||||
while [ "$_root" != "/" ] && [ ! -d "$_root/.specify" ]; do _root="$(dirname "$_root")"; done
|
||||
if [ -z "${REPO_ROOT:-}" ]; then
|
||||
if [ -d "$_root/.specify" ]; then
|
||||
REPO_ROOT="$_root"
|
||||
else
|
||||
git_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
||||
if [ -n "$git_root" ] && [ -d "$git_root/.specify" ]; then
|
||||
REPO_ROOT="$git_root"
|
||||
else
|
||||
REPO_ROOT="$_root"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
exec "$REPO_ROOT/.specify/scripts/bash/update-agent-context.sh" cursor-agent
|
||||
@@ -1,65 +0,0 @@
|
||||
"""Devin for Terminal integration — skills-based agent.
|
||||
|
||||
Devin uses the ``.devin/skills/speckit-<name>/SKILL.md`` layout and
|
||||
reads project context from ``AGENTS.md`` at the repo root. The CLI
|
||||
binary is ``devin`` and skills are invoked via ``/<name>`` inside an
|
||||
interactive ``devin`` session.
|
||||
|
||||
See: https://cli.devin.ai/docs/extensibility/skills/overview
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from ..base import IntegrationOption, SkillsIntegration
|
||||
|
||||
|
||||
class DevinIntegration(SkillsIntegration):
|
||||
"""Integration for Cognition AI's Devin for Terminal."""
|
||||
|
||||
key = "devin"
|
||||
config = {
|
||||
"name": "Devin for Terminal",
|
||||
"folder": ".devin/",
|
||||
"commands_subdir": "skills",
|
||||
"install_url": "https://cli.devin.ai/docs",
|
||||
"requires_cli": True,
|
||||
}
|
||||
registrar_config = {
|
||||
"dir": ".devin/skills",
|
||||
"format": "markdown",
|
||||
"args": "$ARGUMENTS",
|
||||
"extension": "/SKILL.md",
|
||||
}
|
||||
context_file = "AGENTS.md"
|
||||
|
||||
def build_exec_args(
|
||||
self,
|
||||
prompt: str,
|
||||
*,
|
||||
model: str | None = None,
|
||||
output_json: bool = True,
|
||||
) -> list[str] | None:
|
||||
"""Build non-interactive CLI args for Devin for Terminal.
|
||||
|
||||
Devin supports ``devin -p <prompt>`` for single-turn execution
|
||||
and ``--model`` for model selection, but its CLI has no flag
|
||||
for structured JSON output. When ``output_json`` is requested,
|
||||
Devin is still dispatched normally and returns plain-text
|
||||
stdout instead of structured JSON. ``requires_cli=True`` is
|
||||
kept on the integration for tool detection.
|
||||
"""
|
||||
args = [self.key, "-p", prompt]
|
||||
if model:
|
||||
args.extend(["--model", model])
|
||||
return args
|
||||
|
||||
@classmethod
|
||||
def options(cls) -> list[IntegrationOption]:
|
||||
return [
|
||||
IntegrationOption(
|
||||
"--skills",
|
||||
is_flag=True,
|
||||
default=True,
|
||||
help="Install as agent skills (default for Devin)",
|
||||
),
|
||||
]
|
||||
@@ -130,10 +130,7 @@ class ForgeIntegration(MarkdownIntegration):
|
||||
for src_file in templates:
|
||||
raw = src_file.read_text(encoding="utf-8")
|
||||
# Process template with standard MarkdownIntegration logic
|
||||
processed = self.process_template(
|
||||
raw, self.key, script_type, arg_placeholder,
|
||||
context_file=self.context_file or "",
|
||||
)
|
||||
processed = self.process_template(raw, self.key, script_type, arg_placeholder)
|
||||
|
||||
# FORGE-SPECIFIC: Ensure any remaining $ARGUMENTS placeholders are
|
||||
# converted to {{parameters}}
|
||||
@@ -148,8 +145,8 @@ class ForgeIntegration(MarkdownIntegration):
|
||||
)
|
||||
created.append(dst_file)
|
||||
|
||||
# Upsert managed context section into the agent context file
|
||||
self.upsert_context_section(project_root)
|
||||
# Install integration-specific update-context scripts
|
||||
created.extend(self.install_scripts(project_root, manifest))
|
||||
|
||||
return created
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
# update-context.ps1 — Forge integration: create/update AGENTS.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$repoRoot = try { git rev-parse --show-toplevel 2>$null } catch { $null }
|
||||
# If git did not return a repo root, or the git root does not contain .specify,
|
||||
# fall back to walking up from the script directory to find the initialized project root.
|
||||
if (-not $repoRoot -or -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = $scriptDir
|
||||
$fsRoot = [System.IO.Path]::GetPathRoot($repoRoot)
|
||||
while ($repoRoot -and $repoRoot -ne $fsRoot -and -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = Split-Path -Parent $repoRoot
|
||||
}
|
||||
}
|
||||
|
||||
$sharedScript = "$repoRoot/.specify/scripts/powershell/update-agent-context.ps1"
|
||||
|
||||
# Always delegate to the shared updater; fail clearly if it is unavailable.
|
||||
if (-not (Test-Path $sharedScript)) {
|
||||
Write-Error "Error: shared agent context updater not found: $sharedScript"
|
||||
Write-Error "Forge integration requires support in scripts/powershell/update-agent-context.ps1."
|
||||
exit 1
|
||||
}
|
||||
|
||||
& $sharedScript -AgentType forge
|
||||
exit $LASTEXITCODE
|
||||
38
src/specify_cli/integrations/forge/scripts/update-context.sh
Executable file
38
src/specify_cli/integrations/forge/scripts/update-context.sh
Executable file
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env bash
|
||||
# update-context.sh — Forge integration: create/update AGENTS.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
_script_dir="$(cd "$(dirname "$0")" && pwd)"
|
||||
_root="$_script_dir"
|
||||
while [ "$_root" != "/" ] && [ ! -d "$_root/.specify" ]; do _root="$(dirname "$_root")"; done
|
||||
if [ -z "${REPO_ROOT:-}" ]; then
|
||||
if [ -d "$_root/.specify" ]; then
|
||||
REPO_ROOT="$_root"
|
||||
else
|
||||
git_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
||||
if [ -n "$git_root" ] && [ -d "$git_root/.specify" ]; then
|
||||
REPO_ROOT="$git_root"
|
||||
else
|
||||
REPO_ROOT="$_root"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
shared_script="$REPO_ROOT/.specify/scripts/bash/update-agent-context.sh"
|
||||
|
||||
# Always delegate to the shared updater; fail clearly if it is unavailable.
|
||||
if [ ! -x "$shared_script" ]; then
|
||||
echo "Error: shared agent context updater not found or not executable:" >&2
|
||||
echo " $shared_script" >&2
|
||||
echo "Forge integration requires support in scripts/bash/update-agent-context.sh." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exec "$shared_script" forge
|
||||
@@ -19,4 +19,3 @@ class GeminiIntegration(TomlIntegration):
|
||||
"extension": ".toml",
|
||||
}
|
||||
context_file = "GEMINI.md"
|
||||
multi_install_safe = True
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# update-context.ps1 — Gemini CLI integration: create/update GEMINI.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$repoRoot = try { git rev-parse --show-toplevel 2>$null } catch { $null }
|
||||
# If git did not return a repo root, or the git root does not contain .specify,
|
||||
# fall back to walking up from the script directory to find the initialized project root.
|
||||
if (-not $repoRoot -or -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = $scriptDir
|
||||
$fsRoot = [System.IO.Path]::GetPathRoot($repoRoot)
|
||||
while ($repoRoot -and $repoRoot -ne $fsRoot -and -not (Test-Path (Join-Path $repoRoot '.specify'))) {
|
||||
$repoRoot = Split-Path -Parent $repoRoot
|
||||
}
|
||||
}
|
||||
|
||||
& "$repoRoot/.specify/scripts/powershell/update-agent-context.ps1" -AgentType gemini
|
||||
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
# update-context.sh — Gemini CLI integration: create/update GEMINI.md
|
||||
#
|
||||
# Thin wrapper that delegates to the shared update-agent-context script.
|
||||
# Activated in Stage 7 when the shared script uses integration.json dispatch.
|
||||
#
|
||||
# Until then, this delegates to the shared script as a subprocess.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Derive repo root from script location (walks up to find .specify/)
|
||||
_script_dir="$(cd "$(dirname "$0")" && pwd)"
|
||||
_root="$_script_dir"
|
||||
while [ "$_root" != "/" ] && [ ! -d "$_root/.specify" ]; do _root="$(dirname "$_root")"; done
|
||||
if [ -z "${REPO_ROOT:-}" ]; then
|
||||
if [ -d "$_root/.specify" ]; then
|
||||
REPO_ROOT="$_root"
|
||||
else
|
||||
git_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
||||
if [ -n "$git_root" ] && [ -d "$git_root/.specify" ]; then
|
||||
REPO_ROOT="$git_root"
|
||||
else
|
||||
REPO_ROOT="$_root"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
exec "$REPO_ROOT/.specify/scripts/bash/update-agent-context.sh" gemini
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user