* fix(skills): preserve non-ASCII chars in skill frontmatter
Skill SKILL.md frontmatter descriptions containing non-ASCII
characters were escaped to \uXXXX / \xXX sequences because
yaml.safe_dump() was called without allow_unicode=True.
- Add allow_unicode=True to the 7 skill/command frontmatter
safe_dump sites (extensions, presets, claude integration)
- Add regression tests for the render and extension-install paths
Follows the approach of #1936; encoding="utf-8" is already set on
the affected write paths, so no encoding change is needed here.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(_utils): add dump_frontmatter helper
Centralize skill/command frontmatter YAML serialization into a single
_utils.dump_frontmatter helper so no call site can drop allow_unicode or
diverge on formatting. Route the 7 existing sites through it and drop a
now-unused local yaml import.
Switch the extension test fixtures to yaml.safe_dump for parity with the
production safe-dump/safe-load codepaths.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix: prevent extension self-install from deleting source dir (#2990)
`specify extension add <path> --dev --force` permanently deleted the
extension directory without registering it when the source path resolved
to the extension's own install location (`.specify/extensions/<id>`).
With `--force`, `install_from_directory()` removed the existing
installation (the source) and then `shutil.copytree()` tried to copy from
the now-deleted directory, destroying it and crashing.
Add a guard that fails fast with a clear ValidationError when the resolved
source path equals the install destination, before any destructive
operation runs. Includes a regression test asserting the directory and its
contents survive.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: harden extension self-install guard
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix: disable Rich Live transient mode on Windows to prevent PS 5.1 hang
PowerShell 5.1's legacy console host does not reliably support VT escape
sequences. Rich's Live(transient=True) attempts cursor restoration on
context exit, which hangs indefinitely on that console.
Set transient=False when sys.platform == 'win32' in both init.py (progress
tracker) and _console.py (select_with_arrows). The only cosmetic effect is
that progress output remains visible after completion on Windows.
Fixes#2927
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* test: address review feedback on test quality
- Use captured['transient'] instead of .get() for clearer KeyError on failure
- Source guards now assert both the platform check AND transient=_transient usage
- Remove unused imports (MagicMock retained as it's used, removed pytest)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* test: use regex in source guards for resilience to formatting changes
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* test: use single DOTALL regex to verify assignment flows into Live()
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: skip duplicate tracker print on Windows when transient=False
When transient is False, Rich leaves the Live output on screen. The
subsequent console.print(tracker.render()) would duplicate it. Gate
it behind _transient so Windows users see the tracker exactly once.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Manfred Riem <mnriem@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore: bump version to 0.11.0
* chore: begin 0.11.1.dev0 development
---------
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* Initial plan
* Add workflow step catalog: StepRegistry, StepCatalog, CLI commands, and tests
Agent-Logs-Url: https://github.com/github/spec-kit/sessions/2885e646-477d-4df8-b9a3-06d8cb29e748
Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com>
* Potential fix for pull request finding 'An assert statement has a side-effect'
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
* Address PR review: path traversal, cache robustness, collision check, failed-to-load display
- Add resolve()+relative_to() path traversal guards in workflow_step_add and
workflow_step_remove to prevent directory escape via step_id
- Harden _is_url_cache_valid in both StepCatalog and WorkflowCatalog to
coerce fetched_at to float and catch TypeError/ValueError
- Check STEP_REGISTRY and StepRegistry before installing to prevent
collisions with built-in step types or already-installed steps
- Show 'Custom (installed, failed to load)' section in workflow step list
for steps in the registry that failed to load into STEP_REGISTRY
* Fix StepRegistry shape validation and StepCatalog empty-YAML handling
Agent-Logs-Url: https://github.com/github/spec-kit/sessions/0dca6393-f5a9-40de-bb5c-77ba6af033d2
Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com>
* Polish: rename _default to default_registry, strengthen unreadable-file test
Agent-Logs-Url: https://github.com/github/spec-kit/sessions/0dca6393-f5a9-40de-bb5c-77ba6af033d2
Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com>
* Address PR review: atomic install, hostname validation, cache resilience, no dynamic imports in list/info
Agent-Logs-Url: https://github.com/github/spec-kit/sessions/3e18fef0-e2e6-4b3e-9e8d-9adb1e5e464e
Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com>
* Fix shutil.move with existing step_dir: remove before move to avoid subdirectory nesting
Agent-Logs-Url: https://github.com/github/spec-kit/sessions/3e18fef0-e2e6-4b3e-9e8d-9adb1e5e464e
Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com>
* Call load_custom_steps at execution time; enforce hostname in _safe_fetch and _validate_url
Agent-Logs-Url: https://github.com/github/spec-kit/sessions/73865880-fb25-4061-a43e-4e4b4d1c4de6
Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com>
* Wrap YAML parsing in try/except; atomic step install via os.rename() under same fs
Agent-Logs-Url: https://github.com/github/spec-kit/sessions/ff915bc5-ec7e-4e6a-b505-35f5795250df
Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com>
* Validate YAML root is a dict in _load_catalog_config and workflow_step_add; fix WorkflowCatalog hostname validation
Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com>
* Fix load_custom_steps() package imports and add reserved step ID validation
* Move _re/_sys imports out of loop and _RESERVED_STEP_IDS to module level
* Address review: collision-resistant module names, extra_files support, remove orphan dir
* Harden extra_files: warn on non-dict, resolve symlinks in path traversal check
* Switch _safe_fetch and StepCatalog._fetch_single_catalog to use open_url for auth consistency
* Harden step_id validation against path-segment tricks; raise on StepRegistry.save() OSError
* Clean up sys.modules on broken step packages; handle StepValidationError in step add/remove
* Address review thread: int-coerce priorities, sys.modules cleanup, _require_specify_project, registry-first remove
* fix: normalize workflow step catalog metadata fallbacks
* fix: address latest workflow step and catalog review findings
* Handle non-string extra_files keys in workflow step add
* Harden StepRegistry symlink reads and extra_files path/URL validation
* Harden custom step loader and step remove against symlinks and OSError
* Fix StepCatalog.search() to coerce non-string fields before joining
* Fix WorkflowCatalog YAML parsing error handling and isinstance checks
* Harden step registry save and custom step/catalog ID handling
* Harden cache validation and staging OSError handling
* Address review: reorder symlink guard and split mixed test
- Move symlink-parent check before is_dir() in load_custom_steps() so
we never stat an external target through a symlink
- Split test_get_merged_steps_normalizes_list_ids_to_strings into two
focused tests: one for list-id normalization, one for get_step_info
return values
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address review: symlink-before-stat in loader, restore registry on rmtree failure
- load_custom_steps(): check is_symlink() before is_dir() on step
directories so symlinked entries are skipped without statting external
targets
- workflow_step_remove: restore the registry entry when shutil.rmtree()
fails so filesystem and registry state stay consistent and a future
'step add' isn't blocked
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Harden step_id validation and file-write error handling
- _validate_step_id_or_exit: reject whitespace-only/padded IDs,
Windows-invalid characters (<>:"|?*), control characters, trailing
dots/spaces, and Windows reserved device names (con, nul, etc.)
- Wrap step.yml/__init__.py staging writes in OSError handler
- Wrap extra_files disk writes (mkdir + write_bytes) in OSError handler
that names the failing relative path
- Registry rollback on rmtree failure: restore verbatim metadata and
emit a warning if the restore itself fails
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address review: cache symlink guard, verbatim registry rollback, Windows test fix
- StepCatalog: add _is_cache_path_safe() guard that checks for symlinks
in .specify/workflows/steps/.cache path; skip cache reads and writes
when any component is symlinked to prevent writes outside project root
- Registry rollback: write metadata directly to registry.data['steps']
and call save() instead of using add() which overwrites timestamps
- temp_dir fixture: use ignore_errors=True on Windows to avoid flaky
teardown from locked file handles (WinError 32)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Simplify exec_module call by removing redundant nested try/except
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix empty YAML tolerance in WorkflowCatalog.add_catalog, scope ignore_errors to Windows
- WorkflowCatalog.add_catalog(): treat None from yaml.safe_load() (empty
file) as an empty mapping instead of raising 'corrupted'
- temp_dir fixture: limit ignore_errors to sys.platform == 'win32' so
real cleanup issues surface on non-Windows platforms
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Chain exceptions in _load_catalog_config for both catalog classes
Add 'from exc' to preserve root cause in tracebacks while keeping
clean user-facing messages.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Make default catalog tests hermetic by isolating HOME
Monkeypatch Path.home() to project_dir and clear catalog env vars so
tests don't break on machines with a real ~/.specify/step-catalogs.yml
or ~/.specify/workflow-catalogs.yml.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix falsy ID handling in _get_merged_steps for list-based catalogs
Check for None explicitly instead of using 'or' which drops valid
falsy IDs like 0.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Compare reserved step IDs case-insensitively for filesystem safety
On case-insensitive filesystems (Windows, common macOS), variants like
STEP-REGISTRY.JSON would collide with the actual registry file.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add explanatory comments to intentional empty except blocks
Document why cache-read failures are silently ignored in both
WorkflowCatalog and StepCatalog _fetch_single_catalog methods.
Co-authored-by: Copilot <223556219+Copilot@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>
Co-authored-by: Manfred Riem <mnriem@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(dev): add integration scaffolder
* fix(dev): address integration scaffold review feedback
* fix(dev): address scaffold follow-up review
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* fix(dev): default scaffolded integrations to multi_install_safe = False
The scaffold template emitted `multi_install_safe = True` alongside a
placeholder `context_file = "AGENTS.md"`. Registered as-is, that violates the
registry contract (test_safe_integrations_have_distinct_context_files): codex
already pairs AGENTS.md with multi_install_safe = True, so the generated
boilerplate would collide on first registration.
Default the scaffold to False (matching IntegrationBase) so generated code is
registry-test-friendly out of the box; contributors opt in once they pick a
unique context_file. Aligns the generated test skeleton and both scaffold
tests, which previously contradicted each other (one expected True, one False).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(dev): harden scaffold writes and accept case-insensitive --type
- Guard scaffold_integration() against symlinked target directories: walk
each path component under the repo root and refuse symlinked dirs, then
confirm the write destination resolves inside the repo (mirrors the
manifest directory guard). Prevents scaffolding outside the repo when a
contributor's integrations/tests path is symlinked.
- Make the `--type` click.Choice case-insensitive so `--type YAML` is
accepted, matching scaffold_integration()'s strip()/lower() normalization
instead of rejecting at the CLI layer.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(dev): report scaffold filesystem failures as a clean CLI error
The `dev integration scaffold` command only caught FileExistsError/ValueError,
so an OSError raised during mkdir()/write_text() (permission denied, read-only
checkout, a path component that is a file, ...) bubbled up as a traceback
instead of a clean error + exit code. Broaden the handler to OSError (which
also covers FileExistsError) and add coverage for the filesystem-error path.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(dev): move scaffold command under integration
* fix(dev): roll back partial scaffold writes
* fix(dev): correct lint docs and generated test docstring
- local-development.md: ruff check src/ is enforced in CI, not absent
- scaffolded test docstring: drop misleading 'scaffold' wording
* fix(scaffold): create only leaf integration directory
---------
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: add Zed integration
* fix: update integrations stats grid to 31 for consistency
* fix: address Copilot review feedback
- Remove non-actionable --skills flag from ZedIntegration (Zed is always
skills-based, like Agy)
- Align zed_skill_mode predicate with ai_skills for consistency across
init output and hook rendering
- Consolidate claude/cursor/zed slash-skill return blocks in
_render_hook_invocation to reduce duplication
- Override test_options_include_skills_flag for Zed (no --skills flag)
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* fix: address Copilot review round 2
- Make zed_skill_mode unconditional in hook rendering (Zed is always
skills-based, no --skills option)
- Add test_init_persists_ai_skills_for_zed that exercises the actual
CLI init path and verifies HookExecutor renders /speckit-plan
without manual init-options manipulation
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* fix: address copilot review feedback for zed integration
- Update integration count from 31 to 33 in docs/index.md (32 integrations + Generic)
- Make zed_skill_mode unconditional to match extensions.py behavior
- Consolidate slash-skill integrations into a set for consistency
- Move os import to module level in test_integration_zed.py
* fix: refine slash-skill logic and ai-skills validation
- Fix slash-skill integrations: Claude/Cursor require ai_skills=true; Zed/Agy/Devin are always skills
- Allow --ai-skills with --integration (not just --ai) to fix validation error
* fix: remove unused variables and update ai-skills help text
- Add agy_skill_mode and devin_skill_mode variables to fix F841 lint error
- Use all skill mode variables in the slash-skill conditional check
- Update --ai-skills help text to reflect it works with --integration too
* fix: add trae_skill_mode to hook invocation for consistency
Trae is a SkillsIntegration like Zed/Agy/Devin, so it should also be treated
as always-skills-based in hook invocation rendering.
* fix: make Agy always skills-based for consistency
AgyIntegration is a SkillsIntegration subclass with no --skills option,
so it should be treated as always skills-based (like Zed, Devin, Trae).
This aligns init.py skill mode detection with extensions.py hook rendering.
* fix: gate agy_skill_mode and refactor _render_hook_invocation to use sets
Addressed Copilot review comments:
- Restored _is_skills_integration guard on agy_skill_mode in init.py
to be defensive about runtime integration type.
- Refactored _render_hook_invocation() in extensions.py to use
always_slash/conditional_slash frozensets instead of individual
per-agent booleans, eliminating unused variables (F841) and making
it harder for conditions to drift between integrations.
- Centralized slash-skill determination so adding a new unconditional
slash-skill integration is a one-key addition.
* fix: address latest Copilot review comments
- Added copilot to CONDITIONAL_SLASH_AGENTS for consistent
hook invocation rendering with init.py
- Moved always_slash/conditional_slash frozensets to module
scope to avoid per-call reallocation
- Replaced manual os.chdir() with monkeypatch.chdir() in test
- Overrode test_options_include_skills_flag for Zed (no --skills)
* fix: address latest Copilot review comments
- Removed redundant local import yaml in _register_extension_skills
(yaml is already imported at module scope)
- Split --ai-skills usage hint into two separate print statements
for better readability
- Changed integrations count from '33' to '30+' to avoid future drift
* fix: re-add _is_skills_integration definition lost in merge
The _is_skills_integration variable was accidentally dropped during the
web UI merge resolution of upstream/main's removal of legacy --ai flags.
Re-added the definition via isinstance(resolved_integration, SkillsIntegration)
check so that skill-mode booleans work correctly.
* fix: gate zed_skill_mode on _is_skills_integration for consistency
Aligns zed_skill_mode with the other skills-based agents (codex, claude,
cursor-agent, copilot) which all use _is_skills_integration gating.
Since ZedIntegration extends SkillsIntegration, behavior is unchanged.
* fix: remove unused claude_skill_mode and cursor_skill_mode locals in _render_hook_invocation
These variables became unused after the refactor to ALWAYS_SLASH_AGENTS /
CONDITIONAL_SLASH_AGENTS sets. Claude and Cursor-Agent are now handled by the
CONDITIONAL_SLASH_AGENTS path, so the separate boolean locals are dead code.
Fixes ruff F841 and addresses Copilot review feedback that was repeated across
multiple review rounds.
* fix: align agy/trae invocation format in init next-steps with hook rendering and build_command_invocation
- Moved agy and trae from '-<name>' (dollar/Codex format) to
'/speckit-<name>' (slash format) in _display_cmd() to match:
- HookExecutor._render_hook_invocation() (ALWAYS_SLASH_AGENTS for trae,
CONDITIONAL_SLASH_AGENTS for agy)
- SkillsIntegration.build_command_invocation() (default: /speckit-<name>)
- The '$' prefix is specific to Codex; all other skills agents use '/'.
* fix: address Copilot review comments on hook invocation consistency
- Add is_slash_skills_agent() helper to extensions.py to centralize the
agent-to-invocation-format mapping, reducing drift risk between
HookExecutor._render_hook_invocation() and init.py _display_cmd()
- Use the shared helper in both locations; init.py now imports and
delegates to is_slash_skills_agent() instead of maintaining its own
per-agent boolean matrix
- Fix test_hooks_render_skill_invocation to use ai_skills=False,
proving Zed renders /speckit-<name> unconditionally
- Add parameterized TestSlashSkillsSets covering all agents in
ALWAYS_SLASH_AGENTS and CONDITIONAL_SLASH_AGENTS with ai_skills
both true and false
* fix: address Copilot review comments on type safety and test API
- Make is_slash_skills_agent() accept str | None to match its call sites
(init_options.get("ai") can return None)
- Refactor TestSlashSkillsSets to use public execute_hook() API instead of
private _render_hook_invocation() method
* fix: address Copilot review comments on typing and naming clarity
- Add from __future__ import annotations to extensions.py so PEP 604
unions (str | None) are safe regardless of Python version
- Add clarifying _ai_skills_enabled local variable in init.py's
_display_cmd() to make the semantic meaning explicit when passing it
to is_slash_skills_agent()
* fix: move invocation-style logic into shared _invocation_style module
- Extract ALWAYS_SLASH_AGENTS, CONDITIONAL_SLASH_AGENTS, and
is_slash_skills_agent() from extensions.py into new _invocation_style.py
module, eliminating the awkward init.py -> extensions.py import
dependency for invocation-style decision logic
- Both HookExecutor._render_hook_invocation() and init.py _display_cmd()
now import from the shared module instead of one subsystem importing
from the other
- Revert /SKILL.md change: the leading slash is semantically significant
(path component vs filename suffix)
* fix: add None guard before i.options() in test_options_include_skills_flag
get_integration() returns IntegrationBase | None, so i.options()
is a type error without a None check.
* fix: override test_options_include_skills_flag for Zed (always skills, no --skills flag)
Zed is always skills-based and doesn't expose a --skills option.
Override the inherited base test to assert --skills is absent.
* fix: rename test and skip inherited test_options_include_skills_flag for Zed
- Skip inherited test_options_include_skills_flag (not applicable — Zed
is always skills-based with no --skills flag)
- Add test_options_do_not_include_skills_flag with correct name matching
the assertion (--skills is absent)
* fix: add defensive non-string check in is_slash_skills_agent
Reject non-string values for selected_ai to prevent TypeError from
set membership checks when persisted init-options contain corrupted
data (e.g. list or dict instead of string).
---------
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
CITATION.cff was created at v0.7.3 (2026-04-17) and has not been
updated since. The latest stable release is v0.10.2, released on
2026-06-11. This brings the citation metadata in sync with the
published release so tools that ingest CITATION.cff (Zenodo, GitHub's
"Cite this repository" widget, citation managers) surface the correct
version.
Verification:
- `gh release list --repo github/spec-kit --limit 1` → v0.10.2 / 2026-06-11
- CHANGELOG.md `## [0.10.2] - 2026-06-11` confirms the date
- pyproject.toml `version = "0.10.3.dev0"` confirms 0.10.2 is latest stable
AI-assisted contribution.
* chore: bump version to 0.10.4
* chore: begin 0.10.5.dev0 development
---------
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
A non-list result from the items expression is a wiring error (the
template did not resolve to a collection); silently fanning out over
zero items hides it until a confusing downstream failure. Fail the
step with an error naming the expression instead. An explicit empty
list remains valid input.
Fixes#2956
* refactor(presets): convert presets.py module to presets/ package
Pure structural move to mirror integrations/. presets.py becomes
presets/__init__.py with relative imports rebased one level deeper.
No behavior change; public import surface (from .presets import ...)
preserved. Prepares for co-locating preset command handlers in PR-6/8.
* refactor: move preset command handlers to presets/_commands.py (PR-6/8)
Cut the preset_app / preset_catalog_app Typer groups and all 12 command
handlers out of __init__.py into presets/_commands.py, exposing register(app)
— mirrors the integration co-location from PR-5. __init__.py now registers
via _register_preset_cmds(app), dropping ~620 lines (3282 -> 2663).
Handlers lazy-import root helpers (_require_specify_project, get_speckit_version,
_locate_bundled_preset, _display_project_path) via 'from .. import' so test
monkeypatching of specify_cli.<helper> keeps working. _locate_bundled_preset
kept as an explicit re-export in __init__.py for that resolution path.
CLI surface and public imports unchanged. Full suite: 3162 passed, 40 skipped.
* docs: add guide for handling complex features
Add a Concepts page documenting strategies for dealing with large or
complex features where context window exhaustion degrades agent
performance during implementation. Covers limiting tasks per run,
sub-agent delegation, combining both, and decomposing into smaller
specs, with a guideline table for choosing an approach.
Closes#2986
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs: address review feedback on complex features guide
Use task IDs (T001-T010) instead of bare numbers to match the tasks.md
template format, and add the combined scoping + delegation approach to
the selection table for completeness.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs: align complex features guide with command naming conventions
Use the full /speckit.implement command name throughout, match the
command template wording ('must consider'), and use the product names
GitHub Copilot CLI and the GitHub Copilot extension for VS Code.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Manfred Riem <mnriem@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore: bump version to 0.10.3
* chore: begin 0.10.4.dev0 development
---------
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* chore: bump version to 0.10.2
* chore: begin 0.10.3.dev0 development
---------
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* Add Spec Trace extension to community catalog
* docs(catalog): mark Spec Trace as Read+Write
The /speckit.trace.build command writes .specify/trace.md, so the
catalog row's Effect column was wrong. Aligning with the extension's
documented behavior.
* docs(community): add Spec Trace row to extensions.md
The public community extensions table moved from README.md to
docs/community/extensions.md per the repo convention documented in
.github/skills/add-community-extension/SKILL.md. Adding the Spec Trace
row alphabetically between Spec Sync and Spec Validate so the doc stays
in sync with the catalog entry already added.
* fix(catalog): use literal Unicode characters in Spec Trace description
Copilot's review on this PR noted that the Spec Trace entry was the
only one in catalog.community.json using JSON Unicode escape sequences
(\u2192 for the arrow, \u2014 for the em-dash). Every other entry
that uses those characters writes them as literal multi-byte UTF-8
(18 entries with literal em-dash, 5 with literal arrow), so the
escaped form made this row harder to read and review in plain text
and stood out as the only inconsistency in the file.
Replacing the escapes with the literal characters keeps the entry
visually consistent with the rest of the catalog and decodes to the
same string at runtime, so no consumer changes.
* chore(catalog): set Spec Trace timestamps to catalog-add date
Per add-community-extension SKILL.md, a new entry's created_at/updated_at
should reflect the date it is added to the catalog, and the top-level
catalog updated_at must be refreshed on any add. Set the Spec Trace
entry and the catalog-level updated_at to 2026-06-09.
* docs(community): categorize Spec Trace as code
Spec Trace analyzes the test suite (source) and produces a coverage/
traceability report, matching the documented 'code' category (reviews/
validates source) rather than 'process' (orchestrates workflow across
phases). Aligns with the sibling SpecTest row.
Extension-provided commands that declare `argument-hint:` in their
frontmatter had that field dropped from the generated Claude
`.claude/skills/<name>/SKILL.md`, while core template commands keep it.
The extension skill generator built the frontmatter via the shared
build_skill_frontmatter() (name/description/compatibility/metadata only)
and never forwarded argument-hint.
Carry argument-hint from the parsed source command frontmatter into the
skill frontmatter dict before serialization, gated on the integration
exposing inject_argument_hint so only argument-hint-aware agents (Claude)
receive the key and build_skill_frontmatter's shared shape stays unchanged
for every other agent. The value is injected into the dict rather than via
the string-based inject_argument_hint helper, so a folded multi-line
description cannot be split into invalid YAML.
Add regression tests covering a folding description (Claude) and the
non-Claude gate (kimi).
Closes#2903
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Harden preset URL installs against unsafe redirects
Preset URL installs already rejected non-HTTPS source URLs, but the authenticated opener follows redirects. Validate the final response URL before writing the ZIP, preserve GitHub release asset URL resolution after the preset command module split, stream the response to disk, and keep catalog config serialization on safe YAML output.
Constraint: open_url follows redirects, so source URL validation alone does not constrain the downloaded target
Rejected: Keep response.read() for simplicity | large preset downloads should not be buffered entirely in memory
Confidence: high
Scope-risk: narrow
Directive: Keep preset URL policy aligned with workflow installer redirect validation
Tested: uvx ruff check src/specify_cli/__init__.py src/specify_cli/presets/__init__.py src/specify_cli/presets/_commands.py tests/test_presets.py
Tested: uv run pytest tests/test_presets.py -q
Not-tested: Real network redirect integration against a live HTTP server
Co-authored-by: OmX <omx@oh-my-codex.dev>
* Reject malformed preset download URLs
Preset downloads should fail early when a URL lacks a hostname, even if the scheme is HTTPS. The redirect error now describes any disallowed target instead of implying that only non-HTTPS redirects are blocked.
* Prevent credentialed preset redirects from downgrading transport
Preset URL downloads already checked the final URL after urllib followed redirects, but that was too late for authenticated requests because same-host redirects could preserve Authorization during the redirect itself. The authenticated HTTP helper now supports an opt-in redirect validator, and preset downloads use it to reject disallowed redirect targets before following them. The redirect auth handlers also stop preserving credentials across HTTPS to non-HTTPS downgrades as defense in depth.
* test(presets): 修复 URL 解析测试 mock 缺少 redirect_validator 参数
重定向安全加固为 open_url 新增 redirect_validator 参数,
两处 fake_open_url mock 签名未同步导致 TypeError。
补齐参数后全部 3717 个测试通过。
---------
Co-authored-by: OmX <omx@oh-my-codex.dev>
_is_managed() in install_shared_infra now consults manifest.is_recovered()
before treating a hash-matching file as managed. Files marked recovered
(pre-existing on disk, not installed by Spec Kit) are no longer overwritten
by integration use/switch even when their hash matches the manifest entry.
This closes the gap documented in the manifest API: callers using
refresh_managed MUST check is_recovered first.
Co-authored-by: Manfred Riem <mnriem@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat: add category and effect as first-class fields in extension schema
Add `category` and `effect` as optional fields in the extension schema
(`extension.yml`) and community catalog (`catalog.community.json`).
Schema changes:
- Valid categories: docs, code, process, integration, visibility
- Valid effects: read-only, read-write
- Both fields are optional (backward-compatible with existing extensions)
- Validation raises ValidationError for invalid values when present
Propagation:
- Added `category` and `effect` to all 108 entries in catalog.community.json
(populated from the existing docs/community/extensions.md table)
- Updated extension template with commented category/effect fields
- Updated add-community-extension skill with new JSON template fields
- Updated `specify extension info` CLI output to display category/effect
- Added properties to ExtensionManifest class
Tests:
- test_valid_category: all 5 category values pass
- test_valid_effect: both effect values pass
- test_invalid_category: invalid value raises ValidationError
- test_invalid_effect: invalid value raises ValidationError
- test_category_and_effect_optional: omitting fields still works
Closes#2874
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: make category free-form, keep effect validated
Category is a free-form string (only validated as non-empty when present),
while effect remains restricted to 'read-only' or 'read-write'.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: address PR review feedback
- Add type guard before 'in' check for effect to prevent TypeError on
unhashable YAML values (list/dict)
- Comment out category/effect in template so authors must opt in
- Use VALID_EFFECTS constant in test instead of hard-coded values
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: update category docstring to reflect free-form semantics
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs: clarify canonical extension effect values
---------
Co-authored-by: Manfred Riem <mnriem@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
* chore(catalog): add Jira Integration (Sync Engine) extension
Adds a new community-catalog listing for `spec-kit-jira-sync`
(ashbrener/spec-kit-jira-sync), a reconcile-engine bridge that mirrors
spec-kit specs into Jira (Epic per repo, Story per spec, Subtask per
phase): idempotent, drift-aware, fail-closed.
Catalog id is `jira-sync` because the `jira` id is already taken by an
unrelated extension; display name "Jira Integration (Sync Engine)"
disambiguates from the existing "Jira Integration" listing.
Touches the two catalog surfaces:
1. extensions/catalog.community.json - the new "jira-sync" entry,
inserted after the existing "jira" entry. Field shape matches the
sibling "linear" entry exactly.
2. docs/community/extensions.md - the table row, after the existing
Jira Integration row.
JSON validated; diff is the single entry + the one table row.
* catalog(jira-sync): neutral capability-focused description (address Copilot review)
Drop the comparative/absolute framing ('A real …', 'never corrupts your board')
flagged by Copilot; keep the factual, tested capability descriptors (idempotent,
drift-aware, fail-closed). Applies to both the catalog entry and the docs table row.
* chore(catalog): bump jira-sync to v0.2.0 (re-mode + engine unification)
* fix(catalog): jira-sync download_url .tar.gz -> .zip (installer is ZIP-only)
The spec-kit extension installer saves {id}-{version}.zip and extracts via
zipfile.ZipFile (src/specify_cli/extensions.py) — a .tar.gz asset downloads but
fails extraction. Matches every other catalog entry's /archive/refs/tags/vX.zip
convention. Addresses the Copilot review on PR #2895.
---------
Co-authored-by: Ash Brener <ashley@midletearth.com>
* chore: bump version to 0.10.1
* chore: begin 0.10.2.dev0 development
---------
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* chore(catalog): bump linear to v0.3.0 + spec-kit-linear-sync URLs
The Linear extension repo was renamed ashbrener/spec-kit-linear -> spec-kit-linear-sync
and shipped v0.3.0. Update the community catalog entry's download_url (was pinned to
v0.2.0), repository/homepage/documentation/changelog URLs, and version. extension id
stays 'linear' (commands unchanged); old GitHub URLs redirect.
* docs(community): point Linear extension table row at spec-kit-linear-sync
---------
Co-authored-by: Ash Brener <ashley@midletearth.com>