Compare commits

..

13 Commits

Author SHA1 Message Date
github-actions[bot]
44de9235a8 chore: bump version to 0.8.9 2026-05-12 22:50:24 +00:00
Manfred Riem
efdff310a2 docs: revamp landing page with four-pillar card layout (#2531)
Rewrite docs/index.md from a philosophy essay into a landing page
organized around four pillars: Spec-driven by default, Use any coding
agent, Make it your own, and Integrate into your organization.

- Add hero section with GitHub Spec Kit branding and CTA buttons
- Add 2x2 pillar card grid with GitHub Primer color accents
- Add community stats section (96K stars, 200+ contributors, etc.)
- Add navigation cards and footer install CTA
- Move SDD philosophy content to docs/concepts/sdd.md
- Add custom DocFX template overlay with card CSS and dark mode
- Set landing layout for index.md via fileMetadata
- Update toc.yml and docfx.json for new concepts section
2026-05-12 17:39:38 -05:00
Dyan Galih
372b22a9bc feat(extensions): update governance ecosystem extensions to latest versions (#2514)
* feat(extensions): update governance ecosystem extensions to latest versions

* chore: update catalog timestamp to current time
2026-05-12 16:59:56 -05:00
Quratulain-bilal
765e60f1c4 Add changelog extension (#2177)
* Add Spec Scope extension to community catalog

Adds spec-kit-scope: effort estimation and scope tracking from spec artifacts.

4 commands:
- /speckit.scope.estimate — data-driven effort estimation with three-point ranges
- /speckit.scope.compare — side-by-side spec scope comparison
- /speckit.scope.creep — scope creep detection via git history
- /speckit.scope.budget — sprint-ready time budget generation

1 hook: after_specify (auto-estimation)

Turns "how long will this take?" into a data-driven answer.

* Add Spec Changelog extension to community catalog

* Add Spec Changelog extension to community catalog

* fix: drop accidental scope entry, restore Intelligent Agent Orchestrator README row, return Spec Reference Loader to original position

Per Copilot review on PR #2177: this branch is supposed to add only the
Spec Changelog extension. The diff against main also showed (1) a duplicate
'scope' catalog entry, (2) a deletion of the Intelligent Agent Orchestrator
README row, and (3) Spec Reference Loader moved out of alphabetical order.
All three were merge artifacts and have been cleaned up here.

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* fix: keep Spec Changelog row alphabetically sorted

Address Copilot review on PR #2177: the Community Extensions table is
sorted alphabetically by display name, and 'Changelog' precedes 'Critique',
'Diagram', 'Orchestrator', and 'Reference', so the Spec Changelog row
belongs right after Ship Release Extension. Move it into its sorted slot
and keep Spec Reference Loader in its original alphabetical position
(between Spec Orchestrator and Spec Refine).

* fix: remove duplicate Spec Reference Loader row from README

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Manfred Riem <15701806+mnriem@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 12:41:33 -05:00
flipthedog
92186124f3 Add install directory to docfx.json file references (#2522)
* Add install directory to docfx.json file references

Fixed broken link for https://github.github.com/spec-kit/install/uv.md

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-12 09:48:43 -05:00
Manfred Riem
20ef9a72a9 feat(catalog): add BrownKit (brownkit) community extension (#2510) (#2520)
* feat(catalog): add BrownKit (brownkit) community extension (#2510)

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* fix: bump catalog-level updated_at to match newest entry

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-12 07:51:00 -05:00
Eldar Shlomi
cba00ab9a5 fix(kiro-cli): replace literal $ARGUMENTS with prose fallback (#2482)
* fix(kiro-cli): replace literal $ARGUMENTS with prose fallback

Kiro CLI file-based prompts do not natively substitute any
argument placeholder (kirodotdev/Kiro#4141, kiro.dev/docs/cli
manage-prompts), so the literal "$ARGUMENTS" set in
KiroCliIntegration.registrar_config["args"] reached the model
verbatim and broke the prompt — every parameterized SpecKit
command under Kiro CLI was unusable.

Replace the placeholder with a prose fallback that instructs
the model to take its argument from the user's next message,
mirroring the convention used by other integrations whose
target CLI lacks native argument injection.

Add two regression tests in TestKiroCliIntegration:
  - test_rendered_prompts_do_not_contain_raw_arguments
  - test_rendered_prompts_contain_kiro_arg_placeholder
and override the inherited test_registrar_config so it does
not require args == "$ARGUMENTS".

Fixes #1926

* test(kiro-cli): tighten args regression guard + document quirk

Address review feedback on PR #2482.

Two changes that bracket the original bug fix from both sides — code AND
documentation:

1. Test layer (Copilot finding at lines 27, 56)

The previous test_registrar_config asserted only that args != "$ARGUMENTS"
and that args is truthy. That would silently pass if a future change
swapped $ARGUMENTS for $INPUT, {{userMessage}}, <args>, or any other
unsubstituted placeholder syntax — defeating the regression guard for
issue #1926.

Replace with a dual-layer guard:

  - test_registrar_config_args_is_exact_prose_fallback pins args to the
    imported _KIRO_ARG_FALLBACK constant. Wording drift now requires a
    deliberate paired commit (production constant + test).

  - test_registrar_config_args_does_not_look_like_a_placeholder_token is
    an independent regression guard built on a 7-pattern regex set
    covering Bash ($X, ${X}, ${X:-default}), Mustache/Handlebars/Jinja
    ({{X}}, {{{X}}}), Liquid/Jinja control ({% %}), Python str.format /
    .NET ({0}, {var}), angle-bracket (<X>), and Windows (%X%). Patterns
    are anchored to the full string so legitimate prose mentioning a
    placeholder ("the {{magic}} of placeholders") is not flagged.

Also fix the line-56 tautology by importing _KIRO_ARG_FALLBACK directly
into test_rendered_prompts_contain_kiro_arg_placeholder, instead of
reading the constant back from registrar_config["args"]. The test now
verifies the FALLBACK STRING reaches the rendered output, independent
of the integration's own config staying correct.

2. Docs layer (mnriem CHANGES_REQUESTED)

The Kiro CLI row in docs/reference/integrations.md only documented its
alias. Update the notes column to lead with the limitation — Kiro CLI
does not substitute $ARGUMENTS in file-based prompts, so Spec Kit ships
a prose fallback at render time — with inline links to upstream Kiro
"Manage prompts" docs and issue #1926. Style follows the Pi row
("limitation first, alias preserved at end").

Refs #1926
2026-05-12 07:48:25 -05:00
adaumann
a7f6800fcc Preset: Add game-narrative-writing preset to community catalog (#2454)
* Add game-narrative-writing  preset to community catalog

- Preset ID: game-narrative-writing
- Version: 1.0.0
- Author: Andreas Daumann
- Description: Spec-Driven Development for interactive game narrative for pre-production for video games. Authors write in a portable generic format, Twine/Sugarcube (.twee) or Ink (.ink). Covers choice-IF, visual novels, and branching dialogue. Supports Tier 1 mechanic hooks (flag, counter, inventory, timer, trust, currency, npc_state, ending_condition), multi-ending design, series carry-over variable registry, and NPC-focused character architecture.

Co-authored-by: Copilot <copilot@github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-12 07:35:37 -05:00
Asish Kumar
cd951acb9e docs: clarify CLI upgrade discovery (#2519) 2026-05-12 07:19:40 -05:00
WOLIKIMCHENG
756d632129 fix: make template metadata line breaks markdownlint-safe (#2505)
Co-authored-by: root <1647273252@qq.com>
2026-05-12 07:07:41 -05:00
Pascal THUET
0593565607 refactor(catalogs): extract integration catalog config loading (#2497) 2026-05-11 15:25:00 -05:00
Quratulain-bilal
bf47e89249 test(presets): silence expected UserWarnings in self-test composition… (#2373)
* test(presets): silence expected UserWarnings in self-test composition tests

The self-test preset that ships with the repo provides a wrap-strategy
command (speckit.wrap-test) intentionally without a corresponding core
base layer, exercising the 'no base layer' branch of
_reconcile_composed_commands().

Eighteen tests across TestSelfTestPreset and TestPresetSkills install
this preset and trigger an expected UserWarning. Running the suite with
-W error::UserWarning surfaces them as test noise that could obscure
unrelated warnings.

Add class-level pytest.mark.filterwarnings filters to acknowledge the
two known messages ('Cannot compose command speckit.wrap-test' and
'Post-install reconciliation failed for self-test') so other UserWarning
sources still propagate normally.

Fixes #2363

* test(presets): scope filterwarnings to UserWarning category

Address Copilot review on #2373: the previous filterwarnings entries
omitted the warning category, so any warning class with a matching
message would have been silenced. Append :UserWarning to the four
filters so only the deliberately-emitted UserWarnings from
_reconcile_composed_commands() are ignored.

* test(presets): narrow self-test warning filter to install helper only

Address Copilot feedback: the class-level @pytest.mark.filterwarnings on
TestPresetSkills was too broad. The 'Post-install reconciliation failed'
filter could mask real reconciliation regressions, since that warning is
only emitted when _reconcile_composed_commands/_reconcile_skills raises.

Tests in TestPresetSkills already call install_self_test_preset(), which
scopes a narrow filter to the expected wrap-strategy 'Cannot compose'
warning. The class-level filters are redundant for those calls and unsafe
elsewhere, so they are removed.

* test(presets): align TestSelfTestPreset docstring with helper-based filtering

Address Copilot feedback: docstring referred to 'filters above', but the
fix uses warnings.filterwarnings inside install_self_test_preset rather
than class-level decorators. Updated the docstring to describe the actual
mechanism.

* test(presets): remove extra blank line between helper and class (PEP 8)

Address Copilot feedback: PEP 8 expects two blank lines between top-level
definitions; reduce the three blank lines between install_self_test_preset
and TestSelfTestPreset to two.
2026-05-11 15:16:55 -05:00
Manfred Riem
81f772c60b chore: release 0.8.8, begin 0.8.9.dev0 development (#2516)
* chore: bump version to 0.8.8

* chore: begin 0.8.9.dev0 development

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-05-11 12:10:36 -05:00
21 changed files with 956 additions and 218 deletions

View File

@@ -2,6 +2,23 @@
<!-- insert new changelog below this comment -->
## [0.8.9] - 2026-05-12
### Changed
- docs: revamp landing page with four-pillar card layout (#2531)
- feat(extensions): update governance ecosystem extensions to latest versions (#2514)
- Add changelog extension (#2177)
- Add install directory to docfx.json file references (#2522)
- feat(catalog): add BrownKit (brownkit) community extension (#2510) (#2520)
- fix(kiro-cli): replace literal $ARGUMENTS with prose fallback (#2482)
- Preset: Add game-narrative-writing preset to community catalog (#2454)
- docs: clarify CLI upgrade discovery (#2519)
- fix: make template metadata line breaks markdownlint-safe (#2505)
- refactor(catalogs): extract integration catalog config loading (#2497)
- test(presets): silence expected UserWarnings in self-test composition… (#2373)
- chore: release 0.8.8, begin 0.8.9.dev0 development (#2516)
## [0.8.8] - 2026-05-11
### Changed

View File

@@ -208,6 +208,7 @@ The following community-contributed extensions are available in [`catalog.commun
| Blueprint | Stay code-literate in AI-driven development: review a complete code blueprint for every task from spec artifacts before /speckit.implement runs | `docs` | Read+Write | [spec-kit-blueprint](https://github.com/chordpli/spec-kit-blueprint) |
| Branch Convention | Configurable branch and folder naming conventions for /specify with presets and custom patterns | `process` | Read+Write | [spec-kit-branch-convention](https://github.com/Quratulain-bilal/spec-kit-branch-convention) |
| Brownfield Bootstrap | Bootstrap spec-kit for existing codebases — auto-discover architecture and adopt SDD incrementally | `process` | Read+Write | [spec-kit-brownfield](https://github.com/Quratulain-bilal/spec-kit-brownfield) |
| BrownKit | Evidence-driven capability discovery, security and QA risk assessment for existing codebases | `process` | Read+Write | [BrownKit](https://github.com/MaksimShevtsov/BrownKit) |
| 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) |
| Catalog CI | Automated validation for spec-kit community catalog entries — structure, URLs, diffs, and linting | `process` | Read-only | [spec-kit-catalog-ci](https://github.com/Quratulain-bilal/spec-kit-catalog-ci) |
@@ -265,11 +266,12 @@ The following community-contributed extensions are available in [`catalog.commun
| Security Review | Full-project secure-by-design security audits plus staged, branch/PR, plan, task, follow-up, and apply reviews | `code` | Read+Write | [spec-kit-security-review](https://github.com/DyanGalih/spec-kit-security-review) |
| SFSpeckit | Enterprise Salesforce SDLC with 18 commands for the full SDD lifecycle. | `process` | Read+Write | [spec-kit-sf](https://github.com/ysumanth06/spec-kit-sf) |
| Ship Release Extension | Automates release pipeline: pre-flight checks, branch sync, changelog generation, CI verification, and PR creation | `process` | Read+Write | [spec-kit-ship](https://github.com/arunt14/spec-kit-ship) |
| Spec Reference Loader | Reads the ## References section from the feature spec and loads only the listed docs into context | `docs` | Read-only | [spec-kit-spec-reference-loader](https://github.com/KevinBrown5280/spec-kit-spec-reference-loader) |
| Spec Changelog | Auto-generate changelogs and release notes from spec git history and requirement diffs | `docs` | Read-only | [spec-kit-changelog](https://github.com/Quratulain-bilal/spec-kit-changelog) |
| Spec Critique Extension | Dual-lens critical review of spec and plan from product strategy and engineering risk perspectives | `docs` | Read-only | [spec-kit-critique](https://github.com/arunt14/spec-kit-critique) |
| Spec Diagram | Auto-generate Mermaid diagrams of SDD workflow state, feature progress, and task dependencies | `visibility` | Read-only | [spec-kit-diagram-](https://github.com/Quratulain-bilal/spec-kit-diagram-) |
| Spec Kit Schedule | Optimal multi-agent task scheduling via CP-SAT — DAG precedence, hallucination-aware caps, file-conflict avoidance, stochastic durations, replanning, and interactive HTML output | `process` | Read+Write | [spec-kit-schedule](https://github.com/jfranc38/spec-kit-schedule) |
| Spec Orchestrator | Cross-feature orchestration — track state, select tasks, and detect conflicts across parallel specs | `process` | Read-only | [spec-kit-orchestrator](https://github.com/Quratulain-bilal/spec-kit-orchestrator) |
| Spec Reference Loader | Reads the ## References section from the feature spec and loads only the listed docs into context | `docs` | Read-only | [spec-kit-spec-reference-loader](https://github.com/KevinBrown5280/spec-kit-spec-reference-loader) |
| Spec Refine | Update specs in-place, propagate changes to plan and tasks, and diff impact across artifacts | `process` | Read+Write | [spec-kit-refine](https://github.com/Quratulain-bilal/spec-kit-refine) |
| Spec Scope | Effort estimation and scope tracking — estimate work, detect creep, and budget time per phase | `process` | Read-only | [spec-kit-scope-](https://github.com/Quratulain-bilal/spec-kit-scope-) |
| Spec Sync | Detect and resolve drift between specs and implementation. AI-assisted resolution with human approval | `docs` | Read+Write | [spec-kit-sync](https://github.com/bgervin/spec-kit-sync) |

View File

@@ -16,6 +16,7 @@ The following community-contributed presets customize how Spec Kit behaves — o
| Cross-Platform Governance | Adds Bash/PowerShell parity, dry-run/WhatIf parity, Unix man-page expectations, PowerShell comment-based help, and Verb-Noun Cmdlet discipline | 8 templates, 3 commands | — | [spec-kit-preset-cross-platform-governance](https://github.com/hindermath/spec-kit-preset-cross-platform-governance) |
| Explicit Task Dependencies | Adds explicit `(depends on T###)` dependency declarations and an Execution Wave DAG to tasks.md for parallel scheduling | 1 template, 1 command | — | [spec-kit-preset-explicit-task-dependencies](https://github.com/Quratulain-bilal/spec-kit-preset-explicit-task-dependencies) |
| Fiction Book Writing | It adapts the Spec-Driven Development workflow for storytelling to create books or audiobooks (with annotations) in 12 languages: features become story elements, specs become story briefs, plans become story structures, and tasks become scene-by-scene writing tasks. Supports single and multi-POV, all major plot structure frameworks, and two style modes: an author voice sample or humanized AI prose. Supports interactive elements like brainstorming, interview, roleplay and extras like statistics, cover builder and bio command. Export with templates for KDP, D2D etc. | 22 templates, 27 commands, 2 scripts | — | [speckit-preset-fiction-book-writing](https://github.com/adaumann/speckit-preset-fiction-book-writing) |
| Game Narrative Writing | Spec-Driven Development for interactive game narrative pre-production for video games. Authors write in a portable generic format, Twine/Sugarcube (.twee) or Ink (.ink). Covers choice-IF, visual novels, and branching dialogue. Supports Tier 1 mechanic hooks (flag, counter, inventory, timer, trust, currency, npc_state, ending_condition), multi-ending design, series carry-over variable registry, and NPC-focused character architecture. | 22 templates, 36 commands, 2 scripts | — | [speckit-preset-game-narrative-writing](https://github.com/adaumann/speckit-preset-game-narrative-writing) |
| iSAQB Architecture Governance | Adds general iSAQB/CPSA-F and arc42 architecture governance: goals, context, building blocks, runtime and deployment views, quality scenarios, ADRs, risks, and technical debt | 13 templates, 3 commands | — | [spec-kit-preset-isaqb-architecture-governance](https://github.com/hindermath/spec-kit-preset-isaqb-architecture-governance) |
| Jira Issue Tracking | Overrides `speckit.taskstoissues` to create Jira epics, stories, and tasks instead of GitHub Issues via Atlassian MCP tools | 1 command | — | [spec-kit-preset-jira](https://github.com/luno/spec-kit-preset-jira) |
| Model Driven Engineering | Focuses on streamlined commands, app repository support, cross-spec support, and capability-aware project memory for model-driven engineering workflows | 6 templates, 11 commands | MDE extension | [spec-kit-preset-mde](https://github.com/AI-MDE/spec-kit-preset-mde) |

46
docs/concepts/sdd.md Normal file
View File

@@ -0,0 +1,46 @@
# What is Spec-Driven Development?
Spec-Driven Development **flips the script** on traditional software development. For decades, code has been king — specifications were just scaffolding we built and discarded once the "real work" of coding began. Spec-Driven Development changes this: **specifications become executable**, directly generating working implementations rather than just guiding them.
## Core Philosophy
Spec-Driven Development is a structured process that emphasizes:
- **Intent-driven development** where specifications define the "*what*" before the "*how*"
- **Rich specification creation** using guardrails and organizational principles
- **Multi-step refinement** rather than one-shot code generation from prompts
- **Heavy reliance** on advanced AI model capabilities for specification interpretation
## Development Phases
| Phase | Focus | Key Activities |
|-------|-------|----------------|
| **0-to-1 Development** ("Greenfield") | Generate from scratch | <ul><li>Start with high-level requirements</li><li>Generate specifications</li><li>Plan implementation steps</li><li>Build production-ready applications</li></ul> |
| **Creative Exploration** | Parallel implementations | <ul><li>Explore diverse solutions</li><li>Support multiple technology stacks & architectures</li><li>Experiment with UX patterns</li></ul> |
| **Iterative Enhancement** ("Brownfield") | Brownfield modernization | <ul><li>Add features iteratively</li><li>Modernize legacy systems</li><li>Adapt processes</li></ul> |
## Experimental Goals
Our research and experimentation focus on:
### Technology Independence
- Create applications using diverse technology stacks
- Validate the hypothesis that Spec-Driven Development is a process not tied to specific technologies, programming languages, or frameworks
### Enterprise Constraints
- Demonstrate mission-critical application development
- Incorporate organizational constraints (cloud providers, tech stacks, engineering practices)
- Support enterprise design systems and compliance requirements
### User-Centric Development
- Build applications for different user cohorts and preferences
- Support various development approaches (from vibe-coding to AI-native development)
### Creative & Iterative Processes
- Validate the concept of parallel implementation exploration
- Provide robust iterative feature development workflows
- Extend processes to handle upgrades and modernization tasks

View File

@@ -6,7 +6,9 @@
"*.md",
"toc.yml",
"community/*.md",
"reference/*.md"
"concepts/*.md",
"reference/*.md",
"install/*.md"
]
},
{
@@ -49,7 +51,8 @@
"fileMetadataFiles": [],
"template": [
"default",
"modern"
"modern",
"template"
],
"postProcessors": [],
"markdownEngineName": "markdig",
@@ -67,6 +70,11 @@
"repo": "https://github.com/github/spec-kit",
"branch": "main"
}
},
"fileMetadata": {
"_layout": {
"index.md": "landing"
}
}
}
}

View File

@@ -1,67 +1,152 @@
# Spec Kit
<div class="landing-hero">
*Build high-quality software faster.*
# GitHub Spec Kit
**An effort to allow organizations to focus on product scenarios rather than writing undifferentiated code with the help of Spec-Driven Development.**
**Define what to build before building it — with any AI coding agent.**
## What is Spec-Driven Development?
Spec Kit is a toolkit for [Spec-Driven Development](concepts/sdd.md) (SDD), a methodology that puts specifications at the center of AI-assisted software development. Instead of jumping straight to code, you describe *what* to build, refine it through structured phases, and let your AI coding agent implement it.
Spec-Driven Development **flips the script** on traditional software development. For decades, code has been king — specifications were just scaffolding we built and discarded once the "real work" of coding began. Spec-Driven Development changes this: **specifications become executable**, directly generating working implementations rather than just guiding them.
<a href="installation.md" class="btn btn-primary btn-lg">Install Spec Kit</a>&nbsp;
<a href="quickstart.md" class="btn btn-outline-primary btn-lg">Quick Start</a>
## Getting Started
</div>
- [Installation Guide](installation.md)
- [Quick Start Guide](quickstart.md)
- [Upgrade Guide](upgrade.md)
- [Local Development](local-development.md)
---
## Core Philosophy
<div class="pillar-grid">
Spec-Driven Development is a structured process that emphasizes:
<div class="pillar-card">
- **Intent-driven development** where specifications define the "*what*" before the "*how*"
- **Rich specification creation** using guardrails and organizational principles
- **Multi-step refinement** rather than one-shot code generation from prompts
- **Heavy reliance** on advanced AI model capabilities for specification interpretation
### Spec-driven by default
## Development Phases
The core SDD process ships ready to use: **Spec → Plan → Tasks → Implement**.
| Phase | Focus | Key Activities |
|-------|-------|----------------|
| **0-to-1 Development** ("Greenfield") | Generate from scratch | <ul><li>Start with high-level requirements</li><li>Generate specifications</li><li>Plan implementation steps</li><li>Build production-ready applications</li></ul> |
| **Creative Exploration** | Parallel implementations | <ul><li>Explore diverse solutions</li><li>Support multiple technology stacks & architectures</li><li>Experiment with UX patterns</li></ul> |
| **Iterative Enhancement** ("Brownfield") | Brownfield modernization | <ul><li>Add features iteratively</li><li>Modernize legacy systems</li><li>Adapt processes</li></ul> |
Define what to build before building it. Rich templates, quality checklists, and cross-artifact analysis come out of the box. Each phase produces a Markdown artifact that feeds the next — giving your AI coding agent structured context instead of ad-hoc prompts.
## Experimental Goals
<a href="quickstart.md" class="pillar-link">Walk through the workflow →</a>
Our research and experimentation focus on:
</div>
### Technology Independence
<div class="pillar-card">
- Create applications using diverse technology stacks
- Validate the hypothesis that Spec-Driven Development is a process not tied to specific technologies, programming languages, or frameworks
### Use any coding agent
### Enterprise Constraints
<span class="pillar-stat">30 integrations</span> — Copilot, Gemini, Codex, Windsurf, Claude, Forge, Kiro, and more. Switch freely between agents with a single command. No lock-in.
- Demonstrate mission-critical application development
- Incorporate organizational constraints (cloud providers, tech stacks, engineering practices)
- Support enterprise design systems and compliance requirements
Run `specify init` with your agent of choice and Spec Kit sets up the right command files, context rules, and directory structures automatically. If your agent isn't listed, the `generic` integration is an escape hatch for any tool.
### User-Centric Development
<a href="reference/integrations.md" class="pillar-link">See all integrations →</a>
- Build applications for different user cohorts and preferences
- Support various development approaches (from vibe-coding to AI-native development)
</div>
### Creative & Iterative Processes
<div class="pillar-card">
- Validate the concept of parallel implementation exploration
- Provide robust iterative feature development workflows
- Extend processes to handle upgrades and modernization tasks
### Make it your own
## Contributing
<span class="pillar-stat">91 community extensions</span> (50+ authors), <span class="pillar-stat">18 presets</span>, and growing — including entirely different SDD processes:
Please see our [Contributing Guide](https://github.com/github/spec-kit/blob/main/CONTRIBUTING.md) for information on how to contribute to this project.
- **AIDE** — 7-step AI-driven engineering lifecycle
- **Canon** — baseline-driven workflows (spec-first, code-first, spec-drift)
- **Product Forge** — product-management-oriented SDD
- **FX→.NET** — end-to-end .NET Framework migration across 7 phases
- **MAQA** — multi-agent orchestration with quality assurance gates
## Support
Tune the core process with presets, extend it with extensions, orchestrate it with workflows, or replace it entirely. Build and publish your own.
For support, please check our [Support Guide](https://github.com/github/spec-kit/blob/main/SUPPORT.md) or open an issue on GitHub.
<a href="community/presets.md" class="pillar-link">Browse community presets →</a>
</div>
<div class="pillar-card">
### Integrate into your organization
Works offline, behind firewalls, and on **Windows, macOS, and Linux**. Host your own extension and preset catalogs so your organization controls what gets installed.
Community extensions like CI Guard and Architecture Guard add compliance gates and governance that fit the way your team already works.
<a href="installation.md" class="pillar-link">Installation guide →</a>&nbsp;&nbsp;
<a href="reference/extensions.md" class="pillar-link">Extensions reference →</a>
</div>
</div>
---
<div class="community-section">
## Built by the community
**200+ contributors** power the Spec Kit ecosystem — from core integrations to entirely new development processes. Anyone can create and publish an extension, preset, or workflow.
<div class="stats-grid">
<div class="stat-item">
<span class="stat-number">96K+</span>
<span class="stat-label">GitHub stars</span>
</div>
<div class="stat-item">
<span class="stat-number">200+</span>
<span class="stat-label">Contributors</span>
</div>
<div class="stat-item">
<span class="stat-number">30</span>
<span class="stat-label">Integrations</span>
</div>
<div class="stat-item">
<span class="stat-number">91</span>
<span class="stat-label">Extensions</span>
</div>
<div class="stat-item">
<span class="stat-number">18</span>
<span class="stat-label">Presets</span>
</div>
<div class="stat-item">
<span class="stat-number">4</span>
<span class="stat-label">Friends projects</span>
</div>
</div>
<a href="community/presets.md">Presets</a> · <a href="community/walkthroughs.md">Walkthroughs</a> · <a href="community/friends.md">Friends</a>
</div>
---
## Explore the docs
<div class="nav-cards">
<a href="quickstart.md" class="nav-card">
<strong>Getting Started</strong>
<span>Install, configure, and run your first SDD workflow</span>
</a>
<a href="reference/overview.md" class="nav-card">
<strong>Reference</strong>
<span>Core commands, integrations, extensions, presets, and workflows</span>
</a>
<a href="community/presets.md" class="nav-card">
<strong>Community</strong>
<span>Presets, walkthroughs, and friend projects</span>
</a>
<a href="local-development.md" class="nav-card">
<strong>Development</strong>
<span>Contribute to Spec Kit</span>
</a>
<a href="concepts/sdd.md" class="nav-card">
<strong>What is SDD?</strong>
<span>The philosophy behind Spec-Driven Development</span>
</a>
</div>
---
<div class="footer-cta">
```bash
uvx --from git+https://github.com/github/spec-kit.git
specify init my-project --integration copilot
```
Ready to start? Follow the [Quick Start Guide](quickstart.md).
</div>

View File

@@ -23,7 +23,7 @@ The Specify CLI supports a wide range of AI coding agents. When you run `specify
| [Junie](https://junie.jetbrains.com/) | `junie` | |
| [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` |
| [Kiro CLI](https://kiro.dev/docs/cli/) | `kiro-cli` | Kiro CLI does not substitute `$ARGUMENTS` in file-based prompts, so Spec Kit ships a prose fallback at render time (see [Manage prompts](https://kiro.dev/docs/cli/chat/manage-prompts/) and issue [#1926](https://github.com/github/spec-kit/issues/1926)). 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` | |
@@ -65,6 +65,8 @@ Installing an additional integration does not change the default integration. Us
> **Note:** All integration management commands require a project already initialized with `specify init`. To start a new project with a specific agent, use `specify init <project> --integration <key>` instead.
**Version note:** Controlled multi-install support was introduced in Spec Kit 0.8.5. If `specify integration install <key>` says another integration is already installed and only suggests `switch` or `uninstall`, check your local CLI with `specify version` and upgrade it. Running a one-shot command such as `uvx --from git+https://github.com/github/spec-kit.git specify ...` uses a temporary copy for that command only; it does not update the persistent `specify` executable on your `PATH`.
## Uninstall an Integration
```bash

264
docs/template/public/main.css vendored Normal file
View File

@@ -0,0 +1,264 @@
/* Spec Kit landing page — GitHub Primer colors */
:root {
/* GitHub Primer palette */
--gh-blue: #0969da;
--gh-green: #1a7f37;
--gh-purple: #8250df;
--gh-coral: #cf222e;
--gh-orange: #bf8700;
--gh-blue-subtle: #ddf4ff;
--gh-green-subtle: #dafbe1;
--gh-purple-subtle: #fbefff;
--gh-coral-subtle: #ffebe9;
}
[data-bs-theme="dark"] {
--gh-blue: #58a6ff;
--gh-green: #3fb950;
--gh-purple: #bc8cff;
--gh-coral: #f85149;
--gh-orange: #d29922;
--gh-blue-subtle: #0d1d30;
--gh-green-subtle: #0d1d14;
--gh-purple-subtle: #1c0d2e;
--gh-coral-subtle: #2d0f0d;
}
/* Override Bootstrap primary with GitHub blue */
body[data-layout="landing"] {
--bs-primary: var(--gh-blue);
--bs-primary-rgb: 9, 105, 218;
--bs-link-color: var(--gh-blue);
--bs-link-hover-color: var(--gh-blue);
}
[data-bs-theme="dark"] body[data-layout="landing"],
body[data-layout="landing"][data-bs-theme="dark"] {
--bs-primary-rgb: 88, 166, 255;
}
/* Hero section */
.landing-hero {
text-align: center;
padding: 3rem 0 1.5rem;
}
.landing-hero h1 {
font-size: 2.6rem;
font-weight: 800;
margin-bottom: 0.5rem;
background: linear-gradient(135deg, var(--gh-blue), var(--gh-purple));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.landing-hero p {
font-size: 1.15rem;
max-width: 640px;
margin: 0 auto 1.5rem;
opacity: 0.85;
}
.landing-hero .btn-primary {
background-color: var(--gh-blue);
border-color: var(--gh-blue);
color: #fff;
}
.landing-hero .btn-primary:hover {
background-color: #0860ca;
border-color: #0860ca;
}
.landing-hero .btn-outline-primary {
color: var(--gh-blue);
border-color: var(--gh-blue);
}
.landing-hero .btn-outline-primary:hover {
background-color: var(--gh-blue);
border-color: var(--gh-blue);
color: #fff;
}
/* Pillar cards grid */
.pillar-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1.5rem;
margin: 2rem 0;
}
@media (max-width: 768px) {
.pillar-grid {
grid-template-columns: 1fr;
}
}
.pillar-card {
border: 1px solid var(--bs-border-color);
border-radius: 0.5rem;
padding: 1.5rem;
background: var(--bs-body-bg);
transition: box-shadow 0.2s ease-in-out, border-color 0.2s ease-in-out;
border-top: 3px solid transparent;
}
/* Each pillar gets a distinct GitHub color accent */
.pillar-card:nth-child(1) { border-top-color: var(--gh-green); }
.pillar-card:nth-child(2) { border-top-color: var(--gh-blue); }
.pillar-card:nth-child(3) { border-top-color: var(--gh-purple); }
.pillar-card:nth-child(4) { border-top-color: var(--gh-coral); }
.pillar-card:nth-child(1):hover { box-shadow: 0 4px 16px rgba(26, 127, 55, 0.12); }
.pillar-card:nth-child(2):hover { box-shadow: 0 4px 16px rgba(9, 105, 218, 0.12); }
.pillar-card:nth-child(3):hover { box-shadow: 0 4px 16px rgba(130, 80, 223, 0.12); }
.pillar-card:nth-child(4):hover { box-shadow: 0 4px 16px rgba(207, 34, 46, 0.12); }
[data-bs-theme="dark"] .pillar-card:nth-child(1):hover { box-shadow: 0 4px 16px rgba(63, 185, 80, 0.15); }
[data-bs-theme="dark"] .pillar-card:nth-child(2):hover { box-shadow: 0 4px 16px rgba(88, 166, 255, 0.15); }
[data-bs-theme="dark"] .pillar-card:nth-child(3):hover { box-shadow: 0 4px 16px rgba(188, 140, 255, 0.15); }
[data-bs-theme="dark"] .pillar-card:nth-child(4):hover { box-shadow: 0 4px 16px rgba(248, 81, 73, 0.15); }
.pillar-card h3 {
font-size: 1.2rem;
font-weight: 600;
margin-bottom: 0.75rem;
}
/* Pillar headings pick up their card's accent color */
.pillar-card:nth-child(1) h3 { color: var(--gh-green); }
.pillar-card:nth-child(2) h3 { color: var(--gh-blue); }
.pillar-card:nth-child(3) h3 { color: var(--gh-purple); }
.pillar-card:nth-child(4) h3 { color: var(--gh-coral); }
.pillar-card .pillar-stat {
font-weight: 600;
color: var(--gh-blue);
}
.pillar-card:nth-child(3) .pillar-stat {
color: var(--gh-purple);
}
.pillar-card p:last-child {
margin-bottom: 0;
}
.pillar-card ul {
padding-left: 1.2rem;
margin-bottom: 0.5rem;
}
.pillar-card .pillar-link {
display: inline-block;
margin-top: 0.5rem;
font-size: 0.9rem;
font-weight: 500;
}
.pillar-card:nth-child(1) .pillar-link { color: var(--gh-blue); }
.pillar-card:nth-child(2) .pillar-link { color: var(--gh-green); }
.pillar-card:nth-child(3) .pillar-link { color: var(--gh-purple); }
.pillar-card:nth-child(4) .pillar-link { color: var(--gh-coral); }
/* Community stats section */
.community-section {
text-align: center;
padding: 2rem 0;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
margin: 1.5rem auto;
max-width: 700px;
}
@media (max-width: 576px) {
.stats-grid {
grid-template-columns: repeat(2, 1fr);
}
}
.stat-item {
padding: 1rem;
}
.stat-item .stat-number {
display: block;
font-size: 1.8rem;
font-weight: 700;
color: var(--gh-blue);
line-height: 1.2;
}
.stat-item .stat-label {
display: block;
font-size: 0.85rem;
opacity: 0.75;
margin-top: 0.25rem;
}
/* Nav cards */
.nav-cards {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
margin: 1.5rem 0;
}
@media (max-width: 576px) {
.nav-cards {
grid-template-columns: 1fr;
}
}
.nav-card {
border: 1px solid var(--bs-border-color);
border-radius: 0.5rem;
padding: 1rem 1.25rem;
text-decoration: none;
color: inherit;
transition: box-shadow 0.2s ease-in-out, border-color 0.2s ease-in-out;
display: block;
border-left: 3px solid var(--gh-blue);
}
.nav-card:hover {
border-color: var(--gh-blue);
border-left-color: var(--gh-blue);
box-shadow: 0 2px 8px rgba(9, 105, 218, 0.1);
text-decoration: none;
color: inherit;
}
[data-bs-theme="dark"] .nav-card:hover {
box-shadow: 0 2px 8px rgba(88, 166, 255, 0.12);
}
.nav-card strong {
display: block;
margin-bottom: 0.25rem;
color: var(--gh-blue);
}
.nav-card span {
font-size: 0.9rem;
opacity: 0.75;
}
/* Footer CTA */
.footer-cta {
text-align: center;
padding: 2rem 0 1rem;
}
.footer-cta code {
font-size: 1.05rem;
padding: 0.5rem 1rem;
border-radius: 0.375rem;
}

View File

@@ -30,6 +30,12 @@
- name: Workflows
href: reference/workflows.md
# Concepts
- name: Concepts
items:
- name: What is SDD?
href: concepts/sdd.md
# Development workflows
- name: Development
items:

View File

@@ -19,6 +19,12 @@
The CLI tool (`specify`) is separate from your project files. Upgrade it to get the latest features and bug fixes.
Before upgrading, you can check whether a newer released version is available:
```bash
specify self check
```
### If you installed with `uv tool install`
Upgrade to a specific release (check [Releases](https://github.com/github/spec-kit/releases) for the latest tag):
@@ -35,6 +41,8 @@ Specify the desired release tag:
uvx --from git+https://github.com/github/spec-kit.git@vX.Y.Z specify init --here --integration copilot
```
`uvx` runs a temporary copy of Spec Kit for that single command. It does not update a persistent `specify` installed with `uv tool install`, `pipx`, or another tool manager. If a newer feature works through `uvx` but your local `specify` still reports an older version, upgrade the persistent CLI with the command that matches your install method.
### If you installed with `pipx`
Upgrade to a specific release:
@@ -49,7 +57,7 @@ pipx install --force git+https://github.com/github/spec-kit.git@vX.Y.Z
specify check
```
This shows installed tools and confirms the CLI is working.
This shows installed tools and confirms the CLI is working. Use `specify version` to confirm which persistent CLI version is currently on your `PATH`.
---

View File

@@ -1,6 +1,6 @@
{
"schema_version": "1.0",
"updated_at": "2026-05-07T20:05:00Z",
"updated_at": "2026-05-12T21:40:51Z",
"catalog_url": "https://raw.githubusercontent.com/github/spec-kit/main/extensions/catalog.community.json",
"extensions": {
"aide": {
@@ -174,8 +174,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.8.0",
"download_url": "https://github.com/DyanGalih/spec-kit-architecture-guard/archive/refs/tags/v1.8.0.zip",
"version": "1.8.4",
"download_url": "https://github.com/DyanGalih/spec-kit-architecture-guard/archive/refs/tags/v1.8.4.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",
@@ -185,8 +185,8 @@
"speckit_version": ">=0.1.0"
},
"provides": {
"commands": 6,
"hooks": 0
"commands": 10,
"hooks": 3
},
"tags": [
"architecture",
@@ -200,7 +200,7 @@
"downloads": 0,
"stars": 0,
"created_at": "2026-05-05T07:26:00Z",
"updated_at": "2026-05-07T15:37:14Z"
"updated_at": "2026-05-11T14:58:00Z"
},
"archive": {
"name": "Archive Extension",
@@ -368,6 +368,38 @@
"created_at": "2026-04-10T00:00:00Z",
"updated_at": "2026-04-10T00:00:00Z"
},
"brownkit": {
"name": "BrownKit \u2014 Brownfield Discovery for Spec-Kit",
"id": "brownkit",
"description": "Evidence-driven capability discovery, security and QA risk assessment for existing codebases.",
"author": "Maksim Shautsou",
"version": "1.0.1",
"download_url": "https://github.com/MaksimShevtsov/BrownKit/archive/refs/tags/v1.0.1.zip",
"repository": "https://github.com/MaksimShevtsov/BrownKit",
"homepage": "https://github.com/MaksimShevtsov/BrownKit",
"documentation": "https://github.com/MaksimShevtsov/BrownKit/blob/main/README.md",
"changelog": "https://github.com/MaksimShevtsov/BrownKit/blob/main/CHANGELOG.md",
"license": "MIT",
"requires": {
"speckit_version": ">=0.1.0"
},
"provides": {
"commands": 10,
"hooks": 5
},
"tags": [
"brownfield",
"discovery",
"security",
"qa",
"capabilities"
],
"verified": false,
"downloads": 0,
"stars": 0,
"created_at": "2026-05-10T00:00:00Z",
"updated_at": "2026-05-10T00:00:00Z"
},
"bugfix": {
"name": "Bugfix Workflow",
"id": "bugfix",
@@ -467,6 +499,38 @@
"created_at": "2026-04-16T00:00:00Z",
"updated_at": "2026-04-16T00:00:00Z"
},
"changelog": {
"name": "Spec Changelog",
"id": "changelog",
"description": "Auto-generate changelogs and release notes from spec git history and requirement diffs.",
"author": "Quratulain-bilal",
"version": "1.0.0",
"download_url": "https://github.com/Quratulain-bilal/spec-kit-changelog/archive/refs/tags/v1.0.0.zip",
"repository": "https://github.com/Quratulain-bilal/spec-kit-changelog",
"homepage": "https://github.com/Quratulain-bilal/spec-kit-changelog",
"documentation": "https://github.com/Quratulain-bilal/spec-kit-changelog/blob/main/README.md",
"changelog": "https://github.com/Quratulain-bilal/spec-kit-changelog/blob/main/CHANGELOG.md",
"license": "MIT",
"requires": {
"speckit_version": ">=0.4.0"
},
"provides": {
"commands": 4,
"hooks": 1
},
"tags": [
"changelog",
"release-notes",
"documentation",
"git-history",
"notifications"
],
"verified": false,
"downloads": 0,
"stars": 0,
"created_at": "2026-04-11T18:00:00Z",
"updated_at": "2026-04-11T18:00:00Z"
},
"ci-guard": {
"name": "CI Guard",
"id": "ci-guard",
@@ -1481,8 +1545,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.8.0",
"download_url": "https://github.com/DyanGalih/spec-kit-memory-hub/archive/refs/tags/v0.8.0.zip",
"version": "0.8.5",
"download_url": "https://github.com/DyanGalih/spec-kit-memory-hub/archive/refs/tags/v0.8.5.zip",
"repository": "https://github.com/DyanGalih/spec-kit-memory-hub",
"homepage": "https://github.com/DyanGalih/spec-kit-memory-hub",
"documentation": "https://github.com/DyanGalih/spec-kit-memory-hub/blob/main/README.md",
@@ -1492,8 +1556,8 @@
"speckit_version": ">=0.2.0"
},
"provides": {
"commands": 6,
"hooks": 0
"commands": 7,
"hooks": 2
},
"tags": [
"memory",
@@ -1507,7 +1571,7 @@
"downloads": 0,
"stars": 0,
"created_at": "2026-04-23T00:00:00Z",
"updated_at": "2026-05-07T15:37:14Z"
"updated_at": "2026-05-11T14:58:00Z"
},
"memorylint": {
"name": "MemoryLint",
@@ -2215,8 +2279,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.5",
"download_url": "https://github.com/DyanGalih/spec-kit-security-review/archive/refs/tags/v1.4.5.zip",
"version": "1.5.0",
"download_url": "https://github.com/DyanGalih/spec-kit-security-review/archive/refs/tags/v1.5.0.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",
@@ -2226,8 +2290,8 @@
"speckit_version": ">=0.1.0"
},
"provides": {
"commands": 7,
"hooks": 0
"commands": 9,
"hooks": 3
},
"tags": [
"security",
@@ -2240,7 +2304,7 @@
"downloads": 0,
"stars": 0,
"created_at": "2026-04-03T03:24:03Z",
"updated_at": "2026-05-06T22:28:55Z"
"updated_at": "2026-05-11T14:58:00Z"
},
"sf": {
"name": "SFSpeckit — Salesforce Spec-Driven Development",

View File

@@ -256,6 +256,43 @@
"created_at": "2026-04-09T08:00:00Z",
"updated_at": "2026-04-27T08:00:00Z"
},
"game-narrative-writing": {
"name": "Game Narrative Writing",
"id": "game-narrative-writing",
"version": "1.0.0",
"description": "Spec-Driven Development for interactive game-narrative pre-production in video games. Authors write in a portable generic format, Twine/Sugarcube (.twee) or Ink (.ink). Covers choice-IF, visual novels, and branching dialogue. Supports Tier 1 mechanic hooks (flag, counter, inventory, timer, trust, currency, npc_state, ending_condition), multi-ending design, series carry-over variable registry, and NPC-focused character architecture.",
"author": "Andreas Daumann",
"repository": "https://github.com/adaumann/speckit-preset-game-narrative-writing",
"download_url": "https://github.com/adaumann/speckit-preset-game-narrative-writing/archive/refs/tags/v1.0.0.zip",
"homepage": "https://github.com/adaumann/speckit-preset-game-narrative-writing",
"documentation": "https://github.com/adaumann/speckit-preset-game-narrative-writing/blob/main/game-narrative-writing/README.md",
"license": "MIT",
"requires": {
"speckit_version": ">=0.5.0"
},
"provides": {
"templates": 22,
"commands": 36,
"scripts": 2
},
"tags": [
"game-writing",
"interactive-fiction",
"twine",
"ink",
"renpy",
"point-and-click",
"branching-narrative",
"choice-if",
"visual-novel",
"mechanic-hooks",
"game-narrative",
"export",
"series"
],
"created_at": "2026-05-05T08:00:00Z",
"updated_at": "2026-05-05T08:00:00Z"
},
"isaqb-architecture-governance": {
"name": "iSAQB Architecture Governance",
"id": "isaqb-architecture-governance",

View File

@@ -1,6 +1,6 @@
[project]
name = "specify-cli"
version = "0.8.8"
version = "0.8.9"
description = "Specify CLI, part of GitHub Spec Kit. A tool to bootstrap your projects for Spec-Driven Development (SDD)."
requires-python = ">=3.11"
dependencies = [

180
src/specify_cli/catalogs.py Normal file
View File

@@ -0,0 +1,180 @@
"""Shared catalog stack config primitives.
Catalog-backed features use the same local config shape and URL validation
rules. This module keeps those narrow primitives in one place while individual
catalog types keep their active source resolution, fetch, cache, and
domain-specific validation behavior.
"""
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
from typing import ClassVar
import yaml
@dataclass
class CatalogEntry:
"""Represents a single catalog source in a catalog stack."""
url: str
name: str
priority: int
install_allowed: bool
description: str = ""
class CatalogStackBase:
"""Base class for ordered catalog-source resolution.
Subclasses provide catalog-specific metadata and exception classes. Fetching
and schema validation stay in each concrete catalog because those formats
differ across integrations, extensions, presets, and workflows.
"""
ENTRY_CLASS: ClassVar[type[CatalogEntry]] = CatalogEntry
ERROR_TYPE: ClassVar[type[Exception]] = ValueError
VALIDATION_ERROR_TYPE: ClassVar[type[Exception]] = ValueError
CONFIG_FILENAME: ClassVar[str]
@classmethod
def _error(cls, message: str) -> Exception:
return cls.ERROR_TYPE(message)
@classmethod
def _validation_error(cls, message: str) -> Exception:
return cls.VALIDATION_ERROR_TYPE(message)
@classmethod
def _entry(
cls,
*,
url: str,
name: str,
priority: int,
install_allowed: bool,
description: str = "",
) -> CatalogEntry:
return cls.ENTRY_CLASS(
url=url,
name=name,
priority=priority,
install_allowed=install_allowed,
description=description,
)
@classmethod
def _validate_catalog_url(cls, url: str) -> None:
"""Validate that a catalog URL uses HTTPS, except localhost HTTP."""
from urllib.parse import urlparse
parsed = urlparse(url)
is_localhost = parsed.hostname in ("localhost", "127.0.0.1", "::1")
if parsed.scheme != "https" and not (parsed.scheme == "http" and is_localhost):
raise cls._error(
f"Catalog URL must use HTTPS (got {parsed.scheme}://). "
"HTTP is only allowed for localhost."
)
if not parsed.netloc:
raise cls._error("Catalog URL must be a valid URL with a host.")
def _load_catalog_config(self, config_path: Path) -> list[CatalogEntry] | None:
"""Load catalog stack configuration from a YAML file.
Returns ``None`` when the file does not exist. Existing files fail
closed when they are malformed, empty, or contain no usable URLs.
"""
if not config_path.exists():
return None
try:
data = yaml.safe_load(config_path.read_text(encoding="utf-8"))
except (yaml.YAMLError, OSError, UnicodeError) as exc:
raise self._validation_error(
f"Failed to read catalog config {config_path}: {exc}"
) from exc
if data is None:
data = {}
if not isinstance(data, dict):
raise self._validation_error(
f"Invalid catalog config {config_path}: expected a YAML mapping at the root"
)
catalogs_data = data.get("catalogs", [])
if not isinstance(catalogs_data, list):
raise self._validation_error(
f"Invalid catalog config {config_path}: 'catalogs' must be a list, "
f"got {type(catalogs_data).__name__}"
)
if not catalogs_data:
raise self._validation_error(
f"Catalog config {config_path} exists but contains no 'catalogs' entries. "
f"Remove the file to use built-in defaults, or add valid catalog entries."
)
entries: list[CatalogEntry] = []
skipped: list[int] = []
for idx, item in enumerate(catalogs_data):
if not isinstance(item, dict):
raise self._validation_error(
f"Invalid catalog config {config_path}: catalog entry at index {idx}: "
f"expected a mapping, got {type(item).__name__}"
)
url = str(item.get("url", "")).strip()
if not url:
skipped.append(idx)
continue
try:
self._validate_catalog_url(url)
except self.ERROR_TYPE as exc:
raise self._validation_error(
f"Invalid catalog URL in {config_path} at index {idx}: {exc}"
) from exc
raw_priority = item.get("priority", idx + 1)
if isinstance(raw_priority, bool):
raise self._validation_error(
f"Invalid catalog config {config_path}: "
f"Invalid priority for catalog '{item.get('name', idx + 1)}': "
f"expected integer, got {raw_priority!r}"
)
try:
priority = int(raw_priority)
except (TypeError, ValueError):
raise self._validation_error(
f"Invalid catalog config {config_path}: "
f"Invalid priority for catalog '{item.get('name', idx + 1)}': "
f"expected integer, got {raw_priority!r}"
)
raw_install = item.get("install_allowed", False)
if isinstance(raw_install, str):
install_allowed = raw_install.strip().lower() in ("true", "yes", "1")
else:
install_allowed = bool(raw_install)
raw_name = item.get("name")
name = str(raw_name).strip() if raw_name is not None else ""
if not name:
name = f"catalog-{len(entries) + 1}"
entries.append(
self._entry(
url=url,
name=name,
priority=priority,
install_allowed=install_allowed,
description=str(item.get("description", "")),
)
)
entries.sort(key=lambda e: e.priority)
if not entries:
raise self._validation_error(
f"Catalog config {config_path} contains {len(catalogs_data)} "
f"entries but none have valid URLs (entries at indices {skipped} "
f"were skipped). Each catalog entry must have a 'url' field."
)
return entries

View File

@@ -21,6 +21,8 @@ from typing import Any, Dict, List, Optional, Tuple
import yaml
from packaging import version as pkg_version
from ..catalogs import CatalogEntry, CatalogStackBase
# ---------------------------------------------------------------------------
# Errors
@@ -43,21 +45,15 @@ class IntegrationDescriptorError(Exception):
# ---------------------------------------------------------------------------
@dataclass
class IntegrationCatalogEntry:
class IntegrationCatalogEntry(CatalogEntry):
"""Represents a single catalog source in the catalog stack."""
url: str
name: str
priority: int
install_allowed: bool
description: str = ""
# ---------------------------------------------------------------------------
# IntegrationCatalog
# ---------------------------------------------------------------------------
class IntegrationCatalog:
class IntegrationCatalog(CatalogStackBase):
"""Manages integration catalog fetching, caching, and searching."""
DEFAULT_CATALOG_URL = (
@@ -67,136 +63,15 @@ class IntegrationCatalog:
"https://raw.githubusercontent.com/github/spec-kit/main/integrations/catalog.community.json"
)
CACHE_DURATION = 3600 # 1 hour
CONFIG_FILENAME = "integration-catalogs.yml"
ENTRY_CLASS = IntegrationCatalogEntry
ERROR_TYPE = IntegrationCatalogError
VALIDATION_ERROR_TYPE = IntegrationValidationError
def __init__(self, project_root: Path) -> None:
self.project_root = project_root
self.cache_dir = project_root / ".specify" / "integrations" / ".cache"
# -- URL validation ---------------------------------------------------
@staticmethod
def _validate_catalog_url(url: str) -> None:
from urllib.parse import urlparse
parsed = urlparse(url)
is_localhost = parsed.hostname in ("localhost", "127.0.0.1", "::1")
if parsed.scheme != "https" and not (parsed.scheme == "http" and is_localhost):
raise IntegrationCatalogError(
f"Catalog URL must use HTTPS (got {parsed.scheme}://). "
"HTTP is only allowed for localhost."
)
if not parsed.netloc:
raise IntegrationCatalogError(
"Catalog URL must be a valid URL with a host."
)
# -- Catalog stack ----------------------------------------------------
def _load_catalog_config(
self, config_path: Path
) -> Optional[List[IntegrationCatalogEntry]]:
"""Load catalog stack from a YAML file.
Returns None when the file does not exist.
Raises:
IntegrationValidationError: on any local-config / YAML problem
(parse failures, wrong shape, missing/invalid fields,
invalid catalog URLs, etc.). This is a subclass of
:class:`IntegrationCatalogError`, so any caller that already
catches ``IntegrationCatalogError`` keeps working — but
callers that want to distinguish *local config* problems
from *remote/network* problems can match the subclass.
"""
if not config_path.exists():
return None
try:
data = yaml.safe_load(config_path.read_text(encoding="utf-8"))
except (yaml.YAMLError, OSError, UnicodeError) as exc:
raise IntegrationValidationError(
f"Failed to read catalog config {config_path}: {exc}"
) from exc
if data is None:
data = {}
if not isinstance(data, dict):
raise IntegrationValidationError(
f"Invalid catalog config {config_path}: expected a YAML mapping at the root"
)
catalogs_data = data.get("catalogs", [])
if not isinstance(catalogs_data, list):
raise IntegrationValidationError(
f"Invalid catalog config {config_path}: 'catalogs' must be a list, "
f"got {type(catalogs_data).__name__}"
)
if not catalogs_data:
raise IntegrationValidationError(
f"Catalog config {config_path} exists but contains no 'catalogs' entries. "
f"Remove the file to use built-in defaults, or add valid catalog entries."
)
entries: List[IntegrationCatalogEntry] = []
skipped: List[int] = []
for idx, item in enumerate(catalogs_data):
if not isinstance(item, dict):
raise IntegrationValidationError(
f"Invalid catalog config {config_path}: catalog entry at index {idx}: "
f"expected a mapping, got {type(item).__name__}"
)
url = str(item.get("url", "")).strip()
if not url:
skipped.append(idx)
continue
try:
self._validate_catalog_url(url)
except IntegrationCatalogError as exc:
# ``_validate_catalog_url`` raises the base class for direct
# callers (e.g. ``add_catalog`` validating user input); when
# the bad URL came from a local config file, surface it as a
# validation error so CLI handlers can route it accordingly.
raise IntegrationValidationError(
f"Invalid catalog URL in {config_path} at index {idx}: {exc}"
) from exc
raw_priority = item.get("priority", idx + 1)
if isinstance(raw_priority, bool):
raise IntegrationValidationError(
f"Invalid catalog config {config_path}: "
f"Invalid priority for catalog '{item.get('name', idx + 1)}': "
f"expected integer, got {raw_priority!r}"
)
try:
priority = int(raw_priority)
except (TypeError, ValueError):
raise IntegrationValidationError(
f"Invalid catalog config {config_path}: "
f"Invalid priority for catalog '{item.get('name', idx + 1)}': "
f"expected integer, got {raw_priority!r}"
)
raw_install = item.get("install_allowed", False)
if isinstance(raw_install, str):
install_allowed = raw_install.strip().lower() in ("true", "yes", "1")
else:
install_allowed = bool(raw_install)
raw_name = item.get("name")
name = str(raw_name).strip() if raw_name is not None else ""
if not name:
name = f"catalog-{len(entries) + 1}"
entries.append(
IntegrationCatalogEntry(
url=url,
name=name,
priority=priority,
install_allowed=install_allowed,
description=str(item.get("description", "")),
)
)
entries.sort(key=lambda e: e.priority)
if not entries:
raise IntegrationValidationError(
f"Catalog config {config_path} contains {len(catalogs_data)} "
f"entries but none have valid URLs (entries at indices {skipped} "
f"were skipped). Each catalog entry must have a 'url' field."
)
return entries
def get_active_catalogs(self) -> List[IntegrationCatalogEntry]:
"""Return the ordered list of active integration catalogs.
@@ -444,8 +319,6 @@ class IntegrationCatalog:
# -- Catalog-source management ----------------------------------------
CONFIG_FILENAME = "integration-catalogs.yml"
def get_catalog_configs(self) -> List[Dict[str, Any]]:
"""Return the active catalog stack as a list of dicts.

View File

@@ -3,6 +3,14 @@
from ..base import MarkdownIntegration
# Kiro CLI file-based prompts do NOT support any argument-substitution syntax,
# so a raw "$ARGUMENTS" token would reach the model verbatim and break the
# prompt (issue #1926, kirodotdev/Kiro#4141). Use a prose fallback so the
# rendered prompt instructs the model to take its argument from the user's
# next message.
_KIRO_ARG_FALLBACK = "(the user will provide the argument in this conversation)"
class KiroCliIntegration(MarkdownIntegration):
key = "kiro-cli"
config = {
@@ -15,7 +23,7 @@ class KiroCliIntegration(MarkdownIntegration):
registrar_config = {
"dir": ".kiro/prompts",
"format": "markdown",
"args": "$ARGUMENTS",
"args": _KIRO_ARG_FALLBACK,
"extension": ".md",
}
context_file = "AGENTS.md"

View File

@@ -1,6 +1,7 @@
# Implementation Plan: [FEATURE]
**Branch**: `[###-feature-name]` | **Date**: [DATE] | **Spec**: [link]
**Input**: Feature specification from `/specs/[###-feature-name]/spec.md`
**Note**: This template is filled in by the `__SPECKIT_COMMAND_PLAN__` command. See `.specify/templates/plan-template.md` for the execution workflow.
@@ -17,14 +18,22 @@
the iteration process.
-->
**Language/Version**: [e.g., Python 3.11, Swift 5.9, Rust 1.75 or NEEDS CLARIFICATION]
**Primary Dependencies**: [e.g., FastAPI, UIKit, LLVM or NEEDS CLARIFICATION]
**Storage**: [if applicable, e.g., PostgreSQL, CoreData, files or N/A]
**Testing**: [e.g., pytest, XCTest, cargo test or NEEDS CLARIFICATION]
**Language/Version**: [e.g., Python 3.11, Swift 5.9, Rust 1.75 or NEEDS CLARIFICATION]
**Primary Dependencies**: [e.g., FastAPI, UIKit, LLVM or NEEDS CLARIFICATION]
**Storage**: [if applicable, e.g., PostgreSQL, CoreData, files or N/A]
**Testing**: [e.g., pytest, XCTest, cargo test or NEEDS CLARIFICATION]
**Target Platform**: [e.g., Linux server, iOS 15+, WASM or NEEDS CLARIFICATION]
**Project Type**: [e.g., library/cli/web-service/mobile-app/compiler/desktop-app or NEEDS CLARIFICATION]
**Performance Goals**: [domain-specific, e.g., 1000 req/s, 10k lines/sec, 60 fps or NEEDS CLARIFICATION]
**Constraints**: [domain-specific, e.g., <200ms p95, <100MB memory, offline-capable or NEEDS CLARIFICATION]
**Project Type**: [e.g., library/cli/web-service/mobile-app/compiler/desktop-app or NEEDS CLARIFICATION]
**Performance Goals**: [domain-specific, e.g., 1000 req/s, 10k lines/sec, 60 fps or NEEDS CLARIFICATION]
**Constraints**: [domain-specific, e.g., <200ms p95, <100MB memory, offline-capable or NEEDS CLARIFICATION]
**Scale/Scope**: [domain-specific, e.g., 10k users, 1M LOC, 50 screens or NEEDS CLARIFICATION]
## Constitution Check

View File

@@ -1,8 +1,11 @@
# Feature Specification: [FEATURE NAME]
**Feature Branch**: `[###-feature-name]`
**Created**: [DATE]
**Status**: Draft
**Feature Branch**: `[###-feature-name]`
**Created**: [DATE]
**Status**: Draft
**Input**: User description: "$ARGUMENTS"
## User Scenarios & Testing *(mandatory)*
@@ -11,7 +14,7 @@
IMPORTANT: User stories should be PRIORITIZED as user journeys ordered by importance.
Each user story/journey must be INDEPENDENTLY TESTABLE - meaning if you implement just ONE of them,
you should still have a viable MVP (Minimum Viable Product) that delivers value.
Assign priorities (P1, P2, P3, etc.) to each story, where P1 is the most critical.
Think of each story as a standalone slice of functionality that can be:
- Developed independently
@@ -85,7 +88,7 @@
### Functional Requirements
- **FR-001**: System MUST [specific capability, e.g., "allow users to create accounts"]
- **FR-002**: System MUST [specific capability, e.g., "validate email addresses"]
- **FR-002**: System MUST [specific capability, e.g., "validate email addresses"]
- **FR-003**: Users MUST be able to [key interaction, e.g., "reset their password"]
- **FR-004**: System MUST [data requirement, e.g., "persist user preferences"]
- **FR-005**: System MUST [behavior, e.g., "log all security events"]

View File

@@ -6,6 +6,7 @@ description: "Task list template for feature implementation"
# Tasks: [FEATURE NAME]
**Input**: Design documents from `/specs/[###-feature-name]/`
**Prerequisites**: plan.md (required), spec.md (required for user stories), research.md, data-model.md, contracts/
**Tests**: The examples below include test tasks. Tests are OPTIONAL - only include them if explicitly requested in the feature specification.
@@ -25,21 +26,21 @@ description: "Task list template for feature implementation"
- **Mobile**: `api/src/`, `ios/src/` or `android/src/`
- Paths shown below assume single project - adjust based on plan.md structure
<!--
<!--
============================================================================
IMPORTANT: The tasks below are SAMPLE TASKS for illustration purposes only.
The __SPECKIT_COMMAND_TASKS__ command MUST replace these with actual tasks based on:
- User stories from spec.md (with their priorities P1, P2, P3...)
- Feature requirements from plan.md
- Entities from data-model.md
- Endpoints from contracts/
Tasks MUST be organized by user story so each story can be:
- Implemented independently
- Tested independently
- Delivered as an MVP increment
DO NOT keep these sample tasks in the generated tasks.md file.
============================================================================
-->

View File

@@ -1,10 +1,41 @@
"""Tests for KiroCliIntegration."""
import os
import re
from specify_cli.integrations import get_integration
from specify_cli.integrations.kiro_cli import _KIRO_ARG_FALLBACK
from specify_cli.integrations.manifest import IntegrationManifest
from .test_integration_base_markdown import MarkdownIntegrationTests
# Regex shapes that indicate a value is a placeholder token, not prose.
# Covers Bash ($VAR, ${VAR}, ${VAR:-default}), Mustache/Handlebars/Jinja
# ({{var}}, {{{var}}}), Liquid/Jinja control ({% ... %}), Python str.format /
# .NET ({var}, {0}), angle-bracket (<var>), and Windows-style (%VAR%).
# Anchored to the FULL STRING so legitimate prose mentioning a placeholder
# (e.g. "the {{magic}} of placeholders") is not flagged. The Liquid pattern
# is anchored to the START so multi-tag templates fire while mid-sentence
# {%-quotation does not.
_PLACEHOLDER_TOKEN_PATTERNS = (
re.compile(r"^\$\w+$"), # $ARGUMENTS, $args
re.compile(r"^\$\{\w+(?:[:\-+?][^}]*)?\}$"), # ${ARGS}, ${ARGS:-default}
re.compile(r"^\{\{\{?\s*\w+(\s*[|.][^}]*)?\s*\}?\}\}$"), # {{var}} {{{var}}} {{x|y}}
re.compile(r"^\{%"), # {% if x %}{{ x }}{% endif %}
re.compile(r"^<\w+>$"), # <args>
re.compile(r"^%\w+%$"), # %USERNAME%
re.compile(r"^\{(?:\d+|[a-zA-Z_]\w*)(?:[.\[][^}]*)?(?:![rsa])?(?::[^}]*)?\}$"), # {0}, {var}, {0:>5}
)
def _looks_like_placeholder_token(value: str) -> bool:
"""Return True if *value* matches a known placeholder-token shape."""
if not value:
return False
return any(p.search(value) for p in _PLACEHOLDER_TOKEN_PATTERNS)
class TestKiroCliIntegration(MarkdownIntegrationTests):
KEY = "kiro-cli"
FOLDER = ".kiro/"
@@ -12,6 +43,85 @@ class TestKiroCliIntegration(MarkdownIntegrationTests):
REGISTRAR_DIR = ".kiro/prompts"
CONTEXT_FILE = "AGENTS.md"
def test_registrar_config(self):
"""Override base assertion: kiro-cli uses a prose fallback for args
because Kiro CLI file-based prompts do not natively substitute
``$ARGUMENTS`` (see issue #1926 / kirodotdev/Kiro#4141). The
regression-guard load is carried by the two layer tests below
(exact-fallback + placeholder-shape rejection)."""
i = get_integration(self.KEY)
assert i.registrar_config["dir"] == self.REGISTRAR_DIR
assert i.registrar_config["format"] == "markdown"
assert i.registrar_config["extension"] == ".md"
def test_registrar_config_args_is_exact_prose_fallback(self):
"""Layer 1 — pin the exact fallback so wording drift requires a
deliberate paired commit (production constant + test update)."""
i = get_integration(self.KEY)
assert i.registrar_config["args"] == _KIRO_ARG_FALLBACK, (
f"args drifted from the pinned fallback constant. "
f"Got: {i.registrar_config['args']!r}; expected: {_KIRO_ARG_FALLBACK!r}. "
f"If the wording change is intentional, update _KIRO_ARG_FALLBACK and "
f"this test together."
)
def test_registrar_config_args_does_not_look_like_a_placeholder_token(self):
"""Layer 2 — independent regression guard: even if someone bypasses
layer-1 by changing both constant and test, the value still must not
look like ANY placeholder token shape ($X, ${X}, {{X}}, <X>, %X%, {0},
{% %}). Catches the class of regression Copilot called out: a swap
from $ARGUMENTS to $INPUT or {{userMessage}} would fail this test
even if it accidentally passed layer 1."""
i = get_integration(self.KEY)
args = i.registrar_config["args"]
assert not _looks_like_placeholder_token(args), (
f"registrar_config['args'] = {args!r} matches a known placeholder-"
f"token shape — Kiro CLI does not substitute placeholders so this "
f"would reach the model verbatim and break the prompt (issue #1926). "
f"Use a prose fallback instead."
)
def test_rendered_prompts_do_not_contain_raw_arguments(self, tmp_path):
"""Rendered Kiro prompt files must NOT contain the raw ``$ARGUMENTS``
token — Kiro CLI does not substitute it, so the literal would reach
the model and break the prompt (issue #1926)."""
integration = get_integration(self.KEY)
manifest = IntegrationManifest(self.KEY, tmp_path)
integration.setup(tmp_path, manifest, script_type="sh")
prompts_dir = tmp_path / self.REGISTRAR_DIR
rendered = list(prompts_dir.glob("*.md"))
assert rendered, "expected at least one rendered prompt file"
offenders = [
p.name for p in rendered if "$ARGUMENTS" in p.read_text(encoding="utf-8")
]
assert offenders == [], (
f"these rendered prompts still contain the raw $ARGUMENTS token: {offenders}"
)
def test_rendered_prompts_contain_kiro_arg_placeholder(self, tmp_path):
"""The chosen kiro-cli args fallback string must end up in at least
one rendered prompt (proves substitution actually fired, not just
that $ARGUMENTS was removed). Imports the fallback constant directly
instead of reading the field back so the test stays independent of
the integration's own config — even if the registrar_config['args']
regresses, this test still verifies the FALLBACK STRING is in the
rendered output."""
integration = get_integration(self.KEY)
manifest = IntegrationManifest(self.KEY, tmp_path)
integration.setup(tmp_path, manifest, script_type="sh")
expected = _KIRO_ARG_FALLBACK
prompts_dir = tmp_path / self.REGISTRAR_DIR
contents = "\n".join(
p.read_text(encoding="utf-8") for p in prompts_dir.glob("*.md")
)
assert expected in contents, (
f"none of the rendered prompts contain the configured args fallback "
f"({expected!r})"
)
class TestKiroAlias:
"""--ai kiro alias normalizes to kiro-cli and auto-promotes."""

View File

@@ -1949,7 +1949,16 @@ def install_self_test_preset(manager: PresetManager, speckit_version: str = "0.1
class TestSelfTestPreset:
"""Tests using the self-test preset that ships with the repo."""
"""Tests using the self-test preset that ships with the repo.
The self-test preset ships a wrap-strategy command (``speckit.wrap-test``)
without a corresponding core base layer; reconciliation deliberately
surfaces a UserWarning in that case. Tests install via
``install_self_test_preset`` (defined above), which scopes a narrow
``warnings.filterwarnings`` block to that specific message and
``UserWarning`` category — so the expected warning stays quiet without
masking unrelated warnings or real reconciliation failures.
"""
def test_self_test_preset_exists(self):
"""Verify the self-test preset directory and manifest exist."""
@@ -2237,7 +2246,12 @@ class TestInitOptions:
class TestPresetSkills:
"""Tests for preset skill registration and unregistration."""
"""Tests for preset skill registration and unregistration.
Tests that install the self-test preset use ``install_self_test_preset``
which scopes a narrow filter to the expected wrap-strategy warning.
Reconciliation failures remain audible so real regressions surface.
"""
def _write_init_options(self, project_dir, ai="claude", ai_skills=True, script="sh"):
from specify_cli import save_init_options