mirror of
https://github.com/github/spec-kit.git
synced 2026-07-08 23:59:55 +08:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4db16dc21 |
16
CHANGELOG.md
16
CHANGELOG.md
@@ -2,22 +2,6 @@
|
|||||||
|
|
||||||
<!-- insert new changelog below this comment -->
|
<!-- insert new changelog below this comment -->
|
||||||
|
|
||||||
## [0.6.1] - 2026-04-10
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- feat: add bundled lean preset with minimal workflow commands (#2161)
|
|
||||||
- Add Brownfield Bootstrap extension to community catalog (#2145)
|
|
||||||
- Add CI Guard extension to community catalog (#2157)
|
|
||||||
- Add SpecTest extension to community catalog (#2159)
|
|
||||||
- fix: bundled extensions should not have download URLs (#2155)
|
|
||||||
- Add PR Bridge extension to community catalog (#2148)
|
|
||||||
- feat(cursor-agent): migrate from .cursor/commands to .cursor/skills (#2156)
|
|
||||||
- Add TinySpec extension to community catalog (#2147)
|
|
||||||
- chore: bump spec-kit-verify to 1.0.3 and spec-kit-review to 1.0.1 (#2146)
|
|
||||||
- Add Status Report extension to community catalog (#2123)
|
|
||||||
- chore: release 0.6.0, begin 0.6.1.dev0 development (#2144)
|
|
||||||
|
|
||||||
## [0.6.0] - 2026-04-09
|
## [0.6.0] - 2026-04-09
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|||||||
@@ -186,10 +186,8 @@ The following community-contributed extensions are available in [`catalog.commun
|
|||||||
| Archive Extension | Archive merged features into main project memory. | `docs` | Read+Write | [spec-kit-archive](https://github.com/stn1slv/spec-kit-archive) |
|
| 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) |
|
| 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) |
|
||||||
| 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) |
|
| 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) |
|
| 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) |
|
||||||
| Canon | Adds canon-driven (baseline-driven) workflows: spec-first, code-first, spec-drift. Requires Canon Core preset installation. | `process` | Read+Write | [spec-kit-canon](https://github.com/maximiliamus/spec-kit-canon/tree/master/extension) |
|
| Canon | Adds canon-driven (baseline-driven) workflows: spec-first, code-first, spec-drift. Requires Canon Core preset installation. | `process` | Read+Write | [spec-kit-canon](https://github.com/maximiliamus/spec-kit-canon/tree/master/extension) |
|
||||||
| CI Guard | Spec compliance gates for CI/CD — verify specs exist, check drift, and block merges on gaps | `process` | Read-only | [spec-kit-ci-guard](https://github.com/Quratulain-bilal/spec-kit-ci-guard) |
|
|
||||||
| Checkpoint Extension | Commit the changes made during the middle of the implementation, so you don't end up with just one very large commit at the end | `code` | Read+Write | [spec-kit-checkpoint](https://github.com/aaronrsun/spec-kit-checkpoint) |
|
| Checkpoint Extension | Commit the changes made during the middle of the implementation, so you don't end up with just one very large commit at the end | `code` | Read+Write | [spec-kit-checkpoint](https://github.com/aaronrsun/spec-kit-checkpoint) |
|
||||||
| 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) |
|
| 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) |
|
| 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) |
|
||||||
@@ -213,7 +211,6 @@ The following community-contributed extensions are available in [`catalog.commun
|
|||||||
| 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) |
|
| 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) |
|
| 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) |
|
||||||
| 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) |
|
| 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) |
|
| 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: research → product spec → SpecKit → implement → verify → test | `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 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) |
|
||||||
@@ -232,11 +229,8 @@ The following community-contributed extensions are available in [`catalog.commun
|
|||||||
| 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 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 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 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 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 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) |
|
||||||
| 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) |
|
|
||||||
| 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) |
|
| 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 | 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) |
|
||||||
| 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) |
|
|
||||||
| 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) |
|
| 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 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) |
|
| 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) |
|
||||||
|
|||||||
@@ -292,7 +292,7 @@ This tells Spec Kit which feature directory to use when creating specs, plans, a
|
|||||||
```bash
|
```bash
|
||||||
ls -la .claude/commands/ # Claude Code
|
ls -la .claude/commands/ # Claude Code
|
||||||
ls -la .gemini/commands/ # Gemini
|
ls -la .gemini/commands/ # Gemini
|
||||||
ls -la .cursor/skills/ # Cursor
|
ls -la .cursor/commands/ # Cursor
|
||||||
ls -la .pi/prompts/ # Pi Coding Agent
|
ls -la .pi/prompts/ # Pi Coding Agent
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"schema_version": "1.0",
|
"schema_version": "1.0",
|
||||||
"updated_at": "2026-04-10T17:00:00Z",
|
"updated_at": "2026-04-09T00:00:00Z",
|
||||||
"catalog_url": "https://raw.githubusercontent.com/github/spec-kit/main/extensions/catalog.community.json",
|
"catalog_url": "https://raw.githubusercontent.com/github/spec-kit/main/extensions/catalog.community.json",
|
||||||
"extensions": {
|
"extensions": {
|
||||||
"aide": {
|
"aide": {
|
||||||
@@ -138,38 +138,6 @@
|
|||||||
"created_at": "2026-04-08T00:00:00Z",
|
"created_at": "2026-04-08T00:00:00Z",
|
||||||
"updated_at": "2026-04-08T00:00:00Z"
|
"updated_at": "2026-04-08T00:00:00Z"
|
||||||
},
|
},
|
||||||
"brownfield": {
|
|
||||||
"name": "Brownfield Bootstrap",
|
|
||||||
"id": "brownfield",
|
|
||||||
"description": "Bootstrap spec-kit for existing codebases — auto-discover architecture and adopt SDD incrementally.",
|
|
||||||
"author": "Quratulain-bilal",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"download_url": "https://github.com/Quratulain-bilal/spec-kit-brownfield/archive/refs/tags/v1.0.0.zip",
|
|
||||||
"repository": "https://github.com/Quratulain-bilal/spec-kit-brownfield",
|
|
||||||
"homepage": "https://github.com/Quratulain-bilal/spec-kit-brownfield",
|
|
||||||
"documentation": "https://github.com/Quratulain-bilal/spec-kit-brownfield/blob/main/README.md",
|
|
||||||
"changelog": "https://github.com/Quratulain-bilal/spec-kit-brownfield/blob/main/CHANGELOG.md",
|
|
||||||
"license": "MIT",
|
|
||||||
"requires": {
|
|
||||||
"speckit_version": ">=0.4.0"
|
|
||||||
},
|
|
||||||
"provides": {
|
|
||||||
"commands": 4,
|
|
||||||
"hooks": 1
|
|
||||||
},
|
|
||||||
"tags": [
|
|
||||||
"brownfield",
|
|
||||||
"bootstrap",
|
|
||||||
"existing-project",
|
|
||||||
"migration",
|
|
||||||
"onboarding"
|
|
||||||
],
|
|
||||||
"verified": false,
|
|
||||||
"downloads": 0,
|
|
||||||
"stars": 0,
|
|
||||||
"created_at": "2026-04-10T00:00:00Z",
|
|
||||||
"updated_at": "2026-04-10T00:00:00Z"
|
|
||||||
},
|
|
||||||
"bugfix": {
|
"bugfix": {
|
||||||
"name": "Bugfix Workflow",
|
"name": "Bugfix Workflow",
|
||||||
"id": "bugfix",
|
"id": "bugfix",
|
||||||
@@ -237,39 +205,6 @@
|
|||||||
"created_at": "2026-03-29T00:00:00Z",
|
"created_at": "2026-03-29T00:00:00Z",
|
||||||
"updated_at": "2026-03-29T00:00:00Z"
|
"updated_at": "2026-03-29T00:00:00Z"
|
||||||
},
|
},
|
||||||
"ci-guard": {
|
|
||||||
"name": "CI Guard",
|
|
||||||
"id": "ci-guard",
|
|
||||||
"description": "Spec compliance gates for CI/CD — verify specs exist, check drift, and block merges on gaps.",
|
|
||||||
"author": "Quratulain-bilal",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"download_url": "https://github.com/Quratulain-bilal/spec-kit-ci-guard/archive/refs/tags/v1.0.0.zip",
|
|
||||||
"repository": "https://github.com/Quratulain-bilal/spec-kit-ci-guard",
|
|
||||||
"homepage": "https://github.com/Quratulain-bilal/spec-kit-ci-guard",
|
|
||||||
"documentation": "https://github.com/Quratulain-bilal/spec-kit-ci-guard/blob/main/README.md",
|
|
||||||
"changelog": "https://github.com/Quratulain-bilal/spec-kit-ci-guard/blob/main/CHANGELOG.md",
|
|
||||||
"license": "MIT",
|
|
||||||
"requires": {
|
|
||||||
"speckit_version": ">=0.4.0"
|
|
||||||
},
|
|
||||||
"provides": {
|
|
||||||
"commands": 5,
|
|
||||||
"hooks": 2
|
|
||||||
},
|
|
||||||
"tags": [
|
|
||||||
"ci-cd",
|
|
||||||
"compliance",
|
|
||||||
"governance",
|
|
||||||
"quality-gate",
|
|
||||||
"drift-detection",
|
|
||||||
"automation"
|
|
||||||
],
|
|
||||||
"verified": false,
|
|
||||||
"downloads": 0,
|
|
||||||
"stars": 0,
|
|
||||||
"created_at": "2026-04-10T17:00:00Z",
|
|
||||||
"updated_at": "2026-04-10T17:00:00Z"
|
|
||||||
},
|
|
||||||
"checkpoint": {
|
"checkpoint": {
|
||||||
"name": "Checkpoint Extension",
|
"name": "Checkpoint Extension",
|
||||||
"id": "checkpoint",
|
"id": "checkpoint",
|
||||||
@@ -1095,38 +1030,6 @@
|
|||||||
"created_at": "2026-03-27T08:22:30Z",
|
"created_at": "2026-03-27T08:22:30Z",
|
||||||
"updated_at": "2026-03-27T08:22:30Z"
|
"updated_at": "2026-03-27T08:22:30Z"
|
||||||
},
|
},
|
||||||
"pr-bridge": {
|
|
||||||
"name": "PR Bridge",
|
|
||||||
"id": "pr-bridge",
|
|
||||||
"description": "Auto-generate pull request descriptions, checklists, and summaries from spec artifacts.",
|
|
||||||
"author": "Quratulain-bilal",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"download_url": "https://github.com/Quratulain-bilal/spec-kit-pr-bridge-/archive/refs/tags/v1.0.0.zip",
|
|
||||||
"repository": "https://github.com/Quratulain-bilal/spec-kit-pr-bridge-",
|
|
||||||
"homepage": "https://github.com/Quratulain-bilal/spec-kit-pr-bridge-",
|
|
||||||
"documentation": "https://github.com/Quratulain-bilal/spec-kit-pr-bridge-/blob/main/README.md",
|
|
||||||
"changelog": "https://github.com/Quratulain-bilal/spec-kit-pr-bridge-/blob/main/CHANGELOG.md",
|
|
||||||
"license": "MIT",
|
|
||||||
"requires": {
|
|
||||||
"speckit_version": ">=0.4.0"
|
|
||||||
},
|
|
||||||
"provides": {
|
|
||||||
"commands": 3,
|
|
||||||
"hooks": 1
|
|
||||||
},
|
|
||||||
"tags": [
|
|
||||||
"pull-request",
|
|
||||||
"automation",
|
|
||||||
"traceability",
|
|
||||||
"workflow",
|
|
||||||
"review"
|
|
||||||
],
|
|
||||||
"verified": false,
|
|
||||||
"downloads": 0,
|
|
||||||
"stars": 0,
|
|
||||||
"created_at": "2026-04-10T00:00:00Z",
|
|
||||||
"updated_at": "2026-04-10T00:00:00Z"
|
|
||||||
},
|
|
||||||
"presetify": {
|
"presetify": {
|
||||||
"name": "Presetify",
|
"name": "Presetify",
|
||||||
"id": "presetify",
|
"id": "presetify",
|
||||||
@@ -1428,8 +1331,8 @@
|
|||||||
"id": "review",
|
"id": "review",
|
||||||
"description": "Post-implementation comprehensive code review with specialized agents for code quality, comments, tests, error handling, type design, and simplification.",
|
"description": "Post-implementation comprehensive code review with specialized agents for code quality, comments, tests, error handling, type design, and simplification.",
|
||||||
"author": "ismaelJimenez",
|
"author": "ismaelJimenez",
|
||||||
"version": "1.0.1",
|
"version": "1.0.0",
|
||||||
"download_url": "https://github.com/ismaelJimenez/spec-kit-review/archive/refs/tags/v1.0.1.zip",
|
"download_url": "https://github.com/ismaelJimenez/spec-kit-review/archive/refs/tags/v1.0.0.zip",
|
||||||
"repository": "https://github.com/ismaelJimenez/spec-kit-review",
|
"repository": "https://github.com/ismaelJimenez/spec-kit-review",
|
||||||
"homepage": "https://github.com/ismaelJimenez/spec-kit-review",
|
"homepage": "https://github.com/ismaelJimenez/spec-kit-review",
|
||||||
"documentation": "https://github.com/ismaelJimenez/spec-kit-review/blob/main/README.md",
|
"documentation": "https://github.com/ismaelJimenez/spec-kit-review/blob/main/README.md",
|
||||||
@@ -1455,7 +1358,7 @@
|
|||||||
"downloads": 0,
|
"downloads": 0,
|
||||||
"stars": 0,
|
"stars": 0,
|
||||||
"created_at": "2026-03-06T00:00:00Z",
|
"created_at": "2026-03-06T00:00:00Z",
|
||||||
"updated_at": "2026-04-09T00:00:00Z"
|
"updated_at": "2026-03-06T00:00:00Z"
|
||||||
},
|
},
|
||||||
"security-review": {
|
"security-review": {
|
||||||
"name": "Security Review",
|
"name": "Security Review",
|
||||||
@@ -1551,39 +1454,6 @@
|
|||||||
"created_at": "2026-03-18T00:00:00Z",
|
"created_at": "2026-03-18T00:00:00Z",
|
||||||
"updated_at": "2026-03-18T00:00:00Z"
|
"updated_at": "2026-03-18T00:00:00Z"
|
||||||
},
|
},
|
||||||
"spectest": {
|
|
||||||
"name": "SpecTest",
|
|
||||||
"id": "spectest",
|
|
||||||
"description": "Auto-generate test scaffolds from spec criteria, map coverage, and find untested requirements.",
|
|
||||||
"author": "Quratulain-bilal",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"download_url": "https://github.com/Quratulain-bilal/spec-kit-spectest/archive/refs/tags/v1.0.0.zip",
|
|
||||||
"repository": "https://github.com/Quratulain-bilal/spec-kit-spectest",
|
|
||||||
"homepage": "https://github.com/Quratulain-bilal/spec-kit-spectest",
|
|
||||||
"documentation": "https://github.com/Quratulain-bilal/spec-kit-spectest/blob/main/README.md",
|
|
||||||
"changelog": "https://github.com/Quratulain-bilal/spec-kit-spectest/blob/main/CHANGELOG.md",
|
|
||||||
"license": "MIT",
|
|
||||||
"requires": {
|
|
||||||
"speckit_version": ">=0.4.0"
|
|
||||||
},
|
|
||||||
"provides": {
|
|
||||||
"commands": 4,
|
|
||||||
"hooks": 1
|
|
||||||
},
|
|
||||||
"tags": [
|
|
||||||
"testing",
|
|
||||||
"test-generation",
|
|
||||||
"coverage",
|
|
||||||
"quality",
|
|
||||||
"automation",
|
|
||||||
"traceability"
|
|
||||||
],
|
|
||||||
"verified": false,
|
|
||||||
"downloads": 0,
|
|
||||||
"stars": 0,
|
|
||||||
"created_at": "2026-04-10T16:00:00Z",
|
|
||||||
"updated_at": "2026-04-10T16:00:00Z"
|
|
||||||
},
|
|
||||||
"staff-review": {
|
"staff-review": {
|
||||||
"name": "Staff Review Extension",
|
"name": "Staff Review Extension",
|
||||||
"id": "staff-review",
|
"id": "staff-review",
|
||||||
@@ -1646,36 +1516,6 @@
|
|||||||
"created_at": "2026-03-16T00:00:00Z",
|
"created_at": "2026-03-16T00:00:00Z",
|
||||||
"updated_at": "2026-03-16T00:00:00Z"
|
"updated_at": "2026-03-16T00:00:00Z"
|
||||||
},
|
},
|
||||||
"status-report": {
|
|
||||||
"name": "Status Report",
|
|
||||||
"id": "status-report",
|
|
||||||
"description": "Project status, feature progress, and next-action recommendations for spec-driven workflows.",
|
|
||||||
"author": "Open-Agent-Tools",
|
|
||||||
"version": "1.2.5",
|
|
||||||
"download_url": "https://github.com/Open-Agent-Tools/spec-kit-status/archive/refs/tags/v1.2.5.zip",
|
|
||||||
"repository": "https://github.com/Open-Agent-Tools/spec-kit-status",
|
|
||||||
"homepage": "https://github.com/Open-Agent-Tools/spec-kit-status",
|
|
||||||
"documentation": "https://github.com/Open-Agent-Tools/spec-kit-status/blob/main/README.md",
|
|
||||||
"changelog": "https://github.com/Open-Agent-Tools/spec-kit-status/blob/main/CHANGELOG.md",
|
|
||||||
"license": "MIT",
|
|
||||||
"requires": {
|
|
||||||
"speckit_version": ">=0.1.0"
|
|
||||||
},
|
|
||||||
"provides": {
|
|
||||||
"commands": 1,
|
|
||||||
"hooks": 0
|
|
||||||
},
|
|
||||||
"tags": [
|
|
||||||
"workflow",
|
|
||||||
"project-management",
|
|
||||||
"status"
|
|
||||||
],
|
|
||||||
"verified": false,
|
|
||||||
"downloads": 0,
|
|
||||||
"stars": 0,
|
|
||||||
"created_at": "2026-04-08T15:05:14Z",
|
|
||||||
"updated_at": "2026-04-08T15:05:14Z"
|
|
||||||
},
|
|
||||||
"superb": {
|
"superb": {
|
||||||
"name": "Superpowers Bridge",
|
"name": "Superpowers Bridge",
|
||||||
"id": "superb",
|
"id": "superb",
|
||||||
@@ -1751,38 +1591,6 @@
|
|||||||
"created_at": "2026-03-02T00:00:00Z",
|
"created_at": "2026-03-02T00:00:00Z",
|
||||||
"updated_at": "2026-03-02T00:00:00Z"
|
"updated_at": "2026-03-02T00:00:00Z"
|
||||||
},
|
},
|
||||||
"tinyspec": {
|
|
||||||
"name": "TinySpec",
|
|
||||||
"id": "tinyspec",
|
|
||||||
"description": "Lightweight single-file workflow for small tasks — skip the heavy multi-step SDD process.",
|
|
||||||
"author": "Quratulain-bilal",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"download_url": "https://github.com/Quratulain-bilal/spec-kit-tinyspec/archive/refs/tags/v1.0.0.zip",
|
|
||||||
"repository": "https://github.com/Quratulain-bilal/spec-kit-tinyspec",
|
|
||||||
"homepage": "https://github.com/Quratulain-bilal/spec-kit-tinyspec",
|
|
||||||
"documentation": "https://github.com/Quratulain-bilal/spec-kit-tinyspec/blob/main/README.md",
|
|
||||||
"changelog": "https://github.com/Quratulain-bilal/spec-kit-tinyspec/blob/main/CHANGELOG.md",
|
|
||||||
"license": "MIT",
|
|
||||||
"requires": {
|
|
||||||
"speckit_version": ">=0.4.0"
|
|
||||||
},
|
|
||||||
"provides": {
|
|
||||||
"commands": 3,
|
|
||||||
"hooks": 1
|
|
||||||
},
|
|
||||||
"tags": [
|
|
||||||
"lightweight",
|
|
||||||
"small-tasks",
|
|
||||||
"workflow",
|
|
||||||
"productivity",
|
|
||||||
"efficiency"
|
|
||||||
],
|
|
||||||
"verified": false,
|
|
||||||
"downloads": 0,
|
|
||||||
"stars": 0,
|
|
||||||
"created_at": "2026-04-10T00:00:00Z",
|
|
||||||
"updated_at": "2026-04-10T00:00:00Z"
|
|
||||||
},
|
|
||||||
"v-model": {
|
"v-model": {
|
||||||
"name": "V-Model Extension Pack",
|
"name": "V-Model Extension Pack",
|
||||||
"id": "v-model",
|
"id": "v-model",
|
||||||
@@ -1820,8 +1628,8 @@
|
|||||||
"id": "verify",
|
"id": "verify",
|
||||||
"description": "Post-implementation quality gate that validates implemented code against specification artifacts.",
|
"description": "Post-implementation quality gate that validates implemented code against specification artifacts.",
|
||||||
"author": "ismaelJimenez",
|
"author": "ismaelJimenez",
|
||||||
"version": "1.0.3",
|
"version": "1.0.0",
|
||||||
"download_url": "https://github.com/ismaelJimenez/spec-kit-verify/archive/refs/tags/v1.0.3.zip",
|
"download_url": "https://github.com/ismaelJimenez/spec-kit-verify/archive/refs/tags/v1.0.0.zip",
|
||||||
"repository": "https://github.com/ismaelJimenez/spec-kit-verify",
|
"repository": "https://github.com/ismaelJimenez/spec-kit-verify",
|
||||||
"homepage": "https://github.com/ismaelJimenez/spec-kit-verify",
|
"homepage": "https://github.com/ismaelJimenez/spec-kit-verify",
|
||||||
"documentation": "https://github.com/ismaelJimenez/spec-kit-verify/blob/main/README.md",
|
"documentation": "https://github.com/ismaelJimenez/spec-kit-verify/blob/main/README.md",
|
||||||
@@ -1845,7 +1653,7 @@
|
|||||||
"downloads": 0,
|
"downloads": 0,
|
||||||
"stars": 0,
|
"stars": 0,
|
||||||
"created_at": "2026-03-03T00:00:00Z",
|
"created_at": "2026-03-03T00:00:00Z",
|
||||||
"updated_at": "2026-04-09T00:00:00Z"
|
"updated_at": "2026-03-03T00:00:00Z"
|
||||||
},
|
},
|
||||||
"verify-tasks": {
|
"verify-tasks": {
|
||||||
"name": "Verify Tasks Extension",
|
"name": "Verify Tasks Extension",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"schema_version": "1.0",
|
"schema_version": "1.0",
|
||||||
"updated_at": "2026-04-10T00:00:00Z",
|
"updated_at": "2026-04-06T00:00:00Z",
|
||||||
"catalog_url": "https://raw.githubusercontent.com/github/spec-kit/main/extensions/catalog.json",
|
"catalog_url": "https://raw.githubusercontent.com/github/spec-kit/main/extensions/catalog.json",
|
||||||
"extensions": {
|
"extensions": {
|
||||||
"git": {
|
"git": {
|
||||||
@@ -10,13 +10,27 @@
|
|||||||
"description": "Feature branch creation, numbering (sequential/timestamp), validation, and Git remote detection",
|
"description": "Feature branch creation, numbering (sequential/timestamp), validation, and Git remote detection",
|
||||||
"author": "spec-kit-core",
|
"author": "spec-kit-core",
|
||||||
"repository": "https://github.com/github/spec-kit",
|
"repository": "https://github.com/github/spec-kit",
|
||||||
"bundled": true,
|
"download_url": "https://github.com/github/spec-kit/releases/download/ext-git-v1.0.0/git.zip",
|
||||||
"tags": [
|
"tags": [
|
||||||
"git",
|
"git",
|
||||||
"branching",
|
"branching",
|
||||||
"workflow",
|
"workflow",
|
||||||
"core"
|
"core"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"selftest": {
|
||||||
|
"name": "Spec Kit Self-Test Utility",
|
||||||
|
"id": "selftest",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Verifies catalog extensions by programmatically walking through the discovery, installation, and registration lifecycle.",
|
||||||
|
"author": "spec-kit-core",
|
||||||
|
"repository": "https://github.com/github/spec-kit",
|
||||||
|
"download_url": "https://github.com/github/spec-kit/releases/download/selftest-v1.0.0/selftest.zip",
|
||||||
|
"tags": [
|
||||||
|
"testing",
|
||||||
|
"core",
|
||||||
|
"utility"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,22 +1,6 @@
|
|||||||
{
|
{
|
||||||
"schema_version": "1.0",
|
"schema_version": "1.0",
|
||||||
"updated_at": "2026-04-10T00:00:00Z",
|
"updated_at": "2026-03-10T00:00:00Z",
|
||||||
"catalog_url": "https://raw.githubusercontent.com/github/spec-kit/main/presets/catalog.json",
|
"catalog_url": "https://raw.githubusercontent.com/github/spec-kit/main/presets/catalog.json",
|
||||||
"presets": {
|
"presets": {}
|
||||||
"lean": {
|
|
||||||
"name": "Lean Workflow",
|
|
||||||
"id": "lean",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "Minimal core workflow commands - just the prompt, just the artifact",
|
|
||||||
"author": "github",
|
|
||||||
"repository": "https://github.com/github/spec-kit",
|
|
||||||
"bundled": true,
|
|
||||||
"tags": [
|
|
||||||
"lean",
|
|
||||||
"minimal",
|
|
||||||
"workflow",
|
|
||||||
"core"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
---
|
|
||||||
description: Create or update the project constitution.
|
|
||||||
---
|
|
||||||
|
|
||||||
## User Input
|
|
||||||
|
|
||||||
```text
|
|
||||||
$ARGUMENTS
|
|
||||||
```
|
|
||||||
|
|
||||||
## Outline
|
|
||||||
|
|
||||||
1. Create or update the project constitution and store it in `.specify/memory/constitution.md`.
|
|
||||||
- Project name, guiding principles, non-negotiable rules
|
|
||||||
- Derive from user input and existing repo context (README, docs)
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
---
|
|
||||||
description: Execute the implementation plan by processing all tasks in tasks.md.
|
|
||||||
---
|
|
||||||
|
|
||||||
## User Input
|
|
||||||
|
|
||||||
```text
|
|
||||||
$ARGUMENTS
|
|
||||||
```
|
|
||||||
|
|
||||||
## Outline
|
|
||||||
|
|
||||||
1. Read `.specify/feature.json` to get the feature directory path.
|
|
||||||
|
|
||||||
2. **Load context**: `.specify/memory/constitution.md` and `<feature_directory>/spec.md` and `<feature_directory>/plan.md` and `<feature_directory>/tasks.md`.
|
|
||||||
|
|
||||||
3. **Execute tasks** in order:
|
|
||||||
- Complete each task before moving to the next
|
|
||||||
- Mark completed tasks by changing `- [ ]` to `- [x]` in `<feature_directory>/tasks.md`
|
|
||||||
- Halt on failure and report the issue
|
|
||||||
|
|
||||||
4. **Validate**: Verify all tasks are completed and the implementation matches the spec.
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
---
|
|
||||||
description: Create a plan and store it in plan.md.
|
|
||||||
---
|
|
||||||
|
|
||||||
## User Input
|
|
||||||
|
|
||||||
```text
|
|
||||||
$ARGUMENTS
|
|
||||||
```
|
|
||||||
|
|
||||||
## Outline
|
|
||||||
|
|
||||||
1. Read `.specify/feature.json` to get the feature directory path.
|
|
||||||
|
|
||||||
2. **Load context**: `.specify/memory/constitution.md` and `<feature_directory>/spec.md`.
|
|
||||||
|
|
||||||
3. Create an implementation plan and store it in `<feature_directory>/plan.md`.
|
|
||||||
- Technical context: tech stack, dependencies, project structure
|
|
||||||
- Design decisions, architecture, file structure
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
---
|
|
||||||
description: Create a specification and store it in spec.md.
|
|
||||||
---
|
|
||||||
|
|
||||||
## User Input
|
|
||||||
|
|
||||||
```text
|
|
||||||
$ARGUMENTS
|
|
||||||
```
|
|
||||||
|
|
||||||
## Outline
|
|
||||||
|
|
||||||
1. **Ask the user** for the feature directory path (e.g., `specs/my-feature`). Do not proceed until provided.
|
|
||||||
|
|
||||||
2. Create the directory and write `.specify/feature.json`:
|
|
||||||
```json
|
|
||||||
{ "feature_directory": "<feature_directory>" }
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Create a specification from the user input and store it in `<feature_directory>/spec.md`.
|
|
||||||
- Overview, functional requirements, user scenarios, success criteria
|
|
||||||
- Every requirement must be testable
|
|
||||||
- Make informed defaults for unspecified details
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
---
|
|
||||||
description: Create the tasks needed for implementation and store them in tasks.md.
|
|
||||||
---
|
|
||||||
|
|
||||||
## User Input
|
|
||||||
|
|
||||||
```text
|
|
||||||
$ARGUMENTS
|
|
||||||
```
|
|
||||||
|
|
||||||
## Outline
|
|
||||||
|
|
||||||
1. Read `.specify/feature.json` to get the feature directory path.
|
|
||||||
|
|
||||||
2. **Load context**: `.specify/memory/constitution.md` and `<feature_directory>/spec.md` and `<feature_directory>/plan.md`.
|
|
||||||
|
|
||||||
3. Create dependency-ordered implementation tasks and store them in `<feature_directory>/tasks.md`.
|
|
||||||
- Every task uses checklist format: `- [ ] [TaskID] Description with file path`
|
|
||||||
- Organized by phase: setup, foundational, user stories in priority order, polish
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
schema_version: "1.0"
|
|
||||||
|
|
||||||
preset:
|
|
||||||
id: "lean"
|
|
||||||
name: "Lean Workflow"
|
|
||||||
version: "1.0.0"
|
|
||||||
description: "Minimal core workflow commands - just the prompt, just the artifact"
|
|
||||||
author: "github"
|
|
||||||
repository: "https://github.com/github/spec-kit"
|
|
||||||
license: "MIT"
|
|
||||||
|
|
||||||
requires:
|
|
||||||
speckit_version: ">=0.6.0"
|
|
||||||
|
|
||||||
provides:
|
|
||||||
templates:
|
|
||||||
- type: "command"
|
|
||||||
name: "speckit.specify"
|
|
||||||
file: "commands/speckit.specify.md"
|
|
||||||
description: "Lean specify - create spec.md from a feature description"
|
|
||||||
replaces: "speckit.specify"
|
|
||||||
|
|
||||||
- type: "command"
|
|
||||||
name: "speckit.plan"
|
|
||||||
file: "commands/speckit.plan.md"
|
|
||||||
description: "Lean plan - create plan.md from the spec"
|
|
||||||
replaces: "speckit.plan"
|
|
||||||
|
|
||||||
- type: "command"
|
|
||||||
name: "speckit.tasks"
|
|
||||||
file: "commands/speckit.tasks.md"
|
|
||||||
description: "Lean tasks - create tasks.md from plan and spec"
|
|
||||||
replaces: "speckit.tasks"
|
|
||||||
|
|
||||||
- type: "command"
|
|
||||||
name: "speckit.implement"
|
|
||||||
file: "commands/speckit.implement.md"
|
|
||||||
description: "Lean implement - execute tasks from tasks.md"
|
|
||||||
replaces: "speckit.implement"
|
|
||||||
|
|
||||||
- type: "command"
|
|
||||||
name: "speckit.constitution"
|
|
||||||
file: "commands/speckit.constitution.md"
|
|
||||||
description: "Lean constitution - create or update project constitution"
|
|
||||||
replaces: "speckit.constitution"
|
|
||||||
|
|
||||||
tags:
|
|
||||||
- "lean"
|
|
||||||
- "minimal"
|
|
||||||
- "workflow"
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "specify-cli"
|
name = "specify-cli"
|
||||||
version = "0.6.1"
|
version = "0.6.0"
|
||||||
description = "Specify CLI, part of GitHub Spec Kit. A tool to bootstrap your projects for Spec-Driven Development (SDD)."
|
description = "Specify CLI, part of GitHub Spec Kit. A tool to bootstrap your projects for Spec-Driven Development (SDD)."
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
@@ -41,8 +41,6 @@ packages = ["src/specify_cli"]
|
|||||||
"scripts/powershell" = "specify_cli/core_pack/scripts/powershell"
|
"scripts/powershell" = "specify_cli/core_pack/scripts/powershell"
|
||||||
# Bundled extensions (installable via `specify extension add <name>`)
|
# Bundled extensions (installable via `specify extension add <name>`)
|
||||||
"extensions/git" = "specify_cli/core_pack/extensions/git"
|
"extensions/git" = "specify_cli/core_pack/extensions/git"
|
||||||
# Bundled presets (installable via `specify preset add <name>` or `specify init --preset <name>`)
|
|
||||||
"presets/lean" = "specify_cli/core_pack/presets/lean"
|
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
test = [
|
test = [
|
||||||
|
|||||||
@@ -621,31 +621,6 @@ def _locate_bundled_extension(extension_id: str) -> Path | None:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _locate_bundled_preset(preset_id: str) -> Path | None:
|
|
||||||
"""Return the path to a bundled preset, or None.
|
|
||||||
|
|
||||||
Checks the wheel's core_pack first, then falls back to the
|
|
||||||
source-checkout ``presets/<id>/`` directory.
|
|
||||||
"""
|
|
||||||
import re as _re
|
|
||||||
if not _re.match(r'^[a-z0-9-]+$', preset_id):
|
|
||||||
return None
|
|
||||||
|
|
||||||
core = _locate_core_pack()
|
|
||||||
if core is not None:
|
|
||||||
candidate = core / "presets" / preset_id
|
|
||||||
if (candidate / "preset.yml").is_file():
|
|
||||||
return candidate
|
|
||||||
|
|
||||||
# Source-checkout / editable install: look relative to repo root
|
|
||||||
repo_root = Path(__file__).parent.parent.parent
|
|
||||||
candidate = repo_root / "presets" / preset_id
|
|
||||||
if (candidate / "preset.yml").is_file():
|
|
||||||
return candidate
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def _install_shared_infra(
|
def _install_shared_infra(
|
||||||
project_path: Path,
|
project_path: Path,
|
||||||
script_type: str,
|
script_type: str,
|
||||||
@@ -1291,44 +1266,27 @@ def init(
|
|||||||
preset_manager = PresetManager(project_path)
|
preset_manager = PresetManager(project_path)
|
||||||
speckit_ver = get_speckit_version()
|
speckit_ver = get_speckit_version()
|
||||||
|
|
||||||
# Try local directory first, then bundled, then catalog
|
# Try local directory first, then catalog
|
||||||
local_path = Path(preset).resolve()
|
local_path = Path(preset).resolve()
|
||||||
if local_path.is_dir() and (local_path / "preset.yml").exists():
|
if local_path.is_dir() and (local_path / "preset.yml").exists():
|
||||||
preset_manager.install_from_directory(local_path, speckit_ver)
|
preset_manager.install_from_directory(local_path, speckit_ver)
|
||||||
else:
|
else:
|
||||||
bundled_path = _locate_bundled_preset(preset)
|
preset_catalog = PresetCatalog(project_path)
|
||||||
if bundled_path:
|
pack_info = preset_catalog.get_pack_info(preset)
|
||||||
preset_manager.install_from_directory(bundled_path, speckit_ver)
|
if not pack_info:
|
||||||
|
console.print(f"[yellow]Warning:[/yellow] Preset '{preset}' not found in catalog. Skipping.")
|
||||||
else:
|
else:
|
||||||
preset_catalog = PresetCatalog(project_path)
|
try:
|
||||||
pack_info = preset_catalog.get_pack_info(preset)
|
zip_path = preset_catalog.download_pack(preset)
|
||||||
if not pack_info:
|
preset_manager.install_from_zip(zip_path, speckit_ver)
|
||||||
console.print(f"[yellow]Warning:[/yellow] Preset '{preset}' not found in catalog. Skipping.")
|
# Clean up downloaded ZIP to avoid cache accumulation
|
||||||
elif pack_info.get("bundled") and not pack_info.get("download_url"):
|
|
||||||
from .extensions import REINSTALL_COMMAND
|
|
||||||
console.print(
|
|
||||||
f"[yellow]Warning:[/yellow] Preset '{preset}' is bundled with spec-kit "
|
|
||||||
f"but could not be found in the installed package."
|
|
||||||
)
|
|
||||||
console.print(
|
|
||||||
"This usually means the spec-kit installation is incomplete or corrupted."
|
|
||||||
)
|
|
||||||
console.print(f"Try reinstalling: {REINSTALL_COMMAND}")
|
|
||||||
else:
|
|
||||||
zip_path = None
|
|
||||||
try:
|
try:
|
||||||
zip_path = preset_catalog.download_pack(preset)
|
zip_path.unlink(missing_ok=True)
|
||||||
preset_manager.install_from_zip(zip_path, speckit_ver)
|
except OSError:
|
||||||
except PresetError as preset_err:
|
# Best-effort cleanup; failure to delete is non-fatal
|
||||||
console.print(f"[yellow]Warning:[/yellow] Failed to install preset '{preset}': {preset_err}")
|
pass
|
||||||
finally:
|
except PresetError as preset_err:
|
||||||
if zip_path is not None:
|
console.print(f"[yellow]Warning:[/yellow] Failed to install preset '{preset}': {preset_err}")
|
||||||
# Clean up downloaded ZIP to avoid cache accumulation
|
|
||||||
try:
|
|
||||||
zip_path.unlink(missing_ok=True)
|
|
||||||
except OSError:
|
|
||||||
# Best-effort cleanup; failure to delete is non-fatal
|
|
||||||
pass
|
|
||||||
except Exception as preset_err:
|
except Exception as preset_err:
|
||||||
console.print(f"[yellow]Warning:[/yellow] Failed to install preset: {preset_err}")
|
console.print(f"[yellow]Warning:[/yellow] Failed to install preset: {preset_err}")
|
||||||
|
|
||||||
@@ -1380,7 +1338,7 @@ def init(
|
|||||||
step_num = 2
|
step_num = 2
|
||||||
|
|
||||||
# Determine skill display mode for the next-steps panel.
|
# Determine skill display mode for the next-steps panel.
|
||||||
# Skills integrations (codex, kimi, agy, trae, cursor-agent) should show skill invocation syntax.
|
# Skills integrations (codex, kimi, agy, trae) should show skill invocation syntax.
|
||||||
from .integrations.base import SkillsIntegration as _SkillsInt
|
from .integrations.base import SkillsIntegration as _SkillsInt
|
||||||
_is_skills_integration = isinstance(resolved_integration, _SkillsInt)
|
_is_skills_integration = isinstance(resolved_integration, _SkillsInt)
|
||||||
|
|
||||||
@@ -1389,8 +1347,7 @@ def init(
|
|||||||
kimi_skill_mode = selected_ai == "kimi"
|
kimi_skill_mode = selected_ai == "kimi"
|
||||||
agy_skill_mode = selected_ai == "agy" and _is_skills_integration
|
agy_skill_mode = selected_ai == "agy" and _is_skills_integration
|
||||||
trae_skill_mode = selected_ai == "trae"
|
trae_skill_mode = selected_ai == "trae"
|
||||||
cursor_agent_skill_mode = selected_ai == "cursor-agent" and (ai_skills or _is_skills_integration)
|
native_skill_mode = codex_skill_mode or claude_skill_mode or kimi_skill_mode or agy_skill_mode or trae_skill_mode
|
||||||
native_skill_mode = codex_skill_mode or claude_skill_mode or kimi_skill_mode or agy_skill_mode or trae_skill_mode or cursor_agent_skill_mode
|
|
||||||
|
|
||||||
if codex_skill_mode and not ai_skills:
|
if codex_skill_mode and not ai_skills:
|
||||||
# Integration path installed skills; show the helpful notice
|
# Integration path installed skills; show the helpful notice
|
||||||
@@ -1399,9 +1356,6 @@ def init(
|
|||||||
if claude_skill_mode and not ai_skills:
|
if claude_skill_mode and not ai_skills:
|
||||||
steps_lines.append(f"{step_num}. Start Claude in this project directory; spec-kit skills were installed to [cyan].claude/skills[/cyan]")
|
steps_lines.append(f"{step_num}. Start Claude in this project directory; spec-kit skills were installed to [cyan].claude/skills[/cyan]")
|
||||||
step_num += 1
|
step_num += 1
|
||||||
if cursor_agent_skill_mode and not ai_skills:
|
|
||||||
steps_lines.append(f"{step_num}. Start Cursor Agent in this project directory; spec-kit skills were installed to [cyan].cursor/skills[/cyan]")
|
|
||||||
step_num += 1
|
|
||||||
usage_label = "skills" if native_skill_mode else "slash commands"
|
usage_label = "skills" if native_skill_mode else "slash commands"
|
||||||
|
|
||||||
def _display_cmd(name: str) -> str:
|
def _display_cmd(name: str) -> str:
|
||||||
@@ -1411,8 +1365,6 @@ def init(
|
|||||||
return f"/speckit-{name}"
|
return f"/speckit-{name}"
|
||||||
if kimi_skill_mode:
|
if kimi_skill_mode:
|
||||||
return f"/skill:speckit-{name}"
|
return f"/skill:speckit-{name}"
|
||||||
if cursor_agent_skill_mode:
|
|
||||||
return f"/speckit-{name}"
|
|
||||||
return f"/speckit.{name}"
|
return f"/speckit.{name}"
|
||||||
|
|
||||||
steps_lines.append(f"{step_num}. Start using {usage_label} with your AI agent:")
|
steps_lines.append(f"{step_num}. Start using {usage_label} with your AI agent:")
|
||||||
@@ -2182,50 +2134,28 @@ def preset_add(
|
|||||||
console.print(f"[green]✓[/green] Preset '{manifest.name}' v{manifest.version} installed (priority {priority})")
|
console.print(f"[green]✓[/green] Preset '{manifest.name}' v{manifest.version} installed (priority {priority})")
|
||||||
|
|
||||||
elif pack_id:
|
elif pack_id:
|
||||||
# Try bundled preset first, then catalog
|
catalog = PresetCatalog(project_root)
|
||||||
bundled_path = _locate_bundled_preset(pack_id)
|
pack_info = catalog.get_pack_info(pack_id)
|
||||||
if bundled_path:
|
|
||||||
console.print(f"Installing bundled preset [cyan]{pack_id}[/cyan]...")
|
if not pack_info:
|
||||||
manifest = manager.install_from_directory(bundled_path, speckit_version, priority)
|
console.print(f"[red]Error:[/red] Preset '{pack_id}' not found in catalog")
|
||||||
|
raise typer.Exit(1)
|
||||||
|
|
||||||
|
if not pack_info.get("_install_allowed", True):
|
||||||
|
catalog_name = pack_info.get("_catalog_name", "unknown")
|
||||||
|
console.print(f"[red]Error:[/red] Preset '{pack_id}' is from the '{catalog_name}' catalog which is discovery-only (install not allowed).")
|
||||||
|
console.print("Add the catalog with --install-allowed or install from the preset's repository directly with --from.")
|
||||||
|
raise typer.Exit(1)
|
||||||
|
|
||||||
|
console.print(f"Installing preset [cyan]{pack_info.get('name', pack_id)}[/cyan]...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
zip_path = catalog.download_pack(pack_id)
|
||||||
|
manifest = manager.install_from_zip(zip_path, speckit_version, priority)
|
||||||
console.print(f"[green]✓[/green] Preset '{manifest.name}' v{manifest.version} installed (priority {priority})")
|
console.print(f"[green]✓[/green] Preset '{manifest.name}' v{manifest.version} installed (priority {priority})")
|
||||||
else:
|
finally:
|
||||||
catalog = PresetCatalog(project_root)
|
if 'zip_path' in locals() and zip_path.exists():
|
||||||
pack_info = catalog.get_pack_info(pack_id)
|
zip_path.unlink(missing_ok=True)
|
||||||
|
|
||||||
if not pack_info:
|
|
||||||
console.print(f"[red]Error:[/red] Preset '{pack_id}' not found in catalog")
|
|
||||||
raise typer.Exit(1)
|
|
||||||
|
|
||||||
# Bundled presets should have been caught above; if we reach
|
|
||||||
# here the bundled files are missing from the installation.
|
|
||||||
if pack_info.get("bundled") and not pack_info.get("download_url"):
|
|
||||||
from .extensions import REINSTALL_COMMAND
|
|
||||||
console.print(
|
|
||||||
f"[red]Error:[/red] Preset '{pack_id}' is bundled with spec-kit "
|
|
||||||
f"but could not be found in the installed package."
|
|
||||||
)
|
|
||||||
console.print(
|
|
||||||
"\nThis usually means the spec-kit installation is incomplete or corrupted."
|
|
||||||
)
|
|
||||||
console.print("Try reinstalling spec-kit:")
|
|
||||||
console.print(f" {REINSTALL_COMMAND}")
|
|
||||||
raise typer.Exit(1)
|
|
||||||
|
|
||||||
if not pack_info.get("_install_allowed", True):
|
|
||||||
catalog_name = pack_info.get("_catalog_name", "unknown")
|
|
||||||
console.print(f"[red]Error:[/red] Preset '{pack_id}' is from the '{catalog_name}' catalog which is discovery-only (install not allowed).")
|
|
||||||
console.print("Add the catalog with --install-allowed or install from the preset's repository directly with --from.")
|
|
||||||
raise typer.Exit(1)
|
|
||||||
|
|
||||||
console.print(f"Installing preset [cyan]{pack_info.get('name', pack_id)}[/cyan]...")
|
|
||||||
|
|
||||||
try:
|
|
||||||
zip_path = catalog.download_pack(pack_id)
|
|
||||||
manifest = manager.install_from_zip(zip_path, speckit_version, priority)
|
|
||||||
console.print(f"[green]✓[/green] Preset '{manifest.name}' v{manifest.version} installed (priority {priority})")
|
|
||||||
finally:
|
|
||||||
if 'zip_path' in locals() and zip_path.exists():
|
|
||||||
zip_path.unlink(missing_ok=True)
|
|
||||||
else:
|
else:
|
||||||
console.print("[red]Error:[/red] Specify a preset ID, --from URL, or --dev path")
|
console.print("[red]Error:[/red] Specify a preset ID, --from URL, or --dev path")
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
@@ -3071,7 +3001,7 @@ def extension_add(
|
|||||||
priority: int = typer.Option(10, "--priority", help="Resolution priority (lower = higher precedence, default 10)"),
|
priority: int = typer.Option(10, "--priority", help="Resolution priority (lower = higher precedence, default 10)"),
|
||||||
):
|
):
|
||||||
"""Install an extension."""
|
"""Install an extension."""
|
||||||
from .extensions import ExtensionManager, ExtensionCatalog, ExtensionError, ValidationError, CompatibilityError, REINSTALL_COMMAND
|
from .extensions import ExtensionManager, ExtensionCatalog, ExtensionError, ValidationError, CompatibilityError
|
||||||
|
|
||||||
project_root = Path.cwd()
|
project_root = Path.cwd()
|
||||||
|
|
||||||
@@ -3173,19 +3103,6 @@ def extension_add(
|
|||||||
manifest = manager.install_from_directory(bundled_path, speckit_version, priority=priority)
|
manifest = manager.install_from_directory(bundled_path, speckit_version, priority=priority)
|
||||||
|
|
||||||
if bundled_path is None:
|
if bundled_path is None:
|
||||||
# Bundled extensions without a download URL must come from the local package
|
|
||||||
if ext_info.get("bundled") and not ext_info.get("download_url"):
|
|
||||||
console.print(
|
|
||||||
f"[red]Error:[/red] Extension '{ext_info['id']}' is bundled with spec-kit "
|
|
||||||
f"but could not be found in the installed package."
|
|
||||||
)
|
|
||||||
console.print(
|
|
||||||
"\nThis usually means the spec-kit installation is incomplete or corrupted."
|
|
||||||
)
|
|
||||||
console.print("Try reinstalling spec-kit:")
|
|
||||||
console.print(f" {REINSTALL_COMMAND}")
|
|
||||||
raise typer.Exit(1)
|
|
||||||
|
|
||||||
# Enforce install_allowed policy
|
# Enforce install_allowed policy
|
||||||
if not ext_info.get("_install_allowed", True):
|
if not ext_info.get("_install_allowed", True):
|
||||||
catalog_name = ext_info.get("_catalog_name", "community")
|
catalog_name = ext_info.get("_catalog_name", "community")
|
||||||
|
|||||||
@@ -38,8 +38,6 @@ _FALLBACK_CORE_COMMAND_NAMES = frozenset({
|
|||||||
})
|
})
|
||||||
EXTENSION_COMMAND_NAME_PATTERN = re.compile(r"^speckit\.([a-z0-9-]+)\.([a-z0-9-]+)$")
|
EXTENSION_COMMAND_NAME_PATTERN = re.compile(r"^speckit\.([a-z0-9-]+)\.([a-z0-9-]+)$")
|
||||||
|
|
||||||
REINSTALL_COMMAND = "uv tool install specify-cli --force --from git+https://github.com/github/spec-kit.git"
|
|
||||||
|
|
||||||
|
|
||||||
def _load_core_command_names() -> frozenset[str]:
|
def _load_core_command_names() -> frozenset[str]:
|
||||||
"""Discover bundled core command names from the packaged templates.
|
"""Discover bundled core command names from the packaged templates.
|
||||||
@@ -1872,14 +1870,6 @@ class ExtensionCatalog:
|
|||||||
if not ext_info:
|
if not ext_info:
|
||||||
raise ExtensionError(f"Extension '{extension_id}' not found in catalog")
|
raise ExtensionError(f"Extension '{extension_id}' not found in catalog")
|
||||||
|
|
||||||
# Bundled extensions without a download URL must be installed locally
|
|
||||||
if ext_info.get("bundled") and not ext_info.get("download_url"):
|
|
||||||
raise ExtensionError(
|
|
||||||
f"Extension '{extension_id}' is bundled with spec-kit and has no download URL. "
|
|
||||||
f"It should be installed from the local package. "
|
|
||||||
f"Try reinstalling: {REINSTALL_COMMAND}"
|
|
||||||
)
|
|
||||||
|
|
||||||
download_url = ext_info.get("download_url")
|
download_url = ext_info.get("download_url")
|
||||||
if not download_url:
|
if not download_url:
|
||||||
raise ExtensionError(f"Extension '{extension_id}' has no download URL")
|
raise ExtensionError(f"Extension '{extension_id}' has no download URL")
|
||||||
@@ -2180,7 +2170,6 @@ class HookExecutor:
|
|||||||
codex_skill_mode = selected_ai == "codex" and bool(init_options.get("ai_skills"))
|
codex_skill_mode = selected_ai == "codex" and bool(init_options.get("ai_skills"))
|
||||||
claude_skill_mode = selected_ai == "claude" and bool(init_options.get("ai_skills"))
|
claude_skill_mode = selected_ai == "claude" and bool(init_options.get("ai_skills"))
|
||||||
kimi_skill_mode = selected_ai == "kimi"
|
kimi_skill_mode = selected_ai == "kimi"
|
||||||
cursor_skill_mode = selected_ai == "cursor-agent" and bool(init_options.get("ai_skills"))
|
|
||||||
|
|
||||||
skill_name = self._skill_name_from_command(command_id)
|
skill_name = self._skill_name_from_command(command_id)
|
||||||
if codex_skill_mode and skill_name:
|
if codex_skill_mode and skill_name:
|
||||||
@@ -2189,8 +2178,6 @@ class HookExecutor:
|
|||||||
return f"/{skill_name}"
|
return f"/{skill_name}"
|
||||||
if kimi_skill_mode and skill_name:
|
if kimi_skill_mode and skill_name:
|
||||||
return f"/skill:{skill_name}"
|
return f"/skill:{skill_name}"
|
||||||
if cursor_skill_mode and skill_name:
|
|
||||||
return f"/{skill_name}"
|
|
||||||
|
|
||||||
return f"/{command_id}"
|
return f"/{command_id}"
|
||||||
|
|
||||||
|
|||||||
@@ -1,39 +1,21 @@
|
|||||||
"""Cursor IDE integration.
|
"""Cursor IDE integration."""
|
||||||
|
|
||||||
Cursor Agent uses the ``.cursor/skills/speckit-<name>/SKILL.md`` layout.
|
from ..base import MarkdownIntegration
|
||||||
Commands are deprecated; ``--skills`` defaults to ``True``.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from ..base import IntegrationOption, SkillsIntegration
|
|
||||||
|
|
||||||
|
|
||||||
class CursorAgentIntegration(SkillsIntegration):
|
class CursorAgentIntegration(MarkdownIntegration):
|
||||||
key = "cursor-agent"
|
key = "cursor-agent"
|
||||||
config = {
|
config = {
|
||||||
"name": "Cursor",
|
"name": "Cursor",
|
||||||
"folder": ".cursor/",
|
"folder": ".cursor/",
|
||||||
"commands_subdir": "skills",
|
"commands_subdir": "commands",
|
||||||
"install_url": None,
|
"install_url": None,
|
||||||
"requires_cli": False,
|
"requires_cli": False,
|
||||||
}
|
}
|
||||||
registrar_config = {
|
registrar_config = {
|
||||||
"dir": ".cursor/skills",
|
"dir": ".cursor/commands",
|
||||||
"format": "markdown",
|
"format": "markdown",
|
||||||
"args": "$ARGUMENTS",
|
"args": "$ARGUMENTS",
|
||||||
"extension": "/SKILL.md",
|
"extension": ".md",
|
||||||
}
|
}
|
||||||
|
|
||||||
context_file = ".cursor/rules/specify-rules.mdc"
|
context_file = ".cursor/rules/specify-rules.mdc"
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def options(cls) -> list[IntegrationOption]:
|
|
||||||
return [
|
|
||||||
IntegrationOption(
|
|
||||||
"--skills",
|
|
||||||
is_flag=True,
|
|
||||||
default=True,
|
|
||||||
help="Install as agent skills (recommended for Cursor)",
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|||||||
@@ -1587,16 +1587,6 @@ class PresetCatalog:
|
|||||||
f"Preset '{pack_id}' not found in catalog"
|
f"Preset '{pack_id}' not found in catalog"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Bundled presets without a download URL must be installed locally
|
|
||||||
if pack_info.get("bundled") and not pack_info.get("download_url"):
|
|
||||||
from .extensions import REINSTALL_COMMAND
|
|
||||||
raise PresetError(
|
|
||||||
f"Preset '{pack_id}' is bundled with spec-kit and has no download URL. "
|
|
||||||
f"It should be installed from the local package. "
|
|
||||||
f"Use 'specify preset add {pack_id}' to install from the bundled package, "
|
|
||||||
f"or reinstall spec-kit if the bundled files are missing: {REINSTALL_COMMAND}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if not pack_info.get("_install_allowed", True):
|
if not pack_info.get("_install_allowed", True):
|
||||||
catalog_name = pack_info.get("_catalog_name", "unknown")
|
catalog_name = pack_info.get("_catalog_name", "unknown")
|
||||||
raise PresetError(
|
raise PresetError(
|
||||||
|
|||||||
@@ -1,28 +1,11 @@
|
|||||||
"""Tests for CursorAgentIntegration."""
|
"""Tests for CursorAgentIntegration."""
|
||||||
|
|
||||||
from .test_integration_base_skills import SkillsIntegrationTests
|
from .test_integration_base_markdown import MarkdownIntegrationTests
|
||||||
|
|
||||||
|
|
||||||
class TestCursorAgentIntegration(SkillsIntegrationTests):
|
class TestCursorAgentIntegration(MarkdownIntegrationTests):
|
||||||
KEY = "cursor-agent"
|
KEY = "cursor-agent"
|
||||||
FOLDER = ".cursor/"
|
FOLDER = ".cursor/"
|
||||||
COMMANDS_SUBDIR = "skills"
|
COMMANDS_SUBDIR = "commands"
|
||||||
REGISTRAR_DIR = ".cursor/skills"
|
REGISTRAR_DIR = ".cursor/commands"
|
||||||
CONTEXT_FILE = ".cursor/rules/specify-rules.mdc"
|
CONTEXT_FILE = ".cursor/rules/specify-rules.mdc"
|
||||||
|
|
||||||
|
|
||||||
class TestCursorAgentAutoPromote:
|
|
||||||
"""--ai cursor-agent auto-promotes to integration path."""
|
|
||||||
|
|
||||||
def test_ai_cursor_agent_without_ai_skills_auto_promotes(self, tmp_path):
|
|
||||||
"""--ai cursor-agent should work the same as --integration cursor-agent."""
|
|
||||||
from typer.testing import CliRunner
|
|
||||||
from specify_cli import app
|
|
||||||
|
|
||||||
runner = CliRunner()
|
|
||||||
target = tmp_path / "test-proj"
|
|
||||||
result = runner.invoke(app, ["init", str(target), "--ai", "cursor-agent", "--no-git", "--ignore-agent-tools", "--script", "sh"])
|
|
||||||
|
|
||||||
assert result.exit_code == 0, f"init --ai cursor-agent failed: {result.output}"
|
|
||||||
assert (target / ".cursor" / "skills" / "speckit-plan" / "SKILL.md").exists()
|
|
||||||
|
|
||||||
|
|||||||
@@ -2995,122 +2995,6 @@ class TestExtensionAddCLI:
|
|||||||
f"but was called with '{download_called_with[0]}'"
|
f"but was called with '{download_called_with[0]}'"
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_add_bundled_extension_not_found_gives_clear_error(self, tmp_path):
|
|
||||||
"""extension add should give a clear error when a bundled extension is not found locally."""
|
|
||||||
from typer.testing import CliRunner
|
|
||||||
from unittest.mock import patch, MagicMock
|
|
||||||
from specify_cli import app
|
|
||||||
|
|
||||||
runner = CliRunner()
|
|
||||||
|
|
||||||
# Create project structure
|
|
||||||
project_dir = tmp_path / "test-project"
|
|
||||||
project_dir.mkdir()
|
|
||||||
(project_dir / ".specify").mkdir()
|
|
||||||
(project_dir / ".specify" / "extensions").mkdir(parents=True)
|
|
||||||
|
|
||||||
# Mock catalog that returns a bundled extension without download_url
|
|
||||||
mock_catalog = MagicMock()
|
|
||||||
mock_catalog.get_extension_info.return_value = {
|
|
||||||
"id": "git",
|
|
||||||
"name": "Git Branching Workflow",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "Git branching extension",
|
|
||||||
"bundled": True,
|
|
||||||
"_install_allowed": True,
|
|
||||||
}
|
|
||||||
mock_catalog.search.return_value = []
|
|
||||||
|
|
||||||
with patch("specify_cli.extensions.ExtensionCatalog", return_value=mock_catalog), \
|
|
||||||
patch("specify_cli._locate_bundled_extension", return_value=None), \
|
|
||||||
patch.object(Path, "cwd", return_value=project_dir):
|
|
||||||
result = runner.invoke(
|
|
||||||
app,
|
|
||||||
["extension", "add", "git"],
|
|
||||||
catch_exceptions=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
assert result.exit_code != 0
|
|
||||||
assert "bundled with spec-kit" in result.output
|
|
||||||
assert "reinstall" in result.output.lower()
|
|
||||||
|
|
||||||
|
|
||||||
class TestDownloadExtensionBundled:
|
|
||||||
"""Tests for download_extension handling of bundled extensions."""
|
|
||||||
|
|
||||||
def test_download_extension_raises_for_bundled(self, temp_dir):
|
|
||||||
"""download_extension should raise a clear error for bundled extensions without a URL."""
|
|
||||||
from unittest.mock import patch
|
|
||||||
|
|
||||||
project_dir = temp_dir / "project"
|
|
||||||
project_dir.mkdir()
|
|
||||||
(project_dir / ".specify").mkdir()
|
|
||||||
|
|
||||||
catalog = ExtensionCatalog(project_dir)
|
|
||||||
|
|
||||||
bundled_ext_info = {
|
|
||||||
"name": "Git Branching Workflow",
|
|
||||||
"id": "git",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "Git workflow",
|
|
||||||
"bundled": True,
|
|
||||||
}
|
|
||||||
|
|
||||||
with patch.object(catalog, "get_extension_info", return_value=bundled_ext_info):
|
|
||||||
with pytest.raises(ExtensionError, match="bundled with spec-kit"):
|
|
||||||
catalog.download_extension("git")
|
|
||||||
|
|
||||||
def test_download_extension_allows_bundled_with_url(self, temp_dir):
|
|
||||||
"""download_extension should allow bundled extensions that have a download_url (newer version)."""
|
|
||||||
from unittest.mock import patch, MagicMock
|
|
||||||
import urllib.request
|
|
||||||
|
|
||||||
project_dir = temp_dir / "project"
|
|
||||||
project_dir.mkdir()
|
|
||||||
(project_dir / ".specify").mkdir()
|
|
||||||
|
|
||||||
catalog = ExtensionCatalog(project_dir)
|
|
||||||
|
|
||||||
bundled_with_url = {
|
|
||||||
"name": "Git Branching Workflow",
|
|
||||||
"id": "git",
|
|
||||||
"version": "2.0.0",
|
|
||||||
"description": "Git workflow",
|
|
||||||
"bundled": True,
|
|
||||||
"download_url": "https://example.com/git-2.0.0.zip",
|
|
||||||
}
|
|
||||||
|
|
||||||
mock_response = MagicMock()
|
|
||||||
mock_response.read.return_value = b"fake zip data"
|
|
||||||
mock_response.__enter__ = lambda s: s
|
|
||||||
mock_response.__exit__ = MagicMock(return_value=False)
|
|
||||||
|
|
||||||
with patch.object(catalog, "get_extension_info", return_value=bundled_with_url), \
|
|
||||||
patch.object(urllib.request, "urlopen", return_value=mock_response):
|
|
||||||
result = catalog.download_extension("git")
|
|
||||||
assert result.name == "git-2.0.0.zip"
|
|
||||||
|
|
||||||
def test_download_extension_raises_no_url_for_non_bundled(self, temp_dir):
|
|
||||||
"""download_extension should raise 'no download URL' for non-bundled extensions without URL."""
|
|
||||||
from unittest.mock import patch
|
|
||||||
|
|
||||||
project_dir = temp_dir / "project"
|
|
||||||
project_dir.mkdir()
|
|
||||||
(project_dir / ".specify").mkdir()
|
|
||||||
|
|
||||||
catalog = ExtensionCatalog(project_dir)
|
|
||||||
|
|
||||||
non_bundled_ext_info = {
|
|
||||||
"name": "Some Extension",
|
|
||||||
"id": "some-ext",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "Test",
|
|
||||||
}
|
|
||||||
|
|
||||||
with patch.object(catalog, "get_extension_info", return_value=non_bundled_ext_info):
|
|
||||||
with pytest.raises(ExtensionError, match="has no download URL"):
|
|
||||||
catalog.download_extension("some-ext")
|
|
||||||
|
|
||||||
|
|
||||||
class TestExtensionUpdateCLI:
|
class TestExtensionUpdateCLI:
|
||||||
"""CLI integration tests for extension update command."""
|
"""CLI integration tests for extension update command."""
|
||||||
|
|||||||
@@ -2865,182 +2865,3 @@ class TestPresetEnableDisable:
|
|||||||
|
|
||||||
assert result.exit_code == 1
|
assert result.exit_code == 1
|
||||||
assert "corrupted state" in result.output.lower()
|
assert "corrupted state" in result.output.lower()
|
||||||
|
|
||||||
|
|
||||||
# ===== Lean Preset Tests =====
|
|
||||||
|
|
||||||
|
|
||||||
LEAN_PRESET_DIR = Path(__file__).parent.parent / "presets" / "lean"
|
|
||||||
|
|
||||||
LEAN_COMMAND_NAMES = [
|
|
||||||
"speckit.specify",
|
|
||||||
"speckit.plan",
|
|
||||||
"speckit.tasks",
|
|
||||||
"speckit.implement",
|
|
||||||
"speckit.constitution",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class TestLeanPreset:
|
|
||||||
"""Tests for the lean preset that ships with the repo."""
|
|
||||||
|
|
||||||
def test_lean_preset_exists(self):
|
|
||||||
"""Verify the lean preset directory and manifest exist."""
|
|
||||||
assert LEAN_PRESET_DIR.exists()
|
|
||||||
assert (LEAN_PRESET_DIR / "preset.yml").exists()
|
|
||||||
|
|
||||||
def test_lean_manifest_valid(self):
|
|
||||||
"""Verify the lean preset manifest is valid."""
|
|
||||||
manifest = PresetManifest(LEAN_PRESET_DIR / "preset.yml")
|
|
||||||
assert manifest.id == "lean"
|
|
||||||
assert manifest.name == "Lean Workflow"
|
|
||||||
assert manifest.version == "1.0.0"
|
|
||||||
assert len(manifest.templates) == 5 # 5 commands
|
|
||||||
|
|
||||||
def test_lean_provides_core_workflow_commands(self):
|
|
||||||
"""Verify the lean preset provides overrides for core workflow commands."""
|
|
||||||
manifest = PresetManifest(LEAN_PRESET_DIR / "preset.yml")
|
|
||||||
provided_names = {t["name"] for t in manifest.templates}
|
|
||||||
for name in LEAN_COMMAND_NAMES:
|
|
||||||
assert name in provided_names, f"Lean preset missing command: {name}"
|
|
||||||
|
|
||||||
def test_lean_command_files_exist(self):
|
|
||||||
"""Verify that all declared command files actually exist on disk."""
|
|
||||||
manifest = PresetManifest(LEAN_PRESET_DIR / "preset.yml")
|
|
||||||
for tmpl in manifest.templates:
|
|
||||||
tmpl_path = LEAN_PRESET_DIR / tmpl["file"]
|
|
||||||
assert tmpl_path.exists(), f"Missing command file: {tmpl['file']}"
|
|
||||||
|
|
||||||
def test_lean_commands_have_no_scripts(self):
|
|
||||||
"""Verify lean commands have no scripts or agent_scripts in frontmatter."""
|
|
||||||
from specify_cli.agents import CommandRegistrar
|
|
||||||
|
|
||||||
for name in LEAN_COMMAND_NAMES:
|
|
||||||
cmd_path = LEAN_PRESET_DIR / "commands" / f"speckit.{name.split('.')[-1]}.md"
|
|
||||||
content = cmd_path.read_text()
|
|
||||||
frontmatter, _ = CommandRegistrar.parse_frontmatter(content)
|
|
||||||
assert "scripts" not in frontmatter, f"{name} should not have scripts in frontmatter"
|
|
||||||
assert "agent_scripts" not in frontmatter, f"{name} should not have agent_scripts in frontmatter"
|
|
||||||
|
|
||||||
def test_lean_commands_have_no_hooks(self):
|
|
||||||
"""Verify lean commands do not contain extension hook boilerplate."""
|
|
||||||
for name in LEAN_COMMAND_NAMES:
|
|
||||||
cmd_path = LEAN_PRESET_DIR / "commands" / f"speckit.{name.split('.')[-1]}.md"
|
|
||||||
content = cmd_path.read_text()
|
|
||||||
assert "hooks." not in content, f"{name} should not reference extension hooks"
|
|
||||||
assert "extensions.yml" not in content, f"{name} should not reference extensions.yml"
|
|
||||||
|
|
||||||
def test_install_lean_preset(self, project_dir):
|
|
||||||
"""Test installing the lean preset from its directory."""
|
|
||||||
manager = PresetManager(project_dir)
|
|
||||||
manifest = manager.install_from_directory(LEAN_PRESET_DIR, "0.6.0")
|
|
||||||
assert manifest.id == "lean"
|
|
||||||
assert manager.registry.is_installed("lean")
|
|
||||||
|
|
||||||
def test_lean_overrides_commands(self, project_dir):
|
|
||||||
"""Test that lean preset overrides are resolved correctly."""
|
|
||||||
manager = PresetManager(project_dir)
|
|
||||||
manager.install_from_directory(LEAN_PRESET_DIR, "0.6.0")
|
|
||||||
|
|
||||||
resolver = PresetResolver(project_dir)
|
|
||||||
for name in LEAN_COMMAND_NAMES:
|
|
||||||
result = resolver.resolve(name, template_type="command")
|
|
||||||
assert result is not None, f"Lean override for {name} not resolved"
|
|
||||||
|
|
||||||
|
|
||||||
# ===== Bundled Preset Locator Tests =====
|
|
||||||
|
|
||||||
|
|
||||||
class TestBundledPresetLocator:
|
|
||||||
"""Tests for _locate_bundled_preset discovery function."""
|
|
||||||
|
|
||||||
def test_locate_bundled_lean_preset(self):
|
|
||||||
"""_locate_bundled_preset finds the lean preset."""
|
|
||||||
from specify_cli import _locate_bundled_preset
|
|
||||||
|
|
||||||
path = _locate_bundled_preset("lean")
|
|
||||||
assert path is not None
|
|
||||||
assert (path / "preset.yml").is_file()
|
|
||||||
|
|
||||||
def test_locate_bundled_preset_not_found(self):
|
|
||||||
"""_locate_bundled_preset returns None for nonexistent preset."""
|
|
||||||
from specify_cli import _locate_bundled_preset
|
|
||||||
|
|
||||||
path = _locate_bundled_preset("nonexistent-preset")
|
|
||||||
assert path is None
|
|
||||||
|
|
||||||
def test_locate_bundled_preset_rejects_invalid_id(self):
|
|
||||||
"""_locate_bundled_preset rejects IDs with invalid characters."""
|
|
||||||
from specify_cli import _locate_bundled_preset
|
|
||||||
|
|
||||||
assert _locate_bundled_preset("../escape") is None
|
|
||||||
assert _locate_bundled_preset("UPPERCASE") is None
|
|
||||||
assert _locate_bundled_preset("has spaces") is None
|
|
||||||
|
|
||||||
def test_bundled_preset_add_via_cli(self, project_dir):
|
|
||||||
"""Test that 'specify preset add lean' installs the bundled preset."""
|
|
||||||
from typer.testing import CliRunner
|
|
||||||
from unittest.mock import patch
|
|
||||||
from specify_cli import app
|
|
||||||
|
|
||||||
runner = CliRunner()
|
|
||||||
with patch.object(Path, "cwd", return_value=project_dir), \
|
|
||||||
patch("specify_cli.get_speckit_version", return_value="0.6.0"):
|
|
||||||
result = runner.invoke(app, ["preset", "add", "lean"])
|
|
||||||
|
|
||||||
assert result.exit_code == 0, result.output
|
|
||||||
assert "Lean Workflow" in result.output
|
|
||||||
assert "installed" in result.output.lower()
|
|
||||||
|
|
||||||
def test_bundled_preset_in_catalog(self):
|
|
||||||
"""Verify the lean preset is listed in catalog.json with bundled marker."""
|
|
||||||
catalog_path = Path(__file__).parent.parent / "presets" / "catalog.json"
|
|
||||||
catalog = json.loads(catalog_path.read_text())
|
|
||||||
assert "lean" in catalog["presets"]
|
|
||||||
assert catalog["presets"]["lean"]["bundled"] is True
|
|
||||||
assert "download_url" not in catalog["presets"]["lean"]
|
|
||||||
|
|
||||||
def test_bundled_preset_download_raises_error(self, project_dir):
|
|
||||||
"""download_pack raises PresetError for bundled presets without download_url."""
|
|
||||||
catalog = PresetCatalog(project_dir)
|
|
||||||
|
|
||||||
catalog_data = {
|
|
||||||
"test-bundled": {
|
|
||||||
"name": "Test Bundled",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"bundled": True,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
from unittest.mock import patch
|
|
||||||
with patch.object(catalog, "_get_merged_packs", return_value=catalog_data):
|
|
||||||
with pytest.raises(PresetError, match="bundled with spec-kit"):
|
|
||||||
catalog.download_pack("test-bundled")
|
|
||||||
|
|
||||||
def test_bundled_preset_missing_locally_cli_error(self, project_dir):
|
|
||||||
"""CLI shows clear error when bundled preset cannot be found locally."""
|
|
||||||
from typer.testing import CliRunner
|
|
||||||
from unittest.mock import patch
|
|
||||||
from specify_cli import app
|
|
||||||
|
|
||||||
runner = CliRunner()
|
|
||||||
# Patch _locate_bundled_preset to return None (simulating missing files)
|
|
||||||
# and mock the catalog to return a bundled entry for "lean"
|
|
||||||
fake_pack_info = {
|
|
||||||
"id": "lean",
|
|
||||||
"name": "Lean Workflow",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"bundled": True,
|
|
||||||
"_install_allowed": True,
|
|
||||||
}
|
|
||||||
with patch.object(Path, "cwd", return_value=project_dir), \
|
|
||||||
patch("specify_cli._locate_bundled_preset", return_value=None), \
|
|
||||||
patch("specify_cli.presets.PresetCatalog") as MockCatalog:
|
|
||||||
MockCatalog.return_value.get_pack_info.return_value = fake_pack_info
|
|
||||||
result = runner.invoke(app, ["preset", "add", "lean"])
|
|
||||||
|
|
||||||
# Should fail with a helpful error explaining this is a bundled preset
|
|
||||||
# and suggesting how to recover.
|
|
||||||
assert result.exit_code == 1
|
|
||||||
output = strip_ansi(result.output).lower()
|
|
||||||
assert "bundled" in output, result.output
|
|
||||||
assert "reinstall" in output, result.output
|
|
||||||
|
|||||||
Reference in New Issue
Block a user