mirror of
https://github.com/github/spec-kit.git
synced 2026-07-03 20:36:23 +08:00
v0.6.2
64 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
b67b2856b1 |
feat(agents): add Goose AI agent support (#2015)
* feat(integrations): add YamlIntegration base class for YAML recipe agents Signed-off-by: Furkan Köykıran <furkankoykiran@gmail.com> * feat(integrations): add Goose integration subpackage with YAML recipe support Signed-off-by: Furkan Köykıran <furkankoykiran@gmail.com> * feat(integrations): register GooseIntegration in the integration registry Signed-off-by: Furkan Köykıran <furkankoykiran@gmail.com> * feat(agents): add YAML format support to CommandRegistrar for extension/preset commands Signed-off-by: Furkan Köykıran <furkankoykiran@gmail.com> * feat(scripts): add goose agent type to bash update-agent-context script Signed-off-by: Furkan Köykıran <furkankoykiran@gmail.com> * feat(scripts): add goose agent type to PowerShell update-agent-context script Signed-off-by: Furkan Köykıran <furkankoykiran@gmail.com> * docs(agents): add Goose to supported agents table and integration notes Signed-off-by: Furkan Köykıran <furkankoykiran@gmail.com> * docs(readme): add Goose to supported agents table Signed-off-by: Furkan Köykıran <furkankoykiran@gmail.com> * test(integrations): add YamlIntegrationTests base mixin for YAML agent testing Signed-off-by: Furkan Köykıran <furkankoykiran@gmail.com> * test(integrations): add Goose integration tests Signed-off-by: Furkan Köykıran <furkankoykiran@gmail.com> * test(consistency): add Goose consistency checks for config, registrar, and scripts Signed-off-by: Furkan Köykıran <furkankoykiran@gmail.com> * docs(agents): move Goose to YAML Format section in Command File Formats Goose uses YAML recipes, not Markdown. Remove it from the Markdown Format list and add a dedicated YAML Format subsection with a representative recipe example showing prompt: | and {{args}} placeholders. * refactor(agents): delegate render_yaml_command to YamlIntegration Remove the duplicate header dict, yaml.safe_dump call, body indentation, and _human_title logic from CommandRegistrar.render_yaml_command(). Delegate to YamlIntegration._render_yaml() and _human_title() so YAML recipe output stays consistent across the init-time generation and command-registration code paths. * fix(agents): guard alias output path against directory traversal Validate that alias_file resolves within commands_dir before writing. Uses the same resolve().relative_to() pattern already established in extensions.py for ZIP path containment checks. * docs(agents): add Goose to Multi-Agent Support comment list in update-agent-context.sh * fix(agents): add goose to print_summary Usage line in bash context script The print_summary() function listed all supported agents in its Usage output but omitted goose, making it inconsistent with the header docs and the error message in update_specific_agent(). Signed-off-by: Furkan Köykıran <furkankoykiran@gmail.com> * fix(agents): add goose to Print-Summary Usage line in PowerShell context script The Print-Summary function listed all supported agents in its Usage output but omitted goose, making it inconsistent with the ValidateSet and the header documentation. Signed-off-by: Furkan Köykıran <furkankoykiran@gmail.com> * fix(agents): normalize description and title types in YamlIntegration.setup() YAML frontmatter can contain non-string types (null, list, int). Add isinstance checks matching TomlIntegration._extract_description() to ensure Goose recipes always receive valid string fields. Signed-off-by: Furkan Köykıran <furkankoykiran@gmail.com> * fix(agents): validate shared script exists before exec in Goose bash wrapper Add Forge-style check that the shared update-agent-context.sh is present and executable, producing a clear error instead of a cryptic shell exec failure when the shared script is missing. Signed-off-by: Furkan Köykıran <furkankoykiran@gmail.com> * fix(agents): validate shared script exists before invoke in Goose PowerShell wrapper Add Forge-style Test-Path check that the shared update-agent-context.ps1 exists, producing a clear error instead of a cryptic PowerShell failure when the shared script is missing. Signed-off-by: Furkan Köykıran <furkankoykiran@gmail.com> * fix(agents): normalize title and description types in render_yaml_command() Extension/preset frontmatter can contain non-string types. Add isinstance checks matching the normalization in YamlIntegration.setup() so both code paths produce valid Goose recipe fields. Signed-off-by: Furkan Köykıran <furkankoykiran@gmail.com> * fix(agents): replace $ARGUMENTS with arg_placeholder in process_template() Signed-off-by: Furkan Köykıran <furkankoykiran@gmail.com> * test(agents): assert $ARGUMENTS absent from generated YAML recipes Signed-off-by: Furkan Köykıran <furkankoykiran@gmail.com> * test(agents): assert $ARGUMENTS absent from generated TOML commands Signed-off-by: Furkan Köykıran <furkankoykiran@gmail.com> * fix(tests): rewrite docstring to avoid embedded triple-quote in TOML test Signed-off-by: Furkan Köykıran <furkankoykiran@gmail.com> --------- Signed-off-by: Furkan Köykıran <furkankoykiran@gmail.com> |
||
|
|
43cb0fa7ab |
feat: add bundled lean preset with minimal workflow commands (#2161)
* feat: add bundled lean preset with minimal workflow commands Add a lean preset that overrides the 5 core workflow commands (specify, plan, tasks, implement, constitution) with minimal prompts that produce exactly one artifact each — no extension hooks, no scripts, no git branching, no templates. Bundled preset infrastructure: - Add _locate_bundled_preset() mirroring _locate_bundled_extension() - Update 'specify init --preset' to try bundled -> catalog fallback - Update 'specify preset add' to try bundled -> catalog fallback - Add bundled guard in download_pack() for presets without download URLs - Add lean to presets/catalog.json with 'bundled: true' marker - Add lean to pyproject.toml force-include for wheel packaging - Align error messages with bundled extension error pattern Tests: 15 new tests (TestLeanPreset + TestBundledPresetLocator) * refactor: address review — clean up unused imports, strengthen test assertions - Remove unused MagicMock import and cache_dir setup in download test - Assert 'bundled' and 'reinstall' in CLI error output (not just exit code) - Mock catalog in missing-locally test for deterministic bundled error path - Fix test versions to satisfy updated speckit_version >=0.6.0 requirement * refactor: address review — fix constitution paths, add REINSTALL_COMMAND to presets.py - Fix constitution path to .specify/memory/constitution.md in plan, tasks, implement commands (matching core command convention) - Include REINSTALL_COMMAND in download_pack() bundled guard for consistent recovery instructions across bundled extensions and presets * refactor: address review — explicit feature_directory paths, ZIP cleanup in finally - Prefix spec.md/plan.md/tasks.md with <feature_directory>/ in plan, tasks, and implement commands so the agent doesn't operate on repo root by mistake - Move ZIP unlink into finally block in init --preset path so cleanup runs even when install_from_zip raises (matching preset_add pattern) * refactor: address review — replace Unicode em dashes with ASCII, fix grammar - Replace all Unicode em dashes with ASCII hyphens in preset.yml and catalog.json to avoid decode errors on non-UTF-8 environments - Fix grammar: 'store it in tasks.md' -> 'store them in tasks.md' * refactor: address review - align task format between tasks and implement - Remove undefined [P] marker from implement (lean uses sequential execution) - Clarify checkbox update: 'change - [ ] to - [x]' instead of ambiguous '[X]' - Simplify implement to execute tasks in order without parallel complexity * refactor: address review - parse frontmatter instead of raw substring search - Use CommandRegistrar.parse_frontmatter() to check for scripts/agent_scripts keys in YAML frontmatter instead of brittle 'scripts:' substring search |
||
|
|
d1b95c2f59 |
fix: bundled extensions should not have download URLs (#2155)
* fix: bundled extensions should not have download URLs (#2151) - Remove selftest from default catalog (not a published extension) - Replace download_url with 'bundled: true' flag for git extension - Add bundled check in extension add flow with clear error message when bundled extension is missing from installed package - Add bundled check in download_extension() with specific error - Direct users to reinstall via uv with full GitHub URL - Add 3 regression tests for bundled extension handling * refactor: address review - move bundled check up-front, extract reinstall constant - Move bundled check before download_url inspection in download_extension() so bundled extensions can never be downloaded even with a URL present - Extract REINSTALL_COMMAND constant to avoid duplicated install strings * fix: allow bundled extensions with download_url to be updated Bundled extensions should only be blocked from download when they have no download_url. If a newer version is published to the catalog with a URL, users should be able to install it to get bug fixes. Add test for bundled-with-URL download path. |
||
|
|
5732de60d0 |
feat(cursor-agent): migrate from .cursor/commands to .cursor/skills (#2156)
Use SkillsIntegration so workflows ship as speckit-*/SKILL.md. Update init next-steps, extension hook invocation, docs, and tests. Made-with: Cursor |
||
|
|
71143598be |
fix(forge): use hyphen notation in frontmatter name field (#2075)
* fix(forge): use hyphen notation in frontmatter name field
- Changed injected name field from 'speckit.{command}' to 'speckit-{command}'
- Keeps standard filename format 'speckit.{command}.md'
- Aligns with Forge's command naming convention requirements
- All tests pass
* feat(forge): centralize name formatting to fix extension/preset command names
Address PR feedback by centralizing Forge command name formatting to ensure
consistent hyphenated names across both core template setup and
extension/preset command registration.
Changes:
- Add format_forge_command_name() utility function in forge integration
- Update ForgeIntegration._apply_forge_transformations() to use centralized formatter
- Add _format_name_for_agent() helper in CommandRegistrar to apply agent-specific formatting
- Update CommandRegistrar.register_commands() to format names for Forge (both primary commands and aliases)
- Add comprehensive test coverage for the formatter and registrar behavior
Impact:
- Extension commands installed for Forge now use 'name: speckit-my-extension-example'
instead of 'name: speckit.my-extension.example'
- Fixes ZSH/shell compatibility issues with dot notation in command names
- Maintains backward compatibility for all other agents (they continue using dot notation)
- Eliminates duplication between integration setup and registrar paths
Example transformation:
Before: name: speckit.jira.sync-status (breaks in ZSH/Forge)
After: name: speckit-jira-sync-status (works everywhere)
Fixes inconsistency where core templates used hyphens but extension/preset
commands preserved dots, breaking Forge's naming requirements.
* refactor(forge): move name formatting logic to integration module
Move _format_name_for_agent function logic into Forge integration's
registrar_config as a 'format_name' callback, improving separation of
concerns and keeping Forge-specific logic within its integration module.
Changes:
- Remove _format_name_for_agent() from agents.py (shared module)
- Add 'format_name' callback to Forge's registrar_config pointing to format_forge_command_name
- Update CommandRegistrar to use format_name callback when available
- Maintains same behavior: Forge commands use hyphenated names, others use dot notation
Benefits:
- Better encapsulation: Forge-specific logic lives in forge integration
- More extensible: Other integrations can provide custom formatters via registrar_config
- Cleaner separation: agents.py doesn't need to know about specific agent requirements
* fix(forge): make format_forge_command_name idempotent
Handle already-hyphenated names (speckit-foo) to prevent double-prefixing
(speckit-speckit-foo). The function now returns already-formatted names
unchanged, making it safe to call multiple times.
Changes:
- Add early return for names starting with 'speckit-'
- Update docstring to clarify accepted input formats
- Add examples showing idempotent behavior
- Add test coverage for idempotent behavior
Examples:
format_forge_command_name('speckit-plan') -> 'speckit-plan' (unchanged)
format_forge_command_name('speckit.plan') -> 'speckit-plan' (converted)
format_forge_command_name('plan') -> 'speckit-plan' (prefixed)
* test(forge): strengthen name field assertions and clarify comments
Improve test_name_field_uses_hyphenated_format to fail loudly when
the name field is missing instead of silently passing.
Changes:
- Add explicit assertion that name_match is not None before validating value
- Ensures test fails if regex doesn't match (e.g., frontmatter rendering changes)
- Clarify Claude comment: it doesn't use inject_name path but SKILL.md
frontmatter still includes hyphenated name via build_skill_frontmatter()
Before: Test would silently pass if 'name:' field was missing from frontmatter
After: Test explicitly asserts field presence before validating format
* docs(forge): clarify frontmatter name requirement and improve test isolation
Fix misleading docstring and improve test to properly validate that the
format_name callback is Forge-specific.
Changes to src/specify_cli/integrations/forge/__init__.py:
- Reword module docstring to clarify the requirement is specifically for
the frontmatter 'name' field value, not command files or invocation
- Before: 'Requires hyphenated command names ... instead of dot notation'
(implied dot notation unsupported overall)
- After: 'Uses a hyphenated frontmatter name value ... for shell compatibility'
(clarifies it's the frontmatter field, and Forge still supports dot filenames)
Changes to tests/integrations/test_integration_forge.py:
- Replace Claude with Windsurf in test_registrar_does_not_affect_other_agents
- Claude uses build_skill_frontmatter() which always includes hyphenated names,
so testing it didn't validate that format_name callback is Forge-only
- Windsurf is a standard markdown agent without inject_name
- Now asserts NO 'name:' field is present, proving format_name isn't invoked
- This properly validates the callback mechanism is isolated to Forge
* test(forge): use parse_frontmatter for precise YAML validation
Replace regex and string searches with CommandRegistrar.parse_frontmatter()
to validate only YAML frontmatter, not entire file content. Prevents false
positives if command body contains 'name:' lines.
Changes:
- test_forge_specific_transformations: Parse frontmatter dict instead of string search
- test_name_field_uses_hyphenated_format: Replace regex with frontmatter parsing
- test_registrar_formats_extension_command_names_for_forge: Use dict validation
- test_registrar_formats_alias_names_for_forge: Use dict validation
Benefits: More precise, robust against body content, better error messages,
consistent with existing codebase utilities.
---------
Co-authored-by: ericnoam <eric.rodriguez@leovegas.com>
|
||
|
|
2972dec85c |
feat: Git extension stage 2 — GIT_BRANCH_NAME override, --force for existing dirs, auto-install tests (#1940) (#2117)
* feat: Git extension stage 2 — GIT_BRANCH_NAME override, --force for existing dirs, auto-install tests (#1940) - Add GIT_BRANCH_NAME env var override to create-new-feature.sh/.ps1 for exact branch naming (bypasses all prefix/suffix generation) - Fix --force flag for 'specify init <dir>' into existing directories - Add TestGitExtensionAutoInstall tests (auto-install, --no-git skip, commands registered) - Add TestFeatureDirectoryResolution tests (env var, feature.json, priority, branch fallback) - Document GIT_BRANCH_NAME in speckit.git.feature.md and specify.md * fix: remove unused Tuple import (ruff F401) * fix: address Copilot review feedback (#2117) - Fix timestamp regex ordering: check YYYYMMDD-HHMMSS before generic numeric prefix in both bash and PowerShell - Set BRANCH_SUFFIX in GIT_BRANCH_NAME override path so 244-byte truncation logic works correctly - Add 244-byte length check for GIT_BRANCH_NAME in PowerShell - Use existing_items for non-empty dir warning with --force - Skip git extension install if already installed (idempotent --force) - Wrap PowerShell feature.json parsing in try/catch for malformed JSON - Fix PS comment: 'prefix lookup' -> 'exact mapping via Get-FeatureDir' - Remove non-functional SPECIFY_SPEC_DIRECTORY from specify.md template * fix: address second round of Copilot review feedback (#2117) - Guard shutil.rmtree on init failure: skip cleanup when --force merged into a pre-existing directory (prevents data loss) - Bash: error on GIT_BRANCH_NAME >244 bytes instead of broken truncation - Fix malformed numbered list in specify.md (restore missing step 1) - Add claude_skills.exists() assert before iterdir() in test * fix: use UTF-8 byte count for 244-byte branch name limit (#2117) - Bash: use LC_ALL=C wc -c for byte length instead of ${#VAR} - PowerShell: use [System.Text.Encoding]::UTF8.GetByteCount() instead of .Length (UTF-16 code units) * fix: address third round of review feedback (#2117) - Update --dry-run help text in bash and PowerShell (branch name only) - Fix specify.md JSON example: use concrete path, not literal variable - Add TestForceExistingDirectory tests (merge + error without --force) - Add PowerShell Get-FeaturePathsEnv tests (env var + feature.json) * fix: normalize relative paths and fix Test-HasGit compat (#2117) - Bash common.sh: normalize SPECIFY_FEATURE_DIRECTORY and feature.json relative paths to absolute under repo root - PowerShell common.ps1: same normalization using IsPathRooted + Join-Path - PowerShell create-new-feature.ps1: call Test-HasGit without -RepoRoot for compatibility with core common.ps1 (no param) and git-common.ps1 (optional param with default) * test: add GIT_BRANCH_NAME automated tests for bash and PowerShell (#2117) - TestGitBranchNameOverrideBash: 5 tests (exact name, sequential prefix, timestamp prefix, overlong rejection, dry-run) - TestGitBranchNameOverridePowerShell: 4 tests (exact name, sequential prefix, timestamp prefix, overlong rejection) - Tests use extension scripts (not core) via new ext_git_repo and ext_ps_git_repo fixtures * fix: restore git init during specify init + review fixes (#2117) - Restore is_git_repo() and init_git_repo() functions removed in stage 2 - specify init now runs git init AND installs git extension (not just extension install alone) - Add is_dir() guard for non-here path to prevent uncontrolled error when target exists but is a file - Add python3 JSON fallback in common.sh for multi-line feature.json (grep pipeline fails on pretty-printed JSON without jq) * fix: use init_git_repo error_msg in failure output (#2117) * fix: ensure_executable_scripts also covers .specify/extensions/ (#2117) Extension .sh scripts (e.g. create-new-feature.sh, initialize-repo.sh) may lack execute bits after install. Scan both .specify/scripts/ and .specify/extensions/ for permission fixing. * fix: move chmod after extension install + sanitize error_msg (#2117) - ensure_executable_scripts() now runs after git extension install so extension .sh files get execute bits in the same init run - Sanitize init_git_repo error_msg to single line (replace newlines, truncate to 120 chars) to prevent garbled StepTracker output * fix: use tracker.error for git init/extension failures (#2117) Git init failure and extension install failure were reported as tracker.complete (showing green) even on error. Now track a git_has_error flag and call tracker.error when any step fails, so the UI correctly reflects the failure state. * fix: sanitize ext_err in git step tracker for consistent rendering (#2117) |
||
|
|
838bd0fedc | fix(git): surface checkout errors for existing branches (#2122) | ||
|
|
4deb90f4f5 |
fix: restore alias compatibility for community extensions (#2110) (#2125)
Relax alias validation in _collect_manifest_command_names() to only
enforce the 3-part speckit.{ext}.{cmd} pattern on primary command
names. Aliases retain type and duplicate checking but are otherwise
free-form, restoring pre-#1994 behavior.
This unblocks community extensions (e.g. spec-kit-verify) that use
2-part aliases like 'speckit.verify'.
Fixes #2110
|
||
|
|
40fb276023 |
fix: prevent ambiguous TOML closing quotes when body ends with " (#2113) (#2115)
* fix: prevent ambiguous TOML closing quotes when body ends with `"` (#2113) _render_toml_string placed the closing `"""` inline with content, so a body ending with `"` produced `""""` (four consecutive quotes). While technically valid TOML 1.0, this breaks stricter parsers such as Gemini CLI v0.27.2. Insert a newline before the closing delimiter when the body ends with a quote character. Same treatment for the single-quote (`'''`) fallback. Adds both a positive test (body ending with `"` must not produce `""""`) and a negative test (safe bodies keep the inline delimiter). * fix: use line-ending backslash instead of newline for TOML closing delimiters Address PR review feedback: - Replace sep=newline with TOML line-ending backslash so the parsed value does not gain a trailing newline when body ends with a quote. - For literal string (''') fallback, skip to escaped basic string when value ends with single quote instead of inserting a newline. - Make test body multiline so it exercises the """ rendering path, and assert no trailing newline in parsed value. * test: cover escaped basic-string fallback when body has triple-quotes and ends with single-quote Addresses review feedback from PR #2115: adds test for the branch where the body contains '"""' and ends with "'", which forces _render_toml_string() through the escaped basic-string fallback instead of the '''...''' literal-string path (since '''' would produce the same ambiguous-closing-delimiter problem). |
||
|
|
6536bc4102 |
fix speckit issue for trae (#2112)
* 修改trea文件结构错误问题 * 修改trea文件结构错误问题 * 修复trae agent 文件结构错误问题 * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix trae's test case files * Update src/specify_cli/integrations/trae/__init__.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: jiakangning <jiakangning@bytedance.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> |
||
|
|
1a9e4d1d8d |
feat: Git extension stage 1 — bundled extensions/git with hooks on all core commands (#1941)
* feat: add git extension with hooks on all core commands - Create extensions/git/ with 5 commands: initialize, feature, validate, remote, commit - 18 hooks covering before/after for all 9 core commands - Scripts: create-new-feature, initialize-repo, auto-commit, git-common (bash + powershell) - Configurable: branch_numbering, init_commit_message, per-command auto-commit with custom messages - Add hooks to analyze, checklist, clarify, constitution, taskstoissues command templates - Allow hooks-only extensions (no commands required) - Bundle extension in wheel via pyproject.toml force-include - Resolve bundled extensions locally before catalog lookup - Remove planned-but-unimplemented before/after_commit hook refs - Update extension docs (API ref, dev guide, user guide) - 37 new tests covering manifest, install, all scripts (bash+pwsh), config reading, graceful degradation Stage 1: opt-in via 'specify extension add git'. No auto-install, no changes to specify.md or core git init code. Refs: #841, #1382, #1066, #1791, #1191 * fix: set git identity env vars in extension tests for CI runners * fix: address PR review comments - Fix commands property KeyError for hooks-only extensions - Fix has_git() operator precedence in git-common.sh - Align default commit message to '[Spec Kit] Initial commit' across config-template, extension.yml defaults, and both init scripts - Update README to reflect all 5 commands and 18 hooks * fix: address second round of PR review comments - Add type validation for provides.commands (must be list) and hooks (must be dict) in manifest _validate() - Tighten malformed timestamp detection in git-common.sh to catch 7-digit dates without trailing slug (e.g. 2026031-143022) - Pass REPO_ROOT to has_git/Test-HasGit in create-new-feature scripts - Fix initialize command docs: surface errors on git failures, only skip when git is not installed - Fix commit command docs: 'skips with a warning' not 'silently' - Add tests for commands:null and hooks:list rejection * fix: address third round of PR review comments - Remove scripts frontmatter from command files (CommandRegistrar rewrites ../../scripts/ to .specify/scripts/ which points at core scripts, not extension scripts) - Update speckit.git.commit command to derive event name from hook context rather than using a static example - Clarify that hook argument passthrough works via AI agent context (the agent carries conversation state including user's original feature description) * fix: address fourth round of PR review comments - Validate extension_id against ^[a-z0-9-]+$ in _locate_bundled_extension to prevent path traversal (security fix) - Move defaults under config.defaults in extension.yml to match ConfigManager._get_extension_defaults() schema - Ship git-config.yml in extension directory so it's copied during install (provides.config template isn't materialized by ExtensionManager) - Condition handling in hook templates: intentionally matches existing pattern from specify/plan/tasks/implement templates (not a new issue) * fix: add --allow-empty to git commit in initialize-repo scripts Ensures git init succeeds even on empty repos where nothing has been staged yet. * fix: resolve display names to bundled extensions before catalog download When 'specify extension add "Git Branching Workflow"' is used with a display name instead of the ID, the catalog resolver now runs first to map the name to an ID, then checks bundled extensions again with the resolved ID before falling back to network download. Also noted: EXECUTE_COMMAND_INVOCATION and condition handling match the existing pattern in specify/plan/tasks/implement templates (pre-existing, not introduced by this PR). * fix: handle before_/after_ prefixes in auto-commit message derivation - Strip both before_ and after_ prefixes when deriving command name (fixes misleading 'Auto-commit after before_plan' messages) - Include phase (before/after) in default commit messages - Clarify README config example is an override, not default behavior * fix: use portable grep -qw for word boundary in create-new-feature.sh BSD grep (macOS) doesn't support \b as a word boundary. Replace with grep -qw which is POSIX-portable. * fix: validate hook values, numeric --number, and PS warning routing - Validate each hook value is a dict with a 'command' field during manifest _validate() (prevents crash at install time) - Validate --number is a non-negative integer in bash create-new-feature (clear error instead of cryptic shell arithmetic failure) - Route PowerShell no-git warning to stderr in JSON mode so stdout stays valid JSON --------- Co-authored-by: Manfred Riem <15701806+mnriem@users.noreply.github.com> |
||
|
|
7f08f31286 |
[stage2] fix: serialize multiline descriptions in legacy TOML renderer (#2097)
* fix: preserve multiline descriptions in legacy toml renderer * refactor: reuse toml escape helper for prompt fallback |
||
|
|
8b099585c7 |
[stage1] fix: strip YAML frontmatter from TOML integration prompts (#2096)
* fix: correct toml integration frontmatter handling * refactor: reuse frontmatter split in toml integration * fix: preserve toml integration string semantics * docs: align toml integration renderer docstring |
||
|
|
f92e7e8096 |
fix: accept 4+ digit spec numbers in tests and docs (#2094)
Two test assertions in test_timestamp_branches.py used the regex
`\d{3}` (exactly 3 digits) instead of `\d{3,}` (3 or more digits).
While the underlying shell scripts already handle spec numbers ≥ 1000
correctly — printf "%03d" and PowerShell '{0:000}' both expand naturally
beyond 3 digits, and all detection regexes use {3,} — the overly-strict
test assertions would fail with a misleading error if a fixture ever
contained 1000+ spec directories.
Documentation in README.md, spec-driven.md, and the CLI --branch-numbering
help text implied that sequential spec numbers are always 3 digits, which
could lead users to believe a hard limit of 999 exists.
Changes:
- tests/test_timestamp_branches.py: change two \d{3} assertions to \d{3,}
- src/specify_cli/__init__.py: clarify help text to show numbers expand past 999
- README.md: update --branch-numbering docs to note numbers expand beyond 3 digits
- spec-driven.md: update feature numbering description to include 4-digit example
Fixes #2093
Co-authored-by: alex-zwingli <alex-zwingli@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
||
|
|
94ba857b78 |
Add specify integration subcommand for post-init integration management (#2083)
* Initial plan * Add specify integration subcommand (list, install, uninstall, switch) Implements the `specify integration` subcommand group for managing integrations in existing projects after initial setup: - `specify integration list` — shows available integrations and installed status - `specify integration install <key>` — installs an integration into existing project - `specify integration uninstall [key]` — hash-safe removal preserving modified files - `specify integration switch <target>` — uninstalls current, installs target Follows the established `specify <noun> <verb>` CLI pattern used by extensions and presets. Shared infrastructure (scripts, templates) is preserved during uninstall and switch operations. Agent-Logs-Url: https://github.com/github/spec-kit/sessions/1cca6c84-3e12-465d-88b8-a646d3504f63 Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com> * Address review feedback: extract helper, fix return type annotation - Extract _update_init_options_for_integration() to deduplicate init-options update logic between install and switch commands - Fix _parse_integration_options return type to dict[str, Any] | None Agent-Logs-Url: https://github.com/github/spec-kit/sessions/1cca6c84-3e12-465d-88b8-a646d3504f63 Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com> * Potential fix for pull request finding 'Unused import' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> * Address review feedback: validate script type, handle --flag=value, fix metadata cleanup - Add _normalize_script_type() to validate script type against SCRIPT_TYPE_CHOICES - Handle --name=value syntax in _parse_integration_options() - Clear init-options.json keys in no-manifest uninstall early-return path - Clear stale metadata between switch teardown and install phases - Add 5 tests covering the new edge cases * Block --force with different integration, persist script type in init-options - --force on install now rejects overwriting a different integration; users must use 'specify integration switch' instead - _update_init_options_for_integration() now accepts and persists script_type - Fix misleading test docstring for switch metadata test - Add test_force_blocked_with_different_integration * Remove --force from integration install, ensure shared infra on install/switch - Remove --force parameter entirely from integration install; users must uninstall before reinstalling to prevent orphaned files - Auto-install shared infrastructure (.specify/scripts/, .specify/templates/) when missing during install or switch - Add test for shared infra creation on bare project install * Remove redundant installed_key != key check The == key case already returns above, so the != key guard is always true at this point. Simplify to just 'if installed_key:'. * Run shared infra unconditionally, defer metadata removal in switch - Call _install_shared_infra() unconditionally on install and switch since it merges without overwriting existing files - Remove premature metadata cleanup between switch phases; metadata is now only updated after successful Phase 2 install * Add install rollback, graceful manifest errors, clear switch metadata - Attempt teardown rollback on install/switch failure to avoid orphaned files - Catch ValueError/FileNotFoundError on IntegrationManifest.load() in uninstall with user-friendly recovery guidance - Clear metadata immediately after switch teardown so failed Phase 2 doesn't leave stale references to the removed integration * Log rollback failures instead of silently suppressing them * Handle corrupt manifest in switch, distinguish unknown vs missing manifest - Wrap IntegrationManifest.load() in switch with ValueError/FileNotFoundError handling, matching the pattern used in uninstall - Split else branch to report 'unknown integration' vs 'no manifest' separately * Clean up metadata on rollback, broaden init-options match in uninstall - Remove integration.json in install/switch rollback paths so failed installs don't leave stale metadata - Match on both 'integration' and 'ai' keys when clearing init-options.json during uninstall to handle partially-written metadata * Fix recovery guidance for unreadable manifests, fix type annotations - Recovery instructions now guide users through delete manifest → uninstall → reinstall workflow that actually works - Type annotations for optional CLI parameters changed from str to str | None * Allow manifest-only uninstall for unknown/removed integrations - Uninstall no longer requires the integration to be in the registry; falls back to manifest.uninstall() directly when get_integration() returns None - Switch Phase 1 similarly uses manifest-only uninstall for unknown integrations instead of skipping teardown, preventing orphaned files * Fail fast on corrupt integration.json, validate integration options - _read_integration_json() now exits with an actionable error when integration.json exists but is corrupt/unreadable - _parse_integration_options() rejects unknown options, validates flag usage, and requires values for non-flag options * Validate integration.json is a dict, fail fast on missing manifest in switch - _read_integration_json() validates parsed JSON is a dict, not a list/string - Switch fails fast with recovery guidance when manifest is missing instead of silently skipping teardown and risking co-existing integration files --------- 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> |
||
|
|
535ddbe0d2 |
fix: add user-invocable: true to skill frontmatter (#2077)
Skills were missing this field, causing them to be treated as "managed" instead of user-invocable via /speckit-* commands. |
||
|
|
10be484868 |
feat: add argument-hint frontmatter to Claude Code commands (#1951) (#2059)
* feat: add argument-hint frontmatter to Claude Code commands (#1951) Inject argument-hint into YAML frontmatter for Claude agent only during release package generation. Templates remain agent-agnostic; hints are added on the fly in generate_commands() when agent is "claude". Closes #1951 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: scope argument-hint injection to YAML frontmatter only Addresses Copilot review: the awk/regex matched description: anywhere in the file. Now both bash and PowerShell track frontmatter boundaries (--- delimiters) and only inject argument-hint after the first description: inside the frontmatter block. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add argument-hint to Claude integration + tests - Override setup() in ClaudeIntegration to inject argument-hint into YAML frontmatter after description: line, scoped to frontmatter only - Add ARGUMENT_HINTS mapping for all 9 commands - Add tests: hint presence, correct values, frontmatter scoping, ordering after description, and body-safety check Addresses maintainer feedback to cover the new integrations system in src/specify_cli/integrations/claude/__init__.py with tests in tests/integrations/test_integration_claude.py Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: address Copilot review feedback on Claude integration - Remove unused `import re` - Skip injection if argument-hint already exists in frontmatter - Add found_description assertion to test_hint_appears_after_description - Add test_inject_argument_hint_skips_if_already_present test Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor: delegate to super().setup() and post-process for hints - Eliminates setup() duplication by calling super().setup() then post-processing command files to inject argument-hint - Fixes EOL preservation to correctly detect \r\n vs \n - No drift risk if MarkdownIntegration.setup() changes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: use read_bytes/write_bytes for platform-stable EOL handling Address Copilot review: avoid platform newline translation by using read_bytes()/write_bytes() instead of read_text()/write_text() when post-processing SKILL.md files for argument-hint injection. * fix: re-record manifest hash after hint injection, quote hint values - Re-record file hash in manifest after writing argument-hint so check_modified()/uninstall stays in sync - Double-quote argument-hint values to match SKILL.md frontmatter style - Update tests to expect quoted hint values * fix: inject disable-model-invocation into Claude skill frontmatter --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
b8e7851234 |
feat: add Forgecode agent support (#2034)
* feat: add Forgecode (forge) agent support
- Add 'forgecode' to AGENT_CONFIGS in agents.py with .forge/commands
directory, markdown format, and {{parameters}} argument placeholder
- Add 'forgecode' to AGENT_CONFIG in __init__.py with .forge/ folder,
install URL, and requires_cli=True
- Add forgecode binary check in check_tool() mapping agent key
'forgecode' to the actual 'forge' CLI binary
- Add forgecode case to build_variant() in create-release-packages.sh
generating commands into .forge/commands/ with {{parameters}}
- Add forgecode to ALL_AGENTS in create-release-packages.sh
* fix: strip handoffs frontmatter and replace $ARGUMENTS for forgecode
The forgecode agent hangs when listing commands because the 'handoffs'
frontmatter field (a Claude Code-specific feature) contains 'send: true'
entries that forge tries to act on when indexing .forge/commands/ files.
Additionally, $ARGUMENTS in command bodies was never replaced with
{{parameters}}, so user input was not passed through to commands.
Python path (agents.py):
- Add strip_frontmatter_keys: [handoffs] to the forgecode AGENT_CONFIG
entry so register_commands drops the key before rendering
Bash path (create-release-packages.sh):
- Add extra_strip_key parameter to generate_commands; pass 'handoffs'
for the forgecode case in build_variant
- Use regex prefix match (~ "^"extra_key":") instead of exact
equality to handle trailing whitespace after the YAML key
- Add sed replacement of $ARGUMENTS -> $arg_format in the body
pipeline so {{parameters}} is substituted in forgecode command files
* feat: add name field injection for forgecode agent
Forgecode requires both 'name' and 'description' fields in command
frontmatter. This commit adds automatic injection of the 'name' field
during command generation for forgecode.
Changes:
- Python (agents.py): Add inject_name: True to forgecode config and
implement name injection logic in register_commands
- Bash (create-release-packages.sh): Add post-processing step to inject
name field into frontmatter after command generation
This complements the existing handoffs stripping fix (
|
||
|
|
b1832c9477 |
Stage 6: Complete migration — remove legacy scaffold path (#1924) (#2063)
* Stage 6: Complete migration — remove legacy scaffold path (#1924) Remove the legacy GitHub download and offline scaffold code paths. All 26 agents now use the integration system exclusively. Code removal (~1073 lines from __init__.py): - download_template_from_github(), download_and_extract_template() - scaffold_from_core_pack(), _locate_release_script() - install_ai_skills(), _get_skills_dir (restored slim version for presets) - _has_bundled_skills(), _migrate_legacy_kimi_dotted_skills() - AGENT_SKILLS_MIGRATIONS, _handle_agent_skills_migration() - _parse_rate_limit_headers(), _format_rate_limit_error() - Three-way branch in init() collapsed to integration-only Config derivation (single source of truth): - AGENT_CONFIG derived from INTEGRATION_REGISTRY (replaced 180-line dict) - CommandRegistrar.AGENT_CONFIGS derived from INTEGRATION_REGISTRY (replaced 160-line dict) - Backward-compat constants kept for presets/extensions: SKILL_DESCRIPTIONS, NATIVE_SKILLS_AGENTS, DEFAULT_SKILLS_DIR Release pipeline cleanup: - Deleted create-release-packages.sh/.ps1 (948 lines of ZIP packaging) - Deleted create-github-release.sh, generate-release-notes.sh - Deleted simulate-release.sh, get-next-version.sh, update-version.sh - Removed .github/workflows/scripts/ directory entirely - release.yml is now self-contained: check, notes, release all inlined - Install instructions use uv tool install with version tag Test cleanup: - Deleted test_ai_skills.py (tested removed functions) - Deleted test_core_pack_scaffold.py (tested removed scaffold) - Cleaned test_agent_config_consistency.py (removed 19 release-script tests) - Fixed test_branch_numbering.py (removed dead monkeypatches) - Updated auto-promote tests (verify files created, not tip messages) 1089 tests pass, 0 failures, ruff clean. * fix: resolve merge conflicts with #2051 (claude as skills) - Fix circular import: move CommandRegistrar import in claude integration to inside method bodies (was at module level) - Lazy-populate AGENT_CONFIGS via _ensure_configs() to avoid circular import at class definition time - Set claude registrar_config to .claude/commands (extension/preset target) since the integration handles .claude/skills in setup() - Update tests from #2051 to match: registrar_config assertions, remove --integration tip assertions, remove install_ai_skills mocks 1086 tests pass. * fix: properly preserve claude skills migration from #2051 Restore ClaudeIntegration.registrar_config to .claude/skills (not .claude/commands) so extension/preset registrations write to the correct skills directory. Update tests that simulate claude setup to use .claude/skills and check for SKILL.md layout. Some tests still need updating for the full skills path — 10 remaining failures from the #2051 test expectations around the extension/preset skill registration flow. WIP: 1076/1086 pass. * fix: properly handle SKILL.md paths in extension update rollback and tests Fix extension update rollback using _compute_output_name() for SKILL.md agents (converts dots to hyphens in skill directory names). Previously the backup and cleanup code constructed paths with raw command names (e.g. speckit.test-ext.hello/SKILL.md) instead of the correct computed names (speckit-test-ext-hello/SKILL.md). Test fixes for claude skills migration: - Update claude tests to use .claude/skills paths and SKILL.md layout - Use qwen (not claude) for skills-guard tests since claude's agent dir IS the skills dir — creating it triggers command registration - Fix test_extension_command_registered_when_extension_present to check skills path format 1086 tests pass, 0 failures, ruff clean. * fix: address PR review — lazy init, assertions, deprecated flags - _ensure_configs(): catch ImportError (not Exception), don't set _configs_loaded on failure so retries work - Move _ensure_configs() before unregister loop (not inside it) - Module-level try/except catches ImportError specifically - Remove tautology assertion (or True) in test_extensions.py - Strengthen preset provenance assertion to check source: field - Mark --offline, --skip-tls, --debug, --github-token as hidden deprecated no-ops in init() 1086 tests pass. * fix: remove deleted release scripts from pyproject.toml force-include Removes force-include entries for create-release-packages.sh/.ps1 which were deleted but still referenced in [tool.hatch.build]. |
||
|
|
a858c1d6da |
Install Claude Code as native skills and align preset/integration flows (#2051)
* Use Claude skills for generated commands * Fix Claude integration and preset skill flows * Group Claude tests in integration suite * Align Claude skill frontmatter across generators * Fix native skill preset cleanup * Keep legacy AI skills test on legacy path * Move Claude here-mode test to CLI suite |
||
|
|
4f9d966beb |
Stage 5: Skills, Generic & Option-Driven Integrations (#1924) (#2052)
* Stage 5: Skills, Generic & Option-Driven Integrations (#1924) Add SkillsIntegration base class and migrate codex, kimi, agy, and generic to the integration system. Integrations: - SkillsIntegration(IntegrationBase) in base.py — creates speckit-<name>/SKILL.md layout matching release ZIP output byte-for-byte - CodexIntegration — .agents/skills/, --skills default=True - KimiIntegration — .kimi/skills/, --skills + --migrate-legacy options, dotted→hyphenated skill directory migration - AgyIntegration — .agent/skills/, skills-only (commands deprecated v1.20.5) - GenericIntegration — user-specified --commands-dir, MarkdownIntegration - All four have update-context.sh/.ps1 scripts - All four registered in INTEGRATION_REGISTRY CLI changes: - --ai <agent> auto-promotes to integration path for all registered agents - Interactive agent selection also auto-promotes (bug fix) - --ai-skills and --ai-commands-dir show deprecation notices on integration path - Next-steps display shows correct skill invocation syntax for skills integrations - agy added to CommandRegistrar.AGENT_CONFIGS Tests: - test_integration_base_skills.py — reusable mixin with setup, frontmatter, directory structure, scripts, CLI auto-promote, and complete file inventory (sh+ps) tests - Per-agent test files: test_integration_{codex,kimi,agy,generic}.py - Kimi legacy migration tests, generic --commands-dir validation - Registry updated with Stage 5 keys - Removed 9 dead-mock tests, moved 4 integration tests to proper locations - Fixed all bare project-name tests to use tmp_path - Fixed 6 pre-existing ANSI escape code test failures in test_extensions.py and test_presets.py 1524 tests pass, 0 failures. * fix: remove unused variable flagged by ruff (F841) * fix: address PR review — integration-type-aware deprecation messages and early generic validation - --ai-skills deprecation message now distinguishes SkillsIntegration ("skills are the default") from command-based integrations ("has no effect") - --ai-commands-dir validation for generic runs even when auto-promoted, giving clear CLI error instead of late ValueError from setup() - Resolves review comments from #2052 * fix: address PR review round 2 - Remove unused SKILL_DESCRIPTIONS dict from base.py (dead code after switching to template descriptions for ZIP parity) - Narrow YAML parse catch from Exception to yaml.YAMLError - Remove unused shutil import from test_integration_kimi.py - Remove unused _REGISTRAR_EXEMPT class attr from test_registry.py - Reword --ai-commands-dir deprecation to be actionable - Update generic validation error to mention both --ai and --integration * fix: address PR review round 3 - Clarify parsed_options forwarding is intentional (all options passed, integrations decide what to use) - Extract _strip_ansi() helper in test_extensions.py and test_presets.py - Remove unused pytest import (test_cli.py), unused locals (test_integration_base_skills.py) - Reword --ai-commands-dir deprecation to be actionable without referencing the not-yet-implemented --integration-options * fix: address PR review round 4 - Reorder kimi migration: run super().setup() first so hyphenated targets exist, then migrate dotted dirs (prevents user content loss) - Move _strip_ansi() to shared tests/conftest.py, import from there in test_extensions.py, test_presets.py, test_ai_skills.py - Remove now-unused re imports from all three test files * fix: address PR review round 5 - Use write_bytes() for LF-only newlines (no CRLF on Windows) - Add --integration-options CLI parameter — raw string passed through to the integration via opts['raw_options']; the integration owns parsing of its own options - GenericIntegration.setup() reads --commands-dir from raw_options when not in parsed_options (supports --integration-options="...") - Skip early --ai-commands-dir validation when --integration-options is provided (integration validates in its own setup()) - Remove parse_integration_options from core — integrations parse their own options * fix: address PR review round 6 - GenericIntegration is now stateless: removed self._commands_dir instance state, overrides setup() directly to compute destination from parsed_options/raw_options on the stack - commands_dest() raises by design (stateless singleton) - _quote() in SkillsIntegration now escapes backslashes and double quotes to produce valid YAML even with special characters * fix: address PR review round 7 - Support --commands-dir=value form in raw_options parsing (not just --commands-dir value with space separator) - Normalize CRLF to LF in write_file_and_record() before encoding - Persist ai_skills=True in init-options.json when using a SkillsIntegration, so extensions/presets emit SKILL.md overrides correctly even without explicit --ai-skills flag |
||
|
|
b44ffc0101 |
feat(scripts): add --dry-run flag to create-new-feature (#1998)
* feat(scripts): add --dry-run flag to create-new-feature scripts Add a --dry-run / -DryRun flag to both bash and PowerShell create-new-feature scripts that computes the next branch name, spec file path, and feature number without creating any branches, directories, or files. This enables external tools to query the next available name before running the full specify workflow. When combined with --json, the output includes a DRY_RUN field. Without --dry-run, behavior is completely unchanged. Closes #1931 Assisted-By: 🤖 Claude Code * fix(scripts): gate specs/ dir creation behind dry-run check Dry-run was unconditionally creating the root specs/ directory via mkdir -p / New-Item before the dry-run guard. This violated the documented contract of zero side effects. Also adds returncode assertion on git branch --list in tests and adds PowerShell dry-run test coverage (skipped when pwsh unavailable). Addresses review comments on #1998. Assisted-By: 🤖 Claude Code * fix: address PR review feedback - Gate `mkdir -p $SPECS_DIR` behind DRY_RUN check (bash + PowerShell) so dry-run creates zero directories - Add returncode assertion on `git branch --list` in test - Strengthen spec dir test to verify root `specs/` is not created - Add PowerShell dry-run test class (5 tests, skipped without pwsh) - Fix run_ps_script to use temp repo copy instead of project root Assisted-By: 🤖 Claude Code * fix: use git ls-remote for remote-aware dry-run numbering Dry-run now queries remote branches via `git ls-remote --heads` (read-only, no fetch) to account for remote-only branches when computing the next sequential number. This prevents dry-run from returning a number that already exists on a remote. Added test verifying dry-run sees remote-only higher-numbered branches and adjusts numbering accordingly. Assisted-By: 🤖 Claude Code * fix(scripts): deduplicate number extraction and branch scanning logic Extract shared _extract_highest_number helper (bash) and Get-HighestNumberFromNames (PowerShell) to eliminate duplicated number extraction patterns between local branch and remote ref scanning. Add SkipFetch/skip_fetch parameter to check_existing_branches / Get-NextBranchNumber so dry-run reuses the same function instead of inlining duplicate max-of-branches-and-specs logic. Assisted-By: 🤖 Claude Code * fix(tests): use isolated paths for remote branch test Move remote.git and second_clone directories under git_repo instead of git_repo.parent to prevent path collisions with parallel test workers. Assisted-By: 🤖 Claude Code * fix: address PR review feedback - Set GIT_TERMINAL_PROMPT=0 for git ls-remote calls to prevent credential prompts from blocking dry-run in automation scenarios - Add returncode assertion to test_dry_run_with_timestamp git branch --list check Assisted-By: 🤖 Claude Code |
||
|
|
8e14ab1935 |
fix: support feature branch numbers with 4+ digits (#2040)
* fix: support feature branch numbers with 4+ digits in common.sh and common.ps1
The sequential feature number pattern was hardcoded to exactly 3 digits
(`{3}`), causing branches like `1234-feature-name` to be rejected.
Changed to `{3,}` (3 or more digits) to support growing projects.
Also added a guard to exclude malformed timestamp patterns from being
accepted as sequential prefixes.
Closes #344
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: narrow timestamp guard and use [long] to prevent overflow
- Change [int] to [long] in PowerShell Get-CurrentBranch to avoid
overflow for large feature numbers (>2,147,483,647)
- Narrow malformed-timestamp exclusion from ^[0-9]+-[0-9]{6}- to
^[0-9]{7}-[0-9]{6}- so valid sequential branches like
004-123456-fix-bug are not rejected
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add regression tests for 4+ digit feature branch support
Cover check_feature_branch and find_feature_dir_by_prefix with 4-digit
sequential prefixes, as requested in PR review #2040.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: reject timestamp-like branches without trailing slug
Branches like "20260319-143022" (no "-<name>" suffix) were incorrectly
accepted as sequential prefixes. Add explicit rejection for 7-or-8
digit date + 6-digit time patterns with no trailing slug, in both
common.sh and common.ps1.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
||
|
|
682ffbfc0d |
Stage 4: TOML integrations — gemini and tabnine migrated to plugin architecture (#2050)
Add TomlIntegration base class in base.py that mirrors MarkdownIntegration:
- Overrides command_filename() for .toml extension
- Extracts description from YAML frontmatter for top-level TOML key
- Renders prompt body in TOML multiline basic strings with escaped backslashes
- Keeps full processed template (including frontmatter) as prompt body
- Byte-for-byte parity with v0.4.4 release ZIP output
Create integrations/gemini/ and integrations/tabnine/ subpackages:
- Config-only __init__.py subclassing TomlIntegration
- Integration-specific update-context scripts (sh + ps1)
Add TomlIntegrationTests mixin with TOML-specific validations:
- Valid TOML parsing, description/prompt keys, {{args}} placeholder
- Setup/teardown, manifest tracking, install/uninstall round-trips
- CLI auto-promote (--ai) and --integration flag tests
- Complete file inventory tests (sh + ps)
Register both in INTEGRATION_REGISTRY; --ai auto-promote works automatically.
|
||
|
|
255371d367 |
Stage 3: Standard markdown integrations — 19 agents migrated to plugin architecture (#2038)
* Stage 3: Standard markdown integrations — 19 agents migrated to plugin architecture
Migrate all standard markdown integrations to self-contained subpackages
under integrations/. Each subclasses MarkdownIntegration with config-only
overrides (~10 lines per __init__.py).
Integrations migrated (19):
claude, qwen, opencode, junie, kilocode, auggie, roo, codebuddy,
qodercli, amp, shai, bob, trae, pi, iflow, kiro-cli, windsurf,
vibe, cursor-agent
Changes:
- Create integrations/<key>/ subpackage with __init__.py and scripts/
(update-context.sh, update-context.ps1) for each integration
- Register all 19 in INTEGRATION_REGISTRY (20 total with copilot)
- MarkdownIntegration.setup() processes templates (replaces {SCRIPT},
{ARGS}, __AGENT__; strips frontmatter blocks; rewrites paths)
- Extract install_scripts() to IntegrationBase; refactor copilot to use it
- Generalize --ai auto-promote from copilot-only to registry-driven:
any integration registered in INTEGRATION_REGISTRY auto-promotes.
Unregistered agents (gemini, tabnine, codex, kimi, agy, generic)
continue through the legacy --ai path unchanged.
- Fix cursor/cursor-agent key mismatch in CommandRegistrar.AGENT_CONFIGS
- Add missing vibe entry to CommandRegistrar.AGENT_CONFIGS
- Update kiro alias test to reflect auto-promote behavior
Testing:
- Per-agent test files (test_integration_<agent>.py) with shared mixin
- 1316 tests passing, 0 failures
- Complete file inventory tests for both sh and ps variants
- Byte-for-byte validated against v0.4.3 release packages (684 files)
* Address PR review: fix repo root detection and no-op test
- Fix repo root fallback in all 20 update-context.sh scripts: walk up
from script location to find .specify/ instead of falling back to pwd
- Fix repo root fallback in all 20 update-context.ps1 scripts: walk up
from script location to find .specify/ instead of falling back to $PWD
- Add assertions to test_setup_writes_to_correct_directory: verify
expected_dir exists and all command files reside under it
* Fix REPO_ROOT priority: prefer .specify walk-up over git root
In monorepos the git toplevel may differ from the project root that
contains .specify/. The previous fix still preferred git rev-parse
over the walk-up result.
Bash scripts (20): prefer the discovered _root when it contains
.specify/; only accept git root if it also contains .specify/.
PowerShell scripts (20): validate git root contains .specify/ before
using it; fall back to walking up from script directory otherwise.
* Guard git call with try/catch in PowerShell scripts
With $ErrorActionPreference = 'Stop', an unguarded git rev-parse
throws a terminating CommandNotFoundException when git is not
installed, preventing the .specify walk-up fallback from running.
Wrap the git call in try/catch across all 20 update-context.ps1
scripts so the fallback works reliably without git.
* Rename hyphenated package dirs to valid Python identifiers
Rename kiro-cli → kiro_cli and cursor-agent → cursor_agent so the
packages can be imported with normal Python syntax instead of
importlib. The user-facing integration key (IntegrationBase.key)
stays hyphenated to match the actual CLI tool / binary name.
Also reorganize _register_builtins(): imports and registrations
are now grouped alphabetically with clear section comments.
* Reuse CommandRegistrar path rewriting in process_template()
Replace the duplicated regex-based path rewriting in
MarkdownIntegration.process_template() with a call to the shared
CommandRegistrar._rewrite_project_relative_paths() implementation.
This ensures extension-local paths are preserved and boundary rules
stay consistent across the codebase.
* Promote _rewrite_project_relative_paths to public API
Rename CommandRegistrar._rewrite_project_relative_paths() to
rewrite_project_relative_paths() (drop leading underscore) so
integrations can call it without reaching into a private method
across subsystem boundaries.
Addresses PR review feedback:
https://github.com/github/spec-kit/pull/2038#discussion_r3022105627
* Broaden TestRegistrarKeyAlignment to cover all integration keys
Parametrize across ALL_INTEGRATION_KEYS instead of only checking
cursor-agent and vibe. Keeps a separate negative test for the
stale 'cursor' shorthand.
Addresses PR review feedback:
https://github.com/github/spec-kit/pull/2038#discussion_r3022269032
|
||
|
|
3899dcc0d4 |
Stage 2: Copilot integration — proof of concept with shared template primitives (#2035)
* feat: Stage 2a — CopilotIntegration with shared template primitives - base.py: added granular primitives (shared_commands_dir, shared_templates_dir, list_command_templates, command_filename, commands_dest, copy_command_to_directory, record_file_in_manifest, write_file_and_record, process_template) - CopilotIntegration: uses primitives to produce .agent.md commands, companion .prompt.md files, and .vscode/settings.json - Verified byte-for-byte parity with old release script output - Copilot auto-registered in INTEGRATION_REGISTRY - 70 tests (22 new: base primitives + copilot integration) Part of #1924 * feat: Stage 2b — --integration flag, routing, agent.json, shared infra - Added --integration flag to init() (mutually exclusive with --ai) - --ai copilot auto-promotes to integration path with migration nudge - Integration setup writes .specify/agent.json with integration key - _install_shared_infra() copies scripts and templates to .specify/ - init-options.json records 'integration' key when used - 4 new CLI tests: mutual exclusivity, unknown rejection, copilot end-to-end, auto-promote (74 total integration tests) Part of #1924 * feat: Stage 2 completion — integration scripts, integration.json, shared manifest - Added copilot/scripts/update-context.sh and .ps1 (thin wrappers that delegate to the shared update-agent-context script) - CopilotIntegration.setup() installs integration scripts to .specify/integrations/copilot/scripts/ - Renamed agent.json → integration.json with script paths - _install_shared_infra() now tracks files in integration-shared.manifest.json - Updated tests: scripts installed, integration.json has script paths, shared manifest recorded (74 tests) Part of #1924 * refactor: rename shared manifest to speckit.manifest.json Cleaner naming — the shared infrastructure (scripts, templates) belongs to spec-kit itself, not to any specific integration. * fix: copilot update-context scripts reflect target architecture Scripts now source shared functions (via SPECKIT_SOURCE_ONLY=1) and call update_agent_file directly with .github/copilot-instructions.md, rather than delegating back to the shared case statement. * fix: simplify copilot scripts — dispatcher sources common functions Integration scripts now contain only copilot-specific logic (target path + agent name). The dispatcher is responsible for sourcing shared functions before calling the integration script. * fix: copilot update-context scripts are self-contained implementations These scripts ARE the implementation — the dispatcher calls them. They source common.sh + update-agent-context functions, gather feature/plan data, then call update_agent_file with the copilot target path (.github/copilot-instructions.md). * docs: add Stage 7 activation note to copilot update-context scripts * test: add complete file inventory test for copilot integration Validates every single file (37 total) produced by specify init --integration copilot --script sh --no-git. * test: add PowerShell file inventory test for copilot integration Validates all 37 files produced by --script ps variant, including .specify/scripts/powershell/ instead of bash. * refactor: split test_integrations.py into tests/integrations/ directory - test_base.py: IntegrationOption, IntegrationBase, MarkdownIntegration, primitives - test_manifest.py: IntegrationManifest, path traversal, persistence, validation - test_registry.py: INTEGRATION_REGISTRY - test_copilot.py: CopilotIntegration unit tests - test_cli.py: --integration flag, auto-promote, file inventories (sh + ps) - conftest.py: shared StubIntegration helper 76 integration tests + 48 consistency tests = 124 total, all passing. * refactor: move file inventory tests from test_cli to test_copilot File inventories are copilot-specific. test_cli.py now only tests CLI flag mechanics (mutual exclusivity, unknown rejection, auto-promote). * fix: skip JSONC merge to preserve user settings, fix docstring - _merge_vscode_settings() now returns early (skips merge) when existing settings.json can't be parsed (e.g. JSONC with comments), instead of overwriting with empty settings - Updated _install_shared_infra() docstring to match implementation (scripts + templates, speckit.manifest.json) * fix: warn user when JSONC settings merge is skipped * fix: show template content when JSONC merge is skipped User now sees the exact settings they should add manually. * fix: document process_template requirement, merge scripts without rmtree - base.py setup() docstring now explicitly states raw copy behavior and directs to CopilotIntegration for process_template example - _install_shared_infra() uses merge/overwrite instead of rmtree to preserve user-added files under .specify/scripts/ * fix: don't overwrite pre-existing shared scripts or templates Only write files that don't already exist — preserves any user modifications to shared scripts (common.sh etc.) and templates. * fix: warn user about skipped pre-existing shared files Lists all shared scripts and templates that were not copied because they already existed in the project. * test: add test for shared infra skip behavior on pre-existing files Verifies that _install_shared_infra() preserves user-modified scripts and templates while still installing missing ones. * fix: address review — containment check, deterministic prompts, manifest accuracy - CopilotIntegration.setup() adds dest containment check (relative_to) - Companion prompts generated from templates list, not directory glob - _install_shared_infra() only records files actually copied (not pre-existing) - VS Code settings tests made unconditional (assert template exists) - Inventory tests use .as_posix() for cross-platform paths * fix: correct PS1 function names, document SPECKIT_SOURCE_ONLY prerequisite - Fixed Get-FeaturePaths → Get-FeaturePathsEnv, Read-PlanData → Parse-PlanData - Documented that shared scripts must guard Main with SPECKIT_SOURCE_ONLY before these integration scripts can be activated (Stage 7) * fix: add dict type check for settings merge, simplify PS1 to subprocess - _merge_vscode_settings() skips merge with warning if parsed JSON is not a dict (array, null, etc.) - PS1 update-context.ps1 uses & invocation instead of dot-sourcing since the shared script runs Main unconditionally * fix: skip-write on no-op merge, bash subprocess, dynamic integration list - _merge_vscode_settings() only writes when keys were actually added - update-context.sh uses exec subprocess like PS1 version - Unknown integration error lists available integrations dynamically * fix: align path rewriting with release script, add .specify/.specify/ fix Path rewrite regex matches the release script's rewrite_paths() exactly (verified byte-identical output). Added .specify/.specify/ double-prefix fix for additional safety. |
||
|
|
804cd10c71 |
Stage 1: Integration foundation — base classes, manifest system, and registry (#1925)
* feat: Stage 1 — integration foundation (base classes, manifest, registry) Add the integrations package with: - IntegrationBase ABC and MarkdownIntegration base class - IntegrationOption dataclass for per-integration CLI options - IntegrationManifest with SHA-256 hash-tracked install/uninstall - INTEGRATION_REGISTRY (empty, populated in later stages) - 34 tests at 98% coverage Purely additive — no existing code modified. Part of #1924 * fix: normalize manifest keys to POSIX, type manifest parameter - Store manifest file keys using as_posix() after resolving relative to project root, ensuring cross-platform portable manifests - Type the manifest parameter as IntegrationManifest (via TYPE_CHECKING import) instead of Any in IntegrationBase methods * fix: symlink safety in uninstall/setup, handle invalid JSON in load - uninstall() now uses non-resolved path for deletion so symlinks themselves are removed, not their targets; resolve only for containment validation - setup() keeps unresolved dst_file for copy; resolves separately for project-root validation - load() catches json.JSONDecodeError and re-raises as ValueError with the manifest path for clearer diagnostics - Added test for invalid JSON manifest loading * fix: lexical symlink containment, assert project_root consistency - uninstall() now uses os.path.normpath for lexical containment check instead of resolve(), so in-project symlinks pointing outside are still properly removed - setup() asserts manifest.project_root matches the passed project_root to prevent path mismatches between file operations and manifest recording * fix: handle non-files in check_modified/uninstall, validate manifest key - check_modified() treats non-regular-files (dirs, symlinks) as modified instead of crashing with IsADirectoryError - uninstall() skips directories (adds to skipped list), only unlinks files and symlinks - load() validates stored integration key matches the requested key * fix: safe symlink handling in uninstall - Broken symlinks now removable (lexists check via is_symlink fallback) - Symlinks never hashed (avoids following to external targets) - Symlinks only removed with force=True, otherwise skipped * fix: robust unlink, fail-fast config validation, symlink tests - uninstall() wraps path.unlink() in try/except OSError to avoid partial cleanup on race conditions or permission errors - setup() raises ValueError on missing config or folder instead of silently returning empty - Added 3 tests: symlink in check_modified, symlink skip/force in uninstall (47 total) * fix: check_modified uses lexical containment, explicit is_symlink check - check_modified() no longer calls _validate_rel_path (which resolves symlinks); uses lexical checks (is_absolute, '..' in parts) instead - is_symlink() checked before is_file() so symlinks to files are still treated as modified - Fixed templates_dir() docstring to match actual behavior --------- Co-authored-by: Manfred Riem <15701806+mnriem@users.noreply.github.com> |
||
|
|
f8da535d71 |
feat(scripts): add --allow-existing-branch flag to create-new-feature (#1999)
* feat(scripts): add --allow-existing-branch flag to create-new-feature Add an --allow-existing-branch / -AllowExistingBranch flag to both bash and PowerShell create-new-feature scripts. When the target branch already exists, the script switches to it instead of failing. The spec directory and template are still created if missing, but existing spec.md files are not overwritten (prevents data loss on re-runs). The flag is opt-in, so existing behavior is completely unchanged without it. This enables worktree-based workflows and CI/CD pipelines that create branches externally before running speckit.specify. Relates to #1931. Also addresses #1680, #841, #1921. Assisted-By: 🤖 Claude Code * fix: address PR review feedback for allow-existing-branch - Make checkout failure fatal instead of suppressing with || true (bash) - Check $LASTEXITCODE after git checkout in PowerShell - Use Test-Path -PathType Leaf for spec file existence check (PS) - Add PowerShell static assertion test for -AllowExistingBranch flag Assisted-By: 🤖 Claude Code |
||
|
|
796b4f47c4 |
fix: prevent extension command shadowing (#1994)
* fix: prevent extension command shadowing * Validate extension command namespaces * Reuse extension command name pattern |
||
|
|
6b1f45c50c |
Fix Claude Code CLI detection for npm-local installs (#1978)
* Fix Claude Code CLI detection for npm-local installs `specify check` reports "Claude Code CLI (not found)" for users who installed Claude Code via npm-local (the default installer path, common with nvm). The binary lives at ~/.claude/local/node_modules/.bin/claude which was not checked. Add CLAUDE_NPM_LOCAL_PATH as a second well-known location alongside the existing migrate-installer path. Fixes https://github.com/github/spec-kit/issues/550 * Address Copilot review feedback - Remove unused pytest import from test_check_tool.py - Use tmp_path instead of hardcoded /nonexistent/claude for hermetic tests - Simplify redundant exists() + is_file() to just is_file() AI-assisted: Changes applied with Claude Code. * Update tests/test_check_tool.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update tests/test_check_tool.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> |
||
|
|
ccc44dd00a |
Unify Kimi/Codex skill naming and migrate legacy dotted Kimi dirs (#1971)
* fix: unify hyphenated skills and migrate legacy kimi dotted dirs * fix: preserve legacy kimi dotted preset skill overrides * fix: migrate kimi legacy dotted skills without ai-skills flag * fix: harden kimi migration and cache hook init options * fix: apply kimi preset skill overrides without ai-skills flag * fix: keep sequential branch numbering beyond 999 * test: align kimi scaffold skill path with hyphen naming * chore: align hook typing and preset skill comment * fix: restore AGENT_SKILLS_DIR_OVERRIDES compatibility export * refactor: remove AGENT_SKILLS_DIR_OVERRIDES and update callers * fix(ps1): support sequential branch numbers above 999 * fix: resolve preset skill placeholders for skills agents * Fix legacy kimi migration safety and preset skill dir checks * Harden TOML rendering and consolidate preset skill restore parsing * Fix PowerShell overflow and hook message fallback for empty invocations * Restore preset skills from extensions * Refine preset skill restore helpers * Harden skill path and preset checks * Guard non-dict init options * Avoid deleting unmanaged preset skill dirs * Unify extension skill naming with hooks * Harden extension native skill registration * Normalize preset skill titles |
||
|
|
36019ebf1b |
feat: Auto-register ai-skills for extensions whenever applicable (#1840)
* feat: Auto-register ai-skills for extensions whenever applicable * fix: failing test * fix: address copilot review comments – path traversal guard and use short_name in title * fix: address remaining copilot review comments – is_file guard, skills type-validation, and exact extension ownership check on fallback rmtree * fix: address copilot round-3 comments – align skill naming with presets.py convention, safe rmdir on fail, require SKILL.md for fallback rmtree, normalize skill_count in CLI * fix: is_dir() guard in fast-path rmtree and fix ghost-skill assertion naming * fix: path-traversal guard on skill_name in both rmtree paths of _unregister_extension_skills * fix: add SKILL.md ownership check to fast-path rmtree and alias shadowed _get_skills_dir import |
||
|
|
00e5dc1f91 |
Add AIDE, Extensify, and Presetify to community extensions (#1961)
* Add AIDE, Extensify, and Presetify to community extensions Add three extensions from the mnriem/spec-kit-extensions repository: - AI-Driven Engineering (AIDE): structured 7-step workflow for building new projects from scratch with AI assistants - Extensify: create and validate extensions and extension catalogs - Presetify: create and validate presets and preset catalogs Updates both the README community extensions table and catalog.community.json with entries in alphabetical order. * fix(tests): isolate preset search test from community catalog growth Mock get_active_catalogs to return only the default catalog entry so the test uses only its own cached data and won't break as the community preset catalog grows. |
||
|
|
a351c826ee |
fix(cli): add allow_unicode=True and encoding="utf-8" to YAML I/O (#1936)
None of the yaml.dump() calls specify allow_unicode=True, causing non-ASCII characters in extension descriptions to be escaped to \uXXXX sequences in generated .agent.md frontmatter and config files. Add allow_unicode=True to all 6 yaml.dump() call sites, and encoding="utf-8" to all corresponding write_text() and read_text() calls to ensure consistent UTF-8 handling across platforms. |
||
|
|
6223d10d84 |
fix(codex): native skills fallback refresh + legacy prompt suppression (#1930)
* fix(codex): skip legacy prompts and fallback when bundled skills missing * fix(skills): allow native fallback to overwrite existing SKILL.md * fix(codex): defer legacy .codex cleanup until after skills fallback * fix(codex): preserve existing .codex while skipping legacy prompt extraction * docs(skills): clarify overwrite_existing behavior * test(codex): cover fresh-dir suppression of legacy .codex layout * docs(codex): clarify skip_legacy_codex_prompts suppresses full .codex dir * security(init): validate zip member paths before extraction |
||
|
|
bf33980426 |
feat(cli): embed core pack in wheel for offline/air-gapped deployment (#1803)
* feat(cli): embed core pack in wheel + offline-first init (#1711, #1752) Bundle templates, commands, and scripts inside the specify-cli wheel so that `specify init` works without any network access by default. Changes: - pyproject.toml: add hatchling force-include for core_pack assets; bump version to 0.2.1 - __init__.py: add _locate_core_pack(), _generate_agent_commands() (Python port of generate_commands() shell function), and scaffold_from_core_pack(); modify init() to scaffold from bundled assets by default; add --from-github flag to opt back in to the GitHub download path - release.yml: build wheel during CI release job - create-github-release.sh: attach .whl as a release asset - docs/installation.md: add Enterprise/Air-Gapped Installation section - README.md: add Option 3 enterprise install with accurate offline story Closes #1711 Addresses #1752 * fix(tests): update kiro alias test for offline-first scaffold path * feat(cli): invoke bundled release script at runtime for offline scaffold - Embed release scripts (bash + PowerShell) in wheel via pyproject.toml - Replace Python _generate_agent_commands() with subprocess invocation of the canonical create-release-packages.sh, guaranteeing byte-for-byte parity between 'specify init --offline' and GitHub release ZIPs - Fix macOS bash 3.2 compat in release script: replace cp --parents, local -n (nameref), and mapfile with POSIX-safe alternatives - Fix _TOML_AGENTS: remove qwen (uses markdown per release script) - Rename --from-github to --offline (opt-in to bundled assets) - Add _locate_release_script() for cross-platform script discovery - Update tests: remove bash 4+/GNU coreutils requirements, handle Kimi directory-per-skill layout, 576 tests passing - Update CHANGELOG and docs/installation.md * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * fix(offline): error out if --offline fails instead of falling back to network - _locate_core_pack() docstring now accurately describes that it only finds wheel-bundled core_pack/; source-checkout fallback lives in callers - init() --offline + no bundled assets now exits with a clear error (previously printed a warning and silently fell back to GitHub download) - init() scaffold failure under --offline now exits with an error instead of retrying via download_and_extract_template Addresses reviewer comment: https://github.com/github/spec-kit/pull/1803 * fix(offline): address PR review comments - fix(shell): harden validate_subset against glob injection in case patterns - fix(shell): make GENRELEASES_DIR overridable via env var for test isolation - fix(cli): probe pwsh then powershell on Windows instead of hardcoding pwsh - fix(cli): remove unreachable fallback branch when --offline fails - fix(cli): improve --offline error message with common failure causes - fix(release): move wheel build step after create-release-packages.sh - fix(docs): add --offline to installation.md air-gapped example - fix(tests): remove unused genreleases_dir param from _run_release_script - fix(tests): rewrite parity test to run one agent at a time with isolated temp dirs, preventing cross-agent interference from rm -rf * fix(offline): address second round of review comments - fix(shell): replace case-pattern membership with explicit loop + == check for unambiguous glob-safety in validate_subset() - fix(cli): require pwsh (PowerShell 7) only; drop powershell (PS5) fallback since the bundled script uses #requires -Version 7.0 - fix(cli): add bash and zip preflight checks in scaffold_from_core_pack() with clear error messages if either is missing - fix(build): list individual template files in pyproject.toml force-include to avoid duplicating templates/commands/ in the wheel * fix(offline): address third round of review comments - Add 120s timeout to subprocess.run in scaffold_from_core_pack to prevent indefinite hangs during offline scaffolding - Add test_pyproject_force_include_covers_all_templates to catch missing template files in wheel bundling - Tighten kiro alias test to assert specific scaffold path (download vs offline) * fix(offline): address Copilot review round 4 - fix(offline): use handle_vscode_settings() merge for --here --offline to prevent data loss on existing .vscode/settings.json - fix(release): glob wheel filename in create-github-release.sh instead of hardcoding version, preventing upload failures on version mismatch - docs(release): add comment noting pyproject.toml version is synced by release-trigger.yml before the tag is pushed * fix(offline): address review round 5 + offline bundle ZIP - fix(offline): pwsh-only, no powershell.exe fallback; clarify error message - fix(offline): tighten _has_bundled to check scripts dir for source checkouts - feat(release): build specify-bundle-v*.zip with all deps at release time - feat(release): attach offline bundle ZIP to GitHub release assets - docs: simplify air-gapped install to single ZIP download from releases - docs: add Windows PowerShell 7+ (pwsh) requirement note * fix(tests): session-scoped scaffold cache + timeout + dead code removal - Add timeout=300 and returncode check to _run_release_script() to fail fast with clear output on script hangs or failures - Remove unused import specify_cli, _SOURCE_TEMPLATES, bundled_project fixture - Add session-scoped scaffolded_sh/scaffolded_ps fixtures that scaffold once per agent and reuse the output directory across all invariant tests - Reduces test_core_pack_scaffold runtime from ~175s to ~51s (3.4x faster) - Parity tests still scaffold independently for isolation * fix(offline): remove wheel from release, update air-gapped docs to use pip download * fix(tests): handle codex skills layout and iflow agent in scaffold tests Codex now uses create_skills() with hyphenated separator (speckit-plan/SKILL.md) instead of generate_commands(). Update _SKILL_AGENTS, _expected_ext, and _list_command_files to handle both codex ('-') and kimi ('.') skill agents. Also picks up iflow as a new testable agent automatically via AGENT_CONFIG. * fix(offline): require wheel core_pack for --offline, remove source-checkout fallback --offline now strictly requires _locate_core_pack() to find the wheel's bundled core_pack/ directory. Source-checkout fallbacks are no longer accepted at the init() level — if core_pack/ is missing, the CLI errors out with a clear message pointing to the installation docs. scaffold_from_core_pack() retains its internal source-checkout fallbacks so parity tests can call it directly from a source checkout. * fix(offline): remove stale [Unreleased] CHANGELOG section, scope httpx.Client to download path - Remove entire [Unreleased] section — CHANGELOG is auto-generated at release - Move httpx.Client into use_github branch with context manager so --offline path doesn't allocate an unused network client * fix(offline): remove dead --from-github flag, fix typer.Exit handling, add page templates validation - Remove unused --from-github CLI option and docstring example - Add (typer.Exit, SystemExit) re-raise before broad except Exception to prevent duplicate error panel on offline scaffold failure - Validate page templates directory exists in scaffold_from_core_pack() to fail fast on incomplete wheel installs - Fix ruff lint: remove unused shutil import, remove f-prefix on strings without placeholders in test_core_pack_scaffold.py * docs(offline): add v0.6.0 deprecation notice with rationale - Help text: note bundled assets become default in v0.6.0 - Docstring: explain why GitHub download is being retired (no network dependency, no proxy/firewall issues, guaranteed version match) - Runtime nudge: when bundled assets are available but user takes the GitHub download path, suggest --offline with rationale - docs/installation.md: add deprecation notice with full rationale * fix(offline): allow --offline in source checkouts, fix CHANGELOG truncation - Simplify use_github logic: use_github = not offline (let scaffold_from_core_pack handle fallback to source-checkout paths) - Remove hard-fail when core_pack/ is absent — scaffold_from_core_pack already falls back to repo-root templates/scripts/commands - Fix truncated 'skill…' → 'skills' in CHANGELOG.md * fix(offline): sandbox GENRELEASES_DIR and clean up on failure - Pin GENRELEASES_DIR to temp dir in scaffold_from_core_pack() so a user-exported value cannot redirect output or cause rm -rf outside the sandbox - Clean up partial project directory on --offline scaffold failure (same behavior as the GitHub-download failure path) * fix(tests): use shutil.which for bash discovery, add ps parity tests - _find_bash() now tries shutil.which('bash') first so non-standard install locations (Nix, custom CI images) are found - Parametrize parity test over both 'sh' and 'ps' script types to ensure PowerShell variant stays byte-for-byte identical to release script output (353 scaffold tests, 810 total) * fix(tests): parse pyproject.toml with tomllib, remove unused fixture - Use tomllib to parse force-include keys from the actual TOML table instead of raw substring search (avoids false positives) - Remove unused source_template_stems fixture from test_scaffold_command_dir_location * fix: guard GENRELEASES_DIR against unsafe values, update docstring - Add safety check in create-release-packages.sh: reject empty, '/', '.', '..' values for GENRELEASES_DIR before rm -rf - Strip trailing slash to avoid path surprises - Update scaffold_from_core_pack() docstring to accurately describe all failure modes (not just 'assets not found') * fix: harden GENRELEASES_DIR guard, cache parity tests, safe iterdir - Reject '..' path segments in GENRELEASES_DIR to prevent traversal - Session-cache both scaffold and release-script results in parity tests — runtime drops from ~74s to ~45s (40% faster) - Guard cmd_dir.iterdir() in assertion message against missing dirs * fix(tests): exclude YAML frontmatter source metadata from path rewrite check The codex and kimi SKILL.md files have 'source: templates/commands/...' in their YAML frontmatter — this is provenance metadata, not a runtime path that needs rewriting. Strip frontmatter before checking for bare scripts/ and templates/ paths. * fix(offline): surface scaffold failure detail in error output When --offline scaffold fails, look up the tracker's 'scaffold' step detail and print it alongside the generic error message so users see the specific root cause (e.g. missing zip/pwsh, script stderr). --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> |
||
|
|
65ecd5321d |
feat: add timestamp-based branch naming option for specify init (#1911)
* feat: add timestamp-based branch naming option for specify init Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Copilot feedback * Fix test * Copilot feedback * Update tests/test_branch_numbering.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> |
||
|
|
c8af730b14 |
feat: migrate Codex/agy init to native skills workflow (#1906)
* feat: migrate codex and agy to native skills flow * fix: harden codex skill frontmatter and script fallback * fix: clarify skills separator default expansion * fix: rewrite agent_scripts paths for codex skills * fix: align kimi guidance and platform-aware codex fallback |
||
|
|
2bf655e261 |
feat(presets): add enable/disable toggle and update semantics (#1891)
* feat(presets): add enable/disable toggle and update semantics Add preset enable/disable CLI commands and update semantics to match the extension system capabilities. Changes: - Add `preset enable` and `preset disable` CLI commands - Add `restore()` method to PresetRegistry for rollback scenarios - Update `get()` and `list()` to return deep copies (prevents mutation) - Update `list_by_priority()` to filter disabled presets by default - Add input validation to `restore()` for defensive programming - Add 16 new tests covering all functionality and edge cases Closes #1851 Closes #1852 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address PR review - deep copy and error message accuracy - Fix error message in restore() to match actual validation ("dict" not "non-empty dict") - Use copy.deepcopy() in restore() to prevent caller mutation - Apply same fixes to ExtensionRegistry for parity - Add /defensive-check command for pre-PR validation - Add tests for restore() validation and deep copy behavior Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * revert: remove defensive-check command from PR * fix: address PR review - clarify messaging and add parity - Add note to enable/disable output clarifying commands/skills remain active - Add include_disabled parameter to ExtensionRegistry.list_by_priority for parity - Add tests for extension disabled filtering Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address PR review - disabled extension resolution and corrupted entries - Fix _get_all_extensions_by_priority to use include_disabled=True for tracking registered IDs, preventing disabled extensions from being picked up as unregistered directories - Add corrupted entry handling to get() - returns None for non-dict entries - Add integration tests for disabled extension template resolution - Add tests for get() corrupted entry handling in both registries Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: handle corrupted registry in list() methods - Add defensive handling to list() when presets/extensions is not a dict - Return empty dict instead of crashing on corrupted registry - Apply same fix to both PresetRegistry and ExtensionRegistry for parity - Add tests for corrupted registry handling Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: validate top-level registry structure in get() and restore() - get() now validates self.data["presets/extensions"] is a dict before accessing - restore() ensures presets/extensions dict exists before writing - Prevents crashes when registry JSON is parseable but has corrupted structure - Applied same fixes to both PresetRegistry and ExtensionRegistry for parity Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: validate root-level JSON structure in _load() and is_installed() - _load() now validates json.load() result is a dict before returning - is_installed() validates presets/extensions is a dict before checking membership - Prevents crashes when registry file is valid JSON but wrong type (e.g., array) - Applied same fixes to both registries for parity Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: normalize presets/extensions field in _load() - _load() now normalizes the presets/extensions field to {} if not a dict - Makes corrupted registries recoverable for add/update/remove operations - Applied same fix to both registries for parity Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: use raw registry keys to track corrupted extensions - Use registry.list().keys() instead of list_by_priority() for tracking - Corrupted entries are now treated as tracked, not picked up as unregistered - Tighten test assertion for disabled preset resolution - Update test to match new expected behavior for corrupted entries Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: handle None metadata in ExtensionManager.remove() - Add defensive check for corrupted metadata in remove() - Match existing pattern in PresetManager.remove() Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: add keys() method and filter corrupted entries in list() - Add lightweight keys() method that returns IDs without deep copy - Update list() to filter out non-dict entries (match type contract) - Use keys() instead of list().keys() for performance - Fix comment to reflect actual behavior Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address defensive-check findings - deep copy, corruption guards, parity - Extension enable/disable: use delta pattern matching presets - add(): use copy.deepcopy(metadata) in both registries - remove(): guard outer field for corruption in both registries - update(): guard outer field for corruption in both registries Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: deep copy updates in update() to prevent caller mutation Both PresetRegistry.update() and ExtensionRegistry.update() now deep copy the input updates/metadata dict to prevent callers from mutating nested objects after the call. 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> |
||
|
|
f6794685b6 |
feat: add iFlow CLI support (#1875)
Add `iflow` as a supported AI agent (the key users pass to --ai) across all relevant configuration files, release scripts, agent context scripts, and README. Includes consistency tests following the same pattern as kimi/tabnine additions. - README: describe `check` generically (git + all AGENT_CONFIG CLI agents) - README: describe `--ai` with reference to AGENT_CONFIG for full list Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
1a21bdef01 |
Feature/spec kit add pi coding agent pullrequest (#1853)
* feat(ai): add native support for Pi coding agent by pi+gpt 5.4 * docs(pi): document MCP limitations for Pi agent * fix: unitended kimi agent mention added to update-agent-context.ps1 * fix: address reviewer feedback * Apply suggestions from code review Changes in AGENTS.md weren't part of my PR, but the Copilot feedback seems to be correct is correct. I've doublechecked it with contents of test_agent_config_consistency.py and create-release-packages scripts 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> |
||
|
|
6644f69a96 |
fix(ai-skills): exclude non-speckit copilot agent markdown from skill… (#1867)
* fix(ai-skills): exclude non-speckit copilot agent markdown from skill generation * Potential fix for pull request finding Fix missing `.agent` filename suffix Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Fix test assertion speckit.plan.md to speckit.plan.agent Fix test assertion speckit.plan.md to speckit.plan.agent * Fix filter glob based on review suggestions fix(ai-skills): normalize Copilot .agent template names and align template fallback filtering * Add template glob for fallback directory * GH Copilot Suggestions Clarify comment regarding Copilot's use of templates in tests. Add extra test assertion * fix(ai-skills): normalize Copilot .agent templates and preserve fallback behavior fix(ai-skills): handle Copilot .agent templates and fallback filtering Normalize Copilot command template names by stripping the .agent suffix when deriving skill names and metadata sources, so files like speckit.plan.agent.md produce speckit-plan and map to plan.md metadata. Also align Copilot template discovery with speckit.* filtering while preserving fallback to templates/commands/ when .github/agents contains only user-authored markdown files, and add regression coverage for both non-speckit agent exclusion and fallback behavior. * fix(ai-skills): ignore non-speckit markdown commands --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> |
||
|
|
a177a1a6d1 |
feat: add Trae IDE support as a new agent (#1817)
* feat: add Trae IDE support as a new agent Add Trae (https://www.trae.ai/) as a supported AI agent in spec-kit. Trae is an IDE-based agent that uses .trae/rules/ directory for project-level rules in Markdown format. Changes across 9 files: - src/specify_cli/__init__.py: Add trae to AGENT_CONFIG (IDE-based, .trae/ folder, rules subdir, no CLI required) - src/specify_cli/extensions.py: Add trae to CommandRegistrar.AGENT_CONFIGS (.trae/rules, markdown format, .md extension) - README.md: Add Trae to supported agents table, CLI examples, and --ai option description - .github/workflows/scripts/create-release-packages.sh: Add trae to ALL_AGENTS array and build case statement - .github/workflows/scripts/create-release-packages.ps1: Add trae to AllAgents array and switch statement - .github/workflows/scripts/create-github-release.sh: Add trae template zip files to release assets - scripts/bash/update-agent-context.sh: Add TRAE_FILE, trae case in update function, and auto-detect block - scripts/powershell/update-agent-context.ps1: Add TRAE_FILE, ValidateSet entry, switch case, and auto-detect block - tests/test_agent_config_consistency.py: Add 8 consistency tests for trae following established kimi/tabnine patterns * fix: correct Generate-Commands parameter names for trae in PowerShell release script Fix incorrect parameter names in the trae case of Build-Variant: - -Format -> -Extension - -ArgsToken -> -ArgFormat - -OutDir -> -OutputDir These now match the Generate-Commands function signature and all other agent entries in the script. Co-authored-by: Copilot <copilot@github.com> * Update release packaging scripts and agent docs * Update Agent.md * Restore format * Adjust order * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Unused * fix: add TRAE_FILE to update_all_existing_agents() for auto-detect support Add missing update_if_new call for TRAE_FILE in the bash update-agent-context.sh script's update_all_existing_agents() function, matching the PowerShell implementation. This ensures running the script without arguments will correctly auto-detect and update existing Trae agent files. * Add configuration for 'trae' in agents.py * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Refactor trae configuration test for clarity * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Update update-agent-context.sh * Fix formatting in update-agent-context.sh --------- Co-authored-by: root <root@g340-cd52-700-60f9-5561-211-6c32.byted.org> Co-authored-by: root <root@g340-cd52-700-c3d1-c735-796-4b9e.byted.org> Co-authored-by: Copilot <copilot@github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> |
||
|
|
c12b8c1763 |
feat(cli): polite deep merge for settings.json and support JSONC (#1874)
* feat(cli): polite deep merge for settings.json with json5 and safe atomic write * fix(cli): prevent temp fd leak and align merge-policy docs |
||
|
|
d2ecf6560d |
feat(extensions,presets): add priority-based resolution ordering (#1855)
* feat(extensions,presets): add priority-based resolution ordering Add priority field to extension and preset registries for deterministic template resolution when multiple sources provide the same template. Extensions: - Add `list_by_priority()` method to ExtensionRegistry - Add `--priority` option to `extension add` command - Add `extension set-priority` command - Show priority in `extension list` and `extension info` - Preserve priority during `extension update` - Update RFC documentation Presets: - Add `preset set-priority` command - Show priority in `preset info` output - Use priority ordering in PresetResolver for extensions Both systems: - Lower priority number = higher precedence (default: 10) - Backwards compatible with legacy entries (missing priority defaults to 10) - Comprehensive test coverage including backwards compatibility Closes #1845 Closes #1854 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address code review feedback - list_by_priority(): add secondary sort by ID for deterministic ordering, return deep copies to prevent mutation - install_from_directory/zip: validate priority >= 1 early - extension add CLI: validate --priority >= 1 before install - PresetRegistry.update(): preserve installed_at timestamp - Test assertions: use exact source string instead of substring match Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address additional review feedback - PresetResolver: add fallback to directory scanning when registry is empty/corrupted for robustness and backwards compatibility - PresetRegistry.update(): add guard to prevent injecting installed_at when absent in existing entry (mirrors ExtensionRegistry behavior) - RFC: update extension list example to match actual CLI output format Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: restore defensive code and RFC descriptions lost in rebase - Restore defensive code in list_by_priority() with .get() and isinstance check - Restore detailed --from URL and --dev option descriptions in RFC Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: add defensive code to presets list_by_priority() - Add .get() and isinstance check for corrupted/empty registry - Move copy import to module level (remove local import) - Matches defensive pattern used in extensions.py Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address reviewer feedback on priority resolution - Rename _normalize_priority to normalize_priority (public API) - Add comprehensive tests for normalize_priority function (9 tests) - Filter non-dict metadata entries in list_by_priority() methods - Fix extension priority resolution to merge registered and unregistered extensions into unified sorted list (unregistered get implicit priority 10) - Add tests for extension priority resolution ordering (4 tests) The key fix ensures unregistered extensions with implicit priority 10 correctly beat registered extensions with priority > 10, and vice versa. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: DRY refactor and strengthen test assertions - Extract _get_all_extensions_by_priority() helper in PresetResolver to eliminate duplicated extension list construction - Add priority=10 assertion to test_legacy_extension_without_priority_field - Add priority=10 assertion to test_legacy_preset_without_priority_field Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: add isinstance(dict) checks for corrupted registry entries Add defensive checks throughout CLI commands and manager methods to handle cases where registry entries may be corrupted (non-dict values). This prevents AttributeError when calling .get() on non-dict metadata. Locations fixed: - __init__.py: preset/extension info, set-priority, enable/disable, upgrade commands - extensions.py: list_installed() - presets.py: list_installed() Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: normalize priority display to match resolution behavior Use normalize_priority() for all priority display in CLI commands to ensure displayed values match actual resolution behavior when registry data is corrupted/hand-edited. Locations fixed: - extensions.py: list_installed() - presets.py: list_installed(), PresetResolver - __init__.py: preset info, extension info, set-priority commands Also added GraphQL query for unresolved PR comments to CLAUDE.md. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: repair corrupted priority values in set-priority commands Changed set-priority commands to check if the raw stored value is already a valid int equal to the requested priority before skipping. This ensures corrupted values (e.g., "high") get repaired even when setting to the default priority (10). Also removed CLAUDE.md that was accidentally added to the repo. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: harden registry update methods against corrupted entries - Normalize priority when restoring during extension update to prevent propagating corrupted values (e.g., "high", 0, negative) - Add isinstance(dict) checks in ExtensionRegistry.update() and PresetRegistry.update() to handle corrupted entries (string/list) that would cause TypeError on merge Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: use safe fallback for version in list_installed() When registry entry is corrupted (non-dict), metadata becomes {} after the isinstance check. Use metadata.get("version", manifest.version) instead of metadata["version"] to avoid KeyError. 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> |
||
|
|
bef9c2cb59 |
fix(extensions): show extension ID in list output (#1843)
Display the extension ID below the name in `specify extension list` output. This allows users to easily copy the ID when disambiguation is needed. Fixes #1832 Co-authored-by: iamaeroplane <michal.bachorik@gmail.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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 |
||
|
|
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> |