Compare commits

...

52 Commits

Author SHA1 Message Date
github-actions[bot]
2656ee1df7 chore: bump version to 0.3.0 2026-03-13 20:11:58 +00:00
Copilot
69ee7a836e feat(presets): Pluggable preset system with catalog, resolver, and skills propagation (#1787)
* Initial plan

* feat(templates): add pluggable template system with packs, catalog, resolver, and CLI commands

Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com>

* test(templates): add comprehensive unit tests for template pack system

Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com>

* feat(presets): pluggable preset system with template/command overrides, catalog, and resolver

- Rename 'template packs' to 'presets' to avoid naming collision with core templates
- PresetManifest, PresetRegistry, PresetManager, PresetCatalog, PresetResolver in presets.py
- Extract CommandRegistrar to agents.py as shared infrastructure
- CLI: specify preset list/add/remove/search/resolve/info
- CLI: specify preset catalog list/add/remove
- --preset option on specify init
- Priority-based preset stacking (--priority, lower = higher precedence)
- Command overrides registered into all detected agent directories (17+ agents)
- Extension command safety: skip registration if target extension not installed
- Multi-catalog support: env var, project config, user config, built-in defaults
- resolve_template() / Resolve-Template in bash/PowerShell scripts
- Self-test preset: overrides all 6 core templates + 1 command
- Scaffold with 4 examples: core/extension template and command overrides
- Preset catalog (catalog.json, catalog.community.json)
- Documentation: README.md, ARCHITECTURE.md, PUBLISHING.md
- 110 preset tests, 253 total tests passing

* feat(presets): propagate command overrides to skills via init-options

- Add save_init_options() / load_init_options() helpers that persist
  CLI flags from 'specify init' to .specify/init-options.json
- PresetManager._register_skills() overwrites SKILL.md files when
  --ai-skills was used during init and corresponding skill dirs exist
- PresetManager._unregister_skills() restores core template content
  on preset removal
- registered_skills stored in preset registry metadata
- 8 new tests covering skill override, skip conditions, and restore

* fix: address PR check failures (ruff F541, CodeQL URL substring)

- Remove extraneous f-prefix from two f-strings without placeholders
- Replace substring URL check in test with startswith/endswith assertions
  to satisfy CodeQL incomplete URL substring sanitization rule

* fix: address Copilot PR review comments

- Move save_init_options() before preset install so skills propagation
  works during 'specify init --preset --ai-skills'
- Clean up downloaded ZIP after successful preset install during init
- Validate --from URL scheme (require HTTPS, HTTP only for localhost)
- Expose unregister_commands() on extensions.py CommandRegistrar wrapper
  instead of reaching into private _registrar field
- Use _get_merged_packs() for search() and get_pack_info() so all
  active catalogs are searched, not just the highest-priority one
- Fix fetch_catalog() cache to verify cached URL matches current URL
- Fix PresetResolver: script resolution uses .sh extension, consistent
  file extensions throughout resolve(), and resolve_with_source()
  delegates to resolve() to honor template_type parameter
- Fix bash common.sh: fall through to directory scan when python3
  returns empty preset list
- Fix PowerShell Resolve-Template: filter out dot-folders and sort
  extensions deterministically

* fix: narrow empty except blocks and add explanatory comments

* fix: address Copilot PR review comments (round 2)

- Fix init --preset error masking: distinguish "not found" from real errors
- Fix bash resolve_template: skip hidden dirs in extensions (match Python/PS)
- Fix temp dir leaks in tests: use temp_dir fixture instead of mkdtemp
- Fix self-test catalog entry: add note that it's local-only (no download_url)
- Fix Windows path issue in resolve_with_source: use Path.relative_to()
- Fix skill restore path: use project's .specify/templates/commands/ not source tree
- Add encoding="utf-8" to all file read/write in agents.py
- Update test to set up core command templates for skill restoration

* fix: remove self-test from catalog.json (local-only preset)

* fix: address Copilot PR review comments (round 3)

- Fix PS Resolve-Template fallback to skip dot-prefixed dirs (.cache)
- Rename _catalog to _catalog_name for consistency with extension system
- Enforce install_allowed policy in CLI preset add and download_pack()
- Fix shell injection: pass registry path via env var instead of string interpolation

* fix: correct PresetError docstring from template to preset

* Removed CHANGELOG requirement

* Applying review recommendations

* Applying review recommendations

* Applying review recommendations

* Applying review recommendations

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com>
2026-03-13 15:09:14 -05:00
eason
c883952b43 fix: match 'Last updated' timestamp with or without bold markers (#1836)
The template outputs plain text `Last updated: [DATE]` but both
update-agent-context scripts only matched `**Last updated**: [DATE]`
(bold Markdown). Make the bold markers optional in the regex so the
timestamp is refreshed regardless of formatting.

Co-authored-by: easonysliu <easonysliu@tencent.com>
Co-authored-by: Claude (claude-opus-4-6) <noreply@anthropic.com>
2026-03-13 11:46:21 -05:00
KhawarHabibKhan
b9c1a1c7bb Add specify doctor command for project health diagnostics (#1828)
* Add specify doctor command for project health diagnostics

* Add tests for specify doctor command

* Document specify doctor command in README

* Revert "Document specify doctor command in README"

This reverts commit c1cfd06129.

* Revert "Add tests for specify doctor command"

This reverts commit 65e12fb62b.

* Revert "Add specify doctor command for project health diagnostics"

This reverts commit d5bd93248a.

* Add doctor extension to community catalog

* 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 Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-03-13 10:50:07 -05:00
Pierluigi Lenoci
46bc65b1ce fix: harden bash scripts against shell injection and improve robustness (#1809)
- Replace eval of unquoted get_feature_paths output with safe pattern:
  capture into variable, check return code, then eval quoted result
- Use printf '%q' in get_feature_paths to safely emit shell assignments,
  preventing injection via paths containing quotes or metacharacters
- Add json_escape() helper for printf JSON fallback paths, handling
  backslash, double-quote, and control characters when jq is unavailable
- Use jq -cn for safe JSON construction with proper escaping when
  available, with printf + json_escape() fallback
- Replace declare -A (bash 4+) with indexed array for bash 3.2
  compatibility (macOS default)
- Use inline command -v jq check in create-new-feature.sh since it
  does not source common.sh
- Guard trap cleanup against re-entrant invocation by disarming traps
  at entry
- Use printf '%q' for shell-escaped branch names in user-facing output
- Return failure instead of silently returning wrong path on ambiguous
  spec directory matches
- Deduplicate agent file updates via realpath to prevent multiple writes
  to the same file (e.g. AGENTS.md aliased by multiple variables)
2026-03-13 10:47:17 -05:00
Pierluigi Lenoci
017e1c4c2f fix: clean up command templates (specify, analyze) (#1810)
* fix: clean up command templates (specify, analyze)

- specify.md: fix self-referential step number (step 6c → proceed to step 7)
- specify.md: remove empty "General Guidelines" heading before "Quick Guidelines"
- analyze.md: deduplicate {ARGS} — already present in "User Input" section at top

* fix: restore ## Context heading in analyze template

Address PR review feedback from @dhilipkumars: keep the ## Context
markdown heading to preserve structural hierarchy for LLM parsing.
2026-03-13 08:21:55 -05:00
fuyongde
7562664fd1 fix: migrate Qwen Code CLI from TOML to Markdown format (#1589) (#1730)
* fix: migrate Qwen Code CLI from TOML to Markdown format (#1589)

Qwen Code CLI v0.10.0 deprecated TOML format and fully switched to
Markdown as the core format for configuration and interaction files.

- Update create-release-packages.sh: generate .md files with $ARGUMENTS
  instead of .toml files with {{args}} for qwen agent
- Update create-release-packages.ps1: same change for PowerShell script
- Update AGENTS.md: reflect Qwen's new Markdown format in docs and
  remove Qwen from TOML format section
- Update tests/test_ai_skills.py: add commands_dir_qwen fixture and
  tests covering Markdown-format skills installation for Qwen

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

* fix: update CommandRegistrar qwen config to Markdown format

extensions.py CommandRegistrar.AGENT_CONFIGS['qwen'] was still set to
TOML format, causing `specify extension` to write .toml files into
.qwen/commands, conflicting with Qwen Code CLI v0.10.0+ expectations.

- Change qwen format from toml to markdown
- Change qwen args from {{args}} to $ARGUMENTS
- Change qwen extension from .toml to .md
- Add test to assert qwen config is Markdown format

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

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 07:43:14 -05:00
Dhilip
976c9981a4 fix(cli): deprecate explicit command support for agy (#1798) (#1808)
* fix(cli): deprecate explicit command support for agy (#1798)

* docs(cli): add tests and docs for agy deprecation (#1798)

* fix: address review comments for agy deprecation

* fix: address round 2 review comments for agy deprecation

* fix: address round 3 review comments for agy deprecation

* fix: address round 4 review comments for agy deprecation

* fix: address round 5 review comments for agy deprecation

* docs: add inline contextual comments to explain agy deprecation

* docs: clarify historical context in agy deprecation docstring

* fix: correct skills path in deprecation comment and make test mock fully deterministic
2026-03-13 07:35:30 -05:00
Dhilip
d3fc056743 Add /selftest.extension core extension to test other extensions (#1758)
* test(commands): create extension-commands LLM playground sandbox

* update(tests): format LLM evaluation as an automated test runner

* test(commands): map extension-commands python script with timestamps

* test(commands): map extension-commands python script with timestamps

* test(commands): update TESTING.md to evaluate discovery, lint, and deploy explicitly

* test(commands): simplify execution expectations and add timestamp calculation

* fix(tests): address copilot review comments on prompt formatting and relative paths

* fix(tests): resolve copilot PR feedback regarding extension schema structure and argparse mutually exclusive groups

* feat(extensions): add core selftest utility and migrate away from manual tests sandbox

* fix(selftest): update command name array to match spec-kit validation schema

* fix(selftest): wrap arguments in quotes to support multi-word extension names

* update the command to be more meaningful

* fix: if the extension is discovery only, it should not be installable

* Address review comments for selftest documentation

* address review comments

* address review comments

* Update extensions/selftest/commands/selftest.md

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

---------

Co-authored-by: Manfred Riem <15701806+mnriem@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-13 07:26:01 -05:00
Michal Bachorik
58ce653908 feat(extensions): Quality of life improvements for RFC-aligned catalog integration (#1776)
* feat(extensions): implement automatic updates with atomic backup/restore

- Implement automatic extension updates with download from catalog
- Add comprehensive backup/restore mechanism for failed updates:
  - Backup registry entry before update
  - Backup extension directory
  - Backup command files for all AI agents
  - Backup hooks from extensions.yml
- Add extension ID verification after install
- Add KeyboardInterrupt handling to allow clean cancellation
- Fix enable/disable to preserve installed_at timestamp by using
  direct registry manipulation instead of registry.add()
- Add rollback on any update failure with command file,
  hook, and registry restoration

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(extensions): comprehensive name resolution and error handling improvements

- Add shared _resolve_installed_extension helper for ID/display name resolution
  with proper ambiguous name handling (shows table of matches)
- Add _resolve_catalog_extension helper for catalog lookups by ID or display name
- Update enable/disable/update/remove commands to use name resolution helpers
- Fix extension_info to handle catalog errors gracefully:
  - Fallback to local installed info when catalog unavailable
  - Distinguish "catalog unavailable" from "not found in catalog"
  - Support display name lookup for both installed and catalog extensions
- Use resolved display names in all status messages for consistency
- Extract _print_extension_info helper for DRY catalog info printing

Addresses reviewer feedback:
- Ambiguous name handling in enable/disable/update
- Catalog error fallback for installed extensions
- UX message clarity (catalog unavailable vs not found)
- Resolved ID in status messages

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(extensions): properly detect ambiguous names in extension_info

The extension_info command was breaking on the first name match without
checking for ambiguity. This fix separates ID matching from name matching
and checks for ambiguity before selecting a match, consistent with the
_resolve_installed_extension() helper used by other commands.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor(extensions): add public update() method to ExtensionRegistry

Add a proper public API for updating registry metadata while preserving
installed_at timestamp, instead of directly mutating internal registry
data and calling private _save() method.

Changes:
- Add ExtensionRegistry.update() method that preserves installed_at
- Update enable/disable commands to use registry.update()
- Update rollback logic to use registry.update()

This decouples the CLI from registry internals and maintains proper
encapsulation.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(extensions): safely access optional author field in extension_info

ExtensionManifest doesn't expose an author property - the author field
is optional in extension.yml and stored in data["extension"]["author"].
Use safe dict access to avoid AttributeError.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(extensions): address multiple reviewer comments

- ExtensionRegistry.update() now preserves original installed_at timestamp
- Add ExtensionRegistry.restore() for rollback (entry was removed)
- Clean up wrongly installed extension on ID mismatch before rollback
- Remove unused catalog_error parameter from _print_extension_info()

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(extensions): check _install_allowed for updates, preserve backup on failed rollback

- Skip automatic updates for extensions from catalogs with install_allowed=false
- Only delete backup directory on successful rollback, preserve it on failure
  for manual recovery

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(extensions): address reviewer feedback on update/rollback logic

- Hook rollback: handle empty backup_hooks by checking `is not None`
  instead of truthiness (falsy empty dict would skip hook cleanup)
- extension_info: use resolved_installed_id for catalog lookup when
  extension was found by display name (prevents wrong catalog match)
- Rollback: always remove extension dir first, then restore if backup
  exists (handles case when no original dir existed before update)
- Validate extension ID from ZIP before installing, not after
  (avoids side effects of installing wrong extension before rollback)
- Preserve enabled state during updates: re-apply disabled state and
  hook enabled flags after successful update
- Optimize _resolve_catalog_extension: pass query to catalog.search()
  instead of fetching all extensions
- update() now merges metadata with existing entry instead of replacing
  (preserves fields like registered_commands when only updating enabled)
- Add tests for ExtensionRegistry.update() and restore() methods:
  - test_update_preserves_installed_at
  - test_update_merges_with_existing
  - test_update_raises_for_missing_extension
  - test_restore_overwrites_completely
  - test_restore_can_recreate_removed_entry

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* docs(extensions): update RFC to reflect implemented status

- Change status from "Draft" to "Implemented"
- Update all Implementation Phases to show completed items
- Add new features implemented beyond original RFC:
  - Display name resolution for all commands
  - Ambiguous name handling with tables
  - Atomic update with rollback
  - Pre-install ID validation
  - Enabled state preservation
  - Registry update/restore methods
  - Catalog error fallback
  - _install_allowed flag
  - Cache invalidation
- Convert Open Questions to Resolved Questions with decisions
- Add remaining Open Questions (sandboxing, signatures) as future work
- Fix table of contents links

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(extensions): address third round of PR review comments

- Refactor extension_info to use _resolve_installed_extension() helper
  with new allow_not_found parameter instead of duplicating resolution logic
- Fix rollback hook restoration to not create empty hooks: {} in config
  when original config had no hooks section
- Fix ZIP pre-validation to handle nested extension.yml files (GitHub
  auto-generated ZIPs have structure like repo-name-branch/extension.yml)
- Replace unused installed_manifest variable with _ placeholder
- Add display name to update status messages for better UX

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(extensions): address fourth round of PR review comments

Rollback fixes:
- Preserve installed_at timestamp after successful update (was reset by
  install_from_zip calling registry.add)
- Fix rollback to only delete extension_dir if backup exists (avoids
  destroying valid installation when failure happens before modification)
- Fix rollback to remove NEW command files created by failed install
  (files that weren't in original backup are now cleaned up)
- Fix rollback to delete hooks key entirely when backup_hooks is None
  (original config had no hooks key, so restore should remove it)

Cross-command consistency fix:
- Add display name resolution to `extension add` command using
  _resolve_catalog_extension() helper (was only in `extension info`)
- Use resolved extension ID for download_extension() call, not original
  argument which may be a display name

Security fix (fail-closed):
- Malformed catalog config (empty/missing URLs) now raises ValidationError
  instead of silently falling back to built-in catalogs

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(lint): address ruff linting errors and registry.update() semantics

- Remove unused import ExtensionError in extension_info
- Remove extraneous f-prefix from strings without placeholders
- Use registry.restore() instead of registry.update() for installed_at
  preservation (update() always preserves existing installed_at, ignoring
  our override)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: iamaeroplane <michal.bachorik@gmail.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-13 07:23:37 -05:00
Manfred Riem
82f8a13f83 Add Java brownfield walkthrough to community walkthroughs (#1820) 2026-03-12 14:02:02 -05:00
Manfred Riem
0f1cbd74fe chore: bump version to 0.2.1 (#1813)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-03-11 17:41:40 -05:00
Manfred Riem
ec60c5b2fe Added February 2026 newsletter (#1812) 2026-03-11 15:33:30 -05:00
Fanch Daniel
e56d37db8c feat: add Kimi Code CLI agent support (#1790)
* feat: add Kimi Code (kimi) CLI agent support

- Register kimi in AGENT_CONFIG with folder `.kimi/`, markdown format, requires_cli=True
- Register kimi in CommandRegistrar.AGENT_CONFIGS
- Add kimi to supported agents table in AGENTS.md and README.md
- Add kimi to release packaging scripts (bash and PowerShell)
- Add kimi CLI installation to devcontainer post-create script
- Add kimi support to update-agent-context scripts (bash and PowerShell)
- Add 4 consistency tests covering all kimi integration surfaces
- Bump version to 0.1.14 and update CHANGELOG

* fix: include .specify/templates/ and real command files in release ZIPs

- Copy real command files from templates/commands/ (with speckit. prefix)
  instead of generating stubs, so slash commands have actual content
- Add .specify/templates/ to every ZIP so ensure_constitution_from_template
  can find constitution-template.md on init
- Add .vscode/settings.json to every ZIP
- Having 3 top-level dirs prevents the extraction flatten heuristic from
  incorrectly stripping the agent config folder (.kimi/, .claude/, etc.)
- Bump version to 0.1.14.1

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

* fix(kimi): use .kimi/skills/<name>/SKILL.md structure for Kimi Code CLI

Kimi Code CLI uses a skills system, not flat command files:
- Skills live in .kimi/skills/<name>/SKILL.md (project-level)
- Invoked with /skill:<name> (e.g. /skill:speckit.specify)
- Each skill is a directory containing SKILL.md with YAML frontmatter

Changes:
- AGENT_CONFIG["kimi"]["commands_subdir"] = "skills" (was "commands")
- create-release-packages.sh: new create_kimi_skills() function creates
  skill directories with SKILL.md from real template content
- Bump version to 0.1.14.2

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

* fix(test): align kimi commands_subdir assertion with skills structure

* fix: use forward slashes for tabnine path in create-release-packages.ps1

* fix: align kimi to .kimi/skills convention and fix ARGUMENTS unbound variable

* fix: address PR review comments for kimi agent support

  - Fix VERSION_NO_V undefined variable in create-github-release.sh
  - Restore version $1 argument handling in create-release-packages.sh
  - Fix tabnine/vibe/generic cases calling undefined generate_commands
  - Align roo path .roo/rules -> .roo/commands with AGENT_CONFIG
  - Fix kimi extension to use per-skill SKILL.md directory structure
  - Add parent mkdir before dest_file.write_text for nested paths
  - Restore devcontainer tools removed by regression + add Kimi CLI
  - Strengthen test_kimi_in_powershell_validate_set assertion

* fix: restore release scripts and address all PR review comments

  - Restore create-release-packages.sh to original with full generate_commands/
    rewrite_paths logic; add kimi case using create_kimi_skills function
  - Restore create-release-packages.ps1 to original with full Generate-Commands/
    Rewrite-Paths logic; add kimi case using New-KimiSkills function
  - Restore create-github-release.sh to original with proper $1 argument
    handling and VERSION_NO_V; add kimi zip entries
  - Add test_ai_help_includes_kimi for consistency with other agents
  - Strengthen test_kimi_in_powershell_validate_set to check ValidateSet

* fix: address second round of PR review comments

  - Add __AGENT__ and {AGENT_SCRIPT} substitutions in create_kimi_skills (bash)
  - Add __AGENT__ and {AGENT_SCRIPT} substitutions in New-KimiSkills (PowerShell)
  - Replace curl|bash Kimi installer with pipx install kimi-cli in post-create.sh

* fix: align kimi skill naming and add extension registrar test

  - Fix install_ai_skills() to use speckit.<cmd> naming for kimi (dot
    separator) instead of speckit-<cmd>, matching /skill:speckit.<cmd>
    invocation convention and packaging scripts
  - Add test_kimi_in_extension_registrar to verify CommandRegistrar.AGENT_CONFIGS
    includes kimi with correct dir and SKILL.md extension

* fix(test): align kimi skill name assertion with dot-separator convention

  test_skills_install_for_all_agents now expects speckit.specify (dot) for
  kimi and speckit-specify (hyphen) for all other agents, matching the
  install_ai_skills() implementation added in the previous commit.

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 07:57:18 -05:00
Dhilip
33e853e9c9 docs: fix broken links in quickstart guide (#1759) (#1797) 2026-03-11 07:51:04 -05:00
Dhilip
929fab5d98 docs: add catalog cli help documentation (#1793) (#1794)
* docs: add catalog cli help documentation (#1793)

* Fix code block formatting in user guide

Corrected code block syntax for CLI commands in user guide.

* Apply suggestions from code review

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

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-11 06:54:07 -05:00
LifeIsAnAbstraction
56095f06d2 fix: use quiet checkout to avoid exception on git checkout (#1792) 2026-03-10 15:41:27 -05:00
Ben Lawson
2632a0f52d feat(extensions): support .extensionignore to exclude files during install (#1781)
* feat(extensions): support .extensionignore to exclude files during install

Add .extensionignore support so extension authors can exclude files and
folders from being copied when users run 'specify extension add'.

The file uses glob-style patterns (one per line), supports comments (#),
blank lines, trailing-slash directory patterns, and relative path matching.
The .extensionignore file itself is always excluded from the copy.

- Add _load_extensionignore() to ExtensionManager
- Integrate ignore function into shutil.copytree in install_from_directory
- Document .extensionignore in EXTENSION-DEVELOPMENT-GUIDE.md
- Add 6 tests covering all pattern matching scenarios
- Bump version to 0.1.14

* fix(extensions): use pathspec for gitignore-compatible .extensionignore matching

Replace fnmatch with pathspec.GitIgnoreSpec to get proper .gitignore
semantics where * does not cross directory boundaries. This addresses
review feedback on #1781.

Changes:
- Switch from fnmatch to pathspec>=0.12.0 (GitIgnoreSpec.from_lines)
- Normalize backslashes in patterns for cross-platform compatibility
- Distinguish directories from files for trailing-slash patterns
- Update docs to accurately describe supported pattern semantics
- Add edge-case tests: .., absolute paths, empty file, backslashes,
  * vs ** boundary behavior, and ! negation
- Move changelog entry to [Unreleased] section
2026-03-10 12:02:04 -05:00
Adrián Luján Muñoz
4ab91fbadf feat: add Codex support for extension command registration (#1767)
* feat: add Codex support for extension command registration

* test: add codex command registrar mapping check

* test: add codex consistency check to test_agent_config_consistency

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

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 09:50:42 -05:00
Manfred Riem
5c0bedb410 chore: bump version to 0.2.0 (#1786)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-03-09 14:54:08 -05:00
Pavel-tabnine
d92798d5b0 fix: sync agent list comments with actual supported agents (#1785)
Several comment and documentation strings were not updated when
Mistral Vibe support was added, leaving them out of sync with the
code. This fixes:
- update-agent-context.sh: add Generic to Supports header comment
- update-agent-context.ps1: add generic to Multi-Agent header comment
- README.md: add Mistral Vibe to CLI tool-check text
2026-03-09 14:34:54 -05:00
Copilot
ee922cbde9 feat(extensions): support multiple active catalogs simultaneously (#1720)
* Initial plan

* feat(extensions): implement multi-catalog stack support

- Add CatalogEntry dataclass to represent catalog entries
- Add get_active_catalogs() reading SPECKIT_CATALOG_URL, project config,
  user config, or built-in default stack (org-approved + community)
- Add _load_catalog_config() to parse .specify/extension-catalogs.yml
- Add _validate_catalog_url() HTTPS validation helper
- Add _fetch_single_catalog() with per-URL caching, backward-compat for DEFAULT_CATALOG_URL
- Add _get_merged_extensions() that merges all catalogs (priority wins on conflict)
- Update search() and get_extension_info() to use merged results
  annotated with _catalog_name and _install_allowed
- Update clear_cache() to also remove per-URL hash cache files
- Add extension_catalogs CLI command to list active catalogs
- Add catalog add/remove sub-commands for .specify/extension-catalogs.yml
- Update extension_add to enforce install_allowed=false policy
- Update extension_search to show source catalog per result
- Update extension_info to show source catalog with install_allowed status
- Add 13 new tests covering catalog stack, merge conflict resolution,
  install_allowed enforcement, and catalog metadata

Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com>

* docs: update RFC, user guide, and API reference for multi-catalog support

- RFC: replace FUTURE FEATURE section with full implementation docs,
  add catalog stack resolution order, config file examples, merge
  conflict resolution, and install_allowed behavior
- EXTENSION-USER-GUIDE.md: add multi-catalog section with CLI examples
  for catalogs/catalog-add/catalog-remove, update catalog config docs
- EXTENSION-API-REFERENCE.md: add CatalogEntry class docs, update
  ExtensionCatalog docs with new methods and result annotations,
  add catalog CLI commands (catalogs, catalog add, catalog remove)

Also fix extension_catalogs command to correctly show "Using built-in
default catalog stack" when config file exists but has empty catalogs

Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com>

* Potential fix for pull request finding 'Empty except'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

* fix: remove extraneous f-string prefixes (ruff F541)

Remove f-prefix from strings with no placeholders in catalog_remove
and extension_search commands.

Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com>

* fix: address PR review feedback for multi-catalog support

- Rename 'org-approved' catalog to 'default'
- Move 'catalogs' command to 'catalog list' for consistency
- Add 'description' field to CatalogEntry dataclass
- Add --description option to 'catalog add' CLI command
- Align install_allowed default to False in _load_catalog_config
- Add user-level config detection in catalog list footer
- Fix _load_catalog_config docstring (document ValidationError)
- Fix test isolation for test_search_by_tag, test_search_by_query,
  test_search_verified_only, test_get_extension_info
- Update version to 0.1.14 and CHANGELOG
- Update all docs (RFC, User Guide, API Reference)

* fix: wrap _load_catalog_config() calls in catalog_list with try/except

- Check SPECKIT_CATALOG_URL first (matching get_active_catalogs() resolution order)
- Wrap both _load_catalog_config() calls in try/except ValidationError so a
  malformed config file cannot crash `specify extension catalog list` after
  the active catalogs have already been printed successfully

Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
2026-03-09 14:30:27 -05:00
Pavel-tabnine
1df24f1953 Pavel/add tabnine cli support (#1503)
* feat: add Tabnine CLI agent support

Tabnine CLI is a Gemini fork that uses TOML commands with the
.tabnine/agent/ directory structure and TABNINE.md context files.

Changes:
- Add 'tabnine' to AGENT_CONFIG in __init__.py
- Update release scripts (bash + PowerShell) for TOML command generation
- Update agent context scripts (bash + PowerShell)
- Add to GitHub release packages
- Update README.md and AGENTS.md documentation
- Bump version to 0.1.14
- Add 8 new tests for cross-file consistency

* fix: add missing generic to agent-context script usage string
2026-03-09 14:04:02 -05:00
LADISLAV BIHARI
3033834d64 Add Understanding extension to community catalog (#1778)
* Add Understanding extension to community catalog

31 deterministic requirements quality metrics based on IEEE/ISO standards.
Catches ambiguity, missing testability, and structural issues before implementation.
Includes experimental energy-based ambiguity detection.

Repository: https://github.com/Testimonial/understanding
Commands: scan, validate, energy
Hook: after_tasks validation prompt

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

* Sort README table and catalog entries alphabetically

Move Understanding extension entry between Spec Sync and V-Model
to maintain alphabetical ordering in both files.

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

---------

Co-authored-by: Ladislav Bihari <ladislav.bihari@statsperform.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 10:18:41 -05:00
Ben Lawson
4b00078907 Add ralph extension to community catalog (#1780)
- Extension ID: ralph
- Version: 1.0.0
- Author: Rubiss
- Description: Autonomous implementation loop using AI agent CLI
2026-03-09 10:01:34 -05:00
Lautaro Lubatti
2d72f85790 Update README with project initialization instructions (#1772)
* Update README with project initialization instructions

Added instructions for creating a new project and initializing in an existing project.

* Update README.md with alternative one-time usage command for existing projects

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

* Added --ai option to prevent interactive AI selection

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

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-09 09:56:49 -05:00
Ismael
855ac838b8 feat: add review extension to community catalog (#1775)
Add spec-kit-review to catalog.community.json
and the Available Community Extensions table in extensions/README.md.

Co-authored-by: Ismael <ismael.jimenez-martinez@bmw.de>
2026-03-09 09:10:59 -05:00
Sharath Satish
a8ec87e3c2 Add fleet extension to community catalog (#1771)
- Extension ID: fleet
- Version: 1.0.0
- Author: sharathsatish
- Description: Orchestrate a full feature lifecycle with human-in-the-loop gates across all SpecKit phases
2026-03-09 08:13:36 -05:00
Gaël
9d6c05ad5b Integration of Mistral vibe support into speckit (#1725)
* Add Mistral Vibe support to Spec Kit

This commit adds comprehensive support for Mistral Vibe as an AI agent in the
Spec Kit project. The integration includes:

- Added Mistral Vibe to AGENT_CONFIG with proper CLI tool configuration
- Updated README.md with Mistral Vibe in supported agents table and examples
- Modified release package scripts to generate Mistral Vibe templates
- Updated both bash and PowerShell agent context update scripts
- Added appropriate CLI help text and documentation

Mistral Vibe is now fully supported with the same level of integration as
other CLI-based agents like Claude Code, Gemini CLI, etc.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>

* Add Mistral Vibe support to Spec Kit

- Added Mistral Vibe (vibe) to AGENT_CONFIG with proper TOML format support
- Updated CLI help text to include vibe as a valid AI assistant option
- Added Mistral Vibe to release scripts with correct .vibe/agents/ directory structure
- Updated agent context scripts (bash and PowerShell) with proper TOML file paths
- Added Mistral Vibe to README.md supported agents table with v2.0 slash command notes
- Used correct argument syntax {{args}} for Mistral Vibe TOML configurations

Mistral Vibe is now fully integrated with the same level of support as other
CLI-based agents like Gemini and Qwen. Users can now use specify init --ai vibe
to create projects with Mistral Vibe support.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>

* Add Vibe templates to GitHub release script

creation of Mistral vibe zip

* Add 'vibe' agent to release package script

* Add 'vibe' to the list of agents in create-release-packages.sh

* chore: bump version to v1.0.1 [skip ci]

* Add generic spec kit templates to release script

* chore: bump version to v1.0.2 [skip ci]

* Update project version to 0.1.5

* Add generic spec kit templates to release script

* Add 'generic' and 'qodercli' to agent list to be aligned

* Update supported agents in update-agent-context.sh to be aligned

* Update README with new AI assistant options to be aligned

* Document --ai-commands-dir option in README to be aligned

Added new option for AI commands directory in README.

* Fix formatting in README.md for init arguments to be aligned

* Update README with AI assistant options to be aligned

Added AI options to specify init arguments in README.

* Fix formatting in README.md for project-name argument

* Update expected agent types in update-agent-context.sh to be aligned

* Update agent types and usage in update-agent-context.ps1 to be aligned

* Add support for generic AI assistant configuration to be aligned

* Fix formatting in __init__.py clean space

* Update AI assistant options in init command to be aligned

* Add 'qodercli' to expected agent types to be aligned

* Remove 'vibe' case from release package script

Removed the 'vibe' case from the create-release-packages script.

* Update README.md

ok for this

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

* Update .github/workflows/scripts/create-release-packages.ps1

ok to commit

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

* Add commands_subdir key to Mistral Vibe configuration

* Rename specify-agents.toml to specify-agents.md

* Update scripts/bash/update-agent-context.sh

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

* Update .github/workflows/scripts/create-release-packages.sh

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

* Update src/specify_cli/__init__.py

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

* Update src/specify_cli/__init__.py

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

* Fix duplicate 'commands_subdir' in vibe configuration

Removed duplicate 'commands_subdir' entries for 'vibe'.

* Add support for 'vibe' command in release script

add an mkdir and generate command

* Change commands_subdir from 'commands' to 'prompts'

* Update README.md

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

* Update update-agent-context.ps1

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

* Update create-release-packages.sh

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

* Update create-release-packages.ps1

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

* Update agent list in update-agent-context.sh

Kiro

---------

Co-authored-by: Lénaïc Huard <lenaic@lhuard.fr>
Co-authored-by: Mistral Vibe <vibe@mistral.ai>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-09 08:05:13 -05:00
Ryo Hasegawa
3ef12cae3e fix: Remove duplicate options in specify.md (#1765) 2026-03-09 07:38:10 -05:00
Fabián Silva
8618d0a53e fix: use global branch numbering instead of per-short-name detection (#1757)
* fix: remove per-short-name number detection from specify prompt

The specify.md prompt instructed the AI to search for existing branches
filtered by the exact short-name, causing every new feature to start at
001 since no branches matched the new short-name. The underlying
create-new-feature.sh script already has correct global numbering logic
via check_existing_branches() that searches ALL branches and spec
directories.

The fix removes the AI's flawed number-detection steps and tells it to
NOT pass --number, letting the script auto-detect the next globally
available number.

Closes #1744
Closes #1468

🤖 Generated with [Claude Code](https://claude.com/code)

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

* fix: clarify --json flag requirement per Copilot review

- Rephrased step 2 to mention both --short-name and --json flags
- Added explicit note to always include the JSON flag for reliable output parsing

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 07:37:46 -05:00
Copilot
71e6b4da4a Add Community Walkthroughs section to README (#1766)
* Initial plan

* Add Community Walkthroughs section to README.md

Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com>
2026-03-05 12:55:29 -06:00
Michal Bachorik
ad74334a85 feat(extensions): add Jira Integration to community catalog (#1764)
* feat(extensions): add Jira Integration to community catalog

Adds the spec-kit-jira extension to the community catalog.

## Extension Details
- **Name**: Jira Integration
- **Version**: 2.1.0
- **Repository**: https://github.com/mbachorik/spec-kit-jira

## Features
- Create Jira Epics, Stories, and Issues from spec-kit artifacts
- 3-level hierarchy (Epic → Stories → Tasks) or 2-level mode
- Configurable custom field support
- Status synchronization between local tasks and Jira

## Commands
- `/speckit.jira.specstoissues` - Create Jira hierarchy from spec and tasks
- `/speckit.jira.discover-fields` - Discover Jira custom fields
- `/speckit.jira.sync-status` - Sync task completion to Jira

---
This PR was prepared with the assistance of Claude (Anthropic).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: address PR review comments

- Set created_at to catalog submission date (2026-03-05)
- Add Jira Integration to Available Community Extensions table in README

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: iamaeroplane <michal.bachorik@gmail.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-05 07:21:38 -06:00
Pragya Chaurasia
8c3982d65b Add Azure DevOps Integration extension to community catalog (#1734)
* Add Azure DevOps work item synchronization with handoffs system

* Resolving the comments

* Added details of Azure DevOps extention

* t status
t revert --abort
Revert "Add Azure DevOps work item synchronization with handoffs system"

This reverts commit 39ac7e48d6.

* Update extensions/README.md

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

* Update extensions/catalog.community.json

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

---------

Co-authored-by: pragya247 <pragya@microsoft.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-05 07:11:51 -06:00
layla
13dec1de05 Fix docs: update Antigravity link and add initialization example (#1748)
* Fix docs: update Antigravity link and add initialization example

* Update README.md

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

---------

Co-authored-by: Manfred Riem <15701806+mnriem@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-04 08:29:52 -06:00
Manfred Riem
d0a112c60f fix: wire after_tasks and after_implement hook events into command templates (#1702)
* fix: wire after_tasks and after_implement hook events into command templates (#1701)

The HookExecutor backend in extensions.py was fully implemented but
check_hooks_for_event() was never called by anything — the core command
templates had no instructions to check .specify/extensions.yml.

Add a final step to templates/commands/tasks.md (step 6, after_tasks) and
templates/commands/implement.md (step 10, after_implement) that instructs
the AI agent to:

- Read .specify/extensions.yml if it exists
- Filter hooks.{event} to enabled: true entries
- Evaluate any condition fields and skip non-matching hooks
- Output the RFC-specified hook message format, including
  EXECUTE_COMMAND: markers for mandatory (optional: false) hooks

Bumps version to 0.1.7.

* fix: clarify hook condition handling and add YAML error guidance in templates

- Replace ambiguous "evaluate any condition value" instruction with explicit
  guidance to skip hooks with non-empty conditions, deferring evaluation to
  HookExecutor
- Add instruction to skip hook checking silently if extensions.yml cannot
  be parsed or is invalid

* Fix/extension hooks not triggered (#1)

* feat(templates): implement before-hooks check as pre-execution phase

* test(hooks): create scenario for LLMs/Agents on hooks

---------

Co-authored-by: Dhilip <s.dhilipkumar@gmail.com>
2026-03-04 08:16:31 -06:00
Dhilip
c84756b7f3 make c ignores consistent with c++ (#1747) 2026-03-04 08:08:04 -06:00
Manfred Riem
524affca79 chore: bump version to 0.1.13 (#1746)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-03-03 16:08:40 -06:00
Medhat Galal
32c6e7f40c feat: add kiro-cli and AGENT_CONFIG consistency coverage (#1690)
* feat: add kiro-cli and AGENT_CONFIG consistency coverage

* fix: address PR feedback for kiro-cli migration

* test: assert init invocation result in --here mode test

* test: capture init result in here-mode command test

* chore: save local unapproved work in progress

* fix: resolve remaining PR1690 review threads

* Update src/specify_cli/__init__.py

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

* test: reduce brittleness in ai help alias assertion

* fix: resolve PR1690 ruff syntax regression

---------

Co-authored-by: Manfred Riem <15701806+mnriem@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-03 12:04:46 -06:00
Ismael
9cf33e81cc feat: add verify extension to community catalog (#1726)
* feat: add verify extension to community catalog

* Add verify to extension readme

---------

Co-authored-by: Ismael <ismael.jimenez-martinez@bmw.de>
2026-03-03 11:21:06 -06:00
Emi
254e9bbdf0 Add Retrospective Extension to community catalog README table (#1741)
* Add retrospective extension to community catalog.

Register the new retrospective extension release so users can discover and install it from the community catalog.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Add Retrospective Extension to community catalog

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-03-03 09:59:53 -06:00
Zaheer Uddin
6757c90dbd fix(scripts): add empty description validation and branch checkout error handling (#1559)
* fix(scripts): add empty description validation and branch checkout error handling

Adds two critical improvements to both PowerShell and Bash feature creation scripts:

1. Post-trim validation: Prevents creating features with whitespace-only descriptions
2. Branch checkout error handling: Provides clear error messages when branch
   creation fails (e.g., branch already exists) instead of silently continuing

Co-authored-by: Augment Agent <noreply@augmentcode.com>

* fix(scripts): use consistent stderr redirection for branch checkout

---------

Co-authored-by: Augment Agent <noreply@augmentcode.com>
2026-03-03 09:42:46 -06:00
Ismael
f6264d4ef4 fix: correct Copilot extension command registration (#1724)
* fix: correct Copilot extension command registration (#copilot)

- Use .agent.md extension for commands in .github/agents/
- Generate companion .prompt.md files in .github/prompts/
- Clean up .prompt.md files on extension removal
- Add tests for Copilot-specific registration behavior

Bumps version to 0.1.7.

* fix: test copilot prompt cleanup via ExtensionManager.remove() instead of manual unlink

---------

Co-authored-by: Ismael <ismael.jimenez-martinez@bmw.de>
2026-03-03 09:03:38 -06:00
Zaheer Uddin
dd8dbf6344 fix(implement): remove Makefile from C ignore patterns (#1558)
* fix(implement): remove Makefile from C ignore patterns

Makefile is typically source-controlled and should not be in .gitignore.
Replaced with proper autotools-generated files (autom4te.cache/, config.status).

Co-authored-by: Augment Agent <noreply@augmentcode.com>

* fix(implement): restore config.log in C ignore patterns

---------

Co-authored-by: Augment Agent <noreply@augmentcode.com>
2026-03-03 08:28:40 -06:00
Barry Gervin
bf8fb125ad Add sync extension to community catalog (#1728)
* Add sync extension to community catalog

- Extension ID: sync
- Version: 0.1.0
- Author: bgervin
- Description: Detect and resolve drift between specs and implementation

* fix: use main branch in URLs per Copilot review

* Reorder community extensions table alphabetically

Per Copilot review feedback and EXTENSION-PUBLISHING-GUIDE.md

---------

Co-authored-by: Barry Gervin <bgervin@hotmail.com>
2026-03-03 08:18:18 -06:00
Zaheer Uddin
2b92ab444d fix(checklist): clarify file handling behavior for append vs create (#1556)
* fix(checklist): clarify file handling behavior for append vs create

Resolves contradictory instructions in checklist.md lines 97-99 where the
template stated both 'append to existing file' and 'creates a NEW file'.

Changes:
- Clarified that if file doesn't exist, create new with CHK001
- If file exists, append new items continuing from last CHK ID
- Emphasized preservation of existing content (never delete/replace)

Co-authored-by: Augment Agent <noreply@augmentcode.com>

* fix(checklist): align report language with append behavior

---------

Co-authored-by: Augment Agent <noreply@augmentcode.com>
2026-03-03 07:52:02 -06:00
Zaheer Uddin
abe1b7085c fix(clarify): correct conflicting question limit from 10 to 5 (#1557)
Resolves inconsistency where line 92 stated 'Maximum of 10 total questions'
while all other references (lines 2, 91, 100, 134, 158, 178) consistently
specify a maximum of 5 questions.

Co-authored-by: Augment Agent <noreply@augmentcode.com>
2026-03-03 07:46:31 -06:00
Manfred Riem
bfaca2c449 chore: bump version to 0.1.12 (#1737)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-03-02 13:35:43 -06:00
Manfred Riem
78ed453e38 fix: use RELEASE_PAT so tag push triggers release workflow (#1736) 2026-03-02 13:31:18 -06:00
Manfred Riem
658ab2a38c fix: release-trigger uses release branch + PR instead of direct push to main (#1733)
* fix: use release branch + PR instead of direct push to main

Bypass branch protection rules by pushing version bump to a
chore/release-vX.Y.Z branch, tagging that commit, then opening
an auto PR to merge back into main. The release workflow still
triggers immediately from the tag push.

* fix: remove --label automated from gh pr create (label does not exist)
2026-03-02 13:16:13 -06:00
Manfred Riem
2c41d3627e fix: Split release process to sync pyproject.toml version with git tags (#1732)
* fix: split release process to sync pyproject.toml version with git tags (#1721)

- Split release workflow into two: release-trigger.yml and release.yml
- release-trigger.yml: Updates pyproject.toml, generates changelog from commits, creates tag
- release.yml: Triggered by tag push, builds artifacts, creates GitHub release
- Ensures git tags point to commits with correct version in pyproject.toml
- Auto-generates changelog from commit messages since last tag
- Supports manual version input or auto-increment patch version
- Added simulate-release.sh for local testing without pushing
- Added comprehensive RELEASE-PROCESS.md documentation
- Updated pyproject.toml to v0.1.10 to sync with latest release

This fixes the version mismatch issue where tags pointed to commits with
outdated pyproject.toml versions, preventing confusion when installing from source.

* Update .github/workflows/RELEASE-PROCESS.md

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

* Update .github/workflows/scripts/simulate-release.sh

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

* Update .github/workflows/release.yml

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

* Update .github/workflows/release-trigger.yml

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

* fix: harden release-trigger against shell injection and fix stale docs

- Pass workflow_dispatch version input via env: instead of direct
  interpolation into shell script, preventing potential injection attacks
- Validate version input against strict semver regex before use
- Fix RELEASE-PROCESS.md Option 2 still referencing [Unreleased] section
  handling that no longer exists in the workflow

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-02 12:52:13 -06:00
Fabián Silva
b55d00beed fix: prepend YAML frontmatter to Cursor .mdc files (#1699)
* fix: prepend YAML frontmatter to Cursor .mdc files for auto-inclusion

Cursor IDE requires YAML frontmatter with `alwaysApply: true` in .mdc
rule files for them to be automatically loaded. Without this frontmatter,
users must manually configure glob patterns for the rules to take effect.

This fix adds frontmatter generation to both the bash and PowerShell
update-agent-context scripts, handling three scenarios:
- New .mdc file creation (frontmatter prepended after template processing)
- Existing .mdc file update without frontmatter (frontmatter added)
- Existing .mdc file with frontmatter (no duplication)

Closes #669

🤖 Generated with [Claude Code](https://claude.com/code)

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

* refactor: address Copilot review suggestions

- Handle CRLF line endings in frontmatter detection (grep '^---' instead
  of '^---$')
- Fix double blank line after frontmatter in PowerShell New-AgentFile
- Remove unused tempfile import from tests
- Add encoding="utf-8" to all open() calls for cross-platform safety

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 08:20:00 -06:00
74 changed files with 11497 additions and 1112 deletions

View File

@@ -50,8 +50,6 @@
"kilocode.Kilo-Code",
// Roo Code
"RooVeterinaryInc.roo-cline",
// Amazon Developer Q
"AmazonWebServices.amazon-q-vscode",
// Claude Code
"anthropic.claude-code"
],

View File

@@ -8,15 +8,15 @@ run_command() {
local command_to_run="$*"
local output
local exit_code
# Capture all output (stdout and stderr)
output=$(eval "$command_to_run" 2>&1) || exit_code=$?
exit_code=${exit_code:-0}
if [ $exit_code -ne 0 ]; then
echo -e "\033[0;31m[ERROR] Command failed (Exit Code $exit_code): $command_to_run\033[0m" >&2
echo -e "\033[0;31m$output\033[0m" >&2
exit $exit_code
fi
}
@@ -51,32 +51,38 @@ echo -e "\n🤖 Installing OpenCode CLI..."
run_command "npm install -g opencode-ai@latest"
echo "✅ Done"
echo -e "\n🤖 Installing Amazon Q CLI..."
# 👉🏾 https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/command-line-verify-download.html
echo -e "\n🤖 Installing Kiro CLI..."
# https://kiro.dev/docs/cli/
KIRO_INSTALLER_URL="https://kiro.dev/install.sh"
KIRO_INSTALLER_SHA256="7487a65cf310b7fb59b357c4b5e6e3f3259d383f4394ecedb39acf70f307cffb"
KIRO_INSTALLER_PATH="$(mktemp)"
run_command "curl --proto '=https' --tlsv1.2 -sSf 'https://desktop-release.q.us-east-1.amazonaws.com/latest/q-x86_64-linux.zip' -o 'q.zip'"
run_command "curl --proto '=https' --tlsv1.2 -sSf 'https://desktop-release.q.us-east-1.amazonaws.com/latest/q-x86_64-linux.zip.sig' -o 'q.zip.sig'"
cat > amazonq-public-key.asc << 'EOF'
-----BEGIN PGP PUBLIC KEY BLOCK-----
cleanup_kiro_installer() {
rm -f "$KIRO_INSTALLER_PATH"
}
trap cleanup_kiro_installer EXIT
mDMEZig60RYJKwYBBAHaRw8BAQdAy/+G05U5/EOA72WlcD4WkYn5SInri8pc4Z6D
BKNNGOm0JEFtYXpvbiBRIENMSSBUZWFtIDxxLWNsaUBhbWF6b24uY29tPoiZBBMW
CgBBFiEEmvYEF+gnQskUPgPsUNx6jcJMVmcFAmYoOtECGwMFCQPCZwAFCwkIBwIC
IgIGFQoJCAsCBBYCAwECHgcCF4AACgkQUNx6jcJMVmef5QD/QWWEGG/cOnbDnp68
SJXuFkwiNwlH2rPw9ZRIQMnfAS0A/0V6ZsGB4kOylBfc7CNfzRFGtovdBBgHqA6P
zQ/PNscGuDgEZig60RIKKwYBBAGXVQEFAQEHQC4qleONMBCq3+wJwbZSr0vbuRba
D1xr4wUPn4Avn4AnAwEIB4h+BBgWCgAmFiEEmvYEF+gnQskUPgPsUNx6jcJMVmcF
AmYoOtECGwwFCQPCZwAACgkQUNx6jcJMVmchMgEA6l3RveCM0YHAGQaSFMkguoAo
vK6FgOkDawgP0NPIP2oA/jIAO4gsAntuQgMOsPunEdDeji2t+AhV02+DQIsXZpoB
=f8yY
-----END PGP PUBLIC KEY BLOCK-----
EOF
run_command "gpg --batch --import amazonq-public-key.asc"
run_command "gpg --verify q.zip.sig q.zip"
run_command "unzip -q q.zip"
run_command "chmod +x ./q/install.sh"
run_command "./q/install.sh --no-confirm"
run_command "rm -rf ./q q.zip q.zip.sig amazonq-public-key.asc"
run_command "curl -fsSL \"$KIRO_INSTALLER_URL\" -o \"$KIRO_INSTALLER_PATH\""
run_command "echo \"$KIRO_INSTALLER_SHA256 $KIRO_INSTALLER_PATH\" | sha256sum -c -"
run_command "bash \"$KIRO_INSTALLER_PATH\""
kiro_binary=""
if command -v kiro-cli >/dev/null 2>&1; then
kiro_binary="kiro-cli"
elif command -v kiro >/dev/null 2>&1; then
kiro_binary="kiro"
else
echo -e "\033[0;31m[ERROR] Kiro CLI installation did not create 'kiro-cli' or 'kiro' in PATH.\033[0m" >&2
exit 1
fi
run_command "$kiro_binary --help > /dev/null"
echo "✅ Done"
echo -e "\n🤖 Installing Kimi CLI..."
# https://code.kimi.com
run_command "pipx install kimi-cli"
echo "✅ Done"
echo -e "\n🤖 Installing CodeBuddy CLI..."

View File

@@ -8,7 +8,7 @@ body:
value: |
Thanks for requesting a new agent! Before submitting, please check if the agent is already supported.
**Currently supported agents**: Claude Code, Gemini CLI, GitHub Copilot, Cursor, Qwen Code, opencode, Codex CLI, Windsurf, Kilo Code, Auggie CLI, Roo Code, CodeBuddy, Qoder CLI, Amazon Q Developer CLI, Amp, SHAI, IBM Bob, Antigravity
**Currently supported agents**: Claude Code, Gemini CLI, GitHub Copilot, Cursor, Qwen Code, opencode, Codex CLI, Windsurf, Kilo Code, Auggie CLI, Roo Code, CodeBuddy, Qoder CLI, Kiro CLI, Amp, SHAI, IBM Bob, Antigravity
- type: input
id: agent-name

View File

@@ -75,7 +75,7 @@ body:
- Roo Code
- CodeBuddy
- Qoder CLI
- Amazon Q Developer CLI
- Kiro CLI
- Amp
- SHAI
- IBM Bob

View File

@@ -69,7 +69,7 @@ body:
- Roo Code
- CodeBuddy
- Qoder CLI
- Amazon Q Developer CLI
- Kiro CLI
- Amp
- SHAI
- IBM Bob

191
.github/workflows/RELEASE-PROCESS.md vendored Normal file
View File

@@ -0,0 +1,191 @@
# Release Process
This document describes the automated release process for Spec Kit.
## Overview
The release process is split into two workflows to ensure version consistency:
1. **Release Trigger Workflow** (`release-trigger.yml`) - Manages versioning and triggers release
2. **Release Workflow** (`release.yml`) - Builds and publishes artifacts
This separation ensures that git tags always point to commits with the correct version in `pyproject.toml`.
## Before Creating a Release
**Important**: Write clear, descriptive commit messages!
### How CHANGELOG.md Works
The CHANGELOG is **automatically generated** from your git commit messages:
1. **During Development**: Write clear, descriptive commit messages:
```bash
git commit -m "feat: Add new authentication feature"
git commit -m "fix: Resolve timeout issue in API client (#123)"
git commit -m "docs: Update installation instructions"
```
2. **When Releasing**: The release trigger workflow automatically:
- Finds all commits since the last release tag
- Formats them as changelog entries
- Inserts them into CHANGELOG.md
- Commits the updated changelog before creating the new tag
### Commit Message Best Practices
Good commit messages make good changelogs:
- **Be descriptive**: "Add user authentication" not "Update files"
- **Reference issues/PRs**: Include `(#123)` for automated linking
- **Use conventional commits** (optional): `feat:`, `fix:`, `docs:`, `chore:`
- **Keep it concise**: One line is ideal, details go in commit body
**Example commits that become good changelog entries:**
```
fix: prepend YAML frontmatter to Cursor .mdc files (#1699)
feat: add generic agent support with customizable command directories (#1639)
docs: document dual-catalog system for extensions (#1689)
```
## Creating a Release
### Option 1: Auto-Increment (Recommended for patches)
1. Go to **Actions** → **Release Trigger**
2. Click **Run workflow**
3. Leave the version field **empty**
4. Click **Run workflow**
The workflow will:
- Auto-increment the patch version (e.g., `0.1.10` → `0.1.11`)
- Update `pyproject.toml`
- Update `CHANGELOG.md` by adding a new section for the release based on commits since the last tag
- Commit changes to a `chore/release-vX.Y.Z` branch
- Create and push the git tag from that branch
- Open a PR to merge the version bump into `main`
- Trigger the release workflow automatically via the tag push
### Option 2: Manual Version (For major/minor bumps)
1. Go to **Actions** → **Release Trigger**
2. Click **Run workflow**
3. Enter the desired version (e.g., `0.2.0` or `v0.2.0`)
4. Click **Run workflow**
The workflow will:
- Use your specified version
- Update `pyproject.toml`
- Update `CHANGELOG.md` by adding a new section for the release based on commits since the last tag
- Commit changes to a `chore/release-vX.Y.Z` branch
- Create and push the git tag from that branch
- Open a PR to merge the version bump into `main`
- Trigger the release workflow automatically via the tag push
## What Happens Next
Once the release trigger workflow completes:
1. A `chore/release-vX.Y.Z` branch is pushed with the version bump commit
2. The git tag is pushed, pointing to that commit
3. The **Release Workflow** is automatically triggered by the tag push
4. Release artifacts are built for all supported agents
5. A GitHub Release is created with all assets
6. A PR is opened to merge the version bump branch into `main`
> **Note**: Merge the auto-opened PR after the release is published to keep `main` in sync.
## Workflow Details
### Release Trigger Workflow
**File**: `.github/workflows/release-trigger.yml`
**Trigger**: Manual (`workflow_dispatch`)
**Permissions Required**: `contents: write`
**Steps**:
1. Checkout repository
2. Determine version (manual or auto-increment)
3. Check if tag already exists (prevents duplicates)
4. Create `chore/release-vX.Y.Z` branch
5. Update `pyproject.toml`
6. Update `CHANGELOG.md` from git commits
7. Commit changes
8. Push branch and tag
9. Open PR to merge version bump into `main`
### Release Workflow
**File**: `.github/workflows/release.yml`
**Trigger**: Tag push (`v*`)
**Permissions Required**: `contents: write`
**Steps**:
1. Checkout repository at tag
2. Extract version from tag name
3. Check if release already exists
4. Build release package variants (all agents × shell/powershell)
5. Generate release notes from commits
6. Create GitHub Release with all assets
## Version Constraints
- Tags must follow format: `v{MAJOR}.{MINOR}.{PATCH}`
- Example valid versions: `v0.1.11`, `v0.2.0`, `v1.0.0`
- Auto-increment only bumps patch version
- Cannot create duplicate tags (workflow will fail)
## Benefits of This Approach
✅ **Version Consistency**: Git tags point to commits with matching `pyproject.toml` version
✅ **Single Source of Truth**: Version set once, used everywhere
✅ **Prevents Drift**: No more manual version synchronization needed
✅ **Clean Separation**: Versioning logic separate from artifact building
✅ **Flexibility**: Supports both auto-increment and manual versioning
## Troubleshooting
### No Commits Since Last Release
If you run the release trigger workflow when there are no new commits since the last tag:
- The workflow will still succeed
- The CHANGELOG will show "- Initial release" if it's the first release
- Or it will be empty if there are no commits
- Consider adding meaningful commits before releasing
**Best Practice**: Use descriptive commit messages - they become your changelog!
### Tag Already Exists
If you see "Error: Tag vX.Y.Z already exists!", you need to:
- Choose a different version number, or
- Delete the existing tag if it was created in error
### Release Workflow Didn't Trigger
Check that:
- The release trigger workflow completed successfully
- The tag was pushed (check repository tags)
- The release workflow is enabled in Actions settings
### Version Mismatch
If `pyproject.toml` doesn't match the latest tag:
- Run the release trigger workflow to sync versions
- Or manually update `pyproject.toml` and push changes before running the release trigger
## Legacy Behavior (Pre-v0.1.10)
Before this change, the release workflow:
- Created tags automatically on main branch pushes
- Updated `pyproject.toml` AFTER creating the tag
- Resulted in tags pointing to commits with outdated versions
This has been fixed in v0.1.10+.

161
.github/workflows/release-trigger.yml vendored Normal file
View File

@@ -0,0 +1,161 @@
name: Release Trigger
on:
workflow_dispatch:
inputs:
version:
description: 'Version to release (e.g., 0.1.11). Leave empty to auto-increment patch version.'
required: false
type: string
jobs:
bump-version:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0
token: ${{ secrets.RELEASE_PAT }}
- name: Configure Git
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
- name: Determine version
id: version
env:
INPUT_VERSION: ${{ github.event.inputs.version }}
run: |
if [[ -n "$INPUT_VERSION" ]]; then
# Manual version specified - strip optional v prefix
VERSION="${INPUT_VERSION#v}"
# Validate strict semver format to prevent injection
if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Error: Invalid version format '$VERSION'. Must be X.Y.Z (e.g. 1.2.3 or v1.2.3)"
exit 1
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "tag=v$VERSION" >> $GITHUB_OUTPUT
echo "Using manual version: $VERSION"
else
# Auto-increment patch version
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
echo "Latest tag: $LATEST_TAG"
# Extract version number and increment
VERSION=$(echo $LATEST_TAG | sed 's/v//')
IFS='.' read -ra VERSION_PARTS <<< "$VERSION"
MAJOR=${VERSION_PARTS[0]:-0}
MINOR=${VERSION_PARTS[1]:-0}
PATCH=${VERSION_PARTS[2]:-0}
# Increment patch version
PATCH=$((PATCH + 1))
NEW_VERSION="$MAJOR.$MINOR.$PATCH"
echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT
echo "tag=v$NEW_VERSION" >> $GITHUB_OUTPUT
echo "Auto-incremented version: $NEW_VERSION"
fi
- name: Check if tag already exists
run: |
if git rev-parse "${{ steps.version.outputs.tag }}" >/dev/null 2>&1; then
echo "Error: Tag ${{ steps.version.outputs.tag }} already exists!"
exit 1
fi
- name: Create release branch
run: |
BRANCH="chore/release-${{ steps.version.outputs.tag }}"
git checkout -b "$BRANCH"
echo "branch=$BRANCH" >> $GITHUB_ENV
- name: Update pyproject.toml
run: |
sed -i "s/version = \".*\"/version = \"${{ steps.version.outputs.version }}\"/" pyproject.toml
echo "Updated pyproject.toml to version ${{ steps.version.outputs.version }}"
- name: Update CHANGELOG.md
run: |
if [ -f "CHANGELOG.md" ]; then
DATE=$(date +%Y-%m-%d)
# Get the previous tag to compare commits
PREVIOUS_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
echo "Generating changelog from commits..."
if [[ -n "$PREVIOUS_TAG" ]]; then
echo "Changes since $PREVIOUS_TAG"
COMMITS=$(git log --oneline "$PREVIOUS_TAG"..HEAD --no-merges --pretty=format:"- %s" 2>/dev/null || echo "- Initial release")
else
echo "No previous tag found - this is the first release"
COMMITS="- Initial release"
fi
# Create new changelog entry
{
head -n 8 CHANGELOG.md
echo ""
echo "## [${{ steps.version.outputs.version }}] - $DATE"
echo ""
echo "### Changed"
echo ""
echo "$COMMITS"
echo ""
tail -n +9 CHANGELOG.md
} > CHANGELOG.md.tmp
mv CHANGELOG.md.tmp CHANGELOG.md
echo "✅ Updated CHANGELOG.md with commits since $PREVIOUS_TAG"
else
echo "No CHANGELOG.md found"
fi
- name: Commit version bump
run: |
if [ -f "CHANGELOG.md" ]; then
git add pyproject.toml CHANGELOG.md
else
git add pyproject.toml
fi
if git diff --cached --quiet; then
echo "No changes to commit"
else
git commit -m "chore: bump version to ${{ steps.version.outputs.version }}"
echo "Changes committed"
fi
- name: Create and push tag
run: |
git tag -a "${{ steps.version.outputs.tag }}" -m "Release ${{ steps.version.outputs.tag }}"
git push origin "${{ env.branch }}"
git push origin "${{ steps.version.outputs.tag }}"
echo "Branch ${{ env.branch }} and tag ${{ steps.version.outputs.tag }} pushed"
- name: Open pull request
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_PAT }}
run: |
gh pr create \
--base main \
--head "${{ env.branch }}" \
--title "chore: bump version to ${{ steps.version.outputs.version }}" \
--body "Automated version bump to ${{ steps.version.outputs.version }}.
This PR was created by the Release Trigger workflow. The git tag \`${{ steps.version.outputs.tag }}\` has already been pushed and the release artifacts are being built.
Merge this PR to record the version bump and changelog update on \`main\`."
- name: Summary
run: |
echo "✅ Version bumped to ${{ steps.version.outputs.version }}"
echo "✅ Tag ${{ steps.version.outputs.tag }} created and pushed"
echo "✅ PR opened to merge version bump into main"
echo "🚀 Release workflow is building artifacts from the tag"

View File

@@ -2,68 +2,60 @@ name: Create Release
on:
push:
branches: [ main ]
paths:
- 'memory/**'
- 'scripts/**'
- 'src/**'
- 'templates/**'
- '.github/workflows/**'
workflow_dispatch:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Get latest tag
id: get_tag
- name: Extract version from tag
id: version
run: |
chmod +x .github/workflows/scripts/get-next-version.sh
.github/workflows/scripts/get-next-version.sh
VERSION=${GITHUB_REF#refs/tags/}
echo "tag=$VERSION" >> $GITHUB_OUTPUT
echo "Building release for $VERSION"
- name: Check if release already exists
id: check_release
run: |
chmod +x .github/workflows/scripts/check-release-exists.sh
.github/workflows/scripts/check-release-exists.sh ${{ steps.get_tag.outputs.new_version }}
.github/workflows/scripts/check-release-exists.sh ${{ steps.version.outputs.tag }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create release package variants
if: steps.check_release.outputs.exists == 'false'
run: |
chmod +x .github/workflows/scripts/create-release-packages.sh
.github/workflows/scripts/create-release-packages.sh ${{ steps.get_tag.outputs.new_version }}
.github/workflows/scripts/create-release-packages.sh ${{ steps.version.outputs.tag }}
- name: Generate release notes
if: steps.check_release.outputs.exists == 'false'
id: release_notes
run: |
chmod +x .github/workflows/scripts/generate-release-notes.sh
.github/workflows/scripts/generate-release-notes.sh ${{ steps.get_tag.outputs.new_version }} ${{ steps.get_tag.outputs.latest_tag }}
# Get the previous tag for changelog generation
PREVIOUS_TAG=$(git describe --tags --abbrev=0 ${{ steps.version.outputs.tag }}^ 2>/dev/null || echo "")
# Default to v0.0.0 if no previous tag is found (e.g., first release)
if [ -z "$PREVIOUS_TAG" ]; then
PREVIOUS_TAG="v0.0.0"
fi
.github/workflows/scripts/generate-release-notes.sh ${{ steps.version.outputs.tag }} "$PREVIOUS_TAG"
- name: Create GitHub Release
if: steps.check_release.outputs.exists == 'false'
run: |
chmod +x .github/workflows/scripts/create-github-release.sh
.github/workflows/scripts/create-github-release.sh ${{ steps.get_tag.outputs.new_version }}
.github/workflows/scripts/create-github-release.sh ${{ steps.version.outputs.tag }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Update version in pyproject.toml (for release artifacts only)
if: steps.check_release.outputs.exists == 'false'
run: |
chmod +x .github/workflows/scripts/update-version.sh
.github/workflows/scripts/update-version.sh ${{ steps.get_tag.outputs.new_version }}
- name: Commit version bump to main
if: steps.check_release.outputs.exists == 'false'
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add pyproject.toml
git diff --cached --quiet || git commit -m "chore: bump version to ${{ steps.get_tag.outputs.new_version }} [skip ci]"
git push

10
.github/workflows/scripts/create-github-release.sh vendored Normal file → Executable file
View File

@@ -46,12 +46,18 @@ gh release create "$VERSION" \
.genreleases/spec-kit-template-amp-ps-"$VERSION".zip \
.genreleases/spec-kit-template-shai-sh-"$VERSION".zip \
.genreleases/spec-kit-template-shai-ps-"$VERSION".zip \
.genreleases/spec-kit-template-q-sh-"$VERSION".zip \
.genreleases/spec-kit-template-q-ps-"$VERSION".zip \
.genreleases/spec-kit-template-tabnine-sh-"$VERSION".zip \
.genreleases/spec-kit-template-tabnine-ps-"$VERSION".zip \
.genreleases/spec-kit-template-kiro-cli-sh-"$VERSION".zip \
.genreleases/spec-kit-template-kiro-cli-ps-"$VERSION".zip \
.genreleases/spec-kit-template-agy-sh-"$VERSION".zip \
.genreleases/spec-kit-template-agy-ps-"$VERSION".zip \
.genreleases/spec-kit-template-bob-sh-"$VERSION".zip \
.genreleases/spec-kit-template-bob-ps-"$VERSION".zip \
.genreleases/spec-kit-template-vibe-sh-"$VERSION".zip \
.genreleases/spec-kit-template-vibe-ps-"$VERSION".zip \
.genreleases/spec-kit-template-kimi-sh-"$VERSION".zip \
.genreleases/spec-kit-template-kimi-ps-"$VERSION".zip \
.genreleases/spec-kit-template-generic-sh-"$VERSION".zip \
.genreleases/spec-kit-template-generic-ps-"$VERSION".zip \
--title "Spec Kit Templates - $VERSION_NO_V" \

View File

@@ -8,13 +8,13 @@
.DESCRIPTION
create-release-packages.ps1 (workflow-local)
Build Spec Kit template release archives for each supported AI assistant and script type.
.PARAMETER Version
Version string with leading 'v' (e.g., v0.2.0)
.PARAMETER Agents
Comma or space separated subset of agents to build (default: all)
Valid agents: claude, gemini, copilot, cursor-agent, qwen, opencode, windsurf, codex, kilocode, auggie, roo, codebuddy, amp, q, bob, qodercli, shai, agy, generic
Valid agents: claude, gemini, copilot, cursor-agent, qwen, opencode, windsurf, codex, kilocode, auggie, roo, codebuddy, amp, kiro-cli, bob, qodercli, shai, tabnine, agy, vibe, kimi, generic
.PARAMETER Scripts
Comma or space separated subset of script types to build (default: both)
@@ -33,10 +33,10 @@
param(
[Parameter(Mandatory=$true, Position=0)]
[string]$Version,
[Parameter(Mandatory=$false)]
[string]$Agents = "",
[Parameter(Mandatory=$false)]
[string]$Scripts = ""
)
@@ -60,7 +60,7 @@ New-Item -ItemType Directory -Path $GenReleasesDir -Force | Out-Null
function Rewrite-Paths {
param([string]$Content)
$Content = $Content -replace '(/?)\bmemory/', '.specify/memory/'
$Content = $Content -replace '(/?)\bscripts/', '.specify/scripts/'
$Content = $Content -replace '(/?)\btemplates/', '.specify/templates/'
@@ -75,55 +75,55 @@ function Generate-Commands {
[string]$OutputDir,
[string]$ScriptVariant
)
New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null
$templates = Get-ChildItem -Path "templates/commands/*.md" -File -ErrorAction SilentlyContinue
foreach ($template in $templates) {
$name = [System.IO.Path]::GetFileNameWithoutExtension($template.Name)
# Read file content and normalize line endings
$fileContent = (Get-Content -Path $template.FullName -Raw) -replace "`r`n", "`n"
# Extract description from YAML frontmatter
$description = ""
if ($fileContent -match '(?m)^description:\s*(.+)$') {
$description = $matches[1]
}
# Extract script command from YAML frontmatter
$scriptCommand = ""
if ($fileContent -match "(?m)^\s*${ScriptVariant}:\s*(.+)$") {
$scriptCommand = $matches[1]
}
if ([string]::IsNullOrEmpty($scriptCommand)) {
Write-Warning "No script command found for $ScriptVariant in $($template.Name)"
$scriptCommand = "(Missing script command for $ScriptVariant)"
}
# Extract agent_script command from YAML frontmatter if present
$agentScriptCommand = ""
if ($fileContent -match "(?ms)agent_scripts:.*?^\s*${ScriptVariant}:\s*(.+?)$") {
$agentScriptCommand = $matches[1].Trim()
}
# Replace {SCRIPT} placeholder with the script command
$body = $fileContent -replace '\{SCRIPT\}', $scriptCommand
# Replace {AGENT_SCRIPT} placeholder with the agent script command if found
if (-not [string]::IsNullOrEmpty($agentScriptCommand)) {
$body = $body -replace '\{AGENT_SCRIPT\}', $agentScriptCommand
}
# Remove the scripts: and agent_scripts: sections from frontmatter
$lines = $body -split "`n"
$outputLines = @()
$inFrontmatter = $false
$skipScripts = $false
$dashCount = 0
foreach ($line in $lines) {
if ($line -match '^---$') {
$outputLines += $line
@@ -135,7 +135,7 @@ function Generate-Commands {
}
continue
}
if ($inFrontmatter) {
if ($line -match '^(scripts|agent_scripts):$') {
$skipScripts = $true
@@ -148,20 +148,20 @@ function Generate-Commands {
continue
}
}
$outputLines += $line
}
$body = $outputLines -join "`n"
# Apply other substitutions
$body = $body -replace '\{ARGS\}', $ArgFormat
$body = $body -replace '__AGENT__', $Agent
$body = Rewrite-Paths -Content $body
# Generate output file based on extension
$outputFile = Join-Path $OutputDir "speckit.$name.$Extension"
switch ($Extension) {
'toml' {
$body = $body -replace '\\', '\\'
@@ -183,15 +183,15 @@ function Generate-CopilotPrompts {
[string]$AgentsDir,
[string]$PromptsDir
)
New-Item -ItemType Directory -Path $PromptsDir -Force | Out-Null
$agentFiles = Get-ChildItem -Path "$AgentsDir/speckit.*.agent.md" -File -ErrorAction SilentlyContinue
foreach ($agentFile in $agentFiles) {
$basename = $agentFile.Name -replace '\.agent\.md$', ''
$promptFile = Join-Path $PromptsDir "$basename.prompt.md"
$content = @"
---
agent: $basename
@@ -201,31 +201,118 @@ agent: $basename
}
}
# Create Kimi Code skills in .kimi/skills/<name>/SKILL.md format.
# Kimi CLI discovers skills as directories containing a SKILL.md file,
# invoked with /skill:<name> (e.g. /skill:speckit.specify).
function New-KimiSkills {
param(
[string]$SkillsDir,
[string]$ScriptVariant
)
$templates = Get-ChildItem -Path "templates/commands/*.md" -File -ErrorAction SilentlyContinue
foreach ($template in $templates) {
$name = [System.IO.Path]::GetFileNameWithoutExtension($template.Name)
$skillName = "speckit.$name"
$skillDir = Join-Path $SkillsDir $skillName
New-Item -ItemType Directory -Force -Path $skillDir | Out-Null
$fileContent = (Get-Content -Path $template.FullName -Raw) -replace "`r`n", "`n"
# Extract description
$description = "Spec Kit: $name workflow"
if ($fileContent -match '(?m)^description:\s*(.+)$') {
$description = $matches[1]
}
# Extract script command
$scriptCommand = "(Missing script command for $ScriptVariant)"
if ($fileContent -match "(?m)^\s*${ScriptVariant}:\s*(.+)$") {
$scriptCommand = $matches[1]
}
# Extract agent_script command from frontmatter if present
$agentScriptCommand = ""
if ($fileContent -match "(?ms)agent_scripts:.*?^\s*${ScriptVariant}:\s*(.+?)$") {
$agentScriptCommand = $matches[1].Trim()
}
# Replace {SCRIPT}, strip scripts sections, rewrite paths
$body = $fileContent -replace '\{SCRIPT\}', $scriptCommand
if (-not [string]::IsNullOrEmpty($agentScriptCommand)) {
$body = $body -replace '\{AGENT_SCRIPT\}', $agentScriptCommand
}
$lines = $body -split "`n"
$outputLines = @()
$inFrontmatter = $false
$skipScripts = $false
$dashCount = 0
foreach ($line in $lines) {
if ($line -match '^---$') {
$outputLines += $line
$dashCount++
$inFrontmatter = ($dashCount -eq 1)
continue
}
if ($inFrontmatter) {
if ($line -match '^(scripts|agent_scripts):$') { $skipScripts = $true; continue }
if ($line -match '^[a-zA-Z].*:' -and $skipScripts) { $skipScripts = $false }
if ($skipScripts -and $line -match '^\s+') { continue }
}
$outputLines += $line
}
$body = $outputLines -join "`n"
$body = $body -replace '\{ARGS\}', '$ARGUMENTS'
$body = $body -replace '__AGENT__', 'kimi'
$body = Rewrite-Paths -Content $body
# Strip existing frontmatter, keep only body
$templateBody = ""
$fmCount = 0
$inBody = $false
foreach ($line in ($body -split "`n")) {
if ($line -match '^---$') {
$fmCount++
if ($fmCount -eq 2) { $inBody = $true }
continue
}
if ($inBody) { $templateBody += "$line`n" }
}
$skillContent = "---`nname: `"$skillName`"`ndescription: `"$description`"`n---`n`n$templateBody"
Set-Content -Path (Join-Path $skillDir "SKILL.md") -Value $skillContent -NoNewline
}
}
function Build-Variant {
param(
[string]$Agent,
[string]$Script
)
$baseDir = Join-Path $GenReleasesDir "sdd-${Agent}-package-${Script}"
Write-Host "Building $Agent ($Script) package..."
New-Item -ItemType Directory -Path $baseDir -Force | Out-Null
# Copy base structure but filter scripts by variant
$specDir = Join-Path $baseDir ".specify"
New-Item -ItemType Directory -Path $specDir -Force | Out-Null
# Copy memory directory
if (Test-Path "memory") {
Copy-Item -Path "memory" -Destination $specDir -Recurse -Force
Write-Host "Copied memory -> .specify"
}
# Only copy the relevant script variant directory
if (Test-Path "scripts") {
$scriptsDestDir = Join-Path $specDir "scripts"
New-Item -ItemType Directory -Path $scriptsDestDir -Force | Out-Null
switch ($Script) {
'sh' {
if (Test-Path "scripts/bash") {
@@ -240,18 +327,17 @@ function Build-Variant {
}
}
}
# Copy any script files that aren't in variant-specific directories
Get-ChildItem -Path "scripts" -File -ErrorAction SilentlyContinue | ForEach-Object {
Copy-Item -Path $_.FullName -Destination $scriptsDestDir -Force
}
}
# Copy templates (excluding commands directory and vscode-settings.json)
if (Test-Path "templates") {
$templatesDestDir = Join-Path $specDir "templates"
New-Item -ItemType Directory -Path $templatesDestDir -Force | Out-Null
Get-ChildItem -Path "templates" -Recurse -File | Where-Object {
$_.FullName -notmatch 'templates[/\\]commands[/\\]' -and $_.Name -ne 'vscode-settings.json'
} | ForEach-Object {
@@ -263,7 +349,7 @@ function Build-Variant {
}
Write-Host "Copied templates -> .specify/templates"
}
# Generate agent-specific command files
switch ($Agent) {
'claude' {
@@ -280,12 +366,10 @@ function Build-Variant {
'copilot' {
$agentsDir = Join-Path $baseDir ".github/agents"
Generate-Commands -Agent 'copilot' -Extension 'agent.md' -ArgFormat '$ARGUMENTS' -OutputDir $agentsDir -ScriptVariant $Script
# Generate companion prompt files
$promptsDir = Join-Path $baseDir ".github/prompts"
Generate-CopilotPrompts -AgentsDir $agentsDir -PromptsDir $promptsDir
# Create VS Code workspace settings
$vscodeDir = Join-Path $baseDir ".vscode"
New-Item -ItemType Directory -Path $vscodeDir -Force | Out-Null
if (Test-Path "templates/vscode-settings.json") {
@@ -298,7 +382,7 @@ function Build-Variant {
}
'qwen' {
$cmdDir = Join-Path $baseDir ".qwen/commands"
Generate-Commands -Agent 'qwen' -Extension 'toml' -ArgFormat '{{args}}' -OutputDir $cmdDir -ScriptVariant $Script
Generate-Commands -Agent 'qwen' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script
if (Test-Path "agent_templates/qwen/QWEN.md") {
Copy-Item -Path "agent_templates/qwen/QWEN.md" -Destination (Join-Path $baseDir "QWEN.md")
}
@@ -335,9 +419,9 @@ function Build-Variant {
$cmdDir = Join-Path $baseDir ".agents/commands"
Generate-Commands -Agent 'amp' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script
}
'q' {
$cmdDir = Join-Path $baseDir ".amazonq/prompts"
Generate-Commands -Agent 'q' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script
'kiro-cli' {
$cmdDir = Join-Path $baseDir ".kiro/prompts"
Generate-Commands -Agent 'kiro-cli' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script
}
'bob' {
$cmdDir = Join-Path $baseDir ".bob/commands"
@@ -347,12 +431,38 @@ function Build-Variant {
$cmdDir = Join-Path $baseDir ".qoder/commands"
Generate-Commands -Agent 'qodercli' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script
}
'shai' {
$cmdDir = Join-Path $baseDir ".shai/commands"
Generate-Commands -Agent 'shai' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script
}
'tabnine' {
$cmdDir = Join-Path $baseDir ".tabnine/agent/commands"
Generate-Commands -Agent 'tabnine' -Extension 'toml' -ArgFormat '{{args}}' -OutputDir $cmdDir -ScriptVariant $Script
$tabnineTemplate = Join-Path 'agent_templates' 'tabnine/TABNINE.md'
if (Test-Path $tabnineTemplate) { Copy-Item $tabnineTemplate (Join-Path $baseDir 'TABNINE.md') }
}
'agy' {
$cmdDir = Join-Path $baseDir ".agent/commands"
Generate-Commands -Agent 'agy' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script
}
'vibe' {
$cmdDir = Join-Path $baseDir ".vibe/prompts"
Generate-Commands -Agent 'vibe' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script
}
'kimi' {
$skillsDir = Join-Path $baseDir ".kimi/skills"
New-Item -ItemType Directory -Force -Path $skillsDir | Out-Null
New-KimiSkills -SkillsDir $skillsDir -ScriptVariant $Script
}
'generic' {
$cmdDir = Join-Path $baseDir ".speckit/commands"
Generate-Commands -Agent 'generic' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script
}
default {
throw "Unsupported agent '$Agent'."
}
}
# Create zip archive
$zipFile = Join-Path $GenReleasesDir "spec-kit-template-${Agent}-${Script}-${Version}.zip"
Compress-Archive -Path "$baseDir/*" -DestinationPath $zipFile -Force
@@ -360,17 +470,16 @@ function Build-Variant {
}
# Define all agents and scripts
$AllAgents = @('claude', 'gemini', 'copilot', 'cursor-agent', 'qwen', 'opencode', 'windsurf', 'codex', 'kilocode', 'auggie', 'roo', 'codebuddy', 'amp', 'q', 'bob', 'qodercli', 'shai', 'agy', 'generic')
$AllAgents = @('claude', 'gemini', 'copilot', 'cursor-agent', 'qwen', 'opencode', 'windsurf', 'codex', 'kilocode', 'auggie', 'roo', 'codebuddy', 'amp', 'kiro-cli', 'bob', 'qodercli', 'shai', 'tabnine', 'agy', 'vibe', 'kimi', 'generic')
$AllScripts = @('sh', 'ps')
function Normalize-List {
param([string]$Input)
if ([string]::IsNullOrEmpty($Input)) {
return @()
}
# Split by comma or space and remove duplicates while preserving order
$items = $Input -split '[,\s]+' | Where-Object { $_ } | Select-Object -Unique
return $items
}
@@ -381,7 +490,7 @@ function Validate-Subset {
[string[]]$Allowed,
[string[]]$Items
)
$ok = $true
foreach ($item in $Items) {
if ($item -notin $Allowed) {
@@ -425,4 +534,4 @@ foreach ($agent in $AgentList) {
Write-Host "`nArchives in ${GenReleasesDir}:"
Get-ChildItem -Path $GenReleasesDir -Filter "spec-kit-template-*-${Version}.zip" | ForEach-Object {
Write-Host " $($_.Name)"
}
}

View File

@@ -6,7 +6,7 @@ set -euo pipefail
# Usage: .github/workflows/scripts/create-release-packages.sh <version>
# Version argument should include leading 'v'.
# Optionally set AGENTS and/or SCRIPTS env vars to limit what gets built.
# AGENTS : space or comma separated subset of: claude gemini copilot cursor-agent qwen opencode windsurf codex amp shai bob generic (default: all)
# AGENTS : space or comma separated subset of: claude gemini copilot cursor-agent qwen opencode windsurf codex kilocode auggie roo codebuddy amp shai tabnine kiro-cli agy bob vibe qodercli kimi generic (default: all)
# SCRIPTS : space or comma separated subset of: sh ps (default: both)
# Examples:
# AGENTS=claude SCRIPTS=sh $0 v0.2.0
@@ -45,19 +45,19 @@ generate_commands() {
[[ -f "$template" ]] || continue
local name description script_command agent_script_command body
name=$(basename "$template" .md)
# Normalize line endings
file_content=$(tr -d '\r' < "$template")
# Extract description and script command from YAML frontmatter
description=$(printf '%s\n' "$file_content" | awk '/^description:/ {sub(/^description:[[:space:]]*/, ""); print; exit}')
script_command=$(printf '%s\n' "$file_content" | awk -v sv="$script_variant" '/^[[:space:]]*'"$script_variant"':[[:space:]]*/ {sub(/^[[:space:]]*'"$script_variant"':[[:space:]]*/, ""); print; exit}')
if [[ -z $script_command ]]; then
echo "Warning: no script command found for $script_variant in $template" >&2
script_command="(Missing script command for $script_variant)"
fi
# Extract agent_script command from YAML frontmatter if present
agent_script_command=$(printf '%s\n' "$file_content" | awk '
/^agent_scripts:$/ { in_agent_scripts=1; next }
@@ -68,15 +68,15 @@ generate_commands() {
}
in_agent_scripts && /^[a-zA-Z]/ { in_agent_scripts=0 }
')
# Replace {SCRIPT} placeholder with the script command
body=$(printf '%s\n' "$file_content" | sed "s|{SCRIPT}|${script_command}|g")
# Replace {AGENT_SCRIPT} placeholder with the agent script command if found
if [[ -n $agent_script_command ]]; then
body=$(printf '%s\n' "$body" | sed "s|{AGENT_SCRIPT}|${agent_script_command}|g")
fi
# Remove the scripts: and agent_scripts: sections from frontmatter while preserving YAML structure
body=$(printf '%s\n' "$body" | awk '
/^---$/ { print; if (++dash_count == 1) in_frontmatter=1; else in_frontmatter=0; next }
@@ -86,10 +86,10 @@ generate_commands() {
in_frontmatter && skip_scripts && /^[[:space:]]/ { next }
{ print }
')
# Apply other substitutions
body=$(printf '%s\n' "$body" | sed "s/{ARGS}/$arg_format/g" | sed "s/__AGENT__/$agent/g" | rewrite_paths)
case $ext in
toml)
body=$(printf '%s\n' "$body" | sed 's/\\/\\\\/g')
@@ -105,15 +105,14 @@ generate_commands() {
generate_copilot_prompts() {
local agents_dir=$1 prompts_dir=$2
mkdir -p "$prompts_dir"
# Generate a .prompt.md file for each .agent.md file
for agent_file in "$agents_dir"/speckit.*.agent.md; do
[[ -f "$agent_file" ]] || continue
local basename=$(basename "$agent_file" .agent.md)
local prompt_file="$prompts_dir/${basename}.prompt.md"
# Create prompt file with agent frontmatter
cat > "$prompt_file" <<EOF
---
agent: ${basename}
@@ -122,41 +121,104 @@ EOF
done
}
# Create Kimi Code skills in .kimi/skills/<name>/SKILL.md format.
# Kimi CLI discovers skills as directories containing a SKILL.md file,
# invoked with /skill:<name> (e.g. /skill:speckit.specify).
create_kimi_skills() {
local skills_dir="$1"
local script_variant="$2"
for template in templates/commands/*.md; do
[[ -f "$template" ]] || continue
local name
name=$(basename "$template" .md)
local skill_name="speckit.${name}"
local skill_dir="${skills_dir}/${skill_name}"
mkdir -p "$skill_dir"
local file_content
file_content=$(tr -d '\r' < "$template")
# Extract description from frontmatter
local description
description=$(printf '%s\n' "$file_content" | awk '/^description:/ {sub(/^description:[[:space:]]*/, ""); print; exit}')
[[ -z "$description" ]] && description="Spec Kit: ${name} workflow"
# Extract script command
local script_command
script_command=$(printf '%s\n' "$file_content" | awk -v sv="$script_variant" '/^[[:space:]]*'"$script_variant"':[[:space:]]*/ {sub(/^[[:space:]]*'"$script_variant"':[[:space:]]*/, ""); print; exit}')
[[ -z "$script_command" ]] && script_command="(Missing script command for $script_variant)"
# Extract agent_script command from frontmatter if present
local agent_script_command
agent_script_command=$(printf '%s\n' "$file_content" | awk '
/^agent_scripts:$/ { in_agent_scripts=1; next }
in_agent_scripts && /^[[:space:]]*'"$script_variant"':[[:space:]]*/ {
sub(/^[[:space:]]*'"$script_variant"':[[:space:]]*/, "")
print
exit
}
in_agent_scripts && /^[a-zA-Z]/ { in_agent_scripts=0 }
')
# Build body: replace placeholders, strip scripts sections, rewrite paths
local body
body=$(printf '%s\n' "$file_content" | sed "s|{SCRIPT}|${script_command}|g")
if [[ -n $agent_script_command ]]; then
body=$(printf '%s\n' "$body" | sed "s|{AGENT_SCRIPT}|${agent_script_command}|g")
fi
body=$(printf '%s\n' "$body" | awk '
/^---$/ { print; if (++dash_count == 1) in_frontmatter=1; else in_frontmatter=0; next }
in_frontmatter && /^scripts:$/ { skip_scripts=1; next }
in_frontmatter && /^agent_scripts:$/ { skip_scripts=1; next }
in_frontmatter && /^[a-zA-Z].*:/ && skip_scripts { skip_scripts=0 }
in_frontmatter && skip_scripts && /^[[:space:]]/ { next }
{ print }
')
body=$(printf '%s\n' "$body" | sed 's/{ARGS}/\$ARGUMENTS/g' | sed 's/__AGENT__/kimi/g' | rewrite_paths)
# Strip existing frontmatter and prepend Kimi frontmatter
local template_body
template_body=$(printf '%s\n' "$body" | awk '/^---/{p++; if(p==2){found=1; next}} found')
{
printf -- '---\n'
printf 'name: "%s"\n' "$skill_name"
printf 'description: "%s"\n' "$description"
printf -- '---\n\n'
printf '%s\n' "$template_body"
} > "$skill_dir/SKILL.md"
done
}
build_variant() {
local agent=$1 script=$2
local base_dir="$GENRELEASES_DIR/sdd-${agent}-package-${script}"
echo "Building $agent ($script) package..."
mkdir -p "$base_dir"
# Copy base structure but filter scripts by variant
SPEC_DIR="$base_dir/.specify"
mkdir -p "$SPEC_DIR"
[[ -d memory ]] && { cp -r memory "$SPEC_DIR/"; echo "Copied memory -> .specify"; }
# Only copy the relevant script variant directory
if [[ -d scripts ]]; then
mkdir -p "$SPEC_DIR/scripts"
case $script in
sh)
[[ -d scripts/bash ]] && { cp -r scripts/bash "$SPEC_DIR/scripts/"; echo "Copied scripts/bash -> .specify/scripts"; }
# Copy any script files that aren't in variant-specific directories
find scripts -maxdepth 1 -type f -exec cp {} "$SPEC_DIR/scripts/" \; 2>/dev/null || true
;;
ps)
[[ -d scripts/powershell ]] && { cp -r scripts/powershell "$SPEC_DIR/scripts/"; echo "Copied scripts/powershell -> .specify/scripts"; }
# Copy any script files that aren't in variant-specific directories
find scripts -maxdepth 1 -type f -exec cp {} "$SPEC_DIR/scripts/" \; 2>/dev/null || true
;;
esac
fi
[[ -d templates ]] && { mkdir -p "$SPEC_DIR/templates"; find templates -type f -not -path "templates/commands/*" -not -name "vscode-settings.json" -exec cp --parents {} "$SPEC_DIR"/ \; ; echo "Copied templates -> .specify/templates"; }
# NOTE: We substitute {ARGS} internally. Outward tokens differ intentionally:
# * Markdown/prompt (claude, copilot, cursor-agent, opencode): $ARGUMENTS
# * TOML (gemini, qwen): {{args}}
# This keeps formats readable without extra abstraction.
case $agent in
claude)
@@ -169,9 +231,7 @@ build_variant() {
copilot)
mkdir -p "$base_dir/.github/agents"
generate_commands copilot agent.md "\$ARGUMENTS" "$base_dir/.github/agents" "$script"
# Generate companion prompt files
generate_copilot_prompts "$base_dir/.github/agents" "$base_dir/.github/prompts"
# Create VS Code workspace settings
mkdir -p "$base_dir/.vscode"
[[ -f templates/vscode-settings.json ]] && cp templates/vscode-settings.json "$base_dir/.vscode/settings.json"
;;
@@ -180,7 +240,7 @@ build_variant() {
generate_commands cursor-agent md "\$ARGUMENTS" "$base_dir/.cursor/commands" "$script" ;;
qwen)
mkdir -p "$base_dir/.qwen/commands"
generate_commands qwen toml "{{args}}" "$base_dir/.qwen/commands" "$script"
generate_commands qwen md "\$ARGUMENTS" "$base_dir/.qwen/commands" "$script"
[[ -f agent_templates/qwen/QWEN.md ]] && cp agent_templates/qwen/QWEN.md "$base_dir/QWEN.md" ;;
opencode)
mkdir -p "$base_dir/.opencode/command"
@@ -212,15 +272,25 @@ build_variant() {
shai)
mkdir -p "$base_dir/.shai/commands"
generate_commands shai md "\$ARGUMENTS" "$base_dir/.shai/commands" "$script" ;;
q)
mkdir -p "$base_dir/.amazonq/prompts"
generate_commands q md "\$ARGUMENTS" "$base_dir/.amazonq/prompts" "$script" ;;
tabnine)
mkdir -p "$base_dir/.tabnine/agent/commands"
generate_commands tabnine toml "{{args}}" "$base_dir/.tabnine/agent/commands" "$script"
[[ -f agent_templates/tabnine/TABNINE.md ]] && cp agent_templates/tabnine/TABNINE.md "$base_dir/TABNINE.md" ;;
kiro-cli)
mkdir -p "$base_dir/.kiro/prompts"
generate_commands kiro-cli md "\$ARGUMENTS" "$base_dir/.kiro/prompts" "$script" ;;
agy)
mkdir -p "$base_dir/.agent/workflows"
generate_commands agy md "\$ARGUMENTS" "$base_dir/.agent/workflows" "$script" ;;
mkdir -p "$base_dir/.agent/commands"
generate_commands agy md "\$ARGUMENTS" "$base_dir/.agent/commands" "$script" ;;
bob)
mkdir -p "$base_dir/.bob/commands"
generate_commands bob md "\$ARGUMENTS" "$base_dir/.bob/commands" "$script" ;;
vibe)
mkdir -p "$base_dir/.vibe/prompts"
generate_commands vibe md "\$ARGUMENTS" "$base_dir/.vibe/prompts" "$script" ;;
kimi)
mkdir -p "$base_dir/.kimi/skills"
create_kimi_skills "$base_dir/.kimi/skills" "$script" ;;
generic)
mkdir -p "$base_dir/.speckit/commands"
generate_commands generic md "\$ARGUMENTS" "$base_dir/.speckit/commands" "$script" ;;
@@ -230,11 +300,10 @@ build_variant() {
}
# Determine agent list
ALL_AGENTS=(claude gemini copilot cursor-agent qwen opencode windsurf codex kilocode auggie roo codebuddy amp shai q agy bob qodercli generic)
ALL_AGENTS=(claude gemini copilot cursor-agent qwen opencode windsurf codex kilocode auggie roo codebuddy amp shai tabnine kiro-cli agy bob vibe qodercli kimi generic)
ALL_SCRIPTS=(sh ps)
norm_list() {
# convert comma+space separated -> line separated unique while preserving order of first occurrence
tr ',\n' ' ' | awk '{for(i=1;i<=NF;i++){if(!seen[$i]++){printf((out?"\n":"") $i);out=1}}}END{printf("\n")}'
}
@@ -277,4 +346,3 @@ done
echo "Archives in $GENRELEASES_DIR:"
ls -1 "$GENRELEASES_DIR"/spec-kit-template-*-"${NEW_VERSION}".zip

161
.github/workflows/scripts/simulate-release.sh vendored Executable file
View File

@@ -0,0 +1,161 @@
#!/usr/bin/env bash
set -euo pipefail
# simulate-release.sh
# Simulate the release process locally without pushing to GitHub
# Usage: simulate-release.sh [version]
# If version is omitted, auto-increments patch version
# Colors for output
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m' # No Color
echo -e "${BLUE}🧪 Simulating Release Process Locally${NC}"
echo "======================================"
echo ""
# Step 1: Determine version
if [[ -n "${1:-}" ]]; then
VERSION="${1#v}"
TAG="v$VERSION"
echo -e "${GREEN}📝 Using manual version: $VERSION${NC}"
else
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
echo -e "${BLUE}Latest tag: $LATEST_TAG${NC}"
VERSION=$(echo $LATEST_TAG | sed 's/v//')
IFS='.' read -ra VERSION_PARTS <<< "$VERSION"
MAJOR=${VERSION_PARTS[0]:-0}
MINOR=${VERSION_PARTS[1]:-0}
PATCH=${VERSION_PARTS[2]:-0}
PATCH=$((PATCH + 1))
VERSION="$MAJOR.$MINOR.$PATCH"
TAG="v$VERSION"
echo -e "${GREEN}📝 Auto-incremented to: $VERSION${NC}"
fi
echo ""
# Step 2: Check if tag exists
if git rev-parse "$TAG" >/dev/null 2>&1; then
echo -e "${RED}❌ Error: Tag $TAG already exists!${NC}"
echo " Please use a different version or delete the tag first."
exit 1
fi
echo -e "${GREEN}✓ Tag $TAG is available${NC}"
# Step 3: Backup current state
echo ""
echo -e "${YELLOW}💾 Creating backup of current state...${NC}"
BACKUP_DIR=$(mktemp -d)
cp pyproject.toml "$BACKUP_DIR/pyproject.toml.bak"
cp CHANGELOG.md "$BACKUP_DIR/CHANGELOG.md.bak"
echo -e "${GREEN}✓ Backup created at: $BACKUP_DIR${NC}"
# Step 4: Update pyproject.toml
echo ""
echo -e "${YELLOW}📝 Updating pyproject.toml...${NC}"
sed -i.tmp "s/version = \".*\"/version = \"$VERSION\"/" pyproject.toml
rm -f pyproject.toml.tmp
echo -e "${GREEN}✓ Updated pyproject.toml to version $VERSION${NC}"
# Step 5: Update CHANGELOG.md
echo ""
echo -e "${YELLOW}📝 Updating CHANGELOG.md...${NC}"
DATE=$(date +%Y-%m-%d)
# Get the previous tag to compare commits
PREVIOUS_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
if [[ -n "$PREVIOUS_TAG" ]]; then
echo " Generating changelog from commits since $PREVIOUS_TAG"
# Get commits since last tag, format as bullet points
COMMITS=$(git log --oneline "$PREVIOUS_TAG"..HEAD --no-merges --pretty=format:"- %s" 2>/dev/null || echo "- Initial release")
else
echo " No previous tag found - this is the first release"
COMMITS="- Initial release"
fi
# Create temp file with new entry
{
head -n 8 CHANGELOG.md
echo ""
echo "## [$VERSION] - $DATE"
echo ""
echo "### Changed"
echo ""
echo "$COMMITS"
echo ""
tail -n +9 CHANGELOG.md
} > CHANGELOG.md.tmp
mv CHANGELOG.md.tmp CHANGELOG.md
echo -e "${GREEN}✓ Updated CHANGELOG.md with commits since $PREVIOUS_TAG${NC}"
# Step 6: Show what would be committed
echo ""
echo -e "${YELLOW}📋 Changes that would be committed:${NC}"
git diff pyproject.toml CHANGELOG.md
# Step 7: Create temporary tag (no push)
echo ""
echo -e "${YELLOW}🏷️ Creating temporary local tag...${NC}"
git tag -a "$TAG" -m "Simulated release $TAG" 2>/dev/null || true
echo -e "${GREEN}✓ Tag $TAG created locally${NC}"
# Step 8: Simulate release artifact creation
echo ""
echo -e "${YELLOW}📦 Simulating release package creation...${NC}"
echo " (High-level simulation only; packaging script is not executed)"
echo ""
# Check if script exists and is executable
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if [[ -x "$SCRIPT_DIR/create-release-packages.sh" ]]; then
echo -e "${BLUE}In a real release, the following command would be run to create packages:${NC}"
echo " $SCRIPT_DIR/create-release-packages.sh \"$TAG\""
echo ""
echo "This simulation does not enumerate individual package files to avoid"
echo "drifting from the actual behavior of create-release-packages.sh."
else
echo -e "${RED}⚠️ create-release-packages.sh not found or not executable${NC}"
fi
# Step 9: Simulate release notes generation
echo ""
echo -e "${YELLOW}📄 Simulating release notes generation...${NC}"
echo ""
PREVIOUS_TAG=$(git describe --tags --abbrev=0 $TAG^ 2>/dev/null || echo "")
if [[ -n "$PREVIOUS_TAG" ]]; then
echo -e "${BLUE}Changes since $PREVIOUS_TAG:${NC}"
git log --oneline "$PREVIOUS_TAG".."$TAG" | head -n 10
echo ""
else
echo -e "${BLUE}No previous tag found - this would be the first release${NC}"
fi
# Step 10: Summary
echo ""
echo -e "${GREEN}🎉 Simulation Complete!${NC}"
echo "======================================"
echo ""
echo -e "${BLUE}Summary:${NC}"
echo " Version: $VERSION"
echo " Tag: $TAG"
echo " Backup: $BACKUP_DIR"
echo ""
echo -e "${YELLOW}⚠️ SIMULATION ONLY - NO CHANGES PUSHED${NC}"
echo ""
echo -e "${BLUE}Next steps:${NC}"
echo " 1. Review the changes above"
echo " 2. To keep changes: git add pyproject.toml CHANGELOG.md && git commit"
echo " 3. To discard changes: git checkout pyproject.toml CHANGELOG.md && git tag -d $TAG"
echo " 4. To restore from backup: cp $BACKUP_DIR/* ."
echo ""
echo -e "${BLUE}To run the actual release:${NC}"
echo " Go to: https://github.com/github/spec-kit/actions/workflows/release-trigger.yml"
echo " Click 'Run workflow' and enter version: $VERSION"
echo ""

View File

@@ -10,10 +10,6 @@ The toolkit supports multiple AI coding assistants, allowing teams to use their
---
## General practices
- Any changes to `__init__.py` for the Specify CLI require a version rev in `pyproject.toml` and addition of entries to `CHANGELOG.md`.
## Adding New Agent Support
This section explains how to add support for new AI agents/assistants to the Specify CLI. Use this guide as a reference when integrating new AI tools into the Spec-Driven Development workflow.
@@ -35,7 +31,7 @@ Specify supports multiple AI agents by generating agent-specific command files a
| **Gemini CLI** | `.gemini/commands/` | TOML | `gemini` | Google's Gemini CLI |
| **GitHub Copilot** | `.github/agents/` | Markdown | N/A (IDE-based) | GitHub Copilot in VS Code |
| **Cursor** | `.cursor/commands/` | Markdown | `cursor-agent` | Cursor CLI |
| **Qwen Code** | `.qwen/commands/` | TOML | `qwen` | Alibaba's Qwen Code CLI |
| **Qwen Code** | `.qwen/commands/` | Markdown | `qwen` | Alibaba's Qwen Code CLI |
| **opencode** | `.opencode/command/` | Markdown | `opencode` | opencode CLI |
| **Codex CLI** | `.codex/commands/` | Markdown | `codex` | Codex CLI |
| **Windsurf** | `.windsurf/workflows/` | Markdown | N/A (IDE-based) | Windsurf IDE workflows |
@@ -44,9 +40,11 @@ Specify supports multiple AI agents by generating agent-specific command files a
| **Roo Code** | `.roo/rules/` | Markdown | N/A (IDE-based) | Roo Code IDE |
| **CodeBuddy CLI** | `.codebuddy/commands/` | Markdown | `codebuddy` | CodeBuddy CLI |
| **Qoder CLI** | `.qoder/commands/` | Markdown | `qodercli` | Qoder CLI |
| **Amazon Q Developer CLI** | `.amazonq/prompts/` | Markdown | `q` | Amazon Q Developer CLI |
| **Kiro CLI** | `.kiro/prompts/` | Markdown | `kiro-cli` | Kiro CLI |
| **Amp** | `.agents/commands/` | Markdown | `amp` | Amp CLI |
| **SHAI** | `.shai/commands/` | Markdown | `shai` | SHAI CLI |
| **Tabnine CLI** | `.tabnine/agent/commands/` | TOML | `tabnine` | Tabnine CLI |
| **Kimi Code** | `.kimi/skills/` | Markdown | `kimi` | Kimi Code CLI (Moonshot AI) |
| **IBM Bob** | `.bob/commands/` | Markdown | N/A (IDE-based) | IBM Bob IDE |
| **Generic** | User-specified via `--ai-commands-dir` | Markdown | N/A | Bring your own agent |
@@ -86,7 +84,7 @@ This eliminates the need for special-case mappings throughout the codebase.
- `folder`: Directory where agent-specific files are stored (relative to project root)
- `commands_subdir`: Subdirectory name within the agent folder where command/prompt files are stored (default: `"commands"`)
- Most agents use `"commands"` (e.g., `.claude/commands/`)
- Some agents use alternative names: `"agents"` (copilot), `"workflows"` (windsurf, kilocode, agy), `"prompts"` (codex, q), `"command"` (opencode - singular)
- Some agents use alternative names: `"agents"` (copilot), `"workflows"` (windsurf, kilocode), `"prompts"` (codex, kiro-cli), `"command"` (opencode - singular)
- This field enables `--ai-skills` to locate command templates correctly for skill generation
- `install_url`: Installation documentation URL (set to `None` for IDE-based agents)
- `requires_cli`: Whether the agent requires a CLI tool check during initialization
@@ -96,7 +94,7 @@ This eliminates the need for special-case mappings throughout the codebase.
Update the `--ai` parameter help text in the `init()` command to include the new agent:
```python
ai_assistant: str = typer.Option(None, "--ai", help="AI assistant to use: claude, gemini, copilot, cursor-agent, qwen, opencode, codex, windsurf, kilocode, auggie, codebuddy, new-agent-cli, or q"),
ai_assistant: str = typer.Option(None, "--ai", help="AI assistant to use: claude, gemini, copilot, cursor-agent, qwen, opencode, codex, windsurf, kilocode, auggie, codebuddy, new-agent-cli, or kiro-cli"),
```
Also update any function docstrings, examples, and error messages that list available agents.
@@ -117,7 +115,7 @@ Modify `.github/workflows/scripts/create-release-packages.sh`:
##### Add to ALL_AGENTS array
```bash
ALL_AGENTS=(claude gemini copilot cursor-agent qwen opencode windsurf q)
ALL_AGENTS=(claude gemini copilot cursor-agent qwen opencode windsurf kiro-cli)
```
##### Add case statement for directory structure
@@ -317,11 +315,13 @@ Require a command-line tool to be installed:
- **Cursor**: `cursor-agent` CLI
- **Qwen Code**: `qwen` CLI
- **opencode**: `opencode` CLI
- **Amazon Q Developer CLI**: `q` CLI
- **Kiro CLI**: `kiro-cli` CLI
- **CodeBuddy CLI**: `codebuddy` CLI
- **Qoder CLI**: `qodercli` CLI
- **Amp**: `amp` CLI
- **SHAI**: `shai` CLI
- **Tabnine CLI**: `tabnine` CLI
- **Kimi Code**: `kimi` CLI
### IDE-Based Agents
@@ -335,7 +335,7 @@ Work within integrated development environments:
### Markdown Format
Used by: Claude, Cursor, opencode, Windsurf, Amazon Q Developer, Amp, SHAI, IBM Bob
Used by: Claude, Cursor, opencode, Windsurf, Kiro CLI, Amp, SHAI, IBM Bob, Kimi Code, Qwen
**Standard format:**
@@ -360,7 +360,7 @@ Command content with {SCRIPT} and $ARGUMENTS placeholders.
### TOML Format
Used by: Gemini, Qwen
Used by: Gemini, Tabnine
```toml
description = "Command description"

View File

@@ -7,6 +7,260 @@ Recent changes to the Specify CLI and templates are documented here.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.3.0] - 2026-03-13
### Changed
- feat(presets): Pluggable preset system with catalog, resolver, and skills propagation (#1787)
- fix: match 'Last updated' timestamp with or without bold markers (#1836)
- Add specify doctor command for project health diagnostics (#1828)
- fix: harden bash scripts against shell injection and improve robustness (#1809)
- fix: clean up command templates (specify, analyze) (#1810)
- fix: migrate Qwen Code CLI from TOML to Markdown format (#1589) (#1730)
- fix(cli): deprecate explicit command support for agy (#1798) (#1808)
- Add /selftest.extension core extension to test other extensions (#1758)
- feat(extensions): Quality of life improvements for RFC-aligned catalog integration (#1776)
- Add Java brownfield walkthrough to community walkthroughs (#1820)
- chore: bump version to 0.2.1 (#1813)
- Added February 2026 newsletter (#1812)
- feat: add Kimi Code CLI agent support (#1790)
- docs: fix broken links in quickstart guide (#1759) (#1797)
- docs: add catalog cli help documentation (#1793) (#1794)
- fix: use quiet checkout to avoid exception on git checkout (#1792)
- feat(extensions): support .extensionignore to exclude files during install (#1781)
- feat: add Codex support for extension command registration (#1767)
- chore: bump version to 0.2.0 (#1786)
- fix: sync agent list comments with actual supported agents (#1785)
- feat(extensions): support multiple active catalogs simultaneously (#1720)
- Pavel/add tabnine cli support (#1503)
- Add Understanding extension to community catalog (#1778)
- Add ralph extension to community catalog (#1780)
- Update README with project initialization instructions (#1772)
- feat: add review extension to community catalog (#1775)
- Add fleet extension to community catalog (#1771)
- Integration of Mistral vibe support into speckit (#1725)
- fix: Remove duplicate options in specify.md (#1765)
- fix: use global branch numbering instead of per-short-name detection (#1757)
- Add Community Walkthroughs section to README (#1766)
- feat(extensions): add Jira Integration to community catalog (#1764)
- Add Azure DevOps Integration extension to community catalog (#1734)
- Fix docs: update Antigravity link and add initialization example (#1748)
- fix: wire after_tasks and after_implement hook events into command templates (#1702)
- make c ignores consistent with c++ (#1747)
- chore: bump version to 0.1.13 (#1746)
- feat: add kiro-cli and AGENT_CONFIG consistency coverage (#1690)
- feat: add verify extension to community catalog (#1726)
- Add Retrospective Extension to community catalog README table (#1741)
- fix(scripts): add empty description validation and branch checkout error handling (#1559)
- fix: correct Copilot extension command registration (#1724)
- fix(implement): remove Makefile from C ignore patterns (#1558)
- Add sync extension to community catalog (#1728)
- fix(checklist): clarify file handling behavior for append vs create (#1556)
- fix(clarify): correct conflicting question limit from 10 to 5 (#1557)
- chore: bump version to 0.1.12 (#1737)
- fix: use RELEASE_PAT so tag push triggers release workflow (#1736)
- fix: release-trigger uses release branch + PR instead of direct push to main (#1733)
- fix: Split release process to sync pyproject.toml version with git tags (#1732)
## [Unreleased]
### Added
- feat(presets): Pluggable preset system with preset catalog and template resolver
- Preset manifest (`preset.yml`) with validation for artifact, command, and script types
- `PresetManifest`, `PresetRegistry`, `PresetManager`, `PresetCatalog`, `PresetResolver` classes in `src/specify_cli/presets.py`
- CLI commands: `specify preset search`, `specify preset add`, `specify preset list`, `specify preset remove`, `specify preset resolve`, `specify preset info`
- CLI commands: `specify preset catalog list`, `specify preset catalog add`, `specify preset catalog remove` for multi-catalog management
- `PresetCatalogEntry` dataclass and multi-catalog support mirroring the extension catalog system
- `--preset` option for `specify init` to install presets during initialization
- Priority-based preset resolution: presets with lower priority number win (`--priority` flag)
- `resolve_template()` / `Resolve-Template` helpers in bash and PowerShell common scripts
- Template resolution priority stack: overrides → presets → extensions → core
- Preset catalog files (`presets/catalog.json`, `presets/catalog.community.json`)
- Preset scaffold directory (`presets/scaffold/`)
- Scripts updated to use template resolution instead of hardcoded paths
- feat(presets): Preset command overrides now propagate to agent skills when `--ai-skills` was used during init
- feat: `specify init` persists CLI options to `.specify/init-options.json` for downstream operations
- feat(extensions): support `.extensionignore` to exclude files/folders during `specify extension add` (#1781)
## [0.2.1] - 2026-03-11
### Changed
- Added February 2026 newsletter (#1812)
- feat: add Kimi Code CLI agent support (#1790)
- docs: fix broken links in quickstart guide (#1759) (#1797)
- docs: add catalog cli help documentation (#1793) (#1794)
- fix: use quiet checkout to avoid exception on git checkout (#1792)
- feat(extensions): support .extensionignore to exclude files during install (#1781)
- feat: add Codex support for extension command registration (#1767)
- chore: bump version to 0.2.0 (#1786)
- fix: sync agent list comments with actual supported agents (#1785)
- feat(extensions): support multiple active catalogs simultaneously (#1720)
- Pavel/add tabnine cli support (#1503)
- Add Understanding extension to community catalog (#1778)
- Add ralph extension to community catalog (#1780)
- Update README with project initialization instructions (#1772)
- feat: add review extension to community catalog (#1775)
- Add fleet extension to community catalog (#1771)
- Integration of Mistral vibe support into speckit (#1725)
- fix: Remove duplicate options in specify.md (#1765)
- fix: use global branch numbering instead of per-short-name detection (#1757)
- Add Community Walkthroughs section to README (#1766)
- feat(extensions): add Jira Integration to community catalog (#1764)
- Add Azure DevOps Integration extension to community catalog (#1734)
- Fix docs: update Antigravity link and add initialization example (#1748)
- fix: wire after_tasks and after_implement hook events into command templates (#1702)
- make c ignores consistent with c++ (#1747)
- chore: bump version to 0.1.13 (#1746)
- feat: add kiro-cli and AGENT_CONFIG consistency coverage (#1690)
- feat: add verify extension to community catalog (#1726)
- Add Retrospective Extension to community catalog README table (#1741)
- fix(scripts): add empty description validation and branch checkout error handling (#1559)
- fix: correct Copilot extension command registration (#1724)
- fix(implement): remove Makefile from C ignore patterns (#1558)
- Add sync extension to community catalog (#1728)
- fix(checklist): clarify file handling behavior for append vs create (#1556)
- fix(clarify): correct conflicting question limit from 10 to 5 (#1557)
- chore: bump version to 0.1.12 (#1737)
- fix: use RELEASE_PAT so tag push triggers release workflow (#1736)
- fix: release-trigger uses release branch + PR instead of direct push to main (#1733)
- fix: Split release process to sync pyproject.toml version with git tags (#1732)
## [0.2.0] - 2026-03-09
### Changed
- feat: add Kimi Code CLI agent support
- fix: sync agent list comments with actual supported agents (#1785)
- feat(extensions): support multiple active catalogs simultaneously (#1720)
- Pavel/add tabnine cli support (#1503)
- Add Understanding extension to community catalog (#1778)
- Add ralph extension to community catalog (#1780)
- Update README with project initialization instructions (#1772)
- feat: add review extension to community catalog (#1775)
- Add fleet extension to community catalog (#1771)
- Integration of Mistral vibe support into speckit (#1725)
- fix: Remove duplicate options in specify.md (#1765)
- fix: use global branch numbering instead of per-short-name detection (#1757)
- Add Community Walkthroughs section to README (#1766)
- feat(extensions): add Jira Integration to community catalog (#1764)
- Add Azure DevOps Integration extension to community catalog (#1734)
- Fix docs: update Antigravity link and add initialization example (#1748)
- fix: wire after_tasks and after_implement hook events into command templates (#1702)
- make c ignores consistent with c++ (#1747)
- chore: bump version to 0.1.13 (#1746)
- feat: add kiro-cli and AGENT_CONFIG consistency coverage (#1690)
- feat: add verify extension to community catalog (#1726)
- Add Retrospective Extension to community catalog README table (#1741)
- fix(scripts): add empty description validation and branch checkout error handling (#1559)
- fix: correct Copilot extension command registration (#1724)
- fix(implement): remove Makefile from C ignore patterns (#1558)
- Add sync extension to community catalog (#1728)
- fix(checklist): clarify file handling behavior for append vs create (#1556)
- fix(clarify): correct conflicting question limit from 10 to 5 (#1557)
- chore: bump version to 0.1.12 (#1737)
- fix: use RELEASE_PAT so tag push triggers release workflow (#1736)
- fix: release-trigger uses release branch + PR instead of direct push to main (#1733)
- fix: Split release process to sync pyproject.toml version with git tags (#1732)
## [0.1.14] - 2026-03-09
### Added
- feat: add Tabnine CLI agent support
- **Multi-Catalog Support (#1707)**: Extension catalog system now supports multiple active catalogs simultaneously via a catalog stack
- New `specify extension catalog list` command lists all active catalogs with name, URL, priority, and `install_allowed` status
- New `specify extension catalog add` and `specify extension catalog remove` commands for project-scoped catalog management
- Default built-in stack includes `catalog.json` (default, installable) and `catalog.community.json` (community, discovery only) — community extensions are now surfaced in search results out of the box
- `specify extension search` aggregates results across all active catalogs, annotating each result with source catalog
- `specify extension add` enforces `install_allowed` policy — extensions from discovery-only catalogs cannot be installed directly
- Project-level `.specify/extension-catalogs.yml` and user-level `~/.specify/extension-catalogs.yml` config files supported, with project-level taking precedence
- `SPECKIT_CATALOG_URL` environment variable still works for backward compatibility (replaces full stack with single catalog)
- All catalog URLs require HTTPS (HTTP allowed for localhost development)
- New `CatalogEntry` dataclass in `extensions.py` for catalog stack representation
- Per-URL hash-based caching for non-default catalogs; legacy cache preserved for default catalog
- Higher-priority catalogs win on merge conflicts (same extension id in multiple catalogs)
- 13 new tests covering catalog stack resolution, merge conflicts, URL validation, and `install_allowed` enforcement
- Updated RFC, Extension User Guide, and Extension API Reference documentation
## [0.1.13] - 2026-03-03
### Changed
- feat: add kiro-cli and AGENT_CONFIG consistency coverage (#1690)
- feat: add verify extension to community catalog (#1726)
- Add Retrospective Extension to community catalog README table (#1741)
- fix(scripts): add empty description validation and branch checkout error handling (#1559)
- fix: correct Copilot extension command registration (#1724)
- fix(implement): remove Makefile from C ignore patterns (#1558)
- Add sync extension to community catalog (#1728)
- fix(checklist): clarify file handling behavior for append vs create (#1556)
- fix(clarify): correct conflicting question limit from 10 to 5 (#1557)
- chore: bump version to 0.1.12 (#1737)
- fix: use RELEASE_PAT so tag push triggers release workflow (#1736)
- fix: release-trigger uses release branch + PR instead of direct push to main (#1733)
- fix: Split release process to sync pyproject.toml version with git tags (#1732)
## [0.1.13] - 2026-03-03
### Fixed
- **Copilot Extension Commands Not Visible**: Fixed extension commands not appearing in GitHub Copilot when installed via `specify extension add --dev`
- Changed Copilot file extension from `.md` to `.agent.md` in `CommandRegistrar.AGENT_CONFIGS` so Copilot recognizes agent files
- Added generation of companion `.prompt.md` files in `.github/prompts/` during extension command registration, matching the release packaging behavior
- Added cleanup of `.prompt.md` companion files when removing extensions via `specify extension remove`
- Fixed a syntax regression in `src/specify_cli/__init__.py` in `_build_ai_assistant_help()` that broke `ruff` and `pytest` collection in CI.
## [0.1.12] - 2026-03-02
### Changed
- fix: use RELEASE_PAT so tag push triggers release workflow (#1736)
- fix: release-trigger uses release branch + PR instead of direct push to main (#1733)
- fix: Split release process to sync pyproject.toml version with git tags (#1732)
## [0.1.10] - 2026-03-02
### Fixed
- **Version Sync Issue (#1721)**: Fixed version mismatch between `pyproject.toml` and git release tags
- Split release process into two workflows: `release-trigger.yml` for version management and `release.yml` for artifact building
- Version bump now happens BEFORE tag creation, ensuring tags point to commits with correct version
- Supports both manual version specification and auto-increment (patch version)
- Git tags now accurately reflect the version in `pyproject.toml` at that commit
- Prevents confusion when installing from source
## [0.1.9] - 2026-02-28
### Changed
- Updated dependency: bumped astral-sh/setup-uv from 6 to 7
## [0.1.8] - 2026-02-28
### Changed
- Updated dependency: bumped actions/setup-python from 5 to 6
## [0.1.7] - 2026-02-27
### Changed
- Updated outdated GitHub Actions versions
- Documented dual-catalog system for extensions
### Fixed
- Fixed version command in documentation
### Added
- Added Cleanup Extension to README
- Added retrospective extension to community catalog
## [0.1.6] - 2026-02-23
### Fixed

View File

@@ -22,6 +22,7 @@
- [🤔 What is Spec-Driven Development?](#-what-is-spec-driven-development)
- [⚡ Get Started](#-get-started)
- [📽️ Video Overview](#-video-overview)
- [🚶 Community Walkthroughs](#-community-walkthroughs)
- [🤖 Supported AI Agents](#-supported-ai-agents)
- [🔧 Specify CLI Reference](#-specify-cli-reference)
- [📚 Core Philosophy](#-core-philosophy)
@@ -79,7 +80,13 @@ uv tool install specify-cli --force --from git+https://github.com/github/spec-ki
Run directly without installing:
```bash
# Create new project
uvx --from git+https://github.com/github/spec-kit.git specify init <PROJECT_NAME>
# Or initialize in existing project
uvx --from git+https://github.com/github/spec-kit.git specify init . --ai claude
# or
uvx --from git+https://github.com/github/spec-kit.git specify init --here --ai claude
```
**Benefits of persistent installation:**
@@ -139,12 +146,24 @@ Want to see Spec Kit in action? Watch our [video overview](https://www.youtube.c
[![Spec Kit video header](/media/spec-kit-video-header.jpg)](https://www.youtube.com/watch?v=a9eR1xsfvHg&pp=0gcJCckJAYcqIYzv)
## 🚶 Community Walkthroughs
See Spec-Driven Development in action across different scenarios with these community-contributed walkthroughs:
- **[Greenfield .NET CLI tool](https://github.com/mnriem/spec-kit-dotnet-cli-demo)** — Builds a Timezone Utility as a .NET single-binary CLI tool from a blank directory, covering the full spec-kit workflow: constitution, specify, plan, tasks, and multi-pass implement using GitHub Copilot agents.
- **[Greenfield Spring Boot + React platform](https://github.com/mnriem/spec-kit-spring-react-demo)** — Builds an LLM performance analytics platform (REST API, graphs, iteration tracking) from scratch using Spring Boot, embedded React, PostgreSQL, and Docker Compose, with a clarify step and a cross-artifact consistency analysis pass included.
- **[Brownfield ASP.NET CMS extension](https://github.com/mnriem/spec-kit-aspnet-brownfield-demo)** — Extends an existing open-source .NET CMS (CarrotCakeCMS-Core, ~307,000 lines of C#, Razor, SQL, JavaScript, and config files) with two new features — cross-platform Docker Compose infrastructure and a token-authenticated headless REST API — demonstrating how spec-kit fits into existing codebases without prior specs or a constitution.
- **[Brownfield Java runtime extension](https://github.com/mnriem/spec-kit-java-brownfield-demo)** — Extends an existing open-source Jakarta EE runtime (Piranha, ~420,000 lines of Java, XML, JSP, HTML, and config files across 180 Maven modules) with a password-protected Server Admin Console, demonstrating spec-kit on a large multi-module Java project with no prior specs or constitution.
## 🤖 Supported AI Agents
| Agent | Support | Notes |
| ------------------------------------------------------------------------------------ | ------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| [Qoder CLI](https://qoder.com/cli) | ✅ | |
| [Amazon Q Developer CLI](https://aws.amazon.com/developer/learning/q-developer-cli/) | ⚠️ | Amazon Q Developer CLI [does not support](https://github.com/aws/amazon-q-developer-cli/issues/3064) custom arguments for slash commands. |
| [Kiro CLI](https://kiro.dev/docs/cli/) | ✅ | Use `--ai kiro-cli` (alias: `--ai kiro`) |
| [Amp](https://ampcode.com/) | ✅ | |
| [Auggie CLI](https://docs.augmentcode.com/cli/overview) | ✅ | |
| [Claude Code](https://www.anthropic.com/claude-code) | ✅ | |
@@ -160,8 +179,11 @@ Want to see Spec Kit in action? Watch our [video overview](https://www.youtube.c
| [Qwen Code](https://github.com/QwenLM/qwen-code) | ✅ | |
| [Roo Code](https://roocode.com/) | ✅ | |
| [SHAI (OVHcloud)](https://github.com/ovh/shai) | ✅ | |
| [Tabnine CLI](https://docs.tabnine.com/main/getting-started/tabnine-cli) | ✅ | |
| [Mistral Vibe](https://github.com/mistralai/mistral-vibe) | ✅ | |
| [Kimi Code](https://code.kimi.com/) | ✅ | |
| [Windsurf](https://windsurf.com/) | ✅ | |
| [Antigravity (agy)](https://agy.ai/) | ✅ | |
| [Antigravity (agy)](https://antigravity.google/) | ✅ | Requires `--ai-skills` |
| Generic | ✅ | Bring your own agent — use `--ai generic --ai-commands-dir <path>` for unsupported agents |
## 🔧 Specify CLI Reference
@@ -173,14 +195,14 @@ The `specify` command supports the following options:
| Command | Description |
| ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `init` | Initialize a new Specify project from the latest template |
| `check` | Check for installed tools (`git`, `claude`, `gemini`, `code`/`code-insiders`, `cursor-agent`, `windsurf`, `qwen`, `opencode`, `codex`, `shai`, `qodercli`) |
| `check` | Check for installed tools (`git`, `claude`, `gemini`, `code`/`code-insiders`, `cursor-agent`, `windsurf`, `qwen`, `opencode`, `codex`, `kiro-cli`, `shai`, `qodercli`, `vibe`, `kimi`) |
### `specify init` Arguments & Options
| Argument/Option | Type | Description |
| ---------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `<project-name>` | Argument | Name for your new project directory (optional if using `--here`, or use `.` for current directory) |
| `--ai` | Option | AI assistant to use: `claude`, `gemini`, `copilot`, `cursor-agent`, `qwen`, `opencode`, `codex`, `windsurf`, `kilocode`, `auggie`, `roo`, `codebuddy`, `amp`, `shai`, `q`, `agy`, `bob`, `qodercli`, or `generic` (requires `--ai-commands-dir`) |
| `--ai` | Option | AI assistant to use: `claude`, `gemini`, `copilot`, `cursor-agent`, `qwen`, `opencode`, `codex`, `windsurf`, `kilocode`, `auggie`, `roo`, `codebuddy`, `amp`, `shai`, `kiro-cli` (`kiro` alias), `agy`, `bob`, `qodercli`, `vibe`, `kimi`, or `generic` (requires `--ai-commands-dir`) |
| `--ai-commands-dir` | Option | Directory for agent command files (required with `--ai generic`, e.g. `.myagent/commands/`) |
| `--script` | Option | Script variant to use: `sh` (bash/zsh) or `ps` (PowerShell) |
| `--ignore-agent-tools` | Flag | Skip checks for AI agent tools like Claude Code |
@@ -210,15 +232,24 @@ specify init my-project --ai qodercli
# Initialize with Windsurf support
specify init my-project --ai windsurf
# Initialize with Kiro CLI support
specify init my-project --ai kiro-cli
# Initialize with Amp support
specify init my-project --ai amp
# Initialize with SHAI support
specify init my-project --ai shai
# Initialize with Mistral Vibe support
specify init my-project --ai vibe
# Initialize with IBM Bob support
specify init my-project --ai bob
# Initialize with Antigravity support
specify init my-project --ai agy --ai-skills
# Initialize with an unsupported agent (generic / bring your own agent)
specify init my-project --ai generic --ai-commands-dir .myagent/commands/
@@ -393,7 +424,7 @@ specify init . --force --ai claude
specify init --here --force --ai claude
```
The CLI will check if you have Claude Code, Gemini CLI, Cursor CLI, Qwen CLI, opencode, Codex CLI, Qoder CLI, or Amazon Q Developer CLI installed. If you do not, or you prefer to get the templates without checking for the right tools, use `--ignore-agent-tools` with your command:
The CLI will check if you have Claude Code, Gemini CLI, Cursor CLI, Qwen CLI, opencode, Codex CLI, Qoder CLI, Tabnine CLI, Kiro CLI, or Mistral Vibe installed. If you do not, or you prefer to get the templates without checking for the right tools, use `--ignore-agent-tools` with your command:
```bash
specify init <project_name> --ai claude --ignore-agent-tools

View File

@@ -173,6 +173,6 @@ Finally, implement the solution:
## Next Steps
- Read the [complete methodology](../spec-driven.md) for in-depth guidance
- Check out [more examples](../templates) in the repository
- Read the [complete methodology](https://github.com/github/spec-kit/blob/main/spec-driven.md) for in-depth guidance
- Check out [more examples](https://github.com/github/spec-kit/tree/main/templates) in the repository
- Explore the [source code on GitHub](https://github.com/github/spec-kit)

View File

@@ -243,6 +243,34 @@ manager.check_compatibility(
) # Raises: CompatibilityError if incompatible
```
### CatalogEntry
**Module**: `specify_cli.extensions`
Represents a single catalog in the active catalog stack.
```python
from specify_cli.extensions import CatalogEntry
entry = CatalogEntry(
url="https://example.com/catalog.json",
name="default",
priority=1,
install_allowed=True,
description="Built-in catalog of installable extensions",
)
```
**Fields**:
| Field | Type | Description |
|-------|------|-------------|
| `url` | `str` | Catalog URL (must use HTTPS, or HTTP for localhost) |
| `name` | `str` | Human-readable catalog name |
| `priority` | `int` | Sort order (lower = higher priority, wins on conflicts) |
| `install_allowed` | `bool` | Whether extensions from this catalog can be installed |
| `description` | `str` | Optional human-readable description of the catalog (default: empty) |
### ExtensionCatalog
**Module**: `specify_cli.extensions`
@@ -253,30 +281,67 @@ from specify_cli.extensions import ExtensionCatalog
catalog = ExtensionCatalog(project_root)
```
**Class attributes**:
```python
ExtensionCatalog.DEFAULT_CATALOG_URL # default catalog URL
ExtensionCatalog.COMMUNITY_CATALOG_URL # community catalog URL
```
**Methods**:
```python
# Fetch catalog
# Get the ordered list of active catalogs
entries = catalog.get_active_catalogs() # List[CatalogEntry]
# Fetch catalog (primary catalog, backward compat)
catalog_data = catalog.fetch_catalog(force_refresh: bool = False) # Dict
# Search extensions
# Search extensions across all active catalogs
# Each result includes _catalog_name and _install_allowed
results = catalog.search(
query: Optional[str] = None,
tag: Optional[str] = None,
author: Optional[str] = None,
verified_only: bool = False
) # Returns: List[Dict]
) # Returns: List[Dict] — each dict includes _catalog_name, _install_allowed
# Get extension info
# Get extension info (searches all active catalogs)
# Returns None if not found; includes _catalog_name and _install_allowed
ext_info = catalog.get_extension_info(extension_id: str) # Optional[Dict]
# Check cache validity
# Check cache validity (primary catalog)
is_valid = catalog.is_cache_valid() # bool
# Clear cache
# Clear all catalog caches
catalog.clear_cache()
```
**Result annotation fields**:
Each extension dict returned by `search()` and `get_extension_info()` includes:
| Field | Type | Description |
|-------|------|-------------|
| `_catalog_name` | `str` | Name of the source catalog |
| `_install_allowed` | `bool` | Whether installation is allowed from this catalog |
**Catalog config file** (`.specify/extension-catalogs.yml`):
```yaml
catalogs:
- name: "default"
url: "https://raw.githubusercontent.com/github/spec-kit/main/extensions/catalog.json"
priority: 1
install_allowed: true
description: "Built-in catalog of installable extensions"
- name: "community"
url: "https://raw.githubusercontent.com/github/spec-kit/main/extensions/catalog.community.json"
priority: 2
install_allowed: false
description: "Community-contributed extensions (discovery only)"
```
### HookExecutor
**Module**: `specify_cli.extensions`
@@ -543,6 +608,39 @@ EXECUTE_COMMAND: {command}
**Output**: List of installed extensions with metadata
### extension catalog list
**Usage**: `specify extension catalog list`
Lists all active catalogs in the current catalog stack, showing name, description, URL, priority, and `install_allowed` status.
### extension catalog add
**Usage**: `specify extension catalog add URL [OPTIONS]`
**Options**:
- `--name NAME` - Catalog name (required)
- `--priority INT` - Priority (lower = higher priority, default: 10)
- `--install-allowed / --no-install-allowed` - Allow installs from this catalog (default: false)
- `--description TEXT` - Optional description of the catalog
**Arguments**:
- `URL` - Catalog URL (must use HTTPS)
Adds a catalog entry to `.specify/extension-catalogs.yml`.
### extension catalog remove
**Usage**: `specify extension catalog remove NAME`
**Arguments**:
- `NAME` - Catalog name to remove
Removes a catalog entry from `.specify/extension-catalogs.yml`.
### extension add
**Usage**: `specify extension add EXTENSION [OPTIONS]`
@@ -551,13 +649,13 @@ EXECUTE_COMMAND: {command}
- `--from URL` - Install from custom URL
- `--dev PATH` - Install from local directory
- `--version VERSION` - Install specific version
- `--no-register` - Skip command registration
**Arguments**:
- `EXTENSION` - Extension name or URL
**Note**: Extensions from catalogs with `install_allowed: false` cannot be installed via this command.
### extension remove
**Usage**: `specify extension remove EXTENSION [OPTIONS]`
@@ -575,6 +673,8 @@ EXECUTE_COMMAND: {command}
**Usage**: `specify extension search [QUERY] [OPTIONS]`
Searches all active catalogs simultaneously. Results include source catalog name and install_allowed status.
**Options**:
- `--tag TAG` - Filter by tag
@@ -589,6 +689,8 @@ EXECUTE_COMMAND: {command}
**Usage**: `specify extension info EXTENSION`
Shows source catalog and install_allowed status.
**Arguments**:
- `EXTENSION` - Extension ID

View File

@@ -332,6 +332,67 @@ echo "$config"
---
## Excluding Files with `.extensionignore`
Extension authors can create a `.extensionignore` file in the extension root to exclude files and folders from being copied when a user installs the extension with `specify extension add`. This is useful for keeping development-only files (tests, CI configs, docs source, etc.) out of the installed copy.
### Format
The file uses `.gitignore`-compatible patterns (one per line), powered by the [`pathspec`](https://pypi.org/project/pathspec/) library:
- Blank lines are ignored
- Lines starting with `#` are comments
- `*` matches anything **except** `/` (does not cross directory boundaries)
- `**` matches zero or more directories (e.g., `docs/**/*.draft.md`)
- `?` matches any single character except `/`
- A trailing `/` restricts a pattern to directories only
- Patterns containing `/` (other than a trailing slash) are anchored to the extension root
- Patterns without `/` match at any depth in the tree
- `!` negates a previously excluded pattern (re-includes a file)
- Backslashes in patterns are normalised to forward slashes for cross-platform compatibility
- The `.extensionignore` file itself is always excluded automatically
### Example
```gitignore
# .extensionignore
# Development files
tests/
.github/
.gitignore
# Build artifacts
__pycache__/
*.pyc
dist/
# Documentation source (keep only the built README)
docs/
CONTRIBUTING.md
```
### Pattern Matching
| Pattern | Matches | Does NOT match |
|---------|---------|----------------|
| `*.pyc` | Any `.pyc` file in any directory | — |
| `tests/` | The `tests` directory (and all its contents) | A file named `tests` |
| `docs/*.draft.md` | `docs/api.draft.md` (directly inside `docs/`) | `docs/sub/api.draft.md` (nested) |
| `.env` | The `.env` file at any level | — |
| `!README.md` | Re-includes `README.md` even if matched by an earlier pattern | — |
| `docs/**/*.draft.md` | `docs/api.draft.md`, `docs/sub/api.draft.md` | — |
### Unsupported Features
The following `.gitignore` features are **not applicable** in this context:
- **Multiple `.extensionignore` files**: Only a single file at the extension root is supported (`.gitignore` supports files in subdirectories)
- **`$GIT_DIR/info/exclude` and `core.excludesFile`**: These are Git-specific and have no equivalent here
- **Negation inside excluded directories**: Because file copying uses `shutil.copytree`, excluding a directory prevents recursion into it entirely. A negation pattern cannot re-include a file inside a directory that was itself excluded. For example, the combination `tests/` followed by `!tests/important.py` will **not** preserve `tests/important.py` — the `tests/` directory is skipped at the root level and its contents are never evaluated. To work around this, exclude the directory's contents individually instead of the directory itself (e.g., `tests/*.pyc` and `tests/.cache/` rather than `tests/`).
---
## Validation Rules
### Extension ID