mirror of
https://github.com/github/spec-kit.git
synced 2026-07-04 04:45:43 +08:00
Compare commits
10 Commits
copilot/do
...
v0.8.7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ef787ea8c | ||
|
|
5b9f0040e7 | ||
|
|
11f49ebfb2 | ||
|
|
cd44dc2147 | ||
|
|
f5b675e9ee | ||
|
|
38bb88bde1 | ||
|
|
0facb1bdc2 | ||
|
|
2d5e63005d | ||
|
|
793632089a | ||
|
|
c0bf5d0c64 |
15
CHANGELOG.md
15
CHANGELOG.md
@@ -2,6 +2,21 @@
|
||||
|
||||
<!-- insert new changelog below this comment -->
|
||||
|
||||
## [0.8.7] - 2026-05-07
|
||||
|
||||
### Changed
|
||||
|
||||
- feat: add agent-orchestrator to community extension catalog (#2236)
|
||||
- chore: update extension versions in community catalog (#2468)
|
||||
- fix(goose): Declare args parameter in generated recipes (#2402)
|
||||
- feat: Add lingma support (#2348)
|
||||
- docs: Add uv installation guide and inline callouts (#2465)
|
||||
- Add fx-to-dotnet to community extension catalog (#2471)
|
||||
- fix: default non-interactive init to copilot integration (#2414)
|
||||
- fix(forge): use hyphen notation for command refs in Forge integration (#2462)
|
||||
- feat(catalog): add Cost Tracker (cost) community extension (#2448)
|
||||
- chore: release 0.8.6, begin 0.8.7.dev0 development (#2463)
|
||||
|
||||
## [0.8.6] - 2026-05-06
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -56,6 +56,9 @@ Choose your preferred installation method:
|
||||
|
||||
Install once and use everywhere. Pin a specific release tag for stability (check [Releases](https://github.com/github/spec-kit/releases) for the latest):
|
||||
|
||||
> [!NOTE]
|
||||
> The `uv tool install` commands below require **[uv](https://docs.astral.sh/uv/)** — a fast Python package manager. If you see `command not found: uv`, [install uv first](./docs/install/uv.md). The `pipx` alternative does not require uv.
|
||||
|
||||
```bash
|
||||
# Install a specific stable release (recommended — replace vX.Y.Z with the latest tag)
|
||||
uv tool install specify-cli --from git+https://github.com/github/spec-kit.git@vX.Y.Z
|
||||
@@ -212,6 +215,7 @@ The following community-contributed extensions are available in [`catalog.commun
|
||||
| Cleanup Extension | Post-implementation quality gate that reviews changes, fixes small issues (scout rule), creates tasks for medium issues, and generates analysis for large issues | `code` | Read+Write | [spec-kit-cleanup](https://github.com/dsrednicki/spec-kit-cleanup) |
|
||||
| Conduct Extension | Orchestrates spec-kit phases via sub-agent delegation to reduce context pollution. | `process` | Read+Write | [spec-kit-conduct-ext](https://github.com/twbrandon7/spec-kit-conduct-ext) |
|
||||
| Confluence Extension | Create a doc in Confluence summarizing the specifications and planning files | `integration` | Read+Write | [spec-kit-confluence](https://github.com/aaronrsun/spec-kit-confluence) |
|
||||
| Cost Tracker | Track real LLM dollar cost across SDD workflows — per-feature budgets, per-integration comparison, and finance-ready exports | `visibility` | Read+Write | [spec-kit-cost](https://github.com/Quratulain-bilal/spec-kit-cost) |
|
||||
| DocGuard — CDD Enforcement | Canonical-Driven Development enforcement. Validates, scores, and traces project documentation with automated checks, AI-driven workflows, and spec-kit hooks. Zero NPM runtime dependencies. | `docs` | Read+Write | [spec-kit-docguard](https://github.com/raccioly/docguard) |
|
||||
| Extensify | Create and validate extensions and extension catalogs | `process` | Read+Write | [extensify](https://github.com/mnriem/spec-kit-extensions/tree/main/extensify) |
|
||||
| Fix Findings | Automated analyze-fix-reanalyze loop that resolves spec findings until clean | `code` | Read+Write | [spec-kit-fix-findings](https://github.com/Quratulain-bilal/spec-kit-fix-findings) |
|
||||
@@ -219,6 +223,7 @@ The following community-contributed extensions are available in [`catalog.commun
|
||||
| Fleet Orchestrator | Orchestrate a full feature lifecycle with human-in-the-loop gates across all SpecKit phases | `process` | Read+Write | [spec-kit-fleet](https://github.com/sharathsatish/spec-kit-fleet) |
|
||||
| GitHub Issues Integration 1 | Generate spec artifacts from GitHub Issues - import issues, sync updates, and maintain bidirectional traceability | `integration` | Read+Write | [spec-kit-github-issues](https://github.com/Fatima367/spec-kit-github-issues) |
|
||||
| GitHub Issues Integration 2 | Creates and syncs local specs from an existing GitHub issue | `integration` | Read+Write | [spec-kit-issue](https://github.com/aaronrsun/spec-kit-issue) |
|
||||
| Intelligent Agent Orchestrator | Cross-catalog agent discovery and intelligent prompt-to-command routing | `process` | Read+Write | [spec-kit-orchestrator](https://github.com/pragya247/spec-kit-orchestrator) |
|
||||
| Iterate | Iterate on spec documents with a two-phase define-and-apply workflow — refine specs mid-implementation and go straight back to building | `docs` | Read+Write | [spec-kit-iterate](https://github.com/imviancagrace/spec-kit-iterate) |
|
||||
| Jira Integration | Create Jira Epics, Stories, and Issues from spec-kit specifications and task breakdowns with configurable hierarchy and custom field support | `integration` | Read+Write | [spec-kit-jira](https://github.com/mbachorik/spec-kit-jira) |
|
||||
| Learning Extension | Generate educational guides from implementations and enhance clarifications with mentoring context | `docs` | Read+Write | [spec-kit-learn](https://github.com/imviancagrace/spec-kit-learn) |
|
||||
@@ -235,6 +240,7 @@ The following community-contributed extensions are available in [`catalog.commun
|
||||
| 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) |
|
||||
| .NET Framework to Modern .NET Migration | Orchestrate end-to-end .NET Framework to modern .NET migration across 7 phases, with SDD lifecycle integration | `process` | Read+Write | [spec-kit-fx-to-net](https://github.com/RogerBestMsft/spec-kit-FxToNet) |
|
||||
| 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) |
|
||||
@@ -484,7 +490,7 @@ 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:
|
||||
In an interactive terminal, you will be prompted to select the coding agent integration you are using. In non-interactive sessions, such as CI or piped runs, `specify init` defaults to GitHub Copilot unless you pass `--integration`. You can also proactively specify the integration directly in the terminal:
|
||||
|
||||
```bash
|
||||
specify init <project_name> --integration copilot
|
||||
|
||||
60
docs/install/uv.md
Normal file
60
docs/install/uv.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# Installing uv
|
||||
|
||||
[uv](https://docs.astral.sh/uv/) is a fast Python package manager by [Astral](https://astral.sh/). Spec Kit uses `uv` (via `uvx` or `uv tool install`) to run the `specify` CLI without polluting your global Python environment.
|
||||
|
||||
> [!NOTE]
|
||||
> **Already have uv?** Run `uv --version` to confirm it is installed, then head back to the [Installation Guide](../installation.md).
|
||||
|
||||
## Installation
|
||||
|
||||
### macOS and Linux — Standalone Installer
|
||||
|
||||
The quickest way to install uv on macOS or Linux is the official shell script:
|
||||
|
||||
```bash
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
```
|
||||
|
||||
After the script finishes, follow any instructions printed by the installer to add uv to your `PATH`, then open a new terminal.
|
||||
|
||||
### Windows — Standalone Installer
|
||||
|
||||
Run the following in **Command Prompt or PowerShell**:
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
|
||||
```
|
||||
|
||||
After the script finishes, open a new terminal so the `uv` binary is on your `PATH`.
|
||||
|
||||
### macOS — Homebrew
|
||||
|
||||
```bash
|
||||
brew install uv
|
||||
```
|
||||
|
||||
### Windows — WinGet
|
||||
|
||||
```powershell
|
||||
winget install --id=astral-sh.uv -e
|
||||
```
|
||||
|
||||
### Windows — Scoop
|
||||
|
||||
```powershell
|
||||
scoop install uv
|
||||
```
|
||||
|
||||
## Verification
|
||||
|
||||
Confirm that uv is installed and on your `PATH`:
|
||||
|
||||
```bash
|
||||
uv --version
|
||||
```
|
||||
|
||||
You should see output similar to `uv 0.x.y (...)`.
|
||||
|
||||
## Further Reading
|
||||
|
||||
For advanced options (self-update, proxy settings, uninstall, etc.) see the official [uv installation docs](https://docs.astral.sh/uv/getting-started/installation/).
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
The easiest way to get started is to initialize a new project. Pin a specific release tag for stability (check [Releases](https://github.com/github/spec-kit/releases) for the latest):
|
||||
|
||||
> [!NOTE]
|
||||
> The `uvx` commands below require **[uv](https://docs.astral.sh/uv/)**. If you see `command not found: uvx`, [install uv first](./install/uv.md). The `pipx` alternative does not require uv.
|
||||
|
||||
```bash
|
||||
# Install from a specific stable release (recommended — replace vX.Y.Z with the latest tag)
|
||||
uvx --from git+https://github.com/github/spec-kit.git@vX.Y.Z specify init <PROJECT_NAME>
|
||||
@@ -41,6 +44,8 @@ uvx --from git+https://github.com/github/spec-kit.git@vX.Y.Z specify init --here
|
||||
|
||||
### Specify Integration
|
||||
|
||||
Interactive terminals prompt you to choose a coding agent integration during initialization. Non-interactive sessions, such as CI or piped runs, default to GitHub Copilot unless you pass `--integration`.
|
||||
|
||||
You can proactively specify your coding agent integration during initialization:
|
||||
|
||||
```bash
|
||||
|
||||
@@ -28,6 +28,8 @@ Creates a new Spec Kit project with the necessary directory structure, templates
|
||||
|
||||
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.
|
||||
|
||||
When `--integration` is omitted, interactive terminals prompt you to choose an integration. Non-interactive sessions, such as CI or piped runs, default to GitHub Copilot; pass `--integration <key>` to choose a different integration explicitly.
|
||||
|
||||
### Examples
|
||||
|
||||
```bash
|
||||
|
||||
@@ -24,6 +24,7 @@ The Specify CLI supports a wide range of AI coding agents. When you run `specify
|
||||
| [Kilo Code](https://github.com/Kilo-Org/kilocode) | `kilocode` | |
|
||||
| [Kimi Code](https://code.kimi.com/) | `kimi` | Skills-based integration; supports `--migrate-legacy` for dotted→hyphenated directory migration |
|
||||
| [Kiro CLI](https://kiro.dev/docs/cli/) | `kiro-cli` | Alias: `--integration kiro` |
|
||||
| [Lingma](https://lingma.aliyun.com/) | `lingma` | Skills-based integration; skills are installed automatically |
|
||||
| [Mistral Vibe](https://github.com/mistralai/mistral-vibe) | `vibe` | |
|
||||
| [opencode](https://opencode.ai/) | `opencode` | |
|
||||
| [Pi Coding Agent](https://pi.dev) | `pi` | Pi doesn't have MCP support out of the box, so `taskstoissues` won't work as intended. MCP support can be added via [extensions](https://github.com/badlogic/pi-mono/tree/main/packages/coding-agent#extensions) |
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
href: quickstart.md
|
||||
- name: Upgrade
|
||||
href: upgrade.md
|
||||
- name: Install uv
|
||||
href: install/uv.md
|
||||
|
||||
# Reference
|
||||
- name: Reference
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"updated_at": "2026-05-05T07:26:00Z",
|
||||
"updated_at": "2026-05-07T05:51:00Z",
|
||||
"catalog_url": "https://raw.githubusercontent.com/github/spec-kit/main/extensions/catalog.community.json",
|
||||
"extensions": {
|
||||
"aide": {
|
||||
@@ -68,6 +68,38 @@
|
||||
"created_at": "2026-03-31T00:00:00Z",
|
||||
"updated_at": "2026-03-31T00:00:00Z"
|
||||
},
|
||||
"agent-orchestrator": {
|
||||
"name": "Intelligent Agent Orchestrator",
|
||||
"id": "agent-orchestrator",
|
||||
"description": "Cross-catalog agent discovery and intelligent prompt-to-command routing",
|
||||
"author": "pragya247",
|
||||
"version": "0.1.0",
|
||||
"download_url": "https://github.com/pragya247/spec-kit-orchestrator/archive/refs/tags/v0.1.0.zip",
|
||||
"repository": "https://github.com/pragya247/spec-kit-orchestrator",
|
||||
"homepage": "https://github.com/pragya247/spec-kit-orchestrator",
|
||||
"documentation": "https://github.com/pragya247/spec-kit-orchestrator/blob/main/README.md",
|
||||
"changelog": "https://github.com/pragya247/spec-kit-orchestrator/blob/main/CHANGELOG.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.6.1"
|
||||
},
|
||||
"provides": {
|
||||
"commands": 3,
|
||||
"hooks": 1
|
||||
},
|
||||
"tags": [
|
||||
"orchestrator",
|
||||
"routing",
|
||||
"discovery",
|
||||
"agent",
|
||||
"ai"
|
||||
],
|
||||
"verified": false,
|
||||
"downloads": 0,
|
||||
"stars": 0,
|
||||
"created_at": "2026-05-04T00:00:00Z",
|
||||
"updated_at": "2026-05-04T00:00:00Z"
|
||||
},
|
||||
"architect-preview": {
|
||||
"name": "Architect Impact Previewer",
|
||||
"id": "architect-preview",
|
||||
@@ -105,8 +137,8 @@
|
||||
"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",
|
||||
"version": "1.6.7",
|
||||
"download_url": "https://github.com/DyanGalih/spec-kit-architecture-guard/archive/refs/tags/v1.6.7.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",
|
||||
@@ -131,7 +163,7 @@
|
||||
"downloads": 0,
|
||||
"stars": 0,
|
||||
"created_at": "2026-05-05T07:26:00Z",
|
||||
"updated_at": "2026-05-05T07:26:00Z"
|
||||
"updated_at": "2026-05-06T22:28:55Z"
|
||||
},
|
||||
"archive": {
|
||||
"name": "Archive Extension",
|
||||
@@ -580,6 +612,38 @@
|
||||
"created_at": "2026-03-29T00:00:00Z",
|
||||
"updated_at": "2026-03-29T00:00:00Z"
|
||||
},
|
||||
"cost": {
|
||||
"name": "Cost Tracker",
|
||||
"id": "cost",
|
||||
"description": "Track real LLM dollar cost across SDD workflows — per-feature budgets, per-integration comparison, and finance-ready exports.",
|
||||
"author": "Quratulain-bilal",
|
||||
"version": "1.0.0",
|
||||
"download_url": "https://github.com/Quratulain-bilal/spec-kit-cost/archive/refs/tags/v1.0.0.zip",
|
||||
"repository": "https://github.com/Quratulain-bilal/spec-kit-cost",
|
||||
"homepage": "https://github.com/Quratulain-bilal/spec-kit-cost",
|
||||
"documentation": "https://github.com/Quratulain-bilal/spec-kit-cost/blob/main/README.md",
|
||||
"changelog": "https://github.com/Quratulain-bilal/spec-kit-cost/blob/main/CHANGELOG.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.8.0"
|
||||
},
|
||||
"provides": {
|
||||
"commands": 5,
|
||||
"hooks": 0
|
||||
},
|
||||
"tags": [
|
||||
"cost",
|
||||
"budget",
|
||||
"tokens",
|
||||
"visibility",
|
||||
"finance"
|
||||
],
|
||||
"verified": false,
|
||||
"downloads": 0,
|
||||
"stars": 0,
|
||||
"created_at": "2026-05-03T00:00:00Z",
|
||||
"updated_at": "2026-05-05T00:00:00Z"
|
||||
},
|
||||
"diagram": {
|
||||
"name": "Spec Diagram",
|
||||
"id": "diagram",
|
||||
@@ -810,6 +874,44 @@
|
||||
"created_at": "2026-03-06T00:00:00Z",
|
||||
"updated_at": "2026-03-31T00:00:00Z"
|
||||
},
|
||||
"fx-to-dotnet": {
|
||||
"name": ".NET Framework to Modern .NET Migration",
|
||||
"id": "fx-to-dotnet",
|
||||
"description": "Orchestrate end-to-end .NET Framework to modern .NET migration across 7 phases, with SDD lifecycle integration.",
|
||||
"author": "RogerBestMsft",
|
||||
"version": "0.8.0",
|
||||
"download_url": "https://github.com/RogerBestMsft/spec-kit-FxToNet/releases/download/v0.8.0/fx-to-dotnet.zip",
|
||||
"repository": "https://github.com/RogerBestMsft/spec-kit-FxToNet",
|
||||
"homepage": "https://github.com/RogerBestMsft/spec-kit-FxToNet",
|
||||
"documentation": "https://github.com/RogerBestMsft/spec-kit-FxToNet/blob/main/README.md",
|
||||
"license": "MIT",
|
||||
"requires": {
|
||||
"speckit_version": ">=0.1.0",
|
||||
"tools": [
|
||||
{
|
||||
"name": "Microsoft.GitHubCopilot.Modernization.Mcp",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"provides": {
|
||||
"commands": 12,
|
||||
"hooks": 5
|
||||
},
|
||||
"tags": [
|
||||
"dotnet",
|
||||
"migration",
|
||||
"modernization",
|
||||
"framework",
|
||||
"aspnet",
|
||||
"shared-artifact"
|
||||
],
|
||||
"verified": false,
|
||||
"downloads": 0,
|
||||
"stars": 0,
|
||||
"created_at": "2026-05-06T00:00:00Z",
|
||||
"updated_at": "2026-05-06T00:00:00Z"
|
||||
},
|
||||
"github-issues": {
|
||||
"name": "GitHub Issues Integration 1",
|
||||
"id": "github-issues",
|
||||
@@ -1313,8 +1415,8 @@
|
||||
"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",
|
||||
"version": "0.7.9",
|
||||
"download_url": "https://github.com/DyanGalih/spec-kit-memory-hub/archive/refs/tags/v0.7.9.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",
|
||||
@@ -1339,7 +1441,7 @@
|
||||
"downloads": 0,
|
||||
"stars": 0,
|
||||
"created_at": "2026-04-23T00:00:00Z",
|
||||
"updated_at": "2026-05-03T00:00:00Z"
|
||||
"updated_at": "2026-05-06T22:28:55Z"
|
||||
},
|
||||
"memorylint": {
|
||||
"name": "MemoryLint",
|
||||
@@ -2015,8 +2117,8 @@
|
||||
"id": "security-review",
|
||||
"description": "Full-project secure-by-design security audits plus staged, branch/PR, plan, task, follow-up, and apply reviews",
|
||||
"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.4.5",
|
||||
"download_url": "https://github.com/DyanGalih/spec-kit-security-review/archive/refs/tags/v1.4.5.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",
|
||||
@@ -2040,7 +2142,7 @@
|
||||
"downloads": 0,
|
||||
"stars": 0,
|
||||
"created_at": "2026-04-03T03:24:03Z",
|
||||
"updated_at": "2026-05-03T00:00:00Z"
|
||||
"updated_at": "2026-05-06T22:28:55Z"
|
||||
},
|
||||
"sf": {
|
||||
"name": "SFSpeckit — Salesforce Spec-Driven Development",
|
||||
@@ -2905,7 +3007,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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ description: "Create a feature branch with sequential or timestamp numbering"
|
||||
|
||||
# Create Feature Branch
|
||||
|
||||
Create and switch to a new git feature branch for the given specification. This command handles **branch creation only** — the spec directory and files are created by the core `/speckit.specify` workflow.
|
||||
Create and switch to a new git feature branch for the given specification. This command handles **branch creation only** — the spec directory and files are created by the core `__SPECKIT_COMMAND_SPECIFY__` workflow.
|
||||
|
||||
## User Input
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"updated_at": "2026-04-28T00:00:00Z",
|
||||
"updated_at": "2026-04-29T00:00:00Z",
|
||||
"catalog_url": "https://raw.githubusercontent.com/github/spec-kit/main/integrations/catalog.json",
|
||||
"integrations": {
|
||||
"claude": {
|
||||
@@ -210,6 +210,15 @@
|
||||
"repository": "https://github.com/github/spec-kit",
|
||||
"tags": ["cli", "skills"]
|
||||
},
|
||||
"lingma": {
|
||||
"id": "lingma",
|
||||
"name": "Lingma",
|
||||
"version": "1.0.0",
|
||||
"description": "Lingma IDE skills-based integration",
|
||||
"author": "spec-kit-core",
|
||||
"repository": "https://github.com/github/spec-kit",
|
||||
"tags": ["ide", "skills"]
|
||||
},
|
||||
"pi": {
|
||||
"id": "pi",
|
||||
"name": "Pi Coding Agent",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "specify-cli"
|
||||
version = "0.8.7.dev0"
|
||||
version = "0.8.7"
|
||||
description = "Specify CLI, part of GitHub Spec Kit. A tool to bootstrap your projects for Spec-Driven Development (SDD)."
|
||||
requires-python = ">=3.11"
|
||||
dependencies = [
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -90,6 +90,7 @@ def _build_agent_config() -> dict[str, dict[str, Any]]:
|
||||
return config
|
||||
|
||||
AGENT_CONFIG = _build_agent_config()
|
||||
DEFAULT_INIT_INTEGRATION = "copilot"
|
||||
|
||||
AI_ASSISTANT_ALIASES = {
|
||||
"kiro": "kiro-cli",
|
||||
@@ -152,6 +153,9 @@ def _build_ai_deprecation_warning(
|
||||
f"Use [bold]{replacement}[/bold] instead."
|
||||
)
|
||||
|
||||
def _stdin_is_interactive() -> bool:
|
||||
return sys.stdin.isatty()
|
||||
|
||||
SCRIPT_TYPE_CHOICES = {"sh": "POSIX Shell (bash/zsh)", "ps": "PowerShell"}
|
||||
|
||||
CLAUDE_LOCAL_PATH = Path.home() / ".claude" / "local" / "claude"
|
||||
@@ -995,7 +999,8 @@ def init(
|
||||
|
||||
This command will:
|
||||
1. Check that required tools are installed (git is optional)
|
||||
2. Let you choose your coding agent integration
|
||||
2. Let you choose your coding agent integration, or default to Copilot
|
||||
in non-interactive sessions
|
||||
3. Download template from GitHub (or use bundled assets with --offline)
|
||||
4. Initialize a fresh git repository (if not --no-git and no existing repo)
|
||||
5. Optionally set up coding agent integration commands
|
||||
@@ -1162,13 +1167,19 @@ def init(
|
||||
console.print(f"[red]Error:[/red] Invalid AI assistant '{ai_assistant}'. Choose from: {', '.join(AGENT_CONFIG.keys())}")
|
||||
raise typer.Exit(1)
|
||||
selected_ai = ai_assistant
|
||||
elif not _stdin_is_interactive():
|
||||
console.print(
|
||||
f"[dim]Non-interactive session detected: defaulting to '{DEFAULT_INIT_INTEGRATION}'. "
|
||||
"Use --integration to choose a different agent.[/dim]"
|
||||
)
|
||||
selected_ai = DEFAULT_INIT_INTEGRATION
|
||||
else:
|
||||
# Create options dict for selection (agent_key: display_name)
|
||||
ai_choices = {key: config["name"] for key, config in AGENT_CONFIG.items()}
|
||||
selected_ai = select_with_arrows(
|
||||
ai_choices,
|
||||
"Choose your coding agent integration:",
|
||||
"copilot"
|
||||
DEFAULT_INIT_INTEGRATION,
|
||||
)
|
||||
|
||||
# Auto-promote interactively selected agents to the integration path
|
||||
@@ -1233,7 +1244,7 @@ def init(
|
||||
else:
|
||||
default_script = "ps" if os.name == "nt" else "sh"
|
||||
|
||||
if sys.stdin.isatty():
|
||||
if _stdin_is_interactive():
|
||||
selected_script = select_with_arrows(SCRIPT_TYPE_CHOICES, "Choose script type (or press Enter)", default_script)
|
||||
else:
|
||||
selected_script = default_script
|
||||
|
||||
@@ -7,12 +7,12 @@ command files into agent-specific directories in the correct format.
|
||||
"""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Any, Optional
|
||||
|
||||
import platform
|
||||
import re
|
||||
from copy import deepcopy
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import yaml
|
||||
|
||||
|
||||
@@ -25,7 +25,16 @@ def _build_agent_configs() -> dict[str, Any]:
|
||||
if key == "generic":
|
||||
continue
|
||||
if integration.registrar_config:
|
||||
configs[key] = dict(integration.registrar_config)
|
||||
config = dict(integration.registrar_config)
|
||||
# Propagate invoke_separator from the integration class when the
|
||||
# registrar_config dict doesn't already declare it explicitly.
|
||||
# SkillsIntegration subclasses (claude, codex, …) set
|
||||
# invoke_separator="-" as a class attribute but omit it from
|
||||
# registrar_config, so without this they would fall back to "."
|
||||
# when register_commands() resolves __SPECKIT_COMMAND_*__ tokens.
|
||||
if "invoke_separator" not in config:
|
||||
config["invoke_separator"] = integration.invoke_separator
|
||||
configs[key] = config
|
||||
return configs
|
||||
|
||||
|
||||
@@ -419,9 +428,7 @@ class CommandRegistrar:
|
||||
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}"
|
||||
)
|
||||
raise ValueError(f"Output path {candidate!r} escapes directory {base!r}")
|
||||
|
||||
def register_commands(
|
||||
self,
|
||||
@@ -471,7 +478,10 @@ class CommandRegistrar:
|
||||
|
||||
if frontmatter.get("strategy") == "wrap":
|
||||
from .presets import _substitute_core_template
|
||||
body, core_frontmatter = _substitute_core_template(body, cmd_name, project_root, self)
|
||||
|
||||
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:
|
||||
@@ -492,6 +502,16 @@ class CommandRegistrar:
|
||||
body, "$ARGUMENTS", agent_config["args"]
|
||||
)
|
||||
|
||||
# Resolve __SPECKIT_COMMAND_*__ tokens using the agent's invoke separator.
|
||||
# The separator is sourced from agent_config (populated by _build_agent_configs,
|
||||
# which propagates each integration's invoke_separator class attribute).
|
||||
# Deferred import of IntegrationBase avoids a circular import at module load
|
||||
# (base.py itself imports CommandRegistrar lazily).
|
||||
from specify_cli.integrations.base import IntegrationBase # noqa: PLC0415
|
||||
|
||||
_sep = agent_config.get("invoke_separator", ".")
|
||||
body = IntegrationBase.resolve_command_refs(body, _sep)
|
||||
|
||||
output_name = self._compute_output_name(agent_name, cmd_name, agent_config)
|
||||
|
||||
if agent_config["extension"] == "/SKILL.md":
|
||||
@@ -505,12 +525,22 @@ 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)
|
||||
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
|
||||
)
|
||||
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"])
|
||||
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(
|
||||
@@ -685,8 +715,11 @@ class CommandRegistrar:
|
||||
if agent_dir.exists():
|
||||
try:
|
||||
registered = self.register_commands(
|
||||
agent_name, commands, source_id,
|
||||
source_dir, project_root,
|
||||
agent_name,
|
||||
commands,
|
||||
source_id,
|
||||
source_dir,
|
||||
project_root,
|
||||
context_note=context_note,
|
||||
)
|
||||
if registered:
|
||||
|
||||
@@ -66,6 +66,7 @@ def _register_builtins() -> None:
|
||||
from .kilocode import KilocodeIntegration
|
||||
from .kimi import KimiIntegration
|
||||
from .kiro_cli import KiroCliIntegration
|
||||
from .lingma import LingmaIntegration
|
||||
from .opencode import OpencodeIntegration
|
||||
from .pi import PiIntegration
|
||||
from .qodercli import QodercliIntegration
|
||||
@@ -97,6 +98,7 @@ def _register_builtins() -> None:
|
||||
_register(KilocodeIntegration())
|
||||
_register(KimiIntegration())
|
||||
_register(KiroCliIntegration())
|
||||
_register(LingmaIntegration())
|
||||
_register(OpencodeIntegration())
|
||||
_register(PiIntegration())
|
||||
_register(QodercliIntegration())
|
||||
|
||||
@@ -20,6 +20,8 @@ from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import yaml
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .manifest import IntegrationManifest
|
||||
|
||||
@@ -606,6 +608,7 @@ class IntegrationBase(ABC):
|
||||
# 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
|
||||
@@ -953,7 +956,6 @@ class TomlIntegration(IntegrationBase):
|
||||
and ``>``) keep their YAML semantics instead of being treated as
|
||||
raw text.
|
||||
"""
|
||||
import yaml
|
||||
|
||||
frontmatter_text, _ = TomlIntegration._split_frontmatter(content)
|
||||
if not frontmatter_text:
|
||||
@@ -1140,7 +1142,6 @@ class YamlIntegration(IntegrationBase):
|
||||
@staticmethod
|
||||
def _extract_frontmatter(content: str) -> dict[str, Any]:
|
||||
"""Extract frontmatter as a dict from YAML frontmatter block."""
|
||||
import yaml
|
||||
|
||||
if not content.startswith("---"):
|
||||
return {}
|
||||
@@ -1201,24 +1202,38 @@ class YamlIntegration(IntegrationBase):
|
||||
text = text[len("speckit.") :]
|
||||
return text.replace(".", " ").replace("-", " ").replace("_", " ").title()
|
||||
|
||||
@staticmethod
|
||||
def _render_yaml(title: str, description: str, body: str, source_id: str) -> str:
|
||||
|
||||
@classmethod
|
||||
def _build_yaml_header(cls, title: str, description: str) -> dict[str, Any]:
|
||||
"""Build the base YAML header."""
|
||||
header = {
|
||||
"version": "1.0.0",
|
||||
"title": title,
|
||||
"description": description,
|
||||
"author": {"contact": "spec-kit"},
|
||||
"parameters": [
|
||||
{
|
||||
"key": "args",
|
||||
"input_type": "string",
|
||||
"requirement": "optional",
|
||||
"default": "",
|
||||
"description": "User input passed to the command.",
|
||||
}
|
||||
],
|
||||
"extensions": [{"type": "builtin", "name": "developer"}],
|
||||
"activities": ["Spec-Driven Development"],
|
||||
}
|
||||
return header
|
||||
|
||||
@classmethod
|
||||
def _render_yaml(cls, title: str, description: str, body: str, source_id: str) -> str:
|
||||
"""Render a YAML recipe file from title, description, and body.
|
||||
|
||||
Produces a Goose-compatible recipe with a literal block scalar
|
||||
for the prompt content. Uses ``yaml.safe_dump()`` for the
|
||||
header fields to ensure proper escaping.
|
||||
"""
|
||||
import yaml
|
||||
|
||||
header = {
|
||||
"version": "1.0.0",
|
||||
"title": title,
|
||||
"description": description,
|
||||
"author": {"contact": "spec-kit"},
|
||||
"extensions": [{"type": "builtin", "name": "developer"}],
|
||||
"activities": ["Spec-Driven Development"],
|
||||
}
|
||||
header = cls._build_yaml_header(title, description)
|
||||
|
||||
header_yaml = yaml.safe_dump(
|
||||
header,
|
||||
@@ -1227,12 +1242,20 @@ class YamlIntegration(IntegrationBase):
|
||||
default_flow_style=False,
|
||||
).strip()
|
||||
|
||||
# Indent each line for YAML block scalar
|
||||
# Indent the body for YAML block scalar
|
||||
indented = "\n".join(f" {line}" for line in body.split("\n"))
|
||||
|
||||
lines = [header_yaml, "prompt: |", indented, "", f"# Source: {source_id}"]
|
||||
lines = [
|
||||
header_yaml,
|
||||
"prompt: |",
|
||||
indented,
|
||||
"",
|
||||
f"# Source: {source_id}",
|
||||
]
|
||||
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def setup(
|
||||
self,
|
||||
project_root: Path,
|
||||
@@ -1391,7 +1414,6 @@ class SkillsIntegration(IntegrationBase):
|
||||
template. Each SKILL.md has normalised frontmatter containing
|
||||
``name``, ``description``, ``compatibility``, and ``metadata``.
|
||||
"""
|
||||
import yaml
|
||||
|
||||
templates = self.list_command_templates()
|
||||
if not templates:
|
||||
|
||||
@@ -87,8 +87,10 @@ class ForgeIntegration(MarkdownIntegration):
|
||||
"strip_frontmatter_keys": ["handoffs"],
|
||||
"inject_name": True,
|
||||
"format_name": format_forge_command_name, # Custom name formatter
|
||||
"invoke_separator": "-",
|
||||
}
|
||||
context_file = "AGENTS.md"
|
||||
invoke_separator = "-"
|
||||
|
||||
def setup(
|
||||
self,
|
||||
@@ -133,6 +135,7 @@ class ForgeIntegration(MarkdownIntegration):
|
||||
processed = self.process_template(
|
||||
raw, self.key, script_type, arg_placeholder,
|
||||
context_file=self.context_file or "",
|
||||
invoke_separator=self.invoke_separator,
|
||||
)
|
||||
|
||||
# FORGE-SPECIFIC: Ensure any remaining $ARGUMENTS placeholders are
|
||||
|
||||
41
src/specify_cli/integrations/lingma/__init__.py
Normal file
41
src/specify_cli/integrations/lingma/__init__.py
Normal file
@@ -0,0 +1,41 @@
|
||||
"""Lingma IDE integration. — skills-based agent.
|
||||
|
||||
Lingma IDE uses ``.lingma/skills/speckit-<name>/SKILL.md`` layout.
|
||||
In Specify CLI, the Lingma integration is skills-only, and ``--skills``
|
||||
defaults to ``True``.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from ..base import IntegrationOption, SkillsIntegration
|
||||
|
||||
|
||||
class LingmaIntegration(SkillsIntegration):
|
||||
"""Integration for Lingma IDE."""
|
||||
|
||||
key = "lingma"
|
||||
config = {
|
||||
"name": "Lingma",
|
||||
"folder": ".lingma/",
|
||||
"commands_subdir": "skills",
|
||||
"install_url": None,
|
||||
"requires_cli": False,
|
||||
}
|
||||
registrar_config = {
|
||||
"dir": ".lingma/skills",
|
||||
"format": "markdown",
|
||||
"args": "$ARGUMENTS",
|
||||
"extension": "/SKILL.md",
|
||||
}
|
||||
context_file = ".lingma/rules/specify-rules.md"
|
||||
|
||||
@classmethod
|
||||
def options(cls) -> list[IntegrationOption]:
|
||||
return [
|
||||
IntegrationOption(
|
||||
"--skills",
|
||||
is_flag=True,
|
||||
default=True,
|
||||
help="Install as agent skills",
|
||||
),
|
||||
]
|
||||
@@ -81,6 +81,29 @@ class TestInitIntegrationFlag:
|
||||
shared_manifest = project / ".specify" / "integrations" / "speckit.manifest.json"
|
||||
assert shared_manifest.exists()
|
||||
|
||||
def test_noninteractive_init_defaults_to_copilot(self, tmp_path, monkeypatch):
|
||||
from typer.testing import CliRunner
|
||||
from specify_cli import app
|
||||
import specify_cli
|
||||
|
||||
def fail_select(*_args, **_kwargs):
|
||||
raise AssertionError("non-interactive init should not open the integration picker")
|
||||
|
||||
monkeypatch.setattr(specify_cli, "select_with_arrows", fail_select)
|
||||
|
||||
runner = CliRunner()
|
||||
project = tmp_path / "noninteractive"
|
||||
result = runner.invoke(app, [
|
||||
"init", str(project), "--script", "sh", "--no-git", "--ignore-agent-tools",
|
||||
], catch_exceptions=False)
|
||||
|
||||
assert result.exit_code == 0, result.output
|
||||
assert f"defaulting to '{specify_cli.DEFAULT_INIT_INTEGRATION}'" in result.output
|
||||
assert (project / ".github" / "agents" / "speckit.plan.agent.md").exists()
|
||||
|
||||
data = json.loads((project / ".specify" / "integration.json").read_text(encoding="utf-8"))
|
||||
assert data["integration"] == specify_cli.DEFAULT_INIT_INTEGRATION
|
||||
|
||||
def test_ai_copilot_auto_promotes(self, tmp_path):
|
||||
from typer.testing import CliRunner
|
||||
from specify_cli import app
|
||||
|
||||
@@ -196,7 +196,10 @@ class TestClaudeIntegration:
|
||||
try:
|
||||
os.chdir(project)
|
||||
runner = CliRunner()
|
||||
with patch("specify_cli.select_with_arrows", return_value="claude"):
|
||||
with (
|
||||
patch("specify_cli._stdin_is_interactive", return_value=True),
|
||||
patch("specify_cli.select_with_arrows", return_value="claude"),
|
||||
):
|
||||
result = runner.invoke(
|
||||
app,
|
||||
[
|
||||
|
||||
@@ -141,6 +141,7 @@ class TestForgeIntegration:
|
||||
assert actual_commands == expected_commands
|
||||
|
||||
def test_templates_are_processed(self, tmp_path):
|
||||
import re
|
||||
from specify_cli.integrations.forge import ForgeIntegration
|
||||
forge = ForgeIntegration()
|
||||
m = IntegrationManifest("forge", tmp_path)
|
||||
@@ -157,6 +158,11 @@ class TestForgeIntegration:
|
||||
assert "$ARGUMENTS" not in content, f"{cmd_file.name} has unprocessed $ARGUMENTS"
|
||||
# Frontmatter sections should be stripped
|
||||
assert "\nscripts:\n" not in content
|
||||
# Check Forge-specific: command references use hyphen notation, not dot notation
|
||||
assert not re.search(r"/speckit\.[a-z]", content), (
|
||||
f"{cmd_file.name} contains dot-notation command reference (/speckit.<cmd>); "
|
||||
"Forge requires hyphen notation (/speckit-<cmd>) for ZSH compatibility"
|
||||
)
|
||||
|
||||
def test_plan_references_correct_context_file(self, tmp_path):
|
||||
"""The generated plan command must reference forge's context file."""
|
||||
@@ -224,6 +230,33 @@ class TestForgeIntegration:
|
||||
"checklist should contain {{parameters}} in User Input section"
|
||||
)
|
||||
|
||||
def test_command_refs_use_hyphen_notation(self, tmp_path):
|
||||
"""Verify all generated Forge command files use /speckit-foo, not /speckit.foo."""
|
||||
import re
|
||||
from specify_cli.integrations.forge import ForgeIntegration
|
||||
forge = ForgeIntegration()
|
||||
m = IntegrationManifest("forge", tmp_path)
|
||||
forge.setup(tmp_path, m)
|
||||
commands_dir = tmp_path / ".forge" / "commands"
|
||||
|
||||
files_with_refs = []
|
||||
files_with_dot_refs = []
|
||||
for cmd_file in commands_dir.glob("speckit.*.md"):
|
||||
content = cmd_file.read_text(encoding="utf-8")
|
||||
if re.search(r"/speckit-[a-z]", content):
|
||||
files_with_refs.append(cmd_file.name)
|
||||
if re.search(r"/speckit\.[a-z]", content):
|
||||
files_with_dot_refs.append(cmd_file.name)
|
||||
|
||||
assert files_with_dot_refs == [], (
|
||||
f"Files contain dot-notation command references: {files_with_dot_refs}. "
|
||||
"Forge requires hyphen notation (/speckit-<cmd>) for ZSH compatibility."
|
||||
)
|
||||
assert len(files_with_refs) > 0, (
|
||||
"Expected at least one generated Forge command to contain /speckit-<cmd> reference, "
|
||||
"but none were found. Check that __SPECKIT_COMMAND_*__ tokens are being resolved."
|
||||
)
|
||||
|
||||
def test_name_field_uses_hyphenated_format(self, tmp_path):
|
||||
"""Verify that injected name fields use hyphenated format (speckit-plan, not speckit.plan)."""
|
||||
from specify_cli.integrations.forge import ForgeIntegration
|
||||
@@ -401,3 +434,48 @@ class TestForgeCommandRegistrar:
|
||||
assert "name:" not in content, (
|
||||
"Windsurf should not inject name field - format_name callback should be Forge-only"
|
||||
)
|
||||
|
||||
def test_git_extension_command_uses_hyphen_notation(self, tmp_path):
|
||||
"""Verify the git extension's feature command uses /speckit-specify (not /speckit.specify) for Forge."""
|
||||
from pathlib import Path
|
||||
from specify_cli.agents import CommandRegistrar
|
||||
|
||||
# Locate the real git extension command source file
|
||||
repo_root = Path(__file__).resolve().parent.parent.parent
|
||||
ext_dir = repo_root / "extensions" / "git"
|
||||
cmd_source = ext_dir / "commands" / "speckit.git.feature.md"
|
||||
assert cmd_source.exists(), (
|
||||
f"Git extension command source not found at {cmd_source}. "
|
||||
"Ensure extensions/git/commands/speckit.git.feature.md exists."
|
||||
)
|
||||
|
||||
registrar = CommandRegistrar()
|
||||
commands = [
|
||||
{
|
||||
"name": "speckit.git.feature",
|
||||
"file": "commands/speckit.git.feature.md",
|
||||
}
|
||||
]
|
||||
|
||||
registered = registrar.register_commands(
|
||||
"forge",
|
||||
commands,
|
||||
"git",
|
||||
ext_dir,
|
||||
tmp_path,
|
||||
)
|
||||
|
||||
assert "speckit.git.feature" in registered
|
||||
|
||||
forge_cmd = tmp_path / ".forge" / "commands" / "speckit.git.feature.md"
|
||||
assert forge_cmd.exists(), "Expected Forge command file was not created"
|
||||
|
||||
content = forge_cmd.read_text(encoding="utf-8")
|
||||
assert "/speckit-specify" in content, (
|
||||
"Expected '/speckit-specify' (hyphen) in generated Forge git.feature command body, "
|
||||
"but it was not found. Check that __SPECKIT_COMMAND_SPECIFY__ is resolved correctly."
|
||||
)
|
||||
assert "/speckit.specify" not in content, (
|
||||
"Found '/speckit.specify' (dot notation) in generated Forge git.feature command body. "
|
||||
"Forge requires hyphen notation for ZSH compatibility."
|
||||
)
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
"""Tests for GooseIntegration."""
|
||||
|
||||
import yaml
|
||||
from specify_cli.integrations import get_integration
|
||||
from specify_cli.integrations.manifest import IntegrationManifest
|
||||
|
||||
from .test_integration_base_yaml import YamlIntegrationTests
|
||||
|
||||
|
||||
@@ -9,3 +13,27 @@ class TestGooseIntegration(YamlIntegrationTests):
|
||||
COMMANDS_SUBDIR = "recipes"
|
||||
REGISTRAR_DIR = ".goose/recipes"
|
||||
CONTEXT_FILE = "AGENTS.md"
|
||||
|
||||
def test_setup_declares_args_parameter_for_args_prompt(self, tmp_path):
|
||||
# “If a generated Goose recipe uses {{args}} in its prompt, it
|
||||
# must declare a corresponding args parameter.”
|
||||
|
||||
integration = get_integration("goose")
|
||||
assert integration is not None
|
||||
|
||||
manifest = IntegrationManifest("goose", tmp_path)
|
||||
created = integration.setup(tmp_path, manifest, script_type="sh")
|
||||
|
||||
recipe_files = [path for path in created if path.suffix == ".yaml"]
|
||||
assert recipe_files
|
||||
|
||||
for recipe_file in recipe_files:
|
||||
data = yaml.safe_load(recipe_file.read_text(encoding="utf-8"))
|
||||
|
||||
if "{{args}}" not in data["prompt"]:
|
||||
continue
|
||||
|
||||
assert any(
|
||||
param.get("key") == "args"
|
||||
for param in data.get("parameters", [])
|
||||
), f"{recipe_file} uses {{{{args}}}} but does not declare args"
|
||||
|
||||
11
tests/integrations/test_integration_lingma.py
Normal file
11
tests/integrations/test_integration_lingma.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""Tests for LingmaIntegration."""
|
||||
|
||||
from .test_integration_base_skills import SkillsIntegrationTests
|
||||
|
||||
|
||||
class TestLingmaIntegration(SkillsIntegrationTests):
|
||||
KEY = "lingma"
|
||||
FOLDER = ".lingma/"
|
||||
COMMANDS_SUBDIR = "skills"
|
||||
REGISTRAR_DIR = ".lingma/skills"
|
||||
CONTEXT_FILE = ".lingma/rules/specify-rules.md"
|
||||
@@ -5,7 +5,6 @@ from pathlib import Path
|
||||
from specify_cli import AGENT_CONFIG, AI_ASSISTANT_ALIASES, AI_ASSISTANT_HELP
|
||||
from specify_cli.extensions import CommandRegistrar
|
||||
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
@@ -199,3 +198,88 @@ class TestAgentConfigConsistency:
|
||||
def test_ai_help_includes_goose(self):
|
||||
"""CLI help text for --ai should include goose."""
|
||||
assert "goose" in AI_ASSISTANT_HELP
|
||||
|
||||
# --- invoke_separator propagation checks ---
|
||||
|
||||
def test_skills_agents_have_hyphen_invoke_separator_in_agent_configs(self):
|
||||
"""Skills-based agents must expose invoke_separator='-' in AGENT_CONFIGS.
|
||||
|
||||
SkillsIntegration sets ``invoke_separator = "-"`` as a class attribute,
|
||||
but individual skills integrations (claude, codex, …) do not repeat it in
|
||||
their ``registrar_config`` dicts. ``_build_agent_configs()`` must
|
||||
propagate the class attribute so that ``register_commands()`` resolves
|
||||
``__SPECKIT_COMMAND_*__`` tokens with the correct hyphen separator.
|
||||
"""
|
||||
cfg = CommandRegistrar.AGENT_CONFIGS
|
||||
skills_agents = [
|
||||
key for key, c in cfg.items() if c.get("extension") == "/SKILL.md"
|
||||
]
|
||||
assert skills_agents, (
|
||||
"Expected at least one skills-based agent in AGENT_CONFIGS"
|
||||
)
|
||||
for agent in skills_agents:
|
||||
assert cfg[agent].get("invoke_separator") == "-", (
|
||||
f"Skills agent '{agent}' has invoke_separator="
|
||||
f"{cfg[agent].get('invoke_separator')!r} in AGENT_CONFIGS; "
|
||||
"expected '-' (propagated from SkillsIntegration.invoke_separator)"
|
||||
)
|
||||
|
||||
def test_skills_agent_command_token_resolves_with_hyphen(self, tmp_path):
|
||||
"""__SPECKIT_COMMAND_*__ tokens in extension commands resolve to /speckit-<cmd>
|
||||
when registered for a skills-based agent (e.g. claude).
|
||||
|
||||
Regression guard: before the fix, _build_agent_configs() did not
|
||||
propagate invoke_separator from the integration class, so
|
||||
register_commands() fell back to '.' and emitted /speckit.specify instead
|
||||
of /speckit-specify for skills agents.
|
||||
"""
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
from specify_cli.agents import CommandRegistrar
|
||||
|
||||
repo_root = Path(__file__).resolve().parent.parent
|
||||
ext_dir = repo_root / "extensions" / "git"
|
||||
cmd_source = ext_dir / "commands" / "speckit.git.feature.md"
|
||||
assert cmd_source.exists(), (
|
||||
f"Git extension command source not found at {cmd_source}"
|
||||
)
|
||||
assert "__SPECKIT_COMMAND_SPECIFY__" in cmd_source.read_text(
|
||||
encoding="utf-8"
|
||||
), (
|
||||
"Expected __SPECKIT_COMMAND_SPECIFY__ token in speckit.git.feature.md; "
|
||||
"check that the file uses the token rather than a hard-coded ref."
|
||||
)
|
||||
|
||||
registrar = CommandRegistrar()
|
||||
commands = [
|
||||
{"name": "speckit.git.feature", "file": "commands/speckit.git.feature.md"}
|
||||
]
|
||||
|
||||
registered = registrar.register_commands(
|
||||
"claude",
|
||||
commands,
|
||||
"git",
|
||||
ext_dir,
|
||||
tmp_path,
|
||||
)
|
||||
|
||||
assert "speckit.git.feature" in registered
|
||||
skill_file = (
|
||||
tmp_path / ".claude" / "skills" / "speckit-git-feature" / "SKILL.md"
|
||||
)
|
||||
assert skill_file.exists(), (
|
||||
f"Expected Claude skill file not found at {skill_file}"
|
||||
)
|
||||
content = skill_file.read_text(encoding="utf-8")
|
||||
assert "/speckit-specify" in content, (
|
||||
"Expected '/speckit-specify' (hyphen) in generated Claude skill for git.feature; "
|
||||
"__SPECKIT_COMMAND_SPECIFY__ was not resolved with the correct separator."
|
||||
)
|
||||
# Negative lookbehind (?<![a-zA-Z0-9_]) excludes file-path occurrences
|
||||
# such as 'source: git:commands/speckit.git.feature.md' in frontmatter,
|
||||
# where the '/' is a path separator preceded by a word character.
|
||||
assert not re.search(r"(?<![a-zA-Z0-9_])/speckit\.[a-z]", content), (
|
||||
"Found dot-notation command ref (/speckit.<cmd>) in generated Claude skill. "
|
||||
"Skills agents must use hyphen notation."
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user